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

CGAN(Conditional GAN)通过引入条件标签,完美解决了这个问题。其核心思想是:在生成器和判别器的输入中均加入类别标签信息,使生成过程变得可控。但新手实现时常常会遇到以下问题:
- 标签未正确注入网络,导致模型退化为普通 GAN
- 生成器忽视条件信息,产生与标签不符的内容
- 模式崩溃 (Mode Collapse) 现象比普通 GAN 更严重
- 训练过程中判别器过早压倒生成器
技术实现
结构对比
与 DCGAN、WGAN 相比,CGAN 的特别之处在于:
- 输入维度增加:生成器的噪声输入 z 需要与条件标签结合
- 判别器多任务:既要判断真伪,又要评估内容与标签的匹配度
- 特征融合方式 :条件信息可通过拼接(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)是目前最可靠的生成质量评估指标。实现步骤:
- 计算真实图像和生成图像在 Inception-v3 特征空间的均值和协方差
- 使用以下公式计算距离:
# 简化版 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
训练技巧
- 学习率策略:
- 初始值设为 2e-4(Adam 优化器的经验值)
-
使用线性衰减到 0
-
防止模式崩溃:
- 每轮记录生成样本的类别分布
-
当某些类别突然消失时,暂停训练并调小学习率
-
可视化监控:
- 固定几组 (z, label) 作为测试输入,定期生成样本观察变化
- 绘制判别器 / 生成器损失曲线,理想状态应保持动态平衡
避坑指南
类别不平衡处理
当某些类别的样本过少时:
- 在 DataLoader 中设置
weighted_sampler - 调整对应类别的生成器损失权重
- 对少数类别使用更强的数据增强
梯度消失调试
如果判别器过早收敛:
- 检查是否误用了 BatchNorm
- 尝试给生成器添加跳跃连接
- 改用 WGAN-GP 的梯度惩罚策略
显存优化
批量较大时:
- 使用
torch.cuda.empty_cache()定期清理缓存 - 在验证阶段设置
torch.no_grad() - 考虑梯度累积(小 batch 多次计算后再更新)
思考与扩展
如何实现多模态生成?比如同时控制数字类别和字体风格?一个可行的思路是:
- 将不同模态的条件信息分别嵌入
- 设计交叉注意力机制融合多模态条件
- 在判别器中添加多个分类头分别验证各条件
期待你在实践中探索更多可能性!
正文完
发表至: 未分类
近一天内
