☰
STFT与CNN/ResNet结合:Matlab实现旋转机械故障诊断完整指南
2026/10/20 17:27:38 网站建设 项目流程

做旋转机械的故障诊断,最头疼的往往不是找不到特征,而是特征太“躲”了。轴承外圈剥落、内圈裂纹、齿轮断齿,这些故障在振动信号里留下的是一串瞬态冲击,冲击能量往往比正常运行的背景振动低一个数量级,直接时域波形看,早期故障完全淹没在噪声里;直接做FFT看频谱,虽然能把周期性脉冲转换成离散谐波,可故障脉冲发生在什么时间区间、特征频率是否随时间漂移,这类关键信息又被平均掉了。这就是时频分析登场的理由。

把短时傅里叶变换(STFT)和卷积神经网络(CNN)、残差网络(ResNet)结合起来做故障诊断,是目前工程里特别容易复现、也特别适合入门的一条技术路线:STFT负责把一维振动信号变成二维时频图,把“频率成分随时间怎么变化”这个物理过程可视化;CNN和ResNet负责自动从这些时频图里学故障类型对应的空间模式。本文把我实际跑通这套流程的经验整理出来,搭配Matlab代码,从时频图参数怎么选、网络怎么搭、数据怎么准备,到训练过程中层出不穷的坑,一次说清。适合刚接触故障诊断方向的学生,也适合需要在工业现场快速验证算法的工程师。

1. 先想清楚:为什么要先做STFT,再让神经网络上场

1.1 时域、频域各自的局限,以及时频联合作战的必要性

时域分析最直观,幅值随时间变化,能看到冲击、晃振、调制。问题在于振动信号是多种成分的叠加:轴的转频、齿轮的啮合频率、轴承的固有频率、随机噪声混在一起,幅值层面的异常往往不够明显。一个轻微的外圈故障,产生的冲击能量可能比正常振动小几十倍,靠眼力或峭度指标能发现异常,但要区分“外圈故障还是内圈故障”,单纯看时域波形很难回答。

频域分析把信号变换到频率轴,能量集中度大幅提高。FFT能清楚显示哪些频率成分能量高,通过包络解调还能把高频冲击包络中隐含的故障特征频率提取出来。但FFT有一个硬伤:它是全局平均。假设设备的转速在做调整,或故障特征频率在缓慢漂移,频谱里会把这些时变信息模糊成一片宽带,特征频率不再尖锐。严格说,机器人开机、停机、变速过程里的振动信号都是非平稳的,全局频谱天然不适合描述非平稳过程。

时频分析同时保留时间维度和频率维度的信息。STFT的做法很朴素:把长信号切成一个个短段,每个短段分别做FFT,再把各段的频谱按时间顺序排列成一张二维图。横轴是时间,纵轴是频率,颜色深浅代表该时刻该频率的幅值。这样,一个非平稳信号里“哪个时间点出现冲击”“冲击集中在哪些频带”就一目了然了。对于旋转机械故障,等间距冲击脉冲在时频图上表现为一排竖直的亮条纹,转频和啮合频率则是水平方向的恒定亮线,不同的故障类型会形成不同的时频纹理。

1.2 一张时频图对CNN来说意味着什么

很多人把CNN当成一个黑箱分类器,但也得理解它擅长什么。CNN的核心归纳偏置是局部性、平移不变性和层次化特征。卷积核在图像上滑动,先提取边缘、条纹、斑点等低级特征,再在后面的卷积层组合成纹理和部件级特征。时频图恰好是这种特征类型的天然载体:竖条纹反映周期性冲击,横条纹反映稳定的频率分量,斜条纹反映变频信号,亮斑反映瞬态能量集中区。这些几何模式本身就是诊断特征,跟CNN的卷积机制高度匹配。

所以,STFT和CNN的组合,本质上是把故障诊断问题转换成图像识别问题。网络不需要理解采样率、特征频率、边带调制这些物理量,只要它能从时频图中判别出不同故障类别的纹理差异即可。这带来了两个实际好处:一是绕开了人工特征设计,二是在样本量足够时,模型能自动发现工程师也未见得能预见的模式。

1.3 时频图与一维信号直接分类的对比逻辑

也有不少方案直接把原始振动信号喂给一维CNN,甚至用Transformer。这条路能走通,但不代表在所有场景都合适。我做过对比实验,一维CNN要达到相近的准确率,通常需要多一倍的训练样本,而且对切分位置特别敏感:信号相位偏移一个周期,一维网络学到的特征可能完全乱套,需要靠大量数据去“覆盖”这种相位变化。对时移不敏感这个特性,一维卷积只能靠池化和数据增强硬扛,而二维时频图天然具备这个优势——同一个故障的脉冲只要落在窗口内,时频图的纹理结构几乎不变,只是整体左右平移了一点。

另一个常被忽略的点是噪声。振动信号的干扰往往是宽带的,直接在时域做卷积,噪声和故障冲击混在同一维度里。STFT相当于在频域上做了一次带通分解,信号能量按频率分配到不同区间,某些频带里信噪比立刻提高。CNN只在时频图上学习,相当于提前做了一次人工检波,模型压力小很多。下表简单对比了两种输入形态:

输入形态优势短板
一维原始波形信息完全无损,预处理简单对相位/切分位置敏感,样本需求高,噪声干扰大
二维时频图抗噪强,对时移不敏感,符合CNN归纳偏置有STFT参数选择成本,频率分辨率受窗长限制

2. STFT参数选择:窗函数、窗长、重叠率之间的三方博弈

2.1 三种常见窗函数的特点与适用场景

STFT的第一步是加窗。窗函数决定了每段信号截取时的幅值加权方式,直接影响频谱泄漏程度。只要不是对纯正弦信号做整周期截取,矩形窗就会因为边界突变产生严重的频谱泄漏,谱线拖尾会把微弱的故障边带淹没,所以故障诊断领域几乎不用。

汉宁窗(Hann)是我的默认选择。它的主瓣和旁瓣衰减比较均衡,旁瓣衰减约31dB,能量主要集中在主瓣内,对旋转机械这种“强周期成分+微弱故障成分”共存的信号非常友好。汉明窗(Hamming)和汉宁窗外形很像,但旁瓣幅值稍高、衰减稍慢,通信领域用得多,振动诊断里没必要特别偏好。布莱克曼窗(Blackman)的旁瓣衰减高达58dB,能更强地压制泄漏,代价是主瓣明显变宽,频率分辨率下降。如果信号里齿轮啮合频率等强周期性干扰特别突出,可以换布莱克曼窗试一下;如果故障冲击本身的瞬态特性更重要,还是优先保证时间分辨率,用汉宁窗更稳。

2.2 频率分辨率与时间分辨率的折中

这是STFT里最重要的物理约束。窗长N对应的频率分辨率是fs/N,时间分辨率是N/fs,两者乘积恒等于1。窗长越长,频率分辨得越细,但每一段覆盖的时间越长,时间上的定位能力越差;窗长越短,越能抓住瞬态变化的时刻,但频率上越来越糊。这就是时频分析里的“测不准”,没法两头都占。

举个例子,采样率12000Hz,窗长256点时,频率分辨率约46.875Hz;窗长512点,分辨率提高到23.4Hz;窗长1024点,分辨率约11.7Hz。如果轴承外圈故障的特征频率在120Hz左右,窗长256点时一个频率格就有近47Hz,特征峰和相邻谐波很容易糊在一起。反之,如果设备转速高、启动过程中转速变化快,窗长1024点意味着每段覆盖85毫秒以上,转速在这段时间里已经变化了不少,频谱会被拉宽,时频图的时间纹理也变得模糊。

重叠率解决的是另一个问题:窗函数在边缘把信号压低了,如果相邻窗首尾相接而不重叠,加权后各段边缘的贡献会出现周期性起伏。最常见的做法是重叠50%,保证每个点至少被两个窗充分覆盖;兼顾平滑度和计算量,我一般用75%。重叠率越高,时频图在时间轴上越平滑,边缘效应越弱,但计算量和存储量同步上升,做大数据集时要注意体量。

2.3 针对故障振动信号的参数建议

不存在一套参数通吃所有场景,但有一组起始值特别稳妥:采样率8000到20000Hz的轴承/齿轮箱振动信号,窗长512点、Hann窗、75%重叠、nfft取1024(实信号做零填充插值,频率显示更细腻,但不会提高真实分辨率)。如果采样率超过25000Hz,窗长建议升到1024甚至2048,保证最低可分辨频率仍然小于故障特征频率。低速重载设备、转频只有几个赫兹的场景,故障特征频率也很低,这时窗长要更长,比如4096点,否则低频区就几个像素格,没法看。

关于nfft多说一句:nfft可以比窗长大,相当于在频域做插值,让频谱曲线更圆滑,但真实频率分辨率仍由窗长决定。别看到”nfft=8192“就觉得分辨率很高,那是错觉。

还有一个从工程实践中得出的重要细节:不要把时频图存成jpg再读回来喂给网络。jpg压缩会引入块效应和颜色失真,而且保存彩色图时采用的色图映射(jet、parula之类)是任意选择,不同批次数据可能因为颜色尺度不一致,导致模型学到“颜色差异”而不是“故障差异”。正确做法是直接拿STFT复矩阵的幅值做后续处理,存成单通道灰度数值矩阵,尺寸通过imresize调整到网络需要的输入大小。

3. 网络从CNN到ResNet:为什么残差结构更适合故障诊断

3.1 简单CNN在时频图上的短板

一个基础CNN的结构通常是:卷积层提取特征、池化层降采样、Flatten后接全连接分类。浅层CNN在时频图上能取得不错的效果,但想进一步提高准确率时会遇到瓶颈。故障特征分布在多个尺度的时频结构里:冲击瞬态是高频宽带事件,调制边带是低频窄带事件,浅层网络学到的主要是整体能量分布的粗差异,难以捕捉同类故障不同严重程度之间的细微差别。

把网络加深是一条自然思路,但直接堆卷积层会引入两个经典问题:梯度消失和退化。层数一多,反向传播到浅层的梯度越来越小,训练难以收敛;退化更隐蔽——网络明明有能力拟合格率更高,但损失在某个深度后反而上不去,深层网络的表现不如浅层。如果数据量本来就有限,简单加深网络还会加剧过拟合,训练集漂亮、测试集拉胯。

3.2 残差连接为什么有效

ResNet的核心改动其实很小:一个残差块里,输入除了经过卷积非线性映射F(x),还通过一条“捷径”直达输出,整体输出变成F(x)+x。这个恒等映射的意义在于,网络要学的不是完整的输入输出映射,而是“输入和输出之间的差异”。如果残差为零,网络可以直接退化成恒等映射,这样即使堆很多层,理论上也不会比浅层网络更差,优化难度大幅下降。

对故障诊断来说,残差结构还有一个更贴近实际的好处:不同故障类别的时频图整体结构非常相似,正常状态和早期微弱故障之间可能只差某几个频带的一点点能量差异。残差模块天然适合学习这种“差异特征”,而不必从零重建整个时频图的模式。加上梯度能通过捷径直接回传到浅层,深层特征提取会稳定很多。实际体验是,使用ResNet后训练曲线更平滑,对学习率的敏感度比普通CNN低,泛化性也更好。

3.3 在Matlab里定义一个小型ResNet

Matlab环境下跑ResNet有两条路:一是直接调用内置的resnet18或resnet50做迁移学习,优点是收敛快、精度高,缺点是需要GPU资源,且网络参数多,在小数据集上容易过拟合;二是用layerGraph和addLayers自定义一个轻量级残差网络,参数少、训练快、适合嵌入式场景。下面先写迁移学习方案,因为大部分人和我的起步方式一样,先用内置网络把流程跑通。

numClasses = 4; % 根据实际故障类别数修改 % 加载预训练ResNet18 net0 = resnet18; lgraph = layerGraph(net0); % 替换输入层:ResNet18默认接收224x224x3的RGB图 % 我们的时频图是单通道灰度,改成1通道即可,NORMALIZATION设为none lgraph = replaceLayer(lgraph, 'data', ... imageInputLayer([224 224 1], 'Name', 'data', 'Normalization', 'none')); % 替换最后的全连接层和分类层 lgraph = replaceLayer(lgraph, 'fc1000', ... fullyConnectedLayer(numClasses, 'Name', 'fc_fault')); lgraph = replaceLayer(lgraph, 'ClassificationLayer_fc1000', ... classificationLayer('Name', 'output')); % 检查网络结构 analyzeNetwork(lgraph);

注意一点:不同Matlab版本里resnet18的层名可能略有差异,比如某些版本的输入层叫'data',分类层叫'ClassificationLayer_fc1000'。如果你运行时报“找不到层”,就用analyzeNetwork(lgraph)打开网络图,看一眼实际层名,再把replaceLayer的第一个参数改成看到的名称。

如果机器没有独立显卡,或者数据集不大,迁移学习的成本会偏高。这种情况下可以考虑简化方案:缩小输入尺寸到128x128,减少首层卷积通道数,只保留4到6个残差块。自定义残差块的关键结构如下,思路清楚后可以按需拼装:

% 自定义残差块示意:主分支由2个卷积层组成,短路分支是恒等连接 function lgraph = addResBlock(lgraph, inName, blockName, filters) main = [ convolution2dLayer(3, filters, 'Padding', 'same', 'Name', [blockName '_c1']) batchNormalizationLayer('Name', [blockName '_bn1']) reluLayer('Name', [blockName '_relu1']) convolution2dLayer(3, filters, 'Padding', 'same', 'Name', [blockName '_c2']) batchNormalizationLayer('Name', [blockName '_bn2']) ]; lgraph = addLayers(lgraph, main); lgraph = connectLayers(lgraph, inName, [blockName '_c1']); % 使用相加层融合主分支与短路分支 addLayer = additionLayer(2, 'Name', [blockName '_add']); lgraph = addLayers(lgraph, addLayer); lgraph = connectLayers(lgraph, [blockName '_bn2'], [blockName '_add/in1']); lgraph = connectLayers(lgraph, inName, [blockName '_add/in2']); lgraph = connectLayers(lgraph, [blockName '_add'], [blockName '_relu_out']); end

注意如果输入和输出通道数不一致,短路分支需要加一个1x1卷积来调整通道数,否则相加时维度对不上。上述代码是教学示意,实际拼装时还要在addLayer之后接一个relu,形成完整的残差块“卷积-批归一化-卷积-相加-激活”。压缩后的自定义ResNet在轴承故障诊断任务上,只要样本量和特征图质量靠谱,完全可以把准确率做到95%以上,训练时间比全尺寸ResNet短很多。

4. Matlab完整实现流程:从原始信号到诊断结果

4.1 数据准备与样本切分

我用过网上常见的滚动轴承故障公开数据集,也处理过现场采集的齿轮箱振动数据。无论哪种来源,第一步都是把长信号切分成等长样本。为什么要切?深度学习训练需要大量独立样本,一个几秒钟的振动记录通常包含几万到几十万个采样点,直接整段喂给网络既不现实,样本量也不够。切分时两个关键参数是样本长度sampleLen和滑窗步长step。

fs = 12000; % 采样频率,单位Hz,按实际数据修改 sampleLen = 4096; % 每个样本包含的采样点数 step = 256; % 滑窗步长,决定相邻样本的重叠度 allData = cell(0, 1); allLabels = []; for k = 1:numel(fileList) % 读取原始振动信号,不同数据集格式不同,这里用自定义函数示意 sig = loadVibration(fileList{k}); % 预处理 sig = sig - mean(sig); % 去直流 sig = sig / max(abs(sig)); % 幅值归一化 n = floor((length(sig) - sampleLen) / step) + 1; for i = 1:n seg = sig((i-1)*step + 1 : (i-1)*step + sampleLen); allData{end+1, 1} = seg; %#ok<SAGROW> allLabels(end+1, 1) = labelOf(k); %#ok<SAGROW> end end

幅值归一化值得商榷。如果所有样本来自同一传感器、同一增益,归一化会抹掉故障能量的绝对尺度,有时反而不好。我的经验是:先做一次全量统计,如果不同故障类别的幅值差异本身就是分类信息,就不要做全局归一化;如果数据来自不同工况、传感器灵敏度不一致,务必按单个文件归一化,消除采集系统差异。这个选择对结果影响很大,实际调试时要根据混淆矩阵分布来判断。

4.2 批量生成STFT时频图

切好的样本逐个做STFT,转成网络能用的四维数组。这一步是整个流程的核心,也是做完之后基本不用再动的部分,强烈建议把所有样本的时频图一次性生成并缓存到mat文件里,避免以后反复调试网络时重复计算。

function tf = stftFeature(sig, fs) % STFT时频图生成:幅值谱+对数压缩+归一化 winLen = 512; overlap = round(winLen * 0.75); % 75%重叠 nfft = 1024; [S, ~, ~] = spectrogram(sig, hann(winLen), overlap, nfft, fs); tf = abs(S); % 取幅值谱 tf = log(1 + tf); % 对数压缩,抑制强成分、突出弱冲击 tf = (tf - min(tf(:))) / (max(tf(:)) - min(tf(:)) + eps); % 归一化到[0,1] tf = imresize(tf, [224 224]); % 调整为ResNet18所需尺寸 end
numSamples = numel(allData); X = zeros(224, 224, 1, numSamples, 'single'); % 注意最后一维是样本数 for i = 1:numSamples X(:, :, 1, i) = stftFeature(allData{i}, fs); end Y = categorical(allLabels); % 保存缓存 save('tf_cache.mat', 'X', 'Y', '-v7.3');

这里要特别提醒:样本量大的时候,这个四维数组很容易把内存耗尽。224x224x1x10万样本,用single存储也要近20GB,普通台式机直接卡死。如果数据集超过一两万样本,不要一次性存成数组,改成把时频图写成png或mat文件再用imageDatastore读取,或者把样本量控制在可接受范围。几千到一万个样本对单个故障诊断任务通常足够,不必为了堆样本量把内存搞爆。

4.3 划分数据集与图像增强

划分数据集是故障诊断里最容易出错、也最影响论文/实验结果可信度的一步。我强烈建议按原始记录文件划分,而不是按切分后的样本随机洗牌。道理很简单:同一个原始信号的相邻样本高度相关,训练集和测试集如果混入了同一个原始文件的切窗,测试结果会出现严重虚高,这不是模型学会了故障诊断,而是记住了样本。避免数据泄漏是第一原则。

rng(2024); % 假设我们有多个原始记录,按记录编号划分,而不是按样本划分 numRecords = numel(uniqueRecordId); recordIdx = randperm(numRecords); trainRecords = recordIdx(1:round(0.7*numRecords)); valRecords = recordIdx(round(0.7*numRecords)+1:round(0.85*numRecords)); testRecords = recordIdx(round(0.85*numRecords)+1:end); trainMask = ismember(recordIdOfSample, trainRecords); valMask = ismember(recordIdOfSample, valRecords); testMask = ismember(recordIdOfSample, testRecords); trainX = X(:, :, 1, trainMask); trainY = Y(trainMask); valX = X(:, :, 1, valMask); valY = Y(valMask); testX = X(:, :, 1, testMask); testY = Y(testMask);

数据增强方面,我的建议是先不要上复杂增强,先把基准实验跑出来再说。故障诊断里的时频图不像自然图像那样有丰富的旋转、翻转语义,随便翻转可能改变时频结构的物理含义——比如左右翻转等于时间反转,对旋转机械来说通常不符合物理过程。更安全的增强方式是:对原始信号加随机小噪声、微调幅值、在滑窗位置上加少量随机抖动。这些增强要在STFT之前做,而不是对生成的时频图做,这样才能保证增强后的样本仍然来自真实物理分布。

4.4 搭建并训练ResNet

数据准备好之后,训练代码反而比较套路化。注意trainNetwork的第一个参数是输入数据,第二个是标签,第三个是网络结构,第四个是训练选项。如果是之前生成的四维数组,直接传入即可;如果数据量很大用了imageDatastore,就传datastore,标签在datastore里自带。

options = trainingOptions('adam', ... 'InitialLearnRate', 1e-4, ... 'MaxEpochs', 20, ... 'MiniBatchSize', 32, ... 'Shuffle', 'every-epoch', ... 'ValidationData', {valX, valY}, ... 'ValidationFrequency', 10, ... 'Verbose', true, ... 'Plots', 'training-progress', ... 'L2Regularization', 0.0001); netTrained = trainNetwork(trainX, trainY, lgraph, options);

学习率直接给1e-4是我比较喜欢的起始值。内置resnet18预训练模型是为ImageNet设计的,直接在振动信号上微调,学习率给大了很容易把预训练特征冲坏。如果是从头训练自定义小网络,学习率可以适当上调到1e-3,配合学习率衰减。每次训练前固定rng种子,否则同一个代码跑两遍,结果可能差两三个百分点,别问我是怎么知道的。

如果训练时提示“内存不足”,优先调小MiniBatchSize到16或8,而不是急着换机器。如果ValidationLoss在几个epoch后就开始回升,说明学习率大了或过拟合,先把学习率减半。另外一定要等到训练结束后再评估测试集,训练过程中反复拿测试集看结果,本质上也是在测试集上调参,最终报告会失真。

4.5 评估与可视化

训练完成后的标准动作是三件事:计算准确率、画混淆矩阵、看训练曲线。准确率只是一个数字,混淆矩阵才告诉你看不懂的部分:是哪两个类别容易互相混淆,是哪一类召回率偏低。

pred = classify(netTrained, testX); acc = mean(pred == testY); fprintf('测试集准确率: %.2f%%\n', acc * 100); figure; confusionchart(testY, pred, ... 'RowSummary', 'row-normalized', ... 'ColumnSummary', 'column-normalized');

从混淆矩阵里最值得关注的不是对角线上的数字,而是非对角线上的误分类规律。比如外圈故障样本总被误判成正常,说明时频图预处理时低频分量的权重被压得太低;内圈故障和滚动体故障互相混淆,说明两者的冲击间隔差异在当前的窗长/时间分辨率下不够明显。这些线索直接指向下一步该调哪个参数,比盲改网络结构高效得多。

5. 我在实际调参中踩过的坑,和对应的解决方法

5.1 训练集99%、测试集83%:过拟合的典型症状与处理

这是我第一次跑完整流程时遇到的状况,也是绝大多数初学者的必经之路。训练集的loss降到接近0,验证准确率却停在83%左右不动。一开始我以为是网络太深,换成更小的网络后几乎没变化,后来发现根因不在网络,而在数据划分。

当时我偷懒用了randperm对全部滑窗样本直接洗牌,没有按原始文件分组。同一段几秒钟的振动信号切出一千多个窗口,随机洗牌后大量高度相关的窗口同时出现在训练集和验证集,模型相当于提前见过测试题的“近似答案”。换成按原始记录划分后,验证准确率从83%掉到76%,然后再通过数据增强和调参慢慢涨回来——最终结果虽然看起来没有之前“惊艳”,但真实可靠得多。

处理过拟合的正确顺序是:先确认数据划分没有泄漏,再减少模型容量或加正则,最后才考虑换网络结构。具体到训练选项上,L2Regularization调到0.001,或在全连接层后面插一个dropoutLayer(0.5),往往比换个更深的网络管用。

5.2 负载转速变化后准确率骤降:泛化性问题的来源

另一个特别打击人的场景是:模型在某个工况下测试准确率98%,换一个转速、换一个负载后就掉到70%甚至更低。这个问题的本质不是模型坏了,而是时频图里的能量分布既包含故障特征,也包含工况特征。转速升高,转频和故障特征频率整体上移;负载增大,低频段能量占比升高。如果训练数据只覆盖一个工况,网络完全可能学到“这个工况长什么样”而不是“这个故障长什么样”。

缓解办法有三层,按成本从低到高排序。第一层,训练集中尽量覆盖多个转速、多个负载,并且每个工况内部的故障样本数量均衡,按工况划分训练/验证/测试,保证测试工况在训练时没有出现。第二层,增强时对原始信号做时间轴微扰动,相当于模拟转速的小范围波动,或者加入少量高斯白噪声。第三层,把时频图按频带归一化而非全局归一化,让模型更关注频带的相对分布,而不是绝对能量大小。做下来之后,跨工况准确率的差距会明显缩小,但想做到一个模型通吃所有工况,还需要引入域适应等方法,这是后话。

5.3 训练时间过长与内存不足的优化手段

故障诊断数据动辄几万样本,STFT生成的时频图又是224x224,批量训练对算力的要求不低。我最早在纯CPU环境上跑ResNet18,一个epoch要二十多分钟,调几次参数就耗掉半天。后来总结出的优化顺序是:

第一,STFT特征一次性离线生成并缓存,训练期间绝不重复计算。第二,如果内存装不下全部时频图,改用imageDatastore配合augmentedImageDatastore按批次读图,本质是用磁盘IO换内存。第三,如果没有GPU,收缩输入尺寸到128x128,甚至64x64,同时减少首层卷积通道数,用一个自定义小型ResNet替代预训练ResNet18。故障时频图本身结构简单,不像自然图像那样需要极高的输入分辨率,64x64的时频图配合好参数也能达到90%以上的准确率。第四,用single类型存储所有数据,能省一半内存。

还有一个很实用的技巧:先跑一个“小额实验”,比如每个类别只取一两百个样本,把代码调通、参数基本确定后,再全量训练。所有折腾都在小额实验上完成,全量训练只跑最终版本,时间成本能省下一大半。

6. 从能跑通到能用:三个容易忽略的工程细节

6.1 滑窗切分的选择与标签对齐

样本切分时,步长越小,样本量越大,但要注意样本之间的重叠相关会干扰评估。工业场景里做在线诊断时,通常希望模型响应尽可能快,这要求推理窗口尽量短,但训练时用的窗口长度必须和部署时保持一致。我见过有人在训练时用4096点窗,部署时却用1024点窗,时频图的长宽比变了,模型性能骤降。更隐蔽的问题是标签对齐:一段信号里同时包含正常段和故障段时,直接用整段信号的标签给所有切窗打标会引入大量错标样本,必须先把信号按状态分段,再在段内切窗。

6.2 类别不平衡与混淆矩阵视角

故障诊断场景天然存在类别不平衡:正常数据永远最多,严重故障数据很少,早期故障数据更少。直接用准确率评估会被“大多数类”主导,比如正常类占95%,模型全部预测正常也有95%准确率,但这显然没有诊断能力。看混淆矩阵时重点看每一类的召回率,尤其是早期故障类别。如果某类样本过少,最简单的办法是对该类样本做重采样,让参与训练的各类别数量大致一致;再不济,可以把多类别问题拆成“正常vs异常”二分类先做检测,再做“异常属于哪一类”的多分类,这样训练难度会降低不少。

6.3 用CAM看一下网络到底“看”到了什么

跑通模型之后别急着收工,我建议花十分钟用类激活图(gradCAM)看看模型凭什么做出判断。Matlab深度学习工具箱在较近版本里提供了gradCAM函数,可以直接对单张测试样本计算热力图。

% 计算某测试样本的Grad-CAM热力图 scoreMap = gradCAM(netTrained, testX(:, :, 1, idx), 'fc_fault'); figure; subplot(1,2,1); imshow(testX(:, :, 1, idx)); title('时频图'); subplot(1,2,2); imshow(scoreMap); title('Grad-CAM热力图');

注意gradCAM要求网络中的特征层在前向传播时能获得梯度,如果报错,一般是网络结构不支持或层名不对,换成'res5c_relu'这类卷积层名称试试。热力图能告诉我们两件重要的事:模型是关注了故障冲击引起的高频带区域,还是聚在某个莫名其妙的角落。如果热点集中在时频图的边缘或固定位置,多半是数据泄漏、归一化不一致或窗口边界效应在起作用,而不是真正的故障特征。

我自己遇到过一种情况:某类故障的时频图在某个频带始终有固定亮纹,模型实际是靠这条亮纹分类的,但这条亮纹是传感器共振频率,不是轴承故障特征。这类问题如果不用CAM检查,单看准确率是发现不了的。部署到另一台设备上时,传感器不同、共振频率不同,模型立刻失效。所以,可解释性检查不是发论文时的装饰,而是工程落地的安全检查。

把所有参数和流程完整跑通之后,我个人的体会是:对这个任务来说,STFT参数的合理程度通常比网络结构的选择更影响最终结果。窗长选错了,再深的ResNet也救不回来;数据划分不干净,再高的准确率也是假的。先花时间把时频图的质量和数据组织逻辑搞对,再去调网络,这个顺序不要颠倒。最后一个小建议:实验记录里把每次用的窗长、重叠率、滑窗步长、数据划分方案、学习率和随机种子都完整记下来。这类实验随机性比想象中大,重跑一遍差两三个百分点太常见了,没有完整的实验日志,后面复盘时会非常痛苦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询