ホーム » GAN

GAN」カテゴリーアーカイブ

TensorFlow 2.0 : 上級 Tutorials : 生成 :- CycleGAN

TensorFlow 2.0 : 上級 Tutorials : 生成 :- CycleGAN (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/23/2019

* 本ページは、TensorFlow org サイトの TF 2.0 – Advanced Tutorials – Generative の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー開催中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

 

生成 :- CycleGAN

このノートブックは Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks で説明されている、CycleGAN としても知られる、条件付き GAN を使用して不対な (= unpaired) 画像から画像への変換を実演します。このペーパーはメソッドを提案します、これは一つの画像ドメインの特質を捉えてどのような対となる訓練サンプルも総て欠落しながら、これらの特質がどのようにもう一つの画像ドメインに翻訳できるかを見出すことができます。

このノートブックは貴方が Pix2Pix について馴染みがあることを仮定しています、これについては Pix2Pix チュートリアル で学習することができます。CycleGAN のためのコードも類似していますが、主な違いは追加の損失関数と、不対な訓練データの使用です。

CycleGAN はペアデータを必要とすることなく訓練を可能にするために cycle consistency 損失を使用します。換言すれば、それはソースとターゲット・ドメイン間の 1対1 マッピングなしに 1 つのドメインからもう 1 つのドメインへ変換することができます。

これは写真拡張、画像彩色、画風変換, etc. のような多くの興味深いタスクを行なう可能性を広げます。貴方が必要なことの総てはソースとターゲット・データセットです (これは単純に画像のディレクトリです)。

 

入力パイプラインをセットアップする

tensorflow_examples パッケージをインストールします、これは generator と discriminator のインポートを可能にします。

!pip install -q git+https://github.com/tensorflow/examples.git
import tensorflow as tf
from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow_datasets as tfds
from tensorflow_examples.models.pix2pix import pix2pix

import os
import time
import matplotlib.pyplot as plt
from IPython.display import clear_output

tfds.disable_progress_bar()
AUTOTUNE = tf.data.experimental.AUTOTUNE

 

入力パイプライン

このチュートリアルは馬の画像からシマウマの画像に変換するためにモデルを訓練します。このデータセット及び類似のものは ここ で見つけられます。

ペーパー で言及されているように、訓練データセットにランダムな jittering とミラーリングを適用します。これらは overfitting を回避する画像増強テクニックの幾つかです。

これは pix2pix で行われたことに類似しています

  • ランダムな jittering では、画像は 286 x 286 にリサイズされてから 256 x 256 にランダムにクロップされます。
  • ランダムなミラーリングでは、画像はランダムに水平に i.e. 左から右に反転されます。
dataset, metadata = tfds.load('cycle_gan/horse2zebra',
                              with_info=True, as_supervised=True)

train_horses, train_zebras = dataset['trainA'], dataset['trainB']
test_horses, test_zebras = dataset['testA'], dataset['testB']
Downloading and preparing dataset cycle_gan (111.45 MiB) to /home/kbuilder/tensorflow_datasets/cycle_gan/horse2zebra/0.1.0...

/home/kbuilder/.local/lib/python3.5/site-packages/urllib3/connectionpool.py:1004: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
  InsecureRequestWarning,

WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.5/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.5/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

Dataset cycle_gan downloaded and prepared to /home/kbuilder/tensorflow_datasets/cycle_gan/horse2zebra/0.1.0. Subsequent calls will reuse this data.
BUFFER_SIZE = 1000
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
def random_crop(image):
  cropped_image = tf.image.random_crop(
      image, size=[IMG_HEIGHT, IMG_WIDTH, 3])

  return cropped_image
# normalizing the images to [-1, 1]
def normalize(image):
  image = tf.cast(image, tf.float32)
  image = (image / 127.5) - 1
  return image
def random_jitter(image):
  # resizing to 286 x 286 x 3
  image = tf.image.resize(image, [286, 286],
                          method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

  # randomly cropping to 256 x 256 x 3
  image = random_crop(image)

  # random mirroring
  image = tf.image.random_flip_left_right(image)

  return image
def preprocess_image_train(image, label):
  image = random_jitter(image)
  image = normalize(image)
  return image
def preprocess_image_test(image, label):
  image = normalize(image)
  return image
train_horses = train_horses.map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

train_zebras = train_zebras.map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

test_horses = test_horses.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

test_zebras = test_zebras.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)
sample_horse = next(iter(train_horses))
sample_zebra = next(iter(train_zebras))
plt.subplot(121)
plt.title('Horse')
plt.imshow(sample_horse[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Horse with random jitter')
plt.imshow(random_jitter(sample_horse[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7fed182e1cf8>

plt.subplot(121)
plt.title('Zebra')
plt.imshow(sample_zebra[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Zebra with random jitter')
plt.imshow(random_jitter(sample_zebra[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7fed182be7f0>

 

Pix2Pix モデルをインポートして再利用する

Pix2Pix で使用された generator と discriminator をインストールされた tensorflow_examples パッケージを通してインポートします。

このチュートリアルで使用されるモデル・アーキテクチャは pix2pix で使用されたものに非常に類似しています。違いの幾つかは :

ここで訓練される 2 generator ($G$ と $F$) と 2 discriminators (X と Y) (訳注: 原文ママ、正しくは $D_X$ と $D_Y$) があります。

  • Generator $G$ は画像 X から画像 Y への変換を学習します。$(G: X -> Y)$
  • Generator $F$ は画像 Y から画像 X への変換を学習します。$(F: Y -> X)$
  • Discriminator $D_X$ は画像 X と生成された画像 X (F(Y)) を識別します。
  • Discriminator $D_Y$ は画像 Y と生成された画像 Y (G(X)) を識別します。

OUTPUT_CHANNELS = 3

generator_g = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')
generator_f = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')

discriminator_x = pix2pix.discriminator(norm_type='instancenorm', target=False)
discriminator_y = pix2pix.discriminator(norm_type='instancenorm', target=False)
to_zebra = generator_g(sample_horse)
to_horse = generator_f(sample_zebra)
plt.figure(figsize=(8, 8))
contrast = 8

imgs = [sample_horse, to_zebra, sample_zebra, to_horse]
title = ['Horse', 'To Zebra', 'Zebra', 'To Horse']

for i in range(len(imgs)):
  plt.subplot(2, 2, i+1)
  plt.title(title[i])
  if i % 2 == 0:
    plt.imshow(imgs[i][0] * 0.5 + 0.5)
  else:
    plt.imshow(imgs[i][0] * 0.5 * contrast + 0.5)
plt.show()
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

plt.figure(figsize=(8, 8))

plt.subplot(121)
plt.title('Is a real zebra?')
plt.imshow(discriminator_y(sample_zebra)[0, ..., -1], cmap='RdBu_r')

plt.subplot(122)
plt.title('Is a real horse?')
plt.imshow(discriminator_x(sample_horse)[0, ..., -1], cmap='RdBu_r')

plt.show()

 

損失関数

CycleGAN では、訓練するペアとなるデータはありません、そのため訓練の間入力 x とターゲット y ペアに意味がある保証はありません。このためネットワークが正しいマッピングを学習することを強要するために、著者は cycle consistency 損失を提案しています。

discriminator 損失と generator 損失は pix2pix で使用されたものに類似しています。

LAMBDA = 10
loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real, generated):
  real_loss = loss_obj(tf.ones_like(real), real)

  generated_loss = loss_obj(tf.zeros_like(generated), generated)

  total_disc_loss = real_loss + generated_loss

  return total_disc_loss * 0.5
def generator_loss(generated):
  return loss_obj(tf.ones_like(generated), generated)

cycle consistency は結果は元の入力に近くあるべきといういう意味です。例えば、もしセンテンスを英語からフランス語に翻訳して、それからフランス語から英語に翻訳し戻す場合、結果としてのセンテンスは元のセンテンスと同じであるべきということです。

cycle consistency 損失では、

  • 画像 $X$ は generator $G$ を通して渡されます、これは生成画像 $\hat{Y}$ を生成します。
  • 生成画像 $\hat{Y}$ は generator $F$ を通して渡されます、これは cycled 画像 $\hat{X}$ を生成します。
  • $X$ と $\hat{X}$ の間の mean absolute error (平均絶対誤差) が計算されます。
$$forward\ cycle\ consistency\ loss: X -> G(X) -> F(G(X)) \sim \hat{X}$$
$$backward\ cycle\ consistency\ loss: Y -> F(Y) -> G(F(Y)) \sim \hat{Y}$$

def calc_cycle_loss(real_image, cycled_image):
  loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))
  
  return LAMBDA * loss1

上で示されるように、generator $G$ は画像 $X$ を画像 $Y$ に変換する責任を負います。Identity 損失は、画像 $Y$ を generator $G$ に供給した場合、それは real 画像 $Y$ か画像 $Y$ に近い何かを生成するべきであると言っています。

$$Identity\ loss = |G(Y) – Y| + |F(X) – X|$$
def identity_loss(real_image, same_image):
  loss = tf.reduce_mean(tf.abs(real_image - same_image))
  return LAMBDA * 0.5 * loss

総ての generator と discriminator のために optimizer を初期化します。

generator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
generator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

discriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

 

チェックポイント

checkpoint_path = "./checkpoints/train"

ckpt = tf.train.Checkpoint(generator_g=generator_g,
                           generator_f=generator_f,
                           discriminator_x=discriminator_x,
                           discriminator_y=discriminator_y,
                           generator_g_optimizer=generator_g_optimizer,
                           generator_f_optimizer=generator_f_optimizer,
                           discriminator_x_optimizer=discriminator_x_optimizer,
                           discriminator_y_optimizer=discriminator_y_optimizer)

ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)

# if a checkpoint exists, restore the latest checkpoint.
if ckpt_manager.latest_checkpoint:
  ckpt.restore(ckpt_manager.latest_checkpoint)
  print ('Latest checkpoint restored!!')

 

訓練

Note: このチュートリアルのために訓練時間を合理的に保つためにこのサンプルモデルはペーパー (200) よりも少ないエポック (40) の間訓練されます。予測は少し正確ではないかもしれません。

EPOCHS = 40
def generate_images(model, test_input):
  prediction = model(test_input)
    
  plt.figure(figsize=(12, 12))

  display_list = [test_input[0], prediction[0]]
  title = ['Input Image', 'Predicted Image']

  for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.title(title[i])
    # getting the pixel values between [0, 1] to plot it.
    plt.imshow(display_list[i] * 0.5 + 0.5)
    plt.axis('off')
  plt.show()

訓練ループは複雑に見えますけれども、それは 4 つの基本ステップから成ります :

  • 予測を得る。
  • 損失を計算する。
  • 逆伝播を使用して勾配を計算する。
  • 勾配を optimizer に適用する。
@tf.function
def train_step(real_x, real_y):
  # persistent is set to True because the tape is used more than
  # once to calculate the gradients.
  with tf.GradientTape(persistent=True) as tape:
    # Generator G translates X -> Y
    # Generator F translates Y -> X.
    
    fake_y = generator_g(real_x, training=True)
    cycled_x = generator_f(fake_y, training=True)

    fake_x = generator_f(real_y, training=True)
    cycled_y = generator_g(fake_x, training=True)

    # same_x and same_y are used for identity loss.
    same_x = generator_f(real_x, training=True)
    same_y = generator_g(real_y, training=True)

    disc_real_x = discriminator_x(real_x, training=True)
    disc_real_y = discriminator_y(real_y, training=True)

    disc_fake_x = discriminator_x(fake_x, training=True)
    disc_fake_y = discriminator_y(fake_y, training=True)

    # calculate the loss
    gen_g_loss = generator_loss(disc_fake_y)
    gen_f_loss = generator_loss(disc_fake_x)
    
    total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)
    
    # Total generator loss = adversarial loss + cycle loss
    total_gen_g_loss = gen_g_loss + total_cycle_loss + identity_loss(real_y, same_y)
    total_gen_f_loss = gen_f_loss + total_cycle_loss + identity_loss(real_x, same_x)

    disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)
    disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)
  
  # Calculate the gradients for generator and discriminator
  generator_g_gradients = tape.gradient(total_gen_g_loss, 
                                        generator_g.trainable_variables)
  generator_f_gradients = tape.gradient(total_gen_f_loss, 
                                        generator_f.trainable_variables)
  
  discriminator_x_gradients = tape.gradient(disc_x_loss, 
                                            discriminator_x.trainable_variables)
  discriminator_y_gradients = tape.gradient(disc_y_loss, 
                                            discriminator_y.trainable_variables)
  
  # Apply the gradients to the optimizer
  generator_g_optimizer.apply_gradients(zip(generator_g_gradients, 
                                            generator_g.trainable_variables))

  generator_f_optimizer.apply_gradients(zip(generator_f_gradients, 
                                            generator_f.trainable_variables))
  
  discriminator_x_optimizer.apply_gradients(zip(discriminator_x_gradients,
                                                discriminator_x.trainable_variables))
  
  discriminator_y_optimizer.apply_gradients(zip(discriminator_y_gradients,
                                                discriminator_y.trainable_variables))
for epoch in range(EPOCHS):
  start = time.time()

  n = 0
  for image_x, image_y in tf.data.Dataset.zip((train_horses, train_zebras)):
    train_step(image_x, image_y)
    if n % 10 == 0:
      print ('.', end='')
    n+=1

  clear_output(wait=True)
  # Using a consistent image (sample_horse) so that the progress of the model
  # is clearly visible.
  generate_images(generator_g, sample_horse)

  if (epoch + 1) % 5 == 0:
    ckpt_save_path = ckpt_manager.save()
    print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
                                                         ckpt_save_path))

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

Saving checkpoint for epoch 40 at ./checkpoints/train/ckpt-8
Time taken for epoch 40 is 183.51983284950256 sec

 

テストデータセットを使用して生成する

# Run the trained model on the test dataset
for inp in test_horses.take(5):
  generate_images(generator_g, inp)

 

Next Steps

このチュートリアルは Pix2Pix チュートリアルで実装された generator と discriminator から始めて CycleGAN をどのように実装するかを示しました。次のステップとして、TensorFlow データセット からの異なるデータセットを使用して試すことができます。

貴方はまた結果を改善するためにより大きな数のエポックの間訓練することもできます、あるいはここで使用された U-Net generator の代わりに ペーパー で使用された修正された ResNet generator を実装することもできるでしょう。

 

以上






TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- CycleGAN

TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- CycleGAN (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/11/2019

* 本ページは、TensorFlow の本家サイトの TF 2.0 Beta – Advanced Tutorials – Image generation の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

 

画像生成 :- CycleGAN

このノートブックは Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks で説明されている、CycleGAN としても知られる、条件付き GAN を使用して不対の (= unpaired) 画像から画像への変換 (= translation) を実演します。このペーパーはメソッドを提案します、それを通して一つの画像ドメインの特質を捉えてどのような対となる訓練サンプルも欠落しながら、これらの特質がどのようにもう一つの画像ドメインに翻訳できるかを見出します。

このノートブックは貴方が Pix2Pix について馴染みがあることを仮定しています、これについては Pix2Pix チュートリアル で学習することができます。CycleGAN のためのコードも類似していますが、主な違いは追加の損失関数と、不対の訓練データの使用です。

CycleGAN はペアデータを必要とせずに訓練を可能にするために cycle consistency 損失を使用します。換言すれば、それはソースとターゲットドメイン間の 1対1 マッピングなしに一つのドメインからもう一つのドメインへ変換することができます。

これは写真拡張、画像彩色、画風変換, etc. のような多くの興味深いタスクを行なう可能性を広げます。貴方が必要なことの総てはソースとターゲット・データセットです (これは単純に画像のディレクトリです)。

 

入力パイプラインをセットアップする

tensorflow_examples パッケージをインストールします、これは generator と discriminator のインポートを可能にします。

!pip install -q git+https://github.com/tensorflow/examples.git
!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf
from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow_datasets as tfds
from tensorflow_examples.models.pix2pix import pix2pix

import os
import time
import matplotlib.pyplot as plt
from IPython.display import clear_output

tfds.disable_progress_bar()
AUTOTUNE = tf.data.experimental.AUTOTUNE

 

入力パイプライン

このチュートリアルは馬の画像からシマウマの画像に変換するためにモデルを訓練します。このデータセットと類似のものは ここ で見つけられます。

ペーパー で言及されているように、訓練データセットにランダムな jittering とミラーリングを適用します。これらは overfitting を回避する画像増強テクニックの幾つかです。

これは pix2pix で行われたことに類似しています * ランダムな jittering では、画像は 286 x 286 にリサイズされてから 256 x 256 にランダムにクロップされます。* ランダムなミラーリングでは、画像はランダムに水平 i.e. 左から右に反転されます。

dataset, metadata = tfds.load('cycle_gan/horse2zebra',
                              with_info=True, as_supervised=True)

train_horses, train_zebras = dataset['trainA'], dataset['trainB']
test_horses, test_zebras = dataset['testA'], dataset['testB']
Downloading and preparing dataset cycle_gan (111.45 MiB) to /home/kbuilder/tensorflow_datasets/cycle_gan/horse2zebra/0.1.0...

/home/kbuilder/.local/lib/python3.5/site-packages/urllib3/connectionpool.py:851: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
  InsecureRequestWarning)
WARNING: Logging before flag parsing goes to stderr.
W0628 01:59:12.853749 140396912944896 deprecation.py:323] From /home/kbuilder/.local/lib/python3.5/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

Dataset cycle_gan downloaded and prepared to /home/kbuilder/tensorflow_datasets/cycle_gan/horse2zebra/0.1.0. Subsequent calls will reuse this data.
BUFFER_SIZE = 1000
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
def random_crop(image):
  cropped_image = tf.image.random_crop(
      image, size=[IMG_HEIGHT, IMG_WIDTH, 3])

  return cropped_image
# normalizing the images to [-1, 1]
def normalize(image):
  image = tf.cast(image, tf.float32)
  image = (image / 127.5) - 1
  return image
def random_jitter(image):
  # resizing to 286 x 286 x 3
  image = tf.image.resize(image, [286, 286],
                          method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

  # randomly cropping to 256 x 256 x 3
  image = random_crop(image)

  # random mirroring
  image = tf.image.random_flip_left_right(image)

  return image
def preprocess_image_train(image, label):
  image = random_jitter(image)
  image = normalize(image)
  return image
def preprocess_image_test(image, label):
  image = normalize(image)
  return image
train_horses = train_horses.map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

train_zebras = train_zebras.map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

test_horses = test_horses.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)

test_zebras = test_zebras.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(1)
sample_horse = next(iter(train_horses))
sample_zebra = next(iter(train_zebras))
plt.subplot(121)
plt.title('Horse')
plt.imshow(sample_horse[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Horse with random jitter')
plt.imshow(random_jitter(sample_horse[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7fb0100e4e10>

plt.subplot(121)
plt.title('Zebra')
plt.imshow(sample_zebra[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Zebra with random jitter')
plt.imshow(random_jitter(sample_zebra[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7fb029232898>

 

Pix2Pix モデルをインポートして再利用する

Pix2Pix で使用された generator と discriminator をインストールされた tensorflow_examples パッケージを通してインポートします。

このチュートリアルで使用されるモデル・アーキテクチャは pix2pix で使用されたものに非常に類似しています。違いの幾つかは :

ここで訓練される 2 generator ($G$ と $F$) と 2 discriminators (X と Y) (訳注: 原文ママ、正しくは $D_X$ と $D_Y$) があります。

  • Generator $G$ は画像 X から画像 Y への変換を学習します。$(G: X -> Y)$
  • Generator $F$ は画像 Y から画像 X への変換を学習します。$(F: Y -> X)$
  • Discriminator $D_X$ は画像 X と生成された画像 X (F(Y)) を識別します。
  • Discriminator $D_Y$ は画像 Y と生成された画像 Y (G(X)) を識別します。

OUTPUT_CHANNELS = 3

generator_g = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')
generator_f = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')

discriminator_x = pix2pix.discriminator(norm_type='instancenorm', target=False)
discriminator_y = pix2pix.discriminator(norm_type='instancenorm', target=False)
to_zebra = generator_g(sample_horse)
to_horse = generator_f(sample_zebra)
plt.figure(figsize=(8, 8))
contrast = 8

imgs = [sample_horse, to_zebra, sample_zebra, to_horse]
title = ['Horse', 'To Zebra', 'Zebra', 'To Horse']

for i in range(len(imgs)):
  plt.subplot(2, 2, i+1)
  plt.title(title[i])
  if i % 2 == 0:
    plt.imshow(imgs[i][0] * 0.5 + 0.5)
  else:
    plt.imshow(imgs[i][0] * 0.5 * contrast + 0.5)
plt.show()

plt.figure(figsize=(8, 8))

plt.subplot(121)
plt.title('Is a real zebra?')
plt.imshow(discriminator_y(sample_zebra)[0, ..., -1], cmap='RdBu_r')

plt.subplot(122)
plt.title('Is a real horse?')
plt.imshow(discriminator_x(sample_horse)[0, ..., -1], cmap='RdBu_r')

plt.show()

 

損失関数

CycleGAN では、訓練するペアとなるデータはありません、そのため訓練の間入力 x とターゲット y ペアに意味がある保証はありません。このためネットワークが正しいマッピングを学習することを強要するために、著者は cycle consistency 損失を提案しています。

discriminator 損失と generator 損失は pix2pix で使用されたものに類似しています。

LAMBDA = 10
loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real, generated):
  real_loss = loss_obj(tf.ones_like(real), real)

  generated_loss = loss_obj(tf.zeros_like(generated), generated)

  total_disc_loss = real_loss + generated_loss

  return total_disc_loss * 0.5
def generator_loss(generated):
  return loss_obj(tf.ones_like(generated), generated)

cycle consistency は結果は元の入力に近くあるべきといういう意味です。例えば、もしセンテンスを英語からフランス語に翻訳して、それからフランス語から英語に翻訳し戻す場合、結果としてのセンテンスは元のセンテンスと同じであるべきということです。

cycle consistency 損失では、

  • 画像 $X$ は generator $G$ を通して渡されます、これは生成画像 $\hat{Y}$ を生成します。
  • 生成画像 $\hat{Y}$ は generator $F$ を通して渡されます、これは cycled 画像 $\hat{X}$ を生成します。
  • $X$ と $\hat{X}$ の間の mean absolute error (平均絶対誤差) が計算されます。
$$forward\ cycle\ consistency\ loss: X -> G(X) -> F(G(X)) \sim \hat{X}$$
$$backward\ cycle\ consistency\ loss: Y -> F(Y) -> G(F(Y)) \sim \hat{Y}$$

def calc_cycle_loss(real_image, cycled_image):
  loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))
  
  return LAMBDA * loss1

上で示されるように、generator $G$ は画像 $X$ を画像 $Y$ に変換する責任を負います。Identity 損失は、画像 $Y$ が generator $G$ に供給された場合、それは real 画像 $Y$ か画像 $Y$ に近い何かを生成するべきであると言っています。

$$Identity\ loss = |G(Y) – Y| + |F(X) – X|$$
def identity_loss(real_image, same_image):
  loss = tf.reduce_mean(tf.abs(real_image - same_image))
  return LAMBDA * 0.5 * loss

総ての generator と discriminator のために optimizer を初期化します。

generator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
generator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

discriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

 

チェックポイント

checkpoint_path = "./checkpoints/train"

ckpt = tf.train.Checkpoint(generator_g=generator_g,
                           generator_f=generator_f,
                           discriminator_x=discriminator_x,
                           discriminator_y=discriminator_y,
                           generator_g_optimizer=generator_g_optimizer,
                           generator_f_optimizer=generator_f_optimizer,
                           discriminator_x_optimizer=discriminator_x_optimizer,
                           discriminator_y_optimizer=discriminator_y_optimizer)

ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)

# if a checkpoint exists, restore the latest checkpoint.
if ckpt_manager.latest_checkpoint:
  ckpt.restore(ckpt_manager.latest_checkpoint)
  print ('Latest checkpoint restored!!')

 

訓練

Note: このチュートリアルのために訓練時間を合理的に保つためにこのサンプルモデルはペーパー (200) よりも少ないエポック (40) の間訓練されます。予測は少し正確ではないかもしれません。

EPOCHS = 40
def generate_images(model, test_input):
  prediction = model(test_input)
    
  plt.figure(figsize=(12, 12))

  display_list = [test_input[0], prediction[0]]
  title = ['Input Image', 'Predicted Image']

  for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.title(title[i])
    # getting the pixel values between [0, 1] to plot it.
    plt.imshow(display_list[i] * 0.5 + 0.5)
    plt.axis('off')
  plt.show()

訓練ループは複雑に見えますけれども、それは 4 つの基本ステップから成ります : * 予測を得る。* 損失を計算する。* 逆伝播を使用して勾配を計算する。* optimizer に勾配を適用する。

@tf.function
def train_step(real_x, real_y):
  # persistent is set to True because gen_tape and disc_tape is used more than
  # once to calculate the gradients.
  with tf.GradientTape(persistent=True) as gen_tape, tf.GradientTape(
      persistent=True) as disc_tape:
    # Generator G translates X -> Y
    # Generator F translates Y -> X.
    
    fake_y = generator_g(real_x, training=True)
    cycled_x = generator_f(fake_y, training=True)

    fake_x = generator_f(real_y, training=True)
    cycled_y = generator_g(fake_x, training=True)

    # same_x and same_y are used for identity loss.
    same_x = generator_f(real_x, training=True)
    same_y = generator_g(real_y, training=True)

    disc_real_x = discriminator_x(real_x, training=True)
    disc_real_y = discriminator_y(real_y, training=True)

    disc_fake_x = discriminator_x(fake_x, training=True)
    disc_fake_y = discriminator_y(fake_y, training=True)

    # calculate the loss
    gen_g_loss = generator_loss(disc_fake_y)
    gen_f_loss = generator_loss(disc_fake_x)
    
    # Total generator loss = adversarial loss + cycle loss
    total_gen_g_loss = gen_g_loss + calc_cycle_loss(real_x, cycled_x) + identity_loss(real_x, same_x)
    total_gen_f_loss = gen_f_loss + calc_cycle_loss(real_y, cycled_y) + identity_loss(real_y, same_y)

    disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)
    disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)
  
  # Calculate the gradients for generator and discriminator
  generator_g_gradients = gen_tape.gradient(total_gen_g_loss, 
                                            generator_g.trainable_variables)
  generator_f_gradients = gen_tape.gradient(total_gen_f_loss, 
                                            generator_f.trainable_variables)
  
  discriminator_x_gradients = disc_tape.gradient(
      disc_x_loss, discriminator_x.trainable_variables)
  discriminator_y_gradients = disc_tape.gradient(
      disc_y_loss, discriminator_y.trainable_variables)
  
  # Apply the gradients to the optimizer
  generator_g_optimizer.apply_gradients(zip(generator_g_gradients, 
                                             generator_g.trainable_variables))

  generator_f_optimizer.apply_gradients(zip(generator_f_gradients, 
                                             generator_f.trainable_variables))
  
  discriminator_x_optimizer.apply_gradients(
      zip(discriminator_x_gradients,
      discriminator_x.trainable_variables))
  
  discriminator_y_optimizer.apply_gradients(
      zip(discriminator_y_gradients,
      discriminator_y.trainable_variables))
for epoch in range(EPOCHS):
  start = time.time()

  n = 0
  for image_x, image_y in tf.data.Dataset.zip((train_horses, train_zebras)):
    train_step(image_x, image_y)
    if n % 10 == 0:
      print ('.', end='')
    n+=1

  clear_output(wait=True)
  # Using a consistent image (sample_horse) so that the progress of the model
  # is clearly visible.
  generate_images(generator_g, sample_horse)

  if (epoch + 1) % 5 == 0:
    ckpt_save_path = ckpt_manager.save()
    print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
                                                         ckpt_save_path))

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

Time taken for epoch 26 is 109.30256915092468 sec

........................................

 

テストデータセットを使用して生成する

# Run the trained model on the test dataset
for inp in test_horses.take(5):
  generate_images(generator_g, inp)

 

Next Steps

このチュートリアルは Pix2Pix チュートリアルで実装された generator と discriminator から始めて CycleGAN をどのように実装するかを示しました。次のステップとして、TensorFlow データセット からの異なるデータセットを使用して試すことができます。

貴方はまた結果を改善するためにより大きなエポック数の間訓練することもできます、あるいはここで使用された U-Net generator の代わりに ペーパー で使用された修正された ResNet generator を実装することもできます。

 

以上






TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- 深層畳み込み敵対的生成ネットワーク

TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- 深層畳み込み敵対的生成ネットワーク (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/08/2019

* 本ページは、TensorFlow の本家サイトの TF 2.0 Beta – Advanced Tutorials – Image generation の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

画像生成 :- 深層畳み込み敵対的生成ネットワーク

このチュートリアルは DCGAN (Deep Convolutional Generative Adversarial Network, 深層畳み込み敵対的生成ネットワーク) を使用して手書き数字の画像をどのように生成するかを実演します。コードは tf.GradientTape 訓練ループで Keras Sequential API を使用して書かれています。

 

GAN とは何でしょう?

敵対的生成ネットワーク (GAN, Generative Adversarial Networks) は現在コンピュータ・サイエンスで最も興味深いアイデアの一つです。2 つのモデルが敵対するプロセスにより同時に訓練されます。generator (「芸術家 = the artist」) がリアルに見える画像を作成することを学習し、その一方で discriminator (「芸術評論家 = the art critic」) がフェイクからリアル画像を識別することを学習します。

訓練の間、generator はリアルに見える画像を作成することに次第に上達し、その一方で discriminator はそれらを識別することに上達します。discriminator がもはやリアル画像をフェイクから識別できないとき、プロセスは均衡に達します。

このノートブックは MNIST データセット上でこのプロセスを実演します。次のアニメーションは (generator が) 50 エポックの間訓練されたとき generator により生成された画像のシリーズを示します。画像はランダム・ノイズとして始まり、そして時間とともに手書き数字にだんだん似ていきます。

 

TensorFlow と他のライブラリをインポートする

from __future__ import absolute_import, division, print_function, unicode_literals
!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf
tf.__version__
'2.0.0-beta1'
# To generate GIFs
!pip install -q imageio
import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
from tensorflow.keras import layers
import time

from IPython import display

 

データセットをロードして準備する

generator と discriminator を訓練するために MNIST データセットを使用していきます。generator は MNIST データに似ている手書き数字を生成します。

(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # Normalize the images to [-1, 1]
BUFFER_SIZE = 60000
BATCH_SIZE = 256
# Batch and shuffle the data
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)

 

モデルを作成する

generator と discriminator の両者は Keras Sequential API を使用して定義されます。

 

Generator

generator は seed (ランダムノイズ) から画像を生成するために tf.keras.layers.Conv2DTranspose (upsampling) 層を使用します。この seed を入力として取る Dense 層から始めて、それから 28x28x1 の望まれるサイズに達するまで幾度か upsample します。tanh を使用する出力層を除いて、各層のためには tf.keras.layers.LeakyReLU 活性が (使用されることが) 分かるでしょう。

def make_generator_model():
    model = tf.keras.Sequential()
    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Reshape((7, 7, 256)))
    assert model.output_shape == (None, 7, 7, 256) # Note: None is the batch size

    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))
    assert model.output_shape == (None, 7, 7, 128)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
    assert model.output_shape == (None, 14, 14, 64)
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
    assert model.output_shape == (None, 28, 28, 1)

    return model

画像を作成するために (まだ訓練されていない) generator を使用します。

generator = make_generator_model()

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

plt.imshow(generated_image[0, :, :, 0], cmap='gray')
<matplotlib.image.AxesImage at 0x7f02a7556710>

 

Discriminator

discriminator は CNN-ベースの画像分類器です。

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

生成された画像をリアルかフェイクとして分類するために (まだ訓練されていない) discriminator を使用します。モデルはリアル画像のためにはポジティブ値を、そしてフェイク画像のためにはネガティブ値を出力するように訓練されます。

discriminator = make_discriminator_model()
decision = discriminator(generated_image)
print (decision)
tf.Tensor([[0.00165603]], shape=(1, 1), dtype=float32)

 

損失と optimizer を定義する

両者のモデルのために損失関数と optimizer を定義します。

# This method returns a helper function to compute cross entropy loss
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

 

Discriminator 損失

このメソッドは discriminator がフェイク (画像) からリアル画像をどの程度上手く識別できるかを数値化します。それはリアル画像上の discriminator の予測を 1 の配列と、そしてフェイク (生成された) 画像上の discriminator の予測を 0 の配列と比較します。

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

 

Generator 損失

generator 損失はそれがどの程度上手く discriminator を欺くことができたかを数値化します。直感的には、generator が上手く遂行していれば、discriminator はフェイク画像をリアル (or 1) として分類するでしょう。ここでは、生成された画像上の discriminator の決定を 1 の配列と比較します。

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

discriminator と generator の optimizer は異なります、何故ならば 2 つのネットワークを別々に訓練するからです。

generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)

 

チェックポイントをセーブする

このノートブックはモデルをどのようにセーブしてリストアするかも実演します、これは長い実行訓練タスクが中断された場合に有用であり得ます。

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

 

訓練ループを定義する

EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16

# We 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])

訓練ループは generator が入力としてランダムシードを受け取ることから開始されます。そのシードは画像を生成するために使用されます。それから discriminator は (訓練セットからドローされた) リアル画像と (generator により生成された) フェイク画像を分類するために使用されます。損失はこれらのモデルの各々のために計算されて、勾配は generator と discriminator を更新するために使用されます。

# Notice the use of `tf.function`
# This annotation causes the function to be "compiled".
@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))
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 we 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()

 

モデルを訓練する

generator と discriminator を同時に訓練するためには上で定義された train() メソッドを呼び出します。注意してください、GAN の訓練はトリッキーかもしれません。generator と discriminator は互いに圧倒 (= overpower) しないこと (e.g., それらは同様なレートで訓練します) は重要です。

訓練の最初では、生成された画像はランダムノイズのように見えます。訓練が進むにつれて、生成される数字は段々とリアルに見えます。およそ 50 エポック後、それらは MNIST 数字に似ます。Colab のデフォルト設定で約 1 分 / エポックかかるかもしれません。

%%time
train(train_dataset, EPOCHS)

CPU times: user 2min 33s, sys: 31.3 s, total: 3min 5s
Wall time: 3min 48s

最新のチェックポイントをリストアします。

checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f01c00eecc0>

 

GIF を作成する

# Display a single image using the epoch number
def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
display_image(EPOCHS)

訓練の間にセーブされた画像を使用して animated gif を作成するために imageio を使用します。

anim_file = 'dcgan.gif'

with imageio.get_writer(anim_file, mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)

import IPython
if IPython.version_info > (6,2,0,''):
  display.Image(filename=anim_file)

Colab で作業しているのであれば、下のコードでアニメーションをダウンロードできます :

try:
  from google.colab import files
except ImportError:
  pass
else:
  files.download(anim_file)

 

Next steps

このチュートリアルは GAN を書いて訓練するために必要な完全なコードを示しました。次のステップとして、異なるデータセットで実験することを望むかもしれません、例えば、Kaggle で利用可能な Large-scale Celeb Faces Attributes (CelebA) データセットです。GAN についてより多く学習するためには NIPS 2016 Tutorial: Generative Adversarial Networks を勧めます。

 

以上






TensorFlow 2.0 Alpha : 上級 Tutorials : GAN と VAE :- 深層畳み込み敵対的生成ネットワーク

TensorFlow 2.0 Alpha : 上級 Tutorials : GAN と VAE :- 深層畳み込み敵対的生成ネットワーク (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 04/10/2019

* 本ページは、TensorFlow の本家サイトの TF 2.0 Alpha – Advanced Tutorials – GANs and VAEs の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

GAN と VAE :- 深層畳み込み敵対的生成ネットワーク

このチュートリアルは DCGAN (Deep Convolutional Generative Adversarial Network, 深層畳み込み敵対的生成ネットワーク) を使用して手書き数字の画像をどのように生成するかを実演します。コードは tf.GradientTape 訓練ループで Keras Sequential API を使用して書かれています。

 

GAN とは何でしょう?

敵対的生成ネットワーク (GAN, Generative Adversarial Networks) は現在コンピュータ・サイエンスで最も興味深いアイデアの一つです。2 つのモデルが敵対するプロセスにより同時に訓練されます。generator (「芸術家 = the artist」) がリアルに見える画像を作成することを学習し、その一方で discriminator (「芸術評論家 = the art critic」) がフェイクからリアル画像を識別することを学習します。

訓練の間、generator はリアルに見える画像を作成することに次第に上達し、その一方でdiscriminator はそれらを識別することに上達します。discriminator がもはやリアル画像をフェイクから識別できないとき、プロセスは均衡に達します。

このノートブックは MNIST データセット上でこのプロセスを実演します。次のアニメーションは 50 エポックの間訓練された generator により生成された画像のシリーズを示します。画像はランダム・ノイズとして始まり、そして時間とともに手書き数字にだんだん似ていきます。

 

TensorFlow と他のライブラリをインポートする

from __future__ import absolute_import, division, print_function, unicode_literals
!pip install -q tensorflow-gpu==2.0.0-alpha0
Collecting tensorflow-gpu==2.0.0-alpha0
[?25l  Downloading https://files.pythonhosted.org/packages/1a/66/32cffad095253219d53f6b6c2a436637bbe45ac4e7be0244557210dc3918/tensorflow_gpu-2.0.0a0-cp36-cp36m-manylinux1_x86_64.whl (332.1MB)
Successfully installed tensorflow-gpu-2.0.0a0
import tensorflow as tf
tf.__version__
'2.0.0-alpha0'
# To generate GIFs
!pip install -q imageio
Requirement already satisfied: imageio in /usr/local/lib/python3.6/dist-packages (2.4.1)
Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from imageio) (1.14.6)
Requirement already satisfied: pillow in /usr/local/lib/python3.6/dist-packages (from imageio) (4.1.1)
Requirement already satisfied: olefile in /usr/local/lib/python3.6/dist-packages (from pillow->imageio) (0.46)
import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
from tensorflow.keras import layers
import time

from IPython import display

 

データセットをロードして準備する

generator と discriminator を訓練するために MNIST データセットを使用していきます。generator は MNIST データに似ている手書き数字を生成します。

(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
train_images = (train_images - 127.5) / 127.5 # Normalize the images to [-1, 1]
BUFFER_SIZE = 60000
BATCH_SIZE = 256
# Batch and shuffle the data
train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)

 

モデルを作成する

generator と discriminator の両者は Keras Sequential API を使用して定義されます。

 

Generator

generator は seed (ランダムノイズ) から画像を生成するために tf.keras.layers.Conv2DTranspose (upsampling) 層を使用します。この seed を入力として取る Dense 層から始めて、それから 28x28x1 の望まれるサイズに達するまで幾度か upsample します。tanh を使用する出力層を除いて、各層のためには tf.keras.layers.LeakyReLU 活性が (使用されることが) 分かるでしょう。

def make_generator_model():
    model = tf.keras.Sequential()
    model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())
      
    model.add(layers.Reshape((7, 7, 256)))
    assert model.output_shape == (None, 7, 7, 256) # Note: None is the batch size
    
    model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))
    assert model.output_shape == (None, 7, 7, 128)  
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
    assert model.output_shape == (None, 14, 14, 64)    
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU())

    model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
    assert model.output_shape == (None, 28, 28, 1)
  
    return model

画像を作成するために (まだ訓練されていない) generator を使用します。

generator = make_generator_model()

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

plt.imshow(generated_image[0, :, :, 0], cmap='gray')
<matplotlib.image.AxesImage at 0x7f18c841c6d8>

 

Discriminator

discriminator は CNN-ベースの画像分類器です。

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

生成された画像をリアルかフェイクか分類するために (まだ訓練されていない) discriminator を使用します。モデルはリアル画像のためにはポジティブ値を、そしてフェイク画像のためにはネガティブ値を出力するように訓練されます。

discriminator = make_discriminator_model()
decision = discriminator(generated_image)
print (decision)
tf.Tensor([[-0.00070298]], shape=(1, 1), dtype=float32)

 

損失と optimizer を定義する

両者のモデルのために損失関数と optimizer を定義します。

# This method returns a helper function to compute cross entropy loss
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)

 

Discriminator 損失

このメソッドは discriminator がフェイク画像からリアル画像をどの程度上手く識別できるかを数値化します。それはリアル画像上の discriminator の予測を 1 の配列と、そしてフェイク (生成された) 画像上の discriminator の予測を 0 の配列と比較します。

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

 

Generator 損失

generator 損失はそれがどの程度上手く discriminator を欺くことができたかを数値化します。直感的には、generator が上手く遂行していれば、discriminator はフェイク画像をリアル (or 1) として分類するでしょう。ここでは、生成された画像上の discriminator の決定を 1 の配列と比較します。

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

discriminator and the generator の optimizer は異なります、何故ならば 2 つのネットワークを別々に訓練するからです。

generator_optimizer = tf.keras.optimizers.Adam(1e-4)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)

 

チェックポイントをセーブする

このノートブックはモデルをどのようにセーブしてリストアするかも実演します、これは訓練の長い実行が中断された場合に有用であり得ます。

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

 

訓練ループを定義する

EPOCHS = 50
noise_dim = 100
num_examples_to_generate = 16

# We 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])

訓練ループは generator が入力としてランダムシードを受け取ることから開始されます。そのシードは画像を生成するために使用されます。それから discriminator は (訓練セットからドローされた) リアル画像と (generator により生成された) フェイク画像を分類するために使用されます。損失はこれらのモデルの各々のために計算されて、勾配は generator と discriminator を更新するために使用されます。

# Notice the use of `tf.function`
# This annotation causes the function to be "compiled".
@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))
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 we 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()

 

モデルを訓練する

generator と discriminator を同時に訓練するためには上で定義された train() メソッドを呼び出します。注意してください、GAN の訓練はトリッキーです。generator と discriminator は互いに圧倒 (= overpower) しないこと (e.g., それらは同様なレートで訓練します) は重要です。

訓練の最初では、生成された画像はランダムノイズのように見えます。訓練が進むにつれて、生成される数字は段々とリアルに見えます。およそ 50 エポック後、それらは MNIST 数字に似ます。Colab のデフォルト設定で約 1 分 / エポックかかるかもしれません。

%%time
train(train_dataset, EPOCHS)

CPU times: user 3min 10s, sys: 48.1 s, total: 3min 58s
Wall time: 10min 52s

最新のチェックポイントをリストアします。

checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f1840619d30>

 

GIF を作成する

# Display a single image using the epoch number
def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
display_image(EPOCHS)

訓練の間にセーブされた画像を使用して animated gif を作成するために imageio を使用します。

anim_file = 'dcgan.gif'

with imageio.get_writer(anim_file, mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)

import IPython
if IPython.version_info > (6,2,0,''):
  display.Image(filename=anim_file)

Colab で作業しているのであれば、下のコードでアニメーションをダウンロードできます :

try:
  from google.colab import files
except ImportError:
  pass
else:
  files.download(lite_model_path)

 

Next steps

このチュートリアルは GAN を書いて訓練するために必要な完全なコードを示しました。次のステップとして、異なるデータセットで実験することを望むかもしれません、例えば、Kaggle で利用可能な Large-scale Celeb Faces Attributes (CelebA) データセットです。GAN についてより多く学習するためには NIPS 2016 Tutorial: Generative Adversarial Networks を勧めます。

 

以上






ClassCat® TF/ONNX Hub : GAN モデル (Part II) – StarGAN, ACGAN

作成者 :(株)クラスキャット セールスインフォメーション
作成日 : 01/17/2019

 

ClassCat® TF/ONNX Hub とは

「ClassCat® TF/ONNX Hub」はクラスキャットが提供する実用性の高い機械学習モデルのレポジトリです。各モデルは TensorFlow 固有フォーマットと ONNX フォーマットの両者で提供されます。 [ClassCat® ONNX Hub 詳細]

  • ONNX (Open Neural Network Exchange) は深層学習モデルのためのオープンなフォーマットで、異なるオープンソースの深層学習フレームワーク間の相互作用を可能にします。

「ClassCat TF/ONNX Hub」で提供されるモデルについてはクラスキャットが検証の上で仕様を公開致しますので、ユーザ企業は希望するモデルを自由に選択することができます。更に (ユーザ企業の保持するデータセットによる) 再調整も含めて配備・実運用するために必要なトータルサポートを提供致します。

◆ 本ページでは提供モデル群として GAN モデル (Part II) – StarGAN, ACGAN を紹介致します。
StarGAN は顔の表情変換を可能にするモデルとして知られています。

 

 

GAN とは

GAN は敵対的生成ネットワーク (Generative Adversarial Network) と呼称される生成モデルの一種で、深層学習におけるホットな領域の一つとして様々なモデルやその応用が活発に研究されています。

GAN は 2014 年に Ian Goodfellow 氏により創案されペーパー Generative Adversarial Nets で最初に紹介されました。

基本的には 2 つのネットワーク generator と discriminator から構成され、訓練データセットの分布をネットワークに学習させるための仕組みです。その分布から新しい有用なデータを生成することができます :

  • generator のジョブは訓練画像のように見える ‘fake’ 画像を生むことです。
  • discriminator のジョブは画像を見てそれが real 訓練画像か (generator からの) fake 画像かを出力することです。

訓練の間、generator はより良い fake を生成することにより絶えず discriminator を出し抜こうとします。
一方、discriminator はより良い探偵として機能し、real 画像と fake 画像を正しく分類するために動作しています。

このゲームの均衡は、generator が訓練データに直接由来するかのように見える完全な fake 画像を生成しているときに、discriminator が generator 出力について real か fake か常に 50% の信頼度で推測する状態になることです。

 

GAN モデル (Part II) の紹介

Part II では以下の GAN モデルを紹介します :

Part I については こちら を参照してください。

 

StarGAN

StarGAN は顔の表情変換で有名になった、画像変換を主目的とする GAN の一種です。

Cycle GAN では 1 組のドメイン間の画像変換を扱いましたが、StarGAN ではマルチ・ドメイン間の変換を統合的に 1 つのモデルで処理することができます (ここでドメインは同じ属性を持つデータセットを指します)。

“Star” はアーキテクチャのトポロジーがスター型であることを表しています。ドメイン間を総て交差させる非効率性を回避しています。

マルチ・ドメインを処理するためにはクラス (属性) 情報や ACGAN (Auxiliary Classifier) のテクニックを利用しています。

オリジナルのペーパーでは RaFD (Radboud Faces Database) データセットで学習したモデルを CelebA データセットに適用してもいますが、ここでは CelebA だけをを利用したモデルを紹介します。

CelebA ではセレブ (有名人) の画像を多数含むとともに、アノテーションとして属性が示されます。このデータセットでは喜怒哀楽などの表情変換のサポートは限定的ですが、髪の色などの他の属性を多く含みます。

 

髪色の変換

髪色の変換は比較的容易です。
左端の画像が元の入力画像で順次、ブロンド・黒髪・茶髪に変換しています :

 

眼鏡の着脱

眼鏡の装着も容易です :

(眼鏡をかけている元画像は少ないですが) 眼鏡をはずした画像も生成できます :

 

老若変換

より若く画像を変換することもできます :

逆に年齢を高く変換することもできます :

 

笑顔

普通の表情を笑顔に変換することができます :

逆に笑顔を普通の表情に変換することもできます :

 

魅力的に

(どういう規準でアノテーションを作成したのか分かりませんが、) 魅力的に変換することもできます :

 

ACGAN

StarGAN の説明で ACGAN (Auxiliary Classifier) に言及しましたので簡単に説明しておきます。

ACGAN は DCGAN のような (画像変換を目的としていない) 生成モデルです。
但しノイズ (あるいは潜在変数) だけを入力とする DCGAN とは違い、クラス情報を利用します。Conditional GAN や InfoGAN と同系統の GAN になります。

Generator への入力にクラス情報を与え、Discriminator にはクラスを識別させます。

ACGAN モデルで CIFAR-10 画像を生成してみます。元画像が小さく粗いので小動物は分かりにくいですが、上から飛行機、自動車、鳥、ネコ、鹿、犬、カエル、馬、船、トラックの画像です :

 

 

以上






ClassCat® TF/ONNX Hub : 提供モデル例一覧

作成者 :(株)クラスキャット セールスインフォメーション
最終更新日 : 01/20/2019 ; 作成日 : 01/13/2019

 

ClassCat® TF/ONNX Hub とは

「ClassCat® TF/ONNX Hub」はクラスキャットが提供する実用性の高い機械学習モデルのレポジトリです。各モデルは TensorFlow 固有フォーマットと ONNX フォーマットの両者で提供されます。 [ClassCat® ONNX Hub 詳細]

  • ONNX (Open Neural Network Exchange) は深層学習モデルのためのオープンなフォーマットで、異なるオープンソースの深層学習フレームワーク間の相互作用を可能にします。

「ClassCat TF/ONNX Hub」で提供されるモデルについてはクラスキャットが検証の上で仕様を公開致しますので、ユーザ企業は希望するモデルを自由に選択することができます。更に (ユーザ企業の保持するデータセットによる) 再調整も含めて実運用するために必要なトータルサポートを提供致します。

◆ このページでは「ClassCat TF/ONNX Hub」で提供可能なモデル例を一覧にまとめています。

 

ClassCat® TF/ONNX Hub 提供モデル例一覧

 

画像処理

Mask R-CNN 対応 Detectron 互換 物体検出モデル

物体検出」は画像の複数種類の物体の位置を特定してクラス分類することを可能にする、実用的で応用範囲が広い技術です。

深層学習技術の普及により画像全体の情報からクラス分類を行なう「物体認識」については畳み込みニューラルネットワーク (CNN) の利用により手軽に成果が出せるようになりましたが、「物体検出」を行なうためには更に多岐に渡る技術が必要となります。

物体検出の手法としては Fast R-CNN, Faster R-CNN, YOLO 更には SSD などが良く知られていますが、最新技術としては Mask R-CNN が有名です。Mask R-CNN は物体検出した領域についてセマンティック・セグメンテーションも遂行します。

Detectron は FAIR (Facebook AI Research) が開発して 2018 年にオープンソース化した物体検出フレームワークです。クラスキャットでは、機能を Faster R-CNN と Mask R-CNN 中心に限定した Detectron 互換モデルを提供しております。

 

セマンティック・セグメンテーション

セマンティック・セグメンテーション」は簡単に言えば、画像の各ピクセルをクラス分類するタスクです。物体検出と混同されやすいですが、物体検出は物体を囲む最小の bounding box (矩形) の予測を主眼としていますので異なるタスクです。

風景写真のようにオブジェクトが分離しやすい画像では物体検出とそれほど違わない印象を受けますが、生物医学的なタスクではセマンティック・セグメンテーションが目的に良く適合することが多々あります。

 

医療画像処理モデル

医療画像処理の分野は AI の活用が大きく期待されている分野の一つです。

医療画像は通常 DICOM 形式で提供されますが、 一般的なフォーマットに変換すれば後は物体認識 (分類)、物体検出、あるいはセマンティック・セグメンテーションのような汎用 AI 技術が当てはまるタスクが多いです。

ここでは胸部レントゲンを中心に疾患の分類と患部の位置特定を遂行しています。また血液細胞の画像で赤血球、白血球そして血小板を検出する例も示します。

 

顔検出モデル

顔検出 は歩行者検出等の技術と同様に深層学習ブーム以前から研究されてきています。OpenCV や dlib C++ ライブラリを利用した、顔の bounding box 検出や輪郭検出は数多くの実装例がありましたが、深層学習フレームワークと組み合わせることによってより複雑なモデルが考案・開発されるようになりました。

モデルの発展の方向性は多岐に渡ります。顔の単純なローカリゼーション (位置特定) だけでなく、例えば目鼻の位置も併せて特定したり (顔の造形の再構築が可能であるような) 十分な数のランドマーク (目印) を予測するモデルもあります。

更には Pix2Face と呼ばれる、2 次元の顔画像から 3 次元の顔を構築する試みもされています。

 

人物ポーズ推定

上で顔のポーズ推定のためにランドマーク検出するモデルを紹介しましたが、身体全体を扱うモデルもあります。身体全体のポーズ推定でもやはりキーポイントの検出を遂行します。

人物ポーズ推定モデルでは、2 次元 RGB 画像から複数の人物のキーポイントを 2 次元の座標で検出します。キーポイントには目鼻・手足などが選択されています。高速ですので動画にも適用可能です。

最近のモデルとしては OpenPose (CMU) や DensePose (FAIR) が有名で、特に OpenPose が発表されて以来、2 次元画像から人物ポーズ推定するモデルが数多く発表されています。

 

GAN モデル (生成モデル、画像変換)

GAN

GAN」は敵対的生成ネットワーク (Generative Adversarial Network) と呼称される生成モデルの一種で、深層学習におけるホットな領域の一つとして様々なモデルやその応用が活発に研究されています。

基本的には 2 つのネットワーク generator と discriminator から構成され、訓練データセットの分布をネットワークに学習させるための仕組みです。その分布から新しい有用なデータを生成することができます。

Cycle GAN, StarGAN や Pix2Pix のように画像変換を目的とするモデルが多いですが、超解像モデルのように低解像画像を鮮明な高解像画像に変換する実用性を重視したモデルもあります。

StarGAN は髪の色や顔の表情を変換することができます

 


お問合せ
本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット
セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com
WebSite: http://www.classcat.com/
 

以上






ClassCat® TF / ONNX Hub : GAN モデル

作成者 :(株)クラスキャット セールスインフォメーション
作成日 : 01/05/2019

 

ClassCat® TF / ONNX Hub とは

「ClassCat® TF / ONNX Hub」はクラスキャットが提供する実用性の高い機械学習モデルのレポジトリです。各モデルは TensorFlow 固有フォーマットと ONNX フォーマットの両者で提供されます。 [ClassCat® ONNX Hub 詳細]

  • ONNX (Open Neural Network Exchange) は深層学習モデルのためのオープンなフォーマットで、異なるオープンソースの深層学習フレームワーク間の相互作用を可能にします。

「ClassCat TF / ONNX Hub」で提供されるモデルについてはクラスキャットが検証の上で仕様を公開致しますので、ユーザ企業は希望するモデルを自由に選択することができます。更に (ユーザ企業の保持するデータセットによる) 再調整も含めて配備・実運用するために必要なトータルサポートを提供致します。

◆ 本ページでは提供モデル群第一弾として GAN モデル の代表例を紹介致します。

 

GAN とは

GAN は敵対的生成ネットワーク (Generative Adversarial Network) と呼称される生成モデルの一種で、深層学習におけるホットな領域の一つとして様々なモデルやその応用が活発に研究されています。

GAN は 2014 年に Ian Goodfellow 氏により創案されペーパー Generative Adversarial Nets で最初に紹介されました。

基本的には 2 つのネットワーク generator と discriminator から構成され、訓練データセットの分布をネットワークに学習させるための仕組みです。その分布から新しい有用なデータを生成することができます :

  • generator のジョブは訓練画像のように見える ‘fake’ 画像を生むことです。
  • discriminator のジョブは画像を見てそれが real 訓練画像か (generator からの) fake 画像かを出力することです。

訓練の間、generator はより良い fake を生成することにより絶えず discriminator を出し抜こうとします。
一方、discriminator はより良い探偵として機能し、real 画像と fake 画像を正しく分類するために動作しています。

このゲームの均衡は、generator が訓練データに直接由来するかのように見える完全な fake 画像を生成しているときに、discriminator が generator 出力について real か fake か常に 50% の信頼度で推測する状態になることです。

 

GAN モデルの紹介

以下に代表的な GAN モデルを紹介します :

 

DCGAN

DCGAN – 深層畳込み GAN は最もポピュラーな GAN です。画像データセットの特徴をつかまえて新しいデータを生成します。

以下の例でははセレブ (有名人) 画像データセットの特徴を捕捉して新規の画像データを生成しています。

DCGAN の実装の詳細は PyTorch 1.0 Tutorials : 生成モデル : DCGAN が参考になります。

 

超解像 (SRGAN)

SRGAN (Super Resolution GAN) では、低解像度の画像を高解像の画像に変換することができます。

以下の例は、左側からそれぞれ GT (正解) 画像、SRGAN 出力、Bicubic 補間そして Bilinear 補間画像になります。
※ Bicubic, Bilinear 補間は画像処理ソフトで画像拡大時に通常使用される補間法です。

SRGAN 出力は他の補間法に比べて画像が鮮明です。例えば、一番上の蝶の画像の羽の白い部分の黒い模様を比較すると違いがはっきりと分かります。

 

 
【参考】
以下の例は GAN を使用していない超解像モデルです。
左側からそれぞれ GT (正解) 画像、超解像モデル出力、Bicubic 補間画像になります。

GAN を使用しない場合には、超解像モデル出力は Bicubic 補間画像よりも多少鮮明ではあるものの粗い画像になっています。

 

Cycle GAN

Cycle GAN は画像変換を遂行するモデルで、馬とシマウマの変換・写真と絵画間の画風変換、夏と冬の画像変換などで有名になりました。訓練データセットの画像が必ずしもペアである必要がない点が特徴的です。

以下は Cycle GAN を実際に 20 epochs ほど訓練したモデルで画像変換を行なっています。
風景写真をモネ絵画風の画像に変換しています :

※ 風景写真画像は全て ImageNet の URL を利用しており、元画像の版権は所有者に帰属しています。

以下は逆にモネの絵画を写真テイストの画像に変換しています :

次に別のデータセットの例として、車道を含む街の景観をセマンティック・セグメンテーション風に変換しています :

vice versa :

 

Pix2Pix

Pix2Pix も GAN を利用した画像変換です。Pix2Pix については TensorFlow : Tutorials : 画像 : Pix2Pix (Conditional GAN) が詳しいです。

 

以上






TensorFlow : Tutorials : 画像 : Pix2Pix (Conditional GAN)

TensorFlow : Tutorials : 画像 : Pix2Pix (Conditional GAN) (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 09/06/2018

* TensorFlow 1.10 で更に改訂されています。
* TensorFlow 1.9 でドキュメント構成が変更され、数篇が新規に追加されましたので再翻訳しました。
* 本ページは、TensorFlow の本家サイトの Tutorials – Images の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー開催中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

Pix2Pix : tf.keras と eager のサンプル

このノートブックは Image-to-Image Translation with Conditional Adversarial Networks で記述されている、conditional GAN を使用して画像から画像への変換を示します。このテクニックを使用して白黒写真を彩色したり、google マップを google earth に変換したりする等のことができます。ここでは、建物の正面 (= facade) を real な建物に変換します。これを成すために tf.keras と eager execution を使用します。

サンプルでは、CMP Facade データベースを使用します、これはプラハの Czech Technical UniversityCenter for Machine Perception により役立つように提供されています。サンプルを短く保持するために、上のペーパーの著者により作成された、このデータセットの前処理された コピー を使用します。

各エポックは単一の P100 上で 58 秒前後かかります。

下は 200 エポックの間モデルを訓練した後に生成された出力です。

sample output_1

 

TensorFlow をインポートして eager execution を有効にする

# Import TensorFlow >= 1.10 and enable eager execution
import tensorflow as tf
tf.enable_eager_execution()

import os
import time
import numpy as np
import matplotlib.pyplot as plt
import PIL
from IPython.display import clear_output

 

データセットをロードする

データセットと類似のデータセットを ここ からダウンロードできます。

(上の) ペーパーで言及されているように、訓練データセットにランダムに jittering とミラーリングを適用します。

  • ランダム jittering では、画像は 286 x 286 にリサイズされてからランダムに 256 x 256 にクロップされます。
  • ランダム・ミラーリングでは、画像は水平に i.e. 左から右にランダムにフリップ (反転) されます。
path_to_zip = tf.keras.utils.get_file('facades.tar.gz',
                                      cache_subdir=os.path.abspath('.'),
                                      origin='https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/facades.tar.gz', 
                                      extract=True)

PATH = os.path.join(os.path.dirname(path_to_zip), 'facades/')
BUFFER_SIZE = 400
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
def load_image(image_file, is_train):
  image = tf.read_file(image_file)
  image = tf.image.decode_jpeg(image)

  w = tf.shape(image)[1]

  w = w // 2
  real_image = image[:, :w, :]
  input_image = image[:, w:, :]

  input_image = tf.cast(input_image, tf.float32)
  real_image = tf.cast(real_image, tf.float32)

  if is_train:
    # random jittering
    
    # resizing to 286 x 286 x 3
    input_image = tf.image.resize_images(input_image, [286, 286], 
                                        align_corners=True, 
                                        method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
    real_image = tf.image.resize_images(real_image, [286, 286], 
                                        align_corners=True, 
                                        method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
    
    # randomly cropping to 256 x 256 x 3
    stacked_image = tf.stack([input_image, real_image], axis=0)
    cropped_image = tf.random_crop(stacked_image, size=[2, IMG_HEIGHT, IMG_WIDTH, 3])
    input_image, real_image = cropped_image[0], cropped_image[1]

    if np.random.random() > 0.5:
      # random mirroring
      input_image = tf.image.flip_left_right(input_image)
      real_image = tf.image.flip_left_right(real_image)
  else:
    input_image = tf.image.resize_images(input_image, size=[IMG_HEIGHT, IMG_WIDTH], 
                                         align_corners=True, method=2)
    real_image = tf.image.resize_images(real_image, size=[IMG_HEIGHT, IMG_WIDTH], 
                                        align_corners=True, method=2)
  
  # normalizing the images to [-1, 1]
  input_image = (input_image / 127.5) - 1
  real_image = (real_image / 127.5) - 1

  return input_image, real_image

 

バッチを作成し、データセットをマップして (前処理を行なって) シャッフルするために tf.data を使用する

train_dataset = tf.data.Dataset.list_files(PATH+'train/*.jpg')
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.map(lambda x: load_image(x, True))
train_dataset = train_dataset.batch(1)
test_dataset = tf.data.Dataset.list_files(PATH+'test/*.jpg')
test_dataset = test_dataset.map(lambda x: load_image(x, False))
test_dataset = test_dataset.batch(1)

 

generator と discriminator モデルを書く

  • Generator
    • gvenerator のアーキテクチャは変更された U-Net です。
    • エンコーダの各ブロックは (Conv -> Batchnorm -> Leaky ReLU)
    • デコーダの各ブロックは (Transposed Conv -> Batchnorm -> Dropout(applied to the first 3 blocks) -> ReLU)
    • エンコーダとデコーダの間に (U-Net 内のように) スキップ・コネクションがあります。
  • Discriminator
    • discriminator は PatchGAN です。
    • discriminator の各ブロックは (Conv -> BatchNorm -> Leaky ReLU) です。
    • 最後の層の後の出力の shape は (batch_size, 30, 30, 1) です。
    • 出力の各 30×30 パッチが入力画像の 70×70 の断片を分類します (そのようなアーキテクチャは PatchGAN と呼ばれます)。
    • discriminator は 2 つの入力を受け取ります。
      • 入力画像とターゲット画像、これは real としてえ分類されるべきです。
      • 入力画像と生成画像 (generator の出力)、これは fake として分類されるべきです。
      • これらの 2 つの入力をコード (tf.concat([inp, tar], axis=-1)) で一緒に結合します。
  • generator と discriminator を通して進む入力の shape はコードのコメントにあります。

アーキテクチャとハイパーパラメータについて更に学習するためには、ペーパー を参照できます。

OUTPUT_CHANNELS = 3
class Downsample(tf.keras.Model):
    
  def __init__(self, filters, size, apply_batchnorm=True):
    super(Downsample, self).__init__()
    self.apply_batchnorm = apply_batchnorm
    initializer = tf.random_normal_initializer(0., 0.02)

    self.conv1 = tf.keras.layers.Conv2D(filters, 
                                        (size, size), 
                                        strides=2, 
                                        padding='same',
                                        kernel_initializer=initializer,
                                        use_bias=False)
    if self.apply_batchnorm:
        self.batchnorm = tf.keras.layers.BatchNormalization()
  
  def call(self, x, training):
    x = self.conv1(x)
    if self.apply_batchnorm:
        x = self.batchnorm(x, training=training)
    x = tf.nn.leaky_relu(x)
    return x 


class Upsample(tf.keras.Model):
    
  def __init__(self, filters, size, apply_dropout=False):
    super(Upsample, self).__init__()
    self.apply_dropout = apply_dropout
    initializer = tf.random_normal_initializer(0., 0.02)

    self.up_conv = tf.keras.layers.Conv2DTranspose(filters, 
                                                   (size, size), 
                                                   strides=2, 
                                                   padding='same',
                                                   kernel_initializer=initializer,
                                                   use_bias=False)
    self.batchnorm = tf.keras.layers.BatchNormalization()
    if self.apply_dropout:
        self.dropout = tf.keras.layers.Dropout(0.5)

  def call(self, x1, x2, training):
    x = self.up_conv(x1)
    x = self.batchnorm(x, training=training)
    if self.apply_dropout:
        x = self.dropout(x, training=training)
    x = tf.nn.relu(x)
    x = tf.concat([x, x2], axis=-1)
    return x


class Generator(tf.keras.Model):
    
  def __init__(self):
    super(Generator, self).__init__()
    initializer = tf.random_normal_initializer(0., 0.02)
    
    self.down1 = Downsample(64, 4, apply_batchnorm=False)
    self.down2 = Downsample(128, 4)
    self.down3 = Downsample(256, 4)
    self.down4 = Downsample(512, 4)
    self.down5 = Downsample(512, 4)
    self.down6 = Downsample(512, 4)
    self.down7 = Downsample(512, 4)
    self.down8 = Downsample(512, 4)

    self.up1 = Upsample(512, 4, apply_dropout=True)
    self.up2 = Upsample(512, 4, apply_dropout=True)
    self.up3 = Upsample(512, 4, apply_dropout=True)
    self.up4 = Upsample(512, 4)
    self.up5 = Upsample(256, 4)
    self.up6 = Upsample(128, 4)
    self.up7 = Upsample(64, 4)

    self.last = tf.keras.layers.Conv2DTranspose(OUTPUT_CHANNELS, 
                                                (4, 4), 
                                                strides=2, 
                                                padding='same',
                                                kernel_initializer=initializer)
  
  @tf.contrib.eager.defun
  def call(self, x, training):
    # x shape == (bs, 256, 256, 3)    
    x1 = self.down1(x, training=training) # (bs, 128, 128, 64)
    x2 = self.down2(x1, training=training) # (bs, 64, 64, 128)
    x3 = self.down3(x2, training=training) # (bs, 32, 32, 256)
    x4 = self.down4(x3, training=training) # (bs, 16, 16, 512)
    x5 = self.down5(x4, training=training) # (bs, 8, 8, 512)
    x6 = self.down6(x5, training=training) # (bs, 4, 4, 512)
    x7 = self.down7(x6, training=training) # (bs, 2, 2, 512)
    x8 = self.down8(x7, training=training) # (bs, 1, 1, 512)

    x9 = self.up1(x8, x7, training=training) # (bs, 2, 2, 1024)
    x10 = self.up2(x9, x6, training=training) # (bs, 4, 4, 1024)
    x11 = self.up3(x10, x5, training=training) # (bs, 8, 8, 1024)
    x12 = self.up4(x11, x4, training=training) # (bs, 16, 16, 1024)
    x13 = self.up5(x12, x3, training=training) # (bs, 32, 32, 512)
    x14 = self.up6(x13, x2, training=training) # (bs, 64, 64, 256)
    x15 = self.up7(x14, x1, training=training) # (bs, 128, 128, 128)

    x16 = self.last(x15) # (bs, 256, 256, 3)
    x16 = tf.nn.tanh(x16)

    return x16
class DiscDownsample(tf.keras.Model):
    
  def __init__(self, filters, size, apply_batchnorm=True):
    super(DiscDownsample, self).__init__()
    self.apply_batchnorm = apply_batchnorm
    initializer = tf.random_normal_initializer(0., 0.02)

    self.conv1 = tf.keras.layers.Conv2D(filters, 
                                        (size, size), 
                                        strides=2, 
                                        padding='same',
                                        kernel_initializer=initializer,
                                        use_bias=False)
    if self.apply_batchnorm:
        self.batchnorm = tf.keras.layers.BatchNormalization()
  
  def call(self, x, training):
    x = self.conv1(x)
    if self.apply_batchnorm:
        x = self.batchnorm(x, training=training)
    x = tf.nn.leaky_relu(x)
    return x 

class Discriminator(tf.keras.Model):
    
  def __init__(self):
    super(Discriminator, self).__init__()
    initializer = tf.random_normal_initializer(0., 0.02)
    
    self.down1 = DiscDownsample(64, 4, False)
    self.down2 = DiscDownsample(128, 4)
    self.down3 = DiscDownsample(256, 4)
    
    # we are zero padding here with 1 because we need our shape to 
    # go from (batch_size, 32, 32, 256) to (batch_size, 31, 31, 512)
    self.zero_pad1 = tf.keras.layers.ZeroPadding2D()
    self.conv = tf.keras.layers.Conv2D(512, 
                                       (4, 4), 
                                       strides=1, 
                                       kernel_initializer=initializer, 
                                       use_bias=False)
    self.batchnorm1 = tf.keras.layers.BatchNormalization()
    
    # shape change from (batch_size, 31, 31, 512) to (batch_size, 30, 30, 1)
    self.zero_pad2 = tf.keras.layers.ZeroPadding2D()
    self.last = tf.keras.layers.Conv2D(1, 
                                       (4, 4), 
                                       strides=1,
                                       kernel_initializer=initializer)
  
  @tf.contrib.eager.defun
  def call(self, inp, tar, training):
    # concatenating the input and the target
    x = tf.concat([inp, tar], axis=-1) # (bs, 256, 256, channels*2)
    x = self.down1(x, training=training) # (bs, 128, 128, 64)
    x = self.down2(x, training=training) # (bs, 64, 64, 128)
    x = self.down3(x, training=training) # (bs, 32, 32, 256)

    x = self.zero_pad1(x) # (bs, 34, 34, 256)
    x = self.conv(x)      # (bs, 31, 31, 512)
    x = self.batchnorm1(x, training=training)
    x = tf.nn.leaky_relu(x)
    
    x = self.zero_pad2(x) # (bs, 33, 33, 512)
    # don't add a sigmoid activation here since
    # the loss function expects raw logits.
    x = self.last(x)      # (bs, 30, 30, 1)

    return x
# The call function of Generator and Discriminator have been decorated
# with tf.contrib.eager.defun()
# We get a performance speedup if defun is used (~25 seconds per epoch)
generator = Generator()
discriminator = Discriminator()

 

損失関数と optimizer を定義する

  • Discriminator 損失
    • discriminator 損失関数は 2 つの入力を取ります; real 画像、生成画像です。
    • real_loss は real 画像と 1 の配列 (何故ならばこれらは real 画像だからです) の sigmod 交差エントロピー損失です。
    • generated_loss は生成画像とゼロの配列 (何故ならばこれらは fake 画像だからです) の sigmod 交差エントロピー損失です。
    • それから total_loss は real_loss と generated_loss の合計です。
  • Generator 損失
    • それは生成画像と 1 の配列の sigmoid 交差エントロピー損失です。
    • ペーパーはまた L1 損失を含みます、これは生成画像とターゲット画像の間の MAE (mean absolute error, 平均絶対誤差) です。
    • これは生成画像にターゲット画像に構造的に類似することを可能にします。
    • 総計の generator 損失を計算するための式は = gan_loss + LAMBDA * l1_loss, ここで LAMBDA = 100 です。この値はペーパーの著者により決められました。
LAMBDA = 100
def discriminator_loss(disc_real_output, disc_generated_output):
  real_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels = tf.ones_like(disc_real_output), 
                                              logits = disc_real_output)
  generated_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels = tf.zeros_like(disc_generated_output), 
                                                   logits = disc_generated_output)

  total_disc_loss = real_loss + generated_loss

  return total_disc_loss
def generator_loss(disc_generated_output, gen_output, target):
  gan_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels = tf.ones_like(disc_generated_output),
                                             logits = disc_generated_output) 
  # mean absolute error
  l1_loss = tf.reduce_mean(tf.abs(target - gen_output))

  total_gen_loss = gan_loss + (LAMBDA * l1_loss)

  return total_gen_loss
generator_optimizer = tf.train.AdamOptimizer(2e-4, beta1=0.5)
discriminator_optimizer = tf.train.AdamOptimizer(2e-4, beta1=0.5)

 

チェックポイント (オブジェクト・ベースのセーブ)

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

 

訓練

  • データセットに渡り反復することから開始します。
  • generator は入力画像を取り、そして私達は生成出力を得ます。
  • discriminator は最初の入力として input_image と生成画像を受け取ります。2 番目の入力は input_image と the target_image です。
  • 次に generator と discriminator 損失を計算します。
  • それから、generator と discrimator 変数 (入力) の両者に関する損失の勾配を計算してそれらを optimizer に適用します。

 

生成画像

  • 訓練後、幾つかの画像を生成するときです!
  • テスト・データセットから generator に画像を渡します。
  • それから generator は入力画像を私達が期待する出力に翻訳します。
  • 最後のステップは予測をプロットすることです、そして voila !
EPOCHS = 200
def generate_images(model, test_input, tar):
  # the training=True is intentional here since
  # we want the batch statistics while running the model
  # on the test dataset. If we use training=False, we will get 
  # the accumulated statistics learned from the training dataset
  # (which we don't want)
  prediction = model(test_input, training=True)
  plt.figure(figsize=(15,15))

  display_list = [test_input[0], tar[0], prediction[0]]
  title = ['Input Image', 'Ground Truth', 'Predicted Image']

  for i in range(3):
    plt.subplot(1, 3, i+1)
    plt.title(title[i])
    # getting the pixel values between [0, 1] to plot it.
    plt.imshow(display_list[i] * 0.5 + 0.5)
    plt.axis('off')
  plt.show()
def train(dataset, epochs):  
  for epoch in range(epochs):
    start = time.time()

    for input_image, target in dataset:

      with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
        gen_output = generator(input_image, training=True)

        disc_real_output = discriminator(input_image, target, training=True)
        disc_generated_output = discriminator(input_image, gen_output, training=True)

        gen_loss = generator_loss(disc_generated_output, gen_output, target)
        disc_loss = discriminator_loss(disc_real_output, disc_generated_output)

      generator_gradients = gen_tape.gradient(gen_loss, 
                                              generator.variables)
      discriminator_gradients = disc_tape.gradient(disc_loss, 
                                                   discriminator.variables)

      generator_optimizer.apply_gradients(zip(generator_gradients, 
                                              generator.variables))
      discriminator_optimizer.apply_gradients(zip(discriminator_gradients, 
                                                  discriminator.variables))

    if epoch % 1 == 0:
        clear_output(wait=True)
        for inp, tar in test_dataset.take(1):
          generate_images(generator, inp, tar)
          
    # saving (checkpoint) the model every 20 epochs
    if (epoch + 1) % 20 == 0:
      checkpoint.save(file_prefix = checkpoint_prefix)

    print ('Time taken for epoch {} is {} sec\n'.format(epoch + 1,
                                                        time.time()-start))
train(train_dataset, EPOCHS)

 

最新のチェックポイントをリストアしてテストする

# restoring the latest checkpoint in checkpoint_dir
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))

 

テスト・データセット全体上のテスト

# Run the trained model on the entire test dataset
for inp, tar in test_dataset:
  generate_images(generator, inp, tar)
 

以上



TensorFlow : Tutorials : 生成モデル : DCGAN : tf.keras と eager のサンプル

TensorFlow : Tutorials : 生成モデル : DCGAN: tf.keras と eager のサンプル (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 08/29/2018

* TensorFlow 1.10 で更に改訂されています。
* TensorFlow 1.9 でドキュメント構成が変更され、数篇が新規に追加されましたので再翻訳しました。
* 本ページは、TensorFlow の本家サイトの Tutorials – Generative Models の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

DCGAN : tf.keras と eager のサンプル

このノートブックは tf.keras と eager execution を使用して手書き数字の画像をどのように生成するかを示します。それを行なうために、DCGAN (Deep Convolutional Generative Adversarial Networks, 深層畳み込み敵対的生成ネットワーク) を使用します。

このモデルは 2018年7月 時点で、Colab 上単一の Tesla K80 上で訓練するためにエポック毎におよそ ~30 秒かかります (グラフ関数を作成するために tf.contrib.eager.defun を使用)。

下は、generator と discriminator モデルを 150 エポック訓練した後に生成された出力です。

# to generate gifs
!pip install imageio

 

TensorFlow をインポートして eager execution を有効にする

from __future__ import absolute_import, division, print_function

# Import TensorFlow >= 1.10 and enable eager execution
import tensorflow as tf
tf.enable_eager_execution()

import os
import time
import numpy as np
import glob
import matplotlib.pyplot as plt
import PIL
import imageio
from IPython import display

 

データセットをロードする

generator と discriminator を訓練するために MNIST データセットを使用していきます。それから generator は手書き数字を生成します。

(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
# We are normalizing the images to the range of [-1, 1]
train_images = (train_images - 127.5) / 127.5
BUFFER_SIZE = 60000
BATCH_SIZE = 256

 

バッチを作成してデータセットをシャッフルするために tf.data を使用する

train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)

 

generator と discriminator モデルを書く

  • Generator
    • それは、discriminator を欺くために十分に良い、説得力のある画像を生成する 責任を負います。
    • それは Conv2DTranspose (Upsampling) 層で構成されます。完全結合層で開始して、望まれる画像サイズ (mnist 画像サイズ)、(28, 28, 1) に到達するために画像を 2 回アップサンプリングします。
    • tanh 活性を仕様する最後の層を除いて leaky relu 活性を使用します。
  • Discriminator
    • discriminator は real 画像から fake 画像を分類する 責任を負います。
    • 換言すれば、discriminator は (generator から) 生成された画像と real MNIST 画像を与えられます。discriminator のジョブはこれらの画像を (生成された) fake と real (MNIST 画像) に分類することです。
    • 基本的には generator は discriminator を生成された画像が本物であると騙すために十分に良くあるべきです
class Generator(tf.keras.Model):
  def __init__(self):
    super(Generator, self).__init__()
    self.fc1 = tf.keras.layers.Dense(7*7*64, use_bias=False)
    self.batchnorm1 = tf.keras.layers.BatchNormalization()
    
    self.conv1 = tf.keras.layers.Conv2DTranspose(64, (5, 5), strides=(1, 1), padding='same', use_bias=False)
    self.batchnorm2 = tf.keras.layers.BatchNormalization()
    
    self.conv2 = tf.keras.layers.Conv2DTranspose(32, (5, 5), strides=(2, 2), padding='same', use_bias=False)
    self.batchnorm3 = tf.keras.layers.BatchNormalization()
    
    self.conv3 = tf.keras.layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False)

  def call(self, x, training=True):
    x = self.fc1(x)
    x = self.batchnorm1(x, training=training)
    x = tf.nn.relu(x)

    x = tf.reshape(x, shape=(-1, 7, 7, 64))

    x = self.conv1(x)
    x = self.batchnorm2(x, training=training)
    x = tf.nn.relu(x)

    x = self.conv2(x)
    x = self.batchnorm3(x, training=training)
    x = tf.nn.relu(x)

    x = tf.nn.tanh(self.conv3(x))  
    return x
class Discriminator(tf.keras.Model):
  def __init__(self):
    super(Discriminator, self).__init__()
    self.conv1 = tf.keras.layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same')
    self.conv2 = tf.keras.layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same')
    self.dropout = tf.keras.layers.Dropout(0.3)
    self.flatten = tf.keras.layers.Flatten()
    self.fc1 = tf.keras.layers.Dense(1)

  def call(self, x, training=True):
    x = tf.nn.leaky_relu(self.conv1(x))
    x = self.dropout(x, training=training)
    x = tf.nn.leaky_relu(self.conv2(x))
    x = self.dropout(x, training=training)
    x = self.flatten(x)
    x = self.fc1(x)
    return x
generator = Generator()
discriminator = Discriminator()
# Defun gives 10 secs/epoch performance boost
generator.call = tf.contrib.eager.defun(generator.call)
discriminator.call = tf.contrib.eager.defun(discriminator.call)

 

損失関数と optimizer を定義する

  • Discriminator 損失
    • discriminator 損失関数は 2 つの入力を取ります ; real 画像、生成された画像です。
    • real_loss は real 画像と 1 の配列の sigmoid 交差エントロピー損失です (何故ならばこれらは real 画像だからです)。
    • generated_loss は生成された画像とゼロ配列の sigmod 交差エントロピー損失です (何故ならばこれらは fake 画像だからです)。
    • そして total_loss は real_loss と generated_loss の合計です。
  • Generator 損失
    • それは生成された画像と 1 の配列の sigmoid 交差エントロピー損失です。
  • discriminator と generator optimizer は異なります、何故ならばそれらを別々に訓練するからです。
def discriminator_loss(real_output, generated_output):
    # [1,1,...,1] with real output since it is true and we want
    # our generated examples to look like it
    real_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.ones_like(real_output), logits=real_output)

    # [0,0,...,0] with generated images since they are fake
    generated_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.zeros_like(generated_output), logits=generated_output)

    total_loss = real_loss + generated_loss

    return total_loss
def generator_loss(generated_output):
    return tf.losses.sigmoid_cross_entropy(tf.ones_like(generated_output), generated_output)
discriminator_optimizer = tf.train.AdamOptimizer(1e-4)
generator_optimizer = tf.train.AdamOptimizer(1e-4)

 

チェックポイント (オブジェクト・ベースのセーブ)

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

 

訓練

  • データセットに渡り反復することから始めます。
  • generator は入力としてノイズが与えられます、これは generator モデルを通して渡されたとき手書き数字のような画像を出力します。
  • discriminator は real MNIST 画像 と (generator から) 生成された画像が与えられます。
  • 次に、generator と discriminator 損失を計算します。
  • それから、generator と discriminator 変数 (入力) の両者に関する損失の勾配を計算してそれらを optimizer に適用します。

 

画像を生成する

  • 訓練の後、何某かの画像を生成する時です!
  • generator への入力としてノイズ配列を作成することから始めます。
  • それから generator はノイズを手書き数字に変換します。
  • 最後のステップは予測をプロットします。Voila !
EPOCHS = 150
noise_dim = 100
num_examples_to_generate = 16

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement of the gan.
random_vector_for_generation = tf.random_normal([num_examples_to_generate,
                                                 noise_dim])
def generate_and_save_images(model, epoch, test_input):
  # make sure the training parameter is set to False because we
  # don't want to train the batchnorm layer when doing inference.
  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()
def train(dataset, epochs, noise_dim):  
  for epoch in range(epochs):
    start = time.time()
    
    for images in dataset:
      # generating noise from a uniform distribution
      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)
        generated_output = discriminator(generated_images, training=True)
        
        gen_loss = generator_loss(generated_output)
        disc_loss = discriminator_loss(real_output, generated_output)
        
      gradients_of_generator = gen_tape.gradient(gen_loss, generator.variables)
      gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.variables)
      
      generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.variables))
      discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.variables))

      
    if epoch % 1 == 0:
      display.clear_output(wait=True)
      generate_and_save_images(generator,
                               epoch + 1,
                               random_vector_for_generation)
    
    # saving (checkpoint) the model every 15 epochs
    if (epoch + 1) % 15 == 0:
      checkpoint.save(file_prefix = checkpoint_prefix)
    
    print ('Time taken for epoch {} is {} sec'.format(epoch + 1,
                                                      time.time()-start))
  # generating after the final epoch
  display.clear_output(wait=True)
  generate_and_save_images(generator,
                           epochs,
                           random_vector_for_generation)
train(train_dataset, EPOCHS, noise_dim)

 

最新のチェックポイントをリストアする

# restoring the latest checkpoint in checkpoint_dir
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))

 

エポック数を使用して画像を表示する

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
display_image(EPOCHS)

 

総ての保存された画像の GIF を生成する

with imageio.get_writer('dcgan.gif', mode='I') as writer:
  filenames = glob.glob('image*.png')
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)
    
# this is a hack to display the gif inside the notebook
os.system('cp dcgan.gif dcgan.gif.png')
display.Image(filename="dcgan.gif.png")

Colab からアニメーションをダウンロードするためには下のコードをアンコメントします :

#from google.colab import files
#files.download('dcgan.gif')
 

以上



AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com