今回は、シーケンス問題の解決に用いられる時系列予測の実行方法について学びます。
時系列予測とは、時間に依存する入力に基づいて結果を予測しなければならないタイプの問題のことを指します。
時系列データの典型的な例は、株価が時間とともに変化する株式市場のデータです。
同様に、ある場所の1時間ごとの気温も変化しており、これも時系列データとして考えることができます。
時系列データは基本的に連続したデータであるため、時系列問題はしばしばシーケンス問題と呼ばれる。
RNN(Recurrent Neural Networks)は、シーケンス問題を効率的に解くことができることが証明されています。
特に、RNNの変形であるLSTM(Long Short Term Memory Network)は、現在、様々な領域でシーケンス問題の解決に利用されている。
数列問題の種類
配列問題は、大きく分けて次のような種類があります。
- 1対1:入力と出力が1つずつある場合。1対1:入力が1つ、出力が1つの問題。典型的な例としては、画像があり、その画像のラベルを1つ予測したい場合。
-
- 多対一:多対一のシーケンス問題では、入力として一連のデータがあり、単一の出力を予測する必要がある。テキスト分類は多対一系列問題の典型的な例であり、入力として一連の単語があり、単一の出力タグを予測する必要がある。
-
- 1対多。一対多:一対多のシーケンス問題では、入力が一つで出力が一つである。典型的な例は、画像とそれに対応する説明文である。
- 多対多。多対多のシーケンス問題では,シーケンスの入力とシーケンスの出力がある.例えば、7日間の株価を入力とし、次の7日間の株価を出力とするようなものである。チャットボットも多対多シーケンス問題の一例で、テキストシーケンスが入力、別のテキストシーケンスが出力となります。
本記事は連載の第1回目です。
この記事では、LSTMとそのさまざまなバリエーションが、1対1および多対1のシーケンス問題を解くためにどのように使用されるかを見ていきます。
このシリーズの次のパートでは、1対多と多対多のシーケンス問題の解き方を見ていきます。
PythonのKerasライブラリを使用する予定です。
この記事を読んだら、株価予測や天気予報など、過去のデータを元にした問題を解けるようになります。
また、テキストも単語の並びなので、この記事で得た知識は、テキストの分類、言語生成などの自然言語処理タスクの解決にも利用できます。
一対一配列の問題
先に述べたように、一対一の系列問題では、入力と出力が一つずつです。
このセクションでは、2種類のシーケンス問題を見ていきます。
まず、1つの素性を持つ1対1系列問題の解き方を見て、次に複数の素性を持つ1対1系列問題の解き方を見ていきます。
一対一のシーケンス問題
このセクションでは、各タイムステップが単一の特徴を持つ一対一シーケンス問題の解き方を見ていきます。
まず、この記事で使用する必要なライブラリをインポートします。
from numpy import array
from keras.preprocessing.text import one_hot
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers.core import Activation, Dropout, Dense
from keras.layers import Flatten, LSTM
from keras.layers import GlobalMaxPooling1D
from keras.models import Model
from keras.layers.embeddings import Embedding
from sklearn.model_selection import train_test_split
from keras.preprocessing.text import Tokenizer
from keras.layers import Input
from keras.layers.merge import Concatenate
from keras.layers import Bidirectional
import pandas as pd
import numpy as np
import re
import matplotlib.pyplot as plt
データセットの作成
次のステップでは、このセクションで使用するデータセットを準備します。
X = list()
Y = list()
X = [x+1 for x in range(20)]
Y = [y * 15 for y in X]
print(X)
print(Y)
上のスクリプトでは、20個の入力と20個の出力を作成しています。
各入力は1つのタイムステップで構成され、その中に1つの特徴が含まれる。
各出力値は対応する入力値の15倍である。
上記のスクリプトを実行すると、以下のような入力と出力の値が表示されるはずである。
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
[15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300]
LSTM層への入力は3次元の形、すなわち(サンプル、タイムステップ、特徴)である必要がある。
sample は入力データのサンプル数である。
ここでは 20 個のサンプルが入力されている。
time-steps は1サンプルあたりのタイムステップ数である。
ここでは、1タイムステップである。
最後に、featureは1タイムステップあたりの特徴量に相当する。
1つのタイムステップに1つの特徴がある。
以下のコマンドで、データの形状を変更することができます。
X = array(X).reshape(20, 1, 1)
シンプルなLSTMによる解決
では、1層のLSTMでシンプルなLSTMモデルを作ってみましょう。
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(1, 1)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
print(model.summary())
上のスクリプトでは、50ニューロンからなる1つのLSTM層と relu
活性化関数からなるLSTMモデルを作成します。
入力形状は(1,1)であることが分かる。
上記のスクリプトを実行すると、以下のようなサマリーが出力されます。
Layer (type) Output Shape Param #=================================================================
lstm_16 (LSTM) (None, 50) 10400
_________________________________________________________________
dense_15 (Dense) (None, 1) 51
=================================================================
Total params: 10,451
Trainable params: 10,451
Non-trainable params: 0
それでは、モデルを学習してみましょう。
model.fit(X, Y, epochs=2000, validation_split=0.2, batch_size=5)
ここでは、バッチサイズ5で2000エポックについてモデルを学習します。
任意の数を選択することができます。
モデルの学習が完了したら、新しいインスタンスに対して予測を行うことができます。
例えば、30という入力に対して出力を予測したいとします。
実際の出力は30 x 15 = 450となるはずです。
どのような値が得られるか見てみましょう。
まず、テストデータをLSTMが期待する正しい形状、すなわち3D形状に変換する必要がある。
以下のスクリプトは30という数字に対する出力を予測する。
test_input = array([30])
test_input = test_input.reshape((1, 1, 1))
test_output = model.predict(test_input, verbose=0)
print(test_output)
450より少し小さい437.86`という出力値が得られました。
注意:スクリプトを実行して得られる出力は、私のものとは異なるということを述べておく必要がある。
これはLSTMニューラルネットワークがランダムな値やあなたの値で重みを初期化するからです。
しかし、全体としては、結果はあまり変わらないはずです。
スタックドLSTMによる解法
では、stacked LSTMを作成し、より良い結果が得られるか見てみましょう。
データセットはそのままで、モデルを変更します。
以下のスクリプトを見てください。
model = Sequential()
model.add(LSTM(50, activation='relu', return_sequences=True, input_shape=(1, 1)))
model.add(LSTM(50, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
print(model.summary())
上のモデルでは、2つのLSTM層があります。
最初のLSTM層はパラメータ return_sequences
を持っていて、これは True
にセットされている。
return sequenceがTrue
に設定されると、各ニューロンの隠された状態の出力が次のLSTM層の入力として使われる。
上記のモデルの概要は以下の通りである。
_________________________________________________________________
Layer (type) Output Shape Param #=================================================================
lstm_33 (LSTM) (None, 1, 50) 10400
_________________________________________________________________
lstm_34 (LSTM) (None, 50) 20200
_________________________________________________________________
dense_24 (Dense) (None, 1) 51
=================================================================
Total params: 30,651
Trainable params: 30,651
Non-trainable params: 0
________________________
次に、以下のスクリプトのようにモデルを学習させる必要がある。
history = model.fit(X, Y, epochs=2000, validation_split=0.2, verbose=1, batch_size=5)
モデルの学習が完了したら、再びテストデータ点すなわち30に対して予測を行う。
test_input = array([30])
test_input = test_input.reshape((1, 1, 1))
test_output = model.predict(test_input, verbose=0)
print(test_output)
459.85という出力が得られ、1つのLSTM層で達成した437よりも良い数値となりました。
複数の特徴を持つ一対一のシーケンス問題
前節では、各入力サンプルは1つのタイムステップを持ち、各タイムステップは1つの特徴量を持っていました。
このセクションでは、入力タイムステップが複数の特徴を持つ場合の一対一シーケンス問題の解き方について見ていきます。
データセットの作成
まず、データセットを作成しましょう。
以下のスクリプトを見てください。
nums = 25
X1 = list()
X2 = list()
X = list()
Y = list()
X1 = [(x+1)*2 for x in range(25)]
X2 = [(x+1)*3 for x in range(25)]
Y = [x1*x2 for x1,x2 in zip(X1,X2)]
print(X1)
print(X2)
print(Y)
上のスクリプトでは、X1
、X2
、Y
の3つのリストを作成しています。
各リストの要素は25個で、これはサンプルサイズが25個であることを意味する。
最後に、Y
には出力が格納されます。
X1、
X2、
Y`のリストは以下のように出力されます。
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50]
[3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75]
[6, 24, 54, 96, 150, 216, 294, 384, 486, 600, 726, 864, 1014, 1176, 1350, 1536, 1734, 1944, 2166, 2400, 2646, 2904, 3174, 3456, 3750]
出力リストの各要素は、基本的に X1
と X2
のリストの対応する要素の積になります。
例えば、出力リストの2番目の要素は24であり、これはリスト X1
の2番目の要素、すなわち4とリスト X2
の2番目の要素、すなわち6との積である。
入力は X1
と X2
のリストの組み合わせからなり、各リストはカラムとして表現される。
次のスクリプトは最終的な入力を作成する。
X = np.column_stack((X1, X2))
print(X)
以下はその出力である。
[[ 2 3]
[ 4 6]
[ 6 9]
[ 8 12]
[10 15]
[12 18]
[14 21]
[16 24]
[18 27]
[20 30]
[22 33]
[24 36]
[26 39]
[28 42]
[30 45]
[32 48]
[34 51]
[36 54]
[38 57]
[40 60]
[42 63]
[44 66]
[46 69]
[48 72]
[50 75]]
ここで、変数 X
には最終的な特徴セットが格納されています。
この変数には2つの列、つまり1つの入力に対して2つの特徴が含まれていることがわかる。
先ほど説明したように、入力を3次元の形状に変換する必要があります。
入力は25サンプルで、各サンプルは1タイムステップ、各タイムステップは2特徴で構成されている。
以下のスクリプトは入力を再形成する。
X = array(X).reshape(25, 1, 2)
シンプルなLSTMによる解決
これでLSTMのモデルを学習する準備が整いました。
まず、前節と同じように1層のLSTMモデルを開発しましょう。
model = Sequential()
model.add(LSTM(80, activation='relu', input_shape=(1, 2)))
model.add(Dense(10, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
print(model.summary())
ここで、LSTM 層は 80 個のニューロンを含む。
2つの密な層があり、最初の層は10個のニューロンを含み、出力層として機能する2番目の密な層は1個のニューロンを含む。
このモデルの概要は以下の通りである。
Layer (type) Output Shape Param #=================================================================
lstm_38 (LSTM) (None, 80) 26560
_________________________________________________________________
dense_29 (Dense) (None, 10) 810
_________________________________________________________________
dense_30 (Dense) (None, 1) 11
=================================================================
Total params: 27,381
Trainable params: 27,381
Non-trainable params: 0
_________________________________________________________________
None
以下のスクリプトでモデルを学習させます。
model.fit(X, Y, epochs=2000, validation_split=0.2, batch_size=5)
新しいデータ点について、学習したモデルをテストしてみよう。
データポイントは2つの特徴、すなわち(55,80)を持ち、実際の出力は55 x 80 = 4400となるはずである。
我々のアルゴリズムが何を予測するか見てみよう。
以下のスクリプトを実行する。
test_input = array([55,80])
test_input = test_input.reshape((1, 1, 2))
test_output = model.predict(test_input, verbose=0)
print(test_output)
出力は3263.44であり、実際の出力とはかけ離れている。
スタックドLSTMによる解法
では、複数のLSTMと密な層でより複雑なLSTMを作り、答えが改善されるかどうか見てみましょう。
model = Sequential()
model.add(LSTM(200, activation='relu', return_sequences=True, input_shape=(1, 2)))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(LSTM(50, activation='relu', return_sequences=True))
model.add(LSTM(25, activation='relu'))
model.add(Dense(20, activation='relu'))
model.add(Dense(10, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
print(model.summary())
モデルの概要は以下の通りです。
_________________________________________________________________
Layer (type) Output Shape Param #=================================================================
lstm_53 (LSTM) (None, 1, 200) 162400
_________________________________________________________________
lstm_54 (LSTM) (None, 1, 100) 120400
_________________________________________________________________
lstm_55 (LSTM) (None, 1, 50) 30200
_________________________________________________________________
lstm_56 (LSTM) (None, 25) 7600
_________________________________________________________________
dense_43 (Dense) (None, 20) 520
_________________________________________________________________
dense_44 (Dense) (None, 10) 210
_________________________________________________________________
dense_45 (Dense) (None, 1) 11
=================================================================
Total params: 321,341
Trainable params: 321,341
Non-trainable params: 0
次のステップは、モデルを学習し、テストデータ点すなわち(55,80)でテストすることである。
精度を向上させるために、バッチサイズを小さくし、モデルがより複雑になったので、エポック数を減らすこともできます。
以下のスクリプトはLSTMモデルを学習させ、テストデータ点に対して予測を行います。
history = model.fit(X, Y, epochs=1000, validation_split=0.1, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(test_output)
出力では3705.33という値が得られ、4400にはまだ及びませんが、以前1層のLSTMで得られた3263.44という値よりはずっと良くなっています。
LSTM層、密な層、バッチサイズ、エポック数の組み合わせを変えて、より良い結果が得られるかどうか試してみてください。
多対一の配列問題
前節では、LSTMを用いて1対1シーケンス問題を解く方法を見た。
1対1シーケンス問題では、各サンプルは1つまたは複数の特徴量の1つのタイムステップから構成されています。
このような単一時間ステップのデータは、本当の意味でのシーケンスデータとは言えない。
密結合ニューラルネットワークは、単一タイムステップのデータでより良いパフォーマンスを発揮することが証明されています。
実際のシーケンスデータは、過去7日間の株価や、複数の単語を含む文章など、複数のタイムステップから構成されています。
ここでは、多対一の系列問題の解き方について見ていく。
多対一系列問題では、各入力サンプルは複数のタイムステップを持つが、出力は単一の要素で構成される。
入力の各タイムステップは1つまたはそれ以上の特徴を持つことができる。
ここでは、まず1つの特徴を持つ多対一のシーケンス問題から始め、次に入力のタイムステップが複数の特徴を持つ多対一の問題の解き方を見ていくことにする。
単一機能による多対一のシーケンス問題
まず、データセットを作成しよう。
データセットは15個のサンプルで構成される。
各サンプルは3つのタイムステップを持ち、各タイムステップは1つの特徴、すなわち数字で構成される。
各サンプルの出力は、3つのタイムステップのそれぞれの数値の合計となる。
例えば、サンプルに4,5,6のシーケンスが含まれている場合、出力は4 + 5 + 6 = 10になります。
データセットの作成
まず,1 から 45 までの整数のリストを作成しよう.データセットには 15 個のサンプルが必要なので,最初の 45 個の整数を含む整数のリストを再形成することにする.
X = np.array([x+1 for x in range(45)])
print(X)
出力には,最初の45個の整数が表示されているはずである.
[ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45]
これを以下の関数でサンプル数、タイムステップ、特徴量に整形する。
X = X.reshape(15,3,1)
print(X)
上のスクリプトはリストX
を15サンプル、3タイムステップ、1特徴量からなる3次元形状に変換する。
また、このスクリプトは形状を変更したデータを表示する。
[[[ 1]
[ 2]
[ 3]]
[[ 4]
[ 5]
[ 6]]
[[ 7]
[ 8]
[ 9]]
[[10]
[11]
[12]]
[[13]
[14]
[15]]
[[16]
[17]
[18]]
[[19]
[20]
[21]]
[[22]
[23]
[24]]
[[25]
[26]
[27]]
[[28]
[29]
[30]]
[[31]
[32]
[33]]
[[34]
[35]
[36]]
[[37]
[38]
[39]]
[[40]
[41]
[42]]
[[43]
[44]
[45]]]
入力データを正しい形式に変換できたので、次は出力ベクトルを作ってみよう。
先に述べたように、出力の各要素は対応する入力サンプルのタイムステップの値の合計に等しくなります。
次のスクリプトは出力ベクトルを作成します。
Y = list()
for x in X:
Y.append(x.sum())
Y = np.array(Y)
print(Y)
出力配列 Y
は次のようになります。
[ 6 15 24 33 42 51 60 69 78 87 96 105 114 123 132]
シンプルなLSTMによる解決
では、LSTMを1層だけ使ったモデルを作ってみましょう。
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(3, 1)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
以下のスクリプトでモデルを学習させます。
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
モデルの学習が完了したら、それを使ってテストデータに対して予測を行うことができます。
50,51,52という数列の出力を予測してみましょう。
実際の出力は50 + 51 + 52 = 153となるはずです。
次のスクリプトはテスト点を3次元の形状に変換し、出力を予測する。
test_input = array([50,51,52])
test_input = test_input.reshape((1, 3, 1))
test_output = model.predict(test_input, verbose=0)
print(test_output)
出力は145.96となり、実際の出力値である153より7ポイントほど少なくなった。
スタックドLSTMによる解法
それでは、複数のレイヤーを持つ複雑なLSTMモデルを作成し、より良い結果が得られるかどうか見てみましょう。
以下のスクリプトを実行し、複数のLSTMと密な層を持つ複雑なモデルを作成し、学習させます。
model = Sequential()
model.add(LSTM(200, activation='relu', return_sequences=True, input_shape=(3, 1)))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(LSTM(50, activation='relu', return_sequences=True))
model.add(LSTM(25, activation='relu'))
model.add(Dense(20, activation='relu'))
model.add(Dense(10, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
それでは、50, 51, 52というテストシーケンスでモデルをテストしてみましょう。
test_output = model.predict(test_input, verbose=0)
print(test_output)
ここで得られた答えは155.37であり、先ほどの145.96よりも良い結果でした。
この場合、実際の答えである153との差は2ポイントしかありません。
双方向性LSTMによる解法
双方向LSTMは、入力配列から前方・後方の両方向から学習するLSTMの一種である。
最終的なシーケンスの解釈は、前方・後方両方の学習パスの連結となる。
では、双方向LSTMを使ってより良い結果が得られるか見てみましょう。
以下のスクリプトは双方向LSTMモデルを作成し、1つの双方向層とモデルの出力として働く1つの密な層からなる。
from keras.layers import Bidirectional
model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(3, 1)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
次のスクリプトはモデルを学習し、50,51,52のテストシーケンスに対して予測を行う。
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(test_output)
得られた結果は152.26であり、実際の結果よりほんの少し足りないだけです。
したがって、今回のデータセットでは、単層の双方向LSTMが単層とスタック型の双方向LSTMの両方を上回ると結論付けることができます。
複数個の特徴を持つ多対一系列問題
多対一系列問題では、各タイムステップが複数の素性からなる入力がある。
出力は単一の値か、あるいは入力時間ステップの各特徴ごとに1つずつ、複数の値である。
この節ではその両方の場合を取り上げる。
データセットの作成
データセットには15個のサンプルが含まれます。
各サンプルは3つのタイムステップで構成される。
それぞれのtime-stepsは2つの特徴量を持ちます。
2つのリストを作成しましょう。
1つは3の倍数で135まで、つまり45個の要素を含む。
もう1つのリストには、1から225までの5の倍数が含まれます。
2番目のリストも45個の要素を含む。
次のスクリプトは、この2つのリストを作成します。
X1 = np.array([x+3 for x in range(0, 135, 3)])
print(X1)
X2 = np.array([x+5 for x in range(0, 225, 5)])
print(X2)
リストの内容は、次の出力で見ることができます。
[ 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54
57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 102 105 108
111 114 117 120 123 126 129 132 135]
[ 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90
95 100 105 110 115 120 125 130 135 140 145 150 155 160 165 170 175 180
185 190 195 200 205 210 215 220 225]
上記のリストは、それぞれタイムサンプルの1つの特徴を表している。
この2つのリストを結合すると、以下のように集約されたデータセットが作成される。
X = np.column_stack((X1, X2))
print(X)
出力は集約されたデータセットである。
[ 6 10]
[ 9 15]
[ 12 20]
[ 15 25]
[ 18 30]
[ 21 35]
[ 24 40]
[ 27 45]
[ 30 50]
[ 33 55]
[ 36 60]
[ 39 65]
[ 42 70]
[ 45 75]
[ 48 80]
[ 51 85]
[ 54 90]
[ 57 95]
[ 60 100]
[ 63 105]
[ 66 110]
[ 69 115]
[ 72 120]
[ 75 125]
[ 78 130]
[ 81 135]
[ 84 140]
[ 87 145]
[ 90 150]
[ 93 155]
[ 96 160]
[ 99 165]
[102 170]
[105 175]
[108 180]
[111 185]
[114 190]
[117 195]
[120 200]
[123 205]
[126 210]
[129 215]
[132 220]
[135 225]]
LSTMが利用できるように、データを3次元に整形する必要があります。
このデータセットには全部で45行と2列がある。
このデータセットを15サンプル、3タイムステップ、2特徴に再形成する。
X = array(X).reshape(15, 3, 2)
print(X)
以下の出力で15個のサンプルを見ることができます。
[[[ 3 5]
[ 6 10]
[ 9 15]]
[[ 12 20]
[ 15 25]
[ 18 30]]
[[ 21 35]
[ 24 40]
[ 27 45]]
[[ 30 50]
[ 33 55]
[ 36 60]]
[[ 39 65]
[ 42 70]
[ 45 75]]
[[ 48 80]
[ 51 85]
[ 54 90]]
[[ 57 95]
[ 60 100]
[ 63 105]]
[[ 66 110]
[ 69 115]
[ 72 120]]
[[ 75 125]
[ 78 130]
[ 81 135]]
[[ 84 140]
[ 87 145]
[ 90 150]]
[[ 93 155]
[ 96 160]
[ 99 165]]
[[102 170]
[105 175]
[108 180]]
[[111 185]
[114 190]
[117 195]]
[[120 200]
[123 205]
[126 210]]
[[129 215]
[132 220]
[135 225]]]
出力はまた、15個の入力サンプルに対応する15個の値を持っている。
出力される各値は、各入力サンプルの3番目のタイムステップにおける2つの特徴量の合計となる。
例えば、最初のサンプルの3番目のタイムステップは、特徴量9と15を持つので、出力は24になる。
同様に、2番目のサンプルの3番目の時間ステップの2つの特徴量は18と30であり、対応する出力は48となり、以下同様である。
次のスクリプトは出力ベクトルを作成し、表示する。
[ 24 48 72 96 120 144 168 192 216 240 264 288 312 336 360]
では、この多対一列問題を単純な、積み重ねられた、双方向のLSTMによって解いてみよう。
シンプルなLSTMによる解決
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(3, 2)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
モデルを学習させる。
テストデータ点を作成し、そのテスト点に対してモデルを使って予測を行う。
test_input = array([[8, 51],
[11,56],
[14,61]])
test_input = test_input.reshape((1, 3, 2))
test_output = model.predict(test_input, verbose=0)
print(test_output)
入力の 3 番目のタイムステップの 2 つの特徴量の和は 14 + 61 = 75 である。
LSTM1層の我々のモデルは73.41を予測し、これはかなり近い。
スタックドLSTMによる解法
以下のスクリプトは、stacked LSTMを学習させ、テストポイントの予測を行う。
model = Sequential()
model.add(LSTM(200, activation='relu', return_sequences=True, input_shape=(3, 2)))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(LSTM(50, activation='relu', return_sequences=True))
model.add(LSTM(25, activation='relu'))
model.add(Dense(20, activation='relu'))
model.add(Dense(10, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(test_output)
出力は71.56で、単純なLSTMより悪いです。
どうやらstacked LSTMがオーバーフィットしているようです。
双方向性LSTMによる解法
単純な双方向LSTMの学習スクリプトと、テストデータ点の予測に使用されるコードです。
from keras.layers import Bidirectional
model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(3, 2)))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(test_output)
出力は76.82で、75にかなり近い値です。
ここでも双方向LSTMは他のアルゴリズムより優れているようです。
ここまで、我々は異なるタイムステップの複数の特徴量から単一の値を予測してきた。
もう一つ、多対一のシーケンスで、タイムステップの各特徴に対して一つの値を予測したい場合があります。
例えば、本節で使用したデータセットには3つのタイムステップがあり、各タイムステップには2つの特徴があります。
各特徴系列に対して、個別の値を予測したい場合がある。
次の例では、次のような入力があるとすると、それは明らかである。
[[[ 3 5]
[ 6 10]
[ 9 15]]
出力では、以下のように1つのタイムステップに2つの素性を持たせたい。
[12, 20]
出力の最初の値は最初の系列の続きであり、2番目の値は2番目の系列の続きであることがわかるだろう。
このような問題は、出力密層のニューロン数を、出力に欲しい特徴量の数に変更するだけで、解決できます。
しかし、まず出力ベクトル Y
を更新する必要がある。
Y = list()
for x in X:
new_item = list()
new_item.append(x[2][0]+3)
new_item.append(x[2][1]+5)
Y.append(new_item)
Y = np.array(Y)
print(Y)
上のスクリプトは更新された出力ベクトルを作成し、コンソールに表示する。
出力は以下のようになる。
[[ 12 20]
[ 21 35]
[ 30 50]
[ 39 65]
[ 48 80]
[ 57 95]
[ 66 110]
[ 75 125]
[ 84 140]
[ 93 155]
[102 170]
[111 185]
[120 200]
[129 215]
[138 230]]
それでは、単純なLSTM、スタック型LSTM、双方向型LSTMをデータセットに対して学習させましょう。
以下のスクリプトは単純なLSTMを学習させる。
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(3, 2)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
次のステップは、テストデータポイントでモデルをテストすることです。
次のスクリプトはテストデータ点を作成する。
test_input = array([[20,34],
[23,39],
[26,44]])
test_input = test_input.reshape((1, 3, 2))
test_output = model.predict(test_input, verbose=0)
print(test_output)
実際の出力は[29, 45]です。
我々のモデルは[29.089157, 48.469097]を予測し、これはかなり近いものです。
それでは、スタックドLSTMを学習し、テストデータ点の出力を予測しましょう。
model = Sequential()
model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(3, 2)))
model.add(LSTM(50, activation='relu', return_sequences=True))
model.add(LSTM(25, activation='relu'))
model.add(Dense(10, activation='relu'))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=500, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(test_output)
出力は[29.170143, 48.688267]となり、これも実際の出力に非常に近くなりました。
最後に、双方向LSTMを学習させ、テストポイントに対する予測を行います。
from keras.layers import Bidirectional
model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(3, 2)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(test_output)
出力は[29.2071, 48.737988]です。
双方向LSTMが最も正確な予測を行うことが再度確認できます。
結論
シーケンス問題では、現在の入力に加えて、以前の入力も追跡する必要があるため、単純なニューラルネットワークはシーケンス問題の解決に適していない。
ある種の記憶を持つニューラルネットワークの方がシーケンス問題の解決に適している。
LSTMはそのようなネットワークの1つである。
この記事では、LSTMアルゴリズムの異なるバリエーションを使って、一対一および多対一のシーケンス問題を解く方法を見た。
これは記事の最初の部分である。
第二部では、一対多と多対多のシーケンス問題の解き方について見ていきます。
また、チャットボットの作成に最もよく使われるエンコーダ・デコーダの仕組みについても勉強します。