说来惭愧,接触机器学习和深度学习半年了,仍未了解目前非常火热的模型——GAN。
偶然在油管上发现李宏毅老师讲解GAN的课程,讲的真的很好,所以在此记录一下。
0、何时会用到GAN
GAN常被用于Structured Learning(结构化学习),通常机器学习算法是输入一个向量或矩阵,输出一个标量或向量用于回归或分类,而结构化学习输出形式更加复杂,可能是一段文字或一张图片等。当然了,表现在神经网络中的输出依然会是数值的形式,但各数值之间是有结构的,比如输出向量还原成图片是一张人脸的话就要求表示眼睛的区域的像素值比较接近。
上图是结构化学习的一些示例。
相比普通的学习,结构化学习有什么特别之处呢?从目标角度而言,结构化学习要求得到的输出形式更加严格,那么它的困难之处在哪里呢?
在使用神经网络进行分类的时候,比如手写数字识别的时候,我们的类别是固定的,且各个类别都有一些样本,这就使得我们的学习变得很方便,而结构化学习的输出并不是某一个类别(或一个表示各类别概率的向量),而是一张图片、一段文字等,这些东西彼此差别很大,若按分类的思想来说,每个类别可能只会有一个样本,这就使得训练几乎是不可能的。这就是结构化学习的困难所在。
既然结构化学习是困难的,那么我们为什么不直接使用传统的学习方法呢?结构化学习有什么优势呢?
上图简要说明了结构化学习的优势所在,通俗来说,就是可以顾全大局,我们知道,深度学习的优势在于自动特征提取,且可以从低层的特征(曲线、轮廓等)组合成高层的特征(眼睛、鼻子等),结构化学习在此基础上更进一步,它可以学习到各个特征之间的联系,比如鼻子在眼睛以下,嘴巴以上这样的讯息,或者可以从整体角度理解“这个婆娘不是人,九天玄女下凡尘”这样的句子是褒是贬,在此基础上生成(创造)一些以假乱真的结构化输出(图片、诗歌等)。
1、GAN的基本思想
GAN的全称是Generative Adversarial Network,也就是对抗生成网络。我们首先看一下为什么叫“生成”。
如上图所示,GAN包含一个Generator(生成器),这个生成器是一个神经网络,给定输入向量,用于生成一个结构化对象(如图片、句子、语音等)。这里我们以生成图片为例。
在上图例子中,我们看到,输入向量每一个维度都表示了输出图片的某个特征,比如第一维表示头发长短,倒数第二维表示头发颜色是蓝色的程度,最后一维表示嘴张开的大小等。
此外,如上图所示,GAN还包含一个Discriminator(判别器),这个判别器也是一个生成网络,输入一张图片(图片的向量形式),输出一个score,用来表示此图片是否符合某一标准,这里我们的标准是一张图片是否是人手绘出的二次元图片,若是,则给予较高score,否则给予较低分数。
可以看到,前两张图片是清晰的手绘二次元图片,而后两张则不是,因此我们训练判别器使得判别器给予前两张图片高分,后两张图片低分。
现在我们知道Generative说的就是Generator了,那么Adversarial是指什么呢?实际上说的是Generator和Discriminator之间的对抗。下图是一个生动形象的比喻。
李宏毅老师使用了物竞天择的进化过程来说明Generator和Discriminator之间通过对抗共同进化的过程。生成器一开始就像是色彩鲜艳的蝴蝶,判别器一开始就像是嘟嘟鸟(whatever……),一开始嘟嘟鸟很容易发现蝴蝶并进行捕食,因为它的颜色和森林中常见的棕色和绿色不同,一眼就能发现,就这样,蝴蝶进化到了棕色,远看和树干融为一体,嘟嘟鸟随之进化到通过看棕色区域有没有特定纹理来判断蝴蝶位置,然后蝴蝶再次进化,翅膀上有了近乎枯叶的纹理……至此枯叶蝶就诞生了。
这里的蝴蝶其实就是生成器,嘟嘟鸟就是判别器,生成器的目的就是生成图片骗过判别器,而判别器的目的就是为了识别出判别器生成出来的这些“进化不完全”的图片。
回到之前的例子,对抗进化过程如下:随机初始化生成器权重,给定一系列输入,输出一系列图片交给判别器判别;判别器通过对比这些图片和手头真实的手绘图片来给予真实图片高分,生成器生成的图片低分,然后训练出一个判别器;接下来生成器开始迎合判别器的口味,生成器希望改变权重使得输出的图片在当前判别器中取得较高分数,于是生成器进一步进化;而此时判别器依然通过对比生成的图片和真实的手绘图片来给予真实图片高分,生成器生成的图片低分,进一步进化……依次进行下去,最终生成器会产生非常逼真的手绘二次元图片,而且是我们从未见过的,很神奇!
进化的原理是什么呢?一开始生成器生成的图片很差,判别器将其与真实图片对比可能会得出“真实图片有两个眼睛,生成图片没有眼睛”的结论,并以此作为判别标准给图片打分,而此时生成器为了迎合判别器将会生成具有眼睛的图片使得自己取得高分,但判别器此时变得更加刁钻,它为了给生成的图片打低分只能继续“挑刺”,比如得出“真实图片有嘴巴,生成图片没有眼嘴巴”的结论,并以此作为判别标准给图片打分,于是在此过程中生成器就逐步学到了真实图片应具有的特征(比如有眼睛、嘴巴等)。
2、为什么不能仅依靠生成器进行学习
上面我们讲述了GAN的基本思想,确实很巧妙,但是我们为什么一定需要生成器和判别器两个神经网络结合起来进行学习呢?只用其中一个会出现什么问题呢?下面我们假设只使用生成器,看看会出现什么问题。
回顾一下我们生成器的作用,就是给定一个输入向量,输出一张图片。现在假如我有一系列手写数字的图片,想要喂给生成器来训练,如何下手呢?我们当然可以随机给定一些向量,让它们对应不同的图片,但是这样效果很差,因为向量的各个维度未必反映了图片的某些特征。
不要紧,我们可以使用Auto-encoder来解决这个问题。
Auto-encoder的目的很简单,就是得到输入向量的一个压缩表示或者说降维表示。降维后的向量(code)作为输入喂给Decoder后应该能大致还原出原输入,在上图的例子中就是还原出手写的数字9,这样就说明我们的低维表示没有损失太多重要信息,以此为目标进行训练,得到的code可以作为原输入的一个编码。
因此我们只需要把手写数字的图片按像素值展开成向量作为Auto-encoder的输入即可得到不同图片对应的低维向量表示。
不难发现,我们的decoder实际上就起到了生成器的作用,即输入一个向量,得到(还原)一张图片。
然而问题在于,这样训练出来的decoder,喂给它一个随机的向量,它输出的图片一定是手写数字吗?想想都觉得做到这一点太过困难了吧。
上面就是一个真实的例子,若训练一个Decoder使得输入对应手写数字0的图片,输入对应手写数字1的图片,则给定随机二维向量输入会得到上图所示的效果,可以看到很多位置的输出是模糊的,甚至无法分辨是否是手写数字。
当然,这个问题依然是有解决方案的,那就是Variational Auto-encoder(VAE)。
对于VAE我并不是十分了解,但根据李宏毅老师的讲解,这里的表示Encoder得到的code,表示code各个维度上的方差,表示从一个标准正态分布中抽取的随机值,这里我们用的指数和的内积表示噪音,并在原编码基础上加上这个噪音得到一个新的编码。接下来将这个新的编码输入Decoder中进行解码,并争取还原回原图像。
这样做的思路其实不难理解,某个维度上的方差越大,其噪音也趋向于越大,因此这里的内积是合理的。加入噪音进行训练是为了使得我们的Decoder对于不同的输入code更加稳健,这实际上和我们希望的生成图像和真实的手写数字相近的目标是一致的。
那么这个方法有什么问题呢?问题就在于这个方法得到的生成图片是“像素级地接近手写数字图片的”,而不是“整体看来接近手写数字图片的”。
上图给出了这样的例子,上面两张图片在训练好的Generator看来可能是很好的一张手写数字图片(和目标图片很相近),但很明显,这种像素点的多余或缺失是极不自然的,相比之下,下面两张图虽然比起目标图片有多个像素点不一致,但整体看起来就是手写的数字2。
也就是说,各个像素点之间的关联没有被很好的学习到。其实这在我们看来是一个很简单的问题,当我们把一个像素点涂黑的时候,是不应该出现其周围像素点均为白色的情况的(即不应该出现孤立像素点),但只凭Generator是无法很好的学习到这一点的。当然通过增加网络的深度我们可能会部分改善这样的情况,但这会增加计算代价。
上图中绿色区域是目标图像涂黑的像素点,通过VAE学习得到的Generator生成的图片中被涂黑的是蓝色的区域,可以看到,VAE可以很好地学习到在较大时,应该把较大和较小的点涂黑,但它没有学到的事情是要不要把中间的点也涂黑,这也就是只使用Generator进行学习的劣势——没有全局观。
3、为什么不能仅依靠判别器进行学习
说完了为什么只凭借生成器进行学习是不好的,接下来我们要看看只使用判别器会出现什么问题。
首先,只使用判别器并不会出现只使用生成器时出现的问题,它是可以学习到各个像素点之间的联系的。之前出现过的独立像素点的问题,在这里只需要使用一个如图所示的卷积层就能解决问题了。
可是判别器是给定一张图片后判断它好不好的,怎么生成图片呢?难不成要枚举所有可能的图片,从中挑选出分数高的吗?
实际上,就算我们真的可以找到某种方式穷举所有图片并找到得分高的,这个方法依然存在很大问题——判别器如何训练呢?
你可能会说,这不就是一个普通的神经网络吗,直接梯度下降就完事了呗,关键是我们训练集里的数据都是真实的手写数字图片,即都是正例,没有反例,这样训练出来的网络只能判别一张图片是否和训练集中的图片相似而已,并不能真正学到正例和负例的区别。好说,我们自己随机生成一些负例就行了呗。可是问题就在于,这些负例真的是好的吗?
上图展示了负例不够好时判别效果也会很差的例子,可以看到,最终学习好的判别器给一张很模糊的图片打了0.9的高分,这说明这个判别器并没有领会到手绘二次元图片的精髓。
一种可行的算法如上图所示。开始时先随机生成一些负例,学习得到一个判别器,然后穷举图片,找到这个判别器认为表现很好的图片当作下一轮的负例,依次进行下去。
这个算法的执行过程可以看作一个不断修正的过程。如图所示,一开始,我们只知道绿色区域的点分数是比较高的(因为这是我们手头的真实图片对应的数据点),但是我们不知道在这些数据点之外的数据点的分数如何,所以我们随机找一些蓝色的点作为负例,并给予它们很低的分数,这样一来,我们的判别器就学到了这部分蓝色的点分数很低这个事实,此时判别器打分很高的点就不会再包含这部分蓝色的点了,也就是说,此时判别器认为分数很高的样本(和真实图片不同的样本)就是我们还没学好的样本点(因为我们理应给这些样本点打低分的),我们把这部分样本点作为负例对判别器进行修正,如此迭代下去,就会得到一个效果比较好的判别器,可以判别一张图片到底是不是足够真实。
综上所述,生成器和判别器各有优劣,两者结合方能互补。
4、生成器和判别器的结合
上面我们说到,判别器的问题之一就是不便于生成负例图片,穷举显然是不可行的,因此我们用生成器来做这件事。
如图所示,我们固定上一轮学习得到的判别器的权重,然后把生成器的输出作为这个判别器的输入(实际上就是把生成器和判别器首尾相接连在一起),并通过调整生成器的权重使得生成器的输出在判别器中获得高分,这样一来此输出代表的图片就可以作为下一轮判别器训练的负例使用。
总结来说,算法流程如图。
生成器和判别器结合的优点(也是GAN的优点)如下:
网友评论