RNN (リカレント ニューラル ネットワーク) チュートリアル: TensorFlow の例
なぜリカレント ニューラル ネットワーク (RNN) が必要なのでしょうか?
リカレント ニューラル ネットワーク (RNN) を使用すると、メモリ ユニットをモデル化してデータを永続化し、短期的な依存関係をモデル化できます。 データの相関関係やパターンを特定するための時系列予測にも使用されます。 また、人間の脳と同様の動作を提供することで、連続データの予測結果を生成するのにも役立ちます。
人工ニューラル ネットワークの構造は比較的単純で、主に行列の乗算に関するものです。 最初のステップでは、入力に最初はランダムな重みとバイアスが乗算され、活性化関数で変換され、出力値が予測を行うために使用されます。 このステップにより、ネットワークが現実からどれだけ離れているかがわかります。
適用されるメトリックは損失です。損失関数が高いほど、モデルは愚かになります。ネットワークの知識を向上させるには、ネットの重みを調整して最適化する必要があります。確率的勾配降下法は、重みの値を正しい方向に変更するために採用される方法です。調整が完了すると、ネットワークは別のデータ バッチを使用して新しい知識をテストできます。
幸いなことに、誤差は以前よりも小さくなりましたが、十分に小さいわけではありません。 最適化ステップは、エラーが最小化されるまで、つまり、それ以上の情報が抽出できなくなるまで、繰り返し実行されます。
このタイプのモデルの問題は、メモリがまったくないことです。 それは入力と出力が独立していることを意味します。 言い換えれば、モデルは以前のものを気にしません。 ネットワークには履歴データや過去の単語に関する情報が必要であるため、時系列や文章を予測する必要がある場合、いくつかの疑問が生じます。
この問題を克服するために、新しいタイプのアーキテクチャが開発されました。リカレントニューラルネットワーク(以下RNN)
リカレント ニューラル ネットワーク (RNN) とは何ですか?
A リカレントニューラルネットワーク(RNN) のクラスです 人工ニューラルネットワーク 異なるノード間の接続が有向グラフを形成し、時間的な動的な動作を実現します。 これは、フィードフォワード ネットワークから得られるシーケンシャル データをモデル化するのに役立ちます。 人間の脳と同じように機能して、予測結果を提供します。
リカレント ニューラル ネットワークは、記憶状態がニューロンに追加される点を除けば、従来のニューラル ネットワークと非常によく似ています。 メモリを含める計算は簡単です。
XNUMX つのニューロンだけがデータのバッチをフィードする単純なモデルを想像してください。 従来のニューラル ネットでは、モデルは入力に重みと活性化関数を乗算して出力を生成します。 RNN を使用すると、この出力は自分自身に一定の回数だけ送信されます。 私たちは電話します タイムステップ 出力が次の行列乗算の入力になる時間。
たとえば、下の図では、ネットワークが 1 つのニューロンで構成されていることがわかります。 ネットワークは入力と重みの間の行列の乗算を計算し、アクティベーション関数による非線形性を追加します。 t-XNUMXの出力となります。 この出力は、XNUMX 番目の行列乗算の入力になります。
以下では、ステップと出力の形状を理解するために TensorFlow で単純な RNN をコーディングします。
ネットワークは次のもので構成されます。
- XNUMX つの入力
- XNUMXつのニューロン
- 2タイムステップ
ネットワークは下の図のように進みます。
このネットワークは、各アクティブ スクエアで同じ操作を実行するため、「再帰的」と呼ばれます。ネットワークは、アクティブ化関数を使用する前に、入力と前の出力の重みを計算します。
import numpy as np import tensorflow as tf n_inputs = 4 n_neurons = 6 n_timesteps = 2 The data is a sequence of a number from 0 to 9 and divided into three batches of data. ## Data X_batch = np.array([ [[0, 1, 2, 5], [9, 8, 7, 4]], # Batch 1 [[3, 4, 5, 2], [0, 0, 0, 0]], # Batch 2 [[6, 7, 8, 5], [6, 5, 4, 2]], # Batch 3 ])
データ、反復ステージ、出力用のプレースホルダーを使用してネットワークを構築できます。
- データのプレースホルダーを定義する
X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs])
ここに:
- なし: 不明であり、バッチのサイズが必要になります
- n_timesteps: ネットワークが出力をニューロンに送り返す回数
- n_inputs: バッチごとの入力数
- リカレントネットワークを定義する
上の図で述べたように、ネットワークは 6 つのニューロンで構成されています。 ネットワークは XNUMX つのドット積を計算します。
- 最初の重みセット (つまり、6: ニューロンの数に等しい) を持つ入力データ
- 6 番目の重みセットを使用した前の出力 (つまり、XNUMX: 出力の数に対応)
最初のフィードフォワード中、使用可能な値がないため、前の出力の値はゼロに等しいことに注意してください。
RNN を構築するオブジェクトは tf.contrib.rnn.BasicRNNCell で、引数 num_units で入力の数を定義します。
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
ネットワークが定義されたので、出力と状態を計算できます。
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
このオブジェクトは、内部ループを使用して行列を適切な回数乗算します。
リカレント ニューロンは、前のタイム ステップのすべての入力の関数であることに注意してください。 これが、ネットワークが独自のメモリを構築する方法です。 前回の情報は将来に伝播する可能性があります。 これがリカレント ニューラル ネットワークの魔法です
## Define the shape of the tensor X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs]) ## Define the network basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons) outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) init = tf.global_variables_initializer() init = tf.global_variables_initializer() with tf.Session() as sess: init.run() outputs_val = outputs.eval(feed_dict={X: X_batch}) print(states.eval(feed_dict={X: X_batch})) [[ 0.38941205 -0.9980438 0.99750966 0.7892596 0.9978241 0.9999997 ] [ 0.61096436 0.7255889 0.82977575 -0.88226104 0.29261455 -0.15597084] [ 0.62091285 -0.87023467 0.99729395 -0.58261937 0.9811445 0.99969864]]
説明のために、前の状態の値を出力します。 上に表示された出力は、最後の状態からの出力を示しています。 ここですべての出力を印刷します。状態が各バッチの前の出力であることがわかります。 つまり、前の出力にはシーケンス全体に関する情報が含まれています。
print(outputs_val) print(outputs_val.shape) [[[-0.75934666 -0.99537754 0.9735819 -0.9722234 -0.14234993 -0.9984044 ] [ 0.99975264 -0.9983206 0.9999993 -1. -0.9997506 -1. ]] [[ 0.97486496 -0.98773265 0.9969686 -0.99950117 -0.7092863 -0.99998885] [ 0.9326837 0.2673438 0.2808514 -0.7535883 -0.43337247 0.5700631 ]] [[ 0.99628735 -0.9998728 0.99999213 -0.99999976 -0.9884324 -1. ] [ 0.99962527 -0.9467421 0.9997403 -0.99999714 -0.99929446 -0.9999795 ]]] (3, 2, 6)
出力は (3, 2, 6) の形になります。
- 3: バッチ数
- 2: タイムステップの数
- 6: ニューロンの数
リカレント ニューラル ネットワークの最適化は、従来のニューラル ネットワークと同じです。 このリカレント ニューラル ネットワーク チュートリアルの次の部分で、コードの最適化の方法を詳しく説明します。
RNNの応用
RNN には、特に将来の予測に関して、さまざまな用途があります。 金融業界では、RNN は株価や株式市場の方向性 (つまり、プラスかマイナスか) を予測するのに役立ちます。
RNN は、車両の軌道を予測することで自動車事故を回避できるため、自動運転車に役立ちます。
RNN は、テキスト分析、画像キャプション、感情分析、機械翻訳で広く使用されています。 たとえば、映画のレビューを使用して、映画を観た後に観客が感じた感情を理解することができます。 このタスクの自動化は、映画会社がレビューをレビュー、ラベル付け、統合、分析する十分な時間がない場合に非常に役立ちます。 機械はより高いレベルの精度で仕事を行うことができます。
RNN の制限
理論的には、RNN は情報を最新の時刻まで伝送することになっています。 ただし、タイム ステップが長すぎる場合、このすべての情報を伝播することは非常に困難です。 ネットワークに深い層が多すぎると、トレーニングできなくなります。 この問題は次のように呼ばれます。 勾配問題の消失。 覚えていると思いますが、ニューラル ネットワークは勾配降下法アルゴリズムを使用して重みを更新します。 ネットワークが下位層に進むにつれて、勾配は小さくなります。
結論として、勾配は一定のままであり、改善の余地がないことを意味します。 モデルは勾配の変化から学習します。 この変更はネットワークの出力に影響します。 ただし、勾配の差が小さすぎる場合 (つまり、重みが少し変化する場合)、ネットワークは何も学習できず、出力も学習できません。 したがって、勾配消失問題に直面しているネットワークは、適切な解決策に向かって収束することができません。
LSTMの改善
RNN が直面する勾配消失の潜在的な問題を克服するために、Hochreiter、Schmidhuber、Bengio の 3 人の研究者は、Long Short-Term Memory (LSTM) と呼ばれるアーキテクチャを使用して RNN を改良しました。簡単に言うと、LSMT はネットワークに最近の関連過去情報を提供します。マシンはより優れたアーキテクチャを使用して情報を選択し、後の時点に持ち帰ります。
LSTMアーキテクチャはTensorFlowのtf.contrib.rnn.LSTMCellで利用可能です。LSTMはチュートリアルの範囲外です。公式の ドキュメント 詳細については、
時系列の RNN
この TensorFlow RNN チュートリアルでは、時系列データで RNN を使用します。 時系列は前回に依存します。つまり、過去の値には、ネットワークが学習できる関連情報が含まれています。 時系列予測の背後にある考え方は、株価、気温、GDP などの系列の将来の価値を推定することです。
Keras RNN と時系列のデータの準備は少し難しい場合があります。まず、目的は系列の次の値を予測することです。つまり、過去の情報を使用して t + 1 の値を推定します。ラベルは入力シーケンスと同じで、1 期間先にシフトされます。次に、入力数は 10 に設定され、つまり、XNUMX 回につき XNUMX つの観測が行われます。最後に、時間ステップは数値のシーケンスと同じです。たとえば、時間ステップを XNUMX に設定すると、入力シーケンスは XNUMX 回連続して返されます。
以下のグラフを見てください。左側は時系列データ、右側は架空の入力シーケンスを表しています。 2001 年 2016 月から XNUMX 年 XNUMX 月までの毎日のランダムな値を含むデータセットを返す関数を作成します。
# To plot pretty figures %matplotlib inline import matplotlib import matplotlib.pyplot as plt import pandas as pd def create_ts(start = '2001', n = 201, freq = 'M'): rng = pd.date_range(start=start, periods=n, freq=freq) ts = pd.Series(np.random.uniform(-18, 18, size=len(rng)), rng).cumsum() return ts ts= create_ts(start = '2001', n = 192, freq = 'M') ts.tail(5)
出力
2016-08-31 -93.459631 2016-09-30 -95.264791 2016-10-31 -95.551935 2016-11-30 -105.879611 2016-12-31 -123.729319 Freq: M, dtype: float64
ts = create_ts(start = '2001', n = 222) # Left plt.figure(figsize=(11,4)) plt.subplot(121) plt.plot(ts.index, ts) plt.plot(ts.index[90:100], ts[90:100], "b-", linewidth=3, label="A training instance") plt.title("A time series (generated)", fontsize=14) # Right plt.subplot(122) plt.title("A training instance", fontsize=14) plt.plot(ts.index[90:100], ts[90:100], "b-", markersize=8, label="instance") plt.plot(ts.index[91:101], ts[91:101], "bo", markersize=10, label="target", markerfacecolor='red') plt.legend(loc="upper left") plt.xlabel("Time") plt.show()
グラフの右側にはすべてのシリーズが表示されています。2001 年から始まり、2019 年に終わります。ネットワークにすべてのデータを入力しても意味がないため、代わりに、時間ステップに等しい長さのデータ バッチを作成する必要があります。このバッチが X 変数になります。Y 変数は X と同じですが、1 期間だけシフトされます (つまり、t+XNUMX を予測します)。
どちらのベクトルも同じ長さです。 上のグラフの右側でそれがわかります。 線は X 入力の XNUMX 個の値を表し、赤い点はラベル Y の XNUMX 個の値を表します。ラベルは X より XNUMX 周期前に開始され、XNUMX 周期後に終了することに注意してください。
TensorFlow で時系列を予測するための RNN を構築する
この RNN トレーニングでは、上記の系列を予測するための最初の RNN を構築します。 モデルに対していくつかのハイパーパラメータ (モデルのパラメータ、つまりニューロンの数など) を指定する必要があります。
- 入力数:1
- 時間ステップ(時系列のウィンドウ): 10
- ニューロンの数: 120
- 出力数:1
ネットワークは 10 日間のシーケンスから学習し、120 個のリカレント ニューロンを含みます。 モデルに XNUMX つの入力、つまり XNUMX 日を入力します。 自由に値を変更して、モデルが改善されたかどうかを確認してください。
モデルを構築する前に、データセットをトレーニング セットとテスト セットに分割する必要があります。 完全なデータセットには 222 個のデータ ポイントがあります。 最初の 201 ポイントをモデルのトレーニングに使用し、最後の 21 ポイントをモデルのテストに使用します。
トレーニング セットとテスト セットを定義した後、バッチを含むオブジェクトを作成する必要があります。 このバッチには、X 値と Y 値があります。 X 値は 200 周期遅れていることに注意してください。 したがって、最初の 10 個の観測値を使用し、タイム ステップは 20 に等しくなります。 X_batches オブジェクトには、サイズ 10*1 のバッチが XNUMX 個含まれている必要があります。 y_batches は X_batches オブジェクトと同じ形状ですが、ピリオドが XNUMX つ進んでいます。
ステップ1) トレインを作成してテストする
まず最初に、系列を に変換します。 numpy 配列; 次に、以下の TensorFlow RNN の例に示すように、ウィンドウ (つまり、ネットワークが学習する時間の数)、入力数、出力数、およびトレーニング セットのサイズを定義します。
series = np.array(ts) n_windows = 20 n_input = 1 n_output = 1 size_train = 201
その後、配列を XNUMX つのデータセットに分割するだけです。
## Split data train = series[:size_train] test = series[size_train:] print(train.shape, test.shape) (201,) (21,)
ステップ2) X_batches と y_batches を返す関数を作成します。
これを簡単にするために、X_batches 用と y_batches 用の XNUMX つの異なる配列を返す関数を作成できます。
バッチを構築するための RNN TensorFlow 関数を作成しましょう。
X バッチは 1 期間遅れていることに注意してください (値は t-XNUMX になります)。関数の出力は XNUMX つの次元を持つ必要があります。最初の次元はバッチの数、XNUMX 番目の次元はウィンドウのサイズ、最後の次元は入力の数です。
難しいのは、データ ポイントを正しく選択することです。 X データ ポイントの場合は t = 1 から t =200 までの観測値を選択し、Y データ ポイントの場合は t = 2 から 201 までの観測値を返します。正しいデータ ポイントを取得したら、簡単に再形成できます。シリーズ。
バッチを使用してオブジェクトを構築するには、データセットを同じ長さの 20 個のバッチ (つまり 1) に分割する必要があります。 reshape メソッドを使用して -20 を渡すと、シリーズがバッチ サイズと同様になるようにできます。 値 1 はバッチごとの観測数、XNUMX は入力数です。
ラベルに関しては同じ手順を実行する必要があります。
予測したい回数だけデータをシフトする必要があることに注意してください。たとえば、1 日先を予測したい場合は、シリーズを 2 シフトします。XNUMX 日先を予測したい場合は、データを XNUMX シフトします。
x_data = train[:size_train-1]: Select all the training instance minus one day X_batches = x_data.reshape(-1, windows, input): create the right shape for the batch e.g (10, 20, 1) def create_batches(df, windows, input, output): ## Create X x_data = train[:size_train-1] # Select the data X_batches = x_data.reshape(-1, windows, input) # Reshape the data ## Create y y_data = train[n_output:size_train] y_batches = y_data.reshape(-1, windows, output) return X_batches, y_batches
関数が定義されたので、以下の RNN の例に示すように、それを呼び出してバッチを作成できます。
X_batches, y_batches = create_batches(df = train, windows = n_windows, input = n_input, output = n_output)
形状を印刷して、寸法が正しいことを確認できます。
print(X_batches.shape, y_batches.shape) (10, 20, 1) (10, 20, 1)
20 つのデータ バッチと XNUMX 個の観測値のみを含むテスト セットを作成する必要があります。
何日もかけて予測するということは、1 番目の予測値がテスト データセットの最初の日 (t+XNUMX) の真の値に基づくことになることに注意してください。 実際にその真価が分かります。
t+2 (つまり 1 日先) を予測する場合は、予測値 t+3 を使用する必要があります。 t+1 (2 日先) を予測する場合は、予測値 t+XNUMX と t+XNUMX を使用する必要があります。 当然のことながら、t+n 日先を正確に予測するのは困難です。
X_test, y_test = create_batches(df = test, windows = 20,input = 1, output = 1) print(X_test.shape, y_test.shape) (10, 20, 1) (10, 20, 1)
さて、バッチ サイズの準備ができました。RNN アーキテクチャを構築できます。120 個の再帰ニューロンがあることを覚えておいてください。
ステップ3) モデルを構築する
モデルを作成するには、次の XNUMX つの部分を定義する必要があります。
- テンソルを持つ変数
- RNN
- 損失と最適化
ステップ3.1) 変数
適切な形式で X 変数と y 変数を指定する必要があります。 このステップは簡単です。 テンソルは、オブジェクト X_batches および y_batches と同じ次元を持ちます。
たとえば、テンソル X はプレースホルダーです (「入門」のチュートリアルを確認してください) テンソルフロー 変数宣言についてもう一度思い出してください) には XNUMX つの側面があります。
- 注: バッチのサイズ
- n_windows: ウィンドウの長さ。つまり、モデルが過去を振り返る回数
- n_input: 入力数
結果は次のとおりです。
tf.placeholder(tf.float32, [None, n_windows, n_input])
## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output])
ステップ3.2) RNN を作成する
この RNN TensorFlow の例の 2 番目の部分では、ネットワークのアーキテクチャを定義する必要があります。前と同様に、TensorFlow 推定器のオブジェクト BasicRNNCell と dynamic_rnn を使用します。
## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
次の部分は少し複雑ですが、より高速な計算が可能になります。 実行出力を高密度レイヤーに変換してから、入力と同じ次元になるように再変換する必要があります。
stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
ステップ3.3) 損失と最適化を生み出す
モデルの最適化は、実行しているタスクによって異なります。 前回のチュートリアルでは、 CNNの場合、目的は画像を分類することでしたが、この RNN チュートリアルでは、目的は少し異なります。 クラスと比較して連続変数について予測を行うように求められます。
この違いは最適化問題を変えるため、重要です。 連続変数の最適化問題は、平均二乗誤差を最小限に抑えることです。 TF でこれらのメトリクスを構築するには、以下を使用できます。
- tf.reduce_sum(tf.square(出力 – y))
RNN コードの残りの部分は以前と同じです。 Adam オプティマイザーを使用して損失 (つまり、MSE) を削減します。
- tf.train.AdamOptimizer(学習率=学習率)
- オプティマイザー.最小化(損失)
以上で、すべてをまとめれば、モデルをトレーニングする準備が整います。
tf.reset_default_graph() r_neuron = 120 ## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) ## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output]) ## 3. Loss + optimization learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) init = tf.global_variables_initializer()
1500 エポックを使用してモデルをトレーニングし、150 回の反復ごとに損失を出力します。 モデルがトレーニングされたら、以下のリカレント ニューラル ネットワークの例に示すように、テスト セットでモデルを評価し、予測を含むオブジェクトを作成します。
iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test}) 0 MSE: 502893.34 150 MSE: 13839.129 300 MSE: 3964.835 450 MSE: 2619.885 600 MSE: 2418.772 750 MSE: 2110.5923 900 MSE: 1887.9644 1050 MSE: 1747.1377 1200 MSE: 1556.3398 1350 MSE: 1384.6113
この RNN 深層学習チュートリアルでは、最後に、系列の実際の値を予測値とともにプロットできます。 モデルが修正された場合は、予測値が実際の値の上に重ねられる必要があります。
ご覧のとおり、モデルには改善の余地があります。ウィンドウ、バッチ サイズ、リカレント ニューロンの数などのハイパーパラメータを変更するのはあなた次第です。
plt.title("Forecast vs Actual", fontsize=14) plt.plot(pd.Series(np.ravel(y_test)), "bo", markersize=8, label="Actual", color='green') plt.plot(pd.Series(np.ravel(y_pred)), "r.", markersize=8, label="Forecast", color='red') plt.legend(loc="lower left") plt.xlabel("Time") plt.show()
まとめ
リカレント ニューラル ネットワークは、時系列やテキスト分析を処理するための堅牢なアーキテクチャです。前の状態の出力は、時間の経過や単語のシーケンスにわたってネットワークのメモリを保存するためのフィードバックです。
TensorFlow では、次のコードを使用して、時系列用の TensorFlow リカレント ニューラル ネットワークをトレーニングできます。
モデルのパラメータ
n_windows = 20 n_input = 1 n_output = 1 size_train = 201
モデルを定義する
X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
最適化を構築する
learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss)
モデルを訓練する
init = tf.global_variables_initializer() iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test})