こんにちは、ありすけ(@arisukeblog)です。
最近、ブームになっているディープラーニングや機械学習に興味があり、Pythonで使える機械学習ライブラリのTensorFlowを使って遊んでいます。
C言語に慣れ親しんだ私にとっては、Pythonのコーディングのやりやすさにすっかり虜になっています。
今回は、そんな機械学習プログラミングをせっかく覚えたので、TensorFlowを使ってLSTM(Long short-term memory)を組み、日経平均株価(N225)を予測するプログラミングを紹介したいと思います。
※私はディープラーニングや機械学習について独学で学んだ素人であり、プログラミングのコードは書籍とネット情報を駆使して作成したものになりますので、参考程度として捉えて下さい。
予測手法の概要
まず、日経平均株価10日分データをINPUTとし、次の日の日経平均株価をOUTPUTとするLSTMモデルを組みます。
このモデルを使って次の日の日経平均株価の予測を行い、次に、この予測した株価を含む直近10日分のデータをINPUTとしてモデルに与え、さらに次の日の日経平均株価を予測します。
こうすることで、任意の日にちの先の日経平均株価の予測ができる(のではないか?)という寸法です。
イメージ:
予測に使用する日経平均株価データはYahoo! Financeからダウンロードしたものを使います。
また、今回は、株式相場の市場性(市場参加者・規模・時代背景・金利・文化)等を考慮して、学習に使用するデータは、直近の10年間のデータとしました。
本記事の参考文献はこちら
プログラミング紹介
OS:Windows10
使用言語:Python
プログラミング環境:jupyter notebook
まず、ダウンロードしたN225のCSVデータを作業フォルダに入れて、データの読み込み作業を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # kerasとその他ライブラリをインポート from keras.models import Sequential from keras.layers.core import Dense, Activation from keras.layers.recurrent import LSTM from keras.optimizers import Adam from keras.callbacks import EarlyStopping from keras.models import Model from keras.layers import Input, LSTM # SVGの表示に必要なライブラリをインポート from IPython.display import SVG from keras.utils.vis_utils import model_to_dot import os os.environ["PATH"] += os.pathsep +'C:/Program Files/Graphviz/bin/' import pandas as pd # Pandas(DataFrameデータ構造やデータ解析ツールを提供するPythonライブラリ)のインポート from pandas import DataFrame # pandasモジュールのクラスDataFrameをインポート import numpy as np # numpyモジュールをnpで使用できるようにインポート import matplotlib.pyplot as plt #matplotlibをpltで使えるようにインポート import matplotlib.dates as mdates from pandas.plotting import register_matplotlib_converters from datetime import datetime as dt # datetime型 from sklearn.preprocessing import StandardScaler # 標準化用にscikit-learnの前処理ライブラリを読み込み import matplotlib import sys, os #os.getcwd() #カレントディレクトリの表示 df_NikkeiAll = pd.read_csv("^N225_20100711-20200711.csv") # 日経平均のデータの読み込み #データの前処理 #欠損データがあるので、欠損値NaNを除外する df_NikkeiAll_drop = df_NikkeiAll.dropna() df_NikkeiAll_drop.head() # 先頭の5行を表形式で表示 |
まずはこれでCSVがきちんと読み込めたかどうか確認します。以下の表示がでたらOKです。
今回の予測には、”Close”(終値)のデータを使います。
次に、読み込んだデータをグラフ化して、読み込んだデータが意図したものか確認します。
CSVから読み込んだだけではグラフ表示用のデータ型としては不適切なので、少々面倒ですが、グラフ表示に適したデータ型に変換してグラフ表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #datetime64[ns]型に変換した列をpandas.DataFrameに新たな列として追加 df_NikkeiAll_drop['datetime'] = pd.to_datetime(df_NikkeiAll_drop['Date'], format='%Y-%m-%d') # Closeの列のデータのみを取り出し NikkeiData = df_NikkeiAll_drop['Close'].values # datetimeの列のデータのみを取り出し NikkeiDate = df_NikkeiAll_drop['datetime'].values #リシェイプ NikkeiData = NikkeiData.reshape(-1,1) # float64 NikkeiDate = NikkeiDate.reshape(-1,1) # datetime64[ns] # 読み込んだ日経平均をプロット k = 2000 # 表示する数 i = NikkeiData.shape[0]-k j = NikkeiData.shape[0] xdata = NikkeiDate[i:j] ydata = NikkeiData[i:j] #描画するデータの読み込み fig = plt.figure(figsize=(5,5),dpi=100) ax = fig.add_subplot(2,1,1) ax.plot(xdata, ydata) # 軸目盛の設定 ax.xaxis.set_major_locator(mdates.DayLocator(bymonthday=None, interval= 100, tz=None)) ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d")) # ラベルを45deg回転 labels = ax.get_xticklabels() plt.setp(labels, rotation=70, fontsize=10) #ラベル名 plt.xlabel('Date') plt.ylabel('Nikkei225') plt.grid() plt.show() |
実行したら、こんな野暮ったいグラフが出てくればOKです。
次にデータセットを作っていきます。
データセットとは、機械学習において学習用にモデルに合わせてデータを整理したものになります。
10日分の終値データをX、その次の日の終値データをY としたセットを沢山作っていきます。
文書で説明すると分かりづらいと思いますので、以下のイメージになります。
この場合、全部で2438個のデータセットが作れます。
データセットを作った後、8割をLSTMモデルの訓練用、残りの2割をLSTMモデル検証用のデータとして使用します。
ちなみに、ここでデータの正規化を行っています。
コードはこちら。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # 特徴量の尺度を揃える、特徴データを標準化して配列に入れる scaler = StandardScaler() # 特徴データを標準化(平均0、分散1になるように変換) NikkeiData_norm = scaler.fit_transform(NikkeiData) # maxlen 日分の日経平均株価を入力として、1 日後の日経平均株価を予測 # maxlen 日分のN225(Close) と、maxlen+1 日のN225(Close)のデータセットを作成 maxlen = 10 x_data = [] y_data_price = [] #y_data_updown = [] datelabel = [] for i in range(len(NikkeiData_norm) - maxlen): # i を データ数-maxlen まで繰り返し x_data.append(NikkeiData_norm[i:i + maxlen]) # NikkeiData_norm[i]からNikkeiData_norm[i+maxlen]までのデータをx_dataに追加 y_data_price.append(NikkeiData_norm[i + maxlen]) # NikkeiData_norm[i+maxlen]のデータをy_dataに追加 datelabel.append(NikkeiDate[i + maxlen]) x_data = np.asarray(x_data).reshape((-1, maxlen, 1)) y_data_price = np.asarray(y_data_price) # 訓練データサイズ train_size = int(len(x_data) * 0.8) # 全データのうち、80% のサイズを取得 # 訓練用データ x_train = x_data[:train_size] # 全データのうち、80% を訓練用データに格納 y_train_price = y_data_price[:train_size] # 全データのうち、80% を訓練用データに格納 # 検証用データ x_test = x_data[train_size:] # 全データのうち、20% を検証用データに格納 y_test_price = y_data_price[train_size:] # 全データのうち、20% を検証用データに格納 |
ちなみに、今回はモデルへのINPUTとして10日分のデータとしていますが、プログラム上は変数(maxlen)を使って指定しているのでで他の日数にすることもできます。
何日分のデータをINPUTとするのかは、設計者のセンスによるものかもしれませんね。
次に、LSTMモデルを構築します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # LSTM を Keras を使って構築 length_of_sequence = maxlen in_out_neurons = 1 # 入力と出力層の数 hidden_neurons = 300 # 隠れ層の数(えいや) # (*,10,1)のTensorを持った入力を、300個のLSTM中間層に投げ、それを1個の出力層に集約し、linear活性化関数を掛け合わせ model = Sequential() model.add(LSTM(hidden_neurons, batch_input_shape=(None, length_of_sequence, in_out_neurons), return_sequences=False)) model.add(Dense(in_out_neurons)) model.add(Activation("linear")) optimizer = Adam(lr=0.001) model.compile(loss="mean_squared_error", optimizer=optimizer) # hidden_neurons: 隠れ層・・・数が多い程,学習モデルの複雑さが増加 # batch_input_shape: LSTMに入力するデータの形を指定([バッチサイズ,step数,特徴の次元数]) # Dense: ニューロンの数を調節する、今回は、N225のy軸の値が出力なので、ノード数1にする # linear: 線形の活性化関数を用いる # compile: 誤差関数:最小2乗誤差、最適化手法:Adamを用いるように定義 |
ここも設計者によってどのようなモデルを組むのか自由ですが、とりあえずえいやで設定しています。
SVG形式ですと以下のモデルになります。
次に、このLSTMモデルに対し、学習を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #fit(): 訓練データと、教師データ、バッチサイズ、エポックサイズ、バリデーションデータとして訓練データの何%を使用するか指定 #収束判定コールバックを指定することで、収束したら自動的にループを止めることが出来る early_stopping = EarlyStopping(monitor='val_loss', mode='auto', patience=2) hist = model.fit(x_train, y_train_price, batch_size=256, epochs=5, validation_split=0.1, callbacks=[early_stopping] ) #今回は,学習データの10%をvalidationに用いて,5 epochで学習 #1行目のearly_stoppingをcallbacksで定義することで、validationの誤差値(val_loss)の変化が収束したと判定された場合に自動で学習を終了する #modeをautoにすることで収束の判定を自動で行う #patienceは判定値からpatienceの値の分だけのepochは学習して、変化がなければ終了するように判定する #patience=0だとval_lossが上昇した瞬間学習が終了することになる |
batch_sizeというのは、訓練データをいくつかに分けて学習するミニバッチという手法を利用する際に、1個のデータのサイズ(データ数)です。ここでは256個ずつに分けます。
epochというのは、繰り返しの回数を表します。ここでは5回とします。
実行すると、こんな感じで学習が進みます。
次に、学習の過程をグラフ化します。
1 2 3 4 5 6 7 8 9 10 11 12 | # loss(訓練データに対する判定結果)、val_loss(テストデータに対する判定結果)をプロットする loss = hist.history['loss'] val_loss = hist.history['val_loss'] epochs = len(loss) plt.plot(range(epochs), loss, marker='.', label='loss(training data)') plt.plot(range(epochs), val_loss, marker='.', label='val_loss(evaluation data)') plt.legend(loc='best') plt.grid() plt.xlabel('epoch') plt.ylabel('loss') plt.show() |
以下のようにグラフが出てきます。
epochが進むごとにloss(損失)が低くなっているので、学習できていることが分かります。
また、evaluation dataも同様に低くなっているので、過学習なく学習できていると思います。
次に、学習したモデルを使って、テストデータを使って予測を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #グラフ用に日にちデータを作成 datelabel = np.asarray(datelabel) #リストをNumpy配列に変換 datelabel = datelabel[train_size:] #学習データサイズ(全データの80%)分の日にちデータを抽出 datelabel.shape #学習データを用いた予測 predicted = model.predict(x_test) # x_testデータを用いて、10日間のデータを基に次の日の予測を行った結果を格納 # 標準化を戻す predicted_N = scaler.inverse_transform(predicted) y_test_price_N = scaler.inverse_transform(y_test_price) plt.plot(datelabel, predicted_N, marker='.', label='predicted') plt.plot(datelabel, y_test_price_N, marker='.', label='y_test_price') #plt.plot(range(len(predicted)), predicted, marker='.', label='predicted') #plt.plot(range(len(y_test_price)), y_test_price, marker='.', label='y_test_price') plt.legend(loc='best') plt.grid() plt.xlabel('date') plt.ylabel('N225') plt.show() |
予測した結果が出てきます。
10日分のデータを使って予測した結果を集めてプロットしたものが青色のグラフです。
精度よく予測できていそうですが、株価が大きく動いている日は予測結果との差がありそうです。
最後に、冒頭で説明したように任意の日にち目の日経平均株価を予測するプログラムを書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #未来予測 a = y_test_price.shape[0] - 10 # 488 - 10 = 478 tempdata = y_test_price[a:] # x_tempdata = np.asarray(tempdata).reshape((-1, maxlen, 1)) # モデルに入力できるように、(1, maxlen ,1) 形式に変換 predicted_temp = model.predict(x_tempdata) # x_tempdataデータを用いて、10日間のデータを基に次の日の予測を行った結果を格納 a = np.append(tempdata, predicted_temp) # 標準化した10日分データと、予測結果をリストに追加 for i in range(10): b = a[i+1:] # 直近10日間のデータを抽出 x_tempdata = np.asarray(b).reshape((-1, maxlen, 1)) # モデルに入力できるように、(1, maxlen ,1) 形式に変換 predicted_temp = model.predict(x_tempdata) # x_testデータを用いて、10日間のデータを基に次の日の予測を行った結果を格納 a = np.append(a,predicted_temp) # 標準化を戻す predicted_futureN = scaler.inverse_transform(a) #未来予測結果をプロット plt.plot(range(len(predicted_futureN)), predicted_futureN, marker='.', label='future predicted') plt.plot(range(len(predicted_futureN[:10])), predicted_futureN[:10], marker='.', label='real data',color="0.5") #plt.plot(range(len(y_test_price)), y_test_price, marker='.', label='y_test_price') plt.legend(loc='best') plt.grid() plt.xlabel('date') plt.ylabel('N225') plt.show() |
この結果、以下のグラフが出来ました。
10日間のリアルデータを使って、11日後までの予測をした結果ですが、
11、12日目はそれっぽい予測をしていますが、それ以降は右肩下がりの結果となっています。
この理由は詳しくは分かりませんが、直近の値動きが予測結果に大きく影響を与えてそうです。
まとめ
LSTMを使って日経平均株価の次の日の予測をした結果、それっぽい予測値を得ることができました。
ただ、そこから先の予測はもうちょっと工夫が必要みたいです。
今後は、この予測を基に売買を行ったら儲かるのか?を次のテーマとしてやってみたいと思います。
以上、お読みいただきありがとうございました!