今回は、Stanford University/CS231n/Assignment3/Generative Adversarial Networks(GANs/敵対的生成ネットワーク)テンサーフロー編をやる。
Generative Adversarial Networks (GANs)¶
What is a GAN?¶
2014年、Goodfellow et alは、Generative Adversarial Networks(GANs/敵対的生成ネットワー)と呼ばれる生成モデルを訓練するための手法をプレゼンした。GANで、2種類のニューラルネットワークを構築する。最初のネットワークは、識別器(discriminator)と呼ばれる従来型の分類ネットワークだ。この識別器に画像を入力して、リアル(訓練セットに属する)もしくはフェイク(訓練セットに存在しない)であるかを分類させて訓練する。生成器(generator)と呼ばれるもう一方のネットワークは、入力として不規則ノイズを受け、それをニューラルネットを用いて変換して画像を生成する。生成器の目的は、それが生成した画像がリアルであると識別器を欺くことである。
生成器($G$)が識別器($D$)を欺こうとし、識別器は正確に本物と偽物を分類しようとするこのやりとりを以下のようなミニマックスゲームとして考えることができる。
$$\underset{G}{\text{minimize}}\; \underset{D}{\text{maximize}}\; \mathbb{E}_{x \sim p_\text{data}}\left[\log D(x)\right] + \mathbb{E}_{z \sim p(z)}\left[\log \left(1-D(G(z))\right)\right]$$ $x \sim p_\text{data}$が入力データのサンプル、$z \sim p(z)$が不規則ノイズサンプル、$G(z)$がニューラルネットワーク生成器$G$を使って生成された画像、$D$が識別器の出力で入力がリアルである確率を特定する。Goodfellow等は、このミニマックスゲームを解析し、それがいかにして、訓練データ分布と$G$の生成サンプル間のJensen-Shannon divergence(ジェンセン・シャノン・ダイバージェンス)を極小化することと関連しているのかを明らかにしている。
このミニマックスゲームを最適化するために、$G$の目的への勾配降下ステップと$D$の目的への勾配上昇ステップを繰り返す。
- 識別器が正しい選択をする確率を最小化するよう生成器($G$)を更新する。
- 識別器が正しい選択をする確率を最大化するよう識別器($D$)を更新する。
こういった更新は分析には有効である一方で、実際には上手く機能しない。その代わりとして、生成器を更新する際に別の目的を使用して、識別機が誤った選択をする確率を最大化する。この僅かな修正が、識別機の信頼度が高い時に生成器勾配が消失する問題を軽減するのに役立つ。これは、ほとんどのGAN研究論文で使われている標準的なアップデートで、グッドフェロー等による原論文の中でも使われていた。
この宿題では、以下のアプデを交互に行う。
- 識別器が生成画像に対して不正確な選択をする確率を最大化するように生成器($G$)をアプデする。$$\underset{G}{\text{maximize}}\; \mathbb{E}_{z \sim p(z)}\left[\log D(G(z))\right]$$
- 識別器が真・偽画像に対して正しい選択をする確率を最大化するように識別器($D$)をアプデする。$$\underset{D}{\text{maximize}}\; \mathbb{E}_{x \sim p_\text{data}}\left[\log D(x)\right] + \mathbb{E}_{z \sim p(z)}\left[\log \left(1-D(G(z))\right)\right]$$
What else is there?¶
2014年以降、GANsは多くの大規模研修会や何百もの新論文により、大人気の研究分野へと急速に発展している。生成モデルへの他のアプローチと比べ、それらは頻繁に最上級のサンプルを作り出すが、訓練するのが最も困難で細心の注意が必要なモデルになっている(モデルを機能させるのに役立つ17のハック集を含むこのgithub repoを参照のこと)。GAN訓練の安定性と堅牢性を向上することはオープンな研究課題になっており、毎日のように新たな研究論文が発表されている。GANの最新のチュートリアルに関してはここを参照。目的関数(objective function)をWasserstein distanc(ワッサースタイン距離)に変えてモデルアーキテクチャを越えてはるかに安定した結果をもたらす、いくつかのもっと最近の興味深い研究も存在する(WGAN, WGAN-GP)。
GANだけが生成モデルを訓練する方法ではない。生成モデリングへの他のアプローチに関しては、深層学習本の深層生成モデルの章を参照。生成モデルとしてニューラルネットワークを訓練するもう一つの人気の方法が変分オートエンコーダだ(Variational Autoencoders(VAE)はこことここで共発見された)。変分自己符号化器(VAE)は、深層生成モデルを訓練するために、ニューラルネットワークをvariational inference(変分推論)と組み合わせている。このようなモデルは、はるかに安定していて訓練しやすい傾向があるが、今のところはGANと同等の綺麗なサンプルは生成していない。
予想される生成画像のいくつかの見本(若干の違いはあるかもしれない)
Setup¶
from __future__ import print_function, division
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.rcParams['figure.figsize'] = 18, 12 # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
# A bunch of utility functions
def show_images(images):
images = np.reshape(images, [images.shape[0], -1]) # images reshape to (batch_size, D)
sqrtn = int(np.ceil(np.sqrt(images.shape[0])))
sqrtimg = int(np.ceil(np.sqrt(images.shape[1])))
fig = plt.figure(figsize=(sqrtn*2, sqrtn*2))
gs = gridspec.GridSpec(sqrtn, sqrtn)
gs.update(wspace=0.05, hspace=0.05)
for i, img in enumerate(images):
ax = plt.subplot(gs[i])
plt.axis('off')
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_aspect('equal')
plt.imshow(img.reshape([sqrtimg,sqrtimg]))
return
def preprocess_img(x):
return 2 * x - 1.0
def deprocess_img(x):
return (x + 1.0) / 2.0
def rel_error(x,y):
return np.max(np.abs(x - y) / (np.maximum(1e-8, np.abs(x) + np.abs(y))))
def count_params():
"""Count the number of parameters in the current TensorFlow graph """
param_count = np.sum([np.prod(x.get_shape().as_list()) for x in tf.global_variables()])
return param_count
def get_session():
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
session = tf.Session(config=config)
return session
answers = np.load('gan-checks-tf.npz')
%matplotlib inline
Dataset¶
GANでハイパーパラメータを取り扱うのは非常に難しく、多くの訓練エポックも必要となる。今回のエクササイズをGPU無しで取っ付きやすくするために、6万訓練画像と1万テスト画像を持つMNISTデータセットを使って作業する。各画像は黒い背景に白い(0から9の)数字が中心にある画像を含んでいる。これは、畳み込みニューラルネットワークを訓練するのに使われる最初のデータセットの一つで非常に簡単であるため、標準的なCNNモデルなら余裕で正確度99%を超える。
コードを簡素化する目的で、MNISTデータセットをダウンロードしてロードしてくれるTensorFlow MNIST wrapperを使用する。このインターフェースに関するさらなる情報はドキュメンテーションを参照されたし。デフォルトパラメーターは訓練標本のうち5000を取得し、それらを検証データセットに据える。データはMNISTデータと呼ばれるフォルダーに保存される。
Heads-up: TensorFlow MNISTラッパーは、画像をベクトルとして返す。すなわち、それらはサイズ(batch, 784)ということ。それらを画像として扱いたい場合は、それらを(batch,28,28)か(batch,28,28,1)にリサイズしなくてはならない。また、それらはnp.float32型で[0,1]にバウンドされている。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('./cs231n/datasets/MNIST_data', one_hot=False)
# show a batch
show_images(mnist.train.next_batch(16)[0])
LeakyReLU¶
def leaky_relu(x, alpha=0.01):
"""Compute the leaky ReLU activation function.
Inputs:
- x: TensorFlow Tensor with arbitrary shape
- alpha: leak parameter for leaky ReLU
Returns:
TensorFlow Tensor with the same shape as x
"""
# TODO: implement leaky ReLU
x_leak = x*alpha
y = tf.maximum(x, x_leak)
return y
leaky ReLU実装をテストする。エラーは1e-10未満になるはず。
def test_leaky_relu(x, y_true):
tf.reset_default_graph()
with get_session() as sess:
y_tf = leaky_relu(tf.constant(x))
y = sess.run(y_tf)
print('Maximum error: %g'%rel_error(y_true, y))
test_leaky_relu(answers['lrelu_x'], answers['lrelu_y'])
Random Noise¶
shape [batch_size, dim]を持つ-1~1の均一ノイズを持ったTensorFlow Tensorを生成する。
def sample_noise(batch_size, dim):
"""Generate random uniform noise from -1 to 1.
Inputs:
- batch_size: integer giving the batch size of noise to generate
- dim: integer giving the dimension of the the noise to generate
Returns:
TensorFlow Tensor containing uniform noise in [-1, 1] with shape [batch_size, dim]
"""
# TODO: sample and return noise
noise = tf.random_uniform([batch_size, dim], minval=-1, maxval=1)
return noise
ノイズが正しい形と型であるかを確かめる。
def test_sample_noise():
batch_size = 3
dim = 4
tf.reset_default_graph()
with get_session() as sess:
z = sample_noise(batch_size, dim)
# Check z has the correct shape
assert z.get_shape().as_list() == [batch_size, dim]
# Make sure z is a Tensor and not a numpy array
assert isinstance(z, tf.Tensor)
# Check that we get different noise for different evaluations
z1 = sess.run(z)
z2 = sess.run(z)
assert not np.array_equal(z1, z2)
# Check that we get the correct range
assert np.all(z1 >= -1.0) and np.all(z1 <= 1.0)
print("All tests passed!")
test_sample_noise()
Discriminator¶
初めの一歩は識別器の構築。モデル構築にはtf.layersの層を使用すべき。全ての全結合層はバイアス項を含んでいなければならない。
Architecture:
- サイズが784~256の全結合層
- LeakyReLU with alpha 0.01
- サイズが256~256の全結合層
- LeakyReLU with alpha 0.01
- サイズが256~1の全結合層
識別器の出力はshape [batch_size, 1]であり、各batch_size入力は実画像のスコアに対応する実数を含んでいなければならない。
def discriminator(x):
"""Compute discriminator score for a batch of input images.
Inputs:
- x: TensorFlow Tensor of flattened input images, shape [batch_size, 784]
Returns:
TensorFlow Tensor with shape [batch_size, 1], containing the score
for an image being real for each input image.
"""
with tf.variable_scope("discriminator"):
# TODO: implement architecture
init = tf.contrib.layers.xavier_initializer(uniform=True)
x = tf.layers.dense(x, 256, activation=leaky_relu, kernel_initializer=init, name='first_layer')
x = tf.layers.dense(x, 256, activation=leaky_relu, kernel_initializer=init, name='second_layer')
logits = tf.layers.dense(x, 1, kernel_initializer=init, name='logits')
return logits
識別器のパラメーター数が正しいことを確認するためにテストする。
def test_discriminator(true_count=267009):
tf.reset_default_graph()
with get_session() as sess:
y = discriminator(tf.ones((2, 784)))
cur_count = count_params()
if cur_count != true_count:
print('Incorrect number of parameters in discriminator. {0} instead of {1}. Check your achitecture.'.format(cur_count,true_count))
else:
print('Correct number of parameters in discriminator.')
test_discriminator()
Generator¶
今度は生成器をビルドする。モデル構築にはtf.layersの層を使用すべき。全ての全結合層はバイアス項を含んでいなければならない。
Architecture:
- tf.shape(z)1~1024までの全結合層
- ReLU
- 1024~1024までの全結合層
- ReLU
- 1024~784までの全結合層
- TanH (出力を[-1,1]に制限するため)
def generator(z):
"""Generate images from a random noise vector.
Inputs:
- z: TensorFlow Tensor of random noise with shape [batch_size, noise_dim]
Returns:
TensorFlow Tensor of generated images, with shape [batch_size, 784].
"""
with tf.variable_scope("generator"):
# TODO: implement architecture
init = tf.contrib.layers.xavier_initializer(uniform=True)
x = tf.layers.dense(z, 1024, activation=tf.nn.relu, kernel_initializer=init, name='first_layer')
x = tf.layers.dense(x, 1024, activation=tf.nn.relu, kernel_initializer=init, name='second_layer')
x = tf.layers.dense(x, 784, kernel_initializer=init, name='third_layer')
img = tf.tanh(x, name='image')
return img
生成器のパラメーター数が正しいことを確認するためにテストする。
def test_generator(true_count=1858320):
tf.reset_default_graph()
with get_session() as sess:
y = generator(tf.ones((1, 4)))
cur_count = count_params()
if cur_count != true_count:
print('Incorrect number of parameters in generator. {0} instead of {1}. Check your achitecture.'.format(cur_count,true_count))
else:
print('Correct number of parameters in generator.')
test_generator()
GAN Loss¶
生成器/識別器損失を算出する。生成器損失$$\ell_G = -\mathbb{E}_{z \sim p(z)}\left[\log D(G(z))\right]$$識別器損失$$ \ell_D = -\mathbb{E}_{x \sim p_\text{data}}\left[\log D(x)\right] – \mathbb{E}_{z \sim p(z)}\left[\log \left(1-D(G(z))\right)\right]$$生成器/識別器損失を最小化していくのでこれらの式は前出の式によりネゲートされることに留意する。
HINTS: 識別器ラベル生成にtf.ones_likeとtf.zeros_likeを用いる。損失関数計算の促進にsigmoid_cross_entropy lossを使う。期待値を算出する代わりにミニバッチの要素を平均するので、合計するのではなく平均することで損失を組み合わせることを忘れないこと。
def gan_loss(logits_real, logits_fake):
"""Compute the GAN loss.
Inputs:
- logits_real: Tensor, shape [batch_size, 1], output of discriminator
Log probability that the image is real for each real image
- logits_fake: Tensor, shape[batch_size, 1], output of discriminator
Log probability that the image is real for each fake image
Returns:
- D_loss: discriminator loss scalar
- G_loss: generator loss scalar
"""
# TODO: compute D_loss and G_loss
D_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
labels=tf.ones_like(logits_real), logits=logits_real))+tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(labels=tf.zeros_like(logits_fake),
logits=logits_fake))
G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=tf.ones_like(logits_fake),
logits=logits_fake))
return D_loss, G_loss
GAN損失をテストする。生成器/識別器損失の両方が正しいことを確認する。エラーは1e-5未満になる必要がある。
def test_gan_loss(logits_real, logits_fake, d_loss_true, g_loss_true):
tf.reset_default_graph()
with get_session() as sess:
d_loss, g_loss = sess.run(gan_loss(tf.constant(logits_real), tf.constant(logits_fake)))
print("Maximum error in d_loss: %g"%rel_error(d_loss_true, d_loss))
print("Maximum error in g_loss: %g"%rel_error(g_loss_true, g_loss))
test_gan_loss(answers['logits_real'], answers['logits_fake'],
answers['d_loss_true'], answers['g_loss_true'])
Optimizing our loss¶
G_lossとD_lossを別々に最小化する、beta1=0.5、学習率1e-3のAdamOptimizerを作成する。ベータを減少させるためのトリックは、Improved Techniques for Training GANs論文の中で、GAN収束促進に効果的であることが示されている。事実、最新のハイパーパラメータで、beta1をTensorflowデフォルトの0.9に設定した場合、識別器損失が0になり、生成器が完全に学習しなくなる可能性が十分ある。ちなみに、これはGANの一般的な故障モードで、D(x)の学習があまりに急速過ぎる場合(例えば損失がほぼ0になる)、G(z)は全く学習できなくなる。たいていの場合、D(x)は、Adamではなく、MomentumもしくはRMSPropを実装したSGDで訓練されるが、ここでは、D(x)とG(z)の両方にAdamを使う。
# TODO: create an AdamOptimizer for D_solver and G_solver
def get_solvers(learning_rate=1e-3, beta1=0.5):
"""Create solvers for GAN training.
Inputs:
- learning_rate: learning rate to use for both solvers
- beta1: beta1 parameter for both solvers (first moment decay)
Returns:
- D_solver: instance of tf.train.AdamOptimizer with correct learning_rate and beta1
- G_solver: instance of tf.train.AdamOptimizer with correct learning_rate and beta1
"""
D_solver = tf.train.AdamOptimizer(learning_rate=learning_rate, beta1=beta1)
G_solver = tf.train.AdamOptimizer(learning_rate=learning_rate, beta1=beta1)
return D_solver, G_solver
Putting it all together¶
次はちょっとしたレゴ組み立てで、生成器と識別器をどのようにして構築するのかを理解するために、この部分は注意深く読む。
tf.reset_default_graph()
# number of images for each batch
batch_size = 128
# our noise dimension
noise_dim = 96
# placeholder for images from the training dataset
x = tf.placeholder(tf.float32, [None, 784])
# random noise fed into our generator
z = sample_noise(batch_size, noise_dim)
# generated images
G_sample = generator(z)
with tf.variable_scope("") as scope:
#scale images to be -1 to 1
logits_real = discriminator(preprocess_img(x))
# Re-use discriminator weights on new inputs
scope.reuse_variables()
logits_fake = discriminator(G_sample)
# Get the list of variables for the discriminator and generator
D_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'discriminator')
G_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'generator')
# get our solver
D_solver, G_solver = get_solvers()
# get our loss
D_loss, G_loss = gan_loss(logits_real, logits_fake)
# setup training steps
D_train_step = D_solver.minimize(D_loss, var_list=D_vars)
G_train_step = G_solver.minimize(G_loss, var_list=G_vars)
D_extra_step = tf.get_collection(tf.GraphKeys.UPDATE_OPS, 'discriminator')
G_extra_step = tf.get_collection(tf.GraphKeys.UPDATE_OPS, 'generator')
Training a GAN!¶
まぁ、そんなに難しくはなかったはず。反復が数百程度だと黒い背景だけが見え、1000に近付くとぼやけた形、3000を超えると約半分はくっきり鮮明なまともな形に見えるはず。今回のケースでは、単純に、D(x)とG(z)を反復毎に1バッチずつ訓練する。しかし、論文では、しばしば、D(x)とG(z)の訓練を異なるスケジュールで行い、一つを他よりも多くのステップで訓練することもあるし、あるいは、損失が十分満足いくまで一つを訓練してから、他を同じようにして訓練したりさえもする。
# a giant helper function
def run_a_gan(sess, G_train_step, G_loss, D_train_step, D_loss, G_extra_step, D_extra_step,\
show_every=250, print_every=50, batch_size=128, num_epoch=10):
"""Train a GAN for a certain number of epochs.
Inputs:
- sess: A tf.Session that we want to use to run our data
- G_train_step: A training step for the Generator
- G_loss: Generator loss
- D_train_step: A training step for the Generator
- D_loss: Discriminator loss
- G_extra_step: A collection of tf.GraphKeys.UPDATE_OPS for generator
- D_extra_step: A collection of tf.GraphKeys.UPDATE_OPS for discriminator
Returns:
Nothing
"""
# compute the number of iterations we need
max_iter = int(mnist.train.num_examples*num_epoch/batch_size)
for it in range(max_iter):
# every show often, show a sample result
if it % show_every == 0:
samples = sess.run(G_sample)
fig = show_images(samples[:16])
plt.show()
print()
# run a batch of data through the network
minibatch,minbatch_y = mnist.train.next_batch(batch_size)
_, D_loss_curr = sess.run([D_train_step, D_loss], feed_dict={x: minibatch})
_, G_loss_curr = sess.run([G_train_step, G_loss])
# print loss every so often.
# We want to make sure D_loss doesn't go to 0
if it % print_every == 0:
print('Iter: {}, D: {:.4}, G:{:.4}'.format(it,D_loss_curr,G_loss_curr))
print('Final images')
samples = sess.run(G_sample)
fig = show_images(samples[:16])
plt.show()
GANを訓練する。所要時間は、CPUで10分、GPUだと1分以下のはず。
with get_session() as sess:
sess.run(tf.global_variables_initializer())
run_a_gan(sess,G_train_step,G_loss,D_train_step,D_loss,G_extra_step,D_extra_step)
Least Squares GAN¶
今度は、原型GANに代わる新型でより安定したLeast Squares GAN(最小二乗GAN)に注目する。この部分に関しては、損失関数を変えてモデルを再訓練するだけでいい。論文中の式(9)を以下の生成器損失と識別器損失を使って実装する。$$\ell_G = \frac{1}{2}\mathbb{E}_{z \sim p(z)}\left[\left(D(G(z))-1\right)^2\right]$$$$ \ell_D = \frac{1}{2}\mathbb{E}_{x \sim p_\text{data}}\left[\left(D(x)-1\right)^2\right] + \frac{1}{2}\mathbb{E}_{z \sim p(z)}\left[ \left(D(G(z))\right)^2\right]$$
HINTS: 期待値を算出する代わりにミニバッチの要素を平均するので、合計するのではなく平均することで損失を組み合わせることを忘れないこと。$D(x)$と$D(G(z))に代入する場合、識別器の直接出力(score_realとscore_fake)を使う。
def lsgan_loss(score_real, score_fake):
"""Compute the Least Squares GAN loss.
Inputs:
- score_real: Tensor, shape [batch_size, 1], output of discriminator
score for each real image
- score_fake: Tensor, shape[batch_size, 1], output of discriminator
score for each fake image
Returns:
- D_loss: discriminator loss scalar
- G_loss: generator loss scalar
"""
# TODO: compute D_loss and G_loss
D_loss = 0.5*tf.reduce_mean(tf.pow(score_real-1,2))+0.5*tf.reduce_mean(tf.pow(score_fake,2))
G_loss = 0.5*tf.reduce_mean(tf.pow(score_fake-1,2))
return D_loss, G_loss
LSGAN損失をテストする。エラーは1e-7未満である必要がある。
def test_lsgan_loss(score_real, score_fake, d_loss_true, g_loss_true):
with get_session() as sess:
d_loss, g_loss = sess.run(
lsgan_loss(tf.constant(score_real), tf.constant(score_fake)))
print("Maximum error in d_loss: %g"%rel_error(d_loss_true, d_loss))
print("Maximum error in g_loss: %g"%rel_error(g_loss_true, g_loss))
test_lsgan_loss(answers['logits_real'], answers['logits_fake'],
answers['d_loss_lsgan_true'], answers['g_loss_lsgan_true'])
代わりにLSGAN損失を最小化するように新しい訓練ステップを作成する。
D_loss, G_loss = lsgan_loss(logits_real, logits_fake)
D_train_step = D_solver.minimize(D_loss, var_list=D_vars)
G_train_step = G_solver.minimize(G_loss, var_list=G_vars)
with get_session() as sess:
sess.run(tf.global_variables_initializer())
run_a_gan(sess, G_train_step, G_loss, D_train_step, D_loss, G_extra_step, D_extra_step)
Deep Convolutional GANs¶
ノートブックの前半部分では、Ian GoodfellowのオリジナルGANのほぼ直接コピーを実装した。しかし、このネットワーク仕様では、real spatial reasoning(実空間推論)ができない。それには畳み込み層が実装されていないので、基本的に、”sharp edges”のようなものについて推論することができない。従って、このパートでは、識別機と生成器に畳み込みネットワークを利用しているDCGANの着想の一部を導入する。
Discriminator¶
TensorFlow MNIST classificationチュートリアルにヒントを得た、かなり急速にMNISTデータセットで99%を超える正確度を得られる識別器を使う。xの次元は必ず確認して、必要ならリシェイプする。全結合ブロックが[N,D]テンソルを要求する一方、conv2dブロックは[N,H,W,C]テンソルを要求する
Architecture:
- 32 Filters, 5×5, Stride 1, Leaky ReLU(alpha=0.01)
- Max Pool 2×2, Stride 2
- 64 Filters, 5×5, Stride 1, Leaky ReLU(alpha=0.01)
- Max Pool 2×2, Stride 2
- Flatten
- Fully Connected size 4 x 4 x 64, Leaky ReLU(alpha=0.01)
- Fully Connected size 1
def discriminator(x):
"""Compute discriminator score for a batch of input images.
Inputs:
- x: TensorFlow Tensor of flattened input images, shape [batch_size, 784]
Returns:
TensorFlow Tensor with shape [batch_size, 1], containing the score
for an image being real for each input image.
"""
with tf.variable_scope("discriminator"):
# TODO: implement architecture
init = tf.contrib.layers.xavier_initializer(uniform=True)
x = tf.reshape(x, [-1, 28, 28, 1])
x = tf.layers.conv2d(x, 32, 5, activation=leaky_relu, padding='valid',
kernel_initializer=init, name='first_convolution')
x = tf.layers.max_pooling2d(x, 2, 2, padding='same', name='first_maxpool')
x = tf.layers.conv2d(x, 64, 5, activation=leaky_relu, padding='valid',
kernel_initializer=init, name='second_convolution')
x = tf.layers.max_pooling2d(x, 2, 2, padding='same', name='second_maxpool')
x = tf.reshape(x, [-1, 1024])
x = tf.layers.dense(x, 1024, activation=leaky_relu,
kernel_initializer=init, name='dense_layer')
logits = tf.layers.dense(x, 1, kernel_initializer=init, name='logits')
return logits
test_discriminator(1102721)
Generator¶
生成器に対しては、InfoGAN paperからの仕様を正確にコピーする。Appendix C.1 MNISTとtf.nn.conv2d_transposeを参照されたし。常にGANモードで訓練をすること。
Architecture:
- Fully connected of size 1024, ReLU
- BatchNorm
- Fully connected of size 7 x 7 x 128, ReLU
- BatchNorm
- Resize into Image Tensor
- 64 conv2d^T (transpose) filters of 4×4, stride 2, ReLU
- BatchNorm
- 1 conv2d^T (transpose) filter of 4×4, stride 2, TanH
def generator(z):
"""Generate images from a random noise vector.
Inputs:
- z: TensorFlow Tensor of random noise with shape [batch_size, noise_dim]
Returns:
TensorFlow Tensor of generated images, with shape [batch_size, 784].
"""
with tf.variable_scope("generator"):
# TODO: implement architecture
init = tf.contrib.layers.xavier_initializer(uniform=True)
z = tf.layers.dense(z, 1024, activation=tf.nn.relu, kernel_initializer=init, name='dense_0')
z = tf.layers.batch_normalization(z, name='batchnorm_0')
z = tf.layers.dense(z, 6272, activation=tf.nn.relu, kernel_initializer=init, name='dense_1')
z = tf.layers.batch_normalization(z, name='batchnorm_1')
z = tf.reshape(z, [-1, 7, 7, 128])
z = tf.layers.conv2d_transpose(z, 64, 4, strides=2, padding='same', activation=tf.nn.relu,
kernel_initializer=init, name='conv_0')
z = tf.layers.batch_normalization(z, name='batchnorm_2')
z = tf.layers.conv2d_transpose(z, 1, 4, strides=2, padding='same', kernel_initializer=init,
name='conv_1')
z = tf.tanh(z)
img = tf.reshape(z, [-1, 784])
return img
test_generator(6595521)
関数を変更したのでネットワークを再構築する必要がある。
tf.reset_default_graph()
batch_size = 128
# our noise dimension
noise_dim = 96
# placeholders for images from the training dataset
x = tf.placeholder(tf.float32, [None, 784])
z = sample_noise(batch_size, noise_dim)
# generated images
G_sample = generator(z)
with tf.variable_scope("") as scope:
#scale images to be -1 to 1
logits_real = discriminator(preprocess_img(x))
# Re-use discriminator weights on new inputs
scope.reuse_variables()
logits_fake = discriminator(G_sample)
# Get the list of variables for the discriminator and generator
D_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,'discriminator')
G_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,'generator')
D_solver,G_solver = get_solvers()
D_loss, G_loss = lsgan_loss(logits_real, logits_fake)
D_train_step = D_solver.minimize(D_loss, var_list=D_vars)
G_train_step = G_solver.minimize(G_loss, var_list=G_vars)
D_extra_step = tf.get_collection(tf.GraphKeys.UPDATE_OPS,'discriminator')
G_extra_step = tf.get_collection(tf.GraphKeys.UPDATE_OPS,'generator')
Train and evaluate a DCGAN¶
この部分は、GPUを使うことで有意な恩恵が得られるA3の1パート。要求された5エポックに対してGPUは3分を要する一方で、デュアルコアモバイルCPUだと50分かかるので、CPUを使用する場合は、3エポックに減らしても一向に構わない。
import time
start_time = time.time()
with get_session() as sess:
sess.run(tf.global_variables_initializer())
run_a_gan(sess,G_train_step,G_loss,D_train_step,D_loss,G_extra_step,D_extra_step,num_epoch=5)
end_time = time.time()
print("Elapsed time was %g seconds" % (end_time - start_time))
WGAN-GP (Small Extra Credit)¶
以下のパートは上のパートが全部終わってからやる。
今度は、Improved Wasserstein GANを、オリジナルGAN損失関数のより新しくより安定した選択肢として使用する。このパートに関しては、損失関数を変えてモデルを再訓練するだけでいい。論文中のAlgorithm 1を実装する。
また、生成器と対応識別器をmax-pooling(最大プーリング)無しで使う必要があるので、上のDCGANで使った物は使えない。(InfoGANの)DCGAN GeneratorとInfoGAN Appendix C.1 MNIST(Qは使わず、最大でDまでのネットワークを実装するだけでいい)の識別器を組み合わせる。このノートブックのトップで使用したD(x)とG(z)の全結合ペアを使いたい場合、このノートブックで新しい生成器と識別器を自由に定義しても構わない。
Architecture:
- 64 Filters of 4×4, stride 2, LeakyReLU
- 128 Filters of 4×4, stride 2, LeakyReLU
- BatchNorm
- Flatten
- Fully connected 1024, LeakyReLU
- Fully connected size 1
def discriminator(x):
with tf.variable_scope('discriminator'):
# TODO: implement architecture
init = tf.contrib.layers.xavier_initializer()
x = tf.reshape(x, [-1, 28, 28, 1])
x = tf.layers.conv2d(x, 64, 4, activation=leaky_relu, strides=2, padding='valid',
kernel_initializer=init, name='conv_0')
x = tf.layers.conv2d(x, 128, 4, activation=leaky_relu, strides=2, padding='valid',
kernel_initializer=init, name='conv_1')
x = tf.layers.batch_normalization(x, name='batchnorm_0')
x = tf.reshape(x, [-1, 3200])
x = tf.layers.dense(x, 1024, activation=leaky_relu, kernel_initializer=init,
name='dense_0')
logits = tf.layers.dense(x, 1, kernel_initializer=init, name='logits')
return logits
test_discriminator(3411649)
tf.reset_default_graph()
batch_size = 128
# our noise dimension
noise_dim = 96
# placeholders for images from the training dataset
x = tf.placeholder(tf.float32, [None, 784])
z = sample_noise(batch_size, noise_dim)
# generated images
G_sample = generator(z)
with tf.variable_scope("") as scope:
#scale images to be -1 to 1
logits_real = discriminator(preprocess_img(x))
# Re-use discriminator weights on new inputs
scope.reuse_variables()
logits_fake = discriminator(G_sample)
# Get the list of variables for the discriminator and generator
D_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,'discriminator')
G_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,'generator')
D_solver, G_solver = get_solvers()
def wgangp_loss(logits_real, logits_fake, batch_size, x, G_sample):
"""Compute the WGAN-GP loss.
Inputs:
- logits_real: Tensor, shape [batch_size, 1], output of discriminator
Log probability that the image is real for each real image
- logits_fake: Tensor, shape[batch_size, 1], output of discriminator
Log probability that the image is real for each fake image
- batch_size: The number of examples in this batch
- x: the input (real) images for this batch
- G_sample: the generated (fake) images for this batch
Returns:
- D_loss: discriminator loss scalar
- G_loss: generator loss scalar
"""
# TODO: compute D_loss and G_loss
D_loss = tf.reduce_mean(logits_fake-logits_real)
G_loss = -tf.reduce_mean(logits_fake)
# lambda from the paper
lam = 10
# random sample of batch_size (tf.random_uniform)
eps = tf.random_uniform([batch_size,1], minval=0.0, maxval=1.0)
x_hat = eps*x+(1-eps)*G_sample
# Gradients of Gradients is kind of tricky!
with tf.variable_scope('',reuse=True) as scope:
grad_D_x_hat = tf.gradients(discriminator(x_hat), x_hat)
grad_norm = tf.norm(grad_D_x_hat[0], axis=1, ord='euclidean')
grad_pen = tf.reduce_mean(lam*tf.square(grad_norm-1))
D_loss += grad_pen
return D_loss, G_loss
D_loss, G_loss = wgangp_loss(logits_real, logits_fake, 128, x, G_sample)
D_train_step = D_solver.minimize(D_loss, var_list=D_vars)
G_train_step = G_solver.minimize(G_loss, var_list=G_vars)
D_extra_step = tf.get_collection(tf.GraphKeys.UPDATE_OPS,'discriminator')
G_extra_step = tf.get_collection(tf.GraphKeys.UPDATE_OPS,'generator')
import time
start_time = time.time()
with get_session() as sess:
sess.run(tf.global_variables_initializer())
run_a_gan(sess,G_train_step,G_loss,D_train_step,D_loss,G_extra_step,D_extra_step,batch_size=128,num_epoch=5)
end_time = time.time()
print("Elapsed time was %g seconds" % (end_time - start_time))