CS231n/課題3/敵対的生成ネットワーク(TensorFlow)

今回は、Stanford University/CS231n/Assignment3/Generative Adversarial Networks(GANs/敵対的生成ネットワーク)テンサーフロー編をやる。

今までCS231Nで探究してきたニューラルネットワークの全アプリは、入力を受け取ってラベル付きの出力を生成するように訓練される識別モデルだった。これは、単純な画像カテゴリー分類から文生成(未だ分類問題として表現され、我々のラベルは語彙空間にあり、我々は複数単語ラベルをキャプチャーするためのリカレンスを学んだ。)まで多岐にわたる。このノートブックでは、レパートリーを広げてニューラルネットワークを用いて生成モデルを構築する。特に、一連の訓練画像に似た新たな画像を生成するモデルの構築法を学習する。

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$の目的への勾配上昇ステップを繰り返す。

  1. 識別器が正しい選択をする確率を最小化するよう生成器($G$)を更新する。
  2. 識別器が正しい選択をする確率を最大化するよう識別器($D$)を更新する。

こういった更新は分析には有効である一方で、実際には上手く機能しない。その代わりとして、生成器を更新する際に別の目的を使用して、識別機が誤った選択をする確率を最大化する。この僅かな修正が、識別機の信頼度が高い時に生成器勾配が消失する問題を軽減するのに役立つ。これは、ほとんどのGAN研究論文で使われている標準的なアップデートで、グッドフェロー等による原論文の中でも使われていた。

この宿題では、以下のアプデを交互に行う。

  1. 識別器が生成画像に対して不正確な選択をする確率を最大化するように生成器($G$)をアプデする。$$\underset{G}{\text{maximize}}\; \mathbb{E}_{z \sim p(z)}\left[\log D(G(z))\right]$$
  2. 識別器が真・偽画像に対して正しい選択をする確率を最大化するように識別器($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と同等の綺麗なサンプルは生成していない。

予想される生成画像のいくつかの見本(若干の違いはあるかもしれない)

alt text

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])
WARNING:tensorflow:From <ipython-input-2-d681c923e097>:2: read_data_sets (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use alternatives such as official/mnist/dataset.py from tensorflow/models.
WARNING:tensorflow:From /root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:260: maybe_download (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.
Instructions for updating:
Please write your own downloading logic.
WARNING:tensorflow:From /root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/base.py:252: _internal_retry.<locals>.wrap.<locals>.wrapped_fn (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.
Instructions for updating:
Please use urllib or similar directly.
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
WARNING:tensorflow:From /root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:262: extract_images (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use tf.data to implement this functionality.
Extracting ./cs231n/datasets/MNIST_data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
WARNING:tensorflow:From /root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:267: extract_labels (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use tf.data to implement this functionality.
Extracting ./cs231n/datasets/MNIST_data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting ./cs231n/datasets/MNIST_data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting ./cs231n/datasets/MNIST_data/t10k-labels-idx1-ubyte.gz
WARNING:tensorflow:From /root/.pyenv/versions/miniconda3-4.3.30/envs/caffe2/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:290: DataSet.__init__ (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use alternatives such as official/mnist/dataset.py from tensorflow/models.

LeakyReLU

下のセルで、LeakyReLUを実装する。αが少数であるクラスノートを参照するか、この論分の公式(3)を参照。LeakyReLUは、ReLUユニットが消失するのを防ぎ、しばしばGANメソッドで使われる(maxoutユニットとしては、しかしながら、その増大するモデルサイズ故に今回は使用しない)。

HINT: tf.maximumを使用できるはず。

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'])
Maximum error: 0

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()
All tests passed!

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()
Correct number of parameters in 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()
Correct number of parameters in 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_liketf.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'])
Maximum error in d_loss: 0
Maximum error in g_loss: 0

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)
Iter: 0, D: 1.691, G:0.7485
Iter: 50, D: 0.3175, G:1.537
Iter: 100, D: 3.327, G:0.4398
Iter: 150, D: 0.7209, G:1.234
Iter: 200, D: 1.157, G:1.083
Iter: 250, D: 1.086, G:1.357
Iter: 300, D: 1.188, G:1.281
Iter: 350, D: 1.128, G:1.655
Iter: 400, D: 1.083, G:1.599
Iter: 450, D: 1.062, G:1.901
Iter: 500, D: 0.822, G:1.763
Iter: 550, D: 1.746, G:0.5652
Iter: 600, D: 1.805, G:0.7975
Iter: 650, D: 1.221, G:1.929
Iter: 700, D: 1.307, G:1.187
Iter: 750, D: 1.166, G:1.49
Iter: 800, D: 1.414, G:1.918
Iter: 850, D: 0.9288, G:1.912
Iter: 900, D: 1.066, G:1.298
Iter: 950, D: 1.34, G:1.255
Iter: 1000, D: 1.155, G:1.259
Iter: 1050, D: 0.9494, G:2.586
Iter: 1100, D: 1.122, G:1.067
Iter: 1150, D: 1.094, G:1.108
Iter: 1200, D: 1.355, G:0.8963
Iter: 1250, D: 1.163, G:1.079
Iter: 1300, D: 1.213, G:0.9951
Iter: 1350, D: 1.231, G:0.8131
Iter: 1400, D: 1.372, G:0.9715
Iter: 1450, D: 1.326, G:0.9316
Iter: 1500, D: 1.243, G:1.056
Iter: 1550, D: 1.352, G:1.036
Iter: 1600, D: 1.278, G:1.183
Iter: 1650, D: 1.176, G:1.648
Iter: 1700, D: 1.188, G:0.874
Iter: 1750, D: 1.313, G:0.8483
Iter: 1800, D: 1.262, G:0.8873
Iter: 1850, D: 1.178, G:0.9356
Iter: 1900, D: 1.392, G:0.8626
Iter: 1950, D: 1.36, G:1.032
Iter: 2000, D: 1.237, G:1.011
Iter: 2050, D: 1.301, G:0.7515
Iter: 2100, D: 1.294, G:0.957
Iter: 2150, D: 1.315, G:0.8315
Iter: 2200, D: 1.282, G:0.9415
Iter: 2250, D: 1.333, G:0.8355
Iter: 2300, D: 1.206, G:0.9456
Iter: 2350, D: 1.303, G:0.9566
Iter: 2400, D: 1.353, G:1.004
Iter: 2450, D: 1.33, G:0.8948
Iter: 2500, D: 1.315, G:0.8738
Iter: 2550, D: 1.285, G:0.9335
Iter: 2600, D: 1.245, G:0.8885
Iter: 2650, D: 1.265, G:0.7891
Iter: 2700, D: 1.298, G:0.8752
Iter: 2750, D: 1.3, G:0.8667
Iter: 2800, D: 1.303, G:0.9431
Iter: 2850, D: 1.385, G:0.785
Iter: 2900, D: 1.295, G:0.8535
Iter: 2950, D: 1.297, G:0.7784
Iter: 3000, D: 1.293, G:0.7629
Iter: 3050, D: 1.294, G:0.8258
Iter: 3100, D: 1.372, G:0.7602
Iter: 3150, D: 1.311, G:0.8385
Iter: 3200, D: 1.308, G:0.8346
Iter: 3250, D: 1.306, G:0.8817
Iter: 3300, D: 1.359, G:0.7427
Iter: 3350, D: 1.275, G:0.7804
Iter: 3400, D: 1.261, G:0.8209
Iter: 3450, D: 1.285, G:0.8303
Iter: 3500, D: 1.304, G:1.101
Iter: 3550, D: 1.297, G:0.8601
Iter: 3600, D: 1.356, G:0.9365
Iter: 3650, D: 1.268, G:0.7916
Iter: 3700, D: 1.297, G:0.9222
Iter: 3750, D: 1.324, G:0.8891
Iter: 3800, D: 1.329, G:0.7999
Iter: 3850, D: 1.325, G:0.8163
Iter: 3900, D: 1.333, G:0.8768
Iter: 3950, D: 1.292, G:0.8784
Iter: 4000, D: 1.251, G:0.8974
Iter: 4050, D: 1.34, G:0.8432
Iter: 4100, D: 1.321, G:0.8298
Iter: 4150, D: 1.284, G:0.7747
Iter: 4200, D: 1.339, G:0.8071
Iter: 4250, D: 1.278, G:0.8305
Final images

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'])
Maximum error in d_loss: 0
Maximum error in g_loss: 0

代わりに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)
Iter: 0, D: 1.956, G:0.5092
Iter: 50, D: 0.01041, G:0.984
Iter: 100, D: 0.02788, G:0.7324
Iter: 150, D: 0.1901, G:0.3996
Iter: 200, D: 0.07634, G:0.4218
Iter: 250, D: 0.1016, G:0.3327
Iter: 300, D: 0.1275, G:0.4223
Iter: 350, D: 0.1922, G:0.8229
Iter: 400, D: 0.09392, G:0.4051
Iter: 450, D: 0.07409, G:0.5031
Iter: 500, D: 0.08025, G:0.6365
Iter: 550, D: 0.1041, G:0.4065
Iter: 600, D: 0.1671, G:0.2461
Iter: 650, D: 0.1605, G:0.317
Iter: 700, D: 0.09877, G:0.4722
Iter: 750, D: 0.1195, G:0.4593
Iter: 800, D: 0.09981, G:0.3969
Iter: 850, D: 0.3192, G:0.3702
Iter: 900, D: 0.1624, G:0.3507
Iter: 950, D: 0.08492, G:0.3033
Iter: 1000, D: 0.116, G:0.3338
Iter: 1050, D: 0.1995, G:0.2814
Iter: 1100, D: 0.1577, G:1.76
Iter: 1150, D: 0.1327, G:0.3417
Iter: 1200, D: 0.1307, G:0.3687
Iter: 1250, D: 0.1138, G:0.3713
Iter: 1300, D: 0.2022, G:0.2257
Iter: 1350, D: 0.1408, G:0.3157
Iter: 1400, D: 0.142, G:0.3945
Iter: 1450, D: 0.1474, G:0.3691
Iter: 1500, D: 0.144, G:0.295
Iter: 1550, D: 0.1829, G:0.2973
Iter: 1600, D: 0.1768, G:0.1955
Iter: 1650, D: 0.1637, G:0.322
Iter: 1700, D: 0.2008, G:0.2338
Iter: 1750, D: 0.1856, G:0.2089
Iter: 1800, D: 0.2159, G:0.1814
Iter: 1850, D: 0.2303, G:0.1747
Iter: 1900, D: 0.2245, G:0.191
Iter: 1950, D: 0.227, G:0.1915
Iter: 2000, D: 0.2135, G:0.179
Iter: 2050, D: 0.2155, G:0.1789
Iter: 2100, D: 0.2022, G:0.16
Iter: 2150, D: 0.2384, G:0.177
Iter: 2200, D: 0.2301, G:0.1783
Iter: 2250, D: 0.2451, G:0.1578
Iter: 2300, D: 0.2151, G:0.1677
Iter: 2350, D: 0.2215, G:0.173
Iter: 2400, D: 0.2184, G:0.1613
Iter: 2450, D: 0.2371, G:0.1697
Iter: 2500, D: 0.2411, G:0.1628
Iter: 2550, D: 0.229, G:0.1781
Iter: 2600, D: 0.2211, G:0.1817
Iter: 2650, D: 0.2231, G:0.1945
Iter: 2700, D: 0.2275, G:0.1871
Iter: 2750, D: 0.2344, G:0.1841
Iter: 2800, D: 0.2593, G:0.1883
Iter: 2850, D: 0.2349, G:0.1781
Iter: 2900, D: 0.2222, G:0.1617
Iter: 2950, D: 0.2187, G:0.1695
Iter: 3000, D: 0.2424, G:0.1876
Iter: 3050, D: 0.2332, G:0.1644
Iter: 3100, D: 0.2259, G:0.1845
Iter: 3150, D: 0.249, G:0.1798
Iter: 3200, D: 0.2227, G:0.1678
Iter: 3250, D: 0.2212, G:0.1484
Iter: 3300, D: 0.2458, G:0.1734
Iter: 3350, D: 0.232, G:0.1843
Iter: 3400, D: 0.2258, G:0.1775
Iter: 3450, D: 0.2211, G:0.164
Iter: 3500, D: 0.231, G:0.1687
Iter: 3550, D: 0.2312, G:0.165
Iter: 3600, D: 0.2342, G:0.1741
Iter: 3650, D: 0.2294, G:0.1649
Iter: 3700, D: 0.2326, G:0.1603
Iter: 3750, D: 0.2287, G:0.1772
Iter: 3800, D: 0.2266, G:0.1682
Iter: 3850, D: 0.2218, G:0.159
Iter: 3900, D: 0.2185, G:0.1612
Iter: 3950, D: 0.215, G:0.1972
Iter: 4000, D: 0.211, G:0.1747
Iter: 4050, D: 0.2252, G:0.1791
Iter: 4100, D: 0.2211, G:0.1958
Iter: 4150, D: 0.2188, G:0.1689
Iter: 4200, D: 0.2381, G:0.163
Iter: 4250, D: 0.2382, G:0.1629
Final images

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)
Correct number of parameters in discriminator.

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)
Correct number of parameters in generator.

関数を変更したのでネットワークを再構築する必要がある。

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))
Iter: 0, D: 0.5005, G:0.409
Iter: 50, D: 0.02688, G:0.5518
Iter: 100, D: 0.02077, G:0.7056
Iter: 150, D: 0.01959, G:0.5214
Iter: 200, D: 0.03353, G:0.4722
Iter: 250, D: 0.03296, G:0.4296
Iter: 300, D: 0.1711, G:0.7181
Iter: 350, D: 0.06943, G:0.3856
Iter: 400, D: 0.08125, G:0.6661
Iter: 450, D: 0.0816, G:0.5719
Iter: 500, D: 0.164, G:0.211
Iter: 550, D: 0.1053, G:0.2801
Iter: 600, D: 0.09262, G:0.3255
Iter: 650, D: 0.09859, G:0.3344
Iter: 700, D: 0.1124, G:0.3644
Iter: 750, D: 0.08352, G:0.3882
Iter: 800, D: 0.09758, G:0.3708
Iter: 850, D: 0.1136, G:0.2914
Iter: 900, D: 0.1268, G:0.4416
Iter: 950, D: 0.1354, G:0.3675
Iter: 1000, D: 0.1442, G:0.3542
Iter: 1050, D: 0.1559, G:0.2488
Iter: 1100, D: 0.1667, G:0.2582
Iter: 1150, D: 0.1359, G:0.2462
Iter: 1200, D: 0.1509, G:0.3072
Iter: 1250, D: 0.1463, G:0.2611
Iter: 1300, D: 0.1575, G:0.3183
Iter: 1350, D: 0.1847, G:0.2246
Iter: 1400, D: 0.1682, G:0.2793
Iter: 1450, D: 0.215, G:0.1845
Iter: 1500, D: 0.158, G:0.27
Iter: 1550, D: 0.1815, G:0.239
Iter: 1600, D: 0.1717, G:0.2523
Iter: 1650, D: 0.1744, G:0.1865
Iter: 1700, D: 0.1707, G:0.2459
Iter: 1750, D: 0.1679, G:0.2141
Iter: 1800, D: 0.1676, G:0.1835
Iter: 1850, D: 0.1923, G:0.28
Iter: 1900, D: 0.1455, G:0.2096
Iter: 1950, D: 0.1812, G:0.3002
Iter: 2000, D: 0.1583, G:0.2151
Iter: 2050, D: 0.1519, G:0.2398
Iter: 2100, D: 0.186, G:0.2515
Final images
Elapsed time was 57.8568 seconds

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)
Correct number of parameters in discriminator.
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))
Iter: 0, D: 8.311, G:-0.04866
Iter: 50, D: -17.63, G:-0.2537
Iter: 100, D: -18.22, G:2.65
Iter: 150, D: -7.183, G:1.714
Iter: 200, D: -5.929, G:8.509
Iter: 250, D: -5.517, G:-9.227
Iter: 300, D: -7.18, G:-6.544
Iter: 350, D: -6.789, G:-2.366
Iter: 400, D: -3.625, G:3.714
Iter: 450, D: -4.168, G:2.254
Iter: 500, D: -4.246, G:-1.403
Iter: 550, D: -2.499, G:4.752
Iter: 600, D: -1.053, G:-0.4327
Iter: 650, D: -0.8361, G:-3.84
Iter: 700, D: -1.613, G:2.054
Iter: 750, D: -2.174, G:-0.8325
Iter: 800, D: -1.789, G:-1.816
Iter: 850, D: -0.9633, G:3.732
Iter: 900, D: -1.738, G:-1.217
Iter: 950, D: -1.989, G:2.937
Iter: 1000, D: -0.8302, G:-0.3268
Iter: 1050, D: -0.7419, G:0.7376
Iter: 1100, D: -0.6906, G:0.0218
Iter: 1150, D: -0.1247, G:3.209
Iter: 1200, D: -0.9628, G:-1.075
Iter: 1250, D: 0.2005, G:-2.4
Iter: 1300, D: -1.22, G:7.178
Iter: 1350, D: -0.7313, G:3.683
Iter: 1400, D: -0.04833, G:3.558
Iter: 1450, D: -0.5215, G:1.069
Iter: 1500, D: 0.02599, G:-2.084
Iter: 1550, D: -1.943, G:6.66
Iter: 1600, D: -0.5088, G:-2.863
Iter: 1650, D: -0.2891, G:16.04
Iter: 1700, D: -1.426, G:-10.36
Iter: 1750, D: -0.8944, G:-2.172
Iter: 1800, D: 0.05757, G:-1.598
Iter: 1850, D: 0.4521, G:-4.403
Iter: 1900, D: -0.1499, G:3.556
Iter: 1950, D: -0.4843, G:2.637
Iter: 2000, D: -0.5148, G:-4.927
Iter: 2050, D: -0.6988, G:0.2216
Iter: 2100, D: 0.3242, G:2.316
Final images
Elapsed time was 75.5923 seconds
参考サイトhttps://github.com/