![]() | ![]() | ![]() | ![]() |
前書き
リカレントニューラルネットワーク(RNN)は、時系列や自然言語などのシーケンスデータのモデリングに強力なニューラルネットワークのクラスです。
概略的には、RNNレイヤーはfor
ループを使用しfor
、シーケンスのタイムステップを反復処理しますが、これまでに確認したタイムステップに関する情報をエンコードする内部状態を維持します。
Keras RNN APIは、以下に重点を置いて設計されています。
使いやすさ:組み込みの
keras.layers.RNN
、keras.layers.LSTM
、keras.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レイヤーがあります。
keras.layers.SimpleRNN
、前のタイムステップからの出力が次のタイムステップに供給される完全に接続されたRNN。keras.layers.GRU
、 Cho et al。、2014で最初に提案されました。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()
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は、いくつかの便利な機能をサポートしています。
-
dropout
およびrecurrent_dropout
引数を介したrecurrent_dropout
dropout
-
go_backwards
引数を介して、入力シーケンスを逆に処理する機能 unroll
引数を介したループ展開(CPUで短いシーケンスを処理するときに大幅な高速化につながる可能性があります)- ...もっと。
詳細については、 RNNAPIのドキュメントを参照してください。
出力と状態
デフォルトでは、RNNレイヤーの出力にはサンプルごとに1つのベクトルが含まれます。このベクトルは、最後のタイムステップに対応するRNNセル出力であり、入力シーケンス全体に関する情報が含まれています。この出力の形状は(batch_size, units)
ここで、 units
は、レイヤーのコンストラクターに渡されるunits
引数に対応します。
return_sequences=True
を設定すると、RNNレイヤーは各サンプルの出力シーケンス全体を返すこともできます(サンプルごとのタイムステップごとに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()
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を初期化したりするために使用できます。この設定は、エンコーダー-デコーダーのシーケンス間モデルで一般的に使用され、エンコーダーの最終状態がデコーダーの初期状態として使用されます。
内部状態を返すようにRNNレイヤーを構成するには、レイヤーの作成時にreturn_state
パラメーターをTrue
に設定します。 LSTM
は2つの状態テンソルがありますが、 GRU
は1つしかないことに注意してください。
レイヤーの初期状態を構成するには、追加のキーワード引数initial_state
指定してレイヤーを呼び出すだけ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()
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レイヤーに加えて、RNNAPIはセルレベルのAPIも提供します。入力シーケンスのバッチ全体を処理するRNNレイヤーとは異なり、RNNセルは単一のタイムステップのみを処理します。
セルは、RNN層のfor
ループの内側にあります。 keras.layers.RNN
レイヤー内でセルをラップすると、 RNN(LSTMCell(10))
などのシーケンスのバッチを処理できるレイヤーが得られます。
数学的に、 RNN(LSTMCell(10))
と同じ結果を生成LSTM(10)
実際、TF v1.xでのこのレイヤーの実装は、対応するRNNセルを作成し、それをRNNレイヤーでラップするだけでした。ただし、組み込みのGRU
層とLSTM
層を使用すると、CuDNNを使用できるようになり、パフォーマンスが向上する場合があります。
3つの組み込みRNNセルがあり、それぞれが対応するRNNレイヤーに対応しています。
keras.layers.SimpleRNNCell
はSimpleRNN
レイヤーに対応します。keras.layers.GRUCell
はGRU
レイヤーに対応します。keras.layers.LSTMCell
はLSTM
レイヤーに対応します。
セルの抽象化は、汎用のkeras.layers.RNN
クラスとともに、研究用のカスタムRNNアーキテクチャを非常に簡単に実装できるようにします。
クロスバッチステートフルネス
非常に長いシーケンス(場合によっては無限)を処理する場合は、クロスバッチステートフルネスのパターンを使用することをお勧めします。
通常、RNNレイヤーの内部状態は、新しいバッチが表示されるたびにリセットされます(つまり、レイヤーによって表示されるすべてのサンプルは、過去から独立していると見なされます)。レイヤーは、特定のサンプルを処理している間のみ状態を維持します。
ただし、シーケンスが非常に長い場合は、それらを短いシーケンスに分割し、レイヤーの状態をリセットせずに、これらの短いシーケンスをRNNレイヤーに順番にフィードすると便利です。このようにして、レイヤーは、一度に1つのサブシーケンスしか表示されない場合でも、シーケンス全体に関する情報を保持できます。
これを行うには、コンストラクターで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()
使用できます。
完全な例を次に示します。
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)
などのnew_layer(inputs, initial_state=layer.states)
機能APIを介して新しいレイヤーの初期状態として使用できます。 、またはモデルのサブクラス化。
また、シーケンシャルモデルは、単一の入力と出力を持つレイヤーのみをサポートするため、この場合は使用されない可能性があることに注意してください。初期状態の追加の入力により、ここでは使用できません。
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を構築するための簡単なAPIであるkeras.layers.Bidirectional
ラッパーをkeras.layers.Bidirectional
ます。
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()
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 _________________________________________________________________
go_backwards
は、 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
ループに分解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
)
938/938 [==============================] - 7s 6ms/step - loss: 1.2775 - accuracy: 0.5890 - val_loss: 0.4391 - val_accuracy: 0.8663 <tensorflow.python.keras.callbacks.History at 0x7f769430c630>
それでは、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
)
938/938 [==============================] - 36s 37ms/step - loss: 0.3995 - accuracy: 0.8823 - val_loss: 0.3322 - val_accuracy: 0.8953 <tensorflow.python.keras.callbacks.History at 0x7f76941cde10>
NVIDIA GPUとCuDNNがインストールされたマシンで実行する場合、CuDNNで構築されたモデルは、通常のTensorFlowカーネルを使用するモデルと比較してトレーニングがはるかに高速です。
同じCuDNN対応モデルを使用して、CPUのみの環境で推論を実行することもできます。以下の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"))
Predicted result is: [3], target result is: 5
list / dict入力またはネストされた入力を持つ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.layers.RNN
モデルを作成しましょう。
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)
10/10 [==============================] - 1s 27ms/step - loss: 0.8448 - rnn_1_loss: 0.2678 - rnn_1_1_loss: 0.5770 - rnn_1_accuracy: 0.1061 - rnn_1_1_accuracy: 0.0344 <tensorflow.python.keras.callbacks.History at 0x7f7795504208>
keras.layers.RNN
レイヤーでは、シーケンス内の個々のステップの数学ロジックを定義することだけが期待されており、 keras.layers.RNN
レイヤーがシーケンスの反復を処理します。これは、新しい種類のRNN(LSTMバリアントなど)のプロトタイプをすばやく作成するための非常に強力な方法です。
詳細については、 APIドキュメントをご覧ください。