简介:这份PDF论文面向机械故障诊断方向的研究生、工程师及深度学习入门者,聚焦滚动轴承故障特征难以准确表征的痛点,提出基于振谱图与深度卷积神经网络的诊断方案。资源包共1个PDF文件,大小约1.28MB,内容为完整期刊论文,含摘要、引言、基本原理、实验验证与结论等章节,便于系统研读方法细节。论文核心思路是先对轴承振动信号做分帧、加窗、DFT与图像编码,构建表征故障的振谱图,再设计深度卷积神经网络,用已知故障模式的振谱图及标签训练网络,最后以美国凯斯西储大学公开实测振动信号验证,识别准确率达100%。文中还对比了奇异值分解、多尺度模糊熵、经验模态分解及深度信念网络等既有方法,梳理了从数据采集、预处理、特征提取到故障建模的完整流程,并给出网络训练与故障识别两阶段原理,适合作为深度学习落地故障诊断的参考范例。目前已有571人学习。
1. 从振动信号到故障标签:卷积神经网络做滚动轴承诊断到底在解决什么
产线上那台电机最近又开始“嗡嗡”作响,老师傅拿听针贴上去听了几秒,说“外圈有点问题”。这种靠经验听音的判断,在单台设备上管用,一旦扩展到几十上百台旋转机械,就彻底不够用了。滚动轴承故障诊断要解决的,就是把这种模糊的听觉经验,变成可复现、可批量部署的自动判断。卷积神经网络在这里扮演的角色,是直接从原始振动信号里学出故障特征,省掉人工设计特征那一步。传统做法要先算时域指标、再做包络谱、手动挑峰,一套流程走下来,换个工况就得重新调参。CNN 把特征提取和分类揉进一个网络,端到端训练,对变工况的适应能力明显更强。这篇笔记面向的是想把这条路走通的工程师——不管你是刚接触深度学习,还是已经在用 CNN 做一维信号分类,都能从下面找到能直接抄的配置和踩过的坑。
2. 一维卷积神经网络处理轴承振动信号的原理与数据准备
2.1 为什么轴承故障诊断偏爱一维 CNN 而不是二维
轴承振动信号本质是一维时序,采样率通常在 12kHz 到 48kHz 之间。有人会先把信号转成时频图,再套用图像分类那套二维 CNN。这条路能走,但多了一步时频变换,就多了一层信息损失和参数选择——窗长、重叠率、用什么变换,每个选择都会影响最终结果。一维 CNN 直接在原始时序上做卷积,卷积核沿时间轴滑动,学到的就是局部波形模式,比如冲击脉冲的上升沿、衰减振荡的周期。这些恰恰是轴承故障最本质的特征。
从计算量看,一维卷积的参数量远小于二维。一个卷积核大小为 64 的一维卷积层,参数量是 64×输入通道数×输出通道数;换成二维,同样感受野需要 8×8 的核,参数量直接翻到 64 倍。在嵌入式设备或边缘计算盒子上部署时,这个差距直接决定能不能跑起来。我一般会先试一维 CNN,只有在数据量特别大、故障模式极其复杂时,才考虑二维方案。
2.2 振动数据的采集、分段与标签制作
数据准备这一步,翻车的人比模型调参翻车的还多。常见做法是:加速度传感器安装在轴承座径向,采样率至少是轴承故障特征频率的 10 倍以上。以常见的 6205 轴承为例,外圈故障特征频率在转频的 3 到 5 倍左右,转频 30Hz 时特征频率约 100-150Hz,采样率 12kHz 完全够用。
拿到连续振动信号后,要切成固定长度的样本。切法有两种:重叠切分和不重叠切分。重叠切分能增加样本数量,但要注意训练集和验证集之间不能有重叠,否则验证结果会虚高。我一般用 50% 重叠做训练集增强,验证集和测试集用不重叠切分。
import numpy as np def segment_signal(signal, window_size, overlap=0.5): """ 将连续振动信号切分为固定长度样本 signal: 一维振动信号数组 window_size: 每个样本的采样点数,如 1024 overlap: 重叠比例,0 到 1 之间 返回: shape 为 (样本数, window_size) 的数组 """ step = int(window_size * (1 - overlap)) segments = [] for start in range(0, len(signal) - window_size + 1, step): segments.append(signal[start:start + window_size]) return np.array(segments) # 示例:采样率 12kHz,取 1024 点约 85ms,覆盖多个冲击周期 samples = segment_signal(raw_signal, window_size=1024, overlap=0.5) print(f"切分出 {samples.shape[0]} 个样本,每个样本 {samples.shape[1]} 点")这段代码的关键参数是window_size。取太小,一个样本里连一个完整的冲击周期都装不下;取太大,样本里混入太多正常段,故障特征被稀释。经验值是让窗口至少覆盖 3 到 5 个故障冲击周期。对于内圈故障,冲击间隔等于转频倒数,30Hz 转频对应 33ms,1024 点在 12kHz 下是 85ms,能覆盖两到三个周期,基本够用。如果发现模型学不动,先把窗口加大到 2048 试试。
标签制作要严格对应。每个样本来自哪一段信号、那段信号对应哪种故障、故障尺寸多大,都要记录清楚。常见做法是用文件夹名做标签,比如inner_race_007表示内圈故障、直径 0.007 英寸。这样加载数据时直接解析路径就行,不容易出错。
2.3 归一化与数据增强的取舍
振动信号幅值受负载影响很大,同一故障在不同负载下幅值可能差好几倍。不做归一化,模型会偏向幅值大的样本。常见做法是每个样本单独做 z-score 归一化,减均值除标准差。注意是对每个样本自己做,不是对整个数据集做,这样能消除负载带来的幅值差异。
数据增强在轴承诊断里要谨慎。图像分类那套旋转、裁剪、加噪声,在振动信号上不一定成立。加高斯噪声是安全的,相当于模拟传感器噪声。幅值缩放也可以,模拟不同负载。但时间轴上的拉伸或压缩要小心,那会改变故障特征频率,把外圈故障变成别的故障。我一般只用加噪声和幅值缩放两种增强,倍数控制在 0.8 到 1.2 之间。
3. 搭建一维 CNN 故障分类网络:结构、参数与训练脚本
3.1 网络结构设计:几层卷积、卷积核多大、池化怎么选
一维 CNN 做轴承故障分类,结构不需要太深。我试过 3 层卷积和 8 层卷积,在公开数据集上准确率差距不到 1 个百分点,但 8 层训练时间翻了三倍。常见做法是 3 到 5 个卷积块,每个块包含一维卷积、批归一化、ReLU 激活、最大池化。
卷积核大小是关键参数。第一层卷积核要大一些,比如 64 或 128,因为原始振动信号里故障冲击的上升沿比较宽,小卷积核感受野不够。后面几层可以逐步减小到 16 或 8,提取更精细的模式。池化层用最大池化,窗口大小 2 或 4,逐步降低时间维度。
import torch import torch.nn as nn class BearingCNN1D(nn.Module): def __init__(self, num_classes=10, input_length=1024): super().__init__() self.features = nn.Sequential( # 第一层:大卷积核捕捉冲击波形 nn.Conv1d(1, 32, kernel_size=64, stride=2, padding=32), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), # 第二层:中等卷积核提取局部模式 nn.Conv1d(32, 64, kernel_size=32, stride=1, padding=16), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2), # 第三层:小卷积核组合特征 nn.Conv1d(64, 128, kernel_size=16, stride=1, padding=8), nn.BatchNorm1d(128), nn.ReLU(), nn.AdaptiveAvgPool1d(1) # 全局平均池化,输出 128 维 ) self.classifier = nn.Sequential( nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, x): # x: (batch, 1, input_length) x = self.features(x) # (batch, 128, 1) x = x.squeeze(-1) # (batch, 128) return self.classifier(x) model = BearingCNN1D(num_classes=10, input_length=1024) print(f"参数量: {sum(p.numel() for p in model.parameters()):,}")这段网络定义里,第一层stride=2是为了快速降低时间维度,1024 点经过第一层卷积和池化后变成 256 点左右。padding设置为卷积核的一半,保证输出长度不因卷积而缩小。全局平均池化替代了全连接层,参数量大幅减少,过拟合风险也低。Dropout(0.3)放在分类层前,训练时随机丢弃 30% 神经元。
参数量算下来大概 5 万左右,在树莓派上都能跑推理。如果类别更多、数据更复杂,可以把通道数翻倍,但要注意参数量增长是平方级的。
3.2 训练脚本:损失函数、优化器与学习率调度
轴承故障分类是典型的类别不均衡问题。正常样本多,故障样本少,某些故障类型可能只有几十个样本。直接用交叉熵损失,模型会偏向多数类。常见做法是给每个类别加权,权重和样本数成反比。
from torch.utils.data import DataLoader, TensorDataset import torch.optim as optim # 假设 X_train: (N, 1024), y_train: (N,) X_tensor = torch.FloatTensor(X_train).unsqueeze(1) # 增加通道维 y_tensor = torch.LongTensor(y_train) dataset = TensorDataset(X_tensor, y_tensor) loader = DataLoader(dataset, batch_size=64, shuffle=True) # 类别权重:样本少的类别权重大 class_counts = np.bincount(y_train) class_weights = 1.0 / class_counts class_weights = class_weights / class_weights.sum() * len(class_counts) criterion = nn.CrossEntropyLoss(weight=torch.FloatTensor(class_weights)) optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5) for epoch in range(60): model.train() total_loss = 0 for batch_x, batch_y in loader: optimizer.zero_grad() output = model(batch_x) loss = criterion(output, batch_y) loss.backward() optimizer.step() total_loss += loss.item() scheduler.step() if (epoch + 1) % 10 == 0: print(f"Epoch {epoch+1}, Loss: {total_loss/len(loader):.4f}, LR: {scheduler.get_last_lr()[0]:.6f}")weight_decay=1e-4是 L2 正则化,防止权重过大。学习率初始 1e-3,每 20 个 epoch 减半。这个调度策略在轴承数据上比较稳,不会出现损失震荡。如果发现训练损失下降但验证损失上升,先把 dropout 调到 0.5,再考虑减小网络容量。
3.3 验证策略:按工况划分还是随机划分
这是最容易翻车的地方。很多人把数据随机打乱后划分训练集和验证集,结果验证准确率 99%,一上新工况就掉到 60%。原因很简单:同一个工况下的样本被同时分到了训练集和验证集,模型只是记住了这个工况的分布,没有学到真正的故障特征。
正确做法是按工况划分。比如数据集有 0hp、1hp、2hp、3hp 四种负载,那就用 0hp 和 1hp 做训练,2hp 做验证,3hp 做测试。这样验证准确率才反映模型在新工况下的泛化能力。如果按工况划分后准确率掉得厉害,说明模型过拟合了工况,需要加数据增强或者用域适应方法。
我一般会同时看两个指标:随机划分的准确率和按工况划分的准确率。两者差距在 5 个百分点以内,说明模型学到的特征比较鲁棒;差距超过 15 个百分点,就得回头检查数据划分和增强策略了。
4. 滚动轴承故障诊断的避坑与排查清单
4.1 准确率虚高但实际部署失效
现象:验证集准确率 98% 以上,但把模型部署到新设备上,误报率极高。
原因:数据泄露。最常见的是同一个样本的重叠切分片段同时出现在训练集和验证集里。重叠 50% 时,相邻两个样本有 512 个点完全一样,模型相当于在验证集上看到了训练集的副本。
解决:切分样本前先按时间顺序划分训练、验证、测试三段,再在各自段内做重叠切分。或者直接用不重叠切分,牺牲一些样本数量换真实评估。
4.2 损失不下降或震荡剧烈
现象:训练几个 epoch 后损失卡在某个值不动,或者上下大幅震荡。
原因:学习率太大或太小。太大导致震荡,太小导致下降缓慢。另外,如果输入信号没有归一化,梯度尺度差异大,也会震荡。
解决:先用 1e-4 到 1e-3 之间的学习率试。如果震荡,降到 1e-4;如果下降太慢,升到 3e-3。同时检查每个样本是否做了 z-score 归一化。批归一化层也能缓解这个问题,但输入归一化是前提。
4.3 某些故障类别始终分不对
现象:外圈故障和正常样本分得很准,但内圈故障和滚动体故障混淆严重。
原因:内圈故障和滚动体故障的特征频率在频谱上可能重叠,尤其是故障尺寸较小时。另外,如果这两类样本数量少,模型学不充分。
解决:先看混淆矩阵,确认是哪两类在混。如果是样本少,加类别权重或做过采样。如果是特征重叠,考虑增加网络深度或引入频域特征作为辅助输入。我试过把原始信号和包络谱拼接成双通道输入,对内圈和滚动体故障的区分度有明显提升。
4.4 模型在边缘设备上推理太慢
现象:PC 上推理没问题,部署到嵌入式设备后单次推理超过 100ms,满足不了实时性要求。
原因:模型参数量或计算量太大。全连接层是参数量大户,卷积层是计算量大户。
解决:用全局平均池化替代全连接层,参数量能降一个数量级。卷积层用深度可分离卷积替代标准卷积,计算量降到三分之一左右。如果还不行,把输入窗口从 1024 降到 512,但要注意故障特征是否还完整。
4.5 不同批次数据分布不一致导致模型失效
现象:同一台设备,换了传感器或调整了采样率后,模型准确率大幅下降。
原因:模型学到了传感器相关的特征,而不是故障本身的特征。采样率变化会直接改变故障特征频率在频谱上的位置。
解决:训练时加入不同采样率和不同传感器的数据做增强。如果做不到,至少保证部署时的采样率和训练时一致。另外,在输入端做重采样,把所有数据统一到同一个采样率再送进模型。
5. 从分类到可解释:用卷积核激活定位故障冲击
模型告诉你“这是外圈故障”,但你不知道它凭什么这么判断。在工业场景里,这个“凭什么”往往比分类结果本身更重要——维修人员需要知道故障发生在哪个时间点、哪个冲击周期,才能定位到具体位置。一维 CNN 的卷积核激活可以给出这个答案。
做法很简单:取一个测试样本,前向传播到第一层卷积,把每个卷积核的输出拿出来。哪个卷积核在哪个时间点激活最强,就说明那个时间点有它学到的模式。把激活值画出来,和原始振动信号叠在一起,就能看到模型关注的是哪些冲击。
def visualize_conv_activation(model, sample, conv_layer_idx=0): """ 提取指定卷积层的激活并返回 sample: (1, 1, 1024) 的单个样本 """ activations = [] x = sample for i, layer in enumerate(model.features): x = layer(x) if i == conv_layer_idx: activations.append(x.detach().squeeze().numpy()) return activations[0] # 取一个外圈故障样本 sample = X_test[0:1] act = visualize_conv_activation(model, torch.FloatTensor(sample).unsqueeze(1)) # act shape: (32, 时间点数),32 个卷积核各自的激活拿到激活矩阵后,对每个卷积核的激活做归一化,然后找激活峰值对应的时间点。如果多个卷积核的峰值都集中在某个周期附近,说明模型确实学到了周期性的冲击特征。如果激活是散乱的,说明模型可能学到了噪声或工况相关的伪特征,这时候就要回头检查数据质量了。
我一般会抽 10 个测试样本做这个可视化,如果 8 个以上的激活峰值和故障冲击周期对得上,就认为模型学到的特征是可信的。这个方法比单纯看准确率更有说服力,尤其是在向产线人员解释模型决策时。
还有一个进阶用法:把激活峰值的时间间隔算出来,反推故障特征频率,和理论计算值对比。如果对得上,说明模型学到的物理意义是对的;如果对不上,但分类准确率很高,那就要警惕模型是不是在走捷径。这个习惯帮我避免了好几次“准确率虚高但实际不可用”的翻车。希望帮到你。
本文还有配套的精品资源,点击获取