生成对抗网络这个方向我在第一次读到《Generative Adversarial Nets》的时候并没有立刻觉得它“惊艳”。当时最直观的感觉是原来生成模型还能这样定义——不直接去逼近数据分布而是让两个网络互相“逼”着对方提高。这篇论文我前后读了三遍第一遍只记住了min max公式第二遍才真正看懂判别器最优解那条推导第三遍是在自己动手训练某个图像生成Demo时踩了一堆坑之后回过头来重读的。这篇文章就按我自己的学习路径来写先讲论文的动机和核心设计再拆目标函数和理论证明然后结合模拟项目X的训练复盘聊实操中的问题最后说说后续模型在哪些方向上补了原论文的漏洞。1. 为什么我认为这篇论文是自学生成模型的“第一课”1.1 GAN之前生成模型的主流思路在读这篇论文之前我接触过的生成模型大致可以分为两类。一类是显式密度模型典型代表是最大似然估计框架下的模型。它们的目标很直接给数据分布假设一个带参数的概率密度函数然后通过调整参数让训练样本的似然尽量大。这类方法理论上很漂亮但一旦数据维度变高比如图像这种动辄几万维的样本空间密度函数的归一化常数计算会变得极其困难。另一类是自回归模型把联合分布拆成条件分布的乘积逐像素或逐token生成在语音和文本上效果很好但在图像这种高维连续空间里生成速度慢、长期依赖也不好处理。这两类模型有一个共同特点都是在“显式地建模数据分布”。而《Generative Adversarial Nets》走了一条完全不同的路它不显式地写分布不计算似然而是通过一个对抗博弈来隐式地逼近真实分布。这正是这篇论文让我印象最深刻的地方——它在方法论上做出了一个很根本的转向与其辛辛苦苦定义并计算一个复杂的密度函数不如让一个神经网络直接去“生成”再由另一个神经网络去评估生成得好不好。我当时读完引言后的反应是这个思路虽然简洁但蕴含了一个很反直觉的前提——两个网络互相博弈真的能收敛到我们要的那个最优解吗带着这个疑问往下读才慢慢体会到论文里那句“用判别器提供学习信号”到底有多关键。1.2 对抗思路的直觉生成器像伪造者判别器像鉴定师论文里最直觉化的描述就是把生成器比作伪造假币的团队判别器比作鉴定假币的警察。生成器努力让造假结果足够逼真判别器则努力分辨真伪。两者不断对抗最终生成器产出的样本会逼真到“以假乱真”。这个比喻对新手极其友好但它也容易让人忽略一个重点这个博弈里两个角色的目的并不对称。判别器的目标是最大化分类正确率本质上是做一个二分类真实样本标为真生成样本标为假。生成器的目标则恰恰相反它希望最小化判别器对生成样本的判断正确率也就是让判别器把生成样本误判为真。两个网络的目标函数大致上是同一个式子的一正一反这正是原文目标函数的由来。这里有一个很有意思的点生成器并不是直接去模仿训练样本的像素细节它只通过判别器给出的梯度来间接学习。因此整个系统的性能上限其实很大程度上取决于判别器能否提供准确且有信息量的梯度信号。在实际训练前我觉得这个对抗思路有点“太完美”。后来亲手训练后才意识到博弈双方的平衡极其脆弱但这个“脆弱”本身又是另一个值得深挖的问题——我会在后面的实操章节详细展开。2. 原始论文的公式拆解min max目标函数到底在做什么2.1 目标函数里每一项的含义原论文的目标函数是一个极小极大博弈$$\min_{G} \max_{D} V(D,G) \mathbb{E}{x \sim p{data}(x)}[\log D(x)] \mathbb{E}_{z \sim p_z(z)}[\log(1 - D(G(z)))]$$初学者第一次看这个式子容易懵。我在模拟项目X里也遇到过同事问这里面怎么又是min又是max训练的时候参数到底往哪调拆开看就清楚了。先看判别器D这部分。D是一个二分类网络输入一张图片输出一个介于0到1之间的标量表示“这张图片来自真实数据的概率”。对真实样本x我们希望D(x)尽量接近1所以 $\log D(x)$ 要尽量大对生成样本 $G(z)$我们希望D的输出尽量接近0也就是 $\log(1 - D(G(z)))$ 尽量大。所以判别器的目标就是最大化整个式子这对应外层max。再看生成器G。它的参数只影响第二项 $\log(1 - D(G(z)))$。生成器希望自己生成的图片被判别器判定为“真”也就是 $D(G(z))$ 尽量接近1那么 $\log(1 - D(G(z)))$ 就尽量小。因此生成器要最小化整个式子这对应外层min。这样理解以后公式就不再可怕了它本质上是一个二元零和博弈的损失函数两边共享同一个目标但一方的增益就是另一方的损失。不过我在实际中慢慢发现纸上这样写很对称训练时完全不对称——判别器收敛得太快几乎是GAN训练里最常见的病。2.2 “训练D到最优”与“梯度消失”的两个极端论文在推导最优解时先假设生成器G固定然后去求最优判别器D。这个假设在理论上很方便但在实操中就埋下了一个隐患如果每次都把D训练到接近最优生成器的梯度会不会变得非常糟答案是肯定的。我已经记不清在模拟项目X里踩过多少次这种坑了。D太弱G得不到有效梯度生成图像一直是噪声D太强梯度又会迅速消失G更新后几乎没有变化。论文原文在实验部分用一个三层全连接网络加tanh激活跑通了MNIST和CIFAR-10这部分描述得比较简略很多人跳过。但真正动过手的人都知道训练GAN在早期更像是在“抢救”一个随时可能崩溃的训练过程。我会在后面的训练章节详细说这里只强调一个核心原因原始GAN用二元交叉熵损失当D对生成样本的判断已经非常自信且正确时 $\log(1 - D(G(z)))$ 的梯度会趋近于0生成器几乎学不到东西。后来很多研究都试图解决这个梯度消失的问题比如换用最小二乘损失、改用Wasserstein距离本质都是为了让生成器始终能拿到有意义的梯度。2.3 为什么理论推导最后会指向JS散度论文里有个非常漂亮的推导环节在最优判别器确定以后目标函数可以被改写为真实分布与生成分布之间某个距离度量。推导并非“突然出现”而是顺着定义一步步推下来的。我先说结论当D优化到最优时目标函数的值与真实分布 $p_{data}$ 和生成分布 $p_g$ 之间的JS散度Jensen-Shannon散度相差一个常数。也就是说对抗训练本质上是在最小化两个分布之间的JS散度。这里有个初读时很容易忽略的细节JS散度是两个分布之间的对称度量理论上它比不对称的KL散度更适合做生成模型的训练目标。但它有一个致命的边界行为——如果两个分布的重叠非常少JS散度会变成一个常数 $\log 2$梯度信号彻底消失。这个特性从数学上解释了为什么原始GAN在训练初期或分布覆盖不佳时容易遇到梯度消失的问题。当年读到这一段只觉得“数学很优美”后来对照WGAN的论文才明白优美和实用之间的鸿沟恰好就是由JS散度自身的缺陷造成的。理解这一点对阅读后续几乎所有GAN改进论文都有帮助因为它们大多是在换“分布之间距离度量”这件事。3. 全局最优与收敛性证明的阅读方法3.1 命题1固定G时最优D的表达式论文中在讲博奕最优解时给出了一个核心结论当G固定时最优判别器是$$D^*G(x) \frac{p{data}(x)}{p_{data}(x) p_g(x)}$$这个公式的实际含义非常明显。如果某点x在真实分布里概率很高、在生成分布里概率为0那么最优判别器输出接近1也就是判为真实样本反过来如果生成分布概率很高、真实分布很低最优判别器输出接近0。当两个分布概率相等时最优判别器恰好输出0.5这时它已经无法区分真伪了。我建议读者把这一步当作推导重点。大部分GAN变种的数学分析都会从“固定一方、求另一方最优”入手这个“坐标下降式”的思路是理解整个对抗框架的基础。但这里要提醒一句推导中的“最优判别器”是理论上的极限实践中网络容量有限、训练步数有限D几乎不可能真正做到全局最优。所以理论中的收敛保证在实际中只是一种近似倾向。3.2 全局最小值和对称性说明论文还证明了如果我们把最优判别器表达式代回目标函数并且用JS散度表达那么整个博弈的全局最小值在 $p_g p_{data}$ 时取得此时目标函数值等于 $-\log 4$。换句话说生成分布等于真实分布时判别器彻底失效生成器也达到了最理想状态。不过这个证明有一个隐含的对等关系博弈在示意图中出现“生成分布越过真实分布继续偏移”的情况时论文从对称性角度指出生成器最终还是会回到真实分布。理论上的对称性很优雅但是在实际训练里如果生成器过度拟合了训练数据里的某一部分子分布它不但不会自动回到全局最优反而会陷入我下面要讲的模式崩塌。3.3 理论证明的边界凸性要求与现实差距原论文的收敛性分析部分假定了博弈双方都在一个凸函数空间里更新。对这种无限维凸空间的设定作者承认它过于局限无法直接推广到神经网络这种高度非凸的环境。也就是说原始论文从理论上给出的收敛保证更多是“理想化版本的说明”而不是对实际深度网络训练过程的严格保证。这一点很容易被初学者忽略很多人看到“收敛到全局最优”就以为训练GAN是稳的结果自己一跑就发现问题重重。我建议在读论文时把理论部分当成“指向标”它告诉我们如果一切顺利系统的目标是什么但现实中深度网络的非凸性、有限容量和有限步数决定了通往这个目标的路上必然有大量工程陷阱。我在后面结合训练经验展开的很多内容其实都是围绕这个理论假设与现实的落差展开的。4. 实践复现中的真实体验模拟图像生成Demo的复盘4.1 网络结构与训练参数设定我在模拟项目X里搭了一个最接近原论文实验设置的图像生成Demo生成器从100维正态噪声出发通过全连接层映射到784维的像素空间激活函数用ReLU和tanh判别器是结构对称的全连接网络输出用sigmoid二分类概率。训练数据用的是标准的灰度手写数字数据集我刻意没有做任何数据增强想先看看最原始的GAN能做到什么效果。训练参数的初始设定是这样的batch size 128判别器用学习率0.0002的Adam生成器用学习率0.0002的Adam每轮先更新K次判别器再更新1次生成器。这里的K我一开始设成了5结果很快就出问题了判别器的loss稳定下降到接近0生成器梯度迅速消失输出的图像像一张灰蒙蒙的噪声图。这个经历让我明白原论文里那些看起来平平无奇的实验说明其实隐藏了太多没有写出来的调参细节。后来我把K从5改成1也就是让判别器和生成器交替各更新一次训练才勉强稳定下来。但即便这样生成的图像仍然有很多重复模式这就是下面要说的模式崩塌的前兆。4.2 D收敛过快导致的生成退化D收敛过快是最早出现、也最普遍的问题。原因很直接真实样本是固定的判别器只需要把“这批真实样本”和“生成器当前产物”区分开而生成器又很弱所以判别器很容易学到一个能短暂碾压生成器的决策边界。一旦D的loss降到接近0生成器收到的梯度信号就几乎消失看起来就像两个人在下棋对手每步都把你将死你想了一整晚也发现自己一步都走不出去。我试过几种补救办法最简单的就是把判别器训练得更少比如每轮先训练一次D再训练一次G。还有一个很有效的做法是把真实样本和生成样本的标签做soft化就是所谓的标签平滑不把真实样本的标签设为1而是设为0.9不把生成样本的标签设为0而是设为0.1。标签平滑实际上相当于告诉判别器不要把置信度推得太满这在一定程度上保住了生成器沿loss曲面的梯度。另外我把优化器从SGD换成了Adam。SGD在普通分类任务上表现稳定但在GAN这种两个网络争抢的环境中SGD的梯度方向震荡很大很容易把其中一个网络推入饱和区。Adam对每个参数维度做了自适应缩放对梯度的尺度变化更宽容是更稳妥的选择。4.3 模式崩塌的两种典型表现模式崩塌是我在模拟项目X里遭遇的最直观也最让人头疼的问题。第一种表现是类别级崩塌训练好之后生成器只会产出某一类数字或有限的几种数字整批生成的图像“同质化”严重。这是因为生成器找到了一个相对容易被判别器误判的“捷径”类别于是它不必继续拓展覆盖整个数据分布的多样性只需把这个类别做得足够好就能骗过D。第二种表现是样本级重复生成不同的随机编码z结果输出的图像几乎一模一样。这种通常发生在后期判别器对某些样本的判别已经饱和生成器在这些局部点上的梯度几乎为零无法把z映射到更多不同的输出区域。无论是哪种表现本质上都是生成器在绕开覆盖整个真实分布这个难题转而优化一个局部目标。模式崩塌的出现让我重新去理解论文里的理论证明全局最优只有在 $p_g p_{data}$ 时才成立而实际优化的过程并不保证朝向它。要缓解崩塌我试了两种比较有效的手段。第一种是给生成器的输入z加上很小的高斯噪声级别增加一定扰动后生成器被迫学习不同z之间的连续性第二种是频繁把生成的batch拿出来人工目检一旦发现同质率上升立刻降低判别器学习率给生成器喘息的余地。5. 一些让原始GAN能稳定训练的实用补丁5.1 从“二元交叉熵”到“标签平滑”如果要我给第一次复现原始GAN的人一个建议我会把标签平滑列在最前面。设计标签平滑的理念很简单原来判别器看到真实样本要输出1看到生成样本要输出0这是两个硬目标。但用软目标之后判别器不需要把真实样本的logit推到极大值也不需要把生成样本的logit压到极小值相当于给它留了一个缓冲区间。具体做法并不复杂。训练时真样本标签设为0.9左右生成样本标签设为0.1左右或者只在真样本上做平滑生成样本标签仍然是0。这个做过之后我观测到的变化是判别器的loss不会掉到极低生成器每轮的梯度幅度更稳定生成的图像也会更早出现肉眼可辨的结构。虽然标签平滑并不能根治模式崩塌但它能把训练过程从一个“随时会断”的状态拉到一个“还可以慢慢修”的状态。5.2 用RMSprop、Adam代替SGD的实战理由原始GAN论文实验部分虽然用的是自定义网络但并没有特别强调优化器选择。我实际跑下来SGD在GAN上的表现非常糟糕主要原因是梯度的scale变化剧烈。生成器在不同迭代阶段面临的loss表面差异很大SGD对梯度尺度敏感因此更新步长不稳定容易出现振荡或发散。Adam则不然它用梯度的一阶矩估计和二阶矩估计对每个参数做了自适应归一化相当于自动帮我们调整学习率。实践中我常用的参数是学习率0.0002、$\beta_10.5$、$\beta_20.999$。需要注意$\beta_1$ 设成0.5而不是常见的0.9是为了让梯度更新不那么“惯性化”。在普通监督任务里0.9带来平滑但GAN里这种做法容易让优化过程出现慢性的偏移累积。后来我也用到了RMSprop。在某些明显出现判别器loss震荡的场景下RMSprop的更新通常比Adam更温和尤其是当梯度的二阶矩变化比较剧烈时。不过RMSprop对学习率更敏感通常我会把学习率降到1e-4再试。5.3 简单可靠的生成质量评估方法训练GAN时最常被问的问题就是怎么看生成模型练得好不好原始论文里主要靠人工目检和最大似然估计的近似计算。我自己现在常用的组合是“三类指标交叉验证”。第一是人工目检虽然主观但最直接。我通常会固定若干个随机种子z每隔一定轮数把对应样本拼成一张大图保存下来。通过这些固定种子的演化可以直观看到生成器从噪声到结构的过程也能快速发现是否出现了重复模式。第二是FID全称Fréchet Inception Distance。做法是拿生成的图像和真实图像分别输入一个预训练的分类网络取其中一层的特征向量假设这些特征服从高斯分布然后计算两个分布之间的Fréchet距离。FID值越低表示生成分布与真实分布在特征空间里越接近。它比单纯看loss更可信缺点是必须依赖一个与任务不一定完全匹配的预训练模型。第三是最近邻分析。随机选几张生成的图像去训练集里找它们的最近邻真实样本如果生成样本都能找到非常相似的真实样本说明生成器可能只是记住了训练数据而不是学会了分布。这种方法无需额外模型直接在像素空间算欧氏距离虽然粗糙但判断“是否记忆过头”很有效。这三种方法组合使用比单纯盯着训练loss靠谱得多。我在模拟项目X上就是靠固定种子目检FID及时发现了生成质量的波动才及时调整了超参没有让训练白跑几个小时。6. 从这篇论文出发后续改进与我觉得值得保留的思维习惯6.1 DCGAN、WGAN、条件GAN分别补了什么这篇论文以后生成对抗网络的相关工作迎来了爆发。我自己读后续工作的时候有一个基本框架看它究竟动了原始GAN的哪个环节。DCGAN解决的环节是网络结构。把原始的全连接网络换成卷积层和转置卷积层配合批量归一化以及去掉全连接隐藏层收敛稳定性和生成图像质量都有了很明显的提升。它给我的启示是生成器和判别器的表达能力直接决定了对抗博弈能否在有限容量里逼近最优。WGAN解决的环节是分布距离度量。原始GAN用JS散度作为隐式目标但JS散度在分布重叠小的情况下梯度消失。WGAN改用Wasserstein距离并取消了判别器最后一层的sigmoid改成输出一个任意实数用梯度惩罚或权重裁剪来约束判别器的Lipschitz性质。我跑过WGAN训练一个很直观的感受是训练过程比原始GAN稳定太多判别器的loss不再是一个迅速熄灭的信号而是可以长期作为训练进展的指示器。条件GAN解决的环节是控制生成方向。它在生成器的输入里额外拼接一个class标签让生成过程变为“给定类别生成对应图像”。原始GAN里生成器完全不知道自己要生成什么类别的内容条件GAN则让“对抗式分布逼近”引入外部信息这种方法后来在图像翻译、超分辨率重建中都发挥了巨大作用。6.2 论文思维方式的迁移价值回看这篇论文我最想保留的并不是那些已经被后续工作升级或修正的细节而是它背后的思维方式当显式建模难以推进时能不能引入一个“竞争性的代理任务”来隐式逼近目标这个思路并不局限在图像生成。我在其他方向上也试着用这种博弈思想做迁移让一个预测模型和一个评判模型相互对抗以激励预测模型探索更难、更不明确的部分。事实证明这种方式在为模型提供学习信号方面往往比手工设计复杂损失函数更简洁也更接近人类“在竞争中学习”的经验。另一层启发是对理论边界的敏感。原始论文其实清楚写明了收敛保证只存在于凸情形但很多人只看结论不看假设。动手做工程的人更要养成“先看假设和边界再谈结论”的习惯有了这个习惯读任何一篇论文都不容易被人牵着走。我在模拟项目X里跑完原始GAN后最大的收获不是“我会写GAN代码了”而是深刻地理解了“优化目标设计”比“网络结构设计”更容易决定一个模型的上限。今天很多新的生成模型层出不穷但回头来看《Generative Adversarial Nets》这篇论文的价值从来不只是给出了一个能用的模型而是给整个领域示范了一种用博弈眼光看待生成问题的方式。