写在前面
代码实现很有助于对算法的理解,看到了一篇很不错的博文,并根据博文使用TensorFlow实现了简单的GAN。文章很不错,原文地址点这里,下面是对其进行的翻译整理。
序
生成对抗网络(Generative Adversarial Nets,GAN)是一种非常流行的神经网络。它首先由Ian Goodfellow在2014年NIPS大会上发表。从论文被引用的次数中可以看出,该论文燃起了大家对神经网络中对抗学习的兴趣。一时之间,涌现出许多不同的GAN变形:DCGAN,Sequence-GAN,LSTM-GAN等。在NIPS 2016,甚至将会有一整场专门的对抗学习研讨会。
该文代码可以从这里得到。
下面首先让我们回顾一下论文的要点,之后我们会用TensorFlow来实现GAN,使用数据集MNIST进行测试。
生成对抗网络
让我们举一个假币制造商和警察的例子。假币制造商和警察各自的目标是什么呢?
◎ 一个成功的假币制造商会想尽方法骗过警察,使警察分不清假币与真币。
◎ 一个合格的警察会尽力分辨出假币和真币。
现在就产生了冲突。这种情况可以认为是博弈论中的最大最小游戏。这个过程称为对抗过程。
GAN是对抗过程的一个特例,它的组成(警察和假币制造商)是神经网络。第一个网络试图生成数据,第二个网络试图分辨出真实数据和第一个网络生成的伪造数据。第二个网络会输出表示真实数据概率的[0,1]之间的量。
在GAN中,第一个网络称为生成器G(Z),第二个网络称为判别器D(X)。
$$\min_G \max_D {\mathbb E}_{x\sim p_{\rm data}} \log D(x) + {\mathbb E}_{z\sim p_z}[\log (1-D(G(z)))] $$
在平衡点,也就是最大最小游戏中的最优点,第一个网络会生成真实数据,第二个网络输出的概率会是0.5,因为第一个网络生成了真实数据。
我们不禁会想“为什么要训练GAN呢?”,这是因为数据$P_{data}$的概率分布可能非常复杂,很难推断。所以使用对抗网络从分布$P_{data}$中生成样本而不用处理讨厌的概率分布问题会十分不错。如果我们拥有了这样的对抗网络,在另外需要数据$P_{data}$样本的情况下,我们能够很轻松的利用该网络生成这样的样本。
GAN的实现
根据GAN的定义我们需要两个网络。可以是任意的网络,比如卷积网络或仅仅是两层的感知器网络。首先我们使用简单的两层感知器网络。我们使用TensorFlow来实现这样的一个例子。
# Discriminator Net
X = tf.placeholder(tf.float32, shape=[None, 784], name='X')
D_W1 = tf.Variable(xavier_init([784, 128]), name='D_W1')
D_b1 = tf.Variable(tf.zeros(shape=[128]), name='D_b1')
D_W2 = tf.Variable(xavier_init([128, 1]), name='D_W2')
D_b2 = tf.Variable(tf.zeros(shape=[1]), name='D_b2')
theta_D = [D_W1, D_W2, D_b1, D_b2]
# Generator Net
Z = tf.placeholder(tf.float32, shape=[None, 100], name='Z')
G_W1 = tf.Variable(xavier_init([100, 128]), name='G_W1')
G_b1 = tf.Variable(tf.zeros(shape=[128]), name='G_b1')
G_W2 = tf.Variable(xavier_init([128, 784]), name='G_W2')
G_b2 = tf.Variable(tf.zeros(shape=[784]), name='G_b2')
theta_G = [G_W1, G_W2, G_b1, G_b2]
def generator(z):
G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1)
G_log_prob = tf.matmul(G_h1, G_W2) + G_b2
G_prob = tf.nn.sigmoid(G_log_prob)
return G_prob
def discriminator(x):
D_h1 = tf.nn.relu(tf.matmul(x, D_W1) + D_b1)
D_logit = tf.matmul(D_h1, D_W2) + D_b2
D_prob = tf.nn.sigmoid(D_logit)
return D_prob, D_logit
以上代码中,generator(z)接受一个100维的矢量,输出一个784维的矢量(MNIST图片大小(28x28))。z是G(Z)的一个先验。在某种程度上,它学习了从先验空间到$P_{data}$的一个映射。
discriminator(x)接受MNIST图片作为输入,返回一个代表真实图片概率的张量。
现在让我们解释一下GAN的对抗过程。下面是论文中的训练算法:
G_sample = generator(Z)
D_real, D_logit_real = discriminator(X)
D_fake, D_logit_fake = discriminator(G_sample)
D_loss = -tf.reduce_mean(tf.log(D_real) + tf.log(1. - D_fake))
G_loss = -tf.reduce_mean(tf.log(D_fake))
上面我们对损失函数取负是因为它们需要最大化,而TensorFlow的优化器只能进行最小化。
另外根据论文的建议,最好最大化tf.reduce_mean(tf.log(D_fake)),而不是最小化tf.reduce_mean(1-tf.log(D_fake))。
接下来我们对生成网络和判别网络逐个进行对抗训练,损失函数利用上面提到的形式。
# Only update D(X)'s parameters, so var_list = theta_D
D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)
# Only update G(X)'s parameters, so var_list = theta_G
G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G)
def sample_Z(m, n):
'''Uniform prior for G(Z)'''
return np.random.uniform(-1., 1., size=[m, n])
for it in range(1000000):
X_mb, _ = mnist.train.next_batch(mb_size)
_, D_loss_curr = sess.run([D_solver, D_loss], feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})
_, G_loss_curr = sess.run([G_solver, G_loss], feed_dict={Z: sample_Z(mb_size, Z_dim)})
这样我们就完成了!我们通过对G(Z)时而进行采样可以观察训练过程。如下图所示:
刚开始我们使用随机噪声作为输入,随着训练的进行,G(Z)开始越来越趋近$P_{data}$。从G(Z)生成的样本原来越像MNIST数据可以证实。
其他可选择的损失函数
我们可以使用不同的方法来表示D_loss和G_loss。让我们跟随直觉,这是受Brandon Amos关于图像修复的博文启示。
让我们想一想,discriminator(x)试图将所有的输出变为1,也就是我们想最大化真实数据的概率。而discriminator(G_sample)试图将所有的输出变为0,即D(G(Z))希望最小化伪造数据的概率。
那么generator(z)呢?它当然想最大化伪造数据的概率!它与D(G(Z))正相反!
因此,代码可以写成:
# Alternative losses:
D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(D_logit_real, tf.ones_like(D_logit_real)))
D_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(D_logit_fake, tf.zeros_like(D_logit_fake)))
D_loss = D_loss_real + D_loss_fake
G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(D_logit_fake, tf.ones_like(D_logit_fake)))
这里我们使用的是Logistic Loss。改变损失函数不会影响到GAN的训练,因为这只是思考和建模这个问题的不同方式而已。
小结
该篇文章首先介绍了Goodfellow等人在NIPS 2014提出的生成对抗网络GAN。我们研究了对抗过程的形成和它背后的直觉。
接下来,我们用两层神经网络实现了生成器和辨别器网络。然后我们遵循了Goodfellow,等人2014提出的算法来训练GAN。
最后,我们考虑了不同的方法来定义GAN损失函数。在替代损失函数中,我们直观地考虑了这两个网络,并利用逻辑损失来建立替代损失函数。