基于RNN与LSTM的轴承故障检测:从数据预处理到模型训练实战

发布时间:2026/10/10 1:13:23
基于RNN与LSTM的轴承故障检测:从数据预处理到模型训练实战
简介基于RNN模型的轴承故障检测源码与配套数据集是一份面向比赛场景的完整项目实践包。资源面向人工智能、通信工程、自动化、软件工程等专业的在校学生、教师或企业开发者既能作为课程设计、毕业设计的基础也适合初次接触深度学习故障诊断的小白循序渐进地学习。包内共八个文件包含三个脚本文件、两个数据压缩包、一个训练数据表、一个说明文档和一个项目介绍其中数据处理脚本负责划分训练集与测试集模型脚本定义RNN结构主脚本串联整体训练与评估流程代码结构清晰便于二次开发。压缩包整体约47.1MB内容紧凑已有851人学习下载。基于实际轴承数据完成故障检测任务作者测试通过且评审平均分达94.5分读者可借此了解RNN在机械故障诊断中的完整实现思路为自身项目提供可靠借鉴。1. 基于RNN的轴承故障检测这个比赛项目包里到底有什么轴承故障检测是设备维护里最容易拿来当比赛题目的场景之一旋转机械正常运行时振动信号平稳当内圈、外圈或滚动体出现磨损振动波形里会出现规律的冲击但这些冲击往往淹没在噪声里靠人眼看图既慢又容易漏。比赛项目要做的事就是用深度学习模型自动把振动信号分成正常、内圈故障、外圈故障、滚动体故障这几类。这个基于RNN模型的轴承故障检测python源码数据集压缩包解决的就是“拿到数据之后怎么训练一个能用的模型”的问题。压缩包里的内容按这类比赛项目的一般组织方式来看会包含训练数据、测试数据、Python源码和说明文档。源码里通常已经写好数据读取、模型定义和训练入口数据集则提供原始振动波形。也就是说你不需要自己满世界找轴承数据也不用从零搭网络把环境配好、路径改对就能跑出一个baseline。适合两类人一类是刚接触深度学习的在校学生拿它做课程设计或毕业设计另一类是准备参加数据比赛、想快速建立baseline的工程师。从拿到压缩包到跑通整个过程大概一两小时前提是把数据处理这一步做对。2. 数据预处理把原始振动信号切成模型能直接读的样本RNN模型和普通全连接网络最大的差别在于它对输入的组织方式极其敏感。全连接网络你把特征按任意顺序拼成一排也能训RNN却要求输入是“一段有顺序的序列”顺序一旦打乱时间依赖就全没了。所以在跑通这个项目之前花时间把数据处理逻辑理清楚比急着改模型参数重要得多。2.1 原始信号的组织与标签映射别把文件名当摆设比赛给的数据集通常是一个文件夹下有若干个子目录每个子目录代表一种故障类型比如“正常”“内圈故障”“外圈故障”“滚动体故障”目录里装的是不同工况下的振动信号文件。也有另一种组织方式所有信号放在一个目录里故障类型写在文件名的前缀或编号里。第一步是把这些文件读进来同时打上标签。我建议先固定一个标签映射字典不要用枚举顺序去猜否则后面训练跑起来你根本不知道索引1到底对应哪个故障。from pathlib import Path import numpy as np data_root Path(data/train) label_map { 正常: 0, 内圈故障: 1, 外圈故障: 2, 滚动体故障: 3, } X [] # 保存每个文件的振动信号 y [] # 保存类别标签 groups [] # 保存文件级分组信息后面切分数据集要用 for label_name, label in label_map.items(): folder data_root / label_name for file_idx, f in enumerate(folder.glob(*.csv)): signal np.loadtxt(f, delimiter,) X.append(signal) y.append(label) groups.append(f{label_name}_{file_idx})这段代码做了三件事遍历四个类别目录、读取每个CSV文件里的振动时间序列、用目录名映射标签。groups这个变量很容易被忽略但它后面是防止数据泄漏的关键。这里我直接用了CSV如果你的数据是MAT格式只要把读取方式换成scipy.io.loadmat取数组时用mat[list(mat.keys())[-1]].ravel()转成一维数组后面的逻辑完全一样。需要注意原始信号文件长度往往不一样RNN的输入长度是固定的所以不能直接把整个文件喂给模型必须进入下一步滑窗采样。2.2 滑窗、步长与归一化三个参数决定训练效果轴承振动信号是连续采样的一个文件里可能有几万甚至几十万个采样点直接整段输入既不现实也没必要。常见做法是滑动窗口截取。窗口长度怎么定我一般先看采样率和轴的旋转周期让窗口至少覆盖一到两个旋转周期。比如采样率是25.6kHz、轴转速1800rpm一个旋转周期约0.033秒对应大约850个采样点那窗口取1024点就比较合理。步长则控制样本量步长越小重叠越高样本数越多但训练时间也越长。参数推荐值说明窗口长度1024个采样点覆盖1到2个旋转周期足够看到冲击的周期性步长512个采样点窗口重叠率50%样本量和信息冗余比较均衡最小信号长度至少1024短于一个窗口的样本直接丢弃或补零滑窗代码并不复杂def sliding_window(signal, window_len1024, step512): n len(signal) if n window_len: return None num_windows (n - window_len) // step 1 indices np.arange(0, num_windows) * step windows np.stack([signal[i:i window_len] for i in indices]) return windows这段代码把每个原始信号切成了若干个等长窗口每个窗口都是一条独立样本。切完之后还要做归一化。我推荐Z-score而不是min-max振动信号里常常有突发冲击min-max会被个别极大值带偏Z-score用均值和标准差做标准化对这类有离群点的信号稳得多。from sklearn.preprocessing import StandardScaler scaler StandardScaler() # 先用训练集计算均值和标准差 X_train_flat X_train.reshape(-1, 1) scaler.fit(X_train_flat) # 再应用在训练集和验证集上 X_train_scaled scaler.transform(X_train_flat).reshape(X_train.shape)这里有一个玄学细节标准化只能站在训练集上做测试集也用训练集的均值和方差绝对不能用全集重新算否则就引入了未来信息。比赛里这一下偷懒验证分数会被虚高线下自测一时爽一提交就露馅。2.3 按文件切分训练集杜绝数据泄漏的“先手”很多人在这一步翻车而且翻得毫无察觉。直接对所有滑窗样本做随机切分让一部分窗口进训练集、一部分窗口进验证集结果验证准确率特别高心里还以为是模型强。其实是泄漏了同一个原始文件相邻的窗口在时间上高度重叠训练集里出现了验证集“剧透”。正确的做法是按文件分组切分。也就是说同一个原始振动文件切出的所有窗口必须全部进训练集或全部进验证集不能跨集。sklearn的GroupShuffleSplit就是干这个的。from sklearn.model_selection import GroupShuffleSplit gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) for train_idx, val_idx in gss.split(X_windows, y_windows, groupsgroups_repeated): X_train X_windows[train_idx] y_train y_windows[train_idx] X_val X_windows[val_idx] y_val y_windows[val_idx]需要特别留意groups_repeated必须和窗口一一对应也就是每个窗口都要记住它来自哪个原始文件。前面在读取信号时我特意给每个文件编了一个label_name_file_idx的组ID滑窗时把组ID按窗口重复一遍即可。这一层切分逻辑不处理好后面换任何模型、调任何参数结果都是空中楼阁。3. RNN模型搭建为什么实战里最终会用LSTM而不是原始RNN数据处理完毕接下来是模型。这个项目叫“基于RNN模型”但实际训练时你大概率会把它换成LSTM。这不是偷梁换柱而是原始RNN在反向传播时存在梯度消失问题序列稍长就学不动。LSTM通过门控机制把梯度留一条通道传下去算是在RNN框架下的改良实现。3.1 对时序信号的建模思路RNN比全连接网络强在哪如果把每个1024点的窗口摊平送进全连接网络模型会把每个采样点当成独立特征完全没有“前一个冲击之后下一个冲击隔了多久”的概念。而轴承故障信号的本质规律恰恰在时间关系里内圈故障和外圈故障的冲击间隔、衰减方式都有差异这种依赖只有RNN这类循环结构能建模。全连接网络看的是特征的绝对取值RNN看的是特征的变迁顺序。一个经验性结论是在不做手工特征提取的前提下LSTM在这类振动分类任务上通常比同参数量的一维CNN更稳尤其样本量不算大的时候。一维CNN也能提取局部波形特征但需要堆更多层才能拉长感受野比赛项目里用LSTM还是更直接的解法。3.2 输入维度与隐藏层设计用一个可跑的模型把参数说清用PyTorch复现的话模型结构可以简化为LSTM加一个分类头。输入是三维张量形状为(batch_size, seq_len, input_size)其中seq_len是窗口长度1024input_size是1因为每个时间步只有一个振动幅值。import torch import torch.nn as nn class BearingRNN(nn.Module): def __init__(self, n_classes4, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_size1, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout, ) self.head nn.Sequential( nn.Linear(hidden_size, 128), nn.ReLU(), nn.Dropout(dropout), nn.Linear(128, n_classes), ) def forward(self, x): # x: (batch, seq_len, 1) output, (h_n, c_n) self.lstm(x) # 取最后一层的隐藏状态 last_hidden h_n[-1] # (batch, hidden_size) return self.head(last_hidden)这里有几个参数要说明。hidden_size64表示LSTM隐藏单元数太小学不到复杂模式太大容易过拟合毕竟比赛数据集样本量一般只有几千到几万。num_layers2是层数两层LSTM比一层有更强的表达能力但三层以上在数据量不足时收益很小反而拖慢训练。batch_firstTrue让输入输出都按(batch, seq_len, hidden)排列省去经常permute的麻烦。h_n[-1]是最后一层LSTM在最后一个时间步的隐藏状态也有人用output[:, -1, :]两者等价但h_n[-1]在多层的写法上更清晰。注意项目如果用双向LSTMh_n的维度会变需要手动拼接前向和后向一般这个任务没必要用双向单向就够。模型参数取值理由input_size1输入是单通道振动幅值hidden_size64表达能力和参数量平衡num_layers2深度适中避免过拟合dropout0.2抑制全连接头过拟合输出维度4对应四个故障类别3.3 损失函数与优化器类别不均衡时的第一版方案轴承故障数据集常常不均衡正常样本偏多某种故障样本偏少。如果直接用普通交叉熵模型会倾向于把所有样本都判成多的那一类。第一版训练我建议直接给交叉熵加类别权重代价是少数类分错时惩罚更大训练会稍微慢一些但各类别准确率会均衡得多。device torch.device(cuda if torch.cuda.is_available() else cpu) model BearingRNN().to(device) class_weights torch.tensor([1.0, 1.0, 1.6, 2.0], devicedevice) criterion nn.CrossEntropyLoss(weightclass_weights) optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5 )class_weights里的数值按样本量反比大致设一下就行不必太精确。ReduceLROnPlateau的意思是验证集损失几个epoch不下降就把学习率减半patience5控制等待次数这比固定学习率死磕到底省心。Adam优化器基本是这个场景的默认选择学习率1e-3起步是稳妥值模型太浅或数据太少时不需要再调高。4. 训练与推理跑通从数据到分类结果的全部流程模型定义好之后训练流程就是标准的监督分类。这一章把训练主循环和评估环节拆开讲目的是让你跑通之后知道每一步为什么这么写以及跑出来的结果到底意味着什么。4.1 训练主循环与checkpoint怎么判断模型有没有真的在学训练开始前先把所有窗口数据组装成TensorDataset和DataLoader。有一件事容易漏LSTM的输入需要多一个通道维度也就是把(batch, 1024)变成(batch, 1024, 1)。我建议在构造数据集时就完成这个unsqueeze不要在每轮循环里反复改形状。from torch.utils.data import TensorDataset, DataLoader X_train_tensor torch.FloatTensor(X_train_scaled).unsqueeze(-1) y_train_tensor torch.LongTensor(y_train) X_val_tensor torch.FloatTensor(X_val_scaled).unsqueeze(-1) y_val_tensor torch.LongTensor(y_val) train_dataset TensorDataset(X_train_tensor, y_train_tensor) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) n_epochs 60 best_acc 0.0 for epoch in range(n_epochs): model.train() total_loss 0.0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() logits model(xb) loss criterion(logits, yb) loss.backward() optimizer.step() total_loss loss.item() * xb.size(0) # 一个epoch结束后在验证集上评估 model.eval() val_loss 0.0 correct 0 total 0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) logits model(xb) loss criterion(logits, yb) val_loss loss.item() * xb.size(0) preds logits.argmax(dim1) correct (preds yb).sum().item() total yb.size(0) val_acc correct / total val_loss / total scheduler.step(val_loss) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pt) print(fepoch {epoch1:02d} | train_loss {total_loss/len(train_dataset):.4f} | fval_acc {val_acc:.4f} | best {best_acc:.4f})这段代码里model.train()和model.eval()不能省。eval模式下Dropout会关闭批量归一化行为也会改变如果忘了切回训练模式后面每个epoch的指标会忽高忽低。保存模型时只存state_dict而不是整个模型对象是比赛里比较规范的做法换环境时只需要加载权重不依赖原文件的类定义路径。best_acc按验证集准确率更新比按训练loss更新可靠因为训练loss下降完全可能是过拟合。4.2 推理评估与提交格式混淆矩阵和CSV结果训练结束后测试阶段要做两件事一是看分类报告和混淆矩阵搞清楚模型到底在哪一类上表现差二是把预测结果整理成比赛要求的提交格式。from sklearn.metrics import confusion_matrix, classification_report import pandas as pd model.load_state_dict(torch.load(best_model.pt, map_locationdevice)) model.eval() all_preds [] all_labels [] with torch.no_grad(): for xb, yb in test_loader: xb xb.to(device) logits model(xb) preds logits.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(yb.numpy()) labels [正常, 内圈故障, 外圈故障, 滚动体故障] print(classification_report(all_labels, all_preds, target_nameslabels, digits4)) print(confusion_matrix(all_labels, all_preds))如果数据是带标签的测试集这一步直接用如果是比赛要求预测未知标签的测试集就把预测写入CSV。提交格式通常是两列第一列是样本ID第二列是预测标签ID和测试窗口的先后顺序必须严格对应否则提交上去是乱的。submission pd.DataFrame({ sample_id: test_sample_ids, label: all_preds, }) submission.to_csv(submission.csv, indexFalse)这里最容易翻车的不是模型而是test_sample_ids的顺序。测试集加载顺序和预测顺序必须一致如果中途做过任何随机打乱提交文件就废了。我的习惯是测试集永远不shuffleTrue并记录每个窗口对应的文件名编号宁可在CSV里多留一列也不要事后对不上号。5. 避坑与排查轴承故障检测跑通后容易翻车的五个位置训练跑通只是第一步比赛项目里真正花时间的是排错。这一章直接写五个我见过的高频问题每一条都按现象、原因、解决的顺序来。5.1 现象Loss正常下降验证集准确率却死咬25%四分类问题里25%意味着模型等于在瞎猜但训练loss又确实在降这是比赛项目里最迷惑人的信号。原因标签映射写反了。训练集加载时用的label_map和测试集加载时不一致或者某个类别目录在枚举时顺序被系统打乱了模型学到了正确的特征却对应到了错误的标号上。解决把标签映射字典固定下来并保存成JSON文件训练和推理都用同一个映射从文件里读取而不是靠目录遍历顺序。写完之后取几个窗口手动检查一下索引0是不是你想要的“正常类”别把映射只打印出来看一眼就完事。5.2 现象验证分数比训练还高心里反而要发慌正常情况下验证集分数应该略低于训练集。如果验证分数莫名其妙更高第一反应不是兴奋而是检查有没有泄漏。原因滑窗之后直接对窗口随机切分同一个原始文件相邻窗口有大量重叠验证集里出现了和训练集几乎一样的数据。模型相当于考试时偷看了答案分数虚高。解决使用GroupShuffleSplit按原始文件分组切分。如果数据是时间连续的还要注意验证集时间在训练集之后避免把同一时间段的数据切到两边。5.3 现象某个故障类别一直预测正确其他类全乱训练完成之后正常类准确率99%但三个故障类里有一个特别低乱判成正常类。这种情况在比赛结果排行上非常难看。原因类别不均衡。多数类在损失函数里占主导少数类的梯度被淹没了。另一个隐性问题发生在滑窗时某个故障类型样本本来就少滑窗后数量差距进一步拉大。解决先用class_weight给少数类加权如果效果还不够就换WeightedRandomSampler对少数类过采样。注意过采样不是复制少数类进内存而是给采样器一个概率权重训练时每个batch能保证有少数类被采到。5.4 现象训练正常推理时却报维度错误训练跑得好好的一到推理阶段报错说Expected 3D input或者size mismatch这类报错在RNN项目里太常见了。原因LSTM输入必须是(batch, seq_len, feature_dim)三维。训练时数据经过管道已经unsqueeze成三维但推理阶段写了独立加载代码加载出来的窗口形状是(batch, seq_len)少了一个通道维度。解决推理时在模型前加x x.unsqueeze(-1)或者把unsqueeze统一放数据处理函数里不要散落在训练和推理两套代码里。一个更省心的做法是把数据组织成(N, 1024, 1)后在模型内部不处理维度所有形状变化都收敛到数据加载层。5.5 现象同样代码跑多次结果不一样同样的超参数第一次跑准确率96%第二次92%第三次95%总有小幅浮动。如果你还把随机种子忘了设差距会更大。原因深度学习训练本来就是随机初始化加随机batch采样GPU上的卷积和LSTM运算还可能引入不确定性。LSTM内部的矩阵运算在CUDA上存在非确定性分支这几乎是玄学级的波动来源。解决固定一套种子训练前调用torch.manual_seed(42)、np.random.seed(42)DataLoader设置generator。如果结果对竞赛提交很重要打开torch.backends.cudnn.deterministic True和torch.backends.cudnn.benchmark False代价是推理速度小幅下降但能换来可重复性。跑了多个seed之后取平均结果也是比赛里稳成绩的常用手段。6. 后续还可以做的验证让分类结果从“能跑”到“可信”模型训练完最直观的验证是五折交叉验证。把按文件分组后的数据切成五份轮流拿一份做验证其余四份训练最后看五折的平均准确率和方差。方差大说明模型不稳定不是某个故障类别样本太少就是切分时泄漏没清干净。交叉验证过的模型再跑一遍测试集出来的指标才有资格写进报告里。另一个提升可信度的动作是多窗口投票。比赛测试集通常每段信号可以切出多个窗口让每个窗口都做一次预测然后对同一测试样本的所有窗口预测结果做多数投票。这一招不需要训练新模型却能把单窗口边界上的抖动抹平涨分往往比调网络结构还明显。如果有连续几个窗口预测结果来回跳那说明这段信号本身就处于故障的边缘状态需要人工复核。手工特征融合也可以顺手一试把RMS、峰值因子、峭度这些时域统计量拼到LSTM隐藏层输出后面接同一个全连接头。LSTM擅长提取波形时序特征手工特征擅长表达全局能量水平两者互补但记得统计量计算要在滑窗内部完成不要跨窗口混合。验证手段解决的问题代价5折交叉验证估计模型稳定性和泛化水平训练时间约5倍多窗口投票平滑单次预测抖动推理时间增加但可并行手工特征融合补足全局能量信息特征工程代码量增加实体故障样本永远是有限的这个项目能跑通只是开始。从那以后我每次处理轴承故障这类信号分类都强制自己走同一套流程先按文件分组切分再跑LSTM基线然后盯住混淆矩阵查泄漏。先确认结果不是数据泄漏给的假成绩才敢继续往上加特征、换结构。希望这套经验和代码能帮你省下几个晚上的排查时间。本文还有配套的精品资源点击获取