深度学习故障检测实战:多模型融合方法详解
2026/9/15 3:21:43 网站建设 项目流程

简介:围绕CWRU轴承数据集,这份Python源码项目实现了多种深度学习模型的故障检测算法,适合深度学习和故障诊断领域的研究者与工程师参考学习。项目代码结构清晰,包含自编码器(AE)与卷积神经网络(CNN)等模型的不同数据预处理方式,并提供了详细的训练脚本和工具模块。资源共478个文件,压缩包大小1.16MB,以Python源码(.py)为主,辅以编译文件、训练日志(.log)和配置说明等。其中训练日志与指标数据支持TensorBoard可视化,便于对比模型效果。在原有代码基础上,还增加了精确率、召回率、误报率、漏报率等评估指标的计算,并加入模型训练过程与CWRU数据变换的可视化脚本,方便深入理解算法机理。目前已有873人学习下载,对想系统掌握故障检测深度学习流程的开发者,这套代码提供了从数据预处理、模型搭建到训练评估的完整参考实现,具备较强的实践价值。

1. 故障检测为什么需要“多种深度学习”而不是一个模型

在故障检测这个场景里,多放几个深度学习模型进去,不是为了让指标栏更热闹。真实生产线上故障样本往往很少、噪声很大、标签还有错的,单模型一旦因为工况变化或者传感器漂移而误报,维护人员很快就会对整个预警系统失去信任。“多种深度学习”组合的核心价值是互补:CNN 擅长抓局部冲击特征,LSTM 能把时间上的退化趋势记下来,自编码器在几乎没有故障标签时也能靠重构误差发现没见过的新状态。这篇博客就按这种故障检测算法项目最常见的工程落地方式来讲:模型分工、源码里的数据流、模型训练和参数设置、评估与集成方式,最后落成一个能直接上手的验证顺序。适合做工业 AI、设备健康管理,以及需要自己改算法和调参的应用工程师。

2. 故障检测里三种常用深度模型的做法和边界

2.1 一维 CNN 从原始波形里找故障冲击特征

故障信号上常见的是轴转一圈产生的周期性冲击、摩擦带来的调幅成分,以及不稳定的瞬态噪声。傅里叶变换能把整段信号变成频谱,但会丢掉冲击出现的时间位置;时频图保留了时间信息,计算量和参数选择又偏复杂。一维 CNN 恰好落在这个粒度上:卷积核像一个小窗沿时间轴滑动,以近似平移不变的方式抓取“出现了什么样的局部波形”,对信号里的冲击尖峰和幅值突变相当敏感。

在故障检测项目里用一维卷积时,结构上要刻意去匹配故障特征的长度范围。比如滚动轴承的外圈故障,在某个转速下冲击间隔是有规律的;卷积核太小、层数太浅,网络只能看见瞬时的尖峰,反而容易被噪声带偏。我一般会把 kernel_size 设成 7 或 11,用 1 到 2 层卷积、接一个全局平均池化,把时序维度压成单个向量后再进分类头。另一个值得注意的参数是 dilation,膨胀卷积可以在不增加核大小的情况下扩大感受野,对采样率高的长窗口特别有用。

2.2 LSTM 在退化趋势和工况变化上的作用

CNN 对样本的顺序不敏感,因为训练时批次里的样本本来就是随机排列的;LSTM 则把顺序当成核心信息,它建模的是“前一时刻的状态如何过渡到下一时刻”。设备退化过程中,早期故障征兆往往极弱,单看一个短窗口和正常数据几乎没有区别,但把前后几个窗口的状态串起来,特征之间的转移规律就开始分叉了。这正是 LSTM 在既有 CNN 之外能带来增量的地方。

LSTM 还有一个实操优势叫流式推理。振动传感器以固定采样率采集数据,数据源源不断进来,如果用滑窗推分类,每来一个新窗口就要重算一整段长度;换成带状态的 LSTM,更新 hidden state 只需要消费最新一小段数据,计算量基本固定,这在边缘盒子上做实时预警很友好。代价是 LSTM 参数多、收敛慢,故障样本少时容易过拟合,所以源码里通常会在层与层之间加 dropout,并保留双向开关,数据量不够时优先关闭双向。

2.3 自编码器用重构误差给无标签场景兜底

把自编码器单独放进模型集合里,比“多一个模型投票”更实际。故障检测的标注成本很高,很多时候手里只有正常样本,故障样本要等设备真的坏了才能攒出来。自编码器只对正常样本做重建训练,网络相当于把“正常长什么样”压缩成一种低维表示;当输入一个故障样本时,压缩瓶颈无法把它重建回正常形态,重构误差就会明显拉高。

关键在报警阈值怎么定。常见做法是把验证集里的正常样本全部过一遍模型,收集重构误差分布,用均值加 3 倍标准差作为报警线,或者直接用误差的 97.5 分位数。项目说明里一般会把这一步独立成一个脚本,原因是阈值需要跟着数据分布不定期重标定,而不是每次都要重训模型。这个设计在工程上很划算,也是很多源码项目把 AE 单独拆成一个模块的原因。

2.4 多模型怎么做输出协商

把三类模型的输出直接做硬投票有点浪费,因为 AE 输出的是连续误差,CNN 和 LSTM 输出的是类别概率,量纲不一致。更稳的做法是把概率和归一化后的误差加权打成一个分数;权重不定时,可以先分别算出每个模型在验证集上的 AUC,再把 AUC 归一化成权重使用,效果差的模型权重自然低。下表是三者在同一套故障检测任务里的角色对照。

模型输入形态核心机制在故障检测里的角色最容易出的问题
1D CNN[B, C, T]沿时间轴局部卷积抓冲击、幅值突变等局部特征感受野不足,漏掉慢变故障
LSTM[B, T, C]门控状态在时间步之间传递记录退化趋势,适合流式推理样本少时过拟合,训练不稳定
自编码器[B, C, T]压缩特征再重建原信号无标签时用重构误差判异常阈值没校准好,误报率高

三者并存不是必须的,而是给场景留选择空间。单一故障模式明确,CNN 往往已经够用;高噪声下漏检率高,加 LSTM;故障标签几乎没有,先把 AE 跑起来。真正的“多种深度学习”效果,来自对这三类输出的合理融合,而不是把它们并列展示。

3. 用 Python 源码把多模型故障检测框架搭起来

3.1 项目目录和配置文件设计

拿到一套故障检测 Python 源码,最先看的不是算法文件,而是目录和数据入口。一个方便维护的项目结构通常长这样:

fault-detect/ ├── configs/ │ └── train.yaml ├── src/ │ ├── data.py │ ├── models.py │ ├── train.py │ └── evaluate.py ├── data/ ├── checkpoints/ ├── logs/ └── README.md

configs/train.yaml承担所有可调参数,代码里不写死路径和超参。这个设计的意义在于:换数据集、换采样率、换模型组合,都只改配置不动源码。下面是故障检测项目里最常见的配置字段:

data: csv_path: "data/train.csv" signal_col: "vibration" label_col: "label" sample_rate: 10240 window_len: 2048 step_len: 512 train_ratio: 0.7 model: cnn_kernel: 7 lstm_hidden: 64 lstm_layers: 2 dropout: 0.2 train: epochs: 50 batch_size: 64 lr: 0.001 patience: 8

csv_path对应原始采集数据,signal_col是振动幅值列名,label_col是故障标签列。window_lenstep_len是滑窗参数,直接影响模型输入长度和报警延迟,这两项在第 5 章会专门展开。配置里把model单独成段,是因为常有同时跑三个模型再比较的场景,模型名称可以在train.py里用参数传进去。

3.2 数据预处理与滑窗构建

故障检测的原始数据通常是一条很长的振动波形,比如几十万点的一维数组,不能直接丢给模型。需要先做滑窗,把长序列切成固定长度的样本,一个窗口就是一个模型输入。窗口切分时要注意一个细节:标签必须跟随窗口,多数方案的标签取自窗口结束时刻的状态,因为报警只对已经发生的故障有意义。

滑窗构建的最小实现如下:

import numpy as np def to_windows(x: np.ndarray, win: int, step: int): n = (len(x) - win) // step + 1 idx = np.arange(win)[None, :] + np.arange(n)[:, None] * step return x[idx]

这里先生成一个[n, win]的索引矩阵,再通过 NumPy 高级索引一次性取出所有窗口。win是窗口长度,step是窗口滑动的间隔。当step < win时相邻窗口有重叠,样本量增大但信息也重复;当step == win时窗口完全不重叠,数据量最小。

归一化要在滑窗之后进行,并且只能从训练集里计算均值和标准差,再用同一组统计量去转换验证集和测试集。如果整段数据算完归一化再切窗,验证集信息会泄漏进训练过程,后面的评估指标都会虚高。关于泄漏的问题,第 5 章还会再讲。

3.3 CNN 与 LSTM 的最小可运行实现

模型代码建议集中放在一个models.py里,用字典注册后按名字取。CNN 部分用 PyTorch 实现:

import torch import torch.nn as nn class CnnFaultDetector(nn.Module): def __init__(self, n_channels=1, n_classes=2, kernel_size=7): super().__init__() self.features = nn.Sequential( nn.Conv1d(n_channels, 32, kernel_size, padding=kernel_size // 2), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, 5, padding=2), nn.ReLU(), nn.AdaptiveAvgPool1d(1), ) self.classifier = nn.Linear(64, n_classes) def forward(self, x): # x: [batch_size, n_channels, window_len] z = self.features(x).squeeze(-1) return self.classifier(z)

两个一维卷积层构成特征提取主干,AdaptiveAvgPool1d(1)把任意窗口长度都压缩成长度为 1 的向量,因此切换采样率时不用改网络结构。n_channels对应传感器通道数,单一加速度计是 1,三轴信号则是 3。分类头输出的是类别 logits,训练时配合CrossEntropyLoss使用。

LSTM 部分输入顺序是[batch, seq_len, feature],注意和 CNN 的[batch, channel, seq]不同:

class LstmFaultDetector(nn.Module): def __init__(self, n_channels=1, hidden=64, num_layers=2, n_classes=2, dropout=0.2): super().__init__() self.lstm = nn.LSTM( input_size=n_channels, hidden_size=hidden, num_layers=num_layers, batch_first=True, bidirectional=False, dropout=dropout, ) self.head = nn.Linear(hidden, n_classes) def forward(self, x): # x: [batch_size, window_len, n_channels] out, _ = self.lstm(x) return self.head(out[:, -1, :])

out[:, -1, :]取最后一个时间步的隐状态作为整段序列的汇总。num_layers=2时 dropout 才会生效,层数为 1 时 PyTorch 会给出警告。LSTM 的收敛速度比 CNN 慢,训练时建议先把lr降到5e-4量级,否则 loss 容易剧烈抖动。

3.4 自编码器训练与阈值确定

自编码器不输出类别,它输出的是一段重建波形。先看网络结构:

class ReconstructiveAE(nn.Module): def __init__(self, n_channels=1, hidden_dim=32): super().__init__() self.encoder = nn.Sequential( nn.Conv1d(n_channels, 16, 3, padding=1), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, hidden_dim, 3, padding=1), nn.ReLU(), nn.MaxPool1d(2), ) self.decoder = nn.Sequential( nn.Upsample(scale_factor=2, mode="nearest"), nn.Conv1d(hidden_dim, 16, 3, padding=1), nn.ReLU(), nn.Upsample(scale_factor=2, mode="nearest"), nn.Conv1d(16, n_channels, 3, padding=1), ) def forward(self, x): return self.decoder(self.encoder(x))

这个结构依赖输入长度能被 4 整除,所以早期推荐window_len=2048是有意的设计,避免反卷积后尺寸对不上。训练时把窗口长度设计为 2 的幂能省去很多排错。

AE 是正常样本训练,loss 用 MSE 即可:

criterion_ae = nn.MSELoss() # 推理阶段计算每个窗口的重构误差 scores = [] for x in normal_loader: x = x.to(device) rec = ae(x) mse = torch.mean((x - rec) ** 2, dim=(1, 2)) scores.extend(mse.cpu().tolist()) threshold = np.mean(scores) + 3 * np.std(scores)

dim=(1, 2)表示对通道维和窗口内部的所有点求平均,每个窗口得到一个标量误差分数。判断故障时,测试样本的误差大于阈值就标为异常。这里的 3 是一个经验系数,想压低误报就往上调,想更早抓住退化就往下调。它的合理范围取决于现场噪声水平,所以项目说明里通常要求这一步用单独的脚本跑,随手可调。

4. 多模型评估:不平衡样本和数据对比的呈现方式

4.1 故障检测里比准确率更可靠的评价指标

故障检测数据集里正常样本通常远多于故障样本,类别比例放到 10:1 甚至 100:1 都不奇怪。这种数据分布下,全部预测成“正常”也能拿到很高的 accuracy,但这个模型没有使用价值。看一张常见的评估维度对照:

指标在故障检测里的作用需要注意的问题
Accuracy整体正确率样本不平衡时严重虚高
Precision报警中有多少是真故障故障少时可能虚高
Recall真实故障被抓住多少单独追高会刷出大量误报
F1精确率和召回率的调和平均最常见的主评估指标
漏报率故障被当成正常的比例制造业里最不希望看到的指标
误报率正常被当成故障的比例过高会让运维人员关掉系统

工程上更常看的是漏报率和误报率两个单独指标,因为它们的代价不对等。漏报一次可能直接导致设备停机,误报一次只是多派人看一眼。评估时两个值要同时记录,才算把模型放到了真实业务视角里。

用 sklearn 可以一次性算齐:

from sklearn.metrics import confusion_matrix, f1_score cm = confusion_matrix(y_true, y_pred) tn, fp, fn, tp = cm.ravel() fnr = fn / (fn + tp) # 漏报率 fpr = fp / (fp + tn) # 误报率

confusion_matrix的返回顺序是真正的负类在前,所以ravel()解包后依次是 tn、fp、fn、tp。这段代码在故障检测项目里几乎必出现,建议直接封装成评估函数,而不是每次在 notebook 里复制。

4.2 评估表的呈现方式和观察顺序

模型之间的比较,不能只丢一个最终的 F1 数字。需要在同一份测试集上记录每个模型的四类信息:精确率、召回率、漏报率和单窗口推理耗时。推理耗时对边缘部署有直接意义,算力不足时哪怕精度高 1 个百分点,也可能不值得换。

模型PrecisionRecallF1单窗口耗时 ms
CNN0.8920.8640.8782.1
LSTM0.8750.9030.8899.6
自编码器0.8010.7570.7782.8
加权融合0.9040.9150.90912.4

看表时先看漏报率低的模型,再比较 F1。如果只有一个模型的召回率异常高但精确率掉得很厉害,说明它把大量正常波动都报成了故障,这种趋势性报警在连续生产线上会造成报警疲劳。融合行通常会在场时延上微微增大,但 F1 往往更稳,这就是多模型融合最直观的收益。

4.3 把几个模型聚合成一个决策

融合代码不复杂,核心是把三种输出统一到同一量纲。CNN 和 LSTM 已经输出类别概率,AE 只有重构误差,需要先把误差做一个归一化映射,让正常样本接近 1、异常样本接近 0:

import numpy as np def ae_error_to_score(errors, threshold): # 误差越小越可能是正常,分数越接近 1 return np.clip(1.0 - errors / threshold, 0.0, 1.0) def ensemble_score(cnn_prob, lstm_prob, ae_score): weights = np.array([0.4, 0.4, 0.2]) return weights @ np.array([cnn_prob, lstm_prob, ae_score])

ae_error_to_score用阈值把误差压到 0 到 1 之间,误差等于阈值时分数正好为 0。ensemble_score里的权重可以先用验证集 AUC 归一化后替换,也可以手工调整。判别时看融合分数是否大于 0.5,这个阈值的选取和 AE 的阈值一样,应该在验证集上单独校准,而不是直接沿用模型训练时的分类阈值。

5. 参数调整和验证时的几个实际坑

5.1 滑窗长度、步长和设备延迟的关系

滑窗参数是故障检测项目里最容易忽略但影响最大的两个值。window_len决定一个样本里能看到多长的信号,step_len决定相邻窗口之间前移多少点。实时场景里,从新数据到达窗口尾部到模型给出结果,期间的固定延迟就等于step_len / sample_rate。换算很简单:

latency_ms = step_len / sample_rate * 1000

假设采样率 10240 Hz、步长 512,延迟就是 512 / 10240 × 1000 = 50 ms。想要更快,把步长改成 256,延迟降到 25 ms,但推理频率翻倍。参数取舍一般是先把窗口长度定到覆盖 2 到 3 个回转周期,再按可接受的延迟倒推步长。比如轴转速 30 Hz,一个周期约 333 点,窗口取 1024 到 2048 点比较合理,太小抓不全冲击序列,太大又把多段工况混进同一个样本。

步长还会影响训练样本数量。原始数据长度固定时,步长越小窗口越多,类别分布也可能因此改变。在故障工位持续时间短的场景,窗口定密一点能抓到更多故障片段,但相邻样本高度相关,验证集指标看起来很好,现场效果却未必。

5.2 类别不平衡和阈值选取

故障样本数量远远少于正常样本时,先用加权损失,再调整阈值。CrossEntropyLoss支持weight参数,可以直接按类别频率的反比设置:

weights = torch.tensor([1.0, pos_weight]).to(device) criterion = nn.CrossEntropyLoss(weight=weights)

pos_weight是故障类别的权重,最简单取正常样本数除以故障样本数。实际中不必严格执行频率反比,权重给到 2 到 10 之间往往就够。损失加权之后,模型输出概率整体会偏向故障类,此时再在验证集上重新寻找分类阈值。更稳妥的做法是不看默认的 0.5,而是在验证集上遍历阈值 0.3 到 0.7,选 F1 最高的点。

AE 的阈值同理,可以在验证集上收集所有样本的重构误差,画一条误差分布曲线,把阈值定在正常样本分布的右尾,而不是永远用mean + 3 * std

5.3 归一化和时间序列数据泄漏

一维卷积本质上对幅值尺度敏感,所以归一化必须做,但一定不能引入未来信息。很多人把整段信号标准化之后再切窗口,切出的训练集和验证集混着同一条时间线,验证集等于提前偷看了全局的均值和方差,评估结果会被明显抬高。

标准做法是先在时间轴上切出训练段,用训练段算meanstd,再应用到你全部后续数据上。部署阶段更麻烦一点,传感器每时每刻都在产生新数据,不能等一天的数据全攒齐再归一化。常见的做法是把滚动均值和方差存成状态,每进来一个窗口就增量更新,核心原则只有一个:计算当前窗口时,只能用过去的数据,不能用未来的数据。

提示:时间序列里做随机打乱也有泄漏风险。训练集内打乱是允许的,但验证集和测试集必须按时间顺序切,不能把同一条连续波形的不同窗口同时放进训练和验证,这样会把“记忆波形”误判成“检测故障”。

6. 拿到这套源码后先做的三个验证动作

6.1 用仿真信号把全流程跑通

在真实故障数据还没到位时,先用一段可控的仿真信号验证代码链路没有断点。生成一个带周期性冲击的振动信号,只要几十行:

import numpy as np import pandas as pd rng = np.random.default_rng(0) t = np.linspace(0, 2, 20000) normal = 0.6 * np.sin(2 * np.pi * 30 * t) + rng.normal(0, 0.03, t.shape) impt = (t % 0.1) < 0.005 fault = normal + 0.8 * rng.normal(0, 1, t.shape) * impt sig = np.concatenate([normal, fault]) label = np.concatenate([np.zeros(len(normal)), np.ones(len(fault))]) df = pd.DataFrame({"vibration": sig, "label": label}) df.to_csv("data/sim_fault.csv", index=False)

这段代码生成前半段正常、后半段带周期冲击的信号,label列直接给出标签,训练框架可以原样消费。运行之后重点看:训练 loss 是否在下降、验证集 F1 是否明显高于随机水平、AE 的正负样本误差分布是否分开。这三个现象同时出现,说明整套源码的链路是通的。

6.2 核对配置里的采样率和标签列

换到自己的数据时,最先改的是configs/train.yaml里的csv_pathsignal_collabel_col。采样率一旦填错,所有和物理转速相关的窗口设计都会失真。填完后用一套几百条样本的小数据跑一个 epoch,确认data.py正确读出了预期的窗口数量和维度。

6.3 保存验证集概率,重新调阈值

最后一个值得做但不是所有人都做的动作:把验证集上的 CNN 和 LSTM 预测概率、AE 重构误差分别用np.save存成文件。以后调整融合权重或者换阈值,只对这些保存下来的结果重新计算,不需要再跑一遍模型。这样每次调参从分钟级变成秒级,多模型融合方案的迭代速度会明显提升。另一个常被忽略的细节是,保存概率时也要顺带存一份样本时间戳,后续复盘误报时能直接定位到具体工况区间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询