news 2026/10/24 4:23:48

基于深度学习的故障诊断实战:从振动信号到分类结果的完整链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于深度学习的故障诊断实战:从振动信号到分类结果的完整链路

简介:面向深度学习故障诊断方向的研究者与工程实践者,这份资源提供了一套可直接运行的完整方案,涵盖从数据到模型再到预测接口的全流程,帮助读者跳过环境搭建与数据准备的重复劳动,快速进入模型训练与效果验证环节。压缩包共63个文件、约57.74MB,以mat与csv格式的轴承振动数据、py训练与诊断脚本、xml项目配置及h5已训练模型为主,另含readme说明与模型结构图,便于按模块定位与复现。资源已有11425人学习下载,具备较好的参考基础。代码覆盖一维卷积与LSTM两条典型技术路线,包含数据预处理、训练验证、测试评估与预测输出等环节,并附有训练日志与提交结果样例,读者可据此理解时序特征提取、过拟合监控与泛化能力评估的完整思路,也可在现有结构上调整网络层数与参数,迁移到其他设备故障诊断任务中。

1. 从一段振动信号说起:这套深度学习故障诊断代码到底能干什么

设备故障诊断这件事,真正难的不是训练一个网络,而是把一段振动信号变成能进模型的样本。我见过太多人拿到一份"基于深度学习的故障诊断模型代码和数据",第一反应是找train.py,结果打开发现数据是.mat格式,标签藏在文件名里,采样频率写在某个 README 的角落,光是把数据读进内存就耗掉一整天。这套资源解决的正是这个断层:它把故障诊断从原始信号到分类结果的完整链路都摆出来了,包括数据加载、信号预处理、模型定义、训练评估四块,适合两类人——一类是想快速复现一个能跑通的诊断基线、验证自己手上数据能不能用的工程同学,另一类是课程设计或入门研究阶段、需要一份结构清晰可改的参考实现的人。深度学习做故障诊断,核心不是网络多深,而是特征工程和标签对齐做得对不对,这套代码的价值就在这。

2. 项目结构与技术选型:为什么是这套组合

2.1 目录骨架与各模块职责

这类故障诊断项目,常见的目录组织方式是按"数据—模型—训练—评估"四层切开,而不是把所有逻辑塞进一个脚本。我一般会按下面这个结构来读,也建议你按这个顺序读,因为依赖关系是从上往下的:

fault_diagnosis/ ├── data/ │ ├── raw/ # 原始振动信号,常见为 .mat / .csv / .txt │ └── processed/ # 切窗、归一化后的样本,npy 或 pt 格式 ├── datasets/ │ ├── __init__.py │ └── signal_dataset.py # Dataset 封装:读信号、切窗、打标签 ├── models/ │ ├── __init__.py │ ├── cnn1d.py # 一维卷积基线 │ └── resnet1d.py # 残差结构,深一点的版本 ├── utils/ │ ├── preprocess.py # 归一化、FFT、包络谱等 │ ├── metrics.py # 混淆矩阵、准确率、F1 │ └── seed.py # 固定随机种子 ├── configs/ │ └── base.yaml # 采样率、窗长、批大小、学习率 ├── train.py # 训练入口 ├── evaluate.py # 评估入口 └── requirements.txt

这个骨架的关键在于datasets/和utils/preprocess.py的分离:前者只管"怎么把一条样本取出来",后者只管"信号怎么变换"。很多新手把归一化和切窗写进 Dataset 的__getitem__里,训练时每个 epoch 重复算一遍,速度直接掉一半。正确的做法是预处理离线做一次、存成processed/,Dataset 只负责索引和组装。

技术栈上,这类项目基本是 PyTorch + NumPy + SciPy 的组合。PyTorch 负责模型和训练循环,NumPy 处理数组,SciPy 的signal模块做滤波和频谱变换。选 PyTorch 而不是别的框架,主要原因是动态图调试方便——故障诊断里经常要改输入维度、试不同的窗长,静态图每次改都得重新编译,调试成本高。配置文件用 YAML 而不是纯 argparse,是为了让"采样率、窗长、重叠率"这些强相关的参数集中在一处,避免命令行传参传漏。

2.2 数据格式与标签组织方式

故障诊断数据集有个特点:一条"样本"往往不是一行数据,而是一段固定长度的信号窗口。原始数据常见三种形态——.mat(MATLAB 导出的结构体,里面按工况分字段)、按类别分文件夹的.csv、以及一个大表加一列标签。这套代码默认走的是"按类别分文件夹 + 文件名带工况"的组织方式,因为它在跨数据集迁移时最省事。

标签的映射一般放在datasets/signal_dataset.py顶部,用一个字典固定下来:

# 类别名 -> 整数标签,顺序一旦定下就不要改,否则模型权重和标签会对不上 LABEL_MAP = { "normal": 0, "inner_race": 1, "outer_race": 2, "ball": 3, } def parse_label(file_path: str) -> int: # 从路径中提取类别名,例如 data/raw/inner_race/001.mat category = file_path.split("/")[-2] if category not in LABEL_MAP: raise ValueError(f"未知类别: {category}") return LABEL_MAP[category]

这里有个血泪经验:LABEL_MAP的顺序必须和评估阶段画混淆矩阵时的类别顺序完全一致,否则你看到的"正常被判成内圈故障"其实是标签错位,不是模型真的学错了。我一般会把LABEL_MAP单独抽到一个labels.py里,训练和评估都从那里 import,杜绝两处各写一份的情况。

采样率是另一个必须提前确认的参数。常见轴承振动数据的采样率在 12kHz 到 48kHz 之间,窗长一般取 1024 或 2048 点。窗长太短,一个故障冲击周期都盖不住;窗长太长,一条样本里混进多个工况,标签就不纯了。配置文件里我会把这三个参数绑在一起写注释:

# configs/base.yaml data: sample_rate: 12000 # 采样率,必须和原始数据一致 window_size: 2048 # 单条样本点数,约 0.17s overlap: 0.5 # 滑窗重叠率,增大可扩充样本量 normalize: zscore # 归一化方式:zscore / minmax / none

overlap设 0.5 意味着相邻窗口重叠一半,样本量翻倍,但要注意同一段信号切出来的窗口不能同时出现在训练集和验证集里,否则验证准确率虚高,这是最常见的翻车点之一,后面避坑章节会细说。

3. 核心模块拆解:从信号到分类结果的完整链路

3.1 信号预处理:归一化与频域特征

原始振动信号直接喂进网络效果通常很差,原因是幅值受传感器安装位置和负载影响很大,同一故障在不同工况下幅值能差一个数量级。所以预处理第一步是归一化。这套代码默认用 z-score,按单条样本做,而不是按整个数据集做——按数据集做会把工况差异也归一化掉,反而丢失信息。

import numpy as np def zscore(signal: np.ndarray, eps: float = 1e-8) -> np.ndarray: # 按单条样本归一化,保留工况间的幅值差异 mean = signal.mean() std = signal.std() return (signal - mean) / (std + eps) def sliding_window(signal: np.ndarray, window_size: int, overlap: float): # 滑窗切分,step 由重叠率决定 step = int(window_size * (1 - overlap)) windows = [] for start in range(0, len(signal) - window_size + 1, step): windows.append(signal[start:start + window_size]) return np.stack(windows)

eps的作用是防止常量信号(比如传感器掉线)导致除零。sliding_window里range的上界要减window_size再加 1,少加这个 1 会漏掉最后一条完整窗口,样本量对不上时先查这里。

除了时域归一化,很多实现还会加一路频域特征。常见做法是对窗口做 FFT 取幅值谱,和时域信号拼成双通道输入。这一步不是必须的,但对早期故障(冲击特征弱、被噪声淹没)提升明显。我一般会把它做成开关,在配置里控制:

def fft_amplitude(signal: np.ndarray) -> np.ndarray: # 取单边幅值谱,长度与输入一致,便于和时域信号拼接 spectrum = np.abs(np.fft.rfft(signal)) return spectrum

注意rfft输出长度是window_size // 2 + 1,和时域长度不一致,拼接前要么补零要么插值,直接np.concatenate会报维度错误。这是新手最常卡住的地方之一。

3.2 Dataset 与 DataLoader:样本怎么被取出来

Dataset 的职责很单一:给一个索引,返回(信号张量, 标签)。真正容易出错的是训练/验证划分。按窗口随机划分会导致同一段原始信号切出的窗口同时落在两边,验证集泄漏,准确率能虚高十几个点。正确做法是按原始记录(文件)划分,同一文件的窗口只进一个集合。

from torch.utils.data import Dataset import torch class SignalDataset(Dataset): def __init__(self, file_list, window_size, overlap, transform=None): self.samples = [] self.labels = [] self.transform = transform for fp in file_list: sig = load_signal(fp) # 读原始信号 wins = sliding_window(sig, window_size, overlap) label = parse_label(fp) self.samples.append(wins) self.labels.extend([label] * len(wins)) self.samples = np.concatenate(self.samples, axis=0) def __len__(self): return len(self.samples) def __getitem__(self, idx): x = self.samples[idx].astype(np.float32) if self.transform: x = self.transform(x) # 一维卷积要求输入形状为 (C, L),这里 C=1 return torch.from_numpy(x).unsqueeze(0), self.labels[idx]

unsqueeze(0)这一步不能省,PyTorch 的Conv1d要求输入是(batch, channels, length),少这一维会报Expected 3D input。file_list的划分在外部做,训练脚本里按 8:1:1 切文件列表,而不是切样本索引,这是防泄漏的关键。

DataLoader 那边,num_workers设成 CPU 核数的一半左右比较稳,设太大在 Windows 上容易卡死,设 0 又慢。pin_memory=True在有 GPU 时打开,能减少数据搬运开销。

3.3 模型定义:一维卷积基线怎么搭

故障诊断的主流模型是一维卷积,因为振动信号本质是一维时序,用二维卷积把信号转成图再卷,既慢又丢时序结构。一个够用的基线是三层卷积加全局池化:

import torch.nn as nn class CNN1D(nn.Module): def __init__(self, num_classes=4): super().__init__() self.features = nn.Sequential( nn.Conv1d(1, 16, kernel_size=15, stride=2, padding=7), nn.BatchNorm1d(16), nn.ReLU(), nn.Conv1d(16, 32, kernel_size=7, stride=2, padding=3), nn.BatchNorm1d(32), nn.ReLU(), nn.Conv1d(32, 64, kernel_size=5, stride=2, padding=2), nn.BatchNorm1d(64), nn.ReLU(), ) self.pool = nn.AdaptiveAvgPool1d(1) # 全局池化,抹平输入长度差异 self.fc = nn.Linear(64, num_classes) def forward(self, x): x = self.features(x) x = self.pool(x).squeeze(-1) # (B, 64) return self.fc(x)

第一层kernel_size=15是有讲究的:故障冲击的周期特征在时域上跨度较大,感受野太小抓不到。stride=2逐层降采样,配合AdaptiveAvgPool1d让模型对输入窗长不敏感,换数据集时不用改结构。BatchNorm1d放在卷积和激活之间,训练初期收敛更稳,但要注意 batch size 太小时(比如 8 以下)BN 的统计量不准,这时候换成GroupNorm更靠谱。

3.4 训练循环与评估指标

训练循环本身不复杂,关键是评估指标别只看准确率。故障诊断里类别通常不均衡(正常样本远多于故障样本),准确率高但漏检率高是常态。所以评估阶段要同时输出混淆矩阵和每类的召回率。

def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss = 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() optimizer.step() total_loss += loss.item() * x.size(0) return total_loss / len(loader.dataset) @torch.no_grad() def evaluate(model, loader, device): model.eval() preds, targets = [], [] for x, y in loader: x = x.to(device) preds.append(model(x).argmax(dim=1).cpu()) targets.append(y) return torch.cat(preds).numpy(), torch.cat(targets).numpy()

model.eval()和torch.no_grad()两个都要有,前者关掉 BN 的训练统计和 Dropout,后者省显存。评估完拿preds和targets喂给sklearn.metrics.confusion_matrix和classification_report,每类召回率一目了然。如果某一类召回率明显偏低,先别急着调网络,回去查这一类样本量是不是太少、窗长是不是没覆盖到完整冲击周期。

4. 跑起来:环境、数据与训练命令

4.1 环境准备与依赖安装

这套代码对环境的依赖不算重,Python 3.8 以上、PyTorch 1.10 以上就能跑。我一般用 conda 建独立环境,避免和系统里的包打架:

conda create -n fault_diag python=3.9 -y conda activate fault_diag pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install numpy scipy scikit-learn pyyaml tqdm

有 GPU 的话把第一行 pip 换成对应 CUDA 版本的源。scipy是必须的,读.mat文件要用scipy.io.loadmat。装完先跑一句python -c "import torch; print(torch.__version__)"确认没装错版本,这一步能省掉后面一半的报错排查。

4.2 数据放置与配置修改

把原始数据按类别放进data/raw/下对应文件夹,然后改configs/base.yaml里的sample_rate和window_size。这两个参数必须和你的数据匹配,采样率填错会导致频域特征整体偏移,模型学到的频率和真实故障频率对不上,准确率卡在随机水平附近。

改完配置先跑一次预处理,把切好的窗口存到processed/:

python utils/preprocess.py --config configs/base.yaml

这一步会输出每个类别的样本数,如果某个类别样本数为 0,说明文件夹名和LABEL_MAP对不上,或者滑窗参数导致窗口数不足。看到 0 先查文件夹名,再查window_size是不是比信号长度还大。

4.3 训练与评估命令

训练入口接受配置文件路径和输出目录两个参数:

python train.py --config configs/base.yaml --out runs/exp1 --epochs 50 --batch-size 64

--out目录下会存best.pth(验证集最优权重)、train.log(每轮 loss 和指标)、confusion.png(混淆矩阵图)。评估单独跑:

python evaluate.py --ckpt runs/exp1/best.pth --split test

--split test表示用测试集文件列表,这个列表在预处理阶段就固定下来了,不会因为重新训练而变。跑完看classification_report,重点看每类召回率,而不是只看最后那行 accuracy。

5. 避坑与排查:五个真实踩过的坑

5.1 验证准确率虚高,测试集一塌糊涂

现象:训练日志里验证准确率 98%,换测试集掉到 70%。原因:按窗口随机划分,同一段原始信号切出的窗口同时进了训练和验证,模型等于在背答案。解决:按文件划分数据集,同一文件的窗口只进一个集合,划分逻辑写在预处理阶段并固定下来,训练和评估都读同一份划分文件。

5.2 损失不下降,准确率停在类别比例附近

现象:loss 震荡不降,准确率约等于最大类占比。原因:学习率太大(常见于直接抄了 0.1)或者标签没对齐。解决:先把学习率降到 1e-3 试跑 5 个 epoch,看 loss 有没有下降趋势;再打印一个 batch 的(x.shape, y)确认标签范围和类别数一致。标签错位时模型会"学得很好"但全错,这种玄学现象八成是标签问题。

5.3 换数据集后维度报错

现象:Expected input to have 3 dimensions或size mismatch。原因:新数据窗长和模型第一层输入不匹配,或者 Dataset 里漏了unsqueeze(0)。解决:模型里用AdaptiveAvgPool1d抹平长度差异,Dataset 里固定输出(1, L)形状,换数据只改配置不改代码。

5.4 训练速度慢得离谱

现象:一个 epoch 要十几分钟。原因:预处理写进了__getitem__,每个 epoch 重复算 FFT 和归一化。解决:预处理离线做一次存processed/,Dataset 只做索引;num_workers调到 4 到 8,pin_memory=True。

5.5 显存溢出但 batch size 已经很小

现象:batch size 降到 8 还是 OOM。原因:评估阶段没加torch.no_grad(),或者把整个测试集一次性堆进了显存。解决:评估循环加@torch.no_grad(),预测结果及时.cpu()挪出来,别在 GPU 上累积。

6. 二次开发:换数据集和加模块的实操思路

拿到这套代码后,最常见的改造需求是换自己的数据。我的做法是先不动模型,只改datasets/和configs/,跑通一个基线再谈优化。具体三步:第一步,把新数据整理成"按类别分文件夹"的结构,写一个load_signal适配函数,.mat用scipy.io.loadmat取对应字段,.csv用np.loadtxt跳过表头;第二步,改LABEL_MAP和sample_rate,跑预处理看每类样本数是否均衡;第三步,用默认的CNN1D跑 20 个 epoch,拿到混淆矩阵作为基线。基线出来之前不要改网络结构,否则出了问题分不清是数据问题还是模型问题。

加模块方面,两个方向性价比最高。一是加频域分支,把fft_amplitude的输出作为第二通道拼进去,模型第一层Conv1d的in_channels改成 2,其余不动,对早期故障通常有 3 到 5 个点的召回率提升。二是加注意力,在features后面接一个nn.MultiheadAttention或者简单的 SE 模块,但要注意一维序列做注意力计算量随窗长平方增长,窗长 2048 时显存吃紧,建议先降采样再上注意力。

验证改造是否有效,我固定用一套流程:同一份划分文件、同一个随机种子、跑三次取均值,只看每类召回率的变化,不看 accuracy。因为故障诊断里漏检一个故障的代价远大于误报,召回率才是真正该盯的指标。改完记得把配置和划分文件一起存档,不然过两周自己都复现不出来当时的结果。

从那以后我每次换数据集,都强制先跑一遍预处理看样本数分布,再跑 5 个 epoch 看 loss 曲线,这两步不过关绝不往下调模型——这个习惯帮我省掉了大量"模型不行"的误判。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 4:23:38

搞笑短视频内容池:转码、打标、去重与召回排序实战

做内容类产品的人大概都遇到过这种场景:产品经理丢过来一句“我们做一个搞笑短视频频道”,听上去工作量不大,无非是一个列表页加一个播放器。等真正拆需求的时候才发现,列表页背后要解决的问题是内容从哪来、怎么转码、怎么截封面…

作者头像 李华
网站建设 2026/10/24 4:22:14

Linux登录审计:last、lastlog与wtmp/btmp实战

排查一台服务器“到底谁登过、什么时候登的、有没有人在试密码”,很多人的第一反应是翻/var/log/secure或者journalctl -u sshd。但在 Linux 自带的用户管理工具里,其实躺着一套更轻量、更快、几乎零依赖的登录审计入口:last和lastlog。它们不…

作者头像 李华
网站建设 2026/10/24 4:22:14

Linux chmod 权限全解:755/644、目录权限与故障排查

chmod 是 Linux 日常运维里出现频率最高的命令之一,也是最容易被"背答案"背坏的命令之一。很多人能脱口而出 755、644、777,但一旦遇到"权限给了 777 还是提示 Permission denied""普通用户删不掉别人的文件""脚本明…

作者头像 李华
网站建设 2026/10/24 4:20:35

Unity uGUI ScrollRect分页吸附与循环列表优化

简介:Viwepager.zip 是一份基于 Unity3D 引擎的图片轮播完整工程,面向已掌握基础操作、希望深入界面与动画实现的学习者和独立开发者,可用于图片集展示、产品信息轮播等交互界面场景。工程围绕 UI Canvas 搭建图片容器与切换按钮,…

作者头像 李华
网站建设 2026/10/24 4:20:26

VC读取TXT按行显示源码:MFC编码处理与大文件日志查看

简介:这份VC源码包面向刚接触C文件操作与MFC工程组织的开发者,围绕“读取TXT文件并按行显示”给出可直接对照学习的完整示例,覆盖文件打开、逐行读取、控制台输出及资源释放等关键环节。压缩包共38个文件,约1.87MB,以6…

作者头像 李华
网站建设 2026/10/24 4:19:36

XGBoost时序预测实战:从特征工程到滚动回测的完整工程

简介:这份资源面向时间序列预测与机器学习的入门及进阶学习者,用Xgboost算法打通从数据分析、特征工程到模型训练、保存与预测评估的完整链路,可应用于金融、气象、股票等趋势预测场景。压缩包共3个文件,以2个Python脚本和1个CSV数…

作者头像 李华