合成数据:用 GAN 或扩散模型生成训练样本

FreeGuideOnline 15阅读 2026-07-03

min_G max_D V(D,G) = E_{x~p_data}[log D(x)] + E_{z~p_z}[log(1 - D(G(z)))]

这一公式驱动着两个网络的对抗学习。

### 2.2 用 DCGAN 生成 CIFAR-10 图像

推荐从**深度卷积生成对抗网络(DCGAN)**入手,它将卷积层的归纳偏置引入 GAN,稳定易训练。我们直接基于 PyTorch 实现。

**环境准备**:安装 `torch`, `torchvision`, `matplotlib`, `numpy`。

**生成器架构**:输入一个 100 维的随机噪声向量,经过转置卷积层逐步上采样,最终输出 3×32×32 的彩色图像。

```python
import torch.nn as nn

class Generator(nn.Module):
    def __init__(self, nz=100, ngf=64, nc=3):
        super().__init__()
        self.main = nn.Sequential(
            # 输入 (nz) 映射到 (ngf*8)*4*4
            nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, bias=False),
            nn.BatchNorm2d(ngf * 8), nn.ReLU(True),
            # 状态: (ngf*8) x 4 x 4
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4), nn.ReLU(True),
            # 状态: (ngf*4) x 8 x 8
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2), nn.ReLU(True),
            # 状态: (ngf*2) x 16 x 16
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf), nn.ReLU(True),
            # 状态: (ngf) x 32 x 32
            nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False),
            nn.Tanh()
            # 输出: (nc) x 64 x 64  (CIFAR-10大小为32,需调整。此处仅为示例架构,实际需适配)
        )

    def forward(self, input):
        return self.main(input)

注意:针对 32×32 的 CIFAR-10,只需将最后一层输出尺寸适配为 32×32,移除一层上采样即可。实践时可在官方 DCGAN 代码基础上调整。

判别器架构:标准 CNN 分类器,输出输入图像为真的概率。

class Discriminator(nn.Module):
    def __init__(self, nc=3, ndf=64):
        super().__init__()
        self.main = nn.Sequential(
            # 输入 (nc) x 32 x 32
            nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            # 状态: (ndf) x 16 x 16
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),
            # 状态: (ndf*2) x 8 x 8
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),
            # 状态: (ndf*4) x 4 x 4
            nn.Conv2d(ndf * 4, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, input):
        return self.main(input)

训练循环要点

  • 固定判别器,更新生成器;固定生成器,更新判别器。
  • 使用标签平滑(真实样本标签设为 0.9,而非 1.0)和随机噪声增强判别器鲁棒性。
  • 使用 Adam 优化器,学习率推荐 0.0002,beta1=0.5。
  • 监控生成器与判别器损失,避免一方压制另一方。

采样合成数据: 训练完成后,只需从标准正态分布采样一批随机噪声 z,喂给生成器,即可获得一批合成图像。可以将这些图像保存为 .png 文件或直接拼入训练集。

import torch
with torch.no_grad():
    fixed_noise = torch.randn(64, nz, 1, 1, device=device)
    fake_images = generator(fixed_noise).detach().cpu()

2.3 提高合成质量的实用技巧

  • 特征匹配:在判别器中间层计算生成样本与真实样本的统计距离,稳定训练。
  • 渐进增长:从低分辨率开始训练,逐步增加层数生成更高分辨率图像(ProGAN 思路)。
  • 数据增强的适应性:使用 Differentiable Augmentation 同时增强真实与生成样本,防止判别器过拟合。
  • 条件生成:在生成器和判别器输入中拼接类别标签,实现可控类别的合成(CGAN)。这对平衡类别分布特别有用。

3. 扩散模型合成数据实战

扩散模型是当前图像合成质量最高的范式,它通过逐步向数据添加噪声,然后学习逆过程从噪声中恢复出清晰图像。Denoising Diffusion Probabilistic Models (DDPM) 是其典型代表。

3.1 扩散模型直觉

想象一滴墨水滴入清水,扩散过程让墨水分子逐渐遍布整个杯子。扩散模型的前向过程就是不断给图像加高斯噪声,直至变成纯噪声。反向过程则训练一个神经网络从噪声中一步步去噪,重现原始图像。一旦学会这个去噪过程,我们就可以从随机噪声出发,生成全新的图像。

3.2 使用预训练扩散模型快速生成数据

从头训练扩散模型计算量极大,推荐直接使用 Hugging Face 的 diffusers 库调用已经训练好的模型,一步到位生成高质量合成图像。这里以 Stable Diffusion 为例,它使用潜在空间扩散,效率更高。

安装依赖

pip install diffusers transformers accelerate

基础文本到图像生成

from diffusers import StableDiffusionPipeline
import torch

model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
pipe = pipe.to("cuda")

prompt = "a realistic photo of a cat wearing a hat, high quality"
image = pipe(prompt).images[0]
image.save("syn_cat.png")

批量生成特定类别的训练数据: 通过编写包含类别描述、背景、风格变化的提示词列表,循环生成即可。例如生成“不同颜色的汽车在街道”:

prompts = [
    "a red sedan driving on a rainy street",
    "a blue SUV parked in front of a house, sunny day",
    "a yellow sports car on a mountain road, dramatic lighting",
    ...
]
for i, prompt in enumerate(prompts):
    image = pipe(prompt).images[0]
    image.save(f"syn_car_{i}.jpg")

3.3 用 DDPM 自训练小规模数据(概念演示)

如果你希望在自己的数据集上从零训练一个扩散模型,可以基于简化的 DDPM 代码理解整个过程。以下是核心组件的伪代码,帮助你把握脉络。

前向扩散:定义固定调度器 betas,根据时间步 t 直接采样加噪图像:

def q_sample(x_start, t, noise=None):
    # alpha_t = 1 - beta_t ; alpha_bar_t = product_{s=1..t} alpha_s
    sqrt_alphas_bar = torch.sqrt(alphas_bar[t])
    sqrt_one_minus_alphas_bar = torch.sqrt(1 - alphas_bar[t])
    return sqrt_alphas_bar * x_start + sqrt_one_minus_alphas_bar * noise

模型:一个 U-Net,输入加噪图像与时间步 t,输出预测的噪声。

unet = Unet(
    dim=64,
    channels=3,
    dim_mults=(1,2,4,8)
).to(device)

简化训练步

  1. 从数据集中取出一批真实图像 x0
  2. 随机采样时间步 t 和标准高斯噪声 noise
  3. q_sample 生成加噪图像 x_t
  4. 让 U-Net 预测添加的噪声 noise_pred = unet(x_t, t)
  5. 损失函数为预测噪声与真实噪声的 MSE:loss = mse(noise_pred, noise)

采样生成:从纯噪声 x_T 开始,逐步迭代去噪:

@torch.no_grad()
def p_sample(model, x, t):
    predicted_noise = model(x, t)
    alpha = alphas[t]
    alpha_bar = alphas_bar[t]
    # 反向过程公式
    x = 1 / sqrt(alpha) * (x - (1-alpha)/sqrt(1-alpha_bar) * predicted_noise)
    if t > 0:
        x = x + sqrt(beta[t]) * torch.randn_like(x)
    return x