ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

1DCNN轴承故障诊断实战:基于PyTorch直接处理原始振动信号

1DCNN轴承故障诊断实战:基于PyTorch直接处理原始振动信号 简介面向工业设备智能运维工程师与深度学习初学者这份资源提供一套基于1DCNN的轴承故障诊断完整源码覆盖从CWRU数据预处理、模型构建、训练评估到t-SNE可视化与混淆矩阵输出的全流程可用于识别滚动体、内圈、外圈等典型故障。资源包共50个文件约3.64MB以13个Python脚本为核心搭配17张故障与结果分析图、5个工程配置xml、5个txt数据说明以及xls/xlsx格式的混淆矩阵结果表格目录划分清晰便于对照复现。已有425人学习。借助该源码可快速搭建1DCNN诊断基线理解振动信号特征提取与故障分类的工程实现细节并可直接替换数据进行二次开发。1. 一维卷积直接啃原始振动信号1DCNN 轴承故障诊断源码到底能干什么轴承故障诊断在工业现场是个老问题但真正落地时最卡人的不是算法而是特征工程。传统的做法要先算时域指标、频域包络谱再人工挑特征喂给分类器换一台设备、换一个转速特征分布就变模型跟着失效。这套源码走的是另一条路用一维卷积神经网络1DCNN直接处理原始振动信号把“特征提取 分类决策”打包成一个端到端模型。输入是传感器采集的时间序列输出直接是故障类别——正常、内圈故障、外圈故障、滚动体故障。不用手工设计特征也不依赖专家经验去调频谱参数对刚接触深度学习的设备工程师来说这是一条能快速看到结果的技术路线。源码基于 PyTorch 实现在 CWRU 公开轴承数据集上训练和测试包含数据预处理、模型构建、训练、测试、t-SNE 可视化、混淆矩阵导出这一整套完整流程适合想复现 1DCNN 故障诊断项目、或者需要拿一套现成代码做毕业设计和工作参考的读者。下面我把代码拆开讲每一步怎么走、参数怎么调、坑在哪里都给你捋一遍。2. 1DCNN 轴承故障诊断为什么一维卷积比二维图像方案更直接2.1 从振动信号到模型输入一维卷积的天然优势轴承振动信号本质上是时间序列采样率通常在 12kHz 到 48kHz 之间。CWRU 数据集中驱动端振动数据采样率是 12kHz 和 48kHz 两档每个样本点代表传感器在某个瞬间采集到的加速度值。处理这类数据有两种主流思路第一种是把信号做短时傅里叶变换或者小波变换转成二维时频图再用二维 CNN 做图像分类第二种就是这套源码采用的方案——把原始一维信号直接送进一维卷积网络。我个人的观点是如果任务只区分轴承故障类型内圈、外圈、滚动体、正常1DCNN 往往比 2DCNN 更省事。原因在于时频变换这一步会引入额外超参数比如窗函数类型、窗长、重叠率这些参数对最终准确率影响很大调起来非常费时间。而 1DCNN 直接在原始信号上做卷积卷积核在一维序列上滑动本质上是在学习信号局部的时域形态——冲击成分、调制边带、周期性的峰值间隔都是故障轴承振动信号的典型特征。卷积核数量足够多时网络能自己学会捕捉这些模式省掉了中间的人工变换环节。源码里 dataset.py 的核心工作就是把原始信号切分成固定长度的窗口样本。切窗这一步很关键窗口长度直接决定了模型能看到多长时间的信号片段。窗口太短可能截不到一个完整的冲击周期窗口太长样本数量减少训练效率下降。常见做法是取 1024 到 2048 个采样点作为一个窗口对应 12kHz 采样率下大约 85 到 170 毫秒的信号。import numpy as np import torch from torch.utils.data import Dataset class BearingDataset(Dataset): def __init__(self, data_list, labels, window_size1024, stride512): self.data_list data_list # 每条原始振动信号一维 numpy 数组 self.labels labels # 每条信号对应的故障类别标签 self.window_size window_size self.stride stride def __len__(self): return len(self.data_list) def __getitem__(self, idx): signal self.data_list[idx] windows [] # 用滑窗方式切分信号stride 控制相邻窗口的重叠程度 for start in range(0, len(signal) - self.window_size 1, self.stride): segment signal[start:start self.window_size] # 按窗口做 Z-score 归一化保持幅值尺度一致 segment (segment - segment.mean()) / (segment.std() 1e-8) windows.append(segment) windows np.stack(windows) return torch.FloatTensor(windows).unsqueeze(1), self.labels[idx]这里unsqueeze(1)是把形状从(num_windows, window_size)变成(num_windows, 1, window_size)对应 PyTorch 中 Conv1d 需要的输入格式(batch, channels, length)。window_size决定每个样本的长度stride控制切窗步长stride window_size时相邻窗口有重叠等效于数据增强能显著增加训练样本量。CWRU 中每个工况下每条信号大约 12 万到 48 万个采样点按 1024 窗口、512 步长切分一条信号能产生几百个样本数据量完全够用小模型训练。2.2 网络结构拆解CWRUcnn.py 里的卷积层怎么设计源码中 models/CWRUcnn.py 定义了整个 1DCNN 网络结构。轴承故障诊断场景下输入信号长度是固定的1024 或 2048类别数通常是 10 类CWRU 数据集中不同故障位置和故障尺寸的组合网络设计不需要像图像分类那么深三层卷积加两层全连接是常见配置。import torch.nn as nn class CWRUCNN(nn.Module): def __init__(self, num_classes10): super(CWRUCNN, self).__init__() # 第一层大卷积核模拟带通滤波器捕捉冲击特征 self.conv1 nn.Sequential( nn.Conv1d(1, 16, kernel_size64, stride8, padding28), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2) ) # 第二层小卷积核细化局部特征 self.conv2 nn.Sequential( nn.Conv1d(16, 32, kernel_size3, padding1), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2) ) # 第三层继续下采样提取高层抽象特征 self.conv3 nn.Sequential( nn.Conv1d(32, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2) ) self.fc nn.Linear(64 * 32, 64) self.out nn.Linear(64, num_classes) def forward(self, x): x self.conv1(x) x self.conv2(x) x self.conv3(x) x x.view(x.size(0), -1) feat self.fc(x) return self.out(feat)第一层卷积核尺寸设为 64是这套模型的精髓。轴承故障信号中的冲击成分持续时间很短在 12kHz 采样率下一段冲击波形大约跨越 30 到 80 个采样点64 的卷积核长度刚好能覆盖单个冲击的完整波形。用大卷积核等同于让网络学习一个带通滤波特性把有用频带保留下来把噪声抑制掉后面再用 3×1 的小卷积核做特征精修。每个卷积层后都接 BatchNorm 和 ReLUBatchNorm 的作用是稳定训练分布解决振动信号幅值在不同负载下差异大的问题MaxPool 下采样则降低特征维度同时增强模型对微小时间偏移的不变性。全连接层这里有个细节self.fc nn.Linear(64 * 32, 64)其中 32 是第三层卷积输出特征图经过三次池化后剩余的长度。输入 1024经过第一层 stride8 后长度变为 1281024/8两次 MaxPool 后变 32第二层 padding1 保持长度不变池化后变 16第三层再池化后变 8。等等——我上面直接写了64 * 32实际上要按你输入的窗口长度重新算。常见做法是在模型里加一个_to_linear函数用 dummy input 做一次前向传播算出展平后的维度。def _to_linear(self, x): # 用假数据跑一次前向动态计算全连接层输入维度 x self.conv1(x) x self.conv2(x) x self.conv3(x) return x.view(x.size(0), -1).size(1)这样改的好处是当你把window_size从 1024 改成 2048 时不需要手动重新算全连接层的输入维度。这是这套源码里我建议第一个改的地方因为很多人直接在原模型上改窗口长度结果全连接层维度不匹配报size mismatch错误。2.3 损失函数和优化器为什么分类任务默认交叉熵源码中损失函数用的交叉熵优化器用的 Adam。先说结论对于轴承故障这种多分类问题交叉熵是默认选择不太建议换。交叉熵损失函数的形式是-sum(y * log(p))它对模型输出的概率分布和真实标签 one-hot 编码之间的差异做惩罚。配合 Softmax 输出层模型学到的是每个类别的概率分布输出值天然具有可解释性。Adam 优化器有几个关键参数值得注意学习率、betas和eps。源码一般在 config.py 里配置lr初始值建议设为1e-3如果训练过程中 loss 震荡严重降到1e-4。betas保持默认(0.9, 0.999)就行这是 Adam 的一阶矩和二阶矩指数衰减率对 1DCNN 这种小模型不需要调。关键是weight_decay也就是 L2 正则化的系数一般设1e-4到1e-5。轴承数据集不大模型容易过拟合正则化能帮上大忙。关于训练批次大小的选择逻辑和一般图像任务不太一样。振动信号切窗后相邻窗口之间存在高度相关性同一个原始信号切出来的几百个窗口几乎来自同一段振动过程如果 batch 太大且采样不均匀会让模型在某个故障类型上反复拟合导致验证集准确率波动很大。实操中batch_size 128是个比较稳的起点配合shuffleTrue打乱样本顺序避免同源窗口连续出现。3. 把 CWRU 数据集变成能喂给网络的样本data_preprocess.py 与 dataset.py 实战3.1 读取 .mat 文件并提取振动信号CWRU 数据集以 .mat 格式发布每个文件里包含驱动端加速度信号DE 开头、风扇端加速度信号FE 开头和电机转速等字段。源码中 data_preprocess.py 的核心功能就是解析这些 .mat 文件把振动信号抽出来按故障类型和故障尺寸打标签。from scipy import io as scio import numpy as np import os def load_cwru_mat(mat_path): # 读取 .mat 文件 data scio.loadmat(mat_path) # CWRU 数据集中驱动端信号字段名以 DE 开头例如 DE_time key [k for k in data.keys() if k.startswith(DE)][0] signal data[key].flatten() return signal def parse_label(filename): # 根据文件名规则映射标签例如 105.mat 对应内圈故障 0.007 英寸 if 105 in filename or 106 in filename or 107 in filename: return 1 # 内圈故障 elif 118 in filename or 119 in filename or 120 in filename: return 2 # 外圈故障 elif 130 in filename or 131 in filename or 132 in filename: return 3 # 滚动体故障 else: return 0 # 正常CWRU 的文件命名规则是编号 97 到 101 对应正常轴承105 到 107 对应内圈故障118 到 120 对应外圈故障130 到 132 对应滚动体故障编号中间的数字对应故障尺寸比如 105 是 0.007 英寸内圈故障106 是 0.014 英寸107 是 0.021 英寸。实际使用时建议把标签和故障尺寸一起存下来因为很多研究场景不只要区分故障位置还要识别故障严重程度。源码中 labels 可以通过一个字典来维护可读性更好。# 标签映射表编号 - (故障位置, 故障尺寸, 类别ID) label_map { 97: (0, 0, 0), # 正常 105: (1, 0.007, 1), # 内圈 0.007 英寸 106: (1, 0.014, 2), # 内圈 0.014 英寸 107: (1, 0.021, 3), # 内圈 0.021 英寸 118: (2, 0.007, 4), # 外圈 0.007 英寸 # ... 继续补充 }这里有个容易踩的坑CWRU 中正常数据段落是连续的长信号而故障数据通常分多段记录不同段的负载不同。如果不做区分直接全部混在一起划分训练集和测试集就可能出现同一段信号的前半部分进了训练集、后半部分进了测试集的情况导致测试准确率虚高。后面避坑章节会详细展开这个问题数据预处理的正确做法是按“信号段”划分而不是按“窗口样本”划分。3.2 标签平衡与训练集划分轴承故障数据天然存在类别不平衡问题。CWRU 数据集中正常样本通常只占一小部分故障样本根据故障位置不同数量也有差异。如果不做处理模型会倾向于把样本预测为样本量大的类别在少样本类别上表现很差。源码中解决这个问题的方式一般有两种一是对少数类别做过采样复制窗口样本二是用加权采样器在 DataLoader 中给每个类别设置不同的采样概率。from torch.utils.data import WeightedRandomSampler def make_weighted_sampler(labels): # 计算每个类别的样本数 class_counts np.bincount(labels) # 每个样本的权重 总样本数 / (类别数 * 当前类别样本数) weights 1.0 / class_counts[labels] sampler WeightedRandomSampler(weights, num_sampleslen(labels), replacementTrue) return samplerWeightedRandomSampler的工作方式是对每个样本赋予一个权重采样时按照权重概率抽取样本量少的类别权重高被抽到的概率大从而让模型在每个类别上看到的样本量基本均衡。replacementTrue表示有放回抽样少数类样本会被重复抽到这是刻意为之的数据增强手段。训练集和测试集划分比例一般设为 8:2 或 7:3。CWRU 数据集中有 48kHz 和 12kHz 两种采样率的数据还有正常、内圈、外圈、滚动体四种状态、三种故障尺寸、四种负载0hp、1hp、2hp、3hp组合起来类别数量非常多。建议默认按故障位置分 4 大类做 4 分类任务模型更容易收敛如果要做 10 分类区分故障尺寸要确保每个类别的样本量足够否则小类别分类准确率会很低。关于数据归一化我再强调一次切窗之后按窗口做 Z-score 归一化是正确的做法。为什么不能在整个信号上做归一化因为模型在推理阶段拿到的是一段在线采集的新数据你不知道这段数据的全局均值和标准差只能按当前窗口的统计量做归一化。训练时用全局统计、推理时用局部统计这两者不一致会导致训练和推理行为不同严重影响模型性能。3.3 数据增强重叠切窗和随机噪声在很多 1DCNN 轴承故障诊断项目中数据增强是提高泛化能力的关键手段。源码中 data_preprocess.py 的做法是重叠切窗也就是 stride 小于 window_size让相邻窗口有重叠区域。这个操作等效于对信号做了大量平移采样卷积神经网络本来对时间平移有一定不变性但重叠切窗能进一步强化这一点还能显著增加样本量。另一类增强方式是在训练时给信号添加高斯白噪声模拟工业现场的环境干扰。CWRU 数据是在实验室条件下采集的信号干净、背景噪声低但实际工厂里振动信号往往混有大量环境噪声模型在 CWRU 上准确率很高不代表在真实数据上表现好。训练时往输入信号里加少量噪声让模型对噪声不过敏算是一种廉价的域适应。加了噪声后测试准确率可能会下降 0.5% 到 1%但换来的鲁棒性提升在真实场景中更有价值。def add_gaussian_noise(signal, snr_db20): # 按信噪比添加高斯白噪声snr_db 越低噪声越大 signal_power np.mean(signal ** 2) noise_power signal_power / (10 ** (snr_db / 10)) noise np.random.normal(0, np.sqrt(noise_power), sizesignal.shape) return signal noisesnr_db20表示信噪比 20dB噪声功率是信号功率的 1/100属于轻度噪声污染snr_db10时噪声功率是信号功率的 1/10已经比较明显了。训练时可以用随机信噪比在 10 到 30 之间取值等效于给模型做数据增强。4. 训练、测试与评估从 main.py 到 confuse_matrix_rate.xlsx4.1 config.py 参数配置详解源码中 config.py 集中管理所有超参数训练前先确认参数这也是我拿到开源项目后第一个打开的文件。下面这个表格列出了这份源码中核心配置项及其作用训练时按需修改。参数名示例值作用调参注意点data_path./raw_data/CWRU 原始 .mat 文件所在目录路径不要包含中文和空格window_size1024切窗长度不宜小于 512否则截不全冲击波形stride256切窗步长越小样本量越大但训练时间变长batch_size128每个 batch 的样本数显存不足时降到 64 或 32lr1e-3初始学习率训练震荡时降到 1e-4epochs50训练轮数以验证集 loss 不再下降为准num_classes4 或 10分类类别数4 类为故障位置10 类含故障尺寸devicecuda:0训练设备CPU 训练时改为cpu速度慢几十倍seed42随机数种子固定后保证实验结果可复现4.2 训练循环怎么写避免踩坑main.py 的主流程是标准的 PyTorch 训练模板构造数据集和数据加载器实例化模型定义损失函数和优化器循环训练。但有几个细节决定了训练成败。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from models.CWRUcnn import CWRUCNN from dataset import BearingDataset from utils.visualize import plot_loss_curve def train(config): # 固定随机种子保证结果可复现 torch.manual_seed(config.seed) if torch.cuda.is_available(): torch.cuda.manual_seed(config.seed) # 加载数据伪代码实际从 data_preprocess 获取 train_data, train_labels load_train_samples(config.data_path) train_dataset BearingDataset(train_data, train_labels, window_sizeconfig.window_size, strideconfig.stride) train_loader DataLoader(train_dataset, batch_sizeconfig.batch_size, shuffleTrue, num_workers4, drop_lastTrue) model CWRUCNN(num_classesconfig.num_classes).to(config.device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lrconfig.lr, weight_decay1e-4) # 余弦退火学习率调度比固定学习率收敛更稳 scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxconfig.epochs) for epoch in range(config.epochs): model.train() running_loss 0.0 for inputs, labels in train_loader: inputs, labels inputs.to(config.device), labels.to(config.device) # 输入形状 (batch, num_windows, 1, window_size) # 每个原始信号切成多个窗口取均值池化后过模型 b, w, c, l inputs.shape inputs inputs.view(b * w, c, l) labels labels.repeat_interleave(w) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() avg_loss running_loss / len(train_loader) print(fEpoch [{epoch1}/{config.epochs}], Loss: {avg_loss:.4f}) # 每个 epoch 保存一次模型用于后续测试 torch.save(model.state_dict(), fcheckpoints/epoch_{epoch1}.pth)这里有一个非常关键的写法inputs.view(b * w, c, l)把每个 batch 中的多窗口样本展平让每个窗口独立输入模型计算。对应的标签也要用repeat_interleave(w)复制让每个窗口都匹配自己的标签。如果不做这一步模型接收的是(batch, num_windows, 1, window_size)的四维输入而 Conv1d 只接受三维输入(batch, channels, length)会直接报错。drop_lastTrue的作用是丢弃最后一个不足 batch_size 的 batch。因为切窗后样本数不一定能被 batch_size 整除最后一个 batch 如果只有 1 到 2 个样本BatchNorm 层计算出的均值和方差非常不稳定极端情况下会导致 loss 抖动。drop 掉损失的那几个样本换来稳定的训练过程值当。保存模型时建议同时保存训练配置。很多人训练完模型、关掉终端后就忘了当时的 window_size、stride 是什么测试阶段加载模型时输入维度对不上非常折腾。我在工程里习惯直接把 config 序列化为 JSON 存到 checkpoint 目录下每次加载模型时先读 JSON就不会出现这种“模型不知道当初怎么训练”的玄学问题。4.3 test.py 与混淆矩阵导出准确率之外还要看哪些指标测试流程和训练类似区别在于不计算梯度、不更新参数。源码中 test.py 加载训练好的模型跑一遍测试集输出准确率然后把预测结果和真实标签整理成混淆矩阵写到 Excel 文件里。这里我重点讲混淆矩阵怎么看——准确率在类别平衡时才有意义轴承故障类别不平衡时只看准确率会被误导。import pandas as pd from sklearn.metrics import confusion_matrix, classification_report def evaluate(model, test_loader, config): model.eval() all_preds [] all_labels [] with torch.no_grad(): for inputs, labels in test_loader: inputs, labels inputs.to(config.device), labels.to(config.device) b, w, c, l inputs.shape inputs inputs.view(b * w, c, l) labels labels.repeat_interleave(w) outputs model(inputs) # 每个窗口的预测概率取平均再做决策 preds torch.softmax(outputs, dim1).view(b, w, -1).mean(dim1) _, pred_classes torch.max(preds, dim1) # 一个原始信号对应一个标签取第一个窗口的标签即可 all_preds.extend(pred_classes.cpu().numpy()) all_labels.extend(labels[::w].cpu().numpy()) cm confusion_matrix(all_labels, all_preds) report classification_report(all_labels, all_preds, digits4) print(report) # 导出混淆矩阵到 Excel方便后续做报告 df_cm pd.DataFrame(cm) df_cm.to_excel(results/confuse_matrix_rate.xlsx)这里用到了softmax概率平均一个原始信号被切成多个窗口每个窗口单独过模型得到各自类别的概率向量把所有窗口的概率向量取平均再取最大值对应的类别作为这个信号的最终预测。这比单纯“按窗口投票”更平滑窗口置信度高时会自然主导决策。实际效果上这种做法能有效避免单个窗口因为噪声干扰做出错误判断。classification_report会输出每个类别的 precision、recall、F1-score 和 support。看测试结果时不应只看 accuracy重点看每个故障类别的 F1-score。如果内圈故障的 F1-score 明显低于其他类别说明模型对这类故障的区分度不够需要回去检查数据增强策略或者模型结构。常见的处理办法是增加内圈故障信号的采样比例或者检查是否在切窗时把内圈故障的冲击成分截断了。5. 避坑与排查训练 1DCNN 轴承模型最容易翻车的五个环节5.1 测试集准确率 99.8%换个工况直接崩——数据泄漏现象在 CWRU 数据集上切窗后随机划分训练集和测试集测试准确率高达 99% 以上但用另一台设备或不同负载的数据做测试准确率骤降到 60% 甚至更低。原因这是典型的数据泄漏也是最常见的坑。切窗时同一个原始信号被切成大量窗口随机划分训练集和测试集时来自同一段信号的窗口可能同时出现在训练集和测试集中。相邻窗口之间高度相似模型等于直接“背答案”测试集评估结果完全失真。解决按信号段划分而不是按窗口划分。训练集和测试集分别用不同的 .mat 文件或者至少保证同一个 .mat 文件切出来的窗口全部进训练集或全部进测试集。更进一步地可以用不同负载的数据做跨工况验证——用 0hp 负载训练用 1hp、2hp、3hp 负载的数据做测试。这才是检验模型泛化能力的正确姿势。# 按文件级别划分避免数据泄漏 mat_files os.listdir(config.data_path) train_files [f for f in mat_files if f.split(.)[0] in train_ids] test_files [f for f in mat_files if f.split(.)[0] not in train_ids]5.2 训练时 loss 纹丝不动——学习率和网络初始化问题现象第一个 epoch 的 loss 约等于 log(num_classes)比如 4 分类时 loss ≈ 1.386然后好几个 epoch 都不变化。再过几个 epoch 开始下降但下降速度极慢。原因loss 初始值接近ln(类别数)说明模型在做均匀瞎猜——每个类别输出的概率都差不多这是正常的起点。如果 loss 长时间不降大概率是学习率太小梯度更新幅度不足以让损失函数快速下降。另一个潜在原因是第一层大卷积核初始化不当过大或过小的权重都会让梯度消失或梯度爆炸。解决先确认 loss 初始值是否接近ln(num_classes)如果不是说明数据管线有 bug。如果初始值正常但长时间不降把学习率从 1e-3 调到 3e-3 试试。如果调到 1e-2 后 loss 直接变成 NaN说明学习率太大了在 1e-3 附近做微调即可。还有一个技巧给第一层卷积做 Xavier 初始化让权重方差自适应输入维度能明显加速收敛。def init_weights(m): if isinstance(m, nn.Conv1d): nn.init.kaiming_normal_(m.weight, a0.01, modefan_out) nn.init.constant_(m.bias, 0) model.apply(init_weights)5.3 BatchNorm 在训练和推理阶段行为不一致现象训练时验证集准确率很高但部署到实际场景后预测结果明显变差且不是数据分布差异导致的。原因PyTorch 中model.eval()会切换 BatchNorm 的行为——训练时用当前 batch 的均值和方差做归一化推理时用训练阶段累积的全局均值和方差。如果推理阶段漏写了model.eval()BatchNorm 继续用当前 batch 的统计量在 batch 很小或者输入长度不固定时预测结果会不稳定。解决测试代码里必须在模型加载后显式加一行model.eval()并且用torch.no_grad()包住推理过程。同样地如果训练代码里model.train()漏了模型就会一直处于推理模式BatchNorm 不更新训练效果也会打折扣。建议在训练和测试的主函数里都显式声明这两行别指望自己不会忘。5.4 不同负载数据混在一起10 分类模型小类目准确率奇低现象做 10 分类区分故障位置和故障尺寸时个别故障尺寸的样本量很少测试准确率可能很高但分类报告显示部分类别 recall 不到 50%。原因CWRU 数据集中不同故障尺寸对应的样本量不均衡0.007 英寸故障的数据文件数量和 0.021 英寸的不一样切窗后样本数量差异被放大。类别不均衡时交叉熵损失函数被样本量大的类别主导模型对小样本类别学习不足。解决用上一章提到的WeightedRandomSampler给少数类加权或者对小类别做过采样复制。还可以把 10 分类拆成两级模型第一级判断故障位置内圈、外圈、滚动体第二级对每个位置单独做尺寸分类这样每一级的类别数量少、样本分布更均衡效果往往比直接做 10 分类更好。5.5 t-SNE 图完美分离但混淆矩阵一堆错——可视化结果的误导性现象用 t-SNE 对模型提取的特征降维后画图不同类别的点分得清清楚楚看起来模型学得很好。但一看混淆矩阵某些类别之间错分严重。原因t-SNE 的本质是保持高维空间中点的邻居关系投影到二维平面。它只反映局部结构不忠实反映全局分布。如果某个类别的样本在原始特征空间中和另一类确实很接近t-SNE 为了平衡局部和全局关系可能在二维图上把两类硬生生分开造成“特征很分明”的假象。解决t-SNE 只作为辅助诊断工具不作为模型好坏的评判标准。以混淆矩阵、F1-score 为准。如果 t-SNE 中两步分明显但混淆矩阵显示错分检查是否在画 t-SNE 时用了训练集而不是测试集的特征或者perplexity设置不恰当。perplexity控制 t-SNE 考虑邻居的多少一般设置为 5 到 50 之间样本量小时用较小的值样本量大时适当增大。6. 进阶验证用 t-SNE 和混淆矩阵双重确认模型学到的特征6.1 提取模型倒数第二层特征做降维可视化训练完模型只得到一个准确率数字很难判断模型到底学到了什么。t-SNE.py 做的事情是去掉最后的分类输出层用倒数第二层全连接的输出作为特征向量对测试集所有样本提取特征再用 t-SNE 降到二维平面画散点图。如果不同故障类别的点云清晰分离说明模型确实学到了具有区分度的特征。from sklearn.manifold import TSNE import matplotlib.pyplot as plt def extract_features(model, test_loader, config): model.eval() features [] labels [] with torch.no_grad(): for inputs, label in test_loader: inputs inputs.to(config.device) b, w, c, l inputs.shape inputs inputs.view(b * w, c, l) # 把 forward 改为返回全连接层输出而不是分类输出 feat model.feature(inputs) feat feat.view(b, w, -1).mean(dim1) features.append(feat.cpu().numpy()) labels.append(label.numpy()) features np.concatenate(features, axis0) labels np.concatenate(labels, axis0) return features, labels def plot_tsne(features, labels, save_pathimages/t-SNE.png): # perplexity 一般设为样本量的 5% 到 10%太小会失真太大计算量暴涨 tsne TSNE(n_components2, perplexity30, n_iter1000, random_state42) features_2d tsne.fit_transform(features) plt.figure(figsize(10, 8)) scatter plt.scatter(features_2d[:, 0], features_2d[:, 1], clabels, cmaptab10, s10, alpha0.7) plt.colorbar(scatter) plt.savefig(save_path, dpi150)model.feature并不是 PyTorch 内置方法需要在模型定义中显式写一个方法或修改 forward让它返回全连接层输出。源码中的做法通常是把 forward 拆成两段compute_feature返回提取的特征forward内部调用compute_feature再接分类层。测试时调用compute_feature拿特征向量可视化时用特征向量做降维评估分类效果时用完整的 forward 输出。t-SNE 参数中n_iter1000是最大迭代次数通常 750 到 1000 次就已经收敛。如果样本量大可以适当调低到 500 让训练快一些但迭代次数太少会导致嵌入不到位、点云挤成一团。perplexity30是默认值对几千个样本的规模比较稳如果点图出现“线状缠绕”或者“整体挤成球”降低 perplexity 到 10 到 20 重新跑。还有一个值得提的点是angular_rbfTrue和n_jobs-1它们能加速 t-SNE 的计算但对结果没有影响。6.2 混淆矩阵的热力图改进与阈值调整confuse_matrix_rate.xlsx 里存的是数字混淆矩阵肉眼扫数字不方便。我一般会在导出 Excel 之前先画一张热力图把矩阵可视化之后保存为图片再另存一份数字表格用来做报告。这样对比时一目了然哪个类别之间互相错分色块一深一浅立刻就暴露了。import seaborn as sns import matplotlib.pyplot as plt def plot_confusion_matrix(cm, class_names, save_pathimages/confusion_matrix.png): plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(Predicted Label) plt.ylabel(True Label) plt.tight_layout() plt.savefig(save_path, dpi150)可视化之后还有一个值得做的操作检查预测概率的置信度分布。用torch.softmax拿到每个样本的最大概率值画直方图。如果大多数样本的置信度都在 0.95 以上说明模型对自己的判断很有把握如果大量样本集中在 0.6 到 0.8 之间说明模型虽然分类正确但内心不够笃定部署到严苛场景时需要谨慎。我见过一些项目模型准确率 98%但把置信度直方图拉出来看一大批样本的概率在 0.7 附近这类样本在噪声更强的环境下很容易翻车。后来我把置信度低于 0.85 的样本单独筛出来做个“不确定集”专门分析这些样本对应的时域波形发现不少是信号切到了冲击段的边缘信息不足导致误判。从那以后我每次训练完都强制自己走一遍完整验证流程先看分类报告里的 F1-score再跑 t-SNE 看特征分布最后画混淆矩阵热力图和置信度直方图。走完这套流程一份 1DCNN 轴承故障诊断实验才算真正交付而不是仅仅交一个准确率数字。这套源码从数据预处理到模型训练再到可视化验证都齐全你拿到之后建议先在我上面提到的几个关键环节过一遍——数据划分按信号段、模型输出和全连接维度先对齐、训练时记得 drop_last、测试时记得 model.eval()然后按自己的需求改参数。希望这些经验帮到你少走一点我当初走过的弯路。本文还有配套的精品资源点击获取
返回列表