6.2 生成对抗网络GAN

1.生成与判别:一对互相较劲的对手

说起来,前面我们聊生成模型,多半是在讲怎么让网络学会一类东西的概率分布。VAE那一套(1.4 节讲KL散度那一节我们提过)是把分布近似出来,再从中采样。这次我们换一条完全不同的路子,干脆绕开显式拟合分布这一步,让两个网络互相较劲,一个拼命造假,一个拼命辨真。这就是生成对抗网络,也就是大家常说的GAN。

这个想法最早是Ian Goodfellow在2014年提出来的,思路挺有意思。我们不妨想象一下,城里来了一位造假画的师傅,叫老周吧,他手艺一开始不行,画出来的东西一看就假。可城里同时有位鉴画师,叫林先生,专门负责分辨一幅画到底是真迹还是赝品。老周每造一张画,林先生就给它打个分,说你这张是假的,破绽在哪里。老周听了回去琢磨,下一张画就造得更像一点。林先生也不甘示弱,眼睛越擦越亮,能看出来的破绽越来越多。两个人就这么较着劲,一天天过去,老周的手艺越来越精,林先生的眼光也越来越毒。到最后,老周造出来的画连林先生都分辨不出真假了,这时候我们就可以说,老周学到了真迹的那套神韵。

GAN里就是这么两个角色。一个叫生成器,记作 GGGG 就是负责造假的那个网络,它接收一个随机噪声 zzzz 通常是从标准正态分布里采样的随机向量),输出一张假的样本 G(z)G(z)。另一个叫判别器,记作 DDDD 负责分辨真假,它接收一个样本(这个样本可能是真的,也可能是 GG 造的假的),输出一个 0011 之间的数 D(x)D(x)D(x)D(x) 越接近 11,表示 DD 越觉得这个样本是真的,越接近 00,表示 DD 越觉得是假的。这里的 xx 泛指任意一个输入给判别器的样本。生成器想骗过判别器,判别器想识破生成器,两者就这么对抗着一起变强。这就是GAN名字里对抗两个字的由来。

2.极小极大的博弈:理论上的纳什均衡

说穿了,这就是一个博弈。博弈论里有一种经典的局面,叫极小极大,也就是一方想办法把自己的损失压到最低,另一方的目标恰好相反。GAN的训练目标,形式上就是这么一个极小极大的式子:

minGmaxDV(D,G)\min_G \max_D V(D, G)

这里 min\min 表示求最小值,max\max 表示求最大值,下标 GGDD 分别表示对生成器和判别器的参数求极值,V(D,G)V(D, G) 是一个价值函数,衡量的是判别器和生成器当前表现的好坏。这个式子读起来就是,生成器 GG 想让价值函数尽可能小,判别器 DD 想让价值函数尽可能大。把 V(D,G)V(D, G) 展开,是这样:

V(D,G)=Expdata[logD(x)]+Ezpz[log(1D(G(z)))]V(D, G) = \mathbb{E}_{x \sim p_{data}}[\log D(x)] + \mathbb{E}_{z \sim p_z}[\log(1 - D(G(z)))]

这里符号有点多,我一个个解释。E\mathbb{E} 是期望符号,意思是对后面那个表达式在指定分布上求平均。pdatap_{data} 是真实数据服从的分布,xpdatax \sim p_{data} 表示 xx 是从真实数据里取出来的。pzp_z 是噪声 zz 服从的分布(一般就是标准正态分布),zpzz \sim p_z 表示 zz 是从这个噪声分布里采样的。log\log 是对数函数。D(x)D(x) 是判别器对真样本的打分,D(G(z))D(G(z)) 是判别器对假样本 G(z)G(z) 的打分。

我们再看这个式子的含义。前半部分 Expdata[logD(x)]\mathbb{E}_{x \sim p_{data}}[\log D(x)] 要求判别器对真样本的打分 D(x)D(x) 越高越好,因为 logD(x)\log D(x)D(x)D(x) 接近 11 时取得最大值。后半部分 Ezpz[log(1D(G(z)))]\mathbb{E}_{z \sim p_z}[\log(1 - D(G(z)))] 要求判别器对假样本 G(z)G(z) 的打分越低越好,因为 log(1D(G(z)))\log(1 - D(G(z)))D(G(z))D(G(z)) 接近 00 时取得最大值。所以判别器 DD 想把 V(D,G)V(D, G) 拉大,就是要同时做到对真的判真、对假的判假。

生成器 GG 的目标恰好反过来,它想让 V(D,G)V(D, G) 尽可能小,也就是想让判别器对自己造的假样本打高分,让 D(G(z))D(G(z)) 接近 11,这样 log(1D(G(z)))\log(1 - D(G(z))) 就接近 log0\log 0,整个式子就被压下去了。

理论上有这么一个理想结局,叫纳什均衡。在博弈论里,纳什均衡指的是这样一种状态,任何一方单方面改变策略都不会更好。在GAN里,纳什均衡对应的是生成器造出来的假样本分布 pgp_gpgp_g 表示生成器输出的样本所服从的分布)和真实数据分布 pdatap_{data} 完全重合。这时候判别器无论如何也分不出真假,对所有样本都输出 0.50.5(表示五五开,分不清),也就是 D(x)=0.5D(x)=0.5。这恰好就是我们前面说老周造的画连林先生都分辨不出真假的那种状态。我记得看过某部讲伪造大师的电影,结尾那个伪造高手把一幅名画画得连原作者都啧啧称奇,那一刻大概就是这种纳什均衡的味道了。

为什么均衡点是 D(x)=0.5D(x)=0.5pg=pdatap_g=p_{data},我们推一下。 分两步。第一步:固定 GG,求最优判别器 DD^\ast 对固定的 GG,判别器要最大化价值函数。把期望展开成积分,V=[pdata(x)logD(x)+pg(x)log(1D(x))]dxV=\int\bigl[p_{data}(x)\log D(x)+p_g(x)\log(1-D(x))\bigr]dx。对每个 xx 独立最大化被积函数——设 f(D)=pdatalogD+pglog(1D)f(D)=p_{data}\log D+p_g\log(1-D),对 DD 求导令其为零:

fD=pdataDpg1D=0    D(x)=pdata(x)pdata(x)+pg(x)\frac{\partial f}{\partial D}=\frac{p_{data}}{D}-\frac{p_g}{1-D}=0\;\Longrightarrow\;D^\ast(x)=\frac{p_{data}(x)}{p_{data}(x)+p_g(x)}

这就是最优判别器。当 pg=pdatap_g=p_{data} 时,D(x)=pdata2pdata=0.5D^\ast(x)=\frac{p_{data}}{2p_{data}}=0.5,正好印证了均衡时判别器输出 0.50.5

第二步:代回 DD^\ast,证明全局最小值在 pg=pdatap_g=p_{data} 处。DD^\ast 代回价值函数并化简(经过一番对数运算),可以得到 maxDV=2log2+2JSD(pdatapg)\max_D V=-2\log 2+2\cdot\mathrm{JSD}(p_{data}\|p_g),其中 JSD\mathrm{JSD}JS 散度(Jensen–Shannon divergence,是 KL 散度的一种对称化版本,1.4 节讲过 KL)。JS 散度永远非负,且当且仅当两个分布相同时为零。所以 maxDV\max_D V 的最小值是 2log2-2\log 2,恰在 pg=pdatap_g=p_{data}(此时 JSD=0\mathrm{JSD}=0)处取得。这就严格证明了:GAN 的极小极大博弈,本质是在最小化真实分布与生成分布之间的 JS 散度,全局最优解就是 pg=pdatap_g=p_{data}

饱和损失与非饱和损失。 这里有个实战中很关键的细节。原始 GAN 让生成器最小化 E[log(1D(G(z)))]\mathbb E[\log(1-D(G(z)))](即把 VV 压小),这叫饱和损失。问题是:训练初期生成器很烂,D(G(z))D(G(z)) 接近 00,此时 log(1D(G(z)))\log(1-D(G(z))) 的梯度 11D(G(z))(1)\frac{-1}{1-D(G(z))}\cdot(-1) 接近 1-1,梯度很弱,生成器学不动。所以实践中生成器改用非饱和损失 minGE[logD(G(z))]\min_G\,\mathbb E[-\log D(G(z))]——它和饱和损失在最优解附近方向一致,但训练初期 D(G(z))0D(G(z))\to0logD(G(z))+-\log D(G(z))\to+\infty,梯度反而很大,生成器学得快。这一个看似小的改动,是 GAN 能真正训起来的关键之一。

3.模式崩溃:GAN最头疼的毛病

理论听着很美,可GAN在工程上是出了名的难训。最典型的一个毛病,叫模式崩溃,英文是mode collapse。

什么叫模式崩溃呢,简单说,就是生成器偷懒了。它发现只要造某几种样本就能骗过判别器,于是就翻来覆去造那几种,多样性全丢了。比方说我们想让生成器学会生成各种各样的人脸,结果它学会了只造一张脸,造一千张都长得一模一样,只是稍有角度差异。判别器一时半会儿拿它没办法,因为那张脸确实造得挺像。这种情况就像那个造假画的老周,发现某种构图特别能糊弄人,于是天天画那一幅,再也不琢磨别的了。

为什么会这样呢,根子上还是因为这个博弈并不平衡。生成器和判别器是一起训练的,两边都在变,谁太强都不行。判别器要是太强,早早把生成器甩开一大截,生成器收到的梯度就会变得没意义(判别器太厉害,生成器怎么调都没用,梯度自然传不出有用的信息),这有点像3.10 节我们讲过的梯度消失。反过来,生成器要是太强,判别器又跟不上节奏,整个训练也就崩了。

还有训练不稳定的问题。损失曲线一跳一跳的,今天降下去了明天又起来,根本看不清到底收敛没收敛。这一点和VAE形成鲜明对比,VAE的损失函数是个明确的下界,训练起来稳得多,可生成的样本往往偏模糊。GAN正好反过来,生成的图通常很锐利(因为判别器专门盯模糊这种破绽),可训练起来像走钢丝,得反复调超参。

工程上也想了不少办法来缓解这些问题。一种思路是改损失函数,最有名的叫WGAN,它把判别器换成了另一种打分方式,让梯度信号更平稳,训练也跟着稳了一些。另一种思路是从结构上约束生成器,让它不容易塌缩到那几种模式上去。还有一种实用的招数叫小批量判别,让判别器一次看一批样本,从而能识别出这一批是不是都在重复同一个东西。这些手段各管一摊,但没有一招能彻底解决问题,调GAN到现在都还是个手艺活。

4.从DCGAN到StyleGAN:一步步把造假手艺做精

虽说难训,GAN这十来年的演进是真精彩,几乎每隔一阵就有一款新架构出来惊艳大家。

最早把卷积引进GAN的是DCGAN。在它之前,生成器多用全连接层,效果比较糙。DCGAN用上了转置卷积(一种让小特征图逐步放大的卷积操作),把一个随机噪声向量一步步放大成一张像样的图。判别器那边则用普通的卷积一层层抽取特征,最后输出一个真假的判断。DCGAN第一次让大家看到,GAN生成的图可以这么清晰,这么有结构。

后来StyleGAN把这门手艺推到了让人瞠目的地步。它的核心想法是把随机噪声和风格控制分开,先用一个常数张量当作画布的底子,再一层层地往里注入所谓的风格,这些风格控制着每一层生成出的样子,比如人脸的五官轮廓、肤色、发型。这样一来,我们可以精细地调一调风格,让人脸换个发型,或者把一张严肃的脸调得柔和一些。StyleGAN做出来的人脸,那种以假乱真的程度,当年曾在网上引起不小的轰动,大家纷纷拿它生成的脸做头像。说起来那次我刷到过一条新闻,某家游戏公司直接用StyleGAN批量生成NPC的脸,省下了大笔美术成本。

还有一个方向叫条件GAN,记作cGAN。前面说的那些GAN都是无条件的,给个噪声就生成一张图,至于生成什么全看运气。条件GAN的思路是,额外给生成器和判别器都送一个条件 cccc 可以是类别标签,比如猫或者狗,也可以是一张参考图,比如一张素描),让生成器照着条件来造。这样生成器就不再乱来了,你让它造猫它就造猫,让它把一张素描画成水彩它就老老实实画。这个条件 cc 还可以是更丰富的内容,甚至是一段文本(这一思路后来和4.10 节讲的CLIP结合,长出了不少能按文字生成图的模型,3.4 节讲的Transformer则在这些大模型里扮演骨干的角色)。

5.锐利与不稳:和VAE的对比,以及GAN还能做什么

讲到这里,不妨把GAN和VAE放在一起比一比。这俩是生成模型里最有代表性的两条路。

VAE的思路是显式地建模分布,训练时优化的是一个叫证据下界的量(其中包含KL散度,KL散度我们在1.4 节详细讲过,它衡量两个分布差得多远)。VAE的训练很稳,损失曲线该降就降,可代价是它倾向于给出一个平均化的、偏模糊的输出,因为最小化KL散度本质上是在做一种平滑的近似。

GAN则干脆不显式建模分布,靠对抗来逼着生成器把细节做出来。判别器最擅长的就是抓模糊、抓不自然的边缘,生成器为了骗过它,只能把图做得锐利、做得细致。所以GAN生成的图质感通常比VAE强得多。代价就是前面说的,训练不稳,模式崩溃,调起来费劲。

打个不太严谨的比方,VAE像是个稳妥的画匠,按部就班,画得规规矩矩但缺了点锐气。GAN则像那个较劲到收不住手的老周,画出来的东西锋芒毕露,可有时候画着画着就走火入魔了。

GAN在应用上也铺得很开。最经典的就是人脸生成,前面提过StyleGAN,几乎是这个领域的代名词。再有就是图像超分辨率,把一张模糊的低分辨率图放大成清晰的高分辨率图,早期不少超分辨率模型就是用GAN的思路来保证细节够锐利。还有虚拟试衣,输入一张你的照片和一件衣服的图,模型生成你穿上这件衣服的样子,电商领域对这个特别感兴趣。我之前看过一个医学影像的例子,有团队用GAN生成训练数据来补稀缺的病灶样本,帮着下游的分割模型练手,效果还挺不错。我还记得小明有阵子迷上了动漫,拿一个用GAN搭的模型把自己照片转成二次元风格,玩得不亦乐乎。

到了今天,扩散模型(DDPM那一套)和Flow Matching这些新方法在生成质量上普遍超过了GAN,可GAN并没有因此就退场。它生成速度快(只要一次前向,不用像扩散模型那样迭代几十上百步),训练好了以后推理开销小,在对实时性要求高的场景里依然是首选。说到底,每种生成模型都有自己的脾气,GAN这份又快又锐的性子,短时间还真替代不了。

练习

Q1. GAN的判别器 DD 和生成器 GG 各自的目标是什么?为什么说这是一个"极小极大"博弈?

生成器 GG 接收随机噪声 zz 输出假样本 G(z)G(z),想让判别器把它判成真的(D(G(z))D(G(z)) 接近1);判别器 DD 接收样本输出0到1的分数,想对真样本打高分、对假样本打低分。两人目标相反,写成 minGmaxDV(D,G)\min_G\max_D V(D,G),生成器要让价值函数 VV 尽量小、判别器要让 VV 尽量大,这种一方压低损失、另一方目标恰好相反的局面,就是博弈论里的极小极大(minimax)。

Q2. GAN理论上的纳什均衡对应什么状态?此时判别器输出 D(x)D(x) 应该是多少?

纳什均衡对应生成器造出来的假样本分布 pgp_g 和真实数据分布 pdatap_{data} 完全重合,任何一方单方面改变策略都不会更好。此时判别器无论如何也分不出真假,对所有样本都输出 0.50.5(五五开,分不清),即 D(x)=0.5D(x)=0.5。这一点可以从最优判别器 D(x)=pdata(x)/(pdata(x)+pg(x))D^\ast(x)=p_{data}(x)/(p_{data}(x)+p_g(x)) 看出:当 pg=pdatap_g=p_{data}D=0.5D^\ast=0.5,而代回价值函数得 maxDV=2log2+2JSD(pdatapg)\max_D V=-2\log2+2\cdot\text{JSD}(p_{data}\|p_g),JS散度非负且当两分布相同时为零,所以全局最小值 2log2-2\log2 恰在 pg=pdatap_g=p_{data} 处取得——GAN本质是最小化两分布的JS散度。

Q3. 易错点:原始GAN让生成器最小化 E[log(1D(G(z)))]\mathbb{E}[\log(1-D(G(z)))](饱和损失),为什么实践中要换成非饱和损失 minGE[logD(G(z))]\min_G\mathbb{E}[-\log D(G(z))]

因为饱和损失在训练初期有梯度太弱的毛病。训练初期生成器很烂,D(G(z))D(G(z)) 接近0,此时 log(1D(G(z)))\log(1-D(G(z))) 的梯度接近 1-1,很弱,生成器学不动。换成非饱和损失后,训练初期 D(G(z))0D(G(z))\to0logD(G(z))+-\log D(G(z))\to+\infty,梯度反而很大,生成器学得快。两者在最优解附近方向一致,但非饱和损失让GAN能真正训起来,这个看似小的改动是GAN能成的关键之一。

Q4.(面试题) 请解释 GAN 的模式崩溃(mode collapse)是怎么回事,它和 GAN 训练不稳定有什么关系,工程上有哪些缓解办法。

模式崩溃是生成器偷懒:它发现只要造某几种样本就能骗过判别器,于是翻来覆去造那几种,多样性全丢了——比如想生成各种人脸,结果只造一张脸、造一千张都长得一样。根因是这个博弈不平衡:生成器和判别器一起训练、都在变,判别器太强生成器收到的梯度没意义(像梯度消失),生成器太强判别器又跟不上,损失曲线一跳一跳、今天降明天又起,根本看不清收敛没收敛,这就是训练不稳定,模式崩溃是它最典型的表现。工程上缓解的办法有几条:改损失函数,比如WGAN把判别器换成另一种打分方式(满足Lipschitz约束),让梯度信号更平稳、训练更稳;从结构上约束生成器不易塌缩;用小批量判别让判别器一次看一批样本、能识别这批是不是都在重复同一个东西;还有特征匹配、谱归一化等技巧。但这些都是缓解、没有一招能彻底解决,调GAN至今还是手艺活。和VAE对比,VAE训练稳(损失是明确下界)但生成偏模糊,GAN生成锐利(判别器专盯模糊破绽)但训练不稳,两者正好互补。

相关标签
深度学习生成模型GAN