STFT-CNN-LSTM故障诊断模型:时频图与深度学习全流程解析

📅 发布时间:2026/10/20 15:14:05
STFT-CNN-LSTM故障诊断模型:时频图与深度学习全流程解析
1. 内容整体设计与思路拆解做故障诊断的朋友应该都有体会振动信号、电流信号这类时序数据最难的地方不在“有没有故障”而在“故障长什么样、从哪儿开始异常”。同样的轴承、齿轮箱、电机不同工况下的信号形态千差万别靠人工看波形、看频谱效率低不说经验稍有不足就容易漏判。所以这几年基于深度学习的故障诊断方案越来越受关注而STFT-CNN-LSTM这套组合则是把信号处理、空间特征提取和时序建模三件事串起来形成一条完整的处理链路。先说清楚这个模型到底在做什么。它的思路非常直接用**短时傅里叶变换STFT**把一维的时序信号转成二维时频图用**卷积神经网络CNN**从时频图里提取局部特征说白了就是让网络学会“看”频谱图上哪个频率带、哪个时间段出现了异常形态把CNN提取到的特征序列丢给长短期记忆网络LSTM让模型记住这些特征在时间轴上的演化规律。为什么要这么做因为这三者单独拎出来都各有短板。STFT能给出时频联合分布但输出的二维矩阵直接送进全连接网络参数量巨大而且缺乏对局部模式的学习能力CNN擅长提特征但本身对时序依赖的建模能力一般卷积核看到的只是局部窗口LSTM擅长捕捉长期依赖但输入如果是原始信号它很难主动找到“故障特征”到底藏在哪个频率段。三合一之后STFT负责把原始信号变成模型更容易“理解”的二维表征CNN负责在这些表征里挖关键特征LSTM再进一步把这些特征按时间顺序串起来捕捉故障演化趋势。这个设计逻辑和人类专家看图诊断的过程非常接近先看时频图上哪块颜色异常再看异常区域随时间怎么变化最后判断属于什么故障类型。这套方案的价值主要体现在两个层面。一是诊断精度混合模型对非平稳信号的适应性明显优于单一模型特别是负载波动、转速变化场景下STFT的时间窗能保留信号的局部平稳性CNN的平移不变性又能容忍一定的时频位置偏移。二是工程可落地性整套流程在Matlab里可以一条龙跑通从信号读取、STFT变换、网络搭建到训练评估不需要切换语言环境这对习惯了Matlab做信号处理的老工程师来说非常友好。适合谁来参考信号处理方向的在读学生、设备运维工程师、算法岗转行做工业AI的朋友都可以把这套方案作为baseline或者项目原型。它不是什么新潮到没边的方法但胜在结构清晰、容易上手、可解释性也不错用来做故障诊断入门或者工程改造都很合适。2. 核心细节解析与实操要点2.1 STFT的参数选择STFT的本质是给信号加窗然后在窗内做傅里叶变换。参数就两个核心窗函数和窗长以及对应的重叠率。很多人第一次跑STFT都是Matlab里直接spectrogram一把梭默认参数全接受但这对故障诊断来说往往不够。先说窗函数。矩形窗频率分辨率高但旁瓣泄漏严重Hamming窗和Hann窗是折中选择工程上用得最多。Hann窗主瓣稍宽但旁瓣衰减快能有效减少频率泄漏导致的“假峰”Hamming窗在同样的窗长下主瓣更窄、频率分辨略好但旁瓣衰减不如Hann。实际做故障诊断时如果信号里包含明显的周期冲击成分比如轴承外圈故障的特征频率Hann窗的泄漏抑制效果会更稳。如果更关心频率定位的精度可以尝试Hamming。我这里默认推荐Hann因为故障诊断面对的信号大多是强噪声背景下的微弱冲击成分泄漏带来的干扰往往比主瓣变宽更致命。再说窗长。这个直接决定时频图的分辨率布局。窗长越长频率分辨率越高但时间分辨率越差对瞬态冲击的定位能力下降窗长越短则反过来。一个常见的选择是让窗长覆盖几十个采样点保证每个窗内至少有2-3个完整周期的特征频率成分。比如采样率是12kHz轴承故障特征频率在100-200Hz区间那么一个窗至少要包含0.01秒以上的信号对应窗长至少120点实际可以取256或512既保证频域分辨率又不会让时间轴过于粗糙。重叠率一般设置在50%-75%之间重叠太少时频图会有明显的块状感重叠太多计算量上升但收益递减。提示Matlab的spectrogram函数里返回值s就是复数谱矩阵实际输入CNN之前要取幅值谱并考虑是否做对数压缩。直接丢复数或者线性幅值进去网络收敛速度都会受影响。2.2 CNN结构怎么设计才合理CNN是这套模型的特征提取核心但并不意味着越深越好。故障诊断的输入是时频图跟ImageNet那种自然图像有本质区别时频图纹理结构相对简单、类间差异集中在特定频段堆太多卷积层反而容易过拟合训练时间也成倍增加。我建议用三层卷积的结构起步第一层卷积核大一些比如7×7用来捕捉频谱图中的宽频带整体形态第二层、第三层用3×3小卷积核叠加增加非线性表达能力的同时减少参数量。每一层后面接BatchNorm和ReLU池化用最大池化。关键点是最后不要急着接全连接层而是把CNN输出的特征图按时间维度展平成序列交给LSTM。这个衔接细节很多人第一次做容易出错如果直接把CNN的输出压成一维向量丢给LSTM时间顺序信息就丢了LSTM就失去了意义。正确姿势是保留时间步维度让LSTM沿着时间轴扫描CNN逐段输出的特征。通道数的选择也有讲究。第一层输出特征图数量建议从16或32起步后续逐层翻倍。数据集规模小就少一些防止过拟合数据集大、样本充足就适当增加通道数。训练时配合早停机制用验证集损失监控别等训练完再看结果。2.3 LSTM层的时序建模LSTM在这里解决的是一个核心诉求故障特征在时间轴上的演化是连续的不应孤立的判断每一个时间段。比如齿轮磨损渐进式恶化频谱图上某些边频带的幅值会随时间缓慢上升CNN只能看到某个时间窗内有没有异常但判断不了这个异常是瞬时冲击还是持续恶化这就是LSTM的用武之地。LSTM层的设计参数主要是隐藏单元数和层数。隐藏单元数一般取64-128故障类别少、信号长度短就取下限。层数的话实测下来2层LSTM的收益最明显——第一层捕捉短时局部的时序模式第二层在更高抽象层级上捕捉依赖关系。超过两层收益很小训练难度倒是直线上升容易梯度消失而且可解释性变差。最后一层LSTM的输出只需要取最后一个时间步的状态接上全连接层和Softmax分类即可。还有一点容易忽略输入到LSTM之前要不要做序列长度截断。时频图的段数可能上百全部序列输入LSTM会导致训练极慢而且过长序列中早期信息很容易被遗忘。实际操作中可以把时间轴做下采样或切段比如每段处理10-20个时间步。这个数字取决于信号总长度和STFT的hop size需要根据你自己的数据规模实际调试。3. 实操过程与核心环节实现3.1 数据准备与预处理先把数据准备好。假设是轴承故障数据每种故障类别正常、内圈故障、外圈故障、滚动体故障各有若干段原始振动信号。每段信号建议长度在1-2秒采样率12kHz时就是12000-24000个采样点。这是故障诊断实验里很常用的数据规模既有足够的频谱分辨率也不会让STFT计算量失控。数据划分上按照每个类别独立划分训练集和测试集避免同一条连续信号被切开后一部分进训练集、一部分进测试集——这样会造成数据泄漏测试集准确率虚高。建议按照“不同工况、不同样本段”来划分保证测试集对模型来说真正是“没见过的数据”。预处理两步走第一步是去均值和归一化。振动传感器输出的信号通常有直流偏置直接去做STFT会在0Hz附近产生能量聚集浪费时间窗去学习无关的基线成分。去均值之后再做幅值归一化把信号缩放到[-1,1]区间避免不同样本量纲不一致影响后续特征提取。统一归一化方式用全局最大绝对值归一化不要用每个样本归一化到[-1,1]否则会破坏样本间的能量对比信息——能量大小本身也是一种故障特征。第二步是STFT变换。用Matlab代码实现如下% 参数设置 fs 12000; % 采样频率 windowLen 256; % 窗长 overlap 128; % 重叠点数即50%重叠 nfft 512; % FFT点数 % 对单段信号做STFT [s, f, t] spectrogram(signal, hann(windowLen), overlap, nfft, fs); % 取幅值谱并做对数压缩 spec abs(s); spec_log 10 * log10(spec eps); % 缩放到[0,1]区间 spec_norm (spec_log - min(spec_log(:))) / (max(spec_log(:)) - min(spec_log(:) eps);这里几个参数值得展开说。nfft取512大于窗长的256相当于做了零填充让频谱更细腻。谱图矩阵的尺寸是257×NN是时间帧数。要让每个样本的时频图尺寸一致否则CNN无法处理变长输入。这就要在信号段截取时统一长度或者在STFT之后对时间轴做插值/裁剪。推荐前者从源头统一信号长度。对数压缩这一步是必须做的。原始幅值谱的动态范围非常大强故障特征和背景噪声之间可能有几十甚至上百倍的能量差。线性幅值直接送网络梯度的比例会被极大特征主导弱特征学不到。log10之后动态范围被压缩网络更容易学到均衡的表征。3.2 网络结构搭建与参数配置下面直接给出一份完整的模型定义可以直接在Matlab中跑通% 输入层时频图尺寸由spectrogram输出决定 inputLayer imageInputLayer([257, 128, 1], Name, input, Normalization, none); % 第一层卷积大卷积核捕捉宽频形态 conv1 convolution2dLayer([7, 7], 16, Padding, same, Name, conv1); bn1 batchNormalizationLayer(Name, bn1); relu1 reluLayer(Name, relu1); pool1 maxPooling2dLayer([2, 2], Stride, [2, 2], Name, pool1); % 第二层卷积小卷积核细化局部特征 conv2 convolution2dLayer([3, 3], 32, Padding, same, Name, conv2); bn2 batchNormalizationLayer(Name, bn2); relu2 reluLayer(Name, relu2); pool2 maxPooling2dLayer([2, 2], Stride, [2, 2], Name, pool2); % 第三层卷积进一步抽象 conv3 convolution2dLayer([3, 3], 64, Padding, same, Name, conv3); bn3 batchNormalizationLayer(Name, bn3); relu3 reluLayer(Name, relu3); % 展平并重构序列 flatten flattenLayer(Name, flatten); % 关键重构为序列保留时间步维度 % 注意这里需要根据特征图尺寸计算seqLength sequenceLayer sequenceInputLayer(64, Name, seq_in); % LSTM层 lstm1 lstmLayer(128, OutputMode, sequence, Name, lstm1); lstm2 lstmLayer(64, OutputMode, last, Name, lstm2); % 输出层 fc fullyConnectedLayer(numClasses, Name, fc); softmax softmaxLayer(Name, softmax); classLayer classificationLayer(Name, output);这里有一个关键工程细节从CNN到LSTM的序列重构。卷积层输出的特征图是[H, W, C]三维数组W对应的是时间维。要想保留时间顺序就要把特征图按W方向切片每个时间步的向量是[H × C]展平后的结果。这在Matlab里可以通过sequenceInputLayer配合自定义循环来实现或者用convolution2dLayer之后接permute操作把维度重排。最简单的做法是用sequenceFoldingLayer和sequenceUnfoldingLayer配合或者直接采用dlarray高级接口来做。下面给一个直接用dlnetworkdlarray数据的快速示例% 将时频图数据转为自定义形式 % X: [H, W, C, N]其中W是时间帧数 % 对每一批数据按时间维切分 for t 1:size(X, 2) X_seq{t} X(:, t, :, :); % 每个时间步 end实际项目中我用的是更工程化的方案在CNN层之间加入sequenceFoldingLayer来压缩输入等CNN跑完再用sequenceUnfoldingLayer恢复。这个办法稍绕但网络定义能保留trainNetwork的简洁性。如果你不想纠缠这些还有一个更省事的时序建模替代方案把CNN输出的特征图做全局平均池化得到固定长度向量然后用LSTM处理这个向量序列。通过时间滑窗生成多个子样本每个子样本得到一组特征向量按窗口顺序排列成序列。虽然不如端到端正统但在快速验证方案时非常有效。3.3 训练策略与超参数调优网络搭好以后训练策略直接决定模型效果。多轮调参踩坑下来几个关键经验优化器选择Adam是首选。故障诊断的任务通常数据量不大Adam的自适应学习率机制能更快收敛而且对初值不敏感。SGD理论上泛化能力更好但需要精细调整学习率和动量调试成本高。如果数据量超过几万样本、训练时间充裕可以换SGDM试试对比。初始学习率一般从0.001起步MiniBatchSize设为64或128MaxEpochs设为50-100并配合早停。学习率太大容易震荡太小收敛慢容易陷入局部最优。0.001对大部分情况都不错如果loss下降缓慢可以微调到0.003但最好还是用cosine衰减或piecewise计划来调度。早停机制很多人把MaxEpochs设大就不管了完全靠训练到结束。实际上用验证集loss做早停能省下大把时间还能防止过拟合。options trainingOptions(adam, ... InitialLearnRate, 0.001, ... MaxEpochs, 80, ... MiniBatchSize, 64, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 10, ... Plots, training-progress, ... Verbose, true);样本不均衡问题故障诊断数据里正常样本往往远多于故障样本模型很容易偏向多数类。简单有效的办法是加权交叉熵——在classificationLayer里设置Classes对应的权重让少数类的错分代价更高。另一个办法就是数据增强给原始信号加噪声、时域平移、小幅变调生成更多故障样本。但要注意增强后的样本要合理不能制造与物理规律矛盾的样本。3.4 完整训练流程与评估整个流程串起来大概是这样的加载全部信号统一长度如取12000点去均值、归一化每段信号做STFT得到257×N的时频图统一N到固定帧数整体作为4D数组[257, N, 1, 样本数]按类别分层划分训练集/验证集/测试集比例6:2:2搭建CNN-LSTM网络结构设置训练参数用trainNetwork开始训练测试集评估准确率、混淆矩阵、各类别的precision/recall/F1可视化分析训练曲线、混淆矩阵、个别样本的时频图和预测结果对照。评估指标里我特别推荐同时看混淆矩阵和t-SNE特征可视化。混淆矩阵能告诉你模型具体错在哪几类之间比如内圈故障和外圈故障经常混淆这说明时频图里这两类特征的区分度本来就不高可能需要调整STFT参数或增加特征。t-SNE则可以把CNN-LSTM提取到的高维特征投影到二维平面直观看到特征聚类的效果。4. 常见问题与排查技巧实录4.1 时频图尺寸不一致导致无法训练这是最常碰到的问题。STFT的时间帧数取决于信号长度、窗长和重叠点数一旦信号长度不统一时频图尺寸就不一致CNN要求固定输入尺寸直接报错。排查思路先打印所有样本的STFT输出尺寸看看是哪几个样本出了偏差。通常有两种原因一是原始信号长度本身不一致二是不同信号段的截断位置不同导致窗口总数变化。解决的办法要么在信号层面统一截取长度要么在STFT之后把时间帧数统一到固定值。统一时间帧可以插值或者切片但插值会引入一定的人为畸变尽量从源头统一信号长度更稳妥。4.2 训练loss不下降或NaN出现NaN先检查输入数据有没有NaN或Inf。STFT取对数时谱值不能为零加eps就是干这个的。还有归一化时如果分母是零也会产生NaN。排查方法训练前对输入数据做一次any(isnan(X(:)))检查。loss不下降则要分几种情况看如果loss从初始就一直很大且不降大概率是学习率太大或者网络初始化有问题如果loss缓慢下降但验证集准确率不动可能是特征提取层没有学到有效模式试着换更大的卷积核或增多卷积层通道数还有一种可能是数据本身太简单或太难样本类别完全不可分需要回到STFT参数、频谱图可视化检查特征是否存在。4.3 过拟合明显训练集准确率接近100%测试集却明显偏低。这在数据量不大时很常见。应对手段优先级排序第一优先早停这是最省力的第二优先Dropout层在LSTM输出后接一个dropoutLayer(0.5)很有效第三优先减少CNN通道数或LSTM隐藏单元数简化模型容量第四优先数据增强给原始信号加信噪比在10-20dB之间的高斯白噪声。这个方法我在实际项目里效果很显著模型对噪声的鲁棒性也提升了。4.4 CNN到LSTM的维度对接问题在Matlab里把CNN特征图转成LSTM序列输入是新手最容易卡壳的地方。常见的报错是维度不匹配或者sequenceInputLayer期望的输入格式不对。我建议的做法是不要试图在trainNetwork里把convolution2dLayer直接接LSTM这对维度管理很不友好。换成自定义训练循环或使用sequenceFoldingLayer。用sequenceFoldingLayer的好处是可以让CNN部分以普通二维图像方式处理LSTM部分以序列方式处理两者之间的维度变换由折叠/展开层自动管理。下面给个简化的结构图意会一下layers [ sequenceInputLayer(1, Name, sequence_input) sequenceFoldingLayer(Name, fold) convolution2dLayer([7, 7], 16, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer([2, 2], Stride, [2, 2]) % ... 更多卷积层 sequenceUnfoldingLayer(Name, unfold) flattenLayer lstmLayer(128, OutputMode, last) fullyConnectedLayer(numClasses) softmaxLayer classificationLayer ];需要注意sequenceUnfoldingLayer要和sequenceFoldingLayer配对否则会报错。这个用法稍微绕一点但比手动维度变换省心得多。4.5 模型的泛化能力测试在实验数据上表现好不代表在工程现场就能用。现场信号和实验数据的差异现场工况负载随机波动、转速不稳、背景噪声强、可能存在多种故障叠加。建议训练好后做一次简单的泛化测试换一批不同转速、不同负载下的数据跑一遍。如果精度掉了先看是STFT参数不匹配比如转速变了导致特征频率区间漂移还是CNN学到了实验台的特定噪声模式。针对前者可以增大STFT窗长覆盖更宽的频率范围针对后者在有足够数据的情况下应该在训练集里就混入多工况样本。5. 效果评估与对比分析模型训练完成后要做对比实验证明这个混合模型比单一模型有提升。别嫌麻烦这个对比写进文档里才能让方案站得住。通常做这几种对比CNN单模型不用LSTM直接在CNN后接全连接分类LSTM单模型不经过STFT和CNN直接把原始信号或手工特征序列送给LSTMSTFT-CNNSTFTCNN没有LSTM时序建模STFT-CNN-LSTM完整方案。记录每个模型在测试集上的准确率、F1值、训练时间。这里提醒一句训练时间对比要在同一硬件环境下做。LSTM层训练是真的慢比CNN慢一个数量级是正常的所以在效果相当的时候可以交代一句“STFT-CNN-LSTM在训练耗时上代价更高但在诊断精度上带来了显著提升”。一个很典型的对比结果结构是这样CNN单模型在稳态工况下准确率可能已经不错比如93%换成STFT-CNN综合性能更好一些加上LSTM之后在变工况或早期故障弱信号上的准确率提升最明显——因为LSTM能利用时间维度信息把“瞬时特征不清晰但持续演化”的弱故障识别出来。评估可视化部分我强烈建议除了混淆矩阵再加两条曲线PR曲线故障类别通常存在样本不均衡PR曲线比ROC更能反映少数类性能特征t-SNE可视化把测试集样本输入模型提取LSTM最后一个时间步输出的特征向量用t-SNE降维画点。效果好的模型同一类故障的特征点明显聚团不同类别之间有清晰的边界。这一步能非常直观地说服审稿人或领导比任何指标都有说服力。6. 模型部署与工程化建议能跑到96%准确率的模型那只是第一步。真正现场落地还有几个绕不开的坎。模型导出Matlab训练好的模型可以用exportONNXNetwork或codegen生成C代码部署。如果你的现场是PLC或嵌入式设备通常需要转成C/C库。codegen可以把神经网络推断编译成独立可执行程序部署到工控机上不依赖Matlab运行时环境。这一步在工程上很实用。ONNX导出则适合要切到Python推理框架比如ONNX Runtime的场景。推理延迟现场诊断对于实时性要求高。STFTCNNLSTM整条链路对算力有一定要求。实测下来信号长度12000点、STFT窗长256、CNN三层、LSTM128单元单条样本推理时间在CPU上大概几十毫秒显卡上更快。如果你的采样率翻倍比如到25.6kHz推理时间会相应增加先测一下硬件实跑耗时。巡检策略模型预测输出是一个类别概率分布不要只看argmax就下结论。建议设置一个置信度阈值比如最大概率低于0.7就判为“不确定”状态交给人工复核。这是工业现场最重要的安全机制。同时还可以引入滑动窗口连续N个窗口都判为同一故障才触发报警避免单次误判导致停机。增量学习故障类型不会一成不变。新的故障数据积累到一定量后建议用增量学习方式更新模型而不是完全重训。Matlab中可以先加载旧模型用新数据做fine-tune把学习率调低到0.0001新数据为主、旧数据掺一部分防止遗忘。这样模型越用越准维护成本也比全部重训低得多。现场采集的数据质量永远是最大的变量。传感器松动、线缆老化、采样率设置错误这些工程问题比算法问题更常见。模型做得再好数据采集端不靠谱一切都是空中楼阁。7. 写在最后的经验之谈这个STFT-CNN-LSTM模型我在几个项目里都跑过说句实在话它不是一个会让你眼前一亮的“黑科技”而是一个把信号处理、特征提取、时序建模老老实实串起来解决问题的方案组合。如果你现在手头有故障诊断的数据和场景我的建议是不要急着换更花哨的模型——Transformer、图神经网络、自监督学习等新方法在故障诊断领域确实有进展但它们的算力需求和调试难度都更高。先把STFT-CNN-LSTM这个baseline跑通拿到一个稳定的精度基准再决定要不要在此基础上做改进。我自己就是这么过来的先跑CNN再给CNN加多尺度卷积核后来把LSTM接上去解决时序依赖一步步把准确率从94%推到了97%以上。另外这套方法的表达范式不仅限于故障诊断。电机电流信号分析、结构健康监测、语音情感识别、甚至脑电信号分类本质上都是“时序信号时频表征空间特征时间演化”的结构换汤不换药。你在Matlab里调通这套流程以后遇到任何时序分类任务至少有一把顺手可用的钥匙。最后分享一个实用的小细节训练的时候多保存几个检查点checkpoint而不是只留最后一个epoch的模型。我遇到过训练后期验证集准确率已经到顶但最后一个epoch因为学习率调整问题反而略有回落的场景。保留最优模型而不是最新模型这个习惯关键时刻能救命。