基于WDCNN的滚动轴承故障诊断:Python实现与创新拓展
Python环境下基于WDCNN的滚动轴承故障诊断创新与拓展做故障诊断这几年我一直有一个很深的感受算法论文里跑出来的99%准确率到了现场设备上往往要打一个大大的问号。振动信号里混着噪声、负载波动、转速变化实验室里顺风顺水的模型一上场就露怯。直到我认真把WDCNN这套思路吃透并且结合Python生态做了几轮改造之后才真正觉得“深度学习做轴承故障诊断”这条路有了可以落地的抓手。这篇文章就是围绕“Python环境下基于WDCNN的滚动轴承故障诊断”这个项目展开的。我会把这套方案从原理、数据准备、模型搭建、训练调参再到我做的几处创新拓展完整拆开来讲。适合正在入门故障诊断的算法工程师也适合已经在跑CNN模型但觉得效果不够稳、想往上再走一步的同学。文章里所有的代码都是PyTorch实现硬件只需要一张普通显卡整套流程在本地就能复现。WDCNN这个名字全称是Wide Deep Convolutional Neural Network最早是作为“宽卷积核深度卷积神经网络”被提出的。它最大的特点是第一层用一个很大的卷积核去直接处理原始振动信号而不是像图像领域那样用3×3小卷积核堆叠。这个设计背后有一个非常朴素的动机轴承故障信号在时域上往往表现为周期性的冲击这些冲击的频率、相位、幅值携带了故障类型信息如果第一层卷积核太小模型只能看到局部几个点很难捕捉到一次完整的冲击波形。宽卷积核相当于在输入层就做了一次带通滤波加特征粗提取把冲击特征先“捞”出来后面的深层网络再去学习更抽象的故障模式。我用一个实际案例来说明WDCNN的效果。某次现场采集的滚动轴承振动数据包含正常状态、内圈故障、外圈故障、滚动体故障四种状态每种状态下又有不同的故障尺寸。直接拿原始时域信号训练一个普通的2D CNN把一维信号reshape成二维矩阵测试准确率只有85%左右而且对轻微故障的混淆很严重。换用WDCNN结构之后同样的数据、同样的训练轮数测试准确率直接提到了97%以上尤其是在外圈故障和滚动体故障这两个容易混淆的类别上改善非常明显。1. 项目定位与整体思路1.1 故障诊断到底在解决什么问题滚动轴承是旋转机械里最常见的零部件也是最容易出故障的部件之一。一旦轴承出现点蚀、剥落、裂纹振动信号里就会出现规律性的冲击脉冲但现场环境复杂这些冲击往往淹没在齿轮啮合、轴系不平衡、电机电磁干扰等背景噪声里。传统的故障诊断流程是测振动信号做时频分析比如包络谱、小波变换人工去辨识故障特征频率。这套方法的问题是特征频率的计算依赖轴承的结构参数节径、滚子数、接触角等不同型号的轴承要重新算而且转速波动时特征频率会“漂移”人工辨识非常吃力。深度学习做故障诊断的思路就完全不同了把原始振动信号或者经过简单预处理的数据直接喂给神经网络让模型自动从大量样本里学习“正常”和“各类故障”在波形上的本质差别省去了人工特征工程的步骤。WDCNN就是专门为这类一维振动信号设计的网络结构它不需要把信号转成频谱图直接用带噪声的原始波形训练模型的抗噪能力和泛化能力反而更强。我把这个项目定位成“端到端”的诊断方案输入是一段原始振动加速度信号输出是故障类型和故障严重程度的概率分布。整个过程可以在Python一条龙完成从数据读取到模型部署都有对应的库支持。1.2 为什么是WDCNN而不是普通CNN我最早尝试过把振动信号做短时傅里叶变换得到时频谱图直接扔给ResNet一开始效果还行但很快发现两个问题一是时频谱图的生成需要调窗函数、窗长、重叠率一堆参数不同转速下最优参数还不一样二是把一维信号转成图像之后数据量成倍增加训练速度慢而且模型在这个转换过程中丢失了一部分原始波形的时间分辨率信息。WDCNN直接绕开了时频转换这一步。它接受的输入就是原始的一维振动信号采样点序列。第一层宽卷积核比如64或128个采样点在时域上滑动等价于对原始波形做了一组可学习的带通滤波器这组滤波器会在训练中自动调整中心频率和带宽去匹配不同故障冲击的固有频率。这个理念和信号处理里的滤波器组设计不谋而合但WDCNN的滤波器不是人工设计的而是从数据里学出来的。还有一个非常实际的原因WDCNN的参数效率高。因为一维卷积的输入维度本来就不大通常2048或4096个点即便第一层用宽卷积核整个模型的参数量也控制在一百万到几百万之间训练起来比ResNet18轻得多几分钟就能跑完一个epoch这在做实验迭代时太重要了。1.3 项目最终交付了什么这个项目最终的成果是一套完整的、可复现的Python诊断流程包括数据加载与划分模块支持公开数据集和自采数据完成训练集/验证集/测试集的划分数据预处理模块归一化、滑窗截取、数据增强、样本均衡WDCNN模型定义灵活的层数和卷积核配置支持加BN、Dropout、残差连接训练与评估模块自动保存最优模型输出混淆矩阵和各类故障的准确率创新拓展模块我在此基础上加入了注意力机制、迁移学习、以及面向在线监测的轻量化部署方案。整套代码用PyTorch实现环境要求是Python 3.8以上、PyTorch 1.10以上、NumPy、SciPy、scikit-learn、Matplotlib。显存4G以上的显卡就能跑没有GPU用CPU也能完成训练只是速度慢一些。2. WDCNN模型核心设计与原理解析2.1 网络结构逐层拆解WDCNN的结构并不复杂一句话描述就是若干“宽卷积池化”的堆叠最后接全连接层和Softmax分类器。以我常用的一个配置为例输入是长度为2048的振动信号采样点结构如下Conv1: kernel_size 64, stride 8, filters 16 - BatchNorm - ReLU - MaxPool(kernel2, stride2) Conv2: kernel_size 3, stride 1, filters 32 - BatchNorm - ReLU - MaxPool(kernel2, stride2) Conv3: kernel_size 3, stride 1, filters 64 - BatchNorm - ReLU - MaxPool(kernel2, stride2) Conv4: kernel_size 3, stride 1, filters 64 - BatchNorm - ReLU - MaxPool(kernel2, stride2) Conv5: kernel_size 3, stride 1, filters 64 - BatchNorm - ReLU - MaxPool(kernel2, stride2) Flatten - Dense(100) - ReLU - Dropout(0.5) - Dense(num_classes) - Softmax第一次看到这个结构的人往往会问为什么第一层用这么宽的卷积核64个点后面的层反而用3个点的小卷积核这个设计是有意为之的。第一层宽卷积核负责捕获一次完整冲击的“轮廓”。在常见的轴承故障诊断场景中采样率通常在12kHz到48kHz之间一次冲击脉冲的持续时间大约是几十到几百个采样点。卷积核宽度取64就是希望这个卷积核能“看全”一次典型的冲击而不是只看到冲击的某个局部片段。stride8意味着每次移动8个采样点输出长度迅速压缩到256左右既降维又保留了时序关系。后面的小卷积核则是用来在已经粗提取的特征基础上做精细的模式组合。这就好比先拿粗笔画出轮廓再用细笔勾勒细节最后由全连接层把所有的细节特征汇总分类。2.2 宽卷积核背后的信号处理逻辑如果只从深度学习的角度看宽卷积核似乎只是“感受野大一点”。但如果我们从信号处理的角度去理解会看得更清楚第一层卷积本质上是让你在时域里学习的滤波器组。每一个卷积核都可以看作一个滤波器一端卷积一个64点的窗口本质上是在做有限冲激响应滤波。关键在于普通的有限冲激响应滤波器系数是工程师根据目标频段手工设计的比如巴特沃斯带通滤波器的系数计算非常繁琐。而WDCNN第一层的卷积核是通过反向传播自动调节滤波器系数的模型会根据训练数据的故障特征自动学会增强某些频段、抑制另一些频段。这一设计带来的好处是模型对于噪声的鲁棒性明显增强。因为网络在训练时见过大量带噪声的信号第一层滤波器会自动形成某种“匹配滤波”的效果——只对与故障冲击相关频率成分响应强烈对宽带噪声则自动衰减。这也是为什么WDCNN直接在原始波形上训练反而比先做带通滤波再训练的准确率高。后一种做法人为丢弃了可能包含故障信息的频段而WDCNN让模型自己决定保留什么。2.3 全连接层与故障分类的映射关系经过5层卷积和池化输入的2048个点最终被压缩成一组高维特征向量。可以把它理解为模型从原始波形中提取出的“故障指纹”。最后接入的全连接层100个隐藏单元本质上是一个特征变换而Softmax输出层则是把该特征向量映射到各类故障的概率分布上。有一个实践中的细节中间的全连接隐藏层大小取100而不是直接映射到类别数是有意的。100维的隐藏层起到了特征压缩和去相关的作用配合Dropout可以抑制过拟合。如果隐藏层太大比如512一是参数量暴增二是容易记住训练集的噪声模式导致验证集准确率上不去如果太小比如16特征表达能力又不够分类效果会下降。3. Python环境下的数据工程实战3.1 数据来源与预处理流程做故障诊断数据是第一步。我用的是一套公开的滚动轴承故障模拟数据集包含了正常、内圈故障、外圈故障、滚动体故障四种状态每种状态又有几种不同的故障直径尺寸。下载下来的原始数据是.mat格式的振动加速度信号。Python里读取这些数据通常需要用到SciPy的loadmat函数但要注意一点不同版本的数据集保存的变量名可能不一样读取前先用keys()看一下结构。读取、滑窗截取的示例import scipy.io as sio import numpy as np mat sio.loadmat(inner_race_0.021.mat) # 打印mat.keys()确认信号存储的变量名 signal mat[X098_DE_time].reshape(-1) # 变量名是举例以实际为准 # 滑窗截取每个样本2048个点重叠率50% def make_samples(signal, sample_len2048, stride1024): samples [] for i in range(0, len(signal) - sample_len, stride): samples.append(signal[i:i sample_len]) return np.array(samples)滑窗截取的时候有几个细节需要特别注意。首先是滑窗长度和步长的选择。滑窗长度取2048个点在12kHz采样率下对应约0.17秒的信号这足够包含几个完整的故障冲击周期步长取1024即50%重叠是为了在不增加太多数据量的前提下增加样本数。如果数据量本身很大步长可以加大比如取2048即不重叠减少样本冗余加速训练。其次是归一化。原始振动信号的幅值范围可能因传感器灵敏度不同而差异很大如果直接输入网络模型会倾向于学习幅值差异而不是波形特征。我采用的方案是每段样本各自做Z-score标准化公式是(x - mean) / std把每段信号的均值变成0、标准差变成1。这么做有两个好处一是消除了不同样本之间幅值的绝对差异二是让模型关注波形的“形状”而不是波形的“大小”。最后是数据类型的转换。PyTorch的卷积层默认期望输入是float32类型所以要把样本从float64转成float32否则训练时会有隐式类型转换的开销。默认的float64在CPU上问题不大但在GPU上会导致每个step多出一次类型转换的等待训练速度下降明显。3.2 数据增强与样本均衡故障诊断场景里经常遇到一个让人头疼的问题数据不平衡。正常状态的样本通常很好收集但故障样本往往很少尤其是严重故障的样本生产现场根本不允许设备长期带病运行。我在这个项目里用了两招来处理数据不平衡。第一招是过采样加扰动。对于故障类样本除了用原始信号滑窗截取外我还对样本做了加性噪声、幅值缩放和时间轴微移三种增强方式。加性噪声的标准差取原始样本标准差的0.05到0.1倍幅值缩放控制在0.9到1.1之间时间轴微移是沿着时间轴随机滚动几十个采样点。这些扰动都是模拟真实工况中不可避免的随机变化既能扩充样本量又能提升模型的泛化性。第二招是类别加权采样。PyTorch的WeightedRandomSampler可以根据每个类别的样本数量反向计算采样权重让样本少的类别在每个epoch里被采到的概率更高。这样做比简单的重复贴上采样更平滑不容易导致模型对少数类过拟合。from torch.utils.data import WeightedRandomSampler labels np.array([...]) # 所有样本的标签 class_counts np.bincount(labels) weights 1.0 / class_counts[labels] sampler WeightedRandomSampler(weights, num_sampleslen(labels), replacementTrue)训练时把这个sampler传给DataLoader训练集每个epoch的样本组成会动态调整少数类的学习强度得到了保障测试集上的各类故障准确率也均衡了很多。3.3 数据集划分与加载器实现数据划分的原则是按时间先后划分而不是随机划分。有人直接对滑窗后的样本做随机抽取进训练集和测试集这种做法在故障诊断里是有隐患的。原因在于同一个时间段的振动信号高度相似滑窗截取出来的相邻样本几乎就是同一段波形的平移。如果随机划分训练集和测试集里会出现“近亲样本”模型相当于已经见过测试数据的相邻片段测试准确率会虚高。正确的做法是先把整段连续信号按时间分成前70%用于训练、后30%用于测试再在各自内部滑窗。这样可以确保测试样本在时间上和训练样本完全无重叠评估结果才能反映模型的真实泛化能力。如果需要做调参验证可以在训练集内部再按同样方式切出验证集。数据加载部分我用PyTorch的Dataset和DataLoader封装完整代码如下import torch from torch.utils.data import Dataset, DataLoader class BearingDataset(Dataset): def __init__(self, data, labels): self.data torch.tensor(data, dtypetorch.float32).unsqueeze(1) self.labels torch.tensor(labels, dtypetorch.long) def __len__(self): return len(self.labels) def __getitem__(self, idx): return self.data[idx], self.labels[idx] train_dataset BearingDataset(train_data, train_labels) test_dataset BearingDataset(test_data, test_labels) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers4, drop_lastTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse, num_workers4)drop_lastTrue是我踩坑换来的经验。如果最后一个batch的样本数量不足64BN层在计算均值和方差时会用这个不完整的batch统计量训练后期模型已经收敛时这偶尔会导致loss出现瞬时波动。把不足一个batch的尾巴丢掉训练曲线会平稳很多。4. 模型训练与关键参数调优4.1 模型定义与训练代码有了数据和模型结构定义之后训练本身不是一个复杂的工程但有几个关键决策点值得展开。首先是优化器与学习率。故障诊断数据集通常不算太大几千到几万样本我推荐直接用Adam优化器初始学习率设置成3e-4。你可能会问为什么不试更大一点的学习率我试过1e-3前几个epoch的loss确实降得飞快但到了后程容易出现震荡最终收敛的准确率反而比小学习率低1到2个百分点。WDCNN的第一层宽卷积核需要比较精细的调整才能学好学习率太大会把这个滤波器组的系数“打乱”。其次是学习率调度。我用的是StepLR每30个epoch把学习率乘以0.5。整个训练跑60到80个epoch配合这个调度策略测试准确率在最后十几个epoch里还能有小幅提升。具体训练循环的关键代码import torch.nn as nn import torch.optim as optim criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr3e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.5) for epoch in range(80): model.train() running_loss 0.0 for inputs, labels in train_loader: inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() # 每个epoch结束后在验证集上评估一次 model.eval) val_accuracy evaluate(model, val_loader) print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f}, Val Acc: {val_accuracy:.2f}%) # 保存验证集准确率最高的模型关于是否用预训练模型WDCNN不像图像领域的ResNet那样有大规模预训练的权重可以借用它的第一层卷积核参数初始化非常关键。我试过好几种初始化方式最稳定的是nn.init.kaiming_normal_配合modefan_out。这个初始化方法考虑了ReLU的非线性特性能保证信号在深度网络中传播时方差保持稳定不会出现梯度消失或爆炸。如果你直接用默认的PyTorch初始化很可能遇到训练早期loss不下降的问题。4.2 关键参数的计算与分析WDCNN的参数选择不是胡乱拍的每一个都有背后的计算逻辑。以输入长度2048为例说明一下各层输出尺寸的变化输入: (1, 2048) Conv1: kernel64, stride8 - 输出长度 floor((2048 - 64) / 8) 1 249 然后 MaxPool(kernel2, stride2) - 124 Conv2: kernel3, stride1, padding1 - 保持124 MaxPool - 62 Conv3 - 62 - MaxPool - 31 Conv4 - 31 - MaxPool - 15 Conv5 - 15 - MaxPool - 7经过5轮卷积池化长度从2048压缩到7信息保留程度非常紧凑。此时每个通道承载的是整段信号中某个特定模式的激活强度。最后Flatten成7 * 64 448维的向量再映射到100维全连接层。整个模型的参数量大约在30万左右这个量级配合上几千个训练样本不容易过拟合训练速度也快。再聊聊batch size。我建议用64或者128。batch size如果太小比如8或者16BN层的统计量不稳定宽卷积核学到的滤波器系数抖动很厉害每个epoch之间的验证准确率上下波动能达到5个百分点。batch size取64时梯度估计足够稳定显存占用也不高是故障诊断场景下的一个比较稳妥的选择。4.3 评估指标与结果解读训练完成后仅看测试集整体准确率是不够的一定要看混淆矩阵。因为故障诊断场景里不同故障类型的重要性和可检测性差异很大内圈故障和外圈故障的振动传播路径不同可能导致模型对内圈故障识别得很好、对外圈故障却频繁误判。我保存了完整测试集的预测结果用sklearn.metrics输出混淆矩阵和分类报告from sklearn.metrics import confusion_matrix, classification_report predictions [] true_labels [] model.eval) with torch.no_grad(): for inputs, labels in test_loader: inputs inputs.to(device) outputs model(inputs) _, preds torch.max(outputs, 1) predictions.extend(preds.cpu().numpy()) true_labels.extend(labels.numpy()) print(confusion_matrix(true_labels, predictions)) print(classification_report(true_labels, predictions, target_names[Normal, Inner, Outer, Ball]))一个值得关注的现象是外圈故障和滚动体故障之间的混淆最频繁。原因是滚动体故障的冲击特征比较“飘”滚子带着故障点在旋转过程中故障位置与传感器之间的相对位置不断变化导致冲击信号的幅值调制非常明显。模型在学习过程中对这一类特征的提取难度天然就大。如果看到这类混淆可以尝试加深第一层之后的卷积层数量或者增强数据的多样性。最终评估结果在测试集上整体准确率达到97.8%内圈故障和正常状态的F1分数都在98%以上外圈故障和滚动体故障的F1分数在96%左右。如果加上下面要讲的注意力机制改进准确率还能再往上提到98.5%左右。5. 创新与拓展让WDCNN走得更远5.1 引入注意力机制让模型学会“该看哪里”WDCNN的基础结构虽然有效但它还是存在一个短板不同层级的特征对所有输入信号一视同仁地处理。可实际上某一段信号里可能前0.05秒是强噪声后0.1秒才是故障冲击模型应该把注意力集中到有冲击的那一段。标准WDCNN没有这个能力它的滤波器对整段信号平等扫描。我做的第一处创新是在第3层和第4层卷积之间加入了通道注意力模块类似图像领域SE-NetSqueeze-and-Excitation的思路。具体做法是把每个通道的特征图做全局平均池化得到一个通道描述向量再经过两个全连接层学习每个通道的重要程度权重最后把权重乘回原来的特征图。代码实现如下class ChannelAttention(nn.Module): def __init__(self, channels, reduction8): super().__init__() self.avg_pool nn.AdaptiveAvgPool1d(1) self.fc nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels), nn.Sigmoid() ) def forward(self, x): b, c, _ x.size() y self.avg_pool(x).view(b, c) weights self.fc(y).view(b, c, 1) return x * weights加入这个模块之后模型训练时会让网络自动学习“哪些卷积通道对当前样本更重要”。观察训练过程发现面对含噪样本的时候注意力模块会把权重集中在那些对应故障冲击频段的通道上抑制对噪声敏感的通道。测试准确率从97.8%提升到了98.5%这个提升在故障诊断场景里已经是实打实的收益。但也要提醒一句注意力模块不是加得越多越好。我试过在每一层卷积后都加通道注意力结果模型参数量涨了30%训练时间也长了准确率反而没有明显提升。中间层的深层语义信息已经比较抽象强行加注意力只会让梯度传播路径变长。最终只在第3、4层之间加了一处效果最理想。5.2 迁移学习用源域数据解决目标域的冷启动现场工程师经常会遇到一个非常现实的困境设备刚投入使用还没有任何故障数据你拿着一个在实验室数据上训练好的WDCNN模型直接拿去诊断现场设备效果往往不理想。原因很简单现场设备的转速、负载、安装方式、传感器型号都和实验室不同振动信号的分布存在明显的差异这种差异叫作“域偏移”。我用来解决这个问题的方案是基于WDCNN的迁移学习。思路不复杂先把WDCNN在公开数据集上预训练好固定前几层卷积层这些层学习到的是通用的信号滤波特征只微调后面几层和全连接层用现场设备采集到的一小部分有标注数据进行训练。这样做的前提是不同设备轴承故障在信号层面有一些共同的底层规律比如冲击波形的基本形态、频谱包络的趋势这些规律存放在浅层卷积核里可以直接借用而每个设备的特有差异则通过微调深层参数来适应。实际跑下来这个策略的效果非常惊喜。在某次跨设备实验中源域是实验室公开数据目标域是另一套试验台采集的少量数据我只用了目标域每个类别20个样本总共80个样本做微调测试准确率就达到了91%对比直接从零训练同样的数据量之下准确率只有70%出头。如果你要做这个拓展有两点要留意。一是微调时学习率要调小建议1e-4甚至更小否则容易把预训练好的滤波器系数“冲掉”二是第一层宽卷积核的权重最好直接冻结或者使用很小的学习率因为它是整个模型的基石一旦被破坏后面的所有层都会跟着遭殃。5.3 轻量化与在线监测从离线诊断到实时报警WDCNN在验证集上效果好是一回事能不能部署到现场实时跑是另一回事。很多现场设备监测系统的上位机只有CPU连GPU都没有如果模型推理一次要花几百毫秒根本跟不上实时采集的要求。我做的第三处拓展是针对WDCNN的轻量化改造。思路有两条线一是把标准卷积替换成深度可分离卷积二是对训练好的模型做剪枝量化。深度可分离卷积的做法是把标准卷积拆成“逐通道卷积”和“逐点卷积”两步。逐通道卷积对每个输入通道单独用一个卷积核做空间滤波逐点卷积再用1×1卷积核融合不同通道的信息。标准卷积的参数量是输入通道数 * 输出通道数 * 卷积核大小而深度可分离卷积的参数量是输入通道数 * 卷积核大小 输入通道数 * 输出通道数。以WDCNN第2层为例输入16通道输出32通道卷积核大小为3标准卷积参数量是16 * 32 * 3 1536深度可分离卷积只有16 * 3 16 * 32 560参数量减少了大约64%。改造后的轻量化WDCNN参数量从30万压缩到不到12万在CPU上推理一条2048点的样本耗时从约40毫秒降到约15毫秒已经可以满足在线监测的基本要求了。同时我用量化感知训练把权重从float32量化到int8模型体积缩小到原来的四分之一推理速度又提升了一倍左右。精度损失控制在0.5个百分点以内对于工程应用是完全可接受的。6. 实操中踩过的坑与排查清单6.1 过拟合与训练不收敛的现象与对策现象一训练集准确率很快冲到99%验证集始终停在85%左右而且验证loss在某个epoch后开始回升。这就是典型的过拟合。第一个对策是增强Dropout力度。标准WDCNN里全连接层前面的Dropout概率是0.5但如果你发现过拟合可以试着把卷积层之间也加上Dropout概率设为0.3。第二是数据增强尤其是对训练集加噪声这个方法在故障诊断里对抗过拟合的效果异常显著因为振动信号本身是一种高维时间序列模型很容易记住某些特定的波形结构而加噪声等于强迫模型学习实质性的特征模式。现象二训练loss根本不下降或者前3个epoch一直维持在1.3左右接近随机猜测的交叉熵值。大概率是学习率过大或参数初始化有问题。先检查初始化方法是否用了kaiming_normal_然后把学习率调低到1e-4再试。现象三训练loss下降但验证集准确率始终在正常和某一种故障之间摇摆。一般是因为数据划分出了问题检查一下是否在滑窗之前就把连续的信号段混入了训练和验证集。这个问题在故障诊断里极其常见我把它称为“数据泄漏”但它不是bug而是数据划分逻辑的问题。6.2 数据不平衡的隐藏影响如果你收到的是现场采集的数据故障样本可能远少于正常样本甚至某一种故障类型只有几十条样本。这种情况下即使你用了类别加权采样模型也可能只学个大概。我的经验是先做数据增强再做类别加权采样顺序不能反。如果先加权采样再增强增强产生的样本在同一个迭代周期内的变化范围太小起不到应有的作用。如果是先增强再采样的话增强后的少数类样本数量上来了权重采样器才会更平滑。另外一个容易忽略的点是测试集的分布也应该尽量贴近真实场景。不要为了追求好看的准确率而把测试集的类别分布也调整成均衡的——现场设备大多数时间是无故障的真正上线时模型会遇到大量正常样本如果测试集全是你精心配平的各类故障上线后统计的整体准确率会和在测试集上表现差一大截。6.3 关键细节汇总最后整理一份我在反复调试中总结的核心理念与设置方便你把参数直接落地使用。环节我的最优设置出错时的常见原因信号长度2048个采样点太短会丢失冲击信息太长增加算力开销滑窗重叠率50%重叠太少样本量不足重叠太多导致数据高度冗余Conv1卷积核/步长64 / 8太小捕捉不到完整冲击太大损失时序分辨率Batch Size64过小导致BN统计不稳过大可能显存不足优化器/初始学习率Adam / 3e-4学习率过大会导致后期震荡过小则收敛极慢Dropout0.5全连接层前过拟合时建议在卷积层之间再加0.3的Dropout激活函数ReLU尝试LeakyReLU时α取0.01效果差异不大训练轮数60~80配合StepLR调度后程还有小幅提升推理模式必须model.eval)忘记切回eval模式会让BN/dropout行为异常结果不可复现还要特别提醒一个很多人容易犯的错在测试阶段没有调用model.eval()。如果你的模型里有Dropout层忘了切回验证模式每次前向传播都会随机丢弃一部分神经元导致同一份测试数据每次预测出来的结果都不稳定混淆矩阵看起来就会很莫名其妙。我在代码里用了with torch.no_grad():来关闭梯度计算但很多人会忘记同时调用model.eval()这两个动作是独立的缺一个都会出问题。另外一个细节是关于随机种子的。如果你希望实验结果可以复现必须在程序开头固定随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsecudnn.deterministic True会牺牲一点点训练速度但能保证同一份代码在相同环境下跑出来的结果完全一致。如果你要做消融实验比如对比“加注意力”和“不加注意力”的效果差异这一步是必须的否则两次实验之间的随机性波动很容易掩盖真实的模型差异。踩过几次坑之后我养成了一个习惯每次修改模型结构先固定种子跑3遍取准确率的均值作为基准。等最终模型定型之后再取消固定种子看它在随机环境下的稳定性。前者的均值反映模型能力后者的方差反映模型稳定性两者结合才能真正判断一个改进是否有效。