ProphetをOptunaでハイパラチューニングをしてみた

* 製造業出身のデータサイエンティストがお送りする記事
* 今回は時系列解析手法のProphetをOptunaでハイパラチューニングをしてみました

##はじめに
過去に時系列解析に関しては何個か整理しておりますので、興味ある方は参照して頂けますと幸いです。

* [時系列解析](https://qiita.com/DS27/items/1e998a58488e76bfcbdc)
* [状態空間モデル](https://qiita.com/DS27/items/cd0e11c2b9eb097c606b)
* [時系列予測をGBDTで実装してみた](https://qiita.com/DS27/items/85abd5862367ff6eaadb)
* [時系列解析手法のSARIMAモデルを試してみた](https://qiita.com/DS27/items/b70aad44721dfa7ac5f7)

##Prophetとは
Prophet は Facebook が公開しているオープンソースの時系列解析ライブラリです。

Prophetのメリットは大きく下記があります。

1.モデル式設計の柔軟性が高い
2.サンプル間隔の柔軟性が高い
3.高速に計算可能
4.パラメータが理解しやすい

詳細は[Prophetの公式ドキュメント](https://facebook.github.io/prophet/)を参照してください。

##Prophet実装
今回も使用したデータは、月ごとの飛行機の乗客数データです。

```php

# ライブラリーのインポート
import pandas as pd
import numpy as np
import seaborn as sns
import optuna
from matplotlib import pylab as plt
%matplotlib inline

from fbprophet import Prophet
from fbprophet.plot import add_changepoints_to_plot

import warnings
warnings.filterwarnings("ignore")

# https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/AirPassengers.html
df = pd.read_csv('../data/AirPassengers.csv')

# float型に変換
df['#Passengers'] = df['#Passengers'].astype('float64')
df = df.rename(columns={'#Passengers': 'Passengers'})

# datetime型に変換にする
df.Month = pd.to_datetime(df.Month)

# データの中身を確認
df.head()
```
![スクリーンショット 2020-12-05 12.41.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/e5bf2b87-d8c3-49ef-66a9-7e697e6c9b7f.png)

```php
# データの可視化
fig, ax = plt.subplots()
a = sns.lineplot(x="Month", y="Passengers", data=df)
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/a9348171-8ca1-1915-1590-d840da3ba4e0.png)

Prophetでモデルを構築します。

```php
def objective_variable(train, valid):

    def objective(trial):
            params = {
                    'changepoint_range' : trial.suggest_discrete_uniform('changepoint_range', 0.8, 0.95, 0.001),
                    'n_changepoints' : trial.suggest_int('n_changepoints', 20, 35),
                    'changepoint_prior_scale' : trial.suggest_discrete_uniform('changepoint_prior_scale',0.001, 0.5, 0.001),
                    'seasonality_prior_scale' : trial.suggest_discrete_uniform('seasonality_prior_scale',1, 25, 0.1),
            }
            
            # fit_model
            model = Prophet(
                changepoint_range = params['changepoint_prior_scale'],
                n_changepoints=params['n_changepoints'],
                changepoint_prior_scale=params['changepoint_prior_scale'],
                seasonality_prior_scale = params['seasonality_prior_scale'],
            )

            model.fit(train)
            future = model.make_future_dataframe(periods=len(valid))

            forecast = model.predict(future)
            valid_forecast = forecast.tail(len(valid))

            val_mape = np.mean(np.abs((valid_forecast.yhat-valid.y)/valid.y))*100

            return val_mape

    return objective

def optuna_parameter(train, valid):
    study = optuna.create_study(sampler=optuna.samplers.RandomSampler(seed=10))
    study.optimize(objective_variable(train, valid), timeout=500)
    optuna_best_params = study.best_params

    return study

df = df.rename(columns={'Month':'ds','Passengers':'y'})
df = df[['ds','y']]
df_train = df[df['ds'] < '1956-04-01']
df_valid = df[(df['ds'] >= '1956-04-01')&(df['ds'] < '1957-04-01')]
df_test = df[df['ds'] >= '1957-04-01']

study = optuna_parameter(df_train, df_valid)
```

あとは、チューニング結果のハイパーパラメータを使ってモデルの学習と予測を行います。

```php
# fit_model
best_model = Prophet(
    changepoint_range = study.best_params['changepoint_prior_scale'],
    n_changepoints=study.best_params['n_changepoints'],
    seasonality_prior_scale = study.best_params['seasonality_prior_scale'],
    changepoint_prior_scale=study.best_params['changepoint_prior_scale'],
)

best_model.fit(df_train)
feature_test = best_model.make_future_dataframe(periods=len(df_valid)+len(df_test), freq='M')

forecast_test = best_model.predict(feature_test)
forecast_test_plot = best_model.plot(forecast_test)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/efecabd4-8667-c211-95bc-900f67baea07.png)

```php
fig, ax = plt.subplots()
df.y.plot(ax=ax, label='Original', linestyle="dashed")
forecast_test.yhat.plot(ax=ax, label='Predict')
ax.legend()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/97446f69-fec9-9835-6832-4220107dc92b.png)

##さいごに
最後まで読んで頂き、ありがとうございました。
今回は時系列解析手法のProphetをOptunaでハイパラチューニングをしてみました。

訂正要望がありましたら、ご連絡頂けますと幸いです。