简介:这份压缩包提供了一套基于卷积神经网络的轴承故障诊断完整工程源码,面向机械故障诊断、深度学习应用方向的开发者与研究人员,适合希望快速上手CNN振动信号分析的读者。项目以Python实现数据预处理、CNN模型构建与训练评估,核心脚本包括preprocess.py与main.py:前者负责采样率调整、滤波去噪与特征提取,后者完成模型搭建、优化器配置、训练循环与验证集测试集划分;同时配有wdcnn.png网络结构示意图和README环境配置说明,可复现从原始振动信号到故障分类的完整流程。包内共73个文件,主体为CWRU轴承数据集的.mat振动样本(按0HP至3HP不同负载组织),另含Python脚本、依赖清单requirement.txt、图片及Git版本记录等,压缩包整体87.89MB。数据分层清晰,便于按工况加载与训练;已有262人学习下载。通过该项目可掌握CNN在机械设备故障诊断中的落地思路,包括一维振动信号处理、模型参数配置与结果解读方法,是一套适合工程实践与教学演示的完整示例。
1. 别把轴承故障诊断当成图像分类:CNN 在这里到底处理什么
基于卷积神经网络的轴承故障诊断算法模型研究源代码.zip 光看名字就知道,这不是一个能直接点开的可视化软件,而是一整套算法研究工程:数据预处理、网络搭建、训练验证、结果评估全在里面。轴承故障诊断这件事,传统做法是靠人工从振动信号里抠特征,但特征选得好不好常常是一门玄学;换了轴承、换了转速,之前选的特征就可能失效。而卷积神经网络可以自动从振动波形里学习故障特征,把“内圈故障、外圈故障、滚动体故障、正常状态”分出来。这个压缩包适合三类人:要把毕业论文落地的研究生、想用数据驱动方案替换经验判断的设备工程师,以及需要一份基准代码做对比实验的研究员。它的价值不是让你一键得到 99% 准确率,而是把整个模型迭代路径交到你手里。
2. 拿到“算法模型研究源代码”前,先把输入输出和数据集套路摸清
如果你已经解压了 zip,先不要急着运行 train.py。第一步不是看模型,而是看数据从哪里读、标签是怎么挂上的。常见的研究型代码包里一般会有 data_loader.py、model.py、train.py、evaluate.py 这几个文件,有些还会附一段原始振动信号或提取好的特征文件。先花十分钟把 data_loader 读明白,后面能少踩很多坑。我习惯在解压之后顺手对源码做一次版本管理,哪怕只有本地 Git,也算给自己留后悔药。
这里有一个通用原则:卷积神经网络的输入必须是“样本-标签”的对应关系,轴承诊断里每个样本不是一整段时间序列,而是从连续振动信号里切出来的一小段窗口。所以你要先确认三件事:每个样本多长、每个窗口对应什么故障类别、训练集和测试集是怎么划分的。这三件事没搞清楚,后面训练到半夜才翻车就不奇怪了。
2.1 振动信号不是图片:三种常见输入形式与选型对比
很多入门者看到“卷积神经网络”就想到图像识别,于是把一维振动信号硬 reshape 成二维矩阵,再喂给二维 CNN。这里我可以直接告诉你:除非矩阵的行列有明确的物理含义,否则这个 reshape 会让卷积核学到一堆没有意义的组合。轴承诊断的主流做法有三种。
| 输入形式 | 数据结构 | 推荐网络 | 优点 | 缺点 | 常用场景 |
|---|---|---|---|---|---|
| 时域波形 | (1024, 1) | 1D CNN | 信息无损,预处理简单 | 对噪声敏感 | 通用诊断,最容易上手 |
| 频域幅值谱 | (513, 1) | 1D CNN | 特征集中,能突出故障特征频率 | 丢失相位信息 | 固定转速下的故障分类 |
| 时频图 | (频点数, 帧数, 1) | 2D CNN | 兼顾时间和频率 | 计算量大,样本维度高 | 变转速、变工况诊断 |
我一般会在代码里先保留“时域波形 + 1D CNN”这条基线,因为这个方案的数据准备最简单。先用一维卷积神经网络跑通,再根据效果决定要不要换频域或时频图。很多开源算法模型研究源代码也是这么组织的,先给一条 baseline,再对比改进方案。你可以在这个基础上加注意力、残差连接,但数据入口不要轻易换。
下面这段代码演示三种输入形式的数据形状,真实项目里直接加载 data_loader 的输出就行。
import numpy as np fs = 12000 # 常见实验台轴承数据的采样率 t = np.arange(1024) / fs # 模拟一段正常振动信号,实际工程中替换成 .mat / .csv 读取结果 signal = 0.2 * np.sin(2 * np.pi * 30 * t) + 0.01 * np.random.randn(1024) input_time = signal.reshape(1024, 1) # 时域波形,shape=(1024, 1) input_freq = np.abs(np.fft.rfft(signal)).reshape(-1, 1) # 幅值谱,shape=(513, 1)这段代码里,reshape(1024, 1)表示把 1024 个采样点作为一个样本,通道数为 1;np.fft.rfft对实数信号只取一半频点,得到 513 个幅值。注意频域输入在推理时也要做完全相同的变换,否则训练和测试的分布不一致,后面模型大概率翻车。
2.2 从原始振动信号构造训练样本:滑动窗口切片与标签生成
轴承振动数据往往是连续采样的,比如说一个文件里存了十万个点,对应轴承从正常到故障的全过程。不可能把十万个点直接塞进 CNN,因为卷积层的感受野有限,而且一个样本太大,训练数据量反而不够。常见做法是用滑动窗口切片,一个窗口就是一个样本。
def sliding_window_crop(signal, label, win_size=1024, stride=512): """把一段连续振动信号切成多个窗口样本。""" samples, labels = [], [] for start in range(0, len(signal) - win_size + 1, stride): samples.append(signal[start:start + win_size]) labels.append(label) return np.array(samples), np.array(labels) # 使用示例:每 512 点滑动一次,产生 1024 点样本 x = sliding_window_crop(continuous_signal, fault_label, win_size=1024, stride=512)参数说明:win_size是每个样本的点数,至少要覆盖一个转频周期。假设转速是 30 Hz,采样率 12 kHz,那么每转约 400 个点,win_size 取 1024 可以覆盖两圈以上,卷积核才有足够上下文去捕捉故障特征。stride是窗口移动步长,stride 越小样本越多,但相邻样本重叠度太高,会让测试集和训练集长得太像,造成结果虚高。我一般取 win_size 的一半到四分之一,既保证数据量,又不过度重叠。
2.3 标签与数据集划分:为什么测试集不能用同一轴承的连续片段
这是源码包里最常见的黑匣子问题:训练准确率接近满分,测试准确率也不错,但模型一到现场就不行。原因往往不是网络结构,而是数据划分时不严谨。同一个连续振动文件被切成几百个窗口后,如果随机打乱再划分训练集和测试集,测试集里会出现大量与训练集只有几个点偏移的相似窗口,模型等于见过“半道题”。
正确的做法是按“原始记录文件”分组,而不是按“窗口”分组。同一段连续信号的所有窗口只能同时归属训练集或测试集。下面是一个通用划分代码。
from sklearn.model_selection import train_test_split # 假设每个样本的 record_id 表示它来自哪一段连续信号 record_ids = np.array([sample['record_id'] for sample in sample_list]) unique_records = np.unique(record_ids) train_records, test_records = train_test_split( unique_records, test_size=0.2, random_state=42 ) # 根据 record_id 过滤窗口样本 is_train = np.isin(record_ids, train_records) x_train, y_train = x[is_train], y[is_train] x_test, y_test = x[~is_train], y[~is_train]这里关键点是先对unique_records做切分,再映射回所有窗口。np.isin用来判断每个样本到底属于哪个集合。random_state 固定下来,保证实验结果可以复现。这一条如果不做,后续所有准确率都值得怀疑。我一向把这段划分代码放在训练脚本最开始,并且每次跑实验前打印出训练集、测试集对应的 record_id 范围,避免自己也被数据遮蔽。
3. 搭建一维CNN模型:从卷积核到分组参数的落地实现
数据入口搞清楚了,下一步才是网络模型。如果代码包里自带 model.py,你要先看懂它用的是什么卷积架构。如果是你自己写,我建议直接用一维卷积神经网络起步,不要一上来就上残差、注意力这些花活。一维 CNN 在轴承故障诊断上已经很扎实,参数少,训练快,也更容易解释。深度学习卷积神经网络里,一维版本是最贴合振动信号时序结构的。
3.1 一维卷积如何吃进振动序列
一维卷积和二维卷积的区别只有一个:卷积核只在时间方向上滑动。输入形状是(batch_size, input_length, channels),其中 channels 通常取 1,代表振动加速度这一路信号。卷积核在时间轴上扫过,每个位置计算一次窗口内数值和权重的加权和,这其实就是在学习“局部波形模式”。比如滚动体通过外圈缺陷时会产生周期性冲击,一维卷积核如果能学到类似冲击的短波形,后面的池化层就能把这种模式保留下来。
下面是我常用的一个基础模型结构,覆盖了“卷积-池化-全局平均池化-全连接”这条主线。
import tensorflow as tf def build_bearing_cnn(input_length=1024, num_classes=4): inputs = tf.keras.Input(shape=(input_length, 1)) x = tf.keras.layers.Conv1D( filters=32, kernel_size=64, strides=2, padding='same', activation='relu' )(inputs) x = tf.keras.layers.MaxPooling1D(pool_size=2)(x) x = tf.keras.layers.Conv1D( filters=64, kernel_size=32, strides=2, padding='same', activation='relu' )(x) x = tf.keras.layers.GlobalAveragePooling1D()(x) x = tf.keras.layers.Dense(128, activation='relu')(x) outputs = tf.keras.layers.Dense(num_classes, activation='softmax')(x) return tf.keras.Model(inputs, outputs) model = build_bearing_cnn() model.summary()逻辑说明:第一层卷积用 32 个卷积核,每个核覆盖 64 个采样点,相当于在时域上抓取 64 点内的局部冲击;strides=2让特征图长度减半,类似下采样。第二次卷积用 64 个核,核宽 32 点,提取更抽象的模式。GlobalAveragePooling1D 把每个通道压成一个标量,减少全连接层参数,也能缓解过拟合。最后接 softmax 输出四个类别概率:正常、内圈故障、外圈故障、滚动体故障。
参数说明:kernel_size 不要照搬图像领域的 3 或 5。振动信号一个周期可能几百个点,卷积核太小只能看到毛刺,学不到完整冲击特征。我一般先从 32 或 64 开始,再根据故障特征频率调整。model.summary()打印出来的就是卷积神经网络结构图,一眼能看出参数量在哪个层爆掉。
3.2 模型代码里常见的关键参数
研究源代码里最值得读的不是网络层数,而是卷积层的几个超参数。下面这张表基本覆盖了你打开 model.py 时会看到的全部参数。
| 参数 | 常见取值 | 作用 | 调整优先级 |
|---|---|---|---|
| filters | 16~128 | 控制特征通道数 | 中,先固定再扩容 |
| kernel_size | 16~128 | 决定卷积核一次能看多长的波形 | 高,和转速强相关 |
| strides | 1~2 | 影响特征图长度和计算量 | 低,先固定为 1 或 2 |
| padding | same/valid | 决定边界如何处理 | 低,一般用 same |
| pool_size | 2 | 下采样,减少计算量 | 低,默认 2 |
| activation | relu | 激活函数 | 低,默认 relu |
| dropout | 0.3~0.5 | 正则化,防过拟合 | 中,过拟合时再调 |
我见过很多人一上来就把 filters 调到 128,以为模型越宽越准,结果训练集迅速过拟合,验证集 loss 在第 10 个 epoch 就反弹。正确顺序是先把 filters 设为 32,跑通基线,再慢慢加宽。kernel_size 反而值得先试验,因为它直接关系卷积核能不能匹配上故障冲击周期。
3.3 训练脚本的骨架:优化器、学习率、早停与模型保存
模型结构只是骨架,训练策略才是源代码里真正决定效果的部分。研究型模型代码一般会包含优化器设置、学习率、EarlyStopping 和模型保存。学习率我并不是无脑用默认的 1e-3,振动信号经过标准化后分布相对稳定,1e-3 通常没问题,但如果数据量很小,我会降到 3e-4。
model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) callbacks = [ tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=20, restore_best_weights=True ), tf.keras.callbacks.ModelCheckpoint( 'best_bearing_model.h5', monitor='val_accuracy', save_best_only=True ) ] history = model.fit( x_train, y_train, batch_size=64, epochs=100, validation_split=0.2, callbacks=callbacks )逻辑说明:validation_split=0.2是在训练集内部再切一块验证集,注意这里的切分仍然要基于 record_id,不能用默认的随机切。EarlyStopping监控 val_loss,连续 20 个 epoch 不改善就停,并且把权重回滚到验证集最优位置。ModelCheckpoint只保存验证集准确率最高的模型,防止最后一遍训练被过拟合毁掉。
如果你拿到的是 PyTorch 版本源码,思路完全等价,只是把 EarlyStopping 换成手动记录 loss、用torch.save存模型。重点不是框架,而是这份源码有没有保住验证集这一道防线。
4. 跑 CNN 轴承故障诊断代码时反复翻车的 5 个排查顺序
这一章直接给你避坑清单。每个问题我都按“现象 -> 原因 -> 解决”的顺序写,照着排查,比重新翻理论书有效。
4.1 现象:训练集准确率 99%,测试集准确率血崩
这是最典型的训练集、测试集分布不一致。原因主要有两个:一是模型确实过拟合,二是数据划分泄漏。如果训练集和测试集来自同一个连续信号的相邻窗口,测试集和训练集只有半个窗口的偏移,模型靠记忆噪声就能做对,根本谈不上泛化。解决方法是把 2.3 里按 record_id 划分的代码直接写死在 train.py 开头,任何情况下都不要再对窗口做随机 train_test_split。
再补充一个判断技巧:训练集准确率 99% 但测试集明显低于训练集,先打印出测试集所有样本的 record_id,看它们是否和训练集 record_id 有重叠。如果重叠,赶紧改划分代码,而不是去调模型。这是我在源码调试里见过最多、也最容易让人空耗一整天的问题。
4.2 现象:验证集 loss 先降后升,模型越训越“卷”
现象是训练集 loss 一直下降,验证集 loss 到第 20 个 epoch 开始反弹。原因是模型复杂度过高,数据量撑不住。轴承故障诊断的公开数据集通常只有几万到几十万个窗口,简单网络已经能拟合,复杂网络则开始记住训练集噪声。解决方法是先用 3.1 这个两层 CNN 跑通,确认有效后再加残差或注意力,并且加上 Dropout。
很多人把 Dropout 放在最后一个卷积层后面,但实际效果更好的位置是全局平均池化之后、全连接层之前。我在代码里习惯把 Dropout 放在最后一个卷积层之后、全连接之前,rate 设为 0.3。如果源码是 PyTorch 写的,用nn.Dropout(0.3)放在nn.Linear前面,语义是一样的。
4.3 现象:loss 不下降,先别怀疑选错了网络
loss 原地打转的原因里,网络结构选错的概率远低于下面三个:学习率太大导致震荡,输入没有归一化导致梯度不稳定,标签和损失函数不匹配。解决顺序是:先用.mean()和.std()检查输入,再做标准化;然后把学习率降到 1e-4 再试;最后确认 y_train 的取值是 0 到 num_classes-1 的整数。
# 标准化:只在训练集上计算均值方差,避免信息泄漏 mean = x_train.mean(axis=(1, 2), keepdims=True) std = x_train.std(axis=(1, 2), keepdims=True) + 1e-6 x_train = (x_train - mean) / std x_test = (x_test - mean) / std这里有个细节:std + 1e-6是防止某段信号完全平直导致除零。测试集必须沿用训练集的 mean 和 std,不能自己重新计算,否则模型输入的分布就变了。代码虽然短,却是源代码里最容易丢的一步。很多代码包里省了这一步,导致同样的网络在别人电脑上能跑到 90%,在自己电脑上只有 70%。
4.4 现象:训练到一半出现 NaN,问题不一定在梯度
NaN 通常出现在两个时刻:学习率设置太高,或者输入数据本身有问题。轴承振动信号如果采集时不干净,可能出现无穷大值或空值;标签里如果混入了 -1,softmax 交叉熵也得不到正常损失。解决时先跑一个数据断言,成本最低。
assert not np.isnan(x_train).any(), "输入包含 NaN" assert not np.isinf(x_train).any(), "输入包含 Inf" assert y_train.min() >= 0 and y_train.max() < num_classes, "标签越界"如果这三条全过了,再把学习率从 1e-3 降到 3e-4。对一维 CNN 来说,Adam 配合 3e-4 通常足够稳定。不要一开始就去找梯度裁剪,先在数据里找原因,数据比梯度更容易出问题。
4.5 现象:换一台设备的数据,模型几乎瞎猜
在公共数据集上训练得很好的模型,到了自己采集的轴承座上可能只剩三成准确率。原因很简单:不同设备、不同转速、不同负载下,故障特征频率整体偏移,模型学到的只是某个工况下的特例。这个问题的排查顺序是:先检查测试数据的采样率,再看测试现场的转速是否和训练数据接近;如果转速差异超过 20%,就要考虑把输入从时域波形换成频域幅值谱,或者加入转速归一化。更稳妥的做法是把模型当作特征提取器,冻结卷积层,在新工况上用少量带标签数据微调全连接层。这个思路在源码包里通常对应 fine-tune 脚本,如果没有,自己改也不难。
5. 跑通源码后再做一轮超参数实验:从哪里改起才不白费 GPU
代码能跑起来只是开始。你接下来要面对的是“如何提升准确率”,或者“如何说服导师和甲方这个模型可信”。这一章讲实验设计,不是让你去盲目调参。
5.1 先固定输入长度,再试卷积核大小:用消融实验记录结果
轴承振动诊断里,样本长度直接决定卷积核能看到多少周期。如果你把 1024 个点换成 2048 个点,第一层卷积核也会从局部特征变成多周期特征,那么之前的参数就要全部跟着变。所以我建议先把 input_length 固定为 1024,只对 kernel_size 做消融实验。
import itertools from collections import OrderedDict param_grid = OrderedDict({ 'input_length': [1024], 'kernel_size': [16, 32, 64, 128], 'learning_rate': [1e-3], 'batch_size': [64] }) results = [] for combo in itertools.product(*param_grid.values()): x = data_provider(combo[0]) model = build_bearing_cnn(input_length=combo[0], num_classes=4) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=combo[2]), loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) history = model.fit( x_train, y_train, batch_size=combo[3], epochs=30, validation_split=0.2, verbose=0 ) results.append({ 'kernel': combo[1], 'val_acc': max(history.history['val_accuracy']) }) print(results)这里itertools.product按顺序组合参数,每次只跑一个 kernel_size、一个 learning_rate。注意我没有同时改 batch_size,因为参数之间会互相干扰。结果列表里如果 kernel_size=64 的 val_acc 明显高于 16,说明故障冲击周期需要更大的感受野;如果差别不大,就选计算量更小的 16,减少训练时间。
5.2 学习率与 batch size 的联动:一个保守的调整顺序
学习率和 batch size 是强相关的两个参数。batch size 越大,梯度越平滑,学习率可以适当加大;batch size 越小,梯度噪声越大,学习率必须放小。如果同时调这两个参数,模型出一个问题你根本分不清是谁造成的。
我一般先固定 batch size=64,把学习率从 1e-3 往两个方向试:如果 loss 曲线像心电图上蹿下跳,就降到 3e-4;如果前 10 个 epoch 的 loss 变化慢得像蜗牛,再试 2e-3。确定好学习率后,再调 batch size。这个顺序能让你的超参实验只面对一个变量。
下面是一张可以参考的起点表,不保证对所有数据都最优,但能避免你从离谱的参数开始。
| 参数 | 稳妥起点 | 调整范围 | 判断标准 |
|---|---|---|---|
| learning_rate | 1e-3 | 1e-4 ~ 3e-3 | val_loss 是否平滑下降 |
| batch_size | 64 | 32 ~ 256 | val_acc 是否稳定 |
| kernel_size | 64 | 16 ~ 128 | 故障特征是否可见 |
| filters | 32 | 32 ~ 128 | 是否过拟合 |
| dropout | 0.3 | 0.0 ~ 0.5 | 训练/验证准确率差距是否过大 |
filters的调整被我排在最后,因为它只影响模型容量,不影响网络的感知能力。很多论文里会把 filters 作为第一个展示的消融变量,那是为了讲故事;工程落地时,先管好卷积核长度和学习率,收益更大。
5.3 用混淆矩阵而不是只看准确率判断模型有没有“偷看”
准确率这个指标在类别不平衡时几乎没有说服力。假设正常样本占 85%,模型把所有窗口都判成正常,准确率也有 85%,但故障样本一个没抓住。对于故障诊断,故障召回率比整体准确率更重要。你在源码评估部分一定要打印混淆矩阵。
from sklearn.metrics import confusion_matrix, classification_report y_pred = np.argmax(model.predict(x_test, verbose=0), axis=-1) cm = confusion_matrix(y_test, y_pred) print(classification_report( y_test, y_pred, target_names=['正常', '内圈故障', '外圈故障', '滚动体故障'] ))将模型预测转换为一维标签后,confusion_matrix的行是真实标签,列是预测标签。看这张表时不要只看对角线,还要看误分类方向:如果内圈故障样本被大量误判为外圈故障,说明两个类别的振动波形在时域上确实相似,这时应该考虑换用频域或时频输入,而不是无脑加卷积层。
6. 先别急着上线:用“留一段完整数据”验证模型是否真的可复用
超参实验结束、best 模型也选出来了,最后一步不是急着写报告,而是做一次连续信号的整段验证。这种做法在论文里很少出现,但工程落地时非常关键。
6.1 连续信号留出法:让模型面对没切过的整段数据
具体做法是:在采集数据时故意留出一段连续振动信号,这段信号从头到尾不参与滑动窗口切分,也不参与训练、验证、测试。模型训练完之后,把这段长信号按同样的滑动窗口切成一系列窗口,逐窗口预测,然后看整个时间轴上的预测结果是否稳定。
# full_signal: 一段长度超过 1 分钟的连续振动信号 pred_labels = [] for start in range(0, len(full_signal) - win_size + 1, stride): window = full_signal[start:start + win_size].reshape(1, win_size, 1) window = (window - mean) / std pred_labels.append(int(np.argmax(model.predict(window, verbose=0), axis=-1)[0])) from collections import Counter print(Counter(pred_labels).most_common())这段验证的意义和测试集完全不同。测试集即使按 record_id 划分,仍然是从已标记的文件里抽出来的,模型多少能“蹭”到同批次采集的环境噪声。而全段留出的信号是模型从没见过的连续数据,如果预测结果前后一致,说明模型学到的是故障特征而不是某批数据的底色。
6.2 预测结果不稳时,先改输入窗口而非网络结构
如果连续信号验证结果在几个故障类别之间来回横跳,说明单个窗口的信息量不够。这时先不要急着加深网络,优先把 win_size 从 1024 加到 2048,让每个样本覆盖更多个旋转周期;如果还不行,就把输入从时域波形换成频域幅值谱。频域输入能让卷积核直接看到故障特征频率的峰值,跨窗口稳定性通常会好很多。
我自己有一个不太光彩的教训:有一次在公开数据集上跑出了 99% 的准确率,自信满满地把模型拿去处理现场连续信号,结果预测结果在几个故障类别之间疯狂跳动。排查两天才发现,问题不是模型,而是我在数据划分时用了随机切分造成数据泄漏,模型实际学到的不是故障特征,而是同一段记录里噪声的连续性。从那以后,我再也不相信只在切好的测试集上测出来的“高分”。
你拿到这份基于卷积神经网络的研究源代码之后,别急着换网络结构,先把数据划分重构掉,再做一次连续信号验证。如果验证结果稳定,再去调模型参数,每一步效果都看得见。希望帮到你。
本文还有配套的精品资源,点击获取