ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于CWRU的滚动轴承故障诊断Python源码:从数据到CNN/DNN落地

基于CWRU的滚动轴承故障诊断Python源码:从数据到CNN/DNN落地 简介项目源代码包含基于深度神经网络DNN与卷积神经网络CNN的故障诊断模型实现配套CWRU轴承公开数据集可完成数据加载、特征提取、模型训练与诊断结果可视化。压缩包中共41个文件其中30个mat格式文件为CWRU轴承振动数据7个Markdown说明文档记录环境配置与使用步骤4个Python脚本覆盖模型训练与可视化整包约34.86MB目录中设有预处理数据模块与最后一层特征输出模块便于学习从原始振动信号到故障类别判别的完整流程。项目经导师指导并以98.5分的高分通过适合计算机相关专业学生作为毕业设计参考也可用于课程设计、期末大作业或项目实战练习。目前已有145人学习下载资源内附使用说明与项目结构介绍能够帮助读者快速复现实验并掌握深度学习方法在旋转机械故障诊断中的应用思路。1. 深度学习的滚动轴承故障诊断 Python 源码从 CWRU 原始数据到 CNN/DNN 落地做深度学习故障诊断的毕设最常遇到的尴尬是网上教程讲了半天的网络结构但真到动手时从下载 CWRU 原始振动数据到训练出一个像样的分类模型中间隔着数据切窗、标签映射、模型调参、特征可视化一整条流水线。这套 Python 源码项目正好把这条流水线完整走通了creat_data.py 负责把原始振动信号切成训练样本cnn.py 和 dnn.py 分别给出卷积和全连接两种模型路线plot_scatter.py 配合 last_layer_data 把网络最后一层的特征分布直接画成散点图。整套代码基于公开的 CWRU 轴承数据集覆盖了 1730、1750、1772 RPM 等不同工况是那种能直接照着跑完、再自己改着做实验的完整底稿。适合大四做毕设、做课程设计也适合刚入门深度学习、想找一个不绕弯子的实战项目来练手的人。这份源码包对计算机相关专业的学生尤其友好因为从数据处理到模型训练再到结果可视化的代码都拆成了单独文件老师问起哪一步都能翻出对应脚本讲清楚。2. 数据准备与预处理CWRU 振动信号怎么变成可训练样本拿到底层源码的第一件事不是看模型而是先弄清数据是怎么进到网络里的。CWRU 数据是 .mat 格式的振动加速度信号长度动辄几十万点绝不能整段扔给网络训练。creat_data.py 在这套代码里的位置就是数据入口它把原始信号切成固定长度的窗口再打上故障标签最后划分出训练集和测试集。这一步直接决定后面模型能不能收敛也是答辩时第一个会被追问的细节。2.1 先读懂 CWRU 数据转速负载与故障类别CWRU 数据集是滚动轴承故障诊断领域最常用的公开数据故障主要分三类内圈故障IR、外圈故障OR、滚动体故障B再加上正常状态Normal一共四类。每类故障又按损伤直径细分为 0.007、0.014、0.021 英寸等规格。项目里选的是驱动端加速度信号也就是 .mat 文件里的 DE 字段。整份数据里你会看到 1730、1750、1772 这几个数字它们不是随意取的而是不同负载下对应的电机转速。CWRU 的负载分成四档对应关系如下负载 (HP)近似转速 (RPM)01797117722175031730所以项目里出现 1772 RPM说明是 1 HP 负载下的数据1750 对应 2 HP1730 对应 3 HP。很多人第一次下载数据时把所有文件全塞进去训练结果不同负载的数据混在一起模型学的是负载差异而不是故障差异准确率看着很高但换个工况就崩了。正确处理方式是先选定一个负载作为主要实验条件其他负载留作泛化测试。读取这些 .mat 文件用 scipy.io 就可以了不过一定先打印 keys 确认字段名不同副本的数据字段名不完全一样。import scipy.io as sio import numpy as np mat sio.loadmat(178_0.007-IR.mat) # 示例文件名实际按你解压后的路径写 print(mat.keys()) # 确认里面有 DE 还是 DE_time signal np.ravel(mat[DE]) # 统一转成一维逻辑说明loadmat 读出来的是一个字典键名在 CWRU 官方数据里有DE、FE、BA等DE是驱动端加速度计的信号。np.ravel这一步很关键因为有的 .mat 文件存成行向量有的存成列向量不统一后面切窗时会出现维度错误。2.2 creat_data.py 核心逻辑滑窗切片与标签生成原始信号太长训练深度学习模型不能直接输入常见做法是用滑动窗口截取一段一段的时域波形。窗口长度一般取 1024 或 2048 个采样点对应 12 kHz 采样率下约 0.085 到 0.17 秒的信号。creat_data.py 的核心就是这个窗口滑动过程。def creat_samples(signal, window_size1024, stride128, label0): samples [] for start in range(0, len(signal) - window_size, stride): samples.append(signal[start:start window_size]) labels [label] * len(samples) return np.array(samples), np.array(labels)参数说明window_size决定模型看多长的波形1024 是 CWRU 项目里很常用的配置stride是窗口滑动的步长步长越小样本越多但相邻窗口的重叠也越大。这里的label是当前信号对应的故障类别编号外层调用时按文件列表传入。如果你想让模型更稳定可以把窗口长度加到 2048但网络输入尺寸也要同步改不然 CNN 里的全连接层维度会对不上。有了单个文件切窗函数再把它扩展到多文件数据集。def build_dataset(file_list, window_size1024, stride128, train_ratio0.8): X, y [], [] for label, file_path in enumerate(file_list): signal np.ravel(sio.loadmat(file_path)[DE]) samples, labels creat_samples(signal, window_size, stride, label) X.extend(samples) y.extend(labels) X np.array(X, dtypenp.float32) y np.array(y) # 按样本自身做 z-score 归一化不用全局统计量 mean X.mean(axis-1, keepdimsTrue) std X.std(axis-1, keepdimsTrue) 1e-8 X (X - mean) / std idx np.random.permutation(len(X)) split int(len(X) * train_ratio) return X[idx[:split]], y[idx[:split]], X[idx[split:]], y[idx[split:]]逻辑说明这里先对所有文件按顺序编号然后切窗、拼装、归一化、随机打乱。归一化用的是每个样本自己的均值和标准差而不是整个数据集的全局均值因为实际部署时一个样本是独立进入模型的用全局统计量会让测试阶段对分布偏移特别敏感。1e-8是防止某一段信号完全平稳导致标准差为 0。2.3 最容易翻车的划分方式按样本随机还是按文件随机上面代码里用的是全局随机打乱样本这在多数教程里能跑通但严格来说有数据泄漏的风险。因为滑动窗口的步长只有 128相邻窗口之间只差 128 个点窗口重叠率超过 87%。如果这些高度相似的窗口同时出现在训练集和测试集测试准确率会虚高好几个点答辩时老师问到这个问题会很难解释。我一般会在自己的项目里改成按文件划分每个 .mat 文件切出来的所有样本要么全进训练集要么全进测试集。这样测试集是“没见过的那段信号”结果才更可信。# 按文件划分而不是按样本划分 train_files file_list[:int(len(file_list) * train_ratio)] test_files file_list[int(len(file_list) * train_ratio):] def load_samples(files, window_size1024, stride128): X, y [], [] for label, path in enumerate(files): signal np.ravel(sio.loadmat(path)[DE]) samples, labels creat_samples(signal, window_size, stride, label) X.extend(samples) y.extend(labels) return np.array(X, dtypenp.float32), np.array(y) X_train, y_train load_samples(train_files) X_test, y_test load_samples(test_files)参数说明train_ratio取 0.8 或 0.7 都可以但要注意每个类别内部也要按这个比例拆不能所有文件混在一起切否则可能出现某一类故障只在测试集里有。按文件划分后样本量会小于按样本随机切的方式但对故障诊断这种周期性信号测试集仍然有足够的窗口数量。3. CNN 与 DNN 模型实现cnn.py 和 dnn.py 的关键设计数据变成样本之后下一个核心文件就是模型。项目里给了两条完整路线cnn.py 用一维卷积处理振动信号dnn.py 用全连接网络做基线对照。两个模型都封装了构建函数输入都是 (样本数, 1024, 1) 这样的三维结构方便复用。3.1 cnn.py 网络结构一维卷积如何处理振动序列振动信号是典型的一维时间序列所以这里用 Conv1D 而不是 Conv2D。下面的结构是基于 Keras/TensorFlow 的常见写法也是适合 CWRU 量级数据的浅层 CNN。import tensorflow as tf from tensorflow.keras import layers def build_cnn(input_shape(1024, 1), num_classes4): model tf.keras.Sequential([ layers.Conv1D(16, kernel_size32, strides2, paddingsame, activationrelu, input_shapeinput_shape), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2, strides2), layers.Conv1D(32, kernel_size16, strides2, paddingsame, activationrelu), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2, strides2), layers.Conv1D(64, kernel_size8, strides2, paddingsame, activationrelu), layers.GlobalAveragePooling1D(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax) ]) return model逻辑说明第一层卷积核大小为 32覆盖 32 个连续采样点近似能捕捉一个短周期内的局部冲击形态。stride2 让序列长度快速减半减少计算量。BatchNormalization 放在卷积和激活之间能有效避免训练中后期梯度消失。最后用 GlobalAveragePooling1D 代替 Flatten好处是不用手动计算池化后的特征长度修改输入窗口大小也不会报维度错误。Dropout 0.5 是为了抑制过拟合因为 CWRU 信号样本之间本身存在很高的相似性。这个结构在 CWRU 三分类或四分类任务里一般十几轮就能到 95% 以上的训练准确率。如果你发现准确率上不去优先检查是不是数据归一化出了问题而不是急着加层数。3.2 dnn.py 基线模型全连接网络做对照的意义论文里不能只有一个模型这是很多毕设评审的基本要求。dnn.py 提供的全连接网络就是用来和 CNN 做对照的基线结构更简单但能说明“为什么需要卷积”。def build_dnn(input_shape(1024,), num_classes4): model tf.keras.Sequential([ layers.Input(shapeinput_shape), layers.Dense(512, activationrelu), layers.Dropout(0.3), layers.Dense(256, activationrelu), layers.Dropout(0.3), layers.Dense(128, activationrelu), layers.Dense(num_classes, activationsoftmax) ]) return model参数说明DNN 的输入是一维的 1024 点信号没有卷积那种局部连接和平移不变性所以参数非常多。第一层 Dense(512) 就要 1024×512 个参数训练起来很容易过拟合因此每一层后面都跟 Dropout。在同样的数据划分下DNN 准确率通常会比 CNN 低尤其在处理局部冲击特征时差距明显这正是论文里最直观的对比结论。如果你想把对照做得更扎实可以在同一份训练代码里交替训练 cnn.py 和 dnn.py记录各自在测试集上的准确率和 loss最后画一张折线图。3.3 训练配置学习率、batch、epoch 怎么设置才靠谱模型结构搭好了训练参数不能随手抄。CWRU 切窗后的样本量一般有几万个epoch 不必设太多否则模型会记住窗口里的噪声。model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losssparse_categorical_crossentropy, metrics[accuracy] ) callbacks [ tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-6 ), tf.keras.callbacks.EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) ] history model.fit( x_train, y_train, batch_size64, epochs50, validation_data(x_val, y_val), callbackscallbacks, verbose1 )参数说明学习率 1e-3 是 Adam 的常用起点如果 loss 出现振荡就降到 3e-4。batch_size 取 64 比较平衡太小收敛慢太大容易跑进平坦区。EarlyStopping 的 patience10 表示连续 10 轮验证 loss 不下降就停止并恢复历史最优权重。这里补一句经验不要只看训练准确率训练准确率在 CWRU 上很容易冲到 99%验证集准确率才有参考价值。如果验证准确率和训练准确率差距超过 5 个百分点就要考虑调大 dropout 或减小模型容量。4. 特征可视化plot_scatter.py 与 last_layer_data 的配合模型训练完不能只给一个准确率数字。毕设答辩时老师几乎百分百会问“你怎么证明模型学到了有用的特征”。这时候 project 里的 last_layer_data 和 plot_scatter.py 就派上用场了把特征抽取出来再用 t-SNE 画出分布图让类别簇肉眼可见。4.1 last_layer_data把最后一层特征向量导出的正确姿势深度学习模型的 softmax 层输出的是类别概率但真正能表征故障的特征在 softmax 之前的那一层全连接。last_layer_data 的核心就是截断网络取出倒数第二层的输出。# 假设 model 是已经训练好的 cnn 模型倒数第二层是 Dense(128) feature_extractor tf.keras.Model( inputsmodel.input, outputsmodel.get_layer(index-2).output ) train_features feature_extractor.predict(X_train) test_features feature_extractor.predict(X_test) print(train_features.shape) # (样本数, 128)逻辑说明通过tf.keras.Model重建一个从原始输入到指定层输出的新模型index-2指向 softmax 前一层的 Dense(128)。如果你改了网络结构最好用层名来取比如给 Dense 层设置namefeature_dense然后model.get_layer(feature_dense).output避免 index 对不上。提取出来的特征维度是 (样本数, 128)因为可视化要画二维图所以接下来需要降维。4.2 plot_scatter.py用 t-SNE 画散点图一眼看出可分性t-SNE 是一种非线性的降维方法能把高维特征映射到二维平面同时尽量保留样本间的近邻关系。故障诊断里最常拿它看不同类别的聚类效果。from sklearn.manifold import TSNE import matplotlib.pyplot as plt tsne TSNE(n_components2, perplexity30, random_state42, n_iter1000) feat_2d tsne.fit_transform(test_features) plt.figure(figsize(8, 6)) scatter plt.scatter(feat_2d[:, 0], feat_2d[:, 1], cy_test, cmaptab10, s8) plt.colorbar(scatter) plt.xlabel(t-SNE dimension 1) plt.ylabel(t-SNE dimension 2) plt.savefig(features_tsne.png, dpi300) plt.show()参数说明perplexity控制每个点在降维时考虑多少近邻样本量大时取 30 左右比较合适样本量小可以降到 5~10。random_state42必须固定否则每次跑图都不一样。n_iter1000是迭代次数少于 500 容易得到没有意义的局部最优。如果散点图里每个故障类型都聚成独立的簇说明模型提取的特征是有效的如果几个类别纠缠在一起先不要急着调网络检查是不是标签映射错了或者训练收敛不充分。4.3 可视化结果怎么放进论文里这份项目的 README 里也强调过plot_scatter.py 的结果图建议同时放两张一张用训练集特征画一张用测试集特征画。训练集特征图能看出模型有没有把数据硬背下来测试集特征图才是泛化能力的证据。论文里配图的说明文字不要只写“t-SNE 图”要写“不同故障类型在最后一层特征空间中的分布”并且指出每类颜色簇是否紧凑、类别之间是否有重叠。答辩的时候这张图比准确率表格更能让老师信服。另外也可以画混淆矩阵这个不在项目初始文件里但自己加几行代码很容易。下面这段可以放在训练完之后from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay y_pred model.predict(X_test).argmax(axis1) cm confusion_matrix(y_test, y_pred) disp ConfusionMatrixDisplay(cm, display_labels[Normal, IR, OR, B]) disp.plot() plt.savefig(confusion_matrix.png, dpi300)说明混淆矩阵里对角线越亮越好如果某一类被大量分错要去看对应训练样本是不是太少或者两个故障在振动特征上确实很接近。5. 避坑与常见问题跑这个项目最容易翻车的 5 个地方这套源码整体流程清晰但初学者在复现时最容易在数据读取、标签映射、训练复现性这些细节上栽跟头。我把自己跑类似 CWRU 项目时踩过的坑列出来每一条都是“现象 → 原因 → 解决”的结构希望能帮你省下那些通宵排错的时间。5.1 坑一loadmat 读出的信号维度不对切窗时直接报错现象运行 creat_data.py 时提示could not broadcast input array from shape (1,n) into shape (n,)或者模型输入维度不匹配。原因CWRU 的 .mat 文件里有的字段是行向量 (1, 102400)有的是列向量 (102400, 1)直接signal[start:startwindow_size]切片出来是二维数组后续拼接时会变成奇怪的形状。解决读取后用np.ravel统一压缩成一维数组。这是我在读数据阶段必写的一步同时打印一下字段名避免拿到的是DE_time而不是DE。5.2 坑二按样本随机划分导致测试准确率虚高现象训练准确率和验证准确率都接近 99%但把同一个模型放到另一段新信号上测试准确率掉到 85% 以下。原因滑动窗口重叠率太高按样本随机划分时训练集和测试集里有大量来自同一段连续信号的窗口模型实质上“记住”了波形而不是学到了故障特征。解决改成按文件划分一个 .mat 文件切出的所有窗口只进入训练集或测试集。同时把步长调大一些比如从 128 改成 256降低样本间的相关性。这个改动会让测试准确率看起来降两三个点但才是真实水平。5.3 坑三标签顺序和类别对应错乱散点图上一片混乱现象t-SNE 散点图上同一个类别出现多个分离的簇但准确率却不低。原因creat_data 使用enumerate(file_list)给所有.mat文件按列表顺序编号但文件列表顺序和类别真值没有显式对应。比如内圈故障文件排在最前面标签 0 却对应正常状态模型学到了另一种排列方式。解决用显式字典定义类别映射不要依赖列表顺序。label_map { Normal: 0, IR: 1, OR: 2, B: 3 }在给文件打标签时先判断文件名里包含哪个关键词再从这个字典取值。这样即使文件排序变化标签也不会错。5.4 坑四训练时 loss 突然变成 NaN现象前几个 epoch loss 正常下降到了某个 epoch 后 loss 变成 nan准确率瞬间归零。原因最常见的是学习率偏大Adam 在 1e-3 时有概率出现梯度爆炸其次是输入数据里有 NaN 或数值范围过大没有转成 float32。解决先排查数据print(np.isnan(X_train).sum())如果数据没问题就把learning_rate降到 3e-4同时在每个卷积层后保留 BatchNormalization。另外注意 CWRU 原始信号是整数类型切窗后要转成np.float32再做归一化否则均值减完后数据溢出的风险很高。5.5 坑五没有固定随机种子结果无法复现现象同一份代码连续跑三次准确率分别是 96.5%、97.8%、96.2%每次都不同。原因numpy 的随机打乱和 TensorFlow 的权重初始化都有各自的随机源不固定就会导致实验结果不可复现。这在毕业设计里是比较致命的因为你写进论文的结果如果别人复现不了答辩压力会很大。解决在训练脚本最前面统一固定全部随机源。import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)注意如果你的数据生成阶段也用了随机要在 creat_data.py 里也固定一次因为np.random.permutation会消耗随机序列。固定种子后至少在相同环境下结果能稳定复现。6. 进阶技巧把毕设项目改造成带完整实验对比的成果基础流程跑通后如果想让项目从“能跑”变成“能答辩”我建议在这个源码基础上加三个实验点。它们都不需要大规模改代码但能把工作量显著撑起来。先加混淆矩阵和分类报告直接把 sklearn 的classification_report输出到控制台然后分析每一类的精确率和召回率。CWRU 四分类里滚动体故障通常最容易和外圈故障混淆如果你在报告里看到某类召回率明显偏低可以补充说明“该故障的振动冲击模式与另一类相近”这比干巴巴地贴一张准确率更有深度。再加一个故障直径细粒度分类。原始 CWRU 数据里有 0.007、0.014、0.021 英寸三种损伤直径把它们作为额外标签相当于把原来的 4 类变成 10 类左右模型难度明显增加。你可以只保留内圈故障的三个直径加正常状态做 4 分类验证模型能否区分故障严重程度。做法很简单在creat_samples传入的 label 里把直径信息编码进去代码逻辑不用动。最后做跨负载迁移实验这是最有说服力的一步。用 1772 RPM1 HP的数据训练用 1730 RPM3 HP的数据测试。如果准确率下降明显说明模型依赖负载相关的幅值特征如果你在预处理时把每个样本单独归一化跨负载的差距通常会缩小。实验结论可以写成“模型在未知工况下准确率为 89.5%表明提取的特征具有一定跨工况鲁棒性”。我之前做这个项目时第一次同时把三个直径和四种负载的数据一股脑全塞进去结果散点图上游五个簇最后发现是标签顺序错乱。后来我养成了固定种子、按文件划分、显式映射标签这三个习惯才把结果稳定下来。从那以后我每次跑故障诊断实验都强制走一遍先看数据划分再看标签字典最后固定随机种子。希望这些经验能帮你少走几步弯路。本文还有配套的精品资源点击获取
返回列表