Keras : Ex-Tutorials : LSTM リカレント・ネットワークで時系列予測 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/29/2018 (2.2.0)
* 本ページは、Keras 開発チーム推奨の外部チュートリアル・リソースの一つ : “Time Series Prediction with LSTM Recurrent Neural Networks in Python with Keras” を題材にしてまとめ直したものです:
* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
時系列予測問題は予測モデル問題の中でも困難なもので、それは回帰予測モデルと違い、時系列は入力変数の中のシークエンス依存関係の複雑さが追加されます。
シークエンス依存関係を処理するために設計されたニューラルネットのパワフルな型はリカレント・ニューラルネットと呼ばれますが、特に Long Short-Term Memory あるいは LSTM ネットワークは深層学習で使用されるリカレント・ニューラルネットの一種で、非常に巨大なアーキテクチャが成功的に訓練可能です。
この記事では、時系列予測問題への対応を示すために Keras を使用して LSTM ネットワークをどのように開発するかを見ていきます。貴方自身の時系列予測問題や他の更に一般的なシークエンス問題に対してLSTM ネットワークをどのように実装して開発するかを知ることができます :
- 国際線乗客の時系列予測問題。
- 回帰、ウィンドウとタイムステップ・ベースの時系列予測問題の構成のためのLSTM ネットワークをどのように開発するか。
- 非常に長いシークエンスに渡り状態 (メモリ) を保持する LSTM ネットワークを使用してどのように開発して予測するか。
標準的な時系列予測問題のための多くの LSTM を開発していきます。これらの例は時系列予測モデル問題に対して貴方自身の異なる構造の LSTM ネットワークをどのように開発可能かを正確に示すでしょう。
課題
課題は国際線の乗客数予測問題です。これは年月が与えられた場合に、千人単位の国際線乗客の数を予測することが課題です。データは 144 観測結果を持ち、1949 年 1 月から 1960 年 12 月 (12 年間) に渡ります。
データセットはファイル名 “international-airline-passengers.csv“ を持ち “DataMarket webpage as a CSV download” から無料で利用可能です。
以下はファイルの最初の数行のサンプルです :
"Month","International airline passengers: monthly totals in thousands. Jan 49 ? Dec 60" "1949-01",112 "1949-02",118 "1949-03",132 "1949-04",129 "1949-05",121
Pandas ライブラリでこのデータセットを簡単にロードできます。各観測結果が 1 ヶ月の同じ間隔で分けられており、(ここでは日付には興味ありませんので) データセットをロードするときに最初のカラムは除外できます。ダウンロードしたデータセットはまたフッタ情報も持ちますが、これも pandas.read_csv() への skipfooter 引数を (3 フッタ行のための) 3 に設定することで除外可能です。
ロードされればデータセット全体を簡単にプロットできます。そのためのコードは以下のようなものです :
import pandas import matplotlib.pyplot as plt dataset = pandas.read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) plt.plot(dataset) plt.show()
データセットが時間とともに上昇傾向にあることを見て取れます。北半球の休暇期間に多分対応する、データセットの何某かの周期性を見ることもできます :
Long Short-Term Memory ネットワーク
Long Short-Term Memory あるいは LSTM ネットワークは BPTT (Backpropagation Through Time) を使用して訓練されるリカレント・ニューラルネットで勾配消失問題への対処が考慮されています。これは巨大なリカレント・ネットワークを作成して機械学習における困難なシークエンス問題に対処するために利用可能で、最先端の結果を獲得することが期待できます。
ニューロンの代わりに LSTM ネットワークは層を通して接続されるメモリブロックを持ちます。ブロックは古典的なニューロンよりもスマートなコンポーネントと直近のシークエンスのためにメモリを持ちます。ブロックはブロックの状態と出力を管理するゲートを含みます。ブロックは入力シークエンス上で作用してブロック内の各ゲートはそれらがトリガーされるか否かを制御する sigmoid 活性化ユニットを使用し、ブロックの条件節を通して状態の変更と情報の追加を行ないます。
ユニット内部には 3 つのタイプのゲートがあります :
- 忘却ゲート: 条件付きでブロックから何の情報を捨てるかを決定します。
- 入力ゲート: 条件付きでメモリ状態を更新するために入力からのどの値かを決定します。
- 出力ゲート: 条件付きで入力とブロックのメモリを基に何を出力するかを決定します。
各ユニットはミニ状態マシンのようなもので、そこではユニットのゲートは訓練手続きの間に学習される重みを持ちます。
回帰のための LSTM ネットワーク
この問題を回帰問題として表現することができます。つまり、今月の乗客数が与えられた場合、来月の乗客数はどのくらいでしょう?
データの単一カラムを 2-カラムのデータセットに変換する単純な関数を書くことができます : 最初のカラムは今月 (t) の乗客カウントで 2 番目のカラムは予測されるべき、次の月 (t+1) の乗客カウントです。
取り敢えず :
import numpy import matplotlib.pyplot as plt import pandas import math from keras.models import Sequential from keras.layers import Dense from keras.layers import LSTM from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_squared_error
# fix random seed for reproducibility numpy.random.seed(7)
データセットは Pandas dataframe としてロードします。そして dataframe から NumPy 配列を取り出して整数値を浮動小数点値に変換します、これはニューラルネットでモデリングする際に多くの場合、より適切です :
# load the dataset dataframe = pandas.read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) dataset = dataframe.values dataset = dataset.astype('float32')
LSTM は、特に sigmoid (デフォルト) や tanh 活性化関数が使用されるとき、入力データのスケールに敏感です。データを 0-to-1 の範囲にスケールすることは良い実践です、これは正規化とも呼ばれます。scikit-learn ライブラリからの MinMaxScaler 前処理クラスを使用してデータセットを簡単に正規化することができます :
# normalize the dataset scaler = MinMaxScaler(feature_range=(0, 1)) dataset = scaler.fit_transform(dataset)
データをモデル化して訓練データセット上でモデルの技量を見積もった後、新しいまだ見ていないデータ上でのモデルの技量の知見を得ることが必要です。通常の分類問題や回帰問題のためには、これを交差検証で行ないます。
時系列データでは、値のシークエンスが重要です。使用可能な単純な方法は順序付けられたデータセットを訓練とテスト・データセットに分けることです。下のコードは分割点のインデックスを計算して、データをモデルを訓練するために使用可能な観測結果の 67% の訓練データセットを取り分け、残りの 33% をモデルのテストのために残します :
# split into train and test sets train_size = int(len(dataset) * 0.67) test_size = len(dataset) - train_size train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] print(len(train), len(test))
96 48
これで上で説明したように、新しいデータセットを作成する関数を定義できます。
その関数は 2 つの引数を取ります : dataset、これはデータセットに変換することを望む NumPy 配列 ; look_back、これは次の時間区間を予測するために入力変数として使用する前の時間ステップの数です – この場合 1 にデフォルト設定されています。
このデフォルトは、X が与えられた時間 (t) における乗客数、Y が次の時間 (t + 1) における乗客数であるようなデータセットを作成します :
# convert an array of values into a dataset matrix def create_dataset(dataset, look_back=1): dataX, dataY = [], [] for i in range(len(dataset)-look_back-1): a = dataset[i:(i+look_back), 0] dataX.append(a) dataY.append(dataset[i + look_back, 0]) return numpy.array(dataX), numpy.array(dataY)
データ・セットの最初の数行上でこの関数の効果を見てみましょう (分かりやすさのために非正規化された形式で示します) :
X Y 112 118 118 132 132 129 129 121 121 135
これらの最初の 5 行を前のセクションの元のデータセット・サンプルと比較すれば、数字の X=t と Y=t+1 パターンを見ることができます。
モデリングのために訓練とテスト・データセットを準備するためにこの関数を使用します :
# reshape into X=t and Y=t+1 look_back = 1 trainX, trainY = create_dataset(train, look_back) testX, testY = create_dataset(test, look_back)
データの冒頭を確認しておきます :
print (trainX[:10,:]) print (trainY[:10])
[[ 0.01544401] [ 0.02702703] [ 0.05405405] [ 0.04826255] [ 0.03281853] [ 0.05984557] [ 0.08494207] [ 0.08494207] [ 0.06177607] [ 0.02895753]] [ 0.02702703 0.05405405 0.04826255 0.03281853 0.05984557 0.08494207 0.08494207 0.06177607 0.02895753 0. ]
ついでにデータのボトムも確認しましょう :
print(trainX[-10:,:]) print(trainY[-10:])
[[ 0.34749034] [ 0.33397684] [ 0.41119692] [ 0.4034749 ] [ 0.41312739] [ 0.52123547] [ 0.59652507] [ 0.58108103] [ 0.48455599] [ 0.38996139]] [ 0.33397684 0.41119692 0.4034749 0.41312739 0.52123547 0.59652507 0.58108103 0.48455599 0.38996139 0.3223938 ]
LSTM ネットワークは入力データ (X) が [samples, time steps, features] の形式の特定の配列構造で提供されることを想定しています。現在、私達のデータは [samples, features] の形式で各サンプルのための 1 タイムステップとして問題を構成しています。次のように numpy.reshape() を使用して準備された訓練とテスト入力データを想定される構造に変換することができます :
# reshape input to be [samples, time steps, features] trainX = numpy.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1])) testX = numpy.reshape(testX, (testX.shape[0], 1, testX.shape[1]))
ここで変換前の trainX.shape は (94, 1) で reshape 後は (94, 1, 1) になります。
trainX の冒頭を確認しておきます :
print(trainX[:10,:])
[[[ 0.01544401]] [[ 0.02702703]] [[ 0.05405405]] [[ 0.04826255]] [[ 0.03281853]] [[ 0.05984557]] [[ 0.08494207]] [[ 0.08494207]] [[ 0.06177607]] [[ 0.02895753]]]
今、この問題に対する LSTM ネットワークを設計して fit する準備ができました。ネットワークは 1 入力を持つ可視層、4 LSTM ブロックあるいはニューロンを持つ隠れ層、そして単一の値予測を行なう出力層を持ちます。デフォルトの sigmoid 活性化関数が LSTM ブロックのために使用されます。ネットワークは 100 エポック訓練されてサイズ 1 のバッチが使用されます :
# create and fit the LSTM network model = Sequential() model.add(LSTM(4, input_shape=(1, look_back))) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2)
モデルが fit すれば、モデルのパフォーマンスを訓練とテスト・データセット上で見積もることができます。これは新しいモデルのための比較ポイントを与えてくれます。
元データと同じ単位 (月毎に乗客千人) でパフォーマンスをレポートすることを確かなものとするために、エラー・スコアを計算する前に予測を invert することに注意してください :
# 予想を遂行する trainPredict = model.predict(trainX) testPredict = model.predict(testX) # 予測を invert する trainPredict = scaler.inverse_transform(trainPredict) trainY = scaler.inverse_transform([trainY]) testPredict = scaler.inverse_transform(testPredict) testY = scaler.inverse_transform([testY]) # 平方平均二乗誤差 (RMSE, root mean squared error) を計算する trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) print('Train Score: %.2f RMSE' % (trainScore)) testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) print('Test Score: %.2f RMSE' % (testScore))
最後に、モデルの技量の視覚表示を得るために訓練とテスト・データセットの両者のためにモデルを使用して予測を生成することができます。
データがどのように準備されたかにより、x-軸上で元データセットと共に整列されるように予測をシフトしなければなりません。準備ができればデータがプロットされて、元データセットが青色、訓練データセットのための予測が緑色、そしてまだ見ていないテスト・データセットのための予測は赤色で示されます :
# shift train predictions for plotting trainPredictPlot = numpy.empty_like(dataset) trainPredictPlot[:, :] = numpy.nan trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict # shift test predictions for plotting testPredictPlot = numpy.empty_like(dataset) testPredictPlot[:, :] = numpy.nan testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict # plot baseline and predictions plt.plot(scaler.inverse_transform(dataset)) plt.plot(trainPredictPlot) plt.plot(testPredictPlot) plt.show()
サンプルの実行は次のような出力を生成します。まずは 10 エポック :
Epoch 1/10 - 0s - loss: 0.0413 Epoch 2/10 - 0s - loss: 0.0202 Epoch 3/10 - 0s - loss: 0.0145 Epoch 4/10 - 0s - loss: 0.0131 Epoch 5/10 - 0s - loss: 0.0121 Epoch 6/10 - 0s - loss: 0.0111 Epoch 7/10 - 0s - loss: 0.0102 Epoch 8/10 - 0s - loss: 0.0093 Epoch 9/10 - 0s - loss: 0.0081 Epoch 10/10 - 0s - loss: 0.0071 Train Score: 41.80 RMSE Test Score: 115.66 RMSE
続いて 100 エポック :
Epoch 1/100 - 0s - loss: 0.0413 Epoch 2/100 - 0s - loss: 0.0202 Epoch 3/100 - 0s - loss: 0.0146 Epoch 4/100 - 0s - loss: 0.0131 Epoch 5/100 - 0s - loss: 0.0121 Epoch 6/100 - 0s - loss: 0.0111 Epoch 7/100 - 0s - loss: 0.0102 Epoch 8/100 - 0s - loss: 0.0093 Epoch 9/100 - 0s - loss: 0.0081 Epoch 10/100 - 0s - loss: 0.0071 ... Epoch 95/100 - 0s - loss: 0.0020 Epoch 96/100 - 0s - loss: 0.0020 Epoch 97/100 - 0s - loss: 0.0020 Epoch 98/100 - 0s - loss: 0.0020 Epoch 99/100 - 0s - loss: 0.0020 Epoch 100/100 - 0s - loss: 0.0020 Train Score: 22.92 RMSE Test Score: 47.53 RMSE
モデルは、訓練とテスト・データセットの両者にフィットし、ジョブを上手く遂行することが見て取れます。モデルは訓練データセット上でおよそ 23 乗客数 (千人単位) の平均エラー、テスト・データセット上でおよそ 48 乗客数 (千人単位) の平均エラーを持ちます。悪くはありません。
ちなみに、1,000 エポック訓練すると overfitting の傾向が見られます :
Train Score: 22.74 RMSE Test Score: 53.89 RMSE
ウィンドウ・メソッドを使用した回帰のための LSTM
次のタイムステップの予測をするために複数の、直近のタイムステップを使用出来るように問題を構成することもできます。これはウィンドウと呼ばれ、そのサイズは各問題のために調整可能なパラメータです。
例えば、現在の時間 (t) が与えられてシークエンスの次の時間 (t+1) における値を予測することを望む場合、入力変数として現在時間 (t)、そして 2 つの前の時間 (t-1 と t-2) を使用することができます。
回帰問題として構成されるとき、入力変数は t-2, t-1, t で、出力変数は t+1 です。前のセクションで作成した create_dataset() 関数は look_back 引数を 1 から 3 に増やすことで時系列問題のこの形式化を作成できます。
この手形式化を持つデータセットのサンプルは以下のようなものです :
X1 X2 X3 Y 112 118 132 129 118 132 129 121 132 129 121 135 129 121 135 148 121 135 148 148
前のセクションのサンプルをより大きいウィンドウサイズで再実行できますが、念のため、そのウィンドウサイズを変更しただけのコード全体を再掲しておきます :
# LSTM for international airline passengers problem with window regression framing import numpy import matplotlib.pyplot as plt from pandas import read_csv import math from keras.models import Sequential from keras.layers import Dense from keras.layers import LSTM from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_squared_error # convert an array of values into a dataset matrix def create_dataset(dataset, look_back=1): dataX, dataY = [], [] for i in range(len(dataset)-look_back-1): a = dataset[i:(i+look_back), 0] dataX.append(a) dataY.append(dataset[i + look_back, 0]) return numpy.array(dataX), numpy.array(dataY) # fix random seed for reproducibility numpy.random.seed(7) # load the dataset dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) dataset = dataframe.values dataset = dataset.astype('float32') # normalize the dataset scaler = MinMaxScaler(feature_range=(0, 1)) dataset = scaler.fit_transform(dataset) # split into train and test sets train_size = int(len(dataset) * 0.67) test_size = len(dataset) - train_size train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] # reshape into X=t and Y=t+1 look_back = 3 trainX, trainY = create_dataset(train, look_back) testX, testY = create_dataset(test, look_back) # reshape input to be [samples, time steps, features] trainX = numpy.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1])) testX = numpy.reshape(testX, (testX.shape[0], 1, testX.shape[1])) # create and fit the LSTM network model = Sequential() model.add(LSTM(4, input_shape=(1, look_back))) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2) # make predictions trainPredict = model.predict(trainX) testPredict = model.predict(testX) # invert predictions trainPredict = scaler.inverse_transform(trainPredict) trainY = scaler.inverse_transform([trainY]) testPredict = scaler.inverse_transform(testPredict) testY = scaler.inverse_transform([testY]) # calculate root mean squared error trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) print('Train Score: %.2f RMSE' % (trainScore)) testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) print('Test Score: %.2f RMSE' % (testScore)) # shift train predictions for plotting trainPredictPlot = numpy.empty_like(dataset) trainPredictPlot[:, :] = numpy.nan trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict # shift test predictions for plotting testPredictPlot = numpy.empty_like(dataset) testPredictPlot[:, :] = numpy.nan testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict # plot baseline and predictions plt.plot(scaler.inverse_transform(dataset)) plt.plot(trainPredictPlot) plt.plot(testPredictPlot) plt.show()
reshape 前の trainX, trainY の冒頭です。shape はそれぞれ (87, 8) (87,) です :
[[ 0.01544401 0.02702703 0.05405405] [ 0.02702703 0.05405405 0.04826255] [ 0.05405405 0.04826255 0.03281853] [ 0.04826255 0.03281853 0.05984557] [ 0.03281853 0.05984557 0.08494207] [ 0.05984557 0.08494207 0.08494207] [ 0.08494207 0.08494207 0.06177607] [ 0.08494207 0.06177607 0.02895753] [ 0.06177607 0.02895753 0. ] [ 0.02895753 0. 0.02702703]] [ 0.04826255 0.03281853 0.05984557 0.08494207 0.08494207 0.06177607 0.02895753 0. 0.02702703 0.02123553]
reshape 後の trainX の冒頭です。shape は (87, 1, 8) :
[[[ 0.01544401 0.02702703 0.05405405]] [[ 0.02702703 0.05405405 0.04826255]] [[ 0.05405405 0.04826255 0.03281853]] [[ 0.04826255 0.03281853 0.05984557]] [[ 0.03281853 0.05984557 0.08494207]] [[ 0.05984557 0.08494207 0.08494207]] [[ 0.08494207 0.08494207 0.06177607]] [[ 0.08494207 0.06177607 0.02895753]] [[ 0.06177607 0.02895753 0. ]] [[ 0.02895753 0. 0.02702703]]]
サンプルの実行は次のような出力を生成します :
... Epoch 91/100 - 0s - loss: 0.0021 Epoch 92/100 - 0s - loss: 0.0021 Epoch 93/100 - 0s - loss: 0.0021 Epoch 94/100 - 0s - loss: 0.0021 Epoch 95/100 - 0s - loss: 0.0021 Epoch 96/100 - 0s - loss: 0.0021 Epoch 97/100 - 0s - loss: 0.0021 Epoch 98/100 - 0s - loss: 0.0021 Epoch 99/100 - 0s - loss: 0.0022 Epoch 100/100 - 0s - loss: 0.0020 Train Score: 24.19 RMSE Test Score: 58.03 RMSE
エラーが前のセクションのそれ (Train Score: 22.74 RMSE; Test Score: 53.89 RMSE) と比較して僅かばかり増えたことが分かりますが、これはウィンドウ・サイズとネットワーク・アーキテクチャが調整されていないためです。
試しに look_back = 8 として 200 エポック訓練すれば改良されます :
Epoch 191/200 - 0s - loss: 0.0017 Epoch 192/200 - 0s - loss: 0.0017 Epoch 193/200 - 0s - loss: 0.0017 Epoch 194/200 - 0s - loss: 0.0017 Epoch 195/200 - 0s - loss: 0.0017 Epoch 196/200 - 0s - loss: 0.0019 Epoch 197/200 - 0s - loss: 0.0018 Epoch 198/200 - 0s - loss: 0.0017 Epoch 199/200 - 0s - loss: 0.0017 Epoch 200/200 - 0s - loss: 0.0016 Train Score: 20.56 RMSE Test Score: 49.93 RMSE
タイムステップを伴う回帰のための LSTM
タイムステップは時系列問題を構成するためのもう一つの方法を提供します。上のウィンドウ・サンプルのように、次のタイムステップの出力を予測するために時系列の先行するタイプステップを入力として取ることができます。
過去の観測を個別の入力特徴として構成する代わりに、それらを一つの入力特徴の (複数の) タイムステップとして使用することができます、これは実際には問題のより正確な構成になります。
前のウィンドウ・ベースのサンプルと同じデータ表現を使用してこれを行なうことができますが、データを reshape するとき、カラムをタイムステップ次元として設定して特徴次元は 1 に変更し戻す必要があります。例えば :
# reshape input to be [samples, time steps, features] trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1)) testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], 1))
(look_back = 8 の場合、) trainX の reshape 前の shape は (87, 8)で、reshape 後は (87, 8, 1) になります。
このサンプルを実行すると次のような出力を生成します。look_back = 8、200 エポックで訓練しました :
Epoch 191/200 - 0s - loss: 0.0017 Epoch 192/200 - 0s - loss: 0.0017 Epoch 193/200 - 0s - loss: 0.0017 Epoch 194/200 - 0s - loss: 0.0017 Epoch 195/200 - 0s - loss: 0.0018 Epoch 196/200 - 0s - loss: 0.0018 Epoch 197/200 - 0s - loss: 0.0019 Epoch 198/200 - 0s - loss: 0.0017 Epoch 199/200 - 0s - loss: 0.0017 Epoch 200/200 - 0s - loss: 0.0017 Train Score: 21.13 RMSE Test Score: 46.41 RMSE
バッチ間のメモリを持つ LSTM
LSTM ネットワークは、長いシークエンスに渡り記憶することができるメモリを持ちます。
通常は、ネットワーク内の状態は model.predict() や model.evaluate() の各呼び出しに時に加えて、モデルを fit するとき各訓練バッチ後にリセットされますが、LSTM 層を “ステートフル” にすることで、LSTM ネットワークの内部状態が Keras でクリアされるときにより良い制御を得ることができます。これは、それが訓練シークエンス全体に渡り状態を構築できて、予測をするために必要であればその状態を維持することさえ可能であることを意味します。
この場合、ネットワークを fit しているとき訓練データがシャッフルされていないことを必要とします。またエポック後に model.reset_states() の呼び出しによるネットワーク状態の明示的なりセットを要求します。これはエポックの外側のループを作成して各エポック内で model.fit() そして model.reset_states() を呼びださなければならないことを意味します。例えば :
for i in range(100): model.fit(trainX, trainY, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) model.reset_states()
最後に、LSTM 層を構築するとき、stateful パラメータは True に設定されなければなりません、そして input 次元を指定する代わりに、batch_input_shape パラメータを設定することにより、バッチのサンプル数、サンプルのタイムステップ数、そしてタイムステップの特徴数をハードコードしなければなりません。例えば :
model.add(LSTM(4, batch_input_shape=(batch_size, time_steps, features), stateful=True))
それから後でモデルを評価したり予測するときには同じバッチサイズが使用されなければなりません。例えば :
model.predict(trainX, batch_size=batch_size)
ステートフル LSTM を使用するためにタイムステップの例を適応させることができます。
完全なコードは以下のようなものです :
# LSTM for international airline passengers problem with memory import numpy import matplotlib.pyplot as plt from pandas import read_csv import math from keras.models import Sequential from keras.layers import Dense from keras.layers import LSTM from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_squared_error # convert an array of values into a dataset matrix def create_dataset(dataset, look_back=1): dataX, dataY = [], [] for i in range(len(dataset)-look_back-1): a = dataset[i:(i+look_back), 0] dataX.append(a) dataY.append(dataset[i + look_back, 0]) return numpy.array(dataX), numpy.array(dataY) # fix random seed for reproducibility numpy.random.seed(7) # load the dataset dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) dataset = dataframe.values dataset = dataset.astype('float32') # normalize the dataset scaler = MinMaxScaler(feature_range=(0, 1)) dataset = scaler.fit_transform(dataset) # split into train and test sets train_size = int(len(dataset) * 0.67) test_size = len(dataset) - train_size train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] # reshape into X=t and Y=t+1 look_back = 3 trainX, trainY = create_dataset(train, look_back) testX, testY = create_dataset(test, look_back) # reshape input to be [samples, time steps, features] trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1)) testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], 1)) # create and fit the LSTM network batch_size = 1 model = Sequential() model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') for i in range(100): model.fit(trainX, trainY, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) model.reset_states() # make predictions trainPredict = model.predict(trainX, batch_size=batch_size) model.reset_states() testPredict = model.predict(testX, batch_size=batch_size) # invert predictions trainPredict = scaler.inverse_transform(trainPredict) trainY = scaler.inverse_transform([trainY]) testPredict = scaler.inverse_transform(testPredict) testY = scaler.inverse_transform([testY]) # calculate root mean squared error trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) print('Train Score: %.2f RMSE' % (trainScore)) testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) print('Test Score: %.2f RMSE' % (testScore)) # shift train predictions for plotting trainPredictPlot = numpy.empty_like(dataset) trainPredictPlot[:, :] = numpy.nan trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict # shift test predictions for plotting testPredictPlot = numpy.empty_like(dataset) testPredictPlot[:, :] = numpy.nan testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict # plot baseline and predictions plt.plot(scaler.inverse_transform(dataset)) plt.plot(trainPredictPlot) plt.plot(testPredictPlot) plt.show()
サンプルの実行は次のような出力を生成します :
... Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0016 Train Score: 20.34 RMSE Test Score: 64.11 RMSE
結果が悪くなっていることを見て取れます。このモデルは、問題の構造を内在化するために更なるモジュールを必要としより多くのエポックの間訓練されることが必要なのかもしれません。
バッチ間のメモリを持つスタック LSTM
最後に、LSTM の大きな恩恵を見てみます : それらは深層ネットワーク・アーキテクチャにスタックされたとき成功的に訓練されるという事実です。
LSTM ネットワークは他の層タイプがスタックできるのと同じ方法で Keras でスタック可能です。構成に一つ付け足す必要があるのは、先行する LSTM 層は続く LSTM 層それぞれにシークエンスを返さなければならないことです。これは層の return_sequences パラメータを True に設定することにより成されます。
次のようにして、前のセクションのステートフル LSTM を 5 の層を持つように拡張できます :
model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True, return_sequences=True)) model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True))
完全なコードリストは以下です :
# Stacked LSTM for international airline passengers problem with memory import numpy import matplotlib.pyplot as plt from pandas import read_csv import math from keras.models import Sequential from keras.layers import Dense from keras.layers import LSTM from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_squared_error # convert an array of values into a dataset matrix def create_dataset(dataset, look_back=1): dataX, dataY = [], [] for i in range(len(dataset)-look_back-1): a = dataset[i:(i+look_back), 0] dataX.append(a) dataY.append(dataset[i + look_back, 0]) return numpy.array(dataX), numpy.array(dataY) # fix random seed for reproducibility numpy.random.seed(7) # load the dataset dataframe = read_csv('international-airline-passengers.csv', usecols=[1], engine='python', skipfooter=3) dataset = dataframe.values dataset = dataset.astype('float32') # normalize the dataset scaler = MinMaxScaler(feature_range=(0, 1)) dataset = scaler.fit_transform(dataset) # split into train and test sets train_size = int(len(dataset) * 0.67) test_size = len(dataset) - train_size train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:] # reshape into X=t and Y=t+1 look_back = 3 trainX, trainY = create_dataset(train, look_back) testX, testY = create_dataset(test, look_back) # reshape input to be [samples, time steps, features] trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], 1)) testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], 1)) # create and fit the LSTM network batch_size = 1 model = Sequential() model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True, return_sequences=True)) model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') for i in range(100): model.fit(trainX, trainY, epochs=1, batch_size=batch_size, verbose=2, shuffle=False) model.reset_states() # make predictions trainPredict = model.predict(trainX, batch_size=batch_size) model.reset_states() testPredict = model.predict(testX, batch_size=batch_size) # invert predictions trainPredict = scaler.inverse_transform(trainPredict) trainY = scaler.inverse_transform([trainY]) testPredict = scaler.inverse_transform(testPredict) testY = scaler.inverse_transform([testY]) # calculate root mean squared error trainScore = math.sqrt(mean_squared_error(trainY[0], trainPredict[:,0])) print('Train Score: %.2f RMSE' % (trainScore)) testScore = math.sqrt(mean_squared_error(testY[0], testPredict[:,0])) print('Test Score: %.2f RMSE' % (testScore)) # shift train predictions for plotting trainPredictPlot = numpy.empty_like(dataset) trainPredictPlot[:, :] = numpy.nan trainPredictPlot[look_back:len(trainPredict)+look_back, :] = trainPredict # shift test predictions for plotting testPredictPlot = numpy.empty_like(dataset) testPredictPlot[:, :] = numpy.nan testPredictPlot[len(trainPredict)+(look_back*2)+1:len(dataset)-1, :] = testPredict # plot baseline and predictions plt.plot(scaler.inverse_transform(dataset)) plt.plot(trainPredictPlot) plt.plot(testPredictPlot) plt.show()
サンプルの実行は次の出力を生成します。look_back = 8 で 200 エポックです :
Epoch 1/1 - 0s - loss: 0.0016 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Epoch 1/1 - 0s - loss: 0.0015 Train Score: 20.03 RMSE Test Score: 68.57 RMSE
テスト・データセット上の予測は再度、悪化しています。
これは追加の訓練エポックの必要性を提示している更なる証拠と言えます。
以上