深度学习入门站 · 理解深度学习
首页 › 第 15 章

生成对抗网络

造假专家和打假专家互相切磋,最后造出以假乱真的图片。
图 15-1:生成对抗网络(GAN)的对抗训练
图 15-1:生成对抗网络(GAN)的对抗训练

这一章的主角是生成对抗网络(Generative Adversarial Network,GAN)——一种能"凭空造图"的模型,2014 年提出后火遍图像生成领域。它的思路很特别:让两个网络互相"打架"、共同进步,最后留下的那个网络,能把随机噪声变成以假乱真的数据。学完这一章,你会明白 GAN 的"造假 vs 打假"博弈是怎么回事,看得懂那个著名的 min-max 目标函数,也知道它为什么难训练,以及它怎么生成图片、给模糊照片"补像素"。

造假专家 vs 打假专家

先讲一个故事。有个造假币的人手艺越来越精,想做出足以乱真的钞票;银行里的验钞员则发誓识破每一张假币。两人互相较劲:验钞员越厉害,假币越难蒙混过关,逼得造假者改进工艺;造假者工艺越好,验钞员也必须练就更强的眼力。这样循环往复,直到有一天,验钞员对着真币假币完全分不出来——只能抛硬币瞎猜——那一刻,造假者就"出师"了。

💵
生活类比
造假币者就是生成器(Generator):把一堆随机噪声"印刷"成看起来像真实数据的样本;验钞员就是判别器(Discriminator):拿到一张图,输出一个概率,判断"这是真的还是假的"。两人对练的过程,就是 GAN 的训练过程。

正式一点:生成器记作 $g[z, \theta]$,输入是一段随机噪声 $z$(也叫潜变量,通常从标准正态分布里抽取),参数为 $\theta$,输出一张"假样本":

$$x^* = g[z, \theta]$$

这个式子的意思是:让网络把"没啥意义的噪声"改写成"长得像真数据"的样本。判别器记作 $f[x, \phi]$,它把输入 $x$ 变成一个分数,再经过 sigmoid 函数压成"这玩意儿是真的"的概率。训练时给真样本打标签 $y=1$、假样本打标签 $y=0$,用我们熟悉的二元交叉熵来教判别器学会打假。

书里用了一个特别直观的一维例子:真实样本集中在某个位置,生成器 $g[z, \theta] = z + \theta$ 只是把噪声整体平移 $\theta$。训练中判别器学出一条 S 形曲线来分开两类样本,生成器则把 $\theta$ 往"更像真的"方向挪,直到两类样本混在一起、S 曲线变成一条平线——判别器彻底没辙。

零和博弈:min-max 目标函数

GAN 的训练目标是一个极小极大(min-max)博弈。把判别器简写为 $D$、生成器简写为 $G$(书中分别写作 $f$ 和 $g$),目标函数长这样:

$$\min_{G} \max_{D} \ \mathbb{E}_{x \sim p_{\text{data}}}[\log D(x)] + \mathbb{E}_{z \sim p(z)}[\log(1 - D(G(z)))]$$

这个式子要拆开看。第一项里,$D(x)$ 是"判别器认为真图是真的"的概率,判别器希望它越大越好;第二项里,$D(G(z))$ 是"判别器认为假图是真的"的概率——判别器希望它越小越好,生成器却希望它越大越好(骗过判别器)。所以 $\max_D$ 是判别器的目标,$\min_G$ 是生成器的目标:一个要赢,一个要让它输,这就是一场零和博弈。

如果训练顺利,双方会达到一个均衡:生成器的输出分布与真实分布几乎重合,判别器对任何输入都只能给出 0.5——等于瞎猜(博弈论里这种谁都占不到便宜的僵局叫纳什均衡)。实践上我们并不直接优化上面这个大式子,而是把它拆成两个小损失,交替更新:先固定生成器、更新判别器让它更会打假,再固定判别器、更新生成器让它更会造假。

💡
**核心要点**:GAN = 两个网络对练。生成器只学"从噪声到数据"的映射,并不建模数据的概率分布(所以它无法告诉你某张图"有多可能是真的");判别器只是训练时的"陪练",训练一结束就被扔掉,只留下生成器。

骨架代码:PyTorch 里的两个网络

下面用 PyTorch 搭一个最朴素的 GAN 骨架(面向 MNIST 手写数字,28×28 像素):生成器把 100 维噪声变成一张图,判别器把一张图压成一个真假概率。

import torch
import torch.nn as nn

class Generator(nn.Module):
    """造假者:把 100 维噪声 z 变成一张 28x28 的假图"""
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(100, 128)
        self.out = nn.Linear(128, 28 * 28)

    def forward(self, z):
        x = torch.relu(self.fc(z))
        return torch.tanh(self.out(x))     # 像素值压到 [-1, 1]

class Discriminator(nn.Module):
    """验钞员:输入一张图,输出"它是真的"的概率"""
    def __init__(self):
        super().__init__()
        self.fc = nn.Linear(28 * 28, 128)
        self.out = nn.Linear(128, 1)

    def forward(self, x):
        h = torch.relu(self.fc(x))
        return torch.sigmoid(self.out(h))  # sigmoid 压成 (0, 1)

# 训练时两步交替:更新判别器(真图→1、假图→0),再更新生成器(骗过判别器)

训练为什么这么难?

GAN 的概念很简单,训练却出了名的"玄学"。两个最经典的翻车姿势:

  • 模式坍塌(Mode Collapse):生成器发现某一种"必胜"的输出——比如同一张脸——能稳定骗过判别器,于是干脆偷懒,把随机噪声 $z$ 全变成这一个模式。生成出来的样本千篇一律,多样性归零。
  • 梯度消失:如果判别器练得太强、能把真假完美分开,生成器收到的梯度信号就变得极小,怎么改参数都不影响分数,训练直接停摆。因为假样本和真样本在空间里几乎不重叠,而 GAN 的损失本质上是在衡量两个分布的"重叠程度"——不重叠时,这个量没法给生成器指路。
⚠️
两个网络必须势均力敌:判别器太强,生成器学不到东西;太弱,生成器随便混混也能过关。为了治这个毛病,WGAN 把"重叠程度"换成了推土机距离——把一堆概率质量从一处推到另一处要花多少力气,两堆不挨着也能算、也能给梯度,训练稳定性因此大幅提升。

GAN 能干什么?

把噪声变成图片只是起点,判别器这个"裁判"还能用在更多地方:

  • 图像生成:DCGAN、StyleGAN 等能生成以假乱真的人脸与场景;StyleGAN 还能把脸型、发型、肤色等不同尺度的特征分开控制。
  • 超分辨率(SRGAN):给模糊的小图"脑补"高清细节。判别器当"画质裁判",逼着放大网络输出让裁判分不清真假的高清图。
  • 图像翻译:给灰度图着色、把照片变成名画风格(CycleGAN 连配对图片都不需要)。
  • 条件生成:在输入端额外加一个属性向量 $c$,就能指定生成"金发""戴眼镜"等特定特征的人脸。
🧠
判别器的本质,是给"主模型"当一个额外的损失函数——它的输出就是"你生成的东西有多像真的"的评分。在超分辨率、图像翻译等任务里,主模型并不想"随机生成",只是借这个裁判来逼自己输出更真实的结果。

🎯 小测验

1. GAN 中生成器的输入是什么?
2. 当 GAN 训练达到理想均衡时,判别器对真假样本的输出概率应接近多少?
3. 生成器只会生成同一种"以假乱真"的样本,所有输出几乎一模一样,这叫什么?