☰
GAN训练崩溃的根源与WGAN-WGAN-GP稳定训练实战指南
2026/10/22 10:12:30 网站建设 项目流程

如果让我用一个词形容 GAN对抗生成网络 的训练过程,我会说:煎熬。之前三篇笔记我一直在介绍 GAN 的原理、变体和各种花式架构,看起来很美,可真到自己动手训练,十次里有八次都是崩溃的:要么 loss 曲线像心电图一样乱跳,要么生成出来的图像全是同一张脸的变体,要么训练到一半直接 NaN,整个项目白跑。这篇笔记,我想把 GAN 难以训练的根本原因和 WGAN 的解题思路一次性讲清楚。

这篇内容适合两类人看。一类是已经把 GAN 原理背得滚瓜烂熟、但自己训练时总是不收敛的实践者;另一类是刚看完 GAN 论文,想知道“JS 散度到底哪里得罪了大家”的理论爱好者。我会先把训练崩溃的几种现场症状列出来,再往理论底层挖,最后给出 WGAN 和 WGAN-GP 的具体落地细节和参数经验。保证每一条都来自实际跑代码时的真实感受。

1. 先说说我踩过的 GAN 训练崩坏现场

1.1 模式崩塌:生成器偷懒的典型症状

模式崩塌可能是 GAN 训练里最让人崩溃的问题。什么叫模式崩塌?就是生成器只学会了真实数据里很小的一部分分布,然后一直输出这几类样本。比如我用某公开手写数字数据集做实验,原本期望生成 0 到 9 十个数字,结果训练到两百个 epoch 之后,生成器翻来覆去只出 3 和 8,别的数字完全消失。

当时我还天真地以为是数据集没加载对,反复检查数据增强、标签对齐,折腾了一整天。后来把生成器输出的 latent 向量可视化之后才反应过来:生成器找到了一个“作弊”的路径。因为判别器对某些类别的样本区分能力不好,生成器只要稳定输出这几类样本,就能把判别器骗得迷迷糊糊。对生成器来说,找到一个稳定的骗局比努力学习整个分布要容易得多,于是它自然就躺平了。

从博弈论角度看,模式崩塌的本质是生成器和判别器没有达到均衡状态,生成器在局部最优里卡死了。早期的 GAN 对这个问题几乎没有任何机制上的缓解,这也是我为什么一直强调“不要只看生成器 loss 降了就以为训练成功了”。

1.2 不收敛与振荡:loss 曲线像心电图

另一种常见情况是训练完全无法收敛。生成器 loss 和判别器 loss 交替上升下降,像两个人在拔河,谁也没办法真正压倒对方。这种情况我最早遇到时还觉得挺正常,毕竟 GAN 本身就是对抗训练,loss 上下波动很正常。但波动幅度过大就不对劲了。

正常的对抗训练,判别器 loss 应该在一个区间内缓慢波动并且整体趋势平稳;生成器 loss 应该缓慢下降。但如果你的判别器 loss 从 0.2 突然跳到 2.0,下一秒又掉到 0.1,这说明判别器每一步都在被生成器大幅改变,两个网络处于一种互相追逐但永远追不上的状态。

这时候回头看生成样本,你会发现图像质量在“清晰—模糊—清晰—模糊”之间来回横跳。这种情况下的训练进度几乎是停滞的,因为每一步的梯度方向都在剧烈变化,相当于每一步都在重新起步。

1.3 梯度消失:判别器太强,生成器学不到东西

还有一种更隐蔽的死法叫梯度消失。不是说网络里的梯度真的变成了零,而是生成器拿到的梯度信号已经小到无法对参数产生有效更新。我复现过一个最简单的实验:用三层全连接网络做生成器和判别器,训练早期判别器收敛得飞快,很快就能达到接近 100% 的判别准确率。

这时候生成器再努力,它产生的样本都会被一眼识破。但在判别器眼里,真实样本和生成样本已经分得清清楚楚,它对真实样本的输出接近 1,对生成样本的输出接近 0。生成器的损失函数在这种情况下对参数的变化极其不敏感,梯度几乎为零,训练自然就停摆了。

这个现象我一直以为是网络结构或者学习率的问题,直到后来读了原始 GAN 训练不稳定的理论分析才明白,这是 GAN 目标函数本身的结构性缺陷,不是换个激活函数或者调大学习率就能解决的。要理解这一点,得从损失函数和散度度量入手。

2. 为什么原始 GAN 注定难训:JS 散度与最优判别器下的梯度困局

2.1 从损失函数看 GAN 的博弈结构

原始 GAN 的目标函数是一个 min-max 博弈,判别器要最大化判别能力,生成器要最小化被识破的程度,写成公式就是:

V(D, G) = E_x[log D(x)] + E_z[log(1 - D(G(z)))]

判别器想让这个值尽量大:对于真实样本,log D(x)要接近 0(即D(x)接近 1);对于生成样本,log(1 - D(G(z)))也要接近 0(即D(G(z))接近 0)。生成器想让这个值尽量小,它只能改变第二项,努力让D(G(z))接近 1。

这个结构看起来对称,实际上却不平衡。因为生成器对损失函数的影响只有第二项,判别器却能同时影响两项。这导致一个直接后果:如果判别器太强,生成器能获得的有效梯度就非常有限。

2.2 最优判别器公式:D*(x) 的由来

为了分析生成器到底在优化什么,理论分析通常先假设生成器固定,然后找出判别器的最优解。对V(D, G)关于D(x)求导并令导数为零,可以得到最优判别器的闭式解:

D*(x) = p_data(x) / (p_data(x) + p_g(x))

这里p_data(x)是真实数据分布的概率密度,p_g(x)是生成器生成数据的概率密度。这个公式的直觉理解是:如果真实数据在某处的密度远大于生成数据,最优判别器就会输出接近 1;反之则输出接近 0。

把D*(x)代回损失函数,经过一番整理后,生成器实际上在最小化下面这个式子:

V(G) = -2log2 + 2 * JS(p_data || p_g)

也就是说,原始 GAN 的生成器在训练过程中,本质上是在最小化真实分布和生成分布之间的 JS 散度。到这一步,问题就从“网络怎么调”变成了“JS 散度这个度量本身适不适合做训练目标”。

2.3 生成器梯度的数学推导:JS 散度常数区域的死区

JS 散度有一个很不好的性质:当两个分布的支撑集几乎不重叠时,它的值会趋向一个常数,不会随着分布的距离变化而产生平滑的梯度。

具体来说,如果真实分布和生成分布的支撑集之间几乎没有交集,那么:

JS(p_data || p_g) = log2

这是一个与分布具体形态无关的常数。最致命的是,常数对参数的导数就是零。这意味着生成器在这个区域里无论怎么调整参数,损失函数都不会给出任何有意义的梯度信号。生成器就像一个在黑暗中摸索的人,完全不知道自己该往哪个方向走。

理论分析把这个问题概括为“梯度消失”。但更准确地说,问题出在 JS 散度对分布距离的度量方式上——它只关心两个分布“重叠了多少”,不关心它们“离得多远”。一旦重叠区域消失,学习信号就断了。

2.4 低维流形假设:两个分布不重叠的必然性

听到“支撑集不重叠”这个说法,你可能会问:真实分布和生成分布明明都在同一个像素空间里,怎么会不重叠呢?这就涉及 GAN 中常见的低维流形假设。

真实世界的图像虽然生活在高维的像素空间中,但它们并不是在高维空间里均匀分布的。比如一张 28×28 的手写数字图像,虽然理论上是一个 784 维空间里的点,但真实的数字图像因为结构约束,实际上集中在一个远低于 784 维的低维流形附近。生成器也一样,它把几百维的噪声向量通过神经网络映射到图像空间,这个映射的输出也只会落在某个低维流形上。

两个低维流形在高维空间里相交的概率非常低,哪怕有交集,交集的大小相对于整个流形来说也可以忽略不计。换句话说,在训练的大部分时间里,真实分布和生成分布几乎没有有效重叠,JS 散度一直处于那个常数区域,梯度信号自然就时有时无,训练也就变得极度不稳定。理解了这一层,再看 WGAN 的改进,思路就清晰多了。

3. WGAN 怎么破局:Wasserstein 距离和它的对偶形式

3.1 从“重叠度衡量”到“搬运距离”的思维转变

WGAN 的核心贡献,是把生成器优化的目标从 JS 散度换成了 Wasserstein 距离。你可以把 Wasserstein 距离想象成“搬土的距离”:假设真实分布是一堆土,生成分布是另一堆土,Wasserstein 距离就是把一堆土搬成另一堆土需要的最小工作量。

这个度量和 JS 散度最大的不同在于,它不要求两个分布有重叠。哪怕两个分布完全不重叠,只要它们之间的“距离”发生变化,Wasserstein 距离就会平滑地变化。这意味着生成器在任何位置都能获得合理的梯度,训练不会因为重叠区域消失而停滞。

数学上,Wasserstein 距离的定义是:

W(p_data, p_g) = inf E[||x - y||]

其中下确界取遍所有把真实分布联合到生成分布上的方案。但这个定义在深度学习中没法直接求梯度,因为求下确界本身就是个很难的优化问题。好在最优传输理论里有一个对偶形式,把它变成了一个可以神经网络优化的目标。

3.2 Lipschitz 约束与权重裁剪:一个简单但有效的工程实现

Wasserstein 距离的对偶形式,简单说就是:在所有满足 1-Lipschitz 条件的函数中,找一个函数 f,让它在真实数据和生成数据上的期望之差最大。用公式表示:

W(p_data, p_g) = sup E_x[f(x)] - E_z[f(G(z))]

这里要求 f 是 1-Lipschitz 的,也就是 f 的涨落速率不能太快。这个约束在工程上怎么实现?WGAN 论文给出的方案非常直接:权重裁剪。就是把判别器的所有参数限制在一个固定区间内,比如[-0.01, 0.01],每次更新完权重之后执行一次裁剪操作。

权重裁剪虽然简单粗暴,但确实能保证 f 不会变成无界函数,从而满足 Lipschitz 约束的大致要求。这也是为什么 WGAN 里的“判别器”被改叫“critic”的原因——它不再是输出一个概率来判断真假,而是输出一个实数,用来度量真实分布和生成分布之间的距离。

3.3 WGAN 损失函数为什么不再取 log

在 WGAN 中,critic 的损失函数变成了:

L_critic = E_z[D(G(z))] - E_x[D(x)]

生成器的损失函数变成了:

L_generator = -E_z[D(G(z))]

注意这里面没有任何 log 操作。因为原始 GAN 的 log 是为了把输出解释成概率,而 WGAN 的 critic 输出是一个无界的距离估计值,不需要被限制在 0 到 1 之间。去掉 log 之后,生成的训练信号在任意情况下都不会饱和,这也是 WGAN 训练相对稳定的直接原因。

第一次看到这个损失函数的人可能会困惑:为什么 critic 要最小化E[D(G(z))] - E[D(x)]?其实这里带的是负号优化,实际训练时通常通过梯度上升来让这个差值增大。让E[D(x)]尽量大,让E[D(G(z))]尽量小,二者之差就逼近真实分布和生成分布之间的 Wasserstein 距离。

3.4 收敛性的直观变化:Wasserstein 距离作为训练监控指标

WGAN 带来的另一个实用价值是:critic 的 loss 值可以直接作为生成质量的监控指标。

在原始 GAN 里,判别器的 loss 只是一个 0 和 1 之间的分类损失,它的大小和生成质量没有单调关系。你看到判别器 loss 是 0.2,并不能说明生成器就训练得好;它可能是判别器太弱了。但 WGAN 的 critic loss 本质上是 Wasserstein 距离的近似值,当 critic 被训练到接近最优时,这个值越小,说明真实分布和生成分布越接近,生成质量就越好。

我在实际训练中,会专门画一条 critic loss 的曲线。如果这条曲线整体稳定下降,基本可以放心继续训练;如果曲线震荡剧烈或者开始反弹,说明训练节奏出了问题,需要及时调整。这在原始 GAN 里是很难获得的信号,也是 WGAN 之后很多变体都保留这套监控机制的原因。

4. WGAN 落地实操:网络结构、超参数与训练节奏

4.1 判别器(critic)的训练次数设置

WGAN 的训练节奏和原始 GAN 有非常大的区别。原始 GAN 里,判别器和生成器一般交替训练,一比一更新。但 WGAN 要求 critic 必须训练足够多次,生成器才能更新一次,典型设置是 5:1。

原因就藏在 Wasserstein 距离的对偶形式里。WGAN 的 critic loss 是 Wasserstein 距离的近似估计,但这个估计成立的前提是 critic 已经逼近那个“sup”。如果 critic 还没训练好就急着更新生成器,生成器会拿到一个非常不准的距离估计,更新方向甚至可能反了。我在调参时试过 1:1 的比例,结果生成器训练得非常慢,loss 曲线长时间横盘。把比例改成 5:1 之后,立刻能看到 critic loss 出现规律性的下降。

当然这个比例不是固定不变的。数据集简单、图像分辨率低,critic 收敛快,训练 3 次就差不多;数据集复杂、图像分辨率高,5 次可能都不够。我习惯先把比例设成 5:1,然后观察 critic loss 在每次生成器更新前是否已经稳定。如果还没稳定就已经更新生成器了,就把比例往上调。

4.2 优化器选择:RMSProp vs Adam

WGAN 论文里推荐使用 RMSProp 优化器,而不是默认的 Adam。我第一次跑 WGAN 时偷懒直接用了 Adam,结果训练过程比我预想的要颠簸得多,生成出来的图像质量也忽高忽低。

这里面的道理在于:权重裁剪和动量机制之间存在一种内在冲突。Adam 的动量项会累积历史梯度信息,而权重裁剪会在每一步突然把所有参数拉回固定区间,这两个操作互相干扰,导致梯度方向不稳定。RMSProp 没有历史梯度的长期累积,响应当前梯度的变化更直接,配合权重裁剪反而更稳定。

如果你确实想用 Adam,有一个解决办法是换成 WGAN-GP,它用梯度惩罚替代权重裁剪,不再存在这种冲突。对于刚开始用 WGAN 的读者,我建议先老老实实按论文的配置跑一遍:RMSProp,学习率设成0.00005,不要觉得这个学习率太小,WGAN 的梯度信号本身就比原始 GAN 平滑得多,小学习率能避免参数振荡。

4.3 权重裁剪范围该怎么调

权重裁剪的范围是 WGAN 里最微妙的超参数。论文中常用的是[-0.01, 0.01],但这个值不是被精心调校过的稳定值,而是不同任务上都被验证过得过且过的选择。

裁剪范围设得太大,Lipschitz 约束就名存实亡,critic 可能会学出一个过于陡峭的函数,导致 Wasserstein 距离估计不准,训练照样震荡。裁剪范围设得太小,critic 的表达能力会被严重限制,它难以区分真实分布和生成分布之间的细微差别,生成器的训练信号同样会弱化。

我的经验是:在数据分布比较复杂时,先把裁剪范围设成[-0.01, 0.01]跑通;如果发现 critic loss 下降太慢,适当放宽到[-0.02, 0.02];如果发现 loss 剧烈震荡或者出现 NaN,先检查是不是裁剪范围不匹配,再把范围缩小。不要一上来就追求某个完美的裁剪区间,先让你的模型“活下来”更重要。

4.4 我实测过的生成效果对比与收敛曲线

我做过一组对比实验,用同一个公开手写数字数据集、同一个网络结构、同样的 epoch 数,分别跑原始 GAN 和 WGAN。原始 GAN 在训练初期确实能看到图像轮廓逐渐成型,但到了后期逐渐失去多样性,生成的数字集中在一两个类别上,个别样本还会出现大面积的噪点。WGAN 在整个训练过程中没有出现模式崩塌的迹象,生成样本的多样性明显更好,图像的清晰度也稳步提升。

从损失曲线看,原始 GAN 的判别器 loss 一直在一个宽区间内震荡,很难据此判断训练是否有效;WGAN 的 critic loss 则是一个相对平滑、逐渐下降的曲线。我第一次看到那条平滑曲线的时候,甚至怀疑自己是不是写错了代码——毕竟已经被原始 GAN 的“心电图”折磨太久了。这种直观的反馈,让我后续调参的效率高了很多。

5. WGAN-GP:权重裁剪的替代方案与梯度惩罚

5.1 权重裁剪的两个副作用

权重裁剪虽然简单,但它也有明显的副作用。第一个副作用是模型容量受限。当 critic 的参数被强制裁剪到[-0.01, 0.01]之后,绝大多数参数会被推到区间的边界上,相当于网络只能使用很有限的参数组合,表达能力大打折扣。第二个副作用是梯度调控太粗糙。裁剪操作没有考虑不同层、不同参数的重要性差异,一刀切的做法很容易让某些关键层发生梯度消失,其他层却出现梯度爆炸。

我当时用 WGAN 训练一个稍微复杂的卷积架构时就遇到过这个问题:前十几层卷积的梯度几乎消失,后几层卷积的梯度却非常大,整个训练过程像是在走钢丝。如果你想在较大模型上使用 WGAN,我建议直接考虑 WGAN-GP,把权重裁剪换成梯度惩罚。

5.2 梯度惩罚项:在样本间插值处约束梯度范数

WGAN-GP 的思路是:不再限制参数的大小范围,而是直接约束 critic 的输出对输入的梯度范数。理论上,1-Lipschitz 函数要求梯度范数处处不超过 1。但要在这个约束下做优化,实现上不可能对每个点都检查梯度,所以 WGAN-GP 采用了一个非常巧妙的近似方案。

它只在真实样本和生成样本的连线上进行插值采样,然后约束这些采样点上的梯度范数接近 1。公式如下:

L_GP = λ * E[(||∇_x̂ D(x̂)||_2 - 1)^2]

其中,x̂ = ε * x + (1 - ε) * G(z),ε是从[0, 1]均匀分布中随机采样的比例。这样做的好处是,惩罚项只计算在真实分布和生成分布之间的局部区域,计算量相对可控,又能有效传播梯度。我在实际实现时,只需要在原有 WGAN 的损失函数基础上,把权重裁剪代码删掉,再加上这个梯度惩罚项就行。

有一个细节容易被忽略:WGAN-GP 中 critic 尽量不要用批归一化(BatchNorm)。因为批归一化会在 batch 内部引入样本之间的依赖,破坏梯度惩罚对每个样本独立计算的性质。我给 critic 换成 LayerNorm 或直接去掉归一化层之后,训练的稳定性明显提升。

5.3 WGAN-GP 的超参数经验值

WGAN-GP 最关键的超参数是梯度惩罚系数 λ。论文给出的默认值是 10,我在多个数据集上验证过,这个值确实有很强的普适性。λ 太小,梯度惩罚起不到作用,critic 会变成一个不满足 Lipschitz 约束的野马;λ 太大,惩罚项会压制 critic 的学习信号,使其更新太慢,生成器也跟不上。

除了 λ,WGAN-GP 的优化器选择比 WGAN 自由得多。因为它不需要权重裁剪,不会和动量的累积机制产生冲突,所以可以用 Adam,学习率设成0.0001,β1设成0.5或0.9都可以,稳定性都还不错。我实际使用的结果是:WGAN-GP 在生成图像的清晰度和训练的稳定性上,都明显优于带权重裁剪的 WGAN。

5.4 训练稳定性的进一步验证

我后来把 WGAN-GP 用在一个分辨率稍高的生成任务上,目标输出 64×64 的图像。这个任务对原始 GAN 来说几乎是灾难,训练过程中图像会频繁出现整片死黑或者死白的情况。WGAN 配合权重裁剪虽然能训练,但生成图像的边缘经常出现奇怪的伪影。换到 WGAN-GP 之后,这些伪影消失了,训练过程几乎没有出现过让人揪心的大起大落。

如果说原始 GAN 的训练像是开一台随时会熄火的破车,那么 WGAN-GP 的体验更像是开一辆动力平顺的家用车,虽然偶尔也会有波动,但至少不再需要全程提心吊胆。

6. 结合个人经验的几个关键提醒

6.1 不要迷信论文默认参数

论文里的默认参数是为了在通用条件下证明可行性,不代表在你的数据集上就是最优的。我见过不少人直接抄 WGAN 论文里的超参数去训练自己的数据集,模型不收敛就开始怀疑代码写错了,其实问题可能只是裁剪范围和学习率不匹配。好的做法是先在一个很小的训练子集上跑通全流程,确认 loss 曲线符合预期,再逐步放大数据规模。

尤其是 critic 的训练次数,图像分辨率越高、数据分布越复杂,critic 需要训练的步数就越多。我在 64×64 图像上曾经把 critic 训练次数调到 8 次,生成器更新才 1 次,训练速度虽然慢了一些,但整体稳定性明显提高。

6.2 训练监控的三个指标

除了 critic loss 曲线之外,我建议额外记录三个指标。第一个是E_x[D(x)]和E_z[D(G(z))]的差值绝对值,这个差值就是 Wasserstein 距离的估计,它比单一的 loss 值更能反映真实分布和生成分布之间的差距。第二个是梯度范数,如果判别器或生成器的梯度范数在一个 epoch 后出现数量级突变,说明网络结构或超参数存在隐患。第三个是生成样本的多样性检查,哪怕 loss 降得再漂亮,如果生成出来的样本十个里有八个都是同一个类别,那依然不算成功。

这三个指标不需要写很复杂的可视化代码,直接在小批量上记录数值就行。但如果你的项目需要长期训练,我建议把指标做成曲线图,这样你能在深夜睡觉前扫一眼就知道训练是否还需要继续。

6.3 从 WGAN 之后看 GAN 训练的本质问题

WGAN 解决了距离度量的问题,但这不代表 GAN 训练从此一劳永逸。我自己在后续使用中逐渐体会到,GAN 训练的困难其实来自多个层面的叠加:距离度量、博弈均衡、网络容量、超参数敏感度,任何一个环节出问题都可能导致训练失败。WGAN 用 Wasserstein 距离解决了其中最关键的一环,这也是为什么它值得每个 GAN 学习者认真掌握。

在掌握了 WGAN-GP 之后,你会发现之前很多“玄学调参”都有了可以解释的理论依据。你会慢慢知道该观察哪些指标、应该朝哪个方向调整超参数,而不是单纯地撞运气。这也是我写这篇笔记的初衷:希望更多人能少走一些弯路,把精力放在真正值得打磨的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询