Google I / Oの基調講演、製品セッション、ワークショップなどを見るプレイリストを見る

Kerasを使用したリカレントニューラルネットワーク(RNN)

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロードする

前書き

リカレントニューラルネットワーク(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.GRUCho et al。、2014で最初に提案されました。

  3. keras.layers.LSTMHochreiter&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つのベクトル)を返すこともできreturn_sequences=True 。この出力の形状は(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.RNNクラスを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]
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step

モデルインスタンスを作成してトレーニングしましょう。

モデルの損失関数として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 [==============================] - 6s 5ms/step - loss: 1.2900 - accuracy: 0.5779 - val_loss: 0.5115 - val_accuracy: 0.8402
<tensorflow.python.keras.callbacks.History at 0x7f5cf00c6518>

それでは、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 [==============================] - 32s 33ms/step - loss: 0.4511 - accuracy: 0.8632 - val_loss: 0.3877 - val_accuracy: 0.8686
<tensorflow.python.keras.callbacks.History at 0x7f5ce00c5748>

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

png

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 25ms/step - loss: 0.9178 - rnn_1_loss: 0.3211 - rnn_1_1_loss: 0.5968 - rnn_1_accuracy: 0.1097 - rnn_1_1_accuracy: 0.0382
<tensorflow.python.keras.callbacks.History at 0x7f5c391e43c8>

keras.layers.RNNレイヤーでは、シーケンス内の個々のステップの数学ロジックを定義するだけで済み、 keras.layers.RNNレイヤーがシーケンスの反復を処理します。これは、新しい種類のRNN(LSTMバリアントなど)のプロトタイプをすばやく作成するための非常に強力な方法です。

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