以前の続きです。ハンガリーにおけるchikenpox(水疱瘡)のデータを使って、BUTAPESTにおける患者数の3か月分の予想を行っています。今まではAutoMLを使って予測してきました。今回は、LSTM(Long Short Term Memory)を使って予測したいと思います。

Long short-term memory(LSTM)

Neural Netの亜種です。LSTMは、時系列データに適したニューラル・ネットワークの一種です。通常のニューラルネットに以前の状態を記憶するレイヤーが追加されています。

keras(tensorflow)で実装します。なんといっても、LSTMに学習させるデータの形を作るまでが大変です。tensorflowのチュートリアルは鬼のようにデータ加工の部分が長くて辛いです。

ハンガリーのchikenpoxのデータを加工していきます。

まずはどんな形をしているか見てみます。

df = pd.read_csv('hungary_chickenpox.csv')
df["Date"] = pd.to_datetime(df["Date"],format='%d/%m/%Y')

このBUDAPESTの数を予想したいと思います。

最終的にどういう形にするのかというと、numpy ndarrayの形で(record数, lag, feature数)の形にします。featuresは、1変数だと複数のlagを含んでも1です。

lagの配列を作成

まずラグ(shift)を含んだ、numpy ndarrayを作成する関数を自作します。使い方は、X,Y = windowgenerator(データフレーム,目的変数,ラグ数)です。Xは、時点T-1、T-2、、です。Yは時点Tです。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('hungary_chickenpox.csv')
df["Date"] = pd.to_datetime(df["Date"],format='%d/%m/%Y')

def windowgenerator(dataframe,target,shift):
    Y = np.array(df[target])  
    Y = np.expand_dims(Y,axis=1)
    for i in reversed(range(1,shift+1)):   
        if i == (shift):
            X = Y[(shift-i):(len(Y)-i), :] 
        else:
            X2 = Y[(shift-i):(len(Y)-i), :]
            X = np.concatenate([X,X2],axis=1)      
    Y = Y[shift:len(dataframe), :]
    return X,Y

X,Y = windowgenerator(df,"BUDAPEST",3)

X[0:5]をみます。

array([[168, 157, 96],
[157, 96, 163],
[ 96, 163, 122],
[163, 122, 174],
[122, 174, 153]], dtype=int64)

Y[0:5]を見ます。

array([[163],
[122],
[174],
[153],
[115]], dtype=int64)

Standardize

配列から平均を引いて、強制的に平均を0にします。その後、SDで割って強制的にSDを1の配列にします。sklearnの関数を使っておけば後で変更前に戻すことが可能です。

もし、StandardizationではなくNormalization(強制的に0から1の範囲にする)をしたいという場合にはコメントアウトしたところのコードを使います。

# Standardization ( value - mean ) / SD
from sklearn.preprocessing import StandardScaler
stand_x = StandardScaler().fit(X)  
X = stand_x.transform(X)  
stand_y = StandardScaler().fit(Y)  
Y = stand_y.transform(Y) 

'''
#Normalization 0 ~ 1
from sklearn.preprocessing import MinMaxScaler
norm_x = MinMaxScaler().fit(X)  
X = norm_x.transform(X)  
norm_y = MinMaxScaler().fit(Y)  
Y = norm_y.transform(Y)  
'''

Kerasでモデルを作成

モデルを作成します。と、その前にnumpy ndarrayの形を3次元に拡張しておきます。(record数, lag, feature数)。

3~7行は私のPCでなぜか必要になってしまったGPUを使うための呪文です。

X = np.expand_dims(X,axis=2) # ( , ) -> ( , ,1)

from tensorflow.compat.v1 import ConfigProto
from tensorflow.compat.v1 import InteractiveSession
config = ConfigProto()
config.gpu_options.allow_growth = True
session = InteractiveSession(config=config)

from tensorflow import keras
from tensorflow.keras.layers import Input,LSTM,Dense
from tensorflow.keras.optimizers import Adam

inputs = Input(shape=(X.shape[1],X.shape[2]))
lstm_out = LSTM(16)(inputs)
outputs = Dense(1)(lstm_out)
model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(optimizer=Adam(learning_rate=0.01),
              loss= "mse",
              metrics = "mae")
model.summary()
 Layer (type)                 Output Shape              Param #   
 input_14 (InputLayer)        [(None, 3, 1)]            0         
 
 lstm_18 (LSTM)               (None, 16)                1152      
 
 dense_20 (Dense)             (None, 1)                 17        
 Total params: 1,169
 Trainable params: 1,169
 Non-trainable params: 0
 

後記:以下のコードは、改善点すべき点があります。

1未来ずつ、3か月分(13レコード分)を予想します。


# forecast each 1 step (in total 13 records) 
result = pd.DataFrame(0,
                  index=np.arange(13),
                  columns=["pred", "true"])

last13 = len(X)-13
last = len(X)

for i in range(last13,last):
    
    # train, val, testデータを定義
    X_train = X[:i, :, :]
    Y_train = Y[:i, :]
    
    X_test = X[i:i+1, :, :]
    Y_test = Y[i:i+1, :]
          
    model.fit(X_train, Y_train,
              validation_split=0.1,
              epochs=60, 
              verbose=0)
    
    pred = model.predict(X_test)
      
    result.iloc[i-(last13),0] = float(pred[0,0])
    result.iloc[i-(last),1] = Y_test[0,0]

result["pred"] = stand_y.inverse_transform(np.expand_dims(result["pred"],axis=1))
result["true"] = stand_y.inverse_transform(np.expand_dims(result["true"],axis=1))
result["mae"] = abs(result["true"]-result["pred"])
result["mae"].mean()

実行するたびに結果が異なるので安定しませんが、いいときはMAE33.1を出しました。AutokerasのMAE31ですのでAutoMLにはなかなか勝てませんね。

後記:上記コードは、モデルのweightをリセットすることなく繰り返し学習させています。そのため、過学習を起こしてしまっていました。for loopの毎にweightをリセットする必要があります。そのため以下のコードが必要になります。

for loop外で初期weightを保存 weights = model.get_weights()
for loop内で重みを再ロード model.set_weights(weights)

重みを再ロードすることで重みを初期化します。

autokerasのモデルはget_weightsができないので、いったんh5ファイルで保存して、再度tensorflowモデルとして読み込む必要があります。同じモデルですが、こうすることでget_weightsができるようになります。

result_AK = pd.DataFrame(0,
                  index=np.arange(13),
                  columns=["pred", "true_diff"])

last30 = len(df4)-13
last = len(df4)

# Evaluate the best model with testing data.

# Evaluate the best model with testing data.
model_autokeras = ak_model.export_model()
model_autokeras.save("/content/drive/MyDrive/autokeras_model_reset.h5")

from tensorflow.keras.models import load_model
ak_model2 = load_model("/content/drive/MyDrive/autokeras_model_reset.h5",custom_objects=ak.CUSTOM_OBJECTS)

weights = ak_model2.get_weights()

for i in range(last30,last):
  X_train = (df4 >> select(~X["next"])).iloc[0:i]
  y_train = (df4 >> select(X["next"])).iloc[0:i]

  X_test = (df4 >> select(~X["next"])).iloc[i:i+1] 
  y_test = (df4 >> select(X["next"])).iloc[i:i+1] 

  ak_model2.set_weights(weights)
 
  ak_model2.fit(x = X_train, y= y_train,
          epochs = 100,
          validation_split=0.1)
  pred = ak_model2.predict(X_test)
  result_AK.iloc[i-(last30),0] = float(pred)
  result_AK.iloc[i-(last30),1] = y_test.values[0,0]

さらにチェックポイントを作成し、学習中の一番いいモデルを使って予測するコードは以下のようにします。

from tensorflow.keras.callbacks import ModelCheckpoint
import os
import tensorflow as tf

result_AK = pd.DataFrame(0,
                  index=np.arange(13),
                  columns=["pred", "true_diff"])

last30 = len(df4)-13
last = len(df4)

# Evaluate the best model with testing data.
model_autokeras = ak_model.export_model()
model_autokeras.save("/content/drive/MyDrive/autokeras_model_reset.h5")

from tensorflow.keras.models import load_model
ak_model2 = load_model("/content/drive/MyDrive/autokeras_model_reset.h5",custom_objects=ak.CUSTOM_OBJECTS)

weights = ak_model2.get_weights()

for i in range(last30,last):
  X_train = (df4 >> select(~X["next"])).iloc[0:i]
  y_train = (df4 >> select(X["next"])).iloc[0:i]

  X_test = (df4 >> select(~X["next"])).iloc[i:i+1] 
  y_test = (df4 >> select(X["next"])).iloc[i:i+1] 

  ak_model2.set_weights(weights)

  checkpoint_path = "training/cp-{epoch:04d}.ckpt"
  checkpoint_dir = os.path.dirname(checkpoint_path)

  cp_callback = tf.keras.callbacks.ModelCheckpoint(
      filepath=checkpoint_path, 
      verbose=1, 
      save_weights_only=True,
      period=5)

  ak_model2.save_weights(checkpoint_path.format(epoch=0))

  ak_model2.fit(x = X_train, y= y_train,
          epochs = 100,
          validation_split=0.1,
          callbacks = [cp_callback])
  
  latest = tf.train.latest_checkpoint(checkpoint_dir)
  
  ak_model2.load_weights(latest)

  pred = ak_model2.predict(X_test)

  result_AK.iloc[i-(last30),0] = float(pred)
  result_AK.iloc[i-(last30),1] = y_test.values[0,0]

Categories:

category