
1. 项目整体思路与方案选型1.1 为什么选CNN而不是传统方法先说说这个项目是怎么来的。做设备健康管理的人应该都有体会轴承作为旋转机械里最容易出问题的部件之一一旦发生故障轻则停机检修重则整条产线跟着停摆。以前我们做轴承故障诊断靠的是振动信号里的特征提取加分类器典型流程是算时域特征均值、方差、峭度、峰值因子这些或频域特征重心频率、频谱幅值、边频带能量然后丢给SVM、随机森林或者BP神经网络去分类。这套老路线的核心痛点在于特征工程。时域和频域特征怎么组合、哪些指标对早期故障更敏感全靠个人经验反复试。同一个数据集换个人来做特征选得不一样识别效果能差出一大截。而且一旦工况变化转速不同、负载不同之前调好的特征组合很快就失效鲁棒性很差。CNN方案天然绕开了手工特征这个瓶颈。卷积层通过局部感受野和权值共享在训练过程中自动从原始信号里学习不同尺度的判别特征。早期卷积核学的是幅值、周期这类低层特征深层卷积核学的是冲击成分的位置、调制边带的结构化特征——整个过程不需要人为指定特征规则。这个项目我最终选了基于CNN的轴承故障诊断主要看中三点端到端学习输入可以是原始振动波形跳过了大量特征工程环节特征自适应性好训练数据覆盖不同工况时模型有能力学到更泛化的表达推理速度快训练好的模型在普通CPU上单条样本识别时间在毫秒级满足在线监测的要求。当然CNN不是万能的后面会提到它对数据划分、训练策略很敏感这恰恰是项目里最容易掉坑的地方。1.2 整体技术路线怎么搭这个项目的目标很直接输入一段轴承振动信号模型输出对应的故障类型正常、内圈故障、外圈故障、滚动体故障。围绕这个目标我把整个流程拆成了下面几个环节环节具体工作输出数据准备下载公开数据集或采集振动数据确认采样率、转速、故障类型原始振动信号文件信号预处理滑动窗口分段、归一化、去均值/去趋势训练样本集数据集划分按记录划分训练/验证/测试集避免数据泄露三份相互独立的数据模型构建设计1D-CNN网络结构设定损失函数和优化器可训练的模型模型训练分batch训练监控loss和验证集准确率做早停和调参训练好的权重文件评估分析混淆矩阵、精确率/召回率/F1、t-SNE可视化性能报告部署验证用测试集模拟在线诊断流程诊断结论从流程上看并不复杂但每一步都有不少讲究。比如数据预处理里窗口长度取多少、重叠率多高直接决定了模型的输入尺寸和样本量数据集划分是按连续信号整体切还是按每段独立切决定了你的模型在真实场景下会不会“作弊”。2. 数据准备与预处理2.1 数据源选择与类别说明做轴承故障诊断项目数据是第一关。如果你有实验室条件可以在轴承实验台上人为加工故障电火花切割、激光蚀刻等采集不同转速、不同负载下的振动数据。但大部分做课题或者入门实践的人更快的路径是使用公开数据集。业内最常用的公开数据集之一是凯斯西储大学CWRU轴承数据中心的数据它提供了正常轴承、内圈故障、外圈故障、滚动体故障的振动信号采样频率有12kHz和48kHz两档电机负载从0到3马力hp分为四档故障直径包含0.007、0.014、0.021英寸三种尺寸。这些条件组合起来足够支撑一个像样的分类任务。我在项目里选了12kHz采样频率、0负载和2负载两种工况的数据故障直径取0.007英寸也就是早期轻微故障加上正常样本一共四类。原始数据是.mat格式用scipy库可以直接读取。注意CWRU数据里每个文件对应一条连续的振动信号记录长度从几万点到十几万点不等。直接拿整段信号训练肯定不行需要先做分段处理。02负载的数据我留了一部分做跨工况验证专门测试模型的泛化能力。这个设计在后面帮了大忙因为我发现很多在单一工况下表现很好的模型一换工况准确率就掉得厉害这也是工业现场最常见的痛点之一。2.2 信号分段、归一化与数据集划分原始振动信号是一长串时间序列必须切成短样本才能输入CNN。分段有两个参数窗口长度和重叠率。窗口长度我选了1024个采样点。为什么是1024因为采样率12kHz轴承转频通常在20Hz到50Hz之间一个1024点的窗口大致覆盖0.085秒大约包含2到4个旋转周期足够捕捉到故障冲击的周期性特征。窗口太短故障特征不完整窗口太长样本数变少训练效率下降。1024是权衡后的结果。重叠率选了50%。假设一段信号有10000个点窗口长度1024、步长512不考虑末尾补齐的话能切出约18个样本。如果不重叠步长就是1024样本数直接减半。重叠分段可以在有限数据里获得更多训练样本同时相邻样本之间保持一定相关性这对CNN训练是有帮助的。分段之后的归一化也很关键。振动信号幅值受传感器灵敏度、安装位置影响很大不同记录之间的幅值范围差异明显。我做了最简单的零均值归一化让每个样本的均值归零、标准差为1避免模型把幅值大小当成判别特征。import numpy as np from scipy.io import loadmat def segment_signal(data, window_size1024, stride512): samples [] n len(data) n_windows (n - window_size) // stride 1 for i in range(n_windows): start i * stride seg data[start:start window_size] samples.append(seg) return np.array(samples) # 读取CWRU数据示例 mat loadmat(12k_DR/0.007_InnerRace_2.mat) signal mat[X_0_DE_time].flatten() samples segment_signal(signal, 1024, 512) print(samples.shape) # 输出样本数量 # 归一化 samples (samples - samples.mean(axis1, keepdimsTrue)) / \ (samples.std(axis1, keepdimsTrue) 1e-8)数据集划分是这里最值得单独说的一个环节。我的做法是先把每条原始记录切分成样本再把样本按原始记录的来源分组划分。什么意思比如正常类有10条记录内圈故障有10条记录我先从每条记录里分别切出样本然后规定某几条记录的样本全部进训练集另几条记录的样本全部进测试集二者互不重叠。这样做的原因在于CNN的训练目标是学到“故障特征”而不是“记忆某条特定记录”。如果随便切分训练集和测试集中会混入同一条连续记录切出来的样本它们在时间上高度相关模型很可能记住了这条记录的背景噪声和传感器安装特性测试准确率虚高。到了真实场景面对新的振动记录表现就会垮掉。这个坑后面我会在常见问题里展开讲。2.3 一维原始信号还是时频图CNN处理轴承信号有两种常见输入形式一是把一维振动波形直接输入1D-CNN二是先把信号转成二维时频图比如短时傅里叶变换STFT谱图、小波时频图再用2D-CNN识别。两种方案各有适用场景。这个项目里我两条路线都做了对比。一维波形输入的优点是预处理链路短训练速度快数据增强方便加噪、幅值扰动、时间拉伸很适合做工程化落地。缺点是模型需要从原始波形里自己学出频域特征对网络深度和感受野设计要求相对高。时频图路线相当于把频域信息提前用算法“画”出来CNN只需要学图像模式。肉眼也确实能看出区别正常轴承的时频图能量分布均匀内圈故障在特定频带会出现明显的周期性冲击亮线。但代价是把信号转成图像的预处理开销更大而且图像尺寸会增加模型计算量。我在项目里的实测对比是在相同训练条件下用STFT频谱图输入2D-CNN测试准确率比直接输入一维波形高约0.8%~1.5%主要集中在轻微故障的识别上。但一维方案的训练速度大概快一倍模型参数量也更小。如果你做的是可穿戴设备或者边缘端推理一维波形方案性价比更高如果追求极致准确率且算力允许二维时频图值得一试。3. CNN模型设计与训练细节3.1 网络结构怎么设计才靠谱我的模型设计思路是先搭一个简单可用的基线再逐步改进而不是一上来就堆复杂结构。最终的1D-CNN结构如下层名称核尺寸/参数输出尺寸说明输入层-(None, 1024, 1)一维振动波形Conv1d_1kernel64, filters16(None, 961, 16)第一层卷积大卷积核提取原子特征BatchNorm ReLU MaxPoolpool4(None, 240, 16)归一化降采样Conv1d_2kernel16, filters32(None, 225, 32)中层卷积捕捉冲击调制特征BatchNorm ReLU MaxPoolpool4(None, 56, 32)降采样Conv1d_3kernel8, filters64(None, 49, 64)深层卷积BatchNorm ReLU MaxPoolpool4(None, 12, 64)降采样全局平均池化-(None, 64)降低参数量防过拟合Dense ReLU Dropout64(None, 64)隐层Dense Softmax4(None, 4)输出四类概率第一层卷积核用64一方面是因为原始信号的相邻采样点相关性很强大卷积核可以更快覆盖一个局部波形段捕捉冲击特征另一方面大卷积核在后面接MaxPool可以快速降低序列长度减少计算量。网络里每层卷积后面都跟了BatchNorm。这个对训练稳定性帮助非常大尤其是输入信号幅值波动比较明显的时候BatchNorm可以保证每一层的输出分布相对稳定避免梯度消失或爆炸。最后用全局平均池化而不是直接Flatten再接全连接主要原因是参数量大幅下降。如果1024个输入点经过三层卷积后还有12×64的中间特征直接Flatten再接全连接会多出好几倍的参数量在小样本场景下很容易过拟合。全局平均池化把每个特征通道压成一个数再进入全连接层模型容量更可控。说句题外话最近讨论度很高的CSPNet这种改进骨干网络在图像分类任务上通过拆分梯度流来增强CNN的学习能力思路是让不同分支学习不同层级的特征减少重复梯度。在轴承诊断里也有借鉴意义如果你后面想把准确率再往上推一档可以在主干网络里引入类似的分支设计或者注意力机制但对入门项目来说上面这个结构已经够用了。3.2 训练参数、优化器与损失函数设置训练环节有几个关键参数直接决定了收敛速度和质量。优化器Adam初始学习率1e-3。Adam对学习率的敏感度相对低收敛稳定适合作为第一个跑的优化器。损失函数交叉熵损失CrossEntropyLoss多分类的标准选择。Batch size64。CWRU数据经过分段增强后每类样本量在两千到三千之间batch size太小会导致梯度振荡太大又会内存溢出。Epochs30轮起步。搭配早停early stopping监控验证集loss连续5轮不下降就停止训练避免后期过拟合。学习率调度ReduceLROnPlateau验证loss停滞时学习率乘以0.5。训练代码的核心部分大概是这样的import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset model BearingCNN(num_classes4) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) scheduler optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience3 ) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse) best_val_acc 0.0 patience_counter 0 for epoch in range(30): model.train() train_loss 0.0 for x_batch, y_batch in train_loader: optimizer.zero_grad() outputs model(x_batch) loss criterion(outputs, y_batch) loss.backward() optimizer.step() train_loss loss.item() # 验证 model.eval() val_loss 0.0 correct 0 total 0 with torch.no_grad(): for x_batch, y_batch in val_loader: outputs model(x_batch) loss criterion(outputs, y_batch) val_loss loss.item() _, predicted torch.max(outputs, 1) total y_batch.size(0) correct (predicted y_batch).sum().item() val_acc correct / total print(fEpoch {epoch1}: train_loss{train_loss/len(train_loader):.4f}, fval_loss{val_loss/len(val_loader):.4f}, val_acc{val_acc:.4f}) scheduler.step(val_loss) if val_acc best_val_acc: best_val_acc val_acc patience_counter 0 torch.save(model.state_dict(), best_model.pth) else: patience_counter 1 if patience_counter 5: print(Early stopping triggered.) break实际跑下来模型通常在10到15轮之后验证集准确率就能收敛到98%以上。训练时间在普通单张GPU上大概只需要两三分钟CPU上大约十几分钟算力门槛很低这也是CNN轴承故障诊断入门项目适合上手的原因之一。3.3 防过拟合与数据增强的实践经验四分类问题每类两三千个样本模型参数量几十万理论上存在过拟合风险。我实践下来的有效手段主要有这么几个Dropout在全连接层加Dropout比率0.5。训练时随机丢弃一半神经元相当于多个子网络做集成对抑制过拟合作用明显。数据增强对原始振动信号做小幅高斯噪声叠加、幅值随机缩放0.9~1.1倍、时间轴小幅位移。增强后的样本多样性增加模型对噪声环境的鲁棒性更好。我做过一组对比加了数据增强之后在带噪测试集上的准确率可以提升3到5个百分点。早停前面已经说过简单但非常有效。类别均衡确保每个类别的样本数差不多。如果某个故障类型样本特别少可以用重叠率更高的分段方式增加样本量或者对少数类做过采样。这里特别强调一下模型训练初期的目标不是让训练集准确率冲到100%而是让验证集准确率保持稳定。如果你的训练集准确率一开始就99%但验证集只有85%那基本上是数据划分出了问题或者模型容量严重过拟合不是“再训几轮就好”的问题。4. 实验评估与结果分析4.1 用哪些指标评估模型分类任务最直观的指标是准确率Accuracy但在故障诊断场景里只看准确率远远不够。因为实际设备绝大多数时间是正常运行的故障样本占比很低一个把所有样本都判为正常的模型准确率也能做到95%以上——但这显然不是我们想要的诊断系统。所以我在评估时额外关注了精确率Precision、召回率Recall和F1分数。对故障诊断场景召回率尤其重要它代表“所有真实故障样本中被正确识别出来的比例”。漏掉一个故障的代价远大于多报一次误警因为漏检会导致设备带病运行甚至引发连锁故障。计算方式不复杂。以“内圈故障”为例精确率 被正确判定为内圈故障的样本数 / 所有被判定为内圈故障的样本数召回率 被正确判定为内圈故障的样本数 / 真实内圈故障样本总数F1 2 × 精确率 × 召回率 / (精确率 召回率)测试集上的最终结果如下故障类型精确率召回率F1分数正常99.8%100%99.9%内圈故障98.6%97.9%98.2%外圈故障97.4%98.8%98.1%滚动体故障95.7%94.6%95.1%滚动体故障的F1相对最低这符合工程直觉。滚动体在旋转过程中与内外圈的接触位置不断变化特征信号具有周期性调制特性且幅值相对较弱早期轻微故障容易被噪声淹没分类难度天然更高。4.2 混淆矩阵与特征可视化混淆矩阵能够更细致地看出模型在哪些类别之间容易混淆。我测试集上一共跑了大约4000个样本混淆矩阵大致是真实\预测正常内圈外圈滚动体正常998002内圈0970128外圈099856滚动体01411975可以看到主要混叠发生在三种故障之间特别是内圈和滚动体之间。这在物理上也好理解振动信号在传递路径上经过轴承座、传感器安装面不同故障源的特征会相互干扰。为了进一步确认模型学到的特征是否有区分度我做了t-SNE可视化把全局平均池化层输出的64维特征向量降到二维平面。正常样本聚集得非常好形成一个紧凑的簇三种故障样本各自聚簇但滚动体故障的簇比较分散边界离内圈故障簇较近这解释了为什么滚动体故障的F1分数最低。t-SNE可视化的价值在于它把CNN这个“黑盒”打开了一个窗口如果某个类别在特征空间里边界模糊那么后续改进方向就明确了要么增加该类别的训练样本要么在损失函数上加大该类的权重。4.3 不同方案的效果对比项目里我同时跑了好几种输入方案这里放一组对比数据输入方案模型结构测试集准确率单样本推理耗时(CPU)原始波形(1024点)1D-CNN98.2%约2ms功率谱(512点)1D-CNN96.5%约1.5msSTFT时频图2D-CNN99.0%约8ms这个结果让我有些意外的是功率谱输入并没有比原始波形更高——原因是功率谱虽然强化了频率信息但丢掉了相位信息而故障冲击的相位结构对识别有一定帮助。时频图方案用更大的计算量换来了准确率提升适合部署在算力充足的服务器端。5. 常见问题与排查实录5.1 数据划分不当导致的“虚假高分”这个坑我必须放在最前面讲因为几乎所有新手都会踩。一开始我的做法很粗暴把所有信号切片后的样本混合在一起随机打乱按8:2分成训练集和测试集。结果测试集准确率稳在99.5%以上看起来非常完美。但当我用另外一组完全没参与训练的数据来验证时准确率掉到了82%左右。问题出在哪同一个原始记录里切出来的相邻样本高度相似随机划分后测试集里可能混入了同一记录切出的“兄弟样本”模型等于见过答案做考试。正确做法是按记录或者按工况划分数据集。同一段连续信号切出来的所有样本要么全部进训练集要么全部进测试集。只有这样才能模拟“模型面对全新振动记录”的真实场景。改造之后测试准确率从虚高的99.5%回落到真实的97%左右虽然数字降了但这个数字才可信。5.2 训练不收敛或者Loss震荡训练过程中我遇到过Loss反复震荡、不下降的情况。排查下来有两个原因一是学习率偏大。Adam虽然自适应调整学习率但初始学习率设成3e-3时模型在小数据集上容易出现震荡。把学习率降到1e-3或5e-4之后损失曲线明显平滑。二是标签与数据不匹配。有一次我整理数据时把某条正常信号错误地放进了内圈故障文件夹训练时模型对这个样本永远预测错误Loss一直降不下去。后来检查每个类别的样本数量和波形形态才发现问题。所以数据标注环节的核验特别重要我的经验是每类数据抽查几段用Matplotlib画一下时域波形和频谱确认故障特征确实存在再做训练。5.3 测试集效果好实际工况效果差模型在测试集上表现很好拿去给现场新采集的数据做诊断效果却明显下降——这种“实验室到现场的水土不服”本质上就是域偏移。训练数据是0负载条件下采集的现场设备可能是满负荷甚至变负荷运行转速不一样故障特征频率就不同振幅分布也会变化。缓解手段我从三个方向都试过。最简单的是在训练数据里混入不同负载、不同转速的数据扩大数据覆盖范围。其次是做数据增强模拟不同噪声水平、不同传感器灵敏度的变化。再高级一些的做法是加域适配模块比如用对抗训练让特征提取器学到“工况无关”的特征不过这个方案会显著增加复杂度。对入门项目我的建议是先保证训练数据覆盖2~3种工况然后重点观察跨工况准确率的变化把它作为模型泛化能力的重要指标写进评估报告。5.4 训练数据类别不均衡的隐患实际场景中正常样本通常很多故障样本很少类别不均衡会拉偏模型。处理上我建议先用混淆矩阵看损伤分布如果某一类召回率明显偏低就用加权交叉熵损失给样本少的类别更大的权重。具体到PyTorch里只需要在CrossEntropyLoss里传一个weight参数权重设置为各类别样本数的倒数操作成本很低但对少样本类别的准确率提升帮助很大。6. 最后的几点实操心得项目做下来最深的体会是“模型结构只是锦上添花数据组织和实验规范才是决定成败的地基”。CNN的代码实现非常成熟网上随便一搜都有现成的模板真正拉开差距的地方在于你有没有科学划分数据集有没有在训练前确认每类数据的可靠性和可区分性有没有用多工况数据检验泛化能力另外一个想分享的小技巧是实验全程保持固定的随机种子。深度学习模型的训练本身带有随机性如果每次跑结果都不一样你就无法判断一个改动是真正有效还是随机波动。我在项目里设置了全局随机种子并且固定了数据集划分方式这样每次改进的效果都是可比较的实验效率高了很多。最后如果你准备在这个方向上继续深入建议下一步把模型从4分类扩展到更多类别比如不同故障直径、复合故障、不同转速下的故障然后把诊断结果和实际停机检修记录做交叉验证。也可以尝试用轻量化网络结构做边缘端部署毕竟工业现场真正需要的是“本地实时诊断”而不是把数据传回服务器等结果。这套项目的完整代码和数据集整理脚本已经放出来了照着跑一遍应该能在半天内复现文中的结果。