時系列の予測モデルを比較してみた

公開:2021/02/07
更新:2021/02/08
7 min読了の目安(約7000字TECH技術記事

時系列データの予測モデルである、Prophet、sktime、LSTM と lightGBMを使った時系列予測を比較してみました。

コードはGoogle Colabがこちら、GitHubがこちら

ただし途中のグラフで使用しているドロップボックスについては Google Colab 専用の機能になりますのでご留意ください。

細かいコードは割愛しています。

Google Colabでドロップボックス等の機能を使う方法についてはこちら

なお、使っているデータセットはアメリカンフットボールプレーヤのPayton ManningのWikiアクセス数のデータだそうです。

https://en.wikipedia.org/wiki/Peyton_Manning

アメリカンフットボールプレーヤのPayton ManningのWikiアクセス数データのグラフ

Prophet

Prophet はフェイスブック製の時系列用フレームワークで、日付データのカラム名を ds 、目的変数のカラム名を y とする必要があるのでやや癖があります。

またインストールが難しく、Anaconda を使っている場合は conda 経由でインストールするのが無難と思われます。

https://anaconda.org/conda-forge/fbprophet

https://facebook.github.io/prophet/docs/installation.html
from fbprophet import Prophet

# モデルの作成
model = Prophet(weekly_seasonality=True, yearly_seasonality=True, daily_seasonality=True)

# 学習
model.fit(train)

# 予測データの作成
pred = model.predict(test)

https://mikiokubo.github.io/analytics//15forecast

sktime

sktimeも時系列用のフレームワークでです。

また、時系列向けの学習データ、訓練データの分割ライブラリもあります。

スライス指定しなくてもいいのでちょっと便利です。

from sktime.forecasting.model_selection import temporal_train_test_split
train, test = temporal_train_test_split(df, test_size=365)
from sktime.forecasting.all import *

# 日時データをindexに変換
train_sk = train.set_index("ds")
test_sk = test.set_index("ds")

# 学習
model = ThetaForecaster(sp=365)
model.fit(train["y"])

fh = ForecastingHorizon(test.index, is_relative=False)

# 予測データの作成
pred_sktime = model.predict(fh)

https://github.com/alan-turing-institute/sktime/blob/master/examples/01_forecasting.ipynb

LSTM

LSTM は時系列や自然言語処理に向いたニューラルネットワークです。

なお、LSTM は Long Short Term Memory の略だそうです。

後ほど紹介するlightGBMで時系列解析する場合のラグ作成に近いですが、目的変数から特徴量を作成する必要があるようです。

詳細はこちらの記事をご参照ください。

import keras
from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM
from keras.optimizers import Adam
import tensorflow as tf

# 目的変数と説明変数の作成
def make_dataset(low_data, maxlen):

    data, target = [], []

    for i in range(len(low_data)-maxlen):
        data.append(low_data[i:i + maxlen])
        target.append(low_data[i + maxlen])

    re_data = np.array(data).reshape(len(data), maxlen, 1)
    re_target = np.array(target).reshape(len(data), 1)

    return re_data, re_target

# RNNへの入力データ数
window_size = 12

# 入力データと教師データへの分割
X, y = make_dataset(df["y"], window_size)

# データの分割
X_train, X_test, y_train, y_test = temporal_train_test_split(X, y, test_size=365)

# バリデーションデータの作成
X_train, X_val, y_train, y_val = temporal_train_test_split(X_train, y_train, test_size=365)
# ネットワークの構築
model = Sequential() # Sequentialモデル

model.add(LSTM(50, batch_input_shape=(None, window_size, 1))) # LSTM 50層
model.add(Dense(1)) # 出力次元数は1

#コンパイル
model.compile(loss='mean_squared_error', optimizer=Adam() , metrics = ['accuracy'])
model.summary()
Model: "sequential_1"  
_________________________________________________________________  
Layer (type)                 Output Shape              Param #     
=================================================================  
lstm_1 (LSTM)                (None, 50)                10400       
_________________________________________________________________  
dense_1 (Dense)              (None, 1)                 51          
=================================================================  
Total params: 10,451  
Trainable params: 10,451  
Non-trainable params: 0  
# 学習用パラメータ
batch_size = 20
n_epoch = 150

# 学習
hist = model.fit(X_train, y_train,
                 epochs=n_epoch,
                 validation_data=(X_val, y_val),
                 verbose=0,
                 batch_size=batch_size)

# 損失値(Loss)の遷移のプロット
plt.plot(hist.history['loss'],label="train set")
plt.plot(hist.history['val_loss'],label="test set")
plt.title('model loss')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend()
plt.show()

# 予測データの作成
pred_lstm = model.predict(X_test)

詳しい理論はこちらの動画が参考になります。

https://www.youtube.com/watch?v=oxygME2UBFc

lightGBM

import lightgbm as lgbm

# 元のデータを残すため、別途コピー
df_lag = df.copy()

# 時系列特徴量の作成
df_lag["year"]  = df_lag["ds"].dt.year
df_lag["month"] = df_lag["ds"].dt.month
df_lag["day"]  = df_lag["ds"].dt.day
df_lag["dayofweek"] = df_lag["ds"].dt.dayofweek

# ラグの作成 簡単に1週間と1ヶ月で作成
for i in [7, 30]:
    df_lag[f"shift{i}"] = df_lag["y"].shift(i)

# 差分の作成
for i in [7, 30]:
    df_lag[f"deriv{i}"] = df_lag[f"shift{i}"].diff(i)

# 移動平均の作成
for i in [7, 30]:
    df_lag[f"mean{i}"] = df_lag[f"shift{i}"].rolling(12).mean()

# 中央値、最大値、最小値の作成
for i in [7, 30]:
    df_lag[f"median{i}"] = df_lag[f"shift{i}"].rolling(12).median()

for i in [7, 30]:
    df_lag[f"max{i}"] = df_lag[f"shift{i}"].rolling(12).max()

for i in [7, 30]:
    df_lag[f"min{i}"] = df_lag[f"shift{i}"].rolling(12).min()

# NaN部分を削除
df_lag = df_lag[41:]

# ds列をindexにして説明変数から除外
df_lag = df_lag.set_index("ds")

# データの分割
train_lag, test_lag = temporal_train_test_split(df_lag, test_size=365)

# 学習
model = lgbm.LGBMRegressor()
model.fit(train_lag.drop("y", axis=1), train_lag["y"])

# 予測データの作成
pred_lgbm = model.predict(test_lag.drop("y", axis=1))

https://qiita.com/ground0state/items/657861de619a4e4a30de

精度の比較

R2スコア、RMSEで4つの学習モデルの精度を比較してみましょう。

Prophet
R2スコア 0.42594969350334355
RMSE 0.548008873731168

sktime
R2スコア 0.5176028406784783
RMSE 0.5023599230166854

LSTM
R2スコア 0.6668046432993786
RMSE 0.4175053881219272

lightGBM
R2スコア 0.546316880353727
RMSE 0.48717940947755595

いずれも正規化等の処理を行っていませんし、特にlightGBM については別途特徴量を作成しているため、一概には言えないですが、LSTM の精度が一番よくなりました。

また、かなり見づらいですが、4つを比較したグラフはこちら。

以上になります、最後までお読みいただきありがとうございました。