共计 3781 个字符,预计需要花费 10 分钟才能阅读完成。
为什么需要 CGAN?原始 GAN 的局限性
原始 GAN(Generative Adversarial Network)虽然能生成逼真数据,但存在两个明显问题:

- 缺乏生成方向控制:无法指定生成特定类别(如生成数字 ”7″ 而非随机数字)
- 模式崩溃(Mode Collapse):生成器倾向于生成有限几种样本,缺乏多样性
CGAN(Conditional GAN)通过添加条件信息 y(如图像类别标签)解决这些问题。其核心思想是将条件信息同时输入生成器 (Generator) 和判别器(Discriminator),使生成过程可控。
CGAN 损失函数数学原理
CGAN 的目标函数是对原始 GAN 的扩展:
$$\min_G \max_D V(D,G) = \mathbb{E}{x\sim p[\log(1 – D(G(z|y)))]$$}(x)}[\log D(x|y)] + \mathbb{E}_{z\sim p_z(z)
其中:
- $G(z|y)$:给定噪声 z 和条件 y 时生成器的输出
- $D(x|y)$:判别器对真实数据 x(带条件 y)的判别概率
判别器 (Discriminator) 损失
判别器需要最大化正确分类真实 / 生成样本的能力:
$$L_D = -\mathbb{E}{x\sim p[\log(1 – D(G(z|y)))]$$}}[\log D(x|y)] – \mathbb{E}_{z\sim p_z
生成器 (Generator) 损失
生成器需要最小化被判别器识破的概率:
$$L_G = -\mathbb{E}_{z\sim p_z}[\log D(G(z|y))]$$
实际训练中更常用的是非饱和 (Non-saturating) 形式:
$$L_G = \mathbb{E}_{z\sim p_z}[\log(1 – D(G(z|y)))]$$
PyTorch 完整实现
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 定义生成器
class Generator(nn.Module):
def __init__(self, latent_dim, num_classes):
super().__init__()
self.label_emb = nn.Embedding(num_classes, num_classes)
self.model = nn.Sequential(nn.Linear(latent_dim + num_classes, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 512),
nn.LeakyReLU(0.2),
nn.Linear(512, 1024),
nn.LeakyReLU(0.2),
nn.Linear(1024, 784),
nn.Tanh())
def forward(self, z, labels):
c = self.label_emb(labels)
x = torch.cat([z, c], dim=1)
return self.model(x)
# 定义判别器
class Discriminator(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.label_emb = nn.Embedding(num_classes, num_classes)
self.model = nn.Sequential(nn.Linear(784 + num_classes, 1024),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(1024, 512),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(512, 256),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(256, 1),
nn.Sigmoid())
def forward(self, x, labels):
x = x.view(x.size(0), -1)
c = self.label_emb(labels)
x = torch.cat([x, c], dim=1)
return self.model(x)
# 训练参数
batch_size = 64
lr = 0.0002
epochs = 100
latent_dim = 100
# 初始化网络
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
generator = Generator(latent_dim, 10).to(device)
discriminator = Discriminator(10).to(device)
# 损失函数和优化器
adversarial_loss = nn.BCELoss()
optimizer_G = optim.Adam(generator.parameters(), lr=lr)
optimizer_D = optim.Adam(discriminator.parameters(), lr=lr)
# 数据加载
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
dataset = datasets.MNIST('data', train=True, download=True, transform=transform)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 训练循环
for epoch in range(epochs):
for i, (imgs, labels) in enumerate(dataloader):
# 真实数据和噪声
real_imgs = imgs.to(device)
labels = labels.to(device)
z = torch.randn(imgs.size(0), latent_dim).to(device)
gen_labels = torch.randint(0, 10, (imgs.size(0),)).to(device)
# 训练生成器
optimizer_G.zero_grad()
gen_imgs = generator(z, gen_labels)
validity = discriminator(gen_imgs, gen_labels)
g_loss = adversarial_loss(validity, torch.ones_like(validity))
g_loss.backward()
optimizer_G.step()
# 训练判别器
optimizer_D.zero_grad()
real_loss = adversarial_loss(discriminator(real_imgs, labels), torch.ones_like(validity))
fake_loss = adversarial_loss(discriminator(gen_imgs.detach(), gen_labels), torch.zeros_like(validity))
d_loss = (real_loss + fake_loss) / 2
d_loss.backward()
optimizer_D.step()
print(f"[Epoch {epoch}/{epochs}] D loss: {d_loss.item():.4f}, G loss: {g_loss.item():.4f}")
关键调参技巧
- 学习率选择:
- 推荐范围:0.0001~0.0005
-
判别器和生成器可以采用不同学习率(如 D: 0.0004, G: 0.0001)
-
Batch Size 影响:
- 太小(<32)易导致模式崩溃
- 太大(>256)可能降低生成质量
-
MNIST/CIFAR 推荐值:64~128
-
噪声维度:
- latent_dim 通常取 50~200
- 复杂数据集需要更高维度
常见问题与解决方案
1. 模式崩溃(Mode Collapse)
现象:生成器只产生几种相似样本
解决方法:
– 增加 mini-batch discrimination
– 使用 Wasserstein GAN(WGAN)损失
– 尝试不同的噪声分布
2. 梯度消失
现象:判别器过早收敛,生成器无法学习
解决方法:
– 使用 LeakyReLU 替代 ReLU
– 训练时先更新多次判别器
– 添加梯度惩罚(Gradient Penalty)
3. 生成质量不稳定
现象:部分 epoch 生成效果好但无法保持
解决方法:
– 使用指数移动平均 (EMA) 保存模型
– 添加谱归一化(Spectral Normalization)
– 适当降低学习率
延伸练习
- 尝试修改损失函数权重,观察生成效果变化
- 在 CIFAR-10 数据集上测试不同条件信息的编码方式
- 实现 WGAN-GP 版本并比较训练稳定性
总结
通过本文我们深入理解了 CGAN 损失函数的设计原理和实现细节。关键收获包括:
– 条件信息的引入方式(嵌入层 + 拼接)
– 判别器与生成器的博弈平衡
– 实际训练中的调参经验
建议初学者先从 MNIST 等简单数据集开始实验,逐步掌握损失函数对生成效果的影响规律。
