前 言
这是关于使用tensorflow来实现Goodfellow的生成对抗网络论文的教程。对抗网络是一个可以使用大约80行的python代码就可以实现的一个有趣的小深度学习练习,这将使你进入深度学习的一个活跃领域:生成式模型。
对抗网络论文地址:https://arxiv.org/abs/1406.2661
Github上的源码地址:https://github.com/ericjang/genadv_tutorial/blob/master/genadv1.ipynb
章节目录
情景:假币
背景:判别模型vs生成模型
生成对抗网络
实现
流形对齐
预处理判别模型
其他的棘手问题建议
结果
附录
情景:假币
为了更好地解释这篇论文的动机,这里提供一个假设场景: Danielle是一个银行的出纳员,她的工作职责之一就是辨别真币与假币。George是一个制造假币的骗子,因为免费的钱相当激进。
让我们简化一下:假定货币的唯一显著特征就是印在每个钞票上的唯一编号X。这些编码是一个概率分布的随机抽样,其中密度函数pdata只有国家财政部知道(这意味着Danielle与George都不知道)。方便起见,这个教程使用pdata同时指代这个分布与它的概率密度函数(尽管从本质上说概率分布与它的密度函数并不相同)。
George的目标是从pdata生成样例x′,所以他制造的假币与真币难以区分。你可能会问:George事先并不知道pdata,他怎么能从pdata采样?
我们可以在不知道真实的潜在生成过程的情况下制造出计算不可区分的样例[1]。这个潜在的生成过程是财政部所使用的生成样例X的方法-也许是从pdata
抽样的一些有效算法,这些算法依赖于概率密度分布的解析式。
我们可以将这种算法看做“自然(函数)基”,财政部将使用这种直接的方法来印制我们假设的钞票。然而,一个(连续)函数可以用一系列不同的基函数来表征;George 可以使用“神经网络基”,“傅里叶基”或者其它的能用来构建近似器的基来表示相同的抽样算法。从局外人的角度来看,这些抽样器是计算上不可区分的,然而 George的模型并没有将pdata的自然抽样基或者解析式泄露给他。
背景:判别模型vs生成模型
我们使用X和Y代表“观测”和“目标”随机变量。X和Y的联合分布为P(X,Y),我们可以将其看做两变量(可能相关)的概率密度函数。
一个判别式模型可以用来评估条件概率P(Y|X)。例如,给定一个代表像素点的向量x,那么Y=6的概率是多少?(6代表是虎斑猫的类别标签)。 MNIST LeNet, AlexNet和其他的分类器都是判别式模型的实例。
另外一方面,一个生成式模型可以用来估计联合分布P(X,Y)。这意味着我们可以选取(X,Y)值对,然后使用舍取抽样法来从P(X,Y)来获得样例x,y。使用正确的生成模型的另外一种方式,我们可以将一些分布在[0,1]上的随机值转化为一个兔子图。这会很有趣。
当然,生成模型比判别模型更难构建,这两者都是统计学与机器学习研究的热点领域。
生成对抗网络
条件随机场(Conditional Random Field,简称CRF)是一种判别式无向图模型。生成式模型是直接对联合分布进行建模,而判别式模型则是对条件分布进行建模。前面介绍的隐马尔可夫模型和马尔可夫随机场都是生成式模型,而条件随机场是判别式模型。
Goodfellow的论文提出了一个优雅的方式来将神经网络训练成一个可以表示任何(连续)概率密度函数的生成模型。我们构建两个神经网络,分别是D(Danielle)和G(George),然后使用它们来玩一个对抗式的猫捉老鼠的游戏:G是一个生成器,它尝试着从pdata生成伪样例;而D是一个决策器,它试着不会被骗。我们同时训练它们,所以它们将在相互抗争中互相得到提高。当收敛时,我们希望G能够学会完全从pdata抽样,此时D(x)=0.5(对于真假二分类来讲,这个概率等于瞎猜)。
对抗网络已经成功地用来凭空合成下列类型的图片:
- 猫
-
教堂
在这个教程里,我们不会做任何很神奇的东西,但是希望你将会对对抗网络有一个更基本的了解。
实现
我们将训练一个神经网络用来从简单的一维正态分布N(−1,1)中抽样
这里D,G都是小的3层感知机,每层总共有稀薄的11个隐含单元。G的输入是一个噪音分布z∼uniform(0,1)中的单个样例。我们想使用G来将点z1,z2,...zM映射为x1,x2,...xM,这样映射的点xi=G(zi)在pdata(X)密集的地方会密集聚集。因此,在G中输入z将生成伪数据x′。
同时,判别器D,以x为输入,然后输出该输入属于pdata的可能性。令D1和D2为D的副本(它们共享参数,那么D1(x)=D2(x))。D1的输入是从合法的数据分布x∼pdata中得到的单个样例,所以当优化判别器时我们想使D1(x)最大化。D2以x′(G生成的伪数据)为输入,所以当优化D时,我们想使D2(x)最小化。D的损失函数为: log(D1(x))+log(1−D2(G(z)))
里是Python代码:
batch = tf.Variable(0)
obj_d = tf.reduce_mean(tf.log(D1)+tf.log(1-D2))
opt_d = tf.train.GradientDescentOptimizer(0.01)
.minimize(1-obj_d,global_step=batch,var_list=theta_d)
我们之所以要不厌其烦地指定D的两个副本D1和D2,是因为在tensorflow中,我们需要D的一个副本以x为输入,而另外一个副本以G(z)为输入;计算图的相同部分不能被重用于不同的输入。
当优化G时,我们想使D2(X′)最大化(成功骗过D)。G的损失函数为: log(D2(G(z)))
batch=tf.Variable(0)
obj_g=tf.reduce_mean(tf.log(D2))
opt_g=tf.train.GradientDescentOptimizer(0.01)
.minimize(1-obj_g,global_step=batch,var_list=theta_g)
在优化时我们不是仅在某一刻输入一个值对(x,z),而是同时计算M个不同的值对(x,z)的损失梯度,然后用其平均值来更新梯度。从一个小批量样本中估计的随机梯度与整个训练样本的真实梯度非常接近。
训练的循环过程是非常简单的:
# Algorithm 1, GoodFellow et al. 2014for i in range(TRAIN_ITERS):
x= np.random.normal(mu,sigma,M) # sample minibatch from p_data
z= np.random.random(M) # sample minibatch from noise prior
sess.run(opt_d, {x_node: x, z_node: z}) # update discriminator D
z= np.random.random(M) # sample noise prior
sess.run(opt_g, {z_node: z}) # update generator G
流形对齐
简单地用上面的方法并不能得到好结果,因为每次迭代中我们是独立地从pdata和uniform(0,1)中抽样。这并不能使得Z范围中的邻近点能够映射到X范围中的邻近点;在某一小批量训练中,我们可能在训练G中发生下面的映射:0.501→−1.1,0.502→0.01 和0.503→−1.11。映射线相互交叉很多,这将使转化非常不平稳。更糟糕的是,接下来的小批量训练中,可能发生不同的映射:0.5015→1.1,0.5025→−1.1 和0.504→1.01。这表明G进行了一个与前面的小批量训练中完全不同的映射,因此优化器不会得到收敛。
为了解决这个问题,我们想最小化从Z到X的映射线的总长,因为这将使转换尽可能的平顺,而且更加容易学习。 另外一种说法是中将Z转化到X的“向量丛”在小批量训练中要相互关联。
首先,我们将Z的区域拉伸到与X区域的大小相同。以−1为中心点的正态分布其主要概率分布在[−5,5]范围内,所以我们应该从uniform[−5,5]来抽样Z。这样处理后G模型就不需要学习如何将[0,1]区域拉伸10倍。G模型需要学习的越少,越好。接下来,我们将通过由低到高排序的方式使每个小批量中的Z与X对齐。
这里我们不是采用 np.random.random.sort()的方法来抽样Z,而是采用分层抽样的方式-我们在抽样范围内产生M个等距点,然后随机扰动它们。这样处理得到的样本不仅保证其大小顺序,而且可以增加在整个训练空间的代表性。我们接着匹配之前的分层,即排序的Z样本对其排序的X样本。
当然,对于高维问题,由于在二维或者更高维空间里面对点排序并无意义,所以对其输入空间Z与目标空间X并不容易。然而,最小化Z与X流形之间的转化距离仍然有意义[2]。
修改的算法如下:
for i in range(TRAIN_ITERS):
x= np.random.normal(mu,sigma,M).sort()
z= np.linspace(-5.,5.,M)+np.random.random(M)*.01 # stratified
sess.run(opt_d, {x_node: x, z_node: z})
z= np.linspace(-5.,5.,M)+np.random.random(M)*.01
sess.run(opt_g, {z_node: z})
这是使这个例子有效的很关键一步:当使用随机噪音作为输入时,未能正确地对齐转化映射线将会产生一系列其它问题,如过大的梯度很早地关闭ReLU神经元,目标函数停滞,或者性能不能随着批量大小缩放。
预处理判别模型
在原始的算法中,GAN是每次通过梯度下降训练D模型k步,然后训练G一步。但是这里发现在训练对抗网络之前,先对D预训练很多步更有用,这里使用二次代价函数对D进行预训练使其适应pdata。这个代价函数相比对数似然代价函数更容易优化(后者还要处理来自G的生成样本)。很显然pdata就是其自身分布的最优可能性决定边界。
这里是初始的决定边界:
预训练之后:
已经非常接近了,窃喜!
其他的棘手问题建议
模型过大容易导致过拟合,但是在这个例子中,网络过大在极小极大目标下甚至不会收敛-神经元在很大的梯度下很快达到饱和。从浅层的小网络始,除非你觉得有必要再去增加额外的神经元或者隐含层。
刚开始我使用的是ReLU神经元,但是这种神经元一直处于饱和状态(也许由于流形对齐问题)。Tanh激活函数好像更有效。
我必须要调整学习速率才能得到很好的结果。
结果
下面是训练之前的pdata,预训练后的D的决定边界以及生成分布pg:
这是代价函数在训练迭代过程中的变化曲线:
训练之后,pg接近pdata,判别器也基对所有X一视同仁(D=0.5):
这事就完成了训练过程。G已经学会如何从pdata中近似抽样,以至于D已经无法伪数据中分离出真数据。
附言
这里是一个关于计算不可分性的更生动例子:假设我们在训练一个超级大的神经网络来从猫脸分布中抽样。真实猫脸的隐含(生成)数据分布包含:1)一只正在出生的猫,2)某人最终拍下了这个猫的照片。显然,我们的神经网络并不是要学习这个特殊的生成过程,因为这个过程并没有涉及真实的猫。然而,如果我们的网络能够产生无法与真实的猫图片相区分的图片(在多项式时间计算资源内)那么从某种意义上说这些照片与正常的猫照片一样合法。在图灵测试,密码学与假劳力士的背景下,这值得深思。
可以从过拟合的角度看待过量的映射线交叉,学习到的预测或者判别函数已经被样本数据以一种“矛盾”的方式扭曲了(例如,一张猫的照片被分类为狗)。正则化方法可以间接地防止过多的“映射交叉”,但是没有显式地使用排序算法来确保学习到的从Z空间到X空间的映射转化是连续或者对齐的。这种排序机制也许对于提升训练速度非常有效。。。