尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

原版GAN训练不稳?DCGAN/WGAN/CGAN变体全解析

原版GAN训练不稳?DCGAN/WGAN/CGAN变体全解析 做深度学习的朋友应该都跟我一样早期接触GAN的时候既兴奋又痛苦。兴奋的是它居然能让机器学会造假——生成图片、语音、文本甚至完整的视频痛苦的是原版GAN训练起来实在太过折磨loss曲线就像过山车生成结果有时候清一色全是同一种样本怎么调都拉不回来。很多入门教程上来就让你跑DCGAN、WGAN、CGAN但很少有人讲清楚为什么原版GAN不行这些变体各自解决了什么问题改动点到底在哪里如果你也搞不清楚这些问题那这篇文章正好适合你。我会从原版GAN的痛点出发逐个拆解DCGAN、WGAN、CGAN这三种最常见的变体再顺带聊聊InfoGAN、CycleGAN、StyleGAN这些在特定场景下很有用的进阶变体最后把我这些年踩过的坑和排查经验一起分享出来。先说一句题外话搜索GAN相关关键词的时候经常会看到GaN HEMT质子单粒子效应、GaN图腾柱这类条目那是第三代半导体材料氮化镓Gallium Nitride用来做功率器件和射频器件的跟我们要讲的生成对抗网络Generative Adversarial Network完全是两码事只是缩写碰巧一样搜资料的时候注意区分别被带偏了。1. 原版GAN的核心痛点为什么要折腾出这么多变体1.1 生成器与判别器的博弈逻辑要理解各种变体首先得回到最原始的GAN。2014年Ian Goodfellow提出GAN的时候思路特别简单训练两个网络互相博弈。生成器G负责从随机噪声z里伪造数据判别器D负责判断输入到底是真实数据还是伪造数据。整个过程可以写成这样一个极小极大目标函数min_G max_D V(D,G) E[log D(x)] E[log(1 - D(G(z)))]。说白了判别器想最大化自己区分真假的能力生成器想最小化判别器识别出伪造样本的概率。两个网络交替训练理论上最后会达到一个纳什均衡生成器产出的分布p_g完全等于真实数据分布p_data判别器无论怎么判断都只有50%的正确率相当于瞎猜。这个博弈框架看起来漂亮但真正跑起来才知道有多折腾。原版GAN在数学上有个绕不开的问题当判别器训练得太好的时候它提供给生成器的梯度会迅速消失。原因在于原版GAN的损失函数本质上是在优化生成分布与真实分布之间的JS散度Jensen-Shannon Divergence而当两个分布的重叠区域很小甚至完全不相交时JS散度会趋向一个常数log2梯度自然就趋近于零了。1.2 原生GAN的两大顽疾训练不稳定与模式崩塌我在实际训练中感受到的远不止数学层面的问题最典型的就是两大顽疾。第一个是训练不稳定。GAN的收敛不是那种loss持续下降然后平稳的常规曲线而是两个网络互相压制导致的震荡。判别器变强了生成器就被压得死死的生成器稍微反弹判别器又开始崩溃。整个训练过程就像两个人在拔河但绳子随时可能断。很多初学者第一次跑原版GAN看到的loss曲线往往是锯齿状乱跳生成的图片时好时坏完全没法判断训练到底有没有在进展。第二个是模式崩塌Mode Collapse。这是GAN最臭名昭著的毛病生成器找到了一个作弊捷径只生成判别器最难识别的少数几种样本把这些样本做得特别像从而骗过判别器。结果就是生成结果严重缺乏多样性——比如做手写数字生成模型从头到尾只输出同一个写法的1其他九个数字全都消失了。模式崩塌还有更隐蔽的形式叫周期性模式崩塌训练过程中模型在多个模式之间来回切换某一时刻看起来正常下一刻又开始崩塌。这两个问题在图像领域被放得更大。因为图像是高维连续数据分布极其复杂判别器很容易就能找到一条捷径把真图和假图区分开生成器压根没有学到数据的真实结构。正是为了对付这些痛点社区先后提出了DCGAN、WGAN、CGAN等大批变体。理解它们各自的出发点你就知道该在什么场景下用谁。2. DCGAN用卷积架构把GAN稳定在图像领域2.1 DCGAN到底改了什么DCGANDeep Convolutional GAN是2015年Radford等人在ICLR上提出的全称是Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks。它的核心贡献不是发明了什么全新的损失函数而是把之前GAN里那种全连接层随意堆卷积的做法彻底规范成了一套适合图像任务的卷积架构。具体改动有四条每一条都很有讲究。第一用带步长的卷积替代池化层。判别器里原来常用的池化操作会丢失空间信息DCGAN改为使用strided convolution步长卷积做下采样生成器则使用fractionally-strided convolution转置卷积做上采样。这样整个网络从输入到输出都是卷积操作梯度流动更顺畅空间结构信息保留得更完整。第二除了生成器输出层和判别器输入层其他层全部使用BatchNorm。BatchNorm的作用是稳定每一层的输入分布避免梯度在深层网络中爆炸或消失。为什么输出层和输入层不能用因为在生成器输出层强行做BatchNorm相当于把输出值的范围规范化了跟后面要接的Tanh激活函数冲突在判别器输入层用BatchNorm则容易引入真实数据和生成数据之间的统计相关性让判别器作弊。第三去掉全连接隐藏层。全连接层的参数量巨大而且会把图像的空间结构展平成向量破坏局部相关性。DCGAN让噪声z直接通过一个小的映射层reshape成四维张量然后一路卷积上去。第四激活函数的选型很讲究。生成器内部用ReLU输出层用Tanh判别器内部用LeakyReLU斜率设为0.2。为什么生成器输出要用Tanh而不是ReLU因为Tanh的输出范围在[-1,1]能让生成图像的像素值在合理的范围内而且Tanh梯度在原点附近比ReLU更平滑有利于稳定训练。我当时第一次跑DCGAN生成人脸跟原版GAN相比最直观的感受就是终于能稳定地看到图像从噪点逐渐变成清晰轮廓的过程了而不是训练几十个epoch还是一团糊。DCGAN的出现等于给GAN在图像领域的落地铺平了路。2.2 训练DCGAN的实操要点DCGAN虽然比原版稳定但也绝不是无脑跑就能收敛的。我给出几个自己验证过的配置建议。优化器首选Adam学习率建议设成0.0002beta1从默认的0.9下调到0.5。为什么beta1要降因为Adam在一阶矩估计上默认0.9的衰减系数会让梯度历史累积得过长在GAN这种博弈场景下容易造成震荡调成0.5以后梯度更新更短视反而有助于稳定。这个参数是DCGAN作者在论文里明确给出的我实测下来确实有效。噪声向量z的维度一般取100服从标准正态分布。Batch size在图像任务上我建议取64左右太小了梯度噪声大太大了显存扛不住且容易过早收敛到局部最优。还有一个很实用的小技巧固定一个噪声向量作为探针每个epoch都用同一个z生成图片并保存下来这样你就能非常直观地看到生成器在训练过程中的进步而不是每次刷新随机噪声导致对比困难。DCGAN后续还催生了不少改进技巧比如用Spectral Normalization替换BatchNorm用hinge loss替换原来的BCE loss这些在训练更深的GAN时几乎成了标配。如果你发现DCGAN在你的数据上怎么都不收敛优先检查一下是不是学习率太大、batch size太小、或者模型里漏了BatchNorm。3. WGAN与WGAN-GP从数学上根治训练崩塌3.1 从JS散度到Wasserstein距离DCGAN改良的是网络结构但损失函数本身的问题还在。WGANWasserstein GAN换了个思路从损失函数下手从根上解决梯度消失。前面提到原版GAN优化的是JS散度两个分布不重叠时梯度消失。WGAN使用Wasserstein距离也叫Earth Mover距离来度量生成分布和真实分布之间的差距。这个名字很形象——可以理解为把一堆土搬到另一个地方最少需要走多远。与JS散度不同即使两个分布完全没有重叠Wasserstein距离依然能给出一个有意义的、可导的数值梯度就不会消失了。但直接计算Wasserstein距离在高维空间里是算不出来的WGAN用了一个对偶变换把它转化成一个带Lipschitz约束的优化问题。这个约束条件成了WGAN的一系列麻烦的根源。具体实现上WGAN做了几个关键改动把判别器改名叫Critic因为它不再输出真假概率而是输出一个实数评分去掉最后一层的sigmoid激活函数让输出可以是任意实数损失函数也从BCE变成了近似Wasserstein距离的形式E[D(x)] - E[D(G(z))]为了让Critic满足Lipschitz约束每轮更新后把网络权重裁剪到一个小范围内比如[-0.01, 0.01]这就是权重裁剪Weight Clipping。WGAN在训练上的提升是肉眼可见的训练过程稳定了很多loss曲线不再乱跳而且研究者发现loss数值跟生成质量存在相关性——loss越低生成质量越好这在原版GAN里几乎是不可能的。作为原版GAN的诟病之一的模式崩塌也在一定程度上得到了缓解。3.2 权重裁剪的坑与梯度惩罚的修正权重裁剪听起来简单实际用起来却有两个大坑我都是亲自踩过的。第一个坑是容量限制。把权重限制在[-c, c]的范围等于给网络的拟合能力上了一道紧箍咒。c设大了Lipschitz约束根本约束不住c设小了Critic网络变成了一堆只能输出极端值的函数能力的上限被掐死很难学到复杂的分布。我曾经在生成高清人脸时试过不同的c值结果要么是梯度爆炸要么是生成的图片糊成一片。第二个坑更隐蔽权重裁剪会导致梯度要么消失要么爆炸。由于权重被限制在一个小范围内网络为了拟合复杂分布会倾向于把权重推向边界值结果Critic的梯度在边界附近急剧变化梯度范数要么疯涨要么接近于零。WGAN的Loss曲线虽然比原版稳定但有时会看到梯度范数在极大和极小之间剧烈跳动就是这个原因。为了解决这个问题WGAN-GP在2017年提出了改进用梯度惩罚Gradient Penalty替代权重裁剪。具体做法是在真实分布和生成分布之间随机插值采样要求这些采样点上的梯度范数尽量等于1加进损失函数里作为惩罚项λ * E[(||∇D(x_hat)||_2 - 1)^2]λ经验值取10。WGAN-GP还有几个实现细节需要注意。Critic网络内部不要用BatchNorm原因是BatchNorm在计算mini-batch统计量时会引入样本之间的相关性破坏梯度惩罚对每个样本独立计算的前提建议改用LayerNorm或InstanceNorm。训练时Critic每更新5次生成器才更新1次即n_critic5保证Critic有足够强的判别力。我实际用过WGAN-GP之后最明显的感受是loss曲线终于像一条正常的训练曲线了生成质量也稳定提升。代价是每次迭代多了梯度惩罚的计算训练速度稍微慢了一点但在大部分任务上这点开销完全值得。3.3 WGAN-GP的实现要点如果你打算从零实现WGAN-GP有几个地方特别容易出错我列一下。第一插值采样一定要在批维度上做epsilon torch.rand(batch_size, 1, 1, 1)然后x_hat epsilon * x_real (1 - epsilon) * x_fake。很多人直接用了标量epsilon虽然也能跑但梯度惩罚在批量内每个样本上就不是均匀的了。第二计算梯度时一定要把create_graphTrue打开否则autograd没法对梯度再求梯度整个梯度惩罚项就废了。这个错误很隐蔽因为程序不会报错但训练效果会明显变差。第三Critic和Generator的损失分别是critic_loss -torch.mean(critic_real) torch.mean(critic_fake) grad_penaltygen_loss -torch.mean(critic_fake)。注意Critic的目标是让真实样本的评分尽量大、生成样本的评分尽量小所以真实项的负号意味着最大化别搞反了。第四训练轮数设置上WGAN-GP通常需要比DCGAN更多的迭代次数才能收敛因为它对Critic的预训练要求比较高。建议先用小数据集调试通训练流程再上全量数据不然排查问题的时候会很痛苦。4. CGAN给生成过程加上条件这个方向盘4.1 条件信息的注入方式DCGAN和WGAN解决的是怎么生成得稳定、生成得像但都控制不了生成什么。CGANConditional GAN要解决的正是这个问题给生成过程加一个条件让模型按照你的要求去生成指定类别的样本。CGAN是2014年Mirza和Osindero提出的思路极其简单直接既然想让生成器知道现在要生成数字7那就把类别标签这个信息跟噪声z一起喂给生成器对应地判别器在判断真假时也要知道这张图声称是7才能判断这张图到底是真实存在的7还是伪造的7或者是一张真实的8冒充7。具体实现上条件信息的注入方式通常有两种。第一种是拼接concatenation把标签做one-hot编码拼接在噪声向量后面一起输入生成器的第一层判别器则把标签信息拼接到图像上常见的做法是把标签向量做空间复制拼接到图像的通道维上。第二种是更现代的做法先对标签做embedding再通过AdaIN自适应实例归一化这类机制注入到网络的中间层StyleGAN把这个思路发挥到了极致。4.2 CGAN的典型应用CGAN的应用场景比很多人想象得广泛得多。最经典的是手写数字生成。训练一个以数字类别为条件的CGAN你输入3它就专门生成各种写法的3输入9它就生成各种9。这一步在MNIST上很容易实现新手拿来练手非常合适。我当时跑通之后还试着把条件从类别标签换成了属性标签比如给CelebA人脸数据集加上金发戴眼镜微笑等属性条件生成效果让人直呼神奇——你可以精确控制生成人脸的属性组合。再往下延伸CGAN的思想被广泛用在各种条件生成任务上。图像超分辨率SRGAN就是典型的条件生成输入低分辨率图像作为条件生成对应的高分辨率版本。图像翻译pix2pix也是条件生成输入语义分割图生成对应的真实街景照片。文本生成图像就更不用说了OpenAI的DALL·E系列本质上也是大规模条件生成模型。做CGAN训练的时候我最想提醒的一点是条件信息的编码方式直接影响最终效果。类别标签用one-hot编码一般来说够用了但如果类别非常多上千类one-hot会非常稀疏不如用可学习的embedding效果好。另外条件信息的维度不要取得太大否则生成器会过度依赖条件信息而忽略了噪声输入导致生成结果多样性下降——你明明随机采样了两个不同的z生成出来的两张图却几乎一样。5. 其他值得关注的GAN变体与选型建议5.1 InfoGAN、CycleGAN、StyleGAN速览除了DCGAN、WGAN、CGAN这三个最常被提到的变体还有几个在特定场景下绕不开的进阶变体我简单说说它们各自的特点。InfoGAN的思路是无监督地学出可解释的隐含特征。它不依赖任何标签而是通过最大化隐含编码c与生成样本之间的互信息让模型自己学会用某些隐含维度控制生成结果的某个属性——比如控制数字的粗细、倾斜度、旋转角度。你不需要给数据标注训练完之后发现某一维隐含向量变了生成数字的粗细就跟着变这种可解释性在无监督学习里相当迷人。CycleGAN解决的是无配对图像的风格转换问题。以前的图像翻译需要成对的训练数据比如同一场景的白天照片和夜晚照片一一对应这种数据太难收集了。CycleGAN引入了循环一致性损失把一张真实照片转成梵高风格再用另一个生成器把它转回真实照片要求转回来之后跟原图尽量一致。这个约束让模型在完全没有配对数据的情况下也能学习风格转换马变斑马、夏天变冬天这些经典效果都是它的代表作。StyleGAN是NVIDIA在2019年提出的它在人脸生成领域把效果推到了一个此前难以想象的高度。它不再把噪声直接输入生成器的第一层而是通过一个映射网络把噪声变换成中间潜码再用AdaIN逐层控制生成图像的风格。这样设计的巧妙之处在于不同层控制不同尺度的特征——粗层控制姿态和脸型中层控制五官细节细层控制皮肤纹理和颜色。这意味着你可以在不同层之间做插值实现平滑的语义变换比如让人脸在微笑和严肃之间连续过渡这也是StyleGAN在图像编辑领域备受欢迎的原因。5.2 变体对比与选型变体核心改进优势适用场景原版GAN无概念奠基只适合学习原理DCGAN卷积架构规范稳定可靠、易复现通用图像生成的基线模型WGAN/WGAN-GPWasserstein损失训练稳定、loss可参考数据分布复杂、易崩塌的任务CGAN条件信息注入可控制生成类别类别条件生成、图像翻译InfoGAN互信息最大化无监督特征解耦无标注场景下调隐变量CycleGAN循环一致性无需配对数据风格迁移、域转换StyleGAN风格分层控制质量极高、可编辑高分辨率人脸/物体生成选型的时候我的经验是如果你是第一次跑GAN先上DCGAN把它吃透再说如果发现训练不稳定、生成结果总是崩塌果断切换到WGAN-GP如果你有明确的类别控制需求那就上CGAN或者把CGAN和WGAN-GP结合起来用也就是Conditional WGAN-GP实践中效果很稳。进阶的场景再考虑InfoGAN、CycleGAN和StyleGAN不要一上来就追最新最复杂的模型先理解清楚各个变体解决的是什么问题比背诵网络结构有用得多。6. 实战中的常见问题与排查技巧6.1 训练不收敛、loss异常怎么办GAN训练最常见的困惑就是loss看不懂。很多新手一看到生成器loss上升就慌其实根本没必要。GAN是博弈一个网络loss涨另一个网络loss跌是正常的关键要看生成图像的整体趋势。我自己的习惯是每500次迭代保存一批固定噪声的生成图直接看图来判断训练状态而不是盯loss数字。如果训练完全不动生成图像始终是一团噪声或一片模糊优先检查这几个点判别器是不是太强了把判别器的学习率调低一些或者减小网络深度让两个网络势均力敌是不是梯度消失了如果是用BCE损失把logits直接输入BCEWithLogitsLoss别手动加sigmoid再算BCE数值稳定性会差很多是不是数据归一化没做好生成器输出Tanh期望输入数据归一化到[-1,1]如果你用0-255范围的数据判别器会很容易找到真假差异。还有一类问题是loss出现NaN。NaN最常见的原因是梯度爆炸解决方案降低学习率、给输入数据做标准化、或者在判别器里加Spectral Normalization。如果用的是WGAN-GPNaN往往出现在梯度惩罚的计算上检查一下是不是没有开create_graph或者interpolate的epsilon维度是不是对的。6.2 模式崩塌的持久战模式崩塌是GAN里最难缠的问题之一因为它不是简单的参数问题而是训练目标引发的结构性缺陷。我遇到过最典型的场景用GAN生成多类别的图像训练中期开始生成结果只剩下两个类别其他类别怎么都出不来。我常用的应对手段有这么几个按优先级排列。第一增大batch size。模式崩塌往往是因为生成器在少量样本上找到了最优解batch size太小会让这种捷径更明显。把batch size从32翻到128往往就能改善不少。第二使用标签平滑Label Smoothing。把判别器的真实标签从1改成0.9伪造标签从0改成0.1避免判别器过于自信从而给生成器留出更平缓的梯度空间。这个技巧在DCGAN时代就有了至今依然好用。第三用mini-batch discrimination或者特征匹配。mini-batch discrimination让判别器不仅看单张图片还看整个batch内样本的统计差异这样一来生成器只输出单一模式就能被识别出来。实现起来稍微有点复杂但对付模式崩塌确实有效。第四切换到更稳健的损失函数。如果以上手段都试过了还是崩直接换WGAN-GP或者加Spectral Normalization的hinge loss版本。我个人的经验是WGAN-GP虽然在训练速度上比DCGAN慢一些但模式崩塌的概率确实要低很多。6.3 一些我个人觉得好用的习惯最后分享几个我跑了大量GAN实验之后沉淀下来的小习惯不算什么高深理论但确实能省不少事。固定随机种子这件事看起来基础但很多人就是不做。训练GAN的随机性本身很大如果不固定种子你连改动是否有用都没法判断。我一般会固定torch.manual_seed、numpy.random.seed以及设置环境变量保证cudnn行为可复现至少在同一个模型配置下能复现结果。使用梯度累积来控制更新的稳定性。如果显存不够导致batch size上不去可以试试梯度累积把几个小batch的梯度攒起来再更新一次效果接近大批量训练在GAN这种对batch size敏感的任务上非常实用。记录训练过程的中间产物。我习惯每隔固定迭代次数保存生成器权重和生成图片这样即使训练过程中崩了也能找到崩溃前的最后好状态来恢复不至于白跑几个小时的实验。GAN训练经常是前100个epoch好好的第101个epoch突然崩了有checkpoint在手心里踏实很多。模型调参过程一定要有基线对比。我会先跑一套官方推荐的默认参数保存好所有中间结果作为基线之后每次只改动一个变量做对照。GAN的随机性太强同时改两个参数出了问题你根本说不清是哪个参数导致的。说实话玩GAN这些年我最深的体会是与其追求最新最酷的模型架构不如先把几个经典变体的设计思想和训练细节吃透。DCGAN教会我网络结构对稳定性的重要性WGAN教会我损失函数的内涵比形式重要CGAN教会我条件信息的设计决定了生成的可控性。这三条经验放到今天的大规模生成模型里依然适用。希望这篇文章能帮你少走一些我当年走过的弯路。
返回列表