news 2026/10/15 14:53:28

基于WGAN的轴承故障振动信号生成:一维GAN数据增强实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于WGAN的轴承故障振动信号生成:一维GAN数据增强实战

简介:这份资源围绕一维生成对抗网络(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 GAN(WGAN)去生成故障轴承的振动信号,把稀缺的故障样本补起来,给后面的分类、检测模型当数据增强用。整套代码是纯 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_dim=100, signal_len=1024, base_channels=64): 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_size=4, stride=2, padding=1), nn.BatchNorm1d(64), nn.ReLU(True), # 64 -> 32 nn.ConvTranspose1d(64, 32, kernel_size=4, stride=2, padding=1), nn.BatchNorm1d(32), nn.ReLU(True), # 32 -> 16 nn.ConvTranspose1d(32, 16, kernel_size=4, stride=2, padding=1), nn.BatchNorm1d(16), nn.ReLU(True), # 16 -> 1,输出单通道一维信号 nn.ConvTranspose1d(16, 1, kernel_size=4, stride=2, padding=1), 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是输入噪声维度,默认 100;signal_len是目标信号长度,默认 1024,如果你的数据每段是 2048 点,把它改成 2048,同时signal_len // 16会自动跟着变。四层转置卷积每层长度翻倍,16 倍正好还原到signal_len。kernel_size=4, stride=2, padding=1是转置卷积里最常用的「刚好翻倍」组合,记不住就背下来。最后一层用Tanh而不是 ReLU,是因为真实信号在预处理阶段被归一化到了 [-1, 1],生成器输出必须落在同一区间,否则判别器一眼就能靠数值范围区分真假。BatchNorm1d在生成器里很关键,它让每层输入分布稳定,训练初期不至于直接崩掉。

2.3 判别器网络结构

判别器(Critic)干的是相反的事:把一维信号逐层降采样,最后输出一个标量分数,分数越高说明越像真实信号。注意它没有 Sigmoid,这是 WGAN 的硬性要求。

class Critic(nn.Module): def __init__(self, signal_len=1024): super().__init__() self.net = nn.Sequential( nn.Conv1d(1, 16, kernel_size=4, stride=2, padding=1), nn.LeakyReLU(0.2, inplace=True), nn.Conv1d(16, 32, kernel_size=4, stride=2, padding=1), nn.LeakyReLU(0.2, inplace=True), nn.Conv1d(32, 64, kernel_size=4, stride=2, padding=1), nn.LeakyReLU(0.2, inplace=True), nn.Conv1d(64, 128, kernel_size=4, stride=2, padding=1), nn.LeakyReLU(0.2, inplace=True), ) 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_value=0.01): batch_size = real_signal.size(0) # ---- 训练 Critic ---- optC.zero_grad() z = torch.randn(batch_size, noise_dim, device=real_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, device=real_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_len=1024, overlap=0.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_len=1024) loader = DataLoader(dataset, batch_size=64, shuffle=True, drop_last=True)

逻辑说明:overlap=0.5表示相邻窗重叠一半,能在样本少的时候多榨出一些数据。unsqueeze(1)是给数据加一个通道维度,因为Conv1d要求输入形状是(batch, channels, length),振动信号是单通道所以 channels=1。drop_last=True是为了丢掉最后一个不满 batch 的批次,避免 BatchNorm 在 batch_size=1 时报错。归一化里加1e-8是防止某段信号幅值恒定导致除零。

3.2 环境依赖与目录结构

项目依赖很轻,核心就三样:Python 3.8 以上、PyTorch 1.10 以上、NumPy。如果要画训练曲线再加个 Matplotlib。常见做法是用 conda 建个独立环境,避免和系统里的包打架。

conda create -n gan1d python=3.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(), lr=cfg.lr) optC = torch.optim.RMSprop(netC.parameters(), lr=cfg.lr) dataset = load_and_preprocess(cfg.data_path, cfg.signal_len) loader = DataLoader(dataset, batch_size=cfg.batch_size, shuffle=True, drop_last=True) 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(f'epoch {epoch} | loss_c {loss_c:.4f} | loss_g {loss_g:.4f}') if epoch % 50 == 0: torch.save(netG.state_dict(), f'checkpoints/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_value=0.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_norm=1.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 或 32,WGAN 对 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_samples=200, noise_dim=100, signal_len=1024): 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_location=device)) netG.eval() # 切推理模式,BatchNorm 用滑动统计量 with torch.no_grad(): z = torch.randn(n_samples, noise_dim, device=device) fake = netG(z).cpu().numpy() # (n_samples, 1, signal_len) return fake.squeeze(1) # 去掉通道维,变成 (n_samples, signal_len)

逻辑说明:map_location=device保证在 CPU 上也能加载 GPU 训出来的权重。eval()那一步是血泪经验,忘了切模式生成结果每次都不一样,排查半天才发现是 BatchNorm 在作怪。返回时squeeze(1)把通道维去掉,方便直接存成 CSV 或喂给 sklearn 的模型。

改造方向有几个。一是换损失约束,把权重裁剪换成 WGAN-GP 的梯度惩罚,生成质量通常更好,代价是每个 batch 多算一次梯度惩罚项,训练慢一些。二是做条件生成,把故障类别标签作为条件输入拼到噪声里,这样一个生成器就能按类别生成不同故障类型的信号,不用每类训一个模型。三是迁移到其他时序任务,比如生成心电图、机械振动、语音片段,只要把signal_len和归一化范围调一下,网络结构基本不用动。

验证生成质量别只看 loss,我习惯做三件事:一是把生成信号和真实信号叠在同一张时域图上,看包络和冲击成分对不对;二是做 FFT 对比频谱,重点看故障特征频率处有没有峰;三是把合成样本混进训练集,看下游分类器的准确率有没有提升——如果加了合成样本反而掉点,说明生成质量不行,得回去调 Critic 和生成器的平衡。从那以后我每次训完 GAN 都强制走一遍这三步验证,再也不敢只看 loss 曲线就下结论。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 14:52:58

Unity无限地形实现:基于柏林噪声的程序化生成与性能优化

1. 项目概述:为什么“无限地形”不是玄学,而是可落地的工程实践“Unity无限地形生成(基于柏林噪声的简单生成)”这个标题里藏着三个关键信号:Unity——说明它不是理论推演,而是面向真实开发环境的实操方案&…

作者头像 李华
网站建设 2026/10/15 14:51:50

微信小程序云开发实战:图书借阅系统完整搭建与避坑指南

简介:一套基于微信小程序开发的图书借阅系统完整项目资料,面向计算机相关专业学生、毕业设计选题者及微信小程序初阶开发者,可作为从界面设计到后端接口的实战参考。资源共145个文件,压缩包61.25MB,核心文件涵盖WXML页…

作者头像 李华
网站建设 2026/10/15 14:50:17

GD32F103硬件I2C驱动OLED/RTC/温湿度实战:初始化与避坑指南

简介:面向嵌入式开发者的GD32F103硬件I2C实战资源,围绕SSD1306 OLED显示屏、PCF8563 RTC实时时钟和SHT30温湿度传感器,完整呈现从I2C外设配置、OLED初始化到RTC读写及传感器数据解析的代码实现,并涉及中断/DMA选择、错误处理与低功…

作者头像 李华
网站建设 2026/10/15 14:50:08

GMap.NET标绘实战:点线面图形叠加与性能优化

1. 第三篇教程解决什么问题:把业务数据“画”到地图上GMap.NET入门这个系列写到现在,前两篇已经把最基础的地图加载、地图源切换、缩放级别和坐标定位讲完了。也就是说,地图能正常显示出来,初始化配置也跑通了。但这时候你会明显感…

作者头像 李华
网站建设 2026/10/15 14:49:51

Sentaurus sdevice命令核心语句解析:从I-V到辐照损伤仿真

简介:这是一份关于Sentaurus TCAD仿真应用的技术文档,面向微电子、纳米电子与光电子器件设计人员,重点讲解电流电压曲线、电容电压曲线、辐照损伤及重粒子入射、瞬态仿真等典型工况。文件总数仅1个,格式为Word(docx&am…

作者头像 李华