
简介基于卷积神经网络的滚动轴承故障诊断研究论文聚焦深度学习与机械故障诊断的交叉应用面向机械工程、智能制造及相关专业的研究者和学生解决传统时频域方法难以全面表征轴承故障特征、诊断准确率有限的问题。资源为单个PDF文档文件大小1.28MB内容完整、实验数据详实。论文提出对振动信号进行分帧、加窗、DFT与图像编码得到振谱图设计深度卷积神经网络自动提取故障特征并识别轴承状态采用凯斯西储大学公开实测振动信号验证故障识别准确率达到100%并讨论了短时平稳性、特征表征等关键环节。这份资料已有571人学习适合用于故障诊断算法研究、深度学习方法入门或相关课题论文参考。对于想将神经网络、机器学习应用于工业数据建模的读者可从中获得完整的方法流程、网络设计思路与实验对比依据。1. 基于卷积神经网络的滚动轴承故障诊断从数据到模型的完整切入点滚动轴承是旋转机械中最常见也最容易失效的部件振动信号里藏着损伤早期的微弱冲击成分但现场采集到的信号往往被工频、噪声和传递路径严重污染。过去做故障诊断要先设计滤波器、算时域统计量或包络谱特征再交给分类器特征提不好后面模型再花哨也白搭。卷积神经网络在这类任务里的价值是把“人工设计特征”这一步压缩掉直接用带标签的振动信号端到端训练让卷积核自己学习有判别力的局部模式。这和图像分类里的 CNN 在原理上同源但对象变成了时间序列。对 IT 背景的工程师来说信号处理和故障机理不需要一开始就很深反而数据组织方式、网络输入形态和训练细节决定了最终精度。这篇内容按“原理 → 数据 → 建模 → 实战”推进不用领域黑话堆砌每步都能落到代码上。2. 卷积神经网络在滚动轴承上如何工作结构、卷积核和池化的实际含义2.1 卷积核在振动信号上真正做了什么图像里的卷积核抓的是边缘、纹理而一维振动信号上的卷积核抓的是时间上的局部波形片段。一个长度为 3 的卷积核[w1, w2, w3]在某个位置会算出import numpy as np def conv1d_local(x, w): # x: 一维信号片段, w: 卷积核权重 # 计算单次卷积结果对应信号在某一位置的局部加权和 return np.sum(x * w) signal np.array([0.12, -0.35, 0.78, -0.21, 0.55]) kernel np.array([0.5, -1.0, 0.3]) result conv1d_local(signal[0:3], kernel) # 计算第一个位置的卷积 print(f第一个位置的卷积结果: {result:.4f})这段代码说明了 CNN 最底层的计算方式卷积核在信号上滑动每个位置做一次“点乘后求和”。对轴承振动信号来说不同直径滚动体损伤产生的冲击波形宽度不同小卷积核能捕捉陡峭的冲击沿大卷积核能看到整个冲击衰减过程所以网络前面几层通常会堆叠不同尺寸的卷积核来覆盖不同尺度的模式。提示不要一上来就用很大的卷积核。轴承振动采样率动辄 12kHz 到 48kHz大核的感受野虽然大但参数和计算量涨得也快而且会把故障冲击的瞬态细节抹平。2.2 池化与下采样在保特征和压维度之间找平衡CNN 里池化层不改变通道数只压缩序列长度。对故障诊断有一个常见误解觉得池化必然丢信息于是干脆不用池化。实际上一维振动信号里相邻几十个采样点高度相关直接 max pooling 取局部最大冲击值不仅保留了“故障冲击是否出现”这个关键信息还让特征对相位漂移有容忍度。average pooling 更平滑适合整体能量较弱但持续存在的磨损类故障。具体到网络设计一般会在每个卷积块后接一个窗口长度 2、步长 2 的 max pooling让序列长度逐层折半。以一个输入长度 1024 的信号为例经过 4 次池化后长度变成 64最后接全局平均池化或展平后接全连接层。这样设计的好处是后层卷积核的感受野在每一层翻倍能逐步从“局部冲击”组装成“损伤模式”同时整个网络参数量可控。2.3 从 LeNet-5 到工业诊断网络结构与参数演进LeNet-5 是卷积神经网络基本结构里最经典的一个参考起点它确立了“卷积-池化-卷积-池化-全连接”的骨架。工业诊断网络沿用这个骨架但会针对信号特性做调整。下面给出一个常见的一维诊断网络结构表这个结构在中小规模数据上表现稳定网络层输入维度输出维度参数设置Conv1d ReLU(1, 1024)(16, 1024)卷积核 32步长 1padding1MaxPool1d(16, 1024)(16, 512)窗口 2步长 2Conv1d ReLU(16, 512)(32, 512)卷积核 3步长 1padding1MaxPool1d(32, 512)(32, 256)窗口 2步长 2Conv1d ReLU(32, 256)(64, 256)卷积核 3步长 1padding1MaxPool1d(64, 256)(64, 128)窗口 2步长 2Flatten Dense(8192)(128)全连接ReLUDense Softmax(128)故障类别数分类输出这种结构把通道数从 16 增加到 64同时把序列长度从 1024 逐步压缩到 128属于典型的“通道加宽、长度收窄”路线。第一层卷积核尺寸选 32 而非 3是因为轴承故障冲击在 12kHz 采样率下通常持续 20 到 40 个采样点核太短抓不住完整的冲击波形。后面的卷积层用核 3 就够了因为此时输入已经是前层提取好的特征图。注意这个表里的 Dense 层参数量是 8192×128约占整个网络的 60% 以上。如果训练数据少可以考虑在 Dense 前用全局平均池化替代展平操作参数量能大幅下降这是工业场景里很常用的一种压缩手段。3. 构建滚动轴承振动数据集读取、切片与标签的制作3.1 CWRU 数据读取从 .mat 到一维数组CWRU 轴承数据中心是比较常用的公开基准数据源采样率通常为 12kHz 或 48kHz里面以.mat格式保存了不同工况和故障直径下的振动信号。用 SciPy 读取时要留意.mat文件里存储的是结构体数组不同版本的 Matlab 导出的结构可能略有差异。下面是一段可以按需修改的读取代码from scipy.io import loadmat import numpy as np def load_cwru_mat(path, field_name): 读取 CWRU 的 .mat 文件并提取振动信号 Args: path: .mat 文件路径 field_name: 数据字段名如 X105_DE_time Returns: 一维振动信号数组 data loadmat(path) # 注意: 有些 .mat 文件里字段名带有额外的前缀或后缀 if field_name in data: signal data[field_name].flatten() return signal.astype(np.float64) else: # 遍历字典查找包含关键字的字段 for key in data: if field_name.split(_)[0] in key: return data[key].flatten() raise KeyError(f字段 {field_name} 不存在)loadmat读出来的数组默认是二维的列向量.flatten()是必要的不然后面的滑动窗口函数会收到错误的维度。CWRU 数据里一般有两路信号可用驱动端加速度计DE和风扇端加速度计FE实际使用时优先用 DE 信号因为故障激发的冲击在离故障点较近的位置更明显。3.2 滑动窗口采样窗口长度、步长和重叠策略把一段几十秒的连续信号变成训练样本最直接的方式是滑动窗口截取。窗口长度和步长决定了样本总数和样本之间的独立性这是一步容易出错的操作。窗口太短样本里包含的轴承旋转周数不够模型看不到完整的调制周期窗口太长单样本信息量多但样本数量减少且训练更慢。一般建议窗口长度覆盖轴承旋转周期的 4 到 8 倍。以 12kHz 采样率、转速 1772 r/min 为例旋转一圈约需要采样点数量为12000 / (1772 / 60) ≈ 406点取 8 倍就是 3248 点。实际使用中常用 1024 或 2048方便后续网络设计时做整数次池化。下面给出一个滑动窗口截取函数def sliding_window(signal, window_len1024, stride256): 对一维信号做滑动窗口截取支持重叠采样 Args: signal: 一维振动信号 window_len: 每个样本的长度 stride: 窗口滑动步长 Returns: windows: (N, window_len) 的样本矩阵 if stride 0: raise ValueError(stride 必须大于 0) if len(signal) window_len: raise ValueError(信号长度小于窗口长度) windows [] start 0 while start window_len len(signal): windows.append(signal[start:start window_len]) start stride return np.stack(windows)步长等于窗口长度时样本之间无重叠样本独立性最好但总量少步长小于窗口长度时相邻样本共享大量数据属于数据增强的一种形式能缓解样本不足的问题但要小心训练集和测试集之间的泄漏。设定重叠率时应确保样本划分发生在“时间片段”级别而不是在整条连续信号上随机抽取这样才能避免同一个旋转周期里的冲击模式同时出现在训练集和测试集。3.3 标签制作和数据集划分CWRU 数据按故障位置和故障直径命名典型类别包括滚动体故障、内圈故障、外圈故障外加正常状态。构建阶段要做两件事一是把字符串文件名映射成整数标签二是按“工况”而不是“样本”划分训练集和测试集。def make_label_pairs(file_infos): 将文件名映射到整数标签 Args: file_infos: 形如 {file: path, fault_type: Inner, diameter: 0.021} Returns: labels: 整数标签列表 name_mapping: 故障类型映射字典 fault_names sorted(set([f[fault_type] for f in file_infos])) name_mapping {name: idx for idx, name in enumerate(fault_names)} labels [name_mapping[f[fault_type]] for f in file_infos] return np.array(labels), name_mapping # 示例映射展示 example_mapping {Normal: 0, Ball: 1, Inner: 2, Outer: 3} labels np.array([0, 2, 3, 1, 0]) print(标签映射示例:, example_mapping) print(样本标签数组:, labels)划分数据集时最容易犯的错误是不加区分地随机切分导致同一个原始信号文件切出来的样本同时落在训练集和测试集。正确的做法是先按文件或工况分组用scikit-learn的GroupShuffleSplit按组划分from sklearn.model_selection import GroupShuffleSplit groups sample_groups # 每个样本所属的原始信号文件编号 splitter GroupShuffleSplit(n_splits1, test_size0.3, random_state42) train_idx, test_idx next(splitter.split(samples, labels, groupsgroups))参数里groups是每个样本对应的数据来源编号这样能保证同一个文件切出的样本全部进训练集或全部进测试集。这个操作的目的是验证模型对“没见过的工况”的泛化能力而不是简单测记忆力。4. 搭建可复现的 CNN 故障诊断模型网络实现与训练4.1 网络结构设计要点从前面的结构表落在代码上用 PyTorch 可以写出一个简洁但仍接近工业可用的模型。输入是一维振动序列通道数初始为 1逐步扩展到 16 → 32 → 64。每个卷积块固定搭配批归一化和 Dropout前者解决信号幅值在不同设备之间的分布漂移问题后者约束过拟合。import torch import torch.nn as nn class CNN1dForBearingFault(nn.Module): def __init__(self, num_classes4, input_len1024): super().__init__() # 三组卷积块每组包括卷积、批归一化、激活、池化和 Dropout self.features nn.Sequential( nn.Conv1d(1, 16, kernel_size32, stride1, padding15), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), nn.Dropout(0.2), nn.Conv1d(16, 32, kernel_size3, stride1, padding1), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), nn.Dropout(0.2), nn.Conv1d(32, 64, kernel_size3, stride1, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(kernel_size2, stride2), nn.Dropout(0.2), ) # 输入长度 1024 经过 3 次池化后长度变为 128 self.classifier nn.Sequential( nn.Flatten(), nn.Linear(64 * (input_len // 8), 128), nn.ReLU(), nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, x): x x.unsqueeze(1) # (batch, 1, input_len) f self.features(x) out self.classifier(f) return out model CNN1dForBearingFault(num_classes4, input_len1024) sample_input torch.randn(8, 1024) # 模拟 batch_size8 logits model(sample_input) print(输出维度:, logits.shape) def count_parameters(model): return sum(p.numel() for p in model.parameters() if p.requires_grad) print(f可训练参数量: {count_parameters(model):,})输入(batch, 1024)先加一个维度变成(batch, 1, 1024)对应单通道一维信号。卷积层里的padding数量等于(kernel_size - 1) / 2保证输出长度不变三层池化每次把长度折半最后input_len // 8就是 128。Dropout 在网络末尾用 0.5特征层用 0.2前者力度大因为全连接层容易记住训练集中的噪声。提示kernel_size32的卷积核在参数量上相当于 16 个核 3 的卷积但感受野完全不同。信号场景里第一层大核的价值是直接看整体冲击形态后面各层用小核做组合提取更精细的局部关系。4.2 训练参数的选择学习率、批次大小与优化器训练这类网络并不需要很华丽的优化器配置常用的组合是 Adam 余弦退火学习率。刚开始的学习率可以从 1e-3 试如果 loss 震荡明显就降到 3e-4 或 1e-4。批次大小受显存限制一维卷积对显存消耗不大32 或 64 通常都可行。下表列出参数设置的参考值和调整倾向参数推荐范围偏向说明常见误区学习率1e-4 到 1e-3数据量大或用预训练时取大值固定学习率不调整后期震荡批次大小32 到 128越小噪声越大但泛化常更好一味求大导致内存溢出优化器Adam 或 SGDMomentumAdam 收敛快SGD 后期精度可能更高换优化器却不调学习率训练轮数30 到 100早停 小轮数更可信只看训练集精度决定轮数学习率调整最稳妥的方式是余弦退火或 StepLR。前者在训练后段自动缩小步长能跳过尖锐的局部最小点后者需要人为设置下降节点适合经验较多的情况。optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50)weight_decay是 L2 正则项对故障诊断这类中小规模数据很有效能防止模型过度依赖个别异常数值。T_max50表示在 50 个 epoch 内完成学习率从 1e-3 到接近 0 的退火。4.3 训练主循环与早停策略训练循环的写法决定了后面调试的效率。每次迭代都应该在 CPU 上做数据预处理、GPU 上做张量计算并记录每个 epoch 的训练 loss 和测试 accuracy。早停策略是工业环境下最省事的做法每个 epoch 结束后检查验证集 loss连续多个 epoch 不下降时停止训练并恢复最佳模型。def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss 0.0 for signals, labels in loader: signals signals.to(device).float() labels labels.to(device).long() optimizer.zero_grad() logits model(signals) loss criterion(logits, labels) loss.backward() optimizer.step() total_loss loss.item() * signals.size(0) return total_loss / len(loader.dataset) def evaluate(model, loader, criterion, device): model.eval() total_loss 0.0 correct 0 total 0 with torch.no_grad(): for signals, labels in loader: signals signals.to(device).float() labels labels.to(device).long() logits model(signals) loss criterion(logits, labels) total_loss loss.item() * signals.size(0) preds logits.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) acc correct / total return total_loss / len(loader.dataset), acc训练循环里的几个容易出错的点model.train()与model.eval()必须成对出现否则 BatchNorm 和 Dropout 在推理时行为会不一致loss 要用loss.item()取出 Python 数值避免梯度图的引用泄漏评估阶段必须包在torch.no_grad()里否则中间变量会一路残留到显存耗尽。5. 三类实战技巧验证曲线、样本均衡与输入形态选择5.1 用验证曲线判断过拟合还是欠拟合训练中同时记录训练集和验证集的 loss两侧曲线的相对关系会告诉你问题方向。如果训练 loss 持续下降但验证 loss 先降后升说明模型过拟合优先减少模型宽度、增大 Dropout 或增加数据增强如果两侧 loss 都降不下去则说明模型欠拟合先检查学习率是否过大导致震荡再考虑增加网络深度或增大第一层卷积核覆盖范围。判断欠拟合时用一条简单的测试喂入一个 batch 数据看 loss 能否在几十步内明显下降。如果连训练集都拟合不动问题大概率在网络结构或学习率上而不是数据量不够。5.2 类别不均衡时先看混淆矩阵而不是只看准确率轴承故障数据里正常状态的样本往往远多于故障样本准确率会被多数类主导此时应切换到混淆矩阵和每一类的精确率、召回率。故障诊断里漏报一个内圈故障的代价比多报几次假警高得多所以要在召回率和误报率之间做决策。一个最小可行的做法是在训练时给CrossEntropyLoss传入权重向量权重与类别样本数成反比样本越少的类单样本 loss 放大倍数越高。这个改变只涉及一行代码却能避免模型把所有样本都判成多数类。5.3 时域输入、频谱输入和二维时频图的选择最后一步是决定给网络喂什么形态的数据。直接在原始振动信号上训练一维 CNN 是最省事也最接近真实工况的做法模型必须自己抵消相位变化和噪声。把信号做 FFT 后以幅值谱作为输入可以去除相位信息、降低信号长度让网络专注频率特征但代价是丢失冲击的时序关系。二维 CNN 里更常见的是把短时傅里叶变换结果或小波时频图作为输入此时相当于把一维信号转成“时间 × 频率”的二维图像适合直接套用 3d 卷积神经网络和图像分类里成熟的预训练结构。实际选型时有一个简单判断如果采样一致、工况稳定时域一维 CNN 足够如果转速波动大优先考虑带频率信息的输入如果同时有多个测点可以像 3d 卷积神经网络一样把多通道信号堆叠成二维输入让网络在通道维度上比较不同位置的信号差异。无论如何先用一个小数据集跑通最简单的时域一维结构再逐步替换输入形态每一轮改动只验证一个变量会更快逼近当前任务的上限。本文还有配套的精品资源点击获取