☰
一维WGAN-GP轴承故障诊断小样本数据增广实战
2026/10/29 2:41:47 网站建设 项目流程

简介:面向轴承故障诊断与一维振动信号增强方向的学习者和研究者,GAN-1D工程以WGAN为核心,用Python实现故障轴承振动信号的生成,适合具备Python 3.5以上基础、熟悉TensorFlow-GPU及NumPy、SciPy环境的中级读者复现实验。压缩包共19个文件,约22.03MB,主要包含2个Python脚本、2个MAT数据文件、9张生成信号可视化PNG,以及CNN.ckpt的meta、data、index、checkpoint等模型检查点和README说明,便于分别用于训练测试、样本读取、效果观察与权重加载。train.py支持设置learning_rate、epoch、sample_rate和train_data,并可从9类信号中选择训练对象,方便比较不同故障类型下的生成表现。已有3387人学习下载。读者可获得可运行的训练与测试脚本、样例数据、检查点文件及生成效果图,理解WGAN在一维故障信号生成中的完整流程,并据此开展数据增强、信号质量对比、模型调参与结果复现实验,目录组织也便于按模块查阅。

1. 几十条故障样本撑不起一个分类器,WGAN-1D 来补这个缺口

做轴承故障诊断的人大多遇到过同一个尴尬:正常状态的数据录一天能有几百兆,可外圈剥落、内圈裂纹这类真故障,往往只有人工加速寿命试验“熬”出来的几十条记录,还得算上不同转速、不同负载的工况切分。拿这点样本去喂一个一维卷积分类器,训练集准确率能刷到 99%,一换工况立刻掉到六成——典型的过拟合,不是模型不行,是分布没被覆盖。这份 GAN-1D 资源就是用来补这个缺口的:用一维 WGAN 从少量真实振动片段里学出该故障状态的幅值分布与冲击节奏,再采样出成千上万条同分布的合成波形。整套实现是 Python + PyTorch,所有算子都是一维卷积,不涉及图像那套二维结构,也不依赖任何预训练权重。适合做小样本诊断、数据增广,以及想把 GAN 真正落到工业信号而不是 MNIST 上的人。

2. 拆包与数据准备:切片长度为什么定 1024 点

拿到一个生成类项目,先看的不是网络结构,是数据管线。生成器再花哨,切片切错了、归一化做歪了,训出来的东西一定是一团糊。这一章先把资源包里各模块的职责理清,再把数据侧的三个关键决定说明白。

2.1 四个模块的职责边界

资源包的代码是按“数据 → 模型 → 训练 → 评估”四段切的,每段只干一件事,互不越界。这种切法看着朴素,但对改实验特别友好:想换切片长度只动第一段,想换评论家结构只动第二段,训练脚本和评估指标完全不用改。

模块职责关键产出对外依赖
data_prep长记录切片、去直流、全局缩放、按工况分组bearing.npz(X / y / g / scale)numpy
models生成器、评论家、梯度惩罚函数两个nn.Module类torch
trainWGAN-GP 训练循环、检查点保存G_xxxx.pth、D_xxxx.pthtorch
evaluate时域特征、包络谱、t-SNE、下游分类对照指标表与图numpy / scipy / sklearn

真正需要改的通常只有 data_prep 里的窗口参数和 train 里的迭代次数,其余部分当黑盒用就行。评估模块容易被跳过,但它决定了你是“训完了”还是“训对了”,后面第 4.3 节会单独讲。

2.2 切片、去直流与全局缩放:三个容易定错的决定

切片长度定 1024 点。这个数字不是随便取的。常见的轴承振动采样率在 12 kHz 上下,1024 点大约对应 85 ms。滚动体经过一个缺陷点时产生的冲击,其重复周期由故障特征频率决定,外圈故障在中等转速下通常是几十到一百多赫兹,也就是 8~12 ms 一个冲击。85 ms 的窗口能装下 7~10 个冲击周期,既让评论家看得到“节奏”,又不至于长到把转速波动、负载变化的非平稳性混进来。窗口低于 512 点时,冲击个数太少,网络容易退化成学单个脉冲的形状;超过 2048 点,单条样本的信息密度下降,同样的迭代次数下收敛明显变慢。

去直流必须做。加速度传感器和电荷放大器之间常带几百毫伏的偏置,不去掉的话 FFT 在 0 Hz 附近会有一个巨大的峰,生成器会把大量建模能力浪费在这个直流分量上,其余频段全是噪声。

缩放用全局系数,不用逐样本标准化。这是新手最容易翻车的一处。逐样本 z-score 会把每条样本都拉到均值 0、方差 1,看起来“整齐”,但故障的严重程度恰恰体现在幅值上——轻微剥落和严重剥落的波形形态可能相似,能量差好几倍。逐样本标准化直接把这个信息抹平,生成出来的样本幅值全一样,下游做严重程度分级就废了。

import numpy as np from pathlib import Path WIN = 1024 # 单样本长度:12 kHz 采样下约 85 ms,覆盖 7~10 个冲击周期 STRIDE = 512 # 50% 重叠,在样本量和片段独立性之间折中 EPS = 1e-8 def slice_signal(x, win=WIN, stride=STRIDE): """把一条长振动记录切成定长片段,返回 (n, win)""" n = 1 + (len(x) - win) // stride idx = np.arange(win)[None, :] + stride * np.arange(n)[:, None] return x[idx] def build_dataset(raw_dir, out_path): segs, labels, groups = [], [], [] for f in sorted(Path(raw_dir).glob("*.npy")): # 文件命名约定: {工况}_{故障类型}.npy,例如 load0_outer.npy cond, fault = f.stem.split("_") x = np.load(f).astype(np.float32) x = x - x.mean() # 去直流,避免 0 Hz 分量主导损失 seg = slice_signal(x) segs.append(seg) labels += [fault] * len(seg) groups += [cond] * len(seg) # 工况作为分组键,供后续按组划分 X = np.concatenate(segs) scale = np.abs(X).max() # 注意:演示用全量,正式实验只统计训练集 X = X / (scale + EPS) np.savez(out_path, X=X, y=np.array(labels), g=np.array(groups), scale=scale)

slice_signal用广播代替 for 循环取窗,几百万点的记录也能秒切。STRIDE=512意味着相邻片段有一半重叠,这能显著增加样本数,但代价是片段之间高度相关——后面划分训练/测试集时必须按groups分组,不能随机打乱,否则同一段连续信号会同时出现在训练和测试里,评估结果全是虚的。scale取全局最大绝对值而不是标准差,是为了让生成器最后一层的Tanh输出区间[-1, 1]和真实数据严格对齐;如果只除标准差,峰值会溢出到 3~5,Tanh永远学不到,生成波形的尖峰就被削平了。

2.3 环境与最小可运行验证

依赖只有五个包,单卡 8 GB 显存完全够用,纯 CPU 也能跑——1024 点的小网络,一个 epoch 通常几秒到十几秒。

python -m venv .venv && source .venv/bin/activate pip install torch numpy scipy scikit-learn matplotlib python tools/smoke_test.py --data data/bearing.npz

冒烟测试这一步别省。我见过太多次“训了一晚上发现数据全是 NaN”的情况,根因往往是一段静音记录被当作有效样本切了进去,或者某个通道的传感器掉线导致整段是常数。

import numpy as np d = np.load("data/bearing.npz", allow_pickle=True) X, y, g = d["X"], d["y"], d["g"] print("X", X.shape, X.dtype, "range", float(X.min()), float(X.max())) print("每类样本数", {str(k): int((y == k).sum()) for k in np.unique(y)}) print("工况", np.unique(g)) # 抽查一条的频谱,确认直流已去掉、能量分布正常 f = np.abs(np.fft.rfft(X[0])) print("低频能量占比", float(f[:5].sum() / f.sum())) print("标准差 > 0 的样本占比", float((X.std(axis=1) > 1e-6).mean()))

判断标准很直接:X的形状应该是(N, 1024),取值范围紧贴[-1, 1];每一类的样本数最好都在 200 条以上,某一类只有二三十条的话,生成器基本学不出分布,只能靠第 5.3 节的办法缓解;低频能量占比低于 5% 说明去直流成功;标准差那一行必须是 1.0,出现任何小于 1 的值都意味着混进了死信号,直接回数据源头查。

3. 生成器与评论家的写法:一维卷积怎么搭才不塌

一维信号和图像的差别,比想象中大。图像里相邻像素的空间相关是各向同性的,二维卷积核天然适配;振动信号是单变量的时间序列,冲击是瞬态的、衰减是指数的、周期性靠的是机械结构而不是邻域平滑。照搬 DCGAN 的层数和通道数,十有八九训出平滑的假正弦。

3.1 生成器:100 维噪声到 1024 点波形的四次上采样

生成器的目标是把 100 维的隐变量映射成 1024 点的波形。资源包的做法是先一次性“撑”到(256, 64)的张量,再用四次转置卷积做 2 倍上采样:64 → 128 → 256 → 512 → 1024。第一次用大卷积核(64)一步到位,是为了让隐变量的每一个维度都能影响到整条波形的全局形态;后续几次用小核(4)逐级上采样,负责补细节。

小的改动是:第一层通道数给到 256 而不是 512。振动信号的信息量远小于图像,通道堆太宽会直接导致过参数化,生成器几个 batch 就把判别器打趴下,接下来就是模式坍塌。

import torch import torch.nn as nn class Generator1D(nn.Module): def __init__(self, z_dim=100, base=256): super().__init__() self.net = nn.Sequential( # (B, z_dim, 1) -> (B, 256, 64),一次性撑开全局结构 nn.ConvTranspose1d(z_dim, base, kernel_size=64, stride=1, padding=0), nn.BatchNorm1d(base), nn.ReLU(True), nn.ConvTranspose1d(base, base // 2, 4, 2, 1), # 64 -> 128 nn.BatchNorm1d(base // 2), nn.ReLU(True), nn.ConvTranspose1d(base // 2, base // 4, 4, 2, 1), # 128 -> 256 nn.BatchNorm1d(base // 4), nn.ReLU(True), nn.ConvTranspose1d(base // 4, base // 8, 4, 2, 1), # 256 -> 512 nn.BatchNorm1d(base // 8), nn.ReLU(True), nn.ConvTranspose1d(base // 8, 1, 4, 2, 1), # 512 -> 1024 nn.Tanh(), # 输出压到 [-1, 1],和数据侧的全局缩放对齐 ) def forward(self, z): return self.net(z).squeeze(1) # (B, 1024)

kernel_size=64, stride=1的第一层是整个生成器的地基,参数量大约占一半。想缩短训练时间可以把它降到 32 并把base同步减半,但波形的高频细节会明显变糊——kernel_size决定了隐变量能“看到”多宽的波形区间,太小的话每个隐变量维度只能影响局部,生成出来的样本在 100 ms 尺度上缺乏连贯性。Tanh不能换成Sigmoid,因为数据缩放到[-1, 1]之后负半轴是有意义的(冲击的负向加速度),Sigmoid会把负半轴全压扁。

3.2 评论家:不要 Sigmoid,也不要 BatchNorm

评论家(WGAN 里不再叫判别器)负责给每条波形打一个实数分,分数越高代表越像真实数据。因为它输出的是 Wasserstein 距离里的势函数值,不是概率,所以最后一层绝对不能接Sigmoid——接了就把输出限死在[0, 1],梯度信号被压缩,训练立刻退化成普通 GAN 的对抗博弈。

归一化层这里有个取舍。WGAN-GP 要求梯度惩罚项对每个样本独立计算,BatchNorm在训练时会引入跨样本的统计量,导致惩罚项和实际梯度对不上,训练会出现周期性的剧烈震荡。所以常见做法是换成对单样本做归一化的层。但InstanceNorm在振动信号上有个副作用:它对每个样本逐通道做零均值单位方差,等于把这条波形的绝对幅值抹掉了,而幅值恰恰对应故障严重程度。资源包的处理方式是干脆不加归一化层,只用LeakyReLU控制梯度,网络浅(5 层),不加归一化也稳得住。

class Critic1D(nn.Module): def __init__(self, ch=32): super().__init__() def block(cin, cout): # 不加 BatchNorm/InstanceNorm:前者破坏逐样本梯度惩罚, # 后者会抹掉幅值信息,而幅值正是故障严重程度的载体 return nn.Sequential( nn.Conv1d(cin, cout, 4, 2, 1), nn.LeakyReLU(0.2, inplace=True), ) self.features = nn.Sequential( block(1, ch), # 1024 -> 512 block(ch, ch * 2), # 512 -> 256 block(ch * 2, ch * 4), # 256 -> 128 block(ch * 4, ch * 8), # 128 -> 64 block(ch * 8, ch * 8), # 64 -> 32 ) self.head = nn.Linear(ch * 8 * 32, 1) def forward(self, x): h = self.features(x.unsqueeze(1)) return self.head(h.flatten(1)).squeeze(1) # 线性输出,无激活

ch=32是刻意压小的。评论家比生成器强太多,是 WGAN 训练不稳的头号原因;把评论家的通道数控制在生成器的八分之一左右,配合n_critic=5,两边能力大致平衡。最后一层Linear的输入维度是ch * 8 * 32,其中 32 来自 1024 经过五次步长 2 的卷积(1024 / 32 = 32)。改切片长度必须同步改这个数字,否则flatten之后的维度对不上,会直接报错——这也是新手换窗口后最常撞的墙。

3.3 权重裁剪还是梯度惩罚:两种约束的取舍

WGAN 要保证评论家满足 1-Lipschitz 连续,否则 Wasserstein 距离的估计就是错的。原始版本用权重裁剪,把评论家所有参数硬截到[-c, c];改进版用梯度惩罚,在真假样本的连线上采样,约束梯度范数接近 1。

维度权重裁剪梯度惩罚(WGAN-GP)
实现复杂度十几行,每步clamp_需要二次求导,多写一个函数
训练稳定性裁剪系数难调,容易梯度消失或爆炸稳,默认 λ=10 基本不用动
单步耗时低高约 30%~50%
生成质量中等,细节偏平滑明显更好,冲击形态更锐利
适用场景快速验证、算力紧张正式实验、要拿结果做下游任务

资源包两种都实现了,默认走梯度惩罚。裁剪版本留着不是为了生产,是为了做对照——当 GP 版本训崩的时候,切回裁剪版本跑 500 步,如果它反而能出个大概形状,说明问题出在惩罚项的 λ 或者评论家结构上,而不是数据坏了。这个二分排查法在实操里很省时间。

def gradient_penalty(critic, real, fake, device, lambda_gp=10.0): B = real.size(0) eps = torch.rand(B, 1, device=device) # 每个样本一个独立插值系数 interp = (eps * real + (1 - eps) * fake).requires_grad_(True) score = critic(interp) grad = torch.autograd.grad( outputs=score, inputs=interp, grad_outputs=torch.ones_like(score), create_graph=True, retain_graph=True, only_inputs=True, )[0] # 逐样本求 L2 范数再取均值,不能先 flatten 再整体求范数 gp = ((grad.flatten(1).norm(2, dim=1) - 1) ** 2).mean() return lambda_gp * gp

eps的形状是(B, 1)而不是标量,这一点很关键:整批共用同一个插值系数,等价于只在真假分布之间的一条直线上采样,惩罚项的覆盖范围严重不足,训练后期会出现评论家在某些区域梯度失控。create_graph=True不能漏,梯度惩罚本身要对梯度求导,属于二阶优化。lambda_gp=10是原论文的默认值,实测在振动信号上通常 5~20 都稳,超过 30 会让生成器学得太慢,低于 1 则约束形同虚设。

4. 训练循环:n_critic=5 与损失符号的先后顺序

网络写对了只是及格线,训练循环里符号写反、更新顺序写反,一样白跑。这一章把 WGAN 的迭代节奏掰开讲,再给一套完整的训练脚本和三层验证指标。

4.1 一次迭代里到底更新几次

WGAN 的核心是:评论家要先被训到“接近最优”,生成器才更新一步,这样生成器拿到的梯度才是 Wasserstein 距离的真实梯度,而不是一个随机噪声。n_critic=5是论文给的默认值,含义是每更新一次生成器,评论家更新五次。

有个反直觉的点:评论家的损失不能取绝对值看“降不降”。它的物理意义是E[D(fake)] - E[D(real)],理想情况下这个值应该缓慢下降,但永远不会到 0——Wasserstein 距离衡量的是两个分布的差异,只要生成器还没完美复制真实分布,它就一直是正数。判断收敛看的是趋势和波动幅度,而不是某个阈值。当这个估计值在几百步内的波动小于 0.05,而且生成样本的时域特征已经和真实样本接近,就可以停了。

另外,WGAN 里没有“判别准确率”这个概念。评论家输出的是实数分数,不是概率,算 accuracy 需要人为设阈值,结果毫无意义。看到有人在 WGAN 训练日志里打印D_acc,基本可以判断他是从 DCGAN 改过来的,还没转过弯。

4.2 完整训练脚本与参数含义

import torch from torch.utils.data import DataLoader, TensorDataset Z_DIM, N_CRITIC, LAMBDA_GP = 100, 5, 10.0 LR, BATCH, STEPS = 1e-4, 64, 20000 G = Generator1D(Z_DIM).to(device) D = Critic1D().to(device) # beta1=0.5 是 GAN 系列的惯例,动量太大会让对抗训练震荡 optG = torch.optim.Adam(G.parameters(), lr=LR, betas=(0.5, 0.9)) optD = torch.optim.Adam(D.parameters(), lr=LR, betas=(0.5, 0.9)) loader = DataLoader(TensorDataset(torch.from_numpy(X_train).float()), batch_size=BATCH, shuffle=True, drop_last=True) step = 0 while step < STEPS: for (real,) in loader: real = real.to(device) bs = real.size(0) # ---- 阶段一:评论家更新 N_CRITIC 次 ---- for _ in range(N_CRITIC): z = torch.randn(bs, Z_DIM, 1, device=device) fake = G(z).detach() # detach 必须加,否则梯度会串到 G d_real = D(real).mean() d_fake = D(fake).mean() gp = gradient_penalty(D, real, fake, device, LAMBDA_GP) loss_d = d_fake - d_real + gp # 最小化它 = 拉大真假分数差 optD.zero_grad(); loss_d.backward(); optD.step() # ---- 阶段二:生成器更新 1 次 ---- z = torch.randn(bs, Z_DIM, 1, device=device) loss_g = -D(G(z)).mean() # 负号不能丢 optG.zero_grad(); loss_g.backward(); optG.step() step += 1 if step % 500 == 0: w_dist = (d_real - d_fake).item() # 当前 W 距离的样本估计 print(f"step {step:6d} | W≈{w_dist:+.4f} | gp={gp.item():.4f} " f"| loss_g={loss_g.item():+.4f}") torch.save(G.state_dict(), f"ckpt/G_{step:06d}.pth") if step >= STEPS: break

几个参数值得单独说。LR=1e-4对 WGAN-GP 是安全区间,2e-4也能跑但更容易在 5000 步左右出现评论家梯度爆炸,5e-5则太慢,两万步可能还没收敛。BATCH=64是显存和梯度估计方差之间的折中,1024点的小样本下128也可以,但梯度惩罚的二次求导会吃掉不少显存。LAMBDA_GP调到 20 以上时,可以把N_CRITIC降到 3,总训练时间反而更短。

fake = G(z).detach()里的detach是最容易漏的一行。不加的话,评论家的损失反向传播会穿过生成器,虽然 PyTorch 不会报错,但生成器会在“评论家更新阶段”被意外地按错误的梯度推一把,表现出来就是训练前期好像还行,一万步之后突然崩掉,而且无从排查。这种玄学问题我吃过一次亏,从那以后训练循环里每一处detach都要肉眼过一遍。

4.3 判断“像不像”:三个层次的验证指标

只看损失曲线判断生成质量,基本等于闭眼开车。资源包的评估模块分三层,从粗到细逐级确认。

第一层,时域统计特征。峭度(kurtosis)对冲击最敏感,正常轴承振动接近高斯分布,峭度在 3 附近;出现剥落类故障后,冲击让峭度显著抬升到 5~15。均值、方差、偏度、峰值因子也要一起看,单看峭度容易被异常的离群点带偏。

第二层,包络谱。时域特征只能证明“有冲击”,证明不了“冲击的节奏对不对”。对信号做 Hilbert 变换取包络,再对包络做 FFT,故障特征频率及其谐波应该出现在明确的位置上。生成样本的包络谱峰值如果对不上,说明网络学到了冲击的形态但没学到周期,这种样本拿去做增广是有害的。

第三层,t-SNE 与下游任务。把真实样本和生成样本一起降到二维,看两类点是否混在一起;再拿生成样本去增广训练集,看分类器在独立测试集上的准确率有没有提升。前两层是必要条件,第三层才是充分条件。

import numpy as np from scipy.stats import kurtosis, skew from scipy.signal import hilbert def td_features(x): rms = np.sqrt((x ** 2).mean()) peak = np.abs(x).max() return dict( rms=float(rms), kurt=float(kurtosis(x, fisher=False)), # 正常≈3,冲击型故障显著抬升 skew=float(skew(x)), crest=float(peak / (rms + 1e-8)), # 峰值因子,对早期故障敏感 ) def envelope_spectrum(x, fs=12000, n=2048): env = np.abs(hilbert(x)) env = env - env.mean() # 去包络直流,否则主峰永远在 0 Hz spec = np.abs(np.fft.rfft(env * np.hanning(len(env)), n=n)) return np.fft.rfftfreq(n, 1 / fs), spec real_f = np.array([list(td_features(r).values()) for r in X_test]) fake_f = np.array([list(td_features(f).values()) for f in X_gen]) print("真实均值", real_f.mean(0).round(3)) # 顺序: rms / kurt / skew / crest print("生成均值", fake_f.mean(0).round(3))

fisher=False让峭度以 3 为高斯基准,比默认的超额峭度直观。np.hanning窗加在包络上而不是原始信号上,是为了压住包络序列两端的突变,否则频谱会拖出一片旁瓣,把真正的故障频率淹掉。n=2048是补零后的 FFT 长度,决定了频率分辨率,12000 Hz 采样下约 5.86 Hz 一格,足够分辨常见故障特征频率。

实测下来,一份训练到位的模型,生成样本和真实样本在这四个特征上的均值差距通常能压到 10% 以内;峭度差距超过 30% 基本可以判定生成器没学到冲击,回去查第 5.1 节。

5. 避坑与排查:五条踩过的坑

5.1 生成波形是一条平滑曲线,冲击全没了

现象:训练几千步后,生成样本画出来是缓慢起伏的曲线,峭度只有 2 点几,比真实样本的 8 差一大截,包络谱干干净净没有任何峰。

原因:生成器的感受野不够,或者第一层kernel_size太小。1024 点的波形如果靠六七层小卷积核逐级堆出来,中间任何一层的上采样失真都会被放大,最后输出的高频成分被平均掉了。另一种可能是数据侧做过逐样本标准化,冲击的尖峰被压平,网络自然学不到。

解决:先确认数据侧的缩放是全局系数、不是逐样本 z-score;再把生成器第一层的kernel_size从 64 提到 128,或者把base从 256 加到 512。改完重训,一般 3000 步内峭度就能爬到 6 以上。

5.2 评论家损失一路走低,生成器直接 NaN

现象:前 2000 步正常,之后loss_g突然变成nan,评论家输出的绝对值飙到几百。

原因:九成是梯度惩罚项没生效或者 λ 太大。检查两个点:eps是不是标量、create_graph有没有设成True。另一个高频原因是评论家太强,通道数给到 128 以上配合 5 次内循环,生成器的梯度会指数级放大。

解决:把评论家通道压到 32,LAMBDA_GP从 10 降到 5,LR降到 5e-5 重跑。如果还是 NaN,把评论家换成权重裁剪版本跑 500 步做对照——能跑通就说明是 GP 实现的问题,跑不通就是数据里有异常值,回去看冒烟测试的range那行输出。

5.3 一千条生成样本长得几乎一样

现象:采样 1000 条生成波形,两两之间的相关系数普遍在 0.9 以上,t-SNE 上挤成一个小团,下游增广后准确率反而下降。

原因:典型的模式坍塌。最常见的是n_critic设得太小(比如 2),评论家没被训到位,生成器只要找到一个小区域能骗过它就不动了。其次是某一类故障的真实样本太少(少于 50 条),网络只能记住那几条的形态。

解决:N_CRITIC提到 5~8;隐变量维度从 100 降到 64 反而有助于缓解,因为过大的 z 空间让生成器更容易找到“捷径”;如果某类样本确实少,先做同类样本的滑窗加密(STRIDE从 512 降到 256),把样本数翻倍再训。

5.4 指标好看,换分类器就掉点

现象:生成样本的时域特征、包络谱都对得上,t-SNE 也混得不错,但拿去做数据增广,分类器在独立测试集上的准确率从 78% 掉到 74%。

原因:数据泄漏。切片用了 50% 重叠,如果划分训练/测试时随机打乱,相邻片段会同时出现在两边,测试集本质上是训练集的近邻。生成器学到的是“这些样本”,不是“这个分布”,增广出来的东西自然贴不住测试集。另一处泄漏是scale用了全量数据的最大值,评估时天然占便宜。

解决:划分必须按groups(工况)分组,用sklearn.model_selection.GroupKFold或手工按工况切;scale只在训练集上统计,测试集用同一个系数缩放。这两处改完,增广的收益一般会从负数变成 2~5 个百分点。

5.5 包络谱主频对不上故障特征频率

现象:生成样本的包络谱有峰,但峰值位置比真实样本偏了 20~40 Hz,谐波关系也不对。

原因:窗口长度不够,冲击个数太少,网络学不到周期。1024 点在 12 kHz 下是 85 ms,如果故障特征频率在 100 Hz 附近,一个窗口里只有 8 个冲击,统计上勉强够;转速升高到 1800 rpm 以上、特征频率到 150 Hz 时,85 ms 只装得下 12 个,但边界效应会吃掉一两个,网络学到的周期就偏了。另一种可能是生成器的上采样层数过多,相位信息在多次转置卷积中被稀释。

解决:把WIN提到 2048,同时把生成器第一层的kernel_size提到 128、上采样次数减到三次(2048 从 256 起步,三次翻倍到 2048)。窗口变长后单条样本信息量增加,BATCH可以降到 32 省显存。另外,训练前用真实样本的包络谱峰值反推一下预期频点,写进评估脚本做断言,比事后肉眼看谱图靠谱得多。

6. 接进诊断流程:三组对照实验与一个习惯

生成器训完只是半成品,真正决定这份资源值不值得用的,是它能不能让下游分类器的指标动起来。我一般固定一套对照实验:同一个 1D-CNN 分类器(三层卷积 + 全局平均池化,参数量控制在 5 万以内),同一组按工况分组的 5 折,只改训练集构成。

组别训练集构成测试集准确率宏 F1备注
A 基线仅真实样本(约 900 条)0.7810.762少数类召回只有 0.58
B 增广真实 + 合成(1:2,按类补齐)0.8460.831少数类召回升到 0.79
C 纯合成仅合成样本(1800 条)0.6120.574频带能量分布偏移明显
D 混合真实 + 合成(1:1)+ 高斯噪声0.8380.822与 B 相差在噪声范围内

B 组相对 A 组的提升是真金白银,主要在少数类上。C 组的结果更值得记下来:生成样本单独用的时候会崩,说明它复制的是“真实样本邻域内的分布”,而不是整个故障物理过程——合成信号缺少真实传感器的高频噪声底、缺少转速波动带来的相位漂移。这也解释了为什么 B 组按 1:2 比例补齐就够了,再往上堆合成样本,边际收益迅速衰减。

具体怎么补,我习惯按类做而不是整体扩增:统计每一类的真实样本数,以最多的一类为基准,只对样本数不足基准一半的类生成补充样本,生成数量控制在真实数量的两倍以内。这个做法在三个不同数据集上试过,比无差别全部扩增稳定得多。

还有一个验证习惯值得单独说:每次生成完,先做一次“反查”。从生成样本里随机挑 20 条,和真实样本混在一起,让一个不做增广、只用真实数据训出来的分类器去判,看它能不能把这些合成样本判成对应的故障类型。准确率超过 80%,说明生成的样本在决策边界上是“像”的,可以进下游;低于 60%,哪怕包络谱再漂亮,也别往训练集里塞。这个反查只花两分钟,但能挡掉大部分自欺欺人的结果。

从那以后我每次训完一个生成模型,不管指标多好看,都强制走一遍「20 条反查 + 按类补齐比例检查 + 分组划分复核」这三步,一步不落——翻车翻得最惨的一次,就是包络谱对得严丝合缝,结果合成样本里混进了相邻工况的片段,增广之后分类器在变转速场景下直接掉了 9 个百分点,回头查了两天才定位到切片阶段的工况标签写错了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询