ARTICLE DETAIL

资讯详情

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

基于PyTorch的一维CNN滚动轴承故障诊断实战指南

基于PyTorch的一维CNN滚动轴承故障诊断实战指南 简介工业设备的可靠性维护是智能制造的核心环节而滚动轴承作为旋转机械中最易损的部件其故障诊断长期依赖专家经验与人工特征提取。随着深度学习技术的普及振动信号分析正从传统阈值判断转向端到端的智能分类——将时域波形直接输入神经网络即可自动识别内圈、外圈、滚动体等典型故障模式。一维卷积神经网络1D-CNN凭借对局部冲击特征的强捕捉能力和轻量级计算优势成为该类时序诊断任务的高效主干。从公开的CWRU数据集到实验室自采数据通过滑窗切分、标签编码与归一化构建标准样本结合残差网络与合理的训练策略能够在PyTorch框架下稳定达到90%以上的诊断准确率。本文基于完整工程实践给出数据预处理、模型搭建、参数调优以及t-SNE特征可视化验证的全流程代码助力工业维护工程师快速落地智能故障诊断系统。1. 滚动轴承故障诊断为什么深度学习方案值得自己动手跑一遍滚动轴承故障诊断是工业设备维护里最磨人的一件事。现场老师傅靠听音辨位、测振摸温经验值拉满才能判个大概而设备一旦带病运转轻则产线停机重则整机报废。深度学习算法这几年把这件事变成了一个端到端的分类问题采集振动信号喂给神经网络模型自己从时域波形里学故障特征输出内圈故障、外圈故障、滚动体故障或正常状态。这个方案不需要专家画特征、不需要人工调阈值代码量远比想象中少落地的收益却非常直接。这套基于Python的滚动轴承智能故障诊断系统开发核心就三块一份能直接用的完整数据集、一个不太复杂的一维CNN模型、一条从原始振动信号到诊断结论的处理流水线。它对两类人最有用——一类是机械背景、想往智能维护转型的工程师一类是算法出身、想找工业场景落地的开发者。前者缺代码后者缺工业常识这篇文章把两端补上。2. 先把数据整明白振动信号如何变成深度学习样本2.1 公开数据集怎么选CWRU还是实验室自采做滚动轴承故障诊断第一步不是写模型而是确认数据从哪来。工业现场的数据往往涉及设备保密和生产安全拿不到也传不出来所以绝大多数研究者起步用的都是公开数据集。其中凯斯西储大学CWRU轴承数据集是使用最广的基准数据采样率覆盖12 kHz和48 kHz两个档位故障类型包含内圈、外圈、滚动体三类外加正常样本每种故障下还分了不同损伤直径和不同负载工况。这个数据集用来验证算法可行性足够了也是对比论文结果的公共标尺。如果手里有实验室台架自采数据会更贴近真实工况但这套系统的开发流程不受影响。自采要注意传感器安装位置固定电涡流传感器测的是轴位移加速度计测的是壳振动两者频谱特征有差异选加速度计更通用。2.2 滑窗切分把长时序变成固定长度样本深度学习模型吃的是固定长度的输入而振动信号是连续采集的几十秒甚至几分钟的长序列。最常见的做法是用滑窗切分。窗口长度要覆盖至少几个旋转周期这样单个样本里才包含足够多的冲击特征。以1797 rpm约30 Hz为例一个旋转周期约0.033秒取128~256个采样点覆盖一个周期但实际建议取1024或2048个点让模型看到多周期的重复冲击模式。步长一般取窗口的一半保证相邻样本有50%重叠这样数据量翻倍且训练稳定性更好。下面这段代码把原始的CSV信号文件切成训练用的样本import numpy as np import pandas as pd def sliding_window_segment(data, window_size1024, step_size512): 将一维振动信号切分为固定长度的样本 data: 原始振动信号, 1D numpy数组 window_size: 窗口长度, 覆盖2-4个旋转周期 step_size: 滑动步长, 通常取窗口的一半 samples [] total_len len(data) for start in range(0, total_len - window_size 1, step_size): segment data[start:start window_size] samples.append(segment) return np.array(samples) # 读取CWRU或自采的CSV文件, 假设第一列是振动信号 raw pd.read_csv(./data/normal.csv).values[:, 0] samples sliding_window_segment(raw, window_size1024, step_size512) print(f切分得到样本数量: {len(samples)}, 每个样本长度: {samples.shape[1]})逻辑上这个切分过程就是在一维数组上用一个固定长度的框从前往后滑动。window_size决定每个样本包含的信息量——太小抓不住低速旋转情况下的完整冲击周期太大又会让样本之间趋同、计算量增大。CWRU 12 kHz采样下1024个点覆盖约85毫秒在1797 rpm下是约2.5个旋转周期这是工程上经验值。step_size取512保证重叠率50%如果你发现样本数量不足可以把重叠提高到75%step_size256代价是相邻样本的独立性下降后面章节会讲这个坑。2.3 标签怎么打故障类型、负载与损伤直径的编码切出来的每个样本都要有标签。CWRU数据集按故障位置和故障程度命名例如内圈故障0.007英寸、外圈故障0.014英寸、滚动体故障0.021英寸。分类器的输出一般把不同负载下的同种故障视为同一类别因为最终的诊断目标是不受工况影响的故障类型识别而不是故障程度估计。标签编码用整数即可不要用独热编码存盘省内存且读取方便。一个完整的训练集目录结构建议做成这样dataset/ ├── train/ │ ├── normal/ # 正常状态, 标签0 │ │ ├── sample_0001.npy │ │ └── ... │ ├── inner_race/ # 内圈故障, 标签1 │ │ ├── sample_0001.npy │ │ └── ... │ ├── outer_race/ # 外圈故障, 标签2 │ │ ├── sample_0001.npy │ │ └── ... │ └── ball/ # 滚动体故障, 标签3 │ ├── sample_0001.npy │ └── ... └── val/ ├── normal/ ├── inner_race/ ├── outer_race/ └── ball/这种按类别分文件夹的组织方式配合PyTorch的torchvision.datasets.ImageFolder的变体或自己写的Dataset类读取时直接按目录名映射标签非常省事。每个样本建议先做归一化再存npy格式归一化方法对加速度信号用z-scoredef normalize(sample): mean np.mean(sample) std np.std(sample) return (sample - mean) / (std 1e-8)注意这里的1e-8是防止振动信号幅值全为零时除零报错。归一化必须在切分之后的每个样本上独立做而不是对整条长信号一次归一化再切分否则每个样本的均值方差都一样等价于没归一化。2.4 要不要做频域变换时域输入与频谱输入怎么选这个问题设计模型前就要想清楚。一维CNN直接吃时域波形是最省事的选择因为卷积核在时域上能自动提取冲击脉冲、调制边带等特征。送入FFT后的功率谱也是常见选择好处是去掉了相位信息输入更稳定而且特征在频域上更容易对齐。有些方案做STFT时频图后喂二维CNN这在轴承诊断里不是主流原因是振动信号是稳态周期信号时频图的信息增益并不高但训练成本却高不少。我的建议是第一版先做纯时域输入模型不收敛或精度不够时再换FFT频谱输入做对比实验。深度学习最忌讳一开始就把输入设计复杂化简化输入维度更容易定位问题在模型还是数据。3. 用PyTorch搭一维CNN诊断模型网络结构、残差连接与训练闭环3.1 为什么一维CNN是轴承诊断的首选主干轴承振动信号本质是时间序列但用LSTM或Transformer来做并不划算。原因有三一是样本长度通常在1024点LSTM在这么长的序列上展开显存占用高且训练慢二是单通道信号没有明显的长程依赖Transformer的自注意力优势发挥不出来三是一维CNN的结构最简单卷积核在时间轴上滑动天然适合捕捉局部冲击特征且推断速度极快后期部署在工控机上也没压力。一维CNN里最值得借鉴的设计是残差连接。网络深度超过四层之后梯度消失问题开始在数据量小的场景里显现——注意这不是深层网络死亡ReLU的问题而是小数据集上深层网络的优化不稳定。给每个卷积块加一条跳跃连接让梯度有捷径回传模型从学习映射退化成学习残差收敛速度和稳定性都有明显提升。3.2 一个能跑到90%准确率的最小网络结构直接给出一个经过验证的基础结构。它包含3个残差卷积块每个块里是两个一维卷积加ReLU最后接全局平均池化和全连接分类头。输入是1024点单通道波形输出是4类正常、内圈、外圈、滚动体。import torch import torch.nn as nn import torch.nn.functional as F class ResidualBlock1D(nn.Module): 一维残差卷积块 in_channels: 输入通道数 out_channels: 输出通道数 kernel_size: 卷积核大小, 奇数保证padding后长度不变 stride: 控制下采样 def __init__(self, in_channels, out_channels, kernel_size15, stride2): super().__init__() self.conv1 nn.Conv1d(in_channels, out_channels, kernel_size, stride, paddingkernel_size // 2) self.bn1 nn.BatchNorm1d(out_channels) self.conv2 nn.Conv1d(out_channels, out_channels, kernel_size, 1, paddingkernel_size // 2) self.bn2 nn.BatchNorm1d(out_channels) # 当通道数或长度变化时, 用1x1卷积对齐残差 self.shortcut nn.Sequential() if stride ! 1 or in_channels ! out_channels: self.shortcut nn.Sequential( nn.Conv1d(in_channels, out_channels, 1, stride), nn.BatchNorm1d(out_channels) ) def forward(self, x): out F.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) out self.shortcut(x) return F.relu(out) class BearingCNN(nn.Module): 滚动轴承故障诊断一维CNN 输入: (batch, 1, 1024) 输出: (batch, num_classes) def __init__(self, num_classes4, in_channels1): super().__init__() self.features nn.Sequential( ResidualBlock1D(in_channels, 16, kernel_size15, stride2), # 1024 - 512 ResidualBlock1D(16, 32, kernel_size11, stride2), # 512 - 256 ResidualBlock1D(32, 64, kernel_size7, stride2), # 256 - 128 ) self.avgpool nn.AdaptiveAvgPool1d(1) self.fc nn.Linear(64, num_classes) def forward(self, x): x self.features(x) x self.avgpool(x) x torch.flatten(x, 1) return self.fc(x) model BearingCNN(num_classes4) print(f模型参数量: {sum(p.numel() for p in model.parameters())})这个网络的核心设计逻辑是逐层减半时间分辨率、倍增通道数。第一个残差块用大卷积核kernel_size15捕捉低频基频附近的摩损调制特征后面两个块逐步缩小卷积核提炼更细的局部冲击特征。注意每个块内部的两个卷积不改变长度长度变化全靠stride2的卷积实现这样设计让信息每次丢失一半但通道数翻倍信息总量不降。全连接层前的全局平均池化把每个通道压缩成一个标量大幅减少参数数量避免数据量小的情况下过拟合。3.3 训练主流程数据加载、优化器与训练循环数据集用PyTorch标准的Dataset和DataLoader组织。每个样本是npy文件加载后转成tensor标签按子目录名映射。训练循环里值得注意的细节是用torch.utils.data.random_split做训练验证划分而不是从每个类里按文件数手工切——后者的文件列表随机性不可控容易把同一段信号的相邻样本同时分到训练和验证集导致验证结果虚高。import torch from torch.utils.data import Dataset, DataLoader, random_split import os import numpy as np class BearingDataset(Dataset): 按目录结构读取轴承样本的Dataset root_dir: 形如 dataset/train 的根目录 子目录名即为标签名 def __init__(self, root_dir): self.samples [] self.labels [] # 按字母序固定标签映射, 保证多次运行的标签一致 for idx, cls_name in enumerate(sorted(os.listdir(root_dir))): cls_dir os.path.join(root_dir, cls_name) for fname in os.listdir(cls_dir): if fname.endswith(.npy): self.samples.append(os.path.join(cls_dir, fname)) self.labels.append(idx) self.class_names sorted(os.listdir(root_dir)) def __len__(self): return len(self.samples) def __getitem__(self, idx): x np.load(self.samples[idx]).astype(np.float32) x np.expand_dims(x, axis0) # (1, window_size) y self.labels[idx] return torch.from_numpy(x), torch.tensor(y, dtypetorch.long) # 数据划分: 按样本id做索引级划分, 避免DataLoader内部随机打乱导致同源样本串集 dataset BearingDataset(./dataset/train) train_size int(0.8 * len(dataset)) val_size len(dataset) - train_size train_ds, val_ds random_split(dataset, [train_size, val_size], generatortorch.Generator().manual_seed(42)) train_loader DataLoader(train_ds, batch_size64, shuffleTrue, num_workers0) val_loader DataLoader(val_ds, batch_size64, shuffleFalse, num_workers0) device torch.device(cuda if torch.cuda.is_available() else cpu) model BearingCNN(num_classes4).to(device) optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) criterion nn.CrossEntropyLoss() scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) for epoch in range(50): model.train() total_loss, correct, total 0.0, 0, 0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() logits model(xb) loss criterion(logits, yb) loss.backward() optimizer.step() total_loss loss.item() * len(yb) correct (logits.argmax(1) yb).sum().item() total len(yb) scheduler.step() # 验证 model.eval() val_correct, val_total 0, 0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) logits model(xb) val_correct (logits.argmax(1) yb).sum().item() val_total len(yb) print(fEpoch {epoch1:2d} | Train Acc {100.0*correct/total:.2f}% | Val Acc {100.0*val_correct/val_total:.2f}%)几个参数的选型理由优化器用AdamW而不是Adam因为weight_decay在AdamW里实现方式更正确解耦了权重衰减与动量的耦合训练过程更稳这是PyTorch官方推荐的做法。learning rate取1e-3对一维CNN这个规模是安全起点高于这个值容易在初期loss爆炸低于这个值训练时间拉长且容易困在局部极小。CosineAnnealingLR配合50个epoch前期快速下降、后期小幅修整避免了固定学习率后期在loss震荡区反复横跳的问题。batch_size64是显存和梯度稳定性之间的折中如果训练集样本数小于2000建议降到32防止梯度噪声过大。3.4 训练结果怎么看loss下降曲线和混淆矩阵训练跑完先别急着看准确率。第一个要检查的是train loss的下降曲线是否平滑。正常情况是前10个epoch快速下降然后进入缓慢优化阶段最终train loss低于0.1。如果train loss曲线出现了明显的锯齿震荡而不是平滑下降基本上可以判定学习率偏大调低50%再跑。第二个要检查的是验证集的混淆矩阵。准确率这个单一指标有欺骗性——如果外圈故障样本是其他类样本的两倍模型只要全预测成外圈就能拿到很高的准确率但那不是你要的诊断能力。下面这段代码输出标准化后的混淆矩阵import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay # 收集所有验证集预测结果 y_true, y_pred [], [] model.eval() with torch.no_grad(): for xb, yb in val_loader: xb xb.to(device) logits model(xb) y_pred.extend(logits.argmax(1).cpu().numpy()) y_true.extend(yb.numpy()) cm confusion_matrix(y_true, y_pred) disp ConfusionMatrixDisplay(cm, display_labelsdataset.class_names) disp.plot() plt.savefig(confusion_matrix.png, dpi150, bbox_inchestight)如果看到某两类之间频繁互相误判比如滚动体故障和内圈故障混淆不要急着调网络结构。先回头检查样本的原始振动信号确认这两种故障的时域波形特征是否在客观上嵌套——滚动体故障往往表现为周期性不明显的随机冲击内圈故障的冲击也会被转轴旋转调制两者在特征空间本身就靠近。这时合理的手段是增加样本量和做频域输入对比而不是加网络层数。4. 训练参数的精调学习率策略、Batch Size与类别不均衡处理4.1 学习率从1e-3起步用warmup和衰减保住稳定性一维CNN的参数量通常在几十万级别学习率敏感性比视觉大模型低但仍需要系统性的调整策略。最基本的经验是先用1e-3跑10个epoch观察train loss初始阶段的表现如果第一个epoch结束时loss还在1.0以上乱跳说明学习率太高降到3e-4。反过来如果每个epoch的loss下降幅度都小于5%说明学习率偏低可以用5e-3再试。带warmup的学习率调度在小数据集上特别有用。训练初始几个epoch用很小的学习率1e-4预热让BatchNorm的统计量稳定下来之后进入主学习率再余弦退火衰减。这能有效避免第一个epoch的BatchNorm剧烈波动引发的早期不收敛。PyTorch里可以用LambdaLR实现但更省事的方案是用timm库的CosineLRScheduler它内置了warmup参数比手写调度器省心。4.2 Batch Size与梯度稳定性的权衡在故障诊断任务里Batch Size的选择和数据量直接挂钩。CWRU数据集切完后单类样本通常有几万个64的batch size是稳妥选择。当你自采数据只有5000个样本时64的batch会导致每个batch内的样本代表性不足梯度方向在batch之间剧烈变化。小数据量的另一个隐性问题是每个batch内同类样本过多因为滑窗切分的相邻样本高度相似。如果某个batch恰好抽样到大量来自同一段信号的样本梯度会被这个局部特征主导。缓解手段是把切分后的样本做随机打乱后分层抽样或者干脆降低滑窗重叠率。我一般会先把step_size加大到window_size零重叠试一版看准确率是否下降如果下降明显再回退到50%重叠。4.3 类别不均衡轴承故障里最常见的隐蔽问题很多公开数据集的各类样本数设计得接近均衡但在实际项目中正常工况的数据占90%以上故障数据稀少甚至只有几十条。这种情况下的诊断目标不再适合用普通分类交叉熵。两个务实手段第一对少数类做过采样在Dataset类的__getitem__里按类别概率抽样已经切分好的样本可以直接复用不用重新生成。def oversample_indices(labels, target_countNone): 返回过采样后的索引列表, 使各类样本数相近 from collections import Counter counts Counter(labels) max_count max(counts.values()) # 对每个类别循环补齐, 超出部分从原样本中随机重复 indices_by_class {cls: [] for cls in counts} for idx, lb in enumerate(labels): indices_by_class[lb].append(idx) new_indices [] for cls, indices in indices_by_class.items(): num_needed max_count - len(indices) new_indices.extend(indices) if num_needed 0: # 随机重复采样补到目标数量 repeat np.random.choice(indices, sizenum_needed, replaceTrue) new_indices.extend(repeat.tolist()) np.random.shuffle(new_indices) return new_indices这个逻辑是硬过采样实现简单、效果直接。更稳妥的做法是用WeightedRandomSampler它在DataLoader层按权重随机采样不改变数据集本身但实现上要额外维护一个权重数组多数情况下过采样更直观。第二在损失函数层面给少数类更高权重。用带权重的CrossEntropyLoss权重按类别样本数的倒数归一化class_counts np.array([len(os.listdir(os.path.join(./dataset/train, c))) for c in sorted(os.listdir(./dataset/train))]) weights 1.0 / class_counts weights weights / weights.sum() # 归一化 criterion nn.CrossEntropyLoss(weighttorch.tensor(weights, dtypetorch.float32).to(device))两种手段可以叠加但不要同时用两份权重否则少数类会被矫正过度出现把正常样本误判为故障的另一种偏置。我的习惯是先做损失权重如果验证集少数类召回率还低于80%再叠加硬过采样。4.4 用Dropout和早停对抗过拟合深度学习落地里最常见的翻车现场是训练集准确率接近100%、验证集只有70%这就是过拟合。轴承故障数据往往维度单一、工况固定模型的拟合能力远超数据的信息量。除了已有的全局平均池化和残差连接Dropout是最后一个广谱有效的正则手段。在池化之后、全连接层之前加一个p0.5的Dropoutself.dropout nn.Dropout(p0.5) # forward中 x self.avgpool(x) x torch.flatten(x, 1) x self.dropout(x) return self.fc(x)p值不要超过0.5轴承故障的特征维度低Dropout过大会让模型学不动。同时配合早停策略监控验证集准确率连续10个epoch不上升就保存当前最佳模型权重并停止训练。这比训练固定的50个epoch更省时间而且能避免最后几个epoch已经过拟合还把权重保存成了最终的糟糕状态。5. 轴承故障诊断开发中的五个踩坑记录数据泄露、跨负载掉点与不收敛排查5.1 滑窗重叠过大导致数据泄露验证集分数虚高现象训练集和验证集准确率都飙升到99%但换到另一台设备或另一批数据上测试准确率直接掉到70%。原因滑窗切分的step_size很小时相邻样本高度重叠本质上来自同一段信号。random_split按样本索引划分造成了同源样本同时出现在训练集和验证集模型在验证时背题了。解决切分时按原始信号的连续段划分数据源而不是按切分后的样本索引划分。我一般先对每条原始信号标一个segment_id保证同一信号的相邻样本全进训练集或全进验证集。5.2 模型在固定负载下表现好换负载就不行现象训练时只用0 HP负载的数据验证时换成1 HP、2 HP负载准确率掉20个百分点以上。原因不同负载下轴承的振动能量和调制特征不同模型学到了负载相关的表面特征而不是故障本质。解决训练数据里混入多个负载工况的样本对输入做幅值归一化去掉能量差异更彻底的方案是做跨域诊断把特征提取器的输出接入领域对抗网络区分故障特征和负载特征。5.3 第一个epoch训练loss就是NaN现象训练一开始loss就是NaN不下降。原因最常见的是输入数据里含有NaN值比如信号采集时传感器断线。其次是学习率太大导致loss发散到极值后再回传梯度变成NaN。解决数据加载后先检查数值assert np.isfinite(samples).all(), 数据中存在NaN或无穷值这个断言卡在训练脚本开头瞬间定位问题。如果断言通过了就把学习率从1e-3降到1e-4重跑一次看loss是否恢复有限值。5.4 验证集准确率高但单类召回率极低现象总准确率90%但看混淆矩阵发现滚动体故障的召回率只有40%。原因滚动体故障的振动冲击在时域上随机性强且幅值低于内外圈故障是轴承诊断里公认最难识别的一类。另一个原因是样本量少滚动体故障在公开数据集里采集数量本身就少。解决针对该类型单独增加采集时长提高滑窗重叠率增加样本量同时引入时域频域双分支输入给模型补充更丰富的特征。5.5 GPU上跑得好CPU推理结果不一致现象同一套权重在GPU上验证准确率正常换到CPU推理时某些样本分类结果变化。原因PyTorch在GPU上用cuDNN的卷积算法在CPU上用不同的底层实现浮点数累积顺序不同导致细微差异。正常范围内的差异可以接受但如果个别样本翻车明显通常是BatchNorm的running statistics没有在推理模式下更新完整。解决训练完成后务必调用model.eval()并把模型用torch.jit.trace导出为TorchScript格式导出时固定输入尺寸减少运行时不确定性。6. 用t-SNE验证模型学到的东西诊断系统交付前的最后一道验收模型训练完要交付给机械工程师或产线使用光给一个分类准确率是不够的他们需要看见这个模型确实把不同故障分开了。t-SNE是最直观的工具——把模型倒数第二层全连接层前的特征向量降维到二维平面用不同颜色标记不同故障类型如果同色点聚成清晰的团、不同色点互相分离就说明模型学到了有区分力的特征而不是靠偶然的噪声决策边界。from sklearn.manifold import TSNE import matplotlib.pyplot as plt def extract_features(model, loader, device): model.eval() features, labels [], [] with torch.no_grad(): for xb, yb in loader: xb xb.to(device) # 截取到全局平均池化后的特征向量 feat model.features(xb) feat model.avgpool(feat) feat torch.flatten(feat, 1) features.extend(feat.cpu().numpy()) labels.extend(yb.numpy()) return np.array(features), np.array(labels) # 用验证集提取特征向量 feat_val, label_val extract_features(model, val_loader, device) tsne TSNE(n_components2, perplexity30, max_iter500, random_state42) embedding tsne.fit_transform(feat_val) # 可视化 fig, ax plt.subplots(figsize(8, 6)) scatter ax.scatter(embedding[:, 0], embedding[:, 1], clabel_val, cmaptab10, s10, alpha0.7) ax.set_title(t-SNE Visualization of Bearing Fault Features) plt.colorbar(scatter) plt.savefig(tsne_features.png, dpi150, bbox_inchestight)t-SNE的perplexity参数需要随样本数调整验证集样本在几千量级时取30合适样本数上万可以取到50太小会呈现碎片化、太大会过度压缩局部结构。这一张图放到诊断系统的技术方案文档里比任何文字描述都更有说服力。我也习惯用这个图来做交付前的最后排查如果正常类与故障类在t-SNE图上重叠严重说明模型泛化性堪忧我会回到数据层检查是否存在标签错乱或切窗错位而不是继续加卷积层数。这套系统的最大价值不只是训练出一个高准确率模型而是让故障到底长什么样从老师傅的经验变成可量化的数据证据。希望这套从数据准备到模型验证的完整流程能帮你在自己的故障诊断项目里少走几段弯路。本文还有配套的精品资源点击获取
返回列表