PyTorch实现1DCNN轴承故障诊断:端到端原始信号识别实战

发布时间:2026/9/8 23:24:00
PyTorch实现1DCNN轴承故障诊断:端到端原始信号识别实战 简介面向机械故障诊断与深度学习交叉领域学习者提供基于一维深度卷积神经网络的轴承故障诊断完整Python实现以CWRU公开轴承数据集为对象覆盖信号特征提取与模式分类两大核心环节。资源共51个文件以Python源码为主13个py辅以模型结构图17个png、配置文件和说明文档xml/txt/md整体压缩包约3.63MB。代码包含数据预处理、模型定义、训练测试、t-SNE可视化以及混淆矩阵结果导出等模块目录结构清晰便于按流程对照学习和二次开发。目前已有6473人浏览下载适合具备Python基础并希望快速上手DCNN故障诊断实战的研究生或相关领域工程师。 从2022年开始我一直在做机械故障诊断方向的算法落地最开始跟大多数人一样拿着振动信号先做STFT或者小波变换把一维数据画成二维时频图再丢进ResNet里训练折腾一圈下来发现预处理环节的窗口长度、重叠率、小波基选择每一个参数都影响最终诊断精度而且换一个数据集就得重新调。后来我干脆把预处理砍掉直接用一维卷积网络1DCNN吃原始振动信号效果不仅没变差模型还更轻量、训练更快。这篇就把我用Python实现一维深度卷积神经网络做轴承故障诊断的完整思路、代码和踩坑经验整理出来希望对同样在做轴承故障诊断方向的人有帮助。这个项目适合谁如果你是刚接触深度学习故障诊断的学生或者在企业里做设备状态监测、需要搭建一套快速验证的轴承故障分类流程这篇文章可以直接作为参考起点。文中所有代码基于PyTorch实现数据集使用凯斯西储大学CWRU公开轴承数据集我会把数据加载、模型搭建、训练评估的完整链路都讲透同时也会重点聊一聊那些让准确率虚高的隐藏陷阱。1. 为什么我从时频图转向了一维卷积很多入门者接触轴承故障诊断第一反应就是把信号变个样再进网络。这种思路的来源也很自然毕竟图像分类领域太成熟了ResNet、VGG这些模型随手就能拿来用。我最早也是这么干的把振动信号做短时傅里叶变换得到时频图再缩放到224x224去跑分类网络很快就发现几个绕不开的问题。首先是时频变换本身会引入信息损耗。STFT的分辨率受到海森堡测不准原理限制窗函数选短了频率分辨率不够窗函数选长了时间分辨率又变差。选哪个窗、窗长多少、重叠率多少这些参数基本都是靠经验拍脑袋定的。其次小波变换更头疼小波基函数有上百种选择sym4、db10、morlet各不相同换一种小波基诊断结果的分布就完全变样。这意味着你在特征提取阶段就已经丢失了部分信息而网络再强也没法找回原始信号里已经被滤掉的成分。1DCNN的方案直接把这个问题绕过去了。振动信号本身就是一维时序数据一维卷积直接在原始采样点上滑动网络自己学习该关注什么频率成分、该提取什么形态特征。用端到端的方式把特征工程交给模型去完成而不是靠人工设计时频变换的超参数。加拿大渥太华大学的Ince等人2016年在IEEE Transactions on Industrial Electronics上发表的论文里就用一维卷积实现了电机故障检测当时的效果已经优于传统SVM加手工特征的方式这几年做滚动轴承诊断的顶会论文里直接吃原始信号的方案也越来越多。从工程角度说1DCNN还有一个非常现实的价值那就是推理速度快、部署成本低。时频图方案多了一个二维变换的前处理步骤在边缘计算设备上需要额外的算力开销而一维卷积直接把传感器采集到的原始信号送进网络连缓存图像的显存都不用占这在工业现场做实时监测时优势非常明显。2. 一维卷积和池化的直观理解手算一次就彻底明白卷积在二维图像上做的是滑窗加权求和在一维信号上也是一样的逻辑只是窗口只有一个维度。假设有一段信号 x [1, 2, 3, 4, 5]卷积核 w [0.5, 1, 0.5]这两个都是长度为3的向量我们让卷积核从信号开头滑动到结尾每到一个位置就把重叠部分的元素相乘再相加得到输出特征图。第一个位置重叠的是 [1, 2, 3] 和 [0.5, 1, 0.5]计算结果就是 1×0.5 2×1 3×0.5 4。滑动一个步长第二个位置重叠 [2, 3, 4]结果是 2×0.5 3×1 4×0.5 5。第三个位置重叠 [3, 4, 5]结果是 3×0.5 4×1 5×0.5 6。最终输出的特征图就是 [4, 5, 6]长度从5变成了3。这就是一维卷积最核心的计算过程。卷积核里的数值就是网络要学习的参数初始值是随机的通过反向传播不断调整最终让不同的卷积核学会捕捉不同类型的信号形态。有些卷积核会变成一个低通滤波器平滑噪声有些会变成边缘检测器捕捉冲击脉冲这些全在训练过程中自动涌现。了解了卷积核的滑动方式再来看几个关键参数。kernel_size决定每次看多长的局部信号对振动信号来说一般取3到7就足够了大小了捕捉不到有效局部特征太大了又容易把不同故障的冲击混在一起。stride是卷积核每次滑动的步长默认为1如果增大步长可以快速降维但同时会损失部分信息实际模型里降维主要靠池化层而不是大步长卷积。padding是在信号两端补零用来控制输出长度最容易理解的方式是记住输出长度公式L_out (L_in 2×padding - kernel_size) / stride 1。池化层的逻辑更直白MaxPool1d(2)就是每相邻两个值取最大的那个序列长度直接砍半。仍以刚才的输出 [4, 5, 6] 为例最大池化后变成 [5, 6]。池化的意义有两点一是把特征图的尺寸逐步减小让后面的卷积层能看到更大范围的语义信息二是提供一定的平移不变性也就是信号在时间上发生小幅度偏移时池化后的特征仍然能保持相似这对轴承故障诊断非常关键。因为轴承故障时产生的冲击脉冲虽然频率相对固定但在不同采样周期里的精确位置会有细微变化。我设计的网络结构从1024个原始采样点出发经过三层Conv1d加MaxPool1d(2)后信号长度逐级减半从1024到512、再到256、最后到128同时通道数从1扩展到16、32、64。接入全连接层之前特征图维度是64×128经过展平变成8192个神经元再经过两个全连接层输出到4个故障类别。BatchNorm层在每个小批量上做标准化抑制梯度在深层网络中传播时的内部协变量偏移问题Dropout层在训练时随机丢弃部分神经元降低过拟合风险。3. Python落地PyTorch实现1DCNN轴承故障诊断数据集我用的是CWRU这是轴承故障诊断领域最常用的公开数据集。实验台由电机、扭矩传感器和测力计组成振动信号由加速度计采集采样频率12kHz。我选用了驱动端加速度计数据故障位置包括内圈、外圈和滚动体每种故障包含不同损伤直径再加上正常状态组成了4分类问题。3.1 数据加载与预处理以下是加载CWRU的mat文件并提取振动信号的Python代码import numpy as np from scipy.io import loadmat def load_cwru_signal(filepath): mat loadmat(filepath) # CWRU的mat文件中通常包含多个键DE表示驱动端加速度计数据 key [k for k in mat.keys() if k.startswith(X)][0] signal mat[key].flatten() return signal拿到原始信号之后下一步用滑动窗口切成训练样本。这里窗口长度定为1024个采样点也就是约85毫秒的振动数据。为什么选1024而不是512或者2048窗口太短的话单个样本包含的冲击次数不够模型学不到完整的故障特征窗口太长又会显著增加计算量而且轴承故障诊断的场景里你可能需要在更短的时间内做出判断1024是一个经过权衡的选择。步长选512相邻窗口之间有50%的重叠这样能把有限的原始信号扩增出更多样本。def sliding_window(signal, win_size1024, step512): windows [] for start in range(0, len(signal) - win_size, step): windows.append(signal[start:start win_size]) return np.array(windows)3.2 模型定义模型定义直接用PyTorch的nn.Module包装import torch import torch.nn as nn class CNN1D(nn.Module): def __init__(self, num_classes4): super(CNN1D, self).__init__() self.features nn.Sequential( nn.Conv1d(1, 16, kernel_size3, padding1), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size3, padding1), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(64 * 128, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, x): return self.classifier(self.features(x))每层卷积都带padding1所以卷积操作不改变序列长度只有池化层会将序列长度减半。输入1024个点经过三次池化变成128通道数从1变到64全连接层之前展平得到64×1288192维特征。用output model(sample)可以直接打印每一层的shape确认维度是否正确。3.3 训练与评估训练部分我用Adam优化器初始学习率0.001损失函数用交叉熵。总共跑50个epoch批量大小64。为了让训练过程可控我把CoLaunch数据集按7:3划分成训练集和测试集保证测试集里的样本在时间和数据分布上都独立于训练集。import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0, 0, 0 for x, y in loader: x, y x.to(device), y.to(device) optimizer.zero_grad() out model(x) loss criterion(out, y) loss.backward() optimizer.step() total_loss loss.item() * x.size(0) correct (out.argmax(1) y).sum().item() total x.size(0) return total_loss / total, correct / total测试时把model切换到eval模式dropout和batch norm在推理时的行为与训练不同这在PyTorch里对应model.eval()调用。测试集上计算Accuracy和混淆矩阵混淆矩阵能清晰看出哪两个类别之间最容易混淆这是只盯着准确率看不到的。4. 实验复盘同工况与跨工况的差距比想象更大第一次跑完模型的时候测试准确率直接到了99.8%我当时还挺兴奋的。但仔细复盘之后发现这个数好看得太可疑了。CWRU数据的三种负载条件分别是0、1、2马力对应转速1797、1772、1750 r/min。如果我把其中某一个负载的数据同时放进训练集和测试集然后随机打乱划分那么训练集和测试集里可能包含来自同一段原始信号的不同窗口这些窗口在时间上是高度相关的测试自然会很准。这种做法的学术术语叫同工况随机划分在很多论文里能看到但工程上遇到的实际问题是负载和转速随时变化你的模型必须在新工况下也能工作。为了验证真实泛化能力我用负载0的数据训练负载2的数据测试。结果立刻大跌眼镜准确率掉到了85%左右。这不是个别现象我试了不同的随机种子和网络结构准确率基本都在80%到90%之间徘徊。这说明网络学到的一部分特征是负载敏感的一旦转速变了这些特征失效。这个结果也解释了为什么很多公开项目声称准确率99%甚至100%但实际部署到现场就失灵。除了工况问题还有三个隐蔽的数据泄露来源值得特别警惕。第一个就是刚才说的滑动窗口重叠如果步长小于窗口长度相邻样本之间有大量重叠划分数据集时如果没有按时间块切分泄漏会非常严重。正确的做法是先对整个信号划分时间块例如前70%的时间段作为训练源后30%作为测试源再从每个时间段里做滑动窗口。第二个是标准化时用了全量数据的均值和方差这等于测试信息提前进入了训练过程。规范做法是只使用训练集的统计量来标准化测试集。第三个是同一个实验条件下采集的多个文件之间的顺序排列问题如果加载时没有随机打乱模型可能学到文件顺序的规律而不是真正的故障特征。我后来把实验设置改成了下面的样子结果才更接近真实性能实验设置训练数据测试数据准确率同工况随机划分负载0的70%样本负载0的30%样本99.5%跨工况直接迁移负载0全部样本负载2全部样本85.2%混合工况训练负载0负载1混合样本负载2全部样本93.7%结论非常清晰混合工况训练能显著提升跨工况泛化能力但这要求你手头有足够多工况的数据。如果连这一点都做不到后续可以尝试域自适应方法比如在特征提取器后面加一个领域判别器用对抗训练把不同工况的特征分布拉近这类方法对提升跨工况性能很有效但那是另一个层次的话题了。5. 想复现好这个项目以下几个习惯值得养成跑了几百组实验之后我总结出几个让1DCNN项目少走弯路的实操习惯都不复杂但很管用。第一训练时多关注Loss曲线而不是只看Accuracy。Accuracy在高位时很容易对细微变化不敏感Loss却能在过拟合发生前给出预警。我在训练过程中发现验证集Loss开始回升、训练集Loss还在下降时就知道模型开始记住训练数据里的噪声了这时候应该停止训练或者加大Dropout比例。把训练损失和验证损失画在同一个图里如果两者之间的gap越来越大就是过拟合的明确信号。第二先跑小规模实验再上全量数据。我第一次就用全部数据跑了一个50 epoch的完整训练结果发现学习率设大了前几个epoch损失直接发散白白浪费了一个多小时。后面我改成先取每个类别2个样本做一轮快速迭代确认模型的维度没报错、损失在下降再放宽到10%的数据跑5个epoch验证代码逻辑最后才用全量数据做正式训练。这样排查bug的时间成本降到最低。第三保存模型时优先只保存state_dict而不是整个模型。直接torch.save(model, path)虽然方便但加载时要求类定义在同一个模块里别人拿到文件换个文件名就报错。保存state_dict加上一个说明文件结构的配置字典才是干净可复现的做法。torch.save(model.state_dict(), cnn1d_cwru.pth) # 加载时 model CNN1D(num_classes4) model.load_state_dict(torch.load(cnn1d_cwru.pth))我在实际测试中还踩过一个batch size的坑当batch size从64改到16时仍然是同一套代码和同一批数据最终测试准确率却低了2个百分点左右。原因是batch size太小导致BatchNorm层的统计量不够稳定训练过程抖动明显后期学习率也没有相应调整。改成大批量加学习率衰减之后训练曲线平滑了很多。如果你复现时发现效果不如预期优先检查batch size和相关超参数而不是一上来就改网络结构。最后再说一个容易被忽略的点那就是不同故障类型的样本均衡性。CWRU原始数据里正常状态的样本量远少于故障状态的样本量如果直接混合训练模型会对正常状态的识别率偏低而故障诊断场景里漏报正常样本的代价往往最高。我采用的做法是对正常状态样本做更多重叠率更高的滑动窗口扩增并给少数类在损失函数里加一个权重系数这样才把正常状态的精确率和召回率都拉到了95%以上。如果你准备基于这份代码做更多尝试一个很好的方向是先用1DCNN作为特征提取器把中间的8192维特征接上t-SNE降维可视化你会直观看到不同故障类型在特征空间里的聚类形态。再往后可以根据实际需要把模型改成多分支结构一部分卷积核用大尺寸捕捉低频特征另一部分用小尺寸捕捉冲击脉冲这种并行多尺度设计在当前轴承故障诊断的研究中是提升识别率的常见手段。本文还有配套的精品资源点击获取