本文翻译自这个博客,已获得作者授权,翻译的不好还请指教。
生成对抗网络(Generative Adversarial Nets)是一种非常流行的神经网络。它首先由Ian Goodfellow在2014年NIPS大会上发表。它点燃了神经网络中对抗学习的兴趣,这可以从论文被引用的次数中证明。一时之间,冒出来许多不同的GAN:DCGAN,Sequence-GAN,LSTM-GAN等。在NIPS 2016,甚至将会有一整场的对抗学习讨论会。
现在代码可以从https://github.com/wiseodd/generative-models得到。
首先让我们回顾一下论文的要点。之后我们会用TensorFlow来实现GAN,数据集是MNIST。
Generative Adversarial Net
让我们举一个假币制造商和警察的例子。假币制造商和警察各自的目标是什么呢?
- 一个成功的假币制造商会想尽方法骗过警察,使警察分不清假币与真币。
- 一个合格的警察会尽力分辨出假币和真币。
现在就产生了冲突。这种情况可以认为是博弈论中的最大最小游戏。这个过程称为对抗过程。
GAN是对抗过程的一个特例,它的组成(警察和假币制造商)是神经网络。第一个网络试图生成数据,第二个网络试图分辨出真实数据和第一个网络生成的伪造数据。第二个网络会输出表示真实数据概率的张量[0,1]。
在GAN中,第一个网络称为生成器G(Z),第二个网络称为判别器D(X)。
在平衡点,也就是最大最小游戏中的最优点,第一个网络会生成真实数据,第二个网络输出的概率会是0.5,因为第一个网络生成了真实数据。
我们不禁会想“为什么要训练GAN呢?”,这是因为数据分布P_{data}可能非常复杂,很难推断。所以使用对抗网络可以从布P_{data}中生成样本而不用处理讨厌的概率分布问题。
GAN实现
根据GAN的定义我们需要两个网络。可以是任意的网络,比如卷积网络或仅仅是两层的感知器网络。首先我们使用简单的两层感知器网络。
# 判别器
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]
# 生成器
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))。
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))。
接下来我们来训练网络。
# 只更新 D(X)的参数, 所以 var_list = theta_D
D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D)
# 只更新 G(X)的参数, 所以 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)开始越来越趋近P_{data}。
替代的损失函数
我们可以使用不同的方法来表示D_loss和G_loss。
让我们跟随自己的直觉。这个方法根据Brandon Amos’ blog.
让我们想一想,discriminator(x)试图将所有的输出变为1,也就是我们想最大化真实数据的概率。而discriminator(G_sample)试图将所有的输出变为0,即D(G(Z))希望最小化伪造数据的概率。
那么generator(z)呢?它当然想最大化伪造数据的概率!它与D(G(Z))正相反!
因此,代码可以写成:
# 另外的损失:
# -------------------
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的训练。