ARTICLE DETAIL

资讯详情

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

1DCNN滚动轴承故障诊断:Python与PyTorch实战指南

1DCNN滚动轴承故障诊断:Python与PyTorch实战指南 简介面向滚动轴承故障诊断的一维卷积神经网络项目以Python实现兼顾深度学习方法学习与工业设备状态监测应用。资源包共四十九个文件大小约四十五兆字节其中四十个mat文件为不同负载下的振动信号数据集包含正常与多种故障状态另有五个xml文件用于工程配置两个Python脚本分别承担数据预处理和网络模型构建。项目自带的12K轴承数据集划分清晰并配有方便的数据读取程序读者可以跟随数据读取、归一化、训练集划分、一维卷积网络模型搭建、训练与评估的完整步骤进行复现理解卷积层与池化层如何从振动信号中提取故障特征。不同负载工况的数据有助于观察模型在不同运行条件下的识别表现提升对模型泛化能力的认识也适合用来验证模型优化后的效果。目前已有1014人学习下载适合希望快速掌握一维卷积神经网络在故障诊断中应用的学习者也可为机械故障诊断方向的课程设计或毕业课题提供直接参考。1. 1DCNN滚动轴承故障诊断为什么说这是Python能做的最小闭环把原始振动信号直接喂给1DCNN让它在Python里自己学会区分滚动轴承的内圈故障、外圈故障和滚动体故障——这是近两年设备维护团队尝试最多的一条技术路线。传统方法要先做小波包分解、计算峭度和均方根、再交给SVM或随机森林每个环节都是一堆经验参数而1DCNN把特征提取和分类合并成一次端到端训练只要数据齐全用Python重写一遍并不难。这个方案最吸引人的地方不是某个公开数据集上的精度数字而是可复现装好Python环境配置好torch和numpy照着故障诊断代码就能把模型跑起来。适合谁适合有Python基础、想做预测性维护但不想在特征工程上耗太久的人。如果你手头只有振动数据和标签想快速验证深度学习能不能解决你的问题这篇笔记值得看完。2. 1DCNN不是把图片压扁一维卷积如何直接吃振动信号2.1 从波形到故障特征1DCNN在学什么滚动轴承故障的振动信号有一个明显特点周期性冲击。内圈裂纹滚动过缺陷时会产生一个短时冲击冲击引发系统共振表现为一个衰减振荡外圈和滚动体故障也类似只是冲击的间隔和调制方式不同。传统诊断中工程师会去计算时域特征峰峰值、均方根、峭度和频域特征包络谱、特征频率幅值再把这些人工特征塞进分类器。这套流程有效但痛点在于不同转速、不同负载下该选哪个特征、特征间怎么融合全靠经验和反复试。1DCNN的思路是跳过人工特征工程直接把原始采样点当输入。一维卷积核沿着时间轴滑动等价于让模型自己学习一组滤波器。第一层卷积核能捕捉冲击波形后面的层把局部模式组合成“内圈故障”“外圈故障”这类抽象语义。这背后是数据驱动方法的核心故障类型的信息在原始波形里是充分的卷积网络用足够的样本量就能把它学出来。实际训练时你会发现模型自己学到的一维卷积核频率响应往往接近带通滤波器这就是它替代包络分析的方式。那为什么不用二维CNN把信号画成时频谱再分类转成一维用1DCNN有两个实际好处。一是预处理少一步不需要先做短时傅里叶变换也不用反复调窗函数和重叠率二是参数量小得多。二维CNN输入一张2048x256的时频谱图第一层卷积就要学几万甚至几十万个参数而1DCNN输入2048个采样点第一层卷积只有千把个参数。工业现场数据量通常不大模型参数少意味着更不容易过拟合训练也更快。如果只是做轴承故障诊断没有多通道图像输入的需求1DCNN是性价比更高的选择。2.2 输入长度、卷积核与池化步长动手前先定三个参数这三个参数定了模型骨架基本就定了。先说输入长度。一维卷积的输入是振动波形的一个切片这个切片要覆盖足够多的故障冲击周期但又不能长到让训练变慢。以CWRU西储大学轴承数据集为例采样率12kHz转频约30Hz转一圈大约400个采样点。取2048个点大约是5圈能够覆盖多个冲击周期取256就太短模型看到的可能只是一段随机振荡学不到周期性特征。我一般先取2048效果不理想再往4096调。如果你的现场转速很低比如转频10Hz一圈1200个点那就得把窗口加长到4096或8192。再说卷积核大小。第一层卷积核要足够大才能覆盖一个完整的冲击响应。轴承故障冲击在波形上表现为一个衰减振荡持续时间通常有几十个采样点如果第一层卷积核只有3或5模型看到的只是冲击波形的一个碎片首层特征会缺少全局形状。比较稳的做法是第一层卷积核取64配合stride2降采样后面的层用小卷积核3或5去组合特征。我在多个数据集上对比过第一层用64的收敛速度明显快于全程用3最终准确率也高一些。原因不难理解大卷积核相当于一个短时模板对冲击波形更敏感。池化步长决定时序下采样的节奏。常见做法是MaxPool1d(kernel_size2, stride2)每经过一次池化特征长度减半。下采样太激进会丢失冲击的精确相位信息所以我一般只在前两层做stride2池化后面保持stride1让深层特征保留更细的时序细节。这里有个计算公式可以帮你估输出长度卷积后长度 floor((L 2*padding - kernel_size) / stride 1)。我建议每次改模型后先print一层输出形状确认长度没有变成0或负数。2.3 为什么选PyTorch环境配置与框架对比做1DCNN轴承故障诊断PyTorch和TensorFlow都能做我推荐PyTorch。原因有三个模型定义直观torch.nn.Conv1d直接对应一维卷积改通道数和核大小都很好懂数据加载用Dataset和DataLoader写起来顺手对原始振动信号做切片、标准化和标签分配很方便调试体验好中间层输出形状可以直接print不用跑完整图才能看。Keras的Sequential模型虽然更简单但自定义一维数据处理流程时经常要写generator反而绕。环境配置这部分经常是新手的第一道坎。Python安装完成后建议用虚拟环境装依赖避免和系统环境冲突。基础配置是Python 3.8以上然后pip install torch numpy scikit-learn scipy matplotlib。如果你的显卡支持CUDA再装对应版本的torch没有GPU也不影响2048长度的1DCNN在CPU上几十个epoch也能跑完只是别把batch size设太大。环境配好之后下面的代码从加载CWRU数据集到训练一轮半小时内就能看到初步结果。3. 用Python搭建1DCNN轴承故障诊断模型数据、模型、训练全流程3.1 数据集加载把CWRU的.mat文件切成训练样本公开数据集里最常用的是CWRU西储大学轴承数据。每个.mat文件包含一段长时间振动信号文件名里暗含故障位置和损伤直径。加载这类文件要用scipy.io的loadmat而不是numpy的load。写一个通用加载函数import numpy as np from scipy.io import loadmat def load_cwru(path, signal_channelDE): # 读取mat文件CWRU通常包含DE和FE两列加速度信号 data loadmat(path) if signal_channel in data: signal data[signal_channel].flatten() else: # 兼容某些只有一列数据的样本 key [k for k in data.keys() if not k.startswith(__)][0] signal data[key].flatten() return signal.astype(np.float64)逻辑说明CWRU的mat文件里内置了_header等元数据键直接遍历会误取所以要先过滤掉以双下划线开头的键。DE表示驱动端加速度信号FE是风扇端。做外圈故障诊断时DE是主信号FE可以作为对比。参数说明signal_channel参数让你在切换信号源时不用改函数这在复现不同论文时很实用。astype(np.float64)是为了避免部分mat文件默认float32导致后续标准化精度不够。长信号不能直接进模型要切成固定长度的样本。切的时候步长可以小于窗口长度人为制造重叠来扩增样本def make_samples(signal, label, window_size2048, stride1024): samples, labels [], [] for start in range(0, len(signal) - window_size 1, stride): samples.append(signal[start:start window_size]) labels.append(label) return np.array(samples), np.array(labels)逻辑说明stride1024表示相邻样本有50%重叠。CWRU单个文件十几万点用2048窗口加1024步长一个文件能切出一百多个样本足够训练一个小型1DCNN。参数说明stride越小样本量越大但相邻样本高度相似会让训练集和测试集出现信息重叠导致准确率虚高。具体怎么避坑第5章会专门说。窗口长度必须和2.2节定的输入长度一致不要边训边改。把所有类别的样本拼起来做成numpy数组all_samples [] all_labels [] for mat_file, label in file_label_list: # file_label_list自定义比如[(inner.mat,0),(outer.mat,1)] sig load_cwru(mat_file) samp, lab make_samples(sig, label) all_samples.append(samp) all_labels.append(np.full(len(samp), label)) X np.vstack(all_samples) y np.concatenate(all_labels)参数说明file_label_list是工程里自己维护的映射把文件名和类别索引对应起来。类别索引从0开始顺序会决定输出层的神经元含义。建议把类别清单打印出来存成CSV避免训练完不知道类别顺序。3.2 定义1DCNN模型卷积层、批归一化与Dropout模型用一个三层卷积结构就够了不需要堆到十层。层数太深在样本量只有几千时很容易过拟合。import torch import torch.nn as nn class Bearing1DCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( # 第一层大卷积核stride2降采样padding保证首尾不丢 nn.Conv1d(1, 16, kernel_size64, stride2, padding32), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), # 第二层小卷积核 nn.Conv1d(16, 32, kernel_size3, padding1), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), # 第三层 nn.Conv1d(32, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Dropout(0.3), nn.Linear(64, num_classes) ) def forward(self, x): # 输入x形状: [batch, 1, window_size] x self.features(x) x self.classifier(x) return x逻辑说明第一层输入通道是1因为振动信号是单通道。padding32加上kernel_size64和stride2卷积后长度正好是输入长度的一半再经过两次池化输出长度大概是输入长度的八分之一。AdaptiveAvgPool1d(1)是一个很关键的层它不管前面特征长度是多少都压成1个值这样全连接层的输入维度固定为通道数64模型对输入长度有了一定的容忍度。Dropout放在全连接前防止分类层过拟合。第一次用Conv1d的人最容易翻车的地方是输入形状。PyTorch的Conv1d期望输入是[batch, channels, length]不是[batch, length, channels]。如果你习惯用numpy的行向量构造训练数据时就要unsqueeze(1)加一个通道维度我把这步放到训练循环前统一处理。3.3 训练循环损失函数、优化器与学习率调度数据准备好后先划分训练集和测试集再包成DataLoader。注意这里要用分层切分保持各类别比例一致。from torch.utils.data import DataLoader, TensorDataset from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) X_train_t torch.from_numpy(X_train).float().unsqueeze(1) X_test_t torch.from_numpy(X_test).float().unsqueeze(1) y_train_t torch.from_numpy(y_train).long() y_test_t torch.from_numpy(y_test).long() train_loader DataLoader( TensorDataset(X_train_t, y_train_t), batch_size64, shuffleTrue ) test_loader DataLoader( TensorDataset(X_test_t, y_test_t), batch_size64, shuffleFalse )逻辑说明stratifyy是关键参数CWRU数据本身各类别样本量不同如果随机切分某类可能全部落进训练集或测试集结果完全失真。unsqueeze(1)在索引1的位置加一个维度把[batch, length]变成[batch, 1, length]满足Conv1d的输入要求。shuffleTrue只对训练集生效测试集保持顺序以便后面做混淆矩阵。然后是训练循环。用交叉熵损失和Adam优化器加一个简单的学习率调度model Bearing1DCNN(num_classeslen(np.unique(y))) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size20, gamma0.5) epochs 60 for epoch in range(epochs): model.train() total_loss 0.0 for batch_x, batch_y in train_loader: optimizer.zero_grad() out model(batch_x) loss criterion(out, batch_y) loss.backward() optimizer.step() total_loss loss.item() scheduler.step() if (epoch 1) % 10 0: avg_loss total_loss / len(train_loader) print(fepoch {epoch 1}, loss {avg_loss:.4f})参数说明学习率1e-3是Adam配合1DCNN的常见起点跑几个epoch后loss如果完全不降先降到3e-4不要一上来就换优化器。StepLR每20个epoch把学习率乘0.5目的是让模型在后期稳定收敛。epochs60对这个小模型通常够用你可以打印每次epoch的loss如果最后10个epoch的loss下降小于0.01基本可以停了。这里还有一个关键细节输入标准化。原始振动信号幅值通常在正负1附近但不同设备差异很大。建议在训练前计算训练集的均值方差对训练和测试统一处理否则第一个epoch的loss可能直接变成NaN。标准化代码很短但漏掉它会让前面所有努力白费。4. 诊断模型能用吗评估指标、混淆矩阵与跨工况验证4.1 准确率会骗人诊断任务要看精确率、召回率、F1很多第一次跑通1DCNN的人看到测试准确率99%就高兴地收工了。但在故障诊断里准确率是最不靠谱的指标。假设正常样本占95%故障样本占5%模型把所有样本都判成正常准确率也有95%但它实际上一个故障都诊断不出来。所以至少要打印精确率、召回率和F1尤其是故障类的F1。from sklearn.metrics import classification_report, confusion_matrix, accuracy_score model.eval() all_preds [] with torch.no_grad(): for batch_x, _ in test_loader: out model(batch_x) pred out.argmax(dim1) all_preds.extend(pred.cpu().numpy()) print(accuracy:, accuracy_score(y_test, all_preds)) print(classification_report(y_test, all_preds, target_names[normal, inner, outer, ball]))逻辑说明classification_report会输出每个类别的精确率、召回率和F1。你要重点看故障类inner、outer、ball的召回率它代表“真实故障里被正确找出来的比例”。轴承诊断最怕漏报漏一个故障可能就导致设备损坏因此故障类召回率比总体准确率重要得多。参数说明target_names只是显示名顺序要和训练时的标签索引严格一致否则报告会张冠李戴。如果你看到总体准确率很高但某个故障类的F1特别低说明模型对这个类别的样本区分能力不足需要回头检查样本量或特征表达能力而不是继续调epoch。4.2 混淆矩阵看错的是哪两类分类报告只能告诉你好不好混淆矩阵能告诉你错在哪里。轴承故障诊断中最常见的错误是内圈和滚动体互相混淆因为两者都产生周期性冲击只是冲击位置和调制方式不同。画混淆矩阵用matplotlib加sklearnimport matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay cm confusion_matrix(y_test, all_preds) disp ConfusionMatrixDisplay(cm, display_labels[normal, inner, outer, ball]) disp.plot(cmapBlues) plt.savefig(confusion_matrix.png)逻辑说明ConfusionMatrixDisplay会生成带颜色格的图第i行第j列表示“真实类别为i预测为j”的样本数。对角线越深越好。如果某个非对角元素数值明显偏高比如inner预测成ball有50个样本说明这两类波形特征相似模型没有学到足够判别性的模式。解决思路有两个一是增加这两类的样本量并做数据增强二是检查输入窗口长度是否覆盖了完整的故障冲击周期窗口太短时内圈和滚动体的冲击间隔信息是缺失的。4.3 跨工况验证用另一负载的数据测试模型泛化训练集和测试集来自同一段信号准确率再高也不能说明模型到了现场还能用。CWRU数据包含0、1、2、3四种负载对应不同转速常见做法是用其中一种负载做训练另一种负载做测试看正确率下降多少。这个验证能暴露模型是否过拟合到了特定工况。训练负载测试负载数据来源判断标准0 hp1 hp不同运行状态准确率下降应小于5%0 hp2 hp不同运行状态准确率下降应小于8%0 hp3 hp不同运行状态准确率下降应小于10%1 hp0 hp反向迁移同样观察下降幅度实现跨工况验证时不要用上一节的train_test_split而是按负载分组后重新做样本切片# 假设samples_by_load是一个dict键是负载值是(signal, label)列表 train_signals samples_by_load[0] test_signals samples_by_load[1] X_train, y_train [], [] for sig, label in train_signals: samp, lab make_samples(sig, label, window_size2048, stride2048) X_train.extend(samp); y_train.extend(lab) X_test, y_test [], [] for sig, label in test_signals: samp, lab make_samples(sig, label, window_size2048, stride2048) X_test.extend(samp); y_test.extend(lab)参数说明这里stride故意设成2048使样本不重叠避免同一个信号片段既在训练又出现在测试里。跨工况验证的结果往往比同分布测试低一大截这是正常的。如果你发现新负载上准确率跌到刚过随机水平不要急着加模型复杂度先想想特征分布为什么变了——最常见的原因是振动幅值随负载变化模型错误地把幅值当成判别信号。解决方法是做幅值归一化让每个样本除以自己的RMS削弱负载对幅值的影响。下面的结论很重要模型在训练过的工况上再准也只是在记住工况跨工况验证才是判断模型能否落地的最小标准。我刚做这个方向时第一次跨负载测试从99%掉到72%当时还以为代码写错了后来才明白是模型学了负载特征而不是故障特征。5. 避坑1DCNN轴承故障诊断的5个常见翻车现场5.1 现象训练准确率100%测试准确率只有70%这是最常见的问题几乎每个人都踩过。训练集上完美收敛测试集上大幅下跌第一条要怀疑的永远是数据泄漏。我在第3章建议过用stride1024做重叠切片如果训练和测试都来自同一条长信号重叠切出来的样本会高度相似模型等于已经看过测试内容的“邻居”训练时见到过几乎一样的波形测出来自然虚高。原因切片重叠导致训练集和测试集存在信息重叠这是数据预处理阶段的泄漏。解决切样本时把训练和测试先按时间段分开再用不重叠或低重叠的窗口切片。跨工况验证中严格保证训练负载和测试负载的原始信号完全来自不同文件。不要图省事在所有样本堆一起后再随机划分。正确做法是先按信号文件划分再在每个文件内部切片。这样即使有重叠重叠样本也只在同一个集合里不会跨集合泄漏。5.2 现象loss震荡不收敛甚至出现NaN训练过程中loss上下乱跳不往下降更离谱的是第一个epoch就出现NaN。这通常不是模型结构问题而是数据数值范围和优化器参数不匹配。原因振动信号幅值范围过大或过小没有做标准化。比如某些采集卡输出幅值在0.01量级Adam默认学习率1e-3导致梯度步长过大或者原始信号里存在脉冲噪声幅值达到几十导致梯度爆炸。解决训练前做标准化用训练集的均值方差归一化所有样本mean X_train.mean(axis(1, 2), keepdimsTrue) std X_train.std(axis(1, 2), keepdimsTrue) X_train (X_train - mean) / (std 1e-8)这样每个样本就像一个标准化的时间序列幅值范围落在正负几以内。如果标准化后loss仍然震荡把学习率降到3e-4或1e-4。另外检查一下是否为每个样本单独做了标准化这会去掉幅值信息不一定合适建议先做全局标准化再根据效果决定。5.3 现象正常样本全被误判成故障测试阶段模型把大量正常样本预测为故障或者反过来故障样本全漏报。观察混淆矩阵会发现某一行或某一列的数字特别集中。原因类别不平衡。CWRU数据里正常样本和故障样本数量不均衡如果正常样本特别多模型偏向把边界样本往多数类推如果故障类样本特别少模型学会了忽略故障类。解决优先用分层抽样保证训练集类别比例一致代码里的stratifyy就是干这个的。如果仍然不平衡用加权交叉熵损失class_weights torch.tensor([1.0, 2.0, 2.0, 2.0]) # 正常1.0故障2.0 criterion nn.CrossEntropyLoss(weightclass_weights)参数说明weight按训练集各类别样本数量的倒数归一化后设置样本少的类别权重高让模型在训练时更关注少数类。权重大小不需要太精细故障类设成多数类的1.5到3倍足够。切记不要在测试集做任何重采样测试集必须保持真实分布否则评估结果不可信。5.4 现象换一台设备或换一个工况准确率直接崩盘训练时0 hp负载准确率99%拿到1 hp负载上测试只有60%这就是过拟合到工况特征。振动信号里负载不同幅值和转频都变化模型如果只记住了幅值范围就会把“幅值低”当成“正常”把“幅值高”当成“故障”。原因模型过度依赖与故障无关的工况特征泛化性不足。解决做幅值归一化把每个样本除以自己的RMS值减少负载对幅值的影响。更根本的手段是做数据增强比如在训练时对样本随机缩放幅值0.8到1.2倍加入轻微高斯噪声让模型不能依赖绝对幅值。跨工况验证必须成为标准评估流程。我在实际项目中遇到过类似情况最后是靠“按样本RMS归一化随机缩放”把跨负载准确率从60%提到了85%这比换更大的模型有效得多。5.5 现象CPU训练慢到抓狂一个epoch要十分钟很多人拿到代码就直接跑到CPU上训练窗口长度2048、batch_size 64、epoch 60结果等半天。这不一定是你机器不行可能是代码写得太浪费。原因数据加载和GPU同步的瓶颈。最常见的是在训练循环里对每个batch做numpy转torch或是在GPU模型上跑CPU数据导致每次迭代都要拷贝。另一个原因是模型没有进入train模式用了显式的dropout但推理时忘了eval导致训练变慢。解决把数据转换提到循环外如3.3节所示一次性把X转成torch张量。训练时确保DataLoader的num_workers0默认0会让主线程串行读数据train_loader DataLoader( TensorDataset(X_train_t, y_train_t), batch_size64, shuffleTrue, num_workers2, pin_memoryTrue )参数说明num_workers控制子进程预读数据pin_memoryTrue可以在GPU训练时减少传输延迟。CPU训练时pin_memory作用不大但也不会有坏处。如果数据量很大建议先把样本缓存成.pt文件避免每次循环都从原始mat文件读取。这个优化做完60个epoch通常能节省一半以上时间。6. 把1DCNN故障诊断模型用起来TorchScript导出与滑动窗口实时推理训练好的模型不能只在测试集上算准确率最终要放进监控程序里让它在产线上连续处理新采集的数据。这里有两个实用技巧滑动窗口推理和模型导出。滑动窗口推理的做法是设置一个窗口长度比如2048每次从实时数据流中截取最近2048个点预测一次然后向前滑动一小段比如256点继续预测。这样不会漏掉冲击输出还能通过滑动平均来平滑。def sliding_predict(model, signal_stream, window2048, step256, devicecpu): model.eval() preds [] start 0 while start window len(signal_stream): chunk signal_stream[start:start window] chunk torch.from_numpy(chunk).float().unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): out model(chunk) pred out.argmax(dim1).item() preds.append(pred) start step return preds参数说明step设为256窗口2048意味着相邻预测共享88%的数据输出会非常平滑适合做产线报警。实际部署时如果模型在推理阶段性能抖动记得先调用model.eval()关闭Dropout和BatchNorm的训练统计否则同一个输入在不同时刻预测结果可能不一致。导出成TorchScript可以脱离Python训练环境运行只要目标机器装了PyTorch就能加载速度比直接调用nn.Module更快example_input torch.randn(1, 1, 2048) traced_model torch.jit.trace(model.cpu(), example_input) traced_model.save(bearing_1dcnn.pt)我自己的教训是导出前一定要在CPU上做一次完整推理确认trace输入形状和真实数据一致。否则拿到现场一跑就报维度不匹配当场尴尬。另外产线数据的采样率很可能和训练数据不一样部署时实现在线重采样把数据统一到训练时的采样率是比重新训模型更稳妥的预处理。滚动轴承故障诊断这件事做通一个demo不难难的是让模型在换了一台设备、换了一个负载之后依然稳定。我踩过最多的坑就是模型在测试集上看起来完美一到现场就翻车。希望这篇笔记里的代码和避坑经验能帮你少走一段弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表