DCGAN “Deep Convolutional Generative Adversarial Network” に触れる Part(2)

colaboratory

はじめに

こんにちは、swim-loverです。Pythonをとりかかりとして、Pytorch Tensorflowで機械学習を勉強していいます。「使いながら覚える」をコンセプトに勉強しています。

このBlogでは、識別モデルの物体検出について何度か取り扱ってきましたが、今回、生成モデルの一つである、GAN”ギャン”についても試してみます。

Part(1)では、Generatorについて取り上げました。今回は、Discriminatorについて進めていきます。

DCGNA Discriminatorモデルの作成

Conv2D()で畳み込み演算されています。

LeakyReLU()は活性化関数。

こちらに詳しく説明されていました。

【活性化関数】Leaky ReLU関数とは?【超わかりやすく解説】
ニューラルネットワークで重要な活性化関数の1つである「Leaky ReLU関数」を、紹介します。理解のしやすいLeaky ReLU関数をPythonで実装も行いながら紹介しています。

Dropout()は、過学習の防止とのこと。出力の30%は、無効化されるようです。

こちらに詳しく説明されていました。

Dropoutによる過学習の抑制
Kerasを使用して機械学習を行い、実アプリケーションに適用する場合、いかに過学習を抑制するかが重要になります。本記事では、Dropoutによる過学習の抑制の効果を検討します。

6行目のDropoutが終わった時点で、出力データは、14x14x64となります。

10行目のDropoutが終わった時点で、出力データは、7x7x128となります。

Flatten()の多次元配列を一次元配列へ変換。

Dense(1)で、1出力に全結合します。

def make_discriminator_model():
    model = tf.keras.Sequential()
    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
                                     input_shape=[28, 28, 1]))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
    model.add(layers.LeakyReLU())
    model.add(layers.Dropout(0.3))

    model.add(layers.Flatten())
    model.add(layers.Dense(1))

    return model

Descriminatorのモデルを確認します。

generatorが生成したイメージをDescriminatorに入力してみます。

generator=make_generator_model()
noise = tf.random.normal([1, 100])
generated_image = generator(noise, training=False)

plt.imshow(generated_image[0, :, :, 0], cmap='gray')
discriminator = make_discriminator_model()
decision = discriminator(generated_image)
print (decision)

本物である確率が、-0.00020956ということでしょうか?まだ何もトレーニングしていないので、とりあえず動かしてみたということでしょう。

DCGAN 損失関数の設定

Generator、Descriminatorの出力は、本物、偽物の2つのどちらかなので、損失関数には、BinaryCrossentropyを用いています。Crossentropyについては、別の回で取り上げました。

from_logit=Trueは、クロスエントロピーの入力データである予測値y_predをlogit値(ロジット -inf~inf)として扱うか、確率値(0~1)として扱うかの設定のようです。

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

DCGAN Discriminatorの損失関数

real_outputは本物の画像をDiscriminatorに入れた時の出力、ones_like(real_output)は本物の画像ラベルを示しています。corss_entropyの結果であるreal_lossが、1であれば、正しく本物を判定したことになります。

逆に、

Fake_outputは偽物の画像をDiscriminatorに入れた時の出力、Zeros_like(fake_output)は偽物の画像ラベルを示しています。corss_entropyの結果であるfake_lossが、0であれば、正しく偽物を判定したことになります。

Real_lossとfake_lossの合計をtotal_lossとしています。

def discriminator_loss(real_output, fake_output):
    real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    total_loss = real_loss + fake_loss
    return total_loss

DCGAN Generatorの損失関数

Generatorの損失関数は、偽物画像をDiscriminatorに入れた時の出力であるfake_outputとones_like(fake_output)を比較していることに注目します。

もしfake_outputが1であれば、Genaratorが作り出した画像は、Discriminatorで偽物と判断できなかったことになります。

def generator_loss(fake_output):
    return cross_entropy(tf.ones_like(fake_output), fake_output)

DCGAN Optimiztor 最適化アルゴリズム

DiscriminatorとGeneratorのそれぞれにOptimiztorを用意します。Optimiztorは、損失を少なくしていくために用いるアルゴリズムですが、ここでは、Adamを用いています。

最適化アルゴリズムについては、以下で詳細に説明されていました。

【決定版】スーパーわかりやすい最適化アルゴリズム -損失関数からAdamとニュートン法- - Qiita
オミータです。ツイッターで人工知能のことや他媒体で書いている記事など を紹介していますので、人工知能のことをもっと知りたい方などは気軽に@omiita_atiimoをフォローしてください!【決定版…
generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)

DCGAN チェックポイント 再開用ファイルの保存

途中で学習を中断した場合、それまで学習した内容の保存し、再開する機能がTensorflowに予め用意されているようです。実用上、必須の機能だともいますので、サンプルコードをそのまま使用します。

from google.colab import drive
drive.mount('/content/drive')
cd drive/MyDrive/machine/dcgan
!pwd

今回は、checkpointをMyDrive/machne/dcgan以下に保存することにしました。

checkpoint_dir = './'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)

DCGAN パラメーター設定とノイズ生成

エポック数を50で定義。100次元のノイズデータを16個生成します。

ここで作成したseedは以降の処理では使われないようです。

EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16

# You will reuse this seed overtime (so it's easier)
# to visualize progress in the animated GIF)
seed = tf.random.normal([num_examples_to_generate, noise_dim])

DCGAN トレーニング処理

入力は、本物画像です。

6行目 generatorで画像を生成します。

7行目 本物画像を識別しています。

8行目 偽物画像を識別しています。

11行目 generatorのLoss計算

12行目 discriminatorのLoss計算

14,15行目 gradientの勾配の計算(Optimizorで使用)

17,18行目 Optimizerの実行

@tf.function
def train_step(images):
    noise = tf.random.normal([BATCH_SIZE, noise_dim])

    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
      generated_images = generator(noise, training=True)

      real_output = discriminator(images, training=True)
      fake_output = discriminator(generated_images, training=True)

      gen_loss = generator_loss(fake_output)
      disc_loss = discriminator_loss(real_output, fake_output)

    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)

    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))

DCGAN トレーニング本体

入力は、データセット、エポック数です。

6行目でtrain_step()を呼び出しています。データセット数分繰り返し、その外(2行目)で、エポック数回繰り返します。

def train(dataset, epochs):
  for epoch in range(epochs):
    start = time.time()

    for image_batch in dataset:
      train_step(image_batch)

    # Produce images for the GIF as you go
    display.clear_output(wait=True)
    generate_and_save_images(generator,
                             epoch + 1,
                             seed)

    # Save the model every 15 epochs
    if (epoch + 1) % 15 == 0:
      checkpoint.save(file_prefix = checkpoint_prefix)

    print ('Time for epoch {} is {} sec'.format(epoch + 1, time.time()-start))

  # Generate after the final epoch
  display.clear_output(wait=True)
  generate_and_save_images(generator,
                           epochs,
                           seed)

以下は、生成した画像の保存処理です。

def generate_and_save_images(model, epoch, test_input):
  # Notice `training` is set to False.
  # This is so all layers run in inference mode (batchnorm).
  predictions = model(test_input, training=False)

  fig = plt.figure(figsize=(4, 4))

  for i in range(predictions.shape[0]):
      plt.subplot(4, 4, i+1)
      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
      plt.axis('off')

  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()

DCGAN トレーニング実施!

いよいよトレーニングを実施します。

train(train_dataset, EPOCHS)

学習過程を確認してみました。

エポック1

エポック5

エポック10、数字っぽくなってきた感じ。

エポック15

エポック20

エポック30 はっきり数字と認識できる画像もあります。

エポック40

エポック50 最終のエポックです。

まとめ

今回、生成モデルの一つである、DCGANを試してみました。エポック50では、ほとんどの画像で数字と認識できるレベルまで学習できていると思います。

コメント

タイトルとURLをコピーしました