经典论文复现 | LSGAN:最小二乘生成对抗网络

过去几年发表于各大 AI 顶会论文提出的 400 多种算法中,公开算法代码的仅占 6%,其中三分之一的论文做者分享了测试数据,约 54% 的分享包含“伪代码”。这是今年 AAAI 会议上一个严峻的报告。 人工智能这个蓬勃发展的领域正面临着实验重现的危机,就像实验重现问题过去十年来一直困扰着心理学、医学以及其余领域同样。最根本的问题是研究人员一般不共享他们的源代码。 node

可验证的知识是科学的基础,它事关理解。随着人工智能领域的发展,打破不可复现性将是必要的。为此,PaperWeekly 联手百度 PaddlePaddle 共同发起了本次论文有奖复现,咱们但愿和来自学界、工业界的研究者一块儿接力,为 AI 行业带来良性循环。算法

下载安装命令

## CPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

做者丨文永亮网络

学校丨华南理工大学多线程

研究方向丨目标检测、图像生成架构

笔者此次选择复现的是 Least Squares Generative Adversarial Networks,也就是 LSGANs机器学习

640?

近几年来 GAN 是十分火热的,由 Goodfellow 在 14 年发表论文 Generative Adversarial Nets [1] 开山之做以来,生成式对抗网络一直都备受机器学习领域的关注,这种两人零和博弈的思想十分有趣,充分体现了数学的美感。从 GAN 到 WGAN[2] 的优化,再到本文介绍的 LSGANs,再到最近很火的 BigGAN [3],能够说生成式对抗网络的魅力无穷,并且它的用处也是很是奇妙,现在还被用在例如无负样本的状况下如何训练分类器,例如 AnoGAN [4]。 ide

LSGANs 这篇经典的论文主要工做是把交叉熵损失函数换作了最小二乘损失函数,这样作做者认为改善了传统 GAN 的两个问题,即传统 GAN 生成的图片质量不高,并且训练过程十分不稳定。函数

LSGANs 试图使用不一样的距离度量来构建一个更加稳定并且收敛更快的,生成质量高的对抗网络。可是我看过 WGAN 的论文以后分析这一损失函数,其实并不符合 WGAN 做者的分析。在下面我会详细分析一下为何 LSGANs 其实并无那么好用。学习

论文复现代码: 测试

http://aistudio.baidu.com/aistudio/#/projectdetail/25767

LSGANs的优势

咱们知道传统 GAN 生成的图片质量不高,传统的 GANs 使用的是交叉熵损失(sigmoid cross entropy)做为判别器的损失函数。 

在这里说一下我对交叉熵的理解,有两个分布,分别是真实分布 p 和非真实分布 q。

信息熵是640,就是按照真实分布 p 这样的样本空间表达能力强度的相反值,信息熵越大,不肯定性越大,表达能力越弱,咱们记做 H(p)。 交叉熵就是640,能够理解为按照不真实分布 q 这样的样本空间表达能力强度的相反值,记做 H(p,q)。 

KL 散度就是 D(p||q) = H(p,q) - H(p),它表示的是两个分布的差别,由于真实分布 p 的信息熵固定,因此通常由交叉熵来决定,因此这就是为何传统 GAN 会采用交叉熵的缘故,论文也证实了 GAN 损失函数与 KL 散度的关系。 

咱们知道交叉熵通常都是拿来作逻辑分类的,而像最小二乘这种通常会用在线性回归中,这里为何会用最小二乘做为损失函数的评判呢? 

使用交叉熵虽然会让咱们分类正确,可是这样会致使那些在决策边界被分类为真的、可是仍然远离真实数据的假样本(即生成器生成的样本)不会继续迭代,由于它已经成功欺骗了判别器,更新生成器的时候就会发生梯度弥散的问题。 

论文指出最小二乘损失函数会对处于判别成真的那些远离决策边界的样本进行惩罚,把远离决策边界的假样本拖进决策边界,从而提升生成图片的质量。做者用下图详细表达了这一说法:

640

咱们知道传统 GAN 的训练过程十分不稳定,这很大程度上是由于它的目标函数,尤为是在最小化目标函数时可能发生梯度弥散,使其很难再去更新生成器。而论文指出 LSGANs 能够解决这个问题,由于 LSGANs 会惩罚那些远离决策边界的样本,这些样本的梯度是梯度降低的决定方向。

论文指出由于传统 GAN 辨别器 D 使用的是 sigmoid 函数,而且因为 sigmoid 函数饱和得十分迅速,因此即便是十分小的数据点 x,该函数也会迅速忽略样本 x 到决策边界 w 的距离。这就意味着 sigmoid 函数本质上不会惩罚远离决策边界的样本,而且也说明咱们知足于将 x 标注正确,所以辨别器 D 的梯度就会很快地降低到 0。

咱们能够认为,交叉熵并不关心距离,而是仅仅关注因而否正确分类。正如论文做者在下图中所指出的那样,(a)图看到交叉熵损失很容易就达到饱和状态,而(b)图最小二乘损失只在一点达到饱和,做者认为这样训练会更加稳定。

640

LSGANs的损失函数

传统 GAN 的损失函数:

640

LSGANs 的损失函数:

640

其中 G 为生成器(Generator),D 为判别器(Discriminator),z 为噪音,它能够服从归一化或者高斯分布,640为真实数据 x 服从的几率分布,640为 z 服从的几率分布。640为指望值,640同为指望值。

def generator(z, name="G"):    with fluid.unique_name.guard(name+'_'):        fc1 = fluid.layers.fc(input = z, size = 1024)        fc1 = fluid.layers.fc(fc1, size = 128 * 7 * 7)        fc1 = fluid.layers.batch_norm(fc1,act = 'tanh')        fc1 = fluid.layers.reshape(fc1, shape=(-1, 128, 7, 7))        conv1 = fluid.layers.conv2d(fc1, num_filters = 4*64,                                    filter_size=5, stride=1,                                     padding=2, act='tanh')        conv1 = fluid.layers.reshape(conv1, shape=(-1,64,14,14))        conv2 = fluid.layers.conv2d(conv1, num_filters = 4*32,                                     filter_size=5, stride=1,                                    padding=2, act='tanh')        conv2 = fluid.layers.reshape(conv2, shape=(-1,32,28,28))        conv3 = fluid.layers.conv2d(conv2, num_filters = 1,                                     filter_size=5, stride=1,                                    padding=2,act='tanh')#         conv3 = fluid.layers.reshape(conv3, shape=(-1,1,28,28))        print("conv3",conv3)        return conv3
    with fluid.unique_name.guard(name+'_'):
        fc1 = fluid.layers.fc(input = z, size = 1024)
        fc1 = fluid.layers.fc(fc1, size = 128 * 7 * 7)
        fc1 = fluid.layers.batch_norm(fc1,act = 'tanh')
        fc1 = fluid.layers.reshape(fc1, shape=(-112877))


        conv1 = fluid.layers.conv2d(fc1, num_filters = 4*64,
                                    filter_size=5, stride=1, 
                                    padding=2, act='tanh')
        conv1 = fluid.layers.reshape(conv1, shape=(-1,64,14,14))

        conv2 = fluid.layers.conv2d(conv1, num_filters = 4*32, 
                                    filter_size=5, stride=1,
                                    padding=2, act='tanh')
        conv2 = fluid.layers.reshape(conv2, shape=(-1,32,28,28))

        conv3 = fluid.layers.conv2d(conv2, num_filters = 1, 
                                    filter_size=5, stride=1,
                                    padding=2,act='tanh')
#         conv3 = fluid.layers.reshape(conv3, shape=(-1,1,28,28))
        print("conv3",conv3)
        return conv3

 生成器代码展现

def discriminator(image, name="D"):    with fluid.unique_name.guard(name+'_'):        conv1 = fluid.layers.conv2d(input=image, num_filters=32,                                    filter_size=6, stride=2,                                    padding=2)        conv1_act = fluid.layers.leaky_relu(conv1)        conv2 = fluid.layers.conv2d(conv1_act, num_filters=64,                                     filter_size=6, stride=2,                                    padding=2)        conv2 = fluid.layers.batch_norm(conv2)        conv2_act = fluid.layers.leaky_relu(conv2)        fc1 = fluid.layers.reshape(conv2_act, shape=(-1,64*7*7))        fc1 = fluid.layers.fc(fc1, size=512)        fc1_bn = fluid.layers.batch_norm(fc1)        fc1_act = fluid.layers.leaky_relu(fc1_bn)        fc2 = fluid.layers.fc(fc1_act, size=1)        print("fc2",fc2)        return fc2
    with fluid.unique_name.guard(name+'_'):
        conv1 = fluid.layers.conv2d(input=image, num_filters=32,
                                    filter_size=6, stride=2,
                                    padding=2)
        conv1_act = fluid.layers.leaky_relu(conv1)

        conv2 = fluid.layers.conv2d(conv1_act, num_filters=64, 
                                    filter_size=6, stride=2,
                                    padding=2)
        conv2 = fluid.layers.batch_norm(conv2)
        conv2_act = fluid.layers.leaky_relu(conv2)

        fc1 = fluid.layers.reshape(conv2_act, shape=(-1,64*7*7))
        fc1 = fluid.layers.fc(fc1, size=512)
        fc1_bn = fluid.layers.batch_norm(fc1)
        fc1_act = fluid.layers.leaky_relu(fc1_bn)

        fc2 = fluid.layers.fc(fc1_act, size=1)
        print("fc2",fc2)
        return fc2

 判别器代码展现

做者提出了两种 abc 的取值方法: 

1. 使 b - c = 1,b - a = 2,例如 a = -1,b = 1,c = 0:

640

2. 使 c = b,用 0-1 二元标签,咱们能够获得:

640

做者在文献中有详细推倒过程,详细说明了 LSGAN 与 f 散度之间的关系,这里简述一下。

经过对下式求一阶导可获得 D 的最优解:

640

代入:

640

其中另加项640并不影响640的值,由于它不包含参数 G。

最后咱们设 b - c = 1,b - a =2 就能够获得:

640

其中640就是皮尔森卡方散度。

LSGANs未能解决的地方

下面我会指出 LSGANs 给出的损失函数到底符不符合 WGAN 前做的理论。关于 WGAN 前做及 WGAN 论文的分析能够参考本文 [5]

上面咱们指出了 D 的最优解为公式(5),咱们最经常使用的设 a=-1,b=1,c=0 能够得出:

640

把最优判别器带入上面加附加项的生成器损失函数能够表示为:

640

也就是优化上面说的皮尔森卡方散度,其实皮尔森卡方散度和 KL 散度、JS 散度有同样的问题,根据 WGAN 给出的理论,下面用 P1,P2 分别表示640640

当 P1 与 P2 的支撑集(support)是高维空间中的低维流形(manifold)时,P1 与 P2 重叠部分测度(measure)为 0 的几率为 1。也就是 P1 和 P2 不重叠或重叠部分可忽略的可能性很是大。

对于数据点 x,只可能发生以下四种状况:

1. P1(x)=0,P2(x)=0

2. P1(x)!=0,P2(x)!=0

3. P1(x)=0,P2(x)!=0

4. P1(x)!=0,P2(x)=0

能够想象成下面这幅图,假设 P1(x) 分布就是 AB 线段,P2(x) 分布就是 CD 线段,数据点要么在两条线段的其中一条,要么都不在,同时在两条线段上的可能性忽略不计。

640

状况 1 是没有意义的,而状况 2 因为重叠部分可忽略的可能性很是大因此对计算损失贡献为 0,状况 3 能够算出 D*=-1,损失是个定值 1,状况 4 相似。

因此咱们能够得出结论,当 P1 和 P2 不重叠或重叠部分可忽略的可能性很是大时,当判别器达到最优时,生成器仍然是不迭代的,由于此时损失是定值,提供的梯度仍然为 0。同时咱们也能够从另外一个角度出发,WGAN 的 Wasserstein 距离能够变换以下:

640

它要求函数 f 要符合 Lipschitz 连续,但是最小二乘损失函数是不符合的,他的导数是没有上界的。因此结论就是 LSGANs 其实仍是未能解决判别器足够优秀的时候,生成器仍是会发生梯度弥散的问题。

两种模型架构和训练

模型的结构

做者也提出了两类架构:

第一种处理类别少的状况,例如 MNIST、LSUN。网络设计以下:

640

第二类处理类别特别多的情形,其实是个条件版本的 LSGAN。针对手写汉字数据集,有 3740 类,提出的网络结构以下:

640

训练数据

论文中使用了不少场景的数据集,而后比较了传统 GANs 和 LSGANs 的稳定性,最后还经过训练 3740 个类别的手写汉字数据集来评价 LSGANs。

640

 本文使用的数据集列表

在 LSUN 和 HWDB1.0 的这两个数据集上使用 LSGANs 的效果图以下,其中 LSUN 使用了里面的 bedroom, kitchen, church, dining room 和 conference room 五个场景,bedroom 场景还对比了 DCGANs 和 EBGANs 的效果在图 5 中,能够观察到 LSGANs 生成的效果要比那两种的效果好。

640

640

图 7 则体现了 LSGANs 和传统 GANs 生成的图片对比。

640

经过实验观察,做者发现 4 点技巧: 

1. 生成器 G 带有 batch normalization 批处理标准化(如下简称 BN)而且使用 Adam 优化器的话,LSGANs 生成的图片质量好,可是传统 GANs 历来没有成功学习到,会出现 mode collapse 现象;

2. 生成器 G 和判别器 D 都带有 BN 层,而且使用 RMSProp 优化器处理,LSGANs 会生成质量比 GANs 高的图片,而且 GANs 会出现轻微的 mode collapse 现象;

3. 生成器 G 带有 BN 层而且使用 RMSProp 优化器,生成器 G 判别器 D 都带有 BN 层而且使用 Adam 优化器时,LSGANs 与传统 GANs 有着类似的表现;

4. RMSProp 的表现比 Adam 要稳定,由于传统 GANs 在 G 带有 BN 层时,使用 RMSProp 优化能够成功学习,可是使用 Adam 优化却不行。

下面是使用 LSGANs 和 GANs 学习混合高斯分布的数据集,下图展示了生成数据分布的动态结果,能够看到传统 GAN 在 Step 15k 时就会发生 mode collapse 现象,但 LSGANs 很是成功地学习到了混合高斯分布。

640

论文具体实现

笔者使用了 MNIST 数据集进行实验,具体实现效果以下:

LSGANs:

640

GAN:

640

从本次用 MNIST 数据训练的效果来看,LSGANs 生成的效果彷佛是比 GAN 的要清晰高质量一些。

总结

LSGANs 是对 GAN 的一次优化,从实验的状况中,笔者也发现了一些奇怪的现象。我原本是参考论文把判别器 D 的损失值,按真假两种 loss 加起来一并放入 Adam 中优化,可是不管如何都学习不成功,梯度仍是弥散了,最后把 D_fake_loss 和 D_real_loss 分为两个 program,放入不一样的 Adam 中优化判别器D 的参数才达到预期效果。

这篇论文中的思想是很是值得借鉴的,从最小二乘的距离的角度考量,并非判别器分类以后就完事了,可是 LSGANs 其实仍是未能解决判别器足够优秀的时候,生成器梯度弥散的问题。

关于PaddlePaddle

下载安装命令

## CPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

笔者反馈:帮助文档有点少,并且我原本就直接写好了想改为使用 GPU 运算,没找到怎么改;

PaddlePaddle团队:关于如何使用 GPU 运行,能够看下执行器 Executor(单 GPU 或单线程 CPU 执行器)或 ParallelExecutor(多 GPU 或多线程 CPU 执行器,也能够单 GPU/线程 CPU 执行)的文档,前者指定 place 为 CUDAPlace,后者接口有个 use_cuda,具体请参考文档。也能够看 models repo 例子,好比 image_classification 或 text_classification 的例子。 

笔者反馈:Program 这个概念有点新颖,一个模型能够有多个 Program,可是我实现的 GAN 能够只用一个,也能够分别放进三个 Program,没有太了解到 Program 这个概念的优越之处,我仍是像计算图那样使用了,官方也没给出与 TensorFlow 的对比。

PaddlePaddle团队:关于 Program 设计能够参考官方文档。这里提一点,在用户使用的直观感觉中和 TensorFlow graph 不一样的是,凡是放在一个 Program 里 op,只要运行该 Program,这些 op 就都会执行;而 TensorFlow,指定一个 variable,只运行以该 variable 为叶子节点的 graph,其余多余 node 不执行,这是最大的用户感觉到的区别。 

至于一个 Program 仍是多个 Program,看用户使用需求而定,多个 Program 时要注意的东西就比较多,例如是否要参数共享等,固然运行屡次的时间代价也稍多。 若是是 GAN 也能够参考 models repo 的例子。

小道消息:据说全新版本的 PaddlePaddle 已于今日发布哦。

参考文献

[1]. I. Goodfellow, J. Pouget-Abadie, M. Mirza, B. Xu, D. Warde-Farley, S. Ozair, A. Courville, and Y. Bengio, “Generative adversarial nets,” in Advances in Neural Information Processing Systems (NIPS), pp. 2672–2680, 2014.

[2]. M. Arjovsky, S. Chintala, and L. Bottou. Wasserstein GAN. arXiv preprint arXiv:1701.07875, 2017.

[3]. Andrew Brock, Jeff Donahue and Karen Simonyan. Large Scale GAN Training for High Fidelity Natural Image Synthesis. arXiv:1809.11096, 2018.

[4]. Schlegl, Thomas, et al. "Unsupervised Anomaly Detection with Generative Adversarial Networks to Guide Marker Discovery." arXiv preprint arXiv:1703.05921 (2017).

[5]. https://zhuanlan.zhihu.com/p/25071913?from_voters_page=true

640?

文章来源:PaperWeekly

640?wx_fmt=jpeg

本文同步分享在 博客“飞桨PaddlePaddle”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索