☰
WDCNN轴承故障诊断实战:从振动信号到t-SNE可视化
2026/10/14 13:15:55 网站建设 项目流程

简介:这份资源面向深度学习与机械故障诊断方向的学习者和研究者,提供一套基于WDCNN(宽卷积核深度网络)的轴承故障诊断完整实现方案,适合具备一定Python与深度学习基础、希望快速复现经典诊断模型或开展课程设计、科研实验的人群。压缩包共13个文件,约8.66MB,其中10个mat文件为凯斯西储大学轴承数据集的正常与多种故障样本,涵盖内圈、外圈、滚动体等不同故障类型与损伤尺寸;2个py脚本分别负责数据处理与模型训练主流程,另有1张tsne可视化结果图直观展示特征聚类效果。资源将数据加载、模型搭建、训练评估与降维可视化串联为完整链路,读者可据此理解WDCNN的宽卷积结构设计思路,掌握从原始振动信号到故障分类的端到端流程,并借助tsne图分析不同故障类别的特征可分性。目前已有1276人学习下载,适合作为轴承故障诊断入门与进阶的实践参考。

1. 从振动信号到故障标签:WDCNN 轴承诊断到底在做什么

一台产线电机突然异响,拆开发现轴承内圈已经剥落。这种场景在工厂里太常见了,但真正让人头疼的不是换轴承,而是怎么在它彻底抱死之前就发现苗头。基于 WDCNN 的轴承故障诊断,做的就是这件事:拿一段振动加速度信号,直接告诉你是正常、内圈故障、外圈故障还是滚动体故障。它不需要你手工设计特征,卷积核自己会从原始时序里学。适合谁?手上有一维振动数据、想快速跑通一个端到端诊断基线的人;也适合已经用过 FFT 加 SVM、想看看深度学习到底能提升多少的工程师。这一章先把问题定义清楚,后面再动手。

WDCNN 全称 Wide Deep Convolutional Neural Network,核心思路是用宽卷积核在第一层直接吃原始信号,避免早期池化丢掉冲击成分。轴承故障的振动信号里,最有价值的往往是周期性冲击,频率成分复杂且非平稳,传统做法要先做包络谱或者小波变换。WDCNN 把这一步交给网络,第一层卷积核宽度通常设得比较大,比如 64 或 128,目的就是覆盖足够长的局部波形。配合 t-SNE 可视化,你能看到不同故障类型的特征在二维平面上到底分没分开,这比只看准确率更有说服力。

2. 数据准备与 WDCNN 网络结构:从 CWRU 风格数据到可训练张量

2.1 轴承振动数据的典型格式与切窗策略

常见的公开数据集格式是每类故障一个 mat 文件,里面存着驱动端加速度信号,采样率 12kHz 或 48kHz。假设你拿到的是 12kHz 采样的数据,每种故障状态有几十万点。直接整段丢进网络不现实,必须切窗。我一般用 1024 点一个样本,重叠率 50%,这样既能保证每个样本包含至少一个完整冲击周期,又能扩增样本量。标签按故障类型和故障直径分,比如内圈 0.007 英寸、内圈 0.014 英寸算两个类,还是合并成一个内圈类,取决于你的任务粒度。新手建议先做四分类:正常、内圈、外圈、滚动体。

切窗时有个细节:不要跨文件边界切。每个 mat 文件单独切完再打乱,否则训练集和验证集之间会泄漏。下面这段代码演示从一维数组到 (样本数, 1024, 1) 张量的过程。

import numpy as np def make_windows(signal, window_size=1024, overlap=0.5): """ signal: 一维 numpy 数组 window_size: 每个样本长度 overlap: 重叠比例,0.5 表示步长 512 返回: (n_windows, window_size, 1) """ step = int(window_size * (1 - overlap)) windows = [] for start in range(0, len(signal) - window_size + 1, step): seg = signal[start:start + window_size] # 去均值,消除直流偏置 seg = seg - np.mean(seg) windows.append(seg) windows = np.array(windows) return windows.reshape(-1, window_size, 1)

逻辑说明:去均值这一步很关键,传感器信号往往有零点漂移,不去掉的话第一层卷积会浪费容量去拟合常数项。参数方面,window_size 取 1024 是经验值,对应 12kHz 采样约 85ms,足够覆盖低频故障特征;overlap 取 0.5 是扩增和独立性的折中,再高会导致相邻样本过于相似。

2.2 WDCNN 的层配置与第一层宽卷积的作用

WDCNN 的结构不复杂,但第一层宽卷积是灵魂。典型配置是:第一层 Conv1D 核宽 64、通道 16,后面接 BN 和 ReLU,然后池化;第二层核宽 3、通道 32;第三层核宽 3、通道 64;最后全局平均池化加全连接分类。为什么第一层要宽?因为原始振动信号里冲击成分持续时间短,宽核能一次性看到整个冲击包,窄核容易把冲击拆散。BN 放在卷积和激活之间,对轴承数据尤其重要,不同负载下信号幅值差异大,BN 能稳住分布。

import torch import torch.nn as nn class WDCNN(nn.Module): def __init__(self, num_classes=4): super().__init__() self.features = nn.Sequential( # 第一层宽卷积,核宽 64 nn.Conv1d(1, 16, kernel_size=64, stride=2, padding=32), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2), # 第二层 nn.Conv1d(16, 32, kernel_size=3, padding=1), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), # 第三层 nn.Conv1d(32, 64, kernel_size=3, padding=1), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(1) ) self.classifier = nn.Linear(64, num_classes) def forward(self, x): # x: (batch, 1, 1024) feat = self.features(x) feat = feat.view(feat.size(0), -1) return self.classifier(feat)

参数说明:stride=2 在第一层是为了降采样,减少后续计算量;padding=32 保证输出长度不至于缩得太快。如果你数据采样率是 48kHz,第一层核宽可以加到 128,因为同样时间窗口点数更多。num_classes 按你的标签数改。这个网络参数量很小,几万级别,在普通笔记本上就能训。

2.3 训练循环与 t-SNE 特征提取的衔接点

训练本身是标准流程,但为了后面 t-SNE 可视化,需要在验证阶段把倒数第二层的特征存下来。我一般在前向传播时加一个 hook,或者直接改 forward 返回特征。下面这段训练代码同时收集特征。

from sklearn.manifold import TSNE import matplotlib.pyplot as plt def train_and_collect(model, train_loader, val_loader, epochs=30): optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss() model.train() for epoch in range(epochs): for x, y in train_loader: x = x.permute(0, 2, 1) # (batch, 1, 1024) optimizer.zero_grad() out = model(x) loss = criterion(out, y) loss.backward() optimizer.step() # 收集验证集特征 model.eval() feats, labels = [], [] with torch.no_grad(): for x, y in val_loader: x = x.permute(0, 2, 1) f = model.features(x).view(x.size(0), -1) feats.append(f.numpy()) labels.append(y.numpy()) feats = np.concatenate(feats, axis=0) labels = np.concatenate(labels, axis=0) return feats, labels

逻辑说明:permute 是因为 Conv1d 要求通道维在中间,而 DataLoader 出来是 (batch, 1024, 1)。特征收集用 model.features 的输出,已经过全局平均池化,是 64 维向量。t-SNE 对这 64 维降维比直接对原始信号降维稳定得多。

3. t-SNE 可视化:把 64 维特征压到二维看聚类效果

3.1 t-SNE 参数怎么设才不误导人

t-SNE 的困惑度 perplexity 是最关键的参数,一般取 5 到 50。样本量几百到几千时,perplexity 取 30 左右比较稳。学习率用默认 200 或者 auto,迭代 1000 次。注意 t-SNE 每次跑出来的图不一样,因为它是随机初始化。如果你要放论文或报告里,固定 random_state。另外,t-SNE 的簇间距离没有绝对意义,不要根据两个簇离得远就说它们差异大,只能看簇内紧不紧、簇间分不分得开。

def plot_tsne(feats, labels, class_names): tsne = TSNE(n_components=2, perplexity=30, random_state=42, n_iter=1000) emb = tsne.fit_transform(feats) plt.figure(figsize=(8, 6)) for i, name in enumerate(class_names): idx = labels == i plt.scatter(emb[idx, 0], emb[idx, 1], s=8, label=name, alpha=0.7) plt.legend() plt.title('WDCNN feature t-SNE') plt.savefig('tsne.png', dpi=150)

参数说明:n_iter 在新版 sklearn 里改叫 max_iter,如果你版本较新会警告,改成 max_iter=1000 即可。alpha 设 0.7 是为了重叠点能看出密度。class_names 按你的标签顺序写。

3.2 从 t-SNE 图判断模型到底学到了什么

一张好的 t-SNE 图,四类故障应该各自成团,边界清晰。如果正常和某个故障混在一起,说明模型没抓住区分性特征,可能是窗口太短或者第一层核宽不够。如果所有类糊成一团,检查输入是否归一化、BN 是否生效。还有一种情况:训练集分得很开,验证集混在一起,那是过拟合,加 dropout 或者减网络容量。我习惯把训练集和验证集的 t-SNE 并排画,对比着看。

4. 避坑与排查:轴承诊断里最容易翻车的五个地方

4.1 准确率 99% 但换负载就崩

现象:在 0 负载数据上训练,准确率 99%,换到 3 马力负载测试掉到 60%。原因:模型学到了负载相关的幅值特征,而不是故障特征。解决:训练时做幅值归一化,每个样本除以自身标准差;或者用多负载数据混合训练,让 BN 统计量覆盖不同工况。

4.2 第一层宽卷积导致显存爆炸

现象:核宽设 128、通道设 64,batch size 32 就 OOM。原因:第一层输出长度大,通道多,中间激活占显存。解决:第一层 stride 设 4 或 8,先把长度降下来;或者用梯度累积,小 batch 多步累积。

4.3 t-SNE 跑得特别慢

现象:几千个样本跑 t-SNE 要十几分钟。原因:t-SNE 复杂度是 O(n^2),样本多了就慢。解决:从每类里随机采样 200 个,总共 800 个点足够看聚类;或者先用 PCA 降到 20 维再 t-SNE。

4.4 标签泄漏导致验证集虚高

现象:验证准确率比测试高一大截。原因:切窗时重叠部分跨了训练集和验证集。解决:按文件划分训练验证,同一个文件切的窗只能进同一个集合。

4.5 模型对转速变化不鲁棒

现象:训练转速 1730 rpm,测试 1750 rpm 就错。原因:故障特征频率随转速偏移,网络没学过。解决:做阶次分析,把时间域信号重采样到角度域;或者训练时加转速扰动增强。

5. 进阶技巧:用混淆矩阵和特征距离量化诊断边界

准确率是个粗指标,真正要上线得看混淆矩阵。我一般会算两个东西:一是每类召回率,看哪个故障容易漏;二是类间特征距离,用验证集特征算类中心,看哪两类最近。最近的那两类就是模型最容易混的,针对性地加数据或者加宽第一层核。下面这段代码算混淆矩阵和类中心距离。

from sklearn.metrics import confusion_matrix, classification_report from scipy.spatial.distance import cdist def evaluate_detail(model, val_loader, class_names): model.eval() all_pred, all_true, all_feat = [], [], [] with torch.no_grad(): for x, y in val_loader: x = x.permute(0, 2, 1) f = model.features(x).view(x.size(0), -1) out = model(x) pred = out.argmax(dim=1) all_pred.append(pred.numpy()) all_true.append(y.numpy()) all_feat.append(f.numpy()) all_pred = np.concatenate(all_pred) all_true = np.concatenate(all_true) all_feat = np.concatenate(all_feat) print(confusion_matrix(all_true, all_pred)) print(classification_report(all_true, all_pred, target_names=class_names)) # 类中心距离 centers = [] for i in range(len(class_names)): centers.append(all_feat[all_true == i].mean(axis=0)) centers = np.array(centers) dist = cdist(centers, centers, metric='euclidean') print('类中心距离矩阵:\n', dist)

逻辑说明:类中心距离矩阵是对称的,对角线为 0。找非对角线上最小的那个值,对应的两类就是最易混的。比如内圈和外圈距离最近,那就去检查数据里这两种故障的冲击周期是不是被窗口截断了。我自己的习惯是每次训完模型先看混淆矩阵,再看 t-SNE,最后看类中心距离,三个一起才能判断这个模型能不能上产线。别只盯着准确率,那个数字会骗人。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询