【Python】【機械学習】Prophetでの株価予測モデルの精度検証とハイパーパラメータ最適化
Prophetではチューニングできるパラメータ(ハイパーパラメータ)が多くあります。特に、各パラメータが大きい影響力を持っていて、値を少し変えただけでも予測値が大きく変化します。このハイパーパラメータを調整し最適な設定を探すタスクを、ハイパーパラメータチューニングと言います。
ここでは、ハイパーパラメータチューニングをベイズ最適化で行う代表的なライブラリ Oputuna を使って、より良いハイパーパラメータの組合せを探索し、予測精度を比較したいと思います。
なお、最低限のポイントのみの説明にするため、Pythonライブラリ、モジュール等のインストール方法については割愛させて頂きます。お使いのPC環境等に合わせてインストールしてもらえればと思います。
1.ライブラリをインポートして株価データを取得する
時系列機械学習ライブラリである fbprophet から Prophet をインポートします。oputuna、mean_absolute_error、mean_squared_errorをインポートします。
import pandas_datareader.data as web
import datetime
# visualization
import matplotlib.pyplot as plt
%matplotlib inline
from prophet import Prophet
import optuna
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
2.株価データを取得してデータを確認
下記を参考にOHLCV(始値 / 高値 / 安値 / 終値 / 出来高)形式の日経平均株価(^NKX)データを取得します。データの取得期間は、2018年1月1日から2023年6月/1日までです。
start = '2018/01/01'
end = '2023/06/01'
df = web.DataReader('^NKX', 'stooq', start, end)
# 日付を昇順に並び替える
df.sort_index(inplace=True)
# インデックスをリセット
df = df.reset_index()
# 株価データを可視化
fig, ax = plt.subplots(figsize=(15,7))
ax.plot(df['Date'], df['Close'], lw=4)
ax.set_title("Stock price(Close)",fontsize=15)
plt.show()
3.基礎分析をする
まず、可視化して年、月、週の周期性がありそうか確認する
# 年、月、週の周期性がありそうか確認するため、年、月、週、日カラムを追加
df['year'] = df.Date.apply(lambda x: x.year)
df['month'] = df.Date.apply(lambda x: x.month)
df['weekday'] = df.Date.apply(lambda x: x.dayofweek)
df['day'] = df.Date.apply(lambda x: x.day)
年ごとに、月、週、日の終値の平均値を可視化する
# 年ごとに月、週、日の終値平均値を可視化
for i in [2018,2019,2020,2021,2022,2023]:
tmp_df = df[df.year==i].copy()
tmp_df.sort_values('Date',inplace=True)
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 4))
# 月平均値のグラフ化
tmp_df.groupby('month').mean()['Close'].plot(kind='line', title='average stock price(month)', lw=4, color='r', ax=ax1)
# 週平均値のグラフ化
tmp_df.groupby('weekday').mean()['Close'].plot(kind='line', title='average stock price(weekday)', lw=4, color='g', ax=ax2)
# 日平均値のグラフ化
tmp_df.groupby('day').mean()['Close'].plot(kind='line', title='average stock price(day)', lw=4, color='b', ax=ax3)
fig.suptitle('Trends for year: {0}'.format(i), size=20, y=0.95)
plt.tight_layout()
plt.show()
2018年から2023年まで総合的に見た場合、明確な周期性はなさそうです。
4.Prophetで予測する
今回は、以下の条件で株価予測を行います。
対象:日経平均株価(^NKX)
学習期間:2018/1/1~2023/1/1
予測期間:2023/1/1以降、2023/6/1までの終値を予測
4. 1 デフォルトパラメータでの予測と精度検証
Prophetを用いるため、ds カラムと y カラムを追加し、train データと testデータに分割して確認のためグラフを描画します。trainデータセット(df_train)は青で、testデータセット(df_test)は橙で描かれており、2023年以降は、df_test になっておりデータが分割できていることが確認できます。
# 新たに ds列と y列を追加
df['ds'] = df['Date']
df['y'] = df['Close']
# trainデータとtestデータに分割
df_train = df[df['ds'] < '2023-01-01']
df_test = df[df['ds'] >= '2023-01-01']
# 株価データを可視化
fig, ax = plt.subplots(figsize=(15,7))
ax.plot(df_train.ds, df_train.y, label="actual(train dataset)", lw=4)
ax.plot(df_test.ds, df_test.y, label="actual(test dataset)", lw=4)
ax.set_title("Stock price(Close)",fontsize=15)
plt.legend()
plt.show()
予測モデルを構築し、test 期間を含めて予測します。そして予測値と実測値からProphetモデルを評価します。 評価には、RMSE、MAE、MAPEという指標を使います。
・RMSE(Root Mean Squared Error)
予測値と実測値の誤差を二乗して平均し、平方根をとった値です。MSEに平方根をつけたバージョンです。RMSEは予測値と実測値の誤差の大きさを表す指標で、二乗を取るため、大きな誤差がある場合はその影響が大きく出ます。RMSEが小さいほど、予測精度が高いと言えます。MSEを損失関数として使って,RMSEを評価指標として使うことも多いです。
・MAE(Mean Absolute Error)
予測値と実測値の誤差の絶対値を平均した値です。MAEは予測値と実測値の誤差の大きさを表す指標で、二乗を取らないため、大きな誤差があっても影響が小さくなります。MAEが小さいほど、予測精度が高いと言えます。
・MAPE(Mean Absolute Percentage Error)
予測値と実測値の差を実測値で割った値(パーセント誤差)の絶対値を平均した値です。MAPEは、予測値と実測値の相対的な誤差を表す指標で、予測値が小さい場合や0の場合には使用できません。100%の確率値にするため、一般的には最後に100をかけます。MAPEが小さいほど、予測精度が高いと言えます。
# 予測モデル構築
m = Prophet()
m.fit(df_train)
# 予測の実施(学習期間+テスト期間)
df_future = m.make_future_dataframe(periods=len(df_test))
df_pred = m.predict(df_future)
# 元のデータセットに予測値を結合
df['Predict'] = df_pred['yhat']
# 予測精度(テストデータ期間)
## 予測値と実測値
preds = df.iloc[len(df_train):].loc[:, 'Predict']
y = df.iloc[len(df_train):].loc[:, 'y']
## 指標出力
print('RMSE:')
print(np.sqrt(mean_squared_error(y, preds)))
print('-----------------')
print('MAE:')
print(mean_absolute_error(y, preds))
print('-----------------')
print('MAPE(%):')
print(np.mean(abs(y - preds)/y)*100)
最後にグラフを描画して予測時と実測値を比較します。
# 株価データを可視化
fig, ax = plt.subplots(figsize=(15,7))
# テスト期間の実測値と予測値を描画
ax.plot(df.iloc[len(df_train):]['Date'], df.iloc[len(df_train):]['y'], label="Actual")
ax.plot(df.iloc[len(df_train):]['Date'], df.iloc[len(df_train):]['Predict'], label="Forecast")
ax.set_title("Forecast vs Actual",fontsize=15)
plt.legend()
plt.show()
4. 2 Optunaによるパラメータチューニング
Optunaでハイパーパラメータをチューニングします。まずは、以下の手順で最適パラメータの探索します。
目的関数の設定
ハイパーパラメータの探索の実施
最適パラメータの出力
まず目的関数を設定し、ハイパーパラメータの探索を実施して最適なパラメータを出力します。
# 目的関数の設定
def objective(trial):
params = {
'changepoint_prior_scale' : trial.suggest_uniform('changepoint_prior_scale', 0.001,0.5),
'seasonality_prior_scale' : trial.suggest_uniform('seasonality_prior_scale', 0.01,10),
'seasonality_mode' : trial.suggest_categorical('seasonality_mode', ['additive', 'multiplicative']),
'changepoint_range' : trial.suggest_discrete_uniform('changepoint_range', 0.8, 0.95, 0.001),
'n_changepoints' : trial.suggest_int('n_changepoints', 20, 35),
}
m = Prophet(**params)
m.fit(df_train)
df_future = m.make_future_dataframe(periods=len(df_test), freq='D')
df_pred = m.predict(df_future)
preds = df_pred.tail(len(df_test))
val_rmse = np.sqrt(mean_squared_error(df_test.y, preds.yhat))
return val_rmse
# 目的関数の最適化を実行する
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100)
# 最適パラメータの出力
print(f"The best value is : \n {study.best_value}")
print(f"The best parameters are : \n {study.best_params}")
次に、上記の最適化したパラメータを設定して予測と構築したProphetモデルを評価します。 評価には、RMSE、MAE、MAPEという指標を使います。
# 最適パラメータで予測モデル構築
m = Prophet(**study.best_params)
m.fit(df_train)
# 予測の実施(学習期間+テスト期間)
df_future = m.make_future_dataframe(periods=len(df_test))
df_pred = m.predict(df_future)
# 元のデータセットに予測値を結合
df['Predict'] = df_pred['yhat']
# 予測精度(テストデータ期間)
## 予測値と実測値
preds = df.iloc[len(df_train):].loc[:, 'Predict']
y = df.iloc[len(df_train):].loc[:, 'y']
## 指標出力
print('RMSE:')
print(np.sqrt(mean_squared_error(y, preds)))
print('-----------------')
print('MAE:')
print(mean_absolute_error(y, preds))
print('-----------------')
print('MAPE(%):')
print(np.mean(abs(y - preds)/y)*100)
最後にグラフを描画して予測時と実測値を比較します。
# 株価データを可視化
fig, ax = plt.subplots(figsize=(15,7))
# テスト期間の実測値と予測値を描画
ax.plot(df.iloc[len(df_train):]['Date'], df.iloc[len(df_train):]['y'], label="Actual", lw=4)
ax.plot(df.iloc[len(df_train):]['Date'], df.iloc[len(df_train):]['Predict'], label="Forecast", lw=4)
ax.set_title("Forecast vs Actual",fontsize=15)
plt.legend()
plt.show()
Optunaでハイパーパラメータを最適化することで、RMSE、MAE、MAPEの値が小さくなっており、精度が向上していることが確認できました。CV(クロスバリデーション)を合わせた Optuna による最適化については、別の投稿で取り上げていきたいと考えています。