TensorFlow 2.0 : 上級 Tutorials : 生成 :- 深層畳み込み敵対的生成ネットワーク (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/21/2019
* 本ページは、TensorFlow org サイトの TF 2.0 – Advanced Tutorials – Generative の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
| 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
| E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
| Facebook: https://www.facebook.com/ClassCatJP/ |
生成 :- 深層畳み込み敵対的生成ネットワーク
このチュートリアルは 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 により生成された画像のシリーズを示します。画像はランダム・ノイズとして始まり、そして時間とともに手書き数字にだんだん似ていきます。

GAN について更に学習するためには、MIT の Intro to Deep Learning コースを勧めます。
TensorFlow と他のライブラリをインポートする
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
tf.__version__
'2.0.0'
# 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()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11493376/11490434 [==============================] - 0s 0us/step
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 の望まれるサイズに到達するまで幾度かアップサンプリングします。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 0x7f0c49ae6c18>

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.00366611]], 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 分 / エポックかかるかもしれません。
train(train_dataset, EPOCHS)

最新のチェックポイントをリストアします。
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f0b0049df98>
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 を勧めます。
以上