ホーム » 「TensorFlow 2.0 Advanced Tutorials (Beta)」タグがついた投稿

タグアーカイブ: TensorFlow 2.0 Advanced Tutorials (Beta)

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/10/2019

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

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

 

画像生成 :- 画風変換

このチュートリアルは一つの画像をもう一つの画像のスタイルで構成するために深層学習を使用します (Picasso や Van Gogh のように描けたらと思うことがありませんか?)。これは画風変換 (= neural style transfer) として知られこのテクニックは A Neural Algorithm of Artistic Style (Gatys et al.) で概説されています。

画風変換は 2 つの画像 — コンテンツ画像と (有名な画家によるアートワークのような) スタイル参照画像 — を取りそしてそれらを一緒に混ぜ合わせるために使用される最適化テクニックです、その結果出力画像はコンテンツ画像のように見えますが、スタイル参照画像のスタイルで「描かれて」います。

これはコンテンツ画像のコンテンツ統計とスタイル参照画像のスタイル統計が適合するように出力画像を最適化するように実装されます。これらの統計は畳み込みニューラルネットワークを使用して画像から抽出されます。

例として、このウミガメと Wassily Kandinsky のコンポジション 7 の画像を取りましょう :

Green Sea Turtle の画像 -By P.Lindgren [CC BY-SA 3.0]((https://creativecommons.org/licenses/by-sa/3.0), from Wikimedia Common

さて Kandinsky がこのウミガメの絵をこのスタイルだけで書くことを決めたとしたらそれはどのように見えるでしょう?このようなものでしょうか?

 

Setup

モジュールをインポートして configure する

from __future__ import absolute_import, division, print_function, unicode_literals
!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf
import IPython.display as display

import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (12,12)
mpl.rcParams['axes.grid'] = False

import numpy as np
import time
import functools

画像をダウンロードしてスタイル画像とコンテンツ画像を選択します :

content_path = tf.keras.utils.get_file('turtle.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Green_Sea_Turtle_grazing_seagrass.jpg')
style_path = tf.keras.utils.get_file('kandinsky.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/Green_Sea_Turtle_grazing_seagrass.jpg
32768/29042 [=================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg
196608/195196 [==============================] - 0s 0us/step

 

入力を可視化する

画像をロードするための関数を定義してその最大次元を 512 ピクセルに制限します。

def load_img(path_to_img):
  max_dim = 512
  img = tf.io.read_file(path_to_img)
  img = tf.image.decode_image(img, channels=3)
  img = tf.image.convert_image_dtype(img, tf.float32)

  shape = tf.cast(tf.shape(img)[:-1], tf.float32)
  long_dim = max(shape)
  scale = max_dim / long_dim

  new_shape = tf.cast(shape * scale, tf.int32)

  img = tf.image.resize(img, new_shape)
  img = img[tf.newaxis, :]
  return img

画像を表示するための単純な関数を作成します :

def imshow(image, title=None):
  if len(image.shape) > 3:
    image = tf.squeeze(image, axis=0)

  plt.imshow(image)
  if title:
    plt.title(title)
content_image = load_img(content_path)
style_image = load_img(style_path)

plt.subplot(1, 2, 1)
imshow(content_image, 'Content Image')

plt.subplot(1, 2, 2)
imshow(style_image, 'Style Image')

 

コンテンツとスタイル表現を定義する

画像のコンテンツとスタイル表現を得るためにモデルの中間層を使用します。ネットワークの入力層から始めて、最初の幾つかの層活性はエッジやテクスチャーのような低位特徴を表します。ネットワークを通り抜けるにつれて、最後の幾つかの層は高位特徴を表します — 車輪や目のような物体パーツ。このケースでは、貴方は VGG19 ネットワーク・アーキテクチャを使用しています、事前訓練された画像分類ネットワークです。これらの中間層は画像からのコンテンツとスタイルの表現を定義するために必要です。入力画像について、これらの中間層で対応するスタイルとコンテンツ・ターゲット表現をマッチさせようとします。

VGG19 をロードしてそれが正しく使用されていることを確かにするためにそれを画像上でテスト実行します。

x = tf.keras.applications.vgg19.preprocess_input(content_image*255)
x = tf.image.resize(x, (224, 224))
vgg = tf.keras.applications.VGG19(include_top=True, weights='imagenet')
prediction_probabilities = vgg(x)
prediction_probabilities.shape
TensorShape([1, 1000])
predicted_top_5 = tf.keras.applications.vgg19.decode_predictions(prediction_probabilities.numpy())[0]
[(class_name, prob) for (number, class_name, prob) in predicted_top_5]
[('loggerhead', 0.74297667),
 ('leatherback_turtle', 0.11357909),
 ('hermit_crab', 0.054411974),
 ('terrapin', 0.039235227),
 ('mud_turtle', 0.012614701)]

今は分類ヘッドなしで VGG19 をロードして、層名をリストします。

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')

print()
for layer in vgg.layers:
  print(layer.name)
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5
80142336/80134624 [==============================] - 2s 0us/step

input_2
block1_conv1
block1_conv2
block1_pool
block2_conv1
block2_conv2
block2_pool
block3_conv1
block3_conv2
block3_conv3
block3_conv4
block3_pool
block4_conv1
block4_conv2
block4_conv3
block4_conv4
block4_pool
block5_conv1
block5_conv2
block5_conv3
block5_conv4
block5_pool

画像のスタイルとコンテンツを表わすためにネットワークから中間層を選択します :

# Content layer where will pull our feature maps
content_layers = ['block5_conv2'] 

# Style layer of interest
style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1', 
                'block4_conv1', 
                'block5_conv1']

num_content_layers = len(content_layers)
num_style_layers = len(style_layers)

スタイルとコンテンツのための中間層

それでは何故、事前訓練された画像分類ネットワーク内のこれらの中間層出力はスタイルとコンテンツ表現を定義することを可能にするのでしょう。

高位では、ネットワークが (そのために訓練された) 画像分類を遂行するために、それは画像を理解しなければなりません。これは生画像を入力ピクセルとして取り、生画像ピクセルを画像内の特徴表現の複雑な理解へと変換する内部表現を構築することを必要とします。

これはまた何故畳み込みニューラルネットワークが上手く一般化できるかの理由でもあります : それらは不変性を獲得することができてクラス内 (e.g. 猫 vs. 犬) で (背景ノイズと他の妨害には不可知論である) 特徴を定義します。こうして、生画像がモデルに供給されるところと出力分類ラベルの間のどこかで、モデルは複雑な特徴抽出器としてサーブします。モデルの中間層にアクセスすることにより、入力画像のコンテンツとスタイルを記述することができます。

 

モデルを構築する

tf.keras.applications でネットワークは設計されますので Keras functional API を使用して中間層値を容易に抽出できます。

functional API を使用してモデルを定義するには、入力と出力を指定します :

model = Model(inputs, outputs)

次の関数は中間層出力のリストを返す VGG19 モデルを構築します。

def vgg_layers(layer_names):
  """ Creates a vgg model that returns a list of intermediate output values."""
  # Load our model. Load pretrained VGG, trained on imagenet data
  vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
  vgg.trainable = False
  
  outputs = [vgg.get_layer(name).output for name in layer_names]

  model = tf.keras.Model([vgg.input], outputs)
  return model

そしてモデルを作成するために :

style_extractor = vgg_layers(style_layers)
style_outputs = style_extractor(style_image*255)

#Look at the statistics of each layer's output
for name, output in zip(style_layers, style_outputs):
  print(name)
  print("  shape: ", output.numpy().shape)
  print("  min: ", output.numpy().min())
  print("  max: ", output.numpy().max())
  print("  mean: ", output.numpy().mean())
  print()
block1_conv1
  shape:  (1, 336, 512, 64)
  min:  0.0
  max:  835.5256
  mean:  33.97525

block2_conv1
  shape:  (1, 168, 256, 128)
  min:  0.0
  max:  4625.8857
  mean:  199.82687

block3_conv1
  shape:  (1, 84, 128, 256)
  min:  0.0
  max:  8789.239
  mean:  230.78099

block4_conv1
  shape:  (1, 42, 64, 512)
  min:  0.0
  max:  21566.135
  mean:  791.24005

block5_conv1
  shape:  (1, 21, 32, 512)
  min:  0.0
  max:  3189.2542
  mean:  59.179478

 

スタイルを計算する

画像のコンテンツは中間特徴マップの値により表わされます。

画像のスタイルは異なる特徴マップに渡る平均と相関により記述できることが判明しています。各位置で特徴ベクトルの外積を取り、総ての位置に渡りその外積を平均することによりこの情報を含むグラム行列を計算します。このグラム行列は特定の層のために次のように計算できます :

$$G^l_{cd} = \frac{\sum_{ij} F^l_{ijc}(x)F^l_{ijd}(x)}{IJ}$$

これは tf.linalg.einsum 関数を使用して簡潔に実装できます :

def gram_matrix(input_tensor):
  result = tf.linalg.einsum('bijc,bijd->bcd', input_tensor, input_tensor)
  input_shape = tf.shape(input_tensor)
  num_locations = tf.cast(input_shape[1]*input_shape[2], tf.float32)
  return result/(num_locations)

 

スタイルとコンテンツを抽出する

スタイルとコンテンツ tensor を返すモデルを構築します。

class StyleContentModel(tf.keras.models.Model):
  def __init__(self, style_layers, content_layers):
    super(StyleContentModel, self).__init__()
    self.vgg =  vgg_layers(style_layers + content_layers)
    self.style_layers = style_layers
    self.content_layers = content_layers
    self.num_style_layers = len(style_layers)
    self.vgg.trainable = False

  def call(self, inputs):
    "Expects float input in [0,1]"
    inputs = inputs*255.0
    preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs)
    outputs = self.vgg(preprocessed_input)
    style_outputs, content_outputs = (outputs[:self.num_style_layers], 
                                      outputs[self.num_style_layers:])

    style_outputs = [gram_matrix(style_output)
                     for style_output in style_outputs]

    content_dict = {content_name:value 
                    for content_name, value 
                    in zip(self.content_layers, content_outputs)}

    style_dict = {style_name:value
                  for style_name, value
                  in zip(self.style_layers, style_outputs)}
    
    return {'content':content_dict, 'style':style_dict}

画像上で呼び出されたとき、このモデルは style_layers のグラム行列 (style) と content_layers のコンテンツを返します :

extractor = StyleContentModel(style_layers, content_layers)

results = extractor(tf.constant(content_image))

style_results = results['style']

print('Styles:')
for name, output in sorted(results['style'].items()):
  print("  ", name)
  print("    shape: ", output.numpy().shape)
  print("    min: ", output.numpy().min())
  print("    max: ", output.numpy().max())
  print("    mean: ", output.numpy().mean())
  print()

print("Contents:")
for name, output in sorted(results['content'].items()):
  print("  ", name)
  print("    shape: ", output.numpy().shape)
  print("    min: ", output.numpy().min())
  print("    max: ", output.numpy().max())
  print("    mean: ", output.numpy().mean())
Styles:
   block1_conv1
    shape:  (1, 64, 64)
    min:  0.034881677
    max:  26723.18
    mean:  780.96204

   block2_conv1
    shape:  (1, 128, 128)
    min:  0.0
    max:  95840.37
    mean:  11674.933

   block3_conv1
    shape:  (1, 256, 256)
    min:  0.0
    max:  296185.97
    mean:  7241.375

   block4_conv1
    shape:  (1, 512, 512)
    min:  0.0
    max:  3164085.0
    mean:  104884.49

   block5_conv1
    shape:  (1, 512, 512)
    min:  0.0
    max:  66307.836
    mean:  650.05994

Contents:
   block5_conv2
    shape:  (1, 24, 32, 512)
    min:  0.0
    max:  939.0783
    mean:  8.983593

 

勾配降下を実行する

このスタイルとコンテンツ抽出器により、今では画風変換アルゴリズム実装できます。各ターゲットに対する画像の出力のための mean square error を計算することによりこれを行ない、それからこれらの損失の重み付けられた総計を取ります。

スタイルとコンテンツ・ターゲット値を設定します :

style_targets = extractor(style_image)['style']
content_targets = extractor(content_image)['content']

最適化するための画像を含む tf.Variable を定義します。これを迅速に行なうために、それをコンテンツ画像で初期化します (tf.Variable はコンテンツ画像と同じ shape でなければなりません) :

image = tf.Variable(content_image)

これは float 画像ですので、ピクセル値を 0 と 1 の間に保持する関数を定義します :

def clip_0_1(image):
  return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

optimizer を作成します。ペーパーは LBFGS を勧めていますが、Adam もまた問題なく動作します :

opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)

これを最適化するために、総計損失を得るために 2 つの損失の重み付けられた結合を使用します :

style_weight=1e-2
content_weight=1e4
def style_content_loss(outputs):
    style_outputs = outputs['style']
    content_outputs = outputs['content']
    style_loss = tf.add_n([tf.reduce_mean((style_outputs[name]-style_targets[name])**2) 
                           for name in style_outputs.keys()])
    style_loss *= style_weight / num_style_layers

    content_loss = tf.add_n([tf.reduce_mean((content_outputs[name]-content_targets[name])**2) 
                             for name in content_outputs.keys()])
    content_loss *= content_weight / num_content_layers
    loss = style_loss + content_loss
    return loss

画像を更新するために tf.GradientTape を使用します。

@tf.function()
def train_step(image):
  with tf.GradientTape() as tape:
    outputs = extractor(image)
    loss = style_content_loss(outputs)

  grad = tape.gradient(loss, image)
  opt.apply_gradients([(grad, image)])
  image.assign(clip_0_1(image))

今はテストのために数ステップ実行します :

train_step(image)
train_step(image)
train_step(image)
plt.imshow(image.read_value()[0])
WARNING: Logging before flag parsing goes to stderr.
W0628 03:57:17.008671 140611849496320 deprecation.py:323] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/math_grad.py:1205: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where

<matplotlib.image.AxesImage at 0x7fe1d0541390>

それは動作していますので、より長い最適化を遂行します :

import time
start = time.time()

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(image)
    print(".", end='')
  display.clear_output(wait=True)
  imshow(image.read_value())
  plt.title("Train step: {}".format(step))
  plt.show()

end = time.time()
print("Total time: {:.1f}".format(end-start))

Total time: 22.0

 

総計 variation 損失

この基本的実装の一つの不都合な点はそれは多くの高周波な人工物 (= artifacts) を生成することです。画像の高周波成分上で明示的な正則化項を使用してこれらを減じます。画風変換では、これはしばしば総計 variation 損失と呼ばれます :

def high_pass_x_y(image):
  x_var = image[:,:,1:,:] - image[:,:,:-1,:]
  y_var = image[:,1:,:,:] - image[:,:-1,:,:]

  return x_var, y_var
x_deltas, y_deltas = high_pass_x_y(content_image)

plt.figure(figsize=(14,10))
plt.subplot(2,2,1)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Original")

plt.subplot(2,2,2)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Original")

x_deltas, y_deltas = high_pass_x_y(image)

plt.subplot(2,2,3)
imshow(clip_0_1(2*y_deltas+0.5), "Horizontal Deltas: Styled")

plt.subplot(2,2,4)
imshow(clip_0_1(2*x_deltas+0.5), "Vertical Deltas: Styled")

これは高周波成分がどのように増加したかを示します。

また、この高周波成分は基本的にはエッジ検出器です。例えば、Sobel エッジ検出器から類似の出力を得ることができます :

plt.figure(figsize=(14,10))

sobel = tf.image.sobel_edges(content_image)
plt.subplot(1,2,1)
imshow(clip_0_1(sobel[...,0]/4+0.5), "Horizontal Sobel-edges")
plt.subplot(1,2,2)
imshow(clip_0_1(sobel[...,1]/4+0.5), "Vertical Sobel-edges")

これに関連する正則化損失は値の二乗の総計です :

def total_variation_loss(image):
  x_deltas, y_deltas = high_pass_x_y(image)
  return tf.reduce_mean(x_deltas**2) + tf.reduce_mean(y_deltas**2)

 

最適化を再実行する

total_variation_loss のための重みを選択します :

total_variation_weight=1e8

今はそれを train_step 関数に含めます :

@tf.function()
def train_step(image):
  with tf.GradientTape() as tape:
    outputs = extractor(image)
    loss = style_content_loss(outputs)
    loss += total_variation_weight*total_variation_loss(image)

  grad = tape.gradient(loss, image)
  opt.apply_gradients([(grad, image)])
  image.assign(clip_0_1(image))

最適化変数を再初期化します :

image = tf.Variable(content_image)

そして最適化を実行します :

import time
start = time.time()

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(image)
    print(".", end='')
  display.clear_output(wait=True)
  imshow(image.read_value())
  plt.title("Train step: {}".format(step))
  plt.show()

end = time.time()
print("Total time: {:.1f}".format(end-start))

Total time: 24.8

最後に、結果をセーブします :

file_name = 'kadinsky-turtle.png'
mpl.image.imsave(file_name, image[0])

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

以上






TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- FGSM を使用した敵対的サンプル

TensorFlow 2.0 Beta : 上級 Tutorials : 画像生成 :- FGSM を使用した敵対的サンプル (翻訳/解説)

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

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

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

 

画像生成 :- FGSM を使用した敵対的サンプル

このチュートリアルは Goodfellow et al による Explaining and Harnessing Adversarial Examples で記述されているように Fast Gradient Signed メソッド (FGSM) 攻撃を使用して敵対的サンプルを作成します。これはニューラルネットワークを騙す最初の最もポピュラーな攻撃の一つでした。

 

敵対的サンプルとは何でしょう?

敵対的サンプルはニューラルネットワークを混乱させ、与えられた入力の誤分類という結果になる目的で作成された特殊化された (= specialised) 入力です。これらの悪名高い入力は人間の目には見分けがつきませんが、ネットワークに画像の内容を識別することを失敗させます。そのような攻撃の幾つかのタイプがありますが、ここでは焦点は fast gradient sign メソッド攻撃にあります、これはホワイトボックス攻撃でその目標は誤分類を確実にすることです。ホワイトボックス攻撃では攻撃者は攻撃されるモデルへの完全なアクセスを持ちます。下で示される敵対的画像の最も有名な例の一つは前述のペーパーから取りました。

ここで、パンダの画像から始め、攻撃者は元の画像に小さな摂動 (= perturbations) (歪み (= distortion)) を追加します、これはモデルがこの画像をテナガザルとして、高い信頼度でラベル付けする結果になります。これらの摂動を追加する過程は下で説明されます。

 

Fast gradient sign メソッド

fast gradient sign メソッドは敵対的サンプルを作成するニューラルネットワークの勾配を利用することにより動作します。入力画像に対して、メソッドは損失を最大化する新しい画像を作成する入力画像に関する損失の勾配を使用します。この新しい画像は敵対的画像と呼ばれます。これは次の式を使用して要約できます :

\(adv\_x = x + \epsilon*\text{sign}(\nabla_xJ(\theta, x, y))\)

ここで

  • adv_x : 敵対的画像。
  • x : 元の入力画像。
  • y : 元の入力ラベル。
  • $\epsilon$ : 摂動が小さいことを確実にする乗数。
  • $\theta$ : モデルパラメータ。
  • $J$ : 損失。

ここで面白い特質は、勾配は入力画像に関して取られることです。これが成されるのは目的が損失を最大化する画像を作成することであるからです。これを達成する方法は画像の各ピクセルが損失値にどのくらい寄与するかを見出し、それに従って摂動を追加することです。

これはかなり高速に動作します、何故ならば連鎖率を使用してそして必要な勾配を見つけることにより、各ピクセルが損失にどのくらい寄与するかを見い出すことは容易だからです。こうして、画像に関する勾配が使用されます。更に、モデルはもはや訓練されませんので (そのため勾配は訓練可能な変数, i.e. モデルパラメータに関して取られません)、モデルパラメータは定数で在り続けます。唯一のゴールは既に訓練されたモデルを騙すことです。

そこで事前訓練されたモデルを試して騙してみましょう。このチュートリアルでは、モデルは MobileNetV2 で、ImageNet 上で事前訓練されました。

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow==2.0.0-beta1
import tensorflow as tf
import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams['figure.figsize'] = (8, 8)
mpl.rcParams['axes.grid'] = False

事前訓練された MobileNetV2 モデルと ImageNet クラス名をロードしましょう。

pretrained_model = tf.keras.applications.MobileNetV2(include_top=True,
                                                     weights='imagenet')
pretrained_model.trainable = False

# ImageNet labels
decode_predictions = tf.keras.applications.mobilenet_v2.decode_predictions
Downloading data from https://github.com/JonathanCMitchell/mobilenet_v2_keras/releases/download/v1.1/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224.h5
14540800/14536120 [==============================] - 1s 0us/step
# Helper function to preprocess the image so that it can be inputted in MobileNetV2
def preprocess(image):
  image = tf.cast(image, tf.float32)
  image = image/255
  image = tf.image.resize(image, (224, 224))
  image = image[None, ...]
  return image

# Helper function to extract labels from probability vector
def get_imagenet_label(probs):
  return decode_predictions(probs, top=1)[0][0]

 

元の画像

Wikimedia Common から ラブラドール・レトリバー by Mirko CC-BY-SA 3.0 のサンプル画像を使用してそれから敵対的サンプルを作成します。最初のステップはそれを MobileNetV2 モデルに入力として供給できるように前処理することです。

image_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg')
image_raw = tf.io.read_file(image_path)
image = tf.image.decode_image(image_raw)

image = preprocess(image)
image_probs = pretrained_model.predict(image)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg
90112/83281 [================================] - 0s 0us/step

画像を見てみましょう。

plt.figure()
plt.imshow(image[0])
_, image_class, class_confidence = get_imagenet_label(image_probs)
plt.title('{} : {:.2f}% Confidence'.format(image_class, class_confidence*100))
plt.show()
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json
40960/35363 [==================================] - 0s 0us/step

 

敵対的画像を作成する

fast gradient sign メソッドを実装する

最初のステップは摂動を作成することです、これは元の画像を歪めるために使用されて敵対的画像という結果になります。言及したように、このタスクのためには、勾配は画像に関して取られます。

loss_object = tf.keras.losses.CategoricalCrossentropy()

def create_adversarial_pattern(input_image, input_label):
  with tf.GradientTape() as tape:
    tape.watch(input_image)
    prediction = pretrained_model(input_image)
    loss = loss_object(input_label, prediction)

  # Get the gradients of the loss w.r.t to the input image.
  gradient = tape.gradient(loss, input_image)
  # Get the sign of the gradients to create the perturbation
  signed_grad = tf.sign(gradient)
  return signed_grad

結果としての摂動もまた可視化できます。

perturbations = create_adversarial_pattern(image, image_probs)
plt.imshow(perturbations[0])
WARNING: Logging before flag parsing goes to stderr.
W0628 01:18:35.199363 140125669582592 deprecation.py:323] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/math_grad.py:1220: add_dispatch_support..wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
W0628 01:18:35.360933 140125669582592 image.py:648] Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

<matplotlib.image.AxesImage at 0x7f70b87057b8>

epsilon の異なる値のためにこれを試して結果としての画像を観測してみましょう。epsilon の値が増加するにつれてネットワークを騙すことが容易になることに気がつくでしょう、けれども、これはより同一視できる摂動という結果とのトレードオフとなります。

def display_images(image, description):
  _, label, confidence = get_imagenet_label(pretrained_model.predict(image))
  plt.figure()
  plt.imshow(image[0])
  plt.title('{} \n {} : {:.2f}% Confidence'.format(description,
                                                   label, confidence*100))
  plt.show()
epsilons = [0, 0.01, 0.1, 0.15]
descriptions = [('Epsilon = {:0.3f}'.format(eps) if eps else 'Input')
                for eps in epsilons]

for i, eps in enumerate(epsilons):
  adv_x = image + eps*perturbations
  adv_x = tf.clip_by_value(adv_x, 0, 1)
  display_images(adv_x, descriptions[i])

 

Next steps

敵対的攻撃について知った今、先に進んでこれを異なるデータセットと異なるアーキテクチャで試すことができます。貴方自身のモデルを作成して訓練し、そしてそれから同じメソッドを使用してそれを騙すことを試みることもできるかもしれません。epsilon を変更するにつれて予測の信頼度がどのように変化するかを試して見ることもできます。

パワフルですが、このチュートリアルで示される攻撃は敵対的攻撃への研究の単なるスタートで、それからよりパワフルな攻撃を作成する複数のペーパーがあります。敵対的攻撃に加えて、研究は防御の作成にも繋がりました、これは強固な機械学習モデルを作成することを目的とします。敵対的攻撃と防御の包括的なリストのためにこの survey ペーパー をレビューしても良いです。

敵対的攻撃と防御のより多くの実装については、敵対的サンプル・ライブラリ CleverHans を見ることを望むかもしれません。

 

以上






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 までご一報いただけると嬉しいです。

 

画像生成 :- 畳み込み変分オートエンコーダ

このノートブックは変分オートエンコーダを訓練することにより手書き数字をどのように生成するかを実演します (1, 2)。

# to generate gifs
!pip install -q imageio

 

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

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf

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

from IPython import display

 

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

各 MNIST 画像は元々は 784 整数ベクトルで、その各々は 0-255 の間でピクセル強度を表します。私達のモデルでは各ピクセルを Bernoulli 分布でモデル化して、データセットを統計的に二値化します。

(train_images, _), (test_images, _) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
test_images = test_images.reshape(test_images.shape[0], 28, 28, 1).astype('float32')

# Normalizing the images to the range of [0., 1.]
train_images /= 255.
test_images /= 255.

# Binarization
train_images[train_images >= .5] = 1.
train_images[train_images < .5] = 0.
test_images[test_images >= .5] = 1.
test_images[test_images < .5] = 0.
TRAIN_BUF = 60000
BATCH_SIZE = 100

TEST_BUF = 10000

 

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

train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(TRAIN_BUF).batch(BATCH_SIZE)
test_dataset = tf.data.Dataset.from_tensor_slices(test_images).shuffle(TEST_BUF).batch(BATCH_SIZE)

 

生成と推論ネットワークを tf.keras.Sequential で配線する (= wire up)

私達の VAE サンプルでは、生成と推論ネットワークのために 2 つの小さな ConvNet を使用します。これらのニューラルネットは小さいので、コードを単純化するために tf.keras.Sequential を使用します。以下の記述において $x$ と $z$ をそれぞれ観測と潜在 (= latent) 変数を示すものとします。

 

生成ネットワーク (= Generative Network)

これは生成モデルを定義します、これは入力として潜在エンコーディングを取り、観測の条件付き分布, i.e. $p(x|z)$ のためのパラメータを出力します。更に、潜在変数のために単位ガウス事前分布 $p(z)$ を使用します。

 

推論ネットワーク

これは近似事後分布 $q(z|x)$ を定義します、これは入力として観測を取り潜在表現の条件付き分布のためのパラメータのセットを出力します。この例では、この分布を単純に対角ガウス分布 (= diagonal Gaussian) としてモデル化します。この場合、推論ネットワークは factorized Gaussian の平均 (= mean) と対数分散 (= log-variance) パラメータを出力します (分散の代わりの対数分散は直接的には数値的安定性のためです)。

 

再パラメータ化 (= Reparameterization) トリック

最適化の間、最初に単位ガウス分布からサンプリングすることにより $q(z|x)$ からサンプリングできて、それから標準偏差を乗じて平均を加えます。これは勾配がサンプルを通して推論ネットワーク・パラメータに渡せることを確かなものにします。

 

ネットワーク・アーキテクチャ

推論ネットワークのためには、2 つの畳み込み層とそれに続く完全結合層を使用します。生成ネットワークでは、完全結合層と続く 3 つの convolution transpose 層を使用してこのアーキテクチャを反映 (= mirror) します (a.k.a. あるコンテキストでは deconvolutional 層)。注意してください、VAE を訓練するときバッチ正規化の使用を回避することは一般的な実践です、何故ならばミニバッチを使用することによる追加的な偶然性はサンプリングからの偶然性の上に不安定性を悪化させるかもしれないためです。

class CVAE(tf.keras.Model):
  def __init__(self, latent_dim):
    super(CVAE, self).__init__()
    self.latent_dim = latent_dim
    self.inference_net = tf.keras.Sequential(
      [
          tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
          tf.keras.layers.Conv2D(
              filters=32, kernel_size=3, strides=(2, 2), activation='relu'),
          tf.keras.layers.Conv2D(
              filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
          tf.keras.layers.Flatten(),
          # No activation
          tf.keras.layers.Dense(latent_dim + latent_dim),
      ]
    )

    self.generative_net = tf.keras.Sequential(
        [
          tf.keras.layers.InputLayer(input_shape=(latent_dim,)),
          tf.keras.layers.Dense(units=7*7*32, activation=tf.nn.relu),
          tf.keras.layers.Reshape(target_shape=(7, 7, 32)),
          tf.keras.layers.Conv2DTranspose(
              filters=64,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          tf.keras.layers.Conv2DTranspose(
              filters=32,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation='relu'),
          # No activation
          tf.keras.layers.Conv2DTranspose(
              filters=1, kernel_size=3, strides=(1, 1), padding="SAME"),
        ]
    )

  def sample(self, eps=None):
    if eps is None:
      eps = tf.random.normal(shape=(100, self.latent_dim))
    return self.decode(eps, apply_sigmoid=True)

  def encode(self, x):
    mean, logvar = tf.split(self.inference_net(x), num_or_size_splits=2, axis=1)
    return mean, logvar

  def reparameterize(self, mean, logvar):
    eps = tf.random.normal(shape=mean.shape)
    return eps * tf.exp(logvar * .5) + mean

  def decode(self, z, apply_sigmoid=False):
    logits = self.generative_net(z)
    if apply_sigmoid:
      probs = tf.sigmoid(logits)
      return probs

    return logits

 

損失関数と optimizer を定義する

VAE は周辺対数尤度上のエビデンス下限 (ELBO, evidence lower bound) を最大化することにより訓練されます :

$$\log p(x) \ge \text{ELBO} = \mathbb{E}_{q(z|x)}\left[\log \frac{p(x, z)}{q(z|x)}\right].$$

実際に、この期待値の単一サンプル・モンテカルロ推定を最適化します :

$$\log p(x| z) + \log p(z) - \log q(z|x),$$

ここで $z$ は $q(z|x)$ からサンプリングされます。

Note: 私達はまた KL 項を解析的に計算することもできますが、ここでは単純化のために総ての 3 つの項をモンテカルロ estimator に組み入れます。

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

def log_normal_pdf(sample, mean, logvar, raxis=1):
  log2pi = tf.math.log(2. * np.pi)
  return tf.reduce_sum(
      -.5 * ((sample - mean) ** 2. * tf.exp(-logvar) + logvar + log2pi),
      axis=raxis)

def compute_loss(model, x):
  mean, logvar = model.encode(x)
  z = model.reparameterize(mean, logvar)
  x_logit = model.decode(z)

  cross_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
  logpx_z = -tf.reduce_sum(cross_ent, axis=[1, 2, 3])
  logpz = log_normal_pdf(z, 0., 0.)
  logqz_x = log_normal_pdf(z, mean, logvar)
  return -tf.reduce_mean(logpx_z + logpz - logqz_x)

def compute_gradients(model, x):
  with tf.GradientTape() as tape:
    loss = compute_loss(model, x)
  return tape.gradient(loss, model.trainable_variables), loss

def apply_gradients(optimizer, gradients, variables):
  optimizer.apply_gradients(zip(gradients, variables))

 

訓練

  • データセットに渡り反復することから始めます。
  • 各反復の間、近似事後分布 $q(z|x)$ の平均と対数分散パラメータのセットを得るために画像をエンコーダに渡します。
  • それから $q(z|x)$ からサンプリングするために再パラメータ化トリックを適用します。
  • 最後に、生成分布 $p(x|z)$ のロジットを得るために再パラメータ化されたサンプルをデコーダに渡します。
  • Note: 訓練セットの 60k データポイントとテストセットの 10k データポイント持つ、keras によりロードされたデータセットを使用しますので、テストセット上の結果としての ELBO は (Larochelle の MNIST の動的二値化 (= dynamic binarization) を使用している) 文献で報告されている結果よりも僅かにより高いです。

 

画像を生成する

  • 訓練の後、幾つかの画像を生成する時です。
  • 単位ガウス事前分布 $p(z)$ から潜在ベクトルのセットをサンプリングすることから始めます。
  • それから generator は潜在サンプル $z$ を観測のロジットに変換し、分布 $p(x|z)$ を与えます。
  • ここで Bernoulli 分布の確率をプロットします。
epochs = 100
latent_dim = 50
num_examples_to_generate = 16

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement.
random_vector_for_generation = tf.random.normal(
    shape=[num_examples_to_generate, latent_dim])
model = CVAE(latent_dim)
def generate_and_save_images(model, epoch, test_input):
  predictions = model.sample(test_input)
  fig = plt.figure(figsize=(4,4))

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

  # tight_layout minimizes the overlap between 2 sub-plots
  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()
generate_and_save_images(model, 0, random_vector_for_generation)

for epoch in range(1, epochs + 1):
  start_time = time.time()
  for train_x in train_dataset:
    gradients, loss = compute_gradients(model, train_x)
    apply_gradients(optimizer, gradients, model.trainable_variables)
  end_time = time.time()

  if epoch % 1 == 0:
    loss = tf.keras.metrics.Mean()
    for test_x in test_dataset:
      loss(compute_loss(model, test_x))
    elbo = -loss.result()
    display.clear_output(wait=False)
    print('Epoch: {}, Test set ELBO: {}, '
          'time elapse for current epoch {}'.format(epoch,
                                                    elbo,
                                                    end_time - start_time))
    generate_and_save_images(
        model, epoch, random_vector_for_generation)
Epoch: 100, Test set ELBO: -77.58395385742188, time elapse for current epoch 21.326345205307007

 

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

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
plt.imshow(display_image(epochs))
plt.axis('off')# Display images
(-0.5, 287.5, 287.5, -0.5)

 

総てのセーブされた画像の GIF を生成する

anim_file = 'cvae.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)
 

以上






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

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

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

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

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

 

画像生成 :- Pix2Pix

このノートブックは Image-to-Image Translation with Conditional Adversarial Networks で記述されている、条件付き GAN を使用して画像から画像への変換を実演します。このテクニックを使用して白黒写真を彩色したり、google マップを google earth に変換する等のことができます。ここでは、建物の正面 (= facade) をリアルな建物に変換します。

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

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

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

 

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

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf

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

 

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

データセットと類似のデータセットを ここ からダウンロードできます。(上の) ペーパーで言及されているように、訓練データセットにランダムに jittering とミラーリングを適用します。* ランダム jittering では、画像は 286 x 286 にリサイズされてからランダムに 256 x 256 にクロップされます。* ランダム・ミラーリングでは、画像は水平に i.e. 左から右にランダムにフリップ (反転) されます。

_URL = 'https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/facades.tar.gz'

path_to_zip = tf.keras.utils.get_file('facades.tar.gz',
                                      origin=_URL,
                                      extract=True)

PATH = os.path.join(os.path.dirname(path_to_zip), 'facades/')
Downloading data from https://people.eecs.berkeley.edu/~tinghuiz/projects/pix2pix/datasets/facades.tar.gz
30171136/30168306 [==============================] - 0s 0us/step
BUFFER_SIZE = 400
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
def load(image_file):
  image = tf.io.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)

  return input_image, real_image
inp, re = load(PATH+'train/100.jpg')
# casting to int for matplotlib to show the image
plt.figure()
plt.imshow(inp/255.0)
plt.figure()
plt.imshow(re/255.0)
<matplotlib.image.AxesImage at 0x7f23fd34b2e8>

def resize(input_image, real_image, height, width):
  input_image = tf.image.resize(input_image, [height, width],
                                method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
  real_image = tf.image.resize(real_image, [height, width],
                               method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

  return input_image, real_image
def random_crop(input_image, real_image):
  stacked_image = tf.stack([input_image, real_image], axis=0)
  cropped_image = tf.image.random_crop(
      stacked_image, size=[2, IMG_HEIGHT, IMG_WIDTH, 3])

  return cropped_image[0], cropped_image[1]
# normalizing the images to [-1, 1]

def normalize(input_image, real_image):
  input_image = (input_image / 127.5) - 1
  real_image = (real_image / 127.5) - 1

  return input_image, real_image
@tf.function()
def random_jitter(input_image, real_image):
  # resizing to 286 x 286 x 3
  input_image, real_image = resize(input_image, real_image, 286, 286)

  # randomly cropping to 256 x 256 x 3
  input_image, real_image = random_crop(input_image, real_image)

  if tf.random.uniform(()) > 0.5:
    # random mirroring
    input_image = tf.image.flip_left_right(input_image)
    real_image = tf.image.flip_left_right(real_image)

  return input_image, real_image
# As you can see in the images below
# that they are going through random jittering
# Random jittering as described in the paper is to
# 1. Resize an image to bigger height and width
# 2. Randomnly crop to the original size
# 3. Randomnly flip the image horizontally

plt.figure(figsize=(6, 6))
for i in range(4):
  rj_inp, rj_re = random_jitter(inp, re)
  plt.subplot(2, 2, i+1)
  plt.imshow(rj_inp/255.0)
  plt.axis('off')
plt.show()

def load_image_train(image_file):
  input_image, real_image = load(image_file)
  input_image, real_image = random_jitter(input_image, real_image)
  input_image, real_image = normalize(input_image, real_image)

  return input_image, real_image
def load_image_test(image_file):
  input_image, real_image = load(image_file)
  input_image, real_image = resize(input_image, real_image,
                                   IMG_HEIGHT, IMG_WIDTH)
  input_image, real_image = normalize(input_image, real_image)

  return input_image, real_image

 

入力パイプライン

train_dataset = tf.data.Dataset.list_files(PATH+'train/*.jpg')
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.map(load_image_train,
                                  num_parallel_calls=tf.data.experimental.AUTOTUNE)
train_dataset = train_dataset.batch(1)
test_dataset = tf.data.Dataset.list_files(PATH+'test/*.jpg')
# shuffling so that for every epoch a different image is generated
# to predict and display the progress of our model.
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
test_dataset = test_dataset.map(load_image_test)
test_dataset = test_dataset.batch(1)

 

Generator を構築する

  • generator のアーキテクチャは変更された U-Net です。
  • エンコーダの各ブロックは (Conv -> Batchnorm -> Leaky ReLU)
  • デコーダの各ブロックは (Transposed Conv -> Batchnorm -> Dropout(applied to the first 3 blocks) -> ReLU)
  • エンコーダとデコーダの間に (U-Net 内のように) スキップ接続があります。
OUTPUT_CHANNELS = 3
def downsample(filters, size, apply_batchnorm=True):
  initializer = tf.random_normal_initializer(0., 0.02)

  result = tf.keras.Sequential()
  result.add(
      tf.keras.layers.Conv2D(filters, size, strides=2, padding='same',
                             kernel_initializer=initializer, use_bias=False))

  if apply_batchnorm:
    result.add(tf.keras.layers.BatchNormalization())

  result.add(tf.keras.layers.LeakyReLU())

  return result
down_model = downsample(3, 4)
down_result = down_model(tf.expand_dims(inp, 0))
print (down_result.shape)
(1, 128, 128, 3)
def upsample(filters, size, apply_dropout=False):
  initializer = tf.random_normal_initializer(0., 0.02)

  result = tf.keras.Sequential()
  result.add(
    tf.keras.layers.Conv2DTranspose(filters, size, strides=2,
                                    padding='same',
                                    kernel_initializer=initializer,
                                    use_bias=False))

  result.add(tf.keras.layers.BatchNormalization())

  if apply_dropout:
      result.add(tf.keras.layers.Dropout(0.5))

  result.add(tf.keras.layers.ReLU())

  return result
up_model = upsample(3, 4)
up_result = up_model(down_result)
print (up_result.shape)
(1, 256, 256, 3)
def Generator():
  down_stack = [
    downsample(64, 4, apply_batchnorm=False), # (bs, 128, 128, 64)
    downsample(128, 4), # (bs, 64, 64, 128)
    downsample(256, 4), # (bs, 32, 32, 256)
    downsample(512, 4), # (bs, 16, 16, 512)
    downsample(512, 4), # (bs, 8, 8, 512)
    downsample(512, 4), # (bs, 4, 4, 512)
    downsample(512, 4), # (bs, 2, 2, 512)
    downsample(512, 4), # (bs, 1, 1, 512)
  ]

  up_stack = [
    upsample(512, 4, apply_dropout=True), # (bs, 2, 2, 1024)
    upsample(512, 4, apply_dropout=True), # (bs, 4, 4, 1024)
    upsample(512, 4, apply_dropout=True), # (bs, 8, 8, 1024)
    upsample(512, 4), # (bs, 16, 16, 1024)
    upsample(256, 4), # (bs, 32, 32, 512)
    upsample(128, 4), # (bs, 64, 64, 256)
    upsample(64, 4), # (bs, 128, 128, 128)
  ]

  initializer = tf.random_normal_initializer(0., 0.02)
  last = tf.keras.layers.Conv2DTranspose(OUTPUT_CHANNELS, 4,
                                         strides=2,
                                         padding='same',
                                         kernel_initializer=initializer,
                                         activation='tanh') # (bs, 256, 256, 3)

  concat = tf.keras.layers.Concatenate()

  inputs = tf.keras.layers.Input(shape=[None,None,3])
  x = inputs

  # Downsampling through the model
  skips = []
  for down in down_stack:
    x = down(x)
    skips.append(x)

  skips = reversed(skips[:-1])

  # Upsampling and establishing the skip connections
  for up, skip in zip(up_stack, skips):
    x = up(x)
    x = concat([x, skip])

  x = last(x)

  return tf.keras.Model(inputs=inputs, outputs=x)
generator = Generator()

gen_output = generator(inp[tf.newaxis,...], training=False)
plt.imshow(gen_output[0,...])
WARNING: Logging before flag parsing goes to stderr.
W0628 03:19:17.514427 139794851374848 image.py:648] Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

<matplotlib.image.AxesImage at 0x7f237c645f98>

 

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)) で一緒に結合します。
def Discriminator():
  initializer = tf.random_normal_initializer(0., 0.02)

  inp = tf.keras.layers.Input(shape=[None, None, 3], name='input_image')
  tar = tf.keras.layers.Input(shape=[None, None, 3], name='target_image')

  x = tf.keras.layers.concatenate([inp, tar]) # (bs, 256, 256, channels*2)

  down1 = downsample(64, 4, False)(x) # (bs, 128, 128, 64)
  down2 = downsample(128, 4)(down1) # (bs, 64, 64, 128)
  down3 = downsample(256, 4)(down2) # (bs, 32, 32, 256)

  zero_pad1 = tf.keras.layers.ZeroPadding2D()(down3) # (bs, 34, 34, 256)
  conv = tf.keras.layers.Conv2D(512, 4, strides=1,
                                kernel_initializer=initializer,
                                use_bias=False)(zero_pad1) # (bs, 31, 31, 512)

  batchnorm1 = tf.keras.layers.BatchNormalization()(conv)

  leaky_relu = tf.keras.layers.LeakyReLU()(batchnorm1)

  zero_pad2 = tf.keras.layers.ZeroPadding2D()(leaky_relu) # (bs, 33, 33, 512)

  last = tf.keras.layers.Conv2D(1, 4, strides=1,
                                kernel_initializer=initializer)(zero_pad2) # (bs, 30, 30, 1)

  return tf.keras.Model(inputs=[inp, tar], outputs=last)
discriminator = Discriminator()
disc_out = discriminator([inp[tf.newaxis,...], gen_output], training=False)
plt.imshow(disc_out[0,...,-1], vmin=-20, vmax=20, cmap='RdBu_r')
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x7f237c4b16d8>

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

 

損失関数と 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
loss_object = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(disc_real_output, disc_generated_output):
  real_loss = loss_object(tf.ones_like(disc_real_output), disc_real_output)

  generated_loss = loss_object(tf.zeros_like(disc_generated_output), 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 = loss_object(tf.ones_like(disc_generated_output), 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.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=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 と target_image です。
  • 次に generator と discriminator 損失を計算します。
  • それから、generator と discriminator 変数 (入力) の両者に関する損失の勾配を計算してそれらを optimizer に適用します。
  • この手続き全体は下の画像で示されます。

 

画像を生成する

  • 訓練後、幾つかの画像を生成するときです!
  • テスト・データセットから generator に画像を渡します。
  • それから generator は入力画像を私達が期待する出力に翻訳します。
  • 最後のステップは予測をプロットすることです、そして voila !
EPOCHS = 150
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()
@tf.function
def train_step(input_image, target):
  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.trainable_variables)
  discriminator_gradients = disc_tape.gradient(disc_loss,
                                               discriminator.trainable_variables)

  generator_optimizer.apply_gradients(zip(generator_gradients,
                                          generator.trainable_variables))
  discriminator_optimizer.apply_gradients(zip(discriminator_gradients,
                                              discriminator.trainable_variables))
def train(dataset, epochs):
  for epoch in range(epochs):
    start = time.time()

    for input_image, target in dataset:
      train_step(input_image, target)

    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)

Time taken for epoch 150 is 15.114819765090942 sec

 

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

!ls {checkpoint_dir}
checkpoint          ckpt-4.data-00001-of-00002
ckpt-1.data-00000-of-00002  ckpt-4.index
ckpt-1.data-00001-of-00002  ckpt-5.data-00000-of-00002
ckpt-1.index            ckpt-5.data-00001-of-00002
ckpt-2.data-00000-of-00002  ckpt-5.index
ckpt-2.data-00001-of-00002  ckpt-6.data-00000-of-00002
ckpt-2.index            ckpt-6.data-00001-of-00002
ckpt-3.data-00000-of-00002  ckpt-6.index
ckpt-3.data-00001-of-00002  ckpt-7.data-00000-of-00002
ckpt-3.index            ckpt-7.data-00001-of-00002
ckpt-4.data-00000-of-00002  ckpt-7.index
# restoring the latest checkpoint in checkpoint_dir
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f1f3ce33780>

 

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

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

 

以上






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 Beta : 上級 Tutorials : テキストとシークエンス :- 言語理解のための Transformer モデル

TensorFlow 2.0 Beta : 上級 Tutorials : テキストとシークエンス :- 言語理解のための Transformer モデル (翻訳/解説)

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

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

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

 

 

テキストとシークエンス :- 言語理解のための Transformer モデル

このチュートリアルはポルトガル語を英語に翻訳するために Transformer モデル を訓練します。これは テキスト生成attention の知識を仮定する上級サンプルです。

transformer モデルの背後にある中心的なアイデアは self-attention — そのシークエンスの表現を計算するために入力シークエンスの異なる位置に注目する機能です。Transformer は self-attention 層のスタックを作成して下のセクション Scaled dot product attention and Multi-head attention で説明されます。

transformer モデルは RNNCNN の代わりに self-attention 層のスタックを使用して可変サイズの入力を処理します。この一般的なアーキテクチャは幾つかの優位点を持ちます :

  • それはデータに渡る時間的/空間的関係について何も仮定しません。これはオブジェクトのセット (例えば、StarCraft ユニット) を前処理するために理想的です。
  • 層出力は RNN のような直列 (= series) の代わりに、並列に計算できます。
  • 距離のある (= distant) 項目は多くの RNN-ステップや畳み込み層を通すことなく互いの出力に影響を与えることができます (例えば Scene Memory Transformer 参照)。
  • それは long-range 依存性を学習できます。これは多くのシークエンスタスクにおける挑戦 (的課題) です。

アーキテクチャの不都合な点は :

  • 時系列について、時間ステップに対する出力は入力と現在の隠れ状態だけの代わりに履歴全体から計算されます。
  • 入力がテキストのような、時間的/空間的関係を持つ場合、何某かの位置的エンコーディングが追加されなければなりません、そうでないのであればモデルは bag of words を効果的に見るでしょう。

このノートブックでモデルを訓練後、ポルトガル語センテンスを入力して英語翻訳を返すことができます。

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow_datasets as tfds
import tensorflow as tf

import time
import numpy as np
import matplotlib.pyplot as plt

 

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

TFDS を使用して TED Talks Open Translation Project から 葡英翻訳データセット をロードします。

このデータセットはおよそ 50000 訓練サンプル、1100 検証サンプルそして 2000 テストサンプルを含みます。

examples, metadata = tfds.load('ted_hrlr_translate/pt_to_en', with_info=True,
                               as_supervised=True)
train_examples, val_examples = examples['train'], examples['validation']
Downloading and preparing dataset ted_hrlr_translate (124.94 MiB) to /home/kbuilder/tensorflow_datasets/ted_hrlr_translate/pt_to_en/0.0.1...

HBox(children=(IntProgress(value=1, bar_style='info', description='Dl Completed...', max=1, style=ProgressStyl…
HBox(children=(IntProgress(value=1, bar_style='info', description='Dl Size...', max=1, style=ProgressStyle(des…
HBox(children=(IntProgress(value=1, bar_style='info', description='Extraction completed...', max=1, style=Prog…





HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))


HBox(children=(IntProgress(value=0, description='Shuffling...', max=1, style=ProgressStyle(description_width='…
WARNING: Logging before flag parsing goes to stderr.
W0628 06:47:58.377724 140112721143552 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)`

HBox(children=(IntProgress(value=1, bar_style='info', description='Reading...', max=1, style=ProgressStyle(des…
HBox(children=(IntProgress(value=0, description='Writing...', max=51785, style=ProgressStyle(description_width…


HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))


HBox(children=(IntProgress(value=0, description='Shuffling...', max=1, style=ProgressStyle(description_width='…
HBox(children=(IntProgress(value=1, bar_style='info', description='Reading...', max=1, style=ProgressStyle(des…
HBox(children=(IntProgress(value=0, description='Writing...', max=1193, style=ProgressStyle(description_width=…


HBox(children=(IntProgress(value=1, bar_style='info', max=1), HTML(value='')))


HBox(children=(IntProgress(value=0, description='Shuffling...', max=1, style=ProgressStyle(description_width='…
HBox(children=(IntProgress(value=1, bar_style='info', description='Reading...', max=1, style=ProgressStyle(des…
HBox(children=(IntProgress(value=0, description='Writing...', max=1803, style=ProgressStyle(description_width=…
Dataset ted_hrlr_translate downloaded and prepared to /home/kbuilder/tensorflow_datasets/ted_hrlr_translate/pt_to_en/0.0.1. Subsequent calls will reuse this data.

訓練データセットからカスタム部分単語字句解析器を作成します。

tokenizer_en = tfds.features.text.SubwordTextEncoder.build_from_corpus(
    (en.numpy() for pt, en in train_examples), target_vocab_size=2**13)

tokenizer_pt = tfds.features.text.SubwordTextEncoder.build_from_corpus(
    (pt.numpy() for pt, en in train_examples), target_vocab_size=2**13)
sample_string = 'Transformer is awesome.'

tokenized_string = tokenizer_en.encode(sample_string)
print ('Tokenized string is {}'.format(tokenized_string))

original_string = tokenizer_en.decode(tokenized_string)
print ('The original string: {}'.format(original_string))

assert original_string == sample_string
Tokenized string is [7915, 1248, 7946, 7194, 13, 2799, 7877]
The original string: Transformer is awesome.

字句解析器は単語が辞書にない場合には文字列を部分単語に分割することによりエンコードします。

for ts in tokenized_string:
  print ('{} ----> {}'.format(ts, tokenizer_en.decode([ts])))
7915 ----> T
1248 ----> ran
7946 ----> s
7194 ----> former 
13 ----> is 
2799 ----> awesome
7877 ----> .
BUFFER_SIZE = 20000
BATCH_SIZE = 64

入力とターゲットに start と end トークンを追加します。

def encode(lang1, lang2):
  lang1 = [tokenizer_pt.vocab_size] + tokenizer_pt.encode(
      lang1.numpy()) + [tokenizer_pt.vocab_size+1]

  lang2 = [tokenizer_en.vocab_size] + tokenizer_en.encode(
      lang2.numpy()) + [tokenizer_en.vocab_size+1]
  
  return lang1, lang2

Note: この例題を小さくそして比較的高速に保持するため、40 トークンを超える長さを持つサンプルは捨てます。

MAX_LENGTH = 40
def filter_max_length(x, y, max_length=MAX_LENGTH):
  return tf.logical_and(tf.size(x) <= max_length,
                        tf.size(y) <= max_length)

.map() 内の演算はグラフモードで動作して numpy 属性を持たないグラフ tensor を受け取ります。字句解析器は (それを) 整数にエンコードするために文字列か Unicode シンボルを想定します。こうして、エンコーディングを tf.py_function 内で実行する必要があります、これは文字列値を含む numpy 属性を持つ eager tensor を受け取ります。

def tf_encode(pt, en):
  return tf.py_function(encode, [pt, en], [tf.int64, tf.int64])
train_dataset = train_examples.map(tf_encode)
train_dataset = train_dataset.filter(filter_max_length)
# cache the dataset to memory to get a speedup while reading from it.
train_dataset = train_dataset.cache()
train_dataset = train_dataset.shuffle(BUFFER_SIZE).padded_batch(
    BATCH_SIZE, padded_shapes=([-1], [-1]))
train_dataset = train_dataset.prefetch(tf.data.experimental.AUTOTUNE)


val_dataset = val_examples.map(tf_encode)
val_dataset = val_dataset.filter(filter_max_length).padded_batch(
    BATCH_SIZE, padded_shapes=([-1], [-1]))
pt_batch, en_batch = next(iter(val_dataset))
pt_batch, en_batch
W0628 06:50:46.608169 140107050899200 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.string
W0628 06:50:46.610315 140107050899200 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.string
W0628 06:50:46.612855 140107034113792 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.string
W0628 06:50:46.614116 140107034113792 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.string
W0628 06:50:46.616141 140107034113792 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.string

(<tf.Tensor: id=311422, shape=(64, 40), dtype=int64, numpy=
 array([[8214, 1259,    5, ...,    0,    0,    0],
        [8214,  299,   13, ...,    0,    0,    0],
        [8214,   59,    8, ...,    0,    0,    0],
        ...,
        [8214,   95,    3, ...,    0,    0,    0],
        [8214, 5157,    1, ...,    0,    0,    0],
        [8214, 4479, 7990, ...,    0,    0,    0]])>,
 <tf.Tensor: id=311423, shape=(64, 40), dtype=int64, numpy=
 array([[8087,   18,   12, ...,    0,    0,    0],
        [8087,  634,   30, ...,    0,    0,    0],
        [8087,   16,   13, ...,    0,    0,    0],
        ...,
        [8087,   12,   20, ...,    0,    0,    0],
        [8087,   17, 4981, ...,    0,    0,    0],
        [8087,   12, 5453, ...,    0,    0,    0]])>)

 

位置エンコーディング (= Positional encoding)

このモデルはどのようなリカレンスも畳み込みも含まないので、センテンスの単語の相対位置についての何某かの情報をモデルに与えるために位置エンコーディングが追加されます。

位置エンコーディングベクトルは埋め込みベクトルに追加されます。埋め込みは d-次元空間でトークンを表し、そこでは類似の意味を持つトークンは互いに近くなります。しかし埋め込みはセンテンスの単語の相対位置をエンコードしません。そこで位置エンコーディングの追加後、単語はそれらの意味の類似性とセンテンスにおけるそれらの位置に基づき、d-次元空間で互いに近くなります。

それについて更に学習するために 位置エンコーディング のノートブックを見てください。位置エンコーディングを計算するための公式は次のようなものです :

$$\Large{PE_{(pos, 2i)} = sin(pos / 10000^{2i / d_{model}})} $$
$$\Large{PE_{(pos, 2i+1)} = cos(pos / 10000^{2i / d_{model}})} $$
def get_angles(pos, i, d_model):
  angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
  return pos * angle_rates
def positional_encoding(position, d_model):
  angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                          np.arange(d_model)[np.newaxis, :],
                          d_model)
  
  # apply sin to even indices in the array; 2i
  sines = np.sin(angle_rads[:, 0::2])
  
  # apply cos to odd indices in the array; 2i+1
  cosines = np.cos(angle_rads[:, 1::2])
  
  pos_encoding = np.concatenate([sines, cosines], axis=-1)
  
  pos_encoding = pos_encoding[np.newaxis, ...]
    
  return tf.cast(pos_encoding, dtype=tf.float32)
pos_encoding = positional_encoding(50, 512)
print (pos_encoding.shape)

plt.pcolormesh(pos_encoding[0], cmap='RdBu')
plt.xlabel('Depth')
plt.xlim((0, 512))
plt.ylabel('Position')
plt.colorbar()
plt.show()
(1, 50, 512)

 

マスキング

シークエンスのバッチのパッド・トークンの総てをマスクします。それはモデルがパディングを入力として扱わないことを確実にします。マスクはパッド値 0 がどこに存在するかを示します: それはそれらの位置では 1 を出力し、そうでなければ 0 を出力します。

def create_padding_mask(seq):
  seq = tf.cast(tf.math.equal(seq, 0), tf.float32)
  
  # add extra dimensions so that we can add the padding
  # to the attention logits.
  return seq[:, tf.newaxis, tf.newaxis, :]  # (batch_size, 1, 1, seq_len)
x = tf.constant([[7, 6, 0, 0, 1], [1, 2, 3, 0, 0], [0, 0, 0, 4, 5]])
create_padding_mask(x)
<tf.Tensor: id=311439, shape=(3, 1, 1, 5), dtype=float32, numpy=
array([[[[0., 0., 1., 1., 0.]]],


       [[[0., 0., 0., 1., 1.]]],


       [[[1., 1., 1., 0., 0.]]]], dtype=float32)>

look-ahead mask はシークエンスの future トークンをマスクするために使用されます。換言すれば、マスクはどのエントリが使用されるべきでないかを示します。

これは 3 番目の単語を予測するため、最初と 2 番目の単語だけが使用されることを意味します。同様に 4 番目の単語を予測するためには、最初、2 番目そして 3 番目の単語だけが使用されます等々。

def create_look_ahead_mask(size):
  mask = 1 - tf.linalg.band_part(tf.ones((size, size)), -1, 0)
  return mask  # (seq_len, seq_len)
x = tf.random.uniform((1, 3))
temp = create_look_ahead_mask(x.shape[1])
temp
<tf.Tensor: id=311455, shape=(3, 3), dtype=float32, numpy=
array([[0., 1., 1.],
       [0., 0., 1.],
       [0., 0., 0.]], dtype=float32)>

transformer により使用される attention 関数は 3 つの入力を取ります: Q (query), K (key), V (value)。attention 重みを計算するために使用される等式は :

$$\Large{Attention(Q, K, V) = softmax_k(\frac{QK^T}{\sqrt{d_k}}) V} $$

ドット積 attention は depth の平方根の因子でスケールされます。これは、depth の巨大な値のため、ドット積が大きさの点で巨大に増大して (そこでは小さな勾配を持つ) softmax 関数を非常に hard な softmax という結果に押しやるために成されます。

例えば、Q と K が 0 の平均と 1 の分散を持つことを考えます。それらの行列乗算は 0 の平気にと dk の分散を持ちます。こうして、dk の平方根がスケーリングのために使用されます (そして他のどのような数字でもない)、何故ならば Q と K の matmul は 0 の平均と 1 の分散を持つはずですので、gentler softmax を得ます。

マスクは (負の無限大に近い) -1e9 で乗算されます。これが成されるのは、マスクは Q と K のスケールされた行列乗算で総計されて softmax の直前に適用されるからです。目標はこれらのセルをゼロ設定 (= zero out) することで、そして softmax への巨大な負の入力は出力でゼロ近くなります。

def scaled_dot_product_attention(q, k, v, mask):
  """Calculate the attention weights.
  q, k, v must have matching leading dimensions.
  k, v must have matching penultimate dimension, i.e.: seq_len_k = seq_len_v.
  The mask has different shapes depending on its type(padding or look ahead) 
  but it must be broadcastable for addition.
  
  Args:
    q: query shape == (..., seq_len_q, depth)
    k: key shape == (..., seq_len_k, depth)
    v: value shape == (..., seq_len_v, depth_v)
    mask: Float tensor with shape broadcastable 
          to (..., seq_len_q, seq_len_k). Defaults to None.
    
  Returns:
    output, attention_weights
  """

  matmul_qk = tf.matmul(q, k, transpose_b=True)  # (..., seq_len_q, seq_len_k)
  
  # scale matmul_qk
  dk = tf.cast(tf.shape(k)[-1], tf.float32)
  scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)

  # add the mask to the scaled tensor.
  if mask is not None:
    scaled_attention_logits += (mask * -1e9)  

  # softmax is normalized on the last axis (seq_len_k) so that the scores
  # add up to 1.
  attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)  # (..., seq_len_q, seq_len_k)

  output = tf.matmul(attention_weights, v)  # (..., seq_len_q, depth_v)

  return output, attention_weights

softmax 正規化は K 上で行われるので、その値は Q に与えられる重要性の総量を決定します。

出力は attention 重みと V (値) ベクトルの乗算を表します。これは注目したい単語がそのまま保持されて無関係な単語が追い出される (flushed out) ことを確実にします。

def print_out(q, k, v):
  temp_out, temp_attn = scaled_dot_product_attention(
      q, k, v, None)
  print ('Attention weights are:')
  print (temp_attn)
  print ('Output is:')
  print (temp_out)
np.set_printoptions(suppress=True)

temp_k = tf.constant([[10,0,0],
                      [0,10,0],
                      [0,0,10],
                      [0,0,10]], dtype=tf.float32)  # (4, 3)

temp_v = tf.constant([[   1,0],
                      [  10,0],
                      [ 100,5],
                      [1000,6]], dtype=tf.float32)  # (4, 2)

# This `query` aligns with the second `key`,
# so the second `value` is returned.
temp_q = tf.constant([[0, 10, 0]], dtype=tf.float32)  # (1, 3)
print_out(temp_q, temp_k, temp_v)
Attention weights are:
tf.Tensor([[0. 1. 0. 0.]], shape=(1, 4), dtype=float32)
Output is:
tf.Tensor([[10.  0.]], shape=(1, 2), dtype=float32)
# This query aligns with a repeated key (third and fourth), 
# so all associated values get averaged.
temp_q = tf.constant([[0, 0, 10]], dtype=tf.float32)  # (1, 3)
print_out(temp_q, temp_k, temp_v)
Attention weights are:
tf.Tensor([[0.  0.  0.5 0.5]], shape=(1, 4), dtype=float32)
Output is:
tf.Tensor([[550.    5.5]], shape=(1, 2), dtype=float32)
# This query aligns equally with the first and second key, 
# so their values get averaged.
temp_q = tf.constant([[10, 10, 0]], dtype=tf.float32)  # (1, 3)
print_out(temp_q, temp_k, temp_v)
Attention weights are:
tf.Tensor([[0.5 0.5 0.  0. ]], shape=(1, 4), dtype=float32)
Output is:
tf.Tensor([[5.5 0. ]], shape=(1, 2), dtype=float32)

総ての問い合わせを一緒に渡します。

temp_q = tf.constant([[0, 0, 10], [0, 10, 0], [10, 10, 0]], dtype=tf.float32)  # (3, 3)
print_out(temp_q, temp_k, temp_v)
Attention weights are:
tf.Tensor(
[[0.  0.  0.5 0.5]
 [0.  1.  0.  0. ]
 [0.5 0.5 0.  0. ]], shape=(3, 4), dtype=float32)
Output is:
tf.Tensor(
[[550.    5.5]
 [ 10.    0. ]
 [  5.5   0. ]], shape=(3, 2), dtype=float32)

 

マルチヘッド attention

マルチヘッド attention は 4 つのパートから成ります : * 線形層とヘッドへの分割。* スケールされたドット積 attention。* ヘッドの結合。* 最後の線形層。

各マルチヘッド attention ブロックは 3 つの入力を得ます; Q (query), K (key), V (value)。これらは線形 (Dense) 層を通されてマルチヘッドに分割されます。

上で定義された scaled_dot_product_attention は各ヘッドに適用されます (効率のためにブロードキャストされます)。attention ステップでは適切なマスクが使用されなければなりません。それから各ヘッドのための attention 出力は (tf.transposetf.reshape を使用して) 結合されて最後の Dense 層を通されます。

一つの単一の attention ヘッドの代わりに、Q, K と V はマルチヘッドに分割されます、何故ならばそれはモデルに異なる具象空間からの異なる位置における情報に一緒に注意を払うことを可能にするからです。分割の後各ヘッドは削減された次元を持ちますので、総計の計算コストは full 次元を持つ単一ヘッド attention と同じです。

class MultiHeadAttention(tf.keras.layers.Layer):
  def __init__(self, d_model, num_heads):
    super(MultiHeadAttention, self).__init__()
    self.num_heads = num_heads
    self.d_model = d_model
    
    assert d_model % self.num_heads == 0
    
    self.depth = d_model // self.num_heads
    
    self.wq = tf.keras.layers.Dense(d_model)
    self.wk = tf.keras.layers.Dense(d_model)
    self.wv = tf.keras.layers.Dense(d_model)
    
    self.dense = tf.keras.layers.Dense(d_model)
        
  def split_heads(self, x, batch_size):
    """Split the last dimension into (num_heads, depth).
    Transpose the result such that the shape is (batch_size, num_heads, seq_len, depth)
    """
    x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))
    return tf.transpose(x, perm=[0, 2, 1, 3])
    
  def call(self, v, k, q, mask):
    batch_size = tf.shape(q)[0]
    
    q = self.wq(q)  # (batch_size, seq_len, d_model)
    k = self.wk(k)  # (batch_size, seq_len, d_model)
    v = self.wv(v)  # (batch_size, seq_len, d_model)
    
    q = self.split_heads(q, batch_size)  # (batch_size, num_heads, seq_len_q, depth)
    k = self.split_heads(k, batch_size)  # (batch_size, num_heads, seq_len_k, depth)
    v = self.split_heads(v, batch_size)  # (batch_size, num_heads, seq_len_v, depth)
    
    # scaled_attention.shape == (batch_size, num_heads, seq_len_q, depth)
    # attention_weights.shape == (batch_size, num_heads, seq_len_q, seq_len_k)
    scaled_attention, attention_weights = scaled_dot_product_attention(
        q, k, v, mask)
    
    scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])  # (batch_size, seq_len_q, num_heads, depth)

    concat_attention = tf.reshape(scaled_attention, 
                                  (batch_size, -1, self.d_model))  # (batch_size, seq_len_q, d_model)

    output = self.dense(concat_attention)  # (batch_size, seq_len_q, d_model)
        
    return output, attention_weights

試すために MultiHeadAttention 層を作成します。シークエンス y の各位置で、MultiHeadAttention で総ての 8 attention ヘッドをシークエンスの総ての他の位置に渡り実行し、各位置で同じ長さの新しいベクトルを返します。

temp_mha = MultiHeadAttention(d_model=512, num_heads=8)
y = tf.random.uniform((1, 60, 512))  # (batch_size, encoder_sequence, d_model)
out, attn = temp_mha(y, k=y, q=y, mask=None)
out.shape, attn.shape
(TensorShape([1, 60, 512]), TensorShape([1, 8, 60, 60]))

 

ポイント-wise 順伝播ネットワーク

ポイント-wise 順伝播ネットワークは間に ReLU 活性を持つ 2 つの完全結合層から成ります。

def point_wise_feed_forward_network(d_model, dff):
  return tf.keras.Sequential([
      tf.keras.layers.Dense(dff, activation='relu'),  # (batch_size, seq_len, dff)
      tf.keras.layers.Dense(d_model)  # (batch_size, seq_len, d_model)
  ])
sample_ffn = point_wise_feed_forward_network(512, 2048)
sample_ffn(tf.random.uniform((64, 50, 512))).shape
TensorShape([64, 50, 512])

 

エンコーダとデコーダ

transformer モデルは標準的な sequence to sequence with attention model と同じ一般的なパターンに従います。

  • 入力センテンスはシークエンスの各単語/トークンのための出力を生成する N エンコーダ層を通されます。
  • デコーダは次の単語を予測するためにエンコーダの出力とそれ自身の入力 (self-attention) に注目します。

 

エンコーダ層

各エンコーダ層は副層 (= sublayer) から成ります :

  1. (パディング・マスクを持つ) マルチヘッド attention
  2. ポイント-wise な順伝播ネットワーク。

これらの副層の各々はその回りに残差接続を持ち層正規化が続きます。残差接続は深層ネットワークにおける勾配消失問題を回避する助けとなります。各副層の出力は LayerNorm(x + Sublayer(x)) です。正規化は d_model (last) 軸上で行われます。transformer には N エンコーダ層があります。

class EncoderLayer(tf.keras.layers.Layer):
  def __init__(self, d_model, num_heads, dff, rate=0.1):
    super(EncoderLayer, self).__init__()

    self.mha = MultiHeadAttention(d_model, num_heads)
    self.ffn = point_wise_feed_forward_network(d_model, dff)

    self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
    self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
    
    self.dropout1 = tf.keras.layers.Dropout(rate)
    self.dropout2 = tf.keras.layers.Dropout(rate)
    
  def call(self, x, training, mask):

    attn_output, _ = self.mha(x, x, x, mask)  # (batch_size, input_seq_len, d_model)
    attn_output = self.dropout1(attn_output, training=training)
    out1 = self.layernorm1(x + attn_output)  # (batch_size, input_seq_len, d_model)
    
    ffn_output = self.ffn(out1)  # (batch_size, input_seq_len, d_model)
    ffn_output = self.dropout2(ffn_output, training=training)
    out2 = self.layernorm2(out1 + ffn_output)  # (batch_size, input_seq_len, d_model)
    
    return out2
sample_encoder_layer = EncoderLayer(512, 8, 2048)

sample_encoder_layer_output = sample_encoder_layer(
    tf.random.uniform((64, 43, 512)), False, None)

sample_encoder_layer_output.shape  # (batch_size, input_seq_len, d_model)
TensorShape([64, 43, 512])

 

デコーダ層

各デコーダ層は次の副層から成ります :

  1. (look ahead マスクとパディングマスクを持つ) マスクされたマルチヘッド attention
  2. (パディングマスクを持つ) マルチヘッド attention。V (value) と K (key) はエンコーダ出力を入力として受け取ります。Q (query) はマスクされたマルチヘッド attention 副層からの出力を受け取ります。
  3. ポイント wise 順伝播ネットワーク

これらの副層の各々はその回りに残差接続を持ち層正規化が続きます。各副層の出力は LayerNorm(x + Sublayer(x)) です。正規化は d_model (last) 軸上で行われます。

transformer には N デコーダ層があります。

Q がデコーダの最初の attention ブロックからの出力を受け取り、そして K がエンコーダ出力を受け取るとき、attention 重みはエンコーダ出力に基づくデコーダの入力に与えられる重要性を表します。換言すれば、デコーダはエンコーダ出力とそれ自身の出力への self-attending を見ることにより次の単語を予測します。スケールされたドット積 attention セクションの上のデモを見てください。

class DecoderLayer(tf.keras.layers.Layer):
  def __init__(self, d_model, num_heads, dff, rate=0.1):
    super(DecoderLayer, self).__init__()

    self.mha1 = MultiHeadAttention(d_model, num_heads)
    self.mha2 = MultiHeadAttention(d_model, num_heads)

    self.ffn = point_wise_feed_forward_network(d_model, dff)
 
    self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
    self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
    self.layernorm3 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
    
    self.dropout1 = tf.keras.layers.Dropout(rate)
    self.dropout2 = tf.keras.layers.Dropout(rate)
    self.dropout3 = tf.keras.layers.Dropout(rate)
    
    
  def call(self, x, enc_output, training, 
           look_ahead_mask, padding_mask):
    # enc_output.shape == (batch_size, input_seq_len, d_model)

    attn1, attn_weights_block1 = self.mha1(x, x, x, look_ahead_mask)  # (batch_size, target_seq_len, d_model)
    attn1 = self.dropout1(attn1, training=training)
    out1 = self.layernorm1(attn1 + x)
    
    attn2, attn_weights_block2 = self.mha2(
        enc_output, enc_output, out1, padding_mask)  # (batch_size, target_seq_len, d_model)
    attn2 = self.dropout2(attn2, training=training)
    out2 = self.layernorm2(attn2 + out1)  # (batch_size, target_seq_len, d_model)
    
    ffn_output = self.ffn(out2)  # (batch_size, target_seq_len, d_model)
    ffn_output = self.dropout3(ffn_output, training=training)
    out3 = self.layernorm3(ffn_output + out2)  # (batch_size, target_seq_len, d_model)
    
    return out3, attn_weights_block1, attn_weights_block2
sample_decoder_layer = DecoderLayer(512, 8, 2048)

sample_decoder_layer_output, _, _ = sample_decoder_layer(
    tf.random.uniform((64, 50, 512)), sample_encoder_layer_output, 
    False, None, None)

sample_decoder_layer_output.shape  # (batch_size, target_seq_len, d_model)
TensorShape([64, 50, 512])

 

エンコーダ

エンコーダは: 1. 入力埋め込み 2. 位置エンコーディング 3. N エンコーダ層から成ります。

入力は埋め込みを通されます、これは位置エンコーディングにより総計されます。この総計の出力はエンコーダ層への入力です。エンコーダの出力はデコーダへの入力です。

class Encoder(tf.keras.layers.Layer):
  def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, 
               rate=0.1):
    super(Encoder, self).__init__()

    self.d_model = d_model
    self.num_layers = num_layers
    
    self.embedding = tf.keras.layers.Embedding(input_vocab_size, d_model)
    self.pos_encoding = positional_encoding(input_vocab_size, self.d_model)
    
    
    self.enc_layers = [EncoderLayer(d_model, num_heads, dff, rate) 
                       for _ in range(num_layers)]
  
    self.dropout = tf.keras.layers.Dropout(rate)
        
  def call(self, x, training, mask):

    seq_len = tf.shape(x)[1]
    
    # adding embedding and position encoding.
    x = self.embedding(x)  # (batch_size, input_seq_len, d_model)
    x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
    x += self.pos_encoding[:, :seq_len, :]

    x = self.dropout(x, training=training)
    
    for i in range(self.num_layers):
      x = self.enc_layers[i](x, training, mask)
    
    return x  # (batch_size, input_seq_len, d_model)
sample_encoder = Encoder(num_layers=2, d_model=512, num_heads=8, 
                         dff=2048, input_vocab_size=8500)

sample_encoder_output = sample_encoder(tf.random.uniform((64, 62)), 
                                       training=False, mask=None)

print (sample_encoder_output.shape)  # (batch_size, input_seq_len, d_model)
(64, 62, 512)

 

デコーダ

デコーダは: 1. 出力埋め込み 2. 位置エンコーディング 3. N デコーダ層から成ります。

ターゲットは埋め込みを通されます、これは位置エンコーディングにより総計されます。この総計の出力はデコーダ層への入力です。デコーダの出力は最後の線形層への入力です。

class Decoder(tf.keras.layers.Layer):
  def __init__(self, num_layers, d_model, num_heads, dff, target_vocab_size, 
               rate=0.1):
    super(Decoder, self).__init__()

    self.d_model = d_model
    self.num_layers = num_layers
    
    self.embedding = tf.keras.layers.Embedding(target_vocab_size, d_model)
    self.pos_encoding = positional_encoding(target_vocab_size, self.d_model)
    
    self.dec_layers = [DecoderLayer(d_model, num_heads, dff, rate) 
                       for _ in range(num_layers)]
    self.dropout = tf.keras.layers.Dropout(rate)
    
  def call(self, x, enc_output, training, 
           look_ahead_mask, padding_mask):

    seq_len = tf.shape(x)[1]
    attention_weights = {}
    
    x = self.embedding(x)  # (batch_size, target_seq_len, d_model)
    x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
    x += self.pos_encoding[:, :seq_len, :]
    
    x = self.dropout(x, training=training)

    for i in range(self.num_layers):
      x, block1, block2 = self.dec_layers[i](x, enc_output, training,
                                             look_ahead_mask, padding_mask)
      
      attention_weights['decoder_layer{}_block1'.format(i+1)] = block1
      attention_weights['decoder_layer{}_block2'.format(i+1)] = block2
    
    # x.shape == (batch_size, target_seq_len, d_model)
    return x, attention_weights
sample_decoder = Decoder(num_layers=2, d_model=512, num_heads=8, 
                         dff=2048, target_vocab_size=8000)

output, attn = sample_decoder(tf.random.uniform((64, 26)), 
                              enc_output=sample_encoder_output, 
                              training=False, look_ahead_mask=None, 
                              padding_mask=None)

output.shape, attn['decoder_layer2_block2'].shape
(TensorShape([64, 26, 512]), TensorShape([64, 8, 26, 62]))

 

Transformer を作成する

Transformer はエンコーダ、デコーダと最後の線形層から成ります。デコーダの出力は線形層への入力でその出力が返されます。

class Transformer(tf.keras.Model):
  def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, 
               target_vocab_size, rate=0.1):
    super(Transformer, self).__init__()

    self.encoder = Encoder(num_layers, d_model, num_heads, dff, 
                           input_vocab_size, rate)

    self.decoder = Decoder(num_layers, d_model, num_heads, dff, 
                           target_vocab_size, rate)

    self.final_layer = tf.keras.layers.Dense(target_vocab_size)
    
  def call(self, inp, tar, training, enc_padding_mask, 
           look_ahead_mask, dec_padding_mask):

    enc_output = self.encoder(inp, training, enc_padding_mask)  # (batch_size, inp_seq_len, d_model)
    
    # dec_output.shape == (batch_size, tar_seq_len, d_model)
    dec_output, attention_weights = self.decoder(
        tar, enc_output, training, look_ahead_mask, dec_padding_mask)
    
    final_output = self.final_layer(dec_output)  # (batch_size, tar_seq_len, target_vocab_size)
    
    return final_output, attention_weights
sample_transformer = Transformer(
    num_layers=2, d_model=512, num_heads=8, dff=2048, 
    input_vocab_size=8500, target_vocab_size=8000)

temp_input = tf.random.uniform((64, 62))
temp_target = tf.random.uniform((64, 26))

fn_out, _ = sample_transformer(temp_input, temp_target, training=False, 
                               enc_padding_mask=None, 
                               look_ahead_mask=None,
                               dec_padding_mask=None)

fn_out.shape  # (batch_size, tar_seq_len, target_vocab_size)
TensorShape([64, 26, 8000])

 

ハイパーパラメータを設定する

この例題を小さく比較的高速に保つために、num_layers, d_model と dff のための値は減じられます。

transformer の基本モデルで使用された値は ; num_layers=6, d_model = 512, dff = 2048 でした。transformer の総ての他のバージョンについては ペーパー を見てください。

Note: 下の値を変更することにより、多くのタスク上で最先端技術を達成するモデルを得ることができます。

num_layers = 4
d_model = 128
dff = 512
num_heads = 8

input_vocab_size = tokenizer_pt.vocab_size + 2
target_vocab_size = tokenizer_en.vocab_size + 2
dropout_rate = 0.1

 

Optimizer

ペーパー の公式に従うカスタム学習率スケジューラを持つ Adam optimizer を使用します。

$$\Large{lrate = d_{model}^{-0.5} * min(step{\_}num^{-0.5}, step{\_}num * warmup{\_}steps^{-1.5})}$$
class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
  def __init__(self, d_model, warmup_steps=4000):
    super(CustomSchedule, self).__init__()
    
    self.d_model = d_model
    self.d_model = tf.cast(self.d_model, tf.float32)

    self.warmup_steps = warmup_steps
    
  def __call__(self, step):
    arg1 = tf.math.rsqrt(step)
    arg2 = step * (self.warmup_steps ** -1.5)
    
    return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)
learning_rate = CustomSchedule(d_model)

optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.9, beta_2=0.98, 
                                     epsilon=1e-9)
temp_learning_rate_schedule = CustomSchedule(d_model)

plt.plot(temp_learning_rate_schedule(tf.range(40000, dtype=tf.float32)))
plt.ylabel("Learning Rate")
plt.xlabel("Train Step")
Text(0.5, 0, 'Train Step')

 

損失とメトリクス

ターゲット・シークエンスはパディングされていますので、損失を計算するときパディング・マスクを適用することは重要です。

loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction='none')
def loss_function(real, pred):
  mask = tf.math.logical_not(tf.math.equal(real, 0))
  loss_ = loss_object(real, pred)

  mask = tf.cast(mask, dtype=loss_.dtype)
  loss_ *= mask
  
  return tf.reduce_mean(loss_)
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(
    name='train_accuracy')

 

訓練とチェックポインティング

transformer = Transformer(num_layers, d_model, num_heads, dff,
                          input_vocab_size, target_vocab_size, dropout_rate)
def create_masks(inp, tar):
  # Encoder padding mask
  enc_padding_mask = create_padding_mask(inp)
  
  # Used in the 2nd attention block in the decoder.
  # This padding mask is used to mask the encoder outputs.
  dec_padding_mask = create_padding_mask(inp)
  
  # Used in the 1st attention block in the decoder.
  # It is used to pad and mask future tokens in the input received by 
  # the decoder.
  look_ahead_mask = create_look_ahead_mask(tf.shape(tar)[1])
  dec_target_padding_mask = create_padding_mask(tar)
  combined_mask = tf.maximum(dec_target_padding_mask, look_ahead_mask)
  
  return enc_padding_mask, combined_mask, dec_padding_mask

チェックポイント・パスとチェックポイント・マネージャを作成します。これは n エポック毎にチェックポイントをセーブするために使用されます。

checkpoint_path = "./checkpoints/train"

ckpt = tf.train.Checkpoint(transformer=transformer,
                           optimizer=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!!')

ターゲットは tar_inp と tar_real に分割されます。tar_inp はデコーダへの入力として渡されます。tar_real は 1 シフトされたそれと同じ入力です : tar_input の各位置において、tar_real は予測されるべき次のトークンを含みます。

例えば、sentence = "SOS A lion in the jungle is sleeping EOS"

tar_inp = "SOS A lion in the jungle is sleeping"

tar_real = "A lion in the jungle is sleeping EOS"

transformer は自己回帰モデルです : それは一度に一つのパートの予測を行ないます、そして次に何を行なうかを決めるためにそこまでのその出力使用します。

訓練の間この例は (テキスト生成チュートリアル 内のように) teacher-forcing を使用します。teacher forcing は現在の時間ステップでモデルが何を予測するかにかかわらず、次の時間ステップに対して真の出力を渡します。

transformer が各単語を予測するとき、self-attention は次の単語をより良く予測するためにそれに入力シークエンスの前の単語を見ることを許容します。

モデルが期待される出力でピークに達することを防ぐためにモデルは look-ahead マスクを使用します。

EPOCHS = 20
@tf.function
def train_step(inp, tar):
  tar_inp = tar[:, :-1]
  tar_real = tar[:, 1:]
  
  enc_padding_mask, combined_mask, dec_padding_mask = create_masks(inp, tar_inp)
  
  with tf.GradientTape() as tape:
    predictions, _ = transformer(inp, tar_inp, 
                                 True, 
                                 enc_padding_mask, 
                                 combined_mask, 
                                 dec_padding_mask)
    loss = loss_function(tar_real, predictions)

  gradients = tape.gradient(loss, transformer.trainable_variables)    
  optimizer.apply_gradients(zip(gradients, transformer.trainable_variables))
  
  train_loss(loss)
  train_accuracy(tar_real, predictions)

ポルトガル語は入力言語として使用されて英語はターゲット言語として使用されます。

for epoch in range(EPOCHS):
  start = time.time()
  
  train_loss.reset_states()
  train_accuracy.reset_states()
  
  # inp -> portuguese, tar -> english
  for (batch, (inp, tar)) in enumerate(train_dataset):
    train_step(inp, tar)
    
    if batch % 500 == 0:
      print ('Epoch {} Batch {} Loss {:.4f} Accuracy {:.4f}'.format(
          epoch + 1, batch, train_loss.result(), train_accuracy.result()))
      
  if (epoch + 1) % 5 == 0:
    ckpt_save_path = ckpt_manager.save()
    print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
                                                         ckpt_save_path))
    
  print ('Epoch {} Loss {:.4f} Accuracy {:.4f}'.format(epoch + 1, 
                                                train_loss.result(), 
                                                train_accuracy.result()))

  print ('Time taken for 1 epoch: {} secs\n'.format(time.time() - start))
Epoch 1 Batch 0 Loss 4.2989 Accuracy 0.0000
Epoch 1 Batch 500 Loss 3.6015 Accuracy 0.0373
Epoch 1 Loss 3.3603 Accuracy 0.0516
Time taken for 1 epoch: 1183.8238337039948 secs

Epoch 2 Batch 0 Loss 2.6611 Accuracy 0.0990
Epoch 2 Batch 500 Loss 2.4153 Accuracy 0.1205
Epoch 2 Loss 2.3610 Accuracy 0.1255
Time taken for 1 epoch: 59.42831206321716 secs

Epoch 3 Batch 0 Loss 2.2400 Accuracy 0.1385
Epoch 3 Batch 500 Loss 2.1346 Accuracy 0.1485
Epoch 3 Loss 2.1008 Accuracy 0.1515
Time taken for 1 epoch: 61.30026984214783 secs

Epoch 4 Batch 0 Loss 1.9878 Accuracy 0.1736
Epoch 4 Batch 500 Loss 1.9165 Accuracy 0.1728
Epoch 4 Loss 1.8791 Accuracy 0.1767
Time taken for 1 epoch: 58.594091176986694 secs

Epoch 5 Batch 0 Loss 1.7749 Accuracy 0.1979
Epoch 5 Batch 500 Loss 1.6912 Accuracy 0.2004
Saving checkpoint for epoch 5 at ./checkpoints/train/ckpt-1
Epoch 5 Loss 1.6600 Accuracy 0.2032
Time taken for 1 epoch: 58.794411182403564 secs

Epoch 6 Batch 0 Loss 1.5714 Accuracy 0.2166
Epoch 6 Batch 500 Loss 1.5055 Accuracy 0.2208
Epoch 6 Loss 1.4784 Accuracy 0.2231
Time taken for 1 epoch: 58.68531322479248 secs

Epoch 7 Batch 0 Loss 1.3653 Accuracy 0.2448
Epoch 7 Batch 500 Loss 1.3229 Accuracy 0.2414
Epoch 7 Loss 1.2951 Accuracy 0.2441
Time taken for 1 epoch: 58.634846687316895 secs

Epoch 8 Batch 0 Loss 1.1872 Accuracy 0.2604
Epoch 8 Batch 500 Loss 1.1627 Accuracy 0.2610
Epoch 8 Loss 1.1418 Accuracy 0.2628
Time taken for 1 epoch: 63.22854280471802 secs

Epoch 9 Batch 0 Loss 1.0552 Accuracy 0.2804
Epoch 9 Batch 500 Loss 1.0437 Accuracy 0.2758
Epoch 9 Loss 1.0284 Accuracy 0.2769
Time taken for 1 epoch: 59.79085969924927 secs

Epoch 10 Batch 0 Loss 0.9547 Accuracy 0.2947
Epoch 10 Batch 500 Loss 0.9574 Accuracy 0.2864
Saving checkpoint for epoch 10 at ./checkpoints/train/ckpt-2
Epoch 10 Loss 0.9452 Accuracy 0.2872
Time taken for 1 epoch: 59.45661449432373 secs

Epoch 11 Batch 0 Loss 0.8765 Accuracy 0.3056
Epoch 11 Batch 500 Loss 0.8882 Accuracy 0.2959
Epoch 11 Loss 0.8775 Accuracy 0.2965
Time taken for 1 epoch: 59.00851774215698 secs

Epoch 12 Batch 0 Loss 0.8012 Accuracy 0.3108
Epoch 12 Batch 500 Loss 0.8317 Accuracy 0.3032
Epoch 12 Loss 0.8224 Accuracy 0.3038
Time taken for 1 epoch: 59.61574602127075 secs

Epoch 13 Batch 0 Loss 0.7604 Accuracy 0.3212
Epoch 13 Batch 500 Loss 0.7867 Accuracy 0.3095
Epoch 13 Loss 0.7779 Accuracy 0.3101
Time taken for 1 epoch: 61.540324449539185 secs

Epoch 14 Batch 0 Loss 0.7314 Accuracy 0.3242
Epoch 14 Batch 500 Loss 0.7459 Accuracy 0.3155
Epoch 14 Loss 0.7372 Accuracy 0.3159
Time taken for 1 epoch: 59.23609805107117 secs

Epoch 15 Batch 0 Loss 0.6704 Accuracy 0.3338
Epoch 15 Batch 500 Loss 0.7071 Accuracy 0.3212
Saving checkpoint for epoch 15 at ./checkpoints/train/ckpt-3
Epoch 15 Loss 0.7007 Accuracy 0.3212
Time taken for 1 epoch: 59.86083674430847 secs

Epoch 16 Batch 0 Loss 0.6571 Accuracy 0.3312
Epoch 16 Batch 500 Loss 0.6749 Accuracy 0.3257
Epoch 16 Loss 0.6680 Accuracy 0.3260
Time taken for 1 epoch: 58.48514103889465 secs

Epoch 17 Batch 0 Loss 0.6378 Accuracy 0.3368
Epoch 17 Batch 500 Loss 0.6461 Accuracy 0.3304
Epoch 17 Loss 0.6402 Accuracy 0.3304
Time taken for 1 epoch: 59.11438250541687 secs

Epoch 18 Batch 0 Loss 0.5862 Accuracy 0.3455
Epoch 18 Batch 500 Loss 0.6217 Accuracy 0.3336
Epoch 18 Loss 0.6156 Accuracy 0.3337
Time taken for 1 epoch: 59.84031653404236 secs

Epoch 19 Batch 0 Loss 0.5931 Accuracy 0.3424
Epoch 19 Batch 500 Loss 0.5977 Accuracy 0.3377
Epoch 19 Loss 0.5921 Accuracy 0.3378
Time taken for 1 epoch: 58.689385175704956 secs

Epoch 20 Batch 0 Loss 0.5611 Accuracy 0.3485
Epoch 20 Batch 500 Loss 0.5770 Accuracy 0.3408
Saving checkpoint for epoch 20 at ./checkpoints/train/ckpt-4
Epoch 20 Loss 0.5721 Accuracy 0.3408
Time taken for 1 epoch: 59.26789140701294 secs

 

評価する

評価のために次のステップが使用されます :

  • ポルトガル語字句解析器 (tokenizer_pt) を使用して入力センテンスをエンコードします。更に、start と end トークンを追加しますので入力はモデルがそれで訓練されたものと等値です。これがエンコーダ入力です。
  • デコーダ入力は "start token == tokenizer_en.vocab_size" です。
  • パディング・マスクと look ahead マスクを計算する。
  • それからデコーダはエンコーダ出力とそれ自身の出力 (self-attention) を見て予測を出力します。
  • 最後の単語を選択してその argmax を計算します。
  • 予測された単語をデコーダ入力へそれをデコーダに渡すとき結合します。
  • このアプローチでは、デコーダはそれが予測した前の単語を基にして次の単語を予測します。

Note: ここで使用されたモデルは例題を比較的高速に保つためにより少ないキャパシティを持ちますので多分予測は少し正しくないでしょう。ペーパーの結果を再現するためには、上のパラメータを変更することにより、データセット全体と基本 transformer モデルか transformer XL を使用します。

def evaluate(inp_sentence):
  start_token = [tokenizer_pt.vocab_size]
  end_token = [tokenizer_pt.vocab_size + 1]
  
  # inp sentence is portuguese, hence adding the start and end token
  inp_sentence = start_token + tokenizer_pt.encode(inp_sentence) + end_token
  encoder_input = tf.expand_dims(inp_sentence, 0)
  
  # as the target is english, the first word to the transformer should be the
  # english start token.
  decoder_input = [tokenizer_en.vocab_size]
  output = tf.expand_dims(decoder_input, 0)
    
  for i in range(MAX_LENGTH):
    enc_padding_mask, combined_mask, dec_padding_mask = create_masks(
        encoder_input, output)
  
    # predictions.shape == (batch_size, seq_len, vocab_size)
    predictions, attention_weights = transformer(encoder_input, 
                                                 output,
                                                 False,
                                                 enc_padding_mask,
                                                 combined_mask,
                                                 dec_padding_mask)
    
    # select the last word from the seq_len dimension
    predictions = predictions[: ,-1:, :]  # (batch_size, 1, vocab_size)

    predicted_id = tf.cast(tf.argmax(predictions, axis=-1), tf.int32)
    
    # return the result if the predicted_id is equal to the end token
    if tf.equal(predicted_id, tokenizer_en.vocab_size+1):
      return tf.squeeze(output, axis=0), attention_weights
    
    # concatentate the predicted_id to the output which is given to the decoder
    # as its input.
    output = tf.concat([output, predicted_id], axis=-1)

  return tf.squeeze(output, axis=0), attention_weights
def plot_attention_weights(attention, sentence, result, layer):
  fig = plt.figure(figsize=(16, 8))
  
  sentence = tokenizer_pt.encode(sentence)
  
  attention = tf.squeeze(attention[layer], axis=0)
  
  for head in range(attention.shape[0]):
    ax = fig.add_subplot(2, 4, head+1)
    
    # plot the attention weights
    ax.matshow(attention[head][:-1, :], cmap='viridis')

    fontdict = {'fontsize': 10}
    
    ax.set_xticks(range(len(sentence)+2))
    ax.set_yticks(range(len(result)))
    
    ax.set_ylim(len(result)-1.5, -0.5)
        
    ax.set_xticklabels(
        ['']+[tokenizer_pt.decode([i]) for i in sentence]+[''], 
        fontdict=fontdict, rotation=90)
    
    ax.set_yticklabels([tokenizer_en.decode([i]) for i in result 
                        if i < tokenizer_en.vocab_size], 
                       fontdict=fontdict)
    
    ax.set_xlabel('Head {}'.format(head+1))
  
  plt.tight_layout()
  plt.show()
def translate(sentence, plot=''):
  result, attention_weights = evaluate(sentence)
  
  predicted_sentence = tokenizer_en.decode([i for i in result 
                                            if i < tokenizer_en.vocab_size])  

  print('Input: {}'.format(sentence))
  print('Predicted translation: {}'.format(predicted_sentence))
  
  if plot:
    plot_attention_weights(attention_weights, sentence, result, plot)
translate("este é um problema que temos que resolver.")
print ("Real translation: this is a problem we have to solve .")
Input: este é um problema que temos que resolver.
Predicted translation: this is a problem that we have to solve .......... ..... .............. of that 
Real translation: this is a problem we have to solve .
translate("os meus vizinhos ouviram sobre esta ideia.")
print ("Real translation: and my neighboring homes heard about this idea .")
Input: os meus vizinhos ouviram sobre esta ideia.
Predicted translation: my neighbors heard about this idea .
Real translation: and my neighboring homes heard about this idea .
translate("vou então muito rapidamente partilhar convosco algumas histórias de algumas coisas mágicas que aconteceram.")
print ("Real translation: so i 'll just share with you some stories very quickly of some magical things that have happened .")
Input: vou então muito rapidamente partilhar convosco algumas histórias de algumas coisas mágicas que aconteceram.
Predicted translation: so i 'm going to share with you a lot of stories that are very quickly that actually buzzs on top of the data .
Real translation: so i 'll just share with you some stories very quickly of some magical things that have happened .

デコーダの異なる層と attention ブロックをプロット・パラメータに渡すことができます。

translate("este é o primeiro livro que eu fiz.", plot='decoder_layer4_block2')
print ("Real translation: this is the first book i've ever done.")
Input: este é o primeiro livro que eu fiz.
Predicted translation: this is the first book that i did it .


Real translation: this is the first book i've ever done.

 

要約

このチュートリアルでは、位置エンコーディング、マルチヘッド attention、マスキングの重要性そして transformer をどのように作成するかを学習しました。

transformer を訓練するために異なるデータセットを使用してみてください。貴方はまた上のハイパーパラメータを変更して基本 transformer か transformer XL を作成することもできます。また BERT を作成して最先端モデルを訓練するためにここで定義された層を使用することもできます。更に、より良い予測を得るためにビームサーチを実装できます。

 

以上






TensorFlow 2.0 Beta : 上級 Tutorials : テキストとシークエンス :- 画像キャプショニング with Attention

TensorFlow 2.0 Beta : 上級 Tutorials : テキストとシークエンス :- 画像キャプショニング with Attention (翻訳/解説)

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

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

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

 

 

テキストとシークエンス :- 画像キャプショニング with Attention

下のような画像が与えられたとき、私達の目標は “a surfer riding on a wave” のようなキャプションを生成することです。

画像ソース, ライセンス: Public Domain

 
これを達成するために、attention ベースのモデルを使用します、これは (モデルが) キャプションを生成するとき画像のどのパートにモデルが注目しているかを見ることを可能にします。

モデル・アーキテクチャは Show, Attend and Tell: Neural Image Caption Generation with Visual Attention に類似しています。

このノートブックは end-to-end なサンプルです。ノートブックを実行する時、それは MS-COCO データセットをダウンロードし、Inception V3 を使用して画像のサブセットを前処理してキャッシュし、エンコーダ-デコーダ・モデルを訓練して、そして訓練されたモデルを使用して新しい画像上でキャプションを生成します。

このサンプルでは、比較的小さい総量のデータ上でモデルを訓練します — およそ 20,000 画像のための最初の 30,000 キャプションです (データセットの画像毎に複数のキャプションがあるからです)。

from __future__ import absolute_import, division, print_function, unicode_literals
!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf

# You'll generate plots of attention in order to see which parts of an image
# our model focuses on during captioning
import matplotlib.pyplot as plt

# Scikit-learn includes many helpful utilities
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle

import re
import numpy as np
import os
import time
import json
from glob import glob
from PIL import Image
import pickle

 

MS-COCO データセットをダウンロードして準備する

モデルを訓練するために MS-COCO データセット を使用します。このデータセットは 82,000 以上の画像を含み、その各々は少なくとも 5 つの異なるキャプションのアノテーションを持ちます。下のコードはデータセットを自動的にダウンロードして抽出します。

警告: これから先に巨大なダウンロード があります。訓練セットを使用します、これは 13 GB ファイルです。

annotation_zip = tf.keras.utils.get_file('captions.zip',
                                          cache_subdir=os.path.abspath('.'),
                                          origin = 'http://images.cocodataset.org/annotations/annotations_trainval2014.zip',
                                          extract = True)
annotation_file = os.path.dirname(annotation_zip)+'/annotations/captions_train2014.json'

name_of_zip = 'train2014.zip'
if not os.path.exists(os.path.abspath('.') + '/' + name_of_zip):
  image_zip = tf.keras.utils.get_file(name_of_zip,
                                      cache_subdir=os.path.abspath('.'),
                                      origin = 'http://images.cocodataset.org/zips/train2014.zip',
                                      extract = True)
  PATH = os.path.dirname(image_zip)+'/train2014/'
else:
  PATH = os.path.abspath('.')+'/train2014/'
Downloading data from http://images.cocodataset.org/annotations/annotations_trainval2014.zip
252878848/252872794 [==============================] - 6s 0us/step
Downloading data from http://images.cocodataset.org/zips/train2014.zip
13510574080/13510573713 [==============================] - 298s 0us/step

 

オプション: 訓練セットのサイズを制限する

このチュートリアルのための訓練を高速化するため、モデルを訓練するために 30,000 キャプションのサブセットとそれらに対応する画像を使用します。より多くのデータを使用することの選択は改善されたキャプショニング品質という結果になるでしょう。

# Read the json file
with open(annotation_file, 'r') as f:
    annotations = json.load(f)

# Store captions and image names in vectors
all_captions = []
all_img_name_vector = []

for annot in annotations['annotations']:
    caption = ' ' + annot['caption'] + ' '
    image_id = annot['image_id']
    full_coco_image_path = PATH + 'COCO_train2014_' + '%012d.jpg' % (image_id)

    all_img_name_vector.append(full_coco_image_path)
    all_captions.append(caption)

# Shuffle captions and image_names together
# Set a random state
train_captions, img_name_vector = shuffle(all_captions,
                                          all_img_name_vector,
                                          random_state=1)

# Select the first 30000 captions from the shuffled set
num_examples = 30000
train_captions = train_captions[:num_examples]
img_name_vector = img_name_vector[:num_examples]
len(train_captions), len(all_captions)
(30000, 414113)

 

InceptionV3 を使用して画像を前処理する

次に、各画像を分類するために (Imagenet で事前訓練された) InceptionV3 を使用します。最後の畳込み層から特徴を抽出します。

最初に、画像を次により inceptionV3 が想定するフォーマットに変換する必要があります : * 画像を 299px x 299px にリサイズします。* preprocess_input メソッドを使用して画像を正規化するために 画像を前処理します、その結果それは -1 から 1 の範囲のピクセルを含みます、これは InceptionV3 を訓練するために使用された画像のフォーマットに適合させるためです。

def load_image(image_path):
    img = tf.io.read_file(image_path)
    img = tf.image.decode_jpeg(img, channels=3)
    img = tf.image.resize(img, (299, 299))
    img = tf.keras.applications.inception_v3.preprocess_input(img)
    return img, image_path

 

InceptionV3 を初期化して事前訓練された Imagenet 重みをロードする

さて tf.keras モデルを作成します、そこでは出力層は InceptionV3 アーキテクチャの最後の畳み込み層です。この層の出力の shape は 8x8x2048 です。最後の畳み込み層を使用します、何故ならばこのサンプルでは attention を使用しているからです。訓練の間にこの初期化は遂行しません、何故ならばそれはボトルネックになり得るからです。

  • 各画像はネットワークを通して forward されて最後に得られるベクトルは辞書にストアされます (image_name –> feature_vector)。
  • 総ての画像がネットワークを通された後、辞書を pickle 化してそれをディスクにセーブします。
image_model = tf.keras.applications.InceptionV3(include_top=False,
                                                weights='imagenet')
new_input = image_model.input
hidden_layer = image_model.layers[-1].output

image_features_extract_model = tf.keras.Model(new_input, hidden_layer)
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.5/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5
87916544/87910968 [==============================] - 2s 0us/step

 

InceptionV3 から抽出した特徴をキャッシュする

各画像を InceptionV3 で前処理して出力をディスクにキャッシュします。出力を RAM にキャッシングすることはより高速ですがメモリ集約的で、画像毎に 8 * 8 * 2048 floats を必要とします。書く時点で、これは Colab のメモリ制限を超過するでしょう (現在 12 GB メモリ)。

パフォーマンスはより洗練されたキャッシング・ストラテジー (例えば、ディスク I/O へのランダムアクセスを減じるために画像群をシャーディングする) で改善されるかもしれませんが、それはより多くのコードを必要とするでしょう。

キャッシングは GPU を持つ Colab で実行するのにおよそ 10 分間かかります。進捗バーを見ることを望む場合、次ができます :

  1. tqdm をインストールする:
    !pip install -q tqdm

  2. Import tqdm:
    from tqdm import tqdm

  3. 次の行を変更する:
    for img, path in image_dataset:
    to:
    for img, path in tqdm(image_dataset):.
# Get unique images
encode_train = sorted(set(img_name_vector))

# Feel free to change batch_size according to your system configuration
image_dataset = tf.data.Dataset.from_tensor_slices(encode_train)
image_dataset = image_dataset.map(
  load_image, num_parallel_calls=tf.data.experimental.AUTOTUNE).batch(16)

for img, path in image_dataset:
  batch_features = image_features_extract_model(img)
  batch_features = tf.reshape(batch_features,
                              (batch_features.shape[0], -1, batch_features.shape[3]))

  for bf, p in zip(batch_features, path):
    path_of_feature = p.numpy().decode("utf-8")
    np.save(path_of_feature, bf.numpy())

 

キャプションを前処理してトークン化する

  • 最初にキャプションをトークン化します (例えば、スペースで分割することにより)。これはデータの総ての一意な単語の語彙を与えます (例えば、”surfing”, “football” 等)。
  • 次に、(メモリを節約するために) 語彙サイズを top 5,000 単語に制限します。総ての他の単語をトークン “UNK” (unknown) で置き替えます。
  • それから単語-to-インデックスとインデックス-to-単語マッピングを作成します。
  • 最後に、最長のものと同じ長さになるように総てのシークエンスをパッドします。
# Find the maximum length of any caption in our dataset
def calc_max_length(tensor):
    return max(len(t) for t in tensor)
# Choose the top 5000 words from the vocabulary
top_k = 5000
tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words=top_k,
                                                  oov_token="",
                                                  filters='!"#$%&()*+.,-/:;=?@[\]^_`{|}~ ')
tokenizer.fit_on_texts(train_captions)
train_seqs = tokenizer.texts_to_sequences(train_captions)
tokenizer.word_index['<pad>'] = 0
tokenizer.index_word[0] = '<pad>'
# Create the tokenized vectors
train_seqs = tokenizer.texts_to_sequences(train_captions)
# Pad each vector to the max_length of the captions
# If you do not provide a max_length value, pad_sequences calculates it automatically
cap_vector = tf.keras.preprocessing.sequence.pad_sequences(train_seqs, padding='post')
# Calculates the max_length, which is used to store the attention weights
max_length = calc_max_length(train_seqs)

 

データを訓練とテストに分割する

# Create training and validation sets using an 80-20 split
img_name_train, img_name_val, cap_train, cap_val = train_test_split(img_name_vector,
                                                                    cap_vector,
                                                                    test_size=0.2,
                                                                    random_state=0)
len(img_name_train), len(cap_train), len(img_name_val), len(cap_val)
(24000, 24000, 6000, 6000)

 

訓練のために tf.data dataset を作成します

画像とキャプションの準備ができました!次に、モデルを訓練するために使用する tf.data データセットを作成しましょう。

# Feel free to change these parameters according to your system's configuration

BATCH_SIZE = 64
BUFFER_SIZE = 1000
embedding_dim = 256
units = 512
vocab_size = len(tokenizer.word_index) + 1
num_steps = len(img_name_train) // BATCH_SIZE
# Shape of the vector extracted from InceptionV3 is (64, 2048)
# These two variables represent that vector shape
features_shape = 2048
attention_features_shape = 64
# Load the numpy files
def map_func(img_name, cap):
  img_tensor = np.load(img_name.decode('utf-8')+'.npy')
  return img_tensor, cap
dataset = tf.data.Dataset.from_tensor_slices((img_name_train, cap_train))

# Use map to load the numpy files in parallel
dataset = dataset.map(lambda item1, item2: tf.numpy_function(
          map_func, [item1, item2], [tf.float32, tf.int32]),
          num_parallel_calls=tf.data.experimental.AUTOTUNE)

# Shuffle and batch
dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
dataset = dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)

 

モデル

面白い事実: 下のデコーダは ニューラル機械翻訳 with Attention のためのサンプルのものと同一です。

モデル・アーキテクチャは Show, Attend and Tell ペーパーによりインスパイアされています。

  • このサンプルでは、InceptionV3 のより低い畳み込み層から特徴を抽出します、これは shape (8, 8, 2048) のベクトルを与えます。
  • それを (64, 2048) の shape に押しつぶします。
  • それからこのベクトルは CNN エンコーダを通して渡されます (これは単一の完全結合層から成ります)。
  • RNN (ここでは GRU) が次の単語を予測するために画像に渡り注視します。
class BahdanauAttention(tf.keras.Model):
  def __init__(self, units):
    super(BahdanauAttention, self).__init__()
    self.W1 = tf.keras.layers.Dense(units)
    self.W2 = tf.keras.layers.Dense(units)
    self.V = tf.keras.layers.Dense(1)

  def call(self, features, hidden):
    # features(CNN_encoder output) shape == (batch_size, 64, embedding_dim)

    # hidden shape == (batch_size, hidden_size)
    # hidden_with_time_axis shape == (batch_size, 1, hidden_size)
    hidden_with_time_axis = tf.expand_dims(hidden, 1)

    # score shape == (batch_size, 64, hidden_size)
    score = tf.nn.tanh(self.W1(features) + self.W2(hidden_with_time_axis))

    # attention_weights shape == (batch_size, 64, 1)
    # you get 1 at the last axis because you are applying score to self.V
    attention_weights = tf.nn.softmax(self.V(score), axis=1)

    # context_vector shape after sum == (batch_size, hidden_size)
    context_vector = attention_weights * features
    context_vector = tf.reduce_sum(context_vector, axis=1)

    return context_vector, attention_weights
class CNN_Encoder(tf.keras.Model):
    # Since you have already extracted the features and dumped it using pickle
    # This encoder passes those features through a Fully connected layer
    def __init__(self, embedding_dim):
        super(CNN_Encoder, self).__init__()
        # shape after fc == (batch_size, 64, embedding_dim)
        self.fc = tf.keras.layers.Dense(embedding_dim)

    def call(self, x):
        x = self.fc(x)
        x = tf.nn.relu(x)
        return x
class RNN_Decoder(tf.keras.Model):
  def __init__(self, embedding_dim, units, vocab_size):
    super(RNN_Decoder, self).__init__()
    self.units = units

    self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
    self.gru = tf.keras.layers.GRU(self.units,
                                   return_sequences=True,
                                   return_state=True,
                                   recurrent_initializer='glorot_uniform')
    self.fc1 = tf.keras.layers.Dense(self.units)
    self.fc2 = tf.keras.layers.Dense(vocab_size)

    self.attention = BahdanauAttention(self.units)

  def call(self, x, features, hidden):
    # defining attention as a separate model
    context_vector, attention_weights = self.attention(features, hidden)

    # x shape after passing through embedding == (batch_size, 1, embedding_dim)
    x = self.embedding(x)

    # x shape after concatenation == (batch_size, 1, embedding_dim + hidden_size)
    x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)

    # passing the concatenated vector to the GRU
    output, state = self.gru(x)

    # shape == (batch_size, max_length, hidden_size)
    x = self.fc1(output)

    # x shape == (batch_size * max_length, hidden_size)
    x = tf.reshape(x, (-1, x.shape[2]))

    # output shape == (batch_size * max_length, vocab)
    x = self.fc2(x)

    return x, state, attention_weights

  def reset_state(self, batch_size):
    return tf.zeros((batch_size, self.units))
encoder = CNN_Encoder(embedding_dim)
decoder = RNN_Decoder(embedding_dim, units, vocab_size)
optimizer = tf.keras.optimizers.Adam()
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction='none')

def loss_function(real, pred):
  mask = tf.math.logical_not(tf.math.equal(real, 0))
  loss_ = loss_object(real, pred)

  mask = tf.cast(mask, dtype=loss_.dtype)
  loss_ *= mask

  return tf.reduce_mean(loss_)

 

チェックポイント

checkpoint_path = "./checkpoints/train"
ckpt = tf.train.Checkpoint(encoder=encoder,
                           decoder=decoder,
                           optimizer = optimizer)
ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)
start_epoch = 0
if ckpt_manager.latest_checkpoint:
  start_epoch = int(ckpt_manager.latest_checkpoint.split('-')[-1])

 

訓練

  • それぞれの .npy ファイルにストアされている特徴を抽出してからそれらの特徴をエンコーダを通して渡します。
  • エンコーダ出力、(0 に初期化された) 隠れ状態そしてデコーダ入力 (それは start トークンです) がデコーダに渡されます。
  • デコーダは予測とデコーダ隠れ状態を返します。
  • そしてデコーダ隠れ状態はモデルに渡し戻されて予測は損失を計算するために使用されます。
  • デコーダへの次の入力を決めるために teacher forcing を使用します。
  • teacher forcing はそこではターゲット単語がデコーダへの次の入力として渡されるようなテクニックです。
  • 最後のステップは勾配を計算してそれを optimizer に適用してそして backpropagate します。
# adding this in a separate cell because if you run the training cell
# many times, the loss_plot array will be reset
loss_plot = []
@tf.function
def train_step(img_tensor, target):
  loss = 0

  # initializing the hidden state for each batch
  # because the captions are not related from image to image
  hidden = decoder.reset_state(batch_size=target.shape[0])

  dec_input = tf.expand_dims([tokenizer.word_index['']] * BATCH_SIZE, 1)

  with tf.GradientTape() as tape:
      features = encoder(img_tensor)

      for i in range(1, target.shape[1]):
          # passing the features through the decoder
          predictions, hidden, _ = decoder(dec_input, features, hidden)

          loss += loss_function(target[:, i], predictions)

          # using teacher forcing
          dec_input = tf.expand_dims(target[:, i], 1)

  total_loss = (loss / int(target.shape[1]))

  trainable_variables = encoder.trainable_variables + decoder.trainable_variables

  gradients = tape.gradient(loss, trainable_variables)

  optimizer.apply_gradients(zip(gradients, trainable_variables))

  return loss, total_loss
EPOCHS = 20

for epoch in range(start_epoch, EPOCHS):
    start = time.time()
    total_loss = 0

    for (batch, (img_tensor, target)) in enumerate(dataset):
        batch_loss, t_loss = train_step(img_tensor, target)
        total_loss += t_loss

        if batch % 100 == 0:
            print ('Epoch {} Batch {} Loss {:.4f}'.format(
              epoch + 1, batch, batch_loss.numpy() / int(target.shape[1])))
    # storing the epoch end loss value to plot later
    loss_plot.append(total_loss / num_steps)

    if epoch % 5 == 0:
      ckpt_manager.save()

    print ('Epoch {} Loss {:.6f}'.format(epoch + 1,
                                         total_loss/num_steps))
    print ('Time taken for 1 epoch {} sec\n'.format(time.time() - start))
Epoch 1 Batch 0 Loss 2.0500
Epoch 1 Batch 100 Loss 1.1069
Epoch 1 Batch 200 Loss 0.9892
Epoch 1 Batch 300 Loss 0.9893
Epoch 1 Loss 1.075467
Time taken for 1 epoch 208.44214582443237 sec

Epoch 2 Batch 0 Loss 0.8720
Epoch 2 Batch 100 Loss 0.7934
Epoch 2 Batch 200 Loss 0.7722
Epoch 2 Batch 300 Loss 0.8354
Epoch 2 Loss 0.813784
Time taken for 1 epoch 69.56642508506775 sec

Epoch 3 Batch 0 Loss 0.7634
Epoch 3 Batch 100 Loss 0.7146
Epoch 3 Batch 200 Loss 0.6952
Epoch 3 Batch 300 Loss 0.7735
Epoch 3 Loss 0.736129
Time taken for 1 epoch 68.59322023391724 sec

Epoch 4 Batch 0 Loss 0.7161
Epoch 4 Batch 100 Loss 0.6617
Epoch 4 Batch 200 Loss 0.6478
Epoch 4 Batch 300 Loss 0.7399
Epoch 4 Loss 0.689814
Time taken for 1 epoch 68.88294863700867 sec

Epoch 5 Batch 0 Loss 0.6749
Epoch 5 Batch 100 Loss 0.6207
Epoch 5 Batch 200 Loss 0.6135
Epoch 5 Batch 300 Loss 0.7017
Epoch 5 Loss 0.653753
Time taken for 1 epoch 70.6437156200409 sec

Epoch 6 Batch 0 Loss 0.6470
Epoch 6 Batch 100 Loss 0.5872
Epoch 6 Batch 200 Loss 0.5889
Epoch 6 Batch 300 Loss 0.6672
Epoch 6 Loss 0.623586
Time taken for 1 epoch 69.7294511795044 sec

Epoch 7 Batch 0 Loss 0.6234
Epoch 7 Batch 100 Loss 0.5594
Epoch 7 Batch 200 Loss 0.5643
Epoch 7 Batch 300 Loss 0.6386
Epoch 7 Loss 0.598236
Time taken for 1 epoch 67.98005199432373 sec

Epoch 8 Batch 0 Loss 0.6024
Epoch 8 Batch 100 Loss 0.5331
Epoch 8 Batch 200 Loss 0.5449
Epoch 8 Batch 300 Loss 0.6187
Epoch 8 Loss 0.574878
Time taken for 1 epoch 69.89835166931152 sec

Epoch 9 Batch 0 Loss 0.5754
Epoch 9 Batch 100 Loss 0.5234
Epoch 9 Batch 200 Loss 0.5256
Epoch 9 Batch 300 Loss 0.6037
Epoch 9 Loss 0.553293
Time taken for 1 epoch 68.3003740310669 sec

Epoch 10 Batch 0 Loss 0.5569
Epoch 10 Batch 100 Loss 0.4994
Epoch 10 Batch 200 Loss 0.4934
Epoch 10 Batch 300 Loss 0.5600
Epoch 10 Loss 0.533848
Time taken for 1 epoch 69.4975745677948 sec

Epoch 11 Batch 0 Loss 0.5454
Epoch 11 Batch 100 Loss 0.4740
Epoch 11 Batch 200 Loss 0.4806
Epoch 11 Batch 300 Loss 0.5392
Epoch 11 Loss 0.516213
Time taken for 1 epoch 70.42340064048767 sec

Epoch 12 Batch 0 Loss 0.5324
Epoch 12 Batch 100 Loss 0.4572
Epoch 12 Batch 200 Loss 0.4702
Epoch 12 Batch 300 Loss 0.5303
Epoch 12 Loss 0.499344
Time taken for 1 epoch 69.0728235244751 sec

Epoch 13 Batch 0 Loss 0.5100
Epoch 13 Batch 100 Loss 0.4414
Epoch 13 Batch 200 Loss 0.4668
Epoch 13 Batch 300 Loss 0.5122
Epoch 13 Loss 0.482220
Time taken for 1 epoch 68.99846816062927 sec

Epoch 14 Batch 0 Loss 0.4834
Epoch 14 Batch 100 Loss 0.4519
Epoch 14 Batch 200 Loss 0.4227
Epoch 14 Batch 300 Loss 0.4884
Epoch 14 Loss 0.465576
Time taken for 1 epoch 67.78600645065308 sec

Epoch 15 Batch 0 Loss 0.4607
Epoch 15 Batch 100 Loss 0.4202
Epoch 15 Batch 200 Loss 0.4004
Epoch 15 Batch 300 Loss 0.5098
Epoch 15 Loss 0.449775
Time taken for 1 epoch 70.15660309791565 sec

Epoch 16 Batch 0 Loss 0.4475
Epoch 16 Batch 100 Loss 0.4179
Epoch 16 Batch 200 Loss 0.3996
Epoch 16 Batch 300 Loss 0.4680
Epoch 16 Loss 0.436949
Time taken for 1 epoch 68.8959448337555 sec

Epoch 17 Batch 0 Loss 0.4261
Epoch 17 Batch 100 Loss 0.3849
Epoch 17 Batch 200 Loss 0.3903
Epoch 17 Batch 300 Loss 0.4470
Epoch 17 Loss 0.424533
Time taken for 1 epoch 69.10690641403198 sec

Epoch 18 Batch 0 Loss 0.4112
Epoch 18 Batch 100 Loss 0.3673
Epoch 18 Batch 200 Loss 0.3934
Epoch 18 Batch 300 Loss 0.4380
Epoch 18 Loss 0.412055
Time taken for 1 epoch 69.39148497581482 sec

Epoch 19 Batch 0 Loss 0.3895
Epoch 19 Batch 100 Loss 0.3556
Epoch 19 Batch 200 Loss 0.3743
Epoch 19 Batch 300 Loss 0.4130
Epoch 19 Loss 0.399825
Time taken for 1 epoch 68.86549687385559 sec

Epoch 20 Batch 0 Loss 0.3956
Epoch 20 Batch 100 Loss 0.3547
Epoch 20 Batch 200 Loss 0.3462
Epoch 20 Batch 300 Loss 0.4282
Epoch 20 Loss 0.391777
Time taken for 1 epoch 70.18613910675049 sec
plt.plot(loss_plot)
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Loss Plot')
plt.show()

 

キャプション!

  • evaluate 関数は訓練ループに類似しています、ここでは teacher forcing を使用しないことを除いて。各時間ステップにおけるデコーダへの入力は隠れ状態とエンコーダ出力に加えてその前の予測です。
  • モデルが end トークンを予測するとき予測は停止します。
  • そして総ての時間ステップのために attention 重みをストアします。
def evaluate(image):
    attention_plot = np.zeros((max_length, attention_features_shape))

    hidden = decoder.reset_state(batch_size=1)

    temp_input = tf.expand_dims(load_image(image)[0], 0)
    img_tensor_val = image_features_extract_model(temp_input)
    img_tensor_val = tf.reshape(img_tensor_val, (img_tensor_val.shape[0], -1, img_tensor_val.shape[3]))

    features = encoder(img_tensor_val)

    dec_input = tf.expand_dims([tokenizer.word_index['']], 0)
    result = []

    for i in range(max_length):
        predictions, hidden, attention_weights = decoder(dec_input, features, hidden)

        attention_plot[i] = tf.reshape(attention_weights, (-1, )).numpy()

        predicted_id = tf.argmax(predictions[0]).numpy()
        result.append(tokenizer.index_word[predicted_id])

        if tokenizer.index_word[predicted_id] == '':
            return result, attention_plot

        dec_input = tf.expand_dims([predicted_id], 0)

    attention_plot = attention_plot[:len(result), :]
    return result, attention_plot
def plot_attention(image, result, attention_plot):
    temp_image = np.array(Image.open(image))

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

    len_result = len(result)
    for l in range(len_result):
        temp_att = np.resize(attention_plot[l], (8, 8))
        ax = fig.add_subplot(len_result//2, len_result//2, l+1)
        ax.set_title(result[l])
        img = ax.imshow(temp_image)
        ax.imshow(temp_att, cmap='gray', alpha=0.6, extent=img.get_extent())

    plt.tight_layout()
    plt.show()
# captions on the validation set
rid = np.random.randint(0, len(img_name_val))
image = img_name_val[rid]
real_caption = ' '.join([tokenizer.index_word[i] for i in cap_val[rid] if i not in [0]])
result, attention_plot = evaluate(image)

print ('Real Caption:', real_caption)
print ('Prediction Caption:', ' '.join(result))
plot_attention(image, result, attention_plot)
# opening the image
Image.open(img_name_val[rid])
Real Caption: <start> a city street has lighted signs and buildings <end>
Prediction Caption: a group of people are taken in the background <end>

 

貴方自身の画像でそれを試してください

楽しみのために、貴方自身の画像を丁度訓練したモデルでキャプションするために使用できるメソッドを下で提供しました。留意してください、それはデータの比較的小さい量の上で訓練されました、そして貴方の画像は訓練データとは異なるかもしれません (そのため奇妙な結果に備えてください!)

image_url = 'https://tensorflow.org/images/surf.jpg'
image_extension = image_url[-4:]
image_path = tf.keras.utils.get_file('image'+image_extension,
                                     origin=image_url)

result, attention_plot = evaluate(image_path)
print ('Prediction Caption:', ' '.join(result))
plot_attention(image_path, result, attention_plot)
# opening the image
Image.open(image_path)
Prediction Caption: a man in a man in a suit and a picture of a man in a suit and a picture of a man in a suit and a picture of a man in a suit and a picture of a man in a suit and a picture of a

/home/kbuilder/.local/lib/python3.5/site-packages/matplotlib/tight_layout.py:199: UserWarning: Tight layout not applied. tight_layout cannot make axes width small enough to accommodate all axes decorations
  warnings.warn('Tight layout not applied. '

 

以上






TensorFlow 2.0 Beta : 上級 Tutorials :- テキストとシークエンス : ニューラル機械翻訳 with Attention

TensorFlow 2.0 Beta : 上級 Tutorials : テキストとシークエンス :- ニューラル機械翻訳 with Attention (翻訳/解説)

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

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

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

 

テキストとシークエンス :- ニューラル機械翻訳 with Attention

このノートブックは西英翻訳のための sequence to sequence (seq2seq) モデルを訓練します。これは sequence to sequence モデルの何某かの知識を仮定した上級サンプルです。

このノートブックでモデルを訓練した後、”¿todavia estan en casa?” のようなスペイン語のセンテンスを入力して、英語翻訳: “are you still at home?” を返すことができます。

翻訳品質は toy サンプルのために合理的ですが、生成された attention プロットは多分より興味深いです。これは翻訳の間に入力センテンスのどの部分がモデルの注意 (= attention) を持つかを示します :

Note: このサンプルは単一の P100 GPU 上で実行するためにおよそ 10 分かかります。

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf

import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

import unicodedata
import re
import numpy as np
import os
import io
import time

 

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

私達は http://www.manythings.org/anki/ により提供される言語データセットを使用します。このデータセットは次のフォーマットの言語翻訳ペアを含みます :

May I borrow this book? ¿Puedo tomar prestado este libro?

利用可能な様々な言語がありますが、英語-スペイン語データセットを使用します。便宜上、このデータセットのコピーを Google Cloud 上にホストしましたが、貴方自身のコピーをダウンロードすることもできます。データセットをダウンロードした後、データを準備するために取るステップがここにあります :

  1. 各センテンスに start と end トークンを追加します。
  2. 特殊文字を除去してセンテンスをクリーンアップします。
  3. 単語インデックスとリバース単語インデックス (単語 → id と id → 単語のマッピングを行なう辞書) を作成します。
  4. 各センテンスを最大長にパッドします。
# Download the file
path_to_zip = tf.keras.utils.get_file(
    'spa-eng.zip', origin='http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip',
    extract=True)

path_to_file = os.path.dirname(path_to_zip)+"/spa-eng/spa.txt"
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip
2646016/2638744 [==============================] - 0s 0us/step
# Converts the unicode file to ascii
def unicode_to_ascii(s):
    return ''.join(c for c in unicodedata.normalize('NFD', s)
        if unicodedata.category(c) != 'Mn')


def preprocess_sentence(w):
    w = unicode_to_ascii(w.lower().strip())

    # creating a space between a word and the punctuation following it
    # eg: "he is a boy." => "he is a boy ."
    # Reference:- https://stackoverflow.com/questions/3645931/python-padding-punctuation-with-white-spaces-keeping-punctuation
    w = re.sub(r"([?.!,¿])", r" \1 ", w)
    w = re.sub(r'[" "]+', " ", w)

    # replacing everything with space except (a-z, A-Z, ".", "?", "!", ",")
    w = re.sub(r"[^a-zA-Z?.!,¿]+", " ", w)

    w = w.rstrip().strip()

    # adding a start and an end token to the sentence
    # so that the model know when to start and stop predicting.
    w = ' ' + w + ' '
    return w
en_sentence = u"May I borrow this book?"
sp_sentence = u"¿Puedo tomar prestado este libro?"
print(preprocess_sentence(en_sentence))
print(preprocess_sentence(sp_sentence).encode('utf-8'))
<start> may i borrow this book ? <end>
b'<start> \xc2\xbf puedo tomar prestado este libro ? <end>'
# 1. Remove the accents
# 2. Clean the sentences
# 3. Return word pairs in the format: [ENGLISH, SPANISH]
def create_dataset(path, num_examples):
    lines = io.open(path, encoding='UTF-8').read().strip().split('\n')

    word_pairs = [[preprocess_sentence(w) for w in l.split('\t')]  for l in lines[:num_examples]]

    return zip(*word_pairs)
en, sp = create_dataset(path_to_file, None)
print(en[-1])
print(sp[-1])
<start> if you want to sound like a native speaker , you must be willing to practice saying the same sentence over and over in the same way that banjo players practice the same phrase over and over until they can play it correctly and at the desired tempo . <end>
<start> si quieres sonar como un hablante nativo , debes estar dispuesto a practicar diciendo la misma frase una y otra vez de la misma manera en que un musico de banjo practica el mismo fraseo una y otra vez hasta que lo puedan tocar correctamente y en el tiempo esperado . <end>
def max_length(tensor):
    return max(len(t) for t in tensor)
def tokenize(lang):
  lang_tokenizer = tf.keras.preprocessing.text.Tokenizer(
      filters='')
  lang_tokenizer.fit_on_texts(lang)

  tensor = lang_tokenizer.texts_to_sequences(lang)

  tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor,
                                                         padding='post')

  return tensor, lang_tokenizer
def load_dataset(path, num_examples=None):
    # creating cleaned input, output pairs
    targ_lang, inp_lang = create_dataset(path, num_examples)

    input_tensor, inp_lang_tokenizer = tokenize(inp_lang)
    target_tensor, targ_lang_tokenizer = tokenize(targ_lang)

    return input_tensor, target_tensor, inp_lang_tokenizer, targ_lang_tokenizer

 

実験をより速くするためにデータセットのサイズを制限する (オプション)

センテンス > 100,000 の完全なデータセット上で訓練するのは時間がかかります。より速く訓練するために、データセットのサイズを 30,000 センテンスに制限することができます (もちろん、翻訳品質はより少ないデータでは劣化します) :

# Try experimenting with the size of that dataset
num_examples = 30000
input_tensor, target_tensor, inp_lang, targ_lang = load_dataset(path_to_file, num_examples)

# Calculate max_length of the target tensors
max_length_targ, max_length_inp = max_length(target_tensor), max_length(input_tensor)
# Creating training and validation sets using an 80-20 split
input_tensor_train, input_tensor_val, target_tensor_train, target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2)

# Show length
len(input_tensor_train), len(target_tensor_train), len(input_tensor_val), len(target_tensor_val)
(24000, 24000, 6000, 6000)
def convert(lang, tensor):
  for t in tensor:
    if t!=0:
      print ("%d ----> %s" % (t, lang.index_word[t]))
print ("Input Language; index to word mapping")
convert(inp_lang, input_tensor_train[0])
print ()
print ("Target Language; index to word mapping")
convert(targ_lang, target_tensor_train[0])
Input Language; index to word mapping
1 ----> <start>
30 ----> esto
7 ----> es
1109 ----> emocionante
3 ----> .
2 ----> <end>

Target Language; index to word mapping
1 ----> <start>
19 ----> this
8 ----> is
1239 ----> exciting
3 ----> .
2 ----> <end>

 

tf.data データセットを作成する

BUFFER_SIZE = len(input_tensor_train)
BATCH_SIZE = 64
steps_per_epoch = len(input_tensor_train)//BATCH_SIZE
embedding_dim = 256
units = 1024
vocab_inp_size = len(inp_lang.word_index)+1
vocab_tar_size = len(targ_lang.word_index)+1

dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE)
dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)
example_input_batch, example_target_batch = next(iter(dataset))
example_input_batch.shape, example_target_batch.shape
(TensorShape([64, 16]), TensorShape([64, 11]))

 

エンコーダとデコーダ・モデルを書く

attention を持つエンコーダ-デコーダ・モデルを実装します、これについて TensorFlow Neural Machine Translation (seq2seq) チュートリアル で読むことができます。このサンプルは API のより新しいセットを使用しています。このノートブックは seq2seq チュートリアルからの attention 式 を実装しています。次の図は attention メカニズムにより各入力単語に重みが割り当てられて、それからそれがセンテンスの次の単語を予測するためにデコーダにより使用されることを示しています。下の図と式は Luong のペーパー からの attention メカニズムのサンプルです。

入力はエンコーダ・モデルの中を通され、これは shape (batch_size, max_length, hidden_size) のエンコーダ出力と shape (batch_size, hidden_size) のエンコーダ隠れ状態を与えます。

ここに実装される等式があります :

このチュートリアルは Bahdanau attention を使用しています。単純化された形式を書く前に記法を定めましょう :

  • FC = 完全結合 (dense) 層
  • EO = エンコーダ出力
  • H = 隠れ状態
  • X = デコーダへの入力

そして擬似コードは :

  • score = FC(tanh(FC(EO) + FC(H)))
  • attention weights = softmax(score, axis = 1)。デフォルトでは softmax が最後の軸上に適用されますがここではそれを最初の軸上で適用することを望みます、何故ならばスコアの shape は (batch_size, max_length, hidden_size) だからです。Max_length は入力の長さです。各入力に重みを割り当てようとしていますので、softmax はその軸上で適用されるべきです。
  • context vector = sum(attention weights * EO, axis = 1)。上と同じ理由で軸として 1 を選択します。
  • embedding output = デコーダへの入力 X は埋め込み層を通されます。
  • merged vector = concat(embedding output, context vector)
  • それからこのマージされたベクトルが GRU に与えられます。

各ステップにおける総てのベクトルの shape はコードのコメントで指定されます :

class Encoder(tf.keras.Model):
  def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):
    super(Encoder, self).__init__()
    self.batch_sz = batch_sz
    self.enc_units = enc_units
    self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
    self.gru = tf.keras.layers.GRU(self.enc_units,
                                   return_sequences=True,
                                   return_state=True,
                                   recurrent_initializer='glorot_uniform')

  def call(self, x, hidden):
    x = self.embedding(x)
    output, state = self.gru(x, initial_state = hidden)
    return output, state

  def initialize_hidden_state(self):
    return tf.zeros((self.batch_sz, self.enc_units))
encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)

# sample input
sample_hidden = encoder.initialize_hidden_state()
sample_output, sample_hidden = encoder(example_input_batch, sample_hidden)
print ('Encoder output shape: (batch size, sequence length, units) {}'.format(sample_output.shape))
print ('Encoder Hidden state shape: (batch size, units) {}'.format(sample_hidden.shape))
Encoder output shape: (batch size, sequence length, units) (64, 16, 1024)
Encoder Hidden state shape: (batch size, units) (64, 1024)
class BahdanauAttention(tf.keras.Model):
  def __init__(self, units):
    super(BahdanauAttention, self).__init__()
    self.W1 = tf.keras.layers.Dense(units)
    self.W2 = tf.keras.layers.Dense(units)
    self.V = tf.keras.layers.Dense(1)

  def call(self, query, values):
    # hidden shape == (batch_size, hidden size)
    # hidden_with_time_axis shape == (batch_size, 1, hidden size)
    # we are doing this to perform addition to calculate the score
    hidden_with_time_axis = tf.expand_dims(query, 1)

    # score shape == (batch_size, max_length, 1)
    # we get 1 at the last axis because we are applying score to self.V
    # the shape of the tensor before applying self.V is (batch_size, max_length, units)
    score = self.V(tf.nn.tanh(
        self.W1(values) + self.W2(hidden_with_time_axis)))

    # attention_weights shape == (batch_size, max_length, 1)
    attention_weights = tf.nn.softmax(score, axis=1)

    # context_vector shape after sum == (batch_size, hidden_size)
    context_vector = attention_weights * values
    context_vector = tf.reduce_sum(context_vector, axis=1)

    return context_vector, attention_weights
attention_layer = BahdanauAttention(10)
attention_result, attention_weights = attention_layer(sample_hidden, sample_output)

print("Attention result shape: (batch size, units) {}".format(attention_result.shape))
print("Attention weights shape: (batch_size, sequence_length, 1) {}".format(attention_weights.shape))
Attention result shape: (batch size, units) (64, 1024)
Attention weights shape: (batch_size, sequence_length, 1) (64, 16, 1)
class Decoder(tf.keras.Model):
  def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):
    super(Decoder, self).__init__()
    self.batch_sz = batch_sz
    self.dec_units = dec_units
    self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
    self.gru = tf.keras.layers.GRU(self.dec_units,
                                   return_sequences=True,
                                   return_state=True,
                                   recurrent_initializer='glorot_uniform')
    self.fc = tf.keras.layers.Dense(vocab_size)

    # used for attention
    self.attention = BahdanauAttention(self.dec_units)

  def call(self, x, hidden, enc_output):
    # enc_output shape == (batch_size, max_length, hidden_size)
    context_vector, attention_weights = self.attention(hidden, enc_output)

    # x shape after passing through embedding == (batch_size, 1, embedding_dim)
    x = self.embedding(x)

    # x shape after concatenation == (batch_size, 1, embedding_dim + hidden_size)
    x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)

    # passing the concatenated vector to the GRU
    output, state = self.gru(x)

    # output shape == (batch_size * 1, hidden_size)
    output = tf.reshape(output, (-1, output.shape[2]))

    # output shape == (batch_size, vocab)
    x = self.fc(output)

    return x, state, attention_weights
decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)

sample_decoder_output, _, _ = decoder(tf.random.uniform((64, 1)),
                                      sample_hidden, sample_output)

print ('Decoder output shape: (batch_size, vocab size) {}'.format(sample_decoder_output.shape))
Decoder output shape: (batch_size, vocab size) (64, 4935)

 

optimizer と損失関数を定義する

optimizer = tf.keras.optimizers.Adam()
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction='none')

def loss_function(real, pred):
  mask = tf.math.logical_not(tf.math.equal(real, 0))
  loss_ = loss_object(real, pred)

  mask = tf.cast(mask, dtype=loss_.dtype)
  loss_ *= mask

  return tf.reduce_mean(loss_)

 

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

checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(optimizer=optimizer,
                                 encoder=encoder,
                                 decoder=decoder)

 

訓練

  1. 入力をエンコーダを通します、これはエンコーダ出力とエンコーダ隠れ状態を返します。
  2. エンコーダ出力、エンコーダ隠れ状態とデコーダ入力 (これは start トークンです) がデコーダに渡されます。
  3. デコーダは予測とデコーダ隠れ状態を返します。
  4. それからデコーダ隠れ状態はモデルに渡し返されて予測は損失を計算するために使用されます。
  5. デコーダへの次の入力を決めるために teacher forcing を使用します。
  6. teacher forcing はターゲット単語がデコーダへの次の入力として渡されるテクニックです。
  7. 最後のステップは勾配を計算してそれを optimizer に適用して backpropagate します。
@tf.function
def train_step(inp, targ, enc_hidden):
  loss = 0

  with tf.GradientTape() as tape:
    enc_output, enc_hidden = encoder(inp, enc_hidden)

    dec_hidden = enc_hidden

    dec_input = tf.expand_dims([targ_lang.word_index['']] * BATCH_SIZE, 1)

    # Teacher forcing - feeding the target as the next input
    for t in range(1, targ.shape[1]):
      # passing enc_output to the decoder
      predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output)

      loss += loss_function(targ[:, t], predictions)

      # using teacher forcing
      dec_input = tf.expand_dims(targ[:, t], 1)

  batch_loss = (loss / int(targ.shape[1]))

  variables = encoder.trainable_variables + decoder.trainable_variables

  gradients = tape.gradient(loss, variables)

  optimizer.apply_gradients(zip(gradients, variables))

  return batch_loss
EPOCHS = 10

for epoch in range(EPOCHS):
  start = time.time()

  enc_hidden = encoder.initialize_hidden_state()
  total_loss = 0

  for (batch, (inp, targ)) in enumerate(dataset.take(steps_per_epoch)):
    batch_loss = train_step(inp, targ, enc_hidden)
    total_loss += batch_loss

    if batch % 100 == 0:
        print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1,
                                                     batch,
                                                     batch_loss.numpy()))
  # saving (checkpoint) the model every 2 epochs
  if (epoch + 1) % 2 == 0:
    checkpoint.save(file_prefix = checkpoint_prefix)

  print('Epoch {} Loss {:.4f}'.format(epoch + 1,
                                      total_loss / steps_per_epoch))
  print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))
Epoch 1 Batch 0 Loss 4.5541
Epoch 1 Batch 100 Loss 2.2291
Epoch 1 Batch 200 Loss 1.8727
Epoch 1 Batch 300 Loss 1.7644
Epoch 1 Loss 2.0230
Time taken for 1 epoch 56.17962884902954 sec

Epoch 2 Batch 0 Loss 1.5572
Epoch 2 Batch 100 Loss 1.5255
Epoch 2 Batch 200 Loss 1.3902
Epoch 2 Batch 300 Loss 1.2984
Epoch 2 Loss 1.3868
Time taken for 1 epoch 21.577687740325928 sec

Epoch 3 Batch 0 Loss 1.1589
Epoch 3 Batch 100 Loss 1.0942
Epoch 3 Batch 200 Loss 1.0069
Epoch 3 Batch 300 Loss 0.8360
Epoch 3 Loss 0.9756
Time taken for 1 epoch 20.880752086639404 sec

Epoch 4 Batch 0 Loss 0.7405
Epoch 4 Batch 100 Loss 0.7607
Epoch 4 Batch 200 Loss 0.6250
Epoch 4 Batch 300 Loss 0.5633
Epoch 4 Loss 0.6589
Time taken for 1 epoch 21.411985635757446 sec

Epoch 5 Batch 0 Loss 0.4929
Epoch 5 Batch 100 Loss 0.5100
Epoch 5 Batch 200 Loss 0.4399
Epoch 5 Batch 300 Loss 0.4001
Epoch 5 Loss 0.4457
Time taken for 1 epoch 21.400570154190063 sec

Epoch 6 Batch 0 Loss 0.3381
Epoch 6 Batch 100 Loss 0.3662
Epoch 6 Batch 200 Loss 0.2639
Epoch 6 Batch 300 Loss 0.2613
Epoch 6 Loss 0.3043
Time taken for 1 epoch 21.35978865623474 sec

Epoch 7 Batch 0 Loss 0.2534
Epoch 7 Batch 100 Loss 0.2906
Epoch 7 Batch 200 Loss 0.2128
Epoch 7 Batch 300 Loss 0.1770
Epoch 7 Loss 0.2178
Time taken for 1 epoch 21.304248571395874 sec

Epoch 8 Batch 0 Loss 0.1710
Epoch 8 Batch 100 Loss 0.2109
Epoch 8 Batch 200 Loss 0.1711
Epoch 8 Batch 300 Loss 0.1444
Epoch 8 Loss 0.1609
Time taken for 1 epoch 21.66683864593506 sec

Epoch 9 Batch 0 Loss 0.1276
Epoch 9 Batch 100 Loss 0.1570
Epoch 9 Batch 200 Loss 0.1437
Epoch 9 Batch 300 Loss 0.1098
Epoch 9 Loss 0.1195
Time taken for 1 epoch 21.08802342414856 sec

Epoch 10 Batch 0 Loss 0.0835
Epoch 10 Batch 100 Loss 0.1123
Epoch 10 Batch 200 Loss 0.0953
Epoch 10 Batch 300 Loss 0.0959
Epoch 10 Loss 0.0929
Time taken for 1 epoch 21.302005767822266 sec

 

翻訳する

  • evaluate 関数は訓練ループに似ています、ここでは teacher forcing を使用しないことを除いて。各時間ステップでのデコーダへの入力は隠れ状態とエンコーダ出力と共にその前の予測になります。
  • モデルが end トークンを予測するとき予測を停止します。
  • そして総ての時間ステップのために attention 重みをストアします。

Note: エンコーダ出力は一つの入力に対して一度だけ計算されます。

def evaluate(sentence):
    attention_plot = np.zeros((max_length_targ, max_length_inp))

    sentence = preprocess_sentence(sentence)

    inputs = [inp_lang.word_index[i] for i in sentence.split(' ')]
    inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs],
                                                           maxlen=max_length_inp,
                                                           padding='post')
    inputs = tf.convert_to_tensor(inputs)

    result = ''

    hidden = [tf.zeros((1, units))]
    enc_out, enc_hidden = encoder(inputs, hidden)

    dec_hidden = enc_hidden
    dec_input = tf.expand_dims([targ_lang.word_index['']], 0)

    for t in range(max_length_targ):
        predictions, dec_hidden, attention_weights = decoder(dec_input,
                                                             dec_hidden,
                                                             enc_out)

        # storing the attention weights to plot later on
        attention_weights = tf.reshape(attention_weights, (-1, ))
        attention_plot[t] = attention_weights.numpy()

        predicted_id = tf.argmax(predictions[0]).numpy()

        result += targ_lang.index_word[predicted_id] + ' '

        if targ_lang.index_word[predicted_id] == '':
            return result, sentence, attention_plot

        # the predicted ID is fed back into the model
        dec_input = tf.expand_dims([predicted_id], 0)

    return result, sentence, attention_plot
# function for plotting the attention weights
def plot_attention(attention, sentence, predicted_sentence):
    fig = plt.figure(figsize=(10,10))
    ax = fig.add_subplot(1, 1, 1)
    ax.matshow(attention, cmap='viridis')

    fontdict = {'fontsize': 14}

    ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90)
    ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict)

    plt.show()
def translate(sentence):
    result, sentence, attention_plot = evaluate(sentence)

    print('Input: %s' % (sentence))
    print('Predicted translation: {}'.format(result))

    attention_plot = attention_plot[:len(result.split(' ')), :len(sentence.split(' '))]
    plot_attention(attention_plot, sentence.split(' '), result.split(' '))

 

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

# restoring the latest checkpoint in checkpoint_dir
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f83b4381470>
translate(u'hace mucho frio aqui.')
Input: <start> hace mucho frio aqui . <end>
Predicted translation: it s too cold here . <end> 

translate(u'esta es mi vida.')
Input: <start> esta es mi vida . <end>
Predicted translation: this is my life . <end> 

translate(u'¿todavia estan en casa?')
Input: <start> ¿ todavia estan en casa ? <end>
Predicted translation: are you still at home ? <end> 

# wrong translation
translate(u'trata de averiguarlo.')
Input: <start> trata de averiguarlo . <end>
Predicted translation: try to figure it out . <end> 

 

Next steps

  • 翻訳による実験をするために 異なるデータセットをダウンロード します、例えば、英語 to 独語、あるいは英語 to 仏語です。
  • より巨大なデータセット上の訓練で実験します、あるいはより多いエポックを使用します。
 

以上






TensorFlow 2.0 Beta : 上級 Tutorials : テキストとシークエンス :- RNN でテキスト生成

TensorFlow 2.0 Beta : 上級 Tutorials : テキストとシークエンス :- RNN でテキスト生成 (翻訳/解説)

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

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

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

 

テキストとシークエンス :- RNN でテキスト生成

このチュートリアルは文字ベース RNN を使用してテキストをどのように生成するかを実演します。私達は Andrej Karpathy の The Unreasonable Effectiveness of Recurrent Neural Networks から Shakespeare の著作のデータセットで作業します。このデータ (“Shakespear”) から文字のシークエンスが与えられたとき、シークエンスの次の文字 (“e”) を予測するモデルを訓練します。モデルを繰り返し呼び出すことによりテキストのより長いシークエンスが生成できます。

Note: このノートブックをより高速に実行するために GPU アクセラレーションを有効にしてください。Colab では : Runtime > runtime type を変更 > Hardware acclerator > GPU です。ローカルで実行する場合 TensorFlow version >= 1.11 を確実にしてください。

このチュートリアルは tf.keras と eager execution を使用して実装された実行可能なコードを含みます。次は 30 エポックの間訓練されたこのチュートリアルのモデルが文字列 (= string) “Q” で開始されたときのサンプル出力です :

QUEENE:
I had thought thou hadst a Roman; for the oracle,
Thus by All bids the man against the word,
Which are so weak of care, by old care done;
Your children were in your holy love,
And the precipitation through the bleeding throne.

BISHOP OF ELY:
Marry, and will, my lord, to weep in such a one were prettiest;
Yet now I was adopted heir
Of the world's lamentable day,
To watch the next way with his father with his face?

ESCALUS:
The cause why then we are all resolved more sons.

VOLUMNIA:
O, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, it is no sin it should be dead,
And love and pale as any will to that word.

QUEEN ELIZABETH:
But how long have I heard the soul for this world,
And show his hands of life be proved to stand.

PETRUCHIO:
I say he look'd on, if I must be content
To stay him from the fatal of our country's bliss.
His lordship pluck'd from this sentence then for prey,
And then let us twain, being the moon,
were she such a case as fills m

センテンスの幾つかが文法的に正しい一方で、多くは意味を成しません。モデルは単語の意味を学習していませんが、以下を考えてください :

  • 私達のモデルは文字ベースです 訓練を始めたとき、モデルは英単語をどのようにスペルするか、あるいは単語がテキストのユニットであることさえ知りませんでした。
  • 出力の構造は演劇に類似しています — テキストのブロックは一般に話者名で始まり、元のデータセットと同様に総て大文字です。
  • 下で実演されるように、モデルはテキストの小さいバッチ (それぞれ 100 文字) 上で訓練され、コヒーレント構造を持つテキストのより長いシークエンスを依然として生成することが可能です。

 

Setup

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

from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-beta1
import tensorflow as tf

import numpy as np
import os
import time

 

Shakespeare データセットをダウンロードする

貴方自身のデータ上でこのコードを実行するためには次の行を変更します。

path_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt
1122304/1115394 [==============================] - 0s 0us/step

 

データを読む

最初にテキストを少し見てみます :

# Read, then decode for py2 compat.
text = open(path_to_file, 'rb').read().decode(encoding='utf-8')
# length of text is the number of characters in it
print ('Length of text: {} characters'.format(len(text)))
Length of text: 1115394 characters
# Take a look at the first 250 characters in text
print(text[:250])
First Citizen:
Before we proceed any further, hear me speak.

All:
Speak, speak.

First Citizen:
You are all resolved rather to die than to famish?

All:
Resolved. resolved.

First Citizen:
First, you know Caius Marcius is chief enemy to the people.
# The unique characters in the file
vocab = sorted(set(text))
print ('{} unique characters'.format(len(vocab)))
65 unique characters

 

テキストを処理する

テキストをベクトル化する

訓練前に、文字列を数字表現にマップする必要があります。2 つの検索テーブルを作成します: 一つは文字を数字にマップし、そしてもう一つは数字から文字のためです。

# Creating a mapping from unique characters to indices
char2idx = {u:i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)

text_as_int = np.array([char2idx[c] for c in text])

今では各文字に対する整数表現を持ちます。文字を 0 から len(unique) のインデックスとしてマップしたことが分かるでしょう。

print('{')
for char,_ in zip(char2idx, range(20)):
    print('  {:4s}: {:3d},'.format(repr(char), char2idx[char]))
print('  ...\n}')
{
  'Z' :  38,
  'n' :  52,
  'x' :  62,
  'd' :  42,
  'k' :  49,
  'i' :  47,
  'z' :  64,
  'W' :  35,
  'Y' :  37,
  'V' :  34,
  'Q' :  29,
  'X' :  36,
  ';' :  11,
  'K' :  23,
  'D' :  16,
  'E' :  17,
  'b' :  40,
  '.' :   8,
  'P' :  28,
  'u' :  59,
  ...
}
# Show how the first 13 characters from the text are mapped to integers
print ('{} ---- characters mapped to int ---- > {}'.format(repr(text[:13]), text_as_int[:13]))
'First Citizen' ---- characters mapped to int ---- > [18 47 56 57 58  1 15 47 58 47 64 43 52]

 

予測タスク

文字、あるいは文字のシークエンスが与えられたとき、最もありそうな次の文字は何でしょう?これが私達がモデルを訓練して遂行するタスクです。モデルへの入力は文字のシークエンスで、出力 — 各時間ステップにおける次の文字を予測するためにモデルを訓練します。

RNN は前に見た要素に依拠する内部状態を維持しますので、この瞬間までに計算された総ての文字が与えられたとき、次の文字は何でしょう?

 

訓練サンプルとターゲットを作成する

次にテキストをサンプル・シークエンスに分割します。各入力シークエンスはテキストからの seq_length 文字を含みます。

各入力シークエンスに対して、対応するターゲットはテキストの同じ長さを含みます、一つの文字が右にシフトされていることを除いて。

そこでテキストを seq_length+1のチャンクに分解します。例えば、seq_length が 4 そしてテキストが “Hello” であるとします。入力シークエンスは “Hell” で、そしてターゲット・シークエンスは “ello” です。

これを行なうために最初にテキストベクトルを文字インデックスのストリームに変換するために tf.data.Dataset.from_tensor_slices 関数を使用します。

# The maximum length sentence we want for a single input in characters
seq_length = 100
examples_per_epoch = len(text)//seq_length

# Create training examples / targets
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)

for i in char_dataset.take(5):
  print(idx2char[i.numpy()])
F
i
r
s
t

batch メソッドはこれらの個々の文字を望まれるサイズのシークエンスに容易に変換させます。

sequences = char_dataset.batch(seq_length+1, drop_remainder=True)

for item in sequences.take(5):
  print(repr(''.join(idx2char[item.numpy()])))
'First Citizen:\nBefore we proceed any further, hear me speak.\n\nAll:\nSpeak, speak.\n\nFirst Citizen:\nYou '
'are all resolved rather to die than to famish?\n\nAll:\nResolved. resolved.\n\nFirst Citizen:\nFirst, you k'
"now Caius Marcius is chief enemy to the people.\n\nAll:\nWe know't, we know't.\n\nFirst Citizen:\nLet us ki"
"ll him, and we'll have corn at our own price.\nIs't a verdict?\n\nAll:\nNo more talking on't; let it be d"
'one: away, away!\n\nSecond Citizen:\nOne word, good citizens.\n\nFirst Citizen:\nWe are accounted poor citi'

各シークエンスについて、各バッチに単純な関数を適用するために map メソッドを使用して入力とターゲットテキストを形成するためにそれを複製してシフトします :

def split_input_target(chunk):
    input_text = chunk[:-1]
    target_text = chunk[1:]
    return input_text, target_text

dataset = sequences.map(split_input_target)

最初のサンプル入力とターゲット値をプリントします :

for input_example, target_example in  dataset.take(1):
  print ('Input data: ', repr(''.join(idx2char[input_example.numpy()])))
  print ('Target data:', repr(''.join(idx2char[target_example.numpy()])))
Input data:  'First Citizen:\nBefore we proceed any further, hear me speak.\n\nAll:\nSpeak, speak.\n\nFirst Citizen:\nYou'
Target data: 'irst Citizen:\nBefore we proceed any further, hear me speak.\n\nAll:\nSpeak, speak.\n\nFirst Citizen:\nYou '

これらのベクトルの各インデックスは一つの時間ステップとして処理されます。時間ステップ 0 における入力については、モデルは “F” のためのインデックスを受け取りそして次の文字として “i” のためのインデックスを予測しようとします。次の時間ステップでは、それは同じことを行ないますが RNN は現在の入力文字に加えて前のステップのコンテキストも考慮します。

for i, (input_idx, target_idx) in enumerate(zip(input_example[:5], target_example[:5])):
    print("Step {:4d}".format(i))
    print("  input: {} ({:s})".format(input_idx, repr(idx2char[input_idx])))
    print("  expected output: {} ({:s})".format(target_idx, repr(idx2char[target_idx])))
Step    0
  input: 18 ('F')
  expected output: 47 ('i')
Step    1
  input: 47 ('i')
  expected output: 56 ('r')
Step    2
  input: 56 ('r')
  expected output: 57 ('s')
Step    3
  input: 57 ('s')
  expected output: 58 ('t')
Step    4
  input: 58 ('t')
  expected output: 1 (' ')

 

訓練バッチを作成する

テキストを扱いやすいシークエンスに分割するために tf.data を使用しました。しかしこのデータをモデルに供給する前に、データをシャッフルしてそれをバッチにパックする必要があります。

# Batch size
BATCH_SIZE = 64

# Buffer size to shuffle the dataset
# (TF data is designed to work with possibly infinite sequences,
# so it doesn't attempt to shuffle the entire sequence in memory. Instead,
# it maintains a buffer in which it shuffles elements).
BUFFER_SIZE = 10000

dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)

dataset
<BatchDataset shapes: ((64, 100), (64, 100)), types: (tf.int64, tf.int64)>

 

モデルを構築する

モデルを定義するために tf.keras.Sequential を使用します。この単純なサンプルのためにモデルを定義するために 3 つの層が使用されます :

  • tf.keras.layers.Embedding: 入力層。各文字の数字を embedding_dim 次元を持つベクトルにマップする訓練可能な検索テーブルです;
  • tf.keras.layers.GRU: サイズ units=rnn_units を持つ RNN の型です (ここで LSTM 層を使用することもできます)。
  • tf.keras.layers.Dense: vocab_size 出力を持つ出力層。
# Length of the vocabulary in chars
vocab_size = len(vocab)

# The embedding dimension
embedding_dim = 256

# Number of RNN units
rnn_units = 1024
def build_model(vocab_size, embedding_dim, rnn_units, batch_size):
  model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim,
                              batch_input_shape=[batch_size, None]),
    tf.keras.layers.LSTM(rnn_units,
                        return_sequences=True,
                        stateful=True,
                        recurrent_initializer='glorot_uniform'),
    tf.keras.layers.Dense(vocab_size)
  ])
  return model
model = build_model(
  vocab_size = len(vocab),
  embedding_dim=embedding_dim,
  rnn_units=rnn_units,
  batch_size=BATCH_SIZE)
WARNING: Logging before flag parsing goes to stderr.
W0304 03:48:46.706135 140067035297664 tf_logging.py:161] <tensorflow.python.keras.layers.recurrent.UnifiedLSTM object at 0x7f637273ccf8>: Note that this layer is not optimized for performance. Please use tf.keras.layers.CuDNNLSTM for better performance on GPU.

各文字についてモデルは埋め込みを検索し、埋め込みを入力として 1 時間ステップ GRU を実行し、そして次の文字の log-尤度を予測するロジットを生成するために dense 層を適用します :


モデルを通過するデータの図

 

モデルを試す

さてモデルをそれが期待どおりに動作するかを見るために実行します。

最初に出力の shape を確認します :

for input_example_batch, target_example_batch in dataset.take(1):
  example_batch_predictions = model(input_example_batch)
  print(example_batch_predictions.shape, "# (batch_size, sequence_length, vocab_size)")
(64, 100, 65) # (batch_size, sequence_length, vocab_size)

上の例では入力のシークエンス長は 100 ですがモデルは任意の長さの入力上で実行できます :

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (64, None, 256)           16640     
_________________________________________________________________
lstm (LSTM)                  (64, None, 1024)          5246976   
_________________________________________________________________
dense (Dense)                (64, None, 65)            66625     
=================================================================
Total params: 5,330,241
Trainable params: 5,330,241
Non-trainable params: 0
_________________________________________________________________

モデルから実際の予測を得るためには、実際の文字インデックスを得るために出力分布からサンプリングする必要があります。この分布は文字語彙に渡るロジットにより定義されます。

Note: この分布からサンプリングすることは重要です、というのは分布の argmax を取ることはループでモデルを容易に stuck させる可能性があるためです。

バッチの最初のサンプルのためにそれを試します :

sampled_indices = tf.random.categorical(example_batch_predictions[0], num_samples=1)
sampled_indices = tf.squeeze(sampled_indices,axis=-1).numpy()

これは、各時間ステップにおける、次の文字インデックスの予測を与えます :

sampled_indices
array([45, 37,  3, 51, 29,  6, 42, 49, 16,  0,  8, 37, 10, 32, 15, 28, 13,
       24, 11, 59, 35, 59, 38, 36, 21, 63, 34, 56, 48, 23, 28,  8, 30, 28,
       25,  3, 47, 34, 54,  7, 39, 15, 40, 46, 40, 16, 13,  8, 34, 61, 59,
       20, 27, 25,  7, 43, 17, 51, 41, 33, 10, 60,  2, 21, 63, 48, 24, 56,
       43, 39,  9, 46, 14, 38, 55, 40, 20, 32, 38, 29, 46, 12,  4, 60, 11,
       24, 23, 55, 10, 59, 47, 33, 40, 13, 43,  7, 34, 17, 31, 28])

この未訓練モデルにより予測されたテキストを見るためにこれらをデコードします :

print("Input: \n", repr("".join(idx2char[input_example_batch[0]])))
print()
print("Next Char Predictions: \n", repr("".join(idx2char[sampled_indices ])))
Input: 
 'ilent judgment tried it,\nWithout more overture.\n\nLEONTES:\nHow could that be?\nEither thou art most ig'

Next Char Predictions: 
 'gY$mQ,dkD\n.Y:TCPAL;uWuZXIyVrjKP.RPM$iVp-aCbhbDA.VwuHOM-eEmcU:v!IyjLrea3hBZqbHTZQh?&v;LKq:uiUbAe-VESP'

 

モデルを訓練する

この時点で問題は標準的な分類問題として扱うことができます。前の RNN 状態、そしてこの時間ステップで入力が与えられたとき、次の文字のクラスを予測します。

 

optimizer、そして損失関数を装着する

この場合には標準的な tf.keras.losses.sparse_softmax_crossentropy 損失関数が動作します、何故ならばそれは予測の最後の次元に渡り適用されるからです。

私達のモデルはロジットを返しますので、from_logits フラグを設定する必要があります。

def loss(labels, logits):
  return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)

example_batch_loss  = loss(target_example_batch, example_batch_predictions)
print("Prediction shape: ", example_batch_predictions.shape, " # (batch_size, sequence_length, vocab_size)")
print("scalar_loss:      ", example_batch_loss.numpy().mean())
Prediction shape:  (64, 100, 65)  # (batch_size, sequence_length, vocab_size)
scalar_loss:       4.174535

tf.keras.Model.compile メソッドを使用して訓練手続きを configure します。デフォルト引数を持つ tf.keras.optimizers.Adam と損失関数を使用します。

model.compile(optimizer='adam', loss=loss)

 

チェックポイントを構成する

チェックポイントが訓練の間にセーブされることを確実にするために tf.keras.callbacks.ModelCheckpoint を使用します :

# Directory where the checkpoints will be saved
checkpoint_dir = './training_checkpoints'
# Name of the checkpoint files
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt_{epoch}")

checkpoint_callback=tf.keras.callbacks.ModelCheckpoint(
    filepath=checkpoint_prefix,
    save_weights_only=True)

 

訓練を実行する

訓練時間を合理的なものに保持するために、モデルを訓練するために 10 エポックを使用します。

EPOCHS=10
history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])
Epoch 1/10
172/172 [==============================] - 8s 47ms/step - loss: 2.5719
Epoch 2/10
172/172 [==============================] - 7s 39ms/step - loss: 1.8664
Epoch 3/10
172/172 [==============================] - 7s 43ms/step - loss: 1.6230
Epoch 4/10
172/172 [==============================] - 7s 43ms/step - loss: 1.4920
Epoch 5/10
172/172 [==============================] - 7s 40ms/step - loss: 1.4106
Epoch 6/10
172/172 [==============================] - 7s 40ms/step - loss: 1.3502
Epoch 7/10
172/172 [==============================] - 7s 40ms/step - loss: 1.2989
Epoch 8/10
172/172 [==============================] - 7s 40ms/step - loss: 1.2519
Epoch 9/10
172/172 [==============================] - 7s 41ms/step - loss: 1.2077
Epoch 10/10
172/172 [==============================] - 7s 40ms/step - loss: 1.1659

 

テキストを生成する

最新のチェックポイントを復元する

この予測ステップを単純に保持するために、1 のバッチサイズを使用します。

RNN 状態が時間ステップから時間ステップに渡される方法のために、モデルは一度構築された固定バッチサイズだけを受け取ります。

異なる batch_size でモデルを実行するためには、モデルを再構築してチェックポイントから重みを復元する (= restore) 必要があります。

tf.train.latest_checkpoint(checkpoint_dir)
'./training_checkpoints/ckpt_10'
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)

model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))

model.build(tf.TensorShape([1, None]))
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (1, None, 256)            16640     
_________________________________________________________________
lstm_1 (LSTM)                (1, None, 1024)           5246976   
_________________________________________________________________
dense_1 (Dense)              (1, None, 65)             66625     
=================================================================
Total params: 5,330,241
Trainable params: 5,330,241
Non-trainable params: 0
_________________________________________________________________

 

予測ループ

次のコードブロックがテキストを生成します :

  • それは開始文字列を選択し、RNN 状態を初期化してそして生成する文字数を設定することから始まります。
  • 開始文字列と RNN 状態を使用して次の文字の予測分布を得ます。
  • それから、予測された文字のインデックスを計算するために categorical 分布を使用します。この予測された文字をモデルへの次の入力として使用します。
  • モデルにより返された RNN 状態はモデルに供給し戻されます、その結果それは今ではただ一つの単語よりも多くのコンテキストを代わりに持ちます。次の単語を予測した後、変更された RNN 状態が再度モデルに供給し戻されます、これがそれが前に予測された単語からより多くのコンテキストを得るときにどのように学習するかです。

生成されたテキストを見れば、モデルがいつ大文字にするべきかを知り、パラグラフを作成してそして Shakespeare 的な著述語彙を模倣するのを見るでしょう。小さい数の訓練エポックでは、それは首尾一貫したセンテンスを形成することをまだ学習していません。

def generate_text(model, start_string):
  # Evaluation step (generating text using the learned model)

  # Number of characters to generate
  num_generate = 1000

  # Converting our start string to numbers (vectorizing)
  input_eval = [char2idx[s] for s in start_string]
  input_eval = tf.expand_dims(input_eval, 0)

  # Empty string to store our results
  text_generated = []

  # Low temperatures results in more predictable text.
  # Higher temperatures results in more surprising text.
  # Experiment to find the best setting.
  temperature = 1.0

  # Here batch size == 1
  model.reset_states()
  for i in range(num_generate):
      predictions = model(input_eval)
      # remove the batch dimension
      predictions = tf.squeeze(predictions, 0)

      # using a categorical distribution to predict the word returned by the model
      predictions = predictions / temperature
      predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy()

      # We pass the predicted word as the next input to the model
      # along with the previous hidden state
      input_eval = tf.expand_dims([predicted_id], 0)

      text_generated.append(idx2char[predicted_id])

  return (start_string + ''.join(text_generated))
print(generate_text(model, start_string=u"ROMEO: "))
ROMEO: where he skill
That ever we fear their looks.

LEONTES:
S stooph of ournd
The dukes o' the selferur is it of her;
Forses thereof willingly no wealth!

TRANIO:
Mine ears, sir, to them speak. whth ever young with one there her to
do than thy heart? There is a fie,
The reverse honoural gives my dather's death
That waves we'll sovereignty of thy aughts,
Your keels with us, save axhousinous: gentle brifferent, for an intent
I did forbilling ougly age on my sin,
With such man est in thy father's father's sky,
As tells me, Yorknishall and Prittend Henry, and her part withan this place
I'll speak of soldier; then I was; to report on
how horse spokes ready the nie, I knew thee,
Pray you, indeed. You were not 'quirence will wear-fallentut and frazed.

YORK:
Is no beg-of this to the Tower.

KATHARINA:
Here is a war.

Third Citizen:
You have professed them and crse trunk blow his chlician.

QUEEN ELIZABETH:
So sovereour a gentlemen, 'tis a book-so sort,
Amen, unwilling,
your love and wars their pa

結果を改善するために貴方ができる最も容易なことはそれをより長く訓練することです (EPOCHS=30 を試してください)。

貴方はまた異なる開始文字列で実験したり、モデル精度を改良するためにもう一つの RNN 層を追加して試す、あるいは多少のランダム予測を生成するために temperature パラメータを調整することができます。

 

Advanced: カスタマイズされた訓練

上の訓練手続きは単純ですが、多くの制御を貴方に与えません。

そこでモデルを手動でどのように実行するかを見た今、訓練ループをアンパックしてそれを私達自身で実装しましょう。これは例えば、モデルの open-loop 出力を安定させる手助けをするためにカリキュラム学習を実装するための開始点を与えます。

勾配を追跡するために tf.GradientTape を使用します。eager execution ガイド を読むことによりこのアプローチについてより多く学習できます。

手続きは次のように動作します :

  • 最初に、RNN 状態を初期化します。tf.keras.Model.reset_states メソッドを呼び出すことによりこれを行ないます。
  • 次に、データセットに渡り (バッチ毎に) iterate して各々に関連する予測を計算します。
  • tf.GradientTape をオープンして、そしてそのコンテキストで予測と損失を計算します。
  • tf.GradientTape.grads メソッドを使用してモデル変数に関する損失の勾配を計算します。
  • 最後に、optimizer の tf.train.Optimizer.apply_gradients メソッドを使用してステップを下方に取ります。
model = build_model(
  vocab_size = len(vocab),
  embedding_dim=embedding_dim,
  rnn_units=rnn_units,
  batch_size=BATCH_SIZE)
optimizer = tf.keras.optimizers.Adam()
@tf.function
def train_step(inp, target):
  with tf.GradientTape() as tape:
    predictions = model(inp)
    loss = tf.reduce_mean(
        tf.keras.losses.sparse_categorical_crossentropy(
            target, predictions, from_logits=True))
  grads = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(grads, model.trainable_variables))

  return loss
# Training step
EPOCHS = 10

for epoch in range(EPOCHS):
  start = time.time()

  # initializing the hidden state at the start of every epoch
  # initally hidden is None
  hidden = model.reset_states()

  for (batch_n, (inp, target)) in enumerate(dataset):
    loss = train_step(inp, target)

    if batch_n % 100 == 0:
      template = 'Epoch {} Batch {} Loss {}'
      print(template.format(epoch+1, batch_n, loss))

  # saving (checkpoint) the model every 5 epochs
  if (epoch + 1) % 5 == 0:
    model.save_weights(checkpoint_prefix.format(epoch=epoch))

  print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss))
  print ('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

model.save_weights(checkpoint_prefix.format(epoch=epoch))
WARNING: Logging before flag parsing goes to stderr.
W0628 06:41:29.934798 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer
W0628 06:41:29.936547 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.iter
W0628 06:41:29.937477 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.beta_1
W0628 06:41:29.938759 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.beta_2
W0628 06:41:29.939390 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.decay
W0628 06:41:29.940099 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.learning_rate
W0628 06:41:29.940868 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-0.embeddings
W0628 06:41:29.941582 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-2.kernel
W0628 06:41:29.942270 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-2.bias
W0628 06:41:29.944056 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.kernel
W0628 06:41:29.944905 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.recurrent_kernel
W0628 06:41:29.945559 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.bias
W0628 06:41:29.946222 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-0.embeddings
W0628 06:41:29.947816 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-2.kernel
W0628 06:41:29.948982 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-2.bias
W0628 06:41:29.949806 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.kernel
W0628 06:41:29.950479 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.recurrent_kernel
W0628 06:41:29.951171 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.bias
W0628 06:41:29.952606 140162012227328 util.py:252] A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/alpha/guide/checkpoints#loading_mechanics for details.

Epoch 1 Batch 0 Loss 4.172821998596191
Epoch 1 Batch 100 Loss 2.391324758529663
Epoch 1 Loss 2.0715
Time taken for 1 epoch 9.32474422454834 sec

Epoch 2 Batch 0 Loss 2.1118874549865723
Epoch 2 Batch 100 Loss 1.9127529859542847
Epoch 2 Loss 1.7389
Time taken for 1 epoch 6.282292127609253 sec

Epoch 3 Batch 0 Loss 1.7284945249557495
Epoch 3 Batch 100 Loss 1.6665358543395996
Epoch 3 Loss 1.5530
Time taken for 1 epoch 6.350098133087158 sec

Epoch 4 Batch 0 Loss 1.5307190418243408
Epoch 4 Batch 100 Loss 1.528407335281372
Epoch 4 Loss 1.4468
Time taken for 1 epoch 6.355969429016113 sec

Epoch 5 Batch 0 Loss 1.4143530130386353
Epoch 5 Batch 100 Loss 1.451310396194458
Epoch 5 Loss 1.3683
Time taken for 1 epoch 6.345163583755493 sec

Epoch 6 Batch 0 Loss 1.3415405750274658
Epoch 6 Batch 100 Loss 1.3894364833831787
Epoch 6 Loss 1.3122
Time taken for 1 epoch 6.310795068740845 sec

Epoch 7 Batch 0 Loss 1.2863143682479858
Epoch 7 Batch 100 Loss 1.3341155052185059
Epoch 7 Loss 1.2637
Time taken for 1 epoch 6.189915895462036 sec

Epoch 8 Batch 0 Loss 1.2400826215744019
Epoch 8 Batch 100 Loss 1.2844866514205933
Epoch 8 Loss 1.2265
Time taken for 1 epoch 6.186691999435425 sec

Epoch 9 Batch 0 Loss 1.2004377841949463
Epoch 9 Batch 100 Loss 1.2391895055770874
Epoch 9 Loss 1.1848
Time taken for 1 epoch 6.293030500411987 sec

Epoch 10 Batch 0 Loss 1.1650713682174683
Epoch 10 Batch 100 Loss 1.19230055809021
Epoch 10 Loss 1.1492
Time taken for 1 epoch 6.376950740814209 sec
 

以上






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