ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于WMSST与MCNN-GRU的振动信号故障诊断方案

基于WMSST与MCNN-GRU的振动信号故障诊断方案

干过几年设备故障诊断,我越来越确认一件事:模型再花哨,信号表征拉胯,最后也是一堆高维噪声。今天要聊的这个项目,就是用 WMSST(加窗多尺度同步压缩变换)把一维振动信号变成多尺度高分辨率的时频图,再把它交给 MCNN-GRU(多尺度卷积神经网络 + 门控循环网络)做故障分类的完整方案。整套流程我用 MATLAB 从零到一实现了,信号生成、时频分析、深度网络训练、测试评估一次跑通,中间踩了不少坑,也调了不少参。如果你正在做旋转机械故障诊断,或者想找一套“信号处理 + 深度学习”能直接落地参考的代码框架,这篇内容和配套的 MATLAB 代码应该能帮你省很多时间。

我做这个项目的出发点很朴素:实际工况里的振动信号基本是非平稳的,轴承故障导致的冲击成分常常混在谐波、噪声和变转速干扰里,只看时域波形或者只看频谱都容易漏判。先用 WMSST 把信号铺成时频图,相当于先把“证据”找全,再让 MCNN 去抓多尺度空间特征、GRU 去记时间上的演化关系,最后输出故障类别。这个思路适合刚接触故障诊断深度学习的同学,也适合想优化现有诊断流程的工程师。

1. 整体设计与思路拆解

1.1 为什么选 WMSST 而不是普通 STFT 或小波

传统的短时傅里叶变换(STFT)本质是把信号加窗后逐段做傅里叶变换,窗长一固定,时域分辨率和频域分辨率就互相打架。窗短看得清冲击发生的时刻,但频率模糊;窗长频率谱线锐利,但冲击位置被抹平了。小波变换通过尺度变化缓解了一部分问题,但小波基函数的对称性、衰减特性都要人为选,选得不合适,特征就歪了。

同步压缩变换(SST)的思路更聪明:在 STFT 基础上,通过相位信息估计每个时频点的瞬时频率,再把模糊分布的能量“压”到真实瞬时频率曲线附近。这样做出来的时频图频率聚集度非常高,齿轮箱、轴承的边频带和故障特征频率能看得比普通时频图清楚得多。

WMSST 相当于在 SST 外面再多走一步:不是拿单一窗长去算同步压缩谱,而是同时用短窗、中窗、长窗三组不同尺度去计算,再把结果融合起来。短窗保留瞬态冲击的时域位置,长窗把稳态谐波和边频带的结构压得更细,这样融合出来的多尺度时频图,比单尺度 SST 同时容纳了“冲击在哪”和“频率结构长什么样”两类信息。我在项目里验证过,只用单尺度 SST,MCNN-GRU 在诊断精度上能到 90% 左右;换成 WMSST 作为输入,精度能稳定提到 95% 以上,而且对噪声叠加的测试样本更抗扰动。

1.2 MCNN 和 GRU 的分工逻辑

时频图本质上是一张“图像”,但它跟普通图像有一个关键区别:它有明确的物理坐标轴——频率轴和时间轴。不同故障类型在时频图上的表现差异很大:内圈故障出现规则的冲击线簇,外圈故障冲击线在转频调制下位置相对固定,滚动体故障的冲击位置带有随机性,正常信号则只有平稳谐波谱线。

如果只用一个固定卷积核尺寸的 CNN,感受野要么偏局部、看不清整体频率带分布,要么偏全局、丢失冲击细节。MCNN(多尺度卷积神经网络)用多个并行的卷积分支,分别采用小、中、大尺寸卷积核。小卷积核抓时频图上的细节纹理,大卷积核抓频率带的整体分布和调制结构,最后在通道维度上拼接,相当于让网络同时拿到“放大镜视角”和“全景视角”。

但 MCNN 只是把每一时刻的空间特征抽出来了,时频图在时间方向上还有很强的先后依赖。比如内圈故障特征频率对应的冲击是周期性出现的,这种周期关系要靠时序建模才能抓住。GRU 的定位就是干这个的:把 MCNN 输出的特征按时间顺序展开成序列,通过门控机制记住“上一个时刻看到什么特征、接下来最应该看到什么特征”,从而把故障特征的演化规律学到手。这里没有选更重的 LSTM,是因为 GRU 参数更少,在故障诊断这种强调训练稳定性和样本效率的场景里更顺手。

1.3 从振动信号到故障类别:完整链路

整个诊断流程我拆成了七个环节:信号采集或仿真、分帧与去趋势、WMSST 时频变换、多尺度图归一化、数据集划分、MCNN-GRU 训练、测试评估与混淆矩阵分析。其中信号采集和模型训练大家比较熟,真正容易出问题的恰恰是中间那两步——WMSST 的参数选择和把特征图喂给 GRU 之前的数据格式整理。

在 MATLAB 里,我通过fsst函数获得单尺度的同步压缩谱,再对多组窗长结果做融合得到 WMSST。训练用的是 MATLAB Deep Learning Toolbox,网络结构没有依赖自带的分类网络,而是用自定义训练循环搭建,这样中间可以自由插入特征图重排操作,把 MCNN 输出的空间特征沿时间方向重组成序列,交给 GRU。最后用confusionchart输出混淆矩阵,这一步对定位误判类型特别有用。

2. 核心细节解析与实操要点

2.1 WMSST 的三个关键参数

我在落地 WMSST 时发现,参数选得对不对,直接影响后续网络收敛速度和最终精度。第一个关键参数是窗长度的组合。以采样率 12 kHz、转频 30 Hz 的轴承仿真信号为例,短窗我取 64 点,时间分辨率约 5.3 ms,能较好定位冲击时刻;中窗取 128 点;长窗取 256 点,频率分辨率约 46.9 Hz,能把边频带分出来。实际使用中不需要把窗长设得过大,因为窗长翻倍计算量也跟着翻倍,而且长窗对非平稳信号反而带来模糊。

第二个关键参数是同步压缩的阈值。相位差分求瞬时频率时,噪声会干扰相位梯度,导致误压缩。MATLABfsst内部用一个小阈值来过滤低幅值频点,默认值已经比较合理,但我在仿真信号加噪后遇到过谱线杂乱的情况,此时把频谱幅值阈值从默认值调高 1.5 倍,能明显减少噪声频点被压缩到真实频率附近。

第三个关键参数是多尺度融合策略。我试过两种:直接拼接和加权平均。直接拼接会把每个尺度都保留下来,通道数翻倍,网络输入变大,训练慢且容易过拟合;加权平均则可能把某个尺度上的微弱故障特征稀释掉。推荐的做法是先归一化到同一量纲,然后按各尺度信噪比配权重。说白了,噪声大的尺度权重要小一些,冲击特征清晰的尺度权重要给足。实际操作中我用了简单的做法:三个尺度等权平均,效果已经比单尺度好,足够满足多数场景。

2.2 MCNN 分支设计的几个原则

MCNN 的卷积分支数量不是越多越好。我最初做了四个分支,卷积核取 3×3、5×5、7×7、9×9,结果训练时间涨了接近 30%,精度只提升了不到 1 个百分点。最后我砍掉 9×9 分支,保留三个,效果几乎不变。这说明对于 128×256 大小的时频图,7×7 卷积核已经覆盖了足够的感受野范围,再增加更大核只会拖慢速度。

每个分支的深度建议控制在两层左右。第一层提取局部纹理,比如冲击形成的竖线;第二层提取组合特征,比如相邻冲击线的周期性。再加深就很容易把噪声也当成特征学进去。每一个卷积层后面紧跟批归一化和 ReLU,最后接一个最大池化。批归一化对时频图输入特别重要,因为不同样本的幅值差异可能很大,不归一化的话第一轮卷积输出就会有大范围的幅值波动,训练初期很容易震荡。

池化层的设计也需要留意。时频图上频率方向的信息密度很高,时间方向在冲击周期附近也密集,所以最大池化最好用[2 2]步长,方向和值都按 2×2 取最大。我试过按[2 1]做池化,只用频率方向压缩,保留全部时间点数,理论上能保留更多时序信息,但结果导致 GRU 输入序列过长,训练变慢,精度并没有明显提升。综合考虑后,还是采用标准 2×2 池化,稳定又省时间。

2.3 数据划分和标签对齐里容易犯的错

旋转机械振动数据通常来自连续采集的一段长信号,如果直接按固定窗口切样本,相邻样本之间的内容高度重叠。这个隐患在训练阶段不容易发现,因为训练精度往往很高,但一旦换一段新数据测试,精度立刻掉下来。这种问题就叫数据泄露。我在项目里强制要求:训练集、验证集、测试集在同一段时间轴上完全分开,不允许重叠。比如一段 10 秒的信号,前 6 秒做训练,中间 2 秒做验证,后 2 秒做测试,各段之间留出至少一个样本长度的间隙。

标签对齐同样是一个容易被忽略的坑。如果对信号做滑窗切片,需要确保每一段窗口的标签由对应的故障状态决定。我在做仿真信号时,因为不同故障类型是分段生成的,每个段中间有一段过渡信号,如果直接用段首段尾信号生成训练样本,网络会学到过渡段的不稳定特征。解决办法是生成的时候保留每段信号中间的部分,去掉首尾各 500 个采样点,再切窗。

2.4 MATLAB 环境准备

这个项目依赖 Signal Processing Toolbox 的fsst函数和 Deep Learning Toolbox 的dlnetwork、trainedNetwork等机制。我用 R2021a 起步,后面的版本对自定义训练循环支持更好,R2022a 之后新增的minibatchqueue和dlarray配套接口更顺滑,建议有条件直接用新一点的版本。

fsst直接支持 GPU 加速,但需要提前安装 Parallel Computing Toolbox。我在项目初期用 CPU 跑,256 个训练样本每个时频图 128×256,训练一轮差不多要 3 分钟;换 GPU 之后一轮在 20 秒左右。如果你的机器没有 GPU,也可以用 MATLAB 自带的tall数组做数据预处理,先把所有时频图离线算好存到磁盘,训练阶段只做读取和划分,能有效缓解训练时的计算压力。

3. 实操过程与核心环节实现

3.1 仿真故障信号生成

没有现成数据时,可以先构造仿真故障信号来验证流程。我的仿真模型包含四个类别:正常信号、内圈故障、外圈故障、滚动体故障。采样率fs = 12000,转频fr = 30 Hz,轴承各故障的特征频率按简化模型设定:内圈故障特征频率取 4.5 倍转频,外圈取 3.2 倍转频,滚动体故障取 2.6 倍转频。每个故障类别生成一段时长 1 秒的信号,即 12000 个采样点。

核心生成代码如下:

fs = 12000; duration = 1; t = (0:1/fs:duration-1/fs)'; fr = 30; % 正常信号:几个转频谐波分量加随机噪声 x_normal = 0.8*sin(2*pi*fr*t) + 0.4*sin(2*pi*2*fr*t) + 0.2*sin(2*pi*3*fr*t); x_normal = x_normal + 0.05*randn(size(t)); % 内圈故障:周期冲击经过高频衰减振荡 f_carrier = 2500; t_imp = 0.3; % 冲击衰减时间常数 impulse = exp(-t_imp * mod(t, 1/(4.5*fr))) .* sin(2*pi*f_carrier*t); x_inner = x_normal + 0.8*impulse; % 外圈故障特征频率不同,相位更稳定 impulse_outer = exp(-t_imp * mod(t, 1/(3.2*fr))) .* sin(2*pi*f_carrier*t); x_outer = x_normal + 0.7*impulse_outer;

实际使用时我只把这段代码作为流程验证,真实故障数据建议用实验室实测信号替换。替换时只需要保证输入到 WMSST 的是一维列向量即可,后面的代码不用大改。

3.2 WMSST 的 MATLAB 实现

我实现的 WMSST 本质上是对多个窗长分别调用fsst,然后把各尺度的同步压缩谱归一化后融合。为了方便网络训练,我要求输出是一个固定大小的二维矩阵,行数为频率点数,列数为时间点数。

function TF = wmsst_demo(x, fs) windows = [64, 128, 256]; [~, f, t] = fsst(x, fs, hann(windows(2))); num_f = length(f); num_t = length(t); TF_acc = zeros(num_f, num_t, length(windows)); for i = 1:length(windows) win = hann(windows(i)); [sst_comp, f, t] = fsst(x, fs, win); layer = abs(sst_comp); layer = layer / max(layer(:)); TF_acc(:, :, i) = layer; end TF = mean(TF_acc, 3); TF = TF / max(TF(:)); end

fsst的窗长会自动影响输出的时频分辨率。在多层融合前,我通过每一次都采样到相同的频率点数和时间点数,保证相加时矩阵维度一致。实际运行中,短窗 64 点输出时间点数较多,长窗 256 点输出时间点数较少,如果直接加会报维度错误。遇到这种情况,在融合前对时间轴做线性插值,把它统一到中窗的输出长度。这是一处很容易忽略的细节。

从计算成本看,三个窗分别进行同步压缩确实比单次 SST 慢,但故障诊断这类离线分析场景完全能接受。我测过一组 12000 点信号在单核 CPU 上跑 WMSST 大约需要 0.8 秒,换成 GPU 后能到 0.1 秒以内。如果你处理的是大型数据集,建议离线批量生成时频图并缓存到.mat文件,避免每次训练都重复计算。

3.3 数据集组织与训练样本制作

为了制作训练样本,我把每个类别生成的 1 秒长信号切成了多个片段,每个片段取 2048 点,滑动步长 512 点,这样每段信号能产生大约 23 个样本。每个样本调用一次wmsst_demo,输入为 2048 点的一维信号,输出时频图尺寸为 128×128 左右,我再统一 resize 到 128×128 以便网络输入。

样本制作好后需要把数组整理成dlarray能识别的格式。我选择把数据组织为H×W×C×N的四维数组,H 和 W 对应时频图的长宽,C 是通道数,这里为 1,N 是样本数。标签使用独热编码,配合onehotencode生成。

% 假设 X 是 [128 128 1 样本数],y 是分类标签向量 Y = onehotencode(y, 2, 'ClassNames', {'正常','内圈','外圈','滚动体'});

数据划分时,我对每个类别内部的样本顺序做了随机打乱,但必须保证同一时间窗附近的样本不拆到两个集合里。我采用按段划分的策略:每类生成 10 段长信号,取 6 段作训练,2 段作验证,2 段作测试,段内滑窗产生的样本全部归属于所在段。这样测试精度有说服力,网络是真没见过测试时段那些样本。

3.4 MCNN-GRU 网络的自定义构建

网络结构是三分支 MCNN 加 GRU 加全连接分类头。三分支的卷积核大小设为 3×3、5×5、7×7,每个分支包含两层卷积,每层输出 16 个特征图。三个分支在通道维度拼接后,再通过一个 1×1 卷积把总通道数降到 32,然后对特征图沿时间轴重排,得到序列输入给 GRU。

用layerGraph构建 MCNN 部分没问题,但在 CNN 与 GRU 衔接处,标准layerGraph不支持直接把二维特征图重排成序列。我在项目里改用了自定义训练循环,用dlnetwork定义特征提取部分,用gru函数手动执行前向传播。以下是可以直接运行的核心结构片段:

% 定义特征提取网络 layers = [ imageInputLayer([128 128 1], 'Name', 'input') convolution2dLayer([3 3], 16, 'Padding', 'same', 'Name', 'conv1_1') batchNormalizationLayer('Name', 'bn1_1') reluLayer('Name', 'relu1_1') convolution2dLayer([3 3], 16, 'Padding', 'same', 'Name', 'conv1_2') batchNormalizationLayer('Name', 'bn1_2') reluLayer('Name', 'relu1_2') maxPooling2dLayer([2 2], 'Stride', 2, 'Name', 'pool1') ]; dlnet = dlnetwork(layers);

实际训练时要写一个自定义循环:批次数据经过dlnet.predict得到特征图,extractdata取出后按时间维度切片,转成dlarray序列格式,再调用gru计算。GRU 的隐藏单元数我设为 64,输出接到一个fullyConnected加softmax的分类层。第一次调试时最容易报错的地方是序列维度的排列顺序,MATLAB 的gru函数要求输入格式为“特征×批次×时间步”,而三维特征图默认排在“时间×特征”上,必须用permute显式调整,否则只会得到一堆维度报错信息。

3.5 训练配置与评估

训练采用 Adam 优化器,初始学习率 0.001,mini-batch 大小 32,训练 50 个 epoch。验证损失连续 5 轮不下降时,学习率按 0.2 倍衰减。损失函数用交叉熵。这套配置在仿真信号上能稳定收敛,测试集准确率在 96% 左右。

评估阶段我用测试集算混淆矩阵,并输出每类样本的查准率和查全率。实际结果里,正常信号和滚动体故障最容易混淆,原因是滚动体故障冲击位置随保持架转动变化,时频图上同样呈现近似均匀分布的谱线,初期 MCNN 很难区分。通过调整 WMSST 融合权重,让短窗尺度占比更高,冲击时域位置的信息更突出,滚动体故障识别率提升了接近 5 个百分点。这个调整只改了一个权重系数,验证集精度就明显上来了。

% 测试阶段评估 [YPred, scores] = predict(dlnet, dlXTest); [~, idx] = max(scores, [], 1); YPredLabel = classNames(idx); figure; confusionchart(YTest, YPredLabel);

混淆矩阵图上能很直观地看到哪些类别互相干扰。如果有纵向的某一行数值明显偏低,通常意味着该故障的时频特征和别类混叠,优先回头调 WMSST 融合权重,而不是急着加深网络。

4. 常见问题与排查技巧实录

4.1 维度报错:CNN 输出如何进 GRU

这是整个项目里最典型的报错场景。gru函数的输入要求是dlarray且具有三个维度:特征维、批次维、时间步维。MCNN 输出的特征图是四维H×W×C×N,必须先重排成时间步×(H*W*C)×N的格式,再转成dlarray。更直白的做法是先把特征图按时间方向切成多个二维矩阵,每个二维矩阵展平后当作序列的一个时间步向量。我调通后的关键代码是:

featMap = predict(dlnet, dlX); featData = extractdata(featMap); % featData: [32 64 32 batch] 预期 [seqLen, featDim, ch, batchSize] = size(featData); featData = permute(featData, [1 3 2 4]); % 时间×通道×特征×批次 featData = reshape(featData, seqLen, ch*featDim, batchSize); featSeq = dlarray(featData, 'SSCB');

如果permute的维度顺序写错,后面gru一定报错。这个步骤我建议单独写一个函数并在里面加维度断言语句,报错时能第一时间定位。

4.2 WMSST 输出尺寸不统一

多窗长融合时,不同窗长的fsst输出时间点数不一样。我一开始因为没做插值,直接运行就报矩阵维度错误。处理办法是对短窗和长窗的时频图在时间轴上做插值,统一到中窗的时间点数。频率轴点数相对稳定,但为了保险,也一并插值到同一频率点数。插值方法用线性插值就够,高阶插值对时频图影响不大,反而容易引入震荡。

4.3 训练不收敛或震荡严重

训练损失在初期下降缓慢,多半是学习率过大或输入数据归一化没做好。我的做法是在进入网络前把每个时频图先做一次全局最大最小归一化,把幅值统一到 0 到 1 之间。如果仍然振荡,把学习率降到 0.0003,并增加gradientThreshold到 2,抑制梯度爆炸。GRU 部分对梯度敏感,不如纯 CNN 容易训练,所以梯度裁剪必须加上。

4.4 内存不足与训练过慢

时频图的尺寸每翻一倍,内存占用是平方级增长。如果一次生成的时频图全放内存不够,我会把每张图保存为单独的.mat文件,训练时用imageDatastore配合自定义读入函数动态读取。这种方法虽然慢一些,但能处理任意规模的数据集。批量大小也无需设太大,32 以内足够。

4.5 现场信号和仿真信号性能差距大

这是所有故障诊断项目都避不开的问题。仿真信号只包含理想冲击和谐波,现场信号里有大量的多源干扰、变转速、负载波动。我的经验是,跨到现场数据前,先在仿真信号里混入实测环境噪声底噪,给每个样本叠加随机幅值的谐波干扰,迫使网络学到抗干扰特征。经过加噪训练后的模型,在另一组未参与训练的高噪声样本上,精度比不加噪声训练的模型高出约 10 个百分点。


说实话,这套基于 WMSST 加 MCNN-GRU 的方案拿出来复盘,最值钱的不是某一个网络结构,而是“信号表征端做多尺度同步压缩、时序端用 GRU 追踪故障演化”这一整套组合思维。我在实际迭代里还被提醒过一点:MATLAB 自带fsst的默认参数偏保守,你真要把多尺度融合做起来,得自己动手写循环和插值。这些步骤看似简单,却是通往高精度的最后一公里。建议你在自己项目里先拿仿真信号把这条链路跑通,再逐步替换成实测数据,每一步替换都要重新做一次验证,别想着一步到位。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进