
简介压缩包提供了一套完整的基于深度学习的故障检测项目适用于人工智能与深度学习方向的开发者重点面向工业设备预测性维护场景。项目完全基于 Python 构建包含从数据预处理、神经网络结构设计、模型训练到验证测试与最终推理部署的完整流程。资源内部共有四百九十一个文件以 Python 源代码为主其中包含二百五十四个脚本文件、一百六十六个编译后文件另附三十个运行日志可借助 TensorBoard 查看训练过程还有少量配置文件与项目说明。整个压缩包体积仅为一点一九兆便于快速下载与复现实验。目前已有二百一十四人学习使用。通过该项目可以掌握传感器数据清洗与归一化、特征自动提取、多种神经网络模型调优及性能评估方法并可根据实际设备数据修改或扩展代码。资料内还提供文档和环境配置说明帮助开发者复现结果适合希望将深度学习技术落地到实际工程的中高级学习者。1. 拿到 zip 别急着解压先确认这是哪一类故障检测“基于深度学习的故障检测算法.zip”这个命名方式很典型压缩包里面大概率不是一份算法原稿而是把数据集、训练脚本、调用入口和模型权重打成了一个可交付的工程包。深度学习故障检测做到 80% 准确率不难难的是把正常样本和异常样本的分界线划得清楚。不同场景里“故障”的定义完全不一样可能是轴承的振动模式变了可能是电网电压波形畸变也可能是服务器日志里的慢请求聚集这个标题要解决的其实是“怎么用一个统一框架把异常找出来同时能快速迁移到新场景”。如果你是做工业预测维护、设备状态监测或者时序数据挖掘的工程师这套东西是能直接补进自己工具箱的。先对齐一个前提压缩包里的模型通常不会是一个孤立的.pt或者.h5文件而是连同数据加载器、训练配置、推理脚本一起封装的完整工程。也就是说“算法”不是一句话能说清的它是一条链路数据怎么进、特征怎么提取、网络结构长什么样、阈值怎么定。这三件事在代码里是分开的很多人在第一个环节就开始跑偏。2. 故障检测的深度学习算法选型分类、重建与时序预测2.1 三种主流范式有监督分类、自编码重建、时序预测拿到故障检测这个任务第一件事不是打开压缩包看代码而是先判断手里有什么标签。故障检测在算法层面对应三个基本范式每种范式对数据的要求和落地难度相差非常大。有监督分类是最直观的方案把“正常”和“故障”当作两个类别训练一个二分类器或者把若干故障类型当作多分类目标。它的前提是必须存在高质量的历史标注数据而且故障样本要覆盖得足够全面。在设备运行稳定的产线上故障样本往往只占 1% 以下直接训练分类器会严重偏向多数类后面必须配合过采样、欠采样或者 focal loss。特征提取用一维卷积网络类似Conv1D结构处理振动信号很常见几个卷积层加全局池化再加全连接就能出概率输出。自编码重建是另一种更符合“异常”本质的做法。正常数据训练一个自编码器让输入和输出尽量一致当输入出现从未见过的故障模式时重建误差会突然变大用这个误差作为异常分数。压缩包里如果出现ReconstructionError、AnomalyScore这类变量名走的通常就是这条路线。它的好处是不依赖故障样本标注正常数据足够多就能训练尤其适合样本不均衡的场景。代价是重建误差对噪声敏感阈值需要单独定而且对新出现的正常工况也可能误报。时序预测范式则针对有周期性的信号比如电机转速、电网负荷、网络流量。用历史多步输入预测下一时刻的值当预测值与实际值偏差超过预设上限就触发故障。这类方案本质上是在拟合信号的发展趋势对突变型故障敏感对缓变型退化则容易预警太晚。2.2 数据形态决定网络结构二维图像与一维序列压缩包里网络结构怎么选取决于输入数据最初是什么形态。很多做工业检测的人容易犯一个错误无论什么数据都套用CNN的图像处理思路却忽略了故障检测领域大量数据本质上是一维时序信号。如果数据是振动加速度传感器采出来的时间序列Conv1D加滑窗是最直接有效的结构输入形状是(batch, window_size, channel)。如果用Conv2D去处理一维信号还需要先做短时傅里叶变换或者小波变换把序列变成频谱图这一步引入了额外的参数选择和计算开销反而偏离了工程落地的初衷。深度学习网络层数的设置在故障检测里不需要盲目追求深。一维卷积网络的感受野随层数线性增长三层卷积的感受野大概是(kernel_size - 1) * 3 1对于窗口长度为 256 的输入三层卷积加一个全局池化已经完全够用。更深的网络意味着需要更多数据去拟合而故障检测场景里数据量通常不是够不够的问题是多类样本分布不均匀的问题。与此对应的是批量归一化层要加在激活函数之前这能避免滑窗数据中不同样本尺度差异过大引起的梯度抖动。2.3 常见误用把正常样本也当负样本去训练一些从图像分类迁移过来的人会把“正常”和“异常”当作label0与label1然后正常数据全部贴上 0 标签丢进训练集。这个做法在数据量充足时问题不大但在故障检测场景里却有一个隐蔽的坑正常样本之间也存在工况差异。比如设备在不同转速、不同负载下采集的正常信号直接在原始值域上混在一起训练分类器学到的可能不是“故障的特征”而是“不同工况的特征”。后面推理时遇到一个新的正常工况模型得分偏高误报率直接飙升。我一般会先对正常数据做一次聚类按工况划分后分别训练归一化参数或者在数据加载时按工况标签做分层采样。这个细节压缩包里的数据加载器未必帮你处理了拿到后需要自己补上。3. 在本地跑通故障检测最小算法数据加载与模型训练3.1 解压后的工程结构与数据准备动手前先给压缩包建立一个干净的虚拟环境推荐用conda或者venv。深度学习故障检测算法依赖的包通常跑不脱torch、numpy、pandas、sklearn、matplotlib这一组多了反而是累赘。# 创建虚拟环境Python 版本建议 3.9 或 3.10 conda create -n fault_detect python3.10 -y conda activate fault_detect # 安装基础依赖CPU 版本 torch 足够跑通小规模训练 pip install torch --index-url https://download.pytorch.org/whl/cpu pip install numpy pandas scikit-learn matplotlib提示先不要装任何 GPU 版本的深度学习框架。故障检测的数据量通常不大CPU 训练在小型数据集上完全可以跑通先把逻辑跑通再考虑加速否则容易浪费时间在 CUDA 环境排查上。然后是数据侧的标准动作。多数情况下压缩包里会有一个data/目录里面可能是 CSV、.npy或者.parquet。第一步先把时间序列切成滑窗样本同时保留时间戳作为后续故障时间定位的索引。import numpy as np import pandas as pd def create_windows(data: np.ndarray, window_size: int 256, stride: int 32): 将一维时序信号切成滑窗样本返回 (样本数, 窗口长度) 数组 samples [] for start in range(0, len(data) - window_size 1, stride): samples.append(data[start:start window_size]) return np.array(samples) # 读取一份振动信号示例data_path 替换为实际的位置 raw_signal pd.read_csv(data/bearing_signal.csv, headerNone).values[:, 0] windows create_windows(raw_signal, window_size256, stride32) print(windows.shape) # 期望输出类似 (N, 256) del raw_signal这里有两个参数值得停下来解释。window_size256意味着每次模型看到 256 个连续采样点它决定了模型的感受野滑动窗口太小容易丢失故障的上下文信息太大则拉高计算开销stride32是相邻窗口的重叠程度步长越小样本越多但相邻样本高度相关会导致验证集和训练集之间存在信息泄漏做评估时务必先按时间顺序切分再滑窗。3.2 搭建自编码器作为故障检测模型故障检测的自编码器结构与普通图像自编码器没有本质区别关键在编码器输出维度要远小于输入维度强制模型只保留“正常模式”的共性特征。下面这个结构在轴承故障检测场景里被反复使用压缩、重建、误差计算三个环节都在一个模型里完成。import torch import torch.nn as nn class FaultDetectAutoEncoder(nn.Module): 一维卷积自编码器输入为 (batch, 1, window_size)输出与输入同形状 def __init__(self, window_size: int 256, compress_dim: int 32): super().__init__() # 编码器三层一维卷积通道数逐层增加空间维度逐层压缩 self.encoder nn.Sequential( nn.Conv1d(1, 16, kernel_size5, stride2, padding2), # 输出长度 128 nn.BatchNorm1d(16), nn.ReLU(), nn.Conv1d(16, 32, kernel_size5, stride2, padding2), # 输出长度 64 nn.BatchNorm1d(32), nn.ReLU(), nn.Conv1d(32, 64, kernel_size5, stride2, padding2), # 输出长度 32 nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(1) # 压缩到 (batch, 64, 1) ) # 用全连接把特征压缩到指定维度 self.compress nn.Linear(64, compress_dim) # latent 编码 self.expand nn.Linear(compress_dim, 64) # 解码器与编码器对称用转置卷积恢复长度 self.decoder nn.Sequential( nn.ConvTranspose1d(64, 32, kernel_size6, stride2, padding2), # 输出 64 nn.ReLU(), nn.ConvTranspose1d(32, 16, kernel_size6, stride2, padding2), # 输出 128 nn.ReLU(), nn.ConvTranspose1d(16, 1, kernel_size5, stride2, padding2, output_padding1), # 输出 256 ) def forward(self, x): encoded self.encoder(x).squeeze(-1) # (batch, 64) compressed self.compress(encoded) # (batch, compress_dim) expanded self.expand(compressed).unsqueeze(-1) # (batch, 64, 1) reconstructed self.decoder(expanded) return reconstructed网络设计里一个容易被忽视的参数是compress_dim它控制瓶颈层的宽度。压缩得越狠模型对微小扰动的容错能力越差正常样本的重建误差也会偏大阈值不好定。压得太松故障样本也可能被重建得很像正常模式漏报率上升。工程上我会先在验证集上画重建误差的分布分布重叠度高就减小compress_dim正常样本误差的尾部太长就适当放大。3.3 训练循环与异常分数计算训练时只使用正常样本目标函数就是重建误差的均方误差。不要去优化准确率因为在训练集里所有样本都是“正常”准确率没有任何参考意义。from torch.utils.data import DataLoader, TensorDataset window_tensor torch.FloatTensor(windows).unsqueeze(1) # (N, 1, 256) train_data, val_data torch.utils.data.random_split(window_tensor, [0.8, 0.2]) train_loader DataLoader(TensorDataset(train_data), batch_size64, shuffleTrue) val_loader DataLoader(TensorDataset(val_data), batch_size128, shuffleFalse) model FaultDetectAutoEncoder(window_size256, compress_dim32) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() for epoch in range(30): model.train() total_loss 0.0 for batch in train_loader: x batch[0] recon model(x) loss criterion(recon, x) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * x.size(0) avg_loss total_loss / len(train_loader.dataset) if (epoch 1) % 5 0: print(fEpoch {epoch 1:02d}, Loss {avg_loss:.6f}) torch.save(model.state_dict(), autoencoder_fault.pth)这里的学习率1e-3是 Adam 优化器的常见默认值配合BatchNorm1d之后基本不需要再做学习率预热。如果训练过程中损失曲线出现震荡优先降低学习率而不是换模型结构。训练结束后把模型切到评估模式逐条计算每个样本的重建误差作为后续故障判定的基础分数。model.eval() errors [] with torch.no_grad(): for window in windows: input_tensor torch.FloatTensor(window).unsqueeze(0).unsqueeze(0) recon model(input_tensor) error torch.mean((recon - input_tensor) ** 2).item() errors.append(error) errors np.array(errors) print(f重建误差分布: mean{errors.mean():.4f}, fp95{np.percentile(errors, 95):.4f}, fmax{errors.max():.4f})重建误差的均值、分位数、最大值三者要放在一起看。均值代表模型的整体拟合能力如果均值本身就偏高说明模型没有收敛或者正常样本间差异过大95 分位数是用来设定阈值的起点比最大值更稳定不会被个别极端噪声带偏最大值则提示是否存在明显的离群样本可能是坏数据也可能是早期故障的雏形。4. 在真实环境里跑通并调优阈值选定、噪声与类别不平衡4.1 设定故障阈值而不是只看准确率自编码器输出的重建误差是一个连续值压缩包算法落地的最后一步就是把这个连续分数变成一个“是否告警”的离散决策。最常见的方法是设定一个固定阈值超过阈值就报警。问题在于阈值取 95 分位数还是 99 分位数故障的召回率和误报率会差出很多。更稳妥的做法是在验证集上枚举候选阈值计算每个候选点对应的 F1 分数从中选最优值。下面的代码用正常样本与故障样本的重建误差构造一个简单的阈值搜索过程。from sklearn.metrics import precision_recall_fscore_support # y_true: 1 表示故障0 表示正常 # normal_errors 与 fault_errors 分别为两类样本的重建误差 candidate_thresholds np.linspace( np.percentile(normal_errors, 80), np.percentile(normal_errors, 100), 50 ) best_threshold, best_f1 0.0, 0.0 y_true np.concatenate([np.zeros(len(normal_errors)), np.ones(len(fault_errors))]) for thr in candidate_thresholds: y_pred np.concatenate([normal_errors, fault_errors]) thr precision, recall, f1, _ precision_recall_fscore_support( y_true, y_pred, averagebinary, zero_division0 ) if f1 best_f1: best_f1, best_threshold f1, thr print(f最佳阈值 {best_threshold:.4f}, 对应 F1 {best_f1:.4f})提示不要把 F1 当作唯一目标。工业现场如果停机检查成本极高优先压低假阳性False Positive如果故障后果严重优先提高召回率。工程上通常会在 F1 附近再人工微调一个“保守系数”比如将最佳阈值乘 0.9 来偏向召回。4.2 处理类别不平衡与噪声标签故障检测场景里正常样本往往比故障样本多出一两个数量级即使是用自编码重建这条无监督路线最终做阈值搜索时涉及的两类误差分布也会在重叠区域互相干扰。如果故障标签本身还存在错标会让阈值搜索的结果产生偏移。两个实用的缓解手段。第一在计算验证指标时对故障样本进行加权让少数类的误判产生更高的惩罚。第二对故障样本做滑窗增强用重叠系数更小的窗口切出更多故障片段这样在阈值搜索的时候故障分布能覆盖更多形态阈值不太容易偏向正常类。如果你拿到的压缩包数据里已经带有标签文件建议先统计正常与故障的样本量占比如果故障占比低于 5%直接按原始比例做阈值搜索会在误报率上吃亏。4.3 模型部署与推理时的边界问题训练结束后实际线上推理与离线评估的差异往往被人忽略。推理时没有真实标签模型输出重建误差后续的告警逻辑再把误差与阈值比较。这里有一个细节线上数据的量纲可能和训练集不一致。例如训练数据来自某一台设备的传感器部署到另一台设备时如果传感器量程或者安装位置不同信号幅值分布会整体偏移导致重建误差普遍偏大误报频发。解决方案是在模型前面加一层标准化把推理输入映射到训练集的取值范围内。更保守的做法是部署初期先只观察不告警用实际数据分布重新校准阈值后再切换到自动告警。部署场景数据分布变化特征处理手段同一设备长期运行设备缓慢退化幅值小幅上升定期滑动重算阈值取近 7 天正常数据更新同一型号不同个体传感器安装差异导致基线偏移每台设备独立计算均值和标准差做归一化完全不同的工况负载、转速改变导致波形形态变化按工况聚类后分模型或使用工况变量作为条件输入参数层面如果推理延迟敏感可以把DataLoader去掉直接写一个接收单个窗口的推理函数。故障检测场景对单次推理延迟的要求通常不高但吞吐量可能很重要特别是多设备轮询时批处理batch size 取 32 或 64能显著提高设备利用率。5. 高效验证算法效果的技巧用混淆矩阵定位误判根因模型训练完成、阈值调好之后很多人只看一个整体准确率就结束了这个习惯在故障检测场景里会埋大雷。故障检测的根本需求是“不漏报、少误报”而这两个目标对应的是混淆矩阵的四个象限。与其只看分数不如把验证集的预测结果拉成一张矩阵逐格检查。import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay # 假定 y_true 是真实标签y_pred 是基于阈值得到的 0/1 预测 cm confusion_matrix(y_true, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labels[正常, 故障]) disp.plot(cmapBlues) plt.title(Fault Detection Confusion Matrix) plt.savefig(confusion_matrix.png, dpi150)拿到这张图之后按下述顺序排查。先看左上角的真正常数量如果它比预期少说明大量正常样本模型重建得很好阈值设定没有误伤正常信号这是理想状态如果右下角的真故障数量偏低说明模型把某些故障样本重建得很“正常”问题大概率出在compress_dim过大或瓶颈层信息保留过多导致模型容量过强。再看左下角的误报也就是正常样本被判成故障这类误判的原因多数是正常样本里混入了未标注的异常片段比如检修时的锤击信号或者另一台设备的耦合振动。对于右上角的漏报我会把错误样本单独拉出来看时间轴。如果漏报全部集中在某一段连续时间说明那是设备进入了另一种未参与训练的正常运行状态模型没有见过这种模式重建误差自然小。解决思路不是继续调模型而是给系统增加“工况识别”前置模块先判断当前处于什么运行状态再选择对应的检测模型。最后清理掉验证集里和训练集时间重叠的部分防止滑窗重叠造成的信息泄漏影响混淆矩阵的可信度。正确划分后故障检测算法的真实水平就藏在这张矩阵里比任何精度的数字都直观。本文还有配套的精品资源点击获取