PythonとTensorFlowによるGAN入門

生成モデルは、ゼロからデータサンプルを作成することを目的としたAIアーキテクチャの一群である。生成したいもののデータ分布を把握することで、これを実現する。

この種のモデルは盛んに研究されており、その周辺には膨大な量の誇大広告がある。このグラフは、過去数年間にこの分野で発表された論文の数を示しています。

Generative Adversarial Networksに関する最初の論文が発表された2014年以降、生成モデルは信じられないほど強力になっており、画像、動画、音楽、文章など、さまざまな分布の超リアルなデータサンプルを生成できるようになりました。

ここでは、GANによって生成された画像の例を紹介する。

生成モデルとは?

GANsフレームワーク

生成モデルで最も成功したフレームワークは、Generative Adversarial Networks (GANs)と呼ばれるもので、少なくとも近年は提案されている。

GANは、生成器Gと識別器Dという2つのニューラルネットワークで構成される。

生成器の目的は、識別器を欺くようなデータサンプルを生成することである。

生成器はディープニューラルネットワークにほかならない。ランダムなノイズ(通常はガウス分布か一様分布)のベクトルを入力とし、捕捉したい分布のデータサンプルを出力する。

識別器は、これも単なるニューラルネットワークである。その目的は、その名の通り、本物のサンプルと偽物のサンプルを識別することである。したがって、入力はデータサンプルであり、生成器から来るものと実際のデータ分布から来るもののどちらかである。

出力は単純な数値で、入力が本物である確率を表す。高い確率は、入力されたサンプルが本物であることを確信していることを意味する。逆に、確率が低い場合は、そのサンプルが生成器ネットワークから来たものであることに高い信頼性があることを示す。

偽物の美術品を作ろうとする美術贋作者と、本物の絵画と偽物の絵画を区別する必要のある美術評論家を想像してみよう。

このシナリオでは、批評家は識別器の役割を果たし、贋作者は生成器の役割を果たし、批評家からのフィードバックを受けて技術を向上させ、贋作をより説得力のあるものにする。

トレーニング

GANのトレーニングは骨が折れる。学習の不安定さは常に問題であり、多くの研究が学習をより安定させることに焦点を当てている。

バニラGANモデルの基本的な目的関数は以下の通りである。

ここで、Dは識別器ネットワーク、Gは明らかに生成器を意味する。

この式からわかるように、生成器は偽のデータサンプルに対して高い確率を出力させようとすることで、識別器を最大限に混乱させるように最適化する。

逆に、識別器はGから来るサンプルと実際の分布から来るサンプルをより良く区別できるようにしようとする。

敵対的という言葉は、まさにGANSの学習方法からきており、2つのネットワークを互いに戦わせる。

一度モデルを学習すれば、識別器は不要になる。あとは生成器にランダムなノイズベクトルを与えるだけで、うまくいけば現実的な人工的なデータサンプルを得ることができる。

GANsの課題

では、なぜGANは訓練が難しいのでしょうか?前述したように、GANはバニラのままでは非常に学習しにくい。その理由を簡単に説明します。

到達困難なナッシュ均衡

この2つのネットワークは互いに情報を撃ち合うので、入力が本物かどうかを推測するゲームのように描かれることもある。

GANの枠組みは、連続した高次元のパラメータを持つ非凸の2人協力ゲームであり、各プレイヤーはそのコスト関数を最小化したいと考える。このプロセスの最適値は、ナッシュ均衡と呼ばれる。つまり、各プレイヤーが戦略を変えても、他のプレイヤーが戦略を変えないという事実があれば、それ以上パフォーマンスが上がらないということだ。

しかし、GANは通常、勾配降下法を用いて学習させるが、これはコスト関数の低い値を見つけるためのものであり、ゲームのナッシュ均衡を見つけるためのものではない。

モード崩壊

ほとんどのデータ分布はマルチモーダルである。MNISTデータセットを例にとると、0から9の間の異なる数字を指して、データの10の「モード」が存在する。

良い生成モデルであれば、十分なばらつきのあるサンプルを生成することができ、その結果、すべての異なるクラスからサンプルを生成することができる。

しかし、これは常に起こることではありません。

例えば、生成器が “3 “の数字を生成するのが得意になったとしよう。このとき、生成されたサンプルが十分に納得できるものであれば、識別器はそのサンプルに高い確率を与える可能性が高い。

その結果、ジェネレーターは他のクラスを無視し、その特定のモードからのサンプルを生成する方向に押し出されます。その結果、ジェネレーターは他のクラスを無視し、そのモードからのサンプルを生成するようになり、基本的に同じ数字を使い、識別器を通過するたびに、この動作がさらに強化されることになる。

グラデーションの減少

前の例とよく似ているが、識別器はデータサンプルを区別するのに成功しすぎることがある。そうなるとジェネレータの勾配が消え、学習量が少なくなり、収束しなくなる。

このようなアンバランスは、ネットワークを別々に学習させた場合にも起こりうることで、前述と同じである。ニューラルネットワークの進化はかなり予測不可能で、片方がもう片方より1マイルも先に進んでしまうことがある。一緒に訓練すれば、こうしたことが起こらないようにすることがほとんどです。

最新鋭の技術

過去数年間にGANをより強力で安定したものにするために行われた改良と開発のすべてを包括的に示すことは不可能でしょう。

その代わりに、最も成功したアーキテクチャと技術のリストを作成し、より深く理解するための関連リソースへのリンクを提供します。

DCGANs

Deep Convolutional GAN (DCGAN)は、生成器と識別器のネットワークに畳み込みを導入した。

しかし、単純に畳み込み層を追加すればよいというわけではなく、学習がより不安定になる。

DCGANを使いこなすためには、いくつかの工夫が必要であった。

  • 生成器と識別器の両ネットワークにバッチ正規化を適用した。
  • 正則化の手法としてドロップアウトを用いた。
  • 生成器には、ランダムな入力ベクトルを出力画像にアップサンプリングする方法が必要であった。ここでは、畳み込み層の入れ替えが採用されている
  • LeakyReluとTanHのアクティベーションを両ネットワークで使用。

WGANs

Wasserstein GAN (WGAN)は、学習の安定性を向上させることを目的としています。このタイプのモデルの背後には大量の数学が存在する。より親しみやすい説明はこちらにあります。

ここでの基本的な考え方は、あらゆる場所でより滑らかな勾配を持つ新しいコスト関数を提案することです。

この新しいコスト関数は、Wasserstein距離と呼ばれるメトリックを使用し、あらゆる場所でより滑らかな勾配を持つようにしました。

その結果、criticと呼ばれる識別器は、もはや確率として解釈されることのない信頼値を出力するようになった。高い値は、モデルが入力が本物であることを確信していることを意味する。

WGANの重要な改良点は2つあります。

  • 実験におけるモード崩壊の兆候はない。
  • 批評家が良いパフォーマンスをするとき、生成器はまだ学習することができる。

SAGANs

自己注意型GAN(SAGAN)は、GANフレームワークに注意機構を導入したものである。

アテンション機構は、グローバルな情報をローカルに利用することを可能にする。つまり、画像の異なる部分から意味を捉え、その情報を使ってより良いサンプルを生成することができる。

これは、畳み込みが入力サンプルの長期的依存性を捉えるのに非常に不利であるという観察からきている。なぜなら、畳み込みは、受容野がカーネルの空間サイズに依存する局所演算であるからである。

つまり、例えば、画像の左上の出力が右下の出力と何らかの関係があることはありえないのである。

この問題を解決するためには、カーネルのサイズを大きくして、より多くの情報を取得することが考えられます。しかし、これではモデルの計算効率が悪く、学習に非常に時間がかかってしまいます。

自己アテンションはこの問題を解決し、全体的な情報を取得し、それが有用と思われるときに局所的に使用する効率的な方法を提供する。

ビッグガンズ

BigGANは、生成されるサンプルの品質に関する限り、この記事の執筆時点では、多かれ少なかれ最先端であると考えられています。

この研究者が行ったのは、それまでうまくいっていたものをすべてまとめ、それを大規模にスケールアップさせることでした。

ベースラインはSAGANで、これに安定性を向上させるための工夫を加えた。

GANは、モデルにこれ以上機能的な改良を加えていない場合でも、スケーリングによって劇的に恩恵を受けることが証明されました。

GANがスケーリングによって劇的に恩恵を受けることを証明した。
GANは、複数カテゴリの自然画像をモデル化するために訓練されたGenerative Adversarial Networksが、生成されるサンプルの忠実度と多様性の両方において、スケールアップから大きな利益を得ることを実証した。その結果、我々のモデルはImageNetのGANモデルの中で新しいレベルの性能を設定し、大きなマージンをもって最新の技術を改善した。

Pythonで作るシンプルなGAN

コードの実装

それでは、0から9までの数字を生成する簡単なGANを実装してみましょう。

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import os


# Sample z from uniform distribution
def sample_Z(m, n):
    return np.random.uniform(-1., 1., size=[m, n])


def plot(samples):
    fig = plt.figure(figsize=(4, 4))
    gs = gridspec.GridSpec(4, 4)
    gs.update(wspace=0.05, hspace=0.05)


for i, sample in enumerate(samples):
        ax = plt.subplot(gs[i])
        plt.axis('off')
        ax.set_xticklabels([])
        ax.set_yticklabels([])
        ax.set_aspect('equal')
        plt.imshow(sample.reshape(28, 28), cmap='Greys_r')


return fig


ここで、入力サンプルとノイズベクトルのプレースホルダーを定義することができる。

# Input image, for discriminator model.
X = tf.placeholder(tf.float32, shape=[None, 784])


# Input noise for generator.
Z = tf.placeholder(tf.float32, shape=[None, 100])


次に、生成器ネットワークと識別器ネットワークを定義します。これらは隠れ層が1層だけの単純なパーセプトロンです。

隠れ層のニューロンにはrelu activationsを、出力層にはsigmoidsを使います。

def generator(z):
    with tf.variable_scope("generator", reuse=tf.AUTO_REUSE):
        x = tf.layers.dense(z, 128, activation=tf.nn.relu)
        x = tf.layers.dense(z, 784)
        x = tf.nn.sigmoid(x)
    return x


def discriminator(x):
    with tf.variable_scope("discriminator", reuse=tf.AUTO_REUSE):
        x = tf.layers.dense(x, 128, activation=tf.nn.relu)
        x = tf.layers.dense(x, 1)
        x = tf.nn.sigmoid(x)
    return x


これで、モデル、損失関数、最適化器を定義することができます。

# Generator model
G_sample = generator(Z)


# Discriminator models
D_real = discriminator(X)
D_fake = discriminator(G_sample)


# Loss function
D_loss = -tf.reduce_mean(tf.log(D_real) + tf.log(1. - D_fake))
G_loss = -tf.reduce_mean(tf.log(D_fake))


# Select parameters
disc_vars = [var for var in tf.trainable_variables() if var.name.startswith("disc")]
gen_vars = [var for var in tf.trainable_variables() if var.name.startswith("gen")]


# Optimizers
D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=disc_vars)
G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=gen_vars)


最後に、学習ルーチンを記述する。各反復において、識別器と生成器の最適化を1ステップずつ行う。

100回の繰り返しごとに、生成されたサンプルを保存し、学習の進捗を見ることができます。

# Batch size
mb_size = 128


# Dimension of input noise
Z_dim = 100


mnist = input_data.read_data_sets('../../MNIST_data', one_hot=True)


sess = tf.Session()
sess.run(tf.global_variables_initializer())


if not os.path.exists('out2/'):
    os.makedirs('out2/')


i = 0


for it in range(1000000):


# Save generated images every 1000 iterations.
    if it % 1000 == 0:
        samples = sess.run(G_sample, feed_dict={Z: sample_Z(16, Z_dim)})


fig = plot(samples)
        plt.savefig('out2/{}.png'.format(str(i).zfill(3)), bbox_inches='tight')
        i += 1
        plt.close(fig)


# Get next batch of images. Each batch has mb_size samples.
    X_mb, _ = mnist.train.next_batch(mb_size)


# Run disciminator solver
    _, D_loss_curr = sess.run([D_solver, D_loss], feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})


# Run generator solver
    _, G_loss_curr = sess.run([G_solver, G_loss], feed_dict={Z: sample_Z(mb_size, Z_dim)})


# Print loss
    if it % 1000 == 0:
        print('Iter: {}'.format(it))
        print('D loss: {:.4}'. format(D_loss_curr))


結果および改善の可能性

最初の繰り返しでは、ランダムなノイズしか表示されない。

ここでは、ネットワークはまだ何も学習していない。しかし、数分後には、数字がどのように変化しているかがわかります。

リソース

GitHubにコードをアップしていますので、遊んでみてください。

  • SAGANsの説明
  • GANの学習方法の改善
  • GANに関する必読の論文
タイトルとURLをコピーしました