简介:时频分析是处理非平稳信号的经典方法,它同时刻画信号在时间和频率上的能量分布,尤其在旋转机械故障诊断中具有核心价值。短时傅里叶变换(STFT)与连续小波变换(CWT)是两种主流实现,前者依赖窗长权衡分辨率,后者通过尺度变化自适应多分辨率分析,二者都能有效揭示轴承故障冲击的瞬态特征。在深度学习时代,时频图常被作为卷积神经网络的输入特征,大幅提升故障识别准确率。本文以凯斯西储大学(CWRU)轴承数据集为例,从数据读取、参数选择、样本切分到特征可视化,梳理了时频分析落地的完整路径,并总结了常见陷阱与解决方案,帮助工程师快速构建可泛化的诊断流程。
1. 这个数据集为什么值得你亲手做一次时频分析
凯斯西储大学轴承故障数据集(CWRU)是旋转机械故障诊断领域被引用最多的公开数据集之一,网上随便一搜就是一堆论文和代码仓库。但真到自己手里跑一遍,大部分人卡住的地方不是模型,而是信号处理这一层:原始信号拿到手怎么读、时频图参数怎么设、为什么别人能跑出 99% 准确率而你一换负载就不行。市面上讲这个数据集的教程大多停在「用某某算法做分类」的层面,很少把时频分析这一步掰开揉碎讲清楚,而这恰恰是决定整个诊断方案能不能落地的关键环节。
这篇笔记从一个一线工程师的视角,把 CWRU 数据集的时频分析完整走一遍:先讲清楚数据文件里每一列代表什么,再分别用短时傅里叶变换(STFT)和连续小波变换(CWT)做时频图,接着给出从时频图到故障识别的完整代码路径,最后把我在这个数据集上踩过的坑一次性列出来。新手可以照着步骤跑通整个流程,熟手可以直接跳到第 5 章看参数边界和容易翻车的地方。这套方法不需要 GPU,一台普通笔记本就能跑,适合作为你踏入故障诊断信号处理的第一步。
2. 先搞懂 CWRU 数据集的真实结构,不然时频分析就是黑匣子
2.1 数据文件命名规则与采样参数:读文件之前必须知道的几个数字
CWRU 数据集的原始文件是 .mat 格式,从官网下载后你会看到一堆类似12k_Drive_End_B007_0这样的文件名。这个命名规则是理解数据的钥匙:12k表示采样频率 12 kHz,Drive_End表示信号来自驱动端(DE),B007表示故障类型是滚动体故障(Ball fault),故障直径 0.007 英寸,末尾的0表示负载为 0 马力。
类似的还有Fan_End(风扇端)和Normal(正常状态),故障类型包括IR(内圈)、OR(外圈)、B(滚动体)。外圈故障的文件名里还会多出一个位置标注,比如OR007@6表示故障点位于 6 点钟方向。加载.mat文件的方式并不复杂,用以下代码即可获得数组,但关键在于用对键名:
from scipy.io import loadmat import numpy as np # 以 12k_Drive_End_B007_0.mat 为例 mat = loadmat('/path/to/12k_Drive_End_B007_0.mat') print(mat.keys()) # 查看所有键,排除 __header__ 等元信息 # 驱动端振动信号在 'DE_time' 这个键里 de_signal = mat['DE_time'].flatten() print(f'信号长度: {len(de_signal)}, 采样频率: 12000 Hz') # 风扇端信号在 'FE_time',加速度计基座信号在 'BA_time' # 电机转速通常在 'RPM' 里 rpm = mat['RPM'].flatten()[0] print(f'转速: {rpm} rpm')loadmat 读到的是 MATLAB 的字典结构,键名直接对应原文件里保存的变量。DE_time 是驱动端加速度计采集的振动信号,单位是 g(重力加速度),不是位移也不是速度,这一点很多人会搞混。信号长度是 4800 点还是 12000 点并不固定,取决于原始记录时长;12 kHz 采样下一般约 10 秒。处理之前建议统一截取固定长度,避免后续特征提取时维度不一致。
2.2 四种状态与三类故障:标签怎么贴、负载怎么对应
CWRU 数据集涵盖四种健康状态:正常(Normal)、内圈故障(IR)、外圈故障(OR)、滚动体故障(B)。每种故障又有 0.007、0.014、0.021 英寸三种损伤直径,再加上 0~3 马力四种负载工况。组合起来一共是 4(状态)× 3(损伤尺寸)× 4(负载)约 48 个文件,正常样本单独一组。
这里要特别注意负载与转速的对应关系。很多人照搬论文里的转速当成固定值,实际上负载是 0 hp 时转速约 1797 rpm,1 hp 时约 1772 rpm,负载越大转速越低。转速不同意味着同一故障产生的冲击频率不同,做时频分析时如果直接拿不同负载的样本混在一起训练模型,特征分布会严重偏移。我一般习惯先按负载分组做特征提取,再考虑是否做跨负载泛化测试。
文件加载后建议立即构建一个标签函数:
def parse_cwru_filename(filename): # 解析文件名,返回 (状态, 故障直径英寸, 负载hp) name = filename.split('.')[0] if name.startswith('Normal'): return 'Normal', 0, int(name.split('_')[-1]) _, loc, fault, size = name.split('_')[:4] # loc: DE=驱动端, FE=风扇端 # fault: B/IR/OR return fault, float(size[1:])/1000, int(name.split('_')[-1])这个解析函数把文件名映射成结构化的标签信息,后续构建 DataFrame 时可以直接调用。解析时留意文件名分段可能不同,建议先用print确认结构再写正则。故障直径的单位是英寸,但论文里常常直接写 0.007、0.014,实际换算成毫米分别是 0.18、0.36、0.53,不算大。
2.3 信号形态的直观感受:先看一眼原始信号,再决定做什么时频分析
拿到数据后的第一步不是急着跑 STFT 或 CWT,而是先画出原始信号的波形图,直观感受不同故障状态的差异。正常轴承的振动信号幅值大约在 ±0.3 g 之间,而出现剥落故障后,每次滚动体滚过缺陷位置都会产生明显的冲击脉冲,幅值可能达到 ±2 g 以上,内圈和外圈故障的冲击特征还各有不同。
import matplotlib.pyplot as plt plt.figure(figsize=(12, 4)) plt.plot(de_signal[:1024]) plt.title('B007 @ 0hp - Drive End Signal') plt.xlabel('Sample Index') plt.ylabel('Acceleration (g)') plt.show()正常状态信号看起来是相对平稳的宽带随机振动,内圈故障的冲击波形比较尖锐且周期明显,外圈故障的冲击幅度相对均匀——外圈固定不动,滚动体每次经过缺陷都产生几乎等幅的冲击。滚动体故障的冲击特征最弱,因为缺陷随保持架旋转,冲击位置不固定,幅值调制现象明显。这一步的意义在于:时频分析能帮你看到频域上的变化,但首先得知道时域上信号大概长什么样。
3. 时频分析方法怎么选:STFT、CWT 与 HHT 在 CWRU 上的适用性对比
3.1 为什么不能只做 FFT:平稳性假设在轴承故障信号上站不住
传统的傅里叶变换假设信号是平稳的,也就是统计特性不随时间变化。但轴承故障振动信号恰恰是非平稳的——故障冲击在时间上呈周期性出现,冲击瞬间的能量分布和正常运行时段明显不同,频率成分随时间动态变化。FFT 只能告诉你整个时间段内有哪些频率成分,却丢失了「这些成分是什么时候出现的」这一关键信息。
时频分析的核心价值在于同时保留时间和频率两个维度的信息。从 CWRU 数据里截取一秒的信号,FFT 能看出故障特征频率附近有峰值,但看不出冲击脉冲的周期性节奏;而时频图能看到每一个冲击瞬间的频率分布和幅度变化,信息量大得多。做故障诊断时,时频图还能直接观察共振频带的激励情况,辅助确定带通滤波器的中心频率。
3.2 STFT 的窗口选择:窗长、重叠率怎么定才能兼顾时间与频率分辨率
短时傅里叶变换把信号切成一段段短窗口,对每个窗口做 FFT,本质上是把非平稳信号近似为分段平稳信号。STFT 的核心矛盾在于海森堡测不准原理:窗越长频率分辨率越高,但时间分辨率越低,冲击事件的起止时刻被拉平了。
from scipy.signal import stft import numpy as np fs = 12000 # 窗长 256 点,约 21 ms;重叠 50% f, t, Zxx = stft(de_signal, fs=fs, window='hann', nperseg=256, noverlap=128, nfft=512) plt.pcolormesh(t, f, np.abs(Zxx), shading='gouraud', cmap='jet') plt.ylim(0, 3000) # 只看 0-3 kHz,避开高频噪声 plt.xlabel('Time (s)') plt.ylabel('Frequency (Hz)') plt.colorbar(label='Amplitude') plt.show()窗长 256 时频率分辨率约 46.9 Hz,在 12 kHz 采样率下算中规中矩;把 nperseg 改成 512,频率分辨率能到 23.4 Hz,但时间分辨率会降低到约 42 ms,相邻两次冲击(瞬时频率通常在几十 Hz)之间可能被模糊掉。如果目的是观察冲击脉冲的时间节奏,窗长 128 更合适;如果目的是区分两个靠得很近的频谱峰值,窗长 512 更好。overlap 一般设 75%,时间轴更平滑,视觉效果更清楚。
STFT 做出来的时频图横幅较窄,频率轴上限是奈奎斯特频率 6000 Hz。实际观察中发现 CWRU 驱动端信号的共振频带在 2~4 kHz 附近,把 ylim 设置在这个区间内能看到明显的高亮横带,那就是故障冲击激励起结构共振的表现。
3.3 CWT 的尺度与母小波选择:morlet 小波为什么天然适配冲击特征
连续小波变换通过改变尺度参数对信号做多分辨率分析,低频段频率分辨率高、高频段时间分辨率高,这种特性让它特别适合分析轴承故障信号的宽频冲击特征。CWRU 数据上最常用的母小波是 morlet,它是复值小波,形状就是一条衰减振荡,和轴承故障冲击的波形有很高的相似度。
import pywt import numpy as np fs = 12000 # 定义尺度范围:最低频率 50 Hz,最高频率 4000 Hz scales = np.arange(1, 128) coefficients, frequencies = pywt.cwt(de_signal[:4096], scales, 'morl', sampling_period=1/fs) plt.figure(figsize=(12, 5)) plt.imshow(np.abs(coefficients), extent=[0, 4096/fs, frequencies[-1], frequencies[0]], aspect='auto', cmap='jet') plt.ylim(0, 3000) plt.xlabel('Time (s)') plt.ylabel('Frequency (Hz)') plt.colorbar(label='Magnitude') plt.show()pywt.cwt 返回的 frequencies 是从高到低排列的,所以 extent 里frequencies[-1]是下限、frequencies[0]是上限。scale 范围需要根据采样率调整,scale=1 对应的中心频率最高,scale 越大频率越低。CWRU 数据是 12 kHz 采样,把最高频率限制在 4000 Hz 以内可以避开无意义的超高频噪声,把这部分频谱浪费掉的同时突出共振频带。
相比 STFT,CWT 的时频图能量更集中,冲击事件呈现为竖直的亮条,看起来更「尖锐」。缺点是计算量大一些,4096 个点很快,但整段 12000 点信号全部做 CWT 会比较慢。实际工程里我习惯先截取 2048 或 4096 点的小段做精细分析,再决定要不要全量处理。
3.4 Hilbert-Huang 变换在 CWRU 上的局限:模态混叠与端点效应
HHT(Hilbert-Huang 变换)由经验模态分解(EMD)加 Hilbert 变换组成,是另一种常见的时频分析工具,在学术论文里很受欢迎。但我不建议在 CWRU 数据集上把 HHT 作为主要方法。理由很简单:EMD 分解对噪声敏感,CWRU 信号的冲击成分会和噪声分量混叠,导致本征模态函数(IMF)失去物理意义。另一个问题是端点效应——信号两端在分解时容易产生虚假的振荡成分,严重时会污染整个时频图。
如果你确实想用 HHT 做对比实验,至少要把信号两端做镜像延拓,并设定 IMF 数量上限。实际效果和 CWT 相比,在正常信号和滚动体故障上差异不大,但在内圈故障这种调制特征明显的信号上,HHT 的边际谱反而更容易解读——它直接给出瞬时频率的分布,不需要像 CWT 那样去解释尺度转换。HHT 适合做研究性探索,CWT 适合做工程落地。
4. 从时频图到故障识别:样本切分与 CNN 分类的完整路径
4.1 为什么要把连续信号切成样本,以及切多长最合理
拿到一段 10 秒的连续信号,直接整段做特征提取再分类,得到的只是一个样本,泛化能力无从谈起。工程上通用的做法是把连续信号切成若干个短样本,每个样本独立做时频变换,再作为一条数据输入模型。切分长度取决于故障特征频率和转速:CWRU 在 1797 rpm 下转频约 30 Hz,每转 0.033 秒。为了保证每个样本至少包含几个完整的转频周期,样本长度至少要 0.1 秒,我一般取 1024 点(约 0.085 秒)或 2048 点(约 0.17 秒)。
样本太短包含的冲击次数少,特征不稳定;太长则样本数量少,数据增强空间小。1024 点是一个不错的折中,因为对 12 kHz 采样率来说,它能覆盖到 5000 Hz 以上的频率分辨率需求,同时保证一个样本包含 2~3 个转频周期。
4.2 样本级别的数据划分:为什么同一个文件切出的样本不能同时进训练集和测试集
这里有一个初学者最容易犯的错误:直接从同一个 .mat 文件里切出一堆样本,随机分成训练集和测试集。这样做的话,训练集和测试集里有很多样本来自同一条连续信号,它们之间的相关性极强,模型的测试准确率会虚高到接近 100%,但换到另一段信号上立刻拉胯。正确做法是按文件级别划分——同一个文件切出的所有样本要么都在训练集,要么都在测试集。
from sklearn.model_selection import GroupShuffleSplit # 假设 samples 是 (N, 1024) 的二维数组,groups 是每条样本对应的文件名 split = GroupShuffleSplit(n_splits=1, test_size=0.3, random_state=42) train_idx, test_idx = next(split.split(samples, groups=groups)) train_samples, test_samples = samples[train_idx], samples[test_idx] train_labels, test_labels = labels[train_idx], labels[test_idx]GroupShuffleSplit 按组划分,确保同一个组(同一条原始信号)的样本不会被分到两边。训练集和测试集在故障类型、负载上要保持大致相同的分布,否则模型学到的是负载差异而不是故障差异。负载影响的不仅仅是幅值,还有共振频带的激励程度,跨负载泛化是故障诊断里最难的课题之一。
4.3 把样本批量转成 CWT 时频图并保存为多维数组
确认了切分策略之后,接下来写一个批量处理流程:读取所有 CWRU 文件、切分样本、逐条做 CWT、保存成 NumPy 数组。这样后面训练 CNN 时直接从数组里取数据,不用每轮都重复算小波变换。
import pywt import numpy as np from scipy.io import loadmat import os def extract_samples_from_file(filepath, sample_len=1024, step=512): mat = loadmat(filepath) de_signal = mat['DE_time'].flatten() # 用滑窗切样本,步长取 512 保证有一定重叠 samples = [] for start in range(0, len(de_signal) - sample_len, step): samples.append(de_signal[start:start + sample_len]) return np.array(samples) def cwt_filter_bank(signals, fs=12000, scales=np.arange(1, 128)): # 输入 (N, sample_len),输出 (N, len(scales), sample_len) n = len(signals) out = np.zeros((n, len(scales), signals.shape[1])) for i, sig in enumerate(signals): coeff, _ = pywt.cwt(sig, scales, 'morl', sampling_period=1/fs) out[i] = np.abs(coeff) return out # 批量读取:遍历所有 .mat 文件 all_signals, all_groups, all_labels = [], [], [] for fname in sorted(os.listdir('/path/to/cwru_data')): if not fname.endswith('.mat'): continue fault_type, diameter, load = parse_cwru_filename(fname) samples = extract_samples_from_file(fname) all_signals.append(samples) all_groups.extend([fname] * len(samples)) all_labels.extend([fault_type] * len(samples)) all_signals = np.concatenate(all_signals, axis=0) cwt_imgs = cwt_filter_bank(all_signals) # 形状 (N, 128, 1024) np.save('cwru_cwt_images.npy', cwt_imgs) np.save('cwru_cwt_labels.npy', np.array(all_labels)) np.save('cwru_cwt_groups.npy', np.array(all_groups))CWT 系数矩阵的形状是 (样本数, 尺度数, 信号长度),尺度数 127、信号长度 1024。这个数组直接输入 CNN 的输入层时,要加一个通道维度,变成 (N, 127, 1024, 1)。尺度数可以适当减小,比如只取前 64 个尺度,因为 CWRU 的有效频率范围在 2000~4000 Hz,尺度太小的部分全是高频噪声,尺度太大的部分频率太低,失去了故障特征的辨识度。
注意尺度 127、长度 1024 的矩阵存下来,如果样本数达到上千,这个文件会占用不少内存。处理前估算一下:10000 个样本 × 127 × 1024 × 8 字节(float64)约 10 GB,所以保存时用 float32,能省一半空间。或者把尺度数降到 64,直接减少存储和计算量。
4.4 用二维 CNN 对 CWT 时频图做故障分类
有了时频图之后,故障诊断就变成一个标准的图像分类任务。设计一个轻量级 CNN,输入 (127, 1024, 1),通过两层卷积提取时频模式,最后接全连接层输出类别概率。
import tensorflow as tf from tensorflow.keras import layers, models model = models.Sequential([ layers.Input(shape=(127, 1024, 1)), layers.Conv2D(16, 3, activation='relu', padding='same'), layers.MaxPooling2D(2), layers.Conv2D(32, 3, activation='relu', padding='same'), layers.MaxPooling2D(2), layers.Conv2D(64, 3, activation='relu', padding='same'), layers.GlobalAveragePooling2D(), layers.Dense(4, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.summary()这里的类别数是 4,对应正常、内圈、外圈、滚动体四种状态。如果还想区分不同故障直径,就要把类别扩到 10 个(正常 + 3 种故障 × 3 种直径)。四分类是验证流程是否跑通的最短路径,十分类能检验特征是否真的区分了损伤程度。
# 加载保存好的数据 X = np.load('cwru_cwt_images.npy') # (N, 127, 1024) y = np.load('cwru_cwt_labels.npy') groups = np.load('cwru_cwt_groups.npy') # 加通道维度 X = X[..., np.newaxis].astype('float32') # 标签编码 from sklearn.preprocessing import LabelEncoder le = LabelEncoder() y_enc = le.fit_transform(y) # 按文件分组划分训练测试 train_idx, test_idx = next(GroupShuffleSplit(n_splits=1, test_size=0.3, random_state=42).split(X, y_enc, groups=groups)) history = model.fit(X[train_idx], y_enc[train_idx], validation_data=(X[test_idx], y_enc[test_idx]), epochs=20, batch_size=32)实测下来,CWT 时频图 + 简单 CNN 在 CWRU 数据上四分类准确率能轻松达到 98% 以上,关键不是模型有多深,而是输入特征的质量。如果直接把原始信号喂给 1D CNN,准确率会低不少,因为模型需要自己从原始波形里学特征,数据量不够时学不到稳定的冲击模式。时频图相当于把领域知识(冲击特征和共振频带的位置)直接用图像形式提供给了模型。
这种做法把「信号处理」和「深度学习」解耦了:信号处理部分你完全控制,时频图不对就换参数重跑;模型部分很薄,即使换成随机森林或 SVM 也能得到不错的结果,不依赖深度模型的玄学调参。
5. CWRU 时频分析避坑指南:现象、原因、解决
5.1 频率轴显示异常:图上看不到共振频带,只有一片模糊的高频色块
现象:时频图画出来整体偏暗,亮色集中在频率轴顶端,看不到预期的共振频段。
原因:CWT 的尺度范围设得太大,最高频率远超信号有效带宽;或者坐标变换时频率映射反了,把高频当低频显示。STFT 则是 ylim 没设置,默认全频段显示,高频噪声主导了色标范围,把能量相对较弱的共振带压成了背景色。
解决:CWT 的尺度范围不要从 1 直接到 128,先打印出pywt.scale2frequency('morl', scales) / fs检查每个尺度对应的实际频率。STFT 的话在pcolormesh里显式设ylim=(0, 4000),并用vmin/vmax做幅度阈值裁剪,只显示能量集中的部分。CWRU 驱动端信号在 2~4 kHz 有明显的共振激励,把频率轴上限设在这个范围外高一点点最合适。
5.2 训练精度极高但测试精度崩了:数据划分方式出了问题
现象:按样本随机划分后训练准确率接近 99%,测试也 98%,但换一段同类型数据(不同负载)准确率掉到 60%。
原因:这就是典型的同源数据泄露。同一个 .mat 文件切出的样本之间高度相关,本质上是同一条时间序列的近邻片段,放进训练集和测试集之后,模型看到的是几乎相同的模式,等于开卷考试。换到不同负载的数据后,转速变化带来冲击频率偏移,模型没见过这种分布变化,精度自然崩。
解决:坚持按文件分组划分数据。用 GroupShuffleSplit 或 GroupKFold 保证同一个原始信号的样本不出现在划分边界两侧。进一步可以做跨负载验证:用 0hp 的数据训练,用 1hp 的数据测试,这样才能真正评估模型的泛化能力。CWRU 数据集的负载一共 0~3 hp 四档,做 4 折交叉验证、每次用 3 档训练 1 档测试,是更严格的评估方案。
5.3 内圈和外圈故障总是分不清:转速变化导致特征频率重叠
现象:模型在 0hp 数据上对内圈和外圈故障区分得很好,但把 1hp 和 2hp 的样本加进来后,两类故障频繁误分。
原因:内圈故障特征频率和外圈故障特征频率都与转频成正比,负载越大转速越低,特征频率整体下移。如果数据划分时负载分布不均匀(比如训练集里 0hp 样本多,测试集里 2hp 样本多),模型学到的频率边界对不上。外圈故障还有一个位置变量——故障点在 3 点钟方向和 6 点钟方向时,冲击传递路径不同,载荷分布不同,频谱形态有明显差异,如果数据集里混入了不同位置的外圈故障样本,模型会无所适从。
解决:建立标签时把负载和故障位置都显式编码。跨负载训练时,先对信号做阶比分析——把频率轴从 Hz 映射到转频的倍数(阶比),这样不同转速下同一故障类型对应的阶比位置一致,模型学到的特征不再依赖绝对频率。不做阶比的话,至少保证每个负载的样本在训练集和测试集中占比一致,避免分布偏移。
5.4 时频图有「重影」:SCFT 窗口太长导致冲击时刻被模糊
现象:用 STFT 画出来的时频图上,冲击事件看起来是水平扩散的亮斑,时间方向上有拖尾,无法精确判断冲击发生的时刻。
原因:窗函数太长,频率分辨率上去了,时间分辨率却下来了。一个冲击事件在时间上只有几个毫秒,如果用 512 点的窗(约 42 ms),冲击能量被平均摊到整个窗长上,时间方向上自然就糊了。
解决:把 nperseg 降到 128,频率分辨率虽然变差了,但时间分辨率提高到约 10 ms,能清楚看到每个冲击脉冲的瞬间能量爆发。如果既要时间分辨率又要频率分辨率,就该换 CWT 而不是在 STFT 上调参。CWT 的尺度变化天然适配这种需求,低频段看趋势,高频段看瞬态。
5.5 CWT 图有奇怪的竖条纹:尺度参数过大或小波基不匹配
现象:CWT 时频图上出现周期性的竖直条纹,看起来像信号被某种调制干扰,排除故障本身的冲击周期后依然存在。
原因:尺度数量过多导致冗余计算——相邻尺度之间的中心频率差小于频率分辨率,时频图出现重复的能量带,视觉上形成条纹。更隐蔽的原因是母小波选型问题,如果用 haar 这类不连续的小波去匹配 CWRU 的振荡型冲击信号,会产生大量虚假的高频分量。
解决:CWT 时限制尺度数不超过 64,间隔取非线性分布(比如对数间隔)而不是均匀间隔,减少冗余。母小波固定用 morlet,它是复值小波,对相位信息不敏感,在轴承故障信号上表现最稳定。如果换成 db4 之类的实值小波,务必检查小波形状和冲击波形的相似度——CWT 的特征提取效果高度依赖母小波与信号形态的匹配程度。
5.6 内存爆炸:全量数据一次性做 CWT 导致程序卡死
现象:脚本跑了几分钟后内存占用飙升,直接 OOM 崩溃。
原因:CWT 的计算过程中会生成中间复数数组,如果一次性把上千条样本全部塞进cwt_filter_bank,内存直接被中间变量撑爆。
解决:批量处理,每次只处理 32 或 64 条样本,先保存到磁盘再释放内存。或者把信号长度从 1024 缩短到 512,减少每一维度的大小。生成 CWT 图时还可以直接保存为 PNG 图片,后续用 ImageDataGenerator 从文件夹读取,不需要把整个数组常驻内存。这个方案在工厂实际场景里更务实——生产环境的数据量远不止 CWRU 这点规模,必须考虑流式处理。
6. 一个值得一试的进阶验证方法:用 t-SNE 可视化时频特征的可分性
在投入更多精力调模型之前,我建议先用 t-SNE 对 CWT 特征做一次可视化,确认不同故障类别的特征空间分布是否真的分开。这个方法不需要训练任何模型,几分钟就能看出时频分析参数设置是否合理——如果 t-SNE 图上四类样本混成一团,后面的卷积网络调得再好也难有质的提升。
from sklearn.manifold import TSNE # 取少量样本做 t-SNE,避免计算过慢 sample_idx = np.random.choice(len(X), 1000, replace=False) X_sample = X[sample_idx, :, :, 0] # 去掉通道维度 X_flat = X_sample.reshape(len(X_sample), -1) tsne = TSNE(n_components=2, perplexity=30, random_state=42) emb = tsne.fit_transform(X_flat) plt.figure(figsize=(8, 6)) for i, label in enumerate(le.classes_): mask = np.array(all_labels)[sample_idx] == label plt.scatter(emb[mask, 0], emb[mask, 1], s=10, label=label) plt.legend() plt.title('t-SNE on CWT features') plt.show()如果四类样本在二维平面上形成四个明显分离的簇,说明时频图提取的特征质量足够好,模型哪怕用最简单的分类器也能得到不错的结果。如果只有部分类别分开(比如正常和内圈分开了,滚动体和外圈纠缠在一起),回头检查那两类信号的时频图,往往能找到参数设置的问题——比如尺度范围没覆盖故障特征频率所在频段。
我在实际项目里一般把 t-SNE 当作信号处理参数的验收标准:特征可分,说明参数合理;特征重叠,先别急着换模型,回头调时频参数,直到特征分布出现清晰的簇结构再进入建模环节。这个习惯帮我排掉过不少信号处理层面的坑,比如曾经为了追求视觉效果把 STFT 窗长调得过大,模型一直不好,用 t-SNE 一看才发现不同故障类别的特征在空间上完全重叠,换成 CWT 之后簇结构立刻清晰。
CWRU 数据集只是一个起点,真正的工程场景里噪声更大、转速波动更剧烈、故障类型更隐蔽,但时频分析这套方法论是通用的:先把信号变换成信息密度高的特征表示,再做模式识别。这套流程你在这个数据集上跑熟了,遇到实际设备数据时,只需要调整采样率、频率范围和样本长度,其余方法都能平移。希望这篇笔记帮你在轴承故障诊断这个方向上少走些弯路。
本文还有配套的精品资源,点击获取