简介:面向轴承故障诊断与卷积神经网络结合的工程资源,适合信号处理方向的研究生、工程师及机器学习初学者。方法采用高阶变分模态分解对西储大学不同转速下的驱动端振动信号进行多层次分解,提取本征模式函数并削弱噪声,再由CNN自动学习空间特征,完成磨损、裂纹等故障模式的分类识别。压缩包共15个文件,约60.73MB,核心为9个.m脚本,覆盖主程序、特征提取、网络正则化模块和五种适应度函数;3个xlsx存放不同转速的原始振动数据,1个xlsx为HO-VMD特征提取后的数据集,便于对比验证;另附1个txt安装说明和1个pdf河马优化算法论文,辅助复现与理解参数寻优原理。目前已有203人学习下载,可直接运行整个诊断流程,也可基于不同转速数据和多种熵类适应度函数进一步调参,适用于课题预研、入门实践及算法改进。
1. 西储大学轴承故障诊断:HO-VMD-CNN这条路解决什么问题
一台风机轴承的振动信号里,藏着故障的类型、位置和严重程度,但原始波形根本不适合直接扔给神经网络。很多团队在西储大学(CWRU)轴承数据集上做实验,CNN结构改了一轮又一轮,准确率卡在八九成上不去,问题往往出在前处理:信号没分解干净,故障冲击被噪声淹没。HO-VMD-CNN 是这类问题的标准解法——HO(哈里斯鹰优化)自动给 VMD 选最优的模态数和惩罚因子,VMD 把原始振动信号拆成一簇有物理含义的 IMF,CNN 再从分解后的多通道信号上学判别特征。这套流程适合手里有振动数据、想把故障类别自动分出来的人,尤其适合被 VMD 两个参数折磨过的调参工。
2. VMD为什么需要HO来救:从模态混叠到参数黑洞
VMD(变分模态分解)把一段振动信号同时拆成 K 个离散模态,每个模态被约束在一个窄频带里。背后的优化目标是所有模态带宽之和最小,同时要求 K 个模态加起来能完整还原原始信号。这个思路比 EMD(经验模态分解)干净:EMD 靠极值插值递归剥皮,对噪声和采样率敏感,容易出现模态混叠;VMD 转成一个带约束的变分问题,用交替方向乘子法迭代求解,分解结果稳定得多。轴承故障的典型特征是周期性冲击,冲击在频域上会产生以故障特征频率为中心的一簇边带,VMD 天然适合把这些频带分离开。
VMD 的理论形式也不复杂:对每个模态 u_k 做 Hilbert 变换得到解析信号,再乘上指数项把频谱搬到基频附近,最后求梯度范数。迭代过程中,中心频率 ω_k 和模态 u_k 交替更新,直到满足收敛容差。工程里不需要自己实现这么一整套流程,直接用现成的 vmdpy 库就行,但要理解两个关键参数:K 是模态总数,alpha 是带宽惩罚因子。这两个参数不经过寻优,直接拍脑袋定,后面的 CNN 做得再漂亮也白搭。
2.1 VMD在轴承信号上到底做了什么
以 CWRU 驱动端 12 kHz 采样加速度信号为例,正常轴承能量分散在全频带,看不到明显峰值;内圈故障时,冲击调制出的高频共振带和低频特征频率带同时出现。VMD 分解出的第一个模态往往捕获幅值优势频带,后面几个模态捕获谐波和残余噪声。K 设小了,故障冲击和噪声被塞进同一个模态,CNN 学到的是混叠后的复合特征;K 设大了,一个真实频带会被拦腰切成两段,出现没有物理意义的虚假模态。alpha 控制模态带宽的紧致程度:alpha 太小,模态带宽过宽,相邻模态互相重叠;alpha 太大,模态被压得过窄,迭代不易收敛,还可能丢掉冲击的边带成分。这两个参数互相耦合,单靠看包络谱人工判断,眼睛根本不够用。
| 参数 | 影响对象 | 典型表现 |
|---|---|---|
| K(模态数) | 模态数量 | K 过小导致模态混叠、能量交叉;K 过大会出现无意义的虚假模态 |
| alpha(惩罚因子) | 模态带宽 | 过小带宽宽、模态重叠;过大带宽窄、迭代慢或发散 |
| tau(噪声容忍) | 重建保真度 | 工程上一般设 0,依靠保真项约束 |
| init(初始化方式) | 收敛路径 | 用 1(均匀初始化),避免过度依赖首模态 |
VMD 的输出是一组模态数组 u,shape 是 (K, 信号长度),每个模态都是一条一维时域波形。后面做 CNN 输入时,这 K 个模态就是 K 个通道,和图像的 RGB 三通道类似。区别在于:图像通道是像素矩阵,这里每个通道是独立的时间序列,长度可以人为截断。这意味着 CNN 的结构要选一维卷积而不是二维卷积,输入张量是 (batch, K, length)。
2.2 包络熵:把分解质量变成一把刻度尺
要让优化算法去搜 K 和 alpha,先得有一个数值指标能评价“这组参数分解得好不好”。最常见的指标是包络熵。计算方式是:对模态信号做 Hilbert 变换求出包络 a(t),归一化成概率分布 p_i = a(i) / Σa,然后算信息熵 E = -Σ p_i · log p_i。正常轴承振动包络接近随机噪声,概率分布平坦,熵值高;故障轴承的冲击在包络上呈现稀疏的尖峰,概率分布集中在少数点上,熵值低。VMD 参数选得好时,故障模态的包络熵会比欠分解参数低一大截,这就把“分解质量”变成了一个可以数值比较的标量。
实际应用中,每次寻优都要对整段训练信号跑一次 VMD,算每个模态的包络熵。常见做法是取 K 个模态里的最小包络熵作为该组参数的适应度,因为轴承故障冲击的能量往往集中在一个主导模态上,最小包络熵能捕获这个主导故障模态。也有文献取平均包络熵或加权熵,思路一样,只是尺度不同。用包络熵做适应度有个隐含前提:包络熵越低,故障冲击越突出,后续分类越容易。这个前提在大部分轴承数据上成立,但不是绝对成立,后面第 5 章会专门讲它的边界。
2.3 人工试参和网格搜索为什么都不够用
手工试参的流程是:选一组 K 和 alpha,跑 VMD,看各 IMF 的包络谱,比对特征频率,换一组再跑。CWRU 每个类别采样时间约 10 秒,按 2048 点滑窗能切出几百个样本,一次 VMD 耗时几十到几百毫秒,试 8 组参数乘 10 个类别,半天就过去了。最麻烦的是 K 和 alpha 互相影响:K=5 配合 alpha=2000 时看起来不错,但 K=6 时 alpha=2000 反而过分解,单独调一个参数没法收敛到好组合。
网格搜索能系统覆盖参数空间,但组合数膨胀:K 取 3 到 10,alpha 按对数间隔取 15 个值,就是 8×15=120 次 VMD。对一条 12 kHz、10 万点的信号,120 次分解足够跑一个多小时,而且网格是离散的,最优 alpha 往往不在网格点上。更关键的是,CWRU 有多种负载和多个故障直径,不同数据段的最优 K 和 alpha 不一定相同,网格搜索只能针对某一段信号,换一段信号又要重来。把参数选择当成黑盒子优化问题,用 HO 这类群体智能算法直接搜,是目前最省事的做法。
3. 用HO为VMD找最优参数:优化映射与可复现代码
3.1 哈里斯鹰优化怎么映射到VMD寻优
哈里斯鹰优化(Harris Hawks Optimization,简称 HO)是近年比较常用的群体智能算法,模拟哈里斯鹰在沙漠中捕猎兔子的行为。兔子位置代表当前最优解,鹰群在探索阶段随机栖息寻找兔子,在开发阶段根据兔子逃跑能量做软包围、硬包围、渐进式俯冲。把它用到 VMD 参数寻优上,一个鹰的位置就是一组候选参数 [K, alpha],适应度就是跑完 VMD 得到的包络熵。鹰群在 K-alpha 构成的二维平面上移动,每一轮迭代更新所有鹰的位置,然后比较每一处的适应度,保留当前最优兔子位置。
K 本身是整数,但 HO 的位置更新是连续值。常见处理方式是把 K 当连续变量参与鹰的位置运算,评价适应度时再 round 成整数。这样做的好处是算法不用做离散编码,坏处是 K 的梯度几乎为零,可能出现 K 在连续几次迭代里都不变。如果发现 K 一直卡在同一数值,可以改用离散映射:把 K 编码成 [0,1] 连续值,解码时映射到 3 到 10 的整数区间。两种做法我都试过,对 CWRU 数据来说,连续取整通常够用,因为 alpha 才是真正敏感的参数。
3.2 边界设计:K和alpha的范围为什么要这样定
边界设得不对,寻优结果是废的。CWRU 12 kHz 数据上,K 取 3 到 10 比较稳妥:小于 3 会把正常信号和故障冲击压在一起,大于 10 噪声会被逐条拆成独立模态。alpha 取 500 到 5000:alpha 太小带宽约束失效,模态之间互相混叠;alpha 太大迭代代价成倍上升,还可能不收敛。另一个容易忽略的细节是采样方式:alpha 在数千量级跨越很大,初始化时要在指数空间采样,即 np.logspace(np.log(500), np.log(5000), n),而不是线性空间的均匀分布。否则 alpha 大概率落到 500 到 2000 之间,5000 附近几乎永远采样不到,搜索空间被实际拉窄。
种群规模和迭代次数也要克制。种群数量 10 到 20 个,迭代 20 到 30 次,一次完整寻优要跑 200 到 400 次 VMD。对一条 12 kHz、几万点的信号来说,这个量级在几分钟到十几分钟,可以接受。如果信号长度超过 20 万点,建议把种群降到 6、迭代降到 15,先对一小段代表性信号寻优,再把最优参数应用到全部信号上,不要试图一次寻优吃下整条长信号。
3.3 HHO-VMD寻优代码与参数说明
下面是基于 vmdpy 和 numpy 的完整寻优代码。vmdpy 是 VMD 的常见 Python 封装,pip install vmdpy 就能装。如果环境装不上,把 VMD 函数体换成自己实现的变分模态分解即可,接口不变。
import numpy as np from scipy.signal import hilbert from vmdpy import VMD def envelope_entropy(signal): # 对信号取希尔伯特包络,归一化后计算信息熵 amp = np.abs(hilbert(signal)) amp = amp / (np.sum(amp) + 1e-12) return -np.sum(amp * np.log(amp + 1e-12)) def vmd_cost(pos, signal): # pos 就是一组 [K, alpha],K 转成整数再进入 VMD k = int(round(pos[0])) alpha = float(pos[1]) # 参数顺序:signal, alpha, tau, K, DC, init, tol # tau=0 表示不额外引入噪声控制,DC=0 不提取直流,init=1 均匀初始化 try: u, _, _ = VMD(signal, alpha, 0, k, 0, 1, 1e-7) except Exception: # VMD 发散时返回一个大数作为惩罚 return 1e6 # 取 K 个模态里最小的包络熵作为适应度 entropy = np.array([envelope_entropy(m) for m in u]) return np.min(entropy) def hho_vmd(signal, bounds, pop=10, iterations=20, seed=1): rng = np.random.default_rng(seed) dim = len(bounds) # 指数空间初始化 alpha,线性空间初始化 K lo = np.array([b[0] for b in bounds]) hi = np.array([b[1] for b in bounds]) pos = np.zeros((pop, dim)) for d in range(dim): if d == 1: # alpha 维度用对数均匀采样 log_lo, log_hi = np.log(lo[d]), np.log(hi[d]) pos[:, d] = np.exp(rng.uniform(log_lo, log_hi, pop)) else: pos[:, d] = rng.uniform(lo[d], hi[d], pop) # 初始化兔子位置(全局最优) best = pos[0].copy() best_val = vmd_cost(best, signal) for t in range(iterations): for i in range(pop): r = rng.random() if r < 0.5: # 探索阶段:随机跳向种群里的其他位置或当前最优 if rng.random() < 0.5: q = rng.integers(0, pop) pos[i] = pos[q] - rng.random() * np.abs(pos[q] - pos[i]) else: pos[i] = best - rng.random() * np.abs(best - pos[i]) else: # 开发阶段:这里简化成朝最优位置收缩,完整的 HHO 有四种包围策略 pos[i] = best - 0.2 * np.abs(best - pos[i]) * rng.random() # 越界处理:直接裁剪到边界 pos[i] = np.clip(pos[i], lo, hi) # 评估本轮每个鹰位置 for i in range(pop): val = vmd_cost(pos[i], signal) if val < best_val: best_val = val best = pos[i].copy() return best, best_val # 示例:signal 是 CWRU 的一条整段振动信号,12kHz,长度至少几万点 # bounds 顺序是 [K_min, K_max], [alpha_min, alpha_max] # best_params, best_entropy = hho_vmd(signal, bounds=[[3, 10], [500, 5000]])这段代码有几个关键设计。vmd_cost 里把 K round 成整数,alpha 保留浮点,VMD 调用参数顺序不能错,尤其是 tau 和 DC,工程上固定为 0。适应度取最小包络熵而不是平均包络熵,原因前面说过,轴承故障能量集中在一个主导模态。HHO 的探索阶段用了两种随机跳跃,开发阶段简化成收缩包围,不是文献里的完整四策略,但对二维参数寻优已经够用,主要是收敛快、代码短。种群设 10,迭代设 20,总共 200 次 VMD 调用,一条 5 万点信号大约 3 到 5 分钟跑完。
如果想把 K 也处理得更细腻,可以改成离散映射:pos[:,0] 约束在 [0,1],解码时 k = 3 + int(round(pos[:,0] * 7)),这样 K 的每一步变化都能被算法感知到。代价是搜索空间变成一深一浅两条腿,收敛速度略降。建议先跑一次连续取整版,如果发现 K 始终不变且包络熵不理想,再换离散映射。
4. 分解结果如何喂给CNN:样本构造与训练配置
4.1 从CWRU原始文件到训练样本:先分解再滑窗
CWRU 数据集按故障位置和故障直径组织文件。常见做法是取 12 kHz 驱动端加速度信号做十分类:正常用 97.mat(0 马力),内圈故障用 105、106、107 对应 0.007/0.014/0.021 英寸,滚动体故障用 118、119、120,外圈故障用 130、131、132。外圈故障在数据里有 3 点钟、6 点钟、12 点钟三个方向,通常只用其中一个方向,比如 3 点钟,避免类别数膨胀。每个文件信号长度约 12 万点,对应 10 秒采样,样本量完全够用。
关键时序是先分解再滑窗,不是先滑窗再分解。对整段信号做一次 VMD,得到 (K, 整段长度) 的模态数组,然后从模态数组上滑窗取 (K, 2048) 的样本。这样做的好处是 VMD 的边界效应只出现在整段信号的开头和结尾,中间所有窗口的分解质量一致。如果先滑窗再逐窗口分解,每个窗口两端都有边界效应,等于每个样本都被污染。
import numpy as np from vmdpy import VMD def make_samples(signal, label, opt_k, opt_alpha, window=2048, overlap=0.5): # 对整段信号做一次 VMD,得到 opt_k 个模态 u, _, _ = VMD(signal, opt_alpha, 0, opt_k, 0, 1, 1e-7) # u shape 是 (opt_k, len(signal)) # 丢弃开头和结尾各 256 点,规避边界效应 u = u[:, 256: -256] step = int(window * (1 - overlap)) samples = [] labels = [] for start in range(0, u.shape[1] - window, step): samples.append(u[:, start:start + window]) labels.append(label) return np.stack(samples), np.array(labels)样本标签是数字编码,十类对应 0 到 9。滑窗重叠率用 0.5,窗口 2048 点,在 12 kHz 采样下约等于 0.17 秒,包含足够多的冲击周期。这里 window 不用设太大,2048 点对 CNN 来说既保留了局部冲击细节,又不至于让特征图尺寸过大。样本量方面,一条 12 万点信号去掉两端边界后,按 1024 步长能切出约 110 个样本,10 个类别合计 1000 个样本,已经能支撑一个小型 CNN 训练。
训练集和测试集的划分要格外小心。不能直接把全部样本随机打乱再切,因为相邻窗口高度相关,训练集和测试集会互相“抄答案”。正确做法是按文件来源切分:每个类别的 10 秒信号前 60% 只进训练集,后 40% 只进测试集,再在训练集内部做 80/20 的验证集划分。这样测试样本和训练样本来自不同的时间片段,才有评估意义。
4.2 1D-CNN结构:通道数、核大小与感受野
CNN 的输入是 (batch, K, 2048),K 就是 VMD 寻优得到的模态数,通常在 4 到 8 之间。因为输入是一维时间序列,所以用一维卷积 Conv1d。第一层卷积核大小建议设大一点,比如 kernel_size=64、stride=4。理由是一个冲击周期在 12 kHz 下大约持续 50 到 150 点,64 点的卷积核能覆盖住半个到一个冲击周期,相当于让第一层先做一次波形模板匹配。后续卷积核缩小到 3,负责组合局部特征。
全连接层不要堆太深。轴承故障分类任务的特征复杂度远低于图像识别,两层全连接足够,第一层 256,第二层 10。全连接层多了,训练集准确率能到 100%,测试集反而掉 2 到 3 个百分点,典型的过拟合。Dropout 放 0.5,只在全连接前加一层即可。下面是模型的 PyTorch 定义。
import torch import torch.nn as nn class VMDCNN(nn.Module): def __init__(self, n_channels, n_classes=10): super().__init__() # 一维卷积分支:K 个模态当作 K 个通道 self.branch = nn.Sequential( nn.Conv1d(n_channels, 32, kernel_size=64, stride=4, padding=32), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size=3, stride=2, padding=1), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(128) ) self.fc = nn.Sequential( nn.Flatten(), nn.Linear(64 * 128, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, n_classes) ) def forward(self, x): return self.fc(self.branch(x))这个结构很常规,没有花哨模块。第一层 padding=32 是为了补 kernel_size=64 带来的长度损失,配合 stride=4 把 2048 点输入压到 512 左右。中间那层 Conv1d 的 stride=2 继续压缩分辨率,最后一层 AdaptiveAvgPool1d 把特征图统一到固定长度,这样即使窗口长度变了,全连接层的输入维度也不变。想用多尺度 CNN 做并行卷积也是可以的,但对 CWRU 这个规模的数据集作用不大,单分支结构已经足够,很多实验结果都印证了这一点。
4.3 PyTorch训练循环与超参配置
训练超参按常见动作维护习惯来设:Adam 优化器,学习率 1e-3,batch size 64,epoch 50,weight decay 1e-4,遇到验证集连续 5 轮不升就把学习率乘 0.5。50 轮并不是固定值,早停机制会在验证集不再提升时提前截断,避免浪费时间。
import torch def train_vmd_cnn(model, train_loader, val_loader, epochs=50, lr=1e-3, device="cuda"): model.to(device) opt = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=1e-4) lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(opt, factor=0.5, patience=3) loss_fn = nn.CrossEntropyLoss() for epoch in range(epochs): model.train() total_loss = 0.0 for x, y in train_loader: x, y = x.to(device), y.to(device) opt.zero_grad() pred = model(x) loss = loss_fn(pred, y) loss.backward() opt.step() total_loss += loss.item() * x.size(0) model.eval() val_correct = 0 val_total = 0 with torch.no_grad(): for x, y in val_loader: x, y = x.to(device), y.to(device) pred = model(x) val_correct += (pred.argmax(1) == y).sum().item() val_total += y.size(0) val_acc = val_correct / val_total lr_scheduler.step(val_acc) # 实际项目里可以把 TQDM 进度条和模型保存加在这里训练循环本身没有特别之处,重点在于配置。batch size 用 64,因为样本本身只有一两千,用 128 会减少梯度更新次数,收敛变慢。学习率初始 1e-3 是 Conv1d 任务最稳的起点,低于 5e-4 收敛太慢,高于 2e-3 容易振荡。weight_decay 主要约束全连接层,卷积层参数共享本身就带正则效果,不用额外加太多。这里的 ReduceLROnPlateau 的 patience 是 3,意思是验证集连续 3 轮不升就降学习率,比固定 step 降学习率更稳。
4.4 测试集评估:混淆矩阵与准确率
训练完在测试集上做最终评估,输出准确率和混淆矩阵。评估时只跑前向,不计算梯度,预测结果和真实标签对比。常见的十类问题里,最容易混的是内圈故障 0.007 英寸和滚动体故障 0.007 英寸,两者都是轻微故障,冲击幅值小、特征频率接近,CNN 很难区分。如果有几个类互相混淆,不要急着改网络结构,先把对应类别的 VMD 模态画出来看看是不是分解不干净。
import numpy as np from sklearn.metrics import confusion_matrix test_correct = 0 test_total = 0 all_pred = [] all_true = [] model.eval() with torch.no_grad(): for x, y in test_loader: x, y = x.to(device), y.to(device) pred = model(x) test_correct += (pred.argmax(1) == y).sum().item() test_total += y.size(0) all_pred.extend(pred.argmax(1).cpu().numpy()) all_true.extend(y.cpu().numpy()) print(f"test accuracy: {test_correct / test_total:.4f}") cm = confusion_matrix(all_true, all_pred) print(cm)测试准确率能到 95% 以上说明流程基本通了,但别急着高兴。下一步要看跨负载表现,这一步能筛掉很多只在特定转速下有效的假方案。测试集评估之后,模型就不要再回头调参了,否则测试集就变成了训练集的一部分,后面的结果全是水分。
5. HO-VMD-CNN最常翻车的5个环节:现象与排查
5.1 坑1:K上限设大,VMD直接发散
现象:K 上限设到 12 或 15,寻优过程中 VMD 返回的模态数组里出现 NaN,或者分解结果能量远大于原始信号,包络熵突然跳到一个荒谬的大数。原因:K 超过信号实际可分的频带数量后,交替方向乘子法会把噪声一个点一个点地拆开,迭代数值稳定性崩盘。解决:把 K 的上限压到 10,同时在 vmd_cost 里显式检查返回值是否包含 NaN,一旦发现就返回一个很大的惩罚值,让优化算法自动绕过这些区域。我见过有人把 K 上限设到 20,跑了三个小时全部白费。
5.2 坑2:包络熵局部最优,两次寻优结果对不上
现象:固定随机种子跑出 K=6、alpha=2800,换一个种子变成 K=8、alpha=1200,两者的包络熵差距不到 5%。原因:包络熵曲面不是光滑的单峰函数,很多参数组合的评价结果几乎一样,HO 收敛到哪个峰取决于初始种群位置。解决:同一信号跑三次寻优,每次换随机种子,取包络熵最小且出现次数最多的参数组合;或者把寻优结果当作起点,在邻域内再用几个固定组合排查。不要期待一次寻优就得到唯一最优解,HO 的本质是给出一个很好的候选区间。
5.3 坑3:样本切分重叠,测试准确率虚高
现象:全局随机切分样本时测试准确率 98%,改成按时间片划分后掉到 91%,模型结构一个字母没改。原因:滑窗重叠率 0.5 时,相邻样本有 50% 长度的波形完全一样,随机划分会造成训练集和测试集里出现“孪生样本”,测试准确率虚高。解决:按原始文件的时间段划分,每个文件前 60% 进训练集、后 40% 进测试集,再做数据增强或交叉验证也要以文件为单位分组。很多人用 sklearn 的 train_test_split 直接切,默认随机打乱,这个坑踩得无声无息。
5.4 坑4:换个负载准确率掉一半
现象:0 马力数据训练验证 99%,拿到 2 马力数据上直接掉到 70%。原因:CWRU 的负载档位对应不同转速,负载增大后转速下降,故障特征频率跟着偏移,振动幅值也变大,模型在训练集里只见过 0 马力的统计特征。解决:训练集里混合 0、1、2 马力数据,测试集放 3 马力;如果目标是单负载部署,至少对每个样本做 z-score 归一化,消除幅值差异。特征频率偏移靠归一化解决不了,只能靠多负载数据覆盖转速范围。
5.5 坑5:包络熵最优的参数,端到端反而变差
现象:HO 搜出来的 K 和 alpha 包络熵最低,换到 CNN 上准确率比固定参数 K=5、alpha=2000 还低一个多点。原因:适应度函数是单模态最小包络熵,它评价的是“某个模态冲击是否突出”,而 CNN 分类需要的是所有模态的判别信息,两者不完全一致。解决:不要把包络熵当作最终标准,它只是搜索向导。寻优得到候选参数后,用三组参数做对比:固定经验值、HO 最优质、人为微调值,各训一次 CNN,看测试准确率决定最终采用哪个。这套流程看着笨,但在很多数据集上,固定参数并不输给寻优参数,往往是人为微调后的组合拿到最好结果。
6. 这套方案值不值得信:混淆矩阵、t-SNE与跨负载验收
6.1 混淆矩阵看模型在哪里犯糊涂
测试集准确率只是一个平均值,真正暴露问题的是混淆矩阵。拿 CWRU 十分类来说,如果 0.007 英寸内圈故障那一列里混入了不少滚动体故障的预测,说明这两种轻微故障的频带在 VMD 分解后仍然有重叠,CNN 学到的特征不够清晰。这时候第一反应不是加卷积层,而是看这两个类别的模态波形和包络谱差异在哪,必要时把 K 整体加 1 或减 1 重新寻优。
6.2 t-SNE看特征是否真正聚拢
混淆矩阵之外,t-SNE 是检查特征质量最直观的工具。用测试集跑一次前向,把全连接层之前的特征抽出来做二维降维,可视化后如果同一类聚成一团、不同类之间有明显间隔,说明 VMD 分解把故障特征分好了;如果各类混成一滩,再调 CNN 结构也没有意义。
from sklearn.manifold import TSNE import matplotlib.pyplot as plt features, labels = [], [] model.eval() with torch.no_grad(): for x, y in test_loader: f = model.branch(x.to(device)).cpu().numpy() features.append(f.reshape(f.shape[0], -1)) labels.append(y.numpy()) feat = np.concatenate(features) lab = np.concatenate(labels) tsne = TSNE(n_components=2, perplexity=30, random_state=1).fit_transform(feat) plt.scatter(tsne[:, 0], tsne[:, 1], c=lab, cmap="tab10", s=4) plt.show()perplexity 要和样本量匹配,样本几百个时用 5 到 15,样本上千时用 30 左右。t-SNE 只能定性判断,看到重叠不一定是模型的问题,也可能是可视化参数选得不合适,要和混淆矩阵的数据互相印证。
6.3 跨负载测试才是最终验收
西储大学数据有 0、1、2、3 马力四档负载,如果只用 0 马力训练、2 马力测试,大多数方案准确率都会明显下滑,这很正常。但一个值得投入的方案应该做到:训练集包含 0 和 1 马力,测试集用 2 和 3 马力,准确率依然保持在 90% 以上。做到这一点需要 VMD 参数在多个工况下有适应性,而不是针对某一段信号过拟合。我在实际工程里的习惯是把跨负载测试当作硬性门槛,达不到就回头检查样本归一化和数据集划分,而不是继续加网络深度。
这套 HO-VMD-CNN 流程,我最大的教训是不要为了“最优”去找最优:包络熵是导航,不是保险,最终说了算的是测试集上的泛化表现,尤其是在没见过的负载上。把每个环节都跑通之后,这套流程能稳定帮我省掉一半调参时间,也希望帮到你。
本文还有配套的精品资源,点击获取