简介:面向机械故障诊断初学者与振动信号分析研究者的一份时频分析学习文档,以凯斯西储大学(CWRU)轴承故障数据集为对象,围绕正常、内圈、滚珠与外圈故障信号,梳理短时傅里叶变换与连续小波变换的分析流程。文档结合实验台结构、驱动端/风扇端/基座三类振动测点说明数据来源,并对比STFT在重叠比例0.5下尺度16、32、64的时频图差异,给出尺度32的选取依据;CWT部分则比较morl、cmor1-1、cmor1.5-2、cgau8等小波函数及32至256尺度的效果,最终采用cmor1.5-2深入分析。压缩包共1个docx文件,约1.05MB,便携易读,适合作为课程设计、论文复现和故障特征提取的参考资料。已有1584人学习,读者可从中获得数据集理解、时频图参数选择思路与可参照的代码片段,便于快速进入轴承故障诊断实验。
1. 从一段 1024 点的振动波形说起:这份时频分析笔记落在哪一步
轴承故障数据集的入门路径基本都是同一个顺序:先拿到一段驱动端加速度信号,画个时域波形,看着像噪声,然后想着干脆丢进分类器算了。但振动信号属于典型的非平稳序列,冲击成分的重复周期、共振频带的能量分布、故障尺寸变化带来的幅值差异,全都压在时间轴和频率轴的交叠里,不先把这层关系摊开,后面的特征工程和网络调参基本靠猜。这份 docx 笔记做的事很具体:用同一批公开实验台数据,把短时傅里叶变换和 Morlet 系连续小波变换各跑一遍,窗口尺度从 16 试到 256,小波函数从 morl 试到 cgau8,最后落到「STFT 窗口取 32、CWT 用 cmor1.5-2、尺度 128」这套可复现的组合。适合正在做旋转机械故障诊断、需要把一维振动转成时频图喂给二维网络的从业者,也适合刚接触信号处理、想弄明白「窗口长度到底影响什么」的新手。
2. 把 MAT 文件读成一条可分析的曲线:三路通道、命名规则与 1024 点截窗
公开轴承故障实验台的数据包解压后通常是一堆.mat文件,文件名形如21_1.mat、0_0.mat,直接双击打不开,得用 Python 或 MATLAB 读。这一章先把「读进来的是什么」定死,后面的时频图才有意义。
2.1 三路加速度计不是冗余备份,选错通道白干一半
实验台本体由一台 1.5 kW 电机、中间的扭矩传感器/译码器、右侧的功率测试计,以及没画在图上的电子控制器组成。加速度计装在三处:电机壳体驱动端(DE)、风扇端(FE)、电机基座(BA)。三路信号测的是同一台机器,但物理含义差别很大。
| 通道 | 安装位置 | 主要激励来源 | 典型能看到的故障 | 使用建议 |
|---|---|---|---|---|
| DE | 驱动端轴承座附近 | 转子旋转、传动系统啮合 | 内圈、外圈、滚动体局部缺陷 | 首选,信噪比最高,故障冲击最直接 |
| FE | 风扇端壳体 | 风扇叶片旋转、风扇轴承 | 风扇叶片失衡、风扇侧轴承故障 | 传递路径长,高频衰减明显,做辅助验证 |
| BA | 电机底座 | 整机结构振动、安装刚度 | 不平衡、底座松动、软脚 | 更适合做整机状态评估,不适合定位单点缺陷 |
血泪经验是:很多人拿到数据先画基座通道,图上一团糊,就断定「这个数据集不行」。实际上驱动端那条_DE_time才是主战场,风扇端和基座端更多用来做多通道融合或者交叉验证。同一故障在三个通道上的时频图长得不一样,这本身就是信息,但前提是你知道自己在看哪一路。
2.2 loadmat 返回的是字典,键名才是真正的入口
.mat文件在 Python 里读出来不是数组,是一个字典,里面除了数据还有__header__、__version__、__globals__三个元数据键。数据键的名字由原始实验记录决定,不同故障尺寸、不同负载对应的前缀完全不同,写死键名是新手最容易翻车的地方。
from scipy.io import loadmat import numpy as np # loadmat 返回 dict,先看有哪些键,别急着取数据 raw = loadmat('21_1.mat') data_keys = [k for k in raw.keys() if not k.startswith('__')] print(data_keys) # 例如 ['X209_DE_time', '__header__', ...] # reshape(-1) 把 (N,1) 的列向量拉平成 (N,);astype 防止原始整型溢出 sig = raw['X209_DE_time'].reshape(-1).astype(np.float64) print(sig.shape, sig.dtype, sig[:5])逻辑说明:loadmat默认squeeze_me=False,读出来的数值通常是(N, 1)的二维数组,直接送进scipy.signal.stft会按最后一个轴做变换,得到一堆长度为 1 的频谱,图上是空白。reshape(-1)是最省事的修正,比squeeze()更可控,因为它对(N,1)和(1,N)都成立。参数上留意astype(np.float64):部分采集系统存的是整型,后续做 FFT 和对数运算时整型会截断小数,甚至在某些版本里触发类型提升告警。
注意:不要在代码里硬编码
X209_DE_time这样的键名去批量处理整个文件夹,不同文件的编号前缀是变的。稳妥做法是先loadmat再过滤_DE_time结尾的键,取第一个。
2.3 截 1024 点、算清时间轴,别让采样率成为黑匣子
原始记录往往是十几秒甚至几十秒的长序列,几十万点。做时频图时没必要全画,一是图会糊成一片,二是不同文件长度不一致,对比不公平。常见做法是截取前 1024 点做统一窗口,这样四类信号(正常、内圈、滚动体、外圈)的横轴完全对齐,视觉上才能直接比。
import matplotlib matplotlib.rc("font", family='Microsoft YaHei') # 中文字体,缺字体会变方块 import matplotlib.pyplot as plt files = { '正常': ('0_0.mat', 'X097_DE_time'), '内圈': ('21_1.mat', 'X209_DE_time'), '滚动体': ('21_2.mat', 'X222_DE_time'), '外圈': ('21_3.mat', 'X234_DE_time'), } signals = {} for label, (fname, key) in files.items(): s = loadmat(fname)[key].reshape(-1).astype(np.float64) signals[label] = s[:1024] # 统一截 1024 点,保证横轴可比 fs = 12000 # 驱动端通道常见采样率,按实际数据头确认 t_ms = np.arange(1024) / fs * 1000 # 换算成毫秒,1024 点约 85.3 ms print(f'窗口时长: {t_ms[-1]:.2f} ms') fig, axes = plt.subplots(2, 2, figsize=(16, 8)) for ax, (label, s) in zip(axes.ravel(), signals.items()): ax.plot(t_ms, s, linewidth=0.8) ax.set_title(label) ax.set_xlabel('时间 / ms') ax.set_ylabel('加速度 / g') plt.tight_layout() plt.show()逻辑说明:这里刻意把横轴换成毫秒而不是采样点序号。用点序号画图,四类故障的「冲击间隔」看起来只是数字差,换算成毫秒之后才能和理论故障频率对上——比如内圈故障的冲击重复周期如果是 6 ms 左右,对应特征频率约 160 Hz,这个数量级对得上才说明通道选对了。
参数说明:fs = 12000是驱动端通道的常见采样率,实际项目中务必回到数据说明或文件头确认,取错了后面所有频率轴都会整体缩放。t_ms用np.arange(1024)/fs而不是np.linspace(0, 1024/fs, 1024),后者会多出一个端点,导致横轴整体偏移一个采样点,肉眼看不出来,但和理论频率比对时会产生几赫兹的误差。
到这里,四条长度一致、单位统一的一维序列就准备好了。下一步才是把它们变成二维的时频图。
3. STFT 落地:窗口 16、32、64、128 到底该怎么定
短时傅里叶变换的思路不复杂:拿一个固定长度的窗在信号上滑动,每滑一次做一次 FFT,把结果按时间排开就得到时频矩阵。真正要决策的只有两个量——窗有多长,窗之间重叠多少。而这两个量直接决定了图上「时间看得清」还是「频率看得清」。
3.1 时频折中是可以算出来的,不用凭感觉
对采样率 fs、窗长 N 点的 STFT,两个分辨率有明确关系:时间分辨率约等于 N/fs(窗内信号被压成一个时间戳),频率分辨率约等于 fs/N(窗内只有 N 个点,FFT 的频率格点就这么宽)。两者乘积恒为 1,此消彼长,这就是测不准原理在工程上的具体形态。
按 fs = 12 kHz 计算:
| nperseg | 窗内时长 | 频率分辨率 fs/N | 时间分辨率 | 适合观察 |
|---|---|---|---|---|
| 16 | 1.33 ms | 750 Hz | 1.33 ms | 极短冲击的起始时刻,频率几乎分不开 |
| 32 | 2.67 ms | 375 Hz | 2.67 ms | 冲击周期与中频段共振的折中 |
| 64 | 5.33 ms | 187.5 Hz | 5.33 ms | 共振频带轮廓,冲击边缘开始模糊 |
| 128 | 10.67 ms | 93.75 Hz | 10.67 ms | 低频边带结构,冲击被抹成条带 |
| 256 | 21.33 ms | 46.875 Hz | 21.33 ms | 只看低频调制,故障周期基本丢失 |
这张表建议直接抄进实验记录。窗口 16 的时候频率格点宽到 750 Hz,轴承故障的特征频率通常落在几十到几百赫兹,一个格点就跨过去了,图上只能看到一条条竖线;窗口拉到 256,频率是细了,但 21 ms 的窗内已经装下三四个冲击周期,冲击被平均掉,剩下的是一片横向条带。所以「窗口多大合适」这个问题不能脱离采样率单独回答,脱离 fs 谈 nperseg 是没有意义的。
3.2 scipy.signal.stft 的返回值和三个必改参数
from scipy.signal import stft def stft_map(x, fs=12000, nperseg=32, overlap_ratio=0.5): noverlap = int(nperseg * overlap_ratio) # boundary=None / padded=False:不补零,时间轴从 0 开始,帧数与真实数据严格对应 f, t, Z = stft(x, fs=fs, window='hann', nperseg=nperseg, noverlap=noverlap, boundary=None, padded=False) return f, t, np.abs(Z) # Z 是复数,画图取模 f, t, mag = stft_map(signals['内圈'], nperseg=32) print(f.shape, t.shape, mag.shape) # (17,) (63,) (17, 63)逻辑说明:stft返回三个对象,f是频率轴(单位 Hz,前提是传了fs),t是每帧的起始时刻(秒),Z是复数矩阵,形状为(len(f), len(t)),画图必须取np.abs或np.abs**2。上例中 1024 点、窗 32、重叠 16,帧数为(1024-32)/16 + 1 = 63,频率轴长度32/2+1 = 17,这两个数对不上就说明参数被改过。
参数说明:window='hann'是默认值,汉宁窗能压低旁瓣泄漏,对冲击信号比矩形窗干净;boundary=None和padded=False是一对,默认值会给两端补零,导致时间轴出现负值和尾部空帧,画出来的图两端有明显暗带,很多人误以为是信号本身的衰减;noverlap取窗长一半是常规做法,重叠太低时间轴稀疏,太高则相邻帧高度相关,图会显得「糊在一起」。
3.3 四种故障横向对比,把 32 这个结论坐实
单看内圈一条信号选窗口是不够的,要拿正常、内圈、滚动体、外圈四条一起比,看哪种窗口下四类图的差异最明显。
fig, axes = plt.subplots(2, 2, figsize=(16, 8), dpi=100) for ax, (label, s) in zip(axes.ravel(), signals.items()): f, t, mag = stft_map(s, nperseg=32) # pcolormesh 比 imshow 更适合非均匀时间轴,shading='gouraud' 平滑色块 ax.pcolormesh(t * 1000, f, mag, shading='gouraud', cmap='jet') ax.set_title(f'STFT nperseg=32 - {label}') ax.set_xlabel('时间 / ms') ax.set_ylabel('频率 / Hz') plt.tight_layout() plt.show()逻辑说明:t * 1000把秒换成毫秒,和上一章的时域图横轴保持一致,方便左右对照;cmap='jet'是这类故障诊断论文里的常见配色,观感上对比度高,但如果要投稿,建议换viridis或turbo,色盲友好且打印不糊。四个子图用同一套坐标范围,pcolormesh默认按数据自适应,这里因为四段信号长度一致,纵轴上限自动对齐,不需要手动设vmin/vmax。
判读要点:正常信号的时频图能量分布相对均匀,没有周期性竖条;内圈故障在图上表现为等间隔的竖向亮条,间隔对应冲击重复周期;滚动体故障的条带更散,因为滚动体自转导致冲击幅值被调制,条带高度不齐;外圈故障的条带最规整,因为外圈固定,冲击路径稳定。窗口 32 时这四类的差异最容易被一眼分开——窗口 16 时全是竖线,窗口 128 时全是横带。这就是「尺度取 32」的实证依据,不是拍脑袋。
需要提醒的是,这条结论绑定在 1024 点窗口和 12 kHz 采样上。如果换成 48 kHz 采样、截 2048 点,同样取 32 帧数会变,频率分辨率变成 1500 Hz,可能需要往上调到 64 或 128。参数是可以迁移的思想,数值不是。
4. 连续小波变换:morl、cmor、cgau8 怎么选,scales 怎么构造
STFT 的窗口一旦定下就固定了,低频和高频用同一把尺子。轴承故障的麻烦在于:冲击本身是宽带的高频瞬态,而故障的重复周期又落在低频,理想情况是高频处窗短一点、低频处窗长一点。连续小波变换干的就是这件事——用小波函数的伸缩代替固定窗,尺度大的时候小波被拉长看低频,尺度小的时候被压缩看高频。
4.1 为什么冲击型故障更吃小波这一套
滚动体每碾过一次缺陷,产生一个近似脉冲的冲击,冲击激起轴承座的结构共振,形成一段高频衰减振荡。这个结构在时域上表现为「稀疏的尖峰 + 衰减尾巴」,在频域上表现为「共振频带 + 边带」。STFT 用固定窗去套,窗口短则共振频带糊,窗口长则冲击被平均。CWT 的自适应尺度天然适配这种多尺度结构,尤其是复小波还能同时给出幅值和相位,做包络分析时省一步希尔伯特变换。
但小波不是万能药。它的问题在于参数空间更大:小波函数、尺度序列、中心频率,三个东西互相耦合,调错一个图就废。而且 CWT 的计算量随尺度数量线性增长,128 个尺度跑一条 1024 点信号还算轻快,跑到几万点的长序列就要考虑降采样或者改用离散小波。
4.2 尺度序列不是 1 到 N,cparam 这个常数是干什么的
新手最容易写错的一行是scales = np.arange(1, totalscal+1)。这么写能跑,但得到的频率轴是反的,图也和论文里对不上。正确的构造方式是先取小波的中心频率,再算一个常数项,最后倒序生成尺度序列:
import pywt import numpy as np fs = 12000 totalscal = 128 wname = 'cmor1.5-2' # precision 提高数值积分的精度,中心频率算得更准,默认值有时偏差明显 fc = pywt.central_frequency(wname, precision=16) cparam = 2 * fc * totalscal scales = cparam / np.arange(totalscal, 0, -1) # 从大到小,对应频率从低到高 print(f'中心频率 fc={fc:.4f}, cparam={cparam:.2f}') print(f'尺度范围: {scales[0]:.2f} ~ {scales[-1]:.2f}')逻辑说明:pywt.central_frequency返回的是归一化中心频率(相对于小波自身的采样),对cmor1.5-2这类复小波,这个值由带宽参数和中心频率参数共同决定。cparam = 2 * fc * totalscal的作用是把尺度序列映射到「覆盖到奈奎斯特频率附近」的区间,2*fc保证了最高频率落在采样率的一半左右。倒序np.arange(totalscal, 0, -1)让scales[0]最大、scales[-1]最小,对应输出频率从低到高,contourf画出来纵轴才是常规方向。
参数说明:cmor1.5-2的命名规则是cmorB-C,其中 1.5 是带宽参数 B(控制小波在频域的宽度),2 是中心频率参数 C(控制小波振荡的快慢)。这一点经常被写反,按反的理解去调参,带宽和中心频率的耦合关系就全乱了。cgau8是 8 阶复高斯小波,阶数越高对高频越敏感,但对低频的局部化能力下降。
4.3 四种小波函数跑同一段信号,差异一目了然
wavenames = ['cgau8', 'morl', 'cmor1-1', 'cmor1.5-2'] fig, axes = plt.subplots(2, 2, figsize=(16, 8), dpi=100) for ax, wname in zip(axes.ravel(), wavenames): fc = pywt.central_frequency(wname, precision=16) scales = (2 * fc * totalscal) / np.arange(totalscal, 0, -1) # sampling_period 决定输出频率的单位,必须显式传,否则 freqs 是归一化值 coef, freqs = pywt.cwt(signals['内圈'], scales, wname, sampling_period=1.0 / fs) ax.contourf(np.arange(1024) / fs * 1000, freqs, np.abs(coef), levels=32, cmap='jet') ax.set_title(wname) ax.set_xlabel('时间 / ms') ax.set_ylabel('频率 / Hz') plt.tight_layout() plt.show()逻辑说明:每个子图独立算fc,因为四种小波的中心频率不同,直接共用同一组scales会导致频率轴错位——这是横评时最常见的隐性错误,图上看着都有内容,实际上纵轴刻度对不上,结论完全不可信。sampling_period传1/fs之后,返回的freqs单位才是赫兹,不传的话是 0 到 0.5 的归一化值,contourf打出来的纵轴标签写着 Hz 其实是错的。
判读要点:morl的时频块比较圆润,能量集中但边缘拖尾;cmor1-1带宽较宽,竖向分辨率好但频率方向糊;cgau8对冲击的起跳点最敏感,高频细节清楚,低频条带偏弱;cmor1.5-2在两者之间,冲击时刻和共振频带都能看清,四类故障的条带形态差异也最大。所以最终选cmor1.5-2,而不是因为它名字里数字大。
4.4 尺度 32 到 256,把「高频看小尺度」这句话验证一遍
totals = [32, 64, 128, 256] wname = 'cmor1.5-2' fc = pywt.central_frequency(wname, precision=16) fig, axes = plt.subplots(2, 2, figsize=(16, 8), dpi=100) for ax, n in zip(axes.ravel(), totals): scales = (2 * fc * n) / np.arange(n, 0, -1) coef, freqs = pywt.cwt(signals['滚动体'], scales, wname, sampling_period=1.0 / fs) ax.contourf(np.arange(1024) / fs * 1000, freqs, np.abs(coef), levels=32, cmap='jet') ax.set_title(f'totalscal={n}') ax.set_xlabel('时间 / ms') plt.tight_layout() plt.show()逻辑说明:注意cparam随totalscal同步变化(2*fc*n),不能沿用 128 时算出来的常数,否则尺度范围整体偏移,各子图的纵轴覆盖的频段不一致,看起来像是尺度变了,其实频率范围也变了,对比失去意义。
判读要点:totalscal=32时图上只有粗糙的几层,低频区几乎是空白;增加到 256,图底部(低频区)的横向条带明显变密,故障的调制特征开始显现。这正好印证了小波的双尺度性质:想抓冲击的陡峭上升沿,用较小的尺度;想抓故障重复频率带来的低频调制,用较大的尺度。工程上取 128 是折中,既覆盖了共振频带,也保住了低频结构,再往上加计算量翻倍而信息增量有限。
5. 排查:图发黑、频率轴反了、代码直接报错的五个具体坑
5.1 坑一:NameError: name 'sampling_period' is not defined
现象:照着文档敲完pywt.cwt那一行,解释器直接抛未定义变量,或者更隐蔽一点,跑通了但频率轴数字完全不对。 原因:把采样周期当成位置参数漏传了=1/fs,同时把sampling_rate = 1024这个窗口点数当成了采样率写进代码。1024 是截取的点数,不是每秒采样多少个点,两者差了一个数量级。 解决:单独定义fs并从数据说明里核对,所有地方统一用sampling_period=1.0/fs。跑完第一件事是print(freqs[-1]),看最高频率是不是接近 fs/2,对不上就说明采样率错了。
5.2 坑二:时频图上下颠倒,故障频带跑到最底下
现象:contourf出来的图,明显的高能量条带集中在纵轴底部,但理论上内圈故障的共振区应该在中高频段。 原因:scales用正序np.arange(1, totalscal+1)生成,pywt.cwt返回的freqs就变成从高到低递减,而contourf默认按数组顺序铺纵轴,图就反了。 解决:尺度序列必须从大到小,np.arange(totalscal, 0, -1)。改完顺手检查freqs[0] < freqs[-1]是否成立。这个坑最阴的地方在于图看起来「有内容」,不仔细对频率刻度根本发现不了。
5.3 坑三:整张图一片深蓝,只剩几个亮点
现象:时频图上大部分区域接近背景色,只有极少数像素亮,调levels也没用。 原因:np.abs(coef)的动态范围可能有四五个数量级,线性映射到一个色表里,99% 的像素被压到最暗端。STFT 的np.abs(Z)同理。 解决:画图前做对数压缩,20 * np.log10(np.abs(coef) / np.abs(coef).max() + 1e-12),或者用np.log1p。加1e-12是为了防止对零取对数产生-inf,-inf会让contourf直接丢层。
5.4 坑四:复现出来的图和文档对不上,窗口尺寸不一致
现象:文档里写的是对比 16、32、64 三个尺度,按代码跑出来却是 32、64、128、256 四张图,标题写「尺度 16」的那张,实际用的是 32。 原因:文档在写作过程中改过参数,注释和代码没有同步,window_size = 32上方的注释还留着旧值。这在拼装式笔记里非常普遍。 解决:不要相信注释和图片标题,只相信当前代码里传进函数的实参。复现时把每次调用的nperseg打印出来,和子图标题一起核对,对不上就以代码为准并在自己的笔记里改正。
5.5 坑五:四条波形长得几乎一样,分不出故障
现象:正常、内圈、滚动体、外圈四段时域波形叠在一起看,幅值范围接近,肉眼没有明显区别,让人怀疑数据读错了。 原因:一是只截了前 1024 点,缺陷冲击的重复频率在这个窗口内体现得不充分;二是没做去均值,各段信号的直流偏置不同,纵轴被偏置撑开,冲击细节被压缩。 解决:截窗前先减去均值s - s.mean(),必要时做归一化s / np.abs(s).max(),这样四段信号的纵轴量纲一致,冲击幅值差异才看得出来。如果还是分不开,把窗口拉到 4096 点再看,冲击的等间隔结构会清楚很多。
6. 从一张图到一批样本:批量出图与出图前的四项校验
单条信号调通参数只是第一步,真正要做故障分类,需要把几百个.mat文件统一转成尺寸一致的时频图。这一步最容易出的问题是「图能出来,但样本之间不可比」。
6.1 一个函数把文件列表转成图库
import os import numpy as np import pywt import matplotlib.pyplot as plt from scipy.io import loadmat fs = 12000 totalscal = 128 wname = 'cmor1.5-2' fc = pywt.central_frequency(wname, precision=16) scales = (2 * fc * totalscal) / np.arange(totalscal, 0, -1) def mat_to_cwt_image(path, out_path, win=1024): raw = loadmat(path) keys = [k for k in raw if k.endswith('_DE_time')] if not keys: return False s = raw[keys[0]].reshape(-1).astype(np.float64)[:win] if s.size < win: return False s = s - s.mean() # 去直流,避免偏置撑开动态范围 s = s / (np.abs(s).max() + 1e-12) # 幅值归一化,样本之间可比 coef, _ = pywt.cwt(s, scales, wname, sampling_period=1.0 / fs) amp = 20 * np.log10(np.abs(coef) / np.abs(coef).max() + 1e-12) fig = plt.figure(figsize=(2.56, 2.56), dpi=100) # 输出 256x256 ax = fig.add_axes([0, 0, 1, 1]) # 铺满画布,不留白边 ax.contourf(amp, levels=32, cmap='jet') ax.axis('off') fig.savefig(out_path, dpi=100) plt.close(fig) return True ok = 0 for name in os.listdir('data'): if name.endswith('.mat'): if mat_to_cwt_image(os.path.join('data', name), os.path.join('out', name.replace('.mat', '.png'))): ok += 1 print(f'成功转换 {ok} 个文件')逻辑说明:去均值和归一化这两步是让样本可比的关键,不做的话,不同负载工况下的信号幅值差异会被网络当成故障特征学进去,模型在测试集上掉点非常明显。add_axes([0,0,1,1])去掉坐标轴留白,保证输出图里只有时频内容,尺寸严格等于画布尺寸,后续送进卷积网络不需要再裁剪。win参数留给不同长度的数据集调整,低于窗口长度的文件直接跳过而不是补零,补零会在图边缘造出人为的暗带。
参数说明:figsize和dpi相乘得到实际像素,2.56 * 100 = 256。要出 224×224 就改成2.24 * 100,保持整数比例,避免重采样引入插值模糊。levels=32是色阶数量,调高会让细节更丰富但文件更大,32 到 64 之间是常见区间。
6.2 出图前必查的四项
| 检查项 | 期望值 | 判断口径 |
|---|---|---|
| 频率轴上限 | 接近 fs/2 | freqs[-1]与 6000 差太远说明 fs 或 scales 错了 |
| 尺度单调性 | 严格递减 | np.all(np.diff(scales) < 0)为 True |
| 图尺寸 | 与训练输入一致 | 读回 png 的shape核对,别只看figsize |
| 幅值范围 | 对数压缩后约 -60 到 0 dB | 全图接近 0 说明归一化写错,全图接近 -60 说明能量丢失 |
这四项我一般在批量转换后抽 5 张图跑一遍脚本核对,比肉眼翻图快得多。
说一个自己的教训。早期做这类时频图数据集,我把正常样本和故障样本混在一起批量出图,跑完发现模型收敛得特别好,验证集准确率 99%,换成新工况直接崩到 60% 出头。回头查才发现,正常样本的文件普遍比故障样本长,截窗后有几张图因为长度不足被我的补零逻辑处理过,图边缘多了一条人为亮带,网络学的是「有没有亮带」而不是「有没有冲击」。从那以后我每次批量出图都强制走一遍上面那四项校验,尤其是样本长度一致性,宁可直接丢弃不足窗口的文件,也绝不补零凑数。信号处理这行,图上多出来的东西比少掉的东西危险得多。希望帮到你。
本文还有配套的精品资源,点击获取