Amazon SageMaker と GluonTS で株価の予測をしてみよう

2020-02-02
デベロッパーのためのクラウド活用方法

Author : 藤田 充矩

本記事は、Amazon SageMaker と GluonTS を使って、株価予測モデルを作るというテーマで執筆しました。本記事で株価の分析、モデリングや機械学習に興味を持っていただけるとうれしいです。

株価を精度よく予測できると思いますか ?

Developer のみなさん、こんにちは。機械学習ソリューションアーキテクトの藤田です。

最近、Youtube などでも投資をテーマとしたものが増えてきましたね。この記事を読んでいる皆さんの中にも投資に興味を持っている方がいらっしゃるかもしれませんが、機械学習でも株価など投資商品を題材にモデル構築するなどの取り組みが公開されていることも多く、人気のあるテーマの一つとなっています。

今回はタイトルにもある GluonTS というライブラリを使って、株価の予測を行ってみたいと思います。

*注:機械学習の文脈では株価含め時系列予測は難しいタスクの一つです。また予測モデリングの応用などは投資の場合、リスクを伴いますのでご自身の責任で取り扱っていただけますようお願いいたします。

ご注意

本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。

builders.flash メールメンバーへの登録・特典の入手はこちら »

*ハンズオン記事およびソースコードにおける免責事項 »

このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »

毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。 


1. 情報を集める

まず株価予測に必要なデータを集めます。

今回の取り組みでは、株価の時系列データが必要となりますので、インターネットのサイトでデータを取得してきました。


2. GluonTS を使った株価予測

GluonTS は時系列アルゴリズムが多数用意された Python ベースのライブラリです。古典的な時系列モデルに加え、Amazon が開発した DeepAR、近年、流行りの Transformer ベースのアルゴリズムなど多くの Deep Learning ベースのアルゴリズムが使用可能です。

ここでは GluonTS の DeepAR を使って、株価の予測モデルを作りたいと思います。Deep Learning ベースのアルゴリズムの多くは複数の系列 (株価で言えば複数の銘柄) を一つのネットワークで扱うため、系列数が多い場合にモデリング工数が省力化できるといったメリットがあります。

2-1. GluonTS の環境構築

GluonTS は MXNet を前提に機能するため、まずは MXNet をインストールする必要があります。Amazon SageMaker で実行する場合は conda_mxnet_p36 のカーネルや MXNet のコンテナを利用することでこの手間が省けます。
MXNet のインストールが完了したら GluonTS をインストールします。

pip install --upgrade mxnet==1.7
# pip install --upgrade mxnet-cu101 GPUの場合
pip install gluonts

2-2. データの前処理

データは画像のような形式を用意していただければと思います。各列には ‘銘柄コード’, ‘日付’, ‘値 (今回は終値)’ と 3 列のデータとなっています。

ここでは2018、2019 年のデータを使用しています。このデータが stock_price としてオブジェクトに格納されているとします。

次に ‘日付’ に修正を加えます。具体的にはデータの日付で欠損している平日 (主に祝日) の行を追加します。

この処理を行う理由は、DeepAR のアルゴリズム内では、データセットの時間の頻度 (年、月、日などの単位) を基に特徴量を内部で作成するためです。現時点では祝日などを反映したカスタム頻度に対応していないため、ここでは B (BusinessDay, 月曜から金曜までの 5 日間を一つの周期とする) という単位で計算ができるようデータセットを作成します (月曜から日曜の場合はDを使います)。

from datetime import datetime, timedelta
import numpy as np
import pandas as pd


START_DATE = datetime(year=2018, month=1, day=4)
END_DATE = datetime(year=2019, month=12, day=30)
TIMEDELTA = END_DATE - START_DATE


# TIMEDELTAの日数で銘柄コードと土日を除いたBusinessDayのグリッドを作成
keys, dates = np.meshgrid(
    [stock_price['Local Code'].unique()],
    [(START_DATE + timedelta(days=i)) for i in range(TIMEDELTA.days + 1) if (START_DATE + timedelta(days=i)).weekday() not in [5,6]]
)

business_day = pd.DataFrame({
    "Local Code": keys.ravel(), 
    "EndOfDayQuote Date": dates.ravel(),
})

# 作成したグリッドにstock_priceをjoinする
stock_dataset = pd.merge(
    business_day, 
    stock_price, 
    on=["Local Code", "EndOfDayQuote Date"], 
    how="left").sort_values(
    ["Local Code", "EndOfDayQuote Date"]).reset_index(drop=True)

このコードを実行した後の stock_dataset はこのような形になります。

2018 年 1 月 8 日は成人の日のため、証券取引所は休業しておりデータがありませんが、これらの値は欠損のまま、データセットの日付が月曜から金曜まで漏れなく含まれるようにしました。DeepAR では値の欠損はそのまま入力しても機能します。

2-3. GluonTS 形式のデータセット作成

次に GluonTS のアルゴリズムに入力できるようデータを ListDataset へ格納します。

from gluonts.dataset.common import ListDataset
from gluonts.dataset.field_names import FieldName


# 予測期間を20営業日
prediction_length = 20

# データをindexが銘柄コード、カラムが日付、終値が値となるようにPivot展開
stock_dataset = stock_dataset.pivot(index='Local Code', columns='EndOfDayQuote Date', values='EndOfDayQuote Close')

# 学習は最後の20営業日を除いたデータ、テストは全て含まれたデータ
train_target_values = [ts[:-prediction_length] for ts in stock_dataset.values]
test_target_values = stock_dataset.copy().values

# データの開始日付を銘柄数分作成
dates = [pd.Timestamp("2018-01-04", freq='1B') for _ in range(stock_dataset.shape[0])]

# 学習、テストListDatasetを作成
train_ds = ListDataset([
    {
        FieldName.TARGET: target,
        FieldName.START: start,
        FieldName.ITEM_ID: code,
    }
    for (target, start, code) in zip(train_target_values, dates, stock_dataset.index)
], freq="1B")

test_ds = ListDataset([
    {
        FieldName.TARGET: target,
        FieldName.START: start,
        FieldName.ITEM_ID: code,
    }
    for (target, start, code) in zip(test_target_values, dates, stock_dataset.index)
], freq="1B")

2-4. DeepAR の学習

それでは先ほど作ったデータセットを使って学習をしてみましょう。最初に DeepAREstimator からオブジェクトを作成します。

以下のコードは長いように感じられるかもしれませんが、必須とコメントアウトがある freq, prediction_length 以外は入力しなくとも動作します。入力がなかった場合は以下のデフォルトパラメータが入力されているということを知っていただきたく記載しています。

from gluonts.model.deepar import DeepAREstimator
from gluonts.mx.distribution.student_t import StudentTOutput
from gluonts.mx.trainer import Trainer


estimator = DeepAREstimator(
    freq='1B', # 必須
    prediction_length=prediction_length, # 必須
    trainer = Trainer(batch_size=32,
                      clip_gradient=10.0, 
                      ctx='cpu',
                      epochs=100, 
                      hybridize=True, 
                      init="xavier", 
                      learning_rate=0.001, 
                      learning_rate_decay_factor=0.5, 
                      minimum_learning_rate=5e-05, 
                      num_batches_per_epoch=50, 
                      patience=10,
                      weight_decay=1e-08), 
    context_length = prediction_length, 
    num_layers = 2,
    num_cells = 40, 
    cell_type = 'lstm', 
    dropout_rate = 0.1, 
    use_feat_dynamic_real = False, 
    use_feat_static_cat = False, 
    use_feat_static_real = False, 
    cardinality = None, 
    embedding_dimension = None, 
    distr_output = StudentTOutput(), 
    scaling = True, 
    lags_seq = None, 
    time_features = None, 
    num_parallel_samples = 100, 
)

それでは作成したオブジェクトで学習を開始します。train の引数には先ほど作った学習用のデータセットを渡します。

predictor = estimator.train(train_ds)

実行すると学習が始まり、こちらのようなログが出力されていきます。

2-5. 予測と評価

学習が終わったら推論を実行します。DeepAR は確率的な予測ができるアルゴリズムで、今回は予測に 100 個のサンプリングを実行します。この確率的な予測は予測結果の不確実性を理解する上で有用です。

from gluonts.evaluation.backtest import make_evaluation_predictions

forecast_it, ts_it = make_evaluation_predictions(
    dataset=test_ds,                                             
    predictor=predictor,
    num_samples=100
)

print("Obtaining time series conditioning values ...")
tss = list(tqdm(ts_it, total=len(test_ds)))

print("Obtaining time series predictions ...")
forecasts = list(tqdm(forecast_it, total=len(test_ds)))

それでは予測値の評価をしてみましょう。評価は GluonTS にある Evaluator クラスを使うと便利です。

from gluonts.evaluation import Evaluator

evaluator = Evaluator(quantiles=[0.5])
agg_metrics, item_metrics = evaluator(iter(tss), iter(forecasts), num_series=len(test_ds))

print(json.dumps(agg_metrics, indent=4))

このコードを実行すると、こちらのようなメトリクスの一覧が出力されます。

また、予測結果のメトリクスはアイテムごとに取得することも可能です。evaluator は集約されたメトリクスとアイテムごとのメトリクスを返しており、item_metrics を確認してみましょう。

item_metrics.head()

銘柄ごとのメトリクスが取得できました。

クリックすると拡大します

2-6. 予測結果の可視化

最後に予測結果を可視化して、実際の値と予測値を確認してみましょう。
証券コードの数字が小さい方から 5 つの銘柄を取り出して可視化してみます。

import matplotlib.pyplot as plt
import os


plot_log_path = "./plots/"
directory = os.path.dirname(plot_log_path)
if not os.path.exists(directory):
    os.makedirs(directory)


def plot_prob_forecasts(ts_entry, forecast_entry, path, sample_ide, inline=True):
    plot_length = 40
    prediction_intervals = (50, 90)
    legend = ["observations", "median prediction"] + [f"{k}% prediction interval" for k in prediction_intervals][::-1]

    _, ax = plt.subplots(1, 1, figsize=(10, 7))
    ts_entry[-plot_length:].plot(ax=ax)
    forecast_entry.plot(prediction_intervals=prediction_intervals, color='g')
    ax.axvline(ts_entry.index[-prediction_length], color='r')
    plt.legend(legend, loc="upper left")
    plt.title(forecast_entry.item_id)
    if inline:
        plt.show()
        plt.clf()
    else:
        plt.savefig('{}forecast_{}.png'.format(path, forecast_entry.item_id))
        plt.close()


print("Plotting time series predictions ...")
for i in tqdm(range(5)):
    ts_entry = tss[i]
    forecast_entry = forecasts[i]
    plot_prob_forecasts(ts_entry, forecast_entry, plot_log_path, i, inline=True)

こちらが出力される結果となります。

青色が実際の値 (株価の終値)、緑色の線がサンプリングされた推論結果の中央値、濃い緑色の範囲がサンプリングの 50% が含まれる区間、薄い緑色がサンプリングの 90% が含まれる区間となります。

クリックすると拡大します

クリックすると拡大します

クリックすると拡大します

クリックすると拡大します

クリックすると拡大します


3. まとめ

いかがでしたしょうか ?

今回は GluonTS を使って、株価の予測を行ってみました。
さらに精度を向上させるために試すこととしては、

  • アルゴリズムのハイパーパラメータを変える (今回はデフォルトパラメータ使用)
  • 他のアルゴリズムを試してみるのもいいかもしれません。
  • 金融データの場合、生の値ではなく差分系列、対数差分系列に変換すると扱いやすくなる性質があります。これを試してみる価値もあるでしょう。
  • DeepAR 含め、アルゴリズムによっては予測対象となる時系列の他に動的な特徴、静的な特徴を扱うことができます。
    • 動的な特徴には関連しそうな時系列データやターゲット時系列のラグ、イベント有無などがよく使われたりします。
    • 静的な特徴にはアイテムの特性やカテゴリを現すもの、今回で言えば銘柄ごとの業種区分などを入れると良いかもしれません。

などが考えられます。個人的にもそうですが、何かモチベーションになるタスク、テーマがあると機械学習の勉強がはかどったりしますよね。

今回は機械学習で株価予測を行うデモをご紹介しましたが、2021 年 2 月から 日本取引所グループ (JPX) が株価予測のモデルコンペを開催 するようです。今回興味をもたれた Developer のみなさんはチャレンジしてみてはいかがでしょうか。

みなさんに機械学習を楽しんでもらえるとうれしいです !


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

藤田 充矩 (Atsunori Fujita)
アマゾン ウェブ サービス ジャパン合同会社
機械学習 プロトタイプ ソリューション アーキテクト

自然言語処理や時系列分析などを得意にしています。趣味は kaggle です。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する