ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WGAN-GP生成轴承故障振动信号的工程实践

WGAN-GP生成轴承故障振动信号的工程实践

简介:本资源是一个基于Wasserstein GAN(WGAN)的1D信号生成项目,面向机械故障诊断、智能运维及深度学习信号处理方向的研究者与工程师,旨在解决轴承故障数据稀缺问题,通过生成高质量振动信号辅助模型训练与算法验证。压缩包共19个文件,含2个核心Python脚本(train.py、test.py)、9张训练过程可视化PNG图、2个MATLAB格式数据集(data4train.mat、data_aug.mat)、1个README说明文档及TensorFlow模型检查点文件(.meta/.index/.data),整体22.03MB,结构清晰,便于复现实验与二次开发。已有3388人学习下载,资源提供完整训练流程、可调超参接口(学习率、epoch数、采样间隔)及多类故障信号选择支持(x1–x9),附带详细运行说明与环境依赖清单(Python 3.5+、TensorFlow GPU、NumPy/SciPy),是开展时序信号生成研究的实用型开源实践方案。

1. 故障信号生成不是“造数据”,而是给诊断模型喂出带物理意义的振动波形

你手头有一台轴承振动采集设备,采回来几百组真实故障信号——但想训练一个鲁棒的智能诊断模型?远远不够。标注成本高、故障工况难复现、小样本下模型容易过拟合……这时候,GAN-1D 就不是个玩具模型,而是一套能生成符合机械振动物理特性的1维时序信号的工程化工具。它不生成“看起来像”的噪声曲线,而是用Wasserstein GAN(WGAN)结构约束生成器输出的频谱包络、冲击周期性、衰减趋势等关键特征,让合成信号在FFT、包络谱、Hilbert变换后,能通过领域专家的“肉眼判据”。项目面向的是工业预测性维护场景下的算法工程师和嵌入式AI部署人员:你不需要从零推导WGAN理论,只要把实测的正常/内圈/外圈/滚动体故障振动片段(.npy或.csv格式)丢进去,跑通训练脚本,就能拿到可直接喂给CNN、LSTM或Transformer诊断模型的增强数据集。整个流程完全基于Python生态,无硬件依赖,单卡2060即可完成中小规模训练,且生成器导出为ONNX后,能在边缘端做轻量级信号仿真。


2. 技术栈与架构设计:为什么选WGAN而不是DCGAN或StyleGAN?

2.1 核心动机:1D信号生成的三大硬约束

生成1维振动信号和生成图像有本质区别:

  • 时序连续性不可破坏:图像像素可局部打乱,但振动信号中相邻采样点存在强物理耦合,生成器输出若出现突变阶跃,会导致后续包络解调失败;
  • 频域特性必须可解释:诊断模型常依赖频谱峰值位置(如BPFO/BPFI)、边带间隔、谐波比等指标,生成信号的FFT幅值谱需具备清晰主频+合理谐波结构;
  • 小样本稳定性要求高:实际采集的某类故障可能仅3~5组有效样本(每组4096点),传统GAN易模式坍缩,生成结果高度重复。

WGAN通过Wasserstein距离替代JS散度,梯度更平滑、训练更稳定;其带梯度惩罚(WGAN-GP)的判别器强制满足Lipschitz约束,天然抑制高频噪声震荡——这恰好匹配振动信号对“低频主导+瞬态冲击”的建模需求。相比之下,DCGAN的Sigmoid输出层易导致信号两端截断失真;StyleGAN的AdaIN机制在1D序列上缺乏空间语义支撑,反而引入无关相位扰动。

2.2 整体模块划分与数据流向

项目采用标准PyTorch实现,目录结构精简但职责明确:

gan-1d/ ├── data/ # 原始数据存放(支持.npy/.csv) │ ├── normal/ # 正常轴承振动(4096点/样本) │ ├── inner_fault/ # 内圈故障 │ └── outer_fault/ # 外圈故障 ├── models/ # 核心网络定义 │ ├── generator.py # 一维反卷积生成器(含LeakyReLU+BatchNorm1d) │ └── critic.py # WGAN判别器(即Critic,无Sigmoid,全卷积) ├── train.py # 主训练脚本(含WGAN-GP梯度惩罚实现) ├── generate.py # 推理脚本(支持批量生成+保存为.npy) ├── utils/ # 工具函数 │ ├── dataset.py # 自定义TensorDataset,自动归一化到[-1,1] │ └── plot_utils.py # 时域/频域可视化(含包络谱计算) └── configs/ # 配置文件(yaml格式) └── default.yaml # batch_size: 64, lr_g: 1e-4, lr_c: 1e-4, n_critic: 5...

提示:所有网络层均使用nn.Conv1d而非nn.Linear,避免全连接层破坏时序局部性;生成器最后一层无激活函数,配合输入数据归一化至[-1,1],保证输出动态范围可控。

2.3 生成器(Generator)核心实现解析

生成器采用5层转置卷积(Transpose Conv1D),输入为100维标准正态噪声向量z,输出长度固定为4096点。关键设计在于上采样步长与核大小的协同:

# models/generator.py import torch.nn as nn class Generator(nn.Module): def __init__(self, latent_dim=100, signal_length=4096, ngf=64): super().__init__() self.init_length = signal_length // (2 ** 5) # 4096 -> 128 self.ngf = ngf # 先将噪声向量映射为1D特征图 self.linear = nn.Sequential( nn.Linear(latent_dim, ngf * 8 * self.init_length), nn.LeakyReLU(0.2, inplace=True) ) # 5层反卷积上采样:128 → 256 → 512 → 1024 → 2048 → 4096 self.main = nn.Sequential( nn.ConvTranspose1d(ngf * 8, ngf * 4, 4, 2, 1, bias=False), nn.BatchNorm1d(ngf * 4), nn.LeakyReLU(0.2, inplace=True), nn.ConvTranspose1d(ngf * 4, ngf * 2, 4, 2, 1, bias=False), nn.BatchNorm1d(ngf * 2), nn.LeakyReLU(0.2, inplace=True), nn.ConvTranspose1d(ngf * 2, ngf, 4, 2, 1, bias=False), nn.BatchNorm1d(ngf), nn.LeakyReLU(0.2, inplace=True), nn.ConvTranspose1d(ngf, ngf // 2, 4, 2, 1, bias=False), nn.BatchNorm1d(ngf // 2), nn.LeakyReLU(0.2, inplace=True), nn.ConvTranspose1d(ngf // 2, 1, 4, 2, 1, bias=False) # 输出通道=1 ) def forward(self, z): x = self.linear(z).view(-1, self.ngf * 8, self.init_length) return self.main(x).squeeze(1) # [B, 4096]

逻辑说明:

  • init_length=128是反卷积起点长度,由4096/(2^5)确定,确保5次×2上采样后精准达到目标长度;
  • 每层ConvTranspose1d的kernel_size=4, stride=2, padding=1构成标准2倍上采样(等效于最近邻插值+3×3卷积),避免相位偏移;
  • BatchNorm1d作用于通道维度(ngf*8),稳定训练过程,防止梯度爆炸;
  • 最终squeeze(1)移除冗余通道维,输出形状为[batch_size, 4096],与真实信号一致。

2.4 判别器(Critic)与WGAN-GP梯度惩罚实现

判别器为纯卷积结构,无池化层,通过步长控制感受野。WGAN-GP的关键在于对判别器施加梯度惩罚(Gradient Penalty),强制其Lipschitz连续:

# models/critic.py import torch import torch.nn as nn class Critic(nn.Module): def __init__(self, signal_length=4096, ndf=64): super().__init__() self.ndf = ndf self.main = nn.Sequential( # 输入:[B, 1, 4096] nn.Conv1d(1, ndf, 4, 2, 1, bias=False), # 4096→2048 nn.LeakyReLU(0.2, inplace=True), nn.Conv1d(ndf, ndf * 2, 4, 2, 1, bias=False), # 2048→1024 nn.LeakyReLU(0.2, inplace=True), nn.Conv1d(ndf * 2, ndf * 4, 4, 2, 1, bias=False), # 1024→512 nn.LeakyReLU(0.2, inplace=True), nn.Conv1d(ndf * 4, ndf * 8, 4, 2, 1, bias=False), # 512→256 nn.LeakyReLU(0.2, inplace=True), nn.Conv1d(ndf * 8, 1, 4, 2, 1, bias=False) # 256→128 → 输出标量 ) def forward(self, x): return self.main(x).view(-1) # [B] 标量输出 # train.py 中的梯度惩罚计算(关键!) def gradient_penalty(critic, real_data, fake_data, device): batch_size = real_data.size(0) # 生成插值系数 ε ~ Uniform(0,1) alpha = torch.rand(batch_size, 1, device=device) # 构造插值样本:x_hat = ε·real + (1-ε)·fake x_hat = (alpha * real_data + (1 - alpha) * fake_data).requires_grad_(True) # 计算判别器对插值样本的输出 pred_hat = critic(x_hat) # 对x_hat求梯度 gradients = torch.autograd.grad( outputs=pred_hat, inputs=x_hat, grad_outputs=torch.ones_like(pred_hat), create_graph=True, retain_graph=True, only_inputs=True )[0] # 计算梯度范数 ||∇_x_hat D(x_hat)||_2 gradients = gradients.view(gradients.size(0), -1) gradient_norm = torch.sqrt(torch.sum(gradients ** 2, dim=1) + 1e-12) # 惩罚项:λ * (||∇||_2 - 1)^2 penalty = ((gradient_norm - 1) ** 2).mean() return penalty

参数说明:

  • lambda_gp=10是WGAN-GP论文推荐值,在configs/default.yaml中配置,过大导致训练缓慢,过小则约束失效;
  • gradients.view(...)将梯度展平为[B, 4096]再求L2范数,确保每个样本独立计算;
  • 1e-12是数值稳定项,防止开方时出现负数;
  • 该惩罚项加入判别器总损失:loss_c = -torch.mean(critic(real)) + torch.mean(critic(fake)) + lambda_gp * gp。

3. 环境准备与端到端运行流程

3.1 依赖安装与环境隔离

项目依赖极简,但版本兼容性需注意。推荐使用conda创建独立环境(避免与系统PyTorch冲突):

# 创建Python3.8环境(PyTorch 1.12+对1D卷积优化更好) conda create -n gan1d python=3.8 conda activate gan1d # 安装PyTorch(根据CUDA版本选择,此处以11.3为例) pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装其他依赖 pip install numpy matplotlib scikit-learn pyyaml tqdm

注意:不要用pip install torch默认安装CPU版——WGAN训练在GPU上提速超8倍,且ConvTranspose1d在CPU上存在隐式类型转换bug。

3.2 数据准备:从原始采集到标准化输入

真实振动信号常含直流偏置、趋势项、工频干扰。GAN-1D要求输入为去趋势+零均值+归一化至[-1,1]的纯故障冲击序列。提供utils/preprocess_vibration.py辅助脚本:

# utils/preprocess_vibration.py import numpy as np from scipy import signal def preprocess_signal(raw_signal: np.ndarray, fs=20480, cutoff=1000) -> np.ndarray: """对原始振动信号进行预处理""" # 1. 去除线性趋势(消除传感器漂移) detrended = signal.detrend(raw_signal, type='linear') # 2. 带通滤波:保留1kHz内故障特征频段(可根据轴承型号调整) b, a = signal.butter(4, [100, cutoff], btype='band', fs=fs) filtered = signal.filtfilt(b, a, detrended) # 3. 归一化到 [-1, 1] normed = filtered / np.max(np.abs(filtered)) return normed # 示例:处理单个CSV文件 if __name__ == "__main__": raw = np.loadtxt("raw_data/inner_001.csv", delimiter=",") proc = preprocess_signal(raw, fs=20480, cutoff=1500) np.save("data/inner_fault/inner_001.npy", proc[:4096]) # 截取前4096点

执行后,确保data/inner_fault/下每个.npy文件形状为(4096,),且值域严格在[-1,1]内。可用以下命令快速校验:

python -c "import numpy as np; d=np.load('data/inner_fault/inner_001.npy'); print(d.shape, d.min(), d.max())" # 应输出:(4096,) -1.0 1.0

3.3 启动训练:参数调优与监控要点

训练脚本train.py支持命令行参数覆盖配置文件:

python train.py \ --data_dir data/inner_fault/ \ --model_dir models/saved/inner_wgan/ \ --config configs/default.yaml \ --device cuda:0 \ --epochs 200 \ --batch_size 64 \ --lr_g 1e-4 \ --lr_c 1e-4 \ --n_critic 5 \ --lambda_gp 10

关键参数说明:

  • --n_critic 5:每轮生成器更新前,判别器更新5次——这是WGAN稳定训练的黄金比例,低于3易崩溃,高于10收敛过慢;
  • --lr_g/--lr_c:生成器与判别器学习率需相同(WGAN理论要求),且不宜大于1e-4,否则梯度惩罚失效;
  • --model_dir:自动创建目录并保存每20轮的检查点(G_epoch_XX.pth,C_epoch_XX.pth)及loss曲线(losses.npz)。

训练过程中实时监控三项指标:

  1. loss_c_real:判别器对真实信号的平均输出(应缓慢上升,表明判别能力增强);
  2. loss_c_fake:判别器对生成信号的平均输出(应缓慢下降,表明生成质量提升);
  3. gp_loss:梯度惩罚值(应稳定在0.1~1.0之间,若持续>2.0说明λ过大或网络容量不足)。

提示:首次训练建议先用--epochs 20快速验证流程,观察loss是否下降。若loss_c_fake不降反升,大概率是数据未归一化或n_critic设置过低。

3.4 生成与验证:如何判断生成信号“够用”?

训练完成后,用generate.py批量生成信号并可视化验证:

python generate.py \ --generator_path models/saved/inner_wgan/G_epoch_200.pth \ --output_dir generated/inner/ \ --n_samples 100 \ --latent_dim 100 \ --signal_length 4096

生成的.npy文件存于generated/inner/。关键验证步骤分三层:

验证层级方法合格标准
时域层绘制随机10条生成信号叠加图无明显周期性伪影,冲击形态多样,无平台状饱和区
频域层计算FFT幅值谱(加汉宁窗)主频峰清晰(如BPFO=157Hz),谐波结构合理(2×、3×主频可见),无宽频噪声隆起
诊断层将生成信号喂入已训练好的ResNet1D诊断模型分类准确率提升≥3%(对比无增强基线),且混淆矩阵中故障类间区分度提高

utils/plot_utils.py提供一键可视化函数:

from utils.plot_utils import plot_comparison plot_comparison( real_path="data/inner_fault/inner_001.npy", fake_path="generated/inner/fake_001.npy", save_path="figs/inner_compare.png" ) # 输出并排子图:时域波形 + FFT谱 + 包络谱

合格的生成结果应呈现:真实信号中常见的“衰减冲击串”结构(如图中红色箭头所示),而非均匀正弦叠加。


4. 常见问题排查:那些让我重训三次的玄学翻车现场

4.1 现象:训练初期loss_c_fake突然飙升至+1000以上,随后loss_c_real归零

原因:判别器过强,导致生成器梯度消失(vanishing gradient)。常见于n_critic设置过高(如设为10)或lr_c过大(>2e-4)。WGAN理论要求判别器接近最优时生成器才有效更新,但过优判别器会使生成器loss梯度趋近于零。

解决:立即将n_critic降至5,lr_c设为1e-4,并加载上一轮检查点重启。若仍不稳定,临时将lambda_gp从10降至5,待训练平稳后再调回。

4.2 现象:生成信号FFT谱出现尖锐单频峰(如50Hz工频),且所有样本峰位一致

原因:原始数据未去除工频干扰。采集设备接地不良或电源耦合导致50Hz强干扰被模型当作“共性特征”学习。生成器学会在固定频率注入正弦波以欺骗判别器。

解决:回到preprocess_vibration.py,将带通滤波上限cutoff从1000Hz降至800Hz,并增加50Hz陷波器:

# 在filtering后添加 b_notch, a_notch = signal.iirnotch(50, 30, fs=fs) # Q=30 filtered = signal.filtfilt(b_notch, a_notch, filtered)

4.3 现象:生成信号时域波形呈现规律性“方波振荡”,相邻样本高度相似

原因:模式坍缩(mode collapse)早期征兆。根本原因是真实数据多样性不足(如仅3个内圈故障样本),或生成器容量过大(ngf=128导致过拟合)。

解决:

  1. 数据侧:用utils/augment_by_rotation.py对现有样本做相位旋转(np.roll(signal, shift)),生成5倍新样本;
  2. 模型侧:将models/generator.py中ngf从64改为32,降低容量;
  3. 训练侧:在train.py中为生成器loss添加轻微L1正则:loss_g = -torch.mean(critic(fake)) + 0.001 * torch.mean(torch.abs(fake))。

4.4 现象:gradient_penalty值持续>5.0,且loss_c_real与loss_c_fake差值极小(<0.1)

原因:插值样本x_hat梯度计算失效。常见于real_data与fake_data动态范围差异过大(如real归一化到[-1,1],fake因生成器最后一层无激活而溢出)。

解决:检查Generator.forward()末尾是否遗漏torch.tanh()——WGAN要求生成器输出有界,必须显式加tanh:

# 修改models/generator.py最后一行: # return self.main(x).squeeze(1) → 改为: return torch.tanh(self.main(x).squeeze(1))

同时确认utils/dataset.py中归一化逻辑为:data = data / np.max(np.abs(data)),而非/ data.std()。

4.5 现象:训练到100轮后loss平稳,但生成信号包络谱无冲击特征,只有宽带噪声

原因:判别器感受野不足,无法捕捉长周期冲击模式。当前5层卷积最大感受野为1 + 2*(4-1) + 4*(4-1) + 8*(4-1) + 16*(4-1) = 127点(约6ms),而轴承故障冲击周期常达20ms以上。

解决:修改models/critic.py,将前两层卷积核大小从4改为8:

nn.Conv1d(1, ndf, 8, 2, 3, bias=False), # kernel=8, padding=3 → 感受野+6 nn.Conv1d(ndf, ndf * 2, 8, 2, 3, bias=False), # 同理

重新训练,感受野可扩展至255点(12.5ms),覆盖典型冲击周期。


5. 二次开发实战:从信号生成到故障机理嵌入

5.1 场景驱动改造:为生成器注入物理先验

纯数据驱动的GAN易生成“数学正确但物理错误”的信号。例如:内圈故障冲击周期应随转速线性变化,但原始GAN对此无约束。我们可通过条件生成(Conditional GAN)注入转速RPM作为辅助输入:

# 修改models/generator.py class ConditionalGenerator(nn.Module): def __init__(self, latent_dim=100, rpm_dim=1, signal_length=4096, ngf=64): super().__init__() self.rpm_proj = nn.Linear(rpm_dim, 64) # 将RPM映射为64维向量 self.noise_proj = nn.Linear(latent_dim, ngf * 8 * 128 - 64) # 预留空间拼接 # ... 其余结构同原Generator ... def forward(self, z, rpm): rpm_feat = self.rpm_proj(rpm) # [B, 64] noise_feat = self.noise_proj(z) # [B, ngf*8*128 - 64] fused = torch.cat([rpm_feat, noise_feat], dim=1) # [B, ngf*8*128] x = fused.view(-1, self.ngf * 8, 128) return torch.tanh(self.main(x).squeeze(1))

训练时,rpm值需与真实工况一致(如内圈故障样本对应RPM=1500±50)。这样生成的信号,其冲击间隔会自然适配输入RPM,提升下游诊断模型的工况泛化能力。

5.2 轻量化部署:ONNX导出与边缘推理

生成器可导出为ONNX,在Jetson Nano等边缘设备上实时生成仿真信号用于在线测试:

# export_onnx.py import torch from models.generator import Generator # 加载训练好的权重 G = Generator(latent_dim=100, signal_length=4096, ngf=64) G.load_state_dict(torch.load("models/saved/inner_wgan/G_epoch_200.pth")) G.eval() # 构造示例输入 dummy_input = torch.randn(1, 100) # 单样本噪声 # 导出ONNX torch.onnx.export( G, dummy_input, "models/onnx/generator_inner.onnx", input_names=["noise"], output_names=["signal"], dynamic_axes={"noise": {0: "batch"}, "signal": {0: "batch"}}, opset_version=12 ) print("ONNX exported to models/onnx/generator_inner.onnx")

在边缘端用ONNX Runtime推理(Python):

import onnxruntime as ort import numpy as np sess = ort.InferenceSession("models/onnx/generator_inner.onnx") z = np.random.normal(0, 1, (1, 100)).astype(np.float32) fake_signal = sess.run(None, {"noise": z})[0] # [1, 4096] # fake_signal 即可送入本地诊断模型

注意:ONNX导出时opset_version=12兼容性最好;若目标设备为旧版TensorRT,需降为opset_version=11并手动替换ConvTranspose1d为Upsample+Conv1d组合。

5.3 评估体系升级:不只是看Loss,要建诊断闭环

最终价值不在于GAN本身多“酷”,而在于它能否提升诊断准确率。我搭建了自动化评估流水线,每次生成后自动触发诊断测试:

# eval_diagnosis.py from sklearn.metrics import classification_report import joblib def evaluate_generation(generator_path, test_data_dir, classifier_path): # 1. 生成1000个假样本 fake_signals = generate_batch(generator_path, n=1000) # 2. 提取时频特征(与真实数据相同pipeline) X_fake = extract_features(fake_signals) # 返回 [1000, 128] 特征向量 # 3. 加载预训练分类器(如LightGBM) clf = joblib.load(classifier_path) # 4. 预测并统计 y_pred = clf.predict(X_fake) # 输出各类别支持度,若"inner_fault"占比<80%,说明生成偏向性严重 print("Inner fault ratio:", np.mean(y_pred == "inner_fault")) # 运行 evaluate_generation( generator_path="models/saved/inner_wgan/G_epoch_200.pth", test_data_dir="data/test_inner/", classifier_path="models/classifiers/lgbm_inner.pkl" )

这个脚本成为我的“后悔药”:每次调整网络结构后,不再凭感觉看图,而是用诊断准确率变化说话。当发现某次修改使inner_fault识别率从82%升至86.5%,我才真正相信改动有效。

从那以后我每次改生成器,都强制走一遍这个诊断闭环——哪怕多花10分钟,也比对着loss曲线自我感动强。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进