尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深度学习故障检测源码落地:CNN、LSTM与自编码器实践指南

深度学习故障检测源码落地:CNN、LSTM与自编码器实践指南 简介一套面向故障检测研究与深度学习初学者的Python源码项目涵盖自编码器、CNN等多种模型并基于CWRU轴承数据集提供完整训练与评估流程。资源共478个文件以.py源文件为主254个另含编译生成的pyc、训练日志log、配置文件xml等整体压缩包仅1.16MB轻量易用。压缩包内目录规划清晰AE_Datasets与CNN_Datasets分别对应两种模型的数据预处理models存放不同网络结构utils包含训练函数train.py与train_ae.py分别训练不同模型还额外提供了draw_models.py和draw_transform.py用于训练过程指标可视化和CWRU数据变换分析。在train_utils中增强了精确率、召回率、误报率、漏检率等评估指标并支持TensorBoard可视化。目前已有874人学习下载适合想深入理解故障检测算法及PyTorch建模细节的读者参考。1. 拿到“多种深度学习故障检测”源码包后先别急着跑模型刚解压完这类源码包的人十有八九会直接去找train.py双击运行然后对着满屏的依赖报错发呆。我见过太多人卡在环境阶段就放弃了。这里其实藏着一个反直觉的事实跑通这套代码的难度远低于把它换到你自己的数据上。基于多种深度学习的故障检测算法本质上是把 CNN、LSTM、自编码器这类深度网络叠到一张振动信号或电流信号上让模型替你看那些肉眼盯不过来的早期异常。这个源码包适合谁呢——手里有工业设备数据、想用深度学习做故障分类或异常报警的工程师以及准备拿公开数据集做毕设、需要一套完整基线代码的同学。前者关心怎么接上产线数据后者关心怎么在论文里讲清楚模型对比。这一篇文章就顺着这两个需求往下拆。2. 模型选型逻辑CNN、LSTM、自编码器在故障检测里各守哪道关2.1 CNN 做局部特征提取为什么它总是打头阵故障检测里最典型的一个任务是对振动信号做分类——判断当前设备处于正常、不平衡、磨损还是轴承故障状态。传统做法是手工提取时域特征均方根、峰值因子和频域特征特定频带的能量占比再喂给支持向量机或随机森林。这套思路的问题是特征工程极依赖经验换一台设备、换一种工况特征可能就要重新设计。源码里用 CNN 打头阵的常见逻辑是让它自动替代手工特征提取这一步。一维卷积天然适合处理振动信号这种长度固定的时间序列卷积核沿时间轴滑动相当于在学一组可训练的滤波器组。第一层卷积学到的往往是带通滤波器式的响应深层的卷积核则组合出更抽象的模式。和图像分类不一样的是一维卷积核的数量和尺寸不需要很大常见配置是 3 到 5 层卷积核大小在 3 到 7 之间通道数从 16 逐步加到 128最后接全局池化和全连接层。以经典的一维卷积块为例源码里经常见到的写法是import torch.nn as nn class ConvBlock1D(nn.Module): def __init__(self, in_channels, out_channels, kernel_size5, stride1, padding2): super().__init__() self.conv nn.Conv1d(in_channels, out_channels, kernel_size, stride, padding) self.bn nn.BatchNorm1d(out_channels) self.relu nn.ReLU(inplaceTrue) self.pool nn.MaxPool1d(kernel_size2) def forward(self, x): # x 的维度是 (batch, in_channels, seq_len) x self.conv(x) x self.bn(x) x self.relu(x) x self.pool(x) return x这段代码的关键在padding2配合kernel_size5能让卷积输出长度和输入保持一致避免特征长度在多层之间快速缩水。MaxPool1d(2)把长度减半等价于做了一次下采样让后续卷积看到更大的感受野。批量归一化层在故障检测里尤其重要因为不同采样时刻的振动幅值可能差别很大先做归一化能避免网络对幅值尺度过于敏感。参数调整时优先动kernel_size和out_channels不要一上来就加层数——一维信号的信息密度通常撑不起太深的网络层数超过 6 层后收益递减明显。2.2 时序建模LSTM 与 GRU 在故障检测中的选型边界CNN 有一个绕不开的短板它看到的是一个固定窗口窗口内部的顺序信息被卷积核部分捕获但跨窗口的长程依赖处理得很吃力。旋转机械的故障特征偏偏带有明显的周期性比如轴承外圈故障的特征频率通常是转频的某个倍数这个节律只有在足够长的时间跨度里才能稳定显现。LSTM 这类循环网络的价值就在这里——它维护一条跨越时间步的隐藏状态线让模型能记住几十甚至几百个时间步之前的振动模式。源码里常见的设计是把 CNN 和 LSTM 串成一个双阶段结构先用两层一维卷积把原始振动信号初步编码成特征序列然后把特征序列按时间步展开喂给 LSTM。词嵌入、文本生成里那种三层双向 LSTM 的配置在故障检测里并不适用。工业振动信号通常采样率很高一段几秒的数据就有几万个点直接全量展开计算量太大循环步数一多还可能梯度消失。我一般会先在时间维度上做降采样把序列长度压缩到 64 到 128 步再用单层 LSTM、隐藏单元 32 到 64已经够用。import torch.nn as nn class CnnLstmClassifier(nn.Module): def __init__(self, input_channels1, hidden_size64, num_classes4): super().__init__() self.cnn nn.Sequential( nn.Conv1d(input_channels, 16, kernel_size5, stride2, padding2), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.Conv1d(16, 32, kernel_size3, stride2, padding1), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), ) self.lstm nn.LSTM(input_size32, hidden_sizehidden_size, num_layers1, batch_firstTrue) self.classifier nn.Linear(hidden_size, num_classes) def forward(self, x): # x: (batch, 1, seq_len) x self.cnn(x) # 输出 (batch, 32, seq_len/4) x x.permute(0, 2, 1) # 转成 (batch, seq_len/4, 32) 供 LSTM 使用 out, _ self.lstm(x) out out[:, -1, :] # 取最后一个时间步的输出 return self.classifier(out)注意这里的stride2起了两个作用一是减少序列长度把计算量压下来二是让 CNN 输出的每个时间步覆盖原始信号的更长时间区域等价于给 LSTM 喂入的特征做了信息浓缩。取out[:, -1, :]是常见做法但如果你想捕捉整段序列的综合信息也可以改成对全部时间步做平均池化或最大池化。GRU 在故障检测里往往比 LSTM 更实用它是 LSTM 的精简版少了一个门控参数更少、收敛更快尤其适合训练样本不够充足的工业场景。如果你的数据量只有几千条优先选 GRU 而不是 LSTM。2.3 自编码器做无监督异常检测当故障标签稀缺时的无奈与出路故障检测里一个绕不开的痛点是正常数据永远大量存在故障数据却少得可怜。一条产线可能一年都碰不上几次真正的故障但你不能等故障发生了再去训练模型。这时候有监督分类模型容易过拟合到那几条故障样本上换一个工况就失效。自编码器的思路就完全不一样——只拿正常数据训练让它学会“压缩再还原”正常样本的固有模式之后一旦输入故障样本还原误差会显著变大这个误差就是异常分数。实现上是一个先降维再升维的结构编码器把输入信号压到很低的维度称为瓶颈层解码器从瓶颈层还原出原始信号。训练的时候只喂正常样本损失函数就是还原误差。判断时设定一个误差阈值超过阈值就报警。这整套流程不需要一条故障标签是它最大的吸引力。import torch.optim as optim model ConvAutoencoder() optimizer optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() for epoch in range(50): for batch_x in normal_loader: # 自编码器的输入输出都是原始振动信号 recon model(batch_x) loss criterion(recon, batch_x) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 10 0: print(fepoch {epoch}, recon loss: {loss.item():.6f})训练完成后对验证集的正常样本算一遍重构误差取 99 分位数作为阈值比手工去试更靠谱。这个方案有一个需要格外注意的地方如果正常数据本身就包含多种工况比如不同转速自编码器可能会把所有工况都学成“正常”导致真实的早期故障也被还原得很好。解决办法是尽量让训练数据覆盖所有正常工况或者按工况分别建模型。无监督这个路子不是省事而是把功夫花在了数据覆盖上。3. 本地跑通最小检测流程环境、数据目录和那两条关键命令3.1 从解压到跑通环境配置的“最小可行集合”拿到源码包后先别急着装环境。第一步是看清楚目录结构一份规范的故障检测源码包通常长这样目录/文件作用注意事项data/存放原始数据集有的项目只给占位符数据要自己下载configs/模型参数配置文件YAML 或 JSON 格式不要直接改代码里的常量models/网络结构定义按模型名拆分便于对比实验utils/数据加载、指标计算、可视化依赖较多注意版本兼容train.py训练入口读配置、建模型、跑训练循环evaluate.py评估入口加载最优权重输出混淆矩阵requirements.txtPython 依赖列表建议用虚拟环境安装这份目录划分是工业项目里最常见的实践不管源码包实际组织方式差多少你要找的核心入口无非是train.py和evaluate.py这两个文件。优先看configs/里的配置而不是一头扎进训练代码里因为训练代码往往兼容多种模型和数据集写成了一阵子不懂的“意大利面条”配置文件反而是最直白的说明书。环境配置上Python 版本锁定在 3.8 到 3.10 最稳妥。深度学习框架二选一PyTorch 还是 TensorFlow 取决于源码本身用的哪个但如果你打算在这个项目基础上做二次开发我建议你选 PyTorch 实现的那份源码——PyTorch 的调试体验对工程师来说友好得多。装 PyTorch 时用一个国内镜像站能省下大把时间# 使用 conda 创建独立环境避免污染系统 Python conda create -n fault_detection python3.9 -y conda activate fault_detection # 安装 PyTorch CPU 版或 GPU 版按机器实际情况取舍 # 这里以 GPU 版为例CPU 版把 index-url 换成 cpu 对应源即可 pip install torch torchvision torchaudio \ --index-url https://download.pytorch.org/whl/cu118 # 安装项目其余依赖 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple用 conda 建环境这个习惯建议从一开始就坚持。故障检测项目通常依赖一堆数据处理库scipy、numpy、pandas、scikit-learn之间常有版本联动混装极易把系统 Python 搞坏。装完依赖后跑一句python -c import torch; print(torch.__version__)验证安装结果别急着直接跑训练脚本。3.2 数据目录暗藏的“接口约定”跑通训练前数据预处理往往先卡住人。故障检测数据集的常见组织方式有两种一种是每个类别一个文件夹比如normal/、inner_race_fault/、outer_race_fault/另一种是一个 CSV 大表包含多列传感器数据和一列标签。源码里的数据加载逻辑大概率写死了某一种格式换数据集前一定先确认加载代码里读的是什么格式。最省事的方式是把公开数据集按项目预期的格式重新摆放。以 CWRU 轴承数据集这类最常被用到的公开数据为例常见的处理流程是每个故障类型取一段连续信号切成一秒一个样本按类别存进文件夹。import scipy.io as sio import numpy as np import os source_file path/to/your.mat # 原始振动信号 sample_rate 12000 # 采样率单位 Hz window_size 1200 # 每个样本的长度128ms 窗口 data sio.loadmat(source_file)[DE_time].flatten() n_samples len(data) // window_size for i in range(n_samples): sample data[i * window_size : (i 1) * window_size] save_path fdata/train/normal/sample_{i:06d}.npy np.save(save_path, sample.astype(np.float32))window_size选多少直接决定样本质量和训练数据量。窗口太短单个样本包含的周期数不足故障特征不完整窗口太长样本量变少且一段窗口内可能混入转速波动。一个经验值是让窗口内至少包含 5 到 10 个旋转周期。比如转速是 1800 转/分基频就是 30 赫兹每个周期约 0.033 秒按 12 千赫采样率就是约 400 个点窗口取 2048 或 4096 个点都合理。3.3 训练命令与验证命令只关注稳定复现的那几条参数入口弄清楚、数据摆对位置后训练本质上就是一条命令的事。常见的统一入口设计是python train.py --config configs/cnn.yaml所有该调的参数都收拢到 YAML 文件里。只看不过几处关键的训练参数配置里大致是这几类参数常见取值调整依据batch_size32 ~ 128样本总量少时用小值显存不足时往小降learning_rate1e-3 ~ 1e-4Adam 配 1e-3 起步收敛不稳就降一个量级epochs50 ~ 200配合早停策略别光盯着固定轮数patience10 ~ 20验证集指标连续 N 轮不提升就停num_classes按标签数设置常见配置 2正常/故障或 4多类故障signal_length1024 ~ 4096必须和预处理时切分的窗口长度对应训练期间每过若干个 epoch 把训练损失和验证准确率打出来是判断模型是否正常收敛的关键信号。损失在震荡而不是下降时先降学习率不要急着加数据。训练完成后评估脚本会加载最优权重输出测试集上的准确率、精确率、召回率和 F1 分数。故障检测里标签往往不平衡——正常样本远多于故障样本而故障样本里不同故障类型的比例又不一样所以准确率再高也别兴奋重点关注每一类故障的召回率。# 训练最简用法其余参数走配置文件默认值 python train.py --config configs/cnn_lstm.yaml # 评估指定权重路径和测试集配置 python evaluate.py --config configs/cnn_lstm.yaml --checkpoint outputs/best_model.pth如果你拿到的是没封装命令行入口的源码那也简单直接用 Python 方式手动触碰训练的主力部分就好。实际上没有命令行入口的源码才是多数情况所以读懂train.py里的主流程比死记命令更重要。先顺着main()函数把数据加载、模型初始化、训练循环、模型保存这四个节点找着后续的调试会顺很多。4. 换到自己的数据上振动信号预处理、滑窗切分与标签对齐4.1 不同数据源的“格式适配”先从传感器信号说起用源码自带的示例数据跑通后很多人迫不及待换上自己的数据然后果不其然翻车。翻车点通常不在模型而在数据格式。自己的设备可能集成了多个传感器加速度计、速度计、电流互感器、温度探头采样率也不一定是整数还可能存成了工程上惯用的 DAT 或 UFF 格式。每个环节都要收敛到模型接口能吃的“标准张量”上。模型接口其实只在意三件事维度对不对、数值范围合不合理、序列长度是否一致。抓好这三件事格式适配就不难import numpy as np from scipy import signal as sig # 1. 读取自己的振动数据假设是CSV每行一个采样点 raw np.loadtxt(plant_data.csv, delimiter,, skiprows1) # 2. 重采样到统一采样率避免不同设备数据混用时长不齐 target_rate 12000 original_rate 25600 resample_ratio target_rate / original_rate resampled sig.resample_poly(raw, 1, int(1 / resample_ratio)) # 3. 去直流分量很多压电式传感器输出带有直流偏置 resampled resampled - np.mean(resampled) # 4. 幅值归一化到 [-1, 1] max_abs np.max(np.abs(resampled)) resampled resampled / (max_abs 1e-12)重采样是这里面成本最高的一步。resample_poly的原理是先插值再抽取相比直接线性插值能更好地保持频域特征。源采样率 25600 赫兹、目标 12000 赫兹这种非整数倍关系用up1, down2这种整数分式近似会更稳一点。去直流分量容易被忽略但如果不做模型可能学到的是传感器的偏置电压而非真实的振动幅值。幅值归一化这步在换数据集时务必重做——不同传感器的灵敏度不同输出范围可能差一个数量级。4.2 滑窗切分与标签对齐坑最深的一步处理完单条长信号后下一步是切成训练样本。这里有两个常见操作一个是滑窗切分另一个是重叠采样。前者好理解后者是让相邻窗口有一半重叠把数据量翻倍。很多源码包默认用 50% 重叠因为这能有效缓解样本不足的问题。但滑窗切分有一个非常隐蔽的陷阱——数据泄漏。如果你把同一条长信号切出来的所有窗口同时放进训练集和测试集相邻窗口共用同一个时间段的振动信息会造成模型“作弊”。测试时它见过的窗口和训练窗口来自同一时间段评估结果虚高部署到真实场景立刻原形毕露。正确的做法是按“时间段”划分数据而不是按“样本”划分。比如一台设备前 60% 时间段的数据做训练后 40% 做测试即使切出再多重叠窗口训练集和测试集的数据也不会有时间上的交集。def split_and_label(raw_signal, labels, window_size, overlap0.5): step int(window_size * (1 - overlap)) samples, sample_labels [], [] n len(raw_signal) # 按时间顺序切分记录每个窗口的起始时间 for start in range(0, n - window_size 1, step): samples.append(raw_signal[start : start window_size]) # 标签对齐窗口的标签 窗口中心点所在时刻的标签 center start window_size // 2 sample_labels.append(labels[center]) return np.array(samples), np.array(sample_labels)这里标签对齐采用窗口中心点的标签比用起始点更合理因为滑窗中心位置的信号信息最充分。如果你的标签是手工打点的状态段而不是逐点标签建议先把标签重采样到和信号对齐的时间分辨率再做窗口切分。另外重叠率设 0.7、0.8 这类高值要慎用重叠越多相邻样本越相似模型越容易“死记硬背”而不是真正泛化。工业场景下 50% 重叠是默认起步值数据不够再逐步往上加。4.3 做一个“脏数据过滤器”再进模型真实产线数据总会有一些让人措手不及的状况传感器掉线导致一整段全是零值、线缆松动产生的脉冲毛刺、设备停机时记录的静态噪声。这些异常段如果不处理模型要么学歪要么在推理时输出莫名其妙的预测结果。源码自带的数据集通常是人工筛选过的“干净数据”但你自己的数据没有这份优待。常见的做法是在预处理流水线里加一个快速质检步骤统计每段窗口的峰值、均方根和过零率超过合理阈值的窗口直接丢弃或标记成异常段def is_plausible_window(window, fs): rms np.sqrt(np.mean(window ** 2)) peak np.max(np.abs(window)) zero_crossings np.sum(np.diff(np.sign(window)) ! 0) if rms 1e-6: return False # 传感器掉线或静默 if peak 10 * rms 1e-12: return False # 大概率是脉冲毛刺 if zero_crossings fs * 0.01: return False # 极低频漂移不像正常振动 return True这些阈值的确定说实话带着“玄学”成分不同设备差别很大。我自己的习惯是打印一堆正常窗口的统计分布再根据 0.5% 和 99.5% 分位数去定阈值而不是猜。手动设定阈值还有一个副作用容易被忽视过完滤后训练集和测试集的数据分布可能被改变所以过滤规则一旦定好全数据集统一用同一套规则不能在测试集上单独做人工筛选否则评估结果就失真了。5. 避坑指南故障检测源码落地的 5 个“翻车点”5.1 采样率不一致导致特征频率失真现象模型在公开数据集上准确率很高一到自己设备上就崩准确率掉到五成以下。明明是两个都是轴承故障怎么就不行了原因公开数据集采样率多是 12 千赫或 48 千赫自己设备的采样率可能五花八门。模型训练时把 1200 个点当作一个固定时间长度换到不同采样率的设备上同一窗口覆盖的时间长度全变了故障特征频率被“拉伸”或“压缩”CNN 的卷积核对不上。解决所有数据统一重采样之后再做滑窗切分且保证训练阶段的signal_length参数在时间长度上和部署阶段一致。多检查一眼配置别的事都可以往后放。5.2 标签错位一个“样本错位”引发的假报警现象训练时损失降得很快验证集非常完美上线后却在设备正常运行频繁报警。原因标签打点与信号时间基准不完全对齐。有些采集系统记录的标签是 PLC 里的逻辑时间和振动采集卡的时钟有几百毫秒偏差。切窗口时用中心点标签看着合理但几百毫秒对高速旋转设备可能就是好几个周期的偏差。解决调整标签对齐的偏移量把标签序列整体向前或向后平移几十到几百个采样点做一组对比实验选验证集表现最好的偏移量。在触摸部署时为报警加一段“延迟确认”——连续 N 个窗口判定异常才触发报警有效抵抗单个窗口的误判。5.3 训练集和测试集划分不当让模型“作弊”现象测试集准确率高达 99%部署后准确率断崖式下跌怀疑模型泛化能力不行。原因把一个时间段内切出的所有窗口随机打乱后划分训练集测试集相邻窗口本质上是同一条信号的分段信息高度重叠。模型等于见过测试数据的“邻居”评估结果好看并没有实际意义。解决按时间顺序划分数据前 70% 时间段的窗口做训练后 30% 做测试。更进一步用一整台设备的数据训练、另一台设备的数据测试这才贴近真实部署场景。5.4 故障样本太少导致类别不平衡现象正常类准确率 99%但内圈故障类的 F1 分数只有 0.1训练过程对故障类几乎不学习。原因正常样本几万条故障样本几百条交叉熵损失被正常类主导模型选择“全部预测为正常”就能获得很低的损失。解决常见做法在损失函数里给少数类加权torch.nn.CrossEntropyLoss(weightclass_weights)也可以对故障类做滑窗重叠率更高的重采样。但根本上还是要多积累真实故障数据增广只是续命不是根治。5.5 随机种子未固定导致对比实验“假阳性”现象同一个模型同一份数据连续训练三轮准确率忽高忽低两个模型之间零点的几个点差异根本分不出谁好谁坏。原因深度学习训练自带随机性数据加载的顺序、权重初始化的随机数、GPU 上的浮点运算顺序都会造成结果波动。解决在训练脚本开头固定三个随机种子——Python 的random、NumPy 的np.random和 PyTorch 的torch.manual_seed对比实验里所有模型用同一个种子和同一套数据划分。没有固定种子的实验结果本质上不具备可对比性。6. 进阶用法用“影子模式”给检测模型一个真实工况下的试用期模型在离线测试集上指标再漂亮也只是在“过去的数据”上证明自己。真正检验一个故障检测模型能不能用得看它在未来时间里的表现这是完全不同的一回事。我的建议是别急着把模型接到报警链路上先跑一段“影子模式”让它和现有的监控系统并行运行只记录不动作拿两边的输出做交叉验证。影子模式的架构不复杂。产线或测试平台上的采集系统把实时振动数据同时发给两路一路走原有的规则报警逻辑一路走你训练好的深度模型。模型把每个窗口的预测结果和置信度写进日志数据库同时记录下真实设备状态——这个状态可能是事后人工确认的也可能是检修记录里查到的。跑两周到一个月把模型预测和真实状态对齐就能算出一张真实的混淆矩阵。千万别只盯准确率要看漏报率——故障没报出来导致的损失远高于误报。误报顶多是让人多点几次确认按钮漏报则可能让设备带病运行到损坏。影子模式下还有两件事值得做。第一采集模型预测失败的那些样本将它们按“最难样本”的标注补充进训练集做一轮增量训练几个迭代下来误报率通常会明显下降。第二如果模型要用到边缘设备上——比如用 ESP32 这类单片机做振动采集和预处理再将特征上传到上位机跑推理——那就要提前考虑模型体积。用 PyTorch 训练好的模型导出为 TorchScript 或 ONNX 格式再对网络做一轮剪枝和量化剪枝掉那些权重接近零的卷积核往往能在精度几乎不掉的情况下把模型缩小到原来的四分之一。这些手段都值得一试但不要在影子模式跑通之前去碰压缩否则只会多一层排错的负担。最后说一个我自己的教训。之前有一次我把模型在公开数据集上评估的准确率直接写进了项目汇报里结果现场试用时完全不对被质疑得很难看。那之后我养成了一个习惯任何模型指标后面一定要标注数据来源和实验条件——是公开数据集还是现场实测数据是离线回放还是在线实时。这套源码本身只是一个起点真正的价值是你愿意花多少时间打磨数据深度学习模型给到的回报永远和数据的用心程度成正比。希望这一篇笔记帮你在跑通源码和落地自己的故障检测系统这条路上少走几趟弯路。本文还有配套的精品资源点击获取
返回列表