CGAN条件生成对抗网络实战:从零构建图像生成模型

1次阅读
没有评论

共计 2539 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

背景痛点

传统 GAN 虽然能生成逼真图像,但存在一个致命缺陷——无法控制生成内容的类别。比如训练一个手写数字生成模型时,我们无法指定生成 ”7″ 或 ”9″,完全依赖随机采样。这种不可控性在实际应用中成为硬伤。

CGAN 条件生成对抗网络实战:从零构建图像生成模型

CGAN(Conditional GAN)通过引入条件标签,完美解决了这个问题。其核心思想是:在生成器和判别器的输入中均加入类别标签信息,使生成过程变得可控。但新手实现时常常会遇到以下问题:

  • 标签未正确注入网络,导致模型退化为普通 GAN
  • 生成器忽视条件信息,产生与标签不符的内容
  • 模式崩溃 (Mode Collapse) 现象比普通 GAN 更严重
  • 训练过程中判别器过早压倒生成器

技术实现

结构对比

与 DCGAN、WGAN 相比,CGAN 的特别之处在于:

  1. 输入维度增加:生成器的噪声输入 z 需要与条件标签结合
  2. 判别器多任务:既要判断真伪,又要评估内容与标签的匹配度
  3. 特征融合方式 :条件信息可通过拼接(concat) 或嵌入 (embedding) 两种方式注入

条件注入方式

1. 拼接(Concatenation)

最直观的实现方式,直接将标签拼接在噪声向量或图像特征上。以 MNIST 生成为例:

# 生成器输入示例
z = torch.randn(batch_size, latent_dim)  # 随机噪声
y = F.one_hot(labels, num_classes=10)    # 标签 one-hot 编码
generator_input = torch.cat([z, y], dim=1)  # 拼接操作

2. 嵌入(Embedding)

更优雅的实现方式,尤其适合类别数较多的情况:

# 使用嵌入层处理标签
class Generator(nn.Module):
    def __init__(self):
        self.label_embedding = nn.Embedding(num_classes, embedding_dim)
        # ... 其他层定义...

    def forward(self, z, labels):
        embedded_labels = self.label_embedding(labels)
        x = torch.cat([z, embedded_labels], dim=1)
        # ... 后续处理...

完整 PyTorch 实现

以下是生成器的核心代码(判别器结构对称):

import torch.nn as nn

class CGAN_Generator(nn.Module):
    def __init__(self, latent_dim=100, num_classes=10, img_channels=1):
        super().__init__()
        self.label_embedding = nn.Embedding(num_classes, latent_dim)

        self.model = nn.Sequential(# 输入: latent_dim*2 (噪声 + 标签嵌入)
            nn.Linear(latent_dim*2, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 512),
            nn.BatchNorm1d(512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 1024),
            nn.BatchNorm1d(1024),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(1024, img_channels*28*28),  # MNIST 尺寸
            nn.Tanh())

    def forward(self, z, labels):
        # 标签嵌入并与噪声拼接
        label_embed = self.label_embedding(labels)
        input = torch.cat([z, label_embed], dim=1)
        img = self.model(input)
        return img.view(img.size(0), 1, 28, 28)

关键参数说明:

  • latent_dim=100:经验值,过小会导致多样性不足
  • LeakyReLU(0.2):比 ReLU 更适合 GAN,避免梯度消失
  • BatchNorm:加速收敛,但判别器最后一层不要使用

优化实践

FID 评估

Fréchet Inception Distance (FID)是目前最可靠的生成质量评估指标。实现步骤:

  1. 计算真实图像和生成图像在 Inception-v3 特征空间的均值和协方差
  2. 使用以下公式计算距离:
# 简化版 FID 计算
def calculate_fid(real_features, fake_features):
    mu1, sigma1 = real_features.mean(0), torch.cov(real_features.T)
    mu2, sigma2 = fake_features.mean(0), torch.cov(fake_features.T)

    diff = mu1 - mu2
    covmean = sqrtm(sigma1 @ sigma2)

    fid = diff.dot(diff) + torch.trace(sigma1 + sigma2 - 2*covmean)
    return fid

训练技巧

  1. 学习率策略:
  2. 初始值设为 2e-4(Adam 优化器的经验值)
  3. 使用线性衰减到 0

  4. 防止模式崩溃:

  5. 每轮记录生成样本的类别分布
  6. 当某些类别突然消失时,暂停训练并调小学习率

  7. 可视化监控:

  8. 固定几组 (z, label) 作为测试输入,定期生成样本观察变化
  9. 绘制判别器 / 生成器损失曲线,理想状态应保持动态平衡

避坑指南

类别不平衡处理

当某些类别的样本过少时:

  1. 在 DataLoader 中设置weighted_sampler
  2. 调整对应类别的生成器损失权重
  3. 对少数类别使用更强的数据增强

梯度消失调试

如果判别器过早收敛:

  1. 检查是否误用了 BatchNorm
  2. 尝试给生成器添加跳跃连接
  3. 改用 WGAN-GP 的梯度惩罚策略

显存优化

批量较大时:

  1. 使用 torch.cuda.empty_cache() 定期清理缓存
  2. 在验证阶段设置torch.no_grad()
  3. 考虑梯度累积(小 batch 多次计算后再更新)

思考与扩展

如何实现多模态生成?比如同时控制数字类别和字体风格?一个可行的思路是:

  1. 将不同模态的条件信息分别嵌入
  2. 设计交叉注意力机制融合多模态条件
  3. 在判别器中添加多个分类头分别验证各条件

期待你在实践中探索更多可能性!

正文完
 0
评论(没有评论)