基于一维CNN与PyTorch的滚动轴承故障诊断实战
2026/9/13 7:21:14 网站建设 项目流程

简介:振动信号分析是旋转机械状态监测的核心技术,传统频谱分析在变转速、强噪声环境下往往力不从心。深度学习为解决这一难题提供了新思路,其核心原理是通过多层卷积网络自动从原始波形中学习故障特征,替代人工特征工程。一维卷积神经网络尤其适合处理时序信号,它能在不同时间尺度上提取局部冲击模式,实现端到端的智能诊断。这一技术广泛应用于工业设备健康管理、预测性维护等场景,可显著降低非计划停机风险。本文基于凯斯西储大学公开数据集,使用PyTorch搭建一维CNN模型,完整实现滚动轴承故障分类,涵盖数据预处理、模型设计、训练调优与评估部署,测试集准确率接近99%,为工业故障诊断落地提供了可复用的工程实践参考。 做滚动轴承故障诊断这个方向,我最怕听到的一句话是“直接用FFT看看频谱就行”。不是说频谱分析没用,而是当设备转速波动、负载变化、背景噪声一上来,人工看频谱这套活儿不仅耗时,而且极度依赖老师傅的经验。我自己在这个坑里踩过很久,后来把整套流程迁到深度学习上,用Python实现了端到端的智能诊断系统,从数据预处理到模型训练再到结果可视化,全部一条龙跑通。这篇文章就是把这段完整经历拿出来分享,包括数据集怎么选、模型怎么搭、训练参数怎么调、遇到哪些坑,以及最终效果如何。适合正在做工业故障诊断、设备健康管理,或者刚入坑深度学习想找个实际项目练手的读者。

先交代一下项目的基本盘:诊断对象是滚动轴承,故障类型覆盖正常、内圈故障、外圈故障、滚动体故障四类,振动信号作为唯一输入。核心思路是用一维卷积神经网络直接从原始振动波形中学习故障特征,省掉人工构造特征这一步。整套代码基于Python完成,深度学习框架选PyTorch,数据集使用公开的凯斯西储大学(CWRU)轴承数据。项目最终在测试集上达到了接近99%的分类准确率。

1. 项目全景设计:从需求到方案选型

1.1 项目要解决的核心问题

滚动轴承是旋转机械里最容易出故障的部件之一,一旦坏了,轻则设备停机,重则整条产线瘫掉。传统做法是定期停机检修,但这既费钱又影响生产。拿我做过的一个电机轴承包案例来说,一次非计划停机造成的损失,抵得上好几套传感器加边缘计算设备的采购费用。所以大家越来越倾向于做在线监测和智能预警,也就是设备还在转的时候,就能通过振动信号判断它有没有故障、故障在哪个位置、严重程度如何。本项目的核心目标就是把“振动信号→故障类型”这一映射关系用深度学习模型自动建立起来。

这里有个很关键的工程问题:振动信号是非平稳、非线性的,轴承不同位置的故障会在不同频段激起不同的共振响应。传统特征工程需要人工计算时域统计量(均方根、峰值因子、峭度),再做频域分析(包络谱、小波分解),这套流程研究价值高,但落地时每个人提取的特征还不一样,很难标准化复用。深度学习模型则直接把原始波形喂进去,由卷积层自行学习多尺度特征。我整个项目的设计逻辑就是围绕“少做人工特征、多做数据驱动”来展开的。

1.2 为什么选择深度学习而不是传统信号处理方法

先给传统方法一个公道评价:FFT包络谱在恒定转速、单一工况下确实好用,尤其是对轴承内圈、外圈故障特征频率的判断,物理意义非常清晰。但实际工业现场没这么理想,转速波动会把特征频率抹开,负载大范围变化会让幅值规律变得不稳定,再加上齿轮啮合、不对中、松动等干扰源的信号混叠在一起,人工包络分析经常做出矛盾的判断。

深度学习路的优势在于它可以自动学习一个从原始信号到故障类别的高维非线性映射。模型内部数十个卷积核相当于在不同频段、不同时间尺度上做自适应特征提取,这个过程比人肉调包络参数要稳定得多。我并不是说深度学习要完全取代传统方法,在实际工程里两者可以互补——用传统方法做信号质量的快速校验,用深度学习做最终分类。但在项目的主体框架里,深度学习的端到端能力确实最适合做成一个统一的智能诊断系统。

还有一个很现实的选型理由:深度学习模型的推理速度极快。我训练好的模型在普通CPU上处理一段1024个点的信号只需要几毫秒,完全能满足在线实时诊断的延迟要求。如果换成嵌入式设备和TensorRT加速,甚至可以做到微秒级。这对后续往边缘端部署来说是非常大的优势。

1.3 技术栈选型与版本说明

Python在工业算法领域几乎是统治级的存在,所以语言层面没有任何纠结空间。深度学习框架我在PyTorch和TensorFlow之间做了一轮对比,最终选了PyTorch。原因包括三个方面:第一,PyTorch的动态计算图机制在调试模型结构时非常直观,断点打进去能看到每个张量的shape;第二,社区生态现在明显向PyTorch倾斜,很多预训练模型和论文代码都优先给PyTorch版本;第三,torchvision、torchaudio等配套库对信号处理相关的数据流水线支持很友好。

我的开发环境如下表所示,各位可以参照这个版本组合,实测下来兼容性很稳:

组件版本说明
Python3.9.13太老的版本对PyTorch 2.x支持不好
PyTorch2.0.1使用CPU/GPU自动切换逻辑
numpy1.24.3注意与PyTorch版本兼容
scipy1.10.1用于信号读取与预处理
scikit-learn1.2.2数据划分与评估指标计算
matplotlib3.7.1结果可视化

这里特别提醒一下,Python版本不要用太新的,比如3.12在部分情况下安装PyTorch会出兼容问题。你要是刚开始配环境,建议直接用Anaconda创建独立环境,避免把系统Python弄乱。后面第5章我会单独讲常见环境坑。

2. 数据集准备:完整训练的前提

2.1 为什么选择凯斯西储大学(CWRU)轴承数据集

做故障诊断的同行对CWRU数据集应该都不陌生,它是这个领域最经典的公开基准数据。数据由美国凯斯西储大学轴承数据中心发布,采集对象是电机驱动系统中的轴承振动信号。整个数据集的设备配置是在电机两端分别安装加速度传感器,采样频率有12k和48k两档,覆盖了负载0到3马力四种工况,故障类型包括内圈、外圈、滚动体三类,每类又有0.007英寸、0.014英寸、0.021英寸、0.028英寸等不同损伤尺寸。

选择CWRU数据集有几个现实考量。一是公开好获取,不需要自己搭实验台,这对很多人来说是零成本起步的关键。二是样本量大,每次实验采集的连续信号长度都足够滑窗切出大量独立样本,为深度模型训练提供了充足的数据保障。三是标注信息完善,每个文件名的后缀直接标明故障位置和损伤程度,建标签非常方便。四是社区公认度高,用这个数据集跑出来的结果可以跟文献横向对比,验证模型的可靠性。

当然CWRU数据集也有它的局限性,比如采样环境比真实工业现场干净很多,工况变化相对简单。我建议把它当作算法验证的基石,而不是最终部署场景的完全仿真。我的项目主体流程是在CWRU上完成的,后续如果采集到现场轴承信号,只需要做同样的预处理流程,再对模型做微调即可迁移应用。

2.2 数据预处理:滑窗切分、归一化与标签编码

数据预处理的第一个决策点是样本长度。CWRU数据集的采样率是12kHz,意味着每秒有12000个采样点。滚动轴承故障特征频率通常在几十赫兹到几百赫兹之间,我按经验选择单个样本长度为1024个点,约等于85毫秒的信号,这个长度既能覆盖足够多的振动周期,又不会因为过长导致计算量过大。接下来用滑动窗口切分连续信号,窗口步长设为512个点,也就是重叠率50%。这么做的好处是样本数量翻倍,同时相邻样本之间仍有一定独立性,不容易造成严重的数据泄漏。

切分过程用一段简洁的代码就能实现:

import numpy as np def sliding_window_slice(raw_signal, window_size=1024, stride=512): samples = [] total_length = len(raw_signal) for start in range(0, total_length - window_size, stride): end = start + window_size samples.append(raw_signal[start:end]) return np.array(samples)

切好样本后,我做了两步重要处理。第一步是归一化,这里我选择z-score标准化,公式为:

$$x_{\text{norm}} = \frac{x - \mu}{\sigma}$$

其中$\mu$和$\sigma$是每个样本自身的均值和标准差。这样做的目的是消除不同实验工况下信号幅值绝对水平的差异,让模型专注于波形形态,而不是被整体幅值带偏。第二步是给每个样本打标签,四类目标对应0、1、2、3,然后用PyTorch内置的交叉熵损失函数,标签直接用整数索引即可,不需要手动做one-hot编码。

数据切分完成后,我统计了一下样本数量分布,正常情况下每个类别能切出几千个样本,完全够训练一个中等规模的卷积网络。有一点需要特别注意:统计样本时不要只看总数,要确认每个类别的样本量基本均衡,如果某个故障类别样本明显偏少,后续训练很容易出现偏向多数类的问题。

2.3 数据集划分策略与防泄漏要点

很多初学者在做数据划分时习惯直接random split,但在时序信号场景里这个做法隐患很大。原因是同一段连续信号滑窗切出来的相邻样本之间高度相关,如果简单随机划分,训练集和测试集之间可能出现“近亲样本”,导致测试准确率虚高。说白了就是模型作弊了,它可能已经记下了测试集样本的邻近片段。

我采用的做法是先按“实验文件”级别分组,再在文件组层面划分数据集。假设某个故障类型的原始信号长度为20秒,我先把它切成100个文件组,每个组内再做滑窗采样,划分时保证同一文件组的所有样本全部进入训练集或者全部进入测试集,不让它们跨集出现。这样可以最大限度避免时间邻近导致的数据泄漏。

实际划分比例我用了60%训练、15%验证、25%测试,如下表所示:

数据集样本数用途
训练集约12000模型权重学习
验证集约3000超参数调优与早停判断
测试集约5000最终泛化性能评估

这里比例没有采用常见的8:1:1,是因为我宁可训练数据稍少一点,也要保证测试集足够大,让最终的准确率置信区间更窄。数据加载部分我用了PyTorch的Dataset和DataLoader,把预处理后的样本打包成张量,设置batch_size为64,num_workers根据机器配置设为4。这套流程跑下来,数据加载不再是训练瓶颈。

3. 核心模型设计:一维CNN网络的搭建与训练

3.1 模型架构设计思路

针对一维振动信号,最自然的网络结构就是一维卷积神经网络。它的卷积核在时间轴上滑动,相当于用不同长度的滤波器去匹配信号中的局部模式,这跟传统带通滤波器的思想有异曲同工之处,但参数完全由数据驱动学习。我的模型结构经过了多轮迭代,最终确定为四个卷积模块加一个分类头的结构。

具体每层参数配置如下:

卷积核数卷积核大小池化输出维度
输入层---1024×1
Conv1d_1163, padding=1MaxPool(2)512×16
Conv1d_2323, padding=1MaxPool(2)256×32
Conv1d_3643, padding=1MaxPool(2)128×64
Conv1d_41283, padding=1MaxPool(2)64×128
分类头---4

每一层都带上BatchNorm和ReLU激活函数。BatchNorm在这里起到两个作用:一是缓解梯度消失,让深层网络的训练更稳定;二是对每批数据的分布做归一化,让模型对输入幅值波动更鲁棒。分类头用全局平均池化将特征图压缩成一维向量,再接一个线性层输出四个类别的logits。全局平均池化比直接Flatten参数量更少,也不容易过拟合。

模型定义代码如下:

import torch import torch.nn as nn class BearingCNN(nn.Module): def __init__(self, num_classes=4): super().__init__() self.features = nn.Sequential( nn.Conv1d(1, 16, kernel_size=3, padding=1), nn.BatchNorm1d(16), nn.ReLU(inplace=True), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size=3, padding=1), nn.BatchNorm1d(32), nn.ReLU(inplace=True), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size=3, padding=1), nn.BatchNorm1d(64), nn.ReLU(inplace=True), nn.MaxPool1d(2), nn.Conv1d(64, 128, kernel_size=3, padding=1), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.MaxPool1d(2), ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Linear(128, num_classes) ) def forward(self, x): x = self.features(x) x = self.classifier(x) return x

这里要解释一下为什么卷积核大小选了3而不是更大的5或7。小卷积核配合深层堆叠能获得更大的感受野,同时参数量更少。而且工业信号里的局部冲击特征通常在几个采样点内就完成一次起落,卷积核过大反而会把它抹平均。实测下来kernel_size=3的效果比kernel_size=7高大约1个百分点。

3.2 训练配置与超参数选择

训练配置的每个细节都会影响最终性能。我用的损失函数是nn.CrossEntropyLoss,优化器选择Adam而不是SGD。Adam对学习率的敏感度低,收敛速度快,特别适合从零开始训练的实验阶段。当然Adam有个缺点是可能收敛到sharp min(泛化略有折扣),所以我把weight_decay设成1e-4,对模型参数做L2正则化,相当于在优化目标里加一个参数范数的惩罚项,抑制过拟合。

学习率的初始值设为0.001,批量大小64。训练轮数设定为50,但其实到第30轮左右模型就开始趋于收敛。我在训练中加入了余弦退火学习率调度器,做法是让学习率随训练轮数从0.001平滑衰减到接近0,这样可以在训练后期让权重落到更平滑的极小值点,泛化性能更好。训练循环的核心代码如下:

import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = BearingCNN(num_classes=4).to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=50) def train_one_epoch(): model.train() total_loss, correct, total = 0.0, 0, 0 for x_batch, y_batch in train_loader: x_batch = x_batch.to(device) y_batch = y_batch.to(device) optimizer.zero_grad() outputs = model(x_batch) loss = criterion(outputs, y_batch) loss.backward() optimizer.step() total_loss += loss.item() pred = outputs.argmax(dim=1) correct += (pred == y_batch).sum().item() total += y_batch.size(0) return total_loss / len(train_loader), correct / total

训练之前有个容易被忽略的步骤:确认输入张量的维度。卷积神经网络期望输入是(batch, channel, length)的形状,而原始切分出来的样本是(batch, length),所以需要先通过unsqueeze(1)加一个通道维度。任何channel数不对的问题,模型第一层就会报错。

3.3 训练过程监控与模型保存

训练不是模型定义完就万事大吉,过程监控非常关键。我在每个epoch结束时同时记录训练集和验证集的损失值与准确率,并打印出来。训练集loss持续下降而验证集loss开始反弹,这是过拟合的经典信号,需要提前停掉。我在代码里设置了early stopping机制:如果验证集准确率连续8个epoch没有刷新历史最优,就自动终止训练,恢复历史最优权重。

模型保存方面要区分两个概念:只存权重还是存整个模型。我推荐只存state_dict,也就是模型的参数字典。这样文件小,加载灵活,后续如果改了模型类再加载权重也不容易出bug。保存和加载的代码如下:

# 保存模型 torch.save(model.state_dict(), 'best_bearing_cnn.pt') # 加载模型 model = BearingCNN(num_classes=4) model.load_state_dict(torch.load('best_bearing_cnn.pt', map_location='cpu')) model.eval()

训练完成后的效果让我比较满意,50轮跑下来,验证集准确率稳定在98.5%以上。整个训练过程用GPU大约十来分钟,用CPU大约需要四十分钟左右。如果机器没有独立显卡,用小数据集把epoch缩到20也能先跑通流程,把精力放在数据部分,这个我会在常见问题里展开。

4. 系统评估与可视化部署

4.1 混淆矩阵与分类报告解读

准确率只是一个笼统的指标,真正要看的是混淆矩阵。我习惯把所有测试样本的预测结果汇总,画出4x4的混淆矩阵,行代表真实标签,列代表预测标签。对于CWRU数据集来说,最常发生的混淆是在内圈故障和外圈故障之间,因为两者的特征频率在数值上可能比较接近,模型容易犹豫。

我用scikit-learn的classification_report来计算每个类别的精确率、召回率和F1分数。指标定义上,精确率=真正例数/(真正例数+假正例数),召回率=真正例数/(真正例数+假负例数),F1是两者的调和平均数。多分类问题中只看准确率会有盲点,比如总体准确率很高但某个小众类别识别很差,F1就能把这层皮剥开。我实测下来F1均分接近99%,说明模型没有严重偏科。

可视化混淆矩阵的代码如下,生成的图可以直接贴到项目报告里:

import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay y_pred = [] y_true = [] model.eval() with torch.no_grad(): for x_batch, y_batch in test_loader: x_batch = x_batch.to(device) outputs = model(x_batch) preds = outputs.argmax(dim=1).cpu().numpy() y_pred.extend(preds) y_true.extend(y_batch.numpy()) cm = confusion_matrix(y_true, y_pred) disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Normal', 'Inner', 'Outer', 'Ball']) disp.plot(cmap='Blues') plt.show()

4.2 特征可视化:t-SNE降维观察

模型分类准确率高,不代表特征就很干净。为了验证模型确实学到了可分的特征空间,我把训练好的模型当作特征提取器,取全局平均池化层的输出作为每个样本的特征向量,再用t-SNE降维到二维平面做散点图。这里我是手动取特征向量,只需要在模型forward里接一个临时输出。

实操时我把模型倒数第二层的输出保存下来,代码大致逻辑:先拿到与这个分支对应的特征,然后调用sklearn.manifold.TSNE降维。由于t-SNE计算复杂度随样本数几何增长,我随机抽取每个类别各200个样本参与可视化。最终散点图上四类样本呈现非常清晰的聚类,同类紧挨,异类远离。这个图不仅适合放在论文里做支撑材料,也能直观证明网络的表征能力。

需要提醒的是,t-SNE对超参数(特别是perplexity)比较敏感,我习惯把perplexity设为30,和默认值一致,展示效果相对稳定。如果发现聚类图一团糟,先别急着改网络,先检查是不是类别标签错乱。

4.3 模型导出与推理代码示例

训练完成后要把模型用到实际诊断场景,就需要一个简洁的推理脚本。推理阶段最关键的一点是先调用model.eval()切换成评估模式,这会关闭Dropout和BatchNorm的统计更新,保证推理结果稳定。我把单条样本的推理逻辑封装成函数,输入一段1024点的振动信号,输出预测类别和对应置信度。

推理代码如下:

def infer_bearing(model, signal, device='cpu'): model.eval() if len(signal) < 1024: raise ValueError('Signal length must >= 1024') # 截取最近1024个点,并做z-score标准化 segment = signal[-1024:] segment = (segment - segment.mean()) / (segment.std() + 1e-8) tensor = torch.FloatTensor(segment).unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): logits = model(tensor) probs = torch.softmax(logits, dim=1) pred_idx = logits.argmax(dim=1).item() conf = probs[0][pred_idx].item() return pred_idx, conf

这个推理函数在嵌入式设备上也能跑,因为模型本身参数量很小,整体不到几十KB。边缘部署时可以把模型导出为TorchScript或者ONNX格式,再丢到对应的推理引擎里执行。我在项目里导出ONNX在嵌入式设备上实测,单次推理耗时比原PyTorch版本快约一倍。

5. 常见问题与排查技巧实录

5.1 Python环境与依赖包问题

新手最容易卡在环境配置上。最常见的报错是安装PyTorch时下载慢,或者装完版本不匹配。我的建议是直接到PyTorch官网,根据操作系统和CUDA版本选择安装命令,别自己在pypi上乱装。CUDA版本的选择上,如果没有独立显卡就选CPU版本,训练时把device设成cpu即可。

另一个高频坑是numpy版本冲突。PyTorch 2.0对numpy有一份最低版本要求,如果系统装了太老的numpy,运行时会报类似“module compiled against API version”的错误。解决办法是升级到1.24以上版本。再有就是scipy在处理CWRU原始.mat文件时需要用到loadmat,如果你的scipy版本过新,个别.mat文件格式反而支持有问题,这时候可以降级到1.10试试,或者用h5py库手动读取。

5.2 数据加载与内存不足问题

故障诊断的数据集虽然不大,但滑窗切分后样本数量会上涨很快。我调试初期曾试图把全部样本一次性读入内存化成数组,结果在样本量超过5万时内存占用直接拉满。后来我改用PyTorch的Dataset方案,把样本以npy文件或者原始索引形式保存,DataLoader在迭代时才动态加载到内存。这样内存占用只取决于batch_size,不会随着数据集总量线性增长。

如果数据文件本身特别大,可以考虑用内存映射mmap_mode='r'读取,或者先把原始信号处理成特征向量再保存,减少I/O压力。还有一个小技巧:将预处理后的样本批量存成多个chunk文件,每个chunk里包含若干样本,训练时按顺序加载chunk。这个方案我后面在更大规模的风机轴承数据上复用,效果很不错。

5.3 模型泛化与过拟合问题

深度学习模型一旦训练样本量不够,很容易在训练集上跑出100%准确率但测试集表现平平。我处理过拟合有三个招式:第一是数据增强,对振动信号做随机幅值扰动、随机噪声叠加、随机缩放,这些在信号领域跟图像领域的平移旋转增强一个道理,能有效扩充训练集。第二是调整模型容量,比如把卷积核数从128减到64,或者在全连接层加Dropout(0.5)。第三是早停加正则化,就是前面提到的weight_decay和early stopping。

跨工况泛化是另一个容易被忽略的问题。CWRU数据集有0到3马力四种负载工况,如果你用0马力训练,直接拿3马力测试,准确率通常会有明显下降。这是正常的分布偏移现象。想提升跨工况泛化能力,一个实用做法是训练时混合多种工况的数据,让模型学会对转速与负载变化不敏感的特征。我在项目中做了消融实验,混合全部工况训练后,在单独工况测试集上的准确率比单工况训练平均高出2到3个百分点。

结尾

回头再复盘这个项目,我最深的体会是:深度学习模型的训练其实没有想象中那么玄,真正的门槛在数据和工程细节。数据选型、预处理、划分方式、防泄漏这几件事没做好,后面模型再花哨也是空中楼阁。我自己从一开始用频谱图配合二维CNN,到后来直接使用一维原始信号进去,再到现在把模型裁剪后部署到边缘设备,每一步都是被真实问题推着往前走。如果你也想做轴承故障诊断,我建议先用公开数据把整条流程跑通,然后找一台实际设备做数据采集验证。有了自己的数据,模型才有工业落地的底气。最后留一个小建议:训练日志不要随手删,后面调参和写报告时翻旧日志,往往能省下大量重复实验的时间。

本文还有配套的精品资源,点击获取

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

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

立即咨询