Pytorch && GAN


这篇文章必看
为了防止网页失效,我还是打算复制一下
本人公众号:算法的秘密 聚焦面试中各种算法题,欢迎关注

知乎上有个讨论,说学数学的看不起搞深度学习的。曲直对错不论,他们看不起搞深度学习的原因很简单,因为从数学的角度看,深度学习仅仅是一个最优化问题而已。比如,被炒的很热的对抗式生成网络(GAN),从数学看,基本原理很容易就能说明白,剩下的仅仅是需要计算资源去优化参数,是个体力活。

本文的目的就是尽可能简单地从数学角度解释清楚GAN的数学原理,看清它的庐山真面目。

1,从生成模型说起
机器学习的模型可分为生成模型和判别模型。

简单说说二者的区别,以二分类问题来讲,已知一个样本的特征为x,我们要去判断它的类别y(取值为0,1)。也就是要计算p(ylx),假设我们已经有了N个样本。

计算p(ylx)的思路有两个,一个就是直接求,即y关于x的条件分布,逻辑回归就是这样干的。凡是按这种思路做的模型统一称作判别模型

另一个思路是曲线救国,先求出x,y的联合分布p(x,y),然后,根据p(ylx)=p(x,y)/p(x)来计算p(ylx),朴素贝叶斯就是这样干的。凡是按这种思路做的模型统一称作生成模型。

那么,GAN属于什么呢?答案是生成模型,因为它也是在模拟数据的分布。
请问GAN生成的分布模拟的是联合分布p(x,y)还是p(x)?

举个例子,我们有N张尺寸为50*50的小猫的照片,先不管这些猫多可爱,多漂亮,从数学的角度看,一张猫的图片可以理解为2500维空间中的一个点或者说是一个2500维的向量,N张图就是N个点或者说向量。如果我们想让神经网络自动生成一个小猫的图片,所要做的就是假设N张图片都是某个2500维空间中的随机分布的样本,这个分布抽样产生的点就是一张猫的图片。

这里有一个问题,需要再思考一下,我们假设的这个分布存在吗?答案是存在的,因为这是GAN的理论基础,没有这个假设,GAN就玩不下去了。具体的证明过于数学了,此刻,我们只要相信它存在就是了。

假设图片背后隐藏的分布是Pd(d就是data),我们的任务就是用神经网络生成一个分布Pg(g就是generate),只要Pg和Pd很接近,就可以用Pg生成一张小猫的图片了。

2,GAN解决的基本问题
根据上面的讨论,我们要解决两个问题:

一是如何用神经网络构造一个模拟分布Pg,

另一个是如何衡量Pg和Pd是否相似,并根据衡量结果去优化Pg

这就是GAN解决的最根本的两个问题。

3,GAN是如何解决这两个问题的?
第一个问题很容易解决,以上面的小猫问题为例,只要做一个神经网络,它的输入是来自某个特定分布的数,为便于说明,我们就假设这个特定分布是一维的,也就是它产生的数就是一个标量,如1,2.5之类的,经过多层的映射后,产生一个2500维的向量。可以想象,只要输入来自一个特定的分布,映射产生的2500维向量也会形成一个分布。这个分布的概率密度函数就是输入分布的概率密度函数在2500维空间的扩展。

我们来举个例子,为了说明问题,这里假设神经网络映射后也是一个一维的数,不再是上面例子中的2500维向量,但我们要清楚,其原理是一样的。

假设我们的输入分布是一个标准正态分布,其概率密度函数为

在这里插入图片描述

标准正态分布
我们的神经网络就相当于一个函数y=g(x),现在已知x的概率密度函数如上所示,那么y的概率密度函数会是什么样子呢?

没有什么是一个例子说明不了的,如果有,那就两个。

假设g就是一个等值映射,即g(x)=x,显然y与x的概率密度函数一样,也是标准正态分布。

如果g(x)=x+1,那么,y的概率密度函数就是均值为1,方差为1的正态分布。

可见,神经网络对某个特定分布x映射后的y确实是一个分布,其概率密度函数既与x的概率密度函数相关,又与神经网络的结构g函数相关。这里说的网络结构有两层含义,一个网络的内部构造,另一个就是网络的输出向量维数。我们虽然只举了输出为1维的情况,但在输出是多维时,y的分布会有一些新的特点。

比如,一个有趣的问题是,若原始的输入是一维的正态分布,其取值空间是整个一维实数空间,如果输出的是多维向量分布,那么,它的取值空间还会是整个多维空间吗?

这个问题让我联想到《三体》,太阳系遭受二向箔攻击后,变成了二维空间,逃离的人类,开始寻找更高级的宇宙文明,期望有一天,能够将二维化的地球还原成三维的世界。

第二个问题如何解决呢?

回到刚才小猫的问题,假设我们有一万个样本图片,也有了一个生成分布的神经网络G,它的输入是一个正态分布。输出是2500维的向量。

从数学上讲,我们的问题是,如何衡量Pd,Pg的相似性,以及如何使Pg接近Pd。

具体的方法不用我们费心想了,我们直接看现成的就可以。

即再定义一个NN,叫做D,D(x)产生的是0到1之间的一个值

定义函数

V(G, D) = E_(x~Pd)(log(D(x)) + E_(x~Pg)(log(1-D(x)) 这里的E表示的是期望,概率论中的求复合变量的期望

对于一个特定的G,通过调整D,可以得到maxV(G,D),它就能衡量Pd和Pg的difference,这个值越小, 二者差距越小。(如果D把Pd和Pg都判定为1,则这里的V(G,D)由于log0的存在会很小的负数,负无穷,故很小,我猜的,想要出现这样的情况,必须生成器Generator非常强大,强到判别器Discriminator无法区分两者)

为什么maxV(G,D)可以衡量两个分布的差异?(我觉得maxV(G,D)纯粹是为了训练判别器,跟衡量差异有什么关系?)

我们先直接观察V,把D看作一个判别器,V的第一部分表示D对来自真实分布的数据的评分的期望,第二部分表示D对来自G生成的数据的评分与1的差的期望。

最大化V,就是要使D对来自真实数据的评分尽可能高,对来自G生成的数据的评分尽可能低(即让1-D(x)尽可能高)。

至此,我们看到,V越大,表示D对来自真实分布和来自G的数据评分差异越大,真实的样本越接近1,G产生的样本越接近0。

通过调整D,得到的maxV,表示在我们能力范围内,我们找到的最强的D,它能把Pd和Pg产生的数据区分开的程度。自然,这个区分程度就表示了Pd和Pg的差异程度。因为可以想象,Pd和Pg越接近,同样的D,得到的V肯定越小。因为二者产生的数据越难区分开。(这句话好像跟min有关:Pd和Pg越接近,同样的D,得到的V肯定越小)

这里说的maxV(G,D)是只考虑判别器,即希望D具有很强的辨别能力,所以需要最大化这个V(G,D)
最极端的情况,当x属于Pd时,D(X)=1,当x属于Pg时,D(X)=0,
此时V(G,D)取得最大值为0
言外之意是其他情况下都是负数

以上,是通过直观的分析得出的一些认识,我们还可以从数学上进行一些分析。

对V做一些变形,可以得到如下

V = Σ_x(Pd(x)*log(D(x)) + Pg(x)*log(1-D(x)) ,是不是就像概率论中的复合变量的期望。
比如x的概率密度是Pd(x),复合变量是log(D(x)),然后就这样结合就可以求复合变量的期望

maxV时,我们只要看对于一个x,如何最大化这个值即可:

Pd(x)*log(D(x)) + Pg(x)*log(1-D(x))

这里,除了D是变化的,其他都是已经给定的,

令a=Pd(x),D=D(x), b = Pg(x)

a,b都是一个确定的数值,因为x此时是确定的,上式进一步变成这样

alogD + blog(1-D)

对这个式子,求导,很容易得出最佳的D值是:

D^* = a/(a+b) 可见,D的值确实是0到1之间的值

再将D^*带入最上面的V的定义中,经过一系列的计算推导(此处省略了推导过程,感兴趣的小伙伴可以自己推导一下),我们可以得出maxV实际表示的是Pd

和(Pd+Pg)/2的KL散度加上Pg和(Pd+Pg)/2的KL散度

如果令g = (Pd+Pg)/2,则

maxV = -2log2 + KL(Pd||g) + KL(Pg||g)

如果有小伙伴对KL散度不是很理解,推荐看看我的另一篇文章《机器学习面试之各种混乱的熵》,里面有一个很通俗易懂的解释。

这里有一个数学定义:

JSD(Pg||Pd)=1/2*(KL(Pd||g) + KL(Pg||g))

根据定义,maxV就变成这样了:

maxV = -2log2 + 2JSD(Pg||Pd)

JSD全称是Jessen-Sannon Divergence,它是一个对称化的KL散度。

从数学上可以证明,JSD最大值是log2,最小值是0

所以maxV最大值是0,最小值是-2log2。

至此,我们的第二个问题已经解决了一半,将Pd和Pg的差异量化成了一个-2log2到0之间的一个数值。
前面已经说过,这个值越小,Pd和Pg的差异越小,我们的最终的目标就是希望Pg和Pd越接近越好,这样就可以实现无中生有,GAN的终极奥义。
所以接下来需要再求最小化问题,这也是对应了公式中的min max()
即先求max,再求min

另一半问题就是如何针对求出的最大化V的D,更新调整G,使得maxV变小一点。

我们将maxV定义为L(G),即

L(G)=maxV

可见,maxV实际上就是G的损失函数,可以用梯度下降更新神经网络G的各个参数得到G1,使得maxV下降。

有了G1后,我们再次重复上面的最大化V的过程,得到D1,然后再次运用梯度下降,得到G2,如此循环下去,可以期待,Pg将会越来越接近Pd。
在这里插入图片描述

声明:上图来自李宏毅深度学习课程截图,如侵删。

这是从理论上证明的,如果你感觉看得比较晕乎,没关系,我们下面就来看看实践中的做法。

4,实践中的做法
以上的推导都是从理论上进行的分析,在工程实践中,有个问题必须解决:

我们并不能真正知道Pd,我们只是有很多Pd产生的样本,自然也求不出logD(x)关于Pd的期望,对于Pg的期望也有类似困难。

那么,实践中是怎么做的呢?

首先,既然直接计算期望而不可得,我们只能退而求其次,从Pd中sample 出m个样本,再从Pg中sample出m个样本,最大化D(x)在这2m个样本的V值,如下图所示:

在这里插入图片描述

仔细观察上式,最大化V的过程,就是将Pd产生的样本作为正样本,Pg产生的样本作为负样本,训练一个分类器D的过程。

完整的算法如下所示:

在这里插入图片描述
上面的梯度更新一个用的加号maximize,一个用的减号minimize
以minimize为例,当判别器对生成器生成的样本的预测为1时,此时说明生成器是成功的,此时v的值很小,也是说我们需要通过minV来训练生成器。

声明:该图同样来自李宏毅深度学习课程,如侵删。

另外,在工程实践中,还有一个实际的调整,就是将V蚯蚓中的log(1-D(x))改为-log(D(x))

原因是:

D的值在0到1之间,

在刚开始时,由于G生成的图像比较挫,D可以很容易分辨出来,也就是生成的值都远小于0.5,V蚯蚓关于G的参数θg的梯度等于 log(1-D(x))关于D的梯度再乘以D关于θg的梯度,但是此时,log(1-D(x))在0到0.5之间的梯度比较小,这就会导致θg的更新比较慢,但是换成-log(D(x))后,它在0到0.5之间的梯度是比较大的,可以快速的更新g的参数。这符合我们的直觉,就是一开始学习要快一点,后面要慢一点。

5,无总结,不进步
纵观整个GAN,本来我们要求Pg和Pd之间的相似度,由于不能直接求,转而借助一个D,maxV求出一个最佳的D后,maxV就是在衡量Pg和Pd的JS 散度,然后,最小化这个散度值,更新一次Pg,有了新的Pg后,进一步求出最佳的D,然后重复上面的步骤。

这篇文章也可以看

前言

GAN网络是近两年深度学习领域的新秀,火的不行,本文旨在浅显理解传统GAN,分享学习心得。现有GAN网络大多数代码实现使用Python、torch等语言,这里,后面用matlab搭建一个简单的GAN网络,便于理解GAN原理。

GAN的鼻祖之作是2014年NIPS一篇文章:Generative Adversarial Net(https://arxiv.org/abs/1406.2661),可以细细品味。

分享一个目前各类GAN的一个论文整理集合

https://deephunt.in/the-gan-zoo-79597dc8c347

再分享一个目前各类GAN的一个代码整理集合

https://github.com/zhangqianhui/AdversarialNetsPapers

▌开始

我们知道GAN的思想是是一种二人零和博弈思想(two-player game),博弈双方的利益之和是一个常数,比如两个人掰手腕,假设总的空间是一定的,你的力气大一点,那你就得到的空间多一点,相应的我的空间就少一点,相反我力气大我就得到的多一点,但有一点是确定的就是,我两的总空间是一定的,这就是二人博弈,但是呢总利益是一定的。

引申到GAN里面就是可以看成,GAN中有两个这样的博弈者,一个人名字是生成模型(G),另一个人名字是判别模型(D)。他们各自有各自的功能。

相同点是:

这两个模型都可以看成是一个黑匣子,接受输入然后有一个输出,类似一个函数,一个输入输出映射。

不同点是:

生成模型功能:比作是一个样本生成器,输入一个噪声/样本,然后把它包装成一个逼真的样本,也就是输出。

判别模型:比作一个二分类器(如同0-1分类器),来判断输入的样本是真是假。(就是输出值大于0.5还是小于0.5)

我从下面这张图中:
个人认为因为梯度是从后往前传播的,
所以我们要训练G的时候梯度信息肯定是要先流经D,但是应该能禁止梯度更新(你流归你流)吧
实际训练G的时候不会训练D,但是训练D的时候也不会训练G
而我们训练D的时候梯度信息可以人为在生成器末尾终止。

直接上一张个人觉得解释的好的图说明:
在这里插入图片描述
上面的图是不是有问题?
判别器的最后的输出层只有一个神经元
输出的值的范围是0到1
我觉得GAN的目标是以假乱真,即生成器强大到使得判别器认为其生成的样本也是真的,
所以我觉得应该是输出结果概率都是接近1的,而不是尴尬的0.5(这分界点不是很尴尬吗)

在之前,我们首先明白在使用GAN的时候的2个问题

我们有什么?
比如上面的这个图,我们有的只是真实采集而来的人脸样本数据集,仅此而已,而且很关键的一点是我们连人脸数据集的类标签都没有,也就是我们不知道那个人脸对应的是谁。

我们要得到什么?
至于要得到什么,不同的任务得到的东西不一样,我们只说最原始的GAN目的,那就是我们想通过输入一个噪声,模拟得到一个人脸图像,这个图像可以非常逼真以至于以假乱真。

好了再来理解下GAN的两个模型要做什么。

首先判别模型,就是图中右半部分的网络,直观来看就是一个简单的神经网络结构,输入就是一副图像,输出就是一个概率值,用于判断真假使用(概率值大于0.5那就是真,小于0.5那就是假),真假也不过是人们定义的概率而已。

其次是生成模型,生成模型要做什么呢,同样也可以看成是一个神经网络模型,输入是一组随机数Z,输出是一个图像,不再是一个数值而已。从图中可以看到,会存在两个数据集,一个是真实数据集,这好说,另一个是假的数据集,那这个数据集就是有生成网络造出来的数据集。好了根据这个图我们再来理解一下GAN的目标是要干什么:

判别网络的目的:就是能判别出来属于的一张图它是来自真实样本集还是假样本集。假如输入的是真样本,网络输出就接近1,输入的是假样本,网络输出接近0,那么很完美,达到了很好判别的目的。

生成网络的目的:生成网络是造样本的,它的目的就是使得自己造样本的能力尽可能强,强到什么程度呢,你判别网络没法判断我是真样本还是假样本。

有了这个理解我们再来看看为什么叫做对抗网络了。判别网络说,我很强,来一个样本我就知道它是来自真样本集还是假样本集。生成网络就不服了,说我也很强,我生成一个假样本,虽然我生成网络知道是假的,但是你判别网络不知道呀,我包装的非常逼真,以至于判别网络无法判断真假,那么用输出数值来解释就是,生成网络生成的假样本进去了判别网络以后,判别网络给出的结果是一个接近0.5的值,极限情况就是0.5,也就是说判别不出来了,这就是纳什平衡了。

由这个分析可以发现,生成网络与判别网络的目的正好是相反的,一个说我能判别的好,一个说我让你判别不好。所以叫做对抗,叫做博弈。那么最后的结果到底是谁赢呢?这就要归结到设计者,也就是我们希望谁赢了。作为设计者的我们,我们的目的是要得到以假乱真的样本,那么很自然的我们希望生成样本赢了,也就是希望生成样本很真,判别网络能力不足以区分真假样本位置。

▌再理解

知道了GAN大概的目的与设计思路,那么一个很自然的问题来了就是我们该如何用数学方法解决这么一个对抗问题。这就涉及到如何训练这样一个生成对抗网络模型了,还是先上一个图,用图来解释最直接:
在这里插入图片描述
需要注意的是生成模型与对抗模型可以说是完全独立的两个模型,好比就是完全独立的两个神经网络模型,他们之间没有什么联系。

好了那么训练这样的两个模型的大方法就是:单独交替迭代训练。

什么意思?因为是2个网络,不好一起训练,所以才去交替迭代训练,我们一一来看。

假设现在生成网络模型已经有了(当然可能不是最好的生成网络),那么给一堆随机数组,就会得到一堆假的样本集(因为不是最终的生成模型,那么现在生成网络可能就处于劣势,导致生成的样本就不咋地,可能很容易就被判别网络判别出来了说这货是假冒的),但是先不管这个,假设我们现在有了这样的假样本集,真样本集一直都有,现在我们人为的定义真假样本集的标签,因为我们希望真样本集的输出尽可能为1,假样本集为0,很明显这里我们就已经默认真样本集所有的类标签都为1,而假样本集的所有类标签都为0.

有人会说,在真样本集里面的人脸中,可能张三人脸和李四人脸不一样呀,对于这个问题我们需要理解的是,我们现在的任务是什么,我们是想分样本真假,而不是分真样本中那个是张三label、那个是李四label。况且我们也知道,原始真样本的label我们是不知道的。回过头来,我们现在有了真样本集以及它们的label(都是1)、假样本集以及它们的label(都是0),这样单就判别网络来说,此时问题就变成了一个再简单不过的有监督的二分类问题了,直接送到神经网络模型中训练就完事了。假设训练完了,下面我们来看生成网络。

对于生成网络,想想我们的目的,是生成尽可能逼真的样本。那么原始的生成网络生成的样本你怎么知道它真不真呢?就是送到判别网络中,所以在训练生成网络的时候,我们需要联合判别网络一起才能达到训练的目的。什么意思?就是如果我们单单只用生成网络,那么想想我们怎么去训练?误差来源在哪里?细想一下没有,但是如果我们把刚才的判别网络串接在生成网络的后面,这样我们就知道真假了,也就有了误差了。所以对于生成网络的训练其实是对生成-判别网络串接的训练,就像图中显示的那样。好了那么现在来分析一下样本,原始的噪声数组Z我们有,也就是生成了假样本我们有,此时很关键的一点来了,我们要把这些假样本的标签都设置为1,也就是认为这些假样本在生成网络训练的时候是真样本。

为什么要这样呢?我们想想,是不是这样才能起到迷惑判别器的目的,也才能使得生成的假样本逐渐逼近为正样本。好了,重新顺一下思路,现在对于生成网络的训练,我们有了样本集(只有假样本集,没有真样本集),有了对应的label(全为1),是不是就可以训练了?有人会问,这样只有一类样本,训练啥呀?谁说一类样本就不能训练了?只要有误差就行(生成网络的数据后面给识别器看,看最终结果如果loss值很低,则生成器成功欺骗了识别器(把假数据当成和label一样也是1了),如果loss很大(label上尽管是1,但是识别器还是预测为0,识别器是真的认出来了),说明生成器还需提升)。还有人说,你这样一训练,判别网络的网络参数不是也跟着变吗?没错,这很关键,所以在训练这个串接的网络的时候,一个很重要的操作就是不要判别网络的参数发生变化,也就是不让它参数发生更新,只是把误差一直传,传到生成网络那块后更新生成网络的参数。这样就完成了生成网络的训练了。

在完成生成网络训练好,那么我们是不是可以根据目前新的生成网络再对先前的那些噪声Z生成新的假样本了,没错,并且训练后的假样本应该是更真了才对。然后又有了新的真假样本集(其实是新的假样本集),接着真假样本集又都给识别器训练,这样又可以重复上述过程了。我们把这个过程称作为单独交替训练。我们可以实现定义一个迭代次数,交替迭代到一定次数后停止即可。这个时候我们再去看一看噪声Z生成的假样本会发现,原来它已经很真了。

看完了这个过程是不是感觉GAN的设计真的很巧妙,个人觉得最值得称赞的地方可能在于这种假样本在训练过程中的真假变换,这也是博弈得以进行的关键之处。假样本集在训练识别器时候label为0,是为方便计算loss,检验有多少成功欺骗了识别器,被识别器预测为1了。假样本集在训练生成器时候label为1,也是为方便计算loss,检验有多少被识别器发现了,来提升识别器的性能。我们最终目的是得到一个如火纯情的造假者的生成器!识别器是辅助工具罢了。但是识别器也不能太差劲了,得2个同时提升性能,才能达到一个我们理想的生成器。关键在于交替训练的时候要平衡的交替,不能一方太强,否则2者一起训练提升就无法继续了。

▌进一步

文字的描述相信已经让大多数的人知道了这个过程,下面我们来看看原文中几个重要的数学公式描述,首先我们直接上原始论文中的目标公式吧:

在这里插入图片描述
上述这个公式说白了就是一个最大最小优化问题,其实对应的也就是上述的两个优化过程。有人说如果不看别的,能达看到这个公式就拍案叫绝的地步,那就是机器学习的顶级专家,哈哈,真是前路漫漫。同时也说明这个简单的公式意义重大。

这个公式既然是最大最小的优化,那就不是一步完成的,其实对比我们的分析过程也是这样的,这里现优化D,然后在取优化G,本质上是两个优化问题,把拆解就如同下面两个公式:

在这里插入图片描述
可以看到,优化D的时候,也就是判别网络,其实没有生成网络什么事(因为我们拿已经训练好的生成器去生成假样本,来检验造假能力如何,或者说提升识别器能力),后面的G(z)这里就相当于已经得到的假样本。优化D的公式的第一项,使的真样本x输入的时候,得到的结果越大越好,可以理解,因为需要真样本的预测结果越接近于1越好嘛。对于假样本,需要优化是的其结果越小越好(才说明没有被骗过去嘛),也就是D(G(z))越小越好,因为它的标签为0。但是呢第一项是越大,第二项是越小,这不矛盾了,所以呢把第二项改成1-D(G(z)),这样就是越大越好,两者合起来就是越大越好。

那么同样在优化G的时候,这个时候没有真样本什么事(前面时候,说识别器参数不变,拿着已经训练好的识别器,我们只用假样本训练,所以第一项就不存在真样本了,就省略),所以把第一项直接省掉了。这个时候只有假样本,但是我们说这个时候是希望假样本的标签是1的,所以是D(G(z))越大越好,但是呢,为了统一成1-D(G(z))的形式,那么只能是最小化1-D(G(z)),本质上没有区别,只是为了形式的统一。之后这两个优化模型可以合并起来写,就变成了最开始的那个最大最小目标函数了。

所以回过头来我们来看这个最大最小目标函数,里面包含了判别模型的优化,包含了生成模型的以假乱真的优化,完美的阐释了这样一个优美的理论。

https://www.bilibili.com/video/BV1rb4y187vD?spm_id_from=333.999.0.0
李沐是这么解释上面的式子的:log小数是负数。
对于D来说,理想是把真脸判别为1,把假脸判别为0,此时两项的值都log1=0,为max,故maxD;
对于G来说,理想是把D(G(z))的值弄成1,即G很强以至于D将其认为是真的,此时log(1-D(G(z)))为负无穷大,即min,故minG.

▌再进一步
有人说GAN强大之处在于可以自动的学习原始真实样本集的数据分布,不管这个分布多么的复杂,只要训练的足够好就可以学出来。针对这一点,感觉有必要好好理解一下为什么别人会这么说。

我们知道,传统的机器学习方法,我们一般都会定义一个什么模型让数据去学习。比如说假设我们知道原始数据属于高斯分布呀,只是不知道高斯分布的参数,这个时候我们定义高斯分布,然后利用数据去学习高斯分布的参数得到我们最终的模型。再比如说我们定义一个分类器,比如SVM,然后强行让数据进行东变西变,进行各种高维映射,最后可以变成一个简单的分布,SVM可以很轻易的进行二分类分开,其实SVM已经放松了这种映射关系了,但是也是给了一个模型,这个模型就是核映射(什么径向基函数等等),说白了其实也好像是你事先知道让数据该怎么映射一样,只是核映射的参数可以学习罢了。

所有的这些方法都在直接或者间接的告诉数据你该怎么映射一样,只是不同的映射方法能力不一样。那么我们再来看看GAN,生成模型最后可以通过噪声生成一个完整的真实数据(比如人脸),说明生成模型已经掌握了从随机噪声到人脸数据的分布规律了,有了这个规律,想生成人脸还不容易。然而这个规律我们开始知道吗?显然不知道,如果让你说从随机噪声到人脸应该服从什么分布,你不可能知道。这是一层层映射之后组合起来的非常复杂的分布映射规律。然而GAN的机制可以学习到,也就是说GAN学习到了真实样本集的数据分布。

再拿原论文中的一张图来解释:
在这里插入图片描述
这张图表明的是GAN的生成网络如何一步步从均匀分布学习到正太分布的。原始数据x服从正太分布,这个过程你也没告诉生成网络说你得用正太分布来学习,但是生成网络学习到了。假设你改一下x的分布,不管什么分布,生成网络可能也能学到。这就是GAN可以自动学习真实数据的分布的强大之处。

还有人说GAN强大之处在于可以自动的定义潜在损失函数。 什么意思呢,这应该说的是判别网络可以自动学习到一个好的判别方法,其实就是等效的理解为可以学习到好的损失函数,来比较好或者不好的判别出来结果。虽然大的loss函数还是我们人为定义的,基本上对于多数GAN也都这么定义就可以了,但是判别网络潜在学习到的损失函数隐藏在网络之中,不同的问题这个函数就不一样,所以说可以自动学习这个潜在的损失函数。

这个感觉就是数学专业的人弄得,全是专业术语

Pytorch实现GAN

一、什么是 GAN?

在进入技术层面之前,为照顾新入门的开发者,先来介绍下什么是 GAN。

2014 年,Ian Goodfellow 和他在蒙特利尔大学的同事发表了一篇震撼学界的论文。没错,我说的就是《Generative Adversarial Nets》,这标志着生成对抗网络(GAN)的诞生,而这是通过对计算图和博弈论的创新性结合。他们的研究展示,给定充分的建模能力,两个博弈模型能够通过简单的反向传播(backpropagation)来协同训练。

这两个模型的角色定位十分鲜明。给定真实数据集 R,G 是生成器(generator),它的任务是生成能以假乱真的假数据;而 D 是判别器 (discriminator),它从真实数据集或者 G 那里获取数据, 然后做出判别真假的标记。Ian Goodfellow 的比喻是,G 就像一个赝品作坊,想要让做出来的东西尽可能接近真品,蒙混过关。而 D 就是文物鉴定专家,要能区分出真品和高仿(但在这个例子中,造假者 G 看不到原始数据,而只有 D 的鉴定结果——前者是在盲干)。

在这里插入图片描述
理想情况下,D 和 G 都会随着不断训练,做得越来越好——直到 G 基本上成为了一个“赝品制造大师”,而 D 因无法正确区分两种数据分布输给 G。

实践中,Ian Goodfellow 展示的这项技术在本质上是:G 能够对原始数据集进行一种无监督学习,找到以更低维度的方式(lower-dimensional manner)来表示数据的某种方法。而无监督学习之所以重要,就好像 Yann LeCun 的那句话:“无监督学习是蛋糕的糕体”。这句话中的蛋糕,指的是无数学者、开发者苦苦追寻的“真正的 AI”。

二、核心思想

判断器的任务是尽力将假的判断为假的,将真的判断为真的;生成器的任务是使生成的越真越好。两者交替迭代训练。

三、核心代码

real_label = Variable(torch.ones(num_img)).cuda()  # 定义真实的图片label为1
fake_label = Variable(torch.zeros(num_img)).cuda()  # 定义假的图片的label为0
 
# 1, D: 真的判断为真,假的判断为假
real_out = D(real_img)  # 将真实图片放入判别器中
d_loss_real = criterion(real_out, real_label)  # 真的判断为真
 
fake_img = G(z)  # 随机噪声放入生成网络中,生成一张假的图片
fake_out = D(fake_img)  # 判别器判断假的图片,
d_loss_fake = criterion(fake_out, fake_label)  # 得到假的图片的loss
 
d_loss = d_loss_real + d_loss_fake  # 损失包括判真损失和判假损失
# 然后反向传播
 
# 2, G:生成的越真越好
fake_img = G(z)  # 随机噪声输入到生成器中,得到一副假的图片
fake_out = D(fake_img)  # 经过判别器得到的结果
g_loss = criterion(fake_out, real_label)  # 得到的假的图片与真实的图片的label的loss
# 然后反向传播

四、用 PyTorch 训练 GAN

# coding=utf-8
import torch.autograd
import torch.nn as nn
from torch.autograd import Variable
from torchvision import transforms
from torchvision import datasets
from torchvision.utils import save_image
import os
 
# 创建文件夹
if not os.path.exists('./img'):
    os.mkdir('./img')
 
 
def to_img(x):
    out = 0.5 * (x + 1)
    out = out.clamp(0, 1)  # Clamp函数可以将随机变化的数值限制在一个给定的区间[min, max]内:
    out = out.view(-1, 1, 28, 28)  # view()函数作用是将一个多行的Tensor,拼接成一行
    return out
 
 
batch_size = 128
num_epoch = 100
z_dimension = 100
# 图像预处理
img_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))  # (x-mean) / std
])
 
# mnist dataset mnist数据集下载
mnist = datasets.MNIST(
    root='./data/', train=True, transform=img_transform, download=True
)
 
# data loader 数据载入
dataloader = torch.utils.data.DataLoader(
    dataset=mnist, batch_size=batch_size, shuffle=True
)
 
 
# 定义判别器  #####Discriminator######使用多层网络来作为判别器
# 将图片28x28展开成784,然后通过多层感知器,中间经过斜率设置为0.2的LeakyReLU激活函数,
# 最后接sigmoid激活函数得到一个0到1之间的概率进行二分类。
class discriminator(nn.Module):
    def __init__(self):
        super(discriminator, self).__init__()
        self.dis = nn.Sequential(
            nn.Linear(784, 256),  # 输入特征数为784,输出为256
            nn.LeakyReLU(0.2),  # 进行非线性映射
            nn.Linear(256, 256),  # 进行一个线性映射
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()  # 也是一个激活函数,二分类问题中,
            # sigmoid可以班实数映射到【0,1】,作为概率值,
            # 多分类用softmax函数
        )
 
    def forward(self, x):
        x = self.dis(x)
        return x
 
 
# ###### 定义生成器 Generator #####
# 输入一个100维的0~1之间的高斯分布,然后通过第一层线性变换将其映射到256维,
# 然后通过LeakyReLU激活函数,接着进行一个线性变换,再经过一个LeakyReLU激活函数,
# 然后经过线性变换将其变成784维,最后经过Tanh激活函数是希望生成的假的图片数据分布
# 能够在-1~1之间。
class generator(nn.Module):
    def __init__(self):
        super(generator, self).__init__()
        self.gen = nn.Sequential(
            nn.Linear(100, 256),  # 用线性变换将输入映射到256维
            nn.ReLU(True),  # relu激活
            nn.Linear(256, 256),  # 线性变换
            nn.ReLU(True),  # relu激活
            nn.Linear(256, 784),  # 线性变换
            nn.Tanh()  # Tanh激活使得生成数据分布在【-1,1】之间,因为输入的真实数据的经过transforms之后也是这个分布
        )
 
    def forward(self, x):
        x = self.gen(x)
        return x
 
 
# 创建对象
D = discriminator()
G = generator()
if torch.cuda.is_available():
    D = D.cuda()
    G = G.cuda()
 
 
# 首先需要定义loss的度量方式  (二分类的交叉熵)
# 其次定义 优化函数,优化函数的学习率为0.0003
criterion = nn.BCELoss()  # 是单目标二分类交叉熵函数
d_optimizer = torch.optim.Adam(D.parameters(), lr=0.0003)
g_optimizer = torch.optim.Adam(G.parameters(), lr=0.0003)
 
# ##########################进入训练##判别器的判断过程#####################
for epoch in range(num_epoch):  # 进行多个epoch的训练
    for i, (img, _) in enumerate(dataloader):
        num_img = img.size(0)
        # view()函数作用是将一个多行的Tensor,拼接成一行
        # 第一个参数是要拼接的tensor,第二个参数是-1
        # =============================训练判别器==================
        img = img.view(num_img, -1)  # 将图片展开为28*28=784
        real_img = Variable(img).cuda()  # 将tensor变成Variable放入计算图中
        real_label = Variable(torch.ones(num_img)).cuda()  # 定义真实的图片label为1
        fake_label = Variable(torch.zeros(num_img)).cuda()  # 定义假的图片的label为0
 
        # ########判别器训练train#####################
        # 分为两部分:1、真的图像判别为真;2、假的图像判别为假
        # 计算真实图片的损失
        real_out = D(real_img)  # 将真实图片放入判别器中
        d_loss_real = criterion(real_out, real_label)  # 得到真实图片的loss
        real_scores = real_out  # 得到真实图片的判别值,输出的值越接近1越好
        # 计算假的图片的损失
        z = Variable(torch.randn(num_img, z_dimension)).cuda()  # 随机生成一些噪声
        fake_img = G(z).detach()  # 随机噪声放入生成网络中,生成一张假的图片。 # 避免梯度传到G,因为G不用更新, detach分离
        fake_out = D(fake_img)  # 判别器判断假的图片,
        d_loss_fake = criterion(fake_out, fake_label)  # 得到假的图片的loss
        fake_scores = fake_out  # 得到假图片的判别值,对于判别器来说,假图片的损失越接近0越好
        # 损失函数和优化
        d_loss = d_loss_real + d_loss_fake  # 损失包括判真损失和判假损失
        d_optimizer.zero_grad()  # 在反向传播之前,先将梯度归0
        d_loss.backward()  # 将误差反向传播
        d_optimizer.step()  # 更新参数
 
        # ==================训练生成器============================
        # ###############################生成网络的训练###############################
        # 原理:目的是希望生成的假的图片被判别器判断为真的图片,
        # 在此过程中,将判别器固定,将假的图片传入判别器的结果与真实的label对应,
        # 反向传播更新的参数是生成网络里面的参数,
        # 这样可以通过更新生成网络里面的参数,来训练网络,使得生成的图片让判别器以为是真的
        # 这样就达到了对抗的目的
        # 计算假的图片的损失
        z = Variable(torch.randn(num_img, z_dimension)).cuda()  # 得到随机噪声
        fake_img = G(z)  # 随机噪声输入到生成器中,得到一副假的图片
        output = D(fake_img)  # 经过判别器得到的结果
        g_loss = criterion(output, real_label)  # 得到的假的图片与真实的图片的label的loss
        # bp and optimize
        g_optimizer.zero_grad()  # 梯度归0
        g_loss.backward()  # 进行反向传播
        g_optimizer.step()  # .step()一般用在反向传播后面,用于更新生成网络的参数
 
        # 打印中间的损失
        if (i + 1) % 100 == 0:
            print('Epoch[{}/{}],d_loss:{:.6f},g_loss:{:.6f} '
                  'D real: {:.6f},D fake: {:.6f}'.format(
                epoch, num_epoch, d_loss.data.item(), g_loss.data.item(),
                real_scores.data.mean(), fake_scores.data.mean()  # 打印的是真实图片的损失均值
            ))
        if epoch == 0:
            real_images = to_img(real_img.cpu().data)
            save_image(real_images, './img/real_images.png')
    fake_images = to_img(fake_img.cpu().data)
    save_image(fake_images, './img/fake_images-{}.png'.format(epoch + 1))
 
# 保存模型
torch.save(G.state_dict(), './generator.pth')
torch.save(D.state_dict(), './discriminator.pth')

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值