简介这份资源围绕一维生成对抗网络GAN-1D展开采用WGAN结构生成故障轴承振动信号面向具备Python基础、从事机械故障诊断或数据增强方向的学习者与研究人员。它可用于缓解故障样本稀缺问题为轴承状态监测与深度学习模型训练提供扩充数据。压缩包共19个文件约22.03MB包含2个Python脚本训练与测试入口、2个mat数据文件、9张png可视化结果图以及TensorFlow检查点相关文件meta、index、data与checkpoint并附有README说明文档便于快速理解项目结构与运行方式。资源要求Python 3.5以上、TensorFlow GPU及numpy、scipy等依赖训练脚本支持通过命令行调整学习率、训练轮数、采样间隔并可在9类信号中选择训练数据。目前已有3386人学习下载适合希望复现WGAN信号生成流程、研究数据增强策略的读者参考。1. 当你的轴承数据不够用一个用 WGAN 造振动信号的 Python 项目做故障诊断的同行大概都遇到过这种局面实验台上采到的正常样本一大堆可故障样本——尤其是早期微弱故障——少得可怜训练集里正负样本比例能到几十比一。模型在训练集上看着还行一换到真实工况就翻车本质上是判别器没见过几回真正的故障长什么样。这个项目就是冲着这个痛点来的用一维 Wasserstein GANWGAN去生成故障轴承的振动信号把稀缺的故障样本补起来给后面的分类、检测模型当数据增强用。整套代码是纯 Python 写的基于 PyTorch输入输出都是一维时序信号不涉及图像所以叫 GAN-1D。它适合两类人一类是手里有轴承振动数据、想扩样本但不想手工造假的算法工程师另一类是想拿一个干净的一维 GAN 骨架练手、再迁移到自己时序任务上的开发者。下面我按「它怎么搭的 → 怎么跑起来 → 怎么改」的顺序拆一遍。2. 架构拆解一维卷积堆出来的生成器和判别器2.1 为什么是 WGAN 而不是原始 GAN原始 GAN 用 JS 散度衡量生成分布和真实分布的距离一旦两个分布几乎不重叠高维信号里这是常态梯度就消失生成器学不动。WGAN 换成 Wasserstein 距离即使分布不重叠也能给出有意义的梯度训练稳定得多。代价是判别器WGAN 里叫 Critic不能再输出概率最后一层要去掉 Sigmoid而且每次更新生成器之前要把 Critic 多训几次。这个项目里 Critic 每轮迭代更新 5 次生成器更新 1 次这是 WGAN 的标配节奏。另外 WGAN 要求 Critic 满足 Lipschitz 连续原版用权重裁剪weight clipping实现这个项目也是这么做的裁剪阈值一般设 0.01。权重裁剪有个副作用——容易把参数压到边界上导致生成质量下降所以后来有人提出 WGAN-GP 用梯度惩罚替代裁剪。这个项目保留裁剪版本好处是实现简单、依赖少跑起来不容易出玄学问题。2.2 生成器网络结构生成器负责把一段随机噪声映射成和真实振动信号等长的一维序列。振动信号采样率高一段样本动辄几千个点所以生成器用「全连接升维 多层一维转置卷积」的结构逐级把长度放大到目标长度。import torch import torch.nn as nn class Generator(nn.Module): def __init__(self, noise_dim100, signal_len1024, base_channels64): super().__init__() self.signal_len signal_len # 噪声先升维到 128 * (signal_len // 16) 的长度 self.fc nn.Linear(noise_dim, 128 * (signal_len // 16)) self.net nn.Sequential( # 输入通道 128输出 64长度翻倍 nn.ConvTranspose1d(128, 64, kernel_size4, stride2, padding1), nn.BatchNorm1d(64), nn.ReLU(True), # 64 - 32 nn.ConvTranspose1d(64, 32, kernel_size4, stride2, padding1), nn.BatchNorm1d(32), nn.ReLU(True), # 32 - 16 nn.ConvTranspose1d(32, 16, kernel_size4, stride2, padding1), nn.BatchNorm1d(16), nn.ReLU(True), # 16 - 1输出单通道一维信号 nn.ConvTranspose1d(16, 1, kernel_size4, stride2, padding1), nn.Tanh() # 把输出压到 [-1, 1]和归一化后的真实信号对齐 ) def forward(self, z): x self.fc(z) x x.view(x.size(0), 128, self.signal_len // 16) return self.net(x)逻辑说明noise_dim是输入噪声维度默认 100signal_len是目标信号长度默认 1024如果你的数据每段是 2048 点把它改成 2048同时signal_len // 16会自动跟着变。四层转置卷积每层长度翻倍16 倍正好还原到signal_len。kernel_size4, stride2, padding1是转置卷积里最常用的「刚好翻倍」组合记不住就背下来。最后一层用Tanh而不是 ReLU是因为真实信号在预处理阶段被归一化到了 [-1, 1]生成器输出必须落在同一区间否则判别器一眼就能靠数值范围区分真假。BatchNorm1d在生成器里很关键它让每层输入分布稳定训练初期不至于直接崩掉。2.3 判别器网络结构判别器Critic干的是相反的事把一维信号逐层降采样最后输出一个标量分数分数越高说明越像真实信号。注意它没有 Sigmoid这是 WGAN 的硬性要求。class Critic(nn.Module): def __init__(self, signal_len1024): super().__init__() self.net nn.Sequential( nn.Conv1d(1, 16, kernel_size4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv1d(16, 32, kernel_size4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv1d(32, 64, kernel_size4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv1d(64, 128, kernel_size4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), ) self.fc nn.Linear(128 * (signal_len // 16), 1) def forward(self, x): feat self.net(x) feat feat.view(feat.size(0), -1) return self.fc(feat)逻辑说明判别器里用LeakyReLU(0.2)而不是 ReLU是为了避免负半轴梯度全为零导致训练停滞这是 GAN 判别器的常规选择。判别器不加 BatchNorm因为 WGAN 原论文建议判别器不用批归一化用了反而可能破坏 Lipschitz 约束。最后接一个全连接到标量输出没有激活函数。signal_len必须和生成器保持一致两边对不上会在 reshape 时报维度错误这是新手最常踩的坑之一。2.4 训练循环与损失函数WGAN 的损失函数很简洁没有对数、没有 min-max 博弈的绕弯就是两个期望之差。def train_step(real_signal, netG, netC, optG, optC, noise_dim, clip_value0.01): batch_size real_signal.size(0) # ---- 训练 Critic ---- optC.zero_grad() z torch.randn(batch_size, noise_dim, devicereal_signal.device) fake_signal netG(z).detach() # detach 防止梯度传到生成器 loss_c netC(fake_signal).mean() - netC(real_signal).mean() loss_c.backward() optC.step() # 权重裁剪强制 Lipschitz 约束 for p in netC.parameters(): p.data.clamp_(-clip_value, clip_value) # ---- 训练 Generator ---- optG.zero_grad() z torch.randn(batch_size, noise_dim, devicereal_signal.device) fake_signal netG(z) loss_g -netC(fake_signal).mean() # 生成器想让 Critic 给假样本打高分 loss_g.backward() optG.step() return loss_c.item(), loss_g.item()逻辑说明loss_c是「假样本分数均值 - 真样本分数均值」Critic 想把这个差值拉大所以它越小说明 Critic 越分得开真假。loss_g取负号是因为生成器想让 Critic 给假样本打高分也就是让netC(fake).mean()越大越好取负之后就是最小化。detach()那一步千万别漏漏了梯度会顺着假样本回流到生成器等于在训 Critic 的时候偷偷动了生成器训练会乱套。权重裁剪用clamp_原地操作每个 batch 训完 Critic 都要裁一次。实际训练时外层循环里 Critic 更新 5 次、生成器更新 1 次这个比例在train.py里控制。3. 跑起来数据准备、环境配置与训练启动3.1 数据格式与预处理项目吃的是一维振动信号常见来源是轴承实验台采集的加速度传感器数据格式一般是 CSV 或 MAT。不管原始格式是什么喂进网络前都要统一成「每行一个样本、每列一个时间点」的二维数组形状是(样本数, signal_len)。预处理三步走先按signal_len切窗比如原始长信号每 1024 点切一段段与段之间可以重叠 50% 增加样本量再做归一化把每段信号线性映射到 [-1, 1]这一步必须和生成器最后一层的Tanh对应最后转成 PyTorch 的Tensor并封装成DataLoader。import numpy as np import torch from torch.utils.data import TensorDataset, DataLoader def load_and_preprocess(csv_path, signal_len1024, overlap0.5): raw np.loadtxt(csv_path, delimiter,) # 假设单列长信号 raw raw.flatten() step int(signal_len * (1 - overlap)) segments [] for start in range(0, len(raw) - signal_len 1, step): seg raw[start:start signal_len] # 逐段归一化到 [-1, 1] seg 2 * (seg - seg.min()) / (seg.max() - seg.min() 1e-8) - 1 segments.append(seg) data np.stack(segments).astype(np.float32) tensor torch.from_numpy(data).unsqueeze(1) # 加通道维 - (N, 1, L) return TensorDataset(tensor) dataset load_and_preprocess(bearing_fault.csv, signal_len1024) loader DataLoader(dataset, batch_size64, shuffleTrue, drop_lastTrue)逻辑说明overlap0.5表示相邻窗重叠一半能在样本少的时候多榨出一些数据。unsqueeze(1)是给数据加一个通道维度因为Conv1d要求输入形状是(batch, channels, length)振动信号是单通道所以 channels1。drop_lastTrue是为了丢掉最后一个不满 batch 的批次避免 BatchNorm 在 batch_size1 时报错。归一化里加1e-8是防止某段信号幅值恒定导致除零。3.2 环境依赖与目录结构项目依赖很轻核心就三样Python 3.8 以上、PyTorch 1.10 以上、NumPy。如果要画训练曲线再加个 Matplotlib。常见做法是用 conda 建个独立环境避免和系统里的包打架。conda create -n gan1d python3.9 conda activate gan1d pip install torch numpy matplotlib目录结构大致是这样不同版本可能略有出入但核心文件跑不掉GAN-1D/ ├── models/ │ ├── generator.py # 生成器定义 │ └── critic.py # 判别器定义 ├── data/ │ └── bearing_fault.csv # 原始振动信号 ├── utils/ │ ├── data_loader.py # 切窗、归一化、DataLoader │ └── visualize.py # 时域/频域绘图 ├── train.py # 训练主入口 ├── generate.py # 加载权重生成新样本 └── config.py # 超参数集中管理config.py把noise_dim、signal_len、batch_size、n_critic、clip_value、epochs这些超参集中放一起改的时候不用满项目找。这是我在多个项目里养成的习惯超参散落在各个文件里过两周自己都记不清哪个是哪个。3.3 启动训练与观察指标训练入口一般长这样核心是把 Critic 多训几次的逻辑写进循环。import torch from config import cfg from models.generator import Generator from models.critic import Critic from utils.data_loader import load_and_preprocess from torch.utils.data import DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) netG Generator(cfg.noise_dim, cfg.signal_len).to(device) netC Critic(cfg.signal_len).to(device) optG torch.optim.RMSprop(netG.parameters(), lrcfg.lr) optC torch.optim.RMSprop(netC.parameters(), lrcfg.lr) dataset load_and_preprocess(cfg.data_path, cfg.signal_len) loader DataLoader(dataset, batch_sizecfg.batch_size, shuffleTrue, drop_lastTrue) for epoch in range(cfg.epochs): for i, (real,) in enumerate(loader): real real.to(device) # Critic 更新 n_critic 次 for _ in range(cfg.n_critic): loss_c, _ train_step(real, netG, netC, optG, optC, cfg.noise_dim, cfg.clip_value) # Generator 更新 1 次 _, loss_g train_step(real, netG, netC, optG, optC, cfg.noise_dim, cfg.clip_value) print(fepoch {epoch} | loss_c {loss_c:.4f} | loss_g {loss_g:.4f}) if epoch % 50 0: torch.save(netG.state_dict(), fcheckpoints/netG_{epoch}.pth)逻辑说明优化器用RMSprop而不是 Adam这是 WGAN 原论文的推荐Adam 的动量项和权重裁剪配合起来容易让训练不稳定。学习率一般设 5e-5 到 1e-4 之间太大 Critic 会震荡。n_critic就是前面说的 5。观察指标主要看loss_c它应该在一个范围内波动而不是持续下降或持续上升——持续下降说明 Critic 太强、生成器学不动持续上升说明 Critic 太弱。生成质量最终还是要靠肉眼看时域波形和频谱光看 loss 数值会被骗。4. 避坑与排查训练 WGAN 时最容易翻车的几件事4.1 生成信号全是噪声或一条直线现象训练几十轮后生成器输出的信号要么是高频杂乱噪声要么是一条几乎不变的直线。原因通常是 Critic 和生成器失衡。如果 Critic 太强生成器拿到的梯度信号很弱学不出结构如果 Critic 太弱生成器随便输出什么都能骗过它就退化成噪声。解决办法是先看loss_c的量级正常应该在 0.1 到 1 之间波动。偏大就减小n_critic或降低 Critic 学习率偏小就增大n_critic。另一个常见原因是权重裁剪阈值设得不对clip_value0.01是默认值但如果你的信号幅值范围特殊可以试着调到 0.05 或 0.005 看效果。4.2 训练中途 loss 突然变成 NaN现象前几十轮正常某一轮loss_c或loss_g突然变成 NaN之后再也回不来。原因多半是梯度爆炸。WGAN 虽然比原始 GAN 稳但权重裁剪本身不保证梯度有界。解决办法有三条一是把学习率调小从 1e-4 降到 5e-5二是在优化器里加梯度裁剪torch.nn.utils.clip_grad_norm_(netC.parameters(), max_norm1.0)三是检查数据里有没有 NaN 或 Inf归一化时除零会产生 Inf前面加1e-8就是防这个。我一般会在训练循环里加一句if torch.isnan(loss_c): break早停比硬跑到底强。4.3 生成样本和真实样本在频域上对不上现象时域波形看着挺像但一做 FFT 就露馅生成信号的频谱里缺少真实故障信号的特征频率成分。原因是判别器只看时域对频域特征不敏感。解决办法是在判别器输入里拼接频域特征或者干脆在损失函数里加一项频域距离。更简单的做法是加深判别器让它有足够容量去隐式捕捉频域模式。如果数据量允许把signal_len从 1024 加到 2048 或 4096让判别器看到更长的上下文频域分辨率也会跟着提高。4.4 显存不够或训练速度慢得离谱现象batch_size 设 64 就 OOM或者一个 epoch 要跑半小时。原因通常是signal_len太大加上 batch_size 太大显存是两者乘积。解决办法先把 batch_size 降到 16 或 32WGAN 对 batch_size 不敏感小 batch 也能训再检查有没有在 GPU 上跑torch.cuda.is_available()返回 False 的话就是 CPU 在硬扛。另外DataLoader的num_workers设成 4 或 8 能明显加快数据加载但 Windows 上设太大反而会卡设 0 或 2 比较稳。4.5 保存的模型加载后生成结果完全不对现象训练时看着好好的generate.py加载权重后生成的信号面目全非。原因通常是加载时网络结构参数和保存时不一致比如signal_len或noise_dim对不上。解决办法是加载前先打印state_dict的 key 和形状和当前网络对比。另一个坑是保存时用了torch.save(netG, path)保存整个模型加载时如果代码结构变了会直接报错建议只保存state_dict()。还有别忘了加载后调netG.eval()BatchNorm 在训练和推理模式下的行为不一样不切 eval 模式生成结果会带随机性。5. 二次开发把生成器接到你的故障诊断流水线里跑通之后真正有价值的是把这个生成器用起来。最直接的用法是数据增强训练好 WGAN 后用generate.py批量生成故障样本和真实正常样本混在一起喂给下游的分类器或异常检测模型。我一般会生成和真实故障样本等量的合成样本比如真实故障有 200 段就生成 200 段比例控制在 1:1 左右生成太多反而会引入噪声。生成时记得把netG切到eval()模式并且用torch.no_grad()包起来省显存也省时间。import torch from models.generator import Generator def generate_samples(ckpt_path, n_samples200, noise_dim100, signal_len1024): device torch.device(cuda if torch.cuda.is_available() else cpu) netG Generator(noise_dim, signal_len).to(device) netG.load_state_dict(torch.load(ckpt_path, map_locationdevice)) netG.eval() # 切推理模式BatchNorm 用滑动统计量 with torch.no_grad(): z torch.randn(n_samples, noise_dim, devicedevice) fake netG(z).cpu().numpy() # (n_samples, 1, signal_len) return fake.squeeze(1) # 去掉通道维变成 (n_samples, signal_len)逻辑说明map_locationdevice保证在 CPU 上也能加载 GPU 训出来的权重。eval()那一步是血泪经验忘了切模式生成结果每次都不一样排查半天才发现是 BatchNorm 在作怪。返回时squeeze(1)把通道维去掉方便直接存成 CSV 或喂给 sklearn 的模型。改造方向有几个。一是换损失约束把权重裁剪换成 WGAN-GP 的梯度惩罚生成质量通常更好代价是每个 batch 多算一次梯度惩罚项训练慢一些。二是做条件生成把故障类别标签作为条件输入拼到噪声里这样一个生成器就能按类别生成不同故障类型的信号不用每类训一个模型。三是迁移到其他时序任务比如生成心电图、机械振动、语音片段只要把signal_len和归一化范围调一下网络结构基本不用动。验证生成质量别只看 loss我习惯做三件事一是把生成信号和真实信号叠在同一张时域图上看包络和冲击成分对不对二是做 FFT 对比频谱重点看故障特征频率处有没有峰三是把合成样本混进训练集看下游分类器的准确率有没有提升——如果加了合成样本反而掉点说明生成质量不行得回去调 Critic 和生成器的平衡。从那以后我每次训完 GAN 都强制走一遍这三步验证再也不敢只看 loss 曲线就下结论。希望帮到你。本文还有配套的精品资源点击获取