ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WDCNN轴承故障诊断实战:从原理到tSNE可视化全解析

WDCNN轴承故障诊断实战:从原理到tSNE可视化全解析

简介:面向轴承故障诊断与深度学习交叉应用场景,这份资料基于WDCNN一维卷积神经网络实现故障分类,并附带t-SNE降维可视化结果,适合机械故障诊断方向的研究生、工程师以及深度学习初学者参考。压缩包共13个文件,其中10个mat文件涵盖正常、滚动体、内圈、外圈等故障类型,并包含不同损伤直径与负载工况的振动信号数据,便于直接用于模型输入与复现实验;2个Python脚本分别承担数据读取预处理与WDCNN模型训练、t-SNE可视化功能;另含1张特征聚类效果图,整体仅8.66MB,轻量易用。已有1277人学习下载。通过这套代码,读者可以快速掌握从原始振动信号到故障模式识别的完整流程,理解WDCNN如何自动提取时域特征,并通过t-SNE图观察不同故障类别在特征空间中的分布,为后续开展迁移学习、模型调优或进一步对比实验打下基础。数据与代码相互配套,拿到后即可对照运行,尤其适合需要快速复现深度轴承诊断实验的读者。

1. 基于WDCNN的轴承故障诊断:这份资源能让你少走三个月弯路

如果你正准备做设备故障诊断方向的深度学习实验,大概率会在某个平台刷到“WDCNN轴承故障诊断 含tSNE可视化”这个资源包。它解决的是振动信号分类里的一个核心痛点:直接用原始时域信号做输入,不需要人工构造几十个频域特征,靠一个宽卷积核的第一层把局部波形特征“咬住”,后面再用小卷积核加深抽象。我当初拿到这份资源时最直观的感受是:代码不是那种只能跑通Demo的教学脚本,而是把数据切分、模型定义、训练验证到特征可视化串成了一条完整链路。适合谁?适合刚入门深度学习故障诊断的研究生,也适合想快速在轴承数据集上做基线对比的工程师。但如果你只是点一下运行等着出图,大概率会在数据预处理和tSNE参数上翻车,下面这几章就是按我自己复现的顺序写的。

2. 原理与选型:为什么WDCNN能扛住振动信号的强噪声

2.1 宽卷积核的第一层:从原始信号里直接抓特征

WDCNN全称是Wide Deep CNN,最早用在一维振动信号分类上,核心设计思路是在网络第一层使用较宽的卷积核。常见CNN第一层卷积核尺寸在3x3或5x5,而WDCNN第一层往往取64甚至128个采样点长度,这一层不降采样,直接对原始信号滑动卷积。这么做的好处很直接:振动信号里故障冲击往往持续几十个采样点,窄核只能看到局部一两个点,容易把冲击当成噪声;宽核则能包住整个冲击片段,相当于在第一层就做了一个可学习的带通滤波。

# WDCNN第一层宽卷积核定义(PyTorch风格) self.conv1 = nn.Conv1d( in_channels=1, out_channels=16, kernel_size=64, # 宽卷积核,覆盖64个采样点 stride=8, # 步长较大,减少后续计算量 padding=32 # 保证输出长度与输入一致或按公式控制 )

这段代码里kernel_size是核心参数。我一般会先看输入信号长度,如果每段样本是1024点,kernel_size取64相当于覆盖1/16的信号长度,能捕获一个完整的冲击周期;如果原始数据采样率是12kHz,64点大约对应5.3ms,而常见轴承内圈故障特征频率在100Hz到几百Hz,冲击周期远超这个窗口,所以宽核不是越大越好,要和信号特性匹配。stride=8会导致第一层输出长度约为输入/8,后续层再用小卷积核继续压缩。padding的值要根据是否有后续全连接层来定,如果后面接全局池化,padding可以随意点;如果接Flatten,则需要精算输出长度。

2.2 训练流程与资源包文件结构:拿到手先做两件事

这套资源的典型流程分四段:数据切分、模型训练、测试评估、tSNE可视化。网络主体由四个卷积块加一个全连接层组成,每个卷积块包含卷积、批归一化、ReLU和池化。池化层用最大池化比较多,也有的版本用平均池化来保留整体能量。批归一化在这种振动信号任务里几乎是必须的,因为不同工况下振动幅值差异很大,不归一化前面几层的梯度很容易爆炸。

拿到资源包后,我建议先看两个文件:模型结构定义文件和数据读取文件。不要先跑训练,而是确认数据集的路径格式是否与你手头的匹配。很多资源自带的数据集是某一所高校采集的公开轴承数据,包含正常、内圈故障、外圈故障、滚动体故障四类,每类又有不同故障直径。资源包里的标签文件往往是一个Excel或CSV,第一列是文件名,第二列是类别编号。如果你用的是自己的数据,就需要把标签重新映射成从0开始的整数,否则CrossEntropyLoss会直接报错。

# 标签映射的常见处理:把原始类别字符串转为连续整数 label_map = {'normal': 0, 'inner': 1, 'outer': 2, 'ball': 3} labels = [label_map[x] for x in raw_labels]

多数数据集默认类别是平衡的,但有些公开数据里滚动体故障样本只有几十个,而正常样本有上千个。遇到这种不平衡,先别急着上过采样,可以按“每一类使用相同数量样本”的原则切训练集,或者采用加权采样。资源包里如果默认用了随机采样,你需要自己加一个WeightedRandomSampler,否则模型会把正常样本学得特别好,故障样本几乎分不出来。

2.3 资源包文件结构:眼见为实的清单

一个靠谱的WDCNN资源包,目录里通常有这些:data文件夹(原始数据或切好的npy)、models文件夹(网络定义)、train.py、test.py、tsne.py、requirements.txt,以及一个记录超参数的yaml或json文件。如果只有model.py和train.py,没有tsne脚本,那说明tSNE部分需要你自己补。这份资源最值钱的地方就是tSNE那一块,它输出的是测试集在最后一个全连接层前的特征降维结果,而不是模型输出。

文件不多,但每个文件的坑不少。train.py里的数据划分方式决定了你最终报的准确率是否有说服力。我见过一种常见的错误写法:先对整个数据集做标准化,再划分训练集和测试集,这会造成数据泄漏。正确做法是先划分后标准化,且标准化参数只能从训练集统计得出。资源包里的代码如果没注意这一点,你在复现时一定要改过来。

3. 从零跑通训练:数据读取、标签生成与超参数设置

3.1 环境与依赖:版本固定比新功能更重要

这个资源包常见是基于PyTorch或TensorFlow写的。如果是PyTorch版本,建议用Python 3.8到3.10,PyTorch 1.10以上但不要盲目上2.0以上,因为某些老代码里用了torch.Tensor构造函数直接传list的方式,新版虽然也能跑,但有时候会提示警告。CUDA版本如果不是统一环境,最容易碰到的是科学计算库冲突。

# 推荐用conda创建独立环境,避免污染已有环境 conda create -n wdcnn python=3.9 conda activate wdcnn pip install torch==1.13.1 torchvision==0.14.1 pip install numpy scikit-learn matplotlib pandas

安装完成后,先运行一个最简单的数据加载测试,确认能正常读取npy文件。我习惯先把完整训练脚本里所有数据预处理代码抽出来单独跑一遍,看输出的张量shape是否和模型输入对齐。资源包里的输入通常是一维信号,shape是(batch_size, 1, 1024),但有些版本会写成(batch_size, 1024)然后靠模型里的unsqueeze处理,这两种写法在数据加载时完全不一样,直接套用容易报维度错误。

3.2 数据读取和归一化的可复用代码

振动信号的读取不是简单的np.load就完事,还要检查传感器通道、采样率文件以及单位。很多公开数据集的原始信号是.mat格式,里面包含DE(驱动端加速度)和FE(风扇端加速度)等多个通道,做故障诊断通常只用驱动端加速度信号。切样本时,我一般把每个样本长度固定为1024点,对应采样率下约0.085秒,这个长度对大多数轴承故障特征频率已经足够分辨。

# 从连续振动信号中切样本,并做z-score归一化 def make_samples(signal, window_size=1024, step=512, normalise=True): samples = [] for i in range(0, len(signal) - window_size, step): seg = signal[i:i+window_size] if normalise: mu = seg.mean() std = seg.std() if std < 1e-6: std = 1e-6 # 防止静默信号除零 seg = (seg - mu) / std samples.append(seg.reshape(1, -1)) return np.array(samples)

这里step是步长。如果step等于window_size,样本之间没有重叠,样本量少但独立性好;如果step小于window_size,样本重叠,样本量增大,但要小心测试集中与训练集来自同一段连续信号的样本会造成信息泄漏。我习惯用step=512,相当于50%重叠,但必须在切分训练集和测试集时保证来自同一段原始信号的样本全部属于同一个集合,否则验证准确率会虚高到离谱。

3.3 超参数怎么设:学习率、批大小和epochs的边界

WDCNN这类模型不算深,参数总量多在百万级,不需要太大的batch size。以1024点输入、batch size=64为例,一个epoch大约几百个batch,训练时间完全可以接受。学习率初始值我一般用0.001或0.0005,优化器用Adam,但Adam对学习率比较敏感,初始值太大的话前几个batch的loss会从3.0涨到5.0,看起来像炸了;太小的话loss下降得像乌龟爬。

# 一种稳妥的学习率设置:用warmup + 逐步下降 from torch.optim import Adam from torch.optim.lr_scheduler import CosineAnnealingLR optimizer = Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=1e-5)

T_max设为总epochs的一半比较合理,让学习率从1e-3余弦下降到1e-5,后半段模型在低学习率下精细化收敛。如果你发现训练准确率先到99%然后掉到95%,往往是学习率在后期下降太快,模型在最优解附近振荡过大。这类问题不要调大学习率,而是把eta_min提高到1e-4试试。

3.4 快速验证:训练到一半也能判断有没有跑偏

训练脚本打印的每个epoch平均loss,如果前三个epoch里loss从0.7降到0.3以下,说明数据没有喂错。如果loss一直停在某个值附近比如2.3,那大概率是类别标签没有对齐。另一个快速验证方法是训练完第一个epoch后用测试集跑一次,如果准确率接近随机水平(四类就是25%左右),不要急着加大epoch数,先检查数据归一化和标签顺序。

# 训练中每隔几个epoch强制输出训练和验证准确率 python train.py --epochs 50 --log_interval 5

很多资源包的train.py里没有这个log_interval参数,你需要自己改。把验证集准确率打印出来是判断过拟合最直接的办法:训练准确率涨到100%、验证准确率卡在70%,说明模型记住了训练集。这时优先做法是增大样本切分步长、减少重叠,而不是盲目加dropout,因为重叠造成的泄漏比过拟合更难治。

4. tSNE可视化:把特征空间从黑匣子变成能说的故事

4.1 tSNE的困惑度设置:别让可视化骗了你

tSNE是故障诊断论文里最常见的可视化手段,目的是把高维特征映射到二维或三维平面,让人直观看到同类样本是否聚在一起、不同类之间是否分开。这本身没有问题,但tSNE有一个超参数叫perplexity(困惑度),它控制着每个点在降维时考虑多少个近邻。取值太小,比如2或5,每个点只看周围极少的邻居,结果会散成一团;取值太大,比如100或200,所有点会被拉成一坨,看不出类别边界。

# 使用sklearn的TSNE,嗯,保持perplexity在30到50之间 from sklearn.manifold import TSNE from sklearn.preprocessing import StandardScaler feat = features_numpy # shape: (N, 512) feat_scaled = StandardScaler().fit_transform(feat) tsne = TSNE(n_components=2, perplexity=30, n_iter=1000, init='pca', random_state=42) result = tsne.fit_transform(feat_scaled)

当样本数量少于200时,perplexity不能设到50,因为近邻数量超过样本数会报错或结果不稳定。我通常按样本量的平方根来取perplexity值,比如300个测试样本就取17,800个样本取28。另外init='pca'比随机初始化稳定,跑出来的点分布不会每次都不一样。

4.2 提取中间层特征并降维的完整代码

tSNE可视化不是直接把模型输出拿来画图,而是提取模型在最后一个全连接层之前的特征。也就是说,你需要把模型截断,让前向传播输出特征向量而不是分类logits。

# 注册forward hook,提取最后一层池化后的特征 features = {} def hook_fn(module, input, output): features['feat'] = output.detach().cpu().numpy() model.pool3.register_forward_hook(hook_fn) # 在测试集上跑一遍,收集所有特征 model.eval() all_feats = [] all_labels = [] with torch.no_grad(): for x, y in test_loader: out = model(x) # 触发hook all_feats.append(features['feat'].reshape(x.size(0), -1)) all_labels.append(y.numpy()) feats = np.concatenate(all_feats, axis=0)

这里选择哪个层作为特征层很关键。资源包里如果默认挂的是最后一层卷积后的输出,那特征维度可能是(样本数, 64, 16),需要reshape成二维。另一种做法是直接用模型里一个叫fc_input的线性层之前的输出。我比较喜欢挂在最后一个池化层之后,因为这时特征已经高度抽象,但还没有被全连接层压缩到分类维度,保留的区分性信息最多。

4.3 从可视化图里读出的三个结论

画完tSNE散点图,不要只看“分开了”就完事。第一看类间距离:正常类和内圈故障如果完全重叠,说明模型没有学到有效特征,或者数据切分时两类信号本身相似度太高。第二看类内聚集:同一类样本如果分成几个小团,可能是样本来自不同工况或不同负载,模型只学会了工况差异而不是故障差异。第三看异常点:那些游离在类别边缘的点,往往是切样时落在了信号瞬态段或噪声尖峰上。我常做的做法是把这些异常点对应的原始信号打印出来,用matplotlib快速画一下时域波形,十次有九次能看到明显的非周期冲击,说明切样本的起点没对齐。

# 画出tSNE结果并给每类不同颜色 import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) for cls in range(4): idx = np.where(labels == cls)[0] plt.scatter(tsne_result[idx, 0], tsne_result[idx, 1], s=10, alpha=0.7, label=f'Class {cls}') plt.legend() plt.savefig('tsne_result.png', dpi=200)

这个图保存下来,基本就是论文里的Figure 1。但要注意,tSNE的坐标轴没有绝对意义,不能说某个类在x方向偏移代表某种物理量。解释时只能说“类别在特征空间上分离”,不能说“故障特征在特征方向上增大”。

5. 避坑指南:我复现这个资源时踩过的五个坑

5.1 样本切割重叠率过高导致数据泄漏

现象:训练准确率正常,验证准确率高达99.8%,测试准确率反而只有85%。原因:切样本时step设为64,样本窗口1024点,相邻样本重叠率高达93%,同一个故障冲击出现在大量样本里,训练集和测试集如果来自同一段连续信号,就等于同一个事件被两边同时看到。解决:把step设为512以上,并确保训练集和测试集在切割原始信号前就按互不重叠的片段划分,比如前60%的信号只进训练集,后40%只进测试集。

5.2 归一化用错了维度,精度直接掉两个点

现象:loss下降很快,但测试集准确率始终在80%上下波动。原因:对整段连续信号一次性做z-score归一化后再切样本,导致每个窗口的均值和标准差包含未来信息,而且静默段和冲击段的幅值被压到了相似范围。解决:改成对每个单独窗口做归一化,也就是先切样本后归一化,并且把均值和标准差限制在窗口内。这样更符合“实时诊断”的场景,因为实际部署时你只能拿到当前窗口的数据。

5.3 最后一层全连接维度不匹配,报错却查不出原因

现象:修改了输入长度或池化类型后,训练一跑就报mat1 and mat2 shapes cannot be multiplied。原因:全连接层输入维度写死了,例如模型默认输入是1024点,第一个卷积核stride=8,经过两次池化后特征维度是64*64,但你改成2048点后,特征维度翻倍,最后全连接层期望的输入维度对不上。解决:不要手算,在定义模型的forward里加一个print(feat.size()),跑到全连接层之前打印实际维度,然后把全连接层的in_features改掉。

5.4 tSNE每次运行结果都不一样,被质疑“造假”

现象:同一批代码,同一批测试集,昨天跑的可视化图和今天跑的形状完全不同。原因:tSNE的优化是随机初始化的,而且求解过程是非凸优化,不同的随机种子会收敛到不同的局部最优布局。解决:固定random_state=42,同时把init改为pca,这样虽然每次降维结果仍有微小差异,但大局不会变。报告图时一定要在方法里写明随机种子,否则审稿人很容易提出质疑。

5.5 训练集和测试集来自同一工况,评估虚高

现象:模型在你自己的数据上跑出99%准确率,但换到另一个负载或转速下的数据,准确率直接跳水到60%。原因:资源包默认只在一个工况下切分数据,模型可能学会了工况的特征而不是故障的特征。解决:用多工况数据做跨域验证,比如用0负载数据训练,用1负载数据测试。如果你的数据只有单一工况,至少要用更严格的留出法,比如把某一段连续采集的信号整体留出作为测试集,而不是随机打乱后切分。

6. 进阶技巧:给WDCNN加上自适应学习率与特征对比验证

WDCNN的基础版本用固定学习率也能跑,但在实际项目里,我习惯给它加上余弦退火学习率,再配合特征层对比验证,能在不改变网络结构的条件下把测试准确率稳定提升1到2个百分点。做法分两步:第一步是训练过程中用CosineAnnealingLR替换固定学习率,让模型在中期大步探索、后期小步收敛,这比手动设衰减节点要省心;第二步是脚本里同时提取多个中间层的特征,分别做tSNE,然后对比哪个层对故障类型区分得最好。

# 训练完一个epoch后更新学习率 for epoch in range(epochs): train_one_epoch(model, train_loader, optimizer) scheduler.step() lr = optimizer.param_groups[0]['lr'] print(f'Epoch {epoch}: lr={lr:.6f}')

学习率从1e-3降到1e-5的过程中,我发现训练集准确率曲线会出现几次小跳变,这是正常的。真正要注意的是验证集准确率峰值出现的位置:如果峰值出现在第40个epoch之后,说明模型其实需要更多epoch,但过早的余弦下降会削弱后期学习能力,这时可以把T_max调大一点,或者把eta_min抬到1e-4。另外,特征层对比也是排查模型过拟合的一个手段:如果最后一层卷积的tSNE图比中间层更乱,说明中间层学到的特征被最后一层池化破坏了,这时可以检查是不是用了过大的池化核,比如窗口为8的最大池化会把冲击尖峰直接削平。从那以后,我每次拿到类似资源包,都强制自己先看数据切分和特征可视化脚本,再动手改模型结构,这套习惯帮我避开了不少隐性问题,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进