ARTICLE DETAIL

资讯详情

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

轴承故障诊断实战:从振动信号处理到1D CNN模型全解析

轴承故障诊断实战:从振动信号处理到1D CNN模型全解析 简介一份面向机械故障诊断与机器学习初学者的项目源码包以轴承振动序列数据为输入针对外圈故障、内圈故障、滚动体故障等典型类型完成从数据清洗、特征工程到随机森林、KNN、高斯朴素贝叶斯等模型训练与对比的完整流程。资源包含42个文件其中Python脚本26个用于数据处理与模型调用notebook 3个用于交互式演示markdown文档3个及预训练模型3个另附配置与说明文件压缩包仅434KB便于快速部署与二次开发。已有130人学习适合作为毕设课题入门参考或课程设计模板也适合具备Python基础的在校学生和初级算法工程师。通过源码可快速理解振动信号特征提取方法如均方根、峰值、波形因子、模型选择与交叉验证思路同时附带的说明文档和模型文件能帮助读者直接复现故障分类实验节省从零搭建环境的时间。1. 轴承振动序列数据的故障诊断本质上是一道模式识别题现场的老师傅能靠听声音判断轴承状态靠的是几十年积累的经验但产线不会给你几十年也不会让每个班组都配一个听音专家。把轴承振动信号换成数字序列把“听音辨故障”变成“读数据辨故障”这就是本课题要解决的核心问题。轴承包络破损、滚动体点蚀、保持架断裂这些故障会在振动信号里留下不同频段的痕迹而我们要做的就是从原始振动序列里把这些痕迹提取出来交给分类器去识别。这项工作的难点不在分类器本身而在信号处理和特征构造。同一个轴承在不同转速、不同负载下振动幅值可以相差数倍不同故障类型产生的冲击成分又常常混叠在一起。如果直接用原始序列喂给深度模型模型会学到很多和故障无关的转速、噪声成分泛化能力很差。常见做法是先做时频变换再在频域或包络谱上做特征提取最后进入分类器。本课题的整体路径就是这样振动序列 → 预处理与去噪 → 特征工程 → 故障分类。下面的章节会按这个链条逐层展开每一层都会给出可复现的代码和参数建议。2. 从时域到频域为什么原始振动序列必须经过变换2.1 时域波形能看什么又漏掉了什么拿到一组轴承振动数据第一件事永远是画波形图。时域波形能直接暴露的信息包括整体幅值水平、是否存在周期性冲击、是否有明显的工频干扰。用Python读取一份常见的轴承振动数据集如凯斯西储大学的公开数据或实验室自采数据最简单的可视化代码如下import numpy as np import matplotlib.pyplot as plt from scipy.io import loadmat # 加载MAT格式的振动数据文件里通常包含 X 和 y 两个字段 data loadmat(bearing_fault_data.mat) vibration data[X].flatten() # 一维振动序列 fs 12000 # 采样率单位Hz按实际数据集设定 # 截取前2048个点做波形展示2048是2的幂便于后续做FFT t np.arange(2048) / fs plt.figure(figsize(12, 4)) plt.plot(t, vibration[:2048]) plt.xlabel(时间 (s)) plt.ylabel(加速度幅值 (g)) plt.title(轴承振动信号时域波形) plt.show()这段代码里有个关键参数fs。采样率决定了你能观测到的最高频率也就是奈奎斯特频率。轴承故障特征频率通常在几千赫兹的范围内所以采样率一般不低于12kHz如果做的是声发射诊断采样率要求更高往往在100kHz以上。时域波形的局限性在于幅值相近的两段信号可能对应完全不同的故障类型——一个是由外圈缺陷冲击引起另一个可能是由不对中或松动引起。想区分它们必须把信号分解到频域。2.2 包络谱才是轴承故障诊断的主战场直接对原始振动信号做FFT得到的是包含工频、谐波、噪声的混合频谱故障特征并不明显。这是因为轴承故障激起的冲击成分能量很小往往被转频及其倍频淹没。工程上更常用的做法是希尔伯特变换解调先做带通滤波把感兴趣的高频共振频带提取出来再求包络最后对包络做FFT得到包络谱。故障特征频率在包络谱上会呈现明显的峰值。from scipy.signal import hilbert, butter, filtfilt def envelope_spectrum(signal, fs, lowcut1000, highcut5000): # 带通滤波滤除低频转频和超高频噪声突出轴承共振频带 nyquist 0.5 * fs low lowcut / nyquist high highcut / nyquist b, a butter(4, [low, high], btypeband) filtered filtfilt(b, a, signal) # 希尔伯特变换求解析信号取模得到包络 analytic hilbert(filtered) envelope np.abs(analytic) # 对包络信号做FFT得到包络谱 spectrum np.fft.rfft(envelope) freqs np.fft.rfftfreq(len(envelope), 1/fs) return freqs, np.abs(spectrum) freqs, spec envelope_spectrum(vibration, fs) plt.plot(freqs[:2000], spec[:2000]) # 只画0-2000Hz区间故障特征频率集中在低频区 plt.xlabel(频率 (Hz)) plt.ylabel(包络谱幅值) plt.title(包络谱分析结果) plt.show()带通滤波器的截止频率lowcut和highcut怎么定有一个参考路径先对原始信号做功率谱密度分析找到共振峰所在的频带把带通范围设置在该共振峰附近。轴承外圈故障特征频率的计算公式是BPFO n*f_r*(1 - d/D*cos(phi))/2其中n是滚动体数量f_r是转频d是滚动体直径D是节圆直径。先把这些物理参数对应的特征频率算出来再和包络谱峰值对照就能确认故障类型。工程上很少逐个手算通常会把特征频率计算封装成一个函数便于批量处理。做完包络谱之后特征提取就有了依据外圈故障会在BPFO及其倍频处出现峰值内圈故障在BPFI处滚动体故障在BSF处。频带能量比、峰值因子、谱峭度都是从包络谱上衍生出来的常用特征。3. 特征工程的三种路线与一个必做的对比实验3.1 手工特征时域、频域、时频域三件套经验丰富的工程师仍然会保留一套手工特征作为基线。这个基线不是为了打败深度模型而是为了校验深度模型的行为是否合理——如果模型的表现还不如手工特征那多半是数据预处理出了问题。常用特征清单如下。特征类别具体特征物理含义时域特征均方根值、峰值、峰峰值、峭度、波形因子、脉冲因子反映振动能量和冲击强度频域特征重心频率、均方频率、频率方差、频带能量占比反映频谱能量分布形态时频特征小波包分解后各频带能量占比、样本熵反映信号在局部的复杂度峭度是轴承故障诊断里最经典的一个指标正常轴承振动近似服从高斯分布峭度接近于3出现冲击故障后尾部变厚峭度显著大于3。用scipy.stats.kurtosis就能直接算。实际项目中我一般会把时域特征和频域特征拼成一个特征向量再用PCA或t-SNE看看不同故障类别是否可分。如果特征向量的可视化和聚类结果很差花大力气调模型是没有意义的。3.2 端到端的深度学习什么时候值得用深度学习路线的优势在于省去了手工设计特征的环节原始振动序列直接进网络。1D CNN是这个任务里性价比最高的结构它的局部感受野天然适合捕捉振动信号中的冲击波形。一个可复用的1D CNN分类模型框架如下import torch import torch.nn as nn class BearingCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( nn.Conv1d(1, 16, kernel_size64, stride8, padding28), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size32, stride4, padding14), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size16, stride2, padding7), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(1) ) self.classifier nn.Linear(64, num_classes) def forward(self, x): # x 形状: [batch, 1, seq_len] feat self.features(x) feat feat.view(feat.size(0), -1) return self.classifier(feat)模型设计里有几个值得解释的决策点。第一层卷积核设成64是为了覆盖足够长的时序窗口。轴承的冲击信号持续时间很短如果卷积核太小比如3或5单个核只能看到冲击的几个采样点无法捕捉完整的冲击衰减过程。第一层用大步长做下采样也很关键——振动序列往往很长几万个点直接用小步长会把特征图撑得很大参数量随之暴涨。BatchNorm在这类任务里几乎是必备的它能避免不同样本间幅值差异过大导致的梯度震荡。AdaptiveAvgPool1d(1)的作用是把最后输出的时间维压缩成1让全连接层的输入尺寸和序列长度解耦这样模型可以接受任意长度的输入序列。LSTM在这类任务上的表现通常不如1D CNN原因不复杂振动信号中的故障特征是局部冲击模式CNN的卷积核天然适配这类局部模式而LSTM更擅长捕捉长距离依赖但振动序列中并不存在很强的时间依赖关系。计算效率上CNN可以并行处理整段序列LSTM只能逐步迭代处理长序列时训练速度慢几倍。如果确实要上深度学习第一选择是1D CNN如果效果不达标再去比较Transformer或LSTM而不是反过来。3.3 预处理细节决定模型上限无论走哪条路线滑窗切分、归一化、数据划分这三个环节都要做对。滑窗切分的常见做法是每个样本取1024或2048个连续采样点窗口之间用50%重叠率这样数据集能被扩增几倍同时相邻样本之间的标签是一致的。重叠率是超参数设得太低会浪费数据设得太高会让训练集和验证集之间产生信息泄漏——同一个故障段落被同时切进了训练集和验证集验证分数会虚高真实场景下表现却没有那么好。归一化有两种选择样本级归一化和数据集级归一化。我见过的不少项目直接用sklearn.preprocessing.StandardScaler对整个数据集拟合再转换这在故障诊断里其实是错误操作因为测试集的分布信息在训练时是不可见的。正确做法是按训练集的均值和方差对训练集、验证集、测试集分别做相同的标准化变换代码上通过设置scaler.fit只在训练集上调用一次即可实现。数据切分还有一个不能忽视的细节按时间段切而不是按样本序号随机切。同一次实验连续采集的数据前几百个样本可能都来自同一工况随机切分会让相邻样本出现在训练集和测试集中导致测试结果偏乐观。4. 一次完整的实验设计从数据划分到模型评估4.1 实验方案怎么搭才有说服力一个毕业设计级别的轴承故障诊断实验数据通常来源于公开数据集、实验室台架自采或两者结合。无论哪种来源都要明确几个基本设定转速范围、负载条件、采样率、故障类型集合。最标准的做法是建立多组工况下的对比看模型在跨工况情况下是否稳健。以凯斯西储大学数据集为例典型设定如下故障位置故障直径英寸样本数滑窗后类别标签正常012000内圈故障0.00712001外圈故障0.00712002滚动体故障0.00712003验证协议建议采用贝叶斯分层交叉验证或单纯的多次分层抽样但重要的是在报告里写清楚切分策略。项目里用到的训练、验证、测试比例常见是7:1.5:1.5或者直接用五折交叉验证。要特别注意同一段连续信号经过滑窗扩增后样本之间并不独立随机切分会导致同源样本同时出现在训练和测试中测试精度虚高。更严谨的做法是按时间段切分或按工况切分。4.2 训练循环里要盯住的四个指标训练一个轴承故障诊断模型光看accuracy远远不够。类别不平衡在实际项目中是常态——正常样本往往占了80%以上故障样本稀少准确率可能高达95%但对故障样本的召回率只有40%。这个模型在产线上是没法用的因为漏报一个故障可能造成整条产线停机。训练脚本里至少要同时打印accuracy、precision、recall、F1-score以及一个混淆矩阵。核心训练代码框架如下def train_one_epoch(model, dataloader, optimizer, criterion, device): model.train() total_loss 0.0 correct 0 total 0 for batch_x, batch_y in dataloader: batch_x, batch_y batch_x.to(device), batch_y.to(device) optimizer.zero_grad() outputs model(batch_x) loss criterion(outputs, batch_y) loss.backward() optimizer.step() total_loss loss.item() * batch_x.size(0) _, preds torch.max(outputs, 1) correct (preds batch_y).sum().item() total batch_y.size(0) train_acc correct / total avg_loss total_loss / total return avg_loss, train_acc # 训练主循环记录每个epoch的结果 epochs 50 best_val_acc 0.0 for epoch in range(epochs): train_loss, train_acc train_one_epoch( model, train_loader, optimizer, criterion, device ) val_loss, val_acc, val_report evaluate(model, val_loader, device) print(fEpoch {epoch1}/{epochs} | ftrain_loss: {train_loss:.4f} | train_acc: {train_acc:.4f} | fval_loss: {val_loss:.4f} | val_acc: {val_acc:.4f}) # 用验证集准确率做模型保存依据而不是训练集准确率 if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_bearing_model.pt)训练时还有两个设置要留意。学习率初始值设在0.001到0.01之间用ReduceLROnPlateau做自适应衰减验证集损失连续5个epoch不下降就把学习率除以2或除以10。batch size不要设太大1024这样的大batch在振动信号这类局部模式明显的任务上会削弱梯度噪声的正则化作用常见的区间是32到256。把训练曲线画出来看趋势——训练损失下降很快但验证损失开始回升时就该调整正则化策略或提前终止了。4.3 类不平衡问题怎么破故障诊断的常见任务是做小样本故障识别故障样本极少正常样本极多。解决办法有几个层次。传统机器学习时代最常用SMOTE过采样深度学习中更常用的做法是给少量类加大损失权重torch.nn.CrossEntropyLoss里有现成的weight参数按类别样本数倒数归一化后传进去就行class_counts torch.tensor([1200, 300, 280, 260], dtypetorch.float) weights 1.0 / class_counts weights weights / weights.sum() # 归一化让各类别损失贡献均衡 criterion torch.nn.CrossEntropyLoss(weightweights.to(device))另一个实用技巧是做加噪增广。轴承振动数据不像图像那样适合做翻转、裁剪但对原始信号叠加低幅值高斯噪声可以提升模型抗噪性。signal np.random.normal(0, 0.01*np.std(signal), len(signal))噪声幅度控制在原信号标准差的1%到3%之间。增广强度太大会改变故障特征本身太弱则没有效果。这块建议做一组消融实验对比给出量化数据再下结论。4.4 故障诊断的可视化验证不看频谱等于没做模型训练完成后光报一组分类指标是不够的。轴承故障诊断的客户和评审最关心的是你凭什么说它是外圈故障拿一个测试样本来展示它的原始波形、包络谱再把模型预测的类别标注在旁边形成一个完整的证据链。还能用torchviz或者captum工具生成梯度加权热力图在波形图上标出模型关注的波形段看这些部位是否确实存在冲击痕迹。可视化验证不是走形式。实际项目中出现过这样的情况模型准确率到了99%但注意力却落在背景噪声上原因是数据采集时不同故障样本的噪声底噪不同模型学到了噪声和标签的相关性。可视化能把这类虚假相关暴露出来是排查诊断模型可靠性的一道重要工序。5. 跨工况泛化、迁移学习与边缘部署的一个落点毕设做完了实验室数据通常会遇到一个灵魂拷问在A工况下训练的模型到了B工况还能用吗转速变了、负载变了、设备型号变了振动信号的幅值和频率成分全都会变模型性能往往从95%掉到60%左右。解决这个问题的思路之一是迁移学习。常见做法是在源域数据比如公开的大型数据集上预训练模型然后冻结大部分卷积层只微调最后的全连接层或者加一个适配层用目标域的少量数据进行几十轮训练。这样做的前提是源域和目标域的故障类型集合要一致或至少高度重叠。另一个值得尝试的思路是领域对抗网络在特征提取器之后加一个域判别器让特征提取器学会提取不依赖工况的故障特征。这类方法实现起来复杂度高一些但泛化效果的提升往往是明显的。做迁移学习实验时评估重点应该是目标域上的少数样本微调之后准确率提升的幅度以及相比随机初始化的训练能快多少个epoch收敛这两个指标能直接体现迁移的价值。还有一个越来越受关注的落点是边缘部署。训练好的模型要跑到采集站或嵌入式设备上做实时诊断这就要考虑模型大小和推理延迟。1D CNN模型做一下量化精度损失在0.5%以内模型体积能压缩到原来的四分之一。用ONNX导出模型再用ONNX Runtime在CPU或边缘设备上推理python代码量不大import onnx import onnxruntime as ort import numpy as np # 先把PyTorch模型转成ONNX格式 dummy_input torch.randn(1, 1, 2048) torch.onnx.export(model, dummy_input, bearing_cnn.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}) # 用ONNX Runtime加载并推理 session ort.InferenceSession(bearing_cnn.onnx) input_name session.get_inputs()[0].name sample vibration[:2048].reshape(1, 1, -1).astype(np.float32) outputs session.run(None, {input_name: sample})[0] pred_label int(np.argmax(outputs[0])) print(f当前振动样本的故障类别: {pred_label})单次推理时间在普通CPU上大约10到30毫秒完全满足实时产线的需求。部署时还需要留意一个容易被忽略的环节数据采集的采样率在设备运行过程中可能漂移模型部署之后建议定期用已知故障样本做验证。把预测结果和置信度写回数据库一旦置信度持续走低就该回查信号预处理环节看是否出现了采样率配置漂移或传感器劣化的问题。诊断模型的价值在于长期稳定运转而不只是训练论文里的那几个数字。本文还有配套的精品资源点击获取
返回列表