ARTICLE DETAIL

资讯详情

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

CWRU轴承故障诊断毕设实战:从.mat解析到Grad-CAM可视化

CWRU轴承故障诊断毕设实战:从.mat解析到Grad-CAM可视化 简介本资源是一套面向本科毕业设计与工程实践的深度学习故障诊断完整项目聚焦滚动轴承振动信号分析与智能诊断适用于自动化、机械、人工智能方向的学生及初阶工程师。项目提供从数据采集、预处理、模型构建含CNN、DNN等典型网络、训练调优到结果可视化的一站式实现方案覆盖CWRU轴承数据集1730/1750/1772 RPM工况全流程。压缩包共41个文件含30个MATLAB格式原始与处理后振动数据、6个Markdown文档含README、使用说明、实验记录、4个核心Python脚本cnn.py、dnn.py、pre_data.py、plot_scatter.py及1个文本说明总大小34.88MB结构清晰、模块解耦便于复现与二次开发。目前已有240人学习下载读者可直接运行源码、加载全部数据、复现诊断流程并基于现有框架拓展其他故障类型或模型结构。1. 这不是又一个“CNN轴承数据集”的套壳项目它把CWRU原始振动信号从.mat硬解到.npy、用滑动窗切出1024点样本、在DNN/CNN双模型上跑通完整训练-验证-测试闭环还附带故障类型热力图可视化——适合本科毕设答辩前两周才开始动手、但想稳过且能讲清每行代码逻辑的同学我带过三届毕业设计见过太多同学在“基于深度学习的轴承故障诊断”标题下交出一份从GitHub抄来的、连loadmat()路径都没改对的代码。这个资源不是那种——它压缩包里有6个明确命名的.py文件cnn.py,dnn.py,creat_data.py,pre_data.py,plot_scatter.py,last_layer_data.py两个README.md一份使用说明.txt以及完整的CWRU数据集含1750RPM/1730RPM/1772RPM三工况。最关键是所有脚本都按“数据准备→预处理→建模→训练→可视化”流水线组织变量名不缩写train_X,val_y,fault_labels注释覆盖关键参数含义比如window_size1024, step512为什么这么设连plot_scatter.py里画t-SNE降维图时用的perplexity30都写了依据。如果你正卡在“数据怎么读进来”“标签怎么对齐”“验证集准确率死活上不去”这三个毕设高频翻车点这份源码就是你最后七天能真正跑通、能现场演示、能对着答辩PPT逐页解释的技术底稿。2. 数据加载与预处理从CWRU官网下载的.mat文件到可喂入模型的numpy数组中间必须跨过三个硬坎CWRUCase Western Reserve University轴承数据集是工业故障诊断领域的“Hello World”但它的原始.mat文件结构对新手极不友好不是直接存data和label字段而是嵌套在bearing结构体里采样率、通道数、故障尺寸全靠手动查表。这个项目用creat_data.py和pre_data.py把整个流程拧成螺丝钉——不是封装成黑匣子而是每一步都暴露给你看。2.1 解析CWRU原始.mat文件避开scipy.io.loadmat的字段陷阱CWRU官网提供的.mat文件如1750RPM/Normal_1.mat在MATLAB中是结构体但用Python的scipy.io.loadmat读出来会变成dict且字段名带__前缀如__header__,__version__真实数据藏在X097_这类命名怪异的key里。creat_data.py没走捷径而是用h5py重读——因为新版CWRU数据已转为.matv7.3格式本质是HDF5scipy.io.loadmat对此支持不稳定。# creat_data.py 关键片段 import h5py import numpy as np def load_mat_h5(filepath): 用h5py安全读取CWRU v7.3 .mat文件避免scipy.loadmat字段丢失 with h5py.File(filepath, r) as f: # CWRU数据实际存于X097_等key下且是列向量需转置 data_key [k for k in f.keys() if k.startswith(X)][0] raw_data f[data_key][:].T.flatten() # .T解决维度错位flatten压平 return raw_data提示f[data_key][:]读出的是(1, N)二维数组直接.flatten()会保留冗余维度.T.flatten()先转置成(N, 1)再压平才能得到纯一维振动序列。这是CWRU数据解析第一坑——很多教程漏掉.T导致后续FFT频谱全乱。2.2 滑动窗切片与标签对齐1024点窗口512步长的物理意义滚动轴承故障特征频率如内圈故障BPFI通常在数百Hz而CWRU采样率是12kHz。要捕获一个完整故障冲击周期至少需10ms数据120点但CNN需要足够空间提取局部模式。项目选window_size102485ms、step51242.5ms既保证单窗含多个冲击周期又通过重叠避免漏判。# pre_data.py 中的切片逻辑 def sliding_window(data, window_size1024, step512): 按步长切片返回 (n_samples, window_size) 数组 n_samples (len(data) - window_size) // step 1 windows np.zeros((n_samples, window_size)) for i in range(n_samples): start i * step windows[i] data[start:start window_size] return windows # 标签生成每个窗口对应中心点位置的故障状态 def generate_labels(fault_type, n_windows, window_size, step): 标签不是按文件分而是按窗口中心点物理位置映射 # CWRU标注文件给出故障起始时间戳秒转换为样本索引 start_idx int(START_TIME_SEC * SAMPLING_RATE) # 如START_TIME_SEC1.0 → 12000 labels np.zeros(n_windows) for i in range(n_windows): center_idx i * step window_size // 2 labels[i] 1 if center_idx start_idx else 0 # 二分类示意多分类同理 return labels注意generate_labels函数里的center_idx是核心——故障标签必须绑定到窗口中心点对应的物理时刻而非窗口起始点。否则在故障起始边缘区域一半窗在正常区、一半在故障区标签就失真了。这是毕设答辩常被问倒的问题“你标签是怎么打的为什么不是按文件分”。2.3 归一化与频域增强为什么只做min-max归一化不用Z-score轴承振动信号幅值随负载变化极大同一故障在不同转速下振幅可能差5倍。项目在pre_data.py中统一用MinMaxScaler(feature_range(0,1))而非StandardScalerfrom sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_X scaler.fit_transform(train_X.reshape(-1, 1)).reshape(train_X.shape) val_X scaler.transform(val_X.reshape(-1, 1)).reshape(val_X.shape)原因很实在StandardScaler依赖均值和方差而轴承正常状态数据量远大于故障数据CWRU正常样本约50万点单故障仅5万点均值会被正常数据主导导致故障样本归一化后数值坍缩。MinMaxScaler只认全局最大最小值对长尾分布更鲁棒。实测中用StandardScaler时CNN最后一层激活值普遍0.1而MinMaxScaler能保持0.3~0.8的健康范围——这直接影响梯度传播。3. DNN与CNN双模型实现不是堆砌层数而是针对振动信号特性做结构裁剪项目提供dnn.py和cnn.py两个独立训练脚本不是为了炫技而是让毕设答辩时能对比回答“为什么用CNNDNN不行吗”——答案就藏在模型结构里。3.1 DNN模型三层全连接Dropout专治时域统计特征dnn.py不直接喂原始波形而是先手工提取12维时域特征均值、方差、峭度、裕度等再送入DNN# dnn.py 特征工程部分 def time_domain_features(x): 提取12维时域统计特征 features [] features.append(np.mean(x)) # 均值 features.append(np.std(x)) # 标准差 features.append(pd.Series(x).kurtosis()) # 峭度冲击性 features.append(np.max(np.abs(x)) / np.mean(np.abs(x))) # 裕度 features.append(np.sqrt(np.mean(x**2))) # 有效值 # ... 其他7维偏度、脉冲因子、形状因子等 return np.array(features) # DNN结构输入12维 → 64 → 32 → num_classes model Sequential([ Dense(64, activationrelu, input_shape(12,)), Dropout(0.3), Dense(32, activationrelu), Dropout(0.3), Dense(num_classes, activationsoftmax) ])为什么选12维CWRU论文《Bearing Fault Detection via Statistical Features》证实这12个指标对内圈/外圈/滚动体故障区分度最高。比直接喂波形的DNN收敛快3倍且测试集F1-score稳定在0.92±0.01三折交叉验证。3.2 CNN模型一维卷积核尺寸32不是拍脑袋定的cnn.py用1D-CNN处理原始波形1024点但卷积核尺寸kernel_size32有明确物理依据# cnn.py 模型定义 model Sequential([ Conv1D(filters32, kernel_size32, activationrelu, input_shape(1024, 1)), MaxPooling1D(pool_size2), Conv1D(filters64, kernel_size16, activationrelu), MaxPooling1D(pool_size2), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(num_classes, activationsoftmax) ])kernel_size32对应2.67ms32/12000恰好覆盖轴承单次冲击持续时间实测CWRU故障冲击宽度2~3ms第二层kernel_size161.33ms捕捉冲击上升沿细节pool_size2每次降维一半最终Flatten()输出维度为64 * 256 16384远小于原始1024*10241048576避免过拟合。玄学经验如果把kernel_size设成645.33ms模型在训练集准确率99%但验证集暴跌到72%——因为过大的卷积核会模糊冲击边界把正常波动也当成故障特征。3.3 标签编码与损失函数多分类必须用CategoricalCrossentropy别用SparseCWRU数据含10类故障正常9种故障位置/尺寸组合项目严格使用to_categorical编码from tensorflow.keras.utils import to_categorical # train_y 是 [0,1,2,...,9] 的整数标签 train_y_cat to_categorical(train_y, num_classes10) # 变成 (n,10) one-hot model.compile( optimizeradam, losscategorical_crossentropy, # 关键不是sparse_categorical_crossentropy metrics[accuracy] )血泪经验曾见同学用sparse_categorical_crossentropy却喂one-hot标签训练loss恒为nan——因为前者要求标签是整数后者要求one-hot。to_categorical后必须配categorical_crossentropy这是毕设调试阶段最隐蔽的报错源。4. 训练调优与验证不靠运气调参用早停学习率衰减混淆矩阵三板斧毕设最怕“调了三天参数准确率还是85%”。这个项目把训练流程固化成可复现的脚本关键参数全写死在cnn.py顶部且附带README.md里的调参日志。4.1 早停机制monitorval_losspatience15restore_best_weightsTrue# cnn.py 训练部分 callbacks [ EarlyStopping( monitorval_loss, # 监控验证损失非accacc可能震荡 patience15, # 连续15轮不下降才停 restore_best_weightsTrue # 自动载入最优权重不用手动save/load ), ReduceLROnPlateau( monitorval_loss, factor0.5, # loss平台期时lr减半 patience5, min_lr1e-7 ) ] history model.fit( train_X, train_y_cat, validation_data(val_X, val_y_cat), epochs200, batch_size64, callbackscallbacks, verbose1 )为什么monitor用val_loss不用val_acc因为CWRU数据存在类别不平衡正常样本占30%某故障仅5%acc容易虚高。val_loss下降更真实反映模型泛化能力提升。4.2 混淆矩阵可视化plot_scatter.py不只是画图它暴露模型弱点plot_scatter.py用t-SNE将CNN最后一层全连接层输出128维降维到2D并按真实标签着色# plot_scatter.py 关键逻辑 from sklearn.manifold import TSNE # 提取CNN最后一层输出去掉softmax取Dense层激活值 layer_outputs model.layers[-2].output # 倒数第二层Dense(128) feature_extractor Model(inputsmodel.input, outputslayer_outputs) features feature_extractor.predict(test_X) # t-SNE降维 tsne TSNE(n_components2, perplexity30, random_state42) features_2d tsne.fit_transform(features) # 绘制散点图颜色真实标签形状预测标签 plt.scatter(features_2d[:,0], features_2d[:,1], ctest_y, cmaptab10, alpha0.7) plt.colorbar() plt.title(t-SNE of CNN Last Layer Features) plt.show()perplexity30怎么来的t-SNE的perplexity控制邻域大小。CWRU共10类每类样本约2000个perplexity应设为样本数的平方根量级√2000≈45但实测30时类间分离最清晰——这是项目作者调参笔记里写的不是默认值。4.3 避坑常见问题与排查现象→原因→解决现象1训练loss下降但val_loss不降甚至上升原因train_X和val_X用了不同Scalerfit_transformvstransform没分开解决pre_data.py中确保scaler.fit_transform(train_X)后val_X必须用scaler.transform(val_X)绝不能对验证集单独fit_transform。现象2plot_scatter.py报错ValueError: Expected 2D array, got 1D array instead原因test_X未reshape成(n_samples, 1024, 1)CNN输入必须是3D张量解决加test_X test_X.reshape(-1, 1024, 1)检查test_X.shape[2]是否为1。现象3混淆矩阵显示“正常”类准确率99%但“滚动体故障”仅65%原因类别不平衡训练时未加class_weight解决model.fit()中添加class_weightcompute_class_weight(balanced, classesnp.unique(train_y), ytrain_y)自动生成权重。现象4cnn.py运行到model.fit()卡住GPU显存占用100%但无进度原因batch_size64超出显存尤其用RTX306012GB时解决改batch_size32或在fit()前加tf.config.experimental.set_memory_growth(gpus[0], True)。现象5last_layer_data.py导出的特征文件为空原因feature_extractor构建时model.layers[-2]索引错误模型层数变动解决先print(len(model.layers))确认总层数再用model.layers[-3]或model.get_layer(dense_1)精确指定。5. 故障诊断结果可视化从数字指标到可解释热力图让答辩老师一眼看懂你的模型在“看”什么毕设答辩时光说“准确率98.5%”不够得证明模型真的理解了故障物理机制。plot_scatter.py和last_layer_data.py共同构成这套可视化体系——前者展示特征空间分布后者生成Grad-CAM热力图直指模型关注的振动信号区域。5.1 Grad-CAM热力图定位故障冲击发生的位置last_layer_data.py不是简单导出特征而是用Grad-CAM算法反向追踪CNN第一层卷积核的响应热点# last_layer_data.py 关键实现 import tensorflow as tf from tensorflow.keras import backend as K def make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_indexNone): # 构建梯度模型输入图像 → 最后卷积层输出 → 预测分数 grad_model tf.keras.models.Model( [model.inputs], [model.get_layer(last_conv_layer_name).output, model.output] ) with tf.GradientTape() as tape: conv_outputs, predictions grad_model(img_array) if pred_index is None: pred_index tf.argmax(predictions[0]) class_channel predictions[:, pred_index] # 计算梯度对最后一层卷积输出的梯度 grads tape.gradient(class_channel, conv_outputs) pooled_grads tf.reduce_mean(grads, axis(0, 1, 2)) # 全局平均 # 加权组合用梯度加权卷积输出 conv_outputs conv_outputs[0] heatmap conv_outputs pooled_grads[..., tf.newaxis] heatmap tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap) # ReLU 归一化 return heatmap.numpy() # 应用示例 img test_X[0:1] # 取第一个测试样本 heatmap make_gradcam_heatmap(img, model, conv1d_1) # 第一层卷积名 plt.figure(figsize(12, 4)) plt.subplot(1,2,1) plt.plot(img[0,:,0]) # 原始波形 plt.title(Original Vibration Signal) plt.subplot(1,2,2) plt.imshow(heatmap, cmapjet, aspectauto) plt.title(Grad-CAM Heatmap (Focus Areas)) plt.show()为什么选conv1d_1第一层卷积核kernel_size32直接感受原始波形其响应热点对应物理冲击位置深层卷积已抽象为频域模式热力图会模糊。实测中正常样本热力图均匀分布而内圈故障样本热力图在BPFI周期处出现尖峰——这就是答辩时你能指着说“模型在这里发现了故障特征”的证据。5.2 故障类型热力图矩阵用颜色深浅表达各类故障的区分难度plot_scatter.py额外生成一个10×10的混淆矩阵热力图但项目升级版用seaborn.heatmap叠加置信度标注# plot_scatter.py 扩展功能 import seaborn as sns from sklearn.metrics import confusion_matrix # 计算混淆矩阵 y_pred model.predict(test_X).argmax(axis1) cm confusion_matrix(test_y, y_pred) # 绘制带数值的热力图 plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsfault_names, yticklabelsfault_names) plt.title(Confusion Matrix (Test Set)) plt.ylabel(True Label) plt.xlabel(Predicted Label) plt.show()fault_names来自README.md里的映射表标签ID故障类型位置/尺寸0Normal—1IR007内圈0.007英寸2IR014内圈0.014英寸.........这张图直接暴露模型短板若IR007行中IR014列数值高说明小尺寸内圈故障易被误判为大尺寸——这时你就能在答辩时说“下一步我计划用GAN生成更多IR007样本缓解小故障样本不足问题”。5.3 多工况性能对比表1750/1730/1772 RPM下的模型鲁棒性CWRU数据含三种转速项目在README.md里明确列出各工况测试结果工况RPMDNN准确率CNN准确率CNN推理速度ms/sample主要误判类型175092.3%98.7%12.4OR021 → OR007173089.1%97.2%11.8IR007 → Normal177290.5%96.5%13.1B014 → B021为什么1750RPM表现最好因为CWRU官网标注该工况数据质量最高信噪比40dB而1730RPM采集时传感器接触不良引入低频漂移——这解释了DNN在1730RPM准确率最低DNN对噪声敏感而CNN因卷积滤波作用仍保持97%以上。答辩时提到这点老师会认可你读过原始论文。6. 毕设落地技巧从“能跑通”到“能讲透”的三个硬核习惯让我连续三年带的学生答辩零质疑去年指导一个学生用这个项目做毕设他在答辩最后5分钟被问“你模型最后一层输出128维这128个数字到底代表什么物理意义”他当场打开last_layer_data.py用t-SNE降维后的散点图指出“这簇红点IR007和蓝点OR021在特征空间距离很近说明它们的冲击形态相似而绿点Normal离所有故障点都远——所以128维不是任意数字它是模型学到的故障‘指纹’距离度量。”老师点头笑了。这件事让我彻底放弃教学生背答辩话术转而逼他们养成三个习惯——现在我把它们刻进这个项目的每个.py文件注释里。6.1 每个数据文件必须带SHA256校验杜绝“我本地跑通但服务器报错”CWRU数据集官网偶尔更新文件导致.mat哈希值变动。项目在README.md顶部声明数据完整性校验下载后请执行sha256sum 1750RPM/Normal_1.mat应得a1b2c3...d4e5f6 1750RPM/Normal_1.mat全部10类文件哈希值见data_checksums.txt这不是形式主义。去年有学生用百度网盘转存的CWRU数据IR007_1.mat被压缩损坏h5py读取时静默返回空数组训练loss恒为nan。他花了两天查代码最后发现是数据源问题——而校验步骤30秒就能排除。6.2 所有超参数必须集中管理禁用“魔法数字”cnn.py开头不是直接写model.fit(epochs200)而是定义字典# cnn.py 参数配置区修改此处即可调参 CONFIG { sampling_rate: 12000, window_size: 1024, step: 512, num_classes: 10, batch_size: 64, epochs: 200, learning_rate: 0.001, dropout_rate: 0.5, conv1_filters: 32, conv1_kernel: 32, # 物理意义2.67ms conv2_filters: 64, conv2_kernel: 16, # 物理意义1.33ms }为什么conv1_kernel后面要写物理意义因为答辩时老师必问“32怎么来的”。你指着注释说“32点对应2.67ms而CWRU内圈故障冲击持续时间实测2.5±0.3ms”比说“试出来的”有力一万倍。这个习惯让我带的学生90%能主动在PPT里加一页“参数物理依据”。6.3 每次实验必须保存完整日志包括环境信息cnn.py末尾强制记录# 实验日志生成 import platform import tensorflow as tf with open(flogs/train_log_{int(time.time())}.txt, w) as f: f.write(fTime: {time.ctime()}\n) f.write(fOS: {platform.platform()}\n) f.write(fPython: {platform.python_version()}\n) f.write(fTensorFlow: {tf.__version__}\n) f.write(fGPU: {tf.config.list_physical_devices(GPU)}\n) f.write(fConfig: {CONFIG}\n) f.write(fTrain Acc: {history.history[accuracy][-1]:.4f}\n) f.write(fVal Acc: {history.history[val_accuracy][-1]:.4f}\n)血泪教训曾有个学生答辩时说“我在RTX3090上跑出99.2%”老师问“用的CUDA几”他答不上来。后来发现他用的是CUDA 11.2 TF 2.8而实验室服务器是CUDA 11.8 TF 2.12版本不兼容导致复现失败。从那以后我每次让学生跑实验都强制走一遍logs/目录生成——希望帮到你。本文还有配套的精品资源点击获取
返回列表