简介:围绕CWRU轴承数据集,这份PyTorch源码包提供了自编码器(AE)与卷积神经网络(CNN)等多种网络的实现、训练代码及项目说明,面向深度学习故障检测方向的研究者与学习者,便于研读网络结构、对比不同数据预处理方式并开展相关实验。资源共478个文件,压缩包约1.16MB,主体为254个Python脚本,含模型定义、训练流程与可视化脚本;另有tensorboard日志、配置文件等,目录按数据预处理、模型定义、工具函数、训练日志等功能模块划分,层次清晰。已有874人学习下载。代码在原始版本基础上做了改造增强:训练脚本中新增TensorBoard可视化与精确率、召回率、误报率、漏检率等指标记录,并补充了模型ACC/LOSS曲线绘制脚本、CWRU数据CWT和STFT变换分析脚本。整套资源既可帮助初学者通过PyTorch框架理解故障检测建模流程,也能让有基础的读者快速获取可运行源码、日志与指标输出,用于复现实验或改进算法。
1. 多模型故障检测源码实测:CWRU轴承数据、PyTorch训练链路与TensorBoard日志可视化
把CWRU轴承数据集上的故障检测跑通,比单纯跑通一个图像分类要多花不少心思。这份基于多种深度学习的故障检测算法python源码,把CNN和自编码器两类网络、三种数据预处理方式、完整的检测指标与可视化脚本打包到一起,训练日志、TensorBoard曲线、CWT与STFT时频变换图都齐了。它不解决端到端部署,但很适合想研究故障检测算法、想把深度学习真正落到振动数据上的人。对还在折腾环境、刚接触PyTorch的新手来说,这份代码也能当一套标准的训练流水线来模仿。读完后你能回答三个问题:输入信号怎么切、网络怎么选、训练日志里哪些指标才真正说明检测能力。
2. 拿到源码先拆目录:六个模块职责、最小环境配置与数据组织
2.1 目录结构与模块的调用关系
解压之后先别急着跑train.py。我习惯先用tree把整个目录扫一遍,弄清楚每个文件夹是谁在喂谁、谁的输出落在哪里。这份资源和很多散装demo不一样的地方是:它把“数据预处理、网络定义、训练辅助函数、可视化脚本”分得比较清楚,改一个环节不用牵动全部代码。顶层结构大致如下:
├── AE_Datasets/ # 自编码器输入:三种数据预处理方式的相关代码 ├── CNN_Datasets/ # CNN输入:三种数据预处理方式的相关代码 ├── checkpoint/ # 各网络训练过程保存的日志(作者跑实验时的留痕) ├── logs/ # TensorBoard事件文件(events.out.tfevents.*) ├── models/ # 不同网络模型定义(CNN、AE等) ├── utils/ # 训练过程辅助函数(train_utils.py、train_utils_ae.py) ├── draw_models.py # 各模型训练集/验证集ACC与LOSS绘图 ├── draw_transform.py # CWRU数据的CWT/STFT变换分析与绘图 ├── train.py # 训练除自编码器以外的网络模型 └── train_ae.py # 训练自编码器网络模型从调用关系上看,AE_Datasets和CNN_Datasets是数据入口,分别服务自编码器和CNN。models里的网络结构负责接收数据并输出预测,utils里的train_utils.py和train_utils_ae.py封装训练逻辑,train.py和train_ae.py是最外层入口,checkpoint和logs是训练过程中写到磁盘的产物,两个draw脚本把日志和原始数据变成可视化的图。
也就是说,如果你只想看某个网络在CWRU上跑到什么水平,只需要改train.py里模型实例化那一行;如果你想对比不同预处理方式,先在两个Datasets目录里把数据准备好,再启动训练。checkpoint里的日志是作者当时跑实验留下的,我建议先留着不要删,后面跟自己的实验对照时很有用。
2.2 最小可跑环境:PyTorch、TensorBoard与依赖安装
代码基于PyTorch,训练过程用了TensorBoard写入Scalar指标,可视化用到matplotlib和numpy。故障检测常用的时频变换依赖scipy和pywt,后者不是PyTorch自带的,跑draw_transform.py之前必须装。我一般用conda建一个独立环境,避免把系统Python搞得一团糟:
conda create -n fault_detect python=3.8 conda activate fault_detect pip install torch tensorboard numpy scipy matplotlib pip install pywt第一行创建Python 3.8环境,第二行激活环境。PyTorch的安装建议根据自己的CUDA版本选择,CPU机器直接装CPU版也能跑,只是速度慢一些;torch版本不要求最新,TensorBoard的events.out.tfevents.*格式在各版本间是兼容的。pywt是时频分析必须的一环,装完可以用下面这条命令确认导入正常:
python -c "import torch, tensorboard, scipy, pywt; print('ok')"只要不报ImportError,环境就算搭好了。这里有个容易被忽略的点:如果电脑上同时装了多个Python,conda环境激活后还要确认pip对应的就是当前环境,否则很容易出现包装到了base环境、训练时却报ModuleNotFoundError的情况。Windows笔记本上跑这份代码没有额外障碍,logs目录里那串LAPTOP-1FVELO7I主机名说明作者当时就是在笔记本上完成的实验。
2.3 三种数据预处理方式:同一种网络在不同输入下的效果差异
AE_Datasets和CNN_Datasets都写了三种数据预处理方式,这是这份资源里比较有价值的对比实验点。常见的做法是把CWRU原始振动信号按固定窗口长度切段,三种方式的差别主要在于切法和归一化上,我按最常见的一组配置给一个对照。
| 预处理方式 | 输入形态 | 典型适用网络 | 特点 |
|---|---|---|---|
| 原始振动段 | 一维时序数组 | AE、1D-CNN、LSTM | 信息最直接,训练快,对噪声敏感 |
| 归一化/标准化振动段 | 一维时序数组 | AE、1D-CNN、LSTM | 保留波形形态,减小幅值差异影响 |
| 时频变换谱图 | 二维图像 | 2D-CNN | 能看到时间与频率维度的能量分布,计算量大 |
这三种方式在CWRU上都可以跑,但结论可能有差别。我的经验是:一维输入训练快,适合先验证网络结构和调参;想追求更稳的检测效果,可以在确认一维基线之后,再做STFT或CWT谱图喂给2D-CNN,draw_transform.py就是专门用来提前观察信号变换效果的。这里也想提醒一句:无论选哪种方式,训练集和验证集都要在同一预处理管线里生成,不能训练用归一化、验证用原始数据,否则指标再好看都是假的。
3. 训练入口拆解:train.py与train_ae.py的工作流、检测指标与日志写入
3.1 训练脚本骨架:数据加载、模型实例化与epoch循环
train.py和train_ae.py是两份独立入口,逻辑高度相似。它们本身不含太多代码,真正的训练函数在utils/train_utils.py和utils/train_utils_ae.py里,脚本负责解析数据集、构建模型、把参数传进去。打开train.py,核心流程大致是这样:
# train.py 的精简骨架(完整逻辑以项目内源码为准) import torch from torch.utils.data import DataLoader from utils.train_utils import train # 训练封装函数 from models import build_model # 以models目录里实际提供的函数为准 # 数据加载:CNN_Datasets里预处理好的数据通过Dataset包装 train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False) # 按经典CWRU四分类(正常/内圈/外圈/滚动体)构建网络 model = build_model('cnn', num_classes=4) train(model=model, train_loader=train_loader, val_loader=val_loader, epochs=100, lr=1e-3, log_dir='logs/cnn_result', ckpt_dir='checkpoint/cnn_result')这段代码的核心逻辑是:把预处理好的数据包装成DataLoader,训练时shuffle=True打乱顺序,验证时不打乱。模型通过models目录里的构建函数创建,train.py负责传参,train函数内部完成前向、反向、指标计算和日志写入。
参数方面,batch_size设64在大多数笔记本上跑得动,数据量大再考虑128;epochs设100是常见设置,先跑50轮看曲线趋势也行;lr=1e-3是Adam类优化器的常见起点,如果loss震荡明显就降到1e-4。train_ae.py的运行方式几乎一样,区别只在模型换成了自编码器,以及内部走train_utils_ae.py的封装,自动编码器对重构误差的监控和对分类指标不太一样。
3.2 故障检测核心指标:精确率、召回率、误报率、漏检率的计算方式
很多新手训练时只看准确率,这在故障检测里是不够的。CWRU数据集中,正常样本和故障样本数量往往不均衡,如果故障样本只占少数,模型全部预测正常也能拿到很高的准确率,但这样的模型毫无检测能力。所以这份源码在训练函数里特意加了精确率、召回率、误报率、漏检率和F1值,计算逻辑是二分类视角:正类为故障,负类为正常。
# 把多分类预测映射成二分类:0=正常,1=故障 binary_true = (y_true != 0).astype(int) binary_pred = (y_pred != 0).astype(int) # 计算混淆矩阵四要素 TP = ((binary_true == 1) & (binary_pred == 1)).sum() FP = ((binary_true == 0) & (binary_pred == 1)).sum() FN = ((binary_true == 1) & (binary_pred == 0)).sum() TN = ((binary_true == 0) & (binary_pred == 0)).sum() precision = TP / (TP + FP) # 精确率:报故障里真的故障比例 recall = TP / (TP + FN) # 召回率:真故障里被抓住的比例 false_alarm = FP / (FP + TN) # 误报率:正常样本被错判成故障 miss_rate = FN / (FN + TP) # 漏检率:故障样本被漏掉 f1 = 2 * precision * recall / (precision + recall + 1e-8)指标含义很直观:精确率回答“报出来的故障有多少是准的”,召回率回答“真实故障有多少被抓住了”,误报率关注正常样本被误伤,漏检率关注故障样本被漏过。产线场景里误报率高了会导致频繁停机检查,代价很大;学术对比则更常用F1来平衡精确率和召回率。分母上加1e-8是为了防止除零,这是数值稳定性的常规写法。
训练结束后,utils会把这几个数连同Loss值一起写入TensorBoard。你不需要自己算,但必须明白一个道理:acc高不代表检测能力强,只有当precision、recall和F1同时稳定,这个模型才真的可用。
3.3 TensorBoard日志写入:看Scalar曲线而不是等训练结束
训练过程中,train函数会向logs目录写入Scalar数据,格式就是那串events.out.tfevents开头的文件。查看方式很简单:
tensorboard --logdir=logs启动后浏览器访问配置文件里提示的端口,默认是6006。左侧Scalar面板能看到train_loss、val_loss、train_acc、val_acc,以及精确率、召回率、误报率、漏检率、F1值这些曲线。比等100轮跑完再拿终端输出判断要直观得多:loss下降平缓还是震荡,val_acc到哪个epoch开始不再上升,一眼就能看出来。
logs目录里散落着多组events.out.tfevents.*文件,文件名中的时间戳是Unix格式的启动时刻,主机名后面是进程ID。多组文件意味着作者跑过多轮实验,这其实是给后来者留下的一个好处:你可以对比不同实验的曲线,找出哪一组预处理方式或哪个网络参数组合的val曲线最稳。训练到一半发现val_loss反弹,直接停掉改参数,不必等全部跑完。
4. 可视化落地:draw_models.py画曲线、draw_transform.py做时频变换
4.1 draw_models.py:训练集与验证集的ACC/LOSS曲线绘制
训练结束后,终端只能看到最后一轮的数字,中间过程全在日志里。draw_models.py做的就是把这些日志解析出来画成曲线图,方便整体观察趋势。它背后的读取逻辑本质上就是解析TensorBoard事件文件,核心思路是这样:
# draw_models.py 的读取逻辑(绘图细节以项目源码为准) import os import matplotlib.pyplot as plt from tensorboard.backend.event_processing.event_accumulator import EventAccumulator for run in os.listdir('logs'): ea = EventAccumulator(os.path.join('logs', run)) ea.Reload() # 先看有哪些tag,避免写错名字 tags = ea.Tags()['scalars'] train_acc = [s.value for s in ea.Scalars('train_acc')] val_acc = [s.value for s in ea.Scalars('val_acc')] plt.plot(train_acc, label=f'{run}-train') plt.plot(val_acc, label=f'{run}-val') plt.xlabel('epoch') plt.ylabel('acc') plt.legend() plt.savefig('acc_curves.png')EventAccumulator是TensorBoard官方提供的日志解析接口,Reload方法把事件文件读进内存,Scalars方法按tag取出一组标量。如果你的训练函数里tag命名不是train_acc,先通过ea.Tags()['scalars']打印所有键名再改动。这个脚本的价值在于:多个实验跑完后,所有acc曲线叠加在一张图上,哪个模型在第几个epoch过拟合、哪个曲线整体更平稳,一目了然。
LAOSS曲线同理,只是把tag换成train_loss和val_loss。我通常把ACC和LOSS两张大图都导出来放一起看,ACC稳定但LOSS持续走高的组合很值得警惕。
4.2 draw_transform.py:CWT与STFT(汉宁窗)分析CWRU振动信号
CWRU数据集里的原始数据是轴承振动加速度信号,采样频率有12kHz和48kHz两种。直接看时域波形,内圈故障和外圈故障的冲击差异并不明显;把信号变换到时间-频率平面后,不同故障的特征频率带会清晰许多。draw_transform.py做的是这件事,它用短时傅里叶变换和小波变换比较同一段信号的两种时频表示。
# draw_transform.py 核心变换逻辑(以CWRU 12kHz采样为例) import numpy as np from scipy.signal import stft import pywt fs = 12000 # 采样频率,48kHz数据请同步修改 # 方式一:STFT,汉宁窗,窗口256点,重叠128点 f, t, Zxx = stft(signal, fs=fs, window='hann', nperseg=256, noverlap=128) plt.pcolormesh(t, f, np.abs(Zxx), shading='gouraud') # 方式二:CWT,morlet小波,尺度范围1~64 scales = np.arange(1, 64) coefs, freqs = pywt.cwt(signal, scales, 'morl', sampling_period=1/fs) plt.imshow(np.abs(coefs), aspect='auto', cmap='jet', extent=[0, len(signal)/fs, freqs[-1], freqs[0]])STFT是把长信号切成一段段短窗口做傅里叶变换,汉宁窗能抑制频谱泄漏,窗口长度决定时间分辨率和频率分辨率的折中:窗口越长频率越精细,但时间定位越模糊。CWT用morlet小波在不同尺度上做卷积,低频尺度对应频率高、时间分辨率差,高频尺度则相反,整体上比STFT更擅长捕捉瞬态冲击。
| 变换 | 窗口/小波 | 输出维度 | 主要看什么 |
|---|---|---|---|
| STFT(汉宁窗) | 256点窗口,重叠128点 | 频率×时间 | 稳态成分和冲击的频段范围 |
| CWT(morlet) | 尺度1~64 | 尺度×时间 | 瞬态冲击的精确定位 |
在CWRU场景里,轴承故障会周期性产生冲击,这些冲击在时频图上会形成沿时间轴重复的能量线条,特征频率和故障类型一一对应。我第一次把内圈故障样本跑完STFT后,看到低频段有规律的亮带,对照故障特征频率公式一算完全对得上,那种感觉比直接看测试集acc踏实得多。
4.3 checkpoint、logs和events时间戳:一次训练的三种留痕
checkpoint和logs经常被混为一谈,其实分工不同。checkpoint目录是训练过程中控制台输出的日志文本,记录了每个epoch的Loss、acc、precision、recall等数字,适合快速翻看;logs目录是TensorBoard事件文件,适合可视化对比。两者时间戳一致时,对应同一次训练。
logs目录里这组events.out.tfevents.*文件的时间戳集中在1671780001到1671783488之间,按照Unix时间换算不到一个小时,说明作者在连续多轮实验后把结果留了下来。我习惯用这个时间戳做实验编号:每次训练在log_dir里带上日期和模型名,比如logs/20250214_cnn_stft,配合checkpoint文本日志,三个月后回来看还能还原当时的调试过程。
5. 训练避坑:日志对不上、曲线空白、时频图发黑这类问题的排查记录
5.1 现象:TensorBoard里曲线空白,Scalar面板没有数据
第一次打开tensorboard时,Scalar面板空白是常见翻车现场。最常见的原因是logdir传错了层级,logs目录下直接放事件文件,但如果没有按实验分子目录,TensorBoard扫描到的是同一层级下的多个事件文件,可能只显示最后一段。另一个原因是训练还没跑够一个epoch,writer没有flush,事件没有真正落盘。
解决方法是:训练脚本确认每个实验写入独立的子目录,比如logs/cnn_run1,启动tensorboard时用tensorboard --logdir=logs;如果跑了好几个epoch还空白,检查代码里writer.close()或flush()是否执行。事件文件是追加写入的,训练中途强杀进程也可能导致最后一小段数据缺失,但前面的epoch应该还在。
5.2 现象:训练集acc很高,验证集acc明显偏低
这个现象十有八九是数据切分出了问题,而不是网络过拟合。CWRU数据来自同一个轴承的连续振动采样,如果随机把窗口切进训练集和验证集,同一个原始文件里的相邻窗口可能同时出现在两侧,本质是数据泄漏。训练时模型见过这些窗口的邻居,验证自然得分高,换个工况就被打回原形。
解决方法是按样本文件切分而不是按窗口切分:先按CWRU原始数据文件划分train/val,再从文件内部切窗口。我自己踩过这个坑,当时训练集acc到了97%以上,验证集只有83%,改成按文件切分后两边的差距明显收窄。特征频率的计算也要以同一采样率为前提,12kHz和48kHz的数据混着用会直接破坏一致性。
5.3 现象:CWT/STFT图一片黑或者亮得刺眼
先确认画的是np.abs(系数)还是原始复数,很多新手直接pcolormesh复数数组,matplotlib会报警告或者画出诡异色块。排除这个之后,一片黑通常是幅值动态范围问题:信号里有低频分量幅值很大,把其他频段压成了黑色。STFT对这种问题相对宽容,CWT尤其明显,因为小波系数的幅度随尺度变化很大。
解决方法是做对数压缩或者线性归一化。我在处理CWRU数据时习惯对系数取20*log10后的值再画图,动态范围会舒服很多;也可以把cmap的vmin/vmax手动设到百分位数,比如vmin=0.5分位,vmax=99.5分位,这样能避开个别极大值把色标拉伸过头。这类问题多半不是数据坏了,而是显示方式没调对。
5.4 现象:准确率很高,但误报率和漏检率居高不下
这个现象很多人会愣一下,因为它恰好是类别不平衡的典型特征。CWRU里如果正常样本占比大,模型学到的决策边界会偏向多数类,acc被正常样本撑高了,但故障样本漏检严重,误报率也可能异常。单纯盯着acc调参,永远走不出来。
解决方法是把评估重心转移到precision、recall、F1和混淆矩阵上,训练时也可以考虑给少数类加权。这份源码在utils里加了误报率和漏检率的计算,就是明确暗示:故障检测任务不能只看acc。如果你自己训练的模型出现这种状况,先去看混淆矩阵,然后决定是加权还是换输入形式。
5.5 现象:CPU训练很慢,数据加载占了绝大部分时间
没有GPU时训练慢是正常的,但有一种慢完全可以避免:训练循环里每个step都做STFT或CWT变换,相当于把预处理计算重复做了几十遍。CWRU数据总量不大,但如果每个窗口都现场做时频变换,CPU训练会慢得让人怀疑人生。
解决方法是把预处理结果提前算完存成npy,训练时直接加载数组。第一次跑draw_transform.py确定用哪种变换后,我习惯先把整批数据变换好,写入磁盘,再让训练脚本读取,训练速度能提升一个量级。另一个常见卡点是DataLoader的num_workers在Windows上设置不当导致黑屏或死锁,Windows建议设0或1。
6. 用事件日志做验证:解析logs批量对比指标,固定数据入口再谈调参
6.1 批量解析logs目录,用事件文件生成指标对比表
训练脚本每跑一次就往logs写一批事件文件,时间一长,手动翻找某个实验的指标很麻烦。我常用的一个技巧是写一段解析脚本,把logs下所有实验的最后一个epoch指标抽出来,自动生成对比表,省去反复开关TensorBoard的功夫。
# 批量提取logs下各实验的最终指标(tag名以实际写入为准) import os from tensorboard.backend.event_processing.event_accumulator import EventAccumulator for run in sorted(os.listdir('logs')): ea = EventAccumulator(os.path.join('logs', run)) ea.Reload() val_acc = [s.value for s in ea.Scalars('val_acc')] val_f1 = [s.value for s in ea.Scalars('val_f1')] print(f'{run:24s} val_acc={val_acc[-1]:.4f} val_f1={val_f1[-1]:.4f}')这段代码的价值在于把对比从“打开TensorBoard手动记”变成“一条命令出表格”。每一行代表一次实验,val_acc和val_f1取最后一个epoch的值。如果想要更完整的结论,把误报率和漏检率也加进去。多组实验一次性摊开,哪个网络、哪种预处理方式表现稳,立刻有数,不用凭记忆下结论。
6.2 把曲线和时频图回放成调参依据,固定数据入口再谈微调
调参数有一个很容易犯的错:一边改预处理、一边改学习率,最后指标变了也说不清是哪个改动起的作用。我的做法是先用draw_transform.py把CWRU样本的STFT和CWT图看一遍,确定用哪种输入形式,然后把这一项固定下来,之后所有对比都只动网络结构和训练参数。
选择网络时也不只看最终acc,把draw_models.py画出来的多组曲线叠加在一起:验证集acc在第几个epoch见顶、之后是否下滑,比单个数字更有信息量。如果某个模型在第30轮就开始过拟合,即使最终acc看着不错,几轮后换工况也很可能翻车。
从那以后我再拿到一份故障检测源码,都不会直接train.py一把梭,而是先把logs目录里的旧实验全部解析出来,画成对比表,结合时频变换图确认数据入口,再决定要不要动手改参数。这个习惯让我少走了不少弯路。希望帮到你。
本文还有配套的精品资源,点击获取