ホーム » 「TensorFlow Tutorials」タグがついた投稿

タグアーカイブ: TensorFlow Tutorials

TensorFlow : Tutorials : データ表現 : カーネル法を使用した線形モデルの改良

TensorFlow : Tutorials : データ表現 : カーネル法を使用した線形モデルの改良 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 11/23/2018 (v1.12.0)

* 本ページは、TensorFlow の本家サイトの Tutorials – Data representation – Improving Linear Models Using Explicit Kernel Methods を翻訳した上で適宜、補足説明したものです:

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

 

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

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

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

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

 

 

本文

このチュートリアルでは、(explicit) カーネル法の線形モデルとの結合が (訓練と推論時間を本質的に増やすことなく) 後者の予測の品質をどのように劇的に増加させるかを示します。dual カーネル法とは違い、explicit (primal) カーネル法は訓練/推論時間とメモリ要求の両者の観点から訓練データセットのサイズと共に上手くスケールします。

対象読者: explicit カーネル法に関連した概念の高位な概要を提供しますが、このチュートリアルは主として少なくともカーネル法とサポートベクターマシン (SVM) の基本的な知識を既に持つ読者を対象とします。もしカーネル法を知らないのであれば、イントロダクションのために次のソースのいずれかを参照してください :

現在、TensorFlow は密な特徴のための explicit カーネル・マッピングだけをサポートします ; TensorFlow は後のリリースで疎な特徴のためのサポートを提供します。

このチュートリアルは ML モデルのために tf.contrib.learn (TensorFlow の高位機械学習 API) Estimator を使用します。この API に馴染みがない場合には、Estimator ガイド で始めるのが良いでしょう。MNIST データセットを使用します。チュートリアルは次のステップから成ります :

  • 分類のために MNIST データをロードして準備する
  • 単純な線形モデルを構築し、それを訓練し、そして評価データ上でそれを評価します。
  • 線形モデルを kernelized 線形モデルを置き換え、再訓練し、そして再評価します。

 

分類のために MNIST データをロードして準備する

MNIST データセットをロードするために次のユティリティコマンドを実行します :

data = tf.contrib.learn.datasets.mnist.load_mnist()

先のメソッドは (70K サンプルを含む) MNIST データセット 全体をロードしてそれをそれぞれ 55K, 5K, そして 10K サンプルを持つ訓練、検証、そしてテストデータに分割します。各分割は (shape [sample_size, 784] を持つ) 画像のための一つのnumpy 配列と (shape [sample_size, 1] を持つ) ラベルのための一つを含みます。このチュートリアルでは、モデルを訓練して評価するためにそれぞれ訓練と検証分割だけを使用します。

tf.contrib.learn Estimator にデータを供給するために、それを Tensor に変換することは有用です。このため、入力関数を使用します、これは TensorFlow グラフに (実行されたとき) ダウンストリームに使用される Tensor のミニバッチを作成する Ops を追加します。入力関数の更なる背景については、入力関数のこのセクション をチェックしてください。この例では、 tf.train.shuffle_batch Op を使用します、これは numpy 配列を Tensor に変換することに加えて、batch_size と input_fn Ops が実行されるたびに入力をランダム化するか否かを指定することを可能にします (ランダム化は典型的には訓練の間の収束を早めます)。データをロードして準備するための full コードは下のスニペットで示されます。この例では、訓練のためにサイズ 256 のミニバッチをそして評価のためにサンプル全体 (5K エントリ) を使用します。異なるバッチサイズで自由に実験してください。

import numpy as np
import tensorflow as tf

def get_input_fn(dataset_split, batch_size, capacity=10000, min_after_dequeue=3000):

  def _input_fn():
    images_batch, labels_batch = tf.train.shuffle_batch(
        tensors=[dataset_split.images, dataset_split.labels.astype(np.int32)],
        batch_size=batch_size,
        capacity=capacity,
        min_after_dequeue=min_after_dequeue,
        enqueue_many=True,
        num_threads=4)
    features_map = {'images': images_batch}
    return features_map, labels_batch

  return _input_fn

data = tf.contrib.learn.datasets.mnist.load_mnist()

train_input_fn = get_input_fn(data.train, batch_size=256)
eval_input_fn = get_input_fn(data.validation, batch_size=5000)

 

単純な線形モデルを訓練する

今では MNIST データセットに渡り線形モデルを訓練できます。10 数字を表わす 10 クラスで tf.contrib.learn.LinearClassifier estimator を使用します。入力特徴は 784-次元密ベクトルを形成し、これは次のように指定できます :

image_column = tf.contrib.layers.real_valued_column('images', dimension=784)

LinearClassifier estimator を構築し、訓練して評価するための full コードは次のようなものです :

import time

# Specify the feature(s) to be used by the estimator.
image_column = tf.contrib.layers.real_valued_column('images', dimension=784)
estimator = tf.contrib.learn.LinearClassifier(feature_columns=[image_column], n_classes=10)

# Train.
start = time.time()
estimator.fit(input_fn=train_input_fn, steps=2000)
end = time.time()
print('Elapsed time: {} seconds'.format(end - start))

# Evaluate and report metrics.
eval_metrics = estimator.evaluate(input_fn=eval_input_fn, steps=1)
print(eval_metrics)

次のテーブルは評価データ上の結果を要約します。

メトリック
loss 0.25 to 0.30
accuracy 92.5%
training time ~25 seconds on my machine

(訓練) バッチサイズと訓練ステップの数で実験することに加えて、調整可能な 2, 3 の他のパラメータもあります。例えば、利用可能な optimizer のコレクションから他の optimizer を明示的に選択することにより損失を最小化するために使用される最適化メソッドを変更できます。例として、次のコードは特定の学習率と L2-正則化を持つ Follow-The-Regularized-Leader (FTRL) 最適化ストラテジーを使用する LinearClassifier を構築します。

optimizer = tf.train.FtrlOptimizer(learning_rate=5.0, l2_regularization_strength=1.0)
estimator = tf.contrib.learn.LinearClassifier(
    feature_columns=[image_column], n_classes=10, optimizer=optimizer)

パラメータの値にかかわらず、線形モデルがこのデータセット上で達成できる最大精度はおよそ 93% が上限です。

 

線形モデルで explicit カーネル・マッピングを使用する

MNIST に渡る比較的高いエラー (~7%) は入力データが線形分離可能ではないことを示します。分類エラーを減じるために explicit カーネル・マッピングを使用します。

直感: 高位のアイデアは入力空間を (可能なより高位次元の) もう一つの特徴空間に変換するために非線型マップを使用することです、そこでは (変換された) 特徴は (殆ど) 線形分類可能でそれからマップされた特徴上で線形モデルを適用します。これは次の図で示されます :

 

技術的詳細

この例では入力データをマップするために、Rahimi と Recht によるペーパー "Random Features for Large-Scale Kernel Machines" で紹介された、乱択化フーリエ特徴 (Random Fourier Features) を使用します。乱択化フーリエ特徴は次のマッピング :
$$
RFFM(\cdot): \mathbb{R}^d \to \mathbb{R}^D, \quad
RFFM(\mathbf{x}) = \cos(\mathbf{\Omega} \cdot \mathbf{x}+ \mathbf{b})
$$

を通してベクトル \(\mathbf{x} \in \mathbb{R}^d\) を \(\mathbf{x'} \in \mathbb{R}^D\) にマップします。ここで\(\mathbf{\Omega} \in \mathbb{R}^{D \times d}\), \(\mathbf{x} \in \mathbb{R}^d,\) \(\mathbf{b} \in \mathbb{R}^D\) そして cosine は element-wise に適用されます。

この例では、\(\mathbf{\Omega}\) and \(\mathbf{b}\) のエントリはマッピングが次のプロパティを満たすように分布からサンプリングされます :
$$
RFFM(\mathbf{x})^T \cdot RFFM(\mathbf{y}) \approx
e^{-\frac{\|\mathbf{x} – \mathbf{y}\|^2}{2 \sigma^2}}
$$

上の式の右辺の量はRBF (or ガウシアン) カーネル関数として知られています。この関数は機械学習で最も広く使用されるカーネル関数の一つで元の一つよりも異なる、遥かに高い次元空間で類似性を暗黙的に計測します。より詳細については Radial basis function kernel を見てください。

 

カーネル分類器

tf.contrib.kernel_methods.KernelLinearClassifier は事前パッケージされた tf.contrib.learn estimator で、explicit カーネルマッピングのパワーを線形モデルと結合しています。そのコンストラクタは LinearClassifier estimator のそれと殆ど同じですが、分類器が使用する各特徴に適用される explicit カーネルマッピングのリストを指定するための追加のオプションを伴います。次のコードスニペットは LinearClassifier を KernelLinearClassifier とどのように置き換えるかを示します。

# Specify the feature(s) to be used by the estimator. This is identical to the
# code used for the LinearClassifier.
image_column = tf.contrib.layers.real_valued_column('images', dimension=784)
optimizer = tf.train.FtrlOptimizer(
   learning_rate=50.0, l2_regularization_strength=0.001)


kernel_mapper = tf.contrib.kernel_methods.RandomFourierFeatureMapper(
  input_dim=784, output_dim=2000, stddev=5.0, name='rffm')
kernel_mappers = {image_column: [kernel_mapper]}
estimator = tf.contrib.kernel_methods.KernelLinearClassifier(
   n_classes=10, optimizer=optimizer, kernel_mappers=kernel_mappers)

# Train.
start = time.time()
estimator.fit(input_fn=train_input_fn, steps=2000)
end = time.time()
print('Elapsed time: {} seconds'.format(end - start))

# Evaluate and report metrics.
eval_metrics = estimator.evaluate(input_fn=eval_input_fn, steps=1)
print(eval_metrics)

KernelLinearClassifier に渡される唯一の追加のパラメータは対応する特徴カラムに適用される feature_columns からカーネルマッピングのリストへの辞書です。次の行群は分類器に乱択化フーリエ特徴を使用して最初に初期 784-次元画像を 2000-次元ベクトルにマップしてそれから変換されたベクトル上で線形モデルを学習することを指示します :

kernel_mapper = tf.contrib.kernel_methods.RandomFourierFeatureMapper(
  input_dim=784, output_dim=2000, stddev=5.0, name='rffm')
kernel_mappers = {image_column: [kernel_mapper]}
estimator = tf.contrib.kernel_methods.KernelLinearClassifier(
   n_classes=10, optimizer=optimizer, kernel_mappers=kernel_mappers)

stddev パラメータに注意してください。これは近似された RBF カーネルの標準偏差 (\(\sigma\)) で分類で使用される類似尺度を制御します。stddev は典型的にはハイパーパラメータ調整を通して決定されます。

前のコードの実行結果は次のテーブルで要約されます。マッピングの出力次元を増やして標準偏差を調整することにより精度を更に増加できます。

メトリック
loss 0.10
accuracy 97%
training time ~35 seconds on my machine

 

stddev

分類品質は stddev の値に非常に敏感です。次のテーブルはstddev の異なる値について評価データ上の分類器の精度を示します。最適な値は stddev=5.0 です。小さすぎる、あるいは高すぎる stddev 値がどのように劇的に分類精度を下げるかに注意してください。

stddev 評価精度
1.0 0.1362
2.0 0.4764
4.0 0.9654
5.0 0.9766
8.0 0.9714
16.0 0.8878

 

出力次元

直感的には、マッピングの出力次元がより大きくなるにつれて、2 つのマップされたベクトルの内積はカーネルをより密接に近似し、これは典型的にはより良い分類精度に繋がります。これについて考えるもう一つの方法は出力次元は線形モデルの重みの数に等しいということです ; モデルの「自由度」もより大きくなります。けれども、ある閾値後には、より高い出力次元は精度を非常に小さい幅で増加させる一方で、訓練により多くの時間がかかります。これは次の 2 の図で示されます、これは評価精度をそれぞれ出力次元と訓練時間の関数として描きます。

 

Summary

explicit カーネルマッピングは非線形モデルの予測パワーを線形モデルのスケーラビリティと結合します。伝統的な dual カーネル法とは違い、explicit カーネル法は何百万あるいは何億のサンプルにスケール可能です。explicit カーネルマッピングを使用するとき、次の tips を考慮してください :

  • 乱択化フーリエ特徴は密特徴を持つデータセットのために特に効果的です。
  • カーネルマッピングのパラメータはしばしばデータ依存です。モデル品質はこれらのパラメータに非常に敏感であり得ます。最適な値を見つけるためにハイパーパラメータ調整を使用してください。
  • 複数の数値特徴を持つ場合、それらを単一のマルチ次元特徴に結合してカーネルマッピングを結合されたベクトルに適用してください。
 

以上

TensorFlow : Tutorials : 生成モデル : 畳み込み VAE : tf.keras と eager のサンプル

TensorFlow : Tutorials : 生成モデル : 畳み込み変分オートエンコーダ (翻訳/解説)

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

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

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

 

畳み込み変分オートエンコーダ

このノートブックは tf.keras と eager execution を使用して変分オートエンコーダ (= Variational Autoencoder, VAE, [1], [2]) を訓練することにより手書き数字の画像をどのように生成するかを示します。

# to generate gifs
!pip install imageio

 

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

from __future__ import absolute_import, division, print_function

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

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

 

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)$ のためのパラメータを出力します。更に、潜在変数のために単位ガウス prior $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=tf.nn.relu),
          tf.keras.layers.Conv2D(
              filters=64, kernel_size=3, strides=(2, 2), activation=tf.nn.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=tf.nn.relu),
          tf.keras.layers.Conv2DTranspose(
              filters=32,
              kernel_size=3,
              strides=(2, 2),
              padding="SAME",
              activation=tf.nn.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].$$

実際に、この期待値の single-sample モンテカルロ推定を最適化します :

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

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

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

def log_normal_pdf(sample, mean, logvar, raxis=1):
  log2pi = tf.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

optimizer = tf.train.AdamOptimizer(1e-4)
def apply_gradients(optimizer, gradients, variables, global_step=None):
  optimizer.apply_gradients(zip(gradients, variables), global_step=global_step)

 

訓練

  • データセットに渡り反復することから始めます。
  • 各反復の間、近似事後 $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 = tfe.metrics.Mean()
    for test_x in test_dataset.make_one_shot_iterator():
      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)

 

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

def display_image(epoch_no):
  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))
display_image(epochs)  # Display images

 

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

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

以上






TensorFlow : Tutorials : 画像 : tf.keras で画像セグメンテーション

TensorFlow : Tutorials : 画像 : tf.keras で画像セグメンテーション (翻訳/解説)

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

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

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

 

tf.keras で画像セグメンテーション

このチュートリアルではどのように画像をセグメントするかを学習します。セグメンテーションは各ピクセルにおけるオブジェクトのクラスを可視化して、pixel-wise なセグメンテーションを生成するプロセスです。例えば、画像内の人々の位置と境界を識別したり画像から細胞核を識別したりできます。公式には、画像セグメンテーションは画像を識別することを望むピクセルのセット (私達のターゲット) と背景に分割するプロセスを参照します。

特に、このチュートリアルでは Kaggle Carvana 画像 Masking チャレンジ・データセット を使用しています。

データセットは巨大な数の自動車画像を含み、各自動車は異なる角度から取られています。

更に、各自動車画像について、関連する手動による cutout (切り抜き) マスクを持ちます ; タスクは見ていないデータについてこれらの cutout マスクを自動的に作成することです。

 

カバーされる特定の概念

プロセスでは、実践的な経験を構築して次の概念まわりの直感を開発します :

  • Functional API – 私達は Functional API で UNet を実装します、生物医学の画像セグメンテーションのために古典的に使用された畳込みネットワーク・モデルです。
  • カスタム損失関数とメトリクス – バイナリ交差エントロピーと Dice 損失を使用してカスタム損失関数を実装します。dice 係数 (これは損失のために使用されます) と平均 intersection over union もまた実装します、これは訓練プロセスを監視してどのくらい上手く遂行しているかを判断するに役立ちます。
  • keras モデルをセーブしてロードする – 最善なモデルをディスクにセーブします。推論を遂行/モデルを評価することを望むとき、ディスクからモデルをロードします。

 

一般的なワークフローに従います :

  1. データを可視化する/何某かの調査的なデータ解析を遂行する
  2. データ・パイプラインをセットアップして前処理をする
  3. モデルを構築する
  4. モデルを訓練する
  5. モデルを評価する
  6. 反復

By: Raymond Yuan, Software Engineering Intern

 

!pip install kaggle
import os
import glob
import zipfile
import functools

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

from sklearn.model_selection import train_test_split
import matplotlib.image as mpimg
import pandas as pd
from PIL import Image
import tensorflow as tf
import tensorflow.contrib as tfcontrib
from tensorflow.python.keras import layers
from tensorflow.python.keras import losses
from tensorflow.python.keras import models
from tensorflow.python.keras import backend as K

 

総てのファイルを取得する

このチュートリアルは Kaggle からのデータセットを使用していますので、それは貴方の Kaggle アカウントのための API トークンを作成して、それをアップロードすることを必要とします。

import os

# Upload the API token.
def get_kaggle_credentials():
  token_dir = os.path.join(os.path.expanduser("~"),".kaggle")
  token_file = os.path.join(token_dir, "kaggle.json")
  if not os.path.isdir(token_dir):
    os.mkdir(token_dir)
  try:
    with open(token_file,'r') as f:
      pass
  except IOError as no_file:
    try:
      from google.colab import files
    except ImportError:
      raise no_file
    
    uploaded = files.upload()
    
    if "kaggle.json" not in uploaded:
      raise ValueError("You need an API key! see: "
                       "https://github.com/Kaggle/kaggle-api#api-credentials")
    with open(token_file, "wb") as f:
      f.write(uploaded["kaggle.json"])
    os.chmod(token_file, 600)

get_kaggle_credentials()

credential を追加後に kaggle だけをインポートします。

import kaggle

 

Kaggle からデータをダウンロードする

警告、これから先に巨大なダウンロード – 総てのファイルのダウンロードはディスク容量の 14GB を必要とします。

competition_name = 'carvana-image-masking-challenge'
# Download data from Kaggle and unzip the files of interest. 
def load_data_from_zip(competition, file):
  with zipfile.ZipFile(os.path.join(competition, file), "r") as zip_ref:
    unzipped_file = zip_ref.namelist()[0]
    zip_ref.extractall(competition)

def get_data(competition):
    kaggle.api.competition_download_files(competition, competition)
    load_data_from_zip(competition, 'train.zip')
    load_data_from_zip(competition, 'train_masks.zip')
    load_data_from_zip(competition, 'train_masks.csv.zip')

データをダウンロードする前に competition ルールを受諾 しなければなりません。

get_data(competition_name)
mg_dir = os.path.join(competition_name, "train")
label_dir = os.path.join(competition_name, "train_masks")
df_train = pd.read_csv(os.path.join(competition_name, 'train_masks.csv'))
ids_train = df_train['img'].map(lambda s: s.split('.')[0])
x_train_filenames = []
y_train_filenames = []
for img_id in ids_train:
  x_train_filenames.append(os.path.join(img_dir, "{}.jpg".format(img_id)))
  y_train_filenames.append(os.path.join(label_dir, "{}_mask.gif".format(img_id)))
x_train_filenames, x_val_filenames, y_train_filenames, y_val_filenames = \
                    train_test_split(x_train_filenames, y_train_filenames, test_size=0.2, random_state=42)
num_train_examples = len(x_train_filenames)
num_val_examples = len(x_val_filenames)

print("Number of training examples: {}".format(num_train_examples))
print("Number of validation examples: {}".format(num_val_examples))

 

パスがどのように見えるかがここにあります

x_train_filenames[:10]
y_train_filenames[:10]

 

可視化する

データセットの異なる画像のサンプルの幾つかを見てみましょう。

display_num = 5

r_choices = np.random.choice(num_train_examples, display_num)

plt.figure(figsize=(10, 15))
for i in range(0, display_num * 2, 2):
  img_num = r_choices[i // 2]
  x_pathname = x_train_filenames[img_num]
  y_pathname = y_train_filenames[img_num]
  
  plt.subplot(display_num, 2, i + 1)
  plt.imshow(mpimg.imread(x_pathname))
  plt.title("Original Image")
  
  example_labels = Image.open(y_pathname)
  label_vals = np.unique(example_labels)
  
  plt.subplot(display_num, 2, i + 2)
  plt.imshow(example_labels)
  plt.title("Masked Image")  
  
plt.suptitle("Examples of Images and their Masks")
plt.show()

 

セットアップ

幾つかのパラメータの設定から始めましょう。画像の総ての shape を標準化してリサイズします。幾つかの訓練パラメータも設定します :

img_shape = (256, 256, 3)
batch_size = 3
epochs = 5

これらの正確な同じパラメータの使用は貴方のハードウェアにとっては計算集約的過ぎるので、必要に応じてパラメータをいじります。また、UNet バージョンのアーキテクチャにより、画像サイズは 32 の factor で均等に割り切れなければなりません、何故ならば各 MaxPooling2Dlayer で 2 の factor で空間解像度をダウンサンプリングするからです。

貴方のマシンがそれをサポートできるのであれば、より高い解像度の入力画像 (e.g. 512 x 512) を使用してより良いパフォーマンスを獲得するでしょう、何故ならばこれはエンコーディングの間により正確な位置特定とより少ない情報の損失を可能にするからです。更に、モデルをより深くすることもできます。

代わりに、貴方のマシンがそれをサポートできないのであれば、画像解像度 and/or バッチサイズをより小さくしてください。画像解像度を小さくすればパフォーマンスを下げてバッチサイズを小さくすれば訓練時間を増大させます。

 

入力パイプラインを tf.data で構築する

ファイル名から始めますので、モデルと上手く戯れる、堅牢でスケーラブルなデータ・パイプラインを構築する必要があります。

 

入力パイプラインは次のステップから成ります :

  1. ファイル名からファイルのバイトを読みます – 画像とラベルの両者のために。ラベルは実際には自動車か背景 (1, 0) としてアノテートされた各ピクセルを持つ画像であることを思い出してください。
  2. バイトを画像フォーマットにデコードします。
  3. 画像変換を適用します: (オプション、入力パラメータに従って)
    • resize – 画像を (eda か計算/メモリ制限により決定される) 標準サイズにリサイズします。
      • これがオプションである理由は U-Net が完全畳み込みネットワーク (e.g. 完全結合ユニットを持たない) であるからでそのため入力サイズには依拠しません。けれども、もし画像をリサイズしないことを選択する場合、1 のバッチサイズを使用する必要があります、何故ならば可変な画像サイズを一緒にバッチ処理できないからです。
      • 代わりに、同程度の画像のリサイズを回避するために画像をまとめてバケツに入れてそれらをミニバッチ毎にリサイズすることもできます、何故ならばリサイズは補間を通してパフォーマンスに影響を与えるかもしれないからです。
    • hue_delta – ランダム factor で RGB 画像の色合いを調整します。これは実際の画像にのみ適用されます (ラベル画像ではなく)。hue_delta は区間 [0, 0.5] 内でなければなりません。
    • horizontal_flip – 0.5 の確率で中心軸に沿って水平に画像を反転します。この変換はラベルと実際の画像の両者に適用されなければなりません。
    • width_shift_range と height_shift_range は (総計の幅か高さの比率としての) 範囲で、その範囲内で画像を水平か垂直にランダムに変換します。この変換はラベルと実際の画像の両者に適用されなければなりません。
    • rescale – 画像を特定の factor, e.g. 1/ 255 でリスケールします。
  4. データをシャッフルし、データを反復し (エポックに渡り複数回それを反復できます)、データをバッチ処理し、それからバッチを prefetch します (効率性のため)。

 

各パス名を処理する

def _process_pathnames(fname, label_path):
  # We map this function onto each pathname pair  
  img_str = tf.read_file(fname)
  img = tf.image.decode_jpeg(img_str, channels=3)

  label_img_str = tf.read_file(label_path)
  # These are gif images so they return as (num_frames, h, w, c)
  label_img = tf.image.decode_gif(label_img_str)[0]
  # The label image should only have values of 1 or 0, indicating pixel wise
  # object (car) or not (background). We take the first channel only. 
  label_img = label_img[:, :, 0]
  label_img = tf.expand_dims(label_img, axis=-1)
  return img, label_img

 

画像をシフトする

def shift_img(output_img, label_img, width_shift_range, height_shift_range):
  """This fn will perform the horizontal or vertical shift"""
  if width_shift_range or height_shift_range:
      if width_shift_range:
        width_shift_range = tf.random_uniform([], 
                                              -width_shift_range * img_shape[1],
                                              width_shift_range * img_shape[1])
      if height_shift_range:
        height_shift_range = tf.random_uniform([],
                                               -height_shift_range * img_shape[0],
                                               height_shift_range * img_shape[0])
      # Translate both 
      output_img = tfcontrib.image.translate(output_img,
                                             [width_shift_range, height_shift_range])
      label_img = tfcontrib.image.translate(label_img,
                                             [width_shift_range, height_shift_range])
  return output_img, label_img

 

画像をランダムに反転する

def flip_img(horizontal_flip, tr_img, label_img):
  if horizontal_flip:
    flip_prob = tf.random_uniform([], 0.0, 1.0)
    tr_img, label_img = tf.cond(tf.less(flip_prob, 0.5),
                                lambda: (tf.image.flip_left_right(tr_img), tf.image.flip_left_right(label_img)),
                                lambda: (tr_img, label_img))
  return tr_img, label_img

 

変換を augment 関数に集約する

def _augment(img,
             label_img,
             resize=None,  # Resize the image to some size e.g. [256, 256]
             scale=1,  # Scale image e.g. 1 / 255.
             hue_delta=0,  # Adjust the hue of an RGB image by random factor
             horizontal_flip=False,  # Random left right flip,
             width_shift_range=0,  # Randomly translate the image horizontally
             height_shift_range=0):  # Randomly translate the image vertically 
  if resize is not None:
    # Resize both images
    label_img = tf.image.resize_images(label_img, resize)
    img = tf.image.resize_images(img, resize)
  
  if hue_delta:
    img = tf.image.random_hue(img, hue_delta)
  
  img, label_img = flip_img(horizontal_flip, img, label_img)
  img, label_img = shift_img(img, label_img, width_shift_range, height_shift_range)
  label_img = tf.to_float(label_img) * scale
  img = tf.to_float(img) * scale 
  return img, label_img
def get_baseline_dataset(filenames, 
                         labels,
                         preproc_fn=functools.partial(_augment),
                         threads=5, 
                         batch_size=batch_size,
                         shuffle=True):           
  num_x = len(filenames)
  # Create a dataset from the filenames and labels
  dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
  # Map our preprocessing function to every element in our dataset, taking
  # advantage of multithreading
  dataset = dataset.map(_process_pathnames, num_parallel_calls=threads)
  if preproc_fn.keywords is not None and 'resize' not in preproc_fn.keywords:
    assert batch_size == 1, "Batching images must be of the same size"

  dataset = dataset.map(preproc_fn, num_parallel_calls=threads)
  
  if shuffle:
    dataset = dataset.shuffle(num_x)
  
  
  # It's necessary to repeat our data for all epochs 
  dataset = dataset.repeat().batch(batch_size)
  return dataset

 

訓練と検証データセットをセットアップする

画像増強を訓練セットに適用しますが検証データセットには適用しないことに注意してください。

tr_cfg = {
    'resize': [img_shape[0], img_shape[1]],
    'scale': 1 / 255.,
    'hue_delta': 0.1,
    'horizontal_flip': True,
    'width_shift_range': 0.1,
    'height_shift_range': 0.1
}
tr_preprocessing_fn = functools.partial(_augment, **tr_cfg)
val_cfg = {
    'resize': [img_shape[0], img_shape[1]],
    'scale': 1 / 255.,
}
val_preprocessing_fn = functools.partial(_augment, **val_cfg)
train_ds = get_baseline_dataset(x_train_filenames,
                                y_train_filenames,
                                preproc_fn=tr_preprocessing_fn,
                                batch_size=batch_size)
val_ds = get_baseline_dataset(x_val_filenames,
                              y_val_filenames, 
                              preproc_fn=val_preprocessing_fn,
                              batch_size=batch_size)

 

画像増強データパイプラインが期待された結果を生成するか見てみます

temp_ds = get_baseline_dataset(x_train_filenames, 
                               y_train_filenames,
                               preproc_fn=tr_preprocessing_fn,
                               batch_size=1,
                               shuffle=False)
# Let's examine some of these augmented images
data_aug_iter = temp_ds.make_one_shot_iterator()
next_element = data_aug_iter.get_next()
with tf.Session() as sess: 
  batch_of_imgs, label = sess.run(next_element)

  # Running next element in our graph will produce a batch of images
  plt.figure(figsize=(10, 10))
  img = batch_of_imgs[0]

  plt.subplot(1, 2, 1)
  plt.imshow(img)

  plt.subplot(1, 2, 2)
  plt.imshow(label[0, :, :, 0])
  plt.show()

 

モデルを構築する

U-Net モデルを構築します。U-net は特にセグメンテーション・タスクに良く、何故ならばそれは高解像度セグメンテーション・マスクを提供するために上手く位置特定できるからです。更に、それは小さいデータセットで上手く動作して overfitting に対して比較的堅牢です、画像内のパッチの数の観点から訓練データは訓練画像自身の数よりもはるかに巨大だからです。元のモデルと違い、ブロックの各々にバッチ正規化を追加します。

Unet はエンコーダ部とデコーダ部で構築されます。エンコーダ部は Conv, BatchNorm, そして MaxPool が続く Relu 演算の線形スタックから成ります。各 MaxPool は特徴マップの空間的解像度を 2 の factor で減じます。各ブロックの出力を追跡します、これらの高解像特徴マップをデコーダ部に供給しますので。デコーダ部は UpSampling2D, Conv, BatchNorm, と Relus から成ります。デコーダ側上で同じサイズの特徴マップを結合することに注意してください。最後に、各個別のピクセルのためのチャネルに沿って畳み込みを遂行する (kernel size of (1, 1)) 、最後の Conv 演算を追加します、これは最後のセグメンテーション・マスクをグレースケールで出力します。

 

Keras Functional API

Keras functional API は複数の入力/出力モデル、共有層, etc. を持つときに使用されます。それはパワフルな API で tensor を操作して結びついたデータストリームを持つ複雑なグラフを容易に構築することを可能にします。更にそれは層とモデルの両者を tensor 上で呼び出し可能にします。これらのヘルパー関数を構築します、モデルブロック演算を容易にに単純に調査させることを可能にします。

def conv_block(input_tensor, num_filters):
  encoder = layers.Conv2D(num_filters, (3, 3), padding='same')(input_tensor)
  encoder = layers.BatchNormalization()(encoder)
  encoder = layers.Activation('relu')(encoder)
  encoder = layers.Conv2D(num_filters, (3, 3), padding='same')(encoder)
  encoder = layers.BatchNormalization()(encoder)
  encoder = layers.Activation('relu')(encoder)
  return encoder

def encoder_block(input_tensor, num_filters):
  encoder = conv_block(input_tensor, num_filters)
  encoder_pool = layers.MaxPooling2D((2, 2), strides=(2, 2))(encoder)
  
  return encoder_pool, encoder

def decoder_block(input_tensor, concat_tensor, num_filters):
  decoder = layers.Conv2DTranspose(num_filters, (2, 2), strides=(2, 2), padding='same')(input_tensor)
  decoder = layers.concatenate([concat_tensor, decoder], axis=-1)
  decoder = layers.BatchNormalization()(decoder)
  decoder = layers.Activation('relu')(decoder)
  decoder = layers.Conv2D(num_filters, (3, 3), padding='same')(decoder)
  decoder = layers.BatchNormalization()(decoder)
  decoder = layers.Activation('relu')(decoder)
  decoder = layers.Conv2D(num_filters, (3, 3), padding='same')(decoder)
  decoder = layers.BatchNormalization()(decoder)
  decoder = layers.Activation('relu')(decoder)
  return decoder
inputs = layers.Input(shape=img_shape)
# 256

encoder0_pool, encoder0 = encoder_block(inputs, 32)
# 128

encoder1_pool, encoder1 = encoder_block(encoder0_pool, 64)
# 64

encoder2_pool, encoder2 = encoder_block(encoder1_pool, 128)
# 32

encoder3_pool, encoder3 = encoder_block(encoder2_pool, 256)
# 16

encoder4_pool, encoder4 = encoder_block(encoder3_pool, 512)
# 8

center = conv_block(encoder4_pool, 1024)
# center

decoder4 = decoder_block(center, encoder4, 512)
# 16

decoder3 = decoder_block(decoder4, encoder3, 256)
# 32

decoder2 = decoder_block(decoder3, encoder2, 128)
# 64

decoder1 = decoder_block(decoder2, encoder1, 64)
# 128

decoder0 = decoder_block(decoder1, encoder0, 32)
# 256

outputs = layers.Conv2D(1, (1, 1), activation='sigmoid')(decoder0)

 

モデルを定義する

functional API を使用して、モデルに関連する入力と出力を指定することによりモデルを定義しなければなりません。

model = models.Model(inputs=[inputs], outputs=[outputs])

 

カスタム・メトリックと損失関数を定義する

損失とメトリック関数の定義は Keras では単純です。与えられたサンプルに対する True ラベルと同じ与えられたサンプルに対する予測ラベルを取る関数を単純に定義します。

Dice 損失は overlap を計測するメトリックです。Dice 係数 (dice 損失) のための最適化についてのより多くの情報は ペーパー で見つかります、そこでそれが紹介されました。

ここでは dice 損失を使用します、何故ならば設計上それはクラス不均衡な問題でより良い遂行をするからです。そして、dice 係数と IoU メトリックの最大化は私達のセグメンテーション・タスクの実際の目的と目標です。交差エントロピーの使用はより代理的ですが、最大化するのはより簡単です。代わりに、目的を直接的に最大化します。

def dice_coeff(y_true, y_pred):
    smooth = 1.
    # Flatten
    y_true_f = tf.reshape(y_true, [-1])
    y_pred_f = tf.reshape(y_pred, [-1])
    intersection = tf.reduce_sum(y_true_f * y_pred_f)
    score = (2. * intersection + smooth) / (tf.reduce_sum(y_true_f) + tf.reduce_sum(y_pred_f) + smooth)
    return score
def dice_loss(y_true, y_pred):
    loss = 1 - dice_coeff(y_true, y_pred)
    return loss

ここで、バイナリ交差エントロピーと dice 損失を結合した特別な損失関数を使用します。これはより良い結果を経験的に得るためにこのコンペティションで競った人達に基づきます。パフォーマンスを測定するために貴方自身のカスタム損失を試してください (e.g. bce + log(dice_loss), only bce, etc.) !

def bce_dice_loss(y_true, y_pred):
    loss = losses.binary_crossentropy(y_true, y_pred) + dice_loss(y_true, y_pred)
    return loss

 

モデルをコンパイルする

最小化するためにカスタム損失関数を使用します。更に、訓練時にどのメトリックを追跡することを望むかを指定します。メトリックは、パラメータを調整するための訓練プロセスの間は実際には使用されず、しかし代わりに訓練プロセスのパフォーマンスを測定するために使用されることに注意してください。

model.compile(optimizer='adam', loss=bce_dice_loss, metrics=[dice_loss])

model.summary()

 

モデルを訓練する

tf.data でモデルを訓練することはモデルの fit 関数に訓練/検証データセット、ステップとエポック数を単純に提供することを伴います。

Model callback、ModelCheckpoint もまた含みます、これは各エポック後にモデルをディスクにセーブするものです。それが最高のパフォーマンスを示したモデルだけをセーブするようにそれを configure します。モデルのセーブは単なるモデルの重み以上を捕捉することに注意してください : デフォルトでは、それはモデル・アーキテクチャ、重み、そして optimizer の状態のような訓練プロセスについての情報をセーブします。

save_model_path = '/tmp/weights.hdf5'
cp = tf.keras.callbacks.ModelCheckpoint(filepath=save_model_path, monitor='val_dice_loss', save_best_only=True, verbose=1)

fit 関数呼び出しでモデル callback を指定することを忘れないでください。

history = model.fit(train_ds, 
                   steps_per_epoch=int(np.ceil(num_train_examples / float(batch_size))),
                   epochs=epochs,
                   validation_data=val_ds,
                   validation_steps=int(np.ceil(num_val_examples / float(batch_size))),
                   callbacks=[cp])

 

訓練プロセスを可視化する

dice = history.history['dice_loss']
val_dice = history.history['val_dice_loss']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(16, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, dice, label='Training Dice Loss')
plt.plot(epochs_range, val_dice, label='Validation Dice Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Dice Loss')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')

plt.show()

5 エポックだけでさえも、強いパフォーマンスを見ます。

 

実際のパフォーマンスを可視化する

検証セット上のパフォーマンスを可視化します。

実際の設定 (コンペティション、配備, etc.) では完全な画像解像度を持つテストセット上で評価します。

モデルをロードするためには 2 つのオプションを持ちます :

  1. モデル・アーキテクチャは既にメモリ内にあるので、単純に load_weights(save_model_path) を呼び出すことができます。
  2. モデルをスクラッチから (メモリ内のモデル・アーキテクチャを既に持つことなしに異なる設定で) ロードすることを望む場合、単純に次を呼び出します :
    model = models.load_model(save_model_path, custom_objects={'bce_dice_loss': bce_dice_loss, 'dice_loss': dice_loss})
    

    必要なカスタム・オブジェクト、損失とメトリックを指定します、これらはモデルを訓練するために使用しました。

# Alternatively, load the weights directly: model.load_weights(save_model_path)
model = models.load_model(save_model_path, custom_objects={'bce_dice_loss': bce_dice_loss,
                                                           'dice_loss': dice_loss})
# Let's visualize some of the outputs 
data_aug_iter = val_ds.make_one_shot_iterator()
next_element = data_aug_iter.get_next()

# Running next element in our graph will produce a batch of images
plt.figure(figsize=(10, 20))
for i in range(5):
  batch_of_imgs, label = tf.keras.backend.get_session().run(next_element)
  img = batch_of_imgs[0]
  predicted_label = model.predict(batch_of_imgs)[0]

  plt.subplot(5, 3, 3 * i + 1)
  plt.imshow(img)
  plt.title("Input image")
  
  plt.subplot(5, 3, 3 * i + 2)
  plt.imshow(label[0, :, :, 0])
  plt.title("Actual Mask")
  plt.subplot(5, 3, 3 * i + 3)
  plt.imshow(predicted_label[:, :, 0])
  plt.title("Predicted Mask")
plt.suptitle("Examples of Input Image, Label, and Prediction")
plt.show()
 

以上



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

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

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

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

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

 

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

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

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

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

 

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

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

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

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

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

sample output_1

 

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

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

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

 

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

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

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

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

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

  w = tf.shape(image)[1]

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

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

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

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

  return input_image, real_image

 

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

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

 

generator と discriminator モデルを書く

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

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

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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

 

損失関数と optimizer を定義する

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

  total_disc_loss = real_loss + generated_loss

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

  total_gen_loss = gan_loss + (LAMBDA * l1_loss)

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

 

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

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

 

訓練

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

 

生成画像

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

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

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

    for input_image, target in dataset:

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

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

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

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

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

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

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

 

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

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

 

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

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

以上



TensorFlow : Tutorials : 生成モデル : 画像キャプショニング with Attention

TensorFlow : Tutorials : 生成モデル : 画像キャプショニング with Attention (翻訳/解説)

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

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

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

 

画像キャプショニング with Attention

画像キャプショニングは画像のためのキャプション (訳注: 表題、短い説明文) を生成するタスクです。このような画像が与えられたとき :

Image Source, License: Public Domain

 
私達の目標は “a surfer riding on a wave” のようなキャプションを生成することです。ここでは、attention ベースのモデルを使用します。これは、(モデルが) キャプションを生成するとき画像のどのパートにモデルがフォーカスするかを見ることを可能にします。

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

コードは tf.keras と eager execution を使用します。

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

コードは TensorFlow バージョン >=1.9 を必要とします。

このサンプルでは、例として比較的小さい総量のデータ上で訓練しています。単一の P100 GPU 上で、このサンプルは訓練におよそ ~2 時間かかるでしょう。最初の 30,000 キャプション上で訓練します (シャッフルに依拠しておよそ ~20,000 画像上で訓練します、データセットの画像毎に複数のキャプションがあるからです)。

# Import TensorFlow and enable eager execution
# This code requires TensorFlow version >=1.9
import tensorflow as tf
tf.enable_eager_execution()

# We'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/'

 

オプションで、より高速な訓練のために訓練セットのサイズを制限する

このサンプルのために、30,000 キャプションのサブセットを選択し、これと対応する画像をモデルを訓練するために使用します。通例のように、より多くのデータを使用することを選択すれば、キャプショニング品質は改善します。

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

# storing the captions and the image name 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)

# shuffling the captions and image_names together
# setting a random state
train_captions, img_name_vector = shuffle(all_captions,
                                          all_img_name_vector,
                                          random_state=1)

# selecting 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)

 

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

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

最初に、画像を次により inceptionV3 が想定するフォーマットに変換する必要があります :

  • 画像を (299, 299) にリサイズします。
  • preprocess_input メソッドを使用してピクセルを -1 から 1 の範囲に置きます (InceptionV3 を訓練するために使用された画像のフォーマットに適合させるためです)。
def load_image(image_path):
    img = tf.read_file(image_path)
    img = tf.image.decode_jpeg(img, channels=3)
    img = tf.image.resize_images(img, (299, 299))
    img = tf.keras.applications.inception_v3.preprocess_input(img)
    return img, image_path

 

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

そのため、tf.keras モデルを作成します、そこでは出力層は InceptionV3 アーキテクチャの最後の畳み込み層です。

  • 各画像はネットワークを通して forward されて最後に得られるベクトルは辞書にストアされます (image_name –> feature_vector)。
  • 最後の畳み込み層を使用します、何故ならばこのサンプルで attention を使用しているからです。この層の出力の shape は 8x8x2048 です。
  • 訓練の間はこれを行なうことを回避しますので、ボトルネックにはなりません。
  • 総ての画像がネットワークを通された後、辞書を pickle 化してそれをディスクにセーブします。
mage_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)

 

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

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

パフォーマンスは更に多いコードの代償でより洗練されたキャッシング・ストラテジー (e.g., ディスク I/O へのランダムアクセスを減じるために画像をシャーディングする) で改善されるかもしれません。

これは GPU を持つ Colab でおよそ 10 分間かかります。進捗バーを見ることを望むのであれば、次を行なって: install tqdm (!pip install tqdm)、そしてこの行を変更することができます :

for img, path in image_dataset:

to:

for img, path in tqdm(image_dataset):.
# getting the unique images
encode_train = sorted(set(img_name_vector))

# feel free to change the batch_size according to your system configuration
image_dataset = tf.data.Dataset.from_tensor_slices(
                                encode_train).map(load_image).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())

 

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

  • 最初にキャプションをトークン化します (e.g., スペースで分割することにより)。これはデータの総ての一意な単語の語彙を与えます (e.g., “surfing”, “football”, etc)。
  • 次に、メモリを節約するために語彙サイズを top 5,000 単語に制限します。総ての他の単語をトークン “UNK” (for unknown) で置き替えます。
  • 最後に word –> index マッピング (and vice-versa) を作成します。
  • それから最長のものと同じ長さになるように総てのシークエンスをパッドします。
# This will find the maximum length of any caption in our dataset
def calc_max_length(tensor):
    return max(len(t) for t in tensor)
# The steps above is a general process of dealing with text processing

# choosing 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 = {key:value for key, value in tokenizer.word_index.items() if value <= top_k}
# putting  token in the word2idx dictionary
tokenizer.word_index[tokenizer.oov_token] = top_k + 1
tokenizer.word_index[''] = 0
# creating the tokenized vectors
train_seqs = tokenizer.texts_to_sequences(train_captions)
# creating a reverse mapping (index -> word)
index_word = {value:key for key, value in tokenizer.word_index.items()}
# padding each vector to the max_length of the captions
# if the max_length parameter is not provided, pad_sequences calculates that automatically
cap_vector = tf.keras.preprocessing.sequence.pad_sequences(train_seqs, padding='post')
# calculating the max_length 
# used to store the attention weights
max_length = calc_max_length(train_seqs)

 

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

# Create training and validation sets using 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)

 

画像とキャプションの準備ができました!次に、モデルを訓練するために 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)
# shape of the vector extracted from InceptionV3 is (64, 2048)
# these two variables represent that
features_shape = 2048
attention_features_shape = 64
# loading 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))

# using map to load the numpy files in parallel
# NOTE: Be sure to set num_parallel_calls to the number of CPU cores you have
# https://www.tensorflow.org/api_docs/python/tf/py_func
dataset = dataset.map(lambda item1, item2: tf.py_func(
          map_func, [item1, item2], [tf.float32, tf.int32]), num_parallel_calls=8)

# shuffling and batching
dataset = dataset.shuffle(BUFFER_SIZE)
# https://www.tensorflow.org/api_docs/python/tf/contrib/data/batch_and_drop_remainder
dataset = dataset.batch(BATCH_SIZE)
dataset = dataset.prefetch(1)

 

モデル

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

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

  • このサンプルでは、InceptionV3 のより低い畳み込み層から特徴を抽出します、これは shape (8, 8, 2048) のベクトルを与えます。
  • それを (64, 2048) の shape に押しつぶします。
  • それからこのベクトルは CNN エンコーダを通して渡されます (単一の完全結合層から成ります)。
  • RNN (ここでは GRU) が次の単語を予測するために画像を注視します。
def gru(units):
  # If you have a GPU, we recommend using the CuDNNGRU layer (it provides a 
  # significant speedup).
  if tf.test.is_gpu_available():
    return tf.keras.layers.CuDNNGRU(units, 
                                    return_sequences=True, 
                                    return_state=True, 
                                    recurrent_initializer='glorot_uniform')
  else:
    return tf.keras.layers.GRU(units, 
                               return_sequences=True, 
                               return_state=True, 
                               recurrent_activation='sigmoid', 
                               recurrent_initializer='glorot_uniform')
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)
    # we get 1 at the last axis because we 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 we 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 = gru(self.units)
    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.train.AdamOptimizer()

# We are masking the loss calculated for padding
def loss_function(real, pred):
    mask = 1 - np.equal(real, 0)
    loss_ = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=real, logits=pred) * mask
    return tf.reduce_mean(loss_)

 

訓練

  • それぞれの .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 = []
EPOCHS = 20

for epoch in range(EPOCHS):
    start = time.time()
    total_loss = 0
    
    for (batch, (img_tensor, target)) in enumerate(dataset):
        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]))
        
        variables = encoder.variables + decoder.variables
        
        gradients = tape.gradient(loss, variables) 
        
        optimizer.apply_gradients(zip(gradients, variables), tf.train.get_or_create_global_step())
        
        if batch % 100 == 0:
            print ('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1, 
                                                          batch, 
                                                          loss.numpy() / int(target.shape[1])))
    # storing the epoch end loss value to plot later
    loss_plot.append(total_loss / len(cap_vector))
    
    print ('Epoch {} Loss {:.6f}'.format(epoch + 1, 
                                         total_loss/len(cap_vector)))
    print ('Time taken for 1 epoch {} sec\n'.format(time.time() - start))
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.multinomial(tf.exp(predictions), num_samples=1)[0][0].numpy()
        result.append(index_word[predicted_id])

        if 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([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])

 

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

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

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)
 

以上



TensorFlow : Tutorials : 生成モデル : ニューラル機械翻訳 with Attention

TensorFlow : Tutorials : 生成モデル : ニューラル機械翻訳 with Attention (翻訳/解説)

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

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

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

 

ニューラル機械翻訳 with Attention

このノートブックは西英翻訳のための sequence to sequence (seq2seq) モデルを tf.keras と eager execution を使用して訓練します。これは 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

# Import TensorFlow >= 1.9 and enable eager execution
import tensorflow as tf

tf.enable_eager_execution()

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

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

print(tf.__version__)

 

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

私達は 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://download.tensorflow.org/data/spa-eng.zip', 
    extract=True)

path_to_file = os.path.dirname(path_to_zip)+"/spa-eng/spa.txt"
# 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
# 1. Remove the accents
# 2. Clean the sentences
# 3. Return word pairs in the format: [ENGLISH, SPANISH]
def create_dataset(path, num_examples):
    lines = 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 word_pairs
# This class creates a word -> index mapping (e.g,. "dad" -> 5) and vice-versa 
# (e.g., 5 -> "dad") for each language,
class LanguageIndex():
  def __init__(self, lang):
    self.lang = lang
    self.word2idx = {}
    self.idx2word = {}
    self.vocab = set()
    
    self.create_index()
    
  def create_index(self):
    for phrase in self.lang:
      self.vocab.update(phrase.split(' '))
    
    self.vocab = sorted(self.vocab)
    
    self.word2idx[''] = 0
    for index, word in enumerate(self.vocab):
      self.word2idx[word] = index + 1
    
    for word, index in self.word2idx.items():
      self.idx2word[index] = word
def max_length(tensor):
    return max(len(t) for t in tensor)


def load_dataset(path, num_examples):
    # creating cleaned input, output pairs
    pairs = create_dataset(path, num_examples)

    # index language using the class defined above    
    inp_lang = LanguageIndex(sp for en, sp in pairs)
    targ_lang = LanguageIndex(en for en, sp in pairs)
    
    # Vectorize the input and target languages
    
    # Spanish sentences
    input_tensor = [[inp_lang.word2idx[s] for s in sp.split(' ')] for en, sp in pairs]
    
    # English sentences
    target_tensor = [[targ_lang.word2idx[s] for s in en.split(' ')] for en, sp in pairs]
    
    # Calculate max_length of input and output tensor
    # Here, we'll set those to the longest sentence in the dataset
    max_length_inp, max_length_tar = max_length(input_tensor), max_length(target_tensor)
    
    # Padding the input and output tensor to the maximum length
    input_tensor = tf.keras.preprocessing.sequence.pad_sequences(input_tensor, 
                                                                 maxlen=max_length_inp,
                                                                 padding='post')
    
    target_tensor = tf.keras.preprocessing.sequence.pad_sequences(target_tensor, 
                                                                  maxlen=max_length_tar, 
                                                                  padding='post')
    
    return input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_tar

 

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

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

# Try experimenting with the size of that dataset
num_examples = 30000
input_tensor, target_tensor, inp_lang, targ_lang, max_length_inp, max_length_targ = load_dataset(path_to_file, num_examples)
# 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)

 

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

BUFFER_SIZE = len(input_tensor_train)
BATCH_SIZE = 64
embedding_dim = 256
units = 1024
vocab_inp_size = len(inp_lang.word2idx)
vocab_tar_size = len(targ_lang.word2idx)

dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train)).shuffle(BUFFER_SIZE)
dataset = dataset.apply(tf.contrib.data.batch_and_drop_remainder(BATCH_SIZE))

 

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

ここで、attention を持つエンコーダ-デコーダ・モデルを実装します、これについては TensorFlow Neural Machine Translation (seq2seq) チュートリアル で読むことができます。このサンプルは API のより新しいセットを使用しています。このノートブックは seq2seq チュートリアルからの attention 等式を実装しています。次のダイアグラムは 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 は最後の axis 上に適用されますがここではそれを 1st axis 上で適用することを望みます、何故ならばスコアの shape は (batch_size, max_length, hidden_size) だからです。Max_length は入力の長さです。各入力に重みを割り当てようとしていますので、softmax はその軸上で適用されるべきです。
  • context vector = sum(attention weights * EO, axis = 1)。上と同じ理由で axis として 1 を選択します。
  • embedding output = デコーダへの入力 X は埋め込み層を通されます。
  • merged vector = concat(embedding output, context vector)
  • そしてこのマージされたベクトルが GRU に与えられます。

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

def gru(units):
  # If you have a GPU, we recommend using CuDNNGRU(provides a 3x speedup than GRU)
  # the code automatically does that.
  if tf.test.is_gpu_available():
    return tf.keras.layers.CuDNNGRU(units, 
                                    return_sequences=True, 
                                    return_state=True, 
                                    recurrent_initializer='glorot_uniform')
  else:
    return tf.keras.layers.GRU(units, 
                               return_sequences=True, 
                               return_state=True, 
                               recurrent_activation='sigmoid', 
                               recurrent_initializer='glorot_uniform')
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 = gru(self.enc_units)
        
    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))
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 = gru(self.dec_units)
        self.fc = tf.keras.layers.Dense(vocab_size)
        
        # used for attention
        self.W1 = tf.keras.layers.Dense(self.dec_units)
        self.W2 = tf.keras.layers.Dense(self.dec_units)
        self.V = tf.keras.layers.Dense(1)
        
    def call(self, x, hidden, enc_output):
        # enc_output shape == (batch_size, max_length, hidden_size)
        
        # 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(hidden, 1)
        
        # score shape == (batch_size, max_length, hidden_size)
        score = tf.nn.tanh(self.W1(enc_output) + self.W2(hidden_with_time_axis))
        
        # attention_weights shape == (batch_size, max_length, 1)
        # we get 1 at the last axis because we 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 * enc_output
        context_vector = tf.reduce_sum(context_vector, axis=1)
        
        # 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 * max_length, hidden_size)
        output = tf.reshape(output, (-1, output.shape[2]))
        
        # output shape == (batch_size * max_length, vocab)
        x = self.fc(output)
        
        return x, state, attention_weights
        
    def initialize_hidden_state(self):
        return tf.zeros((self.batch_sz, self.dec_units))
encoder = Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)
decoder = Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE)

 

optimizer と損失関数を定義する

optimizer = tf.train.AdamOptimizer()


def loss_function(real, pred):
  mask = 1 - np.equal(real, 0)
  loss_ = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=real, logits=pred) * mask
  return tf.reduce_mean(loss_)

 

訓練

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

for epoch in range(EPOCHS):
    start = time.time()
    
    hidden = encoder.initialize_hidden_state()
    total_loss = 0
    
    for (batch, (inp, targ)) in enumerate(dataset):
        loss = 0
        
        with tf.GradientTape() as tape:
            enc_output, enc_hidden = encoder(inp, hidden)
            
            dec_hidden = enc_hidden
            
            dec_input = tf.expand_dims([targ_lang.word2idx['']] * 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)
        
        total_loss += (loss / int(targ.shape[1]))
        
        variables = encoder.variables + decoder.variables
        
        gradients = tape.gradient(loss, variables)
      
        optimizer.apply_gradients(zip(gradients, variables), tf.train.get_or_create_global_step())

        if batch % 100 == 0:
            print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1,
                                                         batch,
                                                         loss.numpy() / int(targ.shape[1])))
    
    print('Epoch {} Loss {:.4f}'.format(epoch + 1,
                                        total_loss/len(input_tensor)))
    print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

 

翻訳する

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

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

def evaluate(sentence, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ):
    attention_plot = np.zeros((max_length_targ, max_length_inp))
    
    sentence = preprocess_sentence(sentence)

    inputs = [inp_lang.word2idx[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.word2idx['']], 0)

    for t in range(max_length_targ):
        predictions, dec_hidden, attention_weights = decoder(dec_input, dec_hidden, enc_out)
        
        # storing the attention weigths to plot later on
        attention_weights = tf.reshape(attention_weights, (-1, ))
        attention_plot[t] = attention_weights.numpy()

        predicted_id = tf.multinomial(tf.exp(predictions), num_samples=1)[0][0].numpy()

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

        if targ_lang.idx2word[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, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ):
    result, sentence, attention_plot = evaluate(sentence, encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ)
        
    print('Input: {}'.format(sentence))
    print('Predicted translation: {}'.format(result))
    
    attention_plot = attention_plot[:len(result.split(' ')), :len(sentence.split(' '))]
    plot_attention(attention_plot, sentence.split(' '), result.split(' '))
translate('hace mucho frio aqui.', encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ)
translate('esta es mi vida.', encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ)
translate('¿todavia estan en casa?', encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ)
# wrong translation
translate('trata de averiguarlo.', encoder, decoder, inp_lang, targ_lang, max_length_inp, max_length_targ)

 

Next steps

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

以上



TensorFlow : Tutorials : 生成モデル : RNN でテキスト生成

TensorFlow : Tutorials : 生成モデル : RNN でテキスト生成 (翻訳/解説)

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

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

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

 

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

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

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

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

 

RNN を使用するテキスト生成

このノートブックは tf.keras と eager execution を使用して RNN でテキストをどのように生成するかを示します。もし良ければ、より少ないコードを使用して同様の モデル を書くことができます。ここでは、より低いレベルでの実装を示します、これは Neural Machine Translation with Attention のようなより深いサンプルに取り組む前に事前作業として理解するために有用です。

このノートブックは end-to-end なサンプルです。それを実行するとき、Shakespeare の著作のデータセットをダウンロードします。Andrej Karpathy の優れた The Unreasonable Effectiveness of Recurrent Neural Networks から借りた、戯曲のコレクションを使用します。このノートブックはモデルを訓練し、そしてサンプル出力を生成するためにそれを使用します。

下のデフォルト設定で 30 エポックの間の単一層 GRU の訓練後の (string=’w’ で開始した) 出力がここにあります :

were to the death of him
And nothing of the field in the view of hell,
When I said, banish him, I will not burn thee that would live.

HENRY BOLINGBROKE:
My gracious uncle–

DUKE OF YORK:
As much disgraced to the court, the gods them speak,
And now in peace himself excuse thee in the world.

HORTENSIO:
Madam, ‘tis not the cause of the counterfeit of the earth,
And leave me to the sun that set them on the earth
And leave the world and are revenged for thee.

GLOUCESTER:
I would they were talking with the very name of means
To make a puppet of a guest, and therefore, good Grumio,
Nor arm’d to prison, o’ the clouds, of the whole field,
With the admire
With the feeding of thy chair, and we have heard it so,
I thank you, sir, he is a visor friendship with your silly your bed.

SAMPSON:
I do desire to live, I pray: some stand of the minds, make thee remedies
With the enemies of my soul.

MENENIUS:
I’ll keep the cause of my mistress.

POLIXENES:
My brother Marcius!

Second Servant:
Will’t ple

 
もちろん、センテンスの幾つかが文法的に正しい一方で、多くは意味を成しません。しかし、以下を考えてください :

  • 私達のモデルは文字ベースです (訓練を始めたとき、それは妥当な英語単語をどのようにスペルするか、あるいは単語がテキストのユニットであることさえまだ知りません)。
  • 出力の構造は演劇に類似しています (ブロックは話者名で始まり、元のテキストと同様に総て大文字です)。センテンスは一般にピリオドで終わります。テキストを遠くから見れば (あるいは個々の単語を近づいて読まなければ)、それは (演劇の) 脚本からの抜粋であるかのように見えます。

次のステップとして、異なるデータセット上でモデルを訓練する実験ができます – どのような巨大なテキストファイル (ASCII) でもかまいません、そしてその変更を行なうために下のコードの単一の行を修正できます。Have fun!

 

unidecode ライブラリをインストールする

unicode を ASCII に変換するための役立つライブラリ。

!pip install unidecode

 

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

# Import TensorFlow >= 1.9 and enable eager execution
import tensorflow as tf

# Note: Once you enable eager execution, it cannot be disabled. 
tf.enable_eager_execution()

import numpy as np
import re
import random
import unidecode
import time

 

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

このサンプルでは、shakespeare データセット を使用します。貴方の好む任意の他のデータセットも使用できます。

path_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/yashkatariya/shakespeare.txt')

 

データセットを読む

text = unidecode.unidecode(open(path_to_file).read())
# length of text is the number of characters in it
print (len(text))

文字からそれらのインデックス、そしてその逆へとマップする辞書を作成します、これは入力をベクトル化するために使用されます。

# unique contains all the unique characters in the file
unique = sorted(set(text))

# creating a mapping from unique characters to indices
char2idx = {u:i for i, u in enumerate(unique)}
idx2char = {i:u for i, u in enumerate(unique)}
# setting the maximum length sentence we want for a single input in characters
max_length = 100

# length of the vocabulary in chars
vocab_size = len(unique)

# the embedding dimension 
embedding_dim = 256

# number of RNN (here GRU) units
units = 1024

# batch size 
BATCH_SIZE = 64

# buffer size to shuffle our dataset
BUFFER_SIZE = 10000

 

入力と出力 tensor を作成する

入力とターゲット・テキストをベクトル化します、何故ならば私達のモデルは文字列を理解できないからです、数字だけです。

しかしまず、入力と出力ベクトルを作成する必要があります。上で設定した max_length を思い出してください、ここではそれを使用します。入力の max_length チャンクを作成しています、そこでは各入力ベクトルはそのチャンクの最後を除く総ての文字でそしてターゲット・ベクトルはそのチャンクの最初を除く総ての文字です。

例えば、string = ‘tensorflow’ と max_length is 9 を考えます。

すると、input = ‘tensorflo’ そして output = ‘ensorflow’ です。

ベクトル作成後、上で作成した char2idx 辞書を使用して各文字を数字に変換します。

input_text = []
target_text = []

for f in range(0, len(text)-max_length, max_length):
    inps = text[f:f+max_length]
    targ = text[f+1:f+1+max_length]

    input_text.append([char2idx[i] for i in inps])
    target_text.append([char2idx[t] for t in targ])
    
print (np.array(input_text).shape)
print (np.array(target_text).shape)

 

tf.data を使用してバッチを作成してそれらをシャッフルする

dataset = tf.data.Dataset.from_tensor_slices((input_text, target_text)).shuffle(BUFFER_SIZE)
dataset = dataset.apply(tf.contrib.data.batch_and_drop_remainder(BATCH_SIZE))

 

モデルを作成する

Model Subclassing API を使用します、これはモデルを作成してそれを好きなように変更するための完全な柔軟性を与えます。モデルを定義するために 3 つの層を使用します。

  • 埋め込み層
  • GRU 層 (ここで LSTM 層を使用することができます)
  • 完全結合層
class Model(tf.keras.Model):
  def __init__(self, vocab_size, embedding_dim, units, batch_size):
    super(Model, self).__init__()
    self.units = units
    self.batch_sz = batch_size

    self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)

    if tf.test.is_gpu_available():
      self.gru = tf.keras.layers.CuDNNGRU(self.units, 
                                          return_sequences=True, 
                                          return_state=True, 
                                          recurrent_initializer='glorot_uniform')
    else:
      self.gru = tf.keras.layers.GRU(self.units, 
                                     return_sequences=True, 
                                     return_state=True, 
                                     recurrent_activation='sigmoid', 
                                     recurrent_initializer='glorot_uniform')

    self.fc = tf.keras.layers.Dense(vocab_size)
        
  def call(self, x, hidden):
    x = self.embedding(x)

    # output shape == (batch_size, max_length, hidden_size) 
    # states shape == (batch_size, hidden_size)

    # states variable to preserve the state of the model
    # this will be used to pass at every step to the model while training
    output, states = self.gru(x, initial_state=hidden)


    # reshaping the output so that we can pass it to the Dense layer
    # after reshaping the shape is (batch_size * max_length, hidden_size)
    output = tf.reshape(output, (-1, output.shape[2]))

    # The dense layer will output predictions for every time_steps(max_length)
    # output shape after the dense layer == (max_length * batch_size, vocab_size)
    x = self.fc(output)

    return x, states

 

モデルを呼び出して optimizer と損失関数を設定する

model = Model(vocab_size, embedding_dim, units, BATCH_SIZE)
optimizer = tf.train.AdamOptimizer()

# using sparse_softmax_cross_entropy so that we don't have to create one-hot vectors
def loss_function(real, preds):
    return tf.losses.sparse_softmax_cross_entropy(labels=real, logits=preds)

 

モデルを訓練する

ここで GradientTape() の助けを借りてカスタム訓練ループを使用します。

  • モデルの隠れ状態をゼロと shape == (batch_size, number of rnn units) で初期化します。モデルを作成する間に定義された関数を呼び出してこれを行ないます。
  • 次に、データセットに渡り (バッチ毎に) 反復してその入力に関連して予測と隠れ状態を計算します。
  • ここで発生する多くの興味深いことがあります。
    • モデルは (0 で初期化された) 隠れ状態を得ます、それを H2 と呼びそして入力の最初のバッチ、それを I0 と呼びましょう。
    • それからモデルは予測 P1 と H1 を返します。
    • 入力の次のバッチのために、モデルは I1 と H1 を受け取ります。
    • ここで興味深いことはモデルに I1 とともに H1 を渡すことです、これがモデルがどのように学習するかです。バッチからバッチへと学習されたコンテキストはこの隠れ状態に含まれます。
    • データセットが使い尽くされるまでこれを行ない続けます、そしてそれから新しいエポックを開始してこれを繰り返します。
  • 予測を計算した後、上で定義された損失関数を使用して損失を計算します。それからモデル変数 (入力) に関する損失の勾配を計算します。
  • 最後に、apply_gradients 関数を使用して optimizer の助けを借りてその方向にステップを踏みます。

Note:- このノートブックを Tesla K80 GPU を持つ Colab で実行する場合、それはエポック毎に 23 秒かかります。

# Training step

EPOCHS = 30

for epoch in range(EPOCHS):
    start = time.time()
    
    # initializing the hidden state at the start of every epoch
    hidden = model.reset_states()
    
    for (batch, (inp, target)) in enumerate(dataset):
          with tf.GradientTape() as tape:
              # feeding the hidden state back into the model
              # This is the interesting step
              predictions, hidden = model(inp, hidden)
              
              # reshaping the target because that's how the 
              # loss function expects it
              target = tf.reshape(target, (-1,))
              loss = loss_function(target, predictions)
              
          grads = tape.gradient(loss, model.variables)
          optimizer.apply_gradients(zip(grads, model.variables), global_step=tf.train.get_or_create_global_step())

          if batch % 100 == 0:
              print ('Epoch {} Batch {} Loss {:.4f}'.format(epoch+1,
                                                            batch,
                                                            loss))
    
    print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss))
    print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

 

訓練されたモデルを使用して予測する

下のコードブロックはテキストを生成するために使用されます。

  • 開始文字列を選択して隠れ状態を初期化して生成することを望む文字数を設定します。
  • start_string と隠れ除隊を使用して予測を得ます。
  • それから予測された単語のインデックスを計算するために多項分布を使用します。この予測された単語をモデルへの次の入力として使用します。
  • モデルから返される隠れ状態はモデルに供給し返されます、そのためそれは今では単なる一つの単語ではなくより多くのコンテキストを持ちます。次の単語を予測した後、変更された隠れ状態は再度モデルに供給し返され、これはそれが前に予測された単語からのより多くのコンテキストを取得したときにどのように学習するかです。
  • 予測を見れば、モデルはいつ大文字にして、パラグラフを作成するかを知りそしてテキストは著作の shakespeare スタイルに従います。これは非常に素晴らしいです!
# Evaluation step(generating text using the model learned)

# number of characters to generate
num_generate = 1000

# You can change the start string to experiment
start_string = 'Q'
# 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

# hidden state shape == (batch_size, number of rnn units); here batch size == 1
hidden = [tf.zeros((1, units))]
for i in range(num_generate):
    predictions, hidden = model(input_eval, hidden)

    # using a multinomial distribution to predict the word returned by the model
    predictions = predictions / temperature
    predicted_id = tf.multinomial(tf.exp(predictions), num_samples=1)[0][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 += idx2char[predicted_id]

print (start_string + text_generated)

 

Next steps

  • 開始文字列を異なる文字に、あるいはセンテンスの開始を変更する、
  • 異なる (データセット) 上、あるいは異なるパラメータによる訓練で実験する。例えば、Gutenberg プロジェクト は本の巨大なコレクションを含みます。
  • 温度パラメータ (= temperature parameter) で実験する。
  • もう一つの RNN 層を追加する。
 

以上



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

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

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

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

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

 

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

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

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

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

# to generate gifs
!pip install imageio

 

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

from __future__ import absolute_import, division, print_function

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

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

 

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

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

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

 

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

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

 

generator と discriminator モデルを書く

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

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

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

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

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

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

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

 

損失関数と optimizer を定義する

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

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

    total_loss = real_loss + generated_loss

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

 

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

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

 

訓練

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

 

画像を生成する

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

# keeping the random vector constant for generation (prediction) so
# it will be easier to see the improvement of the gan.
random_vector_for_generation = tf.random_normal([num_examples_to_generate,
                                                 noise_dim])
def generate_and_save_images(model, epoch, test_input):
  # make sure the training parameter is set to False because we
  # don't want to train the batchnorm layer when doing inference.
  predictions = model(test_input, training=False)

  fig = plt.figure(figsize=(4,4))
  
  for i in range(predictions.shape[0]):
      plt.subplot(4, 4, i+1)
      plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')
      plt.axis('off')
        
  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))
  plt.show()
def train(dataset, epochs, noise_dim):  
  for epoch in range(epochs):
    start = time.time()
    
    for images in dataset:
      # generating noise from a uniform distribution
      noise = tf.random_normal([BATCH_SIZE, noise_dim])
      
      with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
        generated_images = generator(noise, training=True)
      
        real_output = discriminator(images, training=True)
        generated_output = discriminator(generated_images, training=True)
        
        gen_loss = generator_loss(generated_output)
        disc_loss = discriminator_loss(real_output, generated_output)
        
      gradients_of_generator = gen_tape.gradient(gen_loss, generator.variables)
      gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.variables)
      
      generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.variables))
      discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.variables))

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

 

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

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

 

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

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

 

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

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

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

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

以上



TensorFlow : Tutorials : Keras : モデルをセーブしてリストアする

TensorFlow : Tutorials : Keras : モデルをセーブしてリストアする (翻訳/解説)

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

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

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

 

モデルの進捗は訓練の間に — そして後に — セーブすることが可能です。これは、モデルが (訓練を) 中止したところから再開できて長い訓練時間を回避できることを意味します。セービングはまた貴方がモデルを共有して他の人は貴方のワークを再作成できることもまた意味します。研究モデルとテクニックを公開するとき、多くの機械学習実践者は以下を共有します :

  • モデルを作成するためのコード、そして
  • モデルのために訓練された重み、またはパラメータ

このデータの共有は他の人たちがモデルがどのように動作するかを理解して新しいデータで彼ら自身がそれを試す手助けとなります。

 

セットアップ

インストールとインポート

TensorFlow と依存関係をインストールしてインポートします :

!pip install -q h5py pyyaml 
You are using pip version 10.0.1, however version 18.0 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
from __future__ import absolute_import, division, print_function

import os

import tensorflow as tf
from tensorflow import keras

tf.__version__
'1.9.0'

 

サンプル・データセットを取得する

重みのセーブを示すために MNIST データセットを使用してモデルを訓練します。これらのデモの実行をスピードアップするために、最初の 1000 サンプルだけを使用します :

(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()

train_labels = train_labels[:1000]
test_labels = test_labels[:1000]

train_images = train_images[:1000].reshape(-1, 28 * 28) / 255.0
test_images = test_images[:1000].reshape(-1, 28 * 28) / 255.0
Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
11493376/11490434 [==============================] - 2s 0us/step

 

モデルを定義する

セーブと重みのロードを示すために使用する単純なモデルを構築しましょう。

# Returns a short sequential model
def create_model():
  model = tf.keras.models.Sequential([
    keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10, activation=tf.nn.softmax)
  ])
  
  model.compile(optimizer=tf.keras.optimizers.Adam(), 
                loss=tf.keras.losses.sparse_categorical_crossentropy,
                metrics=['accuracy'])
  
  return model


# Create a basic model instance
model = create_model()
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 512)               401920    
_________________________________________________________________
dropout (Dropout)            (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

 

訓練の間にチェックポイントをセーブする

主要なユースケースは訓練の間と最後にチェックポイントを自動的にセーブすることです。このように訓練されたモデルをそれを再訓練しなければならないことなく、あるいは — 訓練プロセスが中断された場合には — 中止したところから訓練を選択して使用することができます。

tf.keras.callbacks.ModelCheckpoint はこのタスクを遂行する callback です。この callback はチェックポイントを構成するために 2, 3 の引数を取ります。

 

Checkpoint callback 使用方法

モデルを訓練してそれを ModelCheckpoint チェックポイントに渡します :

checkpoint_path = "training_1/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# Create checkpoint callback
cp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path, 
                                                 save_weights_only=True,
                                                 verbose=1)

model = create_model()

model.fit(train_images, train_labels,  epochs = 10, 
          validation_data = (test_images,test_labels),
          callbacks = [cp_callback])  # pass callback to training
Train on 1000 samples, validate on 1000 samples
Epoch 1/10
1000/1000 [==============================] - 0s 342us/step - loss: 1.1603 - acc: 0.6670 - val_loss: 0.6827 - val_acc: 0.7880

Epoch 00001: saving model to training_1/cp.ckpt
Epoch 2/10
1000/1000 [==============================] - 0s 129us/step - loss: 0.4071 - acc: 0.8860 - val_loss: 0.5841 - val_acc: 0.8110

Epoch 00002: saving model to training_1/cp.ckpt
Epoch 3/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.2796 - acc: 0.9350 - val_loss: 0.4610 - val_acc: 0.8520

Epoch 00003: saving model to training_1/cp.ckpt
Epoch 4/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.2025 - acc: 0.9570 - val_loss: 0.4324 - val_acc: 0.8610

Epoch 00004: saving model to training_1/cp.ckpt
Epoch 5/10
1000/1000 [==============================] - 0s 117us/step - loss: 0.1489 - acc: 0.9690 - val_loss: 0.4290 - val_acc: 0.8620

Epoch 00005: saving model to training_1/cp.ckpt
Epoch 6/10
1000/1000 [==============================] - 0s 127us/step - loss: 0.1194 - acc: 0.9780 - val_loss: 0.4143 - val_acc: 0.8700

Epoch 00006: saving model to training_1/cp.ckpt
Epoch 7/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.0845 - acc: 0.9860 - val_loss: 0.4208 - val_acc: 0.8670

Epoch 00007: saving model to training_1/cp.ckpt
Epoch 8/10
1000/1000 [==============================] - 0s 118us/step - loss: 0.0648 - acc: 0.9910 - val_loss: 0.4078 - val_acc: 0.8680

Epoch 00008: saving model to training_1/cp.ckpt
Epoch 9/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.0531 - acc: 0.9970 - val_loss: 0.4184 - val_acc: 0.8670

Epoch 00009: saving model to training_1/cp.ckpt
Epoch 10/10
1000/1000 [==============================] - 0s 121us/step - loss: 0.0391 - acc: 0.9960 - val_loss: 0.4185 - val_acc: 0.8640

Epoch 00010: saving model to training_1/cp.ckpt

 
これは、各エポックの最後に更新される TensorFlow チェックポイント・ファイルの単一のコレクションを作成します :

!ls {checkpoint_dir}
checkpoint  cp.ckpt.data-00000-of-00001  cp.ckpt.index

 
新しい、未訓練のモデルを作成しましょう。重みだけからモデルをリストアするときは、元のモデルと同じアーキテクチャを持つモデルを持たなければなりません。それは同じモデル・アーキテクチャですから、それがモデルの異なるインスタンスであるにもかかわらず重みを共有することができます。

さて未使用の、未訓練のモデルを再構築して、それをテストセット上で評価しましょう。未訓練モデルは偶然レベル (~10% 精度) で遂行します :

model = create_model()

loss, acc = model.evaluate(test_images, test_labels)
print("Untrained model, accuracy: {:5.2f}%".format(100*acc))
1000/1000 [==============================] - 0s 105us/step
Untrained model, accuracy:  7.20%

それからチェックポイントから重みをロードして、再評価します :

model.load_weights(checkpoint_path)
loss,acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
1000/1000 [==============================] - 0s 24us/step
Restored model, accuracy: 86.40%

 

Checkpoint callback オプション

callback は結果としてのチェックポイントに一意の名前を与えて、チェックポイントする頻度を調整するために幾つかのオプションを提供します。

新しいモデルを訓練して、一意に名前付けられたチェックポイントを 5 エポック毎に一度セーブします :

# include the epoch in the file name. (uses `str.format`)
checkpoint_path = "training_2/cp-{epoch:04d}.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(
    checkpoint_path, verbose=1, save_weights_only=True,
    # Save weights, every 5-epochs.
    period=5)

model = create_model()
model.fit(train_images, train_labels,
          epochs = 50, callbacks = [cp_callback],
          validation_data = (test_images,test_labels),
          verbose=0)
Epoch 00005: saving model to training_2/cp-0005.ckpt

Epoch 00010: saving model to training_2/cp-0010.ckpt

Epoch 00015: saving model to training_2/cp-0015.ckpt

Epoch 00020: saving model to training_2/cp-0020.ckpt

Epoch 00025: saving model to training_2/cp-0025.ckpt

Epoch 00030: saving model to training_2/cp-0030.ckpt

Epoch 00035: saving model to training_2/cp-0035.ckpt

Epoch 00040: saving model to training_2/cp-0040.ckpt

Epoch 00045: saving model to training_2/cp-0045.ckpt

Epoch 00050: saving model to training_2/cp-0050.ckpt

 
今、結果としてのチェックポイントを見てみましょう (変更日時によりソート) :

import pathlib

# Sort the checkpoints by modification time.
checkpoints = pathlib.Path(checkpoint_dir).glob("*.index")
checkpoints = sorted(checkpoints, key=lambda cp:cp.stat().st_mtime)
checkpoints = [cp.with_suffix('') for cp in checkpoints]
latest = str(checkpoints[-1])
checkpoints
[PosixPath('training_2/cp-0030.ckpt'),
 PosixPath('training_2/cp-0035.ckpt'),
 PosixPath('training_2/cp-0040.ckpt'),
 PosixPath('training_2/cp-0045.ckpt'),
 PosixPath('training_2/cp-0050.ckpt')]

 

Note: デフォルトの tensorflow フォーマットは 5 つの直近のチェックポイントだけをセーブします。

テストするためには、モデルをリセットして最新のチェックポイントをロードします :

model = create_model()
model.load_weights(latest)
loss, acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
1000/1000 [==============================] - 0s 82us/step
Restored model, accuracy: 87.80%

 

これらのファイルは何でしょう?

上のコードは重みを (訓練された重みだけをバイナリ形式で含む) チェックポイント形式ファイルのコレクションにストアします。チェックポイントは次を含みます : * モデルの重みを含む一つまたはそれ異常のシャード。* どの重みがどのシャードにストアされているかを指し示すインデックスファイル。

単一のマシン上でモデルを訓練しているだけであれば、サフィックス: .data-00000-of-00001 を持つ一つのシャードを持つでしょう。

 

重みを手動でセーブする

上で重みをどのようにモデルにロードするかを見ました。

手動で重みをセーブするのは同様に単純で、Model.save_weights メソッドを使用します。

# Save the weights
model.save_weights('./checkpoints/my_checkpoint')

# Restore the weights
model = create_model()
model.load_weights('./checkpoints/my_checkpoint')

loss,acc = model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
1000/1000 [==============================] - 0s 59us/step
Restored model, accuracy: 87.80%

 

モデル全体をセーブする

モデル全体を、重み値、モデルの構成 (= configuration)、そして optimizer の構成さえも含むファイルにセーブできます。これは元のコードへのアクセスなしにモデルをチェックポイントして後で訓練を — 正確に同じ状態から — 再開することを可能にします。

Keras の完全に機能するモデルのセーブは非常に有用です — それらを TensorFlow.js にロードしてから web ブラウザでそれらを訓練して実行することができます。

Keras は標準 HDF5 を使用した基本的なセーブ・フォーマットを提供します。私達の目的のためには、セーブされたモデルは単一のバイナリ・ブロブとして扱うことができます。

model = create_model()

model.fit(train_images, train_labels, epochs=5)

# Save entire model to a HDF5 file
model.save('my_model.h5')
Epoch 1/5
1000/1000 [==============================] - 0s 317us/step - loss: 1.1730 - acc: 0.6640
Epoch 2/5
1000/1000 [==============================] - 0s 109us/step - loss: 0.4257 - acc: 0.8790
Epoch 3/5
1000/1000 [==============================] - 0s 106us/step - loss: 0.2889 - acc: 0.9240
Epoch 4/5
1000/1000 [==============================] - 0s 104us/step - loss: 0.2171 - acc: 0.9390
Epoch 5/5
1000/1000 [==============================] - 0s 106us/step - loss: 0.1615 - acc: 0.9670

さてそのファイルからモデルを再作成します :

# Recreate the exact same model, including weights and optimizer.
new_model = keras.models.load_model('my_model.h5')
new_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_12 (Dense)             (None, 512)               401920    
_________________________________________________________________
dropout_6 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_13 (Dense)             (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

その精度を確認します :

loss, acc = new_model.evaluate(test_images, test_labels)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
1000/1000 [==============================] - 0s 78us/step
Restored model, accuracy: 86.60%

このテクニックは総てをセーブします :

  • 重み値。
  • モデルの構成 (= configuration) (アーキテクチャ)
  • optimizer 構成

Keras はアーキテクチャを調べてモデルをセーブします。現在、TensorFlow optimizer (from tf.train) をセーブすることはできません。それらを使用するときはモデルをロードした後で再コンパイルする必要があり optimizer の状態を解き放つでしょう。

 

以上



TensorFlow : Tutorials : Keras : overfitting と underfitting を調査する

TensorFlow : Tutorials : Keras : overfitting と underfitting を調査する (翻訳/解説)

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

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

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

 

いつものように、このサンプルのコードは tf.keras API を使用します。

前の例の両者 — 映画レビューの分類、そして住居価格の予測 — では、検証データ上のモデルの精度が幾つかのエポックの訓練後にピークになり、それから減少を開始することを見ました。

換言すれば、モデルは訓練データに overfit しています。overfitting にどのように対処するかを学ぶことは重要です。訓練セット上で高い精度を獲得することはしばしば可能ですが、本当に望むことはテストデータ (あるいは前にまだ見ていないデータ) に上手く一般化されたモデルを開発することです。

overfitting の反対は underfitting です。underfitting は テストデータ上でまだ改善の余地があるときに発生します。これは多くの理由で起こります : モデルが十分にパワフルでない場合、過剰に正則化されている、あるいは単に十分に長く訓練されていない場合。これはネットワークが訓練データ内の関連するパターンを学習していないことを意味しています。

けれども長すぎる訓練をする場合、モデルは overfit し始めて訓練データからテストデータに一般化されないパターンを学習するでしょう。上手くバランスを取る必要があります。どのように適切な数のエポックの間訓練するかを理解することは下で探究するように有用なスキルです。

overfitting を回避するためには、最善の解法はより多くの訓練データを使用することです。より多くのデータ上で訓練されたモデルは自然により良く一般化されます。それが最早可能ではないとき、次善の解法は正則化のようなテクニックを使用することです。これらはモデルがストアできる情報の量とタイプに制約を課します。ネットワークがパターンの小さい数を記憶するだけの余裕がある場合、最適化プロセスはそれに最も目立つパターンに注目するように強制します、これは上手く一般化するより良い可能性を持ちます。

このノートブックでは、2 つの一般的な正則化テクニックを探究します — 重み正則化と dropout です — そしてそれらを使用して IMDB 映画レビュー分類ノートブックを改良します。

import tensorflow as tf
from tensorflow import keras

import numpy as np
import matplotlib.pyplot as plt

print(tf.__version__)
1.9.0

 

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

前のノートブックのように埋め込みを使用するのではなく、ここではセンテンスを multi-hot エンコードします。このモデルは訓練セットに迅速に overfit します。それは overfitting が発生したとき、それとどのように戦うかを示すために使用されます。

リストの multi-hot エンコーディングはそれらを 0 と 1 のベクトルに変えることを意味します。具体的には、これは例えばシークエンス [3, 5] を (1 となる) インデックス 3 と 5 を除いて総て 0 の 10,000 次元ベクトルに変えることを意味します。

NUM_WORDS = 10000

(train_data, train_labels), (test_data, test_labels) = keras.datasets.imdb.load_data(num_words=NUM_WORDS)

def multi_hot_sequences(sequences, dimension):
    # Create an all-zero matrix of shape (len(sequences), dimension)
    results = np.zeros((len(sequences), dimension))
    for i, word_indices in enumerate(sequences):
        results[i, word_indices] = 1.0  # set specific indices of results[i] to 1s
    return results


train_data = multi_hot_sequences(train_data, dimension=NUM_WORDS)
test_data = multi_hot_sequences(test_data, dimension=NUM_WORDS)
Downloading data from https://s3.amazonaws.com/text-datasets/imdb.npz
17465344/17464789 [==============================] - 2s 0us/step

結果としての multi-hot ベクトルの一つを見てみましょう。単語インデックスは頻度でソートされますので、プロットで見て取れるように、インデックス 0 の近くにはより多くの 1-値があることが予想されます :

plt.plot(train_data[0])
[]

 

overfitting を示す

overfitting を回避する最も単純な方法はモデルのサイズ, i.e. モデルの学習可能なパラメータの数 (これは層数と層毎のユニット数により決定されます) を減じることです。深層学習では、モデルの学習可能なパラメータ数はしばしばモデルの “capacity” として参照されます。直感的には、より多くのパラメータを持つモデルはより多くの “記憶 capacity” を持ちそしてそれ故に訓練サンプルとターゲットの間の完全な辞書ライクなマッピング、どのような一般化パワーもないマッピングを簡単に学習可能です、しかしこれは以前に見ていないデータ上で予測を行なうときに役に立ちません。

これを常に念頭に置いてください : 深層学習モデルは訓練データに fit するには良い傾向がありますが、実際の挑戦は一般化であり、fitting ではありません。

その一方で、ネットワークが制限された記憶リソースを持つ場合には、マッピングを簡単には学習することができないでしょう。損失を最小化するためには、より予測力を持つ圧縮された (= compressed) 表現を学習しなければなりません。同時に、モデルを小さくし過ぎれば、それは訓練データに fit することが困難になるでしょう。これは “too much capacity” と “not enough capacity” の間のバランスです。

不幸なことに、モデルの正しいサイズやアーキテクチャ (層の数の視点から、あるいは各層のための正しいサイズが何か) を決定するための魔法の公式はありません。異なるアーキテクチャのシリーズを使用して実験しなければなりません。

適切なモデルサイズを見つけるためには、比較的少ない層とパラメータで始めて、それから検証損失上の戻しが減衰し始めるまで層のサイズを増やしたり新しい層を追加します。これを映画レビュー分類ネットワークで試してみましょう。

ベースラインとして Dense 層だけを使用する単純なモデルを作成します、それからより小さいものと大きいバージョンを作成し、それらを比較してみます。

 

ベースライン・モデルを作成する

baseline_model = keras.Sequential([
    # `input_shape` is only required here so that `.summary` works. 
    keras.layers.Dense(16, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

baseline_model.compile(optimizer='adam',
                       loss='binary_crossentropy',
                       metrics=['accuracy', 'binary_crossentropy'])

baseline_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 16)                160016    
_________________________________________________________________
dense_1 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,305
Trainable params: 160,305
Non-trainable params: 0
_________________________________________________________________
baseline_history = baseline_model.fit(train_data,
                                      train_labels,
                                      epochs=20,
                                      batch_size=512,
                                      validation_data=(test_data, test_labels),
                                      verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 4s - loss: 0.4830 - acc: 0.8082 - binary_crossentropy: 0.4830 - val_loss: 0.3383 - val_acc: 0.8758 - val_binary_crossentropy: 0.3383
Epoch 2/20
 - 4s - loss: 0.2494 - acc: 0.9114 - binary_crossentropy: 0.2494 - val_loss: 0.2851 - val_acc: 0.8862 - val_binary_crossentropy: 0.2851
Epoch 3/20
 - 4s - loss: 0.1816 - acc: 0.9372 - binary_crossentropy: 0.1816 - val_loss: 0.2894 - val_acc: 0.8850 - val_binary_crossentropy: 0.2894
Epoch 4/20
 - 4s - loss: 0.1444 - acc: 0.9516 - binary_crossentropy: 0.1444 - val_loss: 0.3147 - val_acc: 0.8790 - val_binary_crossentropy: 0.3147
Epoch 5/20
 - 4s - loss: 0.1184 - acc: 0.9613 - binary_crossentropy: 0.1184 - val_loss: 0.3409 - val_acc: 0.8747 - val_binary_crossentropy: 0.3409
Epoch 6/20
 - 4s - loss: 0.0960 - acc: 0.9704 - binary_crossentropy: 0.0960 - val_loss: 0.3744 - val_acc: 0.8702 - val_binary_crossentropy: 0.3744
Epoch 7/20
 - 4s - loss: 0.0766 - acc: 0.9788 - binary_crossentropy: 0.0766 - val_loss: 0.4213 - val_acc: 0.8624 - val_binary_crossentropy: 0.4213
Epoch 8/20
 - 4s - loss: 0.0610 - acc: 0.9843 - binary_crossentropy: 0.0610 - val_loss: 0.4507 - val_acc: 0.8634 - val_binary_crossentropy: 0.4507
Epoch 9/20
 - 4s - loss: 0.0472 - acc: 0.9901 - binary_crossentropy: 0.0472 - val_loss: 0.4909 - val_acc: 0.8617 - val_binary_crossentropy: 0.4909
Epoch 10/20
 - 4s - loss: 0.0355 - acc: 0.9942 - binary_crossentropy: 0.0355 - val_loss: 0.5343 - val_acc: 0.8570 - val_binary_crossentropy: 0.5343
Epoch 11/20
 - 4s - loss: 0.0270 - acc: 0.9963 - binary_crossentropy: 0.0270 - val_loss: 0.5752 - val_acc: 0.8584 - val_binary_crossentropy: 0.5752
Epoch 12/20
 - 4s - loss: 0.0202 - acc: 0.9979 - binary_crossentropy: 0.0202 - val_loss: 0.6161 - val_acc: 0.8565 - val_binary_crossentropy: 0.6161
Epoch 13/20
 - 4s - loss: 0.0148 - acc: 0.9989 - binary_crossentropy: 0.0148 - val_loss: 0.6524 - val_acc: 0.8562 - val_binary_crossentropy: 0.6524
Epoch 14/20
 - 4s - loss: 0.0111 - acc: 0.9995 - binary_crossentropy: 0.0111 - val_loss: 0.6874 - val_acc: 0.8538 - val_binary_crossentropy: 0.6874
Epoch 15/20
 - 4s - loss: 0.0084 - acc: 0.9998 - binary_crossentropy: 0.0084 - val_loss: 0.7186 - val_acc: 0.8536 - val_binary_crossentropy: 0.7186
Epoch 16/20
 - 4s - loss: 0.0067 - acc: 0.9999 - binary_crossentropy: 0.0067 - val_loss: 0.7471 - val_acc: 0.8535 - val_binary_crossentropy: 0.7471
Epoch 17/20
 - 4s - loss: 0.0053 - acc: 1.0000 - binary_crossentropy: 0.0053 - val_loss: 0.7722 - val_acc: 0.8536 - val_binary_crossentropy: 0.7722
Epoch 18/20
 - 4s - loss: 0.0044 - acc: 1.0000 - binary_crossentropy: 0.0044 - val_loss: 0.7973 - val_acc: 0.8522 - val_binary_crossentropy: 0.7973
Epoch 19/20
 - 4s - loss: 0.0037 - acc: 1.0000 - binary_crossentropy: 0.0037 - val_loss: 0.8185 - val_acc: 0.8536 - val_binary_crossentropy: 0.8185
Epoch 20/20
 - 4s - loss: 0.0031 - acc: 1.0000 - binary_crossentropy: 0.0031 - val_loss: 0.8396 - val_acc: 0.8522 - val_binary_crossentropy: 0.8396

 

より小さいモデルを作成する

作成したばかりのベースライン・モデルに対して比較するためにより少ない隠れユニットを持つモデルを作成しましょう :

smaller_model = keras.Sequential([
    keras.layers.Dense(4, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(4, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

smaller_model.compile(optimizer='adam',
                loss='binary_crossentropy',
                metrics=['accuracy', 'binary_crossentropy'])

smaller_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_3 (Dense)              (None, 4)                 40004     
_________________________________________________________________
dense_4 (Dense)              (None, 4)                 20        
_________________________________________________________________
dense_5 (Dense)              (None, 1)                 5         
=================================================================
Total params: 40,029
Trainable params: 40,029
Non-trainable params: 0
_________________________________________________________________

そして同じデータを使用してモデルを訓練します :

smaller_history = smaller_model.fit(train_data,
                                    train_labels,
                                    epochs=20,
                                    batch_size=512,
                                    validation_data=(test_data, test_labels),
                                    verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 4s - loss: 0.5875 - acc: 0.7590 - binary_crossentropy: 0.5875 - val_loss: 0.4841 - val_acc: 0.8534 - val_binary_crossentropy: 0.4841
Epoch 2/20
 - 4s - loss: 0.3949 - acc: 0.8851 - binary_crossentropy: 0.3949 - val_loss: 0.3738 - val_acc: 0.8733 - val_binary_crossentropy: 0.3738
Epoch 3/20
 - 4s - loss: 0.3001 - acc: 0.9069 - binary_crossentropy: 0.3001 - val_loss: 0.3222 - val_acc: 0.8838 - val_binary_crossentropy: 0.3222
Epoch 4/20
 - 4s - loss: 0.2469 - acc: 0.9221 - binary_crossentropy: 0.2469 - val_loss: 0.2992 - val_acc: 0.8860 - val_binary_crossentropy: 0.2992
Epoch 5/20
 - 4s - loss: 0.2129 - acc: 0.9320 - binary_crossentropy: 0.2129 - val_loss: 0.2901 - val_acc: 0.8862 - val_binary_crossentropy: 0.2901
Epoch 6/20
 - 4s - loss: 0.1881 - acc: 0.9386 - binary_crossentropy: 0.1881 - val_loss: 0.2850 - val_acc: 0.8878 - val_binary_crossentropy: 0.2850
Epoch 7/20
 - 4s - loss: 0.1689 - acc: 0.9461 - binary_crossentropy: 0.1689 - val_loss: 0.2862 - val_acc: 0.8854 - val_binary_crossentropy: 0.2862
Epoch 8/20
 - 4s - loss: 0.1534 - acc: 0.9506 - binary_crossentropy: 0.1534 - val_loss: 0.2903 - val_acc: 0.8847 - val_binary_crossentropy: 0.2903
Epoch 9/20
 - 4s - loss: 0.1412 - acc: 0.9550 - binary_crossentropy: 0.1412 - val_loss: 0.2964 - val_acc: 0.8828 - val_binary_crossentropy: 0.2964
Epoch 10/20
 - 4s - loss: 0.1291 - acc: 0.9600 - binary_crossentropy: 0.1291 - val_loss: 0.3066 - val_acc: 0.8791 - val_binary_crossentropy: 0.3066
Epoch 11/20
 - 4s - loss: 0.1193 - acc: 0.9646 - binary_crossentropy: 0.1193 - val_loss: 0.3139 - val_acc: 0.8790 - val_binary_crossentropy: 0.3139
Epoch 12/20
 - 4s - loss: 0.1103 - acc: 0.9680 - binary_crossentropy: 0.1103 - val_loss: 0.3267 - val_acc: 0.8759 - val_binary_crossentropy: 0.3267
Epoch 13/20
 - 4s - loss: 0.1022 - acc: 0.9712 - binary_crossentropy: 0.1022 - val_loss: 0.3347 - val_acc: 0.8755 - val_binary_crossentropy: 0.3347
Epoch 14/20
 - 4s - loss: 0.0953 - acc: 0.9735 - binary_crossentropy: 0.0953 - val_loss: 0.3465 - val_acc: 0.8731 - val_binary_crossentropy: 0.3465
Epoch 15/20
 - 4s - loss: 0.0883 - acc: 0.9762 - binary_crossentropy: 0.0883 - val_loss: 0.3589 - val_acc: 0.8712 - val_binary_crossentropy: 0.3589
Epoch 16/20
 - 4s - loss: 0.0822 - acc: 0.9783 - binary_crossentropy: 0.0822 - val_loss: 0.3717 - val_acc: 0.8704 - val_binary_crossentropy: 0.3717
Epoch 17/20
 - 4s - loss: 0.0767 - acc: 0.9811 - binary_crossentropy: 0.0767 - val_loss: 0.3864 - val_acc: 0.8688 - val_binary_crossentropy: 0.3864
Epoch 18/20
 - 4s - loss: 0.0712 - acc: 0.9833 - binary_crossentropy: 0.0712 - val_loss: 0.4017 - val_acc: 0.8668 - val_binary_crossentropy: 0.4017
Epoch 19/20
 - 4s - loss: 0.0659 - acc: 0.9857 - binary_crossentropy: 0.0659 - val_loss: 0.4142 - val_acc: 0.8666 - val_binary_crossentropy: 0.4142
Epoch 20/20
 - 4s - loss: 0.0606 - acc: 0.9877 - binary_crossentropy: 0.0606 - val_loss: 0.4292 - val_acc: 0.8656 - val_binary_crossentropy: 0.4292

 

より大きいモデルを作成する

エクササイズとして、より大きいモデルさえも作成して、それがどのように素早く overfitting し始めるかを見ることができます。次に、このベンチマークに、問題が必要とするよりも遥かにより大きい capacity を持つネットワークを追加しましょう :

bigger_model = keras.models.Sequential([
    keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(512, activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

bigger_model.compile(optimizer='adam',
                     loss='binary_crossentropy',
                     metrics=['accuracy','binary_crossentropy'])

bigger_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_6 (Dense)              (None, 512)               5120512   
_________________________________________________________________
dense_7 (Dense)              (None, 512)               262656    
_________________________________________________________________
dense_8 (Dense)              (None, 1)                 513       
=================================================================
Total params: 5,383,681
Trainable params: 5,383,681
Non-trainable params: 0
_________________________________________________________________

そして、再度、同じデータを使用してモデルを訓練します :

bigger_history = bigger_model.fit(train_data, train_labels,
                                  epochs=20,
                                  batch_size=512,
                                  validation_data=(test_data, test_labels),
                                  verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 8s - loss: 0.3478 - acc: 0.8486 - binary_crossentropy: 0.3478 - val_loss: 0.2999 - val_acc: 0.8788 - val_binary_crossentropy: 0.2999
Epoch 2/20
 - 8s - loss: 0.1424 - acc: 0.9483 - binary_crossentropy: 0.1424 - val_loss: 0.3631 - val_acc: 0.8639 - val_binary_crossentropy: 0.3631
Epoch 3/20
 - 8s - loss: 0.0515 - acc: 0.9854 - binary_crossentropy: 0.0515 - val_loss: 0.4228 - val_acc: 0.8700 - val_binary_crossentropy: 0.4228
Epoch 4/20
 - 8s - loss: 0.0081 - acc: 0.9989 - binary_crossentropy: 0.0081 - val_loss: 0.5690 - val_acc: 0.8706 - val_binary_crossentropy: 0.5690
Epoch 5/20
 - 8s - loss: 0.0013 - acc: 0.9999 - binary_crossentropy: 0.0013 - val_loss: 0.6585 - val_acc: 0.8719 - val_binary_crossentropy: 0.6585
Epoch 6/20
 - 8s - loss: 8.3323e-04 - acc: 1.0000 - binary_crossentropy: 8.3323e-04 - val_loss: 0.6917 - val_acc: 0.8710 - val_binary_crossentropy: 0.6917
Epoch 7/20
 - 8s - loss: 1.8393e-04 - acc: 1.0000 - binary_crossentropy: 1.8393e-04 - val_loss: 0.7174 - val_acc: 0.8717 - val_binary_crossentropy: 0.7174
Epoch 8/20
 - 7s - loss: 1.2353e-04 - acc: 1.0000 - binary_crossentropy: 1.2353e-04 - val_loss: 0.7397 - val_acc: 0.8720 - val_binary_crossentropy: 0.7397
Epoch 9/20
 - 8s - loss: 9.1524e-05 - acc: 1.0000 - binary_crossentropy: 9.1524e-05 - val_loss: 0.7577 - val_acc: 0.8720 - val_binary_crossentropy: 0.7577
Epoch 10/20
 - 8s - loss: 7.1093e-05 - acc: 1.0000 - binary_crossentropy: 7.1093e-05 - val_loss: 0.7716 - val_acc: 0.8721 - val_binary_crossentropy: 0.7716
Epoch 11/20
 - 8s - loss: 5.7032e-05 - acc: 1.0000 - binary_crossentropy: 5.7032e-05 - val_loss: 0.7852 - val_acc: 0.8719 - val_binary_crossentropy: 0.7852
Epoch 12/20
 - 8s - loss: 4.6592e-05 - acc: 1.0000 - binary_crossentropy: 4.6592e-05 - val_loss: 0.7972 - val_acc: 0.8719 - val_binary_crossentropy: 0.7972
Epoch 13/20
 - 8s - loss: 3.8803e-05 - acc: 1.0000 - binary_crossentropy: 3.8803e-05 - val_loss: 0.8071 - val_acc: 0.8722 - val_binary_crossentropy: 0.8071
Epoch 14/20
 - 8s - loss: 3.2781e-05 - acc: 1.0000 - binary_crossentropy: 3.2781e-05 - val_loss: 0.8176 - val_acc: 0.8722 - val_binary_crossentropy: 0.8176
Epoch 15/20
 - 8s - loss: 2.7967e-05 - acc: 1.0000 - binary_crossentropy: 2.7967e-05 - val_loss: 0.8265 - val_acc: 0.8723 - val_binary_crossentropy: 0.8265
Epoch 16/20
 - 7s - loss: 2.4134e-05 - acc: 1.0000 - binary_crossentropy: 2.4134e-05 - val_loss: 0.8344 - val_acc: 0.8722 - val_binary_crossentropy: 0.8344
Epoch 17/20
 - 7s - loss: 2.0978e-05 - acc: 1.0000 - binary_crossentropy: 2.0978e-05 - val_loss: 0.8435 - val_acc: 0.8723 - val_binary_crossentropy: 0.8435
Epoch 18/20
 - 8s - loss: 1.8398e-05 - acc: 1.0000 - binary_crossentropy: 1.8398e-05 - val_loss: 0.8501 - val_acc: 0.8722 - val_binary_crossentropy: 0.8501
Epoch 19/20
 - 8s - loss: 1.6204e-05 - acc: 1.0000 - binary_crossentropy: 1.6204e-05 - val_loss: 0.8572 - val_acc: 0.8722 - val_binary_crossentropy: 0.8572
Epoch 20/20
 - 8s - loss: 1.4384e-05 - acc: 1.0000 - binary_crossentropy: 1.4384e-05 - val_loss: 0.8645 - val_acc: 0.8723 - val_binary_crossentropy: 0.8645

 

訓練検証損失をプロットする

実践は訓練損失を示し、破線は検証損失を示します (remember: より低い検証損失はより良いモデルを示します)。ここで、より小さいネットワークはベースラインモデルよりも後で overfitting し始めて (4 ではなくて 6 エポック後) そしてそのパフーマンスはひとたび overfitting し始めれば遥かによりゆっくりと低下します。

def plot_history(histories, key='binary_crossentropy'):
  plt.figure(figsize=(16,10))
    
  for name, history in histories:
    val = plt.plot(history.epoch, history.history['val_'+key],
                   '--', label=name.title()+' Val')
    plt.plot(history.epoch, history.history[key], color=val[0].get_color(),
             label=name.title()+' Train')

  plt.xlabel('Epochs')
  plt.ylabel(key.replace('_',' ').title())
  plt.legend()

  plt.xlim([0,max(history.epoch)])


plot_history([('baseline', baseline_history),
              ('smaller', smaller_history),
              ('bigger', bigger_history)])

より大きいネットワークは殆ど直ちに、丁度 1 エポック後に overfitting し始めます、そして遥かにより厳しく overfit することに気がつくでしょう。ネットワークがより多くの capacity を持つほど、より迅速に訓練データをモデリングすることが可能です (低い訓練損失という結果になります) が、それはより overfitting しやすいです (訓練と検証損失間の巨大な差異という結果になります) 。

 

ストラテジー

重み正則化を追加する

貴方はオッカムの剃刀の原理を知っているかもしれません: 何かについて 2 つの説明が与えられたとき、最も正しい可能性がある説明は「単純な」もの、最小量の仮定をするものです。これはまたニューラルネットワークにより学習されたモデルにも適用されます : ある訓練データとネットワーク・アーキテクチャが与えられたとき、データを説明できる重み値の複数のセット (複数のモデル) があり、そしてより単純なモデルは複雑なものよりも overfit する可能性が低いです。

このコンテキストでの「単純なモデル」はそこではパラメータ値の分布がより少ないエントロピーを持ちます (あるいはまとめてより少ないパラメータを持つモデルです、上のセクションで見たような)。そのため overfitting を軽減する一般的な方法はその (ネットワークの) 重みを小さい値だけを取るように強制してネットワークの複雑さに制約を置くことです、それは重み値の分布をより「正則 (= regular)」にします。これは「重み正則化」と呼ばれ、それはネットワークの損失関数に巨大な重みを持つことに関連するコストを追加することによって成されます。このコストは 2 つのフレーバーに分けられます :

  • L1 正則化, ここでは追加されるコストは重み係数への (i.e. 重みの「L1 ノルム」と呼称されるものへの) 絶対値への比例項です。
  • L2 正則化, ここでは追加されるコストは重み係数の値の二乗への (i.e. 重みの「L2 ノルム」と呼称されるものへの) 比例項です。L2 正則化はまたニューラルネットワークのコンテキストでは重み減衰とも呼称されます。異なる名前に混乱してはいけません。重み減衰は数学的には L2 正則化と正確時同じです。

tf.keras では、重み正則化は層にキーワード引数として重み regularizer インスタンスを渡すことにより追加されます。今 L2 重み正則化を追加しましょう。

l2_model = keras.models.Sequential([
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
                       activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

l2_model.compile(optimizer='adam',
                 loss='binary_crossentropy',
                 metrics=['accuracy', 'binary_crossentropy'])

l2_model_history = l2_model.fit(train_data, train_labels,
                                epochs=20,
                                batch_size=512,
                                validation_data=(test_data, test_labels),
                                verbose=2)
Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 4s - loss: 0.5401 - acc: 0.7926 - binary_crossentropy: 0.5019 - val_loss: 0.3785 - val_acc: 0.8760 - val_binary_crossentropy: 0.3386
Epoch 2/20
 - 4s - loss: 0.2991 - acc: 0.9103 - binary_crossentropy: 0.2543 - val_loss: 0.3339 - val_acc: 0.8870 - val_binary_crossentropy: 0.2856
Epoch 3/20
 - 4s - loss: 0.2479 - acc: 0.9311 - binary_crossentropy: 0.1970 - val_loss: 0.3392 - val_acc: 0.8854 - val_binary_crossentropy: 0.2863
Epoch 4/20
 - 4s - loss: 0.2230 - acc: 0.9424 - binary_crossentropy: 0.1684 - val_loss: 0.3551 - val_acc: 0.8804 - val_binary_crossentropy: 0.2994
Epoch 5/20
 - 4s - loss: 0.2109 - acc: 0.9484 - binary_crossentropy: 0.1535 - val_loss: 0.3678 - val_acc: 0.8781 - val_binary_crossentropy: 0.3093
Epoch 6/20
 - 4s - loss: 0.1980 - acc: 0.9528 - binary_crossentropy: 0.1389 - val_loss: 0.3845 - val_acc: 0.8745 - val_binary_crossentropy: 0.3249
Epoch 7/20
 - 4s - loss: 0.1915 - acc: 0.9560 - binary_crossentropy: 0.1307 - val_loss: 0.3987 - val_acc: 0.8718 - val_binary_crossentropy: 0.3371
Epoch 8/20
 - 4s - loss: 0.1841 - acc: 0.9600 - binary_crossentropy: 0.1217 - val_loss: 0.4122 - val_acc: 0.8698 - val_binary_crossentropy: 0.3495
Epoch 9/20
 - 4s - loss: 0.1791 - acc: 0.9607 - binary_crossentropy: 0.1158 - val_loss: 0.4294 - val_acc: 0.8684 - val_binary_crossentropy: 0.3656
Epoch 10/20
 - 4s - loss: 0.1775 - acc: 0.9620 - binary_crossentropy: 0.1130 - val_loss: 0.4455 - val_acc: 0.8642 - val_binary_crossentropy: 0.3804
Epoch 11/20
 - 4s - loss: 0.1726 - acc: 0.9639 - binary_crossentropy: 0.1071 - val_loss: 0.4527 - val_acc: 0.8648 - val_binary_crossentropy: 0.3867
Epoch 12/20
 - 4s - loss: 0.1690 - acc: 0.9653 - binary_crossentropy: 0.1029 - val_loss: 0.4718 - val_acc: 0.8604 - val_binary_crossentropy: 0.4054
Epoch 13/20
 - 4s - loss: 0.1682 - acc: 0.9660 - binary_crossentropy: 0.1011 - val_loss: 0.4770 - val_acc: 0.8628 - val_binary_crossentropy: 0.4095
Epoch 14/20
 - 4s - loss: 0.1597 - acc: 0.9693 - binary_crossentropy: 0.0922 - val_loss: 0.4842 - val_acc: 0.8592 - val_binary_crossentropy: 0.4170
Epoch 15/20
 - 4s - loss: 0.1542 - acc: 0.9729 - binary_crossentropy: 0.0871 - val_loss: 0.4987 - val_acc: 0.8596 - val_binary_crossentropy: 0.4315
Epoch 16/20
 - 4s - loss: 0.1525 - acc: 0.9733 - binary_crossentropy: 0.0851 - val_loss: 0.5099 - val_acc: 0.8566 - val_binary_crossentropy: 0.4423
Epoch 17/20
 - 4s - loss: 0.1500 - acc: 0.9742 - binary_crossentropy: 0.0824 - val_loss: 0.5175 - val_acc: 0.8567 - val_binary_crossentropy: 0.4495
Epoch 18/20
 - 4s - loss: 0.1487 - acc: 0.9741 - binary_crossentropy: 0.0805 - val_loss: 0.5354 - val_acc: 0.8552 - val_binary_crossentropy: 0.4668
Epoch 19/20
 - 4s - loss: 0.1466 - acc: 0.9748 - binary_crossentropy: 0.0778 - val_loss: 0.5389 - val_acc: 0.8589 - val_binary_crossentropy: 0.4700
Epoch 20/20
 - 4s - loss: 0.1451 - acc: 0.9755 - binary_crossentropy: 0.0757 - val_loss: 0.5499 - val_acc: 0.8557 - val_binary_crossentropy: 0.4800

 

l2(0.001) は層の重み行列の総ての係数が 0.001 * weight_coefficient_value をネットワークの総計損失に追加することを意味します。このペナルティは訓練時のみに追加されますので、このネットワークに対する損失はテスト時よりも訓練時に遥かに高くなります。

L2 正則化ペナルティのインパクトがここにあります :

見て取れるように、両者のモデルが同じパラメータ数を持つ場合でさえも、L2 正則化されたモデルはベースライン・モデルよりも overfitting に大して遥かにより耐性があります。

 

dropout を追加する

Dropout は最も効果的で最も一般に使用されるニューラルネットワークのための正則化テクニックの一つで、University of Toronto の Hinton とその学生により開発されました。Dropout は層に適用され、訓練の間層の出力特徴の数をランダムに “dropping out” (i.e. ゼロに設定) することから成ります。訓練の間に与えられた入力サンプルに対して与えられた層は通常はベクトル [0.2, 0.5, 1.3, 0.8, 1.1] を返したと仮定します ; dropout を適用した後、このベクトルはランダムに分布する幾つかのゼロ要素を持つでしょう、e.g. [0, 0.5, 1.3, 0, 1.1] です。”dropout 率” はゼロにされる特徴の割合です ; それは通常は 0.2 と 0.5 の間に設定されます。テスト時には、ユニットは drop out されず、代わりに層の出力値は dropout 率に等しい係数でスケールダウンされます、これは訓練時よりもより多いユニットが有効であるという事実のためにバランスを取るためです。

tf.keras では Dropout 層を通してネットワークに dropout を導入できます、それはすぐ前の層の出力に適用されます。

IMDB ネットワークに 2 つの Dropout 層をそれらがどのように上手く overfitting を減じるかを見るために追加しましょう :

dpt_model = keras.models.Sequential([
    keras.layers.Dense(16, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(16, activation=tf.nn.relu),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])

dpt_model.compile(optimizer='adam',
                  loss='binary_crossentropy',
                  metrics=['accuracy','binary_crossentropy'])

dpt_model_history = dpt_model.fit(train_data, train_labels,
                                  epochs=20,
                                  batch_size=512,
                                  validation_data=(test_data, test_labels),
                                  verbose=2)

Train on 25000 samples, validate on 25000 samples
Epoch 1/20
 - 4s - loss: 0.6363 - acc: 0.6294 - binary_crossentropy: 0.6363 - val_loss: 0.5156 - val_acc: 0.8526 - val_binary_crossentropy: 0.5156
Epoch 2/20
 - 4s - loss: 0.4776 - acc: 0.7993 - binary_crossentropy: 0.4776 - val_loss: 0.3629 - val_acc: 0.8782 - val_binary_crossentropy: 0.3629
Epoch 3/20
 - 4s - loss: 0.3731 - acc: 0.8549 - binary_crossentropy: 0.3731 - val_loss: 0.2976 - val_acc: 0.8864 - val_binary_crossentropy: 0.2976
Epoch 4/20
 - 4s - loss: 0.3065 - acc: 0.8850 - binary_crossentropy: 0.3065 - val_loss: 0.2748 - val_acc: 0.8896 - val_binary_crossentropy: 0.2748
Epoch 5/20
 - 4s - loss: 0.2610 - acc: 0.9066 - binary_crossentropy: 0.2610 - val_loss: 0.2751 - val_acc: 0.8882 - val_binary_crossentropy: 0.2751
Epoch 6/20
 - 4s - loss: 0.2245 - acc: 0.9192 - binary_crossentropy: 0.2245 - val_loss: 0.2844 - val_acc: 0.8870 - val_binary_crossentropy: 0.2844
Epoch 7/20
 - 4s - loss: 0.1968 - acc: 0.9266 - binary_crossentropy: 0.1968 - val_loss: 0.2999 - val_acc: 0.8873 - val_binary_crossentropy: 0.2999
Epoch 8/20
 - 4s - loss: 0.1759 - acc: 0.9362 - binary_crossentropy: 0.1759 - val_loss: 0.3136 - val_acc: 0.8859 - val_binary_crossentropy: 0.3136
Epoch 9/20
 - 4s - loss: 0.1596 - acc: 0.9438 - binary_crossentropy: 0.1596 - val_loss: 0.3242 - val_acc: 0.8826 - val_binary_crossentropy: 0.3242
Epoch 10/20
 - 4s - loss: 0.1412 - acc: 0.9498 - binary_crossentropy: 0.1412 - val_loss: 0.3551 - val_acc: 0.8823 - val_binary_crossentropy: 0.3551
Epoch 11/20
 - 4s - loss: 0.1292 - acc: 0.9539 - binary_crossentropy: 0.1292 - val_loss: 0.3745 - val_acc: 0.8792 - val_binary_crossentropy: 0.3745
Epoch 12/20
 - 4s - loss: 0.1176 - acc: 0.9568 - binary_crossentropy: 0.1176 - val_loss: 0.3983 - val_acc: 0.8800 - val_binary_crossentropy: 0.3983
Epoch 13/20
 - 4s - loss: 0.1115 - acc: 0.9582 - binary_crossentropy: 0.1115 - val_loss: 0.4218 - val_acc: 0.8791 - val_binary_crossentropy: 0.4218
Epoch 14/20
 - 4s - loss: 0.1035 - acc: 0.9612 - binary_crossentropy: 0.1035 - val_loss: 0.4368 - val_acc: 0.8780 - val_binary_crossentropy: 0.4368
Epoch 15/20
 - 4s - loss: 0.0949 - acc: 0.9626 - binary_crossentropy: 0.0949 - val_loss: 0.4608 - val_acc: 0.8773 - val_binary_crossentropy: 0.4608
Epoch 16/20
 - 4s - loss: 0.0909 - acc: 0.9626 - binary_crossentropy: 0.0909 - val_loss: 0.4573 - val_acc: 0.8776 - val_binary_crossentropy: 0.4573
Epoch 17/20
 - 4s - loss: 0.0860 - acc: 0.9642 - binary_crossentropy: 0.0860 - val_loss: 0.4963 - val_acc: 0.8770 - val_binary_crossentropy: 0.4963
Epoch 18/20
 - 4s - loss: 0.0832 - acc: 0.9655 - binary_crossentropy: 0.0832 - val_loss: 0.4879 - val_acc: 0.8762 - val_binary_crossentropy: 0.4879
Epoch 19/20
 - 4s - loss: 0.0752 - acc: 0.9698 - binary_crossentropy: 0.0752 - val_loss: 0.5262 - val_acc: 0.8760 - val_binary_crossentropy: 0.5262
Epoch 20/20
 - 4s - loss: 0.0768 - acc: 0.9666 - binary_crossentropy: 0.0768 - val_loss: 0.5360 - val_acc: 0.8754 - val_binary_crossentropy: 0.5360
plot_history([('baseline', baseline_history),
              ('dropout', dpt_model_history)])

dropout の追加はベースライン・モデルを超える明確な改善です。

復習のために: こちらはニューラルネットワークで overfitting を回避するための最も一般的な方法です :

  • より多くの訓練データを得る。
  • ネットワークの capcity を減じる。
  • 重み正則を追加する。
  • dropout を追加する。

そしてこのガイドでカバーされていない 2 つの重要なアプローチはデータ増強とバッチ正規化です。

 

以上



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