简介基于沃瑟斯坦生成对抗网络的故障轴承振动信号生成项目面向故障诊断与深度学习交叉领域的开发者与研究者提供一套可直接落地的完整的一维信号生成对抗网络训练与测试方案。压缩包共十九个文件涵盖训练与测试脚本、两组轴承振动数据、模型权重文件以及九张示例输出图整体约二十二兆字节目录划分清晰便于按需检索。目前已有三千三百八十六人学习使用。基于Python的代码支持调整学习率、训练轮数与采样周期等超参数并内置九种不同故障状态的信号数据方便切换对比。同时提供训练所得的模型检查点与可视化图像可直接复现生成效果为轴承样本增强、特征扩展与异常检测实验提供可靠的参考基准。1. GAN-1D 用 WGAN 生成故障轴承振动信号先回答值不值得做做设备故障诊断的同行基本都撞过同一堵墙现场采集的轴承故障样本少得可怜内圈裂纹、外圈点蚀、滚动体磨损这些工况正常数据攒了几十个 G故障数据可能就几十条。拿这点样本去训 CNN 或 LSTM指标再好看也是过拟合的幻觉。GAN-1D 这个方向就是把一维卷积生成对抗网络用在振动时序上配合 WGAN 的 Wasserstein 距离和梯度惩罚让生成器学会复现故障信号的时域波形和频域特征再拿生成样本去扩充训练集。这篇笔记会从网络结构、PyTorch 最小实现、参数调节到验证回测完整走一遍适合正在做故障诊断数据增强、却对 GAN 训练心里没底的工程师。先说结论这个方案能落地但它的价值不在“生成波形好看”而在“补上的故障样本能让诊断模型涨点”——围绕这个目标下面每一步都按可复现的标准来写。2. 理解 GAN-1D 与 WGAN一维时序生成为什么非要用 WGAN 这套路2.1 一维生成和二维生成的本质差异卷积核怎么“看”振动信号图像 GAN 的输入输出是 H×W 的矩阵卷积核在空间维度上滑动提取的是局部纹理和全局结构。振动信号是长度 N 的一维序列比如采样率 12 kHz 下转一圈采集 1024 个点卷积核只能在时间轴上从左往右扫。这意味着 GAN-1D 的归纳偏置和图像模型完全不同它不需要关注“猫耳朵在哪”只需要关注“这个时间片段的幅值包络和相邻片段的统计关系”。我在实际搭建时发现判别器对振动信号的敏感点集中在三个地方冲击成分的幅值内圈故障每转一圈就敲一下、高频衰减振荡的持续时间、以及背景噪声的方差。普通 GAN 的判别器输出的是“真假概率”它很容易找到一个捷径——只盯着峰值高低判断导致生成器学到“把幅值调大就骗过去了”。WGAN 的判别器严格说叫 critic输出的是一个无上界的实数衡量的是两个分布之间的 Wasserstein 距离它逼着生成器在整体分布形状上对齐而不是钻局部特征的空子。2.2 原始 GAN 训练翻车的三个病根以及 WGAN 对应的三处改动原始 GAN 用 JS 散度做优化目标当生成分布和真实分布的重叠区域很小时JS 散度是一个常数梯度直接消失——这就是新手最常见的“判别器 loss 降到底生成器 loss 不动”现象。WGAN 把目标函数换成 Wasserstein 距离即使两个分布完全不重叠梯度依然存在且方向正确从根上解决了训练初期梯度枯竭的问题。第二处改动是去掉输出层的 Sigmoid。原始 GAN 判别器输出 (0,1) 区间的概率WGAN 的 critic 输出一个实数配合 Lipschitz 约束通常用梯度惩罚实现让 critic 的梯度范数被限制在 1 附近。第三处改动是更新节奏原始 GAN 里判别器和生成器要小心翼翼地保持平衡WGAN 里 critic 可以多训几步n_critic 通常取 5因为 Wasserstein 距离估计得越准生成器拿到的梯度信号越干净。这三处改动对应到代码层面就三行损失函数从 BCE 换成“critic(fake) 均值减 critic(real) 均值”、去掉最后一层 Sigmoid、在损失里加一项梯度惩罚。改动量不大但训练稳定性提升是质变的。下面进入正题直接搭一个能跑的最小工程。3. 用 PyTorch 搭建 WGAN 生成轴承振动信号的最小复现3.1 数据预处理把原始振动信号切成固定长度的训练张量轴承振动数据通常是长时序以凯斯西储大学CWRU数据集为例一个文件里是几秒钟的连续采样。切段时我一般取 1024 个点作为一个样本这个长度在 12 kHz 采样率下约 85 ms能覆盖至少一个转频周期转频 30 Hz 时周期约 33 ms又能让故障特征频率在 FFT 后有足够分辨率。切段步长取 512相邻样本有 50% 重叠相当于做了数据增强。切完必须做 z-score 标准化否则生成器要同时学均值和方差训练负担翻倍。import numpy as np import torch from torch.utils.data import Dataset, DataLoader def segment_signal(data, segment_len1024, stride512): samples [] for i in range(0, len(data) - segment_len 1, stride): seg data[i:i segment_len].astype(np.float32) # z-score 标准化消除不同工况下的幅值差异 seg (seg - seg.mean()) / (seg.std() 1e-8) samples.append(seg) return np.stack(samples) # shape: (num_segments, 1024) class VibrationDataset(Dataset): def __init__(self, samples): self.samples torch.from_numpy(samples).unsqueeze(1) # (N, 1, 1024) def __len__(self): return len(self.samples) def __getitem__(self, idx): return self.samples[idx] # 以单个故障类别为例内圈故障 raw np.load(inner_race_fault.npy) # 一维原始振动信号 segments segment_signal(raw) loader DataLoader(VibrationDataset(segments), batch_size64, shuffleTrue)这里有个容易被忽略的细节unsqueeze(1)把每个样本变成 (1, 1024)对应一维卷积的输入通道格式 (batch, channels, length)。DataLoader的shuffleTrue不能省WGAN 对样本顺序比普通 GAN 更敏感如果同批次全是同一转速段的样本批评者估计的 Wasserstein 距离会偏差很大。提示如果你的原始数据是多个文件不同负载、不同转速不要全部混在一起切每个文件单独切段后再合并。转速不同会导致故障特征频率整体偏移混合训练会让生成器学成“四不像”。3.2 生成器与判别器网络定义一维转置卷积与步长为 2 的下采样生成器的输入是 100 维高斯噪声输出是 1024 点的振动序列。网络骨架用转置卷积逐级上采样每次把长度翻倍16 → 32 → 64 → 128 → 256 → 512 → 1024共 6 层。每层后面接 BatchNorm1d 和 ReLU最后一层用 Tanh 把输出压到 [-1, 1]——因为输入数据标准化后幅值基本落在 ±3 以内Tanh 和 z-score 是配套的。import torch.nn as nn class Generator1D(nn.Module): def __init__(self, z_dim100, output_len1024): super().__init__() self.init_len output_len // 64 # 16 self.fc nn.Linear(z_dim, 128 * self.init_len) self.deconv nn.Sequential( nn.ConvTranspose1d(128, 64, 4, stride2, padding1), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), nn.ConvTranspose1d(64, 32, 4, stride2, padding1), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.ConvTranspose1d(32, 16, 4, stride2, padding1), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.ConvTranspose1d(16, 8, 4, stride2, padding1), nn.BatchNorm1d(8), nn.ReLU(inplaceTrue), nn.ConvTranspose1d(8, 4, 4, stride2, padding1), nn.BatchNorm1d(4), nn.ReLU(inplaceTrue), nn.ConvTranspose1d(4, 1, 4, stride2, padding1), nn.Tanh() ) def forward(self, z): x self.fc(z).view(z.size(0), 128, self.init_len) return self.deconv(x)逻辑说明fc先把 100 维噪声映射到 128×162048 维然后 reshape 成 128 个通道、长度 16 的特征图后续每层转置卷积把长度翻倍。kernel_size4、stride2、padding1 是转置卷积“翻倍长度”的标准配置output_len必须是 64 的倍数才能整除到 16这也是我选 1024 而不是 1000 的直接原因。判别器critic是生成器的镜像用普通卷积逐层下采样把长度从 1024 压到 1。注意两点用 LeakyReLU 而不是 ReLU避免 Dead Neuron最后一层不做 Sigmoid直接输出一个标量。class Critic1D(nn.Module): def __init__(self): super().__init__() self.conv nn.Sequential( nn.Conv1d(1, 16, 4, stride2, padding1), nn.LeakyReLU(0.2), nn.Conv1d(16, 32, 4, stride2, padding1), nn.LeakyReLU(0.2), nn.Conv1d(32, 64, 4, stride2, padding1), nn.LeakyReLU(0.2), nn.Conv1d(64, 128, 4, stride2, padding1), nn.LeakyReLU(0.2), nn.Conv1d(128, 1, 4, stride2, padding1), ) def forward(self, x): # x: (B, 1, 1024) return self.conv(x).mean(dim(1, 2)) # scalar per samplemean(dim(1,2))把最后一层输出的所有值取平均等效于一个全局池化。这个操作能显著减少参数量也让 critic 对输入长度的适配更灵活是 WGAN 实现里的常见做法。3.3 WGAN-GP 的损失函数与梯度惩罚实现WGAN 的原始论文用权重裁剪weight clipping来约束 Lipschitz 条件但裁剪值不好调裁剪狠了梯度消失、裁剪松了约束失效。WGAN-GP 用梯度惩罚替代在真实样本和生成样本的连线上随机取插值点要求批评者在该点的梯度范数尽量接近 1。实现时需要对插值点求二阶梯度涉及torch.autograd.grad这是新手最容易被绊倒的地方。def compute_gradient_penalty(critic, real, fake): batch_size real.size(0) eps torch.rand(batch_size, 1, 1, devicereal.device) # 在 real 和 fake 的连线上随机插值 interpolated (eps * real (1 - eps) * fake).requires_grad_(True) output critic(interpolated) grad torch.autograd.grad( outputsoutput, inputsinterpolated, grad_outputstorch.ones_like(output), create_graphTrue, retain_graphTrue )[0] grad grad.view(batch_size, -1) grad_norm grad.norm(2, dim1) return ((grad_norm - 1) ** 2).mean()逻辑说明eps从均匀分布 U(0,1) 采样插值点介于真实样本与生成样本之间requires_grad_(True)必须显式打开否则autograd.grad会报“not part of the graph”的错误。create_graphTrue是为了让梯度惩罚本身也能被反向传播否则整个判别器损失在 backward 时会断掉。最后(grad_norm - 1)^2是惩罚项等价于要求 critic 的梯度范数处处接近 1超参数 λ通常取 10在外面乘。3.4 训练循环主体n_critic 与损失方向WGAN 的训练节奏是“批评者多走几步生成者走一步”。每个 batch 内先冻结生成器、更新 critic 5 次然后冻结 critic、更新生成器 1 次。生成器的损失是-critic(fake)的均值即它希望 fake 的评分越高越好。import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) generator Generator1D().to(device) critic Critic1D().to(device) # WGAN 经验配置Adam 的 lr 取 1e-4betas 用 (0.5, 0.9) opt_g optim.Adam(generator.parameters(), lr1e-4, betas(0.5, 0.9)) opt_c optim.Adam(critic.parameters(), lr1e-4, betas(0.5, 0.9)) n_critic 5 lambda_gp 10.0 z_dim 100 for epoch in range(200): for real_batch in loader: real_batch real_batch.to(device) # 1. 更新 criticn_critic 次 for _ in range(n_critic): z torch.randn(real_batch.size(0), z_dim, devicedevice) fake_batch generator(z) loss_c (torch.mean(critic(fake_batch)) - torch.mean(critic(real_batch)) lambda_gp * compute_gradient_penalty(critic, real_batch, fake_batch)) opt_c.zero_grad() loss_c.backward() opt_c.step() # 2. 更新 generator1 次 z torch.randn(real_batch.size(0), z_dim, devicedevice) fake_batch generator(z) loss_g -torch.mean(critic(fake_batch)) opt_g.zero_grad() loss_g.backward() opt_g.step() if epoch % 20 0: print(fEpoch {epoch} | critic loss: {loss_c.item():.3f} | gen loss: {loss_g.item():.3f}) # 保存生成的样本供后续频域验证 torch.save(fake_batch.detach().cpu(), fgen_samples_{epoch}.pt)参数说明n_critic5是最常用取值如果 critic 的 loss 一直剧烈震荡可以试着提到 10Adam 的betas用 (0.5, 0.9) 而不是默认的 (0.9, 0.999)因为一阶动量系数 0.9 在 GAN 里容易让梯度历史积累过长导致训练震荡。z_dim100是经验值太小的 z 维度会让生成样本多样性不足太大又增加生成器的映射难度。WGAN 的 loss 数值不像普通 GAN 那样有明确的分数含义它只在相对比较时有意义。如果你看到 critic loss 稳定在某个负数附近、生成器 loss 稳定为正这是正常的真正需要警惕的是 loss 绝对值持续上涨或出现 NaN。到这里一个最小可跑的训练流程已经完整了接下来讲那些调参时才真正决定成败的参数。4. 影响生成质量的四个必调参数信号长度、n_critic、λ 与 batch size4.1 信号长度与 z 维度时间窗口决定可分辨的故障频率下限信号长度 1024 不是拍脑袋定的它直接决定 FFT 的频率分辨率。采样率 fs12 kHz 时1024 点的分辨率是 fs/N ≈ 11.7 Hz。轴承内圈故障特征频率通常在 100~500 Hz 之间11.7 Hz 的分辨率足够区分内圈故障约 162 Hz和外圈故障约 107 Hz。如果把信号长度缩到 256分辨率变成 46.9 Hz两个故障特征频率直接糊在一起生成器根本学不到区分度。z 维度影响的是生成样本的多样性空间。我做过一个对比实验z_dim10 时生成样本的主频集中在两三个固定值上包络谱几乎重合z_dim100 时主频有 ±5 Hz 的合理抖动更接近真实信号的自然波动。z 维度再往上加收益递减反而让生成器需要更多 epoch 才能收敛。4.2 n_critic 与学习率批评者不能永远强于生成者n_critic 控制 critic 每轮训练领先生成器的步数。WGAN 的理论要求 critic 先充分收敛才能给出准确的 Wasserstein 距离估计但实战中 n_critic 太大会导致生成器梯度信号过早消失——批评者强到完美区分真假生成器怎么更新都骗不过它loss 卡死。我的一般做法是初始设 5如果生成器 loss 长时间不下降超过 30 个 epoch 毫无变化把 n_critic 降到 3 或 2给生成器更多更新机会。学习率这里单独说因为它是全流程最玄学的一个超参。Adam 默认 lr1e-3 在 WGAN 上几乎必炸loss 会在前几百个 iteration 里冲到 NaN。WGAN-GP 论文推荐的 lr1e-4 不是随意的梯度惩罚项的量级和普通 BCE 损失差一个数量级lr 太大会让惩罚项主导更新方向把网络权重推向震荡。降 lr 是最优先尝试的止损手段比调任何网络结构都管用。4.3 梯度惩罚系数 λ 与 batch size 的相互作用λ 控制 Lipschitz 约束的强度。论文里的标准值是 10但如果你发现生成样本的频谱“过度平滑”所有频率成分幅值几乎相等像白噪声很可能是 λ 偏大把 critic 约束得太死梯度信号失去了对不同频率成分的区分度。反过来如果 critic loss 持续上涨、生成样本出现尖锐的单一频率峰λ 可能偏小。我一般在 5~20 之间网格搜索这个范围已经覆盖了绝大多数工况。batch size 的影响隐蔽但致命。WGAN-GP 的梯度惩罚是对每个样本独立计算的batch size 越小单个 batch 的惩罚项估计方差越大训练曲线抖动越明显。我用 32 的 batch 训练时critic loss 曲线像心电图换到 64 后明显平滑。如果你的 GPU 显存紧张优先保 batch size宁可把网络通道数减半也别把 batch 降到 32 以下。4.4 训练轮数与模型保存策略不要等到最后一轮才留后手WGAN 不像分类任务那样有明确的收敛点生成质量随训练轮数先升后降降的那一段就是过拟合的翻车现场。我的做法是每 20 个 epoch 保存一次生成样本并顺带计算生成样本和真实样本在频域上的 MMD最大均值差异一旦 MMD 开始回升立即回滚到前一个 checkpoint。这个“后悔药”机制看起来笨但比任何早停策略都可靠因为你永远不知道最优轮数在哪里。训练轮数方面200 epoch 是起点不是终点。小数据集几千个片段大概 100 到 150 轮就能学到像样的包络形状但要让冲击成分的周期抖动也真实300 轮以上更稳妥。关键是观察生成样本的包络谱而不是盯着 loss 看loss 平稳不等于生成质量好。5. WGAN 生成轴承信号的 5 个高频踩坑与排查清单5.1 critic loss 不降反升生成器 loss 直接变 NaN现象训练刚开始几十个 iterationcritic loss 从 -5 一路涨到 50随后日志里出现 inf 或 NaN程序报梯度溢出。原因最常见的是学习率过大Adam 默认 lr1e-3 配上梯度惩罚项后权重更新步长超出合理范围梯度在反向传播时数值爆炸。第二个常见原因是数据里混入了 NaN 或幅值异常大的样本比如传感器掉电瞬间的尖峰z-score 标准化后标准差接近 0导致该样本数值异常。解决先把 lr 降到 1e-4这是 WGAN-GP 最稳的起点。如果还炸用torch.nan_to_num兜底梯度再检查原始数据里是否有超过 5 倍标准差的离群点直接剔除。我的排查顺序永远是“数据干净度 → 学习率 → 网络初始化”别一上来就改结构。5.2 生成信号几乎全是高频噪声包络谱没有明显的故障特征峰现象生成的波形在时域上是密密麻麻的毛刺FFT 之后频谱平平的没有集中在某个频段包络谱上压根找不到内圈故障特征频率。原因生成器把噪声直接映射成了白噪声说明它没有学到信号的结构信息只学会了匹配均值方差。这往往是批评者太弱或梯度惩罚过强导致的——批评者对真实样本和生成样本的区分信息没有有效传递回生成器。另一个很现实的原因是训练轮数不够WGAN 生成器在前 80 轮本来就容易输出噪声这是正常过程。解决先排除轮数问题把训练延长到 200 轮再看。如果仍是白噪声检查compute_gradient_penalty的create_graph是否正确很多复制粘贴的代码在梯度惩罚上少了这一个参数导致批评者的梯度信号是断开的生成器拿不到有效反馈。再把 λ 从 10 调到 5放宽对批评者的约束。5.3 三种故障类型最终只生成一种模式崩塌的变种现象同时训练内圈、外圈、滚动体三类故障数据时生成样本的包络谱全部指向同一类故障其他类别凭空消失。原始 GAN 里这个现象叫 mode collapseWGAN 里不常见但会以“critis 太强”的形式出现。原因WGAN 缓解但没根治模式崩塌当某一类故障样本的数量或幅值特征占主导时critic 估计的 Wasserstein 距离被该类别主导生成器发现“生成这一类能最大程度降低损失”于是放弃了其他类别。解决先统计三个类别的样本数量和标准化后的 RMS 幅值保证均衡。训练时每个 batch 强制从三个类别中各取 1/3 样本而不是全局随机采样。如果幅值差异大按类别单独做标准化而不是全部混在一起算均值和方差。这是我在实际项目中遇到的真实翻车现场最后就是用类别均衡采样解决的。5.4 生成信号的故障特征频率总比真实值偏 5~8 Hz现象真实内圈故障特征频率是 162.5 Hz生成的信号包络谱峰值在 157 Hz 左右且每次训练偏移量不同。时域波形肉眼看没问题一算包络谱就对不上。原因频率偏移主要来自两个地方。一是信号长度 1024 导致 FFT 分辨率只有 11.7 Hz峰值落在哪根谱线上有量化误差二是生成器在拼接局部片段时冲击成分的周期抖动引入了额外调制让包络谱的主峰发生偏移。前者是数学误差后者是模型误差。解决把信号长度加到 2048分辨率提升到 5.9 Hz偏移量减半。如果还偏用重心法读取峰值频率——在峰值附近取 3 个谱线做加权平均能进一步把误差压到 1 Hz 以内。这是纯后处理技巧不需要改网络。5.5 同一份代码两次训练结果完全不一样现象相同的超参数、相同的数据集第一次生成的信号有清晰冲击特征第二次全是噪声或者两次的主频峰位置不同。原因PyTorch 默认的随机初始化、DataLoader shuffle 的随机顺序、GPU 上的非确定性算法三者叠加让训练过程成为一个随机过程。WGAN 对初始化状态尤其敏感不同初始化可能收敛到完全不同的局部最优。解决在训练脚本开头固定三处随机种子torch.manual_seed(42)、np.random.seed(42)、random.seed(42)并在加载 DataLoader 时设置generatortorch.Generator().manual_seed(42)。这是工业落地的血泪经验——没有固定种子任何超参数对比实验都是玄学你根本分不清效果差异来自参数还是来自随机性。6. 验证生成信号用频域包络谱和诊断模型回测证明它真的能用生成信号好不好看不算数能提升诊断模型性能才算数。我会做三件事来验证包络谱对比看故障特征频率是否对齐t-SNE 看生成样本与真实样本在特征空间的分布是否重叠最后用诊断模型回测看加入生成数据后准确率的变化。包络谱对比是最直观的验证对生成信号做 Hilbert 变换取包络再对包络做 FFT。from scipy.signal import hilbert import numpy as np def envelope_spectrum(signal, fs12000): analytic hilbert(signal) env np.abs(analytic) spec np.abs(np.fft.rfft(env)) freqs np.fft.rfftfreq(len(env), 1 / fs) return freqs, spec把真实信号和生成信号的包络谱叠在一张图上重点看故障特征频率如 162.5 Hz处是否有对齐的峰值。误差在 3 Hz 以内说明生成器学到了正确的冲击节奏否则回到第 5.4 节排查。t-SNE 验证更省事把真实样本和生成样本分别用同一个预训练特征提取器编码再用sklearn.manifold.TSNE投影到二维平面观察两组点云是否混在一起。如果生成样本聚成一团离真实样本很远说明生成器学到的是另一种“看起来像”的分布不是真正的故障分布。最后做回测用真实故障样本训一个简单的一维 CNN 分类器测试集固定不变然后在训练集里混入 30% 生成样本重新训练对比测试集准确率。涨点 3~5 个百分点是正常收益不涨甚至下降就要怀疑生成样本的质量——这种情况我遇到一次后来发现是生成样本全是同一类别的模式崩塌混入训练集反而加剧了类别不平衡。这三步做完心里就有底了。最终能支撑你上线跑的不是生成波形和真实信号有多像而是加入生成数据之后诊断模型在真实场景的泛化能力有没有实质提升。到现在我仍然会在每次换数据集之后跑一遍这三步验证留着包络谱图和准确率对比表当过程记录。这套流程踩过的坑实在太多从最初的白噪声生成器到后来能稳定涨点每一步都是在碎掉的 loss 曲线里趟出来的。希望帮到你少走几段弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?