
第一次把WMSST和MCNN-BiGRU这组搭配搬进故障诊断流程时我其实是有点忐忑的。信号处理领域的老方法很多深度学习的方案也不少但能把时频细节、多尺度空间特征和时序依赖同时吃进去的确实不多。跑通程序、看到测试集准确率稳定在98%以上时我知道这个方向选对了。这个项目核心就一句话先用WMSST把一维振动信号变成高分辨率时频图再用MCNN提取不同尺度的空间特征最后用BiGRU沿时间轴建模完成旋转机械的故障分类。整条链路用Matlab实现从数据读取到模型训练、评估都非常顺。适合正在做故障诊断、设备健康监测或者刚入坑深度学习但想知道“怎么把CNN和RNN串起来用”的同学。1. 项目整体设计与思路拆解1.1 故障诊断的难点和传统方法的局限旋转机械电机、轴承、齿轮箱的振动信号绝大多数是非平稳、非线性的。设备一旦出现局部损伤比如轴承外圈剥落、齿轮根部裂纹产生的冲击信号是瞬态的持续时间极短而且能量会被背景噪声盖住。早期故障特征非常微弱用普通幅值谱看可能什么都看不出来。传统时频分析方法各有各的毛病。短时傅里叶变换窗口固定时间分辨率和频率分辨率互相牵制想同时看清冲击时刻和频率成分很难。小波变换虽然能自适应多尺度但时频图上的能量常常“糊成一团”频率带之间的边界不清晰。希尔伯特-黄变换对模态分解质量依赖很强分解不好就全乱了。这些方法不是不能用而是对“瞬时频率变化”和“局部冲击特征”的刻画不够精准。深度学习介入之后很多人直接拿原始时域信号丢给CNN或RNN。CNN擅长提取局部特征但对信号的时间顺序不敏感RNN比如LSTM、GRU擅长处理序列但如果直接喂原始波形网络很难自动注意到故障冲击对应的频率位置。所以问题就变成了怎么把“时频表示”做得足够好同时让模型既能看空间纹理又能看时间演化。WMSSTMCNN-BiGRU就是冲着这两个问题去的。1.2 为什么选WMSSTMCNN-BiGRU这个组合先说WMSST。传统的同步压缩变换SST本质是把小波变换或短时傅里叶变换的系数沿频率方向重新排列让能量更集中。但单尺度的同步压缩在遇到频率成分复杂、噪声较大的信号时容易出现能量泄露和模态混叠。WMSST的做法是在多个尺度上分别做同步压缩然后按照权重融合成一个时频矩阵。权重怎么定根据每个尺度下能量分布的集中程度和噪声水平自适应调整。这个思路和“多个专家投票”有点像个别尺度判断不准没关系加权之后整体结果更稳。MCNN负责“空间纹理提取”。时频图本质上是一张二维图像横轴是时间纵轴是频率像素值代表能量强度。故障冲击会在时频图上留下“条纹”或“亮点”。用不同卷积核尺寸比如3×3、5×5、7×7并列去卷积相当于同时用不同大小的放大镜观察小卷积核抠细节大卷积核看整体最后把特征拼起来。多尺度特征能更好覆盖不同频段的故障模式比单一卷积核的泛化能力明显更强。BiGRU负责“时间演化”。故障发生时振动信号在时间维度上有连续的过渡特征比如冲击的周期性。BiGRU从正向和反向两个方向扫描特征序列相当于既看前文又看后文对周期性和趋势性信息的捕捉比单向GRU更完整。这个模块把MCNN输出的空间特征按时间顺序组织起来最终汇总为分类判断。1.3 整体流程与模型结构整个流程可以拆成五步原始振动信号采集与分帧按照固定窗口长度截取样本对每个样本做WMSST得到时频矩阵统一时频图尺寸并归一化作为模型输入MCNN多尺度卷积提取空间特征拼接后送入BiGRU全连接层加Softmax输出故障类别评估指标用准确率、F1-score和混淆矩阵。模型结构我画了个简化描述输入是128×128×1的时频图经过三个并行的频率维卷积分支卷积核高度分别为3、5、7每个分支输出16个通道然后通道拼接成48个通道再用一个1×1卷积融合通道信息。融合后的特征图保持时间维度不塌缩按时间步切分成特征序列送入包含128个隐藏单元的BiGRU。BiGRU输出的最后时刻隐藏状态接一个4分类全连接层和Softmax。2. 核心技术点拆解时频特征、多尺度卷积、双向时序建模2.1 WMSST加权多尺度同步压缩变换的原理WMSST这里指的是加权的多尺度同步压缩变换Weighted Multi-Scale Synchrosqueezed Transform。要理解它先要清楚同步压缩的核心思路。对连续小波变换每个尺度对应一个频率带小波系数的能量会分布在某个“脊线”附近。同步压缩不是把系数简单搬回原来的频率位置而是先计算瞬时频率再把能量重新分配到最接近真实频率的位置。瞬时频率可以通过对时间变量求偏导估计出来写成通俗点就是每个时间点、每个尺度上系数相位随时间变化的速度就是该尺度分量在该时刻的瞬时频率。WMSST的关键在“多尺度”和“加权”两点。多尺度是指不只做一次小波变换而是在多个不同尺度范围内分别生成时频系数加权是指在融合时不是平均取值而是对每个尺度计算一个权重。权重常用能量集中度指标衡量哪个尺度下的时频能量更集中、背景噪声更少权重就更大。这样做比单尺度同步压缩更抗噪在低速重载工况下特别有用。在Matlab里我用的是小波工具箱配合自写压缩逻辑。核心过程是对信号做尺度连续的小波变换小波基选择复值的Morlet小波因为复值小波能提供相位信息瞬时频率估计更准确估计每个尺度的瞬时频率将能量重新分配到对应的频率槽中对多个尺度做加权融合得到最终时频矩阵。实际使用中Morlet小波的带宽参数需要根据信号主频段调整。带宽参数太小时频率分辨率变差太大时时间分辨率变差。经验值是先做几次试算用一组已知故障样本看时频图中冲击条纹是否清晰再固定参数。2.2 MCNN多尺度卷积怎么设计才合理MCNNMulti-scale CNN不是某个固定网络而是一种结构思路。最常用的做法是构造多个并行的卷积分支每个分支的卷积核大小不同。我在项目里用了三个分支但卷积核的宽度都固定为1只改变高度频率维上的覆盖范围分别取3、5、7。为什么要这样设计因为时频图的纵轴是频率横轴是时间。如果卷积核同时在频率和时间两个方向滑动会把“相邻时刻”也混进来这对送入BiGRU的序列建模是有干扰的。BiGRU本来就负责处理时间轴所以MCNN这里只做“同一时刻内、多个相邻频率带”的特征提取即可。卷积核高度为3表示看当前频率点上下各一个格子的能量高度为7表示看更宽的一段频率范围。不同分支相当于用不同“频率带宽”的窗口去观察同一时刻的频谱结构。每个分支内部都是“卷积批归一化ReLU”的组合。批归一化放在卷积和激活函数之间能让每层输入的分布更稳定收敛更快。三个分支输出的通道数都设为16拼接后得到48个通道再用一个1×1卷积融合不同尺度的信息把通道数降到32减少后续计算量。1×1卷积在这里的作用是跨通道线性组合相当于让网络自己决定每个尺度的特征该占多少权重。2.3 BiGRU在故障诊断中承担什么角色BiGRU就是双向门控循环单元。GRU相比LSTM参数少、收敛快在样本量不算很大的故障诊断场景里更实用。双向的含义是序列按正常时间顺序喂一个GRU同时按逆序再喂一个GRU最后把两个方向对应的隐状态拼接起来。故障信号中存在两类时序信息。一类是故障冲击的周期性比如滚动体损伤会在每个旋转周期内撞击某一点反映在时频图上就是相同位置反复出现的短促亮点。另一类是工况的渐变例如转速上升过程中故障特征频率随转速升高而移动这是一个连续演变的过程。单向GRU只能从前往后看对周期性信息的利用不够充分。BiGRU相当于同时拿到“过去”和“未来”的上下文每个时刻的输出都综合了两个方向的信息判断更稳。在项目里我把MCNN输出的特征图按时间轴切分成一组特征序列每个时间步对应一个特征向量。这些特征向量在BiGRU中被逐个读取最终取最后一个时间步的隐藏状态送到分类器。如果窗口内故障冲击不止一次也可以把所有时间步的隐状态做平均池化效果往往更好。我在实验里试过取“最后时刻”和“平均池化”差距不大但平均池化在噪声大时稍稳一些。2.4 特征融合与模型输入输出设计特征融合的关键在于保持时间维对齐。MCNN各分支并行卷积时因为卷积核高度不同需要设置相应的Padding保证输出尺寸一致。我用的是“Same”填充高度方向的Padding分别为1、2、3这样三个分支输出的时频图高度都不变。时间方向卷积核宽度为1不改变宽度。所以拼接层的输入尺寸完全一致可以做通道维拼接。输入到BiGRU之前需要把“[频率维度 × 时间步 × 通道数]”的特征图转换成“[时间步 × 特征维度]”的序列格式。这个转换看似简单但在Matlab的层图网络里是个常见的坑。后面我会专门讲实现方法。输出端用的是带Softmax的分类层类别数根据数据集设定我这个实验是4类。如果要做故障程度诊断比如“轻微磨损/中度磨损/严重磨损”类别数相应调整即可。3. Matlab实现全流程与代码示例3.1 数据准备与训练集划分我用的是公开的轴承振动数据集采样频率12kHz包含正常、内圈故障、外圈故障、滚动体故障四类每类又分不同损伤程度。拿到数据后第一步不是急着做时频变换而是先把数据集划分想清楚。这里必须提醒一个关键点故障诊断里最隐蔽的错误是数据泄漏。如果直接对整个连续信号做随机划分训练集和验证集里大概率包含同一段信号的相邻片段两个集合高度相似模型学到的是“记忆片段”而不是“故障模式”验证准确率会虚高到99%以上。正确做法是按不同转速、不同载荷段来划分保证训练和验证数据来自完全不同的工况片段。样本构造用滑动窗口窗口长度1024个点步长512。每个窗口作为一个独立样本。为什么选1024因为12kHz采样率下常见轴承故障特征频率在几百赫兹到几千赫兹之间1024个点大约能覆盖0.085秒足够包含一个旋转周期的冲击序列同时不引入过多非平稳成分。做WMSST之后再归一化到0到1之间训练集大约4000个样本测试集1000个左右。对深度模型来说不算大所以后面正则化一定要跟上。3.2 WMSST变换的实现步骤我不会在这里贴完整的几百行代码那会让人失去阅读兴趣。我把自己实际用的核心框架简化一下你可以照着补全。function tfmap wmsst_transform(x, fs) % x: 输入振动信号1x1024 % fs: 采样频率12000 % 返回128x128的时频矩阵 % 1. 连续小波变换 scales 2:0.1:128; wt cwtft({x, 1/fs}, wavelet, morl, scales, scales); W wt.cfs; freqs wt.frequencies; % 2. 对每个尺度、每个时间点估计瞬时频率 phase angle(W); dphase diff(phase, 1, 2); inst_freq zeros(size(W)); inst_freq(:, 1:end-1) dphase / (2 * pi * (1/fs)); inst_freq(:, end) inst_freq(:, end-1); % 3. 同步压缩将能量按瞬时频率重新分配 % 新建一个频率槽矩阵槽数由目标分辨率决定 nbins 128; tfmap zeros(nbins, size(W, 2)); fmin min(freqs); fmax max(freqs); fbin_edges linspace(fmin, fmax, nbins 1); for b 1:size(W, 2) for a 1:size(W, 1) fk inst_freq(a, b); if fk fmin fk fmax idx find(fbin_edges fk, 1, last); tfmap(idx, b) tfmap(idx, b) abs(W(a, b)) * scales(a)^(-1.5); end end end % 4. 多尺度加权融合示意 % 实际代码会对多个尺度区间分别做同步压缩再按能量集中度加权 weight ones(size(tfmap)); % 这里简化生产中需要计算 tfmap tfmap .* weight; % 5. 对数压缩增强弱故障特征 tfmap log(1 abs(tfmap)); % 6. 缩放到统一尺寸 tfmap imresize(tfmap, [128, 128]); end这段代码里最花时间的部分是双循环。如果样本有几万个建议用向量化或MEX加速。另一个工程建议是所有样本的WMSST结果一次性计算完保存成.mat文件训练时直接load不要再重复计算。我在第一次跑实验时没存预处理结果每次训练前重新算时频图白白等了一个多小时。3.3 MCNN-BiGRU模型搭建layerGraphMatlab的Deep Learning Toolbox支持用layerGraph搭建多分支结构。我先搭基础层再把分支接进去。下面是我实际用的简化版本关键是卷积核高度不同、宽度保持1。lgraph layerGraph(); % 输入层128x128的单通道时频图 inputLayer imageInputLayer([128 128 1], Name, input); lgraph addLayers(lgraph, inputLayer); % 分支13个频率格宽度的卷积 branch1 [ convolution2dLayer([3 1], 16, Padding, [1 0], Name, conv_k3) batchNormalizationLayer(Name, bn_k3) reluLayer(Name, relu_k3)]; lgraph addLayers(lgraph, branch1); lgraph connectLayers(lgraph, input, conv_k3); % 分支25个频率格宽度的卷积 branch2 [ convolution2dLayer([5 1], 16, Padding, [2 0], Name, conv_k5) batchNormalizationLayer(Name, bn_k5) reluLayer(Name, relu_k5)]; lgraph addLayers(lgraph, branch2); lgraph connectLayers(lgraph, input, conv_k5); % 分支37个频率格宽度的卷积 branch3 [ convolution2dLayer([7 1], 16, Padding, [3 0], Name, conv_k7) batchNormalizationLayer(Name, bn_k7) reluLayer(Name, relu_k7)]; lgraph addLayers(lgraph, branch3); lgraph connectLayers(lgraph, input, conv_k7); % 通道拼接 concatLayer depthConcatenationLayer(3, Name, concat); lgraph addLayers(lgraph, concatLayer); lgraph connectLayers(lgraph, relu_k3, concat/in1); lgraph connectLayers(lgraph, relu_k5, concat/in2); lgraph connectLayers(lgraph, relu_k7, concat/in3); % 1x1卷积融合不同尺度特征 fuseLayer [ convolution2dLayer(1, 32, Name, conv_fuse) batchNormalizationLayer(Name, bn_fuse) reluLayer(Name, relu_fuse)]; lgraph addLayers(lgraph, fuseLayer); lgraph connectLayers(lgraph, concat, conv_fuse);现在到了最常见的问题怎么把卷积输出的图像特征转成BiGRU能吃的序列特征。Matlab自带的bilstmLayer要求输入是序列格式也就是“特征维度×时间步”。我的做法是写一个自定义层把四维特征图[H W C N]重新排列成[H*C W N]也就是把频率维和通道维压缩成特征维时间维保持不变。自定义层的核心逻辑其实很简单就是reshape和permute。这里不展开全部类定义核心代码如下function Z predict(layer, X) % X: [H W C N] [H, W, C, N] size(X); % 把 H 和 C 合并保留 W 作为时间步 Z reshape(X, [H * C, W, N]); end自定义层通过addLayers加进网络图然后接上BiGRUbgruLayer bilstmLayer(128, OutputMode, last, Name, bgru); fcLayer fullyConnectedLayer(4, Name, fc); softmaxLayer softmaxLayer(Name, softmax); classLayer classificationLayer(Name, output); lgraph addLayers(lgraph, bgruLayer); lgraph connectLayers(lgraph, relu_fuse, bgru); lgraph addLayers(lgraph, fcLayer); lgraph connectLayers(lgraph, bgru, fc); lgraph addLayers(lgraph, softmaxLayer); lgraph connectLayers(lgraph, fc, softmax); lgraph addLayers(lgraph, classLayer); lgraph connectLayers(lgraph, softmax, output);这里OutputMode设为last只取最后一个时间步的隐藏状态。如果你想用平均池化可以在自定义层里先做全局平均再送全连接。两种方式我都试过差别不大但last实现更简单我就沿用了。3.4 训练设置与调参记录训练选项我一般这样设置options trainingOptions(adam, ... InitialLearnRate, 1e-3, ... LearnRateSchedule, piecewise, ... LearnRateDropPeriod, 20, ... LearnRateDropFactor, 0.5, ... MaxEpochs, 80, ... MiniBatchSize, 32, ... ValidationFrequency, 50, ... ValidationData, valSet, ... L2Regularization, 1e-4, ... Plots, training-progress, ... Shuffle, every-epoch);学习率1e-3起步每20轮衰减一半。为什么用这个策略Adam优化器本身对学习率不太敏感但1e-3是训练CNN的常见起点。前面20轮让模型快速找到一个靠谱的区域后面衰减到小范围精调避免loss在最优值附近震荡。Batch size 32在内存和收敛速度之间比较平衡。我实测下来的结果是训练到40轮左右验证准确率突破95%到70轮稳定在98%以上。整个训练在单张消费级显卡上约15分钟放在CPU上可能要一两个小时。如果你看到loss曲线在20轮之后仍然剧烈震荡大概率是学习率过大或者样本批次差异太大先把学习率降到5e-4试试。4. 常见问题速查与避坑实录4.1 时频图质量差导致分类性能上不去典型表现是训练准确率一直徘徊在70%以下。先别急着调网络回头检查WMSST输出。随便挑几个样本看时频图如果能量一片模糊甚至看不出冲击条纹说明尺度范围没选对。尺度上限要与采样频率匹配比如12kHz采样下关注频段在50Hz到5kHz之间尺度范围可以用经验公式估算最小尺度对应奈奎斯特频率最大尺度对应最低关注频率。再检查归一化方式。每张时频图按样本自身最大值归一化还是按整个数据集统一归一化我建议按整个数据集95%分位数来归一化保留类间能量差异。如果每个样本自己归一化会丢失“这个样本整体能量弱于另一个样本”的信息而能量大小本身也是一种故障特征。这个小细节经常导致准确率差几个点。我给自己写过一张速查表现在也贴出来现象可能原因处理方式时频图一片模糊尺度范围过大或过小根据采样频率和故障特征频率重选尺度时频图有横向条纹小波基带宽不合适换更窄带或更宽带的Morlet小波分类准确率长期低于80%归一化方式不合理改用全局归一化保留能量差异输入尺寸报错样本长度不一致用imresize统一成128×1284.2 训练震荡、不收敛怎么排查训练loss不下降甚至上升先看学习率。学习率太大是最常见的原因把初始学习率降到5e-4观察前10轮是否稳定下降。如果还不降检查标签是否从1开始Matlab的classificationLayer要求分类标签是正整数且不能包含0。还有一个隐蔽问题输入时频图尺寸不一致。Matlab的trainNetwork要求所有样本尺寸一致如果你的WMSST输出矩阵因为信号长度不同而变化训练会直接报错。但如果你用“预分配矩阵”的方式强行统一尺寸边界填充值可能不对模型会学到奇怪的边界效应。我建议打印每一批输入的size确认都是128×128×1×32后再训练。如果loss曲线是“下降-反弹-下降-反弹”的锯齿状可能是MiniBatchSize太小梯度估计噪声太大。不要低于16一般32就够了。另外把Shuffle设为every-epoch每个epoch都重新打乱样本顺序能让训练曲线更平滑。4.3 过拟合与“虚假高准确率”问题样本量不大时过拟合非常常见。训练准确率99%验证准确率92%差距超过5个百分点基本可以断定过拟合。我的三个建议在BiGRU输出之后、全连接层之前加一个dropoutLayer(0.5)对抑制过拟合立竿见影。对时频图做数据增强水平小幅度平移、叠加微小高斯噪声、频率轴做轻微扰动。增强时要小心不要破坏时频图的物理意义比如时间平移幅度不要超过一个冲击周期。使用早停机制trainingOptions里有ValidationPatience连续若干轮验证loss不降就停止训练避免最后一轮过拟合。不要过度增加BiGRU隐藏单元数量。128个隐藏单元对这个任务已经足够堆到256以上在小数据集上几乎必然过拟合。MCNN分支通道数同理每分支16通道已经能提取足够特征。4.4 内存不足与训练时间过长时频图如果全部保存成高分辨率单精度矩阵几千个样本可能占好几个GB。我的做法是预处理时直接缩放到128×128不要贪高分辨率。更高分辨率对最终准确率提升很小但计算代价成倍增加完全不划算。训练太慢的另一个原因是每个epoch都在重复读取数据。把预处理后的时频图保存成datastore或直接拼成一个大矩阵训练前一次性load进内存。如果样本量超大用tall数组或者分批次读取的办法。实测下来用matfile分批读取比直接load整个文件更省内存速度也不差。5. 实操经验与进一步扩展5.1 让模型更稳的两个小技巧我在这个项目里踩过最大的坑就是数据划分。一开始用简单随机划分准确率直接飙到99.8%我一度以为模型无敌了。换到跨工况验证就跌到95%以下这才意识到训练集和验证集存在数据泄漏。后来我固定了随机种子并按不同工况段来划分数据才得到可信的结果。所以大家做故障诊断看到超高准确率先别兴奋先想想数据划分是否严谨。第二个技巧是使用类别权重。如果数据集中正常样本占多数模型会倾向于把所有样本都判成正常F1-score很难看。我通过给少数类加更大的损失权重来解决在trainingOptions里可以设置ClassWeights或者手动构造加权交叉熵损失。调整之后每一类的召回率都更均衡整体准确率不降模型的可信度反而更高。5.2 扩展到其他场景的思路WMSSTMCNN-BiGRU并不局限在轴承故障上。我后来把它用到齿轮箱、电机电流信号以及一些工业设备的状态监测中流程基本一样只需要重新调整尺度范围和时频图尺寸。如果设备转速变化范围大可以考虑先做阶比跟踪把信号从等时间采样变成等角度采样再做WMSST效果会好很多。想进一步提升效果可以在MCNN分支中引入注意力机制比如SE模块让网络自动关注更有判别力的频率通道。或者在BiGRU后面再接一个自注意力层只关注和故障冲击强相关的关键时间步。这些改动在Matlab里实现都不难用attentionLayer或者自定义层都能做。如果数据量足够大还可以考虑把WMSST变成可微分的时频层把信号到时频图的变换也并入网络实现端到端训练。不过从工程角度看现在的“预处理训练”两步式流程更可靠出问题也好定位调试起来方便得多。最后再分享一点个人体会。做故障诊断研究模型结构重要但实验设计的严谨性更重要。我自己被“虚假高准确率”坑过不止一次后来养成了固定随机种子、跨工况验证、每次训练完都看混淆矩阵的习惯。这套WMSSTMCNN-BiGRU方案我在多个数据集上复现过只要预处理和超参数按上面的方式来结果都很稳定。希望这篇记录能让你少走一些弯路跑通之后记得回来聊聊你的准确率。