资讯详情 基于CNN-LSTM的轴承故障诊断:从振动信号到故障标签的Python实战
📅 2026/10/3 3:37:36
简介这份资源面向机械故障诊断与深度学习方向的课程设计、毕业设计及入门实践者提供一套基于CNN-LSTM的滚动轴承故障诊断完整Python实现方案。项目针对外环、内环及滚动体三类损伤结合三种轴承规格尺寸构成九种故障类别在负载功率3马力、转速约1730转/分的工况下采集样本通过卷积与长短期记忆网络联合建模完成状态判别。压缩包共30个文件约56.3MB包含csv与mat格式的原始及处理后数据、pth预训练模型权重、py训练与测试脚本、ipynb演示笔记、m数据转换脚本以及xlsx结果记录和png方法示意图覆盖从数据读取、模型搭建到结果验证的完整链路。已有53人学习下载。读者可借助预训练模型快速复现实验参考源码理解重叠采样、网络结构与训练流程并利用文档与结果文件对照排查问题适合作为轴承故障诊断课题的起步模板。1. 从振动信号到故障标签CNN-LSTM 轴承诊断到底在解决什么产线上的一台电机突然异响拆开发现轴承滚道已经剥落。这种场景在工厂里太常见了问题是能不能在它彻底抱死之前就从振动信号里看出苗头基于 CNN-LSTM 的轴承故障诊断系统做的就是这件事——用 Python 把加速度传感器采集的一维振动信号自动映射到「正常 / 内圈故障 / 外圈故障 / 滚动体故障」这些标签上。它适合两类人一类是手里已经有 CWRU 或自采振动数据、想快速搭一套能跑通 baseline 的算法工程师另一类是想搞懂「CNN 和 LSTM 为什么要串起来用」的学生和转行者。整套方案的核心链路是原始信号 → 切片与归一化 → CNN 提局部冲击特征 → LSTM 抓时序依赖 → 全连接分类。源码、预训练模型和项目文档的意义在于你不用从零调参先把 pipeline 跑通再针对自己的工况微调。2. 为什么是 CNN 加 LSTM轴承振动信号的两种特征怎么分工2.1 轴承故障在时域和频域里长什么样轴承一旦出现局部损伤每转一圈滚珠碾过缺陷点就会产生一次冲击这个冲击在时域上表现为周期性脉冲在频域上则对应故障特征频率——内圈、外圈、滚动体的特征频率计算公式不同但都跟转速和轴承几何参数挂钩。问题在于实际采集到的信号里这些冲击往往被噪声和别的机械振动淹没单靠傅里叶变换找峰值在变转速工况下很容易翻车。所以常见做法是双管齐下一方面用 CNN 的卷积核去自动学习冲击波形的局部模式它擅长从短窗口里抓「长什么样」另一方面用 LSTM 去记「多久出现一次」这种周期性和前后依赖。这两件事恰好对应故障诊断的两个关键维度——形态和节律。如果只用 CNN模型对冲击间隔不敏感只用 LSTM又容易在原始高采样率信号上训练不动因为序列太长、梯度传播代价高。串起来用是工程上比较稳的折中。2.2 CNN-LSTM 串行结构的选型理由具体结构上我一般会这样搭输入是一段长度为 1024 或 2048 的一维信号先经过两到三层一维卷积Conv1D每层后面接 BatchNorm 和 ReLU再用最大池化把长度压下来。卷积部分输出的特征图形状是时间步通道数把它直接喂给 LSTM让 LSTM 沿时间步扫描。LSTM 最后一个时间步的隐状态接全连接层输出类别数。为什么不用 CNN 加注意力注意力在长序列上确实强但对轴承这种周期性冲击LSTM 的门控机制对间隔建模更直接而且参数量可控小数据集上不容易过拟合。为什么不用纯 LSTM因为原始信号点太多LSTM 逐点处理训练慢且容易梯度消失。CNN 先做下采样和局部特征提取等于帮 LSTM 把输入「预处理」了一遍。提示如果你的数据是变转速工况建议在 CNN 前加一个角域重采样步骤把时域信号转成角域否则故障特征频率会漂移模型学到的节律就乱了。2.3 用 PyTorch 搭一个最小可跑的 CNN-LSTM下面这段代码是一个能直接跑通的结构定义输入形状是 (batch, 1, 1024)输出 4 类。我把它写成独立模块方便你替换卷积层数或 LSTM 隐层大小。import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, num_classes4, lstm_hidden64): super().__init__() # 卷积部分提取局部冲击特征 self.conv nn.Sequential( nn.Conv1d(1, 16, kernel_size7, stride2, padding3), # 1024 - 512 nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2), # 512 - 256 nn.Conv1d(16, 32, kernel_size5, stride2, padding2), # 256 - 128 nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2) # 128 - 64 ) # LSTM 部分沿时间步建模周期性 self.lstm nn.LSTM(input_size32, hidden_sizelstm_hidden, num_layers1, batch_firstTrue) self.fc nn.Linear(lstm_hidden, num_classes) def forward(self, x): # x: (batch, 1, 1024) x self.conv(x) # (batch, 32, 64) x x.permute(0, 2, 1) # (batch, 64, 32) 时间步在前 out, (h, c) self.lstm(x) # out: (batch, 64, hidden) last out[:, -1, :] # 取最后时间步 return self.fc(last)逻辑说明卷积部分用 stride 和池化把 1024 点压到 64 个时间步每个时间步 32 维特征permute 是把通道维换到特征维因为 LSTM 要求输入是 (batch, seq_len, input_size)。参数上kernel_size 取 7 和 5 是经验值能覆盖一个冲击周期内的采样点lstm_hidden 设 64 是平衡精度和显存数据量大可以加到 128。如果你发现训练 loss 震荡先把 BatchNorm 的 momentum 调小到 0.01 试试。3. 从 CWRU 数据到训练集切片、归一化和标签对齐3.1 数据切片与重叠采样CWRU 数据集的原始文件是 .mat 格式每个文件里是一段驱动端或风扇端的振动信号采样率有 12kHz 和 48kHz 两种。直接整段喂进去不现实必须切片。我一般用窗口长度 1024、重叠率 50% 的方式切这样既能保证每个样本包含至少一个完整冲击周期又能通过重叠扩充样本量。import scipy.io as sio import numpy as np def load_and_slice(mat_path, label, window1024, overlap0.5): data sio.loadmat(mat_path) # CWRU 的键名通常是 DE_time 或 FE_time按实际文件改 signal data[DE_time].flatten() step int(window * (1 - overlap)) samples, labels [], [] for start in range(0, len(signal) - window, step): seg signal[start:start window] samples.append(seg) labels.append(label) return np.array(samples), np.array(labels)逻辑说明step 控制重叠overlap0.5 表示每次移动半个窗口。注意 range 的终点要减 window否则最后一段会越界。标签这里用整数编码0 到 3 分别对应正常、内圈、外圈、滚动体。如果你用的是 48kHz 数据窗口可以放大到 2048因为同样时间内采样点更多。3.2 归一化的坑别用全局均值很多人图省事把整个数据集算一个均值和方差做归一化。这在轴承诊断里是隐患——不同故障类型的信号幅值分布差异很大全局归一化会把故障冲击的幅值压平模型反而学不到东西。正确做法是逐样本归一化或者按训练集统计量分别归一化训练集和测试集。def normalize_per_sample(x): # x: (N, window) mean x.mean(axis1, keepdimsTrue) std x.std(axis1, keepdimsTrue) 1e-8 return (x - mean) / std参数说明1e-8 是防止除零别省。逐样本归一化后每个样本的幅值范围在 -3 到 3 之间训练更稳。如果你要做跨工况迁移建议在归一化后再加一个标准化层把分布对齐。3.3 标签对齐与数据集划分CWRU 的文件命名有规律比如「InnerRace_0.007」表示内圈故障、缺陷直径 0.007 英寸。写一个映射字典把文件名关键词映射到标签比手动标省事。文件名关键词标签类别Normal0正常InnerRace1内圈故障OuterRace2外圈故障Ball3滚动体故障划分时按 7:2:1 分训练、验证、测试注意同一段原始信号切出来的样本不能同时出现在训练集和测试集里否则精度虚高。我一般按原始文件划分再在文件内部切片这样能避免数据泄漏。4. 训练、验证与预训练模型怎么用4.1 训练循环与关键超参训练部分用 Adam 优化器学习率 1e-3配合余弦退火。损失函数用交叉熵。batch size 设 64epoch 先跑 50 轮看收敛情况。import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model CNNLSTM(num_classes4).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) for epoch in range(50): model.train() for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() logits model(x) loss criterion(logits, y) loss.backward() optimizer.step() scheduler.step()逻辑说明余弦退火让学习率从 1e-3 平滑降到接近 0避免后期震荡。如果你发现验证集准确率卡在 80% 上不去先检查数据切片有没有把故障冲击切掉再考虑加一层卷积或增大 LSTM 隐层。4.2 预训练模型怎么加载和微调预训练模型的价值在于它已经在 CWRU 或类似数据集上收敛过你拿过来可以直接推理也可以在自己的数据上微调。加载时注意 state_dict 的键名要匹配如果结构改了用 strictFalse。model CNNLSTM(num_classes4) state torch.load(pretrained_cnn_lstm.pth, map_locationcpu) model.load_state_dict(state, strictFalse) model.to(device) # 微调时只训练全连接层 for name, param in model.named_parameters(): if fc not in name: param.requires_grad False参数说明strictFalse 允许部分层不匹配适合你改了分类数的情况。微调时冻结卷积和 LSTM只训全连接学习率可以设 1e-4跑 10 轮就够。如果你的工况和预训练数据差异大建议解冻 LSTM 一起训但学习率要降到 1e-5。4.3 验证指标别只看准确率轴承诊断里漏报和误报的代价不一样。漏报一个内圈故障可能导致停机误报一个正常轴承只是多检查一次。所以验证时除了 accuracy还要看召回率和混淆矩阵。from sklearn.metrics import classification_report, confusion_matrix model.eval() preds, trues [], [] with torch.no_grad(): for x, y in test_loader: x x.to(device) logits model(x) preds.extend(logits.argmax(dim1).cpu().numpy()) trues.extend(y.numpy()) print(confusion_matrix(trues, preds)) print(classification_report(trues, preds, digits4))逻辑说明confusion_matrix 能看出哪两类容易混比如内圈和外圈在低转速下特征频率接近容易互相误判。classification_report 给出每类的 precision 和 recall如果某一类 recall 明显低说明样本不均衡或特征不明显需要补数据或调权重。5. 避坑与排查训练不收敛、精度虚高、部署翻车5.1 训练 loss 不降反升现象前几个 epoch loss 正常下降之后突然飙升到 nan。原因学习率太大或者归一化没做对导致梯度爆炸。解决先把学习率降到 1e-4检查归一化是否逐样本做了再在 LSTM 后加梯度裁剪。torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)5.2 验证集精度 99% 但实测一塌糊涂现象CWRU 上跑出 99% 准确率换自己的数据直接崩。原因数据泄漏同一段信号切出的样本同时进了训练和测试或者工况差异太大模型过拟合了实验室数据。解决按原始文件划分数据集别按样本随机分在自己的数据上重新微调至少解冻 LSTM 层。5.3 推理时显存爆了现象训练时 batch size 64 没事推理时单样本却报显存不足。原因推理时没加 torch.no_grad()或者模型还在 train 模式BatchNorm 用了 batch 统计量。解决推理前调 model.eval()并用 with torch.no_grad() 包住前向传播。5.4 预训练模型加载后输出全是同一类现象加载预训练权重后预测结果全是正常类。原因state_dict 键名不匹配strictFalse 静默跳过了大部分层实际用的是随机初始化。解决加载后打印 model.state_dict() 的键和保存时的键对比确保卷积和 LSTM 层都匹配上了。5.5 变转速工况下精度骤降现象定转速数据训练很好变转速测试直接掉到 60%。原因故障特征频率随转速漂移模型学到的节律失效。解决加角域重采样把时域信号转成角域或者在训练时做转速归一化把不同转速的数据对齐到同一尺度。6. 把模型塞进产线从单文件推理到批量诊断脚本训练完的模型最终要落到能用的脚本上。我一般会写一个 diagnose.py输入是一个文件夹路径里面是待诊断的 .mat 或 .csv 文件输出是每个文件的预测类别和置信度。核心逻辑是加载模型 → 逐文件切片 → 逐样本推理 → 投票取多数。import os import numpy as np import torch def diagnose_folder(model, folder, window1024, devicecpu): model.eval() results {} for fname in os.listdir(folder): if not fname.endswith(.mat): continue path os.path.join(folder, fname) samples, _ load_and_slice(path, label0, windowwindow) samples normalize_per_sample(samples) tensor torch.tensor(samples, dtypetorch.float32).unsqueeze(1).to(device) with torch.no_grad(): logits model(tensor) probs torch.softmax(logits, dim1) pred probs.argmax(dim1).cpu().numpy() # 多数投票 values, counts np.unique(pred, return_countsTrue) results[fname] (values[counts.argmax()], counts.max() / len(pred)) return results逻辑说明unsqueeze(1) 是把 (N, window) 变成 (N, 1, window)符合模型输入。多数投票能抑制个别样本的误判。置信度用投票占比表示低于 0.6 的建议人工复核。这个脚本可以直接挂到定时任务上每小时扫一次数据文件夹。进阶技巧如果你要部署到边缘设备先把模型转成 ONNX再用 onnxruntime 推理速度能快 2 到 3 倍。转换时注意 LSTM 的 batch_first 参数ONNX 对它的支持在不同版本有差异转完一定要用 onnxruntime 跑一遍验证输出一致。我自己的习惯是每次换新工况的数据先拿预训练模型跑一遍看置信度分布如果大部分样本置信度低于 0.5说明工况差异大必须重新微调别硬上。这个判断能帮你省下不少返工时间。希望帮到你。本文还有配套的精品资源点击获取