Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/ja/guide/keras/rnn.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Keras の再帰型ニューラルネットワーク(RNN)

はじめに

再帰型ニューラルネットワーク(RNN)は、時系列や自然言語などのシーケンスデータのモデリングを強力に行うニューラルネットワークのクラスです。

概略的には、RNN レイヤーは for ループを使用して、それまでに確認した時間ステップに関する情報をエンコードする内部状態を維持しながらシーケンスの時間ステップをイテレートします。

Keras RNN API は、次に焦点を当てて設計されています。

  • 使いやすさ: keras.layers.RNNkeras.layers.LSTMkeras.layers.GRU レイヤーがビルトインされているため、難しい構成選択を行わずに、再帰型モデルを素早く構築できます。

  • カスタマイズしやすさ: カスタムビヘイビアを使って独自の RNN セルレイヤーを構築し(for ループの内部)、一般的な keras.layers.RNN レイヤー(for ループ自体)で使用することもできます。このため、異なるリサーチアイデアを最小限のコードで柔軟に素早くプロトタイプすることができます。

セットアップ

import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers

ビルトイン RNN レイヤー: 単純な例

Keras には、次の 3 つのビルトイン RNN レイヤーがあります。

  1. keras.layers.SimpleRNN: 前の時間ステップの出力が次の時間ステップにフィードされる、完全に連結された RNN です。

  2. keras.layers.GRU: Cho et al., 2014 で初めて提案されたレイヤー。

  3. keras.layers.LSTM: Hochreiter & Schmidhuber, 1997 で初めて提案されたレイヤー。

2015 年始めに、Keras に、LSTM および GRU の再利用可能なオープンソース Python 実装が導入されました。

整数のシーケンスを処理し、そのような整数を 64 次元ベクトルに埋め込み、LSTM レイヤーを使用してベクトルのシーケンスを処理する Sequential モデルの単純な例を次に示しています。

model = 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. model.add(layers.Dense(10)) model.summary()

ビルトイン RNN は、多数の有益な特徴をサポートしています。

  • dropout および recurrent_dropout 引数を介した再帰ドロップアウト

  • go_backwards 引数を介して、入力シーケンスを逆順に処理する能力

  • unroll 引数を介したループ展開(CPU で短いシーケンスを処理する際に大幅な高速化が得られる)

  • など。

詳細については、「RNN API ドキュメント」を参照してください。

出力と状態

デフォルトでは、RNN レイヤーの出力には、サンプル当たり 1 つのベクトルが含まれます。このベクトルは、最後の時間ステップに対応する RNN セル出力で、入力シーケンス全体の情報が含まれます。この出力の形状は (batch_size, units) で、units はレイヤーのコンストラクタに渡される units 引数に対応します。

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

model = 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)) model.summary()

さらに、RNN レイヤーはその最終内部状態を返すことができます。返された状態は、後で RNN 実行を再開する際に使用するか、別の RNN を初期化するために使用できます。この設定は通常、エンコーダ・デコーダ方式の Sequence-to-Sequence モデルで使用され、エンコーダの最終状態がデコーダの初期状態として使用されます。

内部状態を返すように RNN レイヤーを構成するには、レイヤーを作成する際に、return_state パラメータを True に設定します。LSTM には状態テンソルが 2 つあるのに対し、GRU には 1 つしかないことに注意してください。

レイヤーの初期状態を構成するには、追加のキーワード引数 initial_state を使ってレイヤーを呼び出します。次の例に示すように、状態の形状は、レイヤーのユニットサイズに一致する必要があることに注意してください。

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)(decoder_output) model = keras.Model([encoder_input, decoder_input], output) model.summary()

RNN レイヤーと RNN セル

ビルトイン RNN レイヤーのほかに、RNN API は、セルレベルの API も提供しています。入力シーケンスの全バッチを処理する RNN レイヤーとは異なり、RNN セルは単一の時間ステップのみを処理します。

セルは、RNN レイヤーの for ループ内にあります。keras.layers.RNN レイヤー内のセルをラップすることで、シーケンスのバッチを処理できるレイヤー(RNN(LSTMCell(10)) など)を得られます。

数学的には、RNN(LSTMCell(10))LSTM(10) と同じ結果を出します。実際、TF v1.x でのこのレイヤーの実装は、対応する RNN セルを作成し、それを RNN レイヤーにラップするだけでした。ただし、ビルトインの GRULSTM レイヤーを使用すれば、CuDNN が使用できるようになり、パフォーマンスの改善を確認できることがあります。

ビルトイン RNN セルには 3 つあり、それぞれ、それに一致する RNN レイヤーに対応しています。

  • keras.layers.SimpleRNNCellSimpleRNN レイヤーに対応します。

  • keras.layers.GRUCellGRU レイヤーに対応します。

  • keras.layers.LSTMCellLSTM レイヤーに対応します。

セルの抽象化とジェネリックな keras.layers.RNN クラスを合わせることで、リサーチ用のカスタム RNN アーキテクチャの実装を簡単に行えるようになります。

バッチ間のステートフルネス

非常に長い(無限の可能性のある)シーケンスを処理する場合は、バッチ間ステートフルネスのパターンを使用するとよいでしょう。

通常、RNN レイヤーの内部状態は、新しいバッチが確認されるたびにリセットされます(レイヤーが確認する各サンプルは、過去のサンプルとは無関係だと考えられます)。レイヤーは、あるサンプルを処理する間のみ状態を維持します。

ただし、非常に長いシーケンスがある場合、より短いシーケンスに分割し、レイヤーの状態をリセットせずにそれらの短いシーケンスを順次、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() を使用できます。

注意: このセットアップでは、あるバッチのサンプル 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 状態の再利用

RNN の記録済みの状態は、layer.weights() には含まれません。RNN レイヤーの状態を再利用する場合は、layer.states によって状態の値を取得し、new_layer(inputs, initial_state=layer.states) などの Keras Functional API またはモデルのサブクラス化を通じて新しいレイヤーの初期状態として使用することができます。

この場合には、単一の入力と出力を持つレイヤーのみをサポートする Sequential モデルを使用できない可能性があることにも注意してください。このモデルでは追加入力としての初期状態を使用することができません。

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) existing_state = lstm_layer.states new_lstm_layer = layers.LSTM(64) new_output = new_lstm_layer(paragraph3, initial_state=existing_state)

双方向性 RNN

時系列以外のシーケンスについては(テキストなど)、開始から終了までのシーケンスを処理だけでなく、逆順に処理する場合、RNN モデルの方がパフォーマンスに優れていることがほとんどです。たとえば、ある文で次に出現する単語を予測するには、その単語の前に出現した複数の単語だけでなく、その単語に関する文脈があると役立ちます。

Keras は、そのような双方向性のある RNN を構築するために、keras.layers.Bidirectional ラッパーという簡単な API を提供しています。

model = 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)) model.summary()

内部的には、Bidirectional は渡された RNN レイヤーをコピーし、新たにコピーされたレイヤーの go_backwards フィールドを転換して、入力が逆順に処理されるようにします。

Bidirectional RNN の出力は、デフォルトで、フォワードレイヤー出力とバックワードレイヤー出力の総和となります。これとは異なるマージ動作が必要な場合は(連結など)、Bidirectional ラッパーコンストラクタの merge_mode パラメータを変更します。Bidirectional の詳細については、API ドキュメントをご覧ください。

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

TensorFlow 2.0 では、ビルトインの LSTM と GRU レイヤーは、GPU が利用できる場合にデフォルトで CuDNN カーネルを活用するように更新されています。この変更により、以前の keras.layers.CuDNNLSTM/CuDNNGRU レイヤーは使用廃止となったため、実行するハードウェアを気にせずにモデルを構築することができます。

CuDNN カーネルは、特定の前提を以って構築されており、レイヤーはビルトイン LSTM または GRU レイヤーのデフォルト値を変更しない場合は CuDNN カーネルを使用できません。これらには次のような例があります。

  • activation 関数を tanh からほかのものに変更する。

  • recurrent_activation 関数を sigmoid からほかのものに変更する。

  • recurrent_dropout > 0 を使用する。

  • unroll を True に設定する。LSTM/GRU によって内部 tf.while_loop は展開済み for ループに分解されます。

  • use_bias を False に設定する。

  • 入力データが厳密に右詰でない場合にマスキングを使用する(マスクが厳密に右詰データに対応している場合でも、CuDNN は使用されます。これは最も一般的な事例です)。

制約の詳細については、LSTM および GRU レイヤーのドキュメントを参照してください。

利用できる場合に 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 = keras.layers.LSTM(units, input_shape=(None, input_dim)) else: # Wrapping a LSTMCell in a RNN layer will not use CuDNN. lstm_layer = keras.layers.RNN( keras.layers.LSTMCell(units), input_shape=(None, input_dim) ) model = keras.models.Sequential( [ lstm_layer, keras.layers.BatchNormalization(), keras.layers.Dense(output_size), ] ) return model

MNIST データセットを読み込みましょう。

mnist = 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] です。モデルのターゲットは整数ベクトルで、各整数は 0 から 9 の範囲内にあります。

model = build_model(allow_cudnn_kernel=True) model.compile( loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), optimizer="sgd", metrics=["accuracy"], ) model.fit( x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1 )

では、CuDNN カーネルを使用しないモデルと比較してみましょう。

noncudnn_model = build_model(allow_cudnn_kernel=False) noncudnn_model.set_weights(model.get_weights()) noncudnn_model.compile( loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), optimizer="sgd", metrics=["accuracy"], ) noncudnn_model.fit( x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1 )

NVIDIA GPU と CuDNN がインストールされたマシンで実行すると、CuDNN で構築されたモデルの方が、通常の TensorFlow カーネルを使用するモデルに比べて非常に高速に実行されます。

CPU のみの環境で推論を実行する場合でも、同じ CuDNN 対応モデルを使用できます。次の tf.device 注釈は単にデバイスの交換を強制しています。GPU が利用できないな場合は、デフォルトで CPU で実行されます。

実行するハードウェアを気にする必要がなくなったのです。素晴らしいと思いませんか?

import matplotlib.pyplot as plt 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"))

リスト/ディクショナリ入力、またはネストされた入力を使う RNN

ネスト構造の場合、インプルメンターは単一の時間ステップにより多くの情報を含めることができます。たとえば、動画のフレームに、音声と動画の入力を同時に含めることができます。この場合のデータ形状は、次のようになります。

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

別の例では、手書きのデータに、現在のペンの位置を示す座標 x と y のほか、筆圧情報も含めることができます。データは次のように表現できます。

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

次のコードは、このような構造化された入力を受け入れるカスタム RNN セルの構築方法を例に示しています。

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

独自レイヤーの記述に関する詳細は、「サブクラス化による新規レイヤーとモデルの作成」を参照してください。

class NestedCell(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 = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])] self.output_size = [tf.TensorShape([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)] i1 = input_shapes[0][1] i2 = input_shapes[1][1] i3 = input_shapes[1][2] self.kernel_1 = self.add_weight( shape=(i1, self.unit_1), initializer="uniform", name="kernel_1" ) self.kernel_2_3 = self.add_weight( shape=(i2, i3, 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 = (state_1, state_2_3) return output, new_states def get_config(self): return {"unit_1": self.unit_1, "unit_2": unit_2, "unit_3": self.unit_3}

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

上記で定義した keras.layers.RNN レイヤーとカスタムセルを使用する Keras モデルを構築しましょう。

unit_1 = 10 unit_2 = 20 unit_3 = 30 i1 = 32 i2 = 64 i3 = 32 batch_size = 64 num_batches = 10 timestep = 50 cell = NestedCell(unit_1, unit_2, unit_3) rnn = keras.layers.RNN(cell) input_1 = keras.Input((None, i1)) input_2 = keras.Input((None, i2, i3)) outputs = rnn((input_1, input_2)) model = keras.models.Model([input_1, input_2], outputs) model.compile(optimizer="adam", loss="mse", metrics=["accuracy"])

ランダムに生成されたデータでモデルをトレーニングする

このモデルに適した候補データセットを持ち合わせていないため、ランダムな Numpy データを使って実演することにします。

input_1_data = np.random.random((batch_size * num_batches, timestep, i1)) input_2_data = np.random.random((batch_size * num_batches, timestep, i2, i3)) target_1_data = np.random.random((batch_size * num_batches, unit_1)) target_2_data = np.random.random((batch_size * num_batches, 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)

Keras keras.layers.RNN レイヤーでは、シーケンス内の個別のステップの数学ロジックを定義することだけが期待されています。シーケンスのイテレーションは、keras.layers.RNN レイヤーによって処理されます。新しいタイプの RNN(LSTM など) を素早くプロトタイプ化する上で、非常に強力な方法です。

詳細については、API ドキュメントを参照してください。