スポンサーリンク

【Keras】LSTMで時系列データ予測の手順。〜 簡単な株価予測を例に 〜

Keras
スポンサーリンク

今回の記事は機械学習の一種のLTSMを用いて時系列データである株価のデータを予測する(実際に予測はできませんが理論上です。)方法を記事にしていきます。

LSTMはかなり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で株価を予測する(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
_________________________________________________________________

最後に学習を行っていきます。

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で株価を予測する(作成した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の記事を記載しているのできになる方はご参照ください。

Keras
スポンサーリンク
スポンサーリンク
SunnyDayTravel-Blog

コメント

タイトルとURLをコピーしました