资讯动态

基于1D-CNN的工业设备故障诊断:从振动信号到可运行工程实战

发布时间:2026/10/18 10:44:00 来源:尧图企业网站定制
简介这份资源面向深度学习与工业故障诊断方向的研究者和工程师提供一套可直接运行的完整方案帮助读者跳过从零搭建系统的环节快速上手设备异常识别与状态监测的实践。压缩包共63个文件约57.74MB以mat数据文件、py源码脚本、xml配置、csv数据集为主另含h5训练模型、png结构图与readme说明覆盖数据、模型、训练与预测全流程。资源围绕一维卷积与LSTM两条技术路线展开包含数据预处理、模型定义、训练验证与测试评估等模块并附带轴承故障数据集与提交结果文件便于对照复现。目前已有11421人学习下载代码注释较为完整读者可据此理解时序与频域特征提取思路调整网络结构与参数并迁移到其他类型的故障诊断任务中兼具学术参考与工程应用价值。1. 从一段振动信号说起这个故障诊断项目到底能干什么设备还没坏但振动信号里已经藏了征兆。这个项目干的事就是拿一段工业设备的振动或电流时序数据用深度学习模型判断它当前处于哪种健康状态——正常、内圈故障、外圈故障还是滚动体故障。它不是一个空壳 Demo而是把数据加载、模型定义、训练循环、评估指标、模型保存这一整条链路都写全了的可运行工程。适合两类人一类是刚接触深度学习故障诊断、想找个能跑通的完整例子照着改的在校同学另一类是手里有设备数据、想快速搭个基线模型验证思路的工程开发。项目默认用公开的轴承故障数据集格式组织数据换成你自己的 CSV 或 MAT 文件也能跑核心是把「信号 → 特征 → 分类」这条路径用代码固化下来。2. 项目结构与技术选型为什么是 1D-CNN 而不是 LSTM2.1 目录长什么样每个文件负责什么拿到源码包第一件事是看目录。这个项目的结构是典型的「配置与代码分离」写法常见做法是这样组织的fault_diagnosis/ ├── configs/ │ └── default.yaml # 数据路径、超参数、模型类型都在这 ├── data/ │ ├── raw/ # 原始 .mat 或 .csv 信号文件 │ └── processed/ # 切窗、归一化后的 .npy 缓存 ├── src/ │ ├── dataset.py # Dataset 类负责滑窗切分和标签映射 │ ├── models.py # 模型定义1D-CNN / LSTM / Transformer 可选 │ ├── train.py # 训练入口含早停和 checkpoint │ ├── evaluate.py # 混淆矩阵、t-SNE 可视化 │ └── utils.py # 随机种子、日志、指标计算 ├── checkpoints/ # 训练好的权重 ├── requirements.txt └── README.md这个结构的好处是换数据集只动configs/default.yaml里的路径换模型只改一个字符串不用去翻训练脚本。我见过太多把路径硬编码在train.py里的项目换台机器就跑不起来这个项目避开了这个坑。2.2 技术栈与依赖版本核心依赖就四个装起来不折腾依赖作用建议版本PyTorch模型定义与训练≥ 1.12NumPy信号读取与滑窗≥ 1.21scikit-learn混淆矩阵、归一化≥ 1.0Matplotlib损失曲线、t-SNE≥ 3.5requirements.txt里一般会锁死主版本号避免 PyTorch 2.x 和 1.x 在torch.load上的行为差异导致加载旧权重报错。如果你用的是 GPU 环境装 PyTorch 时记得选对应 CUDA 版本的 wheel否则torch.cuda.is_available()会返回 False训练会静默跑在 CPU 上慢到你以为代码写错了。2.3 为什么选 1D-CNN 作为默认模型故障诊断的信号是一维时序长度通常在 1024 到 4096 个采样点之间。1D-CNN 的卷积核直接在时间轴上滑动能捕捉到冲击成分的局部波形特征这正是轴承故障的典型表现——周期性冲击。相比之下LSTM 参数量大、训练慢在样本量只有几千条的时候容易过拟合Transformer 更吃数据没有几万条样本很难训出效果。所以这个项目把 1D-CNN 设为默认同时保留 LSTM 和 Transformer 作为可选项让你能对比。模型定义大致长这样import torch import torch.nn as nn class CNN1D(nn.Module): def __init__(self, in_channels1, num_classes4): super().__init__() # 三层卷积每层后接池化逐步压缩时间维度 self.features nn.Sequential( nn.Conv1d(in_channels, 16, kernel_size15, stride2, padding7), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size7, stride2, padding3), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size3, stride1, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(1) # 全局平均池化输出定长向量 ) # 分类头 self.classifier nn.Sequential( nn.Flatten(), nn.Dropout(0.3), nn.Linear(64, num_classes) ) def forward(self, x): # x 形状: (batch, 1, seq_len) x self.features(x) return self.classifier(x)第一层用大卷积核15是为了覆盖一个冲击周期的宽度步长设 2 减少计算量后面两层核变小提取更细的局部模式。AdaptiveAvgPool1d(1)把任意长度的时间轴压成一个值这样输入信号长度变了也不用改网络结构。Dropout(0.3)是防过拟合的常规操作样本少的时候可以调到 0.5。3. 数据准备与训练流程从原始信号到能跑的模型3.1 滑窗切分把长信号变成训练样本原始振动信号往往是一段几十万点的连续记录不能直接喂给网络。常见做法是滑窗切分用固定长度的窗口在信号上滑动每个窗口继承整段信号的标签。这一步在dataset.py里完成import numpy as np def sliding_window(signal, label, window_size1024, step512): signal: 一维数组单通道振动信号 label: 该段信号对应的故障类别整数 window_size: 每个样本的长度 step: 窗口滑动步长越小样本越多但重叠越大 samples, labels [], [] for start in range(0, len(signal) - window_size 1, step): window signal[start:start window_size] # 每个窗口做零均值单位方差归一化消除量纲影响 window (window - window.mean()) / (window.std() 1e-8) samples.append(window) labels.append(label) return np.array(samples), np.array(labels)window_size设 1024 是经验值对应轴承转频的几个周期step设 512 表示 50% 重叠能在样本量不足时做数据增强。归一化用逐窗口的零均值单位方差比全局归一化更稳因为不同工况下信号幅值差异很大。切完的样本存成.npy缓存到data/processed/下次训练直接读省去重复切分的时间。3.2 训练循环里的关键参数train.py的主循环不复杂但有几个参数直接决定能不能收敛import torch from torch.utils.data import DataLoader def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total 0, 0, 0 for x, y in loader: x, y x.to(device), y.to(device) optimizer.zero_grad() logits model(x) loss criterion(logits, y) loss.backward() # 梯度裁剪防止冲击信号导致的梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0) optimizer.step() total_loss loss.item() * x.size(0) correct (logits.argmax(1) y).sum().item() total x.size(0) return total_loss / total, correct / total学习率一般从1e-3起步配合ReduceLROnPlateau验证损失连续 5 个 epoch 不降就砍半。clip_grad_norm_的max_norm5.0是针对振动信号加的——冲击成分会让某些 batch 的梯度突然变大不裁剪的话 loss 会直接飞掉这是血泪经验。batch size 设 64 比较稳太小梯度噪声大太大在样本少时一个 epoch 迭代次数不够。3.3 评估不能只看准确率故障诊断有个特点类别不平衡。正常样本往往远多于故障样本如果只看准确率模型全预测成正常也能有 80% 以上的数字但毫无意义。所以evaluate.py里必须输出混淆矩阵和每个类别的召回率from sklearn.metrics import confusion_matrix, classification_report def evaluate(model, loader, device, class_names): model.eval() preds, trues [], [] with torch.no_grad(): for x, y in loader: x x.to(device) preds.extend(model(x).argmax(1).cpu().numpy()) trues.extend(y.numpy()) # 打印每个类别的精确率、召回率、F1 print(classification_report(trues, preds, target_namesclass_names)) return confusion_matrix(trues, preds)看混淆矩阵能发现模型到底把哪两类搞混了。比如内圈故障和外圈故障的冲击频率接近模型容易混这时候就要回去看特征提取是不是不够或者窗口长度是不是没覆盖完整周期。4. 避坑与排查跑不起来时先看这几条4.1 现象loss 一直是 nan原因通常是学习率太大或者输入信号没归一化幅值到了几千的量级第一层卷积直接溢出。解决方法是先确认dataset.py里的归一化生效了打印一个 batch 的x.max()看看是不是在 1 附近然后把学习率降到1e-4再试。如果还是 nan检查数据里有没有 NaN 值用np.isnan(signal).any()扫一遍。4.2 现象训练准确率 99%测试只有 60%这是典型的过拟合样本切分时把同一段信号的窗口同时分到了训练集和测试集。滑窗产生的样本高度相关必须按「原始信号段」划分训练测试而不是按窗口随机划分。改法是在切窗之前就把不同文件分好组同一文件的窗口只进一个集合。4.3 现象GPU 显存不够报 CUDA out of memory要么是 batch size 太大要么是信号窗口太长。先把 batch size 减半试如果还不行把window_size从 4096 降到 1024显存占用和序列长度成正比。另外AdaptiveAvgPool1d之前的特征图如果太大也会吃显存可以在中间多加一层池化。4.4 现象换了数据集后类别数对不上报维度错误configs/default.yaml里的num_classes没改。模型分类头的输出维度是写死的换数据集必须同步改这个值同时更新class_names列表否则混淆矩阵的标签会错位。这是最常见的翻车点改完配置记得重新生成 processed 缓存。4.5 现象验证集准确率震荡厉害batch size 太小或者学习率没配 scheduler。先把 batch size 提到 64 以上加上ReduceLROnPlateaupatience 设 5。如果还震荡检查数据里是不是混入了标签错误的样本用evaluate.py把预测置信度低的那批样本导出来人工看一眼。5. 二次开发换模型、加注意力、接自己的数据5.1 换模型只改一个字符串models.py里用工厂函数注册了所有模型configs/default.yaml里model.name改成lstm或transformer就能切换。加新模型也简单继承nn.Module实现forward然后在工厂字典里注册一行。我一般会先把新模型在同一个数据划分上跑一遍和 1D-CNN 的混淆矩阵对比确认有提升再往下调。5.2 加注意力机制的实操如果想让模型自己关注冲击发生的时刻可以在卷积后面加一个通道注意力。最小改动是在CNN1D的features后面插一个 SE 模块class SEBlock(nn.Module): def __init__(self, channels, reduction8): super().__init__() self.fc nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(), nn.Linear(channels // reduction, channels), nn.Sigmoid() ) def forward(self, x): # x: (batch, channels, length) w x.mean(dim2) # 全局平均池化到 (batch, channels) w self.fc(w).unsqueeze(2) # 重新扩成 (batch, channels, 1) return x * w # 通道加权插在最后一层卷积之后、AdaptiveAvgPool1d之前。reduction8是压缩比通道数少的时候可以设 4。加完记得重新训注意力模块初始权重是随机的不重新训直接加载旧权重会报 key 不匹配。5.3 接自己的数据要改哪几处你的数据大概率不是标准格式。需要动的地方有三处dataset.py里的读取函数把scipy.io.loadmat换成pd.read_csv或你自己的解析逻辑标签映射字典把故障名称映射成 0 到 N-1 的整数configs/default.yaml里的data.root和num_classes。改完先跑python -m src.dataset --check如果项目里有这个自检入口确认切出来的样本形状是(N, 1, window_size)再开训。5.4 一个验证改造是否有效的小技巧每次改完模型或数据别急着跑满 100 个 epoch。先用--epochs 10 --subset 0.2跑一个小规模实验看验证损失有没有下降趋势。如果 10 个 epoch 内验证损失不降反升大概率是改动引入了问题不用浪费时间跑完整训练。这个习惯帮我省下了大量等结果的时间。从那以后我每次动模型结构之前都会先在一个固定的小子集上跑通一遍确认改动方向对了再上全量数据。希望这个项目的拆解能帮到你拿到源码后先按第 3 章的流程跑通基线再按第 5 章的思路改成你自己的数据和模型。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑