今回の記事は機械学習の一種のLTSMを用いて時系列データである株価のデータを予測する方法を記載する記事です。データ取得からモデルの学習、モデルによる株価予測の流れで手順をご紹介していきます。機械学習に興味がある方は是非記事読んで見てください。
LSTMとは?
LSTMはCNNとともによく使われる分析手法です。ただし、CNNとは違い、画像分類ほど手軽に行うことができず、文章生成などの参考記事は多いですが、時系列データに対して予測を行う記事は少ないです。数値が変動するような波長状のもの(株価)に対して、手探りですが自身で行ってました。
LSTMで株価予測をする流れ
- データ準備
- ニューラルネットの定義
- モデル作成と評価
- 実際の株価を学習モデルで予測
使用したライブラリはTensorFlow内蔵のKerasですがスタンドアロン型Kerasでも実装できると思うのでその場合は適宜「tensorflow.keras」の部分を「Keras」に変換。また「importerror」回避のために「tensorlfow.keras」をエラー箇所のみ「tensorflow.python.keras」に変換しております。docker上に環境構築している場合はこの「.python」をつけないと「importerror」になってしまうようです。
使用するデータとしては下記からダウンロードできますが念のため説明をしておきます。時系列データが必要なため日系平均のまとめが2017年まで保管されていたのでそれを使用していきます。最終的なゴールとしては今回作成したものを使用して2018年の株価を予測して見てどのような挙動を示すかを確認するところまで進んでいきます。
では本記事のメイン部分に進んでいきましょう。
また、参考にした記事はこちらです。
LSTMで予測するデータ準備
今回使用するデータは日経平均の株価を扱っていきます。
自身でデータを取集することも可能と思われますが下記にリンクはあります。データ名はtrain_atai.csvです。正解ラベルの方はtrain2.csvです。
データの説明ですがcsvにも書いていますが当日からの翌日の増減率を「~-1」「-1~0」「0~1」「1~」の四つの通りに分けてそれぞれone-hot表現で表しています。
データ自体はgitに上げていますのでこちらからダウンロードしてもOKです。
ダウンロードするのはtrain.csvです。これをjupyterのファイルを展開しているフォルダとかに保存してjupytreでコードを実行指定けばOKです。
簡単にできましたね。本当のメインは次からです。実際にコードなども書いていきます。
LSTMニューラルネットの定義
本記事の環境紹介
Python 3.6.9
Docker version 19.03.12
tensorflow 2.2.0
また、本書ではdocker上でtensorflow環境を構築しており、そこでjupyterでブラウザによる操作を行なって今うす。難しいことを言っているように聞こえるかもですが、かなり簡単に環境構築を行うことができるので、環境構築に困っている方はこちらの記事を参考に「docker上にtensorflow+jupyter notebookの環境を構築」して見ましょう。
ニューラルネット構築ソースコード解説
では実際にコードを書いていきます。
from __future__ import print_function
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import csv
from tensorflow.python.keras.layers.core import Activation
from tensorflow.python.keras.layers.core import Dense
from tensorflow.python.keras.layers.core import Dropout
from tensorflow.keras.models import Sequential
from tensorflow.python.keras.utils import np_utils
from tensorflow.keras.utils import plot_model
from tensorflow.python.keras.layers.recurrent import LSTM
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.initializers import glorot_uniform
from tensorflow.keras.initializers import orthogonal
from tensorflow.keras.initializers import TruncatedNormal
ここで必要なモジュールをダウンロード。docker上に環境を作っているためかいくつかのkerasモジュールをインストール する際にフォルダをしっかり指定(.pythonをつける)をしないと「importerror」になるので注意が必要。
次にデータを読み込んでいきます。
# 学習データ
df1 = csv.reader(open('/1113/train_atai.csv', 'r'))
data1 = [ v for v in df1]
mat = np.array(data1)
mat2 = mat[1:] # 見出し行を外す
x_data = mat2[:, 1:].astype(np.float) # 2列目以降を抜き出してfloat変換
print('x_data.shape=', x_data.shape)
# ラベルデータ
# 1%以上/0%以上/-1%以上/-1%未満
df2 = csv.reader(open('/1113/train2.csv', 'r'))
data2 = [ v for v in df2]
mat3 = np.array(data2)
mat4 = mat3[1:] # 見出し行を外す
t_data = mat4[:, 1:].astype(np.float) # 2列目以降を抜き出してfloat変換
print('t_data.shape=', t_data.shape)
#出力#####
x_data.shape= (2196, 6)
t_data.shape= (2196, 4)
ここではcsvからデータを読み込んでいます。
入力は、[○,○,○,○,○,○]の6この要素を持つ配列が[[○,○,○,○,○,○],[○,○,○,○,○,○],…….[○,○,○,○,○,○]]のように2196個並んでいるものをインプット。
出力に関しては入力の6個の配列が4つになっているものが2196個並んでいるというものです。
# 取得したデータを読み込んで終値だけを取り出す
maxlen = 80 # 入力系列数
n_in = x_data.shape[1] # 学習データ(=入力)の列数
n_out = t_data.shape[1] # ラベルデータ(=出力)の列数
len_seq = x_data.shape[0] - maxlen + 1
data = []
target = []
for i in range(0, len_seq):
data.append(x_data[i:i+maxlen, :])
#2つ避ける必要がある
target.append(t_data[i+maxlen - 1, :])
x = np.array(data).reshape(len(data), maxlen, n_in)
t = np.array(target).reshape(len(data), n_out)
# ここからソースコードの後半
n_train = int(len(data)*0.9) # 訓練データ長
x_train,x_test = np.vsplit(x, [n_train]) # 学習データを訓練用とテスト用に分割
t_train,t_test = np.vsplit(t, [n_train]) # ラベルデータを訓練用とテスト用に分割
print(x_train.shape, x_test.shape, t_train.shape, t_test.shape)
#出力#######
(1905, 80, 6) (212, 80, 6) (1905, 4) (212, 4)
ニューラルネットを構築するための関数は下記です。
class Prediction :
def __init__(self, maxlen, n_hidden, n_in, n_out):
self.maxlen = maxlen
self.n_hidden = n_hidden
self.n_in = n_in
self.n_out = n_out
def create_model(self):
model = Sequential()
model.add(LSTM(self.n_hidden, batch_input_shape = (None, self.maxlen, self.n_in),
kernel_initializer = glorot_uniform(seed=20170719),
recurrent_initializer = orthogonal(gain=1.0, seed=20170719),
dropout = 0.5,
recurrent_dropout = 0.5))
model.add(Dropout(0.5))
model.add(Dense(self.n_out,
kernel_initializer = glorot_uniform(seed=20170719)))
model.add(Activation("softmax"))
model.compile(loss="categorical_crossentropy", optimizer = "RMSprop", metrics = ['categorical_accuracy'])
return model
# 学習
def train(self, x_train, t_train, batch_size, epochs) :
early_stopping = EarlyStopping(patience=0, verbose=1)
model = self.create_model()
model.fit(x_train, t_train, batch_size = batch_size, epochs = epochs, verbose = 1,
shuffle = True, callbacks = [early_stopping], validation_split = 0.1)
return model
ニューラルネットの構成はまずは下記のような構造にします。
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
lstm_1 (LSTM) (None, 80) 27840
_________________________________________________________________
dropout_1 (Dropout) (None, 80) 0
_________________________________________________________________
dense_1 (Dense) (None, 4) 324
_________________________________________________________________
activation_1 (Activation) (None, 4) 0
=================================================================
Total params: 28,164
Trainable params: 28,164
Non-trainable params: 0
_________________________________________________________________
ネット定義は完了したので、このネットでモデルを学習させていきましょう。
LSTMでモデル学習
n_hidden = 80 # 出力次元
epochs = 100 # エポック数
batch_size = 10 # ミニバッチサイズ
# モデル定義
prediction = Prediction(maxlen, n_hidden, n_in, n_out)
# 学習
model = prediction.train(x_train, t_train, batch_size, epochs)
# テスト
score = model.evaluate(x_test, t_test, batch_size = batch_size, verbose = 1)
print("score:", score)
# 正答率、準正答率(騰落)集計
preds = model.predict(x_test)
correct = 0
semi_correct = 0
for i in range(len(preds)):
pred = np.argmax(preds[i,:])
tar = np.argmax(t_test[i,:])
if pred == tar :
correct += 1
else :
if pred+tar == 1 or pred+tar == 5 :
semi_correct += 1
print("正答率:", 1.0 * correct / len(preds))
print("準正答率(騰落):", 1.0 * (correct+semi_correct) / len(preds))
Epoch 1/100
172/172 [==============================] - 24s 139ms/step - loss: 1.5717 - categorical_accuracy: 0.2544 - val_loss: 1.5937 - val_categorical_accuracy: 0.3089
Epoch 2/100
172/172 [==============================] - 24s 140ms/step - loss: 1.5014 - categorical_accuracy: 0.2835 - val_loss: 1.4389 - val_categorical_accuracy: 0.3089
Epoch 3/100
172/172 [==============================] - 26s 149ms/step - loss: 1.4525 - categorical_accuracy: 0.3005 - val_loss: 1.4246 - val_categorical_accuracy: 0.3089
Epoch 4/100
172/172 [==============================] - 25s 146ms/step - loss: 1.4297 - categorical_accuracy: 0.2964 - val_loss: 1.3720 - val_categorical_accuracy: 0.3089
Epoch 5/100
172/172 [==============================] - 24s 139ms/step - loss: 1.4318 - categorical_accuracy: 0.2975 - val_loss: 1.3371 - val_categorical_accuracy: 0.3089
Epoch 6/100
172/172 [==============================] - 26s 149ms/step - loss: 1.4086 - categorical_accuracy: 0.2882 - val_loss: 1.3396 - val_categorical_accuracy: 0.3089
Epoch 00006: early stopping
22/22 [==============================] - 0s 20ms/step - loss: 1.3056 - categorical_accuracy: 0.3396
score: [1.305554747581482, 0.3396226465702057]
正答率: 0.33962264150943394
準正答率(騰落): 0.44339622641509435
モデル学習はこれで完了です。早めにアーリーストップで終了しちゃいますね。
精度はあんまりよくなさそう。
大体の記事はここで終わってしまします(今回参考にした記事もここまで。。)が、大事なのはこのあとですよね。実際に現在のデータから将来の株価を予測することができるのか。精度は保証しませんが笑。それに関しては最後の章に記載いたします。
まずはLSTMのニューラルネットの流れに関して私の理解の範疇で解説を行っていきます。
最後のところにmodelの構造を載せております。そこを見ると最後の出力は softmax関数によって出力されるようになっています。そのため確率として出力が行われていると言うことですね。
今回の大前提は[2096,100,6]の行列を入力すると[2096,4]の行列を出力してくれます。
予測部分はpredで占めされているのでそこをしっかり見ていきましょう。まずはx_testの行列がどのようなデータかと言うと下記のようなデータになっています。
print(x_test)
##出力##
[[[ 22270.039063 22390.199219 22162.810547 22362.550781 22362.550781
57100. ]
[ 22420.669922 22463.029297 22377.880859 22410.820313 22410.820313
50100. ]
[ 22484.009766 22602.240234 22452.419922 22601.769531 22601.769531
50400. ]
...
[ 21391.730469 21563.269531 21363.669922 21506.880859 21506.880859
63400. ]
[ 21275.509766 21330.359375 21101.439453 21115.449219 21115.449219
80500. ]
[ 21107.169922 21168.619141 20880.730469 20987.919922 20987.919922
81300. ]]
[[ 22420.669922 22463.029297 22377.880859 22410.820313 22410.820313
50100. ]
[ 22484.009766 22602.240234 22452.419922 22601.769531 22601.769531
50400. ]
[ 22693.689453 22838.060547 22682.390625 22799.640625 22799.640625
50200. ]
# ...長いので割愛。。
#要は2096,80,6の行列が全部入っているのです。
この行列を代入した予測にあたる部分predはどのような形かと言うと下記のようになっています。
print(pred(x_test))
##出力##
[[0.1749952 0.41767403 0.25645477 0.15087593]
[0.1832204 0.38855684 0.24913538 0.17908733]
[0.16417612 0.37370232 0.2743935 0.18772806]
[0.1832204 0.38855684 0.24913538 0.17908733]
[0.18322043 0.3885568 0.24913542 0.17908736]
#...割愛
#要は2096,4の行列が出力されているのです。
見えてきましたね要は下記のような対応をしていることがわかります。
print(x_test[0][0])
#出力
#[22270.039063 22390.199219 22162.810547 22362.550781 22362.550781
57100. ]
print(preds[0])
#出力
#[0.1749952 0.41767403 0.25645477 0.15087593]
#[~-1, -1~0, 0~1, 1~ ]
「x_test[0][0]」の入力に対して「preds[0]」が対応しているということです。出力部分は四つの増減率に対応しているのでこの初回のものを見ると最も確率が高いものは「-1~0」なので「株価は少し下がる」と言うのがこのモデルによる出力になります。
どうでしょうか。これが作成したモデルを利用すると言うことです。この仕組みを利用して実際に最近のデータから次の日の増減率を予測してみましょう。
作成したLSTM学習モデルで株価予測
先ほどの章で株価を予測できるモデルを作成できていると思います。
精度はさておき。このモデルを使用して実際に予測を行なって見ます。
まずどのように行なっていくかですが、実際にデータを整形する必要がります。
モデルの読み込みに関しては今回は行いませんが実際記事はネットにたくさんあるのでそちらをご参照ください。作成したjupyter notebookで下記を実行して明日の株価を予測して見ます。
2019年から2020年の一年間ぐらいのデータをcsvとして入力して見ます。
データはkensyo.csvとしてgitにありますのでダウンロードするなり自身で作成するなりしてください。
df3 = csv.reader(open('/1113/kensyo.csv', 'r'))
data3 = [ v for v in df3]
mat3 = np.array(data3)
mat4 = mat3[1:] # 見出し行を外す
k_data = mat4[:, 1:].astype(np.float) # 2列目以降を抜き出してfloat変換
print('x_data.shape=', k_data.shape)
#出力
#x_data.shape= (243, 6)
入力データのみを整形するので先ほど使用したコードを少々抜粋していきます。
maxlen = 80 # 入力系列数
n_in = k_data.shape[1] # 学習データ(=入力)の列数
#n_out = t_data.shape[1] # ラベルデータ(=出力)の列数
len_seq = k_data.shape[0] - maxlen + 1
data = []
#target = []
for i in range(0, len_seq):
data.append(k_data[i:i+maxlen, :])
#2つ避ける必要がある
# target.append(t_data[i+maxlen - 1, :])
x = np.array(data).reshape(len(data), maxlen, n_in)
#t = np.array(target).reshape(len(data), n_out)
# ここからソースコードの後半
n_train = int(len(data)*0.9) # 訓練データ長
x_train,x_test = np.vsplit(x, [n_train]) # 学習データを訓練用とテスト用に分割
#t_train,t_test = np.vsplit(t, [n_train]) # ラベルデータを訓練用とテスト用に分割
print(x_train.shape, x_test.shape, t_train.shape, t_test.shape)
#出力
#(147, 80, 6) (17, 80, 6) (1905, 4) (212, 4)
よくよく考えればデータを分割する必要はなかったのですが、してしまったので今回はそのまま先ほどと同じようにx_testとして入力して見ましょう。
現在のx_testは「17,80,6」の行列になっています。
preds = model.predict(x_test)
print(preds)
#出力
#[[0.24782957 0.34412682 0.25683862 0.15120508]
[0.26980507 0.36263373 0.23491779 0.13264342]
[0.2476284 0.34221572 0.26034918 0.14980666]
[0.26971483 0.36232114 0.23547849 0.13248555]
[0.24827172 0.3443047 0.25637466 0.15104893]
[0.22713417 0.3723855 0.28445897 0.11602145]
[0.24776866 0.34410226 0.25690255 0.15122657]
[0.23759514 0.35447553 0.293286 0.11464327]
[0.22666097 0.37269455 0.28459275 0.11605183]
[0.23741539 0.35461888 0.29331535 0.11465033]
[0.23741242 0.35462126 0.29331583 0.11465045]
[0.22663653 0.37271485 0.28459617 0.11605258]
[0.22285368 0.3860379 0.26957625 0.12153216]
[0.18662067 0.39038828 0.25354946 0.16944167]
[0.18648882 0.3905096 0.25355896 0.16944261]
[0.17679675 0.41573554 0.26578662 0.14168108]
[0.1667499 0.43482512 0.25558957 0.14283538]]
このようにしっかり確率が出力されています。
このcsvは最新のものなのにざっくりですが明日の予測は最後の出力確率がそれにあたると思います。一番したのここです。「[0.1667499 0.43482512 0.25558957 0.14283538]]」。これを4つの分類「~-1」「-1~0」「0~1」「1~」と紐付けると最も確率の高いものは「-1~0」ですね。
こんな感じで入力に対してきちんと出力されています。
今回のニューラルネットの入出力の形が行列で指定されている形なのでここを変更すれば出てくる形も入力情報も変化させることができます。いろいろ試して見てください。
ただ、なかなか株価予測でしっかりと精度を確率で来ている例は少ないです。夢は持ちすぎずに練習程度と考えてください。
今回の記事は以上です。他にもKeras・TensorFlowの記事を記載しているのできになる方はご参照ください。
コメント