
简介一套面向深度学习初学者的故障诊断入门示例包聚焦设备监测数据中的异常模式识别帮助相关专业学生和工程师快速跨过数据处理与建模门槛。资源共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_size1024, stride512): 滑窗切片返回 (样本数, 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_size1024, stride512) print(f原始信号长度: {len(raw_signal)} - 样本数: {len(samples)})窗口大小和步长是两个关键参数。1024 个点是个常见起点对应的是在常用采样率下能包含数个旋转周期的长度512 的步长意味着窗口之间有一半重叠相当于数据增广。窗口太小单次能看到的波形太短低频故障特征容易丢窗口太大样本量变少训练效率下降。我一般会先按采样率和转频估算一下一个窗口最好覆盖至少 5-10 个旋转周期。2.3 归一化与训练集划分信号直接输入网络不同通道的幅值差异可能很大有的加速度传感器输出在 ±10g有的速度信号在 mm/s 级别。如果不做归一化模型初期训练会很折腾。示例项目在这里对每个窗口做了 z-score 归一化即减均值、除标准差。def normalize_windows(windows, eps1e-6): 每个窗口独立归一化避免全局统计被个别大峰值带偏 mean windows.mean(axis1, keepdimsTrue) std windows.std(axis1, keepdimsTrue) 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_classes4): model models.Sequential([ layers.Input(shapeinput_shape), # 第一个卷积块 layers.Conv1D(16, kernel_size3, strides2, paddingsame), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling1D(pool_size2, strides2), # 第二个卷积块 layers.Conv1D(32, kernel_size3, strides2, paddingsame), layers.BatchNormalization(), layers.ReLU(), layers.MaxPooling1D(pool_size2, strides2), # 第三个卷积块 layers.Conv1D(64, kernel_size3, strides2, paddingsame), layers.BatchNormalization(), layers.ReLU(), layers.GlobalAveragePooling1D(), layers.Dropout(0.3), layers.Dense(num_classes, activationsoftmax) ]) return model model build_model() model.summary()设计上有几个细节值得解释。第一strides2 配合 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( optimizertf.keras.optimizers.Adam(learning_rate0.001), losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( x_train, y_train, validation_data(x_val, y_val), batch_size32, epochs50, verbose1 )用 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, verbose0) 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, verbose0), axis1) 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(monitorval_loss, factor0.5, patience5, min_lr1e-6), ModelCheckpoint(best_model.keras, monitorval_acc, save_best_onlyTrue) ]这段代码不能解决所有问题但能避免两个入门期最常见的翻车场景一是模型训练到一半因学习率过大而在最优解附近震荡二是训练结束后发现最后一轮的权重不是整段训练里最好的那一版。最佳模型保存在本地后后续推理统一从 best_model.keras 加载即可。6. 进阶一步把 CNN 学到的特征可视化验证模型真的学到了故障特征准确率只能说明模型的判别边界在测试集上有效但无法解释它是根据什么做出判断的。对于故障诊断这类工程落地场景需要向同事或导师解释模型为什么这样判断特征可视化就是一个很直观的验证手段。操作上做法是把训练好的模型去掉最后一层分类层保留前面的特征提取部分然后将测试样本传入得到高维特征向量再用 t-SNE 降到二维做散点图。from sklearn.manifold import TSNE import matplotlib.pyplot as plt # 取出去掉分类层的特征提取模型 feature_model tf.keras.Model( inputsmodel.input, outputsmodel.layers[-3].output # 全局平均池化后的输出 ) features feature_model.predict(x_test[:500], verbose0) tsne TSNE(n_components2, perplexity30, random_state42) 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], s10, labelfclass {cls}) plt.legend() plt.savefig(tsne_features.png, dpi150)t-SNE 的结果如果呈现每个类别聚成一团而且类间明显分离说明模型提取的特征确实可以区分不同故障类型。如果聚类效果差比如某些类别混在一起问题大概率不在模型结构上而在数据本身——可能是样本量太少也可能是两种故障的振动特征在原始信号层面真有重叠。perplexity 是 t-SNE 唯一需要关心的参数取值在 5 到 50 之间样本量小就取小值样本量大就取大值一般从 30 开始调。把可视化做完之后还要补一个工程习惯为模型输出配上置信度阈值。现场数据是开放世界的总有模型没见过的故障形态如果模型对所有类别的概率都很低更稳妥的做法是输出未知类而不是强行分配一个类别。具体实现就是在 softmax 输出上设置一个阈值比如最大概率低于 0.8 时判定为未知。我刚开始做故障诊断时觉得模型训练完了就万事大吉后来在一次模拟项目X的回传测试中被现实教育了一回——模型在实验室数据集上准确率很高一到现场数据就频繁错判原因就是测试集中混入了模型从未见过的噪声形态而我没有设置拒识阈值。从那以后我每次部署诊断模型都强制走一遍特征可视化和置信度阈值的流程不是为别的只为了不让自己对着模型拍脑袋说应该没问题。希望这个流程对你也有帮助。本文还有配套的精品资源点击获取