news 2026/10/20 7:21:28

深度学习故障诊断实战:从信号预处理到CNN模型训练与部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度学习故障诊断实战:从信号预处理到CNN模型训练与部署

简介:一套面向深度学习初学者的故障诊断入门示例包,聚焦设备监测数据中的异常模式识别,帮助相关专业学生和工程师快速跨过数据处理与建模门槛。资源共57个文件,包含40个mat格式的样本数据、3个Python源码脚本、2个已训练好的模型权重文件以及10张可视化结果图,压缩包约46.13MB,目录模块划分清晰。目前已有908人参与学习,内容覆盖数据预处理、模型搭建与训练全流程:既有数据清洗、标准化、滑动窗口特征提取等实操方法,也涉及CNN与LSTM等网络结构的选择、模型初始化、优化器对比和正则化调参策略。学习者可对照源码和权重文件,自行复现故障分类实验,并在此基础上扩展至更复杂的设备健康管理场景。

1. 基于深度学习的故障诊断:一个能跑通的入门闭环

做故障诊断的工程师第一次接触深度学习时,大多卡在同一个地方:网上教程全是图像分类、自然语言处理,真到自己手里的振动数据、电流信号,完全不知道从哪下手。这份示例项目就是针对这个缺口来的——它把一套完整的故障诊断流程拆开,从数据预处理、模型搭建到模型训练,每一段都能直接跑通。

项目本身不复杂,核心就三件事:把一维的传感器信号整理成模型能吃的样本,用卷积神经网络自动提取故障特征,再通过训练让模型区分不同故障类型。适合两类人:一是刚入门深度学习、想把它用到工业场景的工程师,二是学校里有研究方向但缺少工程实践的开发者。深度学习在这类任务里的优势在于不需要手动设计特征,但前提是数据预处理和模型设计要匹配,这份示例恰好把这条链路走通了。下面按我实际复现的顺序,把每一步的细节、参数和踩过的坑都写清楚。

2. 数据预处理:从连续信号到可训练样本

2.1 数据来源与文件组织方式

这个示例用的是公开的轴承故障数据集,典型的文件组织方式是一个文件夹对应一种工况。我拿到的压缩包里,几条通道的振动信号分别存在不同目录,每个目录下是连续的 CSV 或 MAT 格式文件。一个容易忽略的点是:数据的采样率是多少、每段信号多长、有没有设置负载参数,这些信息决定了你后面滑窗切片的尺寸。

# 先看数据目录长什么样 import os data_dir = "./bearing_data" for root, dirs, files in os.walk(data_dir): for d in dirs[:5]: print(d) if files: print(f"示例文件: {files[0]}, 文件数: {len(files)}") break

这里就是最基础的盘点,不需要做什么处理。我一般会先确认一个目录下到底有多少个文件、每个文件多大,这直接影响后续整批加载还是逐段读取。如果单个文件就有几十 MB,建议用数据加载器分批读,别一次性全塞进内存。示例项目的做法是直接读取后切片,所以文件数量不多时可以这么干;文件多了,你就得改成边读边处理。

2.2 滑窗切片:把连续信号变成独立样本

原始振动信号是连续的长序列,一个文件可能有几十万甚至上百万个采样点。直接把整段序列丢给模型,序列太长,计算量爆炸,而且一个样本里如果包含多种故障状态,监督信号也不干净。所以要对信号做滑窗切片,每个窗口视作一个独立样本,窗口长度通常是几百到几千个点。

import numpy as np def sliding_window(data, window_size=1024, stride=512): """滑窗切片,返回 (样本数, window_size) 的数组""" num_samples = (len(data) - window_size) // stride + 1 windows = np.zeros((num_samples, window_size)) for i in range(num_samples): start = i * stride windows[i] = data[start:start + window_size] return windows # 示例:取一段 50000 点的信号,切成窗口 raw_signal = np.random.randn(50000) # 模拟振动信号 samples = sliding_window(raw_signal, window_size=1024, stride=512) print(f"原始信号长度: {len(raw_signal)} -> 样本数: {len(samples)}")

窗口大小和步长是两个关键参数。1024 个点是个常见起点,对应的是在常用采样率下能包含数个旋转周期的长度;512 的步长意味着窗口之间有一半重叠,相当于数据增广。窗口太小,单次能看到的波形太短,低频故障特征容易丢;窗口太大,样本量变少,训练效率下降。我一般会先按采样率和转频估算一下,一个窗口最好覆盖至少 5-10 个旋转周期。

2.3 归一化与训练集划分

信号直接输入网络,不同通道的幅值差异可能很大,有的加速度传感器输出在 ±10g,有的速度信号在 mm/s 级别。如果不做归一化,模型初期训练会很折腾。示例项目在这里对每个窗口做了 z-score 归一化,即减均值、除标准差。

def normalize_windows(windows, eps=1e-6): """每个窗口独立归一化,避免全局统计被个别大峰值带偏""" mean = windows.mean(axis=1, keepdims=True) std = windows.std(axis=1, keepdims=True) return (windows - mean) / (std + eps) normalized_samples = normalize_windows(samples) print(f"归一化后样本范围: [{normalized_samples.min():.3f}, {normalized_samples.max():.3f}]")

加一个极小的 eps 是为了防止某段信号完全为零时出现除零错误。这里需要注意的是按窗口独立归一化,而不是按整个数据集做全局归一化。原因很实际:正常工况的振动幅值往往小于故障工况,全局归一化会把故障信号的大幅值压低,相当于人为削弱了故障特征。按窗口归一化更贴近特征本身的形态,训练效果也稳定一些。

数据划分上,示例代码默认按目录划分,也就是把不同工况的目录分到训练集和测试集,而不是把同一目录下的样本随机打乱再分。这是很多入门者容易做错的关键点——同一个文件里切出来的相邻窗口相关性极高,随机划分会导致测试集里混进训练样本的近亲,评估出来的精度虚高。示例项目选择按文件分,这个思路是对的。

3. 模型搭建:用 CNN 完成故障特征提取与分类

3.1 为什么选一维卷积而不是全连接网络

早期做故障诊断的传统流程是先从信号里提取时域统计量(均值、峰值、峭度)和频域特征(频谱峰值、边频带),再丢给全连接网络或 SVM 分类。这套流程的问题在于特征工程依赖经验,换一个设备、换一种工况,原来管用的特征可能就失效了。一维卷积神经网络可以直接吃原始波形,把特征提取和分类放在同一个网络里端到端训练,这是这份示例项目选 CNN 的核心原因。

一维卷积在故障诊断里的优势还在于它具备平移等变性——轴承故障的冲击成分不一定总在窗口的同一个位置上,卷积核对这种位置偏移有一定的宽容度。相比全连接网络,CNN 的参数量也小得多,1024 个输入点全连接第一层如果接 256 个神经元就是 26 万个参数,而同样输入用卷积核处理,参数量只有几千级别,对中小规模数据集更友好。

3.2 网络结构设计与代码实现

示例项目的网络不算深,属于典型的小型 CNN:三个卷积块加全局平均池化再加分类层。卷积核大小取 3,特征图数量逐层翻倍,从 16 到 32 再到 64。这种结构的设计思路是浅层捕获短时冲击形态,深层捕获更宽的调制特征。

import tensorflow as tf from tensorflow.keras import layers, models def build_model(input_shape=(1024, 1), num_classes=4): model = models.Sequential([ layers.Input(shape=input_shape), # 第一个卷积块 layers.Conv1D(16, kernel_size=3, strides=2, padding="same"), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling1D(pool_size=2, strides=2), # 第二个卷积块 layers.Conv1D(32, kernel_size=3, strides=2, padding="same"), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling1D(pool_size=2, strides=2), # 第三个卷积块 layers.Conv1D(64, kernel_size=3, strides=2, padding="same"), layers.BatchNormalization(), layers.ReLU(), layers.GlobalAveragePooling1D(), layers.Dropout(0.3), layers.Dense(num_classes, activation="softmax") ]) return model model = build_model() model.summary()

设计上有几个细节值得解释。第一,strides=2 配合 MaxPooling 逐层把序列长度从 1024 降到 512、256、128,相当于逐层扩大感受野,让最后的全局平均池化能看到整个输入范围的信息。第二,每个卷积后面接 BatchNormalization,这对振动信号这种分布多变的数据很实用,训练收敛明显加快。第三,分类前加 Dropout 而不是在卷积层之间加,故障诊断样本量通常不大,Dropout 放在最后可以防止分类层过拟合。最后一个卷积块后面接 GlobalAveragePooling1D 而不是 Flatten 加 Dense,这样可以大幅减少参数量,例如 64 个特征图直接平均成 64 维向量,再进入分类层,参数开销小很多。

3.3 类不均衡与样本标签构造

故障诊断场景里最常遇到的数据问题是类不均衡:正常样本可能占总量的 60% 以上,而某种特定故障可能只占 5%。如果直接按原始比例训练,模型几乎不需要学特征,只要预测正常类就能拿到不错的准确率。示例项目在这个问题上用了一个比较实用的手法——标签构造时把相同故障类型下的多个工况目录合并为一类,这样至少不会让某个故障类只有几十个样本。

def make_labels(folder_name, class_mapping): """目录名映射到类别索引""" return class_mapping.get(folder_name, -1) class_mapping = { "normal": 0, "inner_race": 1, # 内圈故障 "outer_race": 2, # 外圈故障 "ball": 3 # 滚动体故障 } sample_label = make_labels("inner_race", class_mapping) print(f"内圈故障映射为类别: {sample_label}")

如果样本量差距还是太大,常见做法是加权交叉熵,或者对少数类做过采样,也就是重复切片少数类样本。不过我在复现时发现,滑窗切片的天然优势是同一段信号可以任意调整步长来产生更多样本,所以过采样时不需要复制原始数据,把少数类的窗口步长调小即可。这是信号数据相对于图像数据的一个小便利,很多人没注意到。

4. 模型训练与评估:从训练曲线到混淆矩阵

4.1 训练超参数怎么定

示例项目默认使用 Adam 优化器,初始学习率 0.001,损失函数用交叉熵,批量大小 32,训练轮数 50。这些参数对中小规模数据集算是一个稳妥的起点。我复现时第一个版本直接照搬,发现训练到大约第 20 轮时验证准确率就到 95% 以上,后续基本持平。这里有一个值得注意的经验:如果你发现验证准确率在前期就冲到很高,不用着急庆祝,先看训练集准确率是多少——如果训练集已经接近 100% 而验证集差一截,说明模型已经开始过拟合,应该减少轮数或加强正则化。

model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss="sparse_categorical_crossentropy", metrics=["accuracy"] ) history = model.fit( x_train, y_train, validation_data=(x_val, y_val), batch_size=32, epochs=50, verbose=1 )

用 sparse_categorical_crossentropy 而不是 categorical_crossentropy,是因为标签是整数索引而不是 one-hot 编码,前者省一步转换,本质上没有区别。批量大小 32 是我在 CPU 上也能接受的速度,显存或内存充裕可以调到 64 或 128,训练速度会快一点,但收敛稳定性可能略有波动。学习率这里是固定值,如果需要调优,常见做法是在某些轮次后衰减为原来的 0.1 倍,示例项目没有做这块,说明入门阶段固定学习率已经够用。

4.2 保存模型与再加载

训练完模型之后的第一个动作一定是保存。在这个示例里,推荐的保存格式是 Keras 原生格式,也就是整个模型(含权重和结构)存成一个文件夹。这样后续加载不需要重新构造网络结构,也不会出现权重和结构不匹配的问题。

model.save("fault_diagnosis_model.keras") # 重新加载,直接用于推理 loaded_model = tf.keras.models.load_model("fault_diagnosis_model.keras") # 单条样本推理 sample = x_test[0][np.newaxis, ...] # 加 batch 维度 pred = loaded_model.predict(sample, verbose=0) pred_class = int(np.argmax(pred)) print(f"预测类别: {pred_class}, 概率分布: {pred[0]}")

推理时加 batch 维度是新手最容易忘的一步。训练时输入是 (batch_size, 1024, 1),而单条样本是 (1024, 1),如果直接丢进模型会报维度错误或者被当成 1024 个独立样本处理,结果完全错误。加载模型时注意,不要同时保留多个版本然后互相覆盖,我习惯在训练结束后立刻把模型存一份,文件名里带上训练日期或轮数,防止后面调参把好模型冲掉,这个习惯帮我省了不少重复训练的时间。

4.3 评估:只看准确率远远不够

故障分类的准确率只能告诉你模型整体表现如何,掩盖了类别间的差异。对故障诊断来说,混淆矩阵才是真正需要盯住的输出。比如把外圈故障误判成内圈故障,和把故障误判成正常,工程后果完全不同。前者只是维修时多拆一个部件,后者可能导致设备带病运行。

from sklearn.metrics import confusion_matrix, classification_report y_pred = np.argmax(model.predict(x_test, verbose=0), axis=1) print(classification_report(y_test, y_pred, target_names=["normal", "inner_race", "outer_race", "ball"])) cm = confusion_matrix(y_test, y_pred) print("混淆矩阵:") print(cm)

classification_report 里每个类别的 precision、recall、f1-score 和样本数量一起列出来,一眼就能看出哪个类别是瓶颈。如果某个故障类的 recall 明显偏低,通常不是模型结构的问题,而是数据量太少或者特征和其他类太接近。这种情况下先不要急着加网络深度,优先检查样本数量和特征分布是否可分。

5. 故障诊断入门的五个常见坑:现象、原因、解决

5.1 验证精度很高,换一批数据直接翻车

现象:在数据集上测试准确率到了 98%,拿去处理现场采的新一轮数据,准确率掉到 70% 甚至更低。原因:训练集和测试集来自同一批工况甚至同一时间段,数据分布几乎一致,模型学到的可能是该数据集特有的噪声模式而不是泛化的故障特征。解决:按时间顺序切分,前 70% 时间段训练,后 30% 时间段测试,或者至少保证训练集和测试集来自不同负载或转速工况。

5.2 全局归一化导致故障特征被压缩

现象:模型一直不收敛,训练准确率在 60% 左右徘徊,怎么调学习率都没有明显改善。原因:数据预处理做了全局归一化,故障工况的信号幅值远大于正常工况,归一化后正常信号的方差被放大,故障信号反而被压扁,类间的区分度减弱。解决:改为按每个窗口独立做 z-score 归一化,实验效果基本立竿见影。

5.3 标签和信号没有对齐,模型在乱学

现象:训练损失下降正常,但验证集表现一塌糊涂,甚至出现训练损失降得很低、验证损失反向上升的情况。原因:滑窗切片的顺序错位,或者标签映射时目录和类别对应写反了,模型学到的实际上是一个错误的映射关系。解决:在训练前做一次可视化检查——从每个类别的样本里随机抽几条打印出来,肉眼看一下波形特征是否合理,同时确认标签索引和目录名一一对应。

5.4 数据泄漏:相邻窗口混进了训练和测试

现象:随机划分训练测试集后准确率高达 99%,但模型部署到新数据后效果极差。原因:滑窗产生的相邻窗口高度相似,随机划分时测试集中混入了来自同一原始段落的窗口,相当于一个题目的近似重复出现在考试里。解决:按原始信号的物理单位划分数据集,保证同一个信号段要么全部在训练集,要么全部在测试集,这是信号类数据划分最重要的规矩。

5.5 Adam 学习率不动,训练到一半效果变差

现象:训练前期损失下降顺利,到后期验证准确率反而波动变大,最终保存的模型不是最优的一版。原因:固定学习率 0.001 在后期过大,参数在最优解附近来回震荡。解决:用回调函数监控验证损失,当连续若干轮不下降就把学习率乘以 0.5 或 0.1,同时保存验证指标最好的那一版模型,而不是用最后一轮的权重。

from tensorflow.keras.callbacks import ReduceLROnPlateau, ModelCheckpoint callbacks = [ ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=5, min_lr=1e-6), ModelCheckpoint("best_model.keras", monitor="val_acc", save_best_only=True) ]

这段代码不能解决所有问题,但能避免两个入门期最常见的翻车场景:一是模型训练到一半因学习率过大而在最优解附近震荡,二是训练结束后发现最后一轮的权重不是整段训练里最好的那一版。最佳模型保存在本地后,后续推理统一从 best_model.keras 加载即可。

6. 进阶一步:把 CNN 学到的特征可视化,验证模型真的学到了故障特征

准确率只能说明模型的判别边界在测试集上有效,但无法解释它是根据什么做出判断的。对于故障诊断这类工程落地场景,需要向同事或导师解释"模型为什么这样判断",特征可视化就是一个很直观的验证手段。操作上,做法是把训练好的模型去掉最后一层分类层,保留前面的特征提取部分,然后将测试样本传入,得到高维特征向量,再用 t-SNE 降到二维做散点图。

from sklearn.manifold import TSNE import matplotlib.pyplot as plt # 取出去掉分类层的特征提取模型 feature_model = tf.keras.Model( inputs=model.input, outputs=model.layers[-3].output # 全局平均池化后的输出 ) features = feature_model.predict(x_test[:500], verbose=0) tsne = TSNE(n_components=2, perplexity=30, random_state=42) features_2d = tsne.fit_transform(features) plt.figure(figsize=(8, 6)) for cls in range(4): mask = y_test[:500] == cls plt.scatter(features_2d[mask, 0], features_2d[mask, 1], s=10, label=f"class {cls}") plt.legend() plt.savefig("tsne_features.png", dpi=150)

t-SNE 的结果如果呈现每个类别聚成一团,而且类间明显分离,说明模型提取的特征确实可以区分不同故障类型。如果聚类效果差,比如某些类别混在一起,问题大概率不在模型结构上,而在数据本身——可能是样本量太少,也可能是两种故障的振动特征在原始信号层面真有重叠。perplexity 是 t-SNE 唯一需要关心的参数,取值在 5 到 50 之间,样本量小就取小值,样本量大就取大值,一般从 30 开始调。

把可视化做完之后,还要补一个工程习惯:为模型输出配上置信度阈值。现场数据是开放世界的,总有模型没见过的故障形态,如果模型对所有类别的概率都很低,更稳妥的做法是输出"未知类"而不是强行分配一个类别。具体实现就是在 softmax 输出上设置一个阈值,比如最大概率低于 0.8 时判定为未知。

我刚开始做故障诊断时,觉得模型训练完了就万事大吉,后来在一次模拟项目X的回传测试中被现实教育了一回——模型在实验室数据集上准确率很高,一到现场数据就频繁错判,原因就是测试集中混入了模型从未见过的噪声形态,而我没有设置拒识阈值。从那以后我每次部署诊断模型,都强制走一遍特征可视化和置信度阈值的流程,不是为别的,只为了不让自己对着模型拍脑袋说"应该没问题"。希望这个流程对你也有帮助。

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

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

DVWA靶场DOM型XSS全等级通关:原理、绕过与防御实战

1. 先搞清楚:DOM型XSS和反射型、存储型不是一回事前阵子有朋友在群里问,DVWA里的DOM型XSS到底怎么打,为什么照着反射型XSS的payload直接抄过去却经常不弹窗。DVWA(Damn Vulnerable Web Application)里的XSS模块有三个入…

作者头像 李华
网站建设 2026/10/20 7:20:54

Llama 3 架构精读:词表扩容、GQA 与 FP8 量化长上下文实测

1. 为什么值得花时间精读 Llama 3 的架构细节Llama 3 发布之后,我身边不少做推理部署的朋友第一反应是"又来了一个新模型",第二反应才是"这次架构上到底改了什么"。说实话,如果你只是调用 API 或者跑个现成的对话 Demo&a…

作者头像 李华
网站建设 2026/10/20 7:20:53

棉田虫害轻量化检测:从数据采集到边缘部署全流程实战

种地的都知道,棉田虫害发现晚一周,产量损失就补不回来。我前两年跟着某农业示范基地的植保团队做课题,天天蹲在田里拍虫子、数虫子、被棉铃虫折磨到头秃,最后落地了一套基于深度学习的棉田虫害轻量化检测系统。说它"轻量化&q…

作者头像 李华
网站建设 2026/10/20 7:20:52

Unity传送失效之谜:从物理引擎到刚体休眠的完整排查指南

1. 传送失效:一场由物理引擎引发的"灵异事件"先跟你讲个真实场景:某天你写好了传送代码,transform.position new Vector3(...)一行搞定,测试时角色也确实瞬移过去了。但下一帧,角色要么被弹回原地&#xff…

作者头像 李华
网站建设 2026/10/20 7:20:14

MTCNN人脸检测完整实现:P-Net/R-Net/O-Net三级级联详解

简介:本资源是一套基于MTCNN(Multi-Task Cascaded Convolutional Networks)实现人脸检测的完整开源代码工程,面向深度学习初学者、计算机视觉方向学生及人脸相关应用开发者,解决从模型构建、训练到推理部署的一站式实践…

作者头像 李华
网站建设 2026/10/20 7:19:59

Virtuoso ADE L用户指南翻译实战:模拟IC仿真流程与高频功能详解

接到Virtuoso ADE L用户指南翻译任务时,我第一反应是:这活儿可不好干。作为天天泡在模拟IC设计里的工程师,ADE L这个窗口我几乎每天都要点开,但真要把厚厚一本英文用户指南逐字翻译成中文,等于把平时闭着眼都能操作的界…

作者头像 李华