简介:这份资源面向从事轴承故障诊断、信号处理与深度学习研究的工程师及学生,提供一套基于WGAN生成一维故障轴承振动信号的完整Python实现,用于缓解真实故障样本稀缺、类别不平衡等数据增强难题。压缩包共19个文件,约22.03MB,包含2个Python脚本(训练与测试入口)、2个MAT数据文件、9张生成信号对比图,以及TensorFlow检查点相关文件(meta、index、data与checkpoint)和说明文档,结构清晰,便于直接复现与二次开发。运行环境要求Python 3.5以上、TensorFlow GPU版及numpy、scipy等依赖,训练脚本支持通过命令行调整学习率、迭代轮数、采样频率,并可在9类轴承信号中切换训练数据。目前已有3386人学习下载,读者可借此掌握WGAN在一维时序信号生成中的网络搭建、训练调参与效果评估思路,并对照生成图像验证模型收敛情况。
1. 用 WGAN 生成轴承故障信号:为什么判别器反而不能太强
轴承振动信号做故障诊断,最头疼的从来不是模型结构,而是样本不够。真实工况下,故障轴承的振动数据往往只有几十条,甚至几条,而正常样本却成千上万。拿这种极度不平衡的数据去训分类器,模型会直接学会“全猜正常”,准确率看着挺高,实际一个故障都抓不住。数据增强是常规解法,但传统方法——加噪声、时间缩放、频域扰动——本质上只是把已有样本“揉一揉”,生成不出新的故障模式,多样性提升有限。
生成对抗网络(GAN)看起来是个更彻底的方案:让生成器学出故障信号的真实分布,再采样出无限多的合成样本。但标准 GAN 在振动信号上翻车是常态,训练不稳定、模式崩塌、梯度消失,随便一个都能让整个流程报废。WGAN(Wasserstein GAN)就是冲着这些玄学问题来的——它把判别器从“真/假二分类器”改造成“打分器”,用 Wasserstein 距离替代 JS 散度,理论上要求判别器不能太强,否则梯度就没了。
这篇要拆的,就是一份用 WGAN 生成一维故障轴承振动信号的完整实现。它解决的核心问题很具体:在样本稀缺的工业场景下,如何稳定地生成具有真实故障特征的振动信号。搞懂它,你不仅能理解 WGAN 为什么比标准 GAN 稳,还能看到一维信号生成和图像生成在工程实现上的关键差异——这些差异,恰恰是很多教程不会告诉你的。
2. WGAN 的底层机制:从 JS 散度到 Wasserstein 距离
2.1 标准 GAN 到底哪里不稳
标准 GAN 的判别器输出一个 0 到 1 之间的概率,表示输入是真实样本的概率。生成器的目标是最小化判别器识别出假样本的概率,判别器的目标是最大化区分真假的能力。这个博弈的数学本质是最小化真实分布和生成分布之间的 JS 散度。
问题出在 JS 散度的性质上。当两个分布完全没有重叠时——这在训练初期几乎是必然的——JS 散度是一个常数,梯度为零。判别器越强,越容易把真假样本完全分开,生成器收到的梯度就越接近零,参数根本更新不动。这就是标准 GAN 训练中“判别器太强导致生成器学不动”的底层原因。
常见的缓解手段是限制判别器的训练轮数、加标签平滑、用谱归一化,但这些都是在绕路,没有触及根因。
2.2 Wasserstein 距离为什么能提供有效梯度
WGAN 换了一个度量:Earth Mover's Distance,也叫 Wasserstein 距离。直观理解是,把生成分布“搬”成真实分布所需的最小搬运成本。它的关键性质是:即使两个分布没有重叠,Wasserstein 距离仍然能反映它们之间的距离,梯度不会消失。
但 Wasserstein 距离的定义里有一个“取上确界”的操作,要求函数满足 1-Lipschitz 连续。原始 WGAN 用权重裁剪来近似这个约束——每次更新判别器参数后,把权重强制裁剪到 [-c, c] 区间内。这个做法简单但粗暴,容易导致权重集中在边界值上,判别器退化成极简函数。
这份实现里,判别器(在 WGAN 中叫 Critic,评论器)的输出不再经过 Sigmoid,直接输出一个实数分数。损失函数也不再用二元交叉熵,而是:
# Critic 损失:最大化真实样本分数与生成样本分数之差 critic_loss = -torch.mean(critic(real_data)) + torch.mean(critic(fake_data)) # Generator 损失:最大化生成样本的 Critic 分数 generator_loss = -torch.mean(critic(fake_data))注意这里没有 Sigmoid,没有 BCE,Critic 输出的是无界实数。torch.mean取的是整个 batch 的平均分数,而不是逐样本的交叉熵。这个改动看起来小,但它把整个优化目标从“分类正确率”变成了“分布距离”,梯度性质完全不同。
2.3 权重裁剪的实现细节与参数选择
权重裁剪是 WGAN 区别于后续 WGAN-GP 的标志性操作。在这份实现中,每次 Critic 参数更新后,会遍历所有参数并进行裁剪:
# 权重裁剪:将 Critic 的每个参数限制在 [-clip_value, clip_value] for p in critic.parameters(): p.data.clamp_(-clip_value, clip_value)clip_value通常取 0.01 到 0.1 之间。取值太小,Critic 表达能力被严重限制,学不到有意义的分数函数;取值太大,1-Lipschitz 约束形同虚设,Wasserstein 距离的估计偏差变大。这份实现里取的是 0.01,属于偏保守的选择,适合振动信号这种维度不高、分布相对简单的场景。
还有一个容易被忽略的细节:Critic 每更新一次,Generator 才更新一次,但 Critic 的更新次数通常是 Generator 的 5 倍。这个比例不是随便定的——Wasserstein 距离的估计需要 Critic 足够准确地逼近真实距离函数,如果 Critic 训练不充分,生成器收到的梯度方向就是错的。
提示:权重裁剪是 WGAN 原始论文的做法,后续的 WGAN-GP 用梯度惩罚替代了它,效果更好但计算量更大。这份实现选择权重裁剪,是在效果和计算成本之间做的权衡。
3. 一维振动信号的生成器与判别器设计
3.1 为什么一维信号不能照搬图像 GAN 的结构
图像 GAN 的生成器通常从 100 维噪声开始,经过多次转置卷积逐步放大到 64×64 或 256×256。振动信号是一维时序数据,长度通常在 1024 到 4096 个采样点之间,直接套用图像 GAN 的架构会出问题。
第一个问题是感受野。图像的空间相关性是局部的,卷积核在二维平面上滑动能有效捕捉纹理。振动信号的周期性特征跨越整个时间轴,如果只用小卷积核堆叠,深层网络才能覆盖足够长的时间范围,训练难度陡增。
第二个问题是上采样的方式。图像生成常用的最近邻插值或转置卷积,在一维信号上容易引入棋盘伪影,表现为生成信号中出现规律性的高频毛刺。这份实现里,生成器用的是全连接层加一维转置卷积的混合结构,先通过全连接层把噪声映射到足够大的维度,再用一维转置卷积做精细调整。
3.2 生成器的层级结构与参数含义
生成器的核心逻辑是:输入一个 100 维的噪声向量,输出一条与真实振动信号等长的一维序列。中间经过的每一层都在做“从抽象到具体”的映射。
class Generator(nn.Module): def __init__(self, noise_dim=100, signal_length=1024): super().__init__() self.fc = nn.Sequential( nn.Linear(noise_dim, 256), nn.LeakyReLU(0.2), # 负斜率 0.2,避免神经元死亡 nn.Linear(256, 512), nn.LeakyReLU(0.2), nn.Linear(512, signal_length), nn.Tanh() # 输出归一化到 [-1, 1] ) def forward(self, z): return self.fc(z)noise_dim=100是经验值,太小会导致生成多样性不足,太大则训练收敛慢。signal_length=1024对应振动信号的采样长度,这个值必须和真实数据一致,否则 Critic 无法比较。LeakyReLU(0.2)的负斜率让负值区域也有梯度,避免 ReLU 在生成器中常见的“死亡”问题。最后的Tanh把输出压到 [-1, 1],前提是真实数据也做了相同的归一化——这一步如果忘了,Critic 收到的真假样本尺度不一致,训练必然失败。
3.3 Critic 的设计与梯度惩罚的取舍
Critic 的结构和生成器对称,但方向相反:输入一条一维信号,输出一个实数分数。
class Critic(nn.Module): def __init__(self, signal_length=1024): super().__init__() self.fc = nn.Sequential( nn.Linear(signal_length, 512), nn.LeakyReLU(0.2), nn.Linear(512, 256), nn.LeakyReLU(0.2), nn.Linear(256, 1) # 输出标量分数,无激活函数 ) def forward(self, x): return self.fc(x)注意最后一层没有 Sigmoid,也没有任何激活函数。这是 WGAN 和标准 GAN 在代码层面最直观的区别。Critic 输出的分数可以是任意实数,正数表示“更像真实样本”,负数表示“更像生成样本”。
这份实现没有用梯度惩罚(WGAN-GP),而是用了权重裁剪。梯度惩罚需要在每个 batch 中计算 Critic 对输入插值样本的梯度范数,计算量大约增加 30% 到 50%。对于振动信号这种一维数据,权重裁剪已经能提供足够的稳定性,没必要引入额外开销。但如果你的信号长度超过 4096,或者故障模式特别复杂,梯度惩罚的收益会明显超过成本。
3.4 训练循环中的关键顺序
WGAN 的训练循环有一个严格的顺序:先更新 Critic 多次,再更新 Generator 一次。这个顺序不能反,否则 Wasserstein 距离的估计会严重偏离。
for epoch in range(num_epochs): for _ in range(critic_steps): # 默认 5 次 real_data = next(real_loader) z = torch.randn(batch_size, noise_dim) fake_data = generator(z).detach() # detach 防止梯度传到 Generator critic_loss = -torch.mean(critic(real_data)) + torch.mean(critic(fake_data)) critic_optimizer.zero_grad() critic_loss.backward() critic_optimizer.step() for p in critic.parameters(): # 权重裁剪 p.data.clamp_(-clip_value, clip_value) # 更新 Generator z = torch.randn(batch_size, noise_dim) fake_data = generator(z) generator_loss = -torch.mean(critic(fake_data)) generator_optimizer.zero_grad() generator_loss.backward() generator_optimizer.step()fake_data.detach()这一步很关键。如果不 detach,Critic 的梯度会回传到 Generator,导致 Generator 在 Critic 更新阶段就被间接修改,整个训练逻辑就乱了。critic_steps=5是原始论文推荐的值,实际使用中可以根据数据复杂度调整到 3 到 10 之间。
4. 训练避坑:振动信号生成中的常见翻车现场
4.1 生成信号全是噪声,没有任何周期特征
现象:训练几百轮后,生成器输出的信号看起来像白噪声,完全没有振动信号应有的周期性冲击成分。
原因:最常见的原因是真实数据没有做归一化,或者归一化范围不一致。振动信号的幅值范围可能从 0.001 到 10 不等,如果直接送进网络,Tanh 输出的 [-1, 1] 和真实数据的尺度完全不匹配,Critic 会直接学会“看幅值判真假”,根本不关心波形结构。
解决:对每条真实信号单独做 min-max 归一化到 [-1, 1],或者用全局的均值和标准差做标准化。关键是生成器的输出层激活函数要和归一化方式匹配——用 Tanh 就归一化到 [-1, 1],用 Sigmoid 就归一化到 [0, 1]。
4.2 Critic 损失持续下降,Generator 损失持续上升
现象:训练日志里 Critic 损失一路走低,Generator 损失一路走高,生成质量没有任何改善。
原因:这是典型的“Critic 太强”问题。权重裁剪的clip_value设得太大,Critic 学到了一个很锐利的分数函数,真假样本的分数差距被拉得极大,Generator 的梯度方向虽然存在但幅度太小,参数更新几乎无效。
解决:把clip_value从 0.1 降到 0.01 甚至 0.005,同时增加critic_steps到 10 次。如果还不行,考虑换成 WGAN-GP,用梯度惩罚替代权重裁剪,让 Lipschitz 约束更平滑。
4.3 生成信号在不同轮次之间高度相似
现象:每隔几百轮保存一次生成样本,发现不同轮次生成的信号几乎一模一样,只是幅值有微小差异。
原因:模式崩塌。Generator 发现只要生成某一种特定波形就能骗过 Critic,于是所有噪声输入都映射到同一个输出。这在振动信号生成中特别常见,因为故障冲击的波形模式本来就比较单一。
解决:降低 Critic 的更新频率,从 5 次降到 3 次;在 Generator 的损失里加一个多样性正则项,鼓励不同噪声输入产生不同输出;或者增大noise_dim,给 Generator 更多自由度。
4.4 训练后期生成信号出现高频毛刺
现象:训练初期生成信号还算平滑,到后期反而出现大量高频振荡,看起来像信号被“撕裂”了。
原因:Generator 的最后一层用了转置卷积,而转置卷积的卷积核尺寸和步长不匹配时,会在输出中引入棋盘伪影。一维情况下表现为规律性的高频毛刺。
解决:把转置卷积换成线性插值上采样加普通卷积,或者确保卷积核尺寸能被步长整除。这份实现用全连接层直接输出信号长度,就是为了绕开这个问题。
4.5 用生成数据训练分类器,准确率反而下降
现象:把生成的故障样本加入训练集后,分类器在测试集上的准确率不升反降。
原因:生成样本的分布和真实分布有偏差,分类器学到了生成样本的“伪特征”,这些特征在真实测试集上不存在。本质上是生成质量不够,或者生成样本和真实样本的比例失衡。
解决:先用 MMD(最大均值差异)或 FID 的变体定量评估生成分布和真实分布的距离,确认生成质量达标后再用于增强。生成样本和真实样本的比例控制在 1:1 到 1:3 之间,不要用生成样本完全替代真实样本。
5. 从权重裁剪到梯度惩罚:设计取舍与验证思路
权重裁剪和梯度惩罚的取舍,本质上是约束强度和计算成本之间的权衡。权重裁剪把 Critic 的每个参数硬性限制在一个区间内,实现简单,但会导致参数分布集中在边界值上,Critic 的表达能力被压缩。梯度惩罚则是在损失函数里加一项,惩罚 Critic 对输入梯度的范数偏离 1 的程度,约束更平滑,但每个 batch 都要计算梯度范数,训练时间增加 30% 到 50%。
对于振动信号这种一维数据,信号长度通常在 1024 到 4096 之间,网络参数量不大,权重裁剪的副作用相对可控。但如果你的信号长度超过 8192,或者故障模式超过 10 种,Critic 需要更强的表达能力来区分不同模式,这时候梯度惩罚的优势就体现出来了。
验证生成质量不能只看损失曲线。损失曲线只能告诉你训练是否稳定,不能告诉你生成样本是否真实。我一般会走三条验证路径:
| 验证方法 | 操作 | 判断标准 |
|---|---|---|
| 时域波形对比 | 随机抽 5 条真实信号和 5 条生成信号,画在同一张图上 | 生成信号的冲击周期、衰减包络和真实信号肉眼无显著差异 |
| 频域包络对比 | 对真实和生成信号分别做 FFT,比较频谱包络 | 故障特征频率处的峰值位置一致,幅值差异在 3dB 以内 |
| 分类器验证 | 用真实数据训分类器,在生成数据上测准确率 | 准确率不低于真实测试集的 80% |
这三条路径里,频域包络对比是最敏感的。振动信号的故障特征在时域上可能看起来差不多,但在频域上,故障特征频率及其谐波的分布是高度结构化的。如果生成信号在频域上对不上,时域再像也是假的。
还有一个容易被忽略的点:生成样本的标签一致性。用 WGAN 生成故障信号时,生成器学的是某一类故障的分布。如果你把不同故障类型的信号混在一起训练一个生成器,生成出来的信号可能同时包含多种故障特征,标签就没法打了。正确的做法是每类故障单独训一个生成器,或者用条件 WGAN(CWGAN)把标签作为条件输入。
从那以后我每次训 WGAN 之前,都会先把真实数据做一遍频域分析,确认故障特征频率的位置和幅值范围,再开始调网络。这个习惯帮我省掉了大量“训练看起来正常但生成样本没法用”的返工时间。希望帮到你。
本文还有配套的精品资源,点击获取