简介面向深度学习故障诊断入门学习者这份资源完整演示了从数据预处理、模型搭建到模型训练的全流程可帮助快速掌握基于卷积神经网络等模型的故障识别方法。压缩包共57个文件其中40个mat格式数据文件用于实验输入10个png图片展示可视化结果3个Python脚本对应核心训练流程另有2个h5模型文件和2个markdown说明文档整体约46.13MB目录结构清晰便于按步骤复现。目前已有906人学习下载内容包含数据清洗、标准化、特征工程、训练集划分等预处理操作以及CNN模型构建、训练策略和评估优化要点适合想要系统入门深度学习故障诊断的工程师与学生。1. 基于深度学习的故障诊断入门示例到底在解决什么问题振动数据摆在那里波形从屏幕上看都差不多某些早期故障只在频谱上冒出一个很窄的毛刺。人眼容易漏过去但一个训练好的一维卷积网络能稳定地把几种状态分开。这就是基于深度学习的故障诊断入门示例的价值不依赖老师傅拍脑袋定阈值而是让网络直接从信号里把“长什么样”学出来。这篇文章以滚动轴承振动信号为主线把数据预处理、模型搭建、模型训练三个环节完整跑通覆盖滑窗切分、标准化、Conv1D 结构、训练与早停最后给出五类常见坑。适合想快速验证深度学习方法在故障诊断里是否适用的工程师也适合正在做相关课题、需要一份可复现流程的学生。2. 数据预处理滑动窗口、标准化与数据集划分的落地细节2.1 先搞清楚数据形态与任务定义故障诊断的输入通常是一段一维振动时序信号采样率常见的从 12 kHz 到 51.2 kHz 不等。每个文件里可能是一整段连续采集也可能按工况拆成了多个片段。拿到数据后第一件事不是写网络而是确认三件事标签有几类、每类有没有足够的样本、数据是从哪些工况下采的。常见做法是把问题定义成多分类正常状态一类内圈故障、外圈故障、滚动体故障各一类。类别数直接决定输出层神经元个数。如果手里只有正常和异常两类那就先做二分类把模型跑通了再扩展。这个阶段最重要的工作是画数据分布图把每类的时域波形画出来看一眼确认信号里没有明显的截断、直流偏置或传感器饱和。2.2 滑动窗口切分窗长、步长与重叠率怎么定深度学习网络要求输入是固定长度而原始振动文件长短不一所以必须先把连续信号切成等长片段。这里用到滑动窗口设定一个窗长比如 128 或 1024然后沿着时间轴以固定步长滑动裁切。窗长取多少没有绝对标准但有一个经验参照窗长至少要覆盖几个完整的旋转周期让网络能看到一个周期内的冲击特征步长则控制样本数量和相邻样本的重叠程度。import numpy as np def segment_signal(signal, window_size128, stride64, label0): segments [] for start in range(0, len(signal) - window_size, stride): segment signal[start:start window_size] segments.append(segment) segments np.array(segments) labels np.full(segments.shape[0], label, dtypenp.int64) return segments, labels这段代码把一段一维信号切成若干个长度为window_size的片段并为每个片段打上同一个标签。stride小于window_size时相邻片段有重叠重叠率越高样本量越大但信息冗余也越高。我一般先设window_size128, stride64让重叠率 50%。如果模型表现不好再尝试window_size1024这类更长的窗因为长窗包含的频谱分辨率更高对低转速下的微弱故障更友好。窗长太短时一个窗口里可能只有不到一个旋转周期故障冲击特征不完整窗长太长时瞬态冲击会被大量平稳信号稀释而且样本总数变少。切分完了以后记得打印一下切出来的样本形状确认是(样本数, 窗长)而不是(窗长, 样本数)。2.3 标准化为什么不能用全数据集去 fit振动信号的幅值受传感器灵敏度、安装位置和负载影响很大直接扔进网络会出现训练不稳定的问题。常见做法是 Z-Score 标准化做一次“减均值、除以标准差”把信号拉到零均值、单位方差附近。不要用 Min-Max 归一化因为故障冲击的尖峰值会被压缩反而把特征抹掉了。from sklearn.preprocessing import StandardScaler scaler StandardScaler() scaler.fit(train_2d) # train_2d 形状 (N_samples, window_size) train_norm scaler.transform(train_2d) test_norm scaler.transform(test_2d)这里的StandardScaler只对训练集fit然后用同一套均值和方法去transform测试集。这一点非常关键如果在全量数据上fit验证阶段的信息已经泄漏到了训练过程里后面的评估指标会虚高现场部署时马上翻车。转换完之后把数据 reshape 成深度学习需要的形状train_input train_norm.reshape(-1, 1, window_size).astype(np.float32) test_input test_norm.reshape(-1, 1, window_size).astype(np.float32)reshape的目标形状里-1表示自动计算样本数1是通道数一维信号只有一个通道window_size是序列长度。PyTorch 的Conv1d接受的三维输入就是(batch, channels, length)这一步不做后面模型会直接报形状错误。2.4 数据集划分按样本随机切还是按工况切这一步决定了你的模型是“真会诊断”还是“背答案”。如果直接把切好的所有样本随机train_test_split同一段连续信号的前半段进了训练集、后半段进了验证集二者高度相似验证准确率会高得离谱。更接近真实场景的做法是按数据来源划分把不同负载、不同转速、不同设备编号作为划分单元保证验证集在时间上完全晚于训练集或者干脆是一个没参与训练的设备。devices sorted(set(sample_ids)) train_ids devices[:6] val_ids devices[6:8] test_ids devices[8:] train_mask sample_ids.isin(train_ids) val_mask sample_ids.isin(val_ids) test_mask sample_ids.isin(test_ids)代码里的sample_ids是每条样本对应的设备号或工况号先按编号分层再把完整的设备分给训练、验证和测试。这样得出的准确率才有参考价值。入门项目里最常见的问题就是在这里图省事后来实际部署发现模型根本不鲁棒。3. 模型搭建从一维卷积结构到损失函数定义3.1 为什么入门用一维 CNN而不是 LSTM 或 Transformer很多新手上来就纠结选什么网络其实对故障诊断来说一维卷积网络是最稳妥的入门选择。原因很直接振动故障特征大多是局部冲击形态卷积核天然适合提取局部模式而且 CNN 的平移不变性让它对故障发生时刻不敏感这正好符合“故障随时可能出现”的现实。LSTM 也能做序列建模但训练更慢、对序列长度更敏感Transformer 在小样本场景下容易过拟合。模型训练速度小样本表现参数规模入门友好度1D CNN快好小高LSTM慢中中低Transformer慢容易过拟合大低如果数据量只有几千条样本CNN 是最不容易出问题的。等到后面数据量大了、任务变复杂了再往上叠加注意力机制也不迟。3.2 搭建一个可跑的 Conv1D 网络层结构、核尺寸与输出形状下面这个网络结构是我在诊断 Demo 里最常用的三个卷积块每个块由卷积、批归一化、ReLU 和池化组成最后接一个自适应平均池化和全连接分类头。输入信号长度 128通道数 1。import torch.nn as nn class FaultCNN(nn.Module): def __init__(self, num_classes4): super().__init__() self.features nn.Sequential( nn.Conv1d(1, 16, kernel_size3, padding1), # 输入 1 通道输出 16 通道 nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), # 长度 128 - 64 nn.Conv1d(16, 32, kernel_size3, padding1), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), # 长度 64 - 32 nn.Conv1d(32, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool1d(1), # 池化到长度 1得到 64 维特征 ) self.classifier nn.Linear(64, num_classes) def forward(self, x): x self.features(x) x x.flatten(1) return self.classifier(x)代码里每个Conv1d的kernel_size3表示卷核长度为 3这是捕捉相邻几个采样点关系的常见选择和二维图像里的 3×3 卷积思想一致。padding1保证卷积不缩短序列长度。BatchNorm1d作用是让每层输入分布更稳定对振动信号尤其有效因为不同样本的幅值差异可能很大。AdaptiveAvgPool1d(1)是关键设计不管前面卷积后长度是 32、16 还是其他值池化后都变成 1这层之后就是一个固定维度的特征向量。这样的好处是网络对输入长度不敏感以后换窗长不用改网络结构。全连接层从这里接出num_classes对应类别数。建完模型后建议先用随机数据跑一次前向传播确认形状无误dummy torch.randn(2, 1, 128) model FaultCNN(num_classes4) out model(dummy) print(out.shape) # 期望输出 (2, 4)这一步能快速验证模型结构有没有写错。如果输出形状不是(2, 4)问题多半出在卷积参数或池化层设置上早发现比训练时再排查节省时间。3.3 输出层、损失函数与类别不平衡处理故障诊断里最常见的场景是类别极不均衡正常样本占了九成故障样本只有零星几条。直接训练时模型会倾向于把所有样本都判成正常因为这样 loss 最小。针对这个问题第一个手段是在损失函数上做文章CrossEntropyLoss支持传入每个类别的权重给少样本的类别更大的权重让模型在反向传播时更关注这些类。class_weights torch.tensor([1.0, 2.0, 2.0, 3.0]) criterion nn.CrossEntropyLoss(weightclass_weights)权重大小按类别样本量的倒数来定比如正常类 1000 条、内圈故障 500 条、外圈故障 500 条、滚动体故障 200 条就把权重设为1.0, 2.0, 2.0, 5.0附近。实际操作中我会先把原始数据里的类别分布打出来再根据分布计算权重不要凭感觉填。另一个手段是在数据加载阶段做重采样。PyTorch 的WeightedRandomSampler会按样本权重有放回地抽取让每个 batch 里各类别占比均匀。两个手段不冲突可以同时用但先加损失函数权重就够了重采样等发现效果不够再加。4. 模型训练优化器配置、训练循环与收敛判断4.1 训练配置优化器、学习率与批量大小怎么设训练这件事第一步是选择合适的优化器。故障诊断任务我用的是 Adam 加权重衰减这是最快见效并且对超参数不敏感的组合。学习率从1e-3起步这是常见做法如果 loss 震荡明显降到1e-4如果下降太慢可以试着提到5e-3。批量大小取决于显存和数据量。振动信号输入比较小batch_size64通常没有问题如果样本总数只有几百条批量别超过 32否则一个 epoch 里梯度更新次数太少收敛不稳。数据加载时记得用DataLoader的shuffleTrue打乱顺序这能避免模型学到样本顺序上的假规律。超参数建议值备注优化器Adam自带动量估计收敛稳学习率1e-3 起震荡就调小weight_decay1e-4防止过拟合batch_size32~64看数据量epochs50~100配合早停4.2 训练循环与验证从零写一个训练函数模型定义好了接下来就是最核心的训练循环。这里不推荐直接用框架封装好的高阶接口入门阶段手动写循环能让你清楚地看到数据是怎么流进网络、梯度怎么更新的。下面是一个完整的训练函数框架import torch from torch.utils.data import TensorDataset, DataLoader # 构造 DataLoader train_loader DataLoader( TensorDataset( torch.tensor(train_input, dtypetorch.float32), torch.tensor(train_labels, dtypetorch.long) ), batch_size64, shuffleTrue ) model FaultCNN(num_classes4) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) epochs 60 for epoch in range(epochs): model.train() total_loss, correct, total 0.0, 0, 0 for xb, yb in train_loader: optimizer.zero_grad() y_pred model(xb) loss criterion(y_pred, yb) loss.backward() optimizer.step() total_loss loss.item() * xb.size(0) correct (y_pred.argmax(dim1) yb).sum().item() total yb.size(0) train_loss total_loss / total train_acc correct / total print(fepoch {epoch1} | train loss {train_loss:.4f} | train acc {train_acc:.4f})这段代码的流程是每次取一个 batch 的输入xb和标签yb前向传播得到y_pred计算 lossloss.backward()算梯度optimizer.step()更新权重。zero_grad()必须放在每一步之前否则 PyTorch 会把梯度累加起来导致结果不可控。y_pred.argmax(dim1)表示取每个样本预测概率最大的类别的索引再和真实标签比较。训练模式model.train()要记得开因为BatchNorm1d在训练和推理时行为不同忘了切模式会出现验证集结果忽高忽低的情况。4.3 早停与模型保存让训练自动停在最好的地方故障诊断训练里最常见的现象是验证准确率到某个点就不再提升甚至开始下降这就是过拟合的信号。手动盯着训练曲线去中断不现实我的习惯是写一个简单的早停机制监控验证 loss连续几个 epoch 没有改善就停止训练并保存最佳模型权重。class EarlyStopping: def __init__(self, patience5, min_delta1e-4): self.patience patience self.min_delta min_delta self.counter 0 self.best_score None self.save_path None def check(self, val_loss, model, save_path): if self.best_score is None: self.best_score val_loss self.save_path save_path torch.save(model.state_dict(), save_path) return False if val_loss self.best_score - self.min_delta: self.best_score val_loss self.counter 0 torch.save(model.state_dict(), save_path) else: self.counter 1 return self.counter self.patiencepatience是容忍几个 epoch 不下降一般设 5 到 8。min_delta是下降阈值验证 loss 只有在比历史最好值低超过min_delta时才认为有改善这避免了噪音引起的误判。每次有改善就保存一次权重训练结束加载最后保存的权重得到的就是整个训练过程中验证集上表现最好的模型。保存路径不要带中文字符避免某些环境下读写失败。推理时加载model.load_state_dict(torch.load(best_fault_model.pt)) model.eval()加载模型后记得切到eval()模式同时把torch.no_grad()包在外面既可以省显存又不会影响梯度相关计算。4.4 训练曲线的读法欠拟合、过拟合与玄学区训练完成后把每个 epoch 的 loss 和准确率画出来能直接判断训练状态。如果训练 loss 和验证 loss 下降都慢说明模型欠拟合常见对策是增大网络容量把卷积核通道数翻倍、增大窗长、减少正则化。如果训练 loss 持续下降但验证 loss 开始回升这就是过拟合早停机制应该已经帮你停住了。还有一种情况很多人容易误判训练 loss 下降到很低、验证准确率也高但模型是“记住”了噪声而不是学到故障特征。这个后面第 5 章展开说。所谓“玄学区”是指最后几个 epoch 里 loss 在极小范围内波动准确率却纹丝不动这是合理的不要为了追求更低的 loss 反复调参先看验证集的表现再动手。5. 易踩坑排查故障诊断训练中最常见的五类问题5.1 验证集准确率高新数据集上一测就掉这是故障诊断入门时最容易翻车的坑。现象训练完发现验证集准确率 98%信心满满地把模型拿到现场或新采集的数据上一跑准确率掉到 70% 甚至更低。原因多半是数据划分阶段“随机切分”造成的同一条连续振动信号前 70% 进了训练集、后 30% 进了验证集相邻波形几乎一模一样模型相当于开卷考试。解决方法是严格按照时间或者设备维度划分数据保证验证集的数据分布和训练集有真实差异。如果条件允许用滑窗时把重叠率降低进一步减小泄漏。5.2 训练 loss 一直在高位徘徊怎么调都下不去现象loss 从第一个 epoch 开始就在 1.0 附近来回晃训练了二十个 epoch 也没有明显下降趋势。原因通常有几个方向学习率太大导致梯度震荡数据没有标准化导致不同样本幅值差异过大或者标签与样本错位。解决步骤是先把学习率降到1e-3甚至3e-4再确认输入数据已经做过 Z-Score 标准化之后在数据加载代码里打印一批(xb, yb)看看标签是不是对应正确这一步傻但有效我见过不少“训练不动”其实是数据组装时装错顺序。5.3 正常类准确率很高故障类准确率几乎为零现象混淆矩阵里正常类别那一行数值很高故障类别基本全被预测成正常。原因很直接样本不平衡正常样本占绝大多数交叉熵被多数类别主导模型不需要认真学故障特征也能拿到很低的总 loss。解决方法是给CrossEntropyLoss传入类别权重让少数类的错误在 loss 里占更大比重如果还是不行再上WeightedRandomSampler。改完之后要重点看各类别的召回率而不是总准确率。5.4 换了一个负载或转速模型立刻失灵现象训练数据里全是一种负载条件测试时换另一种负载准确率从 95% 掉到 50%。原因模型学到的是幅值和背景噪声层面的表面关联而不是真正的故障形态。解决思路是训练数据里混入多个工况并在推理时不做任何针对某个具体工况的特化处理。如果数据采集成本高可以先做数据增强——对原始信号加不同强度的高斯噪声、随机缩放幅值、时域平移让网络见过更多变体。5.5 训练结果不稳定跑两次差异很大现象同一份代码、同一个参数两次训练出来的准确率相差好几个点。原因可能集中在随机初始化、数据加载顺序和 GPU 底层的非确定性运算。解决方法是固定随机种子import random import numpy as np import torch random.seed(0) np.random.seed(0) torch.manual_seed(0) torch.cuda.manual_seed_all(0)这三行放在数据切分之前执行能保证大部分场景下结果可复现。如果对稳定性要求特别高可以在训练前固定卷积层初始化方式但一般入门阶段固定随机种子就够了。6. 进阶验证方向从诊断 Demo 到能用6.1 用混淆矩阵和图验证模型学到了什么训练结束后不要只看准确率。我习惯是保存各批次的特征向量用 t-SNE 做降维可视化观察不同故障类别在特征空间里是否分得开。如果 t-SNE 图上类别边界模糊说明模型没抓住本质特征如果类别分离明显说明特征提取是有效的。这一步能帮你在调参时少走很多弯路。混淆矩阵也要每次训练后都看它能暴露哪些类别之间容易互相误判比如内圈故障和外圈故障总是搞混说明这两类的频谱形态本身就很接近。6.2 推理阶段的置信度阈值别让 softmax 输出骗了你模型的softmax输出是一个概率分布但它的绝对值不代表真实的置信度。一个在训练集没见过的异常形态可能也会被模型以 0.9 的置信度分成某一类。我给这类情况设置了置信度阈值低于阈值的样本判定为“不确定”留给人工进一步确认prob torch.softmax(model(xb), dim1) max_prob, pred prob.max(dim1) if max_prob.item() 0.6: label_out -1 # 表示不确定阈值怎么选先在验证集上统计正常样本和各类故障样本的max_prob分布取能够覆盖 95% 正常样本的值。这样模型在遇到训练分布以外的数据时至少不会盲报现场的可信度会大幅提升。6.3 换设备、换传感器之后的迁移微调思路故障诊断模型换到另一台设备上通常直接掉点这时候不要重新训练整个网络。我一般会保留已经学到的卷积层权重只重新训练最后的全连接层学习率调小到1e-4用新设备的一小部分数据做微调。这样既保留了原有故障特征提取能力又能适配新的数据分布需要的数据量远小于从头训练。这些年做过不少类似项目在“入门示例”和“生产可用”之间隔着的从来不是模型大小而是数据划分的意识、置信度的把握和参数细节的积累。我现在的习惯是新项目第一周只处理数据、画图、检查分布不碰模型。这个习惯帮我在后面省下了大量返工时间希望帮到你。本文还有配套的精品资源点击获取