简介:面向故障诊断与数据分类研究者的Matlab完整源码包,聚焦滚动轴承、变压器油气等场景。模型结合马尔可夫场将一维时序信号转换为二维特征图,再通过1D-2D-CNN提取空间特征、GRU捕捉时序依赖,并引入Attention机制增强泛化能力,为多模态融合故障识别提供可复现的实验框架。压缩包共966个文件,以960张训练/测试过程生成的png图像为主,辅以3个m源码文件、2个xlsx数据表和1个txt说明,整体仅9.73MB,适合Matlab2023及以上版本运行。目前已有203人学习下载。内含MTF图像生成到模型训练的完整流程分步实现,覆盖训练集/测试集构造、特征融合与注意力机制应用;图片文件可直观展示分类效果与特征变换过程,xlsx表格记录实验数据,便于对照分析。资源可作为毕业论文、课程设计或算法对比的参考基线,帮助研究者快速掌握MTF与CNN-GRU-Attention融合思路。
1. 基于MTF的1D-2D-CNN-GRU-Attention到底解决什么问题
旋转机械的故障样本通常是一条一维振动信号,几千个采样点里藏着一个周期性的冲击特征。很多人在这一维信号上直接堆1D-CNN,堆到十几层还是打不上理想的准确率——不是网络不行,是一维信号里的时间依赖太隐蔽,卷积核只看局部波形,看不到“状态转移”的规律。MTF(马尔可夫转移场)先把信号按分位数划分成若干状态,再统计状态之间转移的概率,把一维时序变成二维纹理图像,2D-CNN的局部感受野平移不变性正好用来抓这种纹理。这套基于MTF的1D-2D-CNN-GRU-Attention多模态融合方案,就是把原始信号分支和MTF图像分支同时送进网络,再用GRU做时序建模、Attention做关键时间步加权,最后融合分类。对做故障诊断、状态监测方向研究学习的人来说,这是目前Matlab环境下最容易落地的“信号转图像 + 双分支融合”样板,CPU也能跑,重点在于理解编码、网络衔接和训练参数背后的取舍。
2. 把一维振动信号变成MTF图像:编码原理与Matlab实现
2.1 MTF编码为什么有效:马尔可夫转移场保留了信号的时间依赖
MTF的核心思想是“状态转移”。先把连续的一维信号按分位数切成Q个区间,每个采样点落在哪个区间,就叫它处于哪个状态。然后统计“当前状态是i、下一个时刻状态是j”的出现次数,得到一个Q×Q的马尔可夫转移矩阵W,W(i,j)就是状态i转移到状态j的概率。这样一条信号就被压缩成一张小图像,图像的纹理反映了状态转移模式。
关键点在于:普通灰度图把每个时间点的幅值当作像素值,像素之间没有时间关系;而MTF图像里的每个像素是“两个时间点的状态转移概率”,天然保留了时间依赖。齿轮裂纹、轴承外圈故障、内圈故障在振动波形上可能幅值接近,但状态转移的路径完全不同,转成MTF后纹理差异会被放大,这正是2D-CNN擅长区分的东西。
工程上要注意一个简化:标准的MTF是把转移概率按时间坐标展开成N×N的图像(N是信号长度),1280个采样点就是1280×1280,训练时计算量和内存都吃不消,还要再resize,纹理细节反而丢失。常见做法是直接把状态转移矩阵W当作MTF图像,尺寸固定为Q×Q,比如Q=64就是64×64的小图,既能进2D-CNN又保留转移模式。下面代码采用这种工程上的简化方案。
2.2 从信号到MTF的Matlab函数:分桶、转移统计、归一化
把下面的函数保存为signalToMTF.m,输入一段行向量信号和分位数个数Q,输出一张Q×Q的MTF图像。
function mtf = signalToMTF(x, Q) % signalToMTF 把一维信号编码成马尔可夫转移场图像 % 输入: % x : 1×N 的振动信号,行向量 % Q : 分位数个数,通常取 32 / 64 / 128 % 输出: % mtf : Q×Q 的转移概率矩阵,值域 [0,1],直接作为二维图像 % 统一成行向量,防止列向量导致索引混乱 x = x(:)'; % 1. 用分位数把连续信号离散成 Q 个状态 p = linspace(0, 1, Q+1); edges = quantile(x, p(2:end-1)); % Q-1 个分位点 edges = [-inf, edges, inf]; % Q 个区间的边界 state = discretize(x, edges); % state 中每个元素取值 1..Q % 2. 统计一阶马尔可夫转移矩阵 W = zeros(Q, Q); for t = 1:(length(state)-1) i = state(t); j = state(t+1); W(i, j) = W(i, j) + 1; end % 3. 按行归一化成概率,防止某个状态没出现导致除零 rowSum = sum(W, 2); rowSum(rowSum == 0) = 1; W = W ./ rowSum; % 4. 直接输出转移概率矩阵作为 MTF 图像 mtf = W; end这段代码的逻辑拆开看:linspace生成分位点位置,quantile算出对应的信号幅值边界;discretize把每个采样点映射到1到Q的状态编号;循环统计转移次数;最后每一行除以行和,使每一行加起来等于1,表示“从状态i出发,下一步转移到各状态的概率分布”。
参数说明:Q是唯一要调的参数,它同时决定图像尺寸和状态粒度。Q太小,状态分得粗,不同故障的转移模式容易混在一起;Q太大,矩阵稀疏且计算变重。如果信号长度只有几百个点,Q取32更稳妥;如果样本量大、信号平稳,Q取64是默认选择。
2.3 图像尺寸和Q值怎么定:64还是128
我自己的经验是Q=64优先。Q=64时图像是64×64=4096个像素,2D-CNN第一层用3×3卷积核,信息密度合适,训练一个epoch的时间在CPU上也就十几秒。Q=128时图像变成16384像素,细节更多但训练明显变慢,而且机械故障数据通常样本量不大,过多的图像细节容易让网络记住噪声。
| Q值 | MTF图像尺寸 | 状态粒度 | 适用场景 |
|---|---|---|---|
| 16 | 16×16 | 粗 | 信号短、类别少,快速验证 |
| 32 | 32×32 | 中等 | 小样本、样本数<500 |
| 64 | 64×64 | 较细 | 默认推荐,平衡信息和计算量 |
| 128 | 128×128 | 细 | 信号平稳、样本充足、追求上限 |
选Q之前先看一眼转移矩阵的稀疏度:如果W里超过一半元素是0,说明Q偏大,状态分得太碎;如果W里每一行都集中在少数几列,说明Q偏小。用imagesc(mtf)直接查看,图像纹理像“斑马纹”说明状态转移太规律,像“噪点”说明分桶没分好,检查信号里是否有 outliers 干扰了分位点计算。
有一种常见误解是把MTF当成图像增强手段,以为看到图像里有“冲击”才算成功。实际上MTF图像的像素是概率,不是幅值,颜色深浅代表转移可能性,所以图像看起来比较柔和是正常的。2D-CNN要学的是纹理走向和分布差异,不是具体的冲击峰值。
3. 双分支主干设计:1D-CNN分支、2D-CNN分支与GRU-Attention衔接
3.1 为什么用双分支:1D信号与MTF图像的信息互补
单一模态的问题在于信息不完整。原始一维信号保留了冲击的幅值、周期和瞬时波形,但周期性的变化规律需要很长的上下文才能看到;MTF图像把状态转移和缓变趋势浓缩成纹理,但失去了原始幅值尺度,两个不同故障如果转移模式接近,单靠图像也会混淆。
双分支解决这个问题:1D-CNN分支直接加工原始振动信号,用卷积核捕捉局部冲击特征;2D-CNN分支加工MTF图像,捕捉状态转移的全局纹理。两个分支在特征层融合,相当于让网络同时看到“信号长什么样”和“信号怎么跳变”,这是故障识别中很实用的多模态融合思路。
需要说明的是,这里的“多模态”指同一信号的不同视图,而不是传感器层面的多模态。如果后续有转速、温度等其它信号,可以加第三个输入分支,把输出维度对齐后接concat层即可。
3.2 分支结构:1D-CNN的卷积核尺寸与2D-CNN深度怎么搭配
网络结构设计遵循“浅层提取局部特征,深层提取语义特征”的原则。1D分支处理的是时间序列,卷积核不宜太大,7和5是常用尺寸;2D分支处理64×64的MTF图像,3×3卷积核配合最大池化降采样。GRU放在1D分支卷积层之后,对卷积提取的特征序列做时序建模;Attention放在GRU之后,对不同时间步的隐藏状态加权,让分类头更关注与故障相关的时刻。
| 分支 | 层结构 | 输出尺寸 |
|---|---|---|
| 1D分支 | conv1d(1→16, k=7, same) + BN + ReLU | 1280×16 |
| 1D分支 | conv1d(16→32, k=5, same) + BN + ReLU | 1280×32 |
| 1D分支 | GRU(hidden=64, OutputMode=sequence) | 1280×64 |
| 1D分支 | Attention(对时间步加权) | 64 |
| 2D分支 | conv2d(1→16, 3×3, same) + BN + ReLU + pool/2 | 32×32×16 |
| 2D分支 | conv2d(16→32, 3×3, same) + BN + ReLU + pool/2 | 16×16×32 |
| 2D分支 | conv2d(32→64, 3×3, same) + BN + ReLU + pool/2 | 8×8×64 |
| 2D分支 | globalAveragePooling(全局平均池化) | 64 |
| 融合头 | concat → FC(64) → ReLU → Dropout(0.5) → FC(numClass) | 类别数 |
注意GRU层有两个可选的OutputMode。如果设成last,GRU只输出最后一个时间步的隐藏状态,丢掉了中间过程,后面接不了Attention;如果设成sequence,输出全部时间步的隐藏状态(1280×64),Attention才有东西可以加权。完整复现Attention机制时,GRU必须用sequence模式。
3.3 在Matlab里搭出这个网络:layerGraph与自定义attention层
如果先用简化方案验证MTF编码和双分支融合是否有效,可以不接Attention,直接让GRU输出最后一个时间步的隐藏状态,与2D分支的全局池化特征concat后分类。下面是可用trainNetwork直接跑的layerGraph代码:
numClasses = 4; % 按你的故障类别数改 lgraph = layerGraph(); % ===== 分支1:原始一维信号 ===== b1 = [ sequenceInputLayer(1, 'Name', 'input1d') convolution1dLayer(7, 16, 'Padding', 'same', 'Name', 'conv1d_1') batchNormalizationLayer('Name', 'bn1d_1') reluLayer('Name', 'relu1d_1') convolution1dLayer(5, 32, 'Padding', 'same', 'Name', 'conv1d_2') batchNormalizationLayer('Name', 'bn1d_2') reluLayer('Name', 'relu1d_2') gruLayer(64, 'OutputMode', 'last', 'Name', 'gru1d')]; lgraph = addLayers(lgraph, b1); % ===== 分支2:MTF图像 ===== b2 = [ imageInputLayer([64 64 1], 'Normalization', 'none', 'Name', 'input2d') convolution2dLayer([3 3], 16, 'Padding', 'same', 'Name', 'conv2d_1') batchNormalizationLayer('Name', 'bn2d_1') reluLayer('Name', 'relu2d_1') maxPooling2dLayer([2 2], 'Stride', 2, 'Name', 'pool2d_1') convolution2dLayer([3 3], 32, 'Padding', 'same', 'Name', 'conv2d_2') batchNormalizationLayer('Name', 'bn2d_2') reluLayer('Name', 'relu2d_2') maxPooling2dLayer([2 2], 'Stride', 2, 'Name', 'pool2d_2') convolution2dLayer([3 3], 64, 'Padding', 'same', 'Name', 'conv2d_3') batchNormalizationLayer('Name', 'bn2d_3') reluLayer('Name', 'relu2d_3') globalAveragePooling2dLayer('Name', 'gap2d') flattenLayer('Name', 'flat2d')]; lgraph = addLayers(lgraph, b2); % ===== 融合头 ===== head = [ concatenationLayer(1, 2, 'Name', 'concat') fullyConnectedLayer(64, 'Name', 'fc_fusion') reluLayer('Name', 'relu_fc') dropoutLayer(0.5, 'Name', 'drop_fc') fullyConnectedLayer(numClasses, 'Name', 'fc_out') softmaxLayer('Name', 'softmax') classificationLayer('Name', 'classOut')]; lgraph = addLayers(lgraph, head); lgraph = connectLayers(lgraph, 'gru1d', 'concat/in1'); lgraph = connectLayers(lgraph, 'flat2d', 'concat/in2');concat这个层要求两个输入在除拼接维以外的维度一致:GRU输出是N×64(N是batch大小),flatten之后的2D分支输出也是N×64,所以拼接没问题。sequenceInputLayer对每个样本接收一个1×N的double矩阵,特征维度是1,时间步是N;这两点不要写反,否则会报维度错误。
要在Matlab里实现真正的可训练Attention,不要用自定义层硬接,Matlab没有开箱即用的时间步注意力层,自定义层里一旦用stripdims或extractdata,梯度就断了,训练出来的是个黑匣子。正确的做法是用dlnetwork加自定义训练循环,Attention部分用dlarray的向量化运算写在forward函数里。核心代码就几行:
% 假设 gruOut 是 GRU 所有时间步的隐藏状态,尺寸为 T×N×C % T:时间步数,N:batch 大小,C:隐藏单元数 % attnW 是 C×1 的可学习打分权重,attnB 是标量偏置 % 每个时间步的注意力分数 score = sum(gruOut .* attnW, 3) + attnB; % 得到 T×N score = softmax(score, 1); % 沿时间步做 softmax 归一化 % 用归一化权重对所有时间步加权求和,得到上下文向量 context = sum(gruOut .* score, 1); % 得到 1×N×C context = reshape(context, size(context, 2), []); % 变成 N×C这个Attention是加性注意力的简化版本:attnW是每个隐藏单元的重要程度,把每个时间步的所有隐藏单元加权求和成一个分数,再用softmax转成概率权重。由于全程都是dlarray运算,自动微分可以正常回传梯度。想要更复杂的注意力,把attnW换成两层MLP即可,但机械故障样本量通常不大,简化版更稳,不容易过拟合。
4. 数据组织与训练评估:从原始数据到混淆矩阵
4.1 数据集划分与输入格式:等长序列、图像张量与DataStore构建
故障诊断的常规做法是用滑动窗口把长振动信号切成长度相同、有重叠的样本,然后对每个样本调用signalToMTF生成对应的MTF图像。窗口长度选1280还是2560取决于故障特征周期;重叠率建议0.5到0.8,重叠率高能弥补样本量不足,加速收敛。
fs = 12000; % 采样率,按你的数据改 winLen = 1280; % 窗口长度,也是 GRU 的时间步数 stepLen = 256; % 步长,窗口重叠率75% signals = load('fault_data.mat'); % 你的原始振动信号,每行一段长信号 labels = signals.labels; % 对应标签,categorical 类型 % 滑窗切分 allSeq = {}; % 存一维信号,每项是 1×winLen allImg = []; % 存MTF图像,H×W×C×N allLabel = []; Q = 64; % MTF 分位数,与第2章保持一致 for s = 1:size(signals.data, 1) x = signals.data(s, :); nWin = floor((length(x) - winLen) / stepLen) + 1; for w = 1:nWin seg = x((w-1)*stepLen + 1 : (w-1)*stepLen + winLen); seg = seg - mean(seg); % 去直流 seg = seg / std(seg); % 幅值归一化,消除振幅差异 allSeq{end+1, 1} = seg; % 序列分支的数据 img = signalToMTF(seg, Q); allImg(:,:,1,end+1) = img; % 图像分支的数据 allLabel(end+1, 1) = labels(s); end end % 数据集划分:按类别比例分层划分 cv = cvpartition(allLabel, 'HoldOut', 0.2); trainIdx = cv.training; testIdx = cv.test;这段代码做完后,allSeq是cell数组,每个cell是一个1×winLen的double矩阵,供sequenceInputLayer使用;allImg是64×64×1×N的4-D数组,供imageInputLayer使用。去直流和幅值归一化很重要,如果不做,不同工况的全局幅值差异会主导网络学习,模型学到的不是故障类型而是幅值高低。
训练时需要把两个分支的数据打包成CombinedDatastore,否则trainNetwork没法同时喂两个输入:
dsSeq = arrayDatastore(allSeq(trainIdx)); % cell 数组,按观察读取 dsImg = arrayDatastore(allImg(:,:,:,trainIdx), 'IterationDimension', 4); dsLabel = arrayDatastore(allLabel(trainIdx)); dsTrain = combine(dsSeq, dsImg, dsLabel);arrayDatastore对cell数组默认每行一个观察,对图像按第4维迭代,读者只要按这个思路组织,就不会出现“Number of observations must match”这个报错。测试集构造方式相同,只是不用combine成训练格式,评估阶段单独处理。
4.2 训练参数:学习率、mini-batch、早停与梯度裁剪
训练选项直接用内置的trainingOptions,重点调下面几个参数。序列模型加双分支的收敛速度比普通CNN慢,学习率用1e-3不用太大;梯度裁剪阈值设为2,防止GRU训练中梯度爆炸;ValidationFrequency按迭代次数设,保证验证集每个epoch被评估一次。
options = trainingOptions('adam', ... 'InitialLearnRate', 1e-3, ... 'MiniBatchSize', 32, ... 'MaxEpochs', 80, ... 'L2Regularization', 1e-4, ... 'GradientThreshold', 2, ... 'Shuffle', 'every-epoch', ... 'ValidationData', {dsSeqTest, dsImgTest, dsLabelTest}, ... 'ValidationFrequency', floor(numel(trainIdx)/32), ... 'Verbose', true, ... 'Plots', 'training-progress'); net = trainNetwork(dsTrain, lgraph, options);这里ValidationData写成三元素的cell,trainNetwork会按顺序分别匹配input1d、input2d和分类层。验证频率取训练集样本数除以mini-batchSize,这样每个epoch恰好验证一次,方便观察过拟合开始的点位。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| InitialLearnRate | 1e-3 | 双分支+GRU结构较大,1e-2容易震荡 |
| MiniBatchSize | 32 | 显存小就16,图像是64×64影响不大 |
| GradientThreshold | 2 | GRU时间步长1280,不设容易梯度爆炸 |
| L2Regularization | 1e-4 | 防过拟合,样本少时1e-3更激进 |
| MaxEpochs | 80 | 配合早停,实际40轮左右就会收敛 |
4.3 模型评估:混淆矩阵、准确率与逐类召回率
训练完用predict在测试集上做推理。由于网络是多输入,测试数据也要用CombinedDatastore传入predict,否则会报输入数量不匹配。预测分数是N×numClasses,取每一行的最大值作为预测标签。
dsTest = combine(dsSeqTest, dsImgTest); scores = predict(net, dsTest); [~, predIdx] = max(scores, [], 2); predLabel = categorical(predIdx, 1:numClasses, categories(allLabel)); % 整体准确率 acc = mean(predLabel == allLabel(testIdx)); % 混淆矩阵 figure; cm = confusionchart(allLabel(testIdx), predLabel); cm.Title = sprintf('测试集准确率: %.2f%%', acc*100); cm.RowSummary = 'row-normalized'; % 每行显示召回率百分比 cm.ColumnSummary = 'column-normalized';confusionchart的RowSummary设成row-normalized后,每个单元格显示该行类别的召回率,能直接看出哪些故障类别容易被混淆。比如外圈故障和滚动体故障在MTF图像上纹理接近,混淆矩阵里这两行会明显偏浅,这时候就要回到数据增强或增大Q值去补。
5. 避坑与排查:MTF失真、分支失衡与自定义层翻车
5.1 MTF图像像噪点,2D-CNN什么都学不到
现象:用imagesc画MTF图,图像没有纹理,全是杂乱的明暗点,训练时2D分支loss几乎不下降。
原因:信号里存在异常冲击或尖峰,分位点计算被极端值带偏。比如一个幅值为正常值10倍的毛刺会让几乎所有采样点落进同一个分桶,状态转移矩阵只有一两个状态有值,图像自然退化成噪点。
解决:在调signalToMTF之前先对信号做削波或中值滤波,去掉明显离群点。可以用prctile(x, 99)作为阈值把峰值截断,再做分位数分桶。还有一种情况是Q取得太小,比如Q=8,状态数不够导致图像像素分布过于离散,Q至少从32起。
5.2 1D分支loss下降快,2D分支几乎不动
现象:训练日志里1D分支方向的损失在正常下降,但把两个分支单独拿出来验证时,2D分支的准确率一直徘徊在随机水平,融合后效果反而比单用1D分支差。
原因:两个分支的梯度量级严重不平衡。MTF图像像素是0到1的转移概率,数值范围小,2D分支的梯度也相应小,在同样学习率下更新缓慢;1D分支的输入是归一化后的振动幅值,动态范围大,梯度占主导,融合层被1D分支“带跑”。
解决:给不同分支设置不同学习率。在自定义训练循环里分别用两个adam优化器,2D分支学习率设为1e-2,1D分支保持1e-3;如果在layerGraph里不方便分开设置,就在融合前对2D分支特征加一个batchNorm,把特征动态范围拉平。另一个低成本方案是把MTF图像乘上一个缩放系数,比如乘以10,人为放大梯度。
5.3 自定义Attention训练时loss变成NaN
现象:加入Attention后,前几个iteration正常,某个batch后loss突然变成NaN,之后再也回不来。
原因:softmax之前score出现过大的正数或负数,导致softmax输出接近one-hot,梯度消失或溢出。GRU隐藏状态如果未经约束,随着时间步累积会越来越大,score = sum(H.*attnW) 很容易达到几百的量级。
解决:在GRU后面接一个layerNormalizationLayer,或者对attnW做更小的初始化,比如用randn乘0.01。学习率也要降,1e-3以上很容易爆。强烈建议开启GradientThreshold,数值设为2,这能在梯度爆炸时截断更新方向而不是让参数直接飞掉。
5.4 训练集几乎100%,验证集只有70%
现象:训练曲线显示模型在训练集上快速收敛到95%以上,验证集准确率却停滞在70%左右,中间还有明显震荡。
原因:机械故障公开数据集普遍样本量不大,每类几百个样本,双分支网络参数多,特征空间足够大,直接背下了训练集的噪声。另一个原因是滑窗重叠率太低,同一条信号切出来的窗口相关性弱,有效样本数不足。
解决:先把滑窗重叠率提到0.8,让同一故障的局部冲击多次出现;再把dropout提高到0.6,L2正则调到1e-3。如果还不过拟合,把2D分支的卷积核数量减半——MTF图像本身信息密度有限,不需要像ImageNet那样宽的网络。
5.5 trainNetwork报错:Number of observations must match
现象:把序列数据和图像数据combine之后,trainNetwork直接报错,提示两个datastore的观察数量不一致。
原因:arrayDatastore对cell数组默认把“每个元素”当作一个观察,对图像arrayDatastore如果不指定IterationDimension,默认按第一维迭代,64×64×1×N的数组会被当成64个观察而不是N个,观察数量自然对不上。
解决:图像分支的arrayDatastore必须显式指定'IterationDimension', 4,表示第4维是观察维。同理,如果原始信号是多通道,序列数据是cell数组且每个cell是c×T矩阵,那观察维在cell数组维度,arrayDatastore默认行为正确,不需要额外设置。
6. 进阶技巧:注意力可视化、交叉验证与轻量变体
Attention机制除了加权,还有一个很实用的副产物——注意力权重本身可以解释模型在看什么。对测试集里某个样本跑一次前向,把attention score取出来,画成时间轴折线,通常能看到权重集中在故障冲击的起始段。做法是在训练循环的forward函数里额外返回score,不取argmax,而是用mean(score, 2)得到每个时间步的平均权重,再smooth一下。我自己的习惯是把权重导出成CSV,用外部绘图工具看,比Matlab自带图更清晰,也方便放进论文。
模型选型时不要只用单次划分的结果。振动数据相邻窗口之间高度相关,随机划分会导致测试集里混入训练集相邻窗口的信息,准确率虚高。建议用5折交叉验证,每一折都重新滑窗、重新训练,把5次的平均准确率和标准差当最终指标。标准差超过2%就说明模型不稳定,优先检查数据划分而不是继续堆网络深度。
2D分支如果要引入空间注意力,不建议直接上Transformer那套自注意力,样本量撑不住。可以试试coordinate attention这类轻量机制,它把水平和垂直方向的特征分别池化再融合,参数开销几乎为零。在Matlab里用一个自定义层包住,插入到conv2d_2之后,效果比多头注意力在故障数据上更实在。
这套方案的瓶颈不在网络,而在MTF编码。Q值和窗口长度选好,注意力加不加都能到90%以上;选不好,加什么模块都白搭。希望帮到你。
当前是第1次要求你,请整理语言后重新输出.
本文还有配套的精品资源,点击获取