ホーム » RNN

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

TensorFlow 2.0 : ガイド : Keras :- Keras でマスキングとパディング

TensorFlow 2.0 : ガイド : Keras :- Keras でマスキングとパディング (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/

 

ガイド : Keras :- Keras でマスキングとパディング

セットアップ

from __future__ import absolute_import, division, print_function, unicode_literals

import numpy as np

import tensorflow as tf

from tensorflow.keras import layers

 

シークエンス・データをパディング

シークエンス・データを処理するとき、個々のサンプルが異なる長さを持つことは非常に一般的です。次のサンプルを考えます (単語としてトークン化されたテキスト) :

[
  ["The", "weather", "will", "be", "nice", "tomorrow"],
  ["How", "are", "you", "doing", "today"],
  ["Hello", "world", "!"]
]

語彙検索の後、データは整数としてベクトル化されるかもしれません、例えば :

[
  [83, 91, 1, 645, 1253, 927],
  [73, 8, 3215, 55, 927],
  [71, 1331, 4231]
]

データは 2D リストで、そこでは個々のサンプルはそれぞれ長さ 6, 5 と 3 を持ちます。深層学習モデルのための入力データは (この場合 e.g. (batch_size, 6, vocab_size) の shape の) 単一の tensorでなければならないので、最長の項目よりも短いサンプルはあるプレースホルダー値でパディングされる必要があります (代替的に、短いサンプルをパッドする前に長いサンプルを切る詰める (= truncate) こともするかもしれません)。

Keras は共通の長さにシークエンスを切り詰めてパディングする API を提供します : tf.keras.preprocessing.sequence.pad_sequences です。

raw_inputs = [
  [83, 91, 1, 645, 1253, 927],
  [73, 8, 3215, 55, 927],
  [711, 632, 71]
]

# By default, this will pad using 0s; it is configurable via the
# "value" parameter.
# Note that you could "pre" padding (at the beginning) or
# "post" padding (at the end).
# We recommend using "post" padding when working with RNN layers
# (in order to be able to use the 
# CuDNN implementation of the layers).
padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(raw_inputs,
                                                              padding='post')

print(padded_inputs)
[[  83   91    1  645 1253  927]
 [  73    8 3215   55  927    0]
 [ 711  632   71    0    0    0]]

 

マスキング

総てのサンプルが統一された (= uniform) 長さを持つ今、モデルはデータのあるパートは実際にはパディングで無視されるべきであることを知らされなければなりません。そのメカニズムがマスキングです。

Keras モデルで入力マスクを導入するには 3 つの方法があります :

  • keras.layers.Masking 層を追加する。
  • keras.layers.Embedding 層を mask_zero=True で configure する。
  • mask 引数を手動で渡します、この引数をサポートする層を呼び出すとき (e.g. RNN 層)。

 

マスク生成層 : Embedding と Masking

内部的には、これらの層はマスク tensor (shape (batch, sequence_length) の 2D tensor) を作成し、それを Masking か Embedding 層により返される tensor 出力にそれを装着します。

embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
masked_output = embedding(padded_inputs)

print(masked_output._keras_mask)
tf.Tensor(
[[ True  True  True  True  True  True]
 [ True  True  True  True  True False]
 [ True  True  True False False False]], shape=(3, 6), dtype=bool)
masking_layer = layers.Masking()
# Simulate the embedding lookup by expanding the 2D input to 3D,
# with embedding dimension of 10.
unmasked_embedding = tf.cast(
    tf.tile(tf.expand_dims(padded_inputs, axis=-1), [1, 1, 10]),
    tf.float32)

masked_embedding = masking_layer(unmasked_embedding)
print(masked_embedding._keras_mask)
tf.Tensor(
[[ True  True  True  True  True  True]
 [ True  True  True  True  True False]
 [ True  True  True False False False]], shape=(3, 6), dtype=bool)

プリントされた結果から見れるように、マスクは shape (batch_size, sequence_length) を持つ 2D ブーリアン tensor で、そこでは各個々の False エントリは対応する時間ステップは処理の間無視されるべきであることを示します。

 

Functional API と Sequential API のマスク伝播

Functional API か Sequential API を使用するとき、Embedding か Masking 層により生成されたマスクはそれらを使用できる任意の層 (例えば、RNN 層) に対してネットワークを通して伝播されます。Keras は入力に対応するマスクを自動的に取得してそれをどのように使用するかを知る任意の層に渡します。

サブクラス化されたモデルや層の call メソッドでは、マスクは自動的には伝播されませんので、それを必要とする任意の層に mask 引数を手動で渡す必要があることに注意してください。詳細は下のセクションを見てください。

例えば、次の Sequential モデルでは、LSTM 層は自動的にマスクを受け取ります、それはそれがパッドされた値を無視することを意味します :

model = tf.keras.Sequential([
  layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True),
  layers.LSTM(32),
])

これはまた次の Functional API モデルにも当てはまります :

inputs = tf.keras.Input(shape=(None,), dtype='int32')
x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)
outputs = layers.LSTM(32)(x)

model = tf.keras.Model(inputs, outputs)

 

マスク tensor を直接的に層に渡す

(LSTM 層のような) マスクを処理できる層は __call__ メソッドで mask 引数を持ちます。

一方で、マスクを生成する層 (e.g. Embedding) は呼び出せる compute_mask(input, previous_mask) メソッドを公開します。

こうして、このようなことができます :

class MyLayer(layers.Layer):
  
  def __init__(self, **kwargs):
    super(MyLayer, self).__init__(**kwargs)
    self.embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
    self.lstm = layers.LSTM(32)
    
  def call(self, inputs):
    x = self.embedding(inputs)
    # Note that you could also prepare a `mask` tensor manually.
    # It only needs to be a boolean tensor
    # with the right shape, i.e. (batch_size, timesteps).
    mask = self.embedding.compute_mask(inputs)
    output = self.lstm(x, mask=mask)  # The layer will ignore the masked values
    return output

layer = MyLayer()
x = np.random.random((32, 10)) * 100
x = x.astype('int32')
layer(x)
<tf.Tensor: id=4730, shape=(32, 32), dtype=float32, numpy=
array([[-0.00149354, -0.00657718,  0.0043684 , ...,  0.01915387,
         0.00254279,  0.00201567],
       [-0.00874859,  0.00249364,  0.00269479, ..., -0.01414887,
         0.00511035, -0.00541363],
       [-0.00457095, -0.0097013 , -0.00557693, ...,  0.00384533,
         0.00664415,  0.00333986],
       ...,
       [-0.00762534, -0.00543655,  0.0005238 , ...,  0.01187737,
         0.00214507, -0.00063268],
       [ 0.00428915, -0.00258686,  0.00012214, ...,  0.0064177 ,
         0.00800534,  0.00203928],
       [-0.01474019, -0.00349469, -0.00311312, ..., -0.0064069 ,
         0.00472621,  0.005593  ]], dtype=float32)>

 

貴方のカスタム層でマスキングをサポートする

時に貴方は (Embedding のような) マスクを生成する層や、現在のマスクを変更する必要がある層を書く必要があるかもしれません。

例えば、時間次元上で連結する Concatenate 層のような、入力とは異なる時間次元を持つ tensor を生成する任意の層は現在のマスクを変更する必要があるでしょう、その結果下流の層はマスクされた時間ステップを正しく考慮に入れることができます。

これを行なうために、貴方の層は layer.compute_mask() メソッドを実装するべきです、これは入力と現在のマスクが与えられたとき新しいマスクを生成します。

殆どの層は時間次元を変更しませんので、マスキングについて心配する必要はありません。そのような場合 compute_mask() のデフォルトの動作は単に現在のマスクを通過させるだけです。

ここに現在のマスクを変更する必要がある TemporalSplit 層のサンプルがあります。

class TemporalSplit(tf.keras.layers.Layer):
  """Split the input tensor into 2 tensors along the time dimension."""

  def call(self, inputs):
    # Expect the input to be 3D and mask to be 2D, split the input tensor into 2
    # subtensors along the time axis (axis 1).
    return tf.split(inputs, 2, axis=1)
    
  def compute_mask(self, inputs, mask=None):
    # Also split the mask into 2 if it presents.
    if mask is None:
      return None
    return tf.split(mask, 2, axis=1)

first_half, second_half = TemporalSplit()(masked_embedding)
print(first_half._keras_mask)
print(second_half._keras_mask)
tf.Tensor(
[[ True  True  True]
 [ True  True  True]
 [ True  True  True]], shape=(3, 3), dtype=bool)
tf.Tensor(
[[ True  True  True]
 [ True  True False]
 [False False False]], shape=(3, 3), dtype=bool)

ここに入力値からマスクを生成できる CustomEmbedding 層のもう一つのサンプルがあります :

class CustomEmbedding(tf.keras.layers.Layer):
  
  def __init__(self, input_dim, output_dim, mask_zero=False, **kwargs):
    super(CustomEmbedding, self).__init__(**kwargs)
    self.input_dim = input_dim
    self.output_dim = output_dim
    self.mask_zero = mask_zero
    
  def build(self, input_shape):
    self.embeddings = self.add_weight(
      shape=(self.input_dim, self.output_dim),
      initializer='random_normal',
      dtype='float32')
    
  def call(self, inputs):
    return tf.nn.embedding_lookup(self.embeddings, inputs)
  
  def compute_mask(self, inputs, mask=None):
    if not self.mask_zero:
      return None
    return tf.not_equal(inputs, 0)
  
  
layer = CustomEmbedding(10, 32, mask_zero=True)
x = np.random.random((3, 10)) * 9
x = x.astype('int32')

y = layer(x)
mask = layer.compute_mask(x)

print(mask)
tf.Tensor(
[[ True  True  True  True  True  True  True False  True  True]
 [ True  True  True  True  True  True  True  True  True  True]
 [False  True False  True  True  True False  True False  True]], shape=(3, 10), dtype=bool)

 

マスク情報を必要とする層を書く

いくつかの層はマスク消費者 (= consumer) です : それらは call で mask 引数を受け取りそれを特定の時間ステップをスキップするかどうかを決定するために使用します。

そのような層を書くために、単純に call シグネチャで mask=None 引数を追加できます。入力に関連するマスクはそれが利用可能なときにいつでも層に渡されます。

class MaskConsumer(tf.keras.layers.Layer):
  
  def call(self, inputs, mask=None):
    ...

 

要点

それが Keras でマスキングについて知る必要があることの総てです。まとめると :

  • 「マスキング」は層がシークエンス入力である時間ステップをいつスキップ/無視するかをどのように知ることができるかです。
  • 幾つかの層はマスク-generator です : Embedding は (mask_zero=True の場合) 入力値からマスクを生成できて、Masking 層もそれができます。
  • 幾つかの層はマスク-consumer です : それらは __call__ メソッドで mask 引数を公開します。これは RNN 層が当てはまります。
  • Functional API と Sequential API では、マスク情報は自動的に伝播されます。
  • サブクラス化されたモデルを書くときやスタンドアロンな方法で層を使用するとき、層に mask 引数を手動で渡します。
  • 現在のマスクを変更する層を容易に書くことができます、それは新しいマスクを生成するか、入力に関連するマスクを消費します。
 

以上



TensorFlow 2.0 : ガイド : Keras :- Keras でリカレント・ニューラルネットワーク (RNN)

TensorFlow 2.0 : ガイド : Keras :- Keras でリカレント・ニューラルネットワーク (RNN) (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/

 

ガイド : Keras :- Keras でリカレント・ニューラルネットワーク (RNN)

リカレント・ニューラルネットワーク (RNN) は、時系列や自然言語のようなシークエンス・データをモデル化するためのパワフルなニューラルネットワークのクラスです。

模式的には (= schematically)、RNN 層はシークエンスの時間ステップに渡り反復するために for ループを使用し、一方でそれがそこまでに見た時間ステップについての情報をエンコードする内部状態を保持します。

Keras RNN API は以下に焦点を絞って設計されています :

  • 利用の容易さ: 組込み tf.keras.layers.RNN, tf.keras.layers.LSTM, tf.keras.layers.GRU 層は難しい configuration 選択を行わなければならないことなくリカレント・モデルを素早く構築することを可能にします。
  • カスタマイズの容易さ: カスタム動作を持つ貴方自身の RNN セル層 (for ループの内側部) を定義し、そしてそれを一般的な tf.keras.layers.RNN 層 (for ループ自身) で使用することもできます。これは異なる研究アイデアを最小限のコードで柔軟な方法で素早くプロトタイピングすることを可能にします。

 

セットアップ

from __future__ import absolute_import, division, print_function, unicode_literals

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

import tensorflow as tf

from tensorflow.keras import layers

 

単純なモデルを構築する

Keras には 3 つの組込み RNN があります :

  1. tf.keras.layers.SimpleRNN, 完全結合 RNN そこでは前の時間ステップからの出力は次の時間ステップに供給されます。
  2. tf.keras.layers.GRU, 最初に Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation 内で提案されました。
  3. tf.keras.layers.LSTM, 最初に Long Short-Term Memory で提案されました。

2015 年初期に、Keras は LSTM と GRU の最初の再利用可能なオープンソース Python 実装を持ちました。

ここに Sequential モデルの単純なサンプルがあります、これは整数のシークエンスを処理し、各整数を 64-次元ベクトルに埋め込み、それから LSTM 層を使用してベクトルのシークエンスを処理します。

model = tf.keras.Sequential()
# Add an Embedding layer expecting input vocab of size 1000, and
# output embedding dimension of size 64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))

# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))

# Add a Dense layer with 10 units and softmax activation.
model.add(layers.Dense(10, activation='softmax'))

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 64)          64000     
_________________________________________________________________
lstm (LSTM)                  (None, 128)               98816     
_________________________________________________________________
dense (Dense)                (None, 10)                1290      
=================================================================
Total params: 164,106
Trainable params: 164,106
Non-trainable params: 0

 

出力と状態

デフォルトでは、RNN 層の出力はサンプル毎に単一のベクトルを含みます。このベクトルは最後の時間ステップに対応する RNN セル出力で、入力シークエンス全体についての情報を含みます。この出力の shape は (batch_size, units) です、ここで units は層のコンストラクタに渡される units 引数に対応します。

RNN 層はまた各サンプルに対する出力の全体のシークエンス (1 ベクトル per 時間ステップ per サンプル) を返すこともできます、return_sequences=True を設定する場合。この出力の shape は (batch_size, timesteps, units) です。

model = tf.keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))

# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))

# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)
model.add(layers.SimpleRNN(128))

model.add(layers.Dense(10, activation='softmax'))

model.summary() 
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, None, 64)          64000     
_________________________________________________________________
gru (GRU)                    (None, None, 256)         247296    
_________________________________________________________________
simple_rnn (SimpleRNN)       (None, 128)               49280     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 361,866
Trainable params: 361,866
Non-trainable params: 0
_________________________________________________________________

加えて、RNN 層はその最終内部状態を返すことができます。返された状態は後で RNN 実行を再開するためにか、もう一つの RNN を初期化する ために利用できます。この設定は一般にエンコーダ・デコーダ sequence-to-sequence モデルで使用され、そこではエンコーダ最終状態はデコーダの初期状態として使用されます。

RNN 層をその内部状態を返すように configure するには、層を作成するとき return_state パラメータを True に設定します。LSTM は 2 状態 tensor を持ちますが、GRU は一つだけを持つことに注意してください。

層の初期状態を configure するためには、単に層を追加のキーワード引数 initial_state で呼び出します。状態の shape は下のサンプルのように、層の unit サイズに適合する必要があります。

encoder_vocab = 1000
decoder_vocab = 2000

encoder_input = layers.Input(shape=(None, ))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(encoder_input)

# Return states in addition to output
output, state_h, state_c = layers.LSTM(
    64, return_state=True, name='encoder')(encoder_embedded)
encoder_state = [state_h, state_c]

decoder_input = layers.Input(shape=(None, ))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(decoder_input)

# Pass the 2 states to a new LSTM layer, as initial state
decoder_output = layers.LSTM(
    64, name='decoder')(decoder_embedded, initial_state=encoder_state)
output = layers.Dense(10, activation='softmax')(decoder_output)

model = tf.keras.Model([encoder_input, decoder_input], output)
model.summary()
Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, None)]       0                                            
__________________________________________________________________________________________________
input_2 (InputLayer)            [(None, None)]       0                                            
__________________________________________________________________________________________________
embedding_2 (Embedding)         (None, None, 64)     64000       input_1[0][0]                    
__________________________________________________________________________________________________
embedding_3 (Embedding)         (None, None, 64)     128000      input_2[0][0]                    
__________________________________________________________________________________________________
encoder (LSTM)                  [(None, 64), (None,  33024       embedding_2[0][0]                
__________________________________________________________________________________________________
decoder (LSTM)                  (None, 64)           33024       embedding_3[0][0]                
                                                                 encoder[0][1]                    
                                                                 encoder[0][2]                    
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 10)           650         decoder[0][0]                    
==================================================================================================
Total params: 258,698
Trainable params: 258,698
Non-trainable params: 0
__________________________________________________________________________________________________

 

RNN 層と RNN セル

組込み RNN 層に加えて、RNN API はまたセルレベル API も提供します。入力シークエンスのバッチ全体を処理する RNN 層と違い、RNN セルは単一の時間ステップだけを処理します。

セルは RNN 層の for ループの内側にあります。tf.keras.layers.RNN 層の内側でのセルのラッピングはシークエンスのバッチを処理できる層を与えます, e.g. RNN(LSTMCell(10))。

数学的には、RNN(LSTMCell(10)) は LSTM (10) と同じ結果を生成します。実際に、TF v1.x のこの層の実装は単に対応する RNN セルを作成してそれを RNN 層でラッピングしていました。けれども組込み GRU と LSTM 層の使用は CuDNN の使用を有効にしますのでより良いパフォーマンスを見るかもしれません。

3 つの組込み RNN セルがあり、それらの各々は適合する RNN 層に対応します。

セル抽象は、一般的な tf.keras.layers.RNN クラスと一緒に、貴方の研究のためにカスタム RNN アーキテクチャを実装することを非常に容易にします。

 

交差バッチ statefulness

非常に長いシークエンス (多分無限大) を処理するとき、交差バッチ statefulness のパターンを使用することを望むかもしれません。

通常は、RNN 層の内部状態はそれが新しいバッチを見るたびにリセットされます (i.e. 層により見られる総てのサンプルは過去 (のもの) から独立的であると仮定されています)。層は与えられたサンプルを処理する間、状態を維持するだけです。

けれども非常に長いシークエンスを持つ場合、それらをより短いシークエンスに分解して (層の状態をリセットすることなく) それらの短いシークエンスを RNN 層にシーケンシャルに供給することは有用です。このようにして、層は、それが一度に一つの部分シークエンスを見ているだけですが、シークエンス全体についての情報を保持できます。

コンストラクタで stateful=True を設定することによりこれを行なうことができます。

もしシークエンス s = [t0, t1, … t1546, t1547] を持つ場合、それを例えば次のように分割できます :

s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]

それからそれを次を通して処理するでしょう :

lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
  output = lstm_layer(s)

状態をクリアすることを望むとき、layer.reset_states() を使用できます。

Note: このセットアップでは、与えられたバッチ内のサンプル i は前のバッチ内のサンプル i の続きであることが仮定されています。これは総てのバッチはサンプルの同じ数 (バッチサイズ) を含むべきであることを意味します。例えば、もしバッチが [sequence_A_from_t0_to_t100, sequence_B_from_t0_to_t100] を含む場合、次のバッチは [sequence_A_from_t101_to_t200, sequence_B_from_t101_to_t200] を含むべきです。

ここに完全なサンプルがあります :

paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)

lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)

# reset_states() will reset the cached state to the original initial_state.
# If no initial_state was provided, zero-states will be used by default.
lstm_layer.reset_states()

 

双方向 RNN

時系列以外のシークエンス (e.g. テキスト) については、RNN モデルはそれが最初から最後までシークエンスを処理するだけでなく反対にも処理する場合により良く遂行できることが多いです。例えば、センテンスの次の単語を予測するためには、その前に来る単語だけでなく、単語回りのコンテキストを持つことがしばしば有用です。

Keras はそのような双方向 RNN を構築するための容易な API を提供します : tf.keras.layers.Bidirectional ラッパーです。

model = tf.keras.Sequential()

model.add(layers.Bidirectional(layers.LSTM(64, return_sequences=True), 
                               input_shape=(5, 10)))
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10, activation='softmax'))

model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
bidirectional (Bidirectional (None, 5, 128)            38400     
_________________________________________________________________
bidirectional_1 (Bidirection (None, 64)                41216     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                650       
=================================================================
Total params: 80,266
Trainable params: 80,266
Non-trainable params: 0
_________________________________________________________________

内部的には、Bidirectional は渡された RNN 層をコピーして新たにコピーされた層の go_backwards フィールドを反転します、その結果それは入力を反対の順序で処理します。

Bidirectional RNN の出力はデフォルトでは forward 層出力と backward 層出力の合計です。異なるマージ動作, e.g. 連結を必要とする場合には、Bidirectional ラッパー・コンストラクタで merge_mode パラメータを変更します。Bidirectional についてのより詳細は、API doc を確認してください。

 

パフォーマンス最適化と TensorFlow 2.0 の CuDNN カーネル

TensorFlow 2.0 では、組込み LSTM と GRU 層は GPU が利用可能なときデフォルトで CuDNN カーネルを活用するために更新されました。この変更で、以前の keras.layers.CuDNNLSTM/CuDNNGRU 層は deprecated となり、そして貴方はモデルをそれが動作するハードウェアについて心配することなく構築できます。

CuDNN カーネルはある仮定で構築されますので、これは組込み LSTM や GRU 層のデフォルトを変更する場合、層は CuDNN カーネルを使用できないことを意味します。例えば :

  • 活性化関数を tanh から他のものに変更する。
  • recurrent_activation 関数を sigmoid から他のものに変更する。
  • recurrent_dropout > 0 を使用する。
  • unroll を True に設定する、これは LSTM/GRU に内側の tf.while_loop を unrolled for ループに分解します。
  • use_bias を False に設定する。
  • 入力データが厳密に右パディングされないときマスキングを使用する (マスクが厳密に右パディングされたデータに対応する場合、CuDNN は依然として使用できます。これは最も一般的なケースです)。

制約の詳細なリストについては、LSTMGRU 層のためのドキュメントを見てください。

 

利用可能なとき CuDNN カーネルを使用する

パフォーマンスの違いを実演するために単純な LSTM モデルを構築しましょう。

入力シークエンスとしてMNIST 数字の行のシークエンスを使用します (ピクセルの各行を時間ステップとして扱います)、そして数字のラベルを予測します。

batch_size = 64
# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).
# Each input sequence will be of size (28, 28) (height is treated like time).
input_dim = 28

units = 64
output_size = 10  # labels are from 0 to 9

# Build the RNN model
def build_model(allow_cudnn_kernel=True):
  # CuDNN is only available at the layer level, and not at the cell level.
  # This means `LSTM(units)` will use the CuDNN kernel,
  # while RNN(LSTMCell(units)) will run on non-CuDNN kernel.
  if allow_cudnn_kernel:
    # The LSTM layer with default options uses CuDNN.
    lstm_layer = tf.keras.layers.LSTM(units, input_shape=(None, input_dim))
  else:
    # Wrapping a LSTMCell in a RNN layer will not use CuDNN.
    lstm_layer = tf.keras.layers.RNN(
        tf.keras.layers.LSTMCell(units),
        input_shape=(None, input_dim))
  model = tf.keras.models.Sequential([
      lstm_layer,
      tf.keras.layers.BatchNormalization(),
      tf.keras.layers.Dense(output_size, activation='softmax')]
  )
  return model

 

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

mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]

 

モデルインスタンスを作成してそれをコンパイルする

モデルのための損失関数として sparse_categorical_crossentropy を選択します。モデルの出力は [batch_size, 10] の shape を持ちます。モデルのためのターゲットは整数ベクトルで、整数の各々は 0 から 9 の範囲にあります。

model = build_model(allow_cudnn_kernel=True)

model.compile(loss='sparse_categorical_crossentropy', 
              optimizer='sgd',
              metrics=['accuracy'])
model.fit(x_train, y_train,
          validation_data=(x_test, y_test),
          batch_size=batch_size,
          epochs=5)
Train on 60000 samples, validate on 10000 samples
Epoch 1/5
60000/60000 [==============================] - 8s 126us/sample - loss: 0.9247 - accuracy: 0.7083 - val_loss: 0.5664 - val_accuracy: 0.8170
Epoch 2/5
60000/60000 [==============================] - 5s 85us/sample - loss: 0.3930 - accuracy: 0.8821 - val_loss: 0.3145 - val_accuracy: 0.9002
Epoch 3/5
60000/60000 [==============================] - 5s 85us/sample - loss: 0.2515 - accuracy: 0.9241 - val_loss: 0.2888 - val_accuracy: 0.9070
Epoch 4/5
60000/60000 [==============================] - 5s 86us/sample - loss: 0.1961 - accuracy: 0.9407 - val_loss: 0.1784 - val_accuracy: 0.9428
Epoch 5/5
60000/60000 [==============================] - 6s 93us/sample - loss: 0.1649 - accuracy: 0.9510 - val_loss: 0.1326 - val_accuracy: 0.9573

<tensorflow.python.keras.callbacks.History at 0x7f6a980abb38>

 

新しいモデルを CuDNN カーネルなしで構築する

slow_model = build_model(allow_cudnn_kernel=False)
slow_model.set_weights(model.get_weights())
slow_model.compile(loss='sparse_categorical_crossentropy', 
                   optimizer='sgd', 
                   metrics=['accuracy'])
slow_model.fit(x_train, y_train, 
               validation_data=(x_test, y_test), 
               batch_size=batch_size,
               epochs=1)  # We only train for one epoch because it's slower.
Train on 60000 samples, validate on 10000 samples
60000/60000 [==============================] - 21s 349us/sample - loss: 0.1457 - accuracy: 0.9567 - val_loss: 0.1881 - val_accuracy: 0.9387

<tensorflow.python.keras.callbacks.History at 0x7f6a5d4f0b00>

見て取れるように、CuDNN で構築されたモデルは通常の TensorFlow カーネルを使用するモデルに比較して訓練するために遥かに高速です。

同じ CuDNN-有効モデルはまた CPU-only 環境で推論を実行するためにも使用できます。下の tf.device アノテーションは単にデバイス配置を強制します。モデルは GPU が利用可能でない場合にデフォルトで CPU 上で動作します。

最早貴方がその上で実行しているハードウェアについて心配する必要は単純にありません。Isn’t that pretty cool?

with tf.device('CPU:0'):
  cpu_model = build_model(allow_cudnn_kernel=True)
  cpu_model.set_weights(model.get_weights())
  result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
  print('Predicted result is: %s, target result is: %s' % (result.numpy(), sample_label))
  plt.imshow(sample, cmap=plt.get_cmap('gray'))
Predicted result is: [5], target result is: 5

 

リスト/辞書入力、またはネストされた入力を持つ RNN

ネストされた構造は実装者に単一の時間ステップ内により多くの情報を含めることを可能にします。例えば、ビデオフレームは音声とビデオ入力を同時に持てるでしょう。この場合のデータ shape は次のようなものであり得ます :

[batch, timestep, {“video”: [height, width, channel], “audio”: [frequency]}]

もう一つの例では、手書きデータは (ペンの) 圧力情報に加えて、ペンの現在の位置のための座標 x と y の両者を持てるでしょうそこでデータ表現は次のようなものでしょう :

[batch, timestep, {“location”: [x, y], “pressure”: [force]}]

以下のコードはそのような構造化入力を受け取るカスタム RNN セルをどのように構築するかの例を提供します。

 

ネストされた入力/出力をサポートするカスタムセルを定義する

NestedInput = collections.namedtuple('NestedInput', ['feature1', 'feature2'])
NestedState = collections.namedtuple('NestedState', ['state1', 'state2'])

class NestedCell(tf.keras.layers.Layer):

  def __init__(self, unit_1, unit_2, unit_3, **kwargs):
    self.unit_1 = unit_1
    self.unit_2 = unit_2
    self.unit_3 = unit_3
    self.state_size = NestedState(state1=unit_1, 
                                  state2=tf.TensorShape([unit_2, unit_3]))
    self.output_size = (unit_1, tf.TensorShape([unit_2, unit_3]))
    super(NestedCell, self).__init__(**kwargs)

  def build(self, input_shapes):
    # expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]
    input_1 = input_shapes.feature1[1]
    input_2, input_3 = input_shapes.feature2[1:]

    self.kernel_1 = self.add_weight(
        shape=(input_1, self.unit_1), initializer='uniform', name='kernel_1')
    self.kernel_2_3 = self.add_weight(
        shape=(input_2, input_3, self.unit_2, self.unit_3),
        initializer='uniform',
        name='kernel_2_3')

  def call(self, inputs, states):
    # inputs should be in [(batch, input_1), (batch, input_2, input_3)]
    # state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]
    input_1, input_2 = tf.nest.flatten(inputs)
    s1, s2 = states

    output_1 = tf.matmul(input_1, self.kernel_1)
    output_2_3 = tf.einsum('bij,ijkl->bkl', input_2, self.kernel_2_3)
    state_1 = s1 + output_1
    state_2_3 = s2 + output_2_3

    output = [output_1, output_2_3]
    new_states = NestedState(state1=state_1, state2=state_2_3)

    return output, new_states

 

ネストされた入力/出力を持つ RNN モデルを構築する

tf.keras.layers.RNN 層と丁度定義したカスタムセルを使用する Keras モデルを構築しましょう。

unit_1 = 10
unit_2 = 20
unit_3 = 30

input_1 = 32
input_2 = 64
input_3 = 32
batch_size = 64
num_batch = 100
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = tf.keras.layers.RNN(cell)

inp_1 = tf.keras.Input((None, input_1))
inp_2 = tf.keras.Input((None, input_2, input_3))

outputs = rnn(NestedInput(feature1=inp_1, feature2=inp_2))

model = tf.keras.models.Model([inp_1, inp_2], outputs)

model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])

 

ランダムに生成されたデータでモデルを訓練する

このモデルのための良い候補データセットがありませんので、実演のためにランダムな Numpy データを使用します。

input_1_data = np.random.random((batch_size * num_batch, timestep, input_1))
input_2_data = np.random.random((batch_size * num_batch, timestep, input_2, input_3))
target_1_data = np.random.random((batch_size * num_batch, unit_1))
target_2_data = np.random.random((batch_size * num_batch, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]

model.fit(input_data, target_data, batch_size=batch_size)
Train on 6400 samples
6400/6400 [==============================] - 5s 804us/sample - loss: 0.3786 - rnn_1_loss: 0.1166 - rnn_1_1_loss: 0.2620 - rnn_1_accuracy: 0.0997 - rnn_1_1_accuracy: 0.0333

<tensorflow.python.keras.callbacks.History at 0x7f667c8a4b00>

Keras tf.keras.layers.RNN 層では、貴方はシークエンス内の個々のステップのための数学ロジックを定義することが想定されるだけです、そして tf.keras.layers.RNN 層は貴方のためにシークエンス反復を処理します。それは新しい種類の RNN (e.g. LSTM 変種) を素早くプロトタイピングする非常にパワフルな方法です。

より詳細については、API doc を訪ねてください。

 

以上



TensorFlow 2.0 : 上級 Tutorials : テキスト :- RNN でテキスト生成

TensorFlow 2.0 : 上級 Tutorials : テキスト :- RNN でテキスト生成 (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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 でテキスト生成

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

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

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

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

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

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

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

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

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

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

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

 

セットアップ

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

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf

import numpy as np
import os
import time

 

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

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

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

 

データを読む

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

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

All:
Speak, speak.

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

All:
Resolved. resolved.

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

 

テキストを処理する

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

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

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

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

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

print('{')
for char,_ in zip(char2idx, range(20)):
    print('  {:4s}: {:3d},'.format(repr(char), char2idx[char]))
print('  ...\n}')
{
  '&' :   4,
  'E' :  17,
  't' :  58,
  'x' :  62,
  'F' :  18,
  'T' :  32,
  'C' :  15,
  'V' :  34,
  'z' :  64,
  '-' :   7,
  'd' :  42,
  '\n':   0,
  'u' :  59,
  'Y' :  37,
  'p' :  54,
  ' ' :   1,
  'q' :  55,
  'P' :  28,
  'o' :  53,
  'm' :  51,
  ...
}
# Show how the first 13 characters from the text are mapped to integers
print ('{} ---- characters mapped to int ---- > {}'.format(repr(text[:13]), text_as_int[:13]))
'First Citizen' ---- characters mapped to int ---- > [18 47 56 57 58  1 15 47 58 47 64 43 52]

 

予測タスク

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

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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

dataset = sequences.map(split_input_target)

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

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

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

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

 

訓練バッチを作成する

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

# Batch size
BATCH_SIZE = 64

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

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

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

 

モデルを構築する

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

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

# The embedding dimension
embedding_dim = 256

# Number of RNN units
rnn_units = 1024
def build_model(vocab_size, embedding_dim, rnn_units, batch_size):
  model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim,
                              batch_input_shape=[batch_size, None]),
    tf.keras.layers.GRU(rnn_units,
                        return_sequences=True,
                        stateful=True,
                        recurrent_initializer='glorot_uniform'),
    tf.keras.layers.Dense(vocab_size)
  ])
  return model
model = build_model(
  vocab_size = len(vocab),
  embedding_dim=embedding_dim,
  rnn_units=rnn_units,
  batch_size=BATCH_SIZE)

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


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

 

モデルを試す

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

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

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

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

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (64, None, 256)           16640     
_________________________________________________________________
gru (GRU)                    (64, None, 1024)          3938304   
_________________________________________________________________
dense (Dense)                (64, None, 65)            66625     
=================================================================
Total params: 4,021,569
Trainable params: 4,021,569
Non-trainable params: 0
_________________________________________________________________

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

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

バッチの最初のサンプルについてそれを試します :

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

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

sampled_indices
array([37, 31, 41, 40, 56, 41,  4, 29, 48, 31,  7, 14, 43,  1, 49, 32, 39,
       21, 62,  2, 11, 47, 63, 22,  3, 60, 30, 58,  7, 59, 18, 50, 25, 28,
       52, 45, 15,  1, 43, 28, 23,  5, 59, 49, 56, 30, 20, 13, 64, 53, 63,
       64, 27, 32, 44, 44,  2, 28,  0, 53, 50, 34, 26, 16, 23, 47, 49, 48,
       14, 32,  2, 23, 34, 33, 42,  2,  0, 31,  2, 62,  2,  7, 48,  5, 15,
       31, 43, 37, 60,  3, 49,  7, 51, 15, 34, 13,  1, 51,  9, 55])

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

print("Input: \n", repr("".join(idx2char[input_example_batch[0]])))
print()
print("Next Char Predictions: \n", repr("".join(idx2char[sampled_indices ])))
Input: 
 "state\nOf that integrity which should become't,\nNot having the power to do the good it would,\nFor the"

Next Char Predictions: 
 "YScbrc&QjS-Be kTaIx!;iyJ$vRt-uFlMPngC ePK'ukrRHAzoyzOTff!P\nolVNDKikjBT!KVUd!\nS!x!-j'CSeYv$k-mCVA m3q"

 

モデルを訓練する

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

 

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

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

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

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

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

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

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

 

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

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

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

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

 

訓練を実行する

訓練時間を合理的なものに保持するために、モデルを訓練するために 10 エポックを使用します。Colab では、より高速な訓練のためにランタイムを GPU に設定します。

EPOCHS=10
history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])
Epoch 1/10
172/172 [==============================] - 7s 43ms/step - loss: 2.6656
Epoch 2/10
172/172 [==============================] - 6s 33ms/step - loss: 1.9586
Epoch 3/10
172/172 [==============================] - 6s 33ms/step - loss: 1.6907
Epoch 4/10
172/172 [==============================] - 6s 33ms/step - loss: 1.5427
Epoch 5/10
172/172 [==============================] - 6s 34ms/step - loss: 1.4543
Epoch 6/10
172/172 [==============================] - 6s 34ms/step - loss: 1.3929
Epoch 7/10
172/172 [==============================] - 6s 33ms/step - loss: 1.3485
Epoch 8/10
172/172 [==============================] - 6s 33ms/step - loss: 1.3083
Epoch 9/10
172/172 [==============================] - 6s 34ms/step - loss: 1.2738
Epoch 10/10
172/172 [==============================] - 6s 33ms/step - loss: 1.2424

 

テキストを生成する

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

この予測ステップを単純に保持するために、1 のバッチサイズを使用します。

RNN 状態が時間ステップから時間ステップに渡される方法のために、モデルは一度構築された固定バッチサイズだけを受け取ります。

異なる batch_size でモデルを実行するためには、モデルを再構築してチェックポイントから重みを復元する (= restore) 必要があります。

tf.train.latest_checkpoint(checkpoint_dir)
'./training_checkpoints/ckpt_10'
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)

model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))

model.build(tf.TensorShape([1, None]))
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (1, None, 256)            16640     
_________________________________________________________________
gru_1 (GRU)                  (1, None, 1024)           3938304   
_________________________________________________________________
dense_1 (Dense)              (1, None, 65)             66625     
=================================================================
Total params: 4,021,569
Trainable params: 4,021,569
Non-trainable params: 0
_________________________________________________________________

 

予測ループ

次のコードブロックがテキストを生成します :

  • それは開始文字列を選択し、RNN 状態を初期化してそして生成する文字数を設定することから始まります。
  • 開始文字列と RNN 状態を使用して次の文字の予測分布を得ます。
  • それから、予測された文字のインデックスを計算するために categorical 分布を使用します。この予測された文字をモデルへの次の入力として使用します。
  • モデルにより返された RNN 状態はモデルに供給し戻されます、その結果それは今ではただ一つの単語よりも代わりに多くのコンテキストを持ちます。次の単語を予測した後、変更された RNN 状態が再度モデルに供給し戻されます、これがそれが前に予測された単語からより多くのコンテキストを得るときにどのように学習するかです。

生成されたテキストを見れば、モデルがいつ大文字にするべきかを知り、パラグラフを作成してそして Shakespeare 的な著述語彙を模倣するのを見るでしょう。小さい数の訓練エポックでは、それは首尾一貫したセンテンスを形成することをまだ学習していません。

def generate_text(model, start_string):
  # Evaluation step (generating text using the learned model)

  # Number of characters to generate
  num_generate = 1000

  # Converting our start string to numbers (vectorizing)
  input_eval = [char2idx[s] for s in start_string]
  input_eval = tf.expand_dims(input_eval, 0)

  # Empty string to store our results
  text_generated = []

  # Low temperatures results in more predictable text.
  # Higher temperatures results in more surprising text.
  # Experiment to find the best setting.
  temperature = 1.0

  # Here batch size == 1
  model.reset_states()
  for i in range(num_generate):
      predictions = model(input_eval)
      # remove the batch dimension
      predictions = tf.squeeze(predictions, 0)

      # using a categorical distribution to predict the word returned by the model
      predictions = predictions / temperature
      predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy()

      # We pass the predicted word as the next input to the model
      # along with the previous hidden state
      input_eval = tf.expand_dims([predicted_id], 0)

      text_generated.append(idx2char[predicted_id])

  return (start_string + ''.join(text_generated))
print(generate_text(model, start_string=u"ROMEO: "))
ROMEO: he hath the dispointed scronghy
name of adied to speak. Heaven fram so for to thee;
For what said for this damn'd glory?
Either to thee well.

NARCLIFL:
What, is the water, no more imagine man?

PETRUCHIO:
First, kill the greater livith never face
Turned toep prevailor, which broke here from the deee of a wetcherment,
They shall have he roundly for anorn; Signior Blother Gloucester
'fore the heads of me.

CAMILLO:
Away with him addiving?

NORTHUMBERLAND:
Yea, as tell the drack han to jelieve
A proper king in work was not a gentleman,
Madies of war. It is importune us given to strike
Your trumpetested womd of comes your two,
And match next to Romeo?
O Blint come; thou wilts point too what e'er farewell.

Servant:
Amey you, Gremio: O wonder, tell him, he hath
A horse fair spot commission,
Is her and looks in our commonwealth says by the honour.
What fanish put's colour hath this drop the need at Deat men's master London, I
will tell thee, for this once dogether.

POMPEY:
Ad, Cominius.

F

結果を改善するために貴方ができる最も容易なことはそれをより長く訓練することです (EPOCHS=30 を試してください)。

貴方はまた異なる開始文字列で実験したり、モデル精度を改良するためにもう一つの RNN 層を追加して試したり、あるいは多少のランダム予測を生成するために temperature パラメータを調整することができます。

 

Advanced: カスタマイズされた訓練

上の訓練手続きは単純ですが、多くの制御を貴方に与えません。

そこでモデルを手動でどのように実行するかを見た今、訓練ループをアンパックしてそれを私達自身で実装しましょう。これは例えば、モデルの open-loop 出力を安定させる手助けをするためにカリキュラム学習を実装するための開始点を与えます。

勾配を追跡するために tf.GradientTape を使用します。eager execution ガイド を読むことによりこのアプローチについてより多く学習できます。

手続きは次のように動作します :

  • 最初に、RNN 状態を初期化します。tf.keras.Model.reset_states メソッドを呼び出すことによりこれを行ないます。
  • 次に、データセットに渡り (バッチ毎に) iterate して各々に関連する予測を計算します。
  • tf.GradientTape をオープンして、そしてそのコンテキストで予測と損失を計算します。
  • tf.GradientTape.grads メソッドを使用してモデル変数に関する損失の勾配を計算します。
  • 最後に、optimizer の tf.train.Optimizer.apply_gradients メソッドを使用してステップを下方に取ります。
model = build_model(
  vocab_size = len(vocab),
  embedding_dim=embedding_dim,
  rnn_units=rnn_units,
  batch_size=BATCH_SIZE)
optimizer = tf.keras.optimizers.Adam()
@tf.function
def train_step(inp, target):
  with tf.GradientTape() as tape:
    predictions = model(inp)
    loss = tf.reduce_mean(
        tf.keras.losses.sparse_categorical_crossentropy(
            target, predictions, from_logits=True))
  grads = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(grads, model.trainable_variables))

  return loss
# Training step
EPOCHS = 10

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

  # initializing the hidden state at the start of every epoch
  # initally hidden is None
  hidden = model.reset_states()

  for (batch_n, (inp, target)) in enumerate(dataset):
    loss = train_step(inp, target)

    if batch_n % 100 == 0:
      template = 'Epoch {} Batch {} Loss {}'
      print(template.format(epoch+1, batch_n, loss))

  # saving (checkpoint) the model every 5 epochs
  if (epoch + 1) % 5 == 0:
    model.save_weights(checkpoint_prefix.format(epoch=epoch))

  print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss))
  print ('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

model.save_weights(checkpoint_prefix.format(epoch=epoch))
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.iter
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.beta_1
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.beta_2
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.decay
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.learning_rate
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-0.embeddings
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-2.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-2.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.recurrent_kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-0.embeddings
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-2.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-2.bias
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.recurrent_kernel
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.bias
WARNING:tensorflow:A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/alpha/guide/checkpoints#loading_mechanics for details.
Epoch 1 Batch 0 Loss 4.174566268920898
Epoch 1 Batch 100 Loss 2.355130672454834
Epoch 1 Loss 2.1473
Time taken for 1 epoch 6.901602268218994 sec

Epoch 2 Batch 0 Loss 2.1369407176971436
Epoch 2 Batch 100 Loss 1.9057503938674927
Epoch 2 Loss 1.8186
Time taken for 1 epoch 5.320220232009888 sec

Epoch 3 Batch 0 Loss 1.7763842344284058
Epoch 3 Batch 100 Loss 1.6552444696426392
Epoch 3 Loss 1.5838
Time taken for 1 epoch 5.368591547012329 sec

Epoch 4 Batch 0 Loss 1.5609817504882812
Epoch 4 Batch 100 Loss 1.5019890069961548
Epoch 4 Loss 1.4598
Time taken for 1 epoch 5.411896467208862 sec

Epoch 5 Batch 0 Loss 1.4785513877868652
Epoch 5 Batch 100 Loss 1.4962271451950073
Epoch 5 Loss 1.4789
Time taken for 1 epoch 5.458000421524048 sec

Epoch 6 Batch 0 Loss 1.3991695642471313
Epoch 6 Batch 100 Loss 1.3599822521209717
Epoch 6 Loss 1.3643
Time taken for 1 epoch 5.345423936843872 sec

Epoch 7 Batch 0 Loss 1.3120907545089722
Epoch 7 Batch 100 Loss 1.3584436178207397
Epoch 7 Loss 1.3505
Time taken for 1 epoch 5.293265104293823 sec

Epoch 8 Batch 0 Loss 1.26876699924469
Epoch 8 Batch 100 Loss 1.2855678796768188
Epoch 8 Loss 1.3192
Time taken for 1 epoch 5.329954147338867 sec

Epoch 9 Batch 0 Loss 1.1951823234558105
Epoch 9 Batch 100 Loss 1.2914996147155762
Epoch 9 Loss 1.2724
Time taken for 1 epoch 5.299607753753662 sec

Epoch 10 Batch 0 Loss 1.2261385917663574
Epoch 10 Batch 100 Loss 1.231096625328064
Epoch 10 Loss 1.2388
Time taken for 1 epoch 5.462940454483032 sec
 

以上






TensorFlow 2.0 : 上級 Tutorials : テキスト :- RNN でテキスト分類

TensorFlow 2.0 : 上級 Tutorials : テキスト :- RNN でテキスト分類 (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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 でテキスト分類

このテキスト分類チュートリアルはセンチメント分析のために IMDB 巨大映画レビュー・データセット 上で リカレント・ニューラルネットワーク を訓練します。

 

セットアップ

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow_datasets as tfds
import tensorflow as tf

matplotlib をインポートしてグラフをプロットするためのヘルパー関数を作成します :

import matplotlib.pyplot as plt

def plot_graphs(history, string):
  plt.plot(history.history[string])
  plt.plot(history.history['val_'+string], '')
  plt.xlabel("Epochs")
  plt.ylabel(string)
  plt.legend([string, 'val_'+string])
  plt.show()

 

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

IMDB 巨大映画レビュー・データセットは二値分類データセットです — 総てのレビューはポジティブかネガティブなセンチメント (感情) を持ちます。

TFDS を使用してデータセットをダウンロードします。

dataset, info = tfds.load('imdb_reviews/subwords8k', with_info=True,
                          as_supervised=True)
train_dataset, test_dataset = dataset['train'], dataset['test']

dataset info はエンコーダを含みます ( tfds.features.text.SubwordTextEncoder )。

encoder = info.features['text'].encoder
print ('Vocabulary size: {}'.format(encoder.vocab_size))
Vocabulary size: 8185

このテキスト・エンコーダは任意の文字列を可逆的にエンコードし、必要であればバイト・エンコーディングに戻します。

sample_string = 'Hello TensorFlow.'

encoded_string = encoder.encode(sample_string)
print ('Encoded string is {}'.format(encoded_string))

original_string = encoder.decode(encoded_string)
print ('The original string: "{}"'.format(original_string))
Encoded string is [4025, 222, 6307, 2327, 4043, 2120, 7975]
The original string: "Hello TensorFlow."
assert original_string == sample_string
for index in encoded_string:
  print ('{} ----> {}'.format(index, encoder.decode([index])))
4025 ----> Hell
222 ----> o 
6307 ----> Ten
2327 ----> sor
4043 ----> Fl
2120 ----> ow
7975 ----> .

 

訓練のためにデータを準備する

次にこれらのエンコードされた文字列のバッチを作成します。

シークエンスをバッチの最長文字列の長さにゼロ・パッドするために padded_batch メソッドを使用します :

BUFFER_SIZE = 10000
BATCH_SIZE = 64
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.padded_batch(BATCH_SIZE, train_dataset.output_shapes)

test_dataset = test_dataset.padded_batch(BATCH_SIZE, test_dataset.output_shapes)

 

モデルを作成する

tf.keras.Sequential モデルを構築して埋め込み層から始めます。埋め込み層は単語毎に一つのベクトルをストアします。呼び出されたとき、それは単語インデックスのシークエンスをベクトルのシークエンスに変換します。これらのベクトルは訓練可能です。(十分なデータ上で) 訓練後、類似の意味を持つ単語はしばしば類似したベクトルを持ちます。

このインデックス検索は tf.keras.layers.Dense 層を通した one-hot エンコード・ベクトルを渡すのと等価の演算よりも遥かにより効率的です。

リカレント・ニューラルネットワーク (RNN) は要素を通して反復することによってシークエンス入力を処理します。RNN は一つの時間ステップからの出力をそれらの入力 — とそれから次へと渡します。

tf.keras.layers.Bidirectional ラッパーはまた RNN 層とともに使用できます。これは RNN 層を通して入力を foward そして backward に伝播してそれから出力を結合します。これは RNN が長期の (= long range) 依存性を学習する手助けをします。

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(encoder.vocab_size, 64),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

訓練プロセスを構成するために Keras モデルをコンパイルします :

model.compile(loss='binary_crossentropy',
              optimizer=tf.keras.optimizers.Adam(1e-4),
              metrics=['accuracy'])

 

モデルを訓練する

history = model.fit(train_dataset, epochs=10,
                    validation_data=test_dataset, 
                    validation_steps=30)
Epoch 1/10
391/391 [==============================] - 55s 141ms/step - loss: 0.6530 - accuracy: 0.5983 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/10
391/391 [==============================] - 48s 124ms/step - loss: 0.3648 - accuracy: 0.8521 - val_loss: 0.3552 - val_accuracy: 0.8484
Epoch 3/10
391/391 [==============================] - 48s 124ms/step - loss: 0.2560 - accuracy: 0.9024 - val_loss: 0.3304 - val_accuracy: 0.8609
Epoch 4/10
391/391 [==============================] - 48s 123ms/step - loss: 0.2153 - accuracy: 0.9230 - val_loss: 0.3230 - val_accuracy: 0.8615
Epoch 5/10
391/391 [==============================] - 48s 123ms/step - loss: 0.1887 - accuracy: 0.9346 - val_loss: 0.3435 - val_accuracy: 0.8615
Epoch 6/10
391/391 [==============================] - 48s 123ms/step - loss: 0.1711 - accuracy: 0.9416 - val_loss: 0.3324 - val_accuracy: 0.8641
Epoch 7/10
391/391 [==============================] - 48s 122ms/step - loss: 0.1452 - accuracy: 0.9524 - val_loss: 0.3489 - val_accuracy: 0.8531
Epoch 8/10
391/391 [==============================] - 48s 122ms/step - loss: 0.1326 - accuracy: 0.9570 - val_loss: 0.4295 - val_accuracy: 0.8589
Epoch 9/10
391/391 [==============================] - 48s 123ms/step - loss: 0.1168 - accuracy: 0.9643 - val_loss: 0.4225 - val_accuracy: 0.8578
Epoch 10/10
391/391 [==============================] - 48s 122ms/step - loss: 0.1139 - accuracy: 0.9648 - val_loss: 0.4547 - val_accuracy: 0.8568
test_loss, test_acc = model.evaluate(test_dataset)

print('Test Loss: {}'.format(test_loss))
print('Test Accuracy: {}'.format(test_acc))
391/391 [==============================] - 18s 46ms/step - loss: 0.4621 - accuracy: 0.8581
Test Loss: 0.46211091787232766
Test Accuracy: 0.8581200242042542

上のモデルはシークエンスに適用されるパディングをマスクしていません。これはパッドされたシークエンス上で訓練してパッドされていないシークエンス上でテストする場合に歪みに繋がる可能性があります。理想的にはこれを回避するために マスキングを使用する でしょうが、下で見れるようにそれは出力上で小さい影響を持つだけです。

prediction が >= 0.5 であれば、それはポジティブでそうでなければネガティブです。

def pad_to_size(vec, size):
  zeros = [0] * (size - len(vec))
  vec.extend(zeros)
  return vec
def sample_predict(sentence, pad):
  encoded_sample_pred_text = encoder.encode(sample_pred_text)

  if pad:
    encoded_sample_pred_text = pad_to_size(encoded_sample_pred_text, 64)
  encoded_sample_pred_text = tf.cast(encoded_sample_pred_text, tf.float32)
  predictions = model.predict(tf.expand_dims(encoded_sample_pred_text, 0))

  return (predictions)
# predict on a sample text without padding.

sample_pred_text = ('The movie was cool. The animation and the graphics '
                    'were out of this world. I would recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=False)
print (predictions)
[[0.41101742]]
# predict on a sample text with padding

sample_pred_text = ('The movie was cool. The animation and the graphics '
                    'were out of this world. I would recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=True)
print (predictions)
[[0.4186573]]
plot_graphs(history, 'accuracy')

plot_graphs(history, 'loss')

 

2 つあるいはそれ以上の LSTM 層をスタックする

Keras リカレント層は return_sequences コンストラクタ引数で制御される 2 つの利用可能なモードを持ちます :

  • 各時間ステップのための連続する出力の完全なシークエンスを返すか (shape (batch_size, timesteps, output_features) の 3D tensor)、
  • 各入力シークエンスのための最後の出力だけを返します (shape (batch_size, output_features) の 2D tensor)。
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(encoder.vocab_size, 64),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64,  return_sequences=True)),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(loss='binary_crossentropy',
              optimizer=tf.keras.optimizers.Adam(1e-4),
              metrics=['accuracy'])
history = model.fit(train_dataset, epochs=10,
                    validation_data=test_dataset,
                    validation_steps=30)
Epoch 1/10
391/391 [==============================] - 89s 228ms/step - loss: 0.6491 - accuracy: 0.5914 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/10
391/391 [==============================] - 81s 207ms/step - loss: 0.3512 - accuracy: 0.8623 - val_loss: 0.3416 - val_accuracy: 0.8505
Epoch 3/10
391/391 [==============================] - 81s 207ms/step - loss: 0.2710 - accuracy: 0.9048 - val_loss: 0.3342 - val_accuracy: 0.8672
Epoch 4/10
391/391 [==============================] - 81s 208ms/step - loss: 0.2143 - accuracy: 0.9285 - val_loss: 0.3488 - val_accuracy: 0.8766
Epoch 5/10
391/391 [==============================] - 81s 206ms/step - loss: 0.1845 - accuracy: 0.9406 - val_loss: 0.3586 - val_accuracy: 0.8625
Epoch 6/10
391/391 [==============================] - 81s 207ms/step - loss: 0.1615 - accuracy: 0.9513 - val_loss: 0.3647 - val_accuracy: 0.8646
Epoch 7/10
391/391 [==============================] - 81s 207ms/step - loss: 0.1420 - accuracy: 0.9575 - val_loss: 0.4124 - val_accuracy: 0.8693
Epoch 8/10
391/391 [==============================] - 81s 207ms/step - loss: 0.1175 - accuracy: 0.9677 - val_loss: 0.4462 - val_accuracy: 0.8594
Epoch 9/10
391/391 [==============================] - 80s 206ms/step - loss: 0.1034 - accuracy: 0.9734 - val_loss: 0.4958 - val_accuracy: 0.8510
Epoch 10/10
391/391 [==============================] - 82s 209ms/step - loss: 0.0923 - accuracy: 0.9777 - val_loss: 0.5143 - val_accuracy: 0.8573
test_loss, test_acc = model.evaluate(test_dataset)

print('Test Loss: {}'.format(test_loss))
print('Test Accuracy: {}'.format(test_acc))
391/391 [==============================] - 34s 86ms/step - loss: 0.5185 - accuracy: 0.8543
Test Loss: 0.5184965819844505
Test Accuracy: 0.8543199896812439
# predict on a sample text without padding.

sample_pred_text = ('The movie was not good. The animation and the graphics '
                    'were terrible. I would not recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=False)
print (predictions)
[[0.05746633]]
# predict on a sample text with padding

sample_pred_text = ('The movie was not good. The animation and the graphics '
                    'were terrible. I would not recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=True)
print (predictions)
[[0.01921545]]
plot_graphs(history, 'accuracy')

plot_graphs(history, 'loss')

GRU 層 のような他の存在するリカレント層を調べてください。

カスタム RNN を構築することに興味があれば、Keras RNN ガイド を見てください。

 

以上



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

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

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

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

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

 

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

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

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

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

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

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

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

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

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

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

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

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

 

Setup

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

from __future__ import absolute_import, division, print_function, unicode_literals

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

import numpy as np
import os
import time

 

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

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

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

 

データを読む

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

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

All:
Speak, speak.

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

All:
Resolved. resolved.

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

 

テキストを処理する

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

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

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

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

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

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

 

予測タスク

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

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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

dataset = sequences.map(split_input_target)

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

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

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

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

 

訓練バッチを作成する

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

# Batch size
BATCH_SIZE = 64

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

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

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

 

モデルを構築する

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

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

# The embedding dimension
embedding_dim = 256

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

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


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

 

モデルを試す

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

モデルを訓練する

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

 

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

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

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

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

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

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

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

 

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

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

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

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

 

訓練を実行する

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

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

 

テキストを生成する

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

この予測ステップを単純に保持するために、1 のバッチサイズを使用します。

RNN 状態が時間ステップから時間ステップに渡される方法のために、モデルは一度構築された固定バッチサイズだけを受け取ります。

異なる batch_size でモデルを実行するためには、モデルを再構築してチェックポイントから重みを復元する (= restore) 必要があります。

tf.train.latest_checkpoint(checkpoint_dir)
'./training_checkpoints/ckpt_10'
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)

model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))

model.build(tf.TensorShape([1, None]))
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (1, None, 256)            16640     
_________________________________________________________________
lstm_1 (LSTM)                (1, None, 1024)           5246976   
_________________________________________________________________
dense_1 (Dense)              (1, None, 65)             66625     
=================================================================
Total params: 5,330,241
Trainable params: 5,330,241
Non-trainable params: 0
_________________________________________________________________

 

予測ループ

次のコードブロックがテキストを生成します :

  • それは開始文字列を選択し、RNN 状態を初期化してそして生成する文字数を設定することから始まります。
  • 開始文字列と RNN 状態を使用して次の文字の予測分布を得ます。
  • それから、予測された文字のインデックスを計算するために categorical 分布を使用します。この予測された文字をモデルへの次の入力として使用します。
  • モデルにより返された RNN 状態はモデルに供給し戻されます、その結果それは今ではただ一つの単語よりも多くのコンテキストを代わりに持ちます。次の単語を予測した後、変更された RNN 状態が再度モデルに供給し戻されます、これがそれが前に予測された単語からより多くのコンテキストを得るときにどのように学習するかです。

生成されたテキストを見れば、モデルがいつ大文字にするべきかを知り、パラグラフを作成してそして Shakespeare 的な著述語彙を模倣するのを見るでしょう。小さい数の訓練エポックでは、それは首尾一貫したセンテンスを形成することをまだ学習していません。

def generate_text(model, start_string):
  # Evaluation step (generating text using the learned model)

  # Number of characters to generate
  num_generate = 1000

  # Converting our start string to numbers (vectorizing)
  input_eval = [char2idx[s] for s in start_string]
  input_eval = tf.expand_dims(input_eval, 0)

  # Empty string to store our results
  text_generated = []

  # Low temperatures results in more predictable text.
  # Higher temperatures results in more surprising text.
  # Experiment to find the best setting.
  temperature = 1.0

  # Here batch size == 1
  model.reset_states()
  for i in range(num_generate):
      predictions = model(input_eval)
      # remove the batch dimension
      predictions = tf.squeeze(predictions, 0)

      # using a categorical distribution to predict the word returned by the model
      predictions = predictions / temperature
      predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy()

      # We pass the predicted word as the next input to the model
      # along with the previous hidden state
      input_eval = tf.expand_dims([predicted_id], 0)

      text_generated.append(idx2char[predicted_id])

  return (start_string + ''.join(text_generated))
print(generate_text(model, start_string=u"ROMEO: "))
ROMEO: where he skill
That ever we fear their looks.

LEONTES:
S stooph of ournd
The dukes o' the selferur is it of her;
Forses thereof willingly no wealth!

TRANIO:
Mine ears, sir, to them speak. whth ever young with one there her to
do than thy heart? There is a fie,
The reverse honoural gives my dather's death
That waves we'll sovereignty of thy aughts,
Your keels with us, save axhousinous: gentle brifferent, for an intent
I did forbilling ougly age on my sin,
With such man est in thy father's father's sky,
As tells me, Yorknishall and Prittend Henry, and her part withan this place
I'll speak of soldier; then I was; to report on
how horse spokes ready the nie, I knew thee,
Pray you, indeed. You were not 'quirence will wear-fallentut and frazed.

YORK:
Is no beg-of this to the Tower.

KATHARINA:
Here is a war.

Third Citizen:
You have professed them and crse trunk blow his chlician.

QUEEN ELIZABETH:
So sovereour a gentlemen, 'tis a book-so sort,
Amen, unwilling,
your love and wars their pa

結果を改善するために貴方ができる最も容易なことはそれをより長く訓練することです (EPOCHS=30 を試してください)。

貴方はまた異なる開始文字列で実験したり、モデル精度を改良するためにもう一つの RNN 層を追加して試す、あるいは多少のランダム予測を生成するために temperature パラメータを調整することができます。

 

Advanced: カスタマイズされた訓練

上の訓練手続きは単純ですが、多くの制御を貴方に与えません。

そこでモデルを手動でどのように実行するかを見た今、訓練ループをアンパックしてそれを私達自身で実装しましょう。これは例えば、モデルの open-loop 出力を安定させる手助けをするためにカリキュラム学習を実装するための開始点を与えます。

勾配を追跡するために tf.GradientTape を使用します。eager execution ガイド を読むことによりこのアプローチについてより多く学習できます。

手続きは次のように動作します :

  • 最初に、RNN 状態を初期化します。tf.keras.Model.reset_states メソッドを呼び出すことによりこれを行ないます。
  • 次に、データセットに渡り (バッチ毎に) iterate して各々に関連する予測を計算します。
  • tf.GradientTape をオープンして、そしてそのコンテキストで予測と損失を計算します。
  • tf.GradientTape.grads メソッドを使用してモデル変数に関する損失の勾配を計算します。
  • 最後に、optimizer の tf.train.Optimizer.apply_gradients メソッドを使用してステップを下方に取ります。
model = build_model(
  vocab_size = len(vocab),
  embedding_dim=embedding_dim,
  rnn_units=rnn_units,
  batch_size=BATCH_SIZE)
optimizer = tf.keras.optimizers.Adam()
@tf.function
def train_step(inp, target):
  with tf.GradientTape() as tape:
    predictions = model(inp)
    loss = tf.reduce_mean(
        tf.keras.losses.sparse_categorical_crossentropy(
            target, predictions, from_logits=True))
  grads = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(grads, model.trainable_variables))

  return loss
# Training step
EPOCHS = 10

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

  # initializing the hidden state at the start of every epoch
  # initally hidden is None
  hidden = model.reset_states()

  for (batch_n, (inp, target)) in enumerate(dataset):
    loss = train_step(inp, target)

    if batch_n % 100 == 0:
      template = 'Epoch {} Batch {} Loss {}'
      print(template.format(epoch+1, batch_n, loss))

  # saving (checkpoint) the model every 5 epochs
  if (epoch + 1) % 5 == 0:
    model.save_weights(checkpoint_prefix.format(epoch=epoch))

  print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss))
  print ('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

model.save_weights(checkpoint_prefix.format(epoch=epoch))
WARNING: Logging before flag parsing goes to stderr.
W0628 06:41:29.934798 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer
W0628 06:41:29.936547 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.iter
W0628 06:41:29.937477 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.beta_1
W0628 06:41:29.938759 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.beta_2
W0628 06:41:29.939390 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.decay
W0628 06:41:29.940099 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer.learning_rate
W0628 06:41:29.940868 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-0.embeddings
W0628 06:41:29.941582 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-2.kernel
W0628 06:41:29.942270 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-2.bias
W0628 06:41:29.944056 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.kernel
W0628 06:41:29.944905 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.recurrent_kernel
W0628 06:41:29.945559 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'm' for (root).layer_with_weights-1.cell.bias
W0628 06:41:29.946222 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-0.embeddings
W0628 06:41:29.947816 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-2.kernel
W0628 06:41:29.948982 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-2.bias
W0628 06:41:29.949806 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.kernel
W0628 06:41:29.950479 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.recurrent_kernel
W0628 06:41:29.951171 140162012227328 util.py:244] Unresolved object in checkpoint: (root).optimizer's state 'v' for (root).layer_with_weights-1.cell.bias
W0628 06:41:29.952606 140162012227328 util.py:252] A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/alpha/guide/checkpoints#loading_mechanics for details.

Epoch 1 Batch 0 Loss 4.172821998596191
Epoch 1 Batch 100 Loss 2.391324758529663
Epoch 1 Loss 2.0715
Time taken for 1 epoch 9.32474422454834 sec

Epoch 2 Batch 0 Loss 2.1118874549865723
Epoch 2 Batch 100 Loss 1.9127529859542847
Epoch 2 Loss 1.7389
Time taken for 1 epoch 6.282292127609253 sec

Epoch 3 Batch 0 Loss 1.7284945249557495
Epoch 3 Batch 100 Loss 1.6665358543395996
Epoch 3 Loss 1.5530
Time taken for 1 epoch 6.350098133087158 sec

Epoch 4 Batch 0 Loss 1.5307190418243408
Epoch 4 Batch 100 Loss 1.528407335281372
Epoch 4 Loss 1.4468
Time taken for 1 epoch 6.355969429016113 sec

Epoch 5 Batch 0 Loss 1.4143530130386353
Epoch 5 Batch 100 Loss 1.451310396194458
Epoch 5 Loss 1.3683
Time taken for 1 epoch 6.345163583755493 sec

Epoch 6 Batch 0 Loss 1.3415405750274658
Epoch 6 Batch 100 Loss 1.3894364833831787
Epoch 6 Loss 1.3122
Time taken for 1 epoch 6.310795068740845 sec

Epoch 7 Batch 0 Loss 1.2863143682479858
Epoch 7 Batch 100 Loss 1.3341155052185059
Epoch 7 Loss 1.2637
Time taken for 1 epoch 6.189915895462036 sec

Epoch 8 Batch 0 Loss 1.2400826215744019
Epoch 8 Batch 100 Loss 1.2844866514205933
Epoch 8 Loss 1.2265
Time taken for 1 epoch 6.186691999435425 sec

Epoch 9 Batch 0 Loss 1.2004377841949463
Epoch 9 Batch 100 Loss 1.2391895055770874
Epoch 9 Loss 1.1848
Time taken for 1 epoch 6.293030500411987 sec

Epoch 10 Batch 0 Loss 1.1650713682174683
Epoch 10 Batch 100 Loss 1.19230055809021
Epoch 10 Loss 1.1492
Time taken for 1 epoch 6.376950740814209 sec
 

以上






TensorFlow 2.0 Beta : Tutorials : テキストとシークエンス :- RNN でテキスト分類

TensorFlow 2.0 Beta : Beginner Tutorials : テキストとシークエンス :- RNN でテキスト分類 (翻訳/解説)

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

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

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

 

テキストとシークエンス :- RNN でテキスト分類

このテキスト分類チュートリアルはセンチメント分析のために IMDB 巨大映画レビューデータセット 上で リカレント・ニューラルネットワーク を訓練します。

from __future__ import absolute_import, division, print_function, unicode_literals

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

matplotlib をインポートしてグラフをプロットするためのヘルパー関数を作成します :

import matplotlib.pyplot as plt


def plot_graphs(history, string):
  plt.plot(history.history[string])
  plt.plot(history.history['val_'+string])
  plt.xlabel("Epochs")
  plt.ylabel(string)
  plt.legend([string, 'val_'+string])
  plt.show()

 

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

IMDB 巨大映画レビューデータセットは二値分類データセットです — 総てのレビューはポジティブかネガティブなセンチメントを持ちます。

TFDS を使用してデータセットをダウンロードします。データセットは作り付けの部分語字句解析器 (= subword tokenizer) を装備しています。

dataset, info = tfds.load('imdb_reviews/subwords8k', with_info=True,
                          as_supervised=True)
train_dataset, test_dataset = dataset['train'], dataset['test']
Downloading and preparing dataset imdb_reviews (80.23 MiB) to /home/kbuilder/tensorflow_datasets/imdb_reviews/subwords8k/0.1.0...

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




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


HBox(children=(IntProgress(value=0, description='Shuffling...', max=10, style=ProgressStyle(description_width=…
WARNING: Logging before flag parsing goes to stderr.
W0628 05:40:53.455971 139635681199872 deprecation.py:323] From /home/kbuilder/.local/lib/python3.5/site-packages/tensorflow_datasets/core/file_format_adapter.py:209: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
<a href="../../../versions/r2.0/api_docs/python/tf/data/TFRecordDataset"><code>tf.data.TFRecordDataset(path)</code></a>

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


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


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


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


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

 
これは部分語字句解析器ですから、それは任意の文字列を渡すことができて字句解析器はそれをトークン化します。

tokenizer = info.features['text'].encoder
print ('Vocabulary size: {}'.format(tokenizer.vocab_size))
Vocabulary size: 8185
sample_string = 'TensorFlow is cool.'

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

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

assert original_string == sample_string
Tokenized string is [6307, 2327, 4043, 4265, 9, 2724, 7975]
The original string: TensorFlow is cool.

字句解析器は単語がその辞書にない場合には文字列を部分語に分解してエンコードします。

for ts in tokenized_string:
  print ('{} ----> {}'.format(ts, tokenizer.decode([ts])))
6307 ----> Ten
2327 ----> sor
4043 ----> Fl
4265 ----> ow 
9 ----> is 
2724 ----> cool
7975 ----> .
BUFFER_SIZE = 10000
BATCH_SIZE = 64
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.padded_batch(BATCH_SIZE, train_dataset.output_shapes)

test_dataset = test_dataset.padded_batch(BATCH_SIZE, test_dataset.output_shapes)

 

モデルを作成する

tf.keras.Sequential モデルを構築して埋め込み層から始めます。埋め込み層は単語毎に一つのベクトルをストアします。呼び出されたとき、それは単語インデックスのシークエンスをベクトルのシークエンスに変換します。これらのベクトルは訓練可能です。(十分なデータ上で) 訓練後、類似の意味を持つ単語はしばしば同様のベクトルを持ちます。

このインデックス検索は tf.keras.layers.Dense 層を通した one-hot エンコード・ベクトルを渡す等値の演算よりも遥かにより効率的です。

リカレント・ニューラルネットワーク (RNN) は要素を通した iterate によるシークエンス入力を処理します。RNN は一つの時間ステップからの出力をそれらの入力 — そして次へと渡します。

tf.keras.layers.Bidirectional ラッパーはまた RNN 層とともに使用できます。これは RNN 層を通して入力を foward そして backward に伝播してそれから出力を連結します。これは RNN が長期の (= long range) 依存性を学習する手助けをします。

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(tokenizer.vocab_size, 64),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

訓練プロセスを構成するために Keras モデルをコンパイルします :

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

 

モデルを訓練する

history = model.fit(train_dataset, epochs=10,
                    validation_data=test_dataset)
Epoch 1/10

W0628 05:42:47.879820 139635681199872 deprecation.py:323] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/math_grad.py:1250: add_dispatch_support..wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where

391/391 [==============================] - 367s 939ms/step - loss: 0.5840 - accuracy: 0.6862 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/10
391/391 [==============================] - 138s 352ms/step - loss: 0.4049 - accuracy: 0.8229 - val_loss: 0.4430 - val_accuracy: 0.7974
Epoch 3/10
391/391 [==============================] - 102s 262ms/step - loss: 0.3484 - accuracy: 0.8620 - val_loss: 0.4643 - val_accuracy: 0.7994
Epoch 4/10
391/391 [==============================] - 98s 250ms/step - loss: 0.3774 - accuracy: 0.8357 - val_loss: 0.4392 - val_accuracy: 0.8188
Epoch 5/10
391/391 [==============================] - 86s 219ms/step - loss: 0.2587 - accuracy: 0.9017 - val_loss: 0.4777 - val_accuracy: 0.7760
Epoch 6/10
391/391 [==============================] - 81s 208ms/step - loss: 0.2229 - accuracy: 0.9178 - val_loss: 0.4470 - val_accuracy: 0.8356
Epoch 7/10
391/391 [==============================] - 81s 206ms/step - loss: 0.1814 - accuracy: 0.9357 - val_loss: 0.5000 - val_accuracy: 0.8382
Epoch 8/10
391/391 [==============================] - 76s 193ms/step - loss: 0.2660 - accuracy: 0.8936 - val_loss: 0.5101 - val_accuracy: 0.8174
Epoch 9/10
391/391 [==============================] - 80s 204ms/step - loss: 0.2428 - accuracy: 0.9040 - val_loss: 0.5396 - val_accuracy: 0.8051
Epoch 10/10
391/391 [==============================] - 75s 191ms/step - loss: 0.2763 - accuracy: 0.8853 - val_loss: 0.6413 - val_accuracy: 0.6517
test_loss, test_acc = model.evaluate(test_dataset)

print('Test Loss: {}'.format(test_loss))
print('Test Accuracy: {}'.format(test_acc))
    391/Unknown - 20s 51ms/step - loss: 0.6413 - accuracy: 0.6517Test Loss: 0.6412561183695293
Test Accuracy: 0.6516799926757812

上のモデルはシークエンスに適用されるパディングをマスクしていません。これはパッドされたシークエンス上で訓練してパッドされていないシークエンス上でテストする場合に歪みに繋がる可能性があります。理想的にはモデルはパディングを無視することを学習するでしょうが、下で見れるようにそれは出力上で小さい効果を持つだけです。

prediction が >= 0.5 であれば、それはポジティブでそうでなければネガティブです。

def pad_to_size(vec, size):
  zeros = [0] * (size - len(vec))
  vec.extend(zeros)
  return vec
def sample_predict(sentence, pad):
  tokenized_sample_pred_text = tokenizer.encode(sample_pred_text)

  if pad:
    tokenized_sample_pred_text = pad_to_size(tokenized_sample_pred_text, 64)

  predictions = model.predict(tf.expand_dims(tokenized_sample_pred_text, 0))

  return (predictions)
# predict on a sample text without padding.

sample_pred_text = ('The movie was cool. The animation and the graphics '
                    'were out of this world. I would recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=False)
print (predictions)
[[0.63414526]]
# predict on a sample text with padding

sample_pred_text = ('The movie was cool. The animation and the graphics '
                    'were out of this world. I would recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=True)
print (predictions)
[[0.66387755]]
plot_graphs(history, 'accuracy')

plot_graphs(history, 'loss')

 

2 つあるいはそれ以上の LSTM 層をスタックする

Keras リカレント層は return_sequences コンストラクタ引数で制御される 2 つの利用可能なモードを持ちます :

  • 各タイムスタンプのための連続する出力の完全なシークエンスを返すか (shape (batch_size, timesteps, output_features) の 3D tensor)、
  • 各入力シークエンスのための最後の出力だけを返します (shape (batch_size, output_features) の 2D tensor)。
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(tokenizer.vocab_size, 64),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(
        64, return_sequences=True)),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
history = model.fit(train_dataset, epochs=10,
                    validation_data=test_dataset)
Epoch 1/10
391/391 [==============================] - 710s 2s/step - loss: 0.6758 - accuracy: 0.5653 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/10
391/391 [==============================] - 240s 615ms/step - loss: 0.6268 - accuracy: 0.6446 - val_loss: 0.5156 - val_accuracy: 0.7750
Epoch 3/10
391/391 [==============================] - 185s 473ms/step - loss: 0.3965 - accuracy: 0.8324 - val_loss: 0.4141 - val_accuracy: 0.8158
Epoch 4/10
391/391 [==============================] - 166s 424ms/step - loss: 0.3237 - accuracy: 0.8726 - val_loss: 0.4115 - val_accuracy: 0.8274
Epoch 5/10
391/391 [==============================] - 156s 398ms/step - loss: 0.2794 - accuracy: 0.8934 - val_loss: 0.3814 - val_accuracy: 0.8395
Epoch 6/10
391/391 [==============================] - 138s 353ms/step - loss: 0.2052 - accuracy: 0.9269 - val_loss: 0.3805 - val_accuracy: 0.8464
Epoch 7/10
391/391 [==============================] - 140s 359ms/step - loss: 0.1538 - accuracy: 0.9493 - val_loss: 0.4158 - val_accuracy: 0.8466
Epoch 8/10
391/391 [==============================] - 130s 334ms/step - loss: 0.1210 - accuracy: 0.9622 - val_loss: 0.4478 - val_accuracy: 0.8453
Epoch 9/10
391/391 [==============================] - 134s 343ms/step - loss: 0.0934 - accuracy: 0.9723 - val_loss: 0.4828 - val_accuracy: 0.8485
Epoch 10/10
391/391 [==============================] - 137s 351ms/step - loss: 0.0735 - accuracy: 0.9798 - val_loss: 0.5393 - val_accuracy: 0.8396
test_loss, test_acc = model.evaluate(test_dataset)

print('Test Loss: {}'.format(test_loss))
print('Test Accuracy: {}'.format(test_acc))
    391/Unknown - 35s 89ms/step - loss: 0.5393 - accuracy: 0.8396Test Loss: 0.5392520240962962
Test Accuracy: 0.8395599722862244
# predict on a sample text without padding.

sample_pred_text = ('The movie was not good. The animation and the graphics '
                    'were terrible. I would not recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=False)
print (predictions)
[[0.00492772]]
# predict on a sample text with padding

sample_pred_text = ('The movie was not good. The animation and the graphics '
                    'were terrible. I would not recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=True)
print (predictions)
[[0.00426524]]
plot_graphs(history, 'accuracy')

plot_graphs(history, 'loss')

GRU 層 のような他の存在するリカレント層を調べてください。

 

以上



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

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

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

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

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

 

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

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

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

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

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

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

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

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

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

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

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

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

 

Setup

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

from __future__ import absolute_import, division, print_function

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

import numpy as np
import os
import time
Collecting tensorflow-gpu==2.0.0-alpha0
Successfully installed google-pasta-0.1.4 tb-nightly-1.14.0a20190303 tensorflow-estimator-2.0-preview-1.14.0.dev2019030300 tensorflow-gpu==2.0.0-alpha0-2.0.0.dev20190303

 

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

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

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

 

データを読む

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

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

All:
Speak, speak.

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

All:
Resolved. resolved.

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

 

テキストを処理する

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

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

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

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

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

print('{')
for char,_ in zip(char2idx, range(20)):
    print('  {:4s}: {:3d},'.format(repr(char), char2idx[char]))
print('  ...\n}')
{
  '\n':   0,
  ' ' :   1,
  '!' :   2,
  '$' :   3,
  '&' :   4,
  "'" :   5,
  ',' :   6,
  '-' :   7,
  '.' :   8,
  '3' :   9,
  ':' :  10,
  ';' :  11,
  '?' :  12,
  'A' :  13,
  'B' :  14,
  'C' :  15,
  'D' :  16,
  'E' :  17,
  'F' :  18,
  'G' :  19,
  ...
}
# Show how the first 13 characters from the text are mapped to integers
print ('{} ---- characters mapped to int ---- > {}'.format(repr(text[:13]), text_as_int[:13]))
'First Citizen' ---- characters mapped to int ---- > [18 47 56 57 58  1 15 47 58 47 64 43 52]

 

予測タスク

文字、あるいは文字のシークエンスが与えられたとき、最もありそうな次の文字は何でしょう?これが私達がモデルを訓練して遂行するタスクです。モデルへの入力は文字のシークエンスで、出力 — 各時間ステップにおける次の文字を予測するためにモデルを訓練します。RNN は前に見た要素に依拠する内部状態を維持しますので、この瞬間までに計算された総ての文字が与えられたとき、次の文字は何でしょう?

 

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

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

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

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

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

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

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

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

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

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

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

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

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

dataset = sequences.map(split_input_target)

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

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

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

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

 

訓練バッチを作成する

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

# Batch size 
BATCH_SIZE = 64

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

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

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

 

モデルを構築する

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

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

# The embedding dimension 
embedding_dim = 256

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

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

 

モデルを試す

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

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

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

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

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

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

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

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

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

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

sampled_indices
array([21,  2, 58, 40, 42, 32, 39,  7, 18, 38, 30, 58, 23, 58, 37, 10, 23,
       16, 52, 14, 43,  8, 32, 49, 62, 41, 53, 38, 17, 36, 24, 59, 41, 38,
        4, 27, 33, 59, 54, 34, 14,  1,  1, 56, 55, 40, 37,  4, 32, 44, 62,
       59,  1, 10, 20, 29,  2, 48, 37, 26, 10, 22, 58,  5, 26,  9, 23, 26,
       54, 43, 46, 36, 62, 57,  8, 53, 52, 23, 57, 42, 60, 10, 43, 11, 45,
       12, 28, 46, 46, 15, 51,  9, 56,  7, 53, 51,  2,  1, 10, 58])

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

print("Input: \n", repr("".join(idx2char[input_example_batch[0]])))
print()
print("Next Char Predictions: \n", repr("".join(idx2char[sampled_indices ])))
Input: 
 'to it far before thy time?\nWarwick is chancellor and the lord of Calais;\nStern Falconbridge commands'

Next Char Predictions: 
 "I!tbdTa-FZRtKtY:KDnBe.TkxcoZEXLucZ&OUupVB  rqbY&Tfxu :HQ!jYN:Jt'N3KNpehXxs.onKsdv:e;g?PhhCm3r-om! :t"

 

モデルを訓練する

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

 

optimizer、そして損失関数を張り付ける

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

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

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

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

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

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

 

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

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

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

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

 

訓練を実行する

訓練時間を合理的なものに保持するために、モデルを訓練するために 3 エポック (訳注: 原文ママ) を使用します。

EPOCHS=10
history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])
Epoch 1/10
172/172 [==============================] - 31s 183ms/step - loss: 2.7052
Epoch 2/10
172/172 [==============================] - 31s 180ms/step - loss: 2.0039
Epoch 3/10
172/172 [==============================] - 31s 180ms/step - loss: 1.7375
Epoch 4/10
172/172 [==============================] - 31s 179ms/step - loss: 1.5772
Epoch 5/10
172/172 [==============================] - 31s 179ms/step - loss: 1.4772
Epoch 6/10
172/172 [==============================] - 31s 180ms/step - loss: 1.4087
Epoch 7/10
172/172 [==============================] - 31s 179ms/step - loss: 1.3556
Epoch 8/10
172/172 [==============================] - 31s 179ms/step - loss: 1.3095
Epoch 9/10
172/172 [==============================] - 31s 179ms/step - loss: 1.2671
Epoch 10/10
172/172 [==============================] - 31s 180ms/step - loss: 1.2276

 

テキストを生成する

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

この予測ステップを単純に保持するために、1 のバッチサイズを使用します。

RNN 状態が時間ステップから時間ステップに渡される方法のために、モデルは一度構築された固定バッチサイズだけを受け取ります。

異なる batch_size でモデルを実行するためには、モデルを再構築してチェックポイントから重みを復元する必要があります。

tf.train.latest_checkpoint(checkpoint_dir)
'./training_checkpoints/ckpt_10'
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)

model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))

model.build(tf.TensorShape([1, None]))
W0304 03:54:01.201246 140067035297664 tf_logging.py:161] <tensorflow.python.keras.layers.recurrent.UnifiedLSTM object at 0x7f636183c7f0>: Note that this layer is not optimized for performance. Please use tf.keras.layers.CuDNNLSTM for better performance on GPU.
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (1, None, 256)            16640     
_________________________________________________________________
unified_lstm_1 (UnifiedLSTM) (1, None, 1024)           5246976   
_________________________________________________________________
dense_1 (Dense)              (1, None, 65)             66625     
=================================================================
Total params: 5,330,241
Trainable params: 5,330,241
Non-trainable params: 0
_________________________________________________________________

 

予測ループ

次のコードブロックがテキストを生成します :

  • それは開始文字列を選択し、RNN 状態を初期化してそして生成する文字数を設定することから始まります。
  • 開始文字列と RNN 状態を使用して次の文字の予測分布を得ます。
  • それから、予測された文字のインデックスを計算するために categorical 分布を使用します。この予測された文字をモデルへの次の入力として使用します。
  • モデルにより返された RNN 状態はモデルに供給し戻されます、その結果それは今ではただ一つの単語よりも多くのコンテキストを代わりに持ちます。次の単語を予測した後、変更された RNN 状態が再度モデルに供給し戻されます、これがそれが前に予測された単語からより多くのコンテキストを得るときにどのように学習されるかです。

生成されたテキストを見れば、モデルがいつ大文字にするべきかを知り、パラグラフを作成してそして Shakespeare-like な著述語彙を模倣するのを見るでしょう。小さい数の訓練エポックでは、それは首尾一貫したセンテンスを形成することをまだ学習していません。

def generate_text(model, start_string):
  # Evaluation step (generating text using the learned model)

  # Number of characters to generate
  num_generate = 1000

  # Converting our start string to numbers (vectorizing) 
  input_eval = [char2idx[s] for s in start_string]
  input_eval = tf.expand_dims(input_eval, 0)

  # Empty string to store our results
  text_generated = []

  # Low temperatures results in more predictable text.
  # Higher temperatures results in more surprising text.
  # Experiment to find the best setting.
  temperature = 1.0

  # Here batch size == 1
  model.reset_states()
  for i in range(num_generate):
      predictions = model(input_eval)
      # remove the batch dimension
      predictions = tf.squeeze(predictions, 0)

      # using a categorical distribution to predict the word returned by the model
      predictions = predictions / temperature
      predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy()
      
      # We pass the predicted word as the next input to the model
      # along with the previous hidden state
      input_eval = tf.expand_dims([predicted_id], 0)
      
      text_generated.append(idx2char[predicted_id])

  return (start_string + ''.join(text_generated))
print(generate_text(model, start_string=u"ROMEO: "))
ROMEO: now to have weth hearten sonce,
No more than the thing stand perfect your self,
Love way come. Up, this is d so do in friends:
If I fear e this, I poisple
My gracious lusty, born once for readyus disguised:
But that a pry; do it sure, thou wert love his cause;
My mind is come too!

POMPEY:
Serve my master's him: he hath extreme over his hand in the
where they shall not hear they right for me.

PROSSPOLUCETER:
I pray you, mistress, I shall be construted
With one that you shall that we know it, in this gentleasing earls of daiberkers now
he is to look upon this face, which leadens from his master as
you should not put what you perciploce backzat of cast,
Nor fear it sometime but for a pit
a world of Hantua?

First Gentleman:
That we can fall of bastards my sperial;
O, she Go seeming that which I have
what enby oar own best injuring them,
Or thom I do now, I, in heart is nothing gone,
Leatt the bark which was done born.

BRUTUS:
Both Margaret, he is sword of the house person. If born, 

結果を改善するために貴方ができる最も容易なことはそれをより長く訓練することです (EPOCHS=30 を試してください)。

貴方はまた異なる開始文字列で実験したり、モデル精度を改良するためにもう一つの RNN 層を追加して試す、あるいは多少のランダム予測を生成するために temperature パラメータを調整することができます。

 

Advanced: カスタマイズされた訓練

上の訓練手続きは単純ですが、多くの制御を貴方に与えません。

そこでモデルを手動でどのように実行するかを見た今、訓練ループをアンパックしてそれを私達自身で実装しましょう。これは例えば、モデルの open-loop 出力を安定させる手助けをするためにカリキュラム学習を実装するための開始点を与えます。

勾配を追跡するために tf.GradientTape を使用します。eager execution ガイド を読むことによりこのアプローチについてより多く学習できます。

手続きは次のように動作します :

  • 最初に、RNN 状態を初期化します。tf.keras.Model.reset_states メソッドを呼び出すことによりこれを行ないます。
  • 次に、データセットに渡り (バッチ毎に) iterate して各々に関連する予測を計算します。
  • tf.GradientTape をオープンして、そしてそのコンテキストで予測と損失を計算します。
  • tf.GradientTape.grads メソッドを使用してモデル変数に関する損失の勾配を計算します。
  • 最期に、optimizer の tf.train.Optimizer.apply_gradients メソッドを使用してステップを下方に取ります。
model = build_model(
  vocab_size = len(vocab), 
  embedding_dim=embedding_dim, 
  rnn_units=rnn_units, 
  batch_size=BATCH_SIZE)
W0304 03:54:08.030432 140067035297664 tf_logging.py:161] <tensorflow.python.keras.layers.recurrent.UnifiedLSTM object at 0x7f63635efe80>: Note that this layer is not optimized for performance. Please use tf.keras.layers.CuDNNLSTM for better performance on GPU.
optimizer = tf.keras.optimizers.Adam()
@tf.function
def train_step(inp, target):
  with tf.GradientTape() as tape:
    predictions = model(inp)
    loss = tf.reduce_mean(
        tf.keras.losses.sparse_categorical_crossentropy(target, predictions))
  grads = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(grads, model.trainable_variables))
  
  return loss
# Training step
EPOCHS = 10

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

  for (batch_n, (inp, target)) in enumerate(dataset):
    loss = train_step(inp, target)

    if batch_n % 100 == 0:
      template = 'Epoch {} Batch {} Loss {}'
      print(template.format(epoch+1, batch_n, loss))

  # saving (checkpoint) the model every 5 epochs
  if (epoch + 1) % 5 == 0:
    model.save_weights(checkpoint_prefix.format(epoch=epoch))

  print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss))
  print ('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

model.save_weights(checkpoint_prefix.format(epoch=epoch))
Epoch 1 Batch 0 Loss 8.132774353027344
Epoch 1 Batch 100 Loss 3.5028388500213623
Epoch 1 Loss 3.7314
Time taken for 1 epoch 31.78906798362732 sec

Epoch 2 Batch 0 Loss 3.766866445541382
Epoch 2 Batch 100 Loss 3.985184669494629
Epoch 2 Loss 3.9137
Time taken for 1 epoch 29.776747703552246 sec

Epoch 3 Batch 0 Loss 4.023300647735596
Epoch 3 Batch 100 Loss 3.921215534210205
Epoch 3 Loss 3.8976
Time taken for 1 epoch 30.094752311706543 sec

Epoch 4 Batch 0 Loss 3.916696071624756
Epoch 4 Batch 100 Loss 3.900864362716675
Epoch 4 Loss 3.9048
Time taken for 1 epoch 30.09034276008606 sec

Epoch 5 Batch 0 Loss 3.9154434204101562
Epoch 5 Batch 100 Loss 3.9020049571990967
Epoch 5 Loss 3.9725
Time taken for 1 epoch 30.17358922958374 sec

Epoch 6 Batch 0 Loss 3.9781394004821777
Epoch 6 Batch 100 Loss 3.920198917388916
Epoch 6 Loss 3.9269
Time taken for 1 epoch 30.19426202774048 sec

Epoch 7 Batch 0 Loss 3.9400787353515625
Epoch 7 Batch 100 Loss 3.8473968505859375
Epoch 7 Loss 3.8438
Time taken for 1 epoch 30.107476234436035 sec

Epoch 8 Batch 0 Loss 3.852555513381958
Epoch 8 Batch 100 Loss 3.8410544395446777
Epoch 8 Loss 3.8218
Time taken for 1 epoch 30.084821462631226 sec

Epoch 9 Batch 0 Loss 3.843691349029541
Epoch 9 Batch 100 Loss 3.829458236694336
Epoch 9 Loss 3.8420
Time taken for 1 epoch 30.13308310508728 sec

Epoch 10 Batch 0 Loss 3.8553621768951416
Epoch 10 Batch 100 Loss 3.7812960147857666
Epoch 10 Loss 3.7726
Time taken for 1 epoch 30.14617133140564 sec
 

以上






TensorFlow 2.0 Alpha : Tutorials : テキストとシークエンス :- RNN でテキスト分類

TensorFlow 2.0 Alpha : Beginner Tutorials : テキストとシークエンス :- RNN でテキスト分類 (翻訳/解説)

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

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

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

 

テキストとシークエンス :- RNN でテキスト分類

このテキスト分類チュートリアルはセンチメント分析のために IMDB 巨大映画レビューデータセット 上で リカレント・ニューラルネットワーク を訓練します。

from __future__ import absolute_import, division, print_function

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

matplotlib をインポートしてグラフをプロットするためのヘルパー関数を作成します :

import matplotlib.pyplot as plt


def plot_graphs(history, string):
  plt.plot(history.history[string])
  plt.plot(history.history['val_'+string])
  plt.xlabel("Epochs")
  plt.ylabel(string)
  plt.legend([string, 'val_'+string])
  plt.show()

 

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

IMDB 巨大映画レビューデータセットは二値分類データセットです — 総てのレビューはポジティブかネガティブなセンチメントを持ちます。

TFDS を使用してデータセットをダウンロードします。データセットは作り付けの部分語字句解析器 (= subword tokenizer) を装備しています。

dataset, info = tfds.load('imdb_reviews/subwords8k', with_info=True, 
                          as_supervised=True)
train_dataset, test_dataset = dataset['train'], dataset['test']

これは部分語字句解析器ですから、それは任意の文字列を渡すことができて字句解析器はそれをトークン化します。

tokenizer = info.features['text'].encoder
print ('Vocabulary size: {}'.format(tokenizer.vocab_size))
Vocabulary size: 8185
sample_string = 'TensorFlow is cool.'

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

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

assert original_string == sample_string
Tokenized string is [6307, 2327, 4043, 4265, 9, 2724, 7975]
The original string: TensorFlow is cool.

字句解析器は単語がその辞書にない場合には文字列を部分語に分解してエンコードします。

for ts in tokenized_string:
  print ('{} ----> {}'.format(ts, tokenizer.decode([ts])))
6307 ----> Ten
2327 ----> sor
4043 ----> Fl
4265 ----> ow 
9 ----> is 
2724 ----> cool
7975 ----> .
BUFFER_SIZE = 10000
BATCH_SIZE = 64
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.padded_batch(BATCH_SIZE, train_dataset.output_shapes)

test_dataset = test_dataset.padded_batch(BATCH_SIZE, test_dataset.output_shapes)

 

モデルを作成する

tf.keras.Sequential モデルを構築して埋め込み層から始めます。埋め込み層は単語毎に一つのベクトルをストアします。呼び出されたとき、それは単語インデックスのシークエンスをベクトルのシークエンスに変換します。これらのベクトルは訓練可能です。(十分なデータ上で) 訓練後、類似の意味を持つ単語はしばしば同様のベクトルを持ちます。

このインデックス-検索は tf.keras.layers.Dense 層を通した one-hot エンコード・ベクトルを渡す等値の演算よりも遥かにより効率的です。

リカレント・ニューラルネットワーク (RNN) は要素を通した iterate によるシークエンス入力を処理します。RNN は一つのタイムスタンプからの出力をそれらの入力 — そして次へと渡します。

tf.keras.layers.Bidirectional ラッパーはまた RNN 層とともに使用できます。これは RNN 層を通して入力を foward そして backward に伝播してそれから出力を連結します。これは RNN が長期の (= long range) 依存性を学習する手助けをします。

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(tokenizer.vocab_size, 64),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(64)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

訓練プロセスを構成するために Keras モデルをコンパイルします :

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

 

モデルを訓練する

history = model.fit(train_dataset, epochs=10, 
                    validation_data=test_dataset)
Epoch 1/10
391/391 [==============================] - 75s 191ms/step - loss: 0.5536 - accuracy: 0.7140 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/10
391/391 [==============================] - 73s 187ms/step - loss: 0.3922 - accuracy: 0.8311 - val_loss: 0.5141 - val_accuracy: 0.7940
Epoch 3/10
391/391 [==============================] - 71s 182ms/step - loss: 0.3120 - accuracy: 0.8807 - val_loss: 0.4517 - val_accuracy: 0.8098
Epoch 4/10
391/391 [==============================] - 78s 199ms/step - loss: 0.2548 - accuracy: 0.9030 - val_loss: 0.4383 - val_accuracy: 0.8235
Epoch 5/10
391/391 [==============================] - 72s 185ms/step - loss: 0.2387 - accuracy: 0.9078 - val_loss: 0.4918 - val_accuracy: 0.8214
Epoch 6/10
391/391 [==============================] - 71s 182ms/step - loss: 0.1905 - accuracy: 0.9293 - val_loss: 0.4849 - val_accuracy: 0.8162
Epoch 7/10
391/391 [==============================] - 71s 182ms/step - loss: 0.1900 - accuracy: 0.9282 - val_loss: 0.5919 - val_accuracy: 0.8257
Epoch 8/10
391/391 [==============================] - 74s 190ms/step - loss: 0.1321 - accuracy: 0.9526 - val_loss: 0.6331 - val_accuracy: 0.7657
Epoch 9/10
391/391 [==============================] - 73s 187ms/step - loss: 0.3290 - accuracy: 0.8516 - val_loss: 0.6709 - val_accuracy: 0.6501
Epoch 10/10
391/391 [==============================] - 70s 180ms/step - loss: 0.3074 - accuracy: 0.8692 - val_loss: 0.5533 - val_accuracy: 0.7873
test_loss, test_acc = model.evaluate(test_dataset)

print('Test Loss: {}'.format(test_loss))
print('Test Accuracy: {}'.format(test_acc))
    391/Unknown - 19s 47ms/step - loss: 0.5533 - accuracy: 0.7873Test Loss: 0.553319326714
Test Accuracy: 0.787320017815

上のモデルはシークエンスに適用されるパディングをマスクしていません。これはパッドされたシークエンス上で訓練してパッドされていないシークエンス上でテストする場合に歪みに繋がる可能性があります。理想的にはモデルはパディングを無視することを学習するでしょうが、下で見れるようにそれは出力上で小さい効果を持つだけです。

prediction が >= 0.5 であれば、それはポジティブでそうでなければネガティブです。

def pad_to_size(vec, size):
  zeros = [0] * (size - len(vec))
  vec.extend(zeros)
  return vec
def sample_predict(sentence, pad):
  tokenized_sample_pred_text = tokenizer.encode(sample_pred_text)
  
  if pad:
    tokenized_sample_pred_text = pad_to_size(tokenized_sample_pred_text, 64)
  
  predictions = model.predict(tf.expand_dims(tokenized_sample_pred_text, 0))

  return (predictions)
# predict on a sample text without padding.

sample_pred_text = ('The movie was cool. The animation and the graphics '
                    'were out of this world. I would recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=False)
print (predictions)
[[ 0.68914342]]
# predict on a sample text with padding

sample_pred_text = ('The movie was cool. The animation and the graphics '
                    'were out of this world. I would recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=True)
print (predictions)
[[ 0.68634349]]
plot_graphs(history, 'accuracy')

plot_graphs(history, 'loss')

 

2 つあるいはそれ以上の LSTM 層をスタックする

Keras リカレント層は return_sequences コンストラクタ引数で制御される 2 つの利用可能なモードを持ちます :

  • 各タイムスタンプのための連続する出力の完全なシークエンスを返すか (shape (batch_size, timesteps, output_features) の 3D tensor)、
  • 各入力シークエンスのための最後の出力だけを返します (shape (batch_size, output_features) の 2D tensor)。
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(tokenizer.vocab_size, 64),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(
        64, return_sequences=True)),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
history = model.fit(train_dataset, epochs=10, 
                    validation_data=test_dataset)
Epoch 1/10
391/391 [==============================] - 155s 397ms/step - loss: 0.6349 - accuracy: 0.6162 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/10
391/391 [==============================] - 155s 396ms/step - loss: 0.6333 - accuracy: 0.6134 - val_loss: 0.5872 - val_accuracy: 0.6914
Epoch 3/10
391/391 [==============================] - 153s 391ms/step - loss: 0.4199 - accuracy: 0.8217 - val_loss: 0.4361 - val_accuracy: 0.8187
Epoch 4/10
391/391 [==============================] - 156s 398ms/step - loss: 0.3088 - accuracy: 0.8785 - val_loss: 0.4131 - val_accuracy: 0.8319
Epoch 5/10
391/391 [==============================] - 153s 391ms/step - loss: 0.3328 - accuracy: 0.8564 - val_loss: 0.4689 - val_accuracy: 0.7958
Epoch 6/10
391/391 [==============================] - 156s 398ms/step - loss: 0.2383 - accuracy: 0.9128 - val_loss: 0.4299 - val_accuracy: 0.8404
Epoch 7/10
391/391 [==============================] - 152s 388ms/step - loss: 0.2426 - accuracy: 0.9039 - val_loss: 0.4934 - val_accuracy: 0.8299
Epoch 8/10
391/391 [==============================] - 155s 396ms/step - loss: 0.1638 - accuracy: 0.9440 - val_loss: 0.5106 - val_accuracy: 0.8279
Epoch 9/10
391/391 [==============================] - 150s 383ms/step - loss: 0.1616 - accuracy: 0.9420 - val_loss: 0.5287 - val_accuracy: 0.8245
Epoch 10/10
391/391 [==============================] - 154s 394ms/step - loss: 0.1120 - accuracy: 0.9643 - val_loss: 0.5646 - val_accuracy: 0.8070
test_loss, test_acc = model.evaluate(test_dataset)

print('Test Loss: {}'.format(test_loss))
print('Test Accuracy: {}'.format(test_acc))
    391/Unknown - 45s 115ms/step - loss: 0.5646 - accuracy: 0.8070Test Loss: 0.564571284348
Test Accuracy: 0.80703997612
# predict on a sample text without padding.

sample_pred_text = ('The movie was not good. The animation and the graphics '
                    'were terrible. I would not recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=False)
print (predictions)
[[ 0.00393916]]
# predict on a sample text with padding

sample_pred_text = ('The movie was not good. The animation and the graphics '
                    'were terrible. I would not recommend this movie.')
predictions = sample_predict(sample_pred_text, pad=True)
print (predictions)
[[ 0.01098633]]
plot_graphs(history, 'accuracy')

plot_graphs(history, 'loss')

GRU 層 のような他の存在するリカレント層を調べてください。

 

以上



TensorFlow : Tutorials : Sequences : ニューラル機械翻訳 (seq2seq) チュートリアル

TensorFlow : Tutorials : Sequences : ニューラル機械翻訳 (seq2seq) チュートリアル (翻訳/解説)

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

* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページは、TensorFlow 本家サイトの Tutorials – Sequences – Neural Machine Translation (seq2seq) Tutorial を
翻訳した上で適宜、補足説明したものです:

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

 


 

1 章. イントロダクション

 
Sequence-to-sequence (seq2seq) モデル (Sutskever et al., 2014, Cho et al., 2014) は機械翻訳、音声認識、そしてテキスト要約のような様々なタスクで大きな成功を楽しんでいます。このチュートリアルは読者に seq2seq モデルの完全な理解を与えて競争力のある seq2seq モデルをスクラッチからどのように構築するかを示します。ニューラル機械翻訳 (NMT) のタスクにフォーカスします、これは大成功 (= wild success) した seq2seq モデルのための本当に最初のテストベッドでした。含まれるコードは軽量で、高品質で、プロダクション・レディで、そして最新の研究アイデアが組み込まれています。この目標を以下により獲得します :

  1. 最近のデコーダ / attention ラッパー API、TensorFlow 1.2 データ iterator を使用する。
  2. 私達の強い専門技術をリカレントと seq2seq モデルの構築に組み入れる。
  3. 非常にベストな NMT モデルの構築と Google の NMT (GNMT) システム のレプリカのためのティップスとトリックを提供する。

私達は簡単にレプリカが作成できるベンチマークを提供することが重要であると信じます。その結果、完全な実験結果を提供して次の公に利用可能なデータセット上でモデルを事前訓練しました :

  1. Small-スケール: IWSLT Evaluation Campaign により提供される、英越 (= English-Vietnamese) パラレルコーパス of TED talks (133K センテンス・ペア)
  2. Large-スケール: WMT Evaluation Campaign により提供される、独英パラレルコーパス (4.5M センテンス・ペア)

最初に NMT のための seq2seq モデルについての何某かの基本知識を築き上げて、vanilla NMT モデルをどのように構築して訓練するかを説明します。2 番目のパートは attention メカニズムを持つ競争力のある NMT モデルの構築の詳細に入ります。それから、TensorFlow ベストプラクティス、bidirectional (双方向) RNN、ビームサーチ、更に GNMT attention を使用してマルチ GPU へスケールアップするというような、(速度と翻訳品質の両者で) ベストな可能な NMT モデルを構築するためにティップスとトリックを議論します。

 

2 章. 基本

ニューラル機械翻訳の背景

昔に戻れば、伝統的なフレーズ・ベースの翻訳システムはソース・センテンスを複数のチャンクに分解することによりタスクを遂行して、それからそれらをフレーズ・バイ・フレーズに翻訳しました。これは翻訳出力において流暢ではなく私達、人間が、翻訳するようなものではありませんでした。私達はソース・センテンス全体を読み、その意味を理解し、そしてそれから翻訳を生成します。ニューラル機械翻訳 (NMT) はそれを模倣します!

Figure 1. エンコーダ-デコーダ・アーキテクチャ – NMT のための一般的なアプローチの例です。エンコーダはソース・センテンスを “意味” ベクトルに変換します、これは翻訳を生成するためにデコーダを通して渡されます。

 
具体的には、NMT システムは最初に “thought” ベクトル を構築するためにエンコーダを使用して、ソース・センテンスを読みます、それは数字のシークエンスでセンテンスの意味を表します ; それからデコーダは、Figure 1 で示されるように翻訳を吐くためにセンテンス・ベクトルを処理します。これはしばしばエンコーダ-デコーダ・アーキテクチャとして参照されます。この方法で、NMT は伝統的なフレーズ・ベースのアプローチの局所翻訳問題に対処します : それは言語の long-range dependencies, e.g., gender agreements; シンタクス構造; etc., を捕捉することができて、そして Google Neural Machine Translation systems でデモされるような遥かにより流暢な翻訳を生成します。

NMT モデルはそれらの正確なアーキテクチャの観点から様々です。シーケンシャル・データのための自然な選択はリカレント・ニューラルネットワーク (RNN) で、殆どの NMT モデルで使用されます。通常は RNN はエンコーダとデコーダの両者のために使用されます。RNN モデルは、けれども、以下の観点から異なります : (a) 方向性 – 単方向 or 双方向 ; (b) 深さ – シングル- or マルチ層 ; そして (c) タイプ – しばしば vanilla RNN、Long Short-term Memory (LSTM)、または gated recurrent unit (GRU)。興味ある読者は RNN と LSTM についての更なる情報はこの ブログ投稿 で見つけることができます。

このチュートリアルでは、サンプルとして深層マルチ層 RNN を考えます、これは単方向でリカレント・ユニットとして LSTM を使用します。そのようなモデルのサンプルを Figure 2 で示します。このサンプルでは、ソース・センテンス “I am a student” をターゲット・センテンス “Je suis étudiant” に翻訳するモデルを構築します。高いレベルでは、NMT モデルは 2 つのリカレント・ニューラルネットワークから成ります : エンコーダ RNN はどのような予測をすることもなく単純に入力ソース単語を消費します ; デコーダは、一方で、次の単語を予測しながらターゲット・センテンスを処理します。

更なる情報のためには、読者は Luong (2016) を参照してください、これはこのチュートリアルがベースとしているものです。

Figure 2. ニューラル機械翻訳 – ソース・センテンス “I am a student” をターゲット・センテンス “Je suis étudiant” に翻訳するために提案された深層リカレント・アーキテクチャのサンプルです。ここで、”<s>” はデコーディング・プロセスの開始をマークしてその一方で “</s>” はデコーダに停止を伝えます。

 

チュートリアルをインストールする

このチュートリアルをインストールするためには、TensorFlow を貴方のシステム上にインストールする必要があります。このチュートリアルは TensorFlow Nightly を必要とします。TensorFlow をインストールするためには、ここのインストール手順 に従ってください。

ひとたび TensorFlow がインストールされれば、次を実行することでこのチュートリアルのソースコードをダウンロードできます :

git clone https://github.com/tensorflow/nmt/

 

訓練 – 最初の NMT システムをどのようにビルドするか

最初に具体的なコード・スニペットで NMT モデルの構築の中心へと飛び込みましょう、それを通して Figure 2 をより詳細に説明します。データ準備とフルコードは後に回します。このパートはファイル model.py を参照します。

ボトム層では、エンコーダとデコーダ RNN は入力として次を受け取ります : 最初に、ソース・センテンス、それから境界マーカー “\<s>”、これはエンコーディングからデコーディング・モードへの移行を示します、そしてターゲット・センテンスです。訓練のためには、次の tensor をシステムに供給します、これらは time-major 形式で単語インデックスを含みます :

  • encoder_inputs [max_encoder_time, batch_size]: ソース入力単語。
  • decoder_inputs [max_decoder_time, batch_size]: ターゲット入力単語。.
  • decoder_outputs [max_decoder_time, batch_size]: ターゲット出力単語、これらは右に追加された end-of-sentence タグを伴う左に 1 時間ステップシフトされた decoder_inputs です。

ここでは効率のために、複数のセンテンス (batch_size) で一度に訓練します。テスティングは少し異なりまるので、それは後で議論しましょう。

 

埋め込み

単語のカテゴリー的な性質が与えられたとき、モデルは対応する単語表現を取得するために最初にソースとターゲット埋め込みを調べなければなりません。この埋め込み層を動作させるために、最初に各言語のために語彙が選択されます。通常は、語彙サイズ V が選択され、そして最も頻度の高い V 単語だけが一意に扱われます。総ての他の単語は “unknown (未知)” トークンに変換されて総て同じ埋め込みを得ます。埋め込み重み、言語毎に 1 セット、は通常は訓練の間に学習されます。

# Embedding
embedding_encoder = variable_scope.get_variable(
    "embedding_encoder", [src_vocab_size, embedding_size], ...)
# Look up embedding:
#   encoder_inputs: [max_time, batch_size]
#   encoder_emb_inp: [max_time, batch_size, embedding_size]
encoder_emb_inp = embedding_ops.embedding_lookup(
    embedding_encoder, encoder_inputs)

同様に、embedding_decoder と decoder_emb_inp を構築することができます。word2vec や Glove ベクトルのような事前訓練された単語表現で埋め込み重みを初期化することを選択できることに注意してください。一般に、訓練データの巨大な総量が与えられた場合にはスクラッチからこれらの埋め込みを学習できます。

 

エンコーダ

ひとたび取得されれば、単語埋め込みはそれから入力としてメイン・ネットワークに供給されます、これは 2 つのマルチ層 RNN から成ります – ソース言語のためのエンコーダとターゲット言語のためのデコーダです。これらの 2 つの RNN は、原理的には、同じ重みを共有できます ; けれども、実際には、2 つの異なる RNN パラメータをしばしば使用します (そのようなモデルは巨大な訓練データセットにフィットするときより良いジョブを行ないます)。エンコーダ RNN はその開始状態としてゼロ・ベクトルを使用して次のように構築されます :

# Build RNN cell
encoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)

# Run Dynamic RNN
#   encoder_outputs: [max_time, batch_size, num_units]
#   encoder_state: [batch_size, num_units]
encoder_outputs, encoder_state = tf.nn.dynamic_rnn(
    encoder_cell, encoder_emb_inp,
    sequence_length=source_sequence_length, time_major=True)

センテンスは異なる長さを持つことに注意してください、無駄な計算を回避するために、dynamic_rnn に正確なソース・センテンスの長さを source_sequence_length を通して伝えます。私達の入力は time major ですので、time_major=True を設定します。ここでは、シングル層 LSTM, encoder_cell だけを構築します。どのようにマルチ層 LSTM を構築し、dropout を追加し、そして attention を使用するかについては後のセクションで説明します。

 

デコーダ

デコーダもまたソース情報へのアクセスを持つ必要があり、そしてそれを達成する一つの単純な方法はエンコーダの最後の隠れ状態, encoder_state でそれを初期化することです。Figure 2 で、ソース単語 “student” における隠れ状態をデコーダ側に渡します。

# Build RNN cell
decoder_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
# Helper
helper = tf.contrib.seq2seq.TrainingHelper(
    decoder_emb_inp, decoder_lengths, time_major=True)
# Decoder
decoder = tf.contrib.seq2seq.BasicDecoder(
    decoder_cell, helper, encoder_state,
    output_layer=projection_layer)
# Dynamic decoding
outputs, _ = tf.contrib.seq2seq.dynamic_decode(decoder, ...)
logits = outputs.rnn_output

ここで、このコードの中心パートは BasicDecoder, decoder, で、これは入力として (encoder_cell と同様の) decoder_cell、ヘルパー、そして前の encoder_state を受け取ります。デコーダとヘルパーを分離することにより、異なるコードベースを再利用することができます、e.g., TrainingHelper は greedy デコーディングを行なうために GreedyEmbeddingHelper で置き換えられます。それ以上は helper.py を見てださい。

最後に、projection_layer に言及していませんでしたが、これはトップの隠れ状態を次元 V のロジット・ベクトルに変える dense 行列です。このプロセスを Figure 2 のトップに示します。

projection_layer = layers_core.Dense(
    tgt_vocab_size, use_bias=False)

 

損失

上でロジットが与えられ、訓練損失を計算する用意ができました :

crossent = tf.nn.sparse_softmax_cross_entropy_with_logits(
    labels=decoder_outputs, logits=logits)
train_loss = (tf.reduce_sum(crossent * target_weights) /
    batch_size)

ここで、target_weights は decoder_outputs と同じサイズの 0-1 (= zero-one) 行列です。それはターゲット・シークエンス長の外側の位置のパディングを値 0 でマスクします。

重要なノート: 損失を batch_size で除算するので、ハイパーパラメータは batch_size に対して “不変 (= invariant)” であることは指摘する価値があります。人によっては損失を (batch_size * num_time_steps) で分割しますが、これは短いセンテンス上のエラーを軽視します。より微妙なことに、(前者に適用された) 私達のハイパーパラメータは後者の方法のためには使用できません。例えば、両者のアプローチが 1.0 の学習率で SGD を使用する場合、後者のアプローチは事実上 1 / num_time_steps の遥かに小さい学習率を使用します。

 

勾配計算 & 最適化

私達は今 NMT モデルの forward パスを定義しました。backpropagation パスを計算することは単に数行のコードの事柄です :

# Calculate and clip gradients
params = tf.trainable_variables()
gradients = tf.gradients(train_loss, params)
clipped_gradients, _ = tf.clip_by_global_norm(
    gradients, max_gradient_norm)

RNN の訓練における重要なステップの一つは勾配クリッピングです。ここで、global norm でクリップします。max value, max_gradient_norm はしばしば 5 or 1 のような値に設定されます。最後のステップは optimizer の選択です。Adam optimizer は一般的な選択です。学習率もまた選択します。learning_rate の値は通常は 0.0001 から 0.001 の範囲にあります ; そして訓練が進むにつれて減少するように設定できます。

# Optimization
optimizer = tf.train.AdamOptimizer(learning_rate)
update_step = optimizer.apply_gradients(
    zip(clipped_gradients, params))

私達自身の実験では、標準的な SGD (tf.train.GradientDescentOptimizer) を低下する学習率スケジュールで使用し、これはより良いパフォーマンスを生成します。ベンチマーク を見てください。

 

ハンズオン – NMT モデルを訓練しましょう

ベトナム語から英語に翻訳する、私達の本当に最初の NMT モデルを訓練しましょう、コードのエントリ・ポイントは nmt.py です。

この課題のために TED talks の small-スケールのパラレルコーパス ((133K 訓練サンプル) を使用します。ここで使用されるデータの総ては https://nlp.stanford.edu/projects/nmt/ で見つかります。tst2012 を dev データセットとして、そして tst2013 をテスト・データセットとして使用します。

NMT を訓練するためのデータをダンロードする次のコマンドを実行します :\
nmt/scripts/download_iwslt15.sh /tmp/nmt_data

訓練を開始するためには次のコマンドを実行します :

mkdir /tmp/nmt_model
python -m nmt.nmt \
    --src=vi --tgt=en \
    --vocab_prefix=/tmp/nmt_data/vocab  \
    --train_prefix=/tmp/nmt_data/train \
    --dev_prefix=/tmp/nmt_data/tst2012  \
    --test_prefix=/tmp/nmt_data/tst2013 \
    --out_dir=/tmp/nmt_model \
    --num_train_steps=12000 \
    --steps_per_stats=100 \
    --num_layers=2 \
    --num_units=128 \
    --dropout=0.2 \
    --metrics=bleu

上のコマンドは 128-dim 隠れユニットと埋め込みを持つ 2-層 LSTM seq2seq モデルを 12 エポック訓練します。0.2 の dropout 値 (確率 0.8 を保つ) を使用します。もしエラーがないのであれば、訓練につれての perplexity の減少とともに 下に似たログを見るはずです。

# First evaluation, global step 0
  eval dev: perplexity 17193.66
  eval test: perplexity 17193.27
# Start epoch 0, step 0, lr 1, Tue Apr 25 23:17:41 2017
  sample train data:
    src_reverse:   Điều đó , dĩ nhiên , là câu chuyện trích ra từ học thuyết của Karl Marx .
    ref: That , of course , was the  distilled from the theories of Karl Marx .   
  epoch 0 step 100 lr 1 step-time 0.89s wps 5.78K ppl 1568.62 bleu 0.00
  epoch 0 step 200 lr 1 step-time 0.94s wps 5.91K ppl 524.11 bleu 0.00
  epoch 0 step 300 lr 1 step-time 0.96s wps 5.80K ppl 340.05 bleu 0.00
  epoch 0 step 400 lr 1 step-time 1.02s wps 6.06K ppl 277.61 bleu 0.00
  epoch 0 step 500 lr 1 step-time 0.95s wps 5.89K ppl 205.85 bleu 0.00

より詳細は train.py を見てください。

訓練の間にモデルの要約を見るために TensorBoard を開始することもできます :

tensorboard --port 22222 --logdir /tmp/nmt_model/

英語からベトナム語への反対の方向の訓練は単純に次の変更で成されます:\ –src=en –tgt=vi

 

推論 – 翻訳をどのように生成するか

NMT モデルを訓練している一方で (そしてひとたびモデルを訓練したのであれば)、以前に見ていないソース・センテンスが与えられたときに翻訳を得ることができます。このプロセスは推論と呼ばれます。訓練と推論 (テスティング) の間には明確な区別があります : 推論時には、ソース・センテンス, i.e., encoder_inputsへのアクセスを持つのみです。デコーディングを遂行するためには多くの方法があります。デコーディング・メソッドは greedy、サンプリングそしてビームサーチ・デコーディングを含みます。ここでは、greedy デコーディング・ストラテジーを議論します。

アイデアは単純でそれを Figure 3 で示します :

  1. encoder_state を得るために訓練の間と同じ方法でソース・センテンスを依然としてエンコードします、そしてこの encoder_state はデコーダを初期化するために使用されます。
  2. デコーディング (翻訳) プロセスはデコーダが開始シンボル “\<s>” (コードでは tgt_sos_id として参照) を受け取るとすぐに開始されます;
  3. デコーダ側の各 time ステップに対して、RNN 出力をロジットのセットとして扱います。最も尤もらしい単語、最大ロジット値に関連する id、を出力された単語として選択します (これは “greedy” の挙動です)。Figure 3 のサンプルでは、最初のデコーディング・ステップで単語 “moi” が最も高い翻訳確率を持ちます。それからこの単語を次の time ステップへの入力として供給します。
  4. このプロセスが出力シンボルとして文末マーカー “\</s>” (コードでは tgt_eos_id として参照) が生成されるまで続きます。

Figure 3. Greedy デコーディング – 訓練された NMT モデルが greedy サーチを使用してソース・センテンス “je suis étudiant” のためにどのように翻訳を生成するかのサンプルです。

ステップ 3 は推論を訓練とは異なるものにするものです。入力として常に正しいターゲット単語を供給する代わりに、推論はモデルにより推測される単語を使用します。ここに greedy デコーディングを成し遂げるコードがあります。それは訓練デコーダに非常に似ています。

# Helper
helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(
    embedding_decoder,
    tf.fill([batch_size], tgt_sos_id), tgt_eos_id)

# Decoder
decoder = tf.contrib.seq2seq.BasicDecoder(
    decoder_cell, helper, encoder_state,
    output_layer=projection_layer)
# Dynamic decoding
outputs, _ = tf.contrib.seq2seq.dynamic_decode(
    decoder, maximum_iterations=maximum_iterations)
translations = outputs.sample_id

ここで、TrainingHelper の代わりに GreedyEmbeddingHelper を使用します。ターゲット・シークエンス長を前もって知らないので、翻訳の長さを制限するために maximum_iterations を使用します。一つの経験則はソース・センテンス長の 2 倍までデコードすることです。

maximum_iterations = tf.round(tf.reduce_max(source_sequence_length) * 2)

モデルを訓練したら、今では推論ファイルを作成して幾つかのセンテンスを翻訳することができます :

cat > /tmp/my_infer_file.vi
# (copy and paste some sentences from /tmp/nmt_data/tst2013.vi)

python -m nmt.nmt \
    --out_dir=/tmp/nmt_model \
    --inference_input_file=/tmp/my_infer_file.vi \
    --inference_output_file=/tmp/nmt_model/output_infer

cat /tmp/nmt_model/output_infer # To view the inference as output

訓練チェックポイントが存在する限りはモデルがまだ訓練中でも上のコマンドはまた実行できることに注意してください。より詳細は inference.py を見てください。

 

3 章. Intermediate

 
最も基本的な seq2seq モデルを通り抜けました、更に進みましょう!先端技術のニューラル翻訳システムを構築するためには、更なる “秘密のソース” が必要です : attention メカニズム、これは最初に Bahdanau et al., 2015 により導入され、後に Luong et al., 2015 他により洗練されました。attention メカニズムの鍵となるアイデアは、翻訳するときに関係するソースコンテンツに “注意 (attention)” を払うことによって、ターゲットとソース間の直接的なショートカット接続を確立することです。attention メカニズムの素晴らしい副産物は (Figure 4 で示されるような) ソースとターゲット・センテンス間の可視化しやすいアラインメント行列です。

Figure 4. Attention 可視化 – ソースとターゲット・センテンス間のアラインメントの例です。画像は (Bahdanau et al., 2015) から取られています。

vanilla seq2seq モデルでは、デコーディング・プロセスが始まるときエンコードからの最後のソース状態をデコーダに渡すことを思い出してください。これは短いあるいは中ぐらいの長さのセンテンスについては上手く動作します ; けれども、長いセンテンスに対しては、単一の固定長の隠れ状態は情報ボトルネックになります。ソース RNN で計算された隠れ状態の総てを捨てる代わりに、attention メカニズムはデコーダにそれらを覗き見ることを可能にします (それらをソース情報の動的メモリとして扱います)。
そのようにすることで、attention メカニズムはより長いセンテンスの翻訳を改善します。最近では、attention メカニズムはデファクト・スタンダードで多くの他のタスクに成功的に適用されてきています (画像キャプション生成、音声認識、そしてテキスト要約)。

 

Attention メカニズムの背景

(Luong et al., 2015) で提案された attention メカニズムの実例を今記述します、これはオープンソースのツールキット含む幾つかの先端技術システムとこのチュートリアルの TF seq2seq API で使用されているものです。attention メカニズムの他の変形へのコネクションもまた提供します。

Figure 5. Attention メカニズム – (Luong et al., 2015) で説明されている attention ベース NMT システムのサンプルです。attention 計算の最初のステップに詳細にハイライトしています。明確化のため、埋め込みと投射層 (= projection layer) は Figure (2) で示していません。

Figure 5 で図示されるように、attention 計算は総てのデコーダ time ステップで発生します。それは次の段階から成ります :

  1. (Figure 4 内で可視化される) attention 重みを導出するために現在のターゲット隠れ状態を総てのソース状態と比較します。
  2. attention 重みに基づいてソース状態の重み付けられた平均としてコンテキスト・ベクトルを計算します。
  3. 最終的な attention ベクトルを生成するためにコンテキスト・ベクトルを現在のターゲット隠れ状態と結合します。
  4. attention ベクトルは次の time ステップへの入力として供給されます (input feeding)。最初の 3 つのステップは下の等式によって要約できます :

 
ここで、関数 score はターゲット隠れ状態 $h_t$ をソース隠れ状態 $\overline{h}_s$ の各々と比較するために使用されて、結果は attention 重み (ソース位置に渡る分布) を生成するために正規化されます。scoring 関数の様々な選択があります ; ポピュラーな scoreing 関数は Eq. (4) で与えられる multiplicative and additive 形式を含みます。ひとたび計算されれば、attention ベクトルは softmax ロジットと損失を導出するために使用されます。これは vanilla seq2seq モデルのトップ層におけるターゲット隠れ状態に類似しています。関数 f はまた他の形式を取ることもできます。

attention メカニズムの様々な実装は attention_wrapper.py で見つけられます。

 

attention メカニズムでは何が重要でしょう?

上の等式でヒントが与えられているように、多くの異なる attention 変種があります。これらの変種は scoring 関数と attention 関数の形式、そして (Bahdanau et al., 2015) で元々提案されているように scoring 関数内で代わりに前の状態が使用されるか否かに依拠します。実証的に、私達は特定の選択だけが重要であることを見出しました。1 番目に、attention の基本的な形式、i.e. ターゲットとソースの直接コネクションが存在している必要があります。2 番目に、(Luong et al., 2015) で説明されているように過去の attention 決定についてネットワークに伝えるために attention ベクトルを次の timestep に供給することは重要です。最後に、scoring 関数の選択はしばしば異なるパフォーマンスの結果になります。詳細は (後述の) ベンチマーク結果のセクションを見てください。

 

Attention ラッパー API

AttentionWrapper の私達の実装では、(Weston et al., 2015) からメモリ・ネットワークのワークの幾つかの用語を借りています。読み書き可能なメモリを持つ代わりに、このチュートリアルで提案される attention メカニズムは read-only メモリです。特に、ソース隠れ状態 (あるいはそれらの変換されたバージョン、i.e. $W\overline{h}_s$ in Luong’s scoring スタイルまたは $W_2\overline{h}_s$ in Bahdanau’s scoring スタイル) のセットは “メモリ” として参照されます。各 time ステップで、メモリのどの部分を読むかを決定するために現在のターゲット隠れ状態を “query (問合せ)” として使用します。通常は、query は個々のメモリスロットに対応するキーと比較される必要があります。attention メカニズムの上の提示では、ソース隠れ状態 (あるいはそれらの変換されたバージョン、$W_1h_t$ i.e. in Bahdanau’s scoring スタイル) のセットを “キー” として偶々使用しています。他の attention 形式を導出するためにこのメモリ・ネットワーク用語によりインスパイアされるかもしれません!

attention ラッパーのおかげで、vanilla seq2seq コードを attention で拡張することは自明です。この部分はファイル attention_model.py に該当します。

最初に、attention メカニズム, e.g., from (Luong et al., 2015) を定義する必要があります :

# attention_states: [batch_size, max_time, num_units]
attention_states = tf.transpose(encoder_outputs, [1, 0, 2])

# Create an attention mechanism
attention_mechanism = tf.contrib.seq2seq.LuongAttention(
    num_units, attention_states,
    memory_sequence_length=source_sequence_length)

前のエンコーダのセクションでは、encoder_outputs はトップ層における総てのソース隠れ状態のセットで [max_time, batch_size, num_units] の shape を持ちます (何故ならば効率のために time_major を True に設定して dynamic_rnn を使用するからです)。attention メカニズムのためには、渡される “メモリ” が batch major であることを確かなものにする必要があります、そして attention_states を transpose する必要があります。 (non-padding 位置だけに渡り) attention 重みが適切に正規化されることを確かなものにするために source_sequence_length を attention メカニズムに渡します。attention メカニズムを定義しましたので、デコーディング・セルをラップするために AttentionWrapper を使用します :

decoder_cell = tf.contrib.seq2seq.AttentionWrapper(
    decoder_cell, attention_mechanism,
    attention_layer_size=num_units)

コードの残りはデコーダのセクション内のものと殆ど同じです!

 

ハンズオン – attention ベースの NMT モデルを構築する

attention を有効にするには、訓練の間 attention フラグとして luong, scaled_luong, bahdanau または normed_bahdanau の一つを使用する必要があります。フラグはどの attention メカニズムを使用するかを指定します。更に、attention モデルのための新しい辞書を作成する必要がありますので、前に訓練した基本 NMT モデルは再利用しません。

訓練を開始するためには次のコマンドを実行します :

mkdir /tmp/nmt_attention_model

python -m nmt.nmt \
    --attention=scaled_luong \
    --src=vi --tgt=en \
    --vocab_prefix=/tmp/nmt_data/vocab  \
    --train_prefix=/tmp/nmt_data/train \
    --dev_prefix=/tmp/nmt_data/tst2012  \
    --test_prefix=/tmp/nmt_data/tst2013 \
    --out_dir=/tmp/nmt_attention_model \
    --num_train_steps=12000 \
    --steps_per_stats=100 \
    --num_layers=2 \
    --num_units=128 \
    --dropout=0.2 \
    --metrics=bleu

訓練後、推論のための新しい out_dir を伴う同じ推論コマンドを利用できます :

python -m nmt.nmt \
    --out_dir=/tmp/nmt_attention_model \
    --inference_input_file=/tmp/my_infer_file.vi \
    --inference_output_file=/tmp/nmt_attention_model/output_infer

 

4 章. ティップス & トリック

 

訓練、評価、そして推論グラフを構築する

TensorFlow で機械学習モデルを構築するとき、3 つの分離したグラフを構築することがしばしば最善です :

  • 訓練グラフ、これは :
    • ファイル/外部入力のセットから入力データをバッチ処理し、バケットに入れ、そしておそらくはサブサンプリングします。
    • forward と backprop ops を含みます。
    • optimizer を構築して、訓練 op を追加します。
  • 評価グラフ、これは :
    • ファイル/外部入力のセットから入力データをバッチ処理し、バケットに入れます。
    • 訓練 forward ops と訓練では使用されない追加の評価 ops を含みます。
  • 推論グラフ、これは :
    • 入力データのバッチ処理はしないかもしれません。
    • 入力データのサブサンプリングやバケット処理は行ないません。
    • プレースホルダーから入力データを読みます (データは feed_dict を通して、あるいは C++ TensorFlow serving バイナリからデータはグラフに直接供給できます)。
    • モデル forward ops のサブセットと、おそらくは session.run 呼び出し間の状態をストアするため追加の特別な入出力を含みます。

分離したグラフの構築は幾つかの利点があります :

  • 推論グラフは通常は他の 2 つと非常に異なりますので、それを別に構築するのは意味があります。
  • 評価グラフはより単純になります、何故ならばそれはもはや追加の逆伝播 ops の総てを持たないからです。
  • データ供給は各グラフに対して別々に実装可能です。
  • 変数 reuse は遥かにより単純です。例えば、評価グラフにおいて reuse=True を持つ変数スコープを再オープンする必要はありません、それは単に訓練モデルはこれらの変数を既に作成しているからです。従って reuse= 引数を至るところに撒き散らすことなしに同じコードが再利用できます。
  • 分散訓練では、別々のワーカーに訓練、評価、そして推論を遂行させることはよくあります。これらはそれら自身のグラフを構築する必要がいずれにせよあります。従ってシステムをこの方法で構築することは分散訓練のために貴方を準備させることになります。

複雑さの主な源は単一のマシン設定で 3 つのグラフに渡り変数をどのように共有するかとなるでしょう。これは各グラフのために別々のセッションを使用することで解決されます。訓練セッションは定期的にチェックポイントをセーブし、そして評価セッションと推論セッションはチェックポイントからパラメータをレストアします。下のサンプルは 2 つのアプローチ間の主な違いを示します。

Before: 単一グラフ内の 3 つのモデルと単一セッションの共有

with tf.variable_scope('root'):
  train_inputs = tf.placeholder()
  train_op, loss = BuildTrainModel(train_inputs)
  initializer = tf.global_variables_initializer()

with tf.variable_scope('root', reuse=True):
  eval_inputs = tf.placeholder()
  eval_loss = BuildEvalModel(eval_inputs)

with tf.variable_scope('root', reuse=True):
  infer_inputs = tf.placeholder()
  inference_output = BuildInferenceModel(infer_inputs)

sess = tf.Session()

sess.run(initializer)

for i in itertools.count():
  train_input_data = ...
  sess.run([loss, train_op], feed_dict={train_inputs: train_input_data})

  if i % EVAL_STEPS == 0:
    while data_to_eval:
      eval_input_data = ...
      sess.run([eval_loss], feed_dict={eval_inputs: eval_input_data})

  if i % INFER_STEPS == 0:
    sess.run(inference_output, feed_dict={infer_inputs: infer_input_data})

After: 3 つのグラフ内の 3 つのモデル、3 つのセッションが同じ変数を共有する

train_graph = tf.Graph()
eval_graph = tf.Graph()
infer_graph = tf.Graph()

with train_graph.as_default():
  train_iterator = ...
  train_model = BuildTrainModel(train_iterator)
  initializer = tf.global_variables_initializer()

with eval_graph.as_default():
  eval_iterator = ...
  eval_model = BuildEvalModel(eval_iterator)

with infer_graph.as_default():
  infer_iterator, infer_inputs = ...
  infer_model = BuildInferenceModel(infer_iterator)

checkpoints_path = "/tmp/model/checkpoints"

train_sess = tf.Session(graph=train_graph)
eval_sess = tf.Session(graph=eval_graph)
infer_sess = tf.Session(graph=infer_graph)

train_sess.run(initializer)
train_sess.run(train_iterator.initializer)

for i in itertools.count():

  train_model.train(train_sess)

  if i % EVAL_STEPS == 0:
    checkpoint_path = train_model.saver.save(train_sess, checkpoints_path, global_step=i)
    eval_model.saver.restore(eval_sess, checkpoint_path)
    eval_sess.run(eval_iterator.initializer)
    while data_to_eval:
      eval_model.eval(eval_sess)

  if i % INFER_STEPS == 0:
    checkpoint_path = train_model.saver.save(train_sess, checkpoints_path, global_step=i)
    infer_model.saver.restore(infer_sess, checkpoint_path)
    infer_sess.run(infer_iterator.initializer, feed_dict={infer_inputs: infer_input_data})
    while data_to_infer:
      infer_model.infer(infer_sess)

後者のアプローチは分散バージョンに変換されるための「準備」ができていることに注意してください。

新しいアプローチの一つの他の違いは各 session.run 呼び出しでデータを供給するために feed_dicts を使用する (そして関連してバッチ処理、バケット処理、そしてデータの操作を遂行する) 代わりに、stateful iterator オブジェクトを使用します。これらの iterator は入力パイプラインを単一マシンと分散設定の両者において遥かにより簡単にします。次のセクションで (TensorFlow 1.2 で導入された) 新しい入力データパイプラインをカバーします。

 

データ入力パイプライン

TensorFlow 1.2 の前には、ユーザは TensorFlow 訓練と評価パイプラインにデータを供給するために 2 つのオプションを持ちました :

  1. 各訓練 session.run 呼び出しで feed_dict を通してデータを直接供給する。
  2. tf.train (e.g. tf.train.batch) と tf.contrib.train の queueing メカニズムを使用する。
  3. tf.contrib.learn or tf.contrib.slim のような高位フレームワークからのヘルパーを使用する (これは事実上 #2 を使用しています)。

最初のアプローチは TensorFlow に精通していないか、Python でのみ行える変わった入力変更を行なう (i.e. 彼ら自身のミニバッチ queueing) 必要があるユーザのためにより容易です。2 番目と 3 番目のアプローチはより標準的ですが少し柔軟性に欠けます : それらは複数の python スレッドを開始することも要求します (queue runners)。更に、誤って使用される場合、キューはデッドロックか不明瞭なエラーメッセージに繋がります。それにもかかわらず、キューは feed_dict を使用するよりも著しくより効率的で単一マシンと分散訓練の両者に対して標準的です。

TensorFlow 1.2 からは、データを TensorFlow モデルに読み込むために利用可能な新しいシステムがあります : tf.data モジュールで見つかる、データセット iterator です。データ iterator は柔軟で、それについて考えて操作することが容易で、TensorFlow C++ ランタイムを利用して効率性とマルチスレッドを提供します。

dataset はバッチデータ Tensor、ファイル名、または複数のファイル名を含む Tensor から作成できます。幾つかのサンプルです :

# Training dataset consists of multiple files.
train_dataset = tf.data.TextLineDataset(train_files)

# Evaluation dataset uses a single file, but we may
# point to a different file for each evaluation round.
eval_file = tf.placeholder(tf.string, shape=())
eval_dataset = tf.data.TextLineDataset(eval_file)

# For inference, feed input data to the dataset directly via feed_dict.
infer_batch = tf.placeholder(tf.string, shape=(num_infer_examples,))
infer_dataset = tf.data.Dataset.from_tensor_slices(infer_batch)

総ての dataset は入力処理を通して同様に扱えます。これはデータの読み込みとクリーンアップ、(訓練と評価の場合) バケット処理、フィルタリング、そしてバッチ処理を含みます。

各センテンスを単語文字列のベクトルに変換するため、例えばですが、dataset map 変換を使用します :

dataset = dataset.map(lambda string: tf.string_split([string]).values)

それから各センテンス・ベクトルをベクトルとその動的長さを含むタプルに切り替えることができます :

dataset = dataset.map(lambda words: (words, tf.size(words))

最後に、各センテンス上で語彙検索を遂行することができます。検索テーブル・オブジェクト・テーブルが与えられる場合、この map は最初のタプル要素を文字列ベクトルから整数ベクトルに変換します。

dataset = dataset.map(lambda words, size: (table.lookup(words), size))

2 つのデータセットの結合もまた簡単です。もし 2 つのファイルが互いの line-by-line 変換を含みそして各々一つがそれ自身のデータセットに読み込まれる場合、zipped lines のタプルを含む新しい dataset が以下を通して作成されます :

source_target_dataset = tf.data.Dataset.zip((source_dataset, target_dataset))

可変長のセンテンスのバッチ処理もストレートです。次の変換は source_target_dataset から batch_size 要素をバッチ処理し、各バッチでソースとターゲット・ベクトルをそれぞれ最も長いソースとターゲット・ベクトルの長さにパディングします。

batched_dataset = source_target_dataset.padded_batch(
        batch_size,
        padded_shapes=((tf.TensorShape([None]),  # source vectors of unknown size
                        tf.TensorShape([])),     # size(source)
                       (tf.TensorShape([None]),  # target vectors of unknown size
                        tf.TensorShape([]))),    # size(target)
        padding_values=((src_eos_id,  # source vectors padded on the right with src_eos_id
                         0),          # size(source) -- unused
                        (tgt_eos_id,  # target vectors padded on the right with tgt_eos_id
                         0)))         # size(target) -- unused

この dataset から吐かれた値はネストされたタプルでその tensor はサイズ batch_size の一番左の次元を持ちます。その構造は :

  • iterator[0][0] はバッチ化されてパディングされたソース・センテンス行列を持ちます。
  • iterator[0][1] はバッチ化されたソース・サイズ・ベクトルを持ちます。
  • iterator[1][0] はバッチ化されてパディングされたターゲット・センテンス行列を持ちます。
  • iterator[1][1] はターゲット・サイズ・ベクトルを持ちます。

最後に、同じようなサイズのソース・センテンスを一緒にバッチ化するバケット処理もまた可能です。
より詳細と完全な実装のためにはファイル utils/iterator_utils.py を見てください。

Dataset からデータを読むにはコードの 3 行が必要です : iterator を作成し、その値を取得して、そしてそれを初期化します。

batched_iterator = batched_dataset.make_initializable_iterator()

((source, source_lengths), (target, target_lengths)) = batched_iterator.get_next()

# At initialization time.
session.run(batched_iterator.initializer, feed_dict={...})

iterator がひとたび初期化されれば、ソースまたはターゲット・テンソルにアクセスする総ての session.run 呼び出しは基礎的な dataset から次のミニバッチを要求するでしょう。

 

より良い NMT モデルのための他の詳細

Bidirectional RNN

エンコーダ側の双方向性は一般的に (より多くの層が使用されるので何某かの速度の低下を伴い) 良いパフォーマンスを与えます。ここでは単一の bidirectional 層を持つエンコーダをどのように構築するかの単純化されたサンプルを与えます :

# Construct forward and backward cells
forward_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)
backward_cell = tf.nn.rnn_cell.BasicLSTMCell(num_units)

bi_outputs, encoder_state = tf.nn.bidirectional_dynamic_rnn(
    forward_cell, backward_cell, encoder_emb_inp,
    sequence_length=source_sequence_length, time_major=True)
encoder_outputs = tf.concat(bi_outputs, -1)

変数 encoder_outputs と encoder_state はエンコーダのセクションと同じ方法で使用できます。マルチ bidirectional 層のためには、encoder_state を少し操作する必要があることに注意してください、より詳細のためには model.py, メソッド _build_bidirectional_rnn() を見てください。

 

ビーム・サーチ

greedy デコーディングが非常に合理的な翻訳品質を与える一方で、ビーム・サーチ・デコーダはパフォーマンスを更にブーストします。ビーム・サーチの考えは、翻訳時にトップ候補の小さなセット回りを保持することにより総ての可能な翻訳の検索空間をより良く探検することです。ビームのサイズはビーム幅 (= beam width) と呼ばれます : 例えばサイズ 10 の最小のビーム幅で一般的には十分です。更なる情報のためには、読者は Neubig, (2017) のセクション 7.2.3 を参照してください。ここにはビーム・サーチがどのように成されるかのサンプルがあります :

# Replicate encoder infos beam_width times
decoder_initial_state = tf.contrib.seq2seq.tile_batch(
    encoder_state, multiplier=hparams.beam_width)

# Define a beam-search decoder
decoder = tf.contrib.seq2seq.BeamSearchDecoder(
        cell=decoder_cell,
        embedding=embedding_decoder,
        start_tokens=start_tokens,
        end_token=end_token,
        initial_state=decoder_initial_state,
        beam_width=beam_width,
        output_layer=projection_layer,
        length_penalty_weight=0.0)

# Dynamic decoding
outputs, _ = tf.contrib.seq2seq.dynamic_decode(decoder, ...)

デコーダのセクションと同様に、同じ dynamic_decode() API 呼び出しが使用されることに注意してください。ひとたびデコードされれば、次のように翻訳にアクセスできます :

translations = outputs.predicted_ids
# Make sure translations shape is [batch_size, beam_width, time]
if self.time_major:
   translations = tf.transpose(translations, perm=[1, 2, 0])

更なる詳細のためには model.py, メソッド _build_decoder() を見てください。

 

ハイパーパラメータ

追加のパフォーマンスに繋がることができる幾つかのハイパーパラメータがあります。ここでは、私達獅子にの経験に基づいて幾つかをリストします [ Disclaimers: 他の人は私達が書いたことに同意しないかもしれません! ]。

Optimizer: Adam が “unfamiliar” アーキテクチャに対して合理的な結果に繋がることができる一方で、もし SGD で訓練可能であればスケジューリングを伴う SGD は一般的により良いパフォーマンスに繋がるでしょう。

Attention: Bahdanau-スタイル attention は、上手く動作するためにはしばしばエンコーダ側で双方向性を要求します ; その一方で Luong-スタイル attention は異なる設定のために上手く動作する傾向があります。このチュートリアル・コードのためには、Luong & Bahdanau スタイル attention の 2 つの改良種を使用することを推奨します : scaled_luong & normed bahdanau です。

 

マルチ-GPU 訓練

NMT モデルの訓練は数日間かかるかもしれません。異なる RNN 層を異なる GPU 上に置けば訓練スピードを改善できます。マルチ GPU 上に RNN 層を作成するサンプルがここにあります。

cells = []
for i in range(num_layers):
  cells.append(tf.contrib.rnn.DeviceWrapper(
      tf.contrib.rnn.LSTMCell(num_units),
      "/gpu:%d" % (num_layers % num_gpus)))
cell = tf.contrib.rnn.MultiRNNCell(cells)

更に、勾配計算を並列化するためには tf.gradients で colocate_gradients_with_ops オプションを有効にする必要があります。attention ベースの NMT モデルのスピード改良は GPU 数の増加につれて非常に小さいことに気がつくかもしれません。標準的な attention アーキテクチャの一つの大きな欠点は各 time ステップで attention に query するためにトップ (最終) 層の出力を使用することです。これは各デコーディング・ステップはその前のステップが完全に終了するのを待たなければならないことを意味します : それ故に、RNN 層をマルチ GPU 上に単純に置くことではデコーディング・プロセスを並列化できません。

GNMT attention アーキテクチャ は attention に query するためにボトム (最初の) 層の出力を使用してデコーダの計算を並列化します。従って、各デコーディング・ステップはその前の最初の層と attention 計算が終了すればすぐに開始できます。そのアーキテクチャを tf.contrib.rnn.MultiRNNCell のサブクラス、GNMTAttentionMultiCell で実装しました。GNMTAttentionMultiCell でどのようにデコーダ・セルを作成するかのサンプルがここにあります。

cells = []
for i in range(num_layers):
  cells.append(tf.contrib.rnn.DeviceWrapper(
      tf.contrib.rnn.LSTMCell(num_units),
      "/gpu:%d" % (num_layers % num_gpus)))
attention_cell = cells.pop(0)
attention_cell = tf.contrib.seq2seq.AttentionWrapper(
    attention_cell,
    attention_mechanism,
    attention_layer_size=None,  # don't add an additional dense layer.
    output_attention=False,)
cell = GNMTAttentionMultiCell(attention_cell, cells)

 

5 章. ベンチマーク

 
(訳注: ベンチマークについては原文の該当セクション: Benchmarks を参照してください。)

 
以上






TensorFlow : Tutorials : Sequences : お絵描き分類のためのリカレント・ニューラルネットワーク

TensorFlow : Tutorials : Sequences : お絵描き分類のためのリカレント・ニューラルネットワーク (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 07/16/2018
作成日時 : 05/30/2018

* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページは、TensorFlow 本家サイトの Tutorials – Sequences – Recurrent Neural Networks for Drawing Classification を
翻訳した上で適宜、補足説明したものです:

* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

本文

Quick, Draw! はプレーヤーが幾つかの物体を描くことに挑戦してコンピュータがそのお絵描きを認識できるかを見るゲームです。

Quick, Draw! の認識は、x と y の点のストロークのシークエンスとして与えられる、ユーザ入力を取ってそしてユーザが描こうとするオブジェクトカテゴリーを認識する分類器により遂行されます。

このチュートリアルではこの問題に対する RNN ベースの認識器をどのように構築するかを示します。モデルはお絵描きを分類するために畳み込み層、LSTM 層、そして softmax 出力の組み合わせを使用します :

 

 
上の図はこのチュートリアルで構築するモデルの構造を示しています。入力はお絵描きで、x, y, そして n の点のストロークのシークエンスとしてエンコードされます。ここで n は点が新しいストロークの最初の点であるかどうかを示します。

そして 1-次元畳み込みのシリーズが適用されます。それから LSTM 層が適用されて既知のお絵描きのクラスの中から分類の決定を行なうために総ての LSTM ステップの出力の総和が softmax 層に供給されます。

このチュートリアルは 公に利用可能な 実際の Quick, Draw! ゲームからのデータを使用します。このデータセットは 345 カテゴリーの 50M のお絵描きから成ります。

 

チュートリアル・コードを実行する

このチュートリアルのためのコードを試すには :

  1. チュートリアル・コード をダウンロードします。
  2. TFRecord フォーマットのデータを ここ からダウンロードしてそれを unzip します (訳注 : データは約 1 GB 程度あります)。
  3. このチュートリアルで記述されている RNN ベースのモデルを訓練するために次のコマンドでチュートリアル・コードを実行します。ダウンロードから unzip されたデータを指すようにパスは調整してください。
      python train_model.py \
        --training_data=rnn_tutorial_data/training.tfrecord-?????-of-????? \
        --eval_data=rnn_tutorial_data/eval.tfrecord-?????-of-????? \
        --classes_file=rnn_tutorial_data/training.tfrecord.classes
    

 

チュートリアル詳細

データをダウンロードする

このチュートリアルで使用する、TFExamples を含む TFRecord ファイルとして利用可能なデータを作成しました。そのデータはここからダウンロードできます :

http://download.tensorflow.org/data/quickdraw_tutorial_dataset_v1.tar.gz

代わりに ndjson 形式の元データを Google cloud からダウンロードしてそしてそれを次のセクションで説明されるように TFExamples を含む TFRecord ファイルに貴方自身で変換することができます。

 

オプション: 完全な Quick Draw データをダウンロードする

完全な Quick, Draw! データセット はカテゴリーに分かれた ndjson ファイルとして Google Cloud Storage 上で利用可能です。Cloud Console でファイルのリストをブラウズ することができます。

データをダウンロードするため、データセット全体をダウンロードするために gsutil を使用することを推奨します (訳注: 原文まま)。元の .ndjson ファイルは ~22GB のダウンロードを必要とすることに注意してください。

それからgsutil インストールが動作してデータバケットにアクセスできることを確認するために次のコマンドを使用してください :

gsutil ls -r "gs://quickdraw_dataset/full/simplified/*"

これは次のようなファイルの長いリストを出力するでしょう :

gs://quickdraw_dataset/full/simplified/The Eiffel Tower.ndjson
gs://quickdraw_dataset/full/simplified/The Great Wall of China.ndjson
gs://quickdraw_dataset/full/simplified/The Mona Lisa.ndjson
gs://quickdraw_dataset/full/simplified/aircraft carrier.ndjson
...

それからフォルダを作成してデータセットをそこにダウンロードします。

mkdir rnn_tutorial_data
cd rnn_tutorial_data
gsutil -m cp "gs://quickdraw_dataset/full/simplified/*" .

このダウンロードはしばらくかかりそして 23GB より少し大きいデータをダウンロードします。

 

オプション: データを変換する

ndjson ファイルを tf.train.Example protos を含む TFRecord ファイルに変換するためには次のコマンドを実行します。

   python create_dataset.py --ndjson_path rnn_tutorial_data \
      --output_path rnn_tutorial_data

これはデータを訓練のためのクラス毎 10000 項目と評価データのための 1000 項目を持つ TFRecord ファイルの 10 シャードにストアします。

この変換プロセスは以下でより詳細に説明されます。

元の QuickDraw データは ndjson ファイルとしてフォーマットされ、そこでは各行は次のような JSON オブジェクトを含みます :

{"word":"cat",
 "countrycode":"VE",
 "timestamp":"2017-03-02 23:25:10.07453 UTC",
 "recognized":true,
 "key_id":"5201136883597312",
 "drawing":[
   [
     [130,113,99,109,76,64,55,48,48,51,59,86,133,154,170,203,214,217,215,208,186,176,162,157,132],
     [72,40,27,79,82,88,100,120,134,152,165,184,189,186,179,152,131,114,100,89,76,0,31,65,70]
   ],[
     [76,28,7],
     [136,128,128]
   ],[
     [76,23,0],
     [160,164,175]
   ],[
     [87,52,37],
     [175,191,204]
   ],[
     [174,220,246,251],
     [134,132,136,139]
   ],[
     [175,255],
     [147,168]
   ],[
     [171,208,215],
     [164,198,210]
   ],[
     [130,110,108,111,130,139,139,119],
     [129,134,137,144,148,144,136,130]
   ],[
     [107,106],
     [96,113]
   ]
 ]
}

分類器を構築する目的のためには “word” と “drawing” フィールドをケアするだけです。ndjson ファイルを解析する間、drawing フィールドからのストロークを連続するポイントの差異を含むサイズ [number of points, 3] の tensor に変換する関数を使用してそれらを一行ずつ解析します。この関数はまたクラス名を文字列として返します。

def parse_line(ndjson_line):
  """Parse an ndjson line and return ink (as np array) and classname."""
  sample = json.loads(ndjson_line)
  class_name = sample["word"]
  inkarray = sample["drawing"]
  stroke_lengths = [len(stroke[0]) for stroke in inkarray]
  total_points = sum(stroke_lengths)
  np_ink = np.zeros((total_points, 3), dtype=np.float32)
  current_t = 0
  for stroke in inkarray:
    for i in [0, 1]:
      np_ink[current_t:(current_t + len(stroke[0])), i] = stroke[i]
    current_t += len(stroke[0])
    np_ink[current_t - 1, 2] = 1  # stroke_end
  # Preprocessing.
  # 1. Size normalization.
  lower = np.min(np_ink[:, 0:2], axis=0)
  upper = np.max(np_ink[:, 0:2], axis=0)
  scale = upper - lower
  scale[scale == 0] = 1
  np_ink[:, 0:2] = (np_ink[:, 0:2] - lower) / scale
  # 2. Compute deltas.
  np_ink = np_ink[1:, 0:2] - np_ink[0:-1, 0:2]
  return np_ink, class_name

書くためにデータがシャッフルされることを望むのでランダム順序でカテゴリー・ファイルの各々から読みそしてランダム・シャードに書きます。

訓練データのために各クラスの最初の 10000 項目を読みそして評価データのために各クラスの次の 1000 項目を読みます。

それからこのデータは shape [num_training_samples, max_length, 3] の tensor に再フォーマットされます。それからスクリーン座標における元のお絵描きのバウンディング・ボックスを決定してそしてお絵描きが単位 (= unit) 高さを持つようにサイズを正規化します。

最後に、連続ポイントの差異を計算してこれらをキー ink のもとに VarLenFeature として tensorflow.Example にストアします。更に class_index を単一のエントリ FixedLengthFeature としてそして ink の shape を長さ 2 の FixedLengthFeature としてストアします。

 

モデルを定義する

モデルを定義するために新しい Estimator を作成します。

モデルを構築するために :

  1. 入力を元の shape に reshape して戻します – そこではミニバッチはコンテンツの最大長にパディングされます。ink データに加えて各サンプルのための長さとターゲット・クラスもまた持ちます。これは関数 _get_input_tensors 内で起きます。
  2. 入力を _add_conv_layers の畳み込み層のシリーズへ渡し通します。
  3. 畳み込みの出力を _add_rnn_layers の bidirectional LSTM 層のシリーズへ渡します。その最後に、各 time ステップのための出力は入力の簡潔で固定長の埋め込みを持つように総計されます。
  4. _add_fc_layers の softmax 層を使用してこの埋め込みを分類します。

コードではこれは次のように見えます :

inks, lengths, targets = _get_input_tensors(features, targets)
convolved = _add_conv_layers(inks)
final_state = _add_rnn_layers(convolved, lengths)
logits =_add_fc_layers(final_state)

 

_get_input_tensors

入力特徴を得るために最初に特徴辞書から shape を得てそして入力シークエンスの長さを含むサイズ [batch_size] の 1D tensor を作成します。ink は特徴辞書の SparseTensor としてストアされてこれを dense tensor に変換してから[batch_size, ?, 3] になるように reshape します。そして最後に、ターゲットが渡された場合それらがサイズ [batch_size] の 1D tensor としてストアされることを確実にします。

コードではこれはこのように見えます :

shapes = features["shape"]
lengths = tf.squeeze(
    tf.slice(shapes, begin=[0, 0], size=[params["batch_size"], 1]))
inks = tf.reshape(
    tf.sparse_tensor_to_dense(features["ink"]),
    [params["batch_size"], -1, 3])
if targets is not None:
  targets = tf.squeeze(targets)

 

_add_conv_layers

畳み込み層とフィルタの長さの望む数は params 辞書のパラメータ num_conv と conv_len を通して configure されます。

入力はシークエンスで各ポイントは次元 3 を持ちます。私達は 1D 畳み込みを使用します、そこでは 3 入力特徴をチャネルとして扱います。これは、入力が [batch_size, length, 3] tensor で出力が [batch_size, length, number_of_filters] tensor であることを意味します。

convolved = inks
for i in range(len(params.num_conv)):
  convolved_input = convolved
  if params.batch_norm:
    convolved_input = tf.layers.batch_normalization(
        convolved_input,
        training=(mode == tf.estimator.ModeKeys.TRAIN))
  # Add dropout layer if enabled and not first convolution layer.
  if i > 0 and params.dropout:
    convolved_input = tf.layers.dropout(
        convolved_input,
        rate=params.dropout,
        training=(mode == tf.estimator.ModeKeys.TRAIN))
  convolved = tf.layers.conv1d(
      convolved_input,
      filters=params.num_conv[i],
      kernel_size=params.conv_len[i],
      activation=None,
      strides=1,
      padding="same",
      name="conv1d_%d" % i)
return convolved, lengths

 

_add_rnn_layers

畳み込みからの出力を bidirectional LSTM 層へ渡します、そのために contrib からのヘルパー関数を使用します。

outputs, _, _ = contrib_rnn.stack_bidirectional_dynamic_rnn(
    cells_fw=[cell(params.num_nodes) for _ in range(params.num_layers)],
    cells_bw=[cell(params.num_nodes) for _ in range(params.num_layers)],
    inputs=convolved,
    sequence_length=lengths,
    dtype=tf.float32,
    scope="rnn_classification")

より詳細とどのように CUDA アクセラレートされた実装を使用するかについてはコードを見てください。

簡潔で固定長の埋め込みを作成するために、LSTM の出力を総計します。最初にシークエンスがデータを持たないバッチの領域をゼロ設定します。

mask = tf.tile(
    tf.expand_dims(tf.sequence_mask(lengths, tf.shape(outputs)[1]), 2),
    [1, 1, tf.shape(outputs)[2]])
zero_outside = tf.where(mask, outputs, tf.zeros_like(outputs))
outputs = tf.reduce_sum(zero_outside, axis=1)

 

_add_fc_layers

入力の埋め込みは完全結合層に渡されます、それからそれを softmax 層として使用します。

tf.layers.dense(final_state, params.num_classes)

 

損失、予測、そして optimizer

最後に、ModelFn を作成するために loss (損失), train op (訓練 op)、そして predictions (予測) を追加する必要があります :

cross_entropy = tf.reduce_mean(
    tf.nn.sparse_softmax_cross_entropy_with_logits(
        labels=targets, logits=logits))
# Add the optimizer.
train_op = tf.contrib.layers.optimize_loss(
    loss=cross_entropy,
    global_step=tf.train.get_global_step(),
    learning_rate=params.learning_rate,
    optimizer="Adam",
    # some gradient clipping stabilizes training in the beginning.
    clip_gradients=params.gradient_clipping_norm,
    summaries=["learning_rate", "loss", "gradients", "gradient_norm"])
predictions = tf.argmax(logits, axis=1)
return model_fn_lib.ModelFnOps(
    mode=mode,
    predictions={"logits": logits,
                 "predictions": predictions},
    loss=cross_entropy,
    train_op=train_op,
    eval_metric_ops={"accuracy": tf.metrics.accuracy(targets, predictions)})

 

モデルを訓練して評価する

モデルを訓練して評価するために Estimator API の機能に依拠して Experiment API で簡単に訓練と評価を実行することができます :

  estimator = tf.estimator.Estimator(
      model_fn=model_fn,
      model_dir=output_dir,
      config=config,
      params=model_params)
  # Train the model.
  tf.contrib.learn.Experiment(
      estimator=estimator,
      train_input_fn=get_input_fn(
          mode=tf.contrib.learn.ModeKeys.TRAIN,
          tfrecord_pattern=FLAGS.training_data,
          batch_size=FLAGS.batch_size),
      train_steps=FLAGS.steps,
      eval_input_fn=get_input_fn(
          mode=tf.contrib.learn.ModeKeys.EVAL,
          tfrecord_pattern=FLAGS.eval_data,
          batch_size=FLAGS.batch_size),
      min_eval_frequency=1000)

このチュートリアルは貴方をリカレント・ニューラルネットワークと estimator の API に精通させるための比較的小さいデータセット上の単なるクイック・サンプルであることに注意してください。そのようなモデルは巨大なデータセット上で試せばよりパワフルにさえなれるでしょう。

モデルを 1M ステップ訓練するとき top-1 候補上でおよそ 70 % の精度を得ることが期待できます。この精度はゲーム・ダイナミクスゆえに quickdraw ゲームの構築には十分であることに注意してくださいユーザはそれが ready になるまで彼らのお絵描きを調整できるでしょう。また、ゲームは top-1 候補だけを使用せずにターゲット・カテゴリーが固定されたスレッショルドよりも良いスコアを伴って現れる場合にはお絵描きを正しいものとして受け入れます。

 

以上

人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com
AI & Biz WEB セミナー#109
AI 技術を戦略的に実ビジネスで活用するには?  Vol.109
[無料 WEB セミナー] [詳細]
具体的な導入プロセスと運用の考慮ポイントを解説
既に多くの企業が AI 技術の研究開発に乗り出し、あらゆる業界・業種で導入され、活用範囲を拡大しています。しかしながら何をどこから始めるべきか判断できずに AI 技術を活用できていない企業も少なくありません。
本セミナーでは AI 技術の特性を理解し、実ビジネスに適応させる上でのステップと共に、運用時の考慮ポイントについて解説致します。
日時:2021年08月04日(水)
会場:WEBセミナー
共催:クラスキャット、XEENUTS
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)