【Aidemy受講】AIモデルで米国株価を予測 | pythonとAIをゼロから学ぶ

Aidemyで学習成果!LSTMで米国株価を予測してみた

記事のポイント

想定読者:
AIによる株価予測に関心がある方
機械学習・深層学習を勉強されている方
Aidemyを通じて学んだことに興味がある方

トピック:
①米国株の時系列データを予測
②必要なライブラリのインポート
③株価データのインポート
④学習用データとテストデータの分割
⑤LSTMモデルの実装
⑥結果と見えてきた課題

はじめに

私は2月よりAidemy Premium Planのデータ分析講座を受講しました。そして、ここに6ヶ月学んだ成果物を公開します。

本ブログは実際の成果物発表も兼ねており、合格することで卒業となります。

(2022年8月18日追記:晴れて合格となりました!)

私はAIについてほぼ初心者であり、pythonについても完全な初学者です。ただAidemyを通じてpythonについても、また機械学習についてもたくさんのことを学べたと実感しています。

テーマ選定について

ウクライナ情勢や高水準のインフレ懸念により、2022年上期の米国株の動きが非常に目まぐるしいものがあります。特に有力銘柄が軒並み暴落しており、売買タイミングは非常に難しい時期にあります。

かくいう私も米国株には非常に関心があり、米国株の株価予測ができれば面白そうだなということで、過去の時系列データから株価を予測するプログラムを作成してみました。

環境構築

まず、phtyonを実行する環境を整備します。

私はこれまでAidemyで使ってきたGoogle Collaborateを引き続き利用。

phtyon実行環境
Google Colaboratery(最新release日:2022/7/22)

株価予測の実装

予測モデル構築への方針

Aidemyの講座”時系列解析II (RNNとLSTM)”より、LSTMが面白そうなので実装しようと思いました。なぜLSTMかというと、シンプルなRNNでは長期的な記憶保持ができない一方、LSTMは長期的な記憶保持ができるメリットがあります。

ではコードの中身について説明します。

必要なライブラリをインポート

データの取扱に必要なライブラリを利用するため、
・pandas
・numpy
をインポートしました。
ニューラルネットワークには特徴量のスケーリングが一般的なので、
・sklearn.preprocessing
をインポートします。

他にもトレーニングやモデル実装のため、
・sklearn.model_selection # トレーニングデータとテストデータを分割するため
・keras.models、keras.layers # LSTM実装のため

また表を描画するためのセットである、
・matplotlib.pyplot、datetime
をインポートしていきます。

株価データをstooqから取得

次に、APIを使ってStooqからデータが入手できるようなので、データを昇順に取り出し、グラフを描画します。

ここまでのコードは下記となります。

#必要なライブラリのインポート
import math
import pandas_datareader as web
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from keras.models import Sequential
from keras.layers import Dense, LSTM
import matplotlib.pyplot as plt
import datetime

#Stooqから株価をAPIで入手。2014年1月~2021年12月までのデータを昇順に取り出す
df = web.DataReader('KEYS', data_source='stooq', start='2014-01-01', end='2022-8-13').sort_index()
df.head()

#グラフのスタイルを'fivethirtyeight'にする
plt.style.use('fivethirtyeight')

#終値をグラフ化する
plt.figure(figsize=(16,8))
plt.title('Stock Chart')
plt.plot(df['Close'])
plt.xlabel('Date',fontsize=18)
plt.ylabel('ClosePrice($)',fontsize=18)
plt.show()

スクレイピングについて

なお、データ収集にはスクレイピングを利用する方法もありますが、今回の成果物ではできるだけ回避してみました。
スクレイピングでもよかったのですが、サイトへの負荷がかかることや著作権の観点から禁止しているサイトもあります。

今回の成果物発表では、安全にデータを入手する方法として、Stooqが公開してるAPIを用いました。
Stooqでは一日あたり250回のデータ収集アクセスが許可されており、安全にデータ入手することができます。

データ削除とデータ分割

APIで抜き出したデータには、最高値、最安値、終値、および取引量があります。
今回は終値だけを抜き出した時期列データから株価予測を考えます。また、抽出した終値データをトレーニング用およびテスト用データに分割する(それぞれ、80%:20%の割合を考えました)。

#indexと終値だけを抜き出す
data = df.filter(['Close'])

#dataをデータフレームから配列に変換し、データの80%をトレーニングデータとして使用する。
# indexの値をXに格納
X = data.index 
# 終値をyに格納
y = data.values

train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.2, random_state=42)

#トレーニングデータを格納するための変数を作る。
training_length = len(train_X)

正規化を行う

今回はLSTMを実装するが、LSTMはニューラルネットワークの一種です。
通常ニューラルネットワークを実装する場合、Feature Scaling(正規化や標準化)する必要があります。
LSTMにデータを渡す前に、ニューラルネットワーク特徴量を0 ~ 1にスケーリングします。

#データセットを0から1までの値にスケーリング
scaler = MinMaxScaler(feature_range=(0, 1)) 
#fitは変換式を計算し、終値(y)をtransform によりデータを変換
scaled_data = scaler.fit_transform(y)

予測アルゴリズムの検討

過去60日間の終値を学習し、61日目の終値を予測するプログラムを作成したいと思います。
そのため、過去60日間の終値を1まとめとしたデータセットを作っていくことにします。

len(scaled_data)

#正規化された訓練データセットを作成する。データ数はトレーニングデータ数となる。
train_data = scaled_data[0:training_length, :]
len(train_data)


#訓練データを各60個毎にまとめたX_training_setsとy_training_setsのセットに分ける*
X_training_sets = []
y_training_sets = []
for i in range(60, len(train_data)):
    X_training_sets.append(train_data[i-60:i, 0])
    y_training_sets.append(train_data[i, 0])

#補足1
#*X_training_sets = (array([60コ]), array([60コ]), ・・・)と60コの行データが***コ入っている。同様にy_training_sets = (a,b,c,・・・)と***コのデータが入っている

#LSTMを利用するために、訓練データセットをnumpy配列に変換
X_training_sets, y_training_sets = np.array(X_training_sets), np.array(y_training_sets)
#次にLSTMにデータを渡すために入れ子構造にデータ変換**
X_training_sets = np.reshape(X_training_sets, (X_training_sets.shape[0], X_training_sets.shape[1], 1))

#補足2
#**numpy.reshape(numpy.ndarray,(a, b, c))
#データをa個のndarrayに分ける
#その分けられたデータはb行、c列の形状になる
#X_training_sets = array([[a],[b],[c],・・・]) と60行1列のデータを***コ作る

LSTMモデルの実装

LSTMモデルを構築していきます。今回は、50個のニューロンと、2つのLSTM、2つのDenseレイヤー(1つは25ニューロン、1つは1ニューロン)を作成します。

#LSTMネットワークモデルを作成する
model = Sequential()
model.add(LSTM(units=50, return_sequences=True,input_shape=(X_training_sets.shape[1],1)))
model.add(LSTM(units=50, return_sequences=False))
model.add(Dense(units=25))
model.add(Dense(units=1))

#モデルのコンパイル
#誤差計算は”平均二乗誤差(mean_squared_error: MSE)”を、オプティマイザーは"adam"を使う
model.compile(optimizer='adam', loss='mean_squared_error')


#訓練開始
model.fit(X_training_sets, y_training_sets, batch_size=1, epochs=1)

訓練データで得た学習モデルをテストデータで試験

「予測アルゴリズムの検討」および「LSTMモデルの実装」で作成した学習モデルを、分割したテストデータに展開し、実行してみる。

なおモデルの精度を確認するべく、RMSEを実装(デフォルトでは使わないので、演算させる場合は#を外す)。

#テストデータを作成。60個毎に纏めたデータを作っていく。
test_data = scaled_data[training_length - 60: , : ]

X_test_sets = [] #予測値をつくるために使用するデータを入れる
y_test_sets = y[training_length : , : ] #実際の終値データ

for i in range(60,len(test_data)):
    X_test_sets.append(test_data[i-60:i,0]) #正規化されたデータ

#データをnumpyに変換
X_test_sets = np.array(X_test_sets)



#LSTMで読めるような入れ子構造に変換
X_test_sets = np.reshape(X_test_sets, (X_test_sets.shape[0], X_test_sets.shape[1], 1))

#訓練データから生成したモデルにテストデータを入れて予測
y_pred = model.predict(X_test_sets)
y_pred = scaler.inverse_transform(y_pred) #スケーリング(正規化)をもとに戻す



#RMSEをチェックする場合は#を外す
#rmse = np.sqrt(np.mean(y_pred - y_test_sets) ** 2)
#print(rmse)

#テストデータのプロットをグラフに描画
train = data[:training_length]
valid = data[training_length:]
valid['Predictions'] = y_pred#Visualize the data
plt.figure(figsize=(16,8))
plt.title('Stock Chart w/ Model')
plt.xlabel('Date', fontsize=18)
plt.ylabel('USD ($)', fontsize=18)
plt.plot(train['Close'])
plt.plot(valid[['Close', 'Predictions']])
plt.legend(['Train', 'Actual', 'Predictions'], loc='lower right')
plt.show()

print('最新日の終値(予測値)')
print(valid.index[-1], y_pred[-1])

プログラムの実行

今回は米国電子計測器メーカーのKEYSとNATIを実行してみる。これらの銘柄を選んだ理由ですが、KEYSは長期に渡って安定した右肩上がりの株価変動ですが、NATIは暴落を経験しているため、単純な株価変動でも、複雑な株価変動でもLSTMモデルが正確に動作を見極めることができます。

KEYS

※期間は2014年1月1日 ~ 2022年7月31日のグラフである。

NATI

※期間は2015年1月1日 ~ 2022年7月31日のグラフである。

考察

LSTMモデルは1週間超の遅れを伴う

株価は過去60日分の時系列データをもとに最新株価予測を行っているため、大きく値は外さないだろう(=つまり、突拍子もない値は返さず無難なものを返す)とは思っていましたが、逆を返せば株価傾向が最新値へ”反映”されるのに遅延が発生すると予想していました。

先述のグラフのテストデータ区間において、実際終値および予測値のそれぞれ最高値 or 最底値をつけた日を比較することにします。

#検証:終値最大値同士のインデックス(=日時)を比較

cl_max= valid['Close'].idxmax()
pre_max= valid['Predictions'].idxmax()

print(pre_max - cl_max)

KEYS=予測値が実終値より8日間の遅延(うち2022/1/1は休日なので実質7日の遅延)

NATI = 予測値が実終値より5日間の遅延*

*期間を2014年1月1日~2021年12月31日とし、テストデータ期間の底値同士を比較。

NATI-2020暴落部分の確認

意地悪検証として、時系列データだけで2020年の暴落を見抜けるか、という検証を行ってみました。

データは2012年4月~2020年12月までを読み込み、80%のデータを訓練に利用すると以下となった。予測としては暴落を見抜いているが、実際の終値が予測値以上で、AIの予想を超えるほどの暴落だったことがわかります。

また、暴落時の底値の遅延日数としては、7日程度であったことがわかりました。

まとめ

グラフより、実際の終値とLSTMによる予測値の株価推移は非常によいFittingが見て取れます。

しかしミクロ視座で見ると、LSTMの特性上遅延が発生しており予測値の追従には少なくとも5日程度以上の日数遅延があると見込まれる結果となりました。また、株価の暴落予測で見ると、LSTMモデルの予測値よりも実際の終値のほうが低かったため、株価暴落のアルゴリズムとしては少々物足りない結果となりました。

実際の株価暴落要因としてはコロナショックだったようで、時系列データは世界情勢は反映されていません。今回のプログラミングではコロナ要因が検知できていないため、今後の精度向上検討としてはtwitterなど感情分析も加えた株価予測を検討してみたいと思います。