
简介这份资源面向深度学习与工业设备故障诊断方向的学习者和研究者围绕WDCNN宽卷积核深度卷积神经网络在轴承故障诊断中的应用展开适合具备一定Python与深度学习基础、希望快速复现经典诊断模型并理解特征分布的人群。压缩包共13个文件以10个mat数据文件为核心涵盖正常、内圈、外圈、滚动体等多种故障类型与不同损伤尺寸的振动信号另含2个py脚本分别负责数据处理与模型训练主流程并附1张tsne可视化结果图整体约8.66MB结构紧凑、开箱即用。目前已有1276人学习下载说明该方案在同类任务中具有较好的参考价值。读者可借此掌握从原始振动信号到诊断结论的完整链路理解WDCNN的宽卷积核设计思路并通过tsne降维图直观观察不同故障类别的特征聚类效果为论文复现、课程设计或工程预研提供可运行的代码与数据基础。1. 从一段振动信号说起WDCNN 轴承故障诊断到底在解决什么一台产线电机在凌晨三点突然报振动超标拆开发现轴承外圈已经剥落。事后复盘其实振动数据里早有征兆只是没人能从每秒几万点的波形里看出门道。基于 WDCNN 的轴承故障诊断做的就是把这件靠老师傅耳朵和手感的事变成一条能自动跑、能复现、能解释的流水线。WDCNN 全称 Wide First-layer Kernel Convolutional Neural Network核心特征是第一层用很宽的大卷积核直接吃原始振动信号省掉手工特征工程。它适合手里有 CWRU、XJTU-SY 或自采产线数据、想快速搭一套端到端诊断基线的人。配套的 t-SNE 可视化不是装饰它回答的是“模型到底学到了什么”是判断模型是否真的在按故障类型聚类、而不是在按工况偷懒的关键手段。这一篇就把数据准备、网络搭建、训练调参、可视化验证和踩坑排查整条链路讲透。2. WDCNN 为什么用宽卷积核直接吃原始信号2.1 从手工特征到端到端选型背后的取舍传统轴承诊断流程是“去噪 → 分段 → 时域/频域/时频域特征提取 → 特征选择 → 分类器”。这套流程在实验室里能刷到很高的准确率但换一台设备、换一个转速特征分布就漂了得重新调。WDCNN 的思路是把特征提取这一步交给网络自己学输入直接是原始振动序列输出是故障类别。为什么第一层要用宽卷积核振动信号里轴承故障的冲击成分是周期性出现的单个冲击点本身信息量很低必须看一个足够长的窗口才能捕捉到冲击的节奏。窄卷积核比如 3×1感受野太小第一层只能看到局部几个点冲击的周期性结构要经过很多层才能拼出来训练慢且容易过拟合。WDCNN 第一层用 64×1 甚至 128×1 的大核一步就能覆盖一个冲击周期后面的池化层再把这个宽特征压缩整个网络的感受野增长非常快。代价是参数量。第一层宽核的参数量是kernel_size × in_channels × out_channels64×1×16 就是 1024 个参数不算大但如果输入是多通道比如三轴加速度参数量会翻倍。所以 WDCNN 通常第一层通道数控制在 16 或 32后面几层再用 3×1 小核堆深度。2.2 网络结构逐层拆解与张量形状一个能跑通的 WDCNN 主干大致是这样输入(batch, 1, 2048)第一层宽卷积Conv1d(1, 16, kernel_size64, stride16, padding24)输出长度约(204848-64)/161 128形状(batch, 16, 128)。接着BatchNorm1d(16)ReLUMaxPool1d(2)变成(batch, 16, 64)。后面接三组Conv1d(16, 32, 3, padding1) BN ReLU Pool通道数依次 32、64、64长度依次 32、16、8。最后AdaptiveAvgPool1d(1)压成(batch, 64)接全连接Linear(64, num_classes)。这里有个容易翻车的点stride 和 padding 的配合。第一层 stride16 是为了快速降采样但 padding 必须算对否则边界信息丢失严重。我一般用padding (kernel_size - stride) // 2这个经验公式64 和 16 算出来是 24刚好让输出长度是输入的 1/16。import torch import torch.nn as nn class WDCNN(nn.Module): def __init__(self, num_classes10, in_channels1): super().__init__() # 第一层宽卷积大核捕捉冲击周期stride 快速降采样 self.feature nn.Sequential( nn.Conv1d(in_channels, 16, kernel_size64, stride16, padding24), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), # 后续用 3x1 小核堆深度逐步抽象 nn.Conv1d(16, 32, kernel_size3, padding1), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), nn.Conv1d(64, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool1d(1), ) self.classifier nn.Linear(64, num_classes) def forward(self, x): x self.feature(x) # (B, 64, 1) x x.flatten(1) # (B, 64) return self.classifier(x) # (B, num_classes)逻辑说明feature负责从原始波形抽特征classifier做分类。AdaptiveAvgPool1d(1)把任意长度的时序特征压成定长向量这样输入信号长度在一定范围内变化时网络不用改结构。参数说明kernel_size64是第一层宽核stride16控制降采样倍率padding24保证边界不丢num_classes按你的故障类别数改CWRU 十分类就填 10。3. 数据准备与训练从 CWRU 到能跑通的第一个 epoch3.1 振动信号分段、归一化与标签映射拿到 CWRU 的.mat文件后第一步是分段。常见做法是用滑动窗口窗长 2048 点步长 102450% 重叠。窗长不是随便定的采样率 12kHz 下2048 点约 0.17 秒足够覆盖一个轴承故障冲击周期又不至于把多个工况混进一个样本。步长太小会导致训练集和测试集高度相关准确率虚高步长太大样本数不够。50% 重叠是平衡点。归一化用 z-score按每个样本自己做不要用全局均值方差。因为不同负载下振动幅值差异很大全局归一化会把负载信息编码进数据模型可能学成“按负载分类”而不是“按故障分类”。每个样本减自己的均值除自己的标准差能消掉幅值差异保留波形形状。标签映射要固定顺序比如 0正常1内圈2外圈3滚动体后面按故障直径细分。映射表存成 json训练和推理共用避免类别顺序错乱导致准确率看着高其实全错。import numpy as np from scipy.io import loadmat from sklearn.model_selection import train_test_split def segment_signal(signal, window2048, step1024): # 滑动窗口切分返回 (N, window) 数组 segments [] for start in range(0, len(signal) - window 1, step): seg signal[start:start window] # 每个样本独立 z-score消除负载带来的幅值差异 seg (seg - seg.mean()) / (seg.std() 1e-8) segments.append(seg) return np.array(segments) def build_dataset(mat_path, label): data loadmat(mat_path) # CWRU 的 DE 通道键名通常是 X***_DE_time key [k for k in data.keys() if k.endswith(_DE_time)][0] signal data[key].ravel() segs segment_signal(signal) labels np.full(len(segs), label, dtypenp.int64) return segs, labels # 假设已按类别收集好 segs_list 和 labels_list # X np.concatenate(segs_list)[:, None, :] # (N, 1, 2048) # y np.concatenate(labels_list) # X_train, X_test, y_train, y_test train_test_split( # X, y, test_size0.3, stratifyy, random_state42)逻辑说明segment_signal做切分和逐样本归一化build_dataset从 mat 文件读 DE 通道并打标签。参数说明window2048是窗长step1024是步长test_size0.3是测试集比例stratifyy保证各类别比例一致。注意random_state固定否则每次跑结果不一样没法复现。3.2 训练循环、学习率与 batch size 的实操取值优化器用 Adam学习率 1e-3 起步配合ReduceLROnPlateau在验证损失不降时减半。batch size 用 64 或 128太小梯度噪声大太大泛化差。epoch 一般 50 到 100 足够CWRU 这种规模的数据集 30 个 epoch 就能收敛。损失函数用交叉熵如果类别不平衡正常样本远多于故障样本加weight参数按类别频率倒数加权。训练时记录训练损失和验证准确率验证准确率连续 10 个 epoch 不升就早停。import torch from torch.utils.data import TensorDataset, DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) model WDCNN(num_classes10).to(device) optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5) criterion torch.nn.CrossEntropyLoss() train_loader DataLoader( TensorDataset(torch.tensor(X_train, dtypetorch.float32), torch.tensor(y_train)), batch_size64, shuffleTrue) for epoch in range(60): model.train() total_loss 0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() total_loss loss.item() scheduler.step(total_loss) print(fepoch {epoch}, loss {total_loss:.4f}, flr {optimizer.param_groups[0][lr]:.6f})逻辑说明标准训练循环scheduler.step传训练损失驱动学习率衰减。参数说明lr1e-3是初始学习率factor0.5是每次衰减比例patience5是容忍多少个 epoch 不降batch_size64是批大小。如果显存够可以调到 128 加快训练。4. t-SNE 可视化判断模型是真学到故障还是偷懒4.1 提取哪一层特征做 t-SNE 最合适t-SNE 的输入是模型某一层输出的高维特征。选哪一层有讲究用最后一层全连接前的 64 维特征反映的是分类器看到的表示用倒数第二层卷积池化后的特征反映的是更原始的时序模式。我一般两个都看如果最后一层分得很开但卷积层混在一起说明分类器在硬分泛化可能有问题。提取特征时要把模型切到eval()模式关掉 dropout 和 BN 的训练行为否则同一批数据两次前向结果不一样t-SNE 图没法解释。用torch.no_grad()省显存。import torch from sklearn.manifold import TSNE import matplotlib.pyplot as plt model.eval() feats, labels_all [], [] with torch.no_grad(): for xb, yb in DataLoader( TensorDataset(torch.tensor(X_test, dtypetorch.float32), torch.tensor(y_test)), batch_size256): xb xb.to(device) # 取 AdaptiveAvgPool 之后的 64 维特征 f model.feature(xb).flatten(1) feats.append(f.cpu().numpy()) labels_all.append(yb.numpy()) feats np.concatenate(feats) labels_all np.concatenate(labels_all) # perplexity 一般取 5~50样本多取大一点 tsne TSNE(n_components2, perplexity30, random_state42, initpca, learning_rateauto) emb tsne.fit_transform(feats) plt.figure(figsize(8, 6)) for c in np.unique(labels_all): mask labels_all c plt.scatter(emb[mask, 0], emb[mask, 1], s8, labelstr(c)) plt.legend() plt.savefig(tsne.png, dpi150)逻辑说明先批量提特征再喂给 t-SNE 降维到二维最后按类别着色画散点。参数说明perplexity30控制局部与全局的平衡样本少几百就调到 5~10样本多几千可以到 50initpca比随机初始化稳定random_state固定保证图可复现。4.2 怎么读 t-SNE 图聚类、重叠与工况干扰一张健康的 t-SNE 图应该是同类样本聚成一团不同类之间有明显间隔。如果所有类混成一坨说明模型没学到判别性特征回去查数据归一化和学习率。如果类分开了但同一类内部又分成几簇很可能是工况负载、转速在起作用模型把同一故障在不同负载下当成了不同模式。这时候要做对照实验把测试集按负载分组分别画 t-SNE看同一故障在不同负载下是否落到不同簇。如果是说明模型对工况敏感解决办法是在训练时做工况增强或者用域适应方法。t-SNE 图不是拿来看好看的它是诊断模型问题的黑匣子。提示t-SNE 的坐标轴没有物理意义不要解读“样本在 x 轴上偏移代表什么”。只看相对距离和聚类结构。5. 避坑与排查那些让准确率虚高或直接翻车的细节5.1 数据泄漏训练集和测试集来自同一段信号现象测试准确率 99% 以上换一批数据掉到 60%。原因滑动窗口分段后随机划分同一段原始信号切出的相邻窗口分别进了训练集和测试集两者高度相关模型等于在背答案。解决按原始文件或按时间段划分确保训练集和测试集的信号来源不重叠。我一般按采集批次分一批做训练另一批做测试。5.2 归一化方式选错导致模型学工况而非故障现象t-SNE 图上同类故障按负载分成多簇准确率在不同负载间波动大。原因用了全局归一化负载信息被编码进幅值模型走捷径学负载。解决改成逐样本 z-score或者用更鲁棒的归一化中位数和四分位距。改完再画 t-SNE同类应该聚得更紧。5.3 第一层 stride 过大导致冲击特征被跳过现象训练损失降不下去准确率卡在随机水平附近。原因第一层 stride 设成 32 甚至更大宽卷积核滑过信号时直接跳过了冲击点特征提取层拿不到有效信息。解决stride 控制在 8~16配合 padding 保证输出长度合理。如果非要大 stride先把信号做抗混叠滤波。5.4 t-SNE 的 perplexity 设太小导致图不可信现象同一批特征跑两次 t-SNE聚类结构完全不一样。原因perplexity 太小比如 2t-SNE 过度关注局部结果不稳定。解决perplexity 至少设成 5样本多就往上调同时固定random_state和initpca。如果图还是不稳说明特征本身判别性不够回去查模型。5.5 类别不平衡时准确率是假指标现象正常样本占 90%模型全预测正常也有 90% 准确率。原因只看 accuracy忽略了少数类。解决看混淆矩阵和每类的召回率损失函数加类别权重或者用 F1 和 AUC 做模型选择。CWRU 十分类里正常样本通常不多但自采数据里正常占大头这个坑很常见。6. 进阶技巧用 t-SNE 做模型选型和域适应验证训练完一个 WDCNN 只是起点真正决定方案值不值得投入的是模型换工况后还能不能打。我一般会做两件事一是用 t-SNE 对比不同模型结构的特征分布二是用它验证域适应前后的效果。模型选型时把候选结构比如 WDCNN、普通 CNN、ResNet1D在同一测试集上的特征都提出来各画一张 t-SNE。哪个模型的类间间隔大、类内紧凑哪个就更可能泛化。这比只看准确率靠谱因为准确率会被类别不平衡和过拟合掩盖。我做过一组对比两个模型准确率都是 97%但 t-SNE 上一个类间有明显重叠换负载后那个模型掉了 15 个点另一个只掉 3 个点。域适应验证更直接源域比如负载 0训练目标域负载 3测试画目标域特征的 t-SNE。如果目标域样本按真实类别聚开了说明迁移成功如果混成一团或者按域聚说明域偏移没消掉。这时候可以试 CORAL、MMD 或者简单的对抗训练每改一次就画一次图肉眼判断比盯损失曲线直观。# 域适应前后对比同一目标域特征画两张 t-SNE def plot_tsne(feats, labels, title, ax): emb TSNE(n_components2, perplexity30, random_state42, initpca).fit_transform(feats) for c in np.unique(labels): mask labels c ax.scatter(emb[mask, 0], emb[mask, 1], s6, labelstr(c)) ax.set_title(title) fig, axes plt.subplots(1, 2, figsize(14, 6)) plot_tsne(feats_before, y_target, before adaptation, axes[0]) plot_tsne(feats_after, y_target, after adaptation, axes[1]) plt.savefig(domain_adapt.png, dpi150)逻辑说明把域适应前后的目标域特征并排画直接对比聚类结构。参数说明perplexity两张图必须一致否则没有可比性random_state也要一致保证降维的随机性不影响对比结论。一个具体技巧t-SNE 跑得慢样本多的时候先做 PCA 降到 50 维再喂给 t-SNE速度能快好几倍聚类结构基本不变。我一般超过 5000 个样本就这么干。另外t-SNE 图别只存 png把降维后的二维坐标和标签存成 csv后面想换配色、加标注、做子图都不用重跑省时间。血泪经验是别在训练中途频繁画 t-SNE特征还没稳定图变来变去只会干扰判断。等验证准确率收敛了再画一次看准。还有t-SNE 的随机性意味着同一批数据两次运行可能略有差异别因为一次图不好就否定整个模型多跑几个random_state看整体趋势。这套流程我踩过不少坑现在固定成“先看混淆矩阵再看 t-SNE最后换工况验证”三步基本不会误判。希望帮到你。本文还有配套的精品资源点击获取