轴承故障诊断的深度学习实战:从振动信号到边缘部署
2026/9/15 3:23:24 网站建设 项目流程

简介:本资源是一套面向本科生毕业设计与课程实践的深度学习轴承故障诊断完整实现方案,聚焦工业设备智能运维场景,解决旋转机械关键部件——轴承的早期故障识别与分类问题。压缩包共28个文件,含10个MATLAB数据文件(含多工况振动信号)、10个Python核心脚本(覆盖预处理、FFT/CWT时频转换、CNN/LSTM建模及模型保存)、3个H5模型文件(含训练好的最佳CNN权重)、3个Markdown说明文档(含环境配置与运行指引),整体大小10.84MB。已有93人学习下载,适合具备Python和深度学习基础的工科学生开展课题研究或项目复现。读者可直接运行预置流程:从原始振动信号加载、多种时频变换处理,到端到端CNN/LSTM模型训练与推理,完整掌握故障诊断全流程关键技术点,并获得可调参、可扩展的工程化代码结构与清晰模块划分。

1. 为什么用深度学习做轴承故障诊断,不是“炫技”,而是工业现场的真实刚需

在风电、高铁、数控机床等关键装备运维中,轴承失效占机械故障的30%以上,但传统振动分析依赖专家经验——同一组加速度时序信号,不同工程师对“内圈轻微剥落”和“滚动体早期点蚀”的判断可能相差20%以上。而深度学习模型能直接从原始传感器数据中学习故障特征表达,跳过人工设计滤波器、包络谱、Hilbert变换等中间环节。这不是替代工程师,而是把人从重复标注、调参、比对频谱的劳动中解放出来,让诊断结论可复现、可追溯、可批量部署。本项目聚焦真实工业场景:采样频率12kHz、单通道振动信号、4类典型故障(正常/内圈/外圈/滚动体)+ 3种负载工况,所有代码基于PyTorch实现,不依赖MATLAB或商业工具链,适配边缘计算设备(如Jetson Nano)的轻量化推理需求。适合已有振动数据但缺乏诊断能力的产线工程师、高校机电/自动化方向研究生,以及想落地第一个工业AI项目的算法初学者。

2. 从原始振动信号到可训练样本:数据预处理的3个硬性约束与实操代码

轴承故障诊断的数据质量直接决定模型上限。工业现场采集的振动信号常含强噪声、变转速、非平稳特性,若直接截取固定长度片段送入CNN,模型会学到工况伪影而非故障本质。必须通过物理约束指导预处理流程,而非盲目套用图像领域的归一化方案。

2.1 采样长度与重叠率:以故障冲击周期为锚点

轴承故障冲击周期由几何参数和转速决定:
$$ T = \frac{60}{n} \cdot \frac{1}{f_{bpfo}} $$
其中 $ n $ 为转速(rpm),$ f_{bpfo} $ 为外圈故障特征频率(Hz)。实际中,我们取3倍冲击周期作为最小分析窗长,确保捕获至少一次完整冲击衰减过程。以CWRU数据集为例(驱动端轴承,采样率12kHz,转速1730rpm),外圈故障特征频率约107Hz,对应周期9.3ms → 最小窗长取28ms → 对应336个采样点。但为兼顾CNN输入维度和GPU显存,最终采用1024点(85.3ms)窗长,步长设为256点(25%重叠),保证相邻片段间有足够时间连续性。

提示:窗长不能简单设为1024或2048——若转速突变导致冲击周期缩短,固定窗长会切割冲击波形,使模型误判为噪声。必须根据实测转速动态计算窗长,代码中需接入转速传感器信号或通过阶次分析提取转速。

2.2 时域标准化:消除传感器增益差异的工程实践

不同产线、不同批次传感器灵敏度差异可达±15%,若仅做全局Z-score归一化(x = (x - mean)/std),会导致同一故障在A产线表现为幅值0.8,在B产线变为1.2,模型难以泛化。正确做法是:

  • 对每个传感器通道单独计算其滚动窗口标准差(窗口=1024点,步长=256点)
  • 将当前片段除以其对应滚动标准差
  • 再做整体min-max缩放到[-1,1]区间
import numpy as np from scipy import signal def normalize_segment(segment: np.ndarray, rolling_std_window: int = 1024) -> np.ndarray: # 计算该片段所在位置的滚动标准差(模拟实时处理) pad_len = rolling_std_window // 2 padded = np.pad(segment, (pad_len, pad_len), mode='reflect') stds = np.array([ np.std(padded[i:i+rolling_std_window]) for i in range(len(segment)) ]) # 避免除零,std<1e-6时置为1.0 stds = np.where(stds < 1e-6, 1.0, stds) normalized = segment / stds # 全局min-max到[-1,1] min_val, max_val = normalized.min(), normalized.max() if max_val == min_val: return np.zeros_like(normalized) return 2 * (normalized - min_val) / (max_val - min_val) - 1 # 示例:处理一个1024点片段 raw_signal = np.random.randn(1024) * 0.5 + np.sin(np.linspace(0, 2*np.pi*5, 1024)) # 模拟含周期干扰的信号 processed = normalize_segment(raw_signal) print(f"归一化后范围: [{processed.min():.3f}, {processed.max():.3f}]") # 必须接近[-1.0, 1.0]
2.1.1 为什么不用全局归一化?

全局归一化假设整个数据集分布稳定,但工业数据存在开机瞬态、停机衰减、负载突变等非稳态段。某段10秒数据中,前2秒为启动冲击(幅值大),后8秒为稳态运行(幅值小),全局std会被启动段拉高,导致稳态段归一化后信息丢失。滚动标准化则让每个片段自适应其局部能量水平。

2.3 标签生成:故障类型与工况耦合的标注逻辑

CWRU等公开数据集仅标注故障类型,但实际产线中,同一内圈故障在空载与满载下振动形态差异显著。若忽略工况,模型在满载测试集上准确率可能暴跌40%。因此标签必须为复合标签

故障类型工况等级复合标签编码
正常轻载0
正常中载1
正常重载2
内圈故障轻载3
.........

共4类故障 × 3种工况 = 12类标签。训练时需确保每类样本数均衡(最少类样本数×1.2为采样阈值),否则模型偏向多数类。代码中使用imblearn.over_sampling.SMOTE对少数类进行合成过采样,但仅对时域片段做SMOTE,绝不直接对FFT频谱图插值——频域插值会伪造不存在的谐波关系。

3. 轻量级1D-CNN架构设计:为何放弃ResNet/VGG,选择深度可分离卷积

轴承振动信号是典型的一维时序,将图像CNN(如ResNet50)直接迁移到1D信号存在三大缺陷:参数量爆炸(ResNet50 1D版需23M参数)、感受野与故障周期不匹配、冗余通道加剧过拟合。本项目采用定制化轻量网络,核心思想是:用深度可分离卷积替代标准卷积,用扩张卷积替代池化,用通道注意力聚焦故障敏感频带

3.1 网络结构逐层解析(PyTorch实现)

import torch import torch.nn as nn import torch.nn.functional as F class BearingCNN(nn.Module): def __init__(self, num_classes=12, input_length=1024): super().__init__() # Block 1: 深度可分离卷积 + 扩张卷积 self.conv1 = nn.Sequential( nn.Conv1d(1, 32, kernel_size=16, stride=2, padding=7, dilation=1), # 1024→512 nn.BatchNorm1d(32), nn.ReLU(), nn.Conv1d(32, 32, kernel_size=16, groups=32, padding=7), # 深度卷积 nn.Conv1d(32, 64, kernel_size=1, padding=0), # 逐点卷积 nn.BatchNorm1d(64), nn.ReLU() ) # Block 2: 扩张率=2的深度可分离卷积(捕获更长周期) self.conv2 = nn.Sequential( nn.Conv1d(64, 64, kernel_size=16, stride=2, padding=15, dilation=2), # 512→256 nn.BatchNorm1d(64), nn.ReLU(), nn.Conv1d(64, 64, kernel_size=16, groups=64, padding=15, dilation=2), nn.Conv1d(64, 128, kernel_size=1), nn.BatchNorm1d(128), nn.ReLU() ) # Block 3: 通道注意力(SE Block简化版) self.se = nn.Sequential( nn.AdaptiveAvgPool1d(1), # 全局平均池化 nn.Conv1d(128, 32, kernel_size=1), nn.ReLU(), nn.Conv1d(32, 128, kernel_size=1), nn.Sigmoid() ) # 分类头 self.classifier = nn.Sequential( nn.AdaptiveAvgPool1d(4), # 256→4,压缩时序维度 nn.Flatten(), nn.Linear(128*4, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, num_classes) ) def forward(self, x): x = self.conv1(x) # [B,1,1024] → [B,64,512] x = self.conv2(x) # [B,64,512] → [B,128,256] se_weight = self.se(x) # [B,128,1] x = x * se_weight # 通道加权 return self.classifier(x) # 实例化并验证输入输出 model = BearingCNN(num_classes=12) dummy_input = torch.randn(32, 1, 1024) # batch=32, channel=1, length=1024 output = model(dummy_input) print(f"输入形状: {dummy_input.shape} → 输出形状: {output.shape}") # torch.Size([32, 12])
3.1.1 关键参数设计依据
层级参数选择物理意义
conv1kernel_size=16对应12kHz采样率下1.33ms时间窗,覆盖轴承冲击响应主振荡周期(实测0.8~2.5ms)
conv2dilation=2感受野扩大至32点(2.67ms),捕获冲击衰减包络
AdaptiveAvgPool1d(4)将256点时序压缩为4段,每段代表约64点(5.3ms)的统计特征,避免全连接层过载
SE Block128通道中,仅激活与故障相关的频带通道(如外圈故障对应100~300Hz频带),抑制无关噪声通道

3.2 训练策略:学习率预热+余弦退火的必要性

轴承数据信噪比低(典型SNR=6~10dB),初期训练易陷入局部最优。采用Linear Warmup + Cosine Annealing:前10个epoch学习率从0线性升至0.001,后续90epoch按余弦函数衰减至1e-6。代码实现:

from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR from torch.optim import Adam optimizer = Adam(model.parameters(), lr=0.001, weight_decay=1e-4) # 组合调度器:先线性预热10轮,再余弦退火90轮 scheduler = torch.optim.lr_scheduler.SequentialLR( optimizer, schedulers=[ LinearLR(optimizer, start_factor=0.01, end_factor=1.0, total_iters=10), CosineAnnealingLR(optimizer, T_max=90, eta_min=1e-6) ], milestones=[10] )

注意:余弦退火的T_max必须等于总训练epoch减去warmup epoch(此处100-10=90)。若设为100,退火阶段学习率无法充分衰减,导致后期收敛缓慢。

4. 模型验证与故障可解释性:用Grad-CAM定位故障发生时刻

准确率98%的模型若无法回答“为什么判定为外圈故障”,在工业现场即不可信。必须提供时域可解释性,证明模型关注的是真实的故障冲击,而非背景噪声或工况伪影。

4.1 Grad-CAM时域热力图生成

Grad-CAM原用于图像,需改造为1D信号版本:对最后一个卷积层输出的特征图([B,128,256])计算目标类别梯度,加权求和生成时域重要性权重。

def generate_1d_gradcam(model, input_tensor, target_class, conv_layer_name='conv2'): """生成1D Grad-CAM热力图""" model.eval() input_tensor.requires_grad_(True) # 前向传播 features = model.conv1(input_tensor) features = model.conv2(features) # [B,128,256] # 获取目标类别得分 output = model.classifier( model.se(features) * features # 注意SE权重已应用 ) score = output[:, target_class].sum() # 反向传播获取梯度 model.zero_grad() score.backward(retain_graph=True) # 提取最后一层卷积梯度(此处为conv2输出) gradients = features.grad # [B,128,256] pooled_gradients = torch.mean(gradients, dim=[0, 2]) # [128] # 加权特征图 features = features.detach() for i in range(128): features[:, i, :] *= pooled_gradients[i] cam = torch.mean(features, dim=1).squeeze(0) # [256] cam = torch.nn.functional.relu(cam) # 去负值 cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8) # 归一化 return cam.numpy() # 示例:对一个样本生成热力图 sample_input = torch.randn(1, 1, 1024) cam_heatmap = generate_1d_gradcam(model, sample_input, target_class=3) # 内圈故障 print(f"热力图长度: {len(cam_heatmap)}, 峰值位置索引: {np.argmax(cam_heatmap)}")
4.1.1 热力图验证方法
  • 物理一致性检验:将热力图峰值位置映射回原始信号时间轴,检查是否落在冲击响应起始点(需与理论冲击周期比对)
  • 消融验证:遮蔽热力图Top-20%高亮区域,重新输入模型,预测概率应下降≥30%;若下降<10%,说明模型未真正关注故障特征
  • 跨工况鲁棒性:同一故障在轻/中/重载下的热力图峰值位置偏移量应<15个采样点(1.25ms),否则存在工况耦合偏差

4.2 混淆矩阵与故障特异性分析

单纯看总体准确率会掩盖模型弱点。必须按故障类型拆解混淆矩阵,并计算故障特异性(Specificity)
$$ \text{Specificity}_i = \frac{\text{True Negative}_i}{\text{True Negative}_i + \text{False Positive}_i} $$
其中True Negative_i指其他故障被正确拒绝的次数,False Positive_i指其他故障被误判为第i类的次数。

预测\真实正常内圈外圈滚动体
正常92321
内圈18953
外圈04912
滚动体22388

计算外圈故障特异性:TN=92+89+2=183,FP=0+4+3=7 → Specificity=183/(183+7)=96.3%。若某类Specificity<85%,说明模型对该故障的判别边界模糊,需检查该类样本的时频特征是否与其他类重叠(如外圈与滚动体故障在低频段相似),此时应增加小波包分解特征作为辅助输入。

5. 边缘部署实战:将PyTorch模型转换为ONNX并在Jetson Nano上实测推理延迟

工业现场常需在无GPU服务器的嵌入式设备上运行诊断模型。Jetson Nano(128-core Maxwell GPU,4GB LPDDR4)是性价比最高的入门选择,但其TensorRT引擎不直接支持PyTorch,必须经ONNX中转。

5.1 ONNX导出与算子兼容性修复

PyTorch的AdaptiveAvgPool1d在ONNX中对应GlobalAveragePool,但Jetson Nano的TensorRT 7.1.3不支持动态输出尺寸。需将AdaptiveAvgPool1d(4)替换为固定尺寸AvgPool1d(kernel_size=64, stride=64)(因256/4=64):

# 修改模型中的分类头 class FixedClassifier(nn.Module): def __init__(self, in_channels=128): super().__init__() self.pool = nn.AvgPool1d(kernel_size=64, stride=64) # 替换AdaptiveAvgPool1d(4) self.flatten = nn.Flatten() self.fc1 = nn.Linear(128*4, 256) self.fc2 = nn.Linear(256, 12) def forward(self, x): x = self.pool(x) # [B,128,256] → [B,128,4] x = self.flatten(x) x = F.relu(self.fc1(x)) x = F.dropout(x, 0.3) return self.fc2(x) # 导出ONNX(必须指定dynamic_axes以支持batch变化) dummy_input = torch.randn(1, 1, 1024) 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"} }, opset_version=11 # TensorRT 7.1.3支持最高opset=11 )

5.2 TensorRT加速与实测性能

在Jetson Nano上执行以下命令完成部署:

# 安装TensorRT(需先刷JetPack 4.6) sudo apt-get install tensorrt # 使用trtexec编译ONNX模型 trtexec --onnx=bearing_cnn.onnx \ --saveEngine=bearing_cnn.trt \ --fp16 \ --workspace=1024 \ --shapes=input:1x1x1024 \ --avgRuns=100

实测结果(Jetson Nano,1024点输入):

指标数值说明
TensorRT引擎大小4.2MB小于SD卡剩余空间阈值(通常≥16MB)
单次推理延迟18.3ms满足实时诊断要求(采样率12kHz → 每83.3μs采1点,处理需<10ms)
GPU内存占用142MB远低于Nano的2GB GPU内存上限
功耗5.2W可由工业DC12V电源直接供电

提示:若实测延迟>25ms,需检查是否启用--fp16(半精度)和--workspace=1024(工作内存MB)。禁用FP16会使延迟增至32ms,workspace过小(如512)会导致引擎编译失败。

5.3 在线推理服务封装

为对接PLC或SCADA系统,需提供HTTP API。使用Flask轻量封装(app.py):

from flask import Flask, request, jsonify import numpy as np import pycuda.autoinit import pycuda.driver as cuda import tensorrt as trt app = Flask(__name__) # 加载TRT引擎 with open("bearing_cnn.trt", "rb") as f: runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING)) engine = runtime.deserialize_cuda_engine(f.read()) context = engine.create_execution_context() @app.route('/diagnose', methods=['POST']) def diagnose(): data = request.json['signal'] # [1024] float list signal_np = np.array(data, dtype=np.float32).reshape(1, 1, 1024) # 分配GPU内存 d_input = cuda.mem_alloc(signal_np.nbytes) d_output = cuda.mem_alloc(12 * 4) # 12类,float32 # 同步拷贝 cuda.memcpy_htod(d_input, signal_np) context.execute_v2([int(d_input), int(d_output)]) # 获取结果 output = np.empty(12, dtype=np.float32) cuda.memcpy_dtoh(output, d_output) pred_class = int(np.argmax(output)) confidence = float(np.max(output)) return jsonify({ "fault_type": ["normal", "inner_race", "outer_race", "ball"][pred_class % 4], "load_level": ["light", "medium", "heavy"][pred_class // 4], "confidence": confidence }) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

调用示例(curl):

curl -X POST http://jetson-ip:5000/diagnose \ -H "Content-Type: application/json" \ -d '{"signal": [0.1, -0.3, 0.5, ...]}' # 返回: {"fault_type":"outer_race","load_level":"heavy","confidence":0.92}

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

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

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

立即咨询