做故障诊断这几年,我最大的感受是:单一模型很难通吃所有场景。振动信号里既有局部冲击的时域特征,又有调制边带的频域特征,还带着明显的时序演变趋势。早期我试过只用原始波形喂 CNN,效果不稳定,频带重叠一多就抓瞎;也试过纯手工提特征再上分类器,费时费力不说,换个工况就得重新调。后来把短时傅里叶变换(STFT)、卷积神经网络(CNN)和长短期记忆网络(LSTM)串成一条流水线,用 STFT 把一维振动信号变成二维时频图,CNN 负责从时频图里抠局部纹理特征,LSTM 再顺着时间轴把这些特征串起来建模,效果立刻上了一个台阶。这篇就把整个建模过程和 Matlab 实现的关键细节掰开揉碎讲清楚,对正在做旋转机械故障诊断、轴承齿轮箱状态识别,或者想把深度学习用到振动分析里的朋友,应该都有参考价值。
1. 为什么故障诊断需要 STFT、CNN、LSTM 三件套
1.1 传统故障诊断方法的困境
以前做故障诊断,最常见的是两套路子。一套是纯信号处理:对振动信号做 FFT 看频谱,或者算包络谱、功率谱密度,找出特征频率和故障频率做比对。这套方法的问题是高度依赖人工经验,不同轴承型号、不同转速下的故障特征频率得现查现算,稍有不慎就对错了边频。另一套是手工特征加机器学习:先算一堆时域指标(均方根值、峰值因子、峭度)和频域指标,再用 SVM 或随机森林分类。这种做法有效,但特征工程的工作量极大,信号稍微带点非平稳性,指标就会漂移,泛化能力很有限。
深度学习介入之后,最大的变化是把"特征提取"这个环节也交给模型自己学。可实际跑下来又会发现:直接把一维时序信号扔给 CNN 或 LSTM,效果不一定比传统方法好多少。原因也好理解,振动信号本身的信噪比不高,故障特征往往淹没在强背景噪声里,一维卷积的感受野有限,很难同时兼顾局部冲击和全局周期性。
1.2 三种技术的互补逻辑
所以我把思路转成了"信号预处理 + 空间特征提取 + 时序建模"三段式。STFT 干的事情是给信号加窗做分帧,再把每一帧做 FFT,最后把频谱按时间顺序堆叠起来,形成一张时频图。这张图的横轴是时间,纵轴是频率,颜色深浅代表能量大小。原本一维信号里纠缠不清的信息,到了时频图里就变成了二维的图像模式——故障引起的周期性冲击会在时频图上形成规律性的竖条纹或者能量带,CNN 最擅长的恰恰就是从这种二维纹理里找模式。
CNN 处理完时频图之后,输出的是特征图或者特征向量序列。这里有个关键点:CNN 的感受野是局部的,它能看到"某一小段时间内频率成分长什么样",但看不到"这个模式在时间轴上是怎么演化的"。故障信号往往有磨损加剧、冲击周期漂移这类渐变趋势,单靠 CNN 的特征堆叠是表达不出来的。LSTM 就是来接这一棒的,它把 CNN 输出的特征当作时间序列输入,通过门控机制学习特征之间的前后依赖关系,最终把"局部纹理"升级成"时序演变"层面的判别依据。
三者配合起来,逻辑上刚好形成闭环:STFT 解决信号表达问题,CNN 解决空间特征提取问题,LSTM 解决时序依赖建模问题。每一步都有明确分工,比盲目堆叠网络层要靠谱得多。
2. STFT 时频图生成:决定模型上限的第一步
很多人以为 STFT 就是个预处理工具,随便调调参数就行了。实际上这一步直接决定了 CNN 能看到什么信息,参数选得不好,后面网络设计得再精巧也白搭。我在这块踩过不少坑,下面把关键参数和选择逻辑摊开说。
2.1 窗函数、窗长与泄漏抑制
STFT 的核心公式是给信号加窗后做傅里叶变换,窗函数的选择直接影响频谱泄漏的程度。常用的窗函数有矩形窗、汉宁窗、汉明窗和布莱克曼窗。矩形窗的主瓣最窄,频率分辨能力最好,但旁瓣泄漏最严重;布莱克曼窗旁瓣衰减最大,但主瓣变宽,频率分辨率会下降。工程上做故障诊断,我一般默认用汉宁窗,它在主瓣宽度和旁瓣抑制之间折中得最好,尤其在处理轴承故障这种含有密集调制边带的信号时,汉宁窗能把边带模糊的程度控制在可接受范围内。
窗长(也就是 FFT 点数)的选择更关键。窗长决定了时间分辨率和频率分辨率的矛盾折中:窗越长,频率分辨率越高(频率轴更精细),但时间分辨率变差(时频图在时间方向上越来越模糊);窗越短则反过来。这里有一个硬约束,频率分辨率 Δf = fs/N,时间分辨率 Δt = N/fs,两者乘积近似等于 1,不可能同时无限提高,这是由不确定性原理决定的。
实际经验是:先看你的采样频率是多少。比如某试验台振动信号采样率是 12 kHz,故障特征频率集中在几百赫兹以内,又需要捕捉每次冲击发生的时刻,我通常取窗长 256 或 512 个采样点。12 kHz 下取 256 点,频率分辨率大概是 46.9 Hz,对几百赫兹的故障特征频率来说是够用的;时间分辨率是 21.3 ms,能区分开转速不太高时的连续冲击。如果你要分辨的特征频率间隔很窄,比如两个边频只差 5 Hz,那 256 点窗长做不到,得加到 2048 点甚至更长。总之,窗长的选择必须以目标频率间隔和冲击间隔两个约束为基准,而不是拍脑袋定。
2.2 重叠率对时频图平滑度的影响
STFT 还有一个容易被忽略的参数是帧与帧之间的重叠率。如果不做重叠,相邻帧之间会有明显的不连续感,CNN 拿到手的时频图纹理是断裂的,特征提取自然受影响。我一般设重叠率为 75%,也就是说窗每次只移动窗长的四分之一。这样做的好处是时频图的时间轴更平滑,能量带更连续;代价是计算量变大,数据维度变高。
从信息量角度看,重叠率 75% 相当于对每个时间点做了四次冗余观测,相当于对时频幅度做了一次平滑滤波,能有效抑制单帧噪声造成的伪影。我在对比实验里测过:相同网络结构下,重叠率从 50% 提高到 75%,测试准确率能提升一到两个百分点;再继续加到 90% 以上,收益就很有限了,反而让训练时间拉长不少。所以工程上取 75% 是一个性能和算力的合理平衡点。
2.3 Matlab 里生成时频图的两种做法
Matlab 里生成 STFT 时频图有两种常见方式。一种是调用spectrogram函数,直接得到短时傅里叶变换的复数矩阵,取幅度谱后转成灰度图或伪彩色图保存。示例代码如下:
fs = 12000; % 采样率 window = hann(256); % 窗函数 noverlap = round(0.75 * 256); % 重叠率75% nfft = 256; % FFT点数 [S, f, t] = spectrogram(signal, window, noverlap, nfft, fs); spec_img = abs(S); % 幅度谱 % 归一化到0-1范围,便于后续CNN处理 spec_img = (spec_img - min(spec_img(:))) / (max(spec_img(:)) - min(spec_img(:)));另一种是直接用stft函数,它返回一个stft对象,可视化更灵活。但我个人更推荐spectrogram,因为它直接给出频谱矩阵、频率轴和时间轴三个数组,后续做矩阵归一化、切片、批量存储都方便。另外有个小技巧:如果样本数量大,不要每张图都实时算,可以一次性把所有样本的时频图批量生成,保存成.mat文件或者Tall数组,训练阶段直接读入,能省掉大量重复计算时间。
3. CNN 特征提取层的设计与 Matlab 实现要点
时频图生成好了,下一步就是用 CNN 把图里的纹理模式变成高维特征。很多人以为 CNN 部分就是拿来一个预训练网络改改输出层,实际做故障诊断时这招并不好用。预训练网络(比如 VGG、ResNet)是在 ImageNet 这种自然图像上训练的,输入尺寸、颜色分布、纹理统计特征都跟时频图有巨大差异,直接用微调效果很不稳定。我的做法是自己搭一个规模适中、专门针对时频图特点的轻量 CNN。
3.1 输入尺寸与数据归一化
CNN 的输入层尺寸需要跟前处理保持一致。我一般把每张时频图统一调整为 64×64 或者 128×128 的单通道灰度图。为什么不用三通道?因为时频图只有一个幅度维度,强行复制成三通道只会增加无意义的计算量,没有任何信息增益。尺寸选择上,64×64 计算量小,训练快,但会损失一部分细节;128×128 保留的纹理信息更丰富,准确率更好,但显存占用和训练时间明显增加。我自己常用的折中是 96×96,细节和速度兼顾。
归一化这一步绝不能省。振动信号经过 STFT 之后,幅度谱动态范围很大,如果不归一化直接进网络,第一层卷积的权重更新会被大数值样本主导,训练很难收敛。我采用的做法是逐样本最大最小值归一化,把每张图的幅度值压到 [0,1] 区间,这样既能保留相对强弱关系,又避免了大数值绝对幅度带来的尺度偏差。批量归一化层(BN)放在卷积层后面也很有用,它能让中间特征图保持稳定的分布,加速收敛的同时还能起到轻微的正则化作用。
3.2 卷积核尺寸与层数选择的工程判断
时频图里的故障特征有个特点:局部呈现纹理状(比如某个频带上的能量条带),整体呈现周期性重复结构。针对这个特点,我在前几层卷积里主要用小尺寸卷积核,最常用的是 3×3,偶尔叠加一层 5×5。3×3 卷积核的感受野更精细,参数少,堆叠两层之后能覆盖 5×5 的区域,表达复杂度反而比直接用 5×5 更高。大卷积核(7×7 及以上)在时频图上容易把频率轴方向的细节抹掉,我基本不用在第一层。
网络深度方面,我的经验是不要一上来就堆 20 层。时频图的信息密度没有自然图像那么高,故障诊断的分类任务通常只有四到十类,太深的网络容易过拟合到训练集的噪声上。我常用的骨干结构是四到五组卷积块,每组包含:卷积层 + BN + ReLU + 最大池化。第一组用 16 个滤波器,后面逐组翻倍到 32、64、128。这样设计的好处是,浅层捕捉频率纹理,中层组合出频带模式,深层提炼出故障类型的全局判别特征,参数量也不至于失控。
3.3 用 Deep Learning Toolbox 搭建网络的实操写法
Matlab 的 Deep Learning Toolbox 提供了convolution2dLayer、maxPooling2dLayer、batchNormalizationLayer这些层对象,搭网络像拼积木一样直观。下面给出一段核心代码,展示如何构建 CNN 部分:
layers = [ imageInputLayer([96 96 1], 'Normalization', 'none') convolution2dLayer(3, 16, 'Padding', 'same') batchNormalizationLayer() reluLayer() maxPooling2dLayer(2, 'Stride', 2) convolution2dLayer(3, 32, 'Padding', 'same') batchNormalizationLayer() reluLayer() maxPooling2dLayer(2, 'Stride', 2) convolution2dLayer(3, 64, 'Padding', 'same') batchNormalizationLayer() reluLayer() maxPooling2dLayer(2, 'Stride', 2) convolution2dLayer(3, 128, 'Padding', 'same') batchNormalizationLayer() reluLayer() ];这里有三个细节要提醒。第一,imageInputLayer里的Normalization我直接设成'none',因为已经在预处理阶段做过了归一化,避免重复计算。第二,卷积层的Padding用'same'保证特征图尺寸不缩减,池化层才负责降采样,这种设计能让每一层的梯度流动更顺畅。第三,最后一个卷积块没有接池化,是为了保留足够多的空间位置信息,方便后面把特征图转化为 LSTM 需要的时间步序列。
4. LSTM 时序建模:为什么 CNN 之后还要加 LSTM
很多初学者走到这一步会有疑问:CNN 提取出来的特征图不是已经挺好了吗,为什么不展平之后直接接全连接层分类,还要插一个 LSTM?我一开始也是这么干的,后来对比实验做多了才发现,对于转速波动、负载变化这类工况,直接展平会丢掉特征图的空间顺序关系,而 LSTM 恰好能把这些顺序关系当作时间序列来学习。
4.1 从特征图到时间步序列的转化思路
CNN 输出的特征图尺寸是 [H, W, C],其中 H、W 是空间维度,C 是通道数。要把这个三维数据变成 LSTM 的输入,关键在于确定"时间步"对应哪个维度。我采用的方案是把特征图按时间轴方向切成若干条,也就是把 W 维的每一列当作一个时间步,每一列包含 H×C 个特征值。直观地理解:时频图横轴是时间,CNN 垂直方向的卷积核把同一时刻下各频率带的纹理信息综合成一个特征向量,LSTM 再按顺序读这些特征向量,等于在时间上对特征序列做了一次建模。
要注意,这种转化方式要求 CNN 部分不能把时间方向的尺寸压得太小。如果池化次数过多,W 维度只剩下四五个时间步,LSTM 基本就学不到什么时序规律了。我一般在设计网络时特意保留 W 维度不小于 12 个时间步,池化也只沿着频率轴方向做,尽量不压缩时间轴。
Matlab 里这个转化过程用sequenceInputLayer接一个lstmLayer来实现。关键在于用reshape把特征图类的数据转换为序列格式。实操中我习惯在最后一个卷积层后面加一个自定义层或者用flattenLayer结合sequence处理,代码上最省事的做法是直接改变数据维度组织方式:
% 假设 cnnOutput 尺寸为 [H, W, C],即 [特征长度, 时间步, 通道数] % 转换为 LSTM 需要的序列数据:每个时间步是一个特征向量 sequenceData = permute(cnnOutput, [3, 2, 1]); % 变为 [C, W, H] % 再展平每个时间步的特征 sequenceData = reshape(sequenceData, [C * H, W]); % 每个时间步维度为 C*H这里permute和reshape的顺序很容易搞反,我踩过两次坑后总结出一个验证方法:先打印出size(sequenceData),确认第一维是特征长度,第二维是时间步数量,再去建 LSTM 层。如果顺序反了,训练时 Matlab 会直接报序列维度不匹配的错误,调试起来很头疼。
4.2 隐藏单元数与防止过拟合的手段
LSTM 层的隐藏单元数直接影响模型记忆能力。隐藏单元太少,学不到长周期依赖;太多,序列数据量不够时特别容易过拟合。我自己的调参经验是:先从 32 或 64 起步,观察训练集和验证集准确率的差值,如果训练集已经到 99% 但验证集只有 80%,说明是过拟合,可以适当减小隐藏单元数或者增加 Dropout;如果两者都偏低,说明模型容量不够,再往上加。
Matlab 里lstmLayer支持直接设置'Dropout'参数。我通常设置 0.3 到 0.5 之间,放在 LSTM 层之后。这里有个经验:Dropout 加在 LSTM 层内部和加在 LSTM 之后效果不一样。内部 dropout 主要抑制序列内部共适应,对时间依赖建模更有效;输出侧 dropout 则更像特征层面的正则化。我的做法是在 LSTM 层内设置Dropout=0.3,再接一个dropoutLayer(0.5),双管齐下。迭代几轮后如果验证集还在震荡,再调小 LSTM 层内部的比例。
我把核心的网络组合代码放在下面,大家可以直接参考结构:
% 在 CNN 特征图之后接 LSTM layers = [ % 前面是 CNN 各层... flattenLayer() % 可以按需要调整 lstmLayer(64, 'OutputMode', 'last', 'Dropout', 0.3) dropoutLayer(0.5) fullyConnectedLayer(numClasses) % numClasses 为故障类别数 softmaxLayer() classificationLayer() ];OutputMode选'last'还是'sequence'也值得注意。做最终分类时我取'last',只保留最后时间步的输出作为全局表征;如果你的任务需要对每个时间步都做分类(比如在线监测,想实时输出当前时刻的故障类型),那才需要'sequence'。两种模式训练速度差别不大,关键看任务输出形态。
5. 混合模型训练与超参调优的实操经验
网络结构搭好了,真正的坎儿在训练阶段。故障诊断数据集通常没有自然图像那么大,一个工况一个故障类别可能只有几百个样本,训练策略稍有不当,模型就会被某个高幅值的异常样本带偏。下面重点说几个我实测有效的训练细节和最常见的坑。
5.1 数据集划分与标签处理的常见坑
数据集划分上,我强烈建议按"工况单元"划分而不是按"样本"随机划分。什么意思呢?假设你的实验在三种转速下采集了数据,每种转速下每个故障类型有 400 个样本。如果随机打乱划分训练集和测试集,同一个工况下的数据会同时出现在两端,模型记住工况特征也能得分,测试准确率虚高。正确做法是把不同转速工况完全分开,比如用前两个转速的全部样本训练,第三个转速的样本做测试,这样才能验证模型的跨工况泛化能力。
标签处理这块,Matlab 的trainNetwork要求标签必须是categorical类型。很多人直接拿字符串数组当标签,一训练就报错。我在数据整理阶段就会把故障类型映射成类别数组,比如用categorical(labels)转换,同时保证类别顺序和张量顺序一一对应。还有个小细节:标签不要用数字 0、1、2 直接代替,否则训练时容易把类别间的数值大小关系混进去,导致模型学习到错误的偏序信息。
5.2 优化器、学习率与批大小之间的权衡
关于优化器,我最早用带动量的 SGD,后来对比发现 Adam 在这类中等规模数据集上收敛又快又稳。学习率我一般初始设 0.001,训练过程中用piecewise策略逐步衰减:每训练 15 轮左右乘以 0.5。如果是用 Matlab 的trainingOptions函数,可以这样配置:
options = trainingOptions('adam', ... 'InitialLearnRate', 0.001, ... 'MaxEpochs', 40, ... 'MiniBatchSize', 32, ... 'ValidationData', {valImgs, valLabels}, ... 'ValidationFrequency', 10, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.5, ... 'LearnRateDropPeriod', 15, ... 'Shuffle', 'every-epoch', ... 'Verbose', true, ... 'Plots', 'training-progress');批大小我通常选 32 或 64。样本量超过一万时用 64,几千样本时用 32。批大小太大容易收敛到尖锐极小值,泛化性差;太小则梯度噪声大,训练不稳定。还有一点容易被忽略:Shuffle一定要打开,否则每个 epoch 内的样本顺序固定,模型会被训练顺序带偏。
5.3 训练过程中最容易忽视的三个信号
第一个信号是损失曲线的震荡模式。如果训练损失一开始就狂降然后又突然飙升,大概率是学习率偏大,或者输入数据里有极端离群样本,需要先检查时频图的归一化是否出了问题。
第二个信号是验证集准确率长时间不动。这种情况通常不是网络问题,而是 CNN 提取的特征对当前故障类型区分度不够。我会观察测试样本中到底哪几类容易混淆,再回头调整 STFT 的参数——比如故障特征频率落在高频段,就把窗长调短;落在低频段,就加长窗。模型调不上去时先回退一步改前处理,而不是盲目加网络层,这个思维很重要。
第三个信号是训练集和验证集的准确率同步偏低。这说明模型的表达能力不足,优先检查网络宽度是否太窄——比如第一层卷积只有 8 个滤波器,那就太少了,特征根本提取不过来。我一般从 16 起步,如果效果不行先加倍,别急着加深网络。
6. 实验验证与评价:如何判断混合模型真的有效
模型训练完毕,最怕的就是自我感觉良好、实际部署就翻车。我习惯从三个层面做验证:指标层面看准确率和混淆矩阵,泛化层面看跨工况测试,方法层面看消融实验。
6.1 分类指标与混淆矩阵的使用技巧
故障诊断里光看准确率远远不够。如果某几类故障样本本身占比很大,模型把所有样本都分到大类也能拿到不错的准确率,但小类故障全部漏报,这在实际应用中是要出大问题的。我每次训练完都会强制打印混淆矩阵,逐行看每一类故障的召回率。如果发现某两类轴承故障总是互相混淆,我会回到时频图上对比这两类的纹理差异,看是不是窗长太短导致特征频率间隔没分开。
精确率、召回率、F1-score 这三个指标我全部会算一遍。分类任务类别不平衡时,F1-score 比准确率更能反映模型真实水平。这里有个小技巧:Matlab 里用confusionchart和classificationReport可以快速出图出表,测试阶段建议把每类样本数量控制在接近的水平,避免评价指标失真。
6.2 对比 STFT-CNN、CNN-LSTM 和完整模型的差异
为了说明混合模型确实比单一模型强,我做过一组消融实验,对比了三个变体:只用 STFT+CNN(把 CNN 特征展平后直接全连接分类)、只用 CNN+LSTM(输入还是原始一维波形)、以及完整的 STFT+CNN+LSTM。实验所用数据集来自某旋转机械故障模拟试验台,包含正常、内圈故障、外圈故障、滚动体故障四类状态。
结果非常有代表性:STFT+CNN 方案在平稳转速下准确率已经能达到 96% 以上,但一旦用另一个转速下的数据做测试,准确率掉到 82% 左右。CNN+LSTM 方案在时域信号上表现波动很大,准确率在 87% 上下徘徊。完整的 STFT+CNN+LSTM 模型在跨工况测试中稳定在 97% 以上。这个对比清楚说明了 STFT 和 LSTM 的价值所在:STFT 让特征表达更鲁棒,LSTM 让时间依赖被显式建模,二者叠加后的提升不是简单的算术相加,而是质的改善。
6.3 部署阶段需要注意的输入一致性
最后说一个很多人都栽过的坑:训练时用的预处理参数,部署时必须原封不动地重复。你训练时用了 256 点窗长、75% 重叠率、96×96 的输入尺寸,上线推理时如果采样频率变了、窗长换了、重叠率改了,时频图分布就变了,CNN 提取到的特征完全对不上,模型准确率断崖式下跌。我在实际部署中会专门写一个封装函数,把 STFT 参数和图像归一化逻辑做成固定配置,推理和训练走同一套代码路径,杜绝参数漂移。
另外,在线监测场景下还要考虑计算延迟。STFT 加 CNN 加 LSTM 的推理链路不长,用 GPU 加速时单个样本通常能在几十毫秒内完成判断,但 CPU 上跑就会慢不少。如果需要实时报警,建议把 STFT 频段先做个带通滤波,缩小输入尺寸,再把 LSTM 的隐藏单元数适当降低,换取更快的响应速度。准确性稍微让一点,换来现场可用性,这笔账在工程上是划算的。
整套流程跑下来,我最深的体会是:模型结构固然重要,但真正决定效果上限的往往是前处理参数和训练细节。STFT 的窗长、重叠率,CNN 的卷积核尺寸,LSTM 的 hidden size,这些看起来不起眼的数字背后都对应着信号本身的物理特征。你要是刚接触这个方向,建议先按我给的参数跑通一版完整流程,拿到基线结果后再一项一项地调。每改一个参数就记一次实验日志,比凭感觉堆网络要高效得多。后面如果想把这套模型用到更复杂的多传感器融合场景,可以在 STFT 阶段把多个通道的时频图叠加成多通道输入,CNN 的第一层就天然支持多通道卷积,扩展起来也顺理成章。