ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深度学习故障诊断实战:CNN到Transformer模型代码与数据预处理全指南

深度学习故障诊断实战:CNN到Transformer模型代码与数据预处理全指南 简介面向进行设备故障诊断研究的工程师与高校师生基于深度学习的故障诊断模型代码和数据包提供了一套可直接运行的完整方案。内容覆盖传感器数据处理、CNN与LSTM模型定义、训练与验证、测试评估及预测调用流程配套正常与故障状态数据集及已训练模型文件方便快速复现实验并在此基础上修改结构或参数。整个资源包为rar格式共63个文件约57.74MB核心包括8个py源代码、3个csv数据表、10个mat原始数据、5个xml工程配置、1个h5模型文件及说明文档等目录按训练、日志、数据等模块划分便于定位。目前已有11381人浏览学习清晰注释和完整的工程结构使其既能用于论文实验验证也能为工业场景中的故障识别任务提供参考起点。 做故障诊断这几年我手里攒了不少能直接跑的深度学习代码和配套数据集。从最开始的CNN轴承故障分类到后面用Transformer做剩余寿命预测再到把注意力机制塞进模型里硬刚信噪比极低的现场数据一路踩坑无数。这篇博文我就把自己实际在用的模型框架、代码细节和数据预处理心得整理出来给正在入门或者卡在某个环节的朋友一个能直接参考的施工图。文章不是教科书而是我自己实操后觉得最稳的方案包括怎么准备数据、怎么写模型的骨架、怎么把训练好的模型加载到本地去预测新的数据。1. 整体设计思路先把诊断问题翻译成深度学习能听懂的语言1.1 核心需求解析故障诊断这事本质上是一个模式识别问题。设备正常运行的时候振动信号、电流信号都有相对稳定的统计特征一旦出现轴承磨损、齿轮断齿、转子不对中这些信号的时域波形和频域分布就会发生变化。传统方法靠工程师的经验去提取特征比如算一下峭度、峰值因子、边频带能量再用SVM或者随机森林去做分类。但现场数据很复杂负载波动、噪声干扰、转速变化都会让特征提取变得不稳定。深度学习的好处是它能把“特征工程”和“分类器训练”这两步合并成一步。卷积神经网络直接吃原始的一维振动信号通过卷积核自动学习不同频段的响应模式循环神经网络擅长捕捉信号在时间顺序上的依赖关系适合做趋势预测Transformer靠自注意力机制能同时关注到信号中相距很远的两个峰值之间的关系。这次整理的代码库里这几类模型都覆盖到了。1.2 选型方案背后的经验判断我最早做故障诊断的时候也走过弯路。当时迷信复杂的网络结构把ResNet的残差块直接搬到一维信号上结果在公开数据集上准确率确实高但换到现场数据立马翻车。原因很简单实验室数据采样频率固定、工况稳定现场数据却有大量的变工况和噪声干扰。后来我总结出一个比较稳妥的思路当数据量比较小几千个样本时用结构相对简单的CNN加几层全连接当数据量中等几万个样本时可以上残差结构或者带注意力机制的模型只有当数据量非常大且标注质量高的时候才值得去尝试Transformer这类大模型。这个选型逻辑我现在写进了代码库的说明文档里每次用的时候都提醒自己不要为了炫技而堆参数。2. 数据集构建与预处理模型能不能work七成看这里2.1 公开数据集怎么选、怎么下做轴承故障诊断最常用的公开数据集是凯斯西储大学的CWRU数据集。数据是驱动端和风扇端的振动信号采样频率有12kHz和48kHz两档故障类型包括内圈故障、外圈故障、滚动体故障每个故障又分为0.007英寸、0.014英寸、0.021英寸三种损伤程度。这个数据集我用了三年最大的优点是故障类型齐全、样本量大适合做算法验证。下载的时候有个小技巧直接去官网把mat文件全部拉下来用scipy.io.loadmat读取。但如果网络不稳定也可以在GitHub上找别人转好的csv版本只是要确认一下数据的截取范围是不是完整的有些分享者会把信号截短导致频谱信息丢失。我一般会在下载后先做一步检查随便挑一个正常状态和一个内圈故障的样本用matplotlib画出时域波形再用fft看频谱如果故障样本在特征频率处有明显峰值说明数据基本没问题。2.2 数据增强和样本切分的实操细节深度模型训得好不好数据切分方式很关键。我在代码里默认把每个样本的长度设为1024个点对应12kHz采样频率下大约85毫秒的信号。当负载为0的时候电机转速接近1797rpm转频约30Hz一个样本里包含了大约2.5个转轴的旋转周期足够捕捉到轴承故障的特征频率。切分策略上要特别小心CWRU数据集中每个文件是一条连续的长时间序列。如果随机切分然后随机分训练集和测试集同一个文件切出来的样本可能同时出现在两边造成数据泄漏测试集准确率虚高。我踩过这个坑后来改成按文件切分也就是一个文件的所有样本要么全在训练集要么全在测试集。这个细节在代码里加了个参数来控制默认是开启的建议不要关掉。2.3 数据预处理流程预处理部分我主要做三步。第一步去均值把信号的直流分量去掉避免后续卷积计算被偏移量干扰。第二步归一化将每个样本的幅值缩放到[-1, 1]区间公式是 x_norm (x - x_min) / (x_max - x_min) * 2 - 1。第三步对样本做标准化也就是减去均值除以标准差。两步归一化看似重复但前者是为了保持波形形态的直观性后者是为了让模型训练更稳定我在代码里两步都做了。3. 模型代码实现从CNN到注意力机制每一层的设计理由3.1 一维CNN轴承故障分类模型先看应用最广泛的基于一维卷积的轴承故障分类模型这个模型结构比较简洁整体代码流程如下import torch import torch.nn as nn import torch.nn.functional as F class FaultCNN(nn.Module): def __init__(self, num_classes10): super(FaultCNN, self).__init__() self.conv1 nn.Conv1d(1, 16, kernel_size64, stride8, padding28) self.bn1 nn.BatchNorm1d(16) self.conv2 nn.Conv1d(16, 32, kernel_size32, stride4, padding14) self.bn2 nn.BatchNorm1d(32) self.conv3 nn.Conv1d(32, 64, kernel_size16, stride2, padding8) self.bn3 nn.BatchNorm1d(64) self.global_pool nn.AdaptiveAvgPool1d(1) self.fc nn.Linear(64, num_classes) def forward(self, x): x x.unsqueeze(1) x self.bn1(F.relu(self.conv1(x))) x self.bn2(F.relu(self.conv2(x))) x self.bn3(F.relu(self.conv3(x))) x self.global_pool(x) x x.view(x.size(0), -1) x self.fc(x) return x这里的第一个卷积核大小设为64为什么是64不是3因为一维振动信号的局部特征不是像素级别的边缘而是一段包含多个周期的波形。64个采样点在12kHz下对应约5.3毫秒这个时间长度能覆盖大约0.16个转轴周期可以捕捉到故障冲击引起的一个完整的衰减振荡过程。如果卷积核太小看到的只是波形上的毛刺学不到故障冲击的本质特征。第三个卷积层后面接了一个全局平均池化这一层的作用是强制让网络输出与故障类别相关的空间分布。全局平均池化把每个通道的特征图压缩成一个标量好处是大幅度减少参数量降低过拟合风险而且让模型对输入样本的长度不那么敏感。3.2 基于Transformer的故障诊断模型Transformer模型在自然语言处理中大放异彩之后很多人尝试把它用到时间序列分类上。我在轴承故障诊断上也试过Transformer做法是先把长度为1024的一维信号切分成64个长度为16的小片段每个片段投影成一个128维的向量再加上位置编码然后送入TransformerEncoder层。class FaultTransformer(nn.Module): def __init__(self, input_length1024, num_classes10, d_model128, nhead8, num_layers4): super(FaultTransformer, self).__init__() self.patch_size 16 self.num_patches input_length // self.patch_size self.proj nn.Linear(self.patch_size, d_model) self.pos_embed nn.Parameter(torch.randn(1, self.num_patches, d_model)) encoder_layer nn.TransformerEncoderLayer(d_modeld_model, nheadnhead, batch_firstTrue) self.transformer nn.TransformerEncoder(encoder_layer, num_layersnum_layers) self.cls_head nn.Sequential( nn.LayerNorm(d_model), nn.Linear(d_model, num_classes) ) def forward(self, x): x x.view(x.size(0), self.num_patches, self.patch_size) x self.proj(x) self.pos_embed x self.transformer(x) x x.mean(dim1) x self.cls_head(x) return x实验下来Transformer在CWRU数据上准确率能到98%以上并不比CNN高多少但训练时间几乎是CNN的三倍。如果你刚入门我还是建议先用CNN把整个流程跑通Transformer留着在数据量特别大、情况特别复杂的时候再上。3.3 轻量级模型的选择有个很容易被忽略的问题是故障诊断模型很多时候不是跑在服务器上而是要部署到嵌入式设备或者工业电脑上。这种场景对模型大小和推理速度非常敏感。我在一个实际项目中用过MobileNetV3的一维版本把原本的3x3深度可分离卷积改为适配一维信号的形式参数量只有标准CNN的十分之一在树莓派上跑一次推理不到30毫秒。轻量化的另一条路线是知识蒸馏。用一个大模型教师网络在数据集上学到的软标签去指导一个小模型学生网络训练小模型能学到比硬标签更丰富的信息。我在代码库中加了一个简单的蒸馏训练脚本关键点在于教师网络输出层的温度参数。温度设得越高软标签的分布越平滑学生网络能学到的类间相似性信息就越多但温度过高也会模糊掉真正的分类边界具体数值需要交叉验证来定。4. 实战复现GWO-CNN-BiLSTM-Attention混合模型的实现要点4.1 为什么用混合模型最近热词里频繁出现GWO-CNN-BiLSTM-Attention这种融合模型意思是灰狼优化算法优化的卷积神经网络加双向长短期记忆网络加注意力机制。它的思路很清晰CNN负责提取局部特征BiLSTM负责捕捉时间序列中的前后依赖关系注意力机制负责在时间维度上自动加权让模型重点关注最具有判别力的时间段最后的GWO用于自动搜索最优超参数比如学习率、隐藏层神经元个数、正则化系数。这个模型有个显而易见的缺点训练时间很长调参也麻烦。所以我的建议是在你用简单CNN已经能拿到95%准确率的情况下没必要换这个复杂模型。但如果现场工况变化多样、简单模型始终无法收敛那GWO自动调参就体现出优势了它能省去大量手工试错的时间。4.2 每个模块的代码实现拆解CNN部分沿用前面写的FaultCNN里的前两层把输出展平后再送入BiLSTM。BiLSTM的核心是双向结构正向和反向两个LSTM同时处理序列每个时间步的输出拼接在一起这样每个时间步的向量里既包含了过去的信息也包含未来的信息特别适合捕捉故障冲击前后的波形变化。import torch.nn as nn class CNNBiLSTMAttention(nn.Module): def __init__(self, num_classes10, hidden_size64, num_layers2): super(CNNBiLSTMAttention, self).__init__() self.conv1 nn.Conv1d(1, 16, kernel_size64, stride8, padding28) self.bn1 nn.BatchNorm1d(16) self.conv2 nn.Conv1d(16, 32, kernel_size32, stride4, padding14) self.bn2 nn.BatchNorm1d(32) self.lstm nn.LSTM(input_size32, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, bidirectionalTrue) self.attention nn.Sequential( nn.Linear(hidden_size * 2, hidden_size), nn.Tanh(), nn.Linear(hidden_size, 1) ) self.fc nn.Linear(hidden_size * 2, num_classes) def forward(self, x): x x.unsqueeze(1) x self.bn1(torch.relu(self.conv1(x))) x self.bn2(torch.relu(self.conv2(x))) x x.permute(0, 2, 1) lstm_out, _ self.lstm(x) attn_weights torch.softmax(self.attention(lstm_out), dim1) attn_applied torch.sum(lstm_out * attn_weights, dim1) out self.fc(attn_applied) return out注意力机制的计算过程很直观对每个时间步的LSTM输出做一层线性变换加tanh激活得到一个标量分数然后在时间维度上做softmax归一化最后把每个时间步的向量按照这个权重加权求和。这样模型就能自动学会“在故障冲击发生的那几个时间点加大权重”。4.3 灰狼优化算法的调参逻辑灰狼优化算法模拟了灰狼群体的等级制度和围猎行为用四个层次的狼alpha、beta、delta、omega来引导搜索方向。我把训练过程中的验证集准确率作为适应度函数让GWO去搜索CNN初始学习率、BiLSTM隐藏层数量、注意力维度、dropout比例这四个超参的取值。GWO的迭代过程可以在20代以内就收敛到不错的参数组合比网格搜索效率高得多。有一个关键点必须注意GWO每评估一组参数就要完整训练一个模型如果不加控制训练时间会非常恐怖。我的做法是在GWO搜索阶段用较小的训练轮数比如10个epoch和一个固定的随机种子来粗筛找到最佳参数组合后再在完整数据集上用较大轮数做精细训练。5. 模型训练、评估与加载的避坑指南5.1 训练脚本里的关键参数设定训练时我用的损失函数是交叉熵优化器是AdamW初始学习率设为1e-3配合CosineAnnealingLR学习率调度器让学习率在训练过程中周期性地下降再回升这样能帮助模型跳出局部最优。批量大小设为64训练轮数设为50如果验证集准确率在10个epoch内没有提升就触发早停保存最佳模型。checkpoint { epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), best_acc: best_acc, } torch.save(checkpoint, best_model.pth)这里要注意保存模型时要考虑dataloader里的类别顺序。CWRU数据集如果按文件名排序类别索引可能和你预期的不一致。我习惯在训练前把标签映射表保存成json文件加载模型时根据这个映射表把预测的类别索引转成实际的故障类型名称。5.2 加载本地模型做推理的正确方式很多新手问我训练好的模型怎么加载到本地做预测。核心是先重建模型结构再加载权重代码非常简单model FaultCNN(num_classes10) checkpoint torch.load(best_model.pth, map_locationcpu) model.load_state_dict(checkpoint[model_state_dict]) model.eval() def predict(signal): import numpy as np signal (signal - signal.mean()) / (signal.std() 1e-8) tensor torch.FloatTensor(signal).unsqueeze(0).unsqueeze(0) with torch.no_grad(): logits model(tensor) pred_idx torch.argmax(logits, dim1).item() return pred_idx加载模型前有个必须做的动作如果要加载模型中使用的所有类需先确保自定义类可见。我的代码里推荐直接用import导入模型文件中的类定义。加载后务必调用model.eval()否则BatchNorm层在推理时会使用训练时统计量和当前batch属性不一致输出结果可能很奇怪。这里是Dropout在推理时被关闭BatchNorm被切换到使用全局统计量这样模型的输出才是确定的。5.3 模型评估评估指标除了准确率我自己更关注每一类的精确率和召回率专门绘制了混淆矩阵。因为CWRU数据集本身类别均衡准确率作为单一指标问题不大。而在实际工业场景中正常状态样本远多于故障样本如果只看准确率即便把所有样本都识别成正常准确率也可能超过90%这对故障诊断毫无意义。所以代码里我加了F1-score的计算函数并在训练结束后打印每一类的分类报告。6. 常见问题排查与避坑实战记录6.1 训练不收敛loss保持不变遇到最多的情况是learning rate设得过大或者数据没有做归一化。我曾经把学习率设为0.01loss在前几个epoch掉得很快然后就卡住了这是因为AdamW这类优化器对学习率比较敏感相对合理的初始学习率在1e-3到3e-4之间。如果loss一直不降先把学习率调到1e-4再试还是不行就检查数据预处理看送入模型的数据是否方差过低几乎变成一条直线。另外一个容易忽略的点是类别标签是否从0开始连续编号。如果你的标签数组是[1, 2, 3, 4]而不是[0, 1, 2, 3]而模型输出维度设为4交叉熵函数会直接报错或者导致loss计算异常。我在数据处理环节会强制对所有标签做一次LabelEncoder映射。6.2 模型在训练集上效果好测试集上很差这是典型的过拟合。工业现场数据量本来就少如果模型参数量过大确实会把训练集中的噪声都背下来。解决办法有三个方向一是增加Dropout比例我一般设在0.3到0.5之间二是做数据增强在原始信号上加入随机高斯噪声、随机幅度缩放和随机时间偏移模拟实际工况波动三是使用早停验证集准确率不再提升就立即停止训练。数据增强这块特别提醒一下对于振动信号现场有工程师喜欢用SMOTE方法做少数类过采样。这个方法在表格数据上效果不错但直接用在时序信号上容易生成不自然的波形。我更推荐在频域做增强也就是对原始信号做FFT在频谱上叠加随机噪声再逆变换回时域这样得到的增强样本保留了故障特征频率的结构信息模型学起来更可靠。6.3 PyTorch加载模型时报错“Missing key(s) in state_dict”这个报错通常是模型的类名不一致导致的。模型在保存时权重参数的名字和你在推理脚本里重建的模型参数名字对不上最常见的情况是你修改了模型的卷积层个数或者把变量名从conv1改成了conv2d_1。解决办法是加载时设置strictFalse然后打印出state_dict的key和当前模型的key逐一对比。我曾经在重构模型时把self.fc改成了self.classifier导致所有全连接层的权重都匹配不上折腾了好一阵子才排查出来。6.4 加载模型时GPU和CPU不匹配在GPU上训练的模型在无GPU环境中加载会报device错误。最简单的办法是加载时指定map_locationcheckpoint torch.load(best_model.pth, map_locationcpu)如果目标机器有GPU但不是同一型号也建议先用CPU加载权重再用model.to(cuda)转移到当前设备。这一步看起来不起眼但能避免很多新旧版本PyTorch之间设备不兼容的坑。6.5 混淆矩阵中某一类总是被分错如果模型总是把某一类故障识别成另一类不要急着调模型结构先回来看数据。比如滚动体故障的特征频率通常比较微弱采样点不够多或者损伤程度较轻时确实很难与正常状态区分。这种情况下有两个处理思路一是用带通滤波把与故障特征频率无关的噪声滤除二是把原始信号和小波包分解后的细节信号拼接起来作为多通道输入让模型同时看到原始信息和频带细节信息。我在代码库中封装了一个简单的小波变换预处理函数默认使用db4小波基分解层数设为3把第三层的细节系数重构出来和原始信号叠加作为双通道输入。这样改完之后CWRU数据集中滚动体故障的F1值从0.91提升到了0.96效果比较显著。7. 对整个项目的复盘与经验小结从确定用深度学习做故障诊断到整理出这份代码库和数据文档前后累积了不少实际项目的经验。最核心的一点是不要一上来就追求最高的准确率而是先把数据管线跑通。很多初学者把时间花在调整网络结构上结果发现训练集和测试集划分还存在着数据泄漏问题那后面的一切努力都是白费。在实际操作中我体会最深的是要养成保存训练笔记的习惯。每次实验记录数据集版本、切分方式、模型结构、预处理方法、训练参数、测试结果这样在方向跑偏时能够快速回溯。这套代码库使用了固定的随机种子和统一的实验记录结构也方便复现结果。后续如果你想扩展这个框架可以直接替换数据集做电机电流信号的故障诊断也可以把最后的分类层换成回归层去做剩余寿命预测底层的数据加载和训练评估逻辑都不用改动。本文还有配套的精品资源点击获取
返回列表