【株式自動売買×ディープラーニング】LSTMで日経平均株価予測を行うプログラムを書いてみた話。

目次。

 

この文章を読んで、面白い!役に立った!...と思った分だけ、投げ銭していただけると嬉しいです。

ofuse.me

 

 

【宣伝】ギターも歌も下手だけど、弾き語りをやっているので、よければ聴いてください。

www.youtube.com

 

 

 

先にこちらを読んでください。

『株式自動売買プログラムを実践に投入すると精度が急落する謎現象の原因究明に奮闘していた話。』を先に読んでください。

blog.sun-ek2.com

 

特に...

  • 原因部分:正規化処理(min-max normalization)を行うコード。
  • 『【株式自動売買×ディープラーニング】LSTMで日経平均株価予測を行うプログラムを書いてみた話。』の再考。

を読んでください。

 

 

 

はじめに。

以下の画像は、LSTMを使って行った日経平均株価予測の結果。

f:id:sun_ek2:20200807134308p:plain

LSTMを使った日経平均株価予測1。

 

株をやっている人ならこのチャートの形に見覚えがあると思う。途中、株価が大暴落している。…これが2020年2・3月に起こった新型コロナウイルスによる日経平均株価の大暴落である。

 

予測値のグラフは、今日までの日経平均株価のデータで明日の終値を予測し、明日までのデータで明後日の終値を予測し、明後日までのデータで明々後日の終値を予測し、…この過程を繰り返して、それぞれの予測値を線でつないでいる。黒色の線は、実際の日経平均株価の終値のデータ。

 

株式自動売買プログラム開発関連の文章は、以下のカテゴリーにまとめているので、興味のある方はどうぞ。

blog.sun-ek2.com

 

この文章を読む前に『株式自動売買とディープラーニング(ニューラルネットワーク)の話。』を読んで頂けると嬉しいです。

blog.sun-ek2.com

 

今回使ったニューラルネットワークは、Long Short-Term Memory、通常、LSTMと呼ばれるもの。LSTMのハイパーパラメータ(入力データ長、隠れ層のパーセプトロン数とか)は、論文を参考にして決めたので、興味のある方は、『論文版はてなブックマーク(その10:ディープラーニング×株価予測)の話。』もどうぞ。

blog.sun-ek2.com

 

 

 

再帰的ニューラルネットワーク (RNN)の改良版:Long Short-Term Memory (LSTM)って?

俗にディープラーニングだなんて呼ばれているニューラルネットワークには、色々な種類がある。LSTMは、その中の内の1つ。

 

「ディープラーニングを使って、株価を予想したい!」と思い立って、LSTMを選ぶのは、多分、一番よくある選択肢なんじゃないかって思う。

 

Long Short-Term Memory (LSTM)は、株価チャートのような時系列データの予測、Google翻訳をはじめとする自然言語処理(時系列ではないが単語間に一定の関係性がある)などに使われている。

 

 

 

株価チャートと再帰的ニューラルネットワーク (RNN)。

株価がずっと安定的に上昇していれば、みんなは安心して株を買い、今回のコロナによる大暴落のように株価が急に下落すれば、みんなは新たに株を買うことを恐れ、株価が小刻みに上がったり、下がったり(もみ合い)していれば、みんなはしばらく株価が今後、上昇トレンドに乗るか、下降トレンドに乗るか注視するだろう。

 

といった具合に未来の意思決定は、過去から現在までのデータによって行われ、投資家の意思決定の集まりである未来の株価も過去から現在までの株価データの影響を受ける。

 

そうなると、ニューラルネットワークでそれぞれの時刻における株価データを独立して処理するのではなく、「昨日の株価データから得られた出力」を「今日の株価データ」と合わせて、ニューラルネットワークに入力し、明日の株価を予測した方が良さそうである。

 

k日目の出力とk+1日目の株価データを入力し、k+1日目の出力を得て、k+1日目の出力とk+1日目の株価データを入力し、k+2日目の出力を得て…を繰り返すのが再帰的ニューラルネットワーク(RNN)。過去の出力と現在の株価の両方を使って、未来を予測する。

 

 

 

再帰的ニューラルネットワーク(RNN)からLong Short-Term Memory (LSTM)へ。

そんなこんなで登場した再帰的ニューラルネットワーク(RNN)。実は、RNNには色々と問題があった。

 

他のサイトを見れば、詳しいことが書かれている。ここでは、ざっくりと。

 

 

 

損失関数の勾配消失・爆発問題。

損失関数は、ニューラルネットワークの出力と正解データがどれだけ間違っているかを表した関数。

 

「ニューラルネットワークを学習させる」とは「損失関数の値を小さくする」ということ。

 

二次関数の最小値を与えるxを求める問題は、中学校ぐらいでやったことがあると思う。平方完成すれば、最小値が求まる。もしくは二次関数を微分して、0になるときのxの値が最小値(最大値の時もある)を与えるxであった。

 

しかし損失関数の場合、平方完成のようなものでは求めることができない。この場合、平方完成ではなく、最急降下法を使う。損失関数は、ニューラルネットワークが持っている大量の重み(パラメータ)の関数。最急降下法を使ったニューラルネットワークの学習は、現在の重み(パラメータ)地点の損失関数の勾配を偏微分によって求め、その勾配に従って、重み(パラメータ)をちょっとだけ更新するという作業を繰り返すことによって行われる。ちなみに「ちょっとだけ」具合を「学習率」と呼ぶ。

 

ニューラルネットワークは、パーセプトロンの層がたくさん重なったもの。k番目の層の出力がk+1番目の層の入力になり、k+1番目の層の出力がk+2番目の出力になる。k番目の出力がk+1番目の関数の引数となり、k+1番目の出力がk+2番目の関数の引数になり…ニューラルネットワークは、各層に対応する関数が入れ子になった複雑な合成関数であると見なせる。

 

合成関数のk番目の層に対応する重み(パラメータ)に関する勾配は、偏微分の「連鎖律」を使うことによって求めることができる。連鎖律は、理系であれば、大学1年生の頃くらいに習うと思う。簡単に言うと、k番目の層に対応する重み(パラメータ)に関する勾配は、出力層からどんどんと偏微分の値を掛け合わせたものとなっている(誤差逆伝播)。

 

出力層からk番目の層の間が短ければあんまり問題がないが、時系列データを取り扱うRNNのように層の数が多くなると、損失関数の勾配消失・爆発問題が生じてくる。出力層から偏微分を掛け合わしていくとき(損失逆伝播)にどこかの層で偏微分の値が0に限りなく近くなれば、それより前の層の勾配が0に近くなり(勾配消失)、学習が進まなくなる。一方で、出力層から偏微分を掛け合わしていくとき(誤差逆伝播)にそれぞれの層で偏微分の値が大きくなれば、それより前の層の勾配が大きくなり(勾配爆発)、学習が進まなくなる(重み(パラメータ)の更新量が大きすぎると、最小値の谷を下ったり、登ったりして、いつまで経っても最小値に落ち着かない)。

 

 

 

入力データ長が長くなるほど(RNNの層が深くなるほど)、最初のデータを忘れてしまう。

株価が上昇トレンドに乗っていることをニューラルネットワークが検知するためには、少なくともトレンドの始まりから終わりまでのデータを覚えておかなければならない。1日目に株価が上昇して、2日目に株価が上昇して、…n日目に株価が上昇していることを覚えているからこそ、上昇トレンドを検知することができる。

 

RNNは、1日目の株価データから得られた出力と2日目の株価データから2日目の出力を得て、2日目の出力と3日目の株価データから3日目の出力を得て…を繰り返している。この繰り返しの数が多くなればなるほど、1日目の株価データがn日目の出力に及ぼす影響がどんどん小さくなってしまう。

 

これは、時系列データの他に自然言語処理なんかでも問題になっている。文章が長くなればなるほど、最初の方の単語が最終的な出力に及ぼす影響が小さくなる。”He is … .”から始まる長い文章を入力すると、ネットワークの層が深くなり、性別という重要な情報を持つHeのことが忘れ去られてしまうのである。自然言語処理の場合は、LSTM以外の他に、注意機構(Attention)でもこの問題を解決している。注意機構(Attention)は、最後の層からの出力だけではなく、全ての層の出力データも加味して、最終的な出力を出すというもの。

 

 

 

入力重み衝突・出力重み衝突。

株価が小刻みに上昇・下落トレンドを繰り返していたとする。このデータをRNNに加えると、上昇トレンドの特徴を捉えようと重み(パラメータ)が更新され、その後、下落トレンドの特徴を捉えようと重み(パラメータ)が更新され、その後、上昇トレンドを捉えようと重み(パラメータ)が更新され…が繰り返される。

 

折角、上昇トレンドにちょっとだけ順応していた重み(パラメータ)が下落トレンドにとっとだけ順応するように更新され、更新された重み(パラメータ)がまた上昇トレンドにちょっとだけ順応するように更新される…これが繰り返されて、結局どちらにもしっかりと順応せず、学習が進まなくなるのが重み衝突。

 

 

こういった問題を解決するのが…

Long Short-Term Memory (LSTM) !!

 

LSTMの構造は少し複雑。情報の流れが一通りではなく、途中で分岐したりしており、複数の経路から偏微分の値が逆伝播してくるため、勾配が消失しにくい。

 

LSTMには、RNNのように一つ前の情報を加味する機構に加えて、長期的に情報を保持していく機構(cell 状態)が備わっている。

 

LSTMには、sigmoid関数でできた「入力ゲート」、「出力ゲート」が備わっており、上昇トレンドを学習するときには、上昇トレンドに順応するための重み(パラメータ)に影響を及ぼす経路のみに情報を伝播させ、下降トレンドに順応するための重み(パラメータ)に影響を及ぼす経路への情報の伝播を遮断する。

(ちなみにLSTMには、「忘却ゲート」という長期的な情報をリセットするゲートも存在する)

 

 

 

ソースコード(プログラムコード)。

主に以下の記事とPyTorchの公式リファレンスを参考にして実装してみた。

dajiro.com

pytorch.org

 

上の記事のソースコードと僕のソースコードの主な違いは、「正規化の方法」と「ハイパーパラメータ(入力データ長、隠れ層のパーセプトロン数とか)」。

 

あと、上の記事では、隠れ層の0番目のデータを全結合層に入力していたが、僕は、n-1番目のデータを全結合層に入力した。(多分、隠れ層の0番目のデータって、LSTMの最終出力ではなく、最初のcellの出力なのでは?)

 

ハイパーパラメータ(入力データ長、隠れ層のパーセプトロン数とか)」は、先ほど言った通り、主に論文を参考にして決めた。

blog.sun-ek2.com

 

ニューラルネットワークの構造は、普通のLSTM(stacked、bidirectionalとかではない)に全結合層をくっつけた単純なもの。さっさと株価予測をやってみたかったので、論文に出てくるニューラルネットワークように複雑ではない。

 

997営業日分の日経平均株価のデータ(始値、高値、低値、終値、出来高)を使った。古い順から数えて、700営業日分のデータを訓練データに、残りの297営業日分のデータを検証・テストデータにした。検証データを元にニューラルネットワークの重み(パラメータ)は更新していないので、そのままテストデータに流用した。

 

入力は、k日目~k+w日目の始値、高値、低値、終値、出来高で、出力は、k+w日からn日後の終値の予測値。

 

以下、ソースコード。パソコンで見た方が見やすいかも。

import glob
import pandas as pd
import re
from sklearn.metrics import mean_absolute_error
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as tick
from matplotlib import rcParams

rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro', 'Yu Gothic', 'Meirio', 'Takao', 'IPAexGothic', 'IPAPGothic', 'VL PGothic', 'Noto Sans CJK JP']
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")


class Data_holder():
    def __init__(self, stock_prices_raw, train_val_divided, val_flag):
        self.stock_close_price_index = stock_prices_raw.loc[:,'open':'volume'].columns.get_loc('close')
        self.stock_prices = stock_prices_raw.loc[:,'open':'volume'].values
        self.train_val_divided = train_val_divided
        self.val_flag = val_flag
        if self.val_flag != True:
            self.stock_prices = self.stock_prices[:int(self.stock_prices.shape[0]*self.train_val_divided)]
        if self.val_flag == True:
            self.stock_prices = self.stock_prices[int(self.stock_prices.shape[0]*self.train_val_divided):]


    @staticmethod
    def prepare_data(file_source):
        """株価データのcsvファイルをpandas.core.frame.DataFrame形式でloadする関数。

        Args:
            str: csvファイルのuri。

        Return:
            pandas.core.frame.DataFrame: 株価データ。「列名:日付,始値,高値,低値,終値,出来高,取引総額」、「行名:日付」の表。

        """
        stock_data = pd.read_csv(file_source, engine = "python")
        stock_data = stock_data.set_index('date')
        temp = file_source.replace(storage_uri, '')
        temp = re.sub(r'YYYYY', '', temp)
        latest_data = re.sub(r'.csv', '', temp)
        latest_data = [float(x.strip()) for x in latest_data.split(',')]
        temp = pd.DataFrame([latest_data[1:]], columns=stock_data.columns, index=[int(latest_data[0])], dtype=float)
        stock_data = stock_data.append(temp,ignore_index=False)
        return stock_data


    def fragment_normalize_data(self, window_size, predicted_day_num):
        fragment_num = self.stock_prices.shape[0] - window_size - predicted_day_num
        stock_price_flagments = np.zeros((fragment_num, window_size+predicted_day_num, feature_num))
        XXXXX = YYYYY #test時に出力を株価に変換するために必要。

        #正規化。
        """
        詳細は割愛。
        """

        stock_price_labels = torch.tensor(stock_price_flagments[:, window_size+predicted_day_num-1, self.stock_close_price_index], dtype=torch.float, device=device).view(-1, 1)
        stock_price_flagments = torch.tensor(stock_price_flagments[:, :window_size, :], dtype=torch.float, device=device)

        if self.val_flag != True:
            return stock_price_labels, stock_price_flagments
        if self.val_flag == True:
            return stock_price_labels, stock_price_flagments, XXXXX


class StockDataset(torch.utils.data.Dataset):
    def __init__(self, x, y, phase):
        self.sequence = x
        self.label = y
        self.phase = phase


    def __len__(self):
        return len(self.label)


    def __getitem__(self, idx):
        out_data = self.sequence[idx]
        out_label =  self.label[idx]
        return out_data, out_label


class LSTMC_full_connected_layer(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers):
            super().__init__()
            self.input_size = input_size
            self.hidden_size = hidden_size
            self.num_layers = num_layers
            self.batch_size = batch_size
            self.lstm = nn.LSTM(input_size=self.input_size,
                                hidden_size=self.hidden_size,
                                num_layers=self.num_layers,
                                batch_first=True
                                )
            self.dense = nn.Linear(hidden_size, 1)


    def forward(self, x_data, batch_size):
        h0 = torch.zeros(self.num_layers, batch_size, hidden_layer_size)
        c0 = torch.zeros(self.num_layers, batch_size, hidden_layer_size)

        #隠れ層、cellの内部状態は使用しない。
        lstm_out, (hn, cn) = self.lstm(x_data, (h0, c0))
        linear_out = self.dense(lstm_out[:,-1,:].view(x_data.size(0), -1))
        return torch.sigmoid(linear_out)


def loss_func(pred_value, true_value):
    small_value = 1e-4
    pred_value_flattened = pred_value.view(-1)
    true_value_flattened = true_value.view(-1)
    squared_diff_sum = torch.sum((pred_value_flattened - true_value_flattened)**2)
    loss = (squared_diff_sum + small_value)/(pred_value_flattened.size(0) + small_value)
    return loss


def train_model(dataloaders_dict):
    model = LSTMC_full_connected_layer(feature_num, hidden_layer_size, num_layers)
    model.train()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
    loss_train = np.zeros((epochs, 2))
    loss_val = np.zeros((int(epochs/5), 2))

    for i in range(epochs):
        total_loss = 0.0
        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()
                #optimizer.zero_grad() (注1-1)2020/12/16 コメントアウト。詳しくは下記(注1)を参照のこと。
                print('(train)')
            else:
                if((i+1) % 5 == 0):
                    model.eval()
                    print('-------------')
                    print('(val)')
                else:
                    continue

            count = 0
            for x, y in dataloaders_dict[phase]:
                count += 1
                with torch.set_grad_enabled(phase == 'train'):
                    y_pred = model(x, batch_size)
                    loss = loss_func(y_pred, y)
                    total_loss += loss.item()
                    if (phase == 'train'):
                        optimizer.zero_grad() #(注1-2)2020/12/16 追加。詳しくは下記(注1)を参照のこと。
                        loss.backward()
                        optimizer.step()

            total_loss /= count
            if phase == 'train':
                loss_train[i, 0] = i+1
                loss_train[i, 1] = total_loss
            if phase == 'val' and (i+1) % 5 == 0:
                loss_val[int((i+1)/5-1), 0] = i+1
                loss_val[int((i+1)/5)-1, 1] = total_loss
            print(f'epoch: {i+1:3} loss: {total_loss:10.8f}')
    return model, loss_train, loss_val


storage_uri = YYYYY
gl = glob.glob(YYYYY)

window_size = Y
feature_num = 5
train_val_divided = 0.7

batch_size = Y
hidden_layer_size = Y
num_layers = 1
epochs = 50


def train_and_test_NN(stock_prices_raw1, stock_prices_raw2, predicted_day_num):
    data_holder_t = Data_holder(stock_prices_raw1, train_val_divided, val_flag=False)
    data_holder_v = Data_holder(stock_prices_raw2, train_val_divided, val_flag=True)

    stock_price_labels_t, stock_price_flagments_t = data_holder_t.fragment_normalize_data(window_size, predicted_day_num)
    stock_price_labels_v, stock_price_flagments_v, XXXXX = data_holder_v.fragment_normalize_data(window_size, predicted_day_num)

    train_dataset = StockDataset(stock_price_flagments_t, stock_price_labels_t, 'train')
    val_dataset = StockDataset(stock_price_flagments_v, stock_price_labels_v, 'val')

    train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, drop_last=True)
    val_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=False, drop_last=True)
    dataloaders_dict = {'train': train_dataloader, 'val': val_dataloader}

    model, loss_train, loss_val = train_model(dataloaders_dict)
    test_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=1, shuffle=False, drop_last=True)
    prices_pred = np.zeros((len(test_dataloader)))
    prices_true = np.zeros((len(test_dataloader)))

    count = 0
    for x, y in test_dataloader:
        y_pred = model(x, batch_size=1).item()*(YYYYY)
        y = y.item()*(YYYYY)
        prices_pred[count] = y_pred
        prices_true[count] = y
        count += 1
    return prices_pred, prices_true, loss_train, loss_val


stock_prices_raw = Data_holder.prepare_data(gl[0])
drawing_timing = [1, 5, 20, 60, 120]
predicted_day_range = 120
mean_absolute_errors = np.zeros((predicted_day_range))
mean_absolute_price_changing_rate = np.zeros((predicted_day_range))
fig0, ax0 = plt.subplots()

for i in range(predicted_day_range):
    predicted_day_num = i+1
    prices_pred, prices_true, loss_train, loss_val = train_and_test_NN(stock_prices_raw, stock_prices_raw, predicted_day_num)

    mean_absolute_errors[i] = mean_absolute_error(prices_pred/prices_true, prices_true/prices_true)*100

    tmp = 0
    for j in range(len(prices_true)-predicted_day_num):
        tmp += abs((prices_true[j+predicted_day_num] - prices_true[j]) / prices_true[j])
    mean_absolute_price_changing_rate[i] = tmp / (len(prices_true)-predicted_day_num)*100

    if predicted_day_num in drawing_timing:
        x = np.linspace(predicted_day_num-1, len(prices_true)+predicted_day_num-1, len(prices_pred))
        if predicted_day_num == 1:
            ax0.plot(x, prices_true, label='真値', color='black')
        ax0.plot(x, prices_pred, label=str(predicted_day_num)+'営業日後予測')
        ax0.set_xlabel('営業日')
        ax0.set_ylabel('株価(円)')
        ax0.legend()
        ax0.xaxis.set_minor_locator(tick.MultipleLocator(1))
        ax0.grid(which='minor', linewidth=0.5)
        fig0.savefig('日経平均株価予測.png', format="png", dpi=500)


        fig1, ax1 = plt.subplots()
        ax1.plot(loss_train[:, 0], loss_train[:, 1], label='損失関数(訓練)')
        ax1.plot(loss_val[:, 0], loss_val[:, 1], label='損失関数(検証)')
        ax1.set_xlabel('エポック数')
        ax1.set_title('損失関数('+str(predicted_day_num)+'営業日後予測)')
        ax1.set_ylim(0.0, 0.2)
        ax1.legend()
        plt.savefig('損失関数'+str(predicted_day_num)+'.png', format="png", dpi=500)


fig = plt.figure()
x = np.linspace(0, predicted_day_range-1, predicted_day_range)
plt.plot(x, mean_absolute_price_changing_rate, label='株価平均絶対変化率', color='black')
plt.plot(x, mean_absolute_errors, label='予測値平均絶対誤差', color='red')
plt.xlabel('n営業日後予測')
plt.ylabel('平均絶対誤差・株価平均絶対変化率(%)')
plt.legend()
plt.gca().xaxis.set_minor_locator(tick.MultipleLocator(1))
plt.grid(which='minor', linewidth=0.5)
plt.savefig('平均絶対誤差・株価平均絶対変化率(%).png', format="png", dpi=500)

 

(注1)(注1-1)のoptimizer.zero_grad()をコメントアウトし、(注1-2)にoptimizer.zero_grad()を追加した(2020年12月16日)。(注1-1)の位置にoptimizer.zero_grad()があると、epochが終わるまで損失関数の勾配が初期化されない。学習はミニバッチ勾配降下法で行われるので、bachごとに勾配は初期化されなければならない。そのため、optimizer.zero_grad()の正しい位置は、(注1-1)ではなく、(注1-2)。この文章内にあるグラフは、optimizer.zero_grad()が(注1-1)の位置にあるプログラムから得られたもの。

 

 

 

結果。

入力データの1営業日後、5営業日後(約1週間後)、20営業日後(約1か月後)、60営業日後(約3か月後)、120営業日後(約6か月後)の日経平均株価の終値を予測し、結果をグラフにプロットした。(n営業日後の場合、k日~k+w日の入力データからk+w+n日の株価の終値を予測し、k+1日~k+w+1日の入力データからk+w+n+1日の株価の終値を予測し…といった感じ)

 

 

 

損失関数の値の変化。

f:id:sun_ek2:20200807134411p:plain

損失関数の値の変化。

 

載せなくてもいいと思ったが一応。

120営業日後の損失関数の値は、他のどの値よりも大きくなるべきな気がしているが、なぜか一番小さい。120営業日の場合、他の条件よりも訓練データ数が少ないから?(w+120日分の株価データがあって、w日間の株価データを入力する場合、1営業日後の株価を予測するのであれば、120個のデータを用意することができるが、120営業日後の株価を予測するのであれば、1個のデータしか用意することができない)

 

 

 

日経平均株価予測の結果。

f:id:sun_ek2:20200807135045p:plain

LSTMを使った日経平均株価予測2。

 

5営業日後ぐらいまでは、ある程度、細かい株価の変動を予測できている。20営業日後くらいになってくると誤差が目立ってくるが、おおよその株価変動のトレンドは捉えられている。60営業日後以降になると、特に2020年2・3月の新型コロナウイルスによる株価大暴落後の株価予測の精度が著しく落ちる。

 

ビックリしたのが120営業日前の株価データから120営業日後に起こる株価の大暴落を予言していたということ。2020年2・3月の株価大暴落の120営業日前は、2019年の9・10月くらい。2019年の9・10月くらいに株価が大暴落するという予測がされていたということはひょっとするのこの大暴落の真の原因は、新型コロナウイルスではないのかもしれない。

 

 

 

予測値平均絶対誤差と株価平均絶対変化率。

f:id:sun_ek2:20200807134958p:plain

予測値平均絶対誤差と株価平均絶対変化率。

 

n営業日後の株価の予測値の誤差の絶対値の平均をとったものと、k営業日目の株価(真値)とk+n営業日目の株価(真値)の変化率の絶対値の平均をとったものをグラフにプロットしてみた。

 

予測値平均絶対誤差は、株価平均絶対変化率よりも小さいということが分かる。つまり、LSTMによる株価予測の誤差は、日々の株価変動の変化率よりも小さい。例えば、入力データから10営業日後の株価を予測すると誤差が平均2%ぐらい生じる。一方で、株価は10営業日で平均4%ぐらい変動する。株価が10営業日後に4%上昇したとすると、LSTMは、株価が10営業日後に2%~6%上昇すると予測する。LSTMによる株価が上昇するという予測のもと、株を買うと、予測の誤差が株価の変化率よりも小さいので、利益が出る。

 

しかし、120営業日後の予測になると、株価の平均変絶対化率よりも予測の平均絶対茣蓙の方が大きくなるので、利益を出すのは難しい。

(株価平均絶対変化率が60日後に段々、小さくなっている。例えば、株価が3か月の間、上昇して、その後、3か月の間、下落すると現在の株価と6か月後の株価の変化率は小さくなる)

 

 

 

最後に。

ディープラーニング(ニューラルネットワーク)を株式自動売買に使ってみたいなと思いながらも、長い間、手を出すことができなかった。何だか、難しそうっていう印象を抱いていたからだ。

 

ディープラーニング(ニューラルネットワーク)の論文をいくつか読んで、実際にプログラムを書いてみたら、案外、簡単に株価予測ができるようになった。

 

今後、株式売買にディープラーニング(ニューラルネットワーク)をはじめとするAIがどんどん参入していくと思う。人が株式を売買することは、少なくなるのではないだろうか。

 

この文章を読んで、ディープラーニング(ニューラルネットワーク)を使った株価予測に興味を持った人は、勉強を始めてみては?…思っているほど、難しくはないと思う。先ほど紹介した『株式自動売買とディープラーニング(ニューラルネットワーク)の話。』という文章に僕がどうやってディープラーニング(ニューラルネットワーク)の知識を身につけたか書いたので、よければどうぞ。

blog.sun-ek2.com

 

ディープラーニング(ニューラルネットワーク)の分野では「そんなことが機械にできるわけがない!」って言われてきたとこがどんどんとできるようになっている。「コンピュータで株価予測なんてできるわけがない!長年の経験と勘が大事!」って言い張って、ディープラーニング(ニューラルネットワーク)から目を背け続けると、気づいたころには周りはAI使いだらけになっているかもしれない。もう一度言うと、ディープラーニング(ニューラルネットワーク)はできないって言われていたことがどんどんとできるようになっている分野である。

 

ハイパーパラメータ(入力データ長、隠れ層のパーセプトロン数とか)の決め方は、論文を参考にしたが、ネットワーク構造は、まだ単純なままである。

 

論文では、LSTMをアンサンブル化したり、畳み込み層やAttention層をLSTMに組み込んだり、遺伝的アルゴリズムで最適なハイパーパラメータを探索したり、LSTMではなく、畳み込みニューラルネットワーク(CNN)を使って、株価を予測したりしていた。

 

今後、論文から仕入れた知識の有用性を実際の株価データで試してみたいと思う。

 

 

この文章を読んで、面白い!役に立った!...と思った分だけ、投げ銭していただけると嬉しいです。

ofuse.me

 

ブログランキング・にほんブログ村へ
にほんブログ村