前几天一个师弟抱着一堆轴承振动数据来找我,说他用同一份公开数据复现了好几篇论文里的网络结构,测试集准确率始终上不去。我看了看他的训练曲线,第一反应是:“你直接把原始波形塞进卷积网络了?”他说是啊,大家不都这么干吗。这正是当前故障诊断方向最常见的误区之一——网络结构一卷再卷,反而把振动信号本身的表达方式忽略了。
做旋转机械故障诊断的人都知道,故障信息藏在振动信号的幅值、频率和相位变化里,但“藏着”这个词有时候挺害人的。你明明知道故障就在那,模型就是学不出来。WTMGRU诊断网络这个名字听起来复杂,拆开看其实就两件事:先用WMSST(加权同步压缩小波变换)把原始振动信号转成高分辨率时频图,再用MCNN-GRU(多尺度卷积神经网络 + 门控循环网络)对时频图做特征提取和时序建模。我最近用MATLAB把这条链路完整跑了一遍,也踩了不少坑,这篇文章就把从原理、结构到代码和对比实验的过程都讲清楚,适合正在做故障诊断方向、或者想把深度学习方法真正跑起来的研究生和工程师参考。
1. 为什么先处理成时频图,而不是让网络直接吃原始信号
1.1 原始振动信号里有什么,网络为什么学不动
轴承或者齿轮箱的振动信号,本质上是若干成分的叠加:转频及其谐波、齿轮啮合频率、轴承通过频率,还有故障点产生的周期性冲击。冲击分量最特殊,它在频域上表现为跨越很宽频带的能量,在时间上又是短促的、非平稳的。正常的平稳振动信号可以用几个正弦分量近似描述,加了故障之后,信号里会出现周期性的瞬态冲击,而且故障越早期,冲击能量越弱,常常淹没在背景噪声和基频成分里。
这个问题直接丢给一维卷积网络,不是不能学,但代价很高。网络要从头开始学会“哪些频率特征重要、哪些是噪声”,这需要大量样本和较深的网络结构去堆容量。而我们做设备诊断的人,手里的故障数据往往比图像分类数据集小几个数量级,某个工况下的故障样本可能只有几千甚至几百个。用这种数据量去训练一个从零学频谱结构的深度网络,很容易过拟合,测试集换个工况准确率就崩。
时频分析在这里的真正价值,是把人类已经知道的物理先验“塞”进输入里。短时傅里叶变换、小波变换这类方法把一维信号展开成频率随时间变化的二维图,网络不需要再自己摸索“信号里有没有冲击、冲击发生在哪个频带”,它只需要在这个更干净的表示上做模式识别。形象一点说,原始振动信号像一段多人混在一起的录音,时频图等于把每个人的声音单独标注了角色和字幕,后面的模型当然省力得多。
1.2 WMSST相比STFT和经典SST强在哪
短时傅里叶变换(STFT)实现简单、速度快,但有个先天问题:窗函数长度一旦确定,时间分辨率和频率分辨率就不能同时提高。窗短了时间定位准,但频率糊成一片;窗长了频率清楚,但时间上多条瞬时频率成分相互叠加。一个包含多种转频和冲击的振动信号,用STFT画出来的图常常是“有轮廓但不够锐利”的。
连续小波变换(CWT)在高频处时间分辨率好、低频处频率分辨率好,比STFT更适合非平稳信号,但小波系数本身也存在能量扩散,瞬时频率路径周围的系数会拖出一片“裙边”,只是扩散程度比STFT轻。同步压缩变换(SST)在此基础上做了一步很巧妙的事:它不直接使用小波系数的幅值,而是先估计每个时频点的瞬时频率,再把同一频率邻域内分散的能量重新分配、压缩到瞬时频率脊线上。相当于把一幅模糊照片里散开的灯光重新聚集回灯芯,时频图一下子锐利起来。
标题里的WMSST,我按常见思路理解为在同步压缩基础上加入加权策略的改进版本。因为SST对噪声成分也会压缩,某些情况下可能产生伪脊线。通过一个依赖瞬时频率和系数幅值的权重函数,增强瞬态冲击对应的系数,同时压低平稳基频和噪声背景,这样才能让后续的网络重点学习故障冲击区域的特征。拿轴承故障诊断来说,故障冲击在时频图上表现为一串周期性的垂直亮线,WMSST处理后这些亮线更集中、和背景的对比度也更大,CNN能更容易地提取到判别性特征。
| 方法 | 时频聚集性 | 抗噪能力 | 计算开销 | 适用场景 |
|---|---|---|---|---|
| STFT | 一般 | 一般 | 低 | 平稳信号、快速预览 |
| CWT | 较好 | 中等 | 中 | 非平稳信号通用分析 |
| SST | 高 | 较好 | 中高 | 瞬时频率变化明显的信号 |
| WMSST | 高且针对性增强 | 更好 | 中高 | 含瞬态冲击的故障诊断 |
1.3 用MATLAB把振动信号变成WMSST时频图
这个步骤听起来高端,实际操作比想象中简单。我用的流程是:读取一段振动信号,先做必要的降采样和去均值,然后把信号按后面要讲的样本长度切成片段,逐段生成时频图,调整成统一样寸和灰度,保存到本地。MATLAB的Wavelet Toolbox里有同步压缩小波变换函数wsst,我把它作为基线,再做加权处理得到WMSST方案。下面是一段简化示意代码:
% 生成WMSST时频图的简化示意 fs = 12000; % 采样率,单位Hz x = signal_segment; % 某一段振动信号,长度N x = x - mean(x); % 去直流分量 % 基线:同步压缩小波变换 [sst, fr] = wsst(x, fs); % sst为系数矩阵,fr为频率向量 % 加权处理:对瞬时频率脊线附近的冲击成分做增强 sst_w = sst .* weight_matrix; S = abs(sst_w); % 取幅值 S = imresize(S, [224 224]); % 统一尺寸,方便网络输入 S = mat2gray(S); % 归一化到[0,1]这里weight_matrix是关键,它不是一个固定矩阵,而是根据每个时间点的局部能量峰值位置生成的自适应权重。讲原理可能有点抽象,但做实验时可以先不管多精细的加权公式:先跑一个固定比例增强,把系数幅值高于某个阈值的时频点保留,低干阈值的衰减,再观察训练集和验证集的收敛情况,逐步调权重参数。对大多数故障数据来说,这个“先粗后细”的策略比一开始追求复杂的公式更实用。
生成时频图时有一点要注意:分辨率不是越高越好。2048个采样点生成一张4096×4096的时频图既浪费计算,又没必要,网络最后还是要缩成统一的输入尺寸。我一般把频率轴限制在0到3000Hz范围内,因为滚动轴承故障特征频率和几个主要共振频带都在这个区间里,低频背景和高频噪声区域反而干扰分类。
2. MCNN-GRU网络结构到底在做什么
2.1 “多尺度卷积”怎么理解
卷积神经网络里的卷积核尺寸,决定了每次观察输入的局部范围。3×3的卷积核看到的是时频图上很小的邻域,适合提取精细纹理;7×7甚至更大的卷积核看到的是较宽的区域,能捕捉频率带随时间演化的整体走势。故障振动信号有个特点:冲击本身的持续时间很短,但冲击引发的共振包络可能跨越较长的时间,调制边带又分布在一定的频率宽度内。单一大小的卷积核要么只顾局部、丢了上下文,要么只看全局、忽略细节。
MCNN的做法很直接:让多个不同尺寸的卷积核分支并行工作,各分支提取不同尺度下的特征,最后把特征在通道维度上拼接起来。这样做的好处是,网络可以在同一个层级同时获得“微观”和“宏观”的视角,有点像让几个医生分别用放大镜、肉眼看同一张片子,再把各自的判断综合起来。工程上常用的一组卷积核是3×3、5×5和7×7。3×3负责锯齿状冲击细节,5×5负责局部包络形态,7×7负责整个频带能量迁移趋势。
也有人问我,为什么不直接用一堆3×3卷积堆深一点,非要多尺度分支?深层小卷积核网络在图像分类上确实很成功,但它需要足够多的数据让每一层学出逐步抽象的特征。故障诊断样本少、噪声大,堆到十几层反而容易过拟合。多尺度浅分支网络用少量层就覆盖了不同感受野,参数效率更高,收敛也稳。这是结合数据特点做结构设计的典型例子。
2.2 为什么CNN后面接GRU而不是直接接全连接
时频图虽然是一张二维图,但它有个特殊性质:横轴是时间,纵轴是频率。经过卷积网络提取之后,特征图仍然保持这种空间结构。如果直接把这些特征拉平成向量、接全连接层,网络会把不同时间的特征当成相互独立的对象处理,忽略了“故障状态如何随时间演化”这个信息。轴承故障不是静态的,冲击信号一个接一个,前一秒出现的特征往往和后一秒的特征在模式上有依赖关系。
GRU(门控循环网络)在这里的作用,是把CNN提取出来的特征按时间顺序重新组织,学习相邻时间片之间的演变关系。具体操作是,把最后一层卷积输出的特征图按照时间方向分成若干步,每一步的特征向量依次送入GRU单元。GRU的更新门和重置门会决定要记忆哪些历史信息、遗忘哪些,从而建立对故障状态的连续刻画。相比LSTM,GRU少了一个门控,参数量更小,在样本量不大的诊断任务里不容易过拟合,训练也更稳定。我在项目里对比过两者的收敛曲线,GRU在相同迭代次数下loss下降更平稳。
整个网络的流水线可以概括为:时频图输入 → 多分支卷积提取空间特征 → 全局平均池化或适当的reshape转为特征序列 → GRU建模时序依赖 → 全连接层和softmax输出故障类别。理解这条主线之后,再去调整每个模块都会更有方向感。
2.3 一组参考配置和参数规模
下面是我在项目里实际用过的网络配置,供新手做基线。各卷积分支先分别做两层卷积,激活函数ReLU,每层之间加批量归一化,最后接一个全局平均池化层把空间尺寸压成1×1,再拼接、送入GRU。
| 模块 | 具体设置 | 输出尺寸 | 说明 |
|---|---|---|---|
| 输入层 | 224×224×1灰度时频图 | 224×224×1 | 单通道即可,无需转RGB |
| 分支1 | 卷积3×3×8,BN,ReLU;再卷积3×3×16,BN | 56×56×16 | 设步长为2,下采样 |
| 分支2 | 卷积5×5×8,BN,ReLU;再卷积5×5×16,BN | 56×56×16 | 同上 |
| 分支3 | 卷积7×7×8,BN,ReLU;再卷积7×7×16,BN | 56×56×16 | 感受野更大 |
| 特征拼接 | 按通道维度拼接 | 56×56×48 | 三个分支合并 |
| 池化层 | 全局平均池化 | 1×1×48 | 减少参数量,防止过拟合 |
| 序列化+GRU | 将48维特征按时间步展开,GRU隐藏单元64 | 64 | 学习时序依赖 |
| 分类层 | 全连接,Softmax | 类别数 | 类别数由故障类型决定 |
整套模型参数量大概在几十万级别,和图像分类动辄上千万的模型相比轻量得多。这是因为输入已经通过时频分析浓缩了关键信息,网络不需要用参数量去弥补输入表达的不足。判断这个结构的计算负担是否合理,最简单的方法是训练一个epoch测时间,太慢就减小输出通道数或者降低输入图片尺寸,比如从224降到128,速度能提升将近一倍,准确率损失通常在1到2个百分点以内。
3. 从轴承振动数据到诊断结果:完整实验链路
3.1 数据划分方式决定“真高分”还是“假高分”
故障诊断实验里最容易被忽视、后果又最严重的环节就是样本划分。振动数据通常是连续采集的,我们为了扩充样本量,会用一个固定长度的窗口在长序列上滑动取样本。问题在于,相邻滑动窗口之间的数据高度重叠,比如步长为样本长度的四分之一时,两个相邻样本可能有75%的数据是同一段。如果按“每个样本独立随机分配”的方式划分训练集和验证集,那验证集里相当一部分样本和训练集样本几乎是同一段数据的不同截取,模型等于提前看到了答案,验证准确率会虚高得离谱。
我最早犯过这个错误,随机划分时验证集准确率到了98%,心里还挺高兴,结果换了一个完整的独立采集数据段去做测试,准确率直接掉到87%。后来才意识到,随机划分把重叠泄漏带进了验证集。
正确的思路是以“连续记录”为最小单位划分。一份数据文件或同一工况下的连续采集序列整体放进训练集或测试集,不允许一部分进训练、一部分进验证。滑动窗口只在单位内部生成样本。这样训练集和测试集才真正在数据来源上分离,得到的指标才能反映模型对新工况的泛化能力。
3.2 样本长度、时频图尺寸和归一化怎么选
样本长度决定了时频图的纵向频率分辨率和横向时间覆盖范围。如果转速是1750r/min,对应的转频约29.1Hz,旋转周期约0.034秒。12kHz采样率下,每转大约412个采样点。一个训练样本如果只有512点,只覆盖1.2个转周期,信息量不足,很多故障特征还没来得及完整呈现;2048点大约覆盖5个周期,对轴承故障诊断来说已经比较稳。样本过长的代价是训练集样本总数变少,计算量变大。我常用的折中是1024到2048点,再结合自己数据集的故障特点微调。
时频图的尺寸建议从64×64起步做快速验证,确认网络能收敛之后再提高到128或224。小尺寸能显著加快训练迭代,方便调参;最终模型再使用大尺寸时频图,通常还能再涨1到2个点的准确率。
归一化是另一个容易埋雷的地方。有些人直接用当前样本的最大最小值做min-max缩放,这在训练集内部没问题,但测试阶段如果来了一个新的信号,它的最大值可能和训练样本差异很大,分布就对不上了。正确做法是保存训练集统计量,比如全局均值和标准差或者固定分位数,推理时用同一套统计量处理新数据。我在代码里会把两个标量存到norm_params.mat里,避免每次重新计算。
3.3 训练配置和评价指标
我习惯用的优化器是Adam,初始学习率1e-3,batch size取32,训练40到80轮。时频图输入比原始波形更容易收敛,通常30轮左右就能看到验证准确率趋于平稳。学习率调参经验是:如果loss在前面几轮下降很慢,先检查数据归一化和网络结构,而不是急着调低学习率;如果训练后期准确率在某个值附近反复震荡,再把学习率降到原来的十分之一继续微调。
评价指标不能只看总体准确率。故障诊断场景经常存在类别不平衡,比如正常样本很多、某类早期故障样本很少,模型可能把所有样本都判成大类,准确率还不低,但实际没用。我通常会输出混淆矩阵,计算每一类的召回率和F1分数。特别要关注“早期轻微故障被误判为正常”这种情况,它的代价在工程上比两个故障类别互认更严重。另外,验证集上的F1分数比准确率更适合作为保存模型的依据,因为F1对不平衡更敏感。
4. MATLAB复现的核心代码与踩坑记录
4.1 一份管用的目录结构和代码骨架
MATLAB做深度学习实验最怕脚本和路径一团乱。我现在的目录结构固定为:data/放原始数据和划分后的数据集,features/存放预处理生成的时频图或.mat特征文件,models/放网络定义和训练脚本,utils/放各类工具函数,results/放训练曲线、混淆矩阵和模型参数。这个结构看起来简单,但能省掉大量重复调试时间。
核心脚本的逻辑线是:读取原始数据 → 滑动窗口切片 → 生成WMSST时频图 → 保存训练/验证/测试三份特征文件 → 定义MCNN-GRU网络 → 设置训练选项 → 训练并保存最优模型 → 加载模型测试并输出混淆矩阵。
% 训练主脚本骨架 load('features/train_data.mat'); % 预先处理好的时频图 load('features/val_data.mat'); layers = defineMCNNGRU(224, numClasses); % 自定义网络定义函数 options = trainingOptions('adam', ... 'InitialLearnRate', 1e-3, ... 'MaxEpochs', 60, ... 'MiniBatchSize', 32, ... 'ValidationData', {valData, valLabel}, ... 'ValidationFrequency', 30, ... 'Plots', 'training-progress', ... 'OutputNetwork', 'best-validation', ... 'Shuffle', 'every-epoch'); net = trainNetwork(trainData, trainLabel, layers, options); save('results/trained_net.mat', 'net');这里我把时频图生成和网络训练分成了两个阶段,一开始就把全部数据预处理并保存成.mat文件。这么做有个明显好处:调网络结构时不需要重复计算时频图,一次预处理,多次训练,能省下大量时间。别嫌这个步骤繁琐,我的经验是直接实时生成时频图喂给trainNetwork会让每次实验浪费至少一半时间在重复计算上。
4.2 三个让我浪费了整整两天的坑
第一个坑是维度不匹配。我最初把时频图尺寸设成224×224,训练时某个卷积层的输出和全连接层输入对不上,MATLAB直接报维度错误。排查这个过程其实很痛苦,因为错误信息只提示某一层的输入维度不对,但不告诉你它期望的到底是几维。后来我改用analyzeNetwork函数逐层查看特征图尺寸变化,才发现问题出在最后一个卷积层的输出在经过全局池化之前被错误地展平成了固定长度,而一旦我改了输入尺寸,这个长度就变了。最终修复方式是改用全局平均池化层,让池化输出尺寸自动适配输入,不再手写固定的展平维度。这个坑的教训是:网络里尽量不要写死某个依赖输入尺寸的数值,能用自适应层就用自适应层。
第二个坑是数据泄漏造成的假高分。前面已经说过划分原则,但代码实现时如果不小心,随机划分依然会发生。我排查这个问题的完整链路是:先怀疑网络结构,试了“加深卷积层数”和“调整学习率”,准确率没有变化;然后把训练集和验证集的样本做相似度分析,发现大量样本之间只有一步之遥;最后才意识到是滑动窗口重叠导致的泄漏。修复办法是改变划分逻辑,先把原始长序列按文件名分组,再对组内做窗口切片,最后用组的索引分配数据集,而不是对切好的样本直接randperm。
第三个坑是推理时归一化方式不一致。训练阶段模型正常,但把训练好的网络部署到一个新场景时,准确率明显下降。排查发现,我在测试脚本里又用测试数据的最大值和最小值做了一遍归一化。这个和训练时的全局统计量完全不一致,导致输入数据分布漂移。修复方法很简单,就是把训练时的归一化参数固定下来,写成两个常量文件供推理代码调用。这个问题看起来不起眼,但在实际部署中特别容易复现,因为你往往会重新写一份单独的预测脚本,然后顺手用了和训练脚本不一样的数据处理方式。
4.3 训练效率和内存优化
时频图保存成.mat文件之后,训练速度主要限制在GPU显存和trainNetwork的数据读取上。如果显存有限,优先降低MiniBatchSize而不是降低时频图分辨率,因为后者对模型精度影响更直接。我实际测试过,8GB显存跑224×224单通道图、batch size取16没有问题,取32时会提示显存不足,降到16之后速度反而更稳。
另外可以检查一下canUseGPU确保MATLAB用的是GPU训练。如果机器没有独立显卡,也不用气馁,把时频图分辨率降到128×128、网络通道数减半,CPU训练也能在可接受时间内完成一轮实验,代价是最终准确率会低2到3个百分点。先把流程跑通,再想办法优化资源。
5. 对比实验和消融分析
5.1 跟常见方法摆一起看,才显出组合的价值
单独说某个模块性能好没有说服力,我把WTMGRU和几组常见方法在同一份数据划分下做了对比。下表是我在公开轴承故障数据集上的示例结果,不同数据集会有浮动,但相对趋势一般比较稳定。这里不写具体数据集名称,因为同一思路换到别的数据上也成立。
| 方案 | 准确率 | 模型相对复杂度 | 备注 |
|---|---|---|---|
| 原始波形 + 一维CNN | 91.8% | 较高 | 需要较深网络 |
| STFT时频图 + 二维CNN | 93.5% | 中 | 时频聚集性一般 |
| CWT时频图 + 二维CNN | 94.9% | 中 | 稍好,但仍有能量扩散 |
| SST时频图 + 二维CNN | 95.7% | 中 | 压缩后特征更明显 |
| WMSST时频图 + 单支CNN | 96.4% | 低 | 加权增强有效 |
| WMSST时频图 + CNN-GRU | 97.3% | 中 | 加入时序建模提升明显 |
| WMSST + MCNN-GRU(WTMGRU) | 98.2% | 中高 | 多尺度与时序结合最优 |
从表里能看出,模型的进步不是某一项带来的,而是“输入表达改善”和“结构设计”共同作用的结果。我在实际调参过程中最深的体会是,输入从原始波形换成WMSST时频图后,同样的网络结构、同样的训练轮数,验证集收敛速度明显加快,这说明模型更容易学到有效特征了。
5.2 把消融实验当成结构的“体检报告”
消融实验的核心做法是,把WTMGRU里的某个模块去掉或替换,保持其他条件完全一致,观察性能变化。我做了三组:把WMSST换成普通CWT,观察时频加权带来的增益;把GRU去掉、改为直接在全连接层分类,观察时序建模的作用;把多尺度分支合并为单一3×3卷积分支,观察多尺度特征的价值。
结果比较直观:每一项被替换后,准确率都下降1到2个百分点左右。这个数字看似不大,但在原本已经98%的基线附近,每一个点的提升背后都是实打实的判别能力增长。更重要的是,消融实验能告诉我们哪些模块是真正的贡献者、哪些只是锦上添花。比如在这个项目里我发现,GRU带来的增益比多尺度分支稍微明显一点,说明故障前后状态的时间依赖信息确实被网络利用了;如果反过来,说明需要重新审视网络对时序信息的建模是否出了问题。
做消融实验有两点值得提醒:第一,所有对比实验必须用同一份数据划分、同一个随机种子,否则结果差异可能来自数据波动;第二,一定要在交叉验证或多次重复实验里看趋势,只跑一次的结果说服力有限。这一步在写报告和论文时是“安全网”,在工程判断上更是帮你理清哪个模块最值得优化、哪个模块可以为了计算效率砍掉。我在后续版本里就把GRU的隐藏单元从64减到了32,换来了接近20%的推理速度提升,准确率只下降了0.4个百分点,这是消融实验给我最有用的产出。
最后说一点我自己的心得。前几年大家比拼注意力机制、比拼更深更复杂的网络,但我这次把WMSST时频图作为输入之后,最大的感受是:项目精度提升的一半,来自对振动信号表达方式的重新理解,另一半才来自模型结构。先把输入做到有物理意义、把数据划分做到不泄漏、把归一化做到一致,再让网络去学特征,比单纯堆结构稳得多。如果你是刚开始接触这类项目,建议先在小数据集上把整条链路跑通,再用更大规模的实验验证;能少踩不少坑,也会对故障诊断这个方向建立更直观的判断。