HelloAI
L3 第 10 篇 🐥 难度 🕒 13 分钟

GAN 与 VAE:生成模型的两条路线

Diffusion 之前主导生成 AI 的两大架构。理解它们才理解为什么 Diffusion 赢了。

阿莱
2026/9/16
📓 动手跑代码 Run on Colab View source Colab 免费 GPU · 无需安装环境

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 游戏)

minGmaxDExpdata[logD(x)]+Ezpz[log(1D(G(z)))]\min_G \max_D \mathbb{E}_{x \sim p_{data}}[\log D(x)] + \mathbb{E}_{z \sim p_z}[\log(1 - D(G(z)))]
  • 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 = 重建 + 正则

L=E[xx2]+DKL(q(zx)p(z))L = \mathbb{E}[\|x - x'\|^2] + D_{KL}(q(z|x) \| p(z))

第一项:让重建逼近原图。 第二项:让潜空间分布逼近标准高斯(让”任何随机向量都能生成合理图像”)。

第二项的 KL 散度强迫潜空间是连续 + 平滑的——这就是 VAE 相对普通 autoencoder 的优势。

重参数化技巧(reparameterization trick)

直接从 q(zx)q(z|x) 采样不可导,无法 backprop。技巧:

# 不可导:
z = sample(N(mu, sigma))

# 可导:
epsilon = sample(N(0, 1))   # 不依赖参数
z = mu + sigma * epsilon    # 这一步可导!

把”随机性”挪到 ϵ\epsilon,让 μ,σ\mu, \sigma 的梯度能流回 encoder。这是 VAE 训练的关键 trick

VAE 的”模糊”问题

VAE 重建的图像总是有点模糊—— 这是 MSE 损失 + 高斯假设的副作用(“平均化” 不同可能性)。

这是为什么 GAN 在视觉质量上长期领先 VAE。

但 VAE 的潜空间更有用:可以做插值、操作语义维度等。

三、GAN vs VAE 对比

维度GANVAE
图像质量锐利、细节多偏模糊
训练稳定性不稳定(模式崩溃)稳定
可控性难(潜空间无结构)强(连续平滑)
数学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 账号登录评论
⚠️ Giscus 评论未配置 —— 在 src/components/Comments.astro 顶部填入 仓库 ID 和分类 ID(见组件注释里的配置步骤)。