Kerasで多変量LSTM

単変量の時系列はkerasでもよく見るのですが、株価や売上などを予測する時などには複数の要因が関わってきますので、今回は複数の時系列データを使って予測してみました。

#ソースの紹介
##コード
「MACHINE LEARNING MASTERY」で紹介されているコードを基本に、多変量対応にしました。
[Time Series Prediction with LSTM Recurrent Neural Networks in Python with Keras](http://machinelearningmastery.com/time-series-prediction-lstm-recurrent-neural-networks-python-keras/)

jupyterで見れるコードの全貌はこちら
https://github.com/tizuo/keras/blob/master/LSTM%20with%20multi%20variables.ipynb

##データ
サンプルデータは以下から拝借しました。一番左のice_salesを予測します。
[アイスクリームの売れ方](https://oku.edu.mie-u.ac.jp/~okumura/stat/160118.html)

|ice_sales|year|month|avg_temp|total_rain|humidity|num_day_over25deg|
|:-------:|:--:|:---:|:------:|:--------:|:------:|----------------:|
|331|2003|1|9.3|101|46|0|
|268|2003|2|9.9|53.5|52|0|
|365|2003|3|12.7|159.5|49|0|
|492|2003|4|19.2|121|61|3|
|632|2003|5|22.4|172.5|65|7|
|730|2003|6|26.6|85|69|21|
|821|2003|7|26|187.5|75|21|

![ダウンロード.png](https://qiita-image-store.s3.amazonaws.com/0/193707/43b23346-17e9-164d-3bda-923a444752c6.png)

#データの標準化
データを標準化します。LSTMはセンシティブなので扱う数値を標準化されなければならないと説明されていました。

```python:
scaler = MinMaxScaler(feature_range=(0, 1))
dataset = scaler.fit_transform(dataset)
```
#テストデータを分ける
今回は後半1/3のデータを予測させますので、学習用と分けます。

```python:
train_size = int(len(dataset) * 0.67)
test_size = len(dataset) - train_size
train, test = dataset[0:train_size,:], dataset[train_size:len(dataset),:]
```

#データの整形
3ヶ月前までの値を使って次の月の値を学習させるので、以下のような形で整形します。
これを使う変数分作成し、1時点分のデータにそれぞれ格納します。

|Y|3ヶ月前|2ヶ月前|1ヶ月前|
|:-------:|:--:|:---:|:------:|
|1月の値|10月の値|11月の値|12月の値|
|2月の値|11月の値|12月の値|1月の値|
|3月の値|12月の値|1月の値|2月の値|

今回は年間で1セットとみなし、ルックバックは12でデータを作ります。

```python:
def create_dataset(dataset, look_back=1):
    dataX, dataY = [], []
    for i in range(len(dataset)-look_back-1):
        xset = []
        for j in range(dataset.shape[1]):
            a = dataset[i:(i+look_back), j]
            xset.append(a)
        dataY.append(dataset[i + look_back, 0])      
        dataX.append(xset)
    return numpy.array(dataX), numpy.array(dataY)

look_back = 12
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)
```

このデータをkerasのLSTMで受け付けられている形式に変換します。
[行数]＞[変数数]＞[カラム数(ルックバック数)]

```python:
trainX = numpy.reshape(trainX, (trainX.shape[0], trainX.shape[1], trainX.shape[2]))
testX = numpy.reshape(testX, (testX.shape[0], testX.shape[1], testX.shape[2]))
```

#モデル作成
input_shapeには、変数数とルックバック数が入ります。出力数は特に考えず、サンプル通り4にしています。

```python:
model = Sequential()
model.add(LSTM(4, input_shape=(testX.shape[1], look_back)))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(trainX, trainY, epochs=1000, batch_size=1, verbose=2)
```
#検証
予測は通常と同じです。

```python:
trainPredict = model.predict(trainX)
testPredict = model.predict(testX)
```
この後、Yを標準化前の数字に戻します。scalerが元のデータセットと同じ形でないと受け付けられないため、存在していたカラムの数だけ値を0で埋めています。
もっとスマートな方法があれば誰か教えてください。

```python:
pad_col = numpy.zeros(dataset.shape[1]-1)
def pad_array(val):
    return numpy.array([numpy.insert(pad_col, 0, x) for x in val])
    
trainPredict = scaler.inverse_transform(pad_array(trainPredict))
trainY = scaler.inverse_transform(pad_array(trainY))
testPredict = scaler.inverse_transform(pad_array(testPredict))
testY = scaler.inverse_transform(pad_array(testY))
```
ようやく標準偏差を出します。

```python:
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))
```
変数の数を変えていったところ、以下のようになりました。
当然ですが、投げる変数は選ばないといけませんね。

||アイスクリームの売上だけで学習したモデル|アイスと25度以上日数|全部|
|:-------:|:--:|:---:|:------:|
|Train Score|30.20 RMSE|15.19 RMSE|8.44 RMSE|
|Test Score|111.97 RMSE|108.09 RMSE|112.90 RMSE|

![ダウンロード (1).png](https://qiita-image-store.s3.amazonaws.com/0/193707/3bdb9530-2573-6b2a-597f-abb3a66a9e4f.png)