资讯详情

基于深度学习的滚动轴承故障诊断:从振动信号到PyTorch工程实践

📅 2026/10/13 10:11:08 | 华诺云谱 👁 阅读
基于深度学习的滚动轴承故障诊断:从振动信号到PyTorch工程实践
简介这是一份基于深度学习的滚动轴承故障诊断方法完整项目主要面向计算机相关专业毕业设计学生、需要项目实战练习的学习者也适合作为课程设计或期末大作业。项目包含全部源码与数据涵盖数据处理、模型构建、训练评估与可视化等环节可帮助读者系统掌握利用卷积神经网络等深度学习方法处理西储大学CWRU轴承数据的流程。压缩包为zip格式共41个文件主要由30个mat数据文件、4个Python源码文件、6个md文档和1个使用说明txt组成整体约34.87MB源码中包含模型构建、数据预处理与可视化等模块说明文档对目录结构和运行方式做了详细说明。所有代码经过严格调试下载后可直接运行能够完整复现滚动轴承故障分类与诊断结果。已有971人学习或下载适合需要快速启动毕业设计或开展故障诊断方向练习的读者使用。1. 你不是在下论文你是在跑一套能出结果的工程如果你打开这个压缩包看到的不是论文截图和PPT而是train.py、data_process.py、models.py、checkpoints/这类完整工程结构说明这份「基于深度学习的滚动轴承故障诊断方法」是拿真实振动数据喂出来过的。它解决的是一个非常具体的工业问题拿到一段轴承振动加速度信号判断该轴承处于正常、外圈故障、内圈故障还是滚动体故障更进一步还能根据故障直径细分严重程度。这类诊断是旋转机械状态监测的基础也是机械、电气、自动化方向毕业设计里高频出现的选题。技术栈就是 Python PyTorch数据是实验台采集的一维波形。这篇笔记不冒充你手里那份源码的作者我只按一线跑过类似项目的习惯把数据怎么切、模型怎么选、参数怎么调、坑藏在哪里讲透。目标是让拿到这个标题下任何压缩包的读者都能从「解压」一路跑到「出测试报告」。2. 数据先行先把振动信号切成样本模型才有东西可学2.1 数据目录与标签先搞清每个文件对应哪类故障拿到解压后的文件夹先别急着跑train.py。我一般第一件事是列出目录树确认数据组织方式。常见的轴承故障诊断数据有两种存放风格。一种是每个类别一个子目录目录名直接叫normal、inner_race_fault_1里面是一段段原始信号 CSV另一种是只有一个大 CSV第一列是标签后面是采样点。你可以用下面这个小脚本快速生成数据清单顺便确认标签能不能正确读取import os from collections import Counter data_path ./data # 换成你的实际路径 label_map {} # 假设数据是按子目录分类的 for cls_name in sorted(os.listdir(data_path)): cls_dir os.path.join(data_path, cls_name) if os.path.isdir(cls_dir): files os.listdir(cls_dir) label_map[cls_name] len(files) print(f{cls_name}: {len(files)} 个文件) print(总类别数:, len(label_map))这段逻辑很简单但能暴露很多问题。如果某个类别文件数比其他类别少一个数量级说明样本不平衡在数据源头就存在后面训练要用加权损失或重采样。如果目录名出现inner_race_fault_0.18_0.21这种写法说明标签里还附带了故障直径和转速信息理论上可以做成更细的多分类任务而不是只有四类。看完目录就要确认采样率。振动数据的采样率直接决定了后续滑窗取多少点。工业上常用的是 12kHz 或 48kHz 采样率一般会在原数据集的说明文件里给出。如果没有说明文件可以读一个数据文件看相邻采样点的时间间隔。import pandas as pd df pd.read_csv(./data/normal/1.csv, headerNone) print(文件shape:, df.shape) print(前10个采样点:, df.iloc[:10, 0].tolist())逻辑说明这一步不是为了看波形是为了确认数据是单通道还是多通道。很多采集台会同时记录加速度、速度和位移但故障诊断模型一般只取加速度通道因为故障冲击在加速度信号上最明显。如果发现每个文件是 8 列甚至 16 列那说明是多通道需要选通道或做融合不能想当然地只读第一列。参数说明headerNone是避免把第一行采样点误当成列名iloc[:10, 0]取的是第一通道前十个点看数值量级能判断数据是否经过了归一化。如果数值在 ±1 之间说明可能已经做过归一化如果出现几百甚至上千就要自己处理。2.2 用滑窗把一维信号切样本窗口长度与步长的选择深度学习模型输入尺寸是固定的但原始振动信号往往是一段几秒甚至几十秒的连续波形。需要把长波形切成固定长度的片段每个片段作为一个训练样本。这里有两个关键参数窗口长度win_len和滑动步长stride。窗口长度通常由轴承的故障特征频率和转速共同决定。一个经验法则是窗口内至少包含 10 个完整的轴承转频周期。比如转速为 1800 r/min转频是 30Hz采样率 12kHz那么一个转频周期就是 400 个采样点10 个周期是 4000 点。切一个 4096 点的窗口基本够用这也是很多方案默认用 4096 或 2048 的原因。import numpy as np def sliding_window(signal, win_len4096, stride2048): samples [] n len(signal) for start in range(0, n - win_len 1, stride): samples.append(signal[start:start win_len]) return np.array(samples) # 假设你读到了一个长度为 100000 的数组 raw_signal np.random.randn(100000) # 示例数据 samples sliding_window(raw_signal, win_len4096, stride2048) print(窗口数量:, samples.shape[0])逻辑说明滑窗的目的是把每个独立样本当成一个「独立观测」。如果步长等于窗口长度相邻窗口没有重叠样本数量最少如果步长小于窗口长度相邻窗口有数据重叠样本数量会增加。重叠的好处是样本量更大、模型更不容易过拟合坏处是相邻样本高度相似如果直接把训练集和测试集从同一段波形上切出来会造成严重的数据泄露。参数说明win_len是每个样本的点数一般取 1024、2048、4096 这类 2 的幂次方便卷积网络下采样stride决定样本密度常见取win_len // 2让重叠率 50%。如果想更保守可以设stride win_len完全没有重叠避免样本间自相关过高。我在跑类似项目时会先用一个小脚本统计滑窗后各类别的样本数量确认类别分布没有被滑窗改变。更合理的方式是对每段原始长信号独立进行滑窗然后按「原始信号段」划分训练集和测试集而不是把所有窗口混在一起随机划分。这是很多初版代码最容易犯的错。2.3 数据集划分与归一化别把同一段信号拆到训练集和测试集数据划分原则在故障诊断里比模型结构更重要。如果训练集和测试集都来自同一段原始信号的不同窗口模型其实已经见过测试数据了测试准确率虚高真实场景一换设备就崩。正确做法是把每段原始记录当作一个整体同一段信号的所有窗口只能全部进训练集或全部进测试集。from sklearn.model_selection import train_test_split # data_dict: {类别名: [原始信号段列表]}每个元素是一整段numpy数组 def build_dataset(data_dict, win_len4096, stride2048): all_x, all_y [], [] for cls_idx, (cls_name, segments) in enumerate(data_dict.items()): for seg in segments: windows sliding_window(seg, win_len, stride) all_x.extend(windows) all_y.extend([cls_idx] * len(windows)) return np.array(all_x), np.array(all_y) # 假设你已经按段组织了数据 x_all, y_all build_dataset(data_dict) # 注意labels是0,1,2,3 # 这里按原始段索引划分才是安全的但上面已合并窗口所以需要保留段归属 # 更稳妥的写法见下文逻辑说明如果build_dataset把不同段的所有窗口全部混在一起然后直接train_test_split(x_all, y_all, test_size0.2)就会发生段间穿插。要避免这种事应该先给每段信号一个唯一编号然后把段编号作为分层变量划分再根据段编号取对应的窗口。我常用的折中方案是先不合并而是维护一个segment_ids数组每个窗口带着它来自哪一段的编号。划分测试集时先随机抽几个段编号作为测试段剩下的段作为训练段。这样就算训练集和测试集窗口在时间上有重叠也不至于直接把同一条波形的邻近窗口分到两边。归一化也要在划分之后做而且只能用训练集的统计量。错误的做法是对全部数据做StandardScaler再用它 transform 测试集因为测试集的均值方差已经泄露给了模型。正确做法是from sklearn.preprocessing import StandardScaler # 假设x_train是训练集窗口shape: (n_train, win_len) scaler StandardScaler() x_train_scaled scaler.fit_transform(x_train.reshape(-1, 1)).reshape(x_train.shape) # 测试集用同一个scaler不重新fit x_test_scaled scaler.transform(x_test.reshape(-1, 1)).reshape(x_test.shape)参数说明fit_transform只在训练集上调用一次transform在测试集上只做转换。如果你观察原始信号幅值不大也可以不做归一化直接喂给卷积网络但归一化之后训练会稳定很多尤其是当你使用的激活函数是 Sigmoid 或 Tanh 时。ReLU 对归一化没那么敏感但仍然建议做因为后续优化器的学习率设置会简单很多。3. 模型选型与构建一维 CNN 是轴承故障诊断的性价比之选3.1 为什么不用图像识别那套二维化有信息损失很多毕业设计会把振动信号转成频谱图或时频图然后用 ResNet 这类图像分类模型去分类。这个方法不是不行但它绕了一个弯。原始振动信号本身就是时间序列一维卷积天然适合提取局部冲击特征。如果先把信号做短时傅里叶变换变成二维图像再把图片输入 ResNet中间的时频分辨率选择、颜色映射、图片缩放都会引入人为干扰而且计算量翻了不止一倍。跑过项目的人大多会先试一维模型。一维 CNN 输入是(batch_size, 1, win_len)输出是类别概率整个过程直观可控。对于滚动轴承故障故障点产生的冲击会在振动波形上形成周期性的衰减振荡一维卷积核恰好能捕捉到这种局部波形模式而不需要额外做特征工程。相比之下二维模型更适合在空间结构明确的数据上工作比如触摸屏图片、X光片而不是一维波形。所以项目里如果有Conv1d而不是Conv2d说明作者选型是合理的。如果你想超越 baseline可以在保持一维结构不变的前提下增加注意力机制或循环单元而不是换成图像模型。3.2 一维 CNN baseline 的 PyTorch 实现下面给出一段可以直接训练的基础一维 CNN 代码结构简单适合作为第一个可跑通的版本import torch import torch.nn as nn class FaultCNN(nn.Module): def __init__(self, num_classes4, win_len4096): super().__init__() self.features nn.Sequential( nn.Conv1d(1, 16, kernel_size15, stride2, padding7), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2), # 长度减半 nn.Conv1d(16, 32, kernel_size7, stride2, padding3), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2), nn.Conv1d(32, 64, kernel_size7, stride2, padding3), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool1d(1), # 全局平均池化得到 [batch, 64] ) self.classifier nn.Linear(64, num_classes) def forward(self, x): # x: [batch, win_len] or [batch, 1, win_len] if x.dim() 2: x x.unsqueeze(1) x self.features(x) # [batch, 64, 1] x x.squeeze(-1) # [batch, 64] out self.classifier(x) return out逻辑说明第一层卷积核大小设为 15是考虑到振动冲击在波形上通常占据十几个采样点大卷积核能更好地覆盖一个冲击响应。步长 2 配合 padding 7 保持输入输出长度近似不变再用 MaxPool 减半。第二层第三层逐步增大通道数把感受野扩展同时降低序列长度。最后用全局平均池化把每个通道压缩成一个值输入全连接层。参数说明num_classes是你的故障类别数先别追求多任务能四类分对就成功了一半。win_len要与你的滑窗长度一致。kernel_size如果太大模型参数会膨胀太小又抓不住冲击一般第一层取 7~20后面层取 5~9。BatchNorm1d对于加速收敛很有帮助尤其是输入信号幅值分布不稳定的时候。如果你把这份代码跑在 CPU 上一个 epoch 也不慢因为 4096 点的窗口经过三次池化后长度已经很小计算量不大。这也是这个方向适合毕业设计的原因——不需要很高的硬件门槛。3.3 加一个 LSTM 分支能提升多少纯 CNN 的缺陷是只关注局部感受野内的模式而轴承故障信号中的故障冲击是有周期性重复的。CNN 靠堆叠层数也能学到长距离依赖但效率不高。常见做法是加入一个双向 LSTM 分支把 CNN 提取的局部特征序列送入 LSTM捕捉冲击的周期性。import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, num_classes4, win_len4096): super().__init__() self.cnn nn.Sequential( nn.Conv1d(1, 16, 15, stride2, padding7), nn.ReLU(), nn.MaxPool1d(2), # 长度 /2 nn.Conv1d(16, 32, 7, stride2, padding3), nn.ReLU(), nn.MaxPool1d(2), # 长度 /2 ) self.lstm nn.LSTM(input_size32, hidden_size64, num_layers1, batch_firstTrue, bidirectionalTrue) self.dropout nn.Dropout(0.3) self.classifier nn.Linear(128, num_classes) # 双向拼接 def forward(self, x): if x.dim() 2: x x.unsqueeze(1) x self.cnn(x) # [batch, 32, L] x x.transpose(1, 2) # [batch, L, 32] 进LSTM out, _ self.lstm(x) # [batch, L, 128] out out.mean(dim1) # 对时间维取平均 out self.dropout(out) return self.classifier(out)逻辑说明transpose把通道维变成 LSTM 的时间步维每个时间步的特征是 CNN 输出的 32 维通道。双向 LSTM 编码过去和未来的信息最后对时间步求平均得到一个 128 维的向量表示。这里没有用最后一个时间步的输出因为故障特征可能出现在任意时间段平均池化能更稳定地汇总。参数说明bidirectionalTrue会带来两倍参数量隐藏单元设 64 是折中。dropout0.3是为了防止 LSTM 过拟合因为 LSTM 的参数量通常不小。如果你发现 LSTM 训练明显慢于 CNN可以考虑减少 hidden_size 或去掉双向改成单向即可。根据我在类似项目里的经验CNN-LSTM 在准确率上比纯 CNN 一般能提升 1~3 个百分点但训练时间会增加一倍。如果你的 baseline 已经到 95%再为了那 1 个百分点搭上额外算力不值当但如果你的任务有 8 类甚至 12 类LSTM 的时序建模能力会更有优势。4. 训练与评估从损失曲线到混淆矩阵逐步打通全流程4.1 训练脚本数据加载器、优化器、损失函数与早停有了模型和定型的数据集下一步就是把训练脚本写完整。我通常把训练过程封装成一个函数支持传入模型、数据加载器、优化器和早停参数。import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset import numpy as np def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total 0, 0, 0 for x_batch, y_batch in loader: x_batch, y_batch x_batch.to(device), y_batch.to(device) optimizer.zero_grad() out model(x_batch) loss criterion(out, y_batch) loss.backward() optimizer.step() total_loss loss.item() * x_batch.size(0) pred out.argmax(dim1) correct (pred y_batch).sum().item() total y_batch.size(0) return total_loss / total, correct / total逻辑说明TensorDataset和DataLoader配合是最省事的做法先把 NumPy 数组转成张量再封装成数据集。这里的criterion是交叉熵损失nn.CrossEntropyLoss()它内部已经做了 Softmax所以模型最后一层不需要额外加 Softmax。训练循环里optimizer.zero_grad()必须在每一步清空梯度不然梯度会累加。参数说明device建议用torch.device(cuda if torch.cuda.is_available() else cpu)不要手动写死。数据加载器里有两个关键参数batch_size和shuffle。batch_size常见取 32、64、128取决于显存。shuffleTrue只在训练时用验证和测试必须shuffleFalse否则指标波动会显得很随机。早停是一种很有效的防过拟合策略。训练过程中监控验证集的 loss如果连续多个 epoch 验证 loss 不下降甚至上升就提前终止并把最优模型权重存下来。best_loss float(inf) patience, wait 15, 0 for epoch in range(1, 201): train_loss, train_acc train_one_epoch(model, train_loader, optimizer, criterion, device) val_loss, val_acc evaluate(model, val_loader, criterion, device) if val_loss best_loss: best_loss val_loss torch.save(model.state_dict(), best_model.pth) wait 0 else: wait 1 if wait patience: print(f早停于 epoch {epoch}) break逻辑说明patience是容忍连续多少个 epoch 没有改进。这里设 15适合类别多、训练波动大的场景。如果设太小比如 5可能会在损失探底之前就被打断。保存模型时只保存state_dict不保存整个模型对象这样加载的时候更灵活也不容易踩 PyTorch 版本兼容的坑。4.2 评估脚本准确率、F1、混淆矩阵别只盯着 loss训练期间的 loss 是模型内部优化的信号但最终报告里老师或评审更看重的是测试集上的准确率、F1-score 和混淆矩阵。尤其是不平衡数据下准确率会被多数类主导F1 才能反映少数类的表现。from sklearn.metrics import confusion_matrix, f1_score, classification_report def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0, 0, 0 all_preds, all_labels [], [] with torch.no_grad(): for x_batch, y_batch in loader: x_batch, y_batch x_batch.to(device), y_batch.to(device) out model(x_batch) loss criterion(out, y_batch) total_loss loss.item() * x_batch.size(0) pred out.argmax(dim1) all_preds.extend(pred.cpu().numpy()) all_labels.extend(y_batch.cpu().numpy()) correct (pred y_batch).sum().item() total y_batch.size(0) acc correct / total avg_loss total_loss / total f1 f1_score(all_labels, all_preds, averagemacro) cm confusion_matrix(all_labels, all_preds) return avg_loss, acc, f1, cm逻辑说明torch.no_grad()告诉 PyTorch 不用计算梯度评估阶段大幅减少内存占用。all_preds和all_labels要转成 NumPy 数组再传给 sklearn不然 Python 列表在分类数量很多时性能会很差。f1_score的averagemacro让每个类别权重相等忽略类别样本量适合找出哪个类别最弱。参数说明f1_score的average参数有三种常用取值macro适合类别不平衡时公平评估weighted会按样本量加权更贴近整体表现binary只用于二分类。故障诊断通常有 4 类以上所以用macro更合理。混淆矩阵的行是真实类别列是预测类别如果哪一行除对角线外还有值说明那一类容易和另一类混淆通常物理上是外圈与滚动体故障特征频率相近导致的。4.3 训练日志与模型保存让每次实验可复现训练日志写得好不好直接影响你调参的效率。我习惯在每个 epoch 打印三行核心信息epoch 数、训练 loss、验证准确率。同时把数据记录到 CSV方便画曲线。import csv log_path train_log.csv with open(log_path, w, newline) as f: writer csv.writer(f) writer.writerow([epoch, train_loss, train_acc, val_loss, val_acc]) for epoch in range(1, 201): train_loss, train_acc train_one_epoch(...) val_loss, val_acc, _, _ evaluate(...) writer.writerow([epoch, train_loss, train_acc, val_loss, val_acc])逻辑说明把日志落盘比仅打印在控制台强太多。一旦你中途想横向对比不同学习率或不同窗口长度的实验CSV 可以直接读入 pandas 画图。避免每次重新跑一遍找结果。模型保存有两点要注意。第一保存的时候带上验证集性能指标比如best_model_acc_0.954.pth这样下次打开文件夹就能知道哪个模型最好。第二如果用了自定义网络结构加载模型时必须先实例化一个结构相同的模型再用load_state_dict加载权重。model FaultCNN(num_classes4, win_len4096) model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval()参数说明map_locationcpu是在服务器上训练的模型要拿到本地 CPU 推理时用的。如果不加PyTorch 默认要找到原来的 GPU没有 CUDA 就会报错。5. 避坑手册滚动轴承故障诊断最容易翻车的五个地方5.1 现象测试准确率接近 100%但新数据一测就崩——样本泄露这是我最常看到的翻车现场。某同学交来的报告里训练准确率 99.2%测试准确率 98.7%看得人很兴奋。结果把训练好的模型拿去识别另一台实验台上采集的数据准确率直接掉到 70%。合着之前 98% 都是因为训练集和测试集里包含同一条原始波形的窗口。原因滑窗时把同一段原始信号的所有窗口全部汇入一个大池子然后随机打乱并划分训练集和测试集。模型很可能学到了测试集窗口的细微噪声模式而不是轴承故障本质特征。解决调整划分策略保证同一条原始信号段的所有窗口只出现在一个集合里。代码层面把每段信号加个 ID按 ID 划分。具体做法是先用group_id数组记录每个窗口属于哪个段然后对 ID 做train_test_split再根据 ID 映射回窗口。如果原数据本身每条记录只有几分钟可以把前 60% 时间段作为训练后 40% 时间段作为测试这也是一种不泄露的划分。5.2 现象十类故障里有一类准确率极低——样本不平衡与加权损失项目里如果有 10 个类别你会发现有的类别训练样本就只有两三百个而正常样本有五六千。模型为了降低整体 loss 会把多数类学得很好少数类则几乎不学最后准确率 90%但看一下混淆矩阵滚动体故障那一类是 0%。原因交叉熵损失对所有样本一视同仁多数类的梯度淹没了少数类。解决最直接的做法是修改损失函数权重。PyTorch 里CrossEntropyLoss自带weight参数权重按每类样本数的倒数来算from sklearn.utils.class_weight import compute_class_weight import torch class_weights compute_class_weight(class_weightbalanced, classesnp.unique(y_train), yy_train) class_weights torch.tensor(class_weights, dtypetorch.float32).to(device) criterion nn.CrossEntropyLoss(weightclass_weights)逻辑说明compute_class_weight会自动把样本少的类别权重调高样本多的类别权重调低。这样少数类每次更新时梯度占比更大。这比手动复制少数类样本SMOTE 在时序里效果不稳定要稳定得多。参数说明如果用了加权损失学习率可能需要稍微调低因为权重变大等效于放大了梯度学习率不变容易导致训练震荡。5.3 现象训练波动巨大loss 曲线像锯齿——学习率与归一化问题训练时打印 loss发现每个 epoch 之间波动剧烈前一个 epoch 是 0.8后一个直接 2.3再过两个 epoch 又掉回 0.5。这种情况下模型很难收敛到稳定点。原因最常见的是学习率偏大加上输入信号没有归一化导致梯度在各 batch 间变化范围很大。另一种可能是在 CNN 里用了BatchNorm但 batch size 太小比如 4BatchNorm 统计不稳定。解决先把学习率降到 1e-3 或 1e-4观察十个 epoch。如果波动还大检查输入数据是否做了标准化。另外把 batch size 提高到 32 以上。如果你已经用了 Adam确认没有同时手动设置momentum那是 SGD 的参数Adam 不吃这一套。我习惯在训练开始时先用一个小数据集跑 5 个 epoch如果 loss 能稳定下降再换全量训练。这样能快速把「模型实现 bug」和「超参数问题」分开。5.4 现象验证集上很好换台机器就不行——设备与转速耦合很多采集数据是在同一台实验台上、同一组转速和负载下完成的。模型学到的是「这个转速下的振动模式」而不是「这个故障类型的特征」。换一台转速不同的设备准确率下降是非常自然的。原因故障特征频率与转速强相关不同类别的区分度可能一部分来自转速信息。模型无法区分是「轴承坏了」还是「转速变了」。解决如果你的毕业设计要求泛化性建议在数据预处理阶段做包络谱或阶比分析把时域信号转成与转速无关的阶比域。更简单的实验方案是训练时把不同转速的数据混合让模型同时见到多种转速。如果你手里的数据集只有单一转速那就不要强调「跨设备通用」在论文里把实验条件写清楚这本身也是一种严谨。作为工程兜底可以在推理前先做一次快速转速估计然后选择对应的模型而不是指望一个模型吃遍天。5.5 现象复现别人结果对不上——随机种子、数据版本与平台差异拿着同一个压缩包里的源码按同样的参数跑结果和 README 里的测试准确率差了五六个百分点。这不一定说明你操作错了多数是随机性在捣乱。原因PyTorch 默认使用随机初始化如果代码没有固定随机种子每次训练结果都不同。数据预处理时如果用train_test_split且初始随机种子不同划分结果也会不同直接影响最终指标。解决在训练脚本开头固定随机种子import random, numpy as np, torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)逻辑说明这行代码放在import torch之后、任何模型和数据加载之前。torch.manual_seed同时影响 CPU 和当前 GPU 的随机数生成器。np.random.seed是为了让数据预处理里的随机操作可复现。如果代码里还有DataLoader的worker最好在加载器里设generator否则多进程数据加载依然可能不完全可复现。参数说明不同 PyTorch 版本甚至同一版本在不同 GPU 芯片上的卷积实现略有差异微小精度差异是正常的。你只需要保证实验组之间使用相同环境就能对比出谁的因素影响结果。6. 进阶把诊断结果变成可验证的故障特征频率6.1 用特征图可视化模型关注的时间片段分类准确率达到 95% 之后评审可能会问一句你的模型凭什么判断这是内圈故障很多源码到此就打住但我建议多做一步提取模型最后一层卷积层的输出看看模型对哪段波形响应最强烈。def extract_attention(model, x, layer_namefeatures): activations {} def hook_fn(module, input, output): activations[layer_name] output.detach() hook getattr(model, layer_name).register_forward_hook(hook_fn) with torch.no_grad(): out model(x.unsqueeze(0).to(device)) hook.remove() act activations[layer_name].squeeze(0) # [C, L] # 对所有通道取平均得到每个时间位置的激活强度 act_mean act.mean(dim0).cpu().numpy() return act_mean逻辑说明register_forward_hook是 PyTorch 提供的钩子函数在前向传播经过指定模块时触发能拿到该模块的输出。这里拿到的是最后一层卷积层的特征图时间维长度已经比原始信号短了很多但依然能反应到哪个区间有强响应。把act_mean和原始波形对应画在同一张图里你通常会看到激活峰值正好出现在冲击明显的时刻这就给模型提供了直观的证据。参数说明如果想做更细腻的可视化可以把梯度也钩下来做 Grad-CAM。不过对一维信号简单的平均激活图已经足够有说服力也容易向非机器学习背景的老师解释。6.2 计算故障特征频率做硬校验深度学习模型是黑匣子但轴承故障诊断本身有一套经典理论就是特征频率计算公式。你可以用这套公式反过来验证模型做出的分类是否物理合理。常见滚动轴承有内圈故障频率 BPI、外圈故障频率 BPO、滚动体故障频率 BSF它们的近似公式假设外圈固定外圈故障频率BPO n/2 * fr * (1 - d/D * cos α)内圈故障频率BPI n/2 * fr * (1 d/D * cos α)滚动体故障频率BSF D/d * fr * (1 - (d/D * cos α)^2)其中n是滚动体数量fr是转频d是滚动体直径D是节圆直径α是接触角。你可以写一个简易计算器def calc_characteristic_freq(n, fr, d, D, alpha_deg): alpha np.radians(alpha_deg) cos np.cos(alpha) bpo n / 2 * fr * (1 - d / D * cos) bpi n / 2 * fr * (1 d / D * cos) bsf D / d * fr * (1 - (d / D * cos) ** 2) return {bpo: bpo, bpi: bpi, bsf: bsf} print(calc_characteristic_freq(n9, fr29.8, d7.94, D39.04, alpha_deg0))逻辑说明输出的这些频率值会在振动信号的包络谱上出现明显的峰值。当你用模型跑出一个「内圈故障」的预测结果后可以再计算内圈故障频率 BPI然后在测试样本的包络谱上找对应峰值。如果模型分类为内圈故障但在 BPI 位置没有峰值说明模型可能学错了特征。这种做法能把深度学习输出和传统信号处理结合起来在毕业设计的答辩里是非常好的加分项。参数说明具体轴承的n, d, D, α需要查看数据集的实验台说明文件不同轴承差别很大。如果你的压缩包里没有参数文件可以先根据模型给出的类别反向去寻找对应频率用峰值搜索法确认而不是硬套公式。做这一步的意义不只是好看它能帮你判断当前模型是否真的可用。如果模型在大多数测试样本上都能和特征频率对上号说明它学到了物理上正确的模式如果只是耦合了噪声分布那你最终交付的项目就会在换数据时崩掉。我现在的习惯是每跑完一批测试就抽样三五个样本做频率校验这比盲目调参更让人安心。希望这个习惯对你有用希望这份笔记帮你在做类似项目时少踩几个坑。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑