众所周知,GANs 的训练尤为困难,笔者自从跳入了 GANs 这个领域(坑),就一直在跟如何训练 GANs 作「对抗训练」,受启发于 ganhacks,并结合本身的经验记录总结了一些经常使用的训练 GANs 的方法,以备后用。安全
什么是 GANs?网络
GANs(Generative Adversarial Networks)能够说是一种强大的「万能」数据分布拟合器,主要由一个生成器(generator)和判别器(discriminator)组成。生成器主要从一个低维度的数据分布中不断拟合真实的高维数据分布,而判别器主要是为了区分数据是来源于真实数据仍是生成器生成的数据,他们之间相互对抗,不断学习,最终达到Nash均衡,即任何一方的改进都不会致使整体的收益增长,这个时候判别器再也没法区分是生成器生成的数据仍是真实数据。机器学习
GANs 最初由 Ian Goodfellow [1] 于 2014 年提出,目前已经在图像、语音、文字等方面获得普遍研究和应用,特别是在图像生成方面,可谓是遍地开花,例如图像风格迁移(style transfer)、图像修复(image inpainting)、超分辨率(super resolution)等。函数
GANs 出了什么问题?性能
GANs 一般被定义为一个 minimax 的过程:学习
其中 P_r 是真实数据分布,P_z 是随机噪声分布。乍一看这个目标函数,感受有点相互矛盾,其实这就是 GANs 的精髓所在—— 对抗训练。优化
在原始的 GANs 中,判别器要不断的提升判别是非的能力,即尽量的将真实样本分类为正例,将生成样本分类为负例,因此判别器须要优化以下损失函数:设计
做为对抗训练,生成器须要不断将生成数据分布拉到真实数据分布,Ian Goodfellow 首先提出了以下式的生成器损失函数:3d
因为在训练初期阶段,生成器的能力比较弱,判别器这时候也比较弱,但仍然能够足够精准的区分生成样本和真实样本,这样 D(x) 就很是接近1,致使 log(1-D(x)) 达到饱和,后续网络就很难再调整过来。为了解决训练初期阶段饱和问题,做者提出了另一个损失函数,即:code
以上面这个两个生成器目标函数为例,简单地分析一下GAN模型存在的几个问题:
Ian Goodfellow 论文里面已经给出,固定 G 的参数,咱们获得最优的 D^*:
也就是说,只有当 P_r=P_g 时候,不论是真实样本和生成样本,判别器给出的几率都是 0.5,这个时候就没法区分样本究竟是来自于真实样本仍是来自于生成样本,这是最理想的状况。
1. 对于第一种目标函数
在最优判别器下 D^* 下,咱们给损失函数加上一个与 G 无关的项,(3) 式变成:
注意,该式子其实就是判别器的损失函数的相反数。
把最优判别器 D^* 带入,能够获得:
到这里,咱们就能够看清楚咱们到底在优化什么东西了,在最优判别器的状况下,其实咱们在优化两个分布的 JS 散度。固然在训练过程当中,判别器一开始不是最优的,可是随着训练的进行,咱们优化的目标也逐渐接近JS散度,而问题偏偏就出如今这个 JS 散度上面。一个直观的解释就是只要两个分布之间的没有重叠或者重叠部分能够忽略不计,那么大几率上咱们优化的目标就变成了一个常数 -2log2,这种状况经过判别器传递给生成器的梯度就是零,也就是说,生成器不可能从判别器那里学到任何有用的东西,这也就致使了没法继续学习。
Arjovsky [2] 以其精湛的数学技巧提供一个更严谨的一个数学推导(手动截图原论文了)。
在 Theorm2.4 成立的状况下:
抛开上面这些文绉绉的数学表述,其实上面讲的核心内容就是当两个分布的支撑集是没有交集的或者说是支撑集是低维的流形空间,随着训练的进行,判别器不断接近最优判别器,会致使生成器的梯度到处都是为0。
2. 对于第二种目标函数
一样在最优判别器下,优化 (4) 式等价优化以下
仔细盯着上面式子几秒钟,不难发现咱们优化的目标是相互悖论的,由于 KL 散度和 JS 散度的符号相反,优化 KL 是把两个分布拉近,可是优化 -JS 是把两个分布推远,这「一推一拉」就会致使梯度更新很是不稳定。此外,咱们知道 KL 不是对称的,对于生成器没法生成真实样本的状况,KL 对 loss 的贡献很是大,而对于生成器生成的样本多样性不足的时候,KL 对 loss 的贡献很是小。
而 JS 是对称的,不会改变 KL 的这种不公平的行为。这就解释了咱们常常在训练阶段常常看见两种状况,一个是训练 loss 抖动很是大,训练不稳定;另一个是即便达到了稳定训练,生成器也大几率上只生成一些安全保险的样本,这样就会致使模型缺少多样性。
此外,在有监督的机器学习里面,常常会出现一些过拟合的状况,然而 GANs 也不例外。当生成器训练得愈来愈好时候,生成的数据越接近于有限样本集合里面的数据。特别是当训练集里面包含有错误数据时候,判别器会过拟合到这些错误的数据,对于那些未见的数据,判别器就不能很好的指导生成器去生成可信的数据。这样就会致使 GANs 的泛化能力比较差。
综上所述,原始的 GANs 在训练稳定性、模式多样性以及模型泛化性能方面存在着或多或少的问题,后续学术上的工做大多也是基于此进行改进(填坑)。
训练 GAN 的经常使用策略
上一节都是基于一些简单的数学或者经验的分析,可是根本缘由目前没有一个很好的理论来解释;尽管理论上的缺陷,咱们仍然能够从一些经验中发现一些实用的 tricks,让你的 GANs 再也不难训。这里列举的一些 tricks 可能跟 ganhacks 里面的有些重复,更多的是补充,可是为了完整起见,部分也添加在这里。
1. model choice
若是你不知道选择什么样的模型,那就选择 DCGAN[3] 或者 ResNet[4] 做为 base model。
2. input layer
假如你的输入是一张图片,将图片数值归一化到 [-1, 1];假如你的输入是一个随机噪声的向量,最好是从 N(0, 1) 的正态分布里面采样,不要从 U(0,1) 的均匀分布里采样。
3. output layer
使用输出通道为 3 的卷积做为最后一层,能够采用 1x1 或者 3x3 的 filters,有的论文也使用 9x9 的 filters。(注:ganhacks 推荐使用 tanh)
4. transposed convolution layer
在作 decode 的时候,尽可能使用 upsample+conv2d 组合代替 transposed_conv2d,能够减小 checkerboard 的产生 [5];
在作超分辨率等任务上,能够采用 pixelshuffle [6]。在 tensorflow 里,能够用 tf.depth_to_sapce 来实现 pixelshuffle 操做。
5. convolution layer
因为笔者常常作图像修复方向相关的工做,推荐使用 gated-conv2d [7]。
6. normalization
虽然在 resnet 里的标配是 BN,在分类任务上表现很好,可是图像生成方面,推荐使用其余 normlization 方法,例如 parameterized 方法有 instance normalization [8]、layer normalization [9] 等,non-parameterized 方法推荐使用 pixel normalization [10]。假如你有选择困难症,那就选择大杂烩的 normalization 方法——switchable normalization [11]。
7. discriminator
想要生成更高清的图像,推荐 multi-stage discriminator [10]。简单的作法就是对于输入图片,把它下采样(maxpooling)到不一样 scale 的大小,输入三个不一样参数但结构相同的 discriminator。
8. minibatch discriminator
因为判别器是单独处理每张图片,没有一个机制能告诉 discriminator 每张图片之间要尽量的不类似,这样就会致使判别器会将全部图片都 push 到一个看起来真实的点,缺少多样性。minibatch discriminator [22] 就是这样这个机制,显式地告诉 discriminator 每张图片应该要不类似。在 tensorflow 中,一种实现 minibatch discriminator 方式以下:
上面是经过一个可学习的网络来显示度量每一个样本之间的类似度,PGGAN 里提出了一个更廉价的不须要学习的版本,即经过统计每一个样本特征每一个像素点的标准差,而后取他们的平均,把这个平均值复制到与当前 feature map 同样空间大小单通道,做为一个额外的 feature maps 拼接到原来的 feature maps 里,一个简单的 tensorflow 实现以下:
9. GAN loss
除了第二节提到的原始 GANs 中提出的两种 loss,还能够选择 wgan loss [12]、hinge loss、lsgan loss [13]等。wgan loss 使用 Wasserstein 距离(推土机距离)来度量两个分布之间的差别,lsgan 采用相似最小二乘法的思路设计损失函数,最后演变成用皮尔森卡方散度代替了原始 GAN 中的 JS 散度,hinge loss 是迁移了 SVM 里面的思想,在 SAGAN [14] 和 BigGAN [15] 等都是采用该损失函数。
ps: 我本身常用没有 relu 的 hinge loss 版本。
10. other loss
一般状况下,GAN loss 配合上面几种 loss,效果会更好。
11. gradient penalty
Gradient penalty 首次在 wgan-gp 里面提出来的,记为 1-gp,目的是为了让 discriminator 知足 1-lipchitchz 连续,后续 Mescheder, Lars M. et al [19] 又提出了只针对正样本或者负样本进行梯度惩罚,记为 0-gp-sample。Thanh-Tung, Hoang et al [20] 提出了 0-gp,具备更好的训练稳定性。三者的对好比下:
12. Spectral normalization [21]
谱归一化是另一个让判别器知足 1-lipchitchz 连续的利器,建议在判别器和生成器里同时使用。
ps: 在我的实践中,它比梯度惩罚更有效。
13. one-size label smoothing [22]
平滑正样本的 label,例如 label 1 变成 0.9-1.1 之间的随机数,保持负样本 label 仍然为 0。我的经验代表这个 trick 可以有效缓解训练不稳定的现象,可是不能根本解决问题,假如模型不够好的话,随着训练的进行,后期 loss 会飞。
14. add supervised labels
15. instance noise (decay over time)
在原始 GAN 中,咱们其实在优化两个分布的 JS 散度,前面的推理代表在两个分布的支撑集没有交集或者支撑集是低维的流形空间,他们之间的 JS 散度大几率上是 0;而加入 instance noise 就是强行让两个分布的支撑集之间产生交集,这样 JS 散度就不会为 0。新的 JS 散度变为:
16. TTUR [23]
在优化 G 的时候,咱们默认是假定咱们的 D 的判别能力是比当前的 G 的生成能力要好的,这样 D 才能指导 G 朝更好的方向学习。一般的作法是先更新 D 的参数一次或者屡次,而后再更新 G 的参数,TTUR 提出了一个更简单的更新策略,即分别为 D 和 G 设置不一样的学习率,让 D 收敛速度更快。
17. training strategy
PGGAN 是一个渐进式的训练技巧,由于要生成高清(eg, 1024x1024)的图片,直接从一个随机噪声生成这么高维度的数据是比较难的;既然无法一蹴而就,那就按部就班,首先从简单的低纬度的开始生成,例如 4x4,而后 16x16,直至咱们所须要的图片大小。在 PGGAN 里,首次实现了高清图片的生成,而且能够作到以假乱真,可见其威力。此外,因为咱们大部分的操做都是在比较低的维度上进行的,训练速度也不比其余模型逊色多少。
coarse-to-refine 能够说是 PGGAN 的一个特例,它的作法就是先用一个简单的模型,加上一个 l1 loss,训练一个模糊的效果,而后再把这个模糊的照片送到后面的 refine 模型里,辅助对抗 loss 等其余 loss,训练一个更加清晰的效果。这个在图片生成里面普遍应用。
18. Exponential Moving Average [24]
EMA主要是对历史的参数进行一个指数平滑,能够有效减小训练的抖动。强烈推荐!!!
总结
训练 GAN 是一个精(折)细(磨)的活,一不当心你的 GAN 可能就是一部惊悚大片。笔者结合本身的经验以及看过的一些文献资料,列出了经常使用的 tricks,在此抛砖引玉,因为笔者能力和视野有限,有些不正确之处或者没补全的 tricks,还望斧正。