简介这份基于深度学习1DCNN轴承故障诊断的源码包面向工业设备健康管理与故障预测场景特别适用于研究轴承故障识别、熟悉卷积神经网络在时间序列信号处理中应用的研究人员与工程技术人员。资源共50个文件压缩包约3.64MB包含13个Python源码文件覆盖数据预处理、模型构建、训练测试、可视化等完整流程17张PNG图像展示了正常状态、滚动体故障、内圈故障、外圈故障等模式下信号波形及特征分布配置文件、标注文本和Excel表格则用于环境配置、数据说明和混淆矩阵等结果统计便于实验分析。核心代码基于CWRU轴承数据集实现了1DCNN故障分类并提供t-SNE降维可视化、混淆矩阵评估等方法同时按模型定义、基础模块、工具函数、配置参数等划分目录结构清晰易读方便二次开发。目前已有424人学习下载适合希望快速掌握1DCNN轴承故障诊断完整工程实现并参考其代码组织与实验记录方式的研究者。1. 为什么轴承故障诊断选 1DCNN从信号形态到模型结构振动信号本质上是一维时间序列无论是加速度传感器采集的原始波形还是经包络解调后的特征波形都不存在天然的二维空间结构。传统思路是先做短时傅里叶变换或小波变换把一维振动信号转成时频图再交给二维 CNN 分类这套流程在公开数据集上效果不错但代价是计算量上去了而且时频变换的分辨率窗口一旦选错反而把原始信号里的冲击特征抹平。1DCNN 直接以原始振动序列作为输入卷积核沿时间轴滑动在训练过程中自己学习出相当于一组带通滤波器的特征提取器不需要人工指定中心频率和带宽。做轴承故障诊断选 1DCNN核心理由是信号形态匹配、模型参数少、推理速度快适合从实验室迁移到边缘设备。本文直接从机理和参数出发给出可复现的 PyTorch 实现、训练评估流程以及真正部署时容易踩的泛化问题。适合正在做设备状态监测、故障诊断课题或者想把深度学习模型落到实际产线振动监测系统里的工程师。2. 1DCNN 在故障诊断中的工作机制与 4 个必调参数2.1 一维卷积如何捕捉轴承故障振动特征轴承故障会产生周期性冲击外圈故障、内圈故障、滚动体故障的特征频率分别对应 BPFO、BPFI、BSF这些频率由轴承几何尺寸和转频决定。故障冲击在时域上表现为幅值调制的高频衰减振荡频域上则在特征频率及其倍频处出现边带。传统诊断依赖包络谱找到这些峰值而 1DCNN 做的事情本质上是端到端地学习“从原始波形到故障类别”的映射中间不需要显式计算特征频率。一维卷积的计算过程是一个固定长度的卷积核在序列上逐步滑动每滑动一次做一次点积。核内权重就是网络要学习的参数。多个卷积核并行运算就得到多个不同的特征图。浅层卷积核倾向学出类似高通或带通的响应深层卷积核则把多个频带特征组合成某种故障模式。这比人工筛选特征的优势在于特征是从数据里长出来的对陌生工况的适应能力通常更强。判断一个 1DCNN 结构是否合理先看有效感受野是否覆盖一个完整的故障冲击周期。假如输入的窗口长度是 1024 个采样点采样率是 12 kHz那么这个窗口对应约 85 ms 的振动信号足够包含多个故障冲击周期。三层 stride2 的卷积核大小分别为 7、5、3有效感受野扩展到 23 个点配合下采样操作实际能感知的上下文远大于这个数值。窗口不够长模型就只能看到冲击的局部容易把不同故障的冲击混淆。2.2 直接影响诊断准确率的 4 个参数2.2.1 核大小决定有效感受野与频带选择核大小决定了每个卷积输出点看到的原始信号长度。核太小感受野不足需要堆更多层才能覆盖一个冲击周期核太大参数增多且容易过拟合。常见做法是首层用较大的核比如 7 或 9用于捕捉低速轴承的低频冲击后续层逐步缩小到 3 或 5用于细化局部模式。def effective_receptive_field(kernel_sizes, strides): rf 1 stride_product 1 for k, s in zip(kernel_sizes, strides): rf (k - 1) * stride_product stride_product * s return rf print(effective_receptive_field([7, 5, 3], [2, 2, 2])) # 输出 23这段代码计算多层卷积堆叠后的有效感受野逻辑是逐层累加当前核大小带来的增量增量要乘以之前所有步长的乘积。第一层核贡献 6第二层贡献 4×2第三层贡献 2×4合计 23。也就是说第三层输出的每个点对应输入序列上 23 个连续点。如果你发现模型对短促冲击不敏感优先检查这个数值是否足够覆盖一个冲击周期。2.2.2 步长与下采样控制时间分辨率stride2 的卷积直接把序列长度减半作用是增大后续层的感受野并减少计算量。但步长过大会损失时间分辨率两个间隔很近的冲击会被合并成一个峰值对滚动体故障这种冲击间隔不规则的情况尤其不利。第一层尽量用 stride1保留完整的冲击细节第二层开始再下采样。也可以用 MaxPooling 代替 stride2 卷积两者效果接近Pooling 不引入额外参数。2.2.3 膨胀卷积不加深网络也能扩大感受野当窗口长度是 2048 甚至 4096 时堆更多 stride2 卷积会让特征图变得太薄。膨胀卷积通过在小核内部插入空洞让 3×1 的核实际覆盖 7 个或 15 个点。我在处理低频大直径轴承时第一层就会用 dilation2 的 5×1 卷积等效核长度是 9不增加参数但把感受野翻倍。膨胀率不宜设得过大否则卷积核覆盖范围太稀疏会漏掉连续冲击。2.2.4 残差连接网络深了之后不掉点不要以为 1DCNN 结构简单就不需要残差。当特征图通道数超过 64、层数超过 6 层时梯度消失会明确反映在训练损失不下降上。残差连接把输入直接加到卷积输出上保证了梯度至少有一条“高速公路”传回浅层。实际工程里我用过的最稳定的结构是前两层卷积 池化之后接两组残差块每个残差块里是两个 3×1 卷积最后接全局平均池化和全连接层。参数调整的总体原则是先固定窗口长度和网络层数用一组默认参数跑通再逐个调核大小和膨胀率。以下是一组在 12 kHz 采样、1024 点窗口下常用的起点参数参数推荐值作用调大/调小的影响第一层核大小7 或 9确定基础感受野过大易过拟合过小需加深网络第一层步长1保留冲击细节stride2 会损失时间分辨率膨胀率1 或 2扩展感受野过大导致特征稀疏残差块数量2 或 3加深网络提取组合特征过多增加训练难度和参数Dropout0.30.5防止全连接层过拟合过大导致欠拟合过小无效果3. 用 PyTorch 把 1DCNN 故障诊断模型从零跑通3.1 一个可运行的最小模型定义下面这个模型直接基于原始振动序列做分类不包含任何手工特征。输入形状是 (batch, 1, 1024)输出是各个故障类别的 logits。代码里的每个卷积层都配了 BatchNorm这是 1DCNN 里非常关键的一步——振动信号幅值在不同传感器、不同转速下差异很大BatchNorm 能把中间特征拉回到稳定分布显著加速收敛。import torch import torch.nn as nn class CNN1D(nn.Module): def __init__(self, in_channels1, num_classes10): super().__init__() self.features nn.Sequential( nn.Conv1d(in_channels, 32, kernel_size7, stride1, padding3), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.Conv1d(32, 32, kernel_size3, stride2, padding1), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.Conv1d(32, 64, kernel_size3, stride2, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.Conv1d(64, 64, kernel_size3, stride2, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool1d(1), ) self.classifier nn.Sequential( nn.Dropout(0.5), nn.Linear(64, num_classes), ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)参数说明第一层卷积的 padding3 保证 stride1 时输出长度仍为 1024不会因为边界截断丢失开头和结尾的冲击。后续三层 stride2 把长度从 1024 降到 128再经 AdaptiveAvgPool1d(1) 压成每个通道一个数值送入全连接层。AdaptiveAvgPool 的好处是网络不限制输入长度——如果你后续想用 2048 点窗口这个模型结构不用改。全连接层前的 Dropout 放在特征拼接之后配合后面训练代码里的早停策略使用。3.2 训练循环与验证逻辑训练循环里最容易犯的错误是验证指标只看准确率。轴承故障数据通常类别均衡准确率能说明问题但也必须记录每一类的召回率否则内圈故障识别不好时准确率依然可能很高。def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total 0, 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) correct (logits.argmax(1) y).sum().item() total y.size(0) return total_loss / total, correct / total def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0, 0, 0 with torch.no_grad(): for x, y in loader: x, y x.to(device), y.to(device) logits model(x) loss criterion(logits, y) total_loss loss.item() * x.size(0) correct (logits.argmax(1) y).sum().item() total y.size(0) return total_loss / total, correct / total这段代码把训练和验证拆成两个函数。训练模式下的 model.train() 会启用 Dropout 和 BatchNorm 的批统计量验证时必须切到 model.eval()否则 BatchNorm 会用当前 batch 的均值和方差而不是运行中的全局统计量导致验证指标上下剧烈跳动。如果你发现验证准确率忽高忽低先检查是不是忘了切模式。优化器我一般用 AdamW初始学习率 1e-3配合余弦退火调度器训练 30 个 epoch 左右验证损失连续 5 个 epoch 不降就停。3.3 数据组织滑动窗口切分与样本独立性连续采集的振动数据不能直接拿来训练要做滑窗切分。窗口长度设为 1024步长可以设 512也就是相邻窗口有 50% 重叠。重叠率高了数据量变大但相邻样本高度相似模型会倾向于记住样本间的重复模式而不是真正的故障特征。两类做法都有人用不重叠窗口更适合原始数据量充足的场景重叠采样适合数据量有限的场景。无论哪种切分必须在训练之前完成并且要记录每个窗口在原始采集时序中的位置。最隐蔽的问题是数据泄漏如果在同一个连续信号段上同时切出训练集和测试集样本这两个样本大概率高度相似测试集就会虚高模型真正换一台设备或换一个工况时立刻打回原形。合理的做法是把不同负载、不同转速下采集的信号文件先分组在文件级别划分训练集和测试集而不是在样本级别划分。3.4 跑通一次小规模训练的完整命令python train.py \ --data_dir ./data/CWRU \ --window_size 1024 \ --stride 512 \ --batch_size 64 \ --epochs 50 \ --lr 1e-3 \ --seed 42这里的 seed 参数必须固定。深度学习模型训练包含大量随机性初始化权重、打乱数据顺序都依赖随机数生成器。固定 seed 后同样的数据和参数设置可以复现几乎一致的结果这不仅是学术实验的基本要求也是排查问题时判断改动是否有效的必要条件。train.py 内部会读取 --seed 设置 torch.manual_seed、numpy.random.seed 和 random.seed。提示跑实验之前先确认训练损失能不能在一个 epoch 内明显下降。如果训练损失纹丝不动问题基本出在前向传播或数据标签上先不要盲目调参。4. 实验设计数据划分、混淆矩阵与诊断结果解读4.1 最容易翻车的数据划分方式先看两种常见做法。第一种错误做法是把所有样本混在一起用 train_test_split 按比例随机划分。对普通图像分类这种方式问题不大但对同一段连续振动信号切出来的窗口随机划分会让同一时间片段的相邻窗口分别进入训练集和测试集相当于开卷考试。第二种错误做法是重复采样——为了让少数类数量充足直接把原始信号片段复制粘贴几次模型学到的只是复制痕迹。我在实验里采用的标准划分方式是数据集负载样本数比例说明训练集0 HP60%只用一种负载的数据训练验证集1 HP20%用于早停和超参数选择测试集2 HP20%模拟现场负载变化这样做是最小成本地模拟“训练时没见过的工况”对评估模型泛化能力有参考价值。from sklearn.model_selection import GroupShuffleSplit from sklearn.metrics import classification_report, confusion_matrix split GroupShuffleSplit(n_splits1, test_size0.4, random_state42) train_idx, temp_idx next(split.split(X, y, groupssource_file_ids)) val_idx, test_idx next( GroupShuffleSplit(n_splits1, test_size0.5, random_state42) .split(X[temp_idx], y[temp_idx], groupssource_file_ids[temp_idx]) )这段代码用 GroupShuffleSplit 按 source_file_ids 分组切分保证来自同一个原始信号文件的所有窗口不会同时出现在训练集和测试集中。逻辑上它先分出 60% 作为训练再把剩下的 40% 按文件分组对半切成验证和测试。group 参数的每个值代表一个原始采集文件这是防止数据泄漏的关键。4.2 混淆矩阵与每一类故障的召回率训练完成后用测试集计算混淆矩阵和分类报告代码如下model.eval() all_preds, all_labels [], [] with torch.no_grad(): for x, y in test_loader: logits model(x.to(device)) all_preds.extend(logits.argmax(1).cpu().tolist()) all_labels.extend(y.tolist()) cm confusion_matrix(all_labels, all_preds) report classification_report( all_labels, all_preds, target_names[normal, outer, inner, ball], digits3, ) print(report)分类报告会按类别输出 precision、recall、f1-score。对轴承故障诊断召回率比精确率更重要——漏报一个内圈故障的代价远高于把一个正常样本误报成故障。观察报告时可以按下面的表格形态逐类检查类别精确率召回率F1可能原因normal0.990.970.98正常样本振动幅值低特征明显outer0.970.960.96外圈固定冲击间隔稳定inner0.970.980.97内圈随轴旋转调制复杂但仍可分ball0.950.930.94滚动体打滑导致冲击间隔不稳定表格中的数据格式是示例具体数值会随数据集和随机种子浮动。重点是看每一行的召回率差距如果 ball 类召回率明显低于其他类通常不是模型结构问题而是滚动体故障的冲击信号本身存在随机相位同类样本内部差异大。可以先增加该类的窗口重叠率或者把输入窗口加倍到 2048 点让模型看到多个冲击周期。4.3 训练曲线与过拟合信号训练曲线记录训练损失、验证损失、验证准确率三个量建议每个 epoch 写入同一个 CSV 文件后续绘图和分析。判断逻辑很直接训练损失持续下降但验证损失开始回升这是过拟合的明确信号应该加大 Dropout 或提前停止。训练损失和验证损失同步不再下降说明模型容量已经到上限需要加宽通道数或加深网络层数。训练损失下降缓慢验证损失震荡优先检查学习率是否过大尤其是 AdamW 配合余弦退火时初始学习率超过 5e-3 就会出现这种情况。看到验证准确率在某个 epoch 后突然跌到接近随机水平不要立刻怀疑模型结构优先检查验证集里是否混入了训练集样本。这种情况在按文件分组切分之前经常出现表现为轻微过拟合后的准确率波动突然变成断崖式下跌。5. 进阶验证跨负载泛化、可视化诊断证据与模型导出5.1 用对抗验证法量化“域偏移”训练负载与测试负载不同时准确率下降本质上是数据分布发生了偏移。对抗验证法可以量化偏移程度把训练集数据打上标签 0测试集数据打上标签 1训练一个二分类器看它能否区分两组数据。如果分类准确率接近 90% 以上说明两组数据分布差异非常明显正常训练的模型大概率迁移不好。X_all torch.cat([X_train, X_test]) y_domain torch.cat([torch.zeros(len(X_train)), torch.ones(len(X_test))]) # 训练一个两层 1DCNN 二分类器观察验证准确率当对抗验证准确率偏高时需要考虑对信号做幅值归一化或频域对齐。一个简单做法是对每个窗口做 z-score 标准化消除传感器灵敏度和增益差异。5.2 类别激活图确认模型在看哪里模型判对容易但判断依据是否合理需要可视化验证。对于用全局平均池化的 1DCNN可以把最后一个卷积层的特征图按类别权重加权得到每个时间位置的贡献值。def cam_1d(feature_maps, fc_weight, class_idx): weights fc_weight[class_idx] cam torch.matmul(weights, feature_maps) # (batch, time) return camfeature_maps 是最后一个卷积层输出形状为 (batch, channels, time)fc_weight 是全连接层权重矩阵。计算出的 cam 值越大表示该时间段对分类结果的贡献越高。正常的诊断模型会高亮故障冲击出现的时刻附近如果高亮区域集中在信号尾部或其他随机位置说明模型学到的是设备噪声而不是故障特征。5.3 从 PyTorch 导出 ONNX 用于现场推理现场部署通常不用训练框架导出 ONNX 是通用做法。导出时固定输入形状方便后续用 ONNX Runtime 或 TensorRT 加速。dummy torch.randn(1, 1, 1024) torch.onnx.export( model, dummy, cnn1d_bearing.onnx, input_names[signal], output_names[logits], dynamic_axes{signal: {0: batch_size}}, opset_version17, )dynamic_axes 里只把 batch 维设为动态时间维保持固定这样模型推理时不需要处理变长序列的分组问题。导出后用 onnxruntime 加载输入数据直接传一个 numpy 数组就能得到结果整个推理链路不再依赖 PyTorch 环境现场设备只需要安装 ONNX Runtime模型体积也小得多。部署前记得确认模型输入做了和训练时完全一致的标准化处理这是导出后最容易忽略导致推理结果异常的原因。本文还有配套的精品资源点击获取