简介:本资源为基于深度学习的故障检测算法完整项目源码包,面向具备Python基础、希望将深度学习落地于工业设备预测性维护的开发者与研究人员。项目围绕传感器时序数据展开,涵盖数据预处理、模型定义、训练脚本、验证测试与推理部署等环节,可帮助读者理解CNN、RNN、LSTM等网络在故障识别中的选型与调参思路。压缩包共491个文件,以254个py源码与166个pyc编译文件为主,另含30个log运行日志、5个xml配置及若干TensorBoard事件文件,整体约1.19MB,目录结构清晰,便于按模块检索复现。目前已有214人学习下载。通过该资源,读者可获取一套可运行的故障检测工程范例,掌握从特征自动提取到模型评估的完整流程,并借鉴日志与可视化记录排查训练问题,适合作为课程设计、科研实验或生产环境原型开发的参考。
1. 从一份「基于深度学习的故障检测算法.zip」说起:它到底能解决什么
设备维护的老办法是定期巡检加阈值报警,阈值定高了漏报,定低了天天误报,运维人员被折腾到麻木。这份「基于深度学习的故障检测算法.zip」指向的正是这个痛点:用数据驱动的方式,让模型自己从振动、电流、温度、声学等传感器信号里学出「正常长什么样」,一旦偏离就报警。它适合三类人:手里有设备运行数据但不知道怎么用的运维工程师、想把深度学习落地到工业场景的算法同学、以及做预测性维护方向的学生和研究者。核心思路不复杂——把时序信号切成窗口,提取特征或直接喂给网络,训练一个能区分正常与异常的模型。难点在于数据极度不平衡、故障样本稀少、工况漂移严重,这些才是决定方案能不能真正上线的关键。下面按「先立住原理、再动手复现、最后避坑」的顺序拆开讲。
2. 故障检测为什么不能直接套用图像分类那套:时序信号与标签的本质差异
2.1 故障检测任务的三种建模范式
很多人第一次做故障检测,习惯性把传感器数据画成图,然后套一个 CNN 做分类,结果发现效果远不如论文里写的。问题出在任务定义上。故障检测严格来说分三种范式:第一种是有监督分类,前提是你有大量标注好的故障样本,每个类别都有足够数据;第二种是异常检测(无监督或半监督),只用正常数据训练,推理时判断样本偏离正常分布的程度;第三种是剩余寿命预测,输出的是连续值而非类别。工业现场最常见的情况是:正常数据海量,故障数据极少,甚至只有正常数据。这时候硬做有监督分类,模型会直接学会「全部预测为正常」这个偷懒策略,准确率看着很高,但一个故障都抓不到。
我一般会先问清楚数据情况再决定范式。如果故障样本每个类别少于 50 条,优先考虑异常检测;如果故障类型明确且样本充足,才走有监督路线。这个选择直接决定了后面网络结构、损失函数和评估指标的设计,选错了后面全白做。
2.2 时序窗口切分与标签对齐
时序故障检测绕不开窗口切分。原始信号是一长串连续采样点,模型需要固定长度的输入,所以要用滑动窗口切。这里有两个参数必须想清楚:窗口长度和步长。窗口太短,故障的频域特征还没体现出来;窗口太长,故障发生的时间定位就模糊了。常见做法是让窗口至少覆盖 2 到 3 个故障特征周期。比如轴承故障特征频率是 100Hz,采样率 10kHz,那一个特征周期是 100 个采样点,窗口至少取 256 或 512。
标签对齐是另一个容易翻车的地方。如果一条记录里故障发生在第 3000 到 3500 个采样点,窗口切出来之后,哪些窗口算故障、哪些算正常,需要按窗口与故障区间的重叠比例来判定。我通常设一个阈值,比如重叠超过 50% 就标为故障,否则标为正常。这个阈值不是固定的,要根据故障持续时间和窗口长度调整。
import numpy as np def sliding_window(signal, window_size, step): """将一维时序信号切分为滑动窗口""" windows = [] for start in range(0, len(signal) - window_size + 1, step): windows.append(signal[start:start + window_size]) return np.array(windows) def label_windows(n_samples, window_size, step, fault_intervals, overlap_threshold=0.5): """根据故障区间为每个窗口打标签""" labels = [] for start in range(0, n_samples - window_size + 1, step): win_start, win_end = start, start + window_size max_overlap = 0 for f_start, f_end in fault_intervals: overlap = max(0, min(win_end, f_end) - max(win_start, f_start)) max_overlap = max(max_overlap, overlap) ratio = max_overlap / window_size labels.append(1 if ratio >= overlap_threshold else 0) return np.array(labels)上面两个函数是整个流程的入口。window_size建议从 256 起步,step一般取窗口的一半或四分之一,步长越小样本越多但相邻窗口冗余也越大。overlap_threshold设 0.5 是经验值,如果故障持续时间很短,可以降到 0.3,避免故障窗口被漏标。注意标签对齐错了,后面模型再好也是白搭,这一步值得花时间验证。
2.3 数据不平衡的处理策略
故障检测的数据不平衡是常态,正常样本可能是故障样本的几十倍甚至上百倍。直接训练会导致模型偏向多数类。常见做法有三种:重采样、代价敏感损失、以及数据增强。重采样里,过采样少数类容易过拟合,欠采样多数类会丢信息,我一般用 SMOTE 的时序版本或者简单的窗口重叠采样来增加故障样本。代价敏感损失更省事,在交叉熵里给少数类更高的权重。
import torch import torch.nn as nn class WeightedBCELoss(nn.Module): def __init__(self, pos_weight): super().__init__() self.pos_weight = pos_weight # 正样本权重,通常设为负正样本比例 def forward(self, logits, targets): return nn.functional.binary_cross_entropy_with_logits( logits, targets, pos_weight=self.pos_weight ) # 假设正常样本 10000,故障样本 200,则 pos_weight 约为 50 criterion = WeightedBCELoss(pos_weight=torch.tensor([50.0]))pos_weight的计算方式是负样本数除以正样本数。这个值设太大模型会疯狂误报,设太小又抓不到故障,建议从比例值开始,在验证集上根据 F1 分数微调。如果数据增强后样本已经比较均衡,pos_weight可以降到 5 到 10 左右。评估指标上,别只看准确率,重点看召回率和 F1,工业场景漏报的代价远大于误报。
3. 从原始信号到模型输入:特征工程与网络结构选型
3.1 时域、频域、时频域特征怎么选
原始振动信号直接喂网络不是不行,但收敛慢、对噪声敏感。常见做法是先做特征提取,再送进模型。时域特征包括均方根、峰值因子、峭度、偏度,计算快,对冲击类故障敏感。频域特征通过 FFT 得到频谱,看故障特征频率处的幅值。时频域用短时傅里叶变换或小波变换,能得到随时间变化的频率分布,适合非平稳信号。
我的经验是:如果故障表现为周期性冲击,时域峭度加频域特征就够了;如果工况变化剧烈,比如变转速变负载,时频图更稳。但时频图数据量大,对显存要求高,小规模场景没必要上。实际项目里我经常把时域和频域特征拼成一个向量,维度控制在 50 以内,再送进全连接网络或一维卷积,效果和直接上二维时频图差不多,但训练快很多。
from scipy.stats import kurtosis, skew from scipy.fft import fft import numpy as np def extract_features(window): """提取时域和频域特征""" feats = [] # 时域特征 feats.append(np.sqrt(np.mean(window ** 2))) # RMS feats.append(np.max(np.abs(window))) # 峰值 feats.append(kurtosis(window)) # 峭度 feats.append(skew(window)) # 偏度 # 频域特征 spectrum = np.abs(fft(window))[:len(window) // 2] feats.append(np.mean(spectrum)) # 频谱均值 feats.append(np.std(spectrum)) # 频谱标准差 feats.append(np.argmax(spectrum)) # 主频位置 return np.array(feats)这段代码对每个窗口提取 7 维特征。kurtosis对冲击故障特别敏感,正常轴承峭度接近 3,出现剥落时会明显增大。argmax(spectrum)给出主频位置,如果主频偏移到故障特征频率附近,就是异常信号。特征提取后建议做标准化,否则量纲差异会让网络训练不稳定。
3.2 一维 CNN 与 LSTM 的取舍
特征工程之后,网络结构选型是下一个决策点。一维 CNN 擅长提取局部模式,计算效率高,适合窗口内特征明显的场景。LSTM 或 GRU 擅长建模长程依赖,适合故障逐渐演化的场景,但训练慢、调参难。我一般先用一维 CNN 打基线,如果效果不够再考虑加 LSTM 层做混合模型。
import torch.nn as nn class CNN1D(nn.Module): def __init__(self, in_channels=1, num_classes=2): super().__init__() self.net = nn.Sequential( nn.Conv1d(in_channels, 16, kernel_size=7, padding=3), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size=5, padding=2), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Linear(32, num_classes) ) def forward(self, x): return self.net(x)这个网络只有两层卷积,参数量小,适合样本量不大的场景。kernel_size第一层取 7 是为了覆盖足够的局部波形,第二层取 5 逐步抽象。BatchNorm1d在时序任务里能明显加速收敛。如果窗口长度是 512,经过两次池化后特征长度是 128,AdaptiveAvgPool1d(1)把它压成 1,再接全连接分类。这个结构在多数故障检测任务上能拿到不错的基线,训练时间也短。
3.3 训练流程与验证集划分
训练流程里最容易出错的是验证集划分。时序数据不能随机打乱划分,否则相邻窗口会同时出现在训练集和验证集,造成数据泄漏,验证指标虚高。正确做法是按时间顺序切分,前 70% 做训练,中间 15% 做验证,最后 15% 做测试。如果数据来自多台设备,也可以按设备划分,用一部分设备训练,另一部分设备验证,这样更能反映泛化能力。
def temporal_split(windows, labels, train_ratio=0.7, val_ratio=0.15): """按时间顺序划分数据集,避免数据泄漏""" n = len(windows) train_end = int(n * train_ratio) val_end = int(n * (train_ratio + val_ratio)) return (windows[:train_end], labels[:train_end], windows[train_end:val_end], labels[train_end:val_end], windows[val_end:], labels[val_end:])这个函数保证训练集在时间上早于验证集和测试集。如果设备工况随时间漂移,这种划分能真实反映模型在未来数据上的表现。训练时用早停策略,验证集损失连续若干轮不下降就停,避免过拟合。学习率用余弦退火或阶梯下降,初始值 1e-3 比较稳。
4. 故障检测落地时最容易翻车的五个地方
4.1 现象:验证集 F1 很高,上线后几乎不报警
原因通常是数据泄漏。相邻窗口高度相似,随机划分让训练集和验证集共享了大量几乎相同的样本,模型只是记住了这些样本。解决方法是严格按时间或设备划分,并且检查窗口之间是否有重叠。如果步长小于窗口长度,相邻窗口天然重叠,划分时要在边界处留出缓冲区间,把边界附近的窗口丢弃。
4.2 现象:模型对训练过的故障类型很准,新故障类型完全抓不到
原因是有监督分类只能识别见过的类别,对未知故障无能为力。解决方法是引入异常检测分支,用自编码器或单类 SVM 在正常数据上训练,推理时同时看分类置信度和重构误差。如果重构误差超过阈值,即使分类器说是正常,也触发报警。两个分支的阈值需要在验证集上联合调。
4.3 现象:同一台设备今天正常明天报警,阈值飘忽不定
原因是工况变化导致信号分布漂移,固定阈值失效。解决方法是做在线归一化或自适应阈值。常见做法是用滑动窗口统计最近一段时间的均值和方差,对输入做标准化,让模型看到的分布保持稳定。阈值也可以按最近正常数据的分布动态调整,比如取最近 1000 个正常窗口重构误差的 99 分位数作为报警线。
4.4 现象:训练损失正常下降,但验证损失震荡剧烈
原因可能是批次太小或学习率太高。时序任务里批次太小会让梯度噪声大,建议批次至少 64。学习率从 1e-3 开始,如果震荡就降到 1e-4。另外检查一下输入是否做了标准化,未标准化的数据会让网络难以收敛。如果用了 LSTM,还要注意梯度裁剪,防止梯度爆炸。
4.5 现象:推理延迟太高,满足不了实时性要求
原因是模型太大或特征提取太慢。解决方法包括:用一维 CNN 替代 LSTM,减少参数量;把特征提取从 Python 循环改成向量化计算;用 ONNX 或 TorchScript 导出模型加速推理。如果还是不够,可以考虑知识蒸馏,用大模型教一个小模型,推理时只跑小模型。实时性要求高的场景,窗口步长可以适当加大,减少推理次数。
5. 把模型压到能上线的程度:量化、导出与在线验证的一个具体技巧
模型训练完只是第一步,能不能在工控机或边缘设备上跑起来才是关键。我一般会先做动态量化,把浮点权重转成 int8,模型体积能缩小到四分之一左右,推理速度提升明显,精度损失通常在 1% 以内。PyTorch 里几行代码就能搞定。
import torch # 假设 model 已经训练好并切换到 eval 模式 model.eval() quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear, torch.nn.Conv1d}, dtype=torch.qint8 ) torch.save(quantized_model.state_dict(), "fault_detector_quantized.pt")quantize_dynamic只量化线性层和卷积层,对时序模型很友好。量化后要在验证集上重新跑一遍,确认 F1 下降不超过 2 个百分点。如果下降太多,可以只量化全连接层,保留卷积层为浮点。导出成 ONNX 之后,用 ONNX Runtime 推理,在 CPU 上通常能比原生 PyTorch 快 2 到 3 倍。
上线之后别急着全量替换人工巡检,先做影子模式:模型在后台跑,报警只记录不触发动作,和人工判断对比一段时间。我习惯至少跑两周,统计误报率和漏报率,确认稳定后再逐步接入报警系统。这个过程中要持续收集新数据,定期用新数据微调模型,因为设备状态会随磨损变化。最后说一个我踩过的坑:曾经因为忘了在推理前做和训练时一致的标准化,导致上线后模型输出全乱,排查了一整天才发现是均值方差没对齐。模型部署里,预处理和后处理的一致性比模型结构本身更容易出问题,建议把预处理逻辑封装成一个函数,训练和推理共用同一份代码。希望帮到你。
本文还有配套的精品资源,点击获取