KerasのLSTMで配列問題を解く。第2回

LSTMで配列問題を解く2回連載の2回目、最終回です。

本シリーズのパート1では、LSTMを用いて一対一および多対一のシーケンス問題を解く方法を説明しました。

このパートでは、KerasのLSTMを使って1対多と多対多のシーケンス問題を解く方法を説明します。

画像キャプションは、1対多シーケンス問題の典型的な例で、入力として1つの画像があり、単語列の形で画像の説明を予測する必要があります。

同様に、過去Y日間の株価を入力とする、今後X日間の株式市場予測は、多対多シーケンス問題の典型的な例と言えます。

この記事では、一対多と多対多の問題の非常に基本的な例を見ていきます。

しかし、この記事で学んだ概念は、今後の記事で見ていく株価予測や自動画像キャプション付けなどの高度なシーケンス問題を解くための基礎となるものである。

1対多連番問題

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+3 for x in range(-2, 43, 3)]


for i in X:
    output_vector = list()
    output_vector.append(i+1)
    output_vector.append(i+2)
    Y.append(output_vector)


print(X)
print(Y)


以下はその出力である。

[1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43]
[[2, 3], [5, 6], [8, 9], [11, 12], [14, 15], [17, 18], [20, 21], [23, 24], [26, 27], [29, 30], [32, 33], [35, 36], [38, 39], [41, 42], [44, 45]]


入力には、1つのタイムステップと1つの特徴量を持つ15個のサンプルが含まれる。

入力サンプルの各値に対して、対応する出力ベクトルは次の2つの整数を含む。

例えば、入力が 4 であれば、出力ベクトルは値 5 と 6 を含むことになる。

したがって、この問題は単純な1対多シーケンス問題である。

以下のスクリプトはLSTMが必要とするようにデータを再形成する。

X = np.array(X).reshape(15, 1, 1)
Y = np.array(Y)


これでモデルを学習することができます。

ここでは単純なLSTMと積み重ねられたLSTMを学習させる。

シンプルなLSTMによる解決
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(1, 1)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
model.fit(X, Y, epochs=1000, validation_split=0.2, batch_size=3)


モデルの学習が完了したら、テストデータに対して予測を行うことができる。

test_input = array([10])
test_input = test_input.reshape((1, 1, 1))
test_output = model.predict(test_input, verbose=0)
print(test_output)


テストデータには値10が含まれている。

出力では、11と12を含むベクトルが得られるはず。

私が受け取った出力は [10.982891 12.109697] で、これは実際に期待される出力に非常に近いものです。

スタックド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(2))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)


test_output = model.predict(test_input, verbose=0)
print(test_output)


答えは[11.00432 11.99205]で、実際の出力に非常に近い。

双方向性LSTMによる解法

次のスクリプトは、双方向LSTMを学習させ、テストセットで予測を行います。

from keras.layers import Bidirectional


model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(1, 1)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')


history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(test_output)


受け取った出力は[11.035181 12.082813] です。

複数個の特徴を持つ一対多の系列問題

このセクションでは、入力サンプルが1つのタイムステップを持ち、2つの特徴を持つ、1対多シーケンス問題を見ます。

出力は2つの要素からなるベクトルである。

データセットの作成

いつものように、まずはデータセットを作成します。

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)]


for x1, x2 in zip(X1, X2):
    output_vector = list()
    output_vector.append(x1+1)
    output_vector.append(x2+1)
    Y.append(output_vector)


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]]


各入力タイムステップは2つの特徴から構成されていることがわかる。

出力は入力サンプルのタイムステップの2つの特徴に対応する次の2つの要素を含むベクトルとなる。

例えば、入力サンプル [2, 3] の場合、出力は [3, 4] となる、といった具合である。

データの形を変えてみよう。

X = np.array(X).reshape(25, 1, 2)
Y = np.array(Y)


シンプルなLSTMによる解決
model = Sequential()
model.add(LSTM(50, activation='relu', input_shape=(1, 2)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
model.fit(X, Y, epochs=1000, validation_split=0.2, batch_size=3)


テストポイントを作成し、このアルゴリズムがどの程度うまく機能するかを見てみましょう。

test_input = array([40, 60])
test_input = test_input.reshape((1, 1, 2))
test_output = model.predict(test_input, verbose=0)
print(test_output)


入力は[40, 60]で、出力は[41, 61]であるべきである。

単純なLSTMによって予測された出力は[40.946873 60.941723]で、これは期待された出力に非常に近いものです。

スタックドLSTMによる解法
model = Sequential()
model.add(LSTM(50, activation='relu', return_sequences=True, input_shape=(1, 2)))
model.add(LSTM(50, activation='relu'))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)


test_input = array([40, 60])
test_input = test_input.reshape((1, 1, 2))
test_output = model.predict(test_input, verbose=0)
print(test_output)


この場合の出力は [40.978477 60.994644]

双方向性LSTMによる解法
from keras.layers import Bidirectional


model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(1, 2)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')


history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(test_output)


得られた出力は、[41.0975 61.159065] である。

多対多連続問題

一対多や多対一のシーケンス問題では、出力ベクトルは複数の値を含むことができ ることを見た。

問題によって、複数の値を含む出力ベクトルは、単一出力(厳密には1つの タイムステップデータを含むので)、複数出力(1つのベクトルに複数の値を含むので) と考えることができる。

しかし、シーケンス問題では、時間ステップに分割された複数の出力が欲しい場合もある。

つまり、入力の各タイムステップに対応する出力が欲しいのである。

このようなモデルは、長さが変化する多対多のシーケンス問題を解くために用いることができる。

エンコーダ・デコーダモデル

このような配列の問題を解決するために、エンコーダーデコーダーモデルが設計されている。

エンコーダデコーダモデルは、基本的に2つのLSTM層を持つニューラルネットワークアーキテクチャのファンシーネームである。

最初の層はエンコーダー層として働き、入力配列を符号化する。

デコーダもまたLSTM層であり、エンコーダLSTMからエンコードされたシーケンス、前の隠れ状態、そして現在の入力の3つの入力を受け入れる。

学習時には、各タイムステップの実際の出力がエンコーダ・デコーダモデルの学習に使われる。

予測時には、エンコーダ出力、現在の隠れ状態、および前回の出力を入力として、各タイムステップで予測を行います。

これらの概念は、次のセクションで実際に見ていただくと、より理解しやすくなると思います。

単一機能による多対多配列の問題

このセクションでは、入力サンプルの各タイムステップが1つの特徴を含むエンコーダデコーダモデルによって多対多シーケンス問題を解くことにします。

まず、データセットを作成する。

データセットの作成
X = list()
Y = list()
X = [x for x in range(5, 301, 5)]
Y = [y for y in range(20, 316, 5)]


X = np.array(X).reshape(20, 3, 1)
Y = np.array(Y).reshape(20, 3, 1)


入力 X には 20 個のサンプルが含まれ、各サンプルには 3 個のタイムステップと 1 個の特徴が含まれる。

1つの入力サンプルは以下のようなものである。

[[[  5]
  [ 10]
  [ 15]]


入力サンプルは、基本的に5の3連続した倍数である3つの値を含んでいることがわかる。

上記の入力サンプルに対応する出力シーケンスは以下の通りである。

[[[ 20]
  [ 25]
  [ 30]]


出力には、次の3つの連続した5の倍数の値が含まれる。

この場合の出力は、これまでのセクションで見てきたものとは異なることがおわかりいただけると思います。

エンコーダデコーダモデルの場合、出力もサンプル数、タイムステップ、特徴量を含む3次元形式に変換する必要があります。

これは、デコーダがタイムステップごとに出力を生成するためである。

データセットを作成したので、次のステップはモデルを学習することである。

次のセクションでは、スタック型LSTMと双方向型LSTMのモデルを学習します。

スタックドLSTMによる解法

以下のスクリプトは、stacked LSTMを用いたエンコーダデコーダモデルを作成します。

from keras.layers import RepeatVector
from keras.layers import TimeDistributed


model = Sequential()


# encoder layer
model.add(LSTM(100, activation='relu', input_shape=(3, 1)))


# repeat vector
model.add(RepeatVector(3))


# decoder layer
model.add(LSTM(100, activation='relu', return_sequences=True))


model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')


print(model.summary())


上のスクリプトでは、最初のLSTM層がエンコーダ層になっています。

次に、モデルに繰り返しベクトルを追加しています。

繰り返しベクトルはエンコーダの出力を入力として、各時間ステップでデコーダに繰り返し与える。

例えば、出力には3つのタイムステップがあります。

各出力時間ステップを予測するために、デコーダはリピートベクトルからの値、前の出力からの隠れた状態、そして現在の入力を使用することになる。

次にデコーダ層である。

出力はタイムステップという3次元の形式なので、デコーダモデルの return_sequencesTrue に設定されています。

TimeDistributed` レイヤーは、各タイムステップの出力を個別に予測するために使用されます。

上記のスクリプトで作成したエンコーダ・デコーダモデルのモデル概要は以下の通りです。

Layer (type)                 Output Shape              Param #=================================================================
lstm_40 (LSTM)               (None, 100)               40800
_________________________________________________________________
repeat_vector_7 (RepeatVecto (None, 3, 100)            0
_________________________________________________________________
lstm_41 (LSTM)               (None, 3, 100)            80400
_________________________________________________________________
time_distributed_7 (TimeDist (None, 3, 1)              101
=================================================================
Total params: 121,301
Trainable params: 121,301
Non-trainable params: 0


繰り返しベクトルはエンコーダの出力を繰り返すだけで、学習するためのパラメータはないことがわかります。

次のスクリプトは上記のエンコーダデコーダモデルを学習させます。

history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)


テストポイントを作成し、エンコーダデコーダモデルが多段出力を予測できるかどうか見てみましょう。

以下のスクリプトを実行してください。

test_input = array([300, 305, 310])
test_input = test_input.reshape((1, 3, 1))
test_output = model.predict(test_input, verbose=0)
print(test_output)


入力シーケンスには3つのタイムステップ値300、305、310が含まれている。

出力は次の5の3乗、すなわち315、320、325であるべきです。

次のような出力があった。

[[[316.02878]
  [322.27145]
  [328.5536 ]]]


出力が3Dフォーマットであることがわかる。

双方向性LSTMによる解法

双方向LSTMでエンコーダデコーダモデルを作成し、より良い結果が得られるかどうか見てみましょう。

from keras.layers import RepeatVector
from keras.layers import TimeDistributed


model = Sequential()
model.add(Bidirectional(LSTM(100, activation='relu', input_shape=(3, 1))))
model.add(RepeatVector(3))
model.add(Bidirectional(LSTM(100, activation='relu', return_sequences=True)))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')


history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)


上記のスクリプトは、双方向LSTMによるエンコーダデコーダモデルを学習させます。

それでは、テストポイント、すなわち[300, 305, 310]に対する予測を行ってみましょう。

test_output = model.predict(test_input, verbose=0)
print(test_output)


出力は以下の通りです。

[[[315.7526 ]
  [321.47153]
  [327.94025]]]


双方向LSTMによって得られた出力は、単純なスタックドLSTMベースのエンコーダデコーダモデルによって得られたものよりも優れています。

複数の特徴量を持つ多対多連続問題

もうお分かりかもしれませんが、多対多シーケンス問題では、入力サンプルの各タイムステップに複数の特徴が含まれています。

データセットの作成

この問題のために簡単なデータセットを作ってみましょう。

X = list()
Y = list()
X1 = [x1 for x1 in range(5, 301, 5)]
X2 = [x2 for x2 in range(20, 316, 5)]
Y = [y for y in range(35, 331, 5)]


X = np.column_stack((X1, X2))


上のスクリプトでは、2つのリスト X1X2 を作成している。

リスト X1 には 5 から 300 (この値を含む) までのすべての 5 の倍数が、リスト X2 には 20 から 315 (この値を含む) までのすべての 5 の倍数が格納される。

最後に、リスト Y が出力されますが、これは 35 から 330 (含む) までのすべての 5 の倍数を含んでいます。

最終的な入力リスト X は、 X1X2 を列方向にマージしたものである。

いつものように、入力 X と出力 Y をLSTMの学習に使う前に、形状を変更する必要がある。

X = np.array(X).reshape(20, 3, 2)
Y = np.array(Y).reshape(20, 3, 1)


入力 X は2つの特徴量を持つ3つのタイムステップの20個のサンプルにリシェイプされ、出力は1つの特徴量を持つ同様の次元にリシェイプされたことがわかります。

入力の最初のサンプルは次のようなものである。

[[ 5  20]
[ 10  25]
[ 15  30]]


この入力は6つの連続した整数5の倍数を含み、2つの列にそれぞれ3つずつある。

上記の入力サンプルに対応する出力は以下の通りである。

[[ 35]
[ 40]
[ 45]]


見てわかるように、出力は次の3つの連続した5の倍数を含んでいる。

それでは、上記のシーケンスを学習するためにエンコーダデコーダモデルを学習してみましょう。

まず、単純なスタックドLSTMベースのエンコーダ・デコーダを学習させます。

スタックドLSTMによる解法

次のスクリプトは、stacked LSTMモデルを学習させます。

入力形状が(3, 2)となり、3つのタイムステップと2つの特徴量が入力されていることがわかる。

from keras.layers import RepeatVector
from keras.layers import TimeDistributed


model = Sequential()
model.add(LSTM(100, activation='relu', input_shape=(3, 2)))
model.add(RepeatVector(3))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')


history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)


それでは、予測に使用するテストポイントを作成しましょう。

X1 = [300, 305, 310]
X2 = [315, 320, 325]


test_input = np.column_stack((X1, X2))


test_input = test_input.reshape((1, 3, 2))
print(test_input)


テストポイントは次のようになる。

[[[300 315]
  [305 320]
  [310 325]]]


上記のテストポイントの実際の出力は[330, 335, 340]です。

では、モデルが何を予測するのか見てみましょう。

test_output = model.predict(test_input, verbose=0)
print(test_output)


予測された出力は以下の通りです。

[[[324.5786 ]
  [328.89658]
  [335.67603]]]


この出力は正しいとは言い難い。

双方向性LSTMによる解法

それでは、双方向LSTMを用いたエンコーダデコーダモデルを学習させ、より良い結果が得られるかどうかを見てみましょう。

以下のスクリプトで学習させます。

from keras.layers import RepeatVector
from keras.layers import TimeDistributed


model = Sequential()
model.add(Bidirectional(LSTM(100, activation='relu', input_shape=(3, 2))))
model.add(RepeatVector(3))
model.add(Bidirectional(LSTM(100, activation='relu', return_sequences=True)))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')


history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)


以下のスクリプトはテストセットに対して予測を行います。

test_output = model.predict(test_input, verbose=0)
print(test_output)


以下はその出力です。

[[[330.49133]
  [335.35327]
  [339.64398]]]


達成された出力は実際の出力、すなわち[330, 335, 340]にかなり近いです。

したがって、我々の双方向LSTMは単純なLSTMよりも優れていることがわかります。

結論

今回は「KerasのLSTMで配列問題を解く」の記事の後編です(前編はこちら)。

この記事では、LSTMで1対多と多対多のシーケンス問題を解く方法を見ました。

また、エンコーダーデコーダーモデルを使用して、多段階の出力を予測する方法を見ました。

エンコーダ・デコーダモデルは、ニューラル機械翻訳やチャットボット開発など、様々な自然言語処理アプリケーションで使用されています。

次回は、NLPにおけるエンコーダ・デコーダモデルの応用について見ていきます。

タイトルとURLをコピーしました