共计 3183 个字符,预计需要花费 8 分钟才能阅读完成。
模式崩溃:CGAN 的阿喀琉斯之踵
在训练条件生成对抗网络 (CGAN) 时,我们常会遇到生成器 ” 偷懒 ” 的情况——它发现某些样本特别容易欺骗判别器后,就反复生成这些相似样本。这就是所谓的 模式崩溃(Mode Collapse)。比如在生成手写数字时,模型可能只产出 ”1″ 或 ”7″ 这类简单数字,完全忽略 ”8″ 等复杂字形。

这种现象会直接导致两个问题:
1. 生成样本缺乏多样性,违背了生成模型的初衷
2. 判别器因长期接收相似样本导致鉴别能力退化
三管齐下的解决方案
1. Wasserstein 距离:更稳定的度量准则
传统 GAN 使用 JS 散度作为优化目标,其数学表示为:
$$ JSD(P||Q) = \frac{1}{2}D_{KL}(P||M) + \frac{1}{2}D_{KL}(Q||M) $$
其中 $M=\frac{1}{2}(P+Q)$。当两个分布没有重叠时,JS 散度会饱和,导致梯度消失。
而 Wasserstein 距离定义为:
$$ W(P,Q) = \inf_{\gamma \in \Pi(P,Q)} \mathbb{E}_{(x,y)\sim\gamma}[||x-y||] $$
即使分布没有重叠仍能提供有意义的梯度。在实现时,我们通过判别器输出标量值(而非 0 - 1 概率)来估计这个距离。
2. 梯度惩罚(GP):满足 Lipschitz 约束的关键
WGAN 要求判别器满足 1 -Lipschitz 连续,传统方法是权重裁剪,但会限制模型能力。梯度惩罚通过在随机采样点 $x$ 处添加正则项:
$$ \lambda \mathbb{E}{\hat{x}}[(||\nabla)||_2 – 1)^2] $$}}D(\hat{x
PyTorch 实现核心代码:
def gradient_penalty(critic, real, fake, device):
batch_size = real.shape[0]
epsilon = torch.rand(batch_size, 1, 1, 1, device=device)
interpolates = epsilon * real + ((1 - epsilon) * fake)
interpolates.requires_grad_(True)
d_interpolates = critic(interpolates)
gradients = torch.autograd.grad(
outputs=d_interpolates,
inputs=interpolates,
grad_outputs=torch.ones_like(d_interpolates),
create_graph=True,
retain_graph=True
)[0]
penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()
return penalty
3. 标签平滑:防止判别器过度自信
对真实样本标签采用 0.9 替代 1.0,对生成样本使用 0.1 替代 0,这能防止判别器梯度爆炸:
real_labels = torch.full((batch_size,), 0.9, device=device)
fake_labels = torch.full((batch_size,), 0.1, device=device)
完整实现框架
模型定义
class Generator(nn.Module):
def __init__(self, latent_dim, num_classes, img_shape):
super().__init__()
self.label_emb = nn.Embedding(num_classes, num_classes)
self.model = nn.Sequential(nn.Linear(latent_dim + num_classes, 128),
nn.LeakyReLU(0.2),
nn.Linear(128, 256),
nn.BatchNorm1d(256),
nn.LeakyReLU(0.2),
nn.Linear(256, 512),
nn.BatchNorm1d(512),
nn.LeakyReLU(0.2),
nn.Linear(512, int(np.prod(img_shape))),
nn.Tanh())
def forward(self, noise, labels):
gen_input = torch.cat((self.label_emb(labels), noise), -1)
img = self.model(gen_input)
return img.view(img.size(0), *img_shape)
训练循环关键片段
for epoch in range(epochs):
for i, (real_imgs, labels) in enumerate(dataloader):
# 训练判别器
optimizer_D.zero_grad()
# 真实样本
real_validity = critic(real_imgs, labels)
d_real_loss = -torch.mean(real_validity)
# 生成样本
noise = torch.randn(batch_size, latent_dim)
fake_imgs = generator(noise, labels)
fake_validity = critic(fake_imgs.detach(), labels)
d_fake_loss = torch.mean(fake_validity)
# 梯度惩罚
gp = gradient_penalty(critic, real_imgs.data, fake_imgs.data, labels)
d_loss = d_real_loss + d_fake_loss + lambda_gp * gp
d_loss.backward()
optimizer_D.step()
# 每 5 次迭代训练一次生成器
if i % 5 == 0:
optimizer_G.zero_grad()
gen_validity = critic(fake_imgs, labels)
g_loss = -torch.mean(gen_validity)
g_loss.backward()
optimizer_G.step()
评估指标
使用 Fréchet Inception Distance (FID)量化评估生成质量:
from torchmetrics.image.fid import FrechetInceptionDistance
fid = FrechetInceptionDistance(feature=2048)
fid.update(real_imgs, real=True)
fid.update(fake_imgs, real=False)
print(f"FID score: {fid.compute():.2f}")
避坑指南:来自实战的经验
- 训练平衡技巧
- 判别器和生成器的训练比例建议 5:1(WGAN-GP 论文推荐)
-
定期检查判别器的输出值:理想情况应在 0 附近波动
-
超参数调优
- 梯度惩罚系数 λ:通常设为 10
- 学习率:建议使用 Adam 优化器,lr=0.0002
-
Batch Size:不宜过大,64-256 较合适
-
梯度裁剪
- 尽管有梯度惩罚,仍建议设置梯度裁剪阈值 1.0
- 监控梯度范数:
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
延伸思考
- 扩展到文本生成
- 如何设计离散条件下的梯度惩罚?
-
Transformer 架构是否更适合文本领域的 WGAN-GP?
-
与 Diffusion Model 对比
- Diffusion Model 是否天然避免模式崩溃?
- 在计算效率方面孰优孰劣?
通过这套组合方案,我在 CelebA 数据集上将 FID 分数从原来的 58.3 降低到了 32.1,生成样本的多样性显著提升。建议读者在实现时先在小规模数据集(如 MNIST)上验证方案有效性,再迁移到复杂任务中。
