Now Loading...

Now Loading...

はじめに

今回は「生成モデル」という題目で、画像を生成するタスクについて勉強していきましょう。画像生成技術は、人工知能分野でも注目されている分野の一つであり、最近では下の画像のように空想人物の顔や有名人の顔画像を本物と見分けがつかないほど高精細に生成することができるようになりました。

図1. 画像生成技術の発展”Ian Goodfellow Twitter”

指定した画像を生成する技術はもちろん、モノクロ画像をカラー画像に変換したり、昼の画像を夜の画像に変換したりする画像生成技術も確立されました。

図2. 画像から画像への変換 “Image-to-Image Translation with Conditional Adversarial Networks”

深層学習のモデルは大きく識別モデルと生成モデルという2つに分けることができます。今まで取り組んできたニューラルネットワークを用いた分類問題は、全てデータを分類する決定境界を見つけようとする識別モデルです。では、生成モデルとは一体何でしょうか? 生成モデルは、データがどのようにして生成されるのかを表す分布を学習します。つまり、正解データからそっくりな画像を生成するための分布を一生懸命学習するのが生成モデルなのです。

図3. 識別モデル
図4. 生成モデル

GAN(敵対的生成ネットワーク)について

生成モデルにも色々と種類がありますが、今回はその中でも有名なGAN(Generative Adversarial Network)について学んでいきましょう。GANは今まで学習してきたネットワークと構造が違い、2つのニューラルネットワークモデルで構成されます。その二つのニューラルネットワークとは

  1. 識別器D: 本物画像かGが作った偽画像かを判定する
  2. 生成器G: Dが認識できないほどそっくりな画像を生成する

の二つです。GANでは、生成器が識別器を騙すような画像を最終的な目標とします。敵対生成という言葉は「生成器が識別器を騙す」という意味で使われています。次に識別器と生成器について詳しく見ていきましょう。

識別器 – Discriminator

識別器の役目は本物画像と生成器がつくった画像を正しく見分けることでした。では、識別器はどのように機能するのでしょうか?

図5. 識別器

まずノイズzを生成器Gにわたし、偽画像を生成します。次に偽画像と本物画像を識別器Dにわたし、本物or偽物(0 or 1)で判定します。そして、正解からの誤差を損失関数として計算し、誤差逆伝播により識別器を学習させます。この時、識別器のみを学習させるので、生成器の重みは固定されています。本物の場合には1を、偽物の場合0を返すとします。正しく識別することを目標とするため、出力D(G(x))を0に近づけることを目標とします。

生成器 – Generator

生成器とは適当なノイズから画像を生成するモデルのことを指します。

図6 生成器

まずノイズ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. 画像を生成し、識別器で評価
  2. 識別器の損失関数を求め、勾配を計算して識別器の重みを更新
  3. 画像を生成し、生成器で評価
  4. 生成器の損失関数を求め、勾配を計算して生成器の重みを更新
  5. 1~4のサイクルで計算した各ネットワークでの損失関数を記録
  6. 5までのステップをnum_epochで指定した回数だけ繰り返す

行なっている処理は通常のニューラルネットワークの学習と本質的には変わらないのですが、ネットワークの個数が一個から二個に増えたことで、処理すべき工程が増え若干手間がかかります。

上の処理を行うと、GPU環境内でも時間がかかりますので、あらかじめ学習した学習済みモデルを読み込んで学習は終了したものとして以下進めていきます。自分で学習したいという方は、上のコードをご自分の環境で実行してみてください。

 

推論

学習済みモデルが適切に読み込まれたようです。では、学習済みの生成器で画像を生成してみましょう。

 

うっすらではありますが、数字ぽい画像が生成されたことがわかります。両者を比較してみましょう。

図7. 本物画像と生成画像

比較すると、生成画像は高精細とは言い切れるほどの画像ではありませんが、上のような簡単なネットワークを数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

\ シェア /

【2/15(木)開催】G検定模擬試験

E資格スピードパッケージ2023#2修了者合格率100%達成

zero to one E資格 jdla

zero to oneの「E資格」向け認定プログラム

日本ディープラーニング協会の実施するE資格の受験ならzero to oneの「E資格」向け認定プログラム (税込165,000円) をおすすめします。当講座は、東京大学大学院工学系研究科の松尾豊教授と東北大学大学院情報科学研究科の岡谷貴之教授が監修する実践的なプログラムとなっています。
厚生労働省の教育訓練給付制度対象のE資格認定プログラムの中では最安値※となり、実質負担額49,500円~(支給割合70%の場合)で受講可能です。※2023年弊社調べ zero to one E資格 jdla

人工知能基礎講座を提供中

人工知能の第一人者である東京大学の松尾豊教授が監修した人工知能基礎講座を受講してみませんか? 人工知能の歴史から自然言語処理、機械学習、深層学習といった最先端のトピックやAIに関わる法律問題まで網羅しているので全てのビジネスパーソン・AIの初学者におすすめです。

サンプル動画

人工知能基礎講座はこちら↓ zero to one G検定 人工知能基礎 jdla

AI初学者・ビジネスパーソン向けのG検定対策講座

G検定受験前にトレーニングしたい方向けの問題集「G検定実践トレーニング」も提供中です。 zero to one E資格 jdla