GAN 与 VAE:生成模型的两条路线
Diffusion 之前主导生成 AI 的两大架构。理解它们才理解为什么 Diffusion 赢了。
L5-02 讲了 Diffusion。这一篇讲它的”前辈”——GAN 和 VAE。
虽然 2023+ Diffusion 在图像生成上胜出,但:
- GAN 在某些场景仍最强(StyleGAN 的人脸细节)
- VAE 是 Diffusion 的基础(Latent Diffusion = VAE encoder + Diffusion + VAE decoder)
不懂这两个,就只是”调用 Diffusers 库”——懂了,才理解整个生成 AI 的演化逻辑。
一句话定义
| 模型 | 核心思想 |
|---|---|
| VAE(2013) | 把数据压缩到一个连续概率空间,再从中采样还原 |
| GAN(2014) | 让生成器 vs 判别器博弈,谁也骗不了谁 = 生成器学会了真实分布 |
| Diffusion(2020) | 从纯噪声开始,一步一步去噪还原真实图像 |
一、GAN:博弈生成
L5-02 已经介绍过基本思想。这里讲架构和训练细节。
架构
随机噪声 z (高斯)
↓
[生成器 G] (CNN)
↓
假图 G(z)
↓
←————— 真图 x
[判别器 D] (CNN)
↓
0 (假) 或 1 (真)
目标函数(minimax 游戏)
- D 想最大化:真图给高分,假图给低分
- G 想最小化:让 D 对自己生成的假图也给高分
理论上,这个博弈的纳什均衡是 D 无法分辨(输出 0.5)—— 此时 G 完美捕获真实分布。
训练循环
for batch in real_data:
# 1. 训判别器
real_imgs = batch
fake_imgs = G(noise)
d_loss = -log(D(real_imgs)) - log(1 - D(fake_imgs))
d_loss.backward()
optimizer_D.step()
# 2. 训生成器
fake_imgs = G(noise)
g_loss = -log(D(fake_imgs)) # 想骗过 D
g_loss.backward()
optimizer_G.step()
GAN 的难训出名
历史上 GAN 训练极不稳定:
| 问题 | 表现 |
|---|---|
| 模式崩溃 | 生成器只学到几种样本(少样多样性) |
| 判别器太强 | D 准确率 100% → G 没有梯度可学 |
| 训练震荡 | Loss 上下跳,永远不收敛 |
| 超参敏感 | 调一点点就坏 |
后续工作(WGAN、StyleGAN、Progressive GAN)大多是为了解决这些。
GAN 的代表作
| 模型 | 突破 |
|---|---|
| DCGAN(2015) | 第一次用 CNN,让训练稳定 |
| Progressive GAN(2017) | 从低分辨率逐步加细节 |
| StyleGAN / StyleGAN2(2018-2019) | 人脸生成 SOTA(thispersondoesnotexist) |
| BigGAN(2018) | ImageNet 大规模条件生成 |
| CycleGAN(2017) | 无配对图像翻译(马 ↔ 斑马) |
二、VAE:概率压缩
核心思想
让神经网络学一个”压缩 + 还原”管道,但压缩到一个概率分布而不是固定向量:
真图 x
↓
[Encoder] (CNN → MLP)
↓
潜变量分布 q(z|x) (高斯:μ, σ)
↓ 采样
潜变量 z
↓
[Decoder] (MLP → CNN 反卷积)
↓
重建图 x'
Loss = 重建 + 正则
第一项:让重建逼近原图。 第二项:让潜空间分布逼近标准高斯(让”任何随机向量都能生成合理图像”)。
第二项的 KL 散度强迫潜空间是连续 + 平滑的——这就是 VAE 相对普通 autoencoder 的优势。
重参数化技巧(reparameterization trick)
直接从 采样不可导,无法 backprop。技巧:
# 不可导:
z = sample(N(mu, sigma))
# 可导:
epsilon = sample(N(0, 1)) # 不依赖参数
z = mu + sigma * epsilon # 这一步可导!
把”随机性”挪到 ,让 的梯度能流回 encoder。这是 VAE 训练的关键 trick。
VAE 的”模糊”问题
VAE 重建的图像总是有点模糊—— 这是 MSE 损失 + 高斯假设的副作用(“平均化” 不同可能性)。
这是为什么 GAN 在视觉质量上长期领先 VAE。
但 VAE 的潜空间更有用:可以做插值、操作语义维度等。
三、GAN vs VAE 对比
| 维度 | GAN | VAE |
|---|---|---|
| 图像质量 | 锐利、细节多 | 偏模糊 |
| 训练稳定性 | 不稳定(模式崩溃) | 稳定 |
| 可控性 | 难(潜空间无结构) | 强(连续平滑) |
| 数学 | minimax 博弈,无明确目标 | 变分推断,清晰概率框架 |
| 多样性 | 易丢失(mode collapse) | 自然覆盖 |
| 生成速度 | 一次前向 = 一张图 | 一次前向 = 一张图 |
四、与 Diffusion 的关系
Diffusion 借鉴了两者的长处:
- 像 VAE:明确的概率框架,loss = 变分下界
- 像 GAN:生成质量极高
- 但比两者都好:训练稳定(GAN 弱项)+ 图像锐利(VAE 弱项)
Latent Diffusion(Stable Diffusion 的核心):
真图 → VAE encoder → 潜变量 → Diffusion → 潜变量 → VAE decoder → 真图
VAE 仍然在 Diffusion 内部,作为压缩/解压缩组件——不是被淘汰,是被”集成”。
五、何时还用 GAN / VAE
还在用 GAN 的场景
- StyleGAN 系列:高保真人脸 / 单类目高质量生成
- GAN-based 超分辨率(ESRGAN)
- CycleGAN-style 图像翻译(依然是某些场景的 SOTA)
- 实时生成(一次前向,比 Diffusion 多步快得多)
还在用 VAE 的场景
- Latent space 操作:风格迁移、属性编辑
- 异常检测:重建误差大 = 异常
- 数据压缩:作为可学习的压缩器
- 任何 Diffusion / Flow-based 模型的潜空间 部分
一个最小 VAE 代码
import torch
import torch.nn as nn
class VAE(nn.Module):
def __init__(self, input_dim=784, latent_dim=20):
super().__init__()
# Encoder
self.fc1 = nn.Linear(input_dim, 400)
self.fc_mu = nn.Linear(400, latent_dim)
self.fc_logvar = nn.Linear(400, latent_dim)
# Decoder
self.fc2 = nn.Linear(latent_dim, 400)
self.fc3 = nn.Linear(400, input_dim)
def encode(self, x):
h = torch.relu(self.fc1(x))
return self.fc_mu(h), self.fc_logvar(h)
def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + eps * std
def decode(self, z):
h = torch.relu(self.fc2(z))
return torch.sigmoid(self.fc3(h))
def forward(self, x):
mu, logvar = self.encode(x.view(-1, 784))
z = self.reparameterize(mu, logvar)
x_recon = self.decode(z)
return x_recon, mu, logvar
def vae_loss(x, x_recon, mu, logvar):
recon = nn.functional.binary_cross_entropy(x_recon, x.view(-1, 784), reduction='sum')
kl = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
return recon + kl
50 行,能在 MNIST 上跑出基本生成。
GAN 和 VAE 像”高速公路上的两条车道”—— 2014-2020 它们独立发展,各有所长。
Diffusion(2020+)相当于把两条道合并:保留 VAE 的概率框架 + 借鉴 GAN 的生成质量。 然后 Latent Diffusion 还继承了 VAE 的压缩能力。
学 GAN / VAE 不是学”已经过时的东西”—— 是学Diffusion 为什么这样设计。
下一篇推荐:L5-02 Diffusion 数学 或 可视化:GAN 训练博弈。
读到这里说明你认真在学 🎯
订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。
讨论区
· 用 GitHub 账号登录评论src/components/Comments.astro 顶部填入
仓库 ID 和分类 ID(见组件注释里的配置步骤)。