简介:本资源面向具备一定数学基础与MATLAB编程经验的研究人员、工程师,聚焦机械工程、工业互联网与人工智能交叉领域的轴承故障诊断需求。内容围绕小波时频图与Swin Transformer的融合方法展开,讲解如何将振动信号经小波变换转为时频图,再由Swin Transformer完成多尺度特征提取与深层特征学习,从而提升故障分类的准确性与鲁棒性,并涉及噪声干扰、多故障类型辨识、样本与标签不足、计算成本偏高等挑战的应对思路。压缩包共1个docx文件,约60KB,以文字与代码详解形式呈现项目背景、模型架构、技术难点、创新点及GUI设计思路,目录按项目目标与意义、挑战与解决方案、特点与创新、应用领域等模块组织,便于系统性阅读。已有84人学习,适合希望将小波时频分析与深度学习用于设备在线监测、自动化故障分类与预警实践,并借鉴完整程序实例的读者参考。
1. 振动信号到二维时频图,再到 Swin Transformer 的诊断链路
轴承故障诊断的老路子是算时域指标(峭度、均方根、峰值因子)或做包络谱,再靠人工阈值切档位,换一台设备、换一个转速,阈值就得重调。小波时频图换了个思路:把一段振动信号变成二维图像,横轴时间、纵轴频率、颜色深浅表示能量,滚动体剥落、内圈裂纹这类冲击成分在图上表现为等间隔的竖直条纹,肉眼能看,卷积网络和注意力网络也能学。Swin Transformer 用窗口划分加位移窗口,把注意力的计算复杂度压到与图像尺寸近似线性,同时保留跨窗口的全局建模能力,对时频纹理高度相似的内圈、外圈、滚动体故障,区分度通常比纯 CNN 稳一些。整条链路在 MATLAB 里是闭环的:读振动信号,做连续小波变换出时频图,喂给搭建好的 Swin Transformer 训练分类器,最后用 GUI 把加载、绘图、推理、导出四步包成一个可交付的小工具。适合做设备状态监测的工程师、用 MATLAB 做深度学习的研究生,以及需要把算法交付给现场人员的那类项目。
2. 用小波时频图把一维振动信号转成可训练的图像数据集
2.1 连续小波变换在轴承故障诊断里的选型理由
短时傅里叶变换的窗长固定,低频段分辨率不够,冲击成分容易糊在噪声里。连续小波变换(CWT)用可伸缩的母小波,低频用长窗、高频用短窗,正好对上轴承冲击「高频载波 + 低频重复频率」的物理结构。MATLAB 的cwtfilterbank把滤波器组预先算好,批量处理时复用同一个对象,比每次调用cwt快得多,这是我处理几千段信号时最先做的一处优化。
母小波选amor(Morlet 的解析形式)是常见做法。它时频聚集性好,对衰减振荡型冲击响应敏感。morse小波在能量集中度上也不错,但参数多,调参成本高;bump适合平稳信号,对冲击不敏感。工程上直接上amor就够用。
VoicesPerOctave控制每个倍频程内的频率采样点,默认 10,取 12 到 16 图像更细腻,代价是矩阵变大。FrequencyLimits我一般设成[10 fs/2],把工频和直流附近的无用能量切掉,图的对比度会明显提升。
2.2 MATLAB 批量生成时频图并落盘的完整脚本
下面这段脚本做四件事:读入原始信号、去趋势加带通、分段做 CWT、按类别写进文件夹。分类数据集用文件夹名当标签,MATLAB 的imageDatastore能直接读,不用再维护标签表。
% buildTFImages.m —— 批量生成轴承时频图数据集 clear; clc; fs = 12000; % CWRU 驱动端采样率,12 kHz winLen = 2048; % 每段信号长度,约 170 ms overlap = 0.5; % 段间重叠,缓解样本量不足 imgSize = [224 224]; % 与 Swin 输入尺寸对齐 dataDir = fullfile(pwd, 'cwru'); % 原始 .mat 存放目录 outDir = fullfile(pwd, 'tfdataset'); % 输出图像根目录 if ~exist(outDir, 'dir'); mkdir(outDir); end % 滤波器组只构建一次,循环里复用,避免重复计算 fb = cwtfilterbank('SignalLength', winLen, ... 'SamplingFrequency', fs, ... 'Wavelet', 'amor', ... 'VoicesPerOctave', 12, ... 'FrequencyLimits', [10 fs/2]); files = dir(fullfile(dataDir, '*.mat')); for k = 1:numel(files) S = load(fullfile(files(k).folder, files(k).name)); fn = fieldnames(S); x = S.(fn{1}); % 变量名不固定,取第一个变量 x = double(x(:)); x = detrend(x); % 去趋势,消掉慢漂移 x = bandpass(x, [10 5000], fs); % 带通,压掉工频和高频噪 x = x / (max(abs(x)) + eps); % 整段归一化,防止幅值漂移 step = round(winLen * (1 - overlap)); nSeg = floor((numel(x) - winLen) / step) + 1; for s = 1:nSeg i0 = (s-1)*step + 1; seg = x(i0 : i0 + winLen - 1); seg = seg - mean(seg); % 逐段去均值 cfs = wt(fb, seg); % 复数矩阵:频率 x 时间 img = abs(cfs); img = log10(img + 1e-8); % 对数压缩,突出弱冲击 img = img - min(img(:)); % 线性拉伸到 [0,1] img = img / (max(img(:)) + eps); img = imresize(img, imgSize, 'bicubic'); img = im2uint8(img); % 存 8 位灰度图,省空间 label = regexp(files(k).name, '^([A-Za-z0-9]+)_', 'tokens', 'once'); labelDir = fullfile(outDir, label{1}); if ~exist(labelDir, 'dir'); mkdir(labelDir); end imwrite(img, fullfile(labelDir, sprintf('seg_%04d.png', s))); end end参数说明:winLen决定频率分辨率下限,2048 点在 12 kHz 下覆盖约 0.17 s,能容纳两到三个冲击周期;再短会切碎重复频率,再长则样本量骤减。overlap取 0.5 是经验值,重叠太大会让训练集和验证集高度相关,评估结果虚高。imresize的bicubic比默认的最近邻更平滑,避免插值引入阶梯状伪影。
注意:训练、验证、测试三个子集的划分必须在分段之后按「段」随机划分,不能按「文件」划分后各自分段,否则同一段信号的相邻片段会跨集泄漏。
2.3 数据增强与类别不均衡的处理方式
时频图增强不能照搬自然图像的翻转。水平翻转等于把时间轴倒过来,破坏冲击的先后顺序,物理上不成立;竖直翻转等于把频率轴倒过来,同样不合理。能用的增强有这几类:
| 增强方式 | MATLAB 实现 | 适用性 |
|---|---|---|
| 小幅平移 | imtranslate,位移不超过 8 像素 | 时间起点不同,物理合理 |
| 加性高斯噪声 | imnoise(img,'gaussian',0,0.01) | 模拟传感器底噪波动 |
| 随机裁剪再缩放 | imresize(imcrop(img,[8 8 208 208]),[224 224]) | 轻微尺度扰动 |
| 亮度/对比度抖动 | imadjust(img, [0.02 0.98], []) | 模拟增益变化 |
| Mixup | 两张图按比例线性混合,标签同步加权 | 小样本下提点明显 |
当正常样本远多于故障样本时,用imageDatastore配合countEachLabel先看清分布,再用augmentedImageDatastore做在线增强,或者在损失里加类别权重。重症样本量少于一成的场合,先把少数类过采样到接近多数类,再叠加 Mixup,比直接改损失函数更稳。
3. 在 MATLAB 中复现 Swin Transformer 的窗口注意力结构
3.1 Swin Transformer 与纯 CNN 在时频图上的差别
时频图上的故障特征有两类尺度:细尺度是单个冲击的竖直亮纹,粗尺度是亮纹之间的间隔规律。纯 CNN 靠堆卷积层扩感受野,浅层拿细节、深层拿全局,中间隔着好几层池化,细粒度的间隔规律容易被抹掉。Swin Transformer 用 4×4 的 patch 起步,每个 stage 通过 PatchMerging 下采样两倍,通道数翻倍,四层之后感受野覆盖全图,同时每一层的窗口内自注意力都在原地保留细节。
关键的位移窗口(Shifted Window)机制解决的是「窗口之间不通信」的问题:第 2、4 个 block 把特征图整体偏移半个窗口,再重新划分窗口做注意力,等价于让相邻窗口之间产生交叉,成本却没有增加。这和 CNN 的空洞卷积目的一样,但注意力的权重是数据驱动的。
W-MSA和SW-MSA交替堆叠,是 Swin 最容易写错的地方。如果只写窗口内注意力不做位移,网络退化成局部注意力网络,在轴承故障这种靠长程周期判别的任务上,准确率通常掉 3 到 5 个百分点。
3.2 窗口划分与还原的 MATLAB 实现
MATLAB 深度学习工具箱没有内置 Swin,需要自己写窗口切分函数,再用dlnetwork和自定义层串起来。数据布局统一用[H W C B],中间量用[wh*ww C nWin B]。
function [x, info] = windowPartition(x, winSize) % 把 [H W C B] 切成不重叠窗口,输出 [wh*ww C nWin B] [H, W, C, B] = size(x); wh = winSize(1); ww = winSize(2); nH = H / wh; nW = W / ww; info = [H W C B wh ww]; x = reshape(x, wh, nH, ww, nW, C, B); x = permute(x, [1 3 5 2 4 6]); % [wh ww C nH nW B] x = reshape(x, wh*ww, C, nH*nW, B); end function x = windowReverse(x, info) % 把窗口拼回 [H W C B] H = info(1); W = info(2); C = info(3); B = info(4); wh = info(5); ww = info(6); nH = H / wh; nW = W / ww; x = reshape(x, wh, ww, C, nH, nW, B); x = permute(x, [1 4 2 5 3 6]); % [H W C B] x = reshape(x, H, W, C, B); end逻辑说明:reshape先把空间维拆成「窗口内坐标 + 窗口编号」,permute把窗口两维挪到前面,再reshape合并成序列维。还原时按逆序操作。windowReverse依赖info里保存的原始尺寸,位移窗口的还原要用circshift反向移回来,否则图像会错位。
位移版划分在调用前先整体循环移位:
function x = shiftAndPartition(x, winSize, shiftSize) x = circshift(x, [-shiftSize(1), -shiftSize(2), 0, 0]); x = windowPartition(x, winSize); endshiftSize一般取winSize/2,即窗口 7 时移 3。
3.3 相对位置偏置与 attention mask 的写法
窗口内自注意力之外,Swin 还给每个注意力头加一组可学习的相对位置偏置。把相对坐标展平后建一个(2M-1)^2大小的偏置表,训练时跟着一起更新。
function bias = relativePositionBias(winSize, numHeads, params) % 生成相对位置偏置查表索引 M = winSize(1); coords = 1:M; [xx, yy] = meshgrid(coords, coords); xx = xx(:); yy = yy(:); relX = xx - xx'; % [M^2 M^2] relY = yy - yy'; relX = relX + M - 1; % 偏移到 0..2M-2 relY = relY + M - 1; idx = relX * (2*M - 1) + relY + 1; % 扁平化索引 bias = params.T(idx, :); % T: [(2M-1)^2, numHeads] bias = permute(bias, [1 2 3]); % 后续 reshape 到 [N N numHeads] end位移窗口还必须配一个 mask。因为位移后重新切窗口,一个窗口里可能混进原本不相邻的两个区域,不能让它们互相注意。做法是给不该注意的位置在 softmax 前加大负数:
attn = attn + relBias + maskMat; % maskMat 中屏蔽位置为 -100 attn = softmax(attn, 'DataFormat', 'CB');maskMat在每次前向传播前根据当前图像尺寸算一次并缓存,不必每层重算。窗口尺寸 7、特征图 56×56 时,一个 batch 的 mask 矩阵约几十 MB,缓存一份即可。
3.4 网络组装与训练参数配置
把 PatchEmbed、若干 Swin Block、PatchMerging、全局平均池化和全连接拼成dlnetwork。训练用自定义循环,因为trainNetwork对多输入自定义层的支持有限。
% 组装主干 layers = [ patchEmbedLayer(4, 96, 'Name', 'patch_embed') swinStage(96, 2, 3, 7, 'Name', 'stage1') patchMergingLayer('Name', 'merge1') swinStage(192, 2, 6, 7, 'Name', 'stage2') patchMergingLayer('Name', 'merge2') swinStage(384, 6, 12, 7, 'Name', 'stage3') patchMergingLayer('Name', 'merge3') swinStage(768, 2, 24, 7, 'Name', 'stage4') globalAvgPoolLayer('Name', 'gap') fullyConnectedLayer(10, 'Name', 'fc') softmaxLayer('Name', 'prob')]; net = dlnetwork(layers);参数说明:patchEmbedLayer(4, 96)表示 4×4 patch、96 通道;swinStage(C, nBlock, nHead, M)里nBlock是块数、nHead是注意力头数、M是窗口边长。Swin-Tiny 的配置是{2, 2, 6, 2}块数、{3, 6, 12, 24}头数,总参数量约 28M,输入 224×224 时在单张 12 GB 显存上 batch size 取 16 比较稳。
| 参数 | 常用取值 | 调整影响 |
|---|---|---|
MiniBatchSize | 16 / 32 | 显存不够先降这个,别降图像尺寸 |
InitialLearnRate | 3e-4 到 1e-4 | 太大前几个 epoch 就震荡 |
L2Regularization | 0.05 | 小样本下防过拟合,0.1 会欠拟合 |
MaxEpochs | 60 到 100 | 配合验证集早停 |
GradientDecayFactor | 0.9 | AdamW 常用值 |
LearnRateDropPeriod | 10 | 每 10 轮降一半 |
4. App Designer 构建轴承故障诊断 GUI 的完整步骤
4.1 界面控件布局与回调划分
用 App Designer 新建一个 Blank App,主图分成左右两块。左边放两个UIAxes:上面显示时频图,下面显示十类故障的概率条形图。右边从上到下依次是文件选择按钮、采样率输入框、诊断按钮、结果文本区、导出报告按钮。控件命名建议用BtnLoad、BtnDiagnose、AxesTF、AxesProb这种前缀式,后面对回调函数定位快。
回调函数按职责拆成三类:IO 类只负责读文件写文件,处理类调用信号处理函数,推理类只管predict和画图。这样拆过之后,算法更新时只动处理类,界面代码基本不动。
注意:
UIAxes上直接调imshow需要指定Parent参数,否则会弹独立窗口,这是 App Designer 新手最常踩的坑。
4.2 加载信号到输出诊断结论的一条龙代码
以下三个回调构成核心流程,写在一个.mlapp里即可。
function BtnLoadPushed(app, event) [f, p] = uigetfile({'*.mat;*.csv;*.txt', '振动信号'}, '选择信号文件'); if isequal(f, 0); return; end app.SignalPath = fullfile(p, f); app.LblStatus.Text = ['已加载: ' f]; end function BtnDiagnosePushed(app, event) if isempty(app.SignalPath) uialert(app.UIFigure, '请先加载信号文件', '提示'); return; end fs = app.FsEditField.Value; % 采样率从界面读,默认 12000 x = readSignal(app.SignalPath); % 支持 mat / csv / txt 三种 x = double(x(:)); x = detrend(x); x = bandpass(x, [10 min(5000, fs/2-1)], fs); x = x / (max(abs(x)) + eps); seg = x(1:min(2048, numel(x))); img = signal2tfimage(seg, fs, app.FilterBank); % 与训练完全一致 app.TFImage = img; imshow(img, 'Parent', app.AxesTF); % 灰度图展示 dlX = dlarray(repmat(img, 1, 1, 3, 1), 'SSCB'); % 复制成三通道 scores = predict(app.Net, dlX); scores = extractdata(scores); [pMax, idx] = max(scores); app.LastScores = scores; bar(app.AxesProb, scores, 'FaceColor', [0.3 0.6 0.9]); app.AxesProb.XTickLabel = app.ClassNames; app.LblResult.Text = sprintf('诊断结论: %s (置信度 %.2f%%)', ... app.ClassNames{idx}, pMax*100); end function BtnExportPushed(app, event) [f, p] = uiputfile('*.pdf', '保存诊断报告'); if isequal(f, 0); return; end fig = figure('Visible', 'off', 'Position', [100 100 800 600]); subplot(2,1,1); imshow(app.TFImage); title('小波时频图'); subplot(2,1,2); bar(app.LastScores); title('各类故障概率'); xticklabels(app.ClassNames); xtickangle(45); exportgraphics(fig, fullfile(p, f), 'ContentType', 'vector'); close(fig); app.LblStatus.Text = ['报告已保存: ' f]; end逻辑说明:signal2tfimage是把第 2 章的生成逻辑封装成独立函数,滤波器组对象存在app.FilterBank里,App 启动时构建一次,避免每次点击都重建。推理时输入通道数必须和训练一致,灰度图统一repmat成三通道,这一步漏掉会直接报维度不匹配。
4.3 模型加载、路径管理与打包发布
App 启动时用startupFcn加载训练好的dlnetwork:app.Net = load('swin_bearing.mat').Net;,同时构建滤波器组和类别名。文件路径统一用相对路径加fullfile拼接,不要用绝对路径,否则打包成 exe 之后在别人机器上必然找不到模型。
打包用 MATLAB Compiler 的 Application Compiler,把.mlapp主文件和swin_bearing.mat、signal2tfimage.m、readSignal.m一起加进「Files required for your application to run」。生成的 exe 需要目标机器装对应版本的 MATLAB Runtime,Runtime 版本必须和编译机一致,这个坑在交付现场经常出现。
5. 训练之后的调参与排错技巧
5.1 学习率 warmup 与 batch size 的联动
Swin 这类带自注意力的网络前几百步梯度方差大,直接上 1e-3 很容易在第一个 epoch 就把权重推到坏区域。我一般做 5 个 epoch 的线性 warmup,从 1e-6 爬到 1e-4,再按余弦退火降到 1e-5。batch size 和初始学习率大致按平方根关系同步放大,batch 从 16 加到 64 时学习率乘 2 就够,乘 4 往往过冲。
lr = 1e-6 + (1e-4 - 1e-6) * min(1, epoch / 5); % warmup lr = 1e-5 + 0.5*(1e-4 - 1e-5)*(1 + cos(pi*epoch/60)); % 余弦退火过拟合的早期信号是训练损失继续降而验证损失两三轮不降,此时先查验证集的划分有没有跨段泄漏,再考虑加 L2 或提高增强强度。数据量超过两万张时,L2 设 0.05 通常比 0.1 效果更好。
5.2 几个高频报错的定位思路
| 报错信息 | 常见原因 | 处理方式 |
|---|---|---|
Dimensions of arrays being concatenated are not consistent | 自定义层输出格式与下一层不匹配 | 每层显式写Formattable并在predict里打印 size |
Out of memory on device | batch 或图像尺寸过大 | 降MiniBatchSize,检查是否忘了dlarray转 GPU |
Unable to resolve the name 'windowPartition' | 函数文件不在路径 | addpath或用打包后的 exe 时未加入依赖 |
| 准确率停在 10% 附近 | 标签顺序与ClassNames不一致 | imageDatastore的Labels与网络输出列逐一核对 |
| 验证集准确率远高于测试集 | 同段信号跨集泄漏 | 按段随机划分,删掉重叠片段 |
cwtfilterbank报 SignalLength 不匹配 | 段长在预处理时被改动 | 固定winLen,预处理后不裁剪 |
排错顺序建议从数据侧开始:先固定一个 200 张图的小子集,用最简单的三层 CNN 训到接近 100%。如果小 CNN 都学不动,问题在标签或图像生成;如果小 CNN 能学动而 Swin 不行,问题在自定义层或 mask 实现。
5.3 用混淆矩阵和 t-SNE 判断模型学到了什么
confusionchart直观,但只能看最终分类结果。想知道特征空间有没有真的分开,取倒数第二层的输出做 t-SNE:
feat = predict(dlnetwork(net.Layers(1:end-2)), dlX); % 去掉 fc 和 softmax feat = extractdata(feat); % [C B] Y = tsne(feat', 'NumDimensions', 2, 'Perplexity', 30); gscatter(Y(:,1), Y(:,2), labels);同一类在 t-SNE 图上散成两三团,说明这一类内部存在两个子模式,常见于不同负载条件下的同一故障。这时要么在数据集里补足该条件下的样本,要么在训练时把负载作为一个辅助输入分支。混淆矩阵里内圈和外圈互相错分,则多半是时频图频率范围设得过窄,把区分两类故障的特征频带切掉了,把FrequencyLimits上限从 5000 Hz 调到 6000 Hz 再训一次,通常能看到改善。
本文还有配套的精品资源,点击获取