Contents
はじめに
今回は「生成モデル」という題目で、画像を生成するタスクについて勉強していきましょう。画像生成技術は、人工知能分野でも注目されている分野の一つであり、最近では下の画像のように空想人物の顔や有名人の顔画像を本物と見分けがつかないほど高精細に生成することができるようになりました。
指定した画像を生成する技術はもちろん、モノクロ画像をカラー画像に変換したり、昼の画像を夜の画像に変換したりする画像生成技術も確立されました。
深層学習のモデルは大きく識別モデルと生成モデルという2つに分けることができます。今まで取り組んできたニューラルネットワークを用いた分類問題は、全てデータを分類する決定境界を見つけようとする識別モデルです。では、生成モデルとは一体何でしょうか? 生成モデルは、データがどのようにして生成されるのかを表す分布を学習します。つまり、正解データからそっくりな画像を生成するための分布を一生懸命学習するのが生成モデルなのです。
GAN(敵対的生成ネットワーク)について
生成モデルにも色々と種類がありますが、今回はその中でも有名なGAN(Generative Adversarial Network)について学んでいきましょう。GANは今まで学習してきたネットワークと構造が違い、2つのニューラルネットワークモデルで構成されます。その二つのニューラルネットワークとは
- 識別器D: 本物画像かGが作った偽画像かを判定する
- 生成器G: Dが認識できないほどそっくりな画像を生成する
の二つです。GANでは、生成器が識別器を騙すような画像を最終的な目標とします。敵対生成という言葉は「生成器が識別器を騙す」という意味で使われています。次に識別器と生成器について詳しく見ていきましょう。
識別器 – Discriminator
識別器の役目は本物画像と生成器がつくった画像を正しく見分けることでした。では、識別器はどのように機能するのでしょうか?
まずノイズzを生成器Gにわたし、偽画像を生成します。次に偽画像と本物画像を識別器Dにわたし、本物or偽物(0 or 1)で判定します。そして、正解からの誤差を損失関数として計算し、誤差逆伝播により識別器を学習させます。この時、識別器のみを学習させるので、生成器の重みは固定されています。本物の場合には1を、偽物の場合0を返すとします。正しく識別することを目標とするため、出力D(G(x))を0に近づけることを目標とします。
生成器 – Generator
生成器とは適当なノイズから画像を生成するモデルのことを指します。
まずノイズzを生成器Gにわたし、偽画像を生成します。次に偽画像と本物画像を識別器Dにわたし、本物or偽物(0 or 1)で判定します。ここまでは識別器と同じです。次に損失関数を計算し、生成器のみ学習させるように誤差逆伝播を行います。この時、識別器の重みは固定されています。生成器は識別器が間違うようなそっくり画像を生成することが目標であるため、D(G(x))=1に近づけることを目標とします。
損失関数
GANの損失関数はこれまで学んできた二乗平均誤差(MSE)や交差エントロピー(BCE)とは異なり若干複雑です。損失関数の定義式を以下に示しますが、数式の細かな理解は不要です。今から説明することのみを抑えてもらえば大丈夫です。
GANネットワークの損失関数の式は以下のように表すことができます。
生成器の損失関数を識別器の損失関数をと以下定義します。
まず識別器についてですが、入力が本物であるかどうかを示す確率をとし、は入力から生成された画像が偽物であるかどうかを示す確率です。最終的な目標は、元の画像は本物と判別し、生成した画像は偽物と判断することです。
次に生成器についてですが、は入力から生成された画像が本物であるかどうかを示す確率となります。最終的な目標は、生成画像を本物と判断することです。
GAN(敵対的生成ネットワーク)の実装
未学習の状態
GANの構造について理解が進んだところで、実際に画像を生成する課題に取り組みましょう。畳み込みニューラルネットワークの回にも用いたMNISTデータを用いて、 手書き数字の画像を生成するGANネットワークを作ってみましょう。
MNISTデータがどのような画像であったかプログラムを実行して可視化してみましょう。
0~9の手書きの数字が書かれたデータが表示されました。今回は、この画像を機械に作ってもらいましょう。では次に、適当なノイズから画像を生成する生成器(generator)と生成した画像の真偽を見分ける識別器(discriminator)の作成を行いましょう。
生成器、識別器双方学習させていない状態なので、デタラメな値を返します. 本来ならば、数字が描かれた画像が返ってくるべきですが、単なるノイズを画像にしたものが返され、識別器は本物ならば正の値を偽物ならば負の値を返しますが、学習をさせていない状態では適当な値が返ってきます。最終的には、識別器が本物と見間違えるような画像を生成していきます。
最適化手法
生成器の損失関数は、識別器の損失関数はと以下定義されました。
上の式をもとに損失関数を実装すると、以下のように書くことが出来ます。
#交差エントロピー(分類タスクの損失関数に登場する概念)
BCE = tf.keras.losses.BinaryCrossentropy(from_logits=True)
#識別器の損失関数
def discriminator_loss(real_output,fake_output):
#本物画像が本物と判定されるようにしたい
#実画像での識別結果と1の配列を比較する
real_loss = BCE(tf.ones_like(real_output),real_output)
#偽物画像が偽物と判定されるようにしたい
#偽画像での識別結果と0の配列を比較する
fake_loss = BCE(tf.zeros_like(fake_output),fake_output)
#両者を合わせたものがdiscriminatorの損失関数として定義される
return real_loss + fake_loss
#生成器の損失関数
def generator_loss(fake_output):
#本物そっくりの画像を目指す(識別器で1と返すことを目指す)
return BCE(tf.ones_like(fake_output), fake_output)
損失関数の各項についてわからなくなった方は上のコードのコメント部分を参照してみてください。これで識別器と生成器の両方の損失関数を定義することが出来ました。では、次に二つの損失関数を別々に最小化することでネットワークの最適化を行います。別々に学習させることは冒頭でも説明した通りです。
最適化の手法は、誤差逆伝播と呼ばれる手法を用います。TensorFlowでは、誤差逆伝播を用いてネットワークの重みの最適化法を設定する関数がありますので、今回はそれを用います。最適化の手法は以下のようにして記述します。
#最適化手法(Adamと呼ばれる勾配降下手法を用います)
#generator
generator_optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
#discriminator
discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
モデルの学習
ではモデルの訓練に入っていきましょう。少し難しい書き方をしていますので、ここはざっくりと読み飛ばしてもらって構いません。
d_loss_obj = tf.metrics.Mean()
g_loss_obj = tf.metrics.Mean()
device = ['/device:GPU:0' if tf.test.gpu_device_name() =='/device:GPU:0' else '/cpu:0'][0]
# GPUでのモデルの実装コードです
# deviceを引数に渡して、学習させます
def train_model(denerator, discriminator, train_dataset, num_epoch, device):
G_optimizer = tf.keras.optimizers.Adam(1e-4)
D_optimizer = tf.keras.optimizers.Adam(1e-4)
# 本物(1)か偽物(0)かのラベルを生成します
label_real = tf.ones(shape=(batch_size, 1))
label_fake = tf.zeros(shape=(batch_size, 1))
for epoch in range(num_epoch):
training=True
print("\nEpoch {}/{}".format(epoch+1, num_epoch))
print("-----------")
epoch_g_loss = 0.0
epoch_d_loss = 0.0
for train_x in train_dataset:
## 1. Discriminatorの学習
# ノイズをガウス分布からサンプリングします
input_z = tf.random.normal(shape=(batch_size, latent_dim))
with tf.device(device):
with tf.GradientTape() as tape:
# 真の画像を判定
d_out_real = discriminator(train_x, training=True)
# 偽の画像を生成して判別
fake_img = generator(input_z, training=True)
d_out_fake = discriminator(fake_img, training=True)
# 損失を計算
d_loss = discriminator_loss(d_out_real,d_out_fake)
grads = tape.gradient(d_loss, discriminator.trainable_variables)
D_optimizer.apply_gradients(zip(grads, discriminator.trainable_variables))
d_loss_obj(d_loss)
# 2. Generatorの学習
with tf.device(device):
with tf.GradientTape() as tape:
# 偽の画像を生成して判別
fake_img = generator(input_z, training=True)
d_out_fake = discriminator(fake_img, training=True)
# 損失を計算
g_loss = generator_loss(d_out_fake)
grads = tape.gradient(g_loss, generator.trainable_variables)
G_optimizer.apply_gradients(zip(grads, generator.trainable_variables))
g_loss_obj(g_loss)
# 記録
epoch_g_loss += g_loss_obj.result()*batch_size # ミニバッチの損失の合計を加える
epoch_d_loss += d_loss_obj.result()*batch_size
# initialize loss object
g_loss_obj.reset_state()
d_loss_obj.reset_state()
epoch_g_loss = epoch_g_loss / 60000 #トレーニングデータのバッチ数で割ります
epoch_d_loss = epoch_d_loss / 60000
print("epoch {} || Epoch_G_Loss: {:.4f} || Epoch_D_Loss: {:.4f}".format(
epoch+1, epoch_g_loss, epoch_d_loss))
print("Training Done!")
model.save('GAN\model')
return generator,discriminator
num_epoch = 20
#GPU環境内での実行をおすすめします
# G_update, D_update = train_model(generator, discriminator, train_dataset, 20, device)
このブロックで行なっている処理は、以下の通りです。
- 画像を生成し、識別器で評価
- 識別器の損失関数を求め、勾配を計算して識別器の重みを更新
- 画像を生成し、生成器で評価
- 生成器の損失関数を求め、勾配を計算して生成器の重みを更新
- 1~4のサイクルで計算した各ネットワークでの損失関数を記録
- 5までのステップをnum_epochで指定した回数だけ繰り返す
行なっている処理は通常のニューラルネットワークの学習と本質的には変わらないのですが、ネットワークの個数が一個から二個に増えたことで、処理すべき工程が増え若干手間がかかります。
上の処理を行うと、GPU環境内でも時間がかかりますので、あらかじめ学習した学習済みモデルを読み込んで学習は終了したものとして以下進めていきます。自分で学習したいという方は、上のコードをご自分の環境で実行してみてください。
推論
学習済みモデルが適切に読み込まれたようです。では、学習済みの生成器で画像を生成してみましょう。
うっすらではありますが、数字ぽい画像が生成されたことがわかります。両者を比較してみましょう。
比較すると、生成画像は高精細とは言い切れるほどの画像ではありませんが、上のような簡単なネットワークを数10回トレーニングさせることで識別できるレベルの画像を生成できる技術は大変興味深いです。ご自身の開発環境に余裕のある方は、epochを20回以上回して結果がどう変わるか比較してみると面白いと思います。
まとめ
今回は、生成モデルの中でも最も有名なGANネットワークについて学習しました。世の中にある生成モデルは今回紹介したモデルの派生が多いので、生成モデルについて興味のある方はぜひ押さえておきましょう。次回は生成モデルの応用編ということで、身の回りの画像の変換を行い、例えば「昼の画像→夜の画像」を生成することができるGANモデルを作り上げましょう。
参考文献
[1] Phillip Isola, Jun-Yan Zhu, Tinghui Zhou, Alexei A. Efros. (2018) “Image-to-Image Translation with Conditional Adversarial Networks”. Arxiv
[2] TensorFlow Core. “DCGAN”. TensorFlow