ディープラーニングを使って株価予想してみた!
IT技術
ディープラーニングで株価予想はできるのか?
「株で大儲けしたい!」
誰しも一度は考えたことがあるかもしれません。
そこで、ディープラーニングを使って株価を予想できないか検証してみようと思います!
目指せ!不労所得!
それでは早速行ってみましょう!
ディープランニングで株価予想する上での注意事項
まず、株価分析の手法は、以下の2つに大別できます。
【ファンダメンタル分析】
ファンダメンタル分析は、企業の業績や、市場の方向性を分析して、株価の予想を行う手法です。
【テクニカル分析】
テクニカル分析は、過去の株価を用いて、株価の予想を行う手法です。
今回使うのはテクニカル分析
今回使うディープラーニングは、説明変数として過去の株価を用いるので、「テクニカル分析」を行うことになります。
ですが、過去の様々な論文で、テクニカル分析の効果がないことが実証済みです。
ですので、過度な期待はせず温かい目でご覧ください。
セットアップ
今回は、PC に Python がインストールされていることを前提で進めたいと思います。
それでは最初に、ディープラーニングを行う上では欠かせない「TensorFlow」と、「Keras」をインストールしましょう。
TensorFlowとKerasのインストール
これらは、pip でインストールできます。
コマンドラインに、下記の内容を入力してください。
$pip install tensorflow
$pip install keras
モジュールをインストール
インストール出来たら、次のようにモジュールをインストールしてください。
1import pandas as pd
2import numpy as np
3from sklearn.model_selection import train_test_split
4from keras.callbacks import EarlyStopping
5import matplotlib.pyplot as plt
6from keras.layers import Dense, LSTM, Dropout, Flatten
7from keras.models import Model
TensorFlow 、Keras の他にも「pandas」や「numpy」などを使います。
インストールされていない場合は、その都度 pip でインストールしてください。
使用するデータ
今回使用するデータはコチラ
- データ期間:2000/10~2019/10
- 週次の Topix データ
プロットしてみる
pd.read_csv で保存してあるデータを読み込んで、プロットしてみます。
1data = pd.read_csv(r'******.csv',
2 encoding='shift_jis',
3 index_col='Date',
4 parse_dates=True,
5 dtype='float64').dropna(axis = 1).dropna()
6data.plot()
時系列データとして読み込む
1data.head()
元のデータは1列目に「日付(Date)」、2列目に「topix(Topix)」が格納されていました。
ですので、data を時系列データとして読み込むために、pd.read_csv の引数として、index_col=’Date 、parse_dates=True を指定しました。
encoding 引数は忘れがちですが、自分が保存した文字コードを指定しないとエラーになってしまうので注意してください。
欠損値がある場合には、dropna() メソッドで取り除いておくことをおススメします。
(欠損値が残っていると、ディープラーニングの入力用テンソルを作成する際に、少々めんどくさくなってしまいます)
ディープラーニングモデルの作成
では、Kerasでディープラーニングのモデルを作成してみましょう。
今回作るモデルは、あくまで一例ですので、皆さんも是非自分のモデルを作ってみてください。
Keras は、下記の2通りでディープラーニングモデルを構築できます。
- sequential model
- functional API model
今回のような単一入力、単一出力の場合は「sequential model」で十分です。
sequential model は、モデルに層を追加していく要領で、誰でも簡単にモデルを作成することができます。
もう1つの「functional API model」は、多入力・多出力の場合や、より複雑なモデルになる場合に使用しましょう。
予想を行う2つのモデルを用意
今回は、直近20週間の Topix を用いて、次の週の Topix を予想するモデルを2つ用意しました。
- model_1: 全結合層(Dense)のみ用いたモデル
- model_2: LSTM層、全結合層を組み合わせたモデル
全結合層は、最も一般的なニューラルネットワークの層で、入力と層のパラメータとの内積を、出力次元の分計算します。
LSTM層は、時系列データ処理に特化した層で、過去データの情報を保ちつつ出力できます。
モデル作成:コード
それでは、実際にモデルを作成してみましょう。
まずはコードを見てみます。
model_1
1model_1 = models.Sequential()
2model_1.add(Dense(5, activation='relu', input_shape=(20,)))
3model_1.add(Dropout(0.5))
4model_1.add(Dense(1, activation='linear'))
5model_1.summary()
6model_1.compile(optimizer='adam',
7 loss='mse',
8 metrics=['mae'])
model_2
1model_2 = models.Sequential()
2model_2.add(LSTM(10,
3 dropout=0.2,
4 recurrent_dropout=0.2,
5 input_shape=(20,1)))
6model_2.add(Dense(5, activation='relu'))
7model_2.add(Dropout(0.5))
8model_2.add(Dense(1, activation='linear'))
9model_2.summary()
10model_2.compile(optimizer='adam',
11 loss='mse',
12 metrics=['mae'])
sequential modelを用いることで、こんな簡単にモデルを作成することができました!
モデルの作成:解説
基本的な流れを説明します。
models.Sequential()
まず、models.Sequential() でモデルのインスタンスを作成し、addメソッドで層を追加していきます。
引数
各層の一番最初の引数には出力次元を入力し、それ以降は各層固有の引数が続きます。
(様々な引数が用意されているので、是非 Keras公式ドキュメントなどで調べてみてください)
input_shape引数
最初の層には、input_shape引数に入力次元のタプルを指定してください。
activation引数
Dense層には、activation引数がありますが、一般的に中間層ならば ’relu’ を指定すればよいでしょう。
こうすることで、非線形変換が可能になります。
ただし、今回は回帰問題なので、最後の Dense層の activation は、’linear’ と指定しておきましょう。
Dropout層
model_1、model_2 共に、途中に Dropout層を挟んでいます。
Dropout層は過学習を抑制する効果があり、ディープラーニングモデルを作成する際には入れておくと良いと思います。
compileメソッド
最後に、compileメソッドを用いる際に、問題に対応した誤差関数(loss)を指定するように注意しましょう。今回で言えば回帰問題を解くので loss=’mae’ を指定しました。
入力用、出力用テンソルの作成
ここで、入力用、出力用のテンソルを作成します。
作成の際には、「入力次元」「出力次元」が、modelと整合的になるように注意してください。
今回は、model_1 に合わせて、出力次元を (sample,timesteps) とします。
入力用、出力用テンソルを出力する関数
pd.dataframe を入力し、入力用、出力用テンソルを出力する関数は以下のようになります。
1def getInputLabel(data, period=20):
2 period = period
3 input_tensor = []
4 label_tensor = []
5 for i in range(0, len(data) - period, 1):
6 input_tensor.append(data.values[i:i + period,0])
7 label_tensor.append(data.values[i + period,0])
8 input_tensor = np.array(input_tensor)
9 label_tensor = np.array(label_tensor)
10 return input_tensor, label_tensor
ここで、Topixデータを当てはめてしまいたいところですが、Topix の値は、先ほどプロットしたように変動幅が大きいため、標準化するよう心がけましょう。
(そのままの値を用いると大きな値の影響度が大きくなってしまいます)
Topixデータを標準化
よって、以下のように Topixデータを標準化してから当てはめていきます。
1tmp = data - data.mean()
2tmp = tmp/data.std()
3input_tensor, label_tensor = getInputLabel(data = tmp)
トレーニングデータとテストデータに分割
さて、機械学習の分野では、「トレーニングデータでパラメータの学習」を行い、「テストデータを用いて そのパラメータの評価」を行います。
幸いにも、sklearn.model_selection に、そのような関数が用意されているので使わせて頂きましょう。
1X_train, X_test, y_train, y_test = train_test_split(input_tensor, label_tensor, test_size=0.2,
2 random_state=100, shuffle = False)
とても簡単ですね。
test_size=0.2 と指定することでデータを、8:2でトレーニングデータとテストデータに分割しました。
ディープラーニング実践
それでは、まずは下準備していきます。
モデルの過学習を防ぐために、以下のようにコールバックを設定しましょう。
1earlystopping = EarlyStopping(monitor='loss', patience=5)
EarlyStopping の引数を簡単に説明すると
- monitor: どの値で過学習を判断するのか
- patience: どのくらのエポック数改善しなければ学習を終了するか
株価は、1、2エポック学習が進まなくても、その後急に改善することがあるので、patience=5 に設定しました。
モデルのパラメータを学習させてみる
では、いよいよモデルのパラメータを学習させましょう!
とは言っても難しい作業はなく、以下のように model の fitメソッドを呼び出せばいいだけです。
ただし、model_2 を学習させる際は、次元を一つ増やすために、入力用テンソルを X_train[:,:,np.newaxis] としていることに注意してください。
model_1
1model_1.fit(X_train, y_train, batch_size=10, epochs=50, callbacks=[earlystopping])
model_2
1model_2.fit(X_train[:,:,np.newaxis], y_train, batch_size=10, epochs=50, callbacks=[earlystopping])
学習終了
EarlyStopping が作動し
- model_1は17エポック
- model_2は14エポック
で学習が終了しました。
今回は「バッチ数10」「エポック数50」で行いました。
ですが、適当なので、自分で行う際は変えていただいて構いません!
学習の過程を見てみると、どちらのモデルも「loss」が減っていて学習が上手く行われていることが分かります。
結果
テストデータで結果を図示してみましょう。
上手く予想できているのでしょうか…
model_1の予測
1model_1
2
3predicted = model_1.predict(X_test)
4result = pd.DataFrame(predicted)
5result.columns = ['predict']
6result['actual'] = y_test
7result.plot()
8plt.show()
model_2の予測
1model_2
2predicted = model_2.predict(X_test[:,:,np.newaxis])
3result = pd.DataFrame(predicted)
4result.columns = ['predict']
5result['actual'] = y_test
6result.plot()
7plt.show()
見た感じ、model_1の方が予測の精度がよさそうです。
LSTM を使っている model_2 はどうしたのでしょう…?
株価との相性が悪いのでしょうか?
価格の上げ下げの正解率で評価してみる
別の観点から評価してみましょう。
株価を予想するモチベーションは、なんと言っても「お金を稼ぐ」こと。
価格の上げ下げの正解率は、どのくらいか調べてみましょう。
正解率を調べる関数を、以下のように定義しました。
1def updown(actual, predict):
2 act = np.sign(np.diff(actual))
3 pre = np.sign(np.diff(predict[:,0]))
4 tmp = act*pre>0
5 return np.sum(tmp)/len(tmp)
正解率を調べた結果は
では、「model_1」「model_2」のどちらが稼ぐことのできるモデルか調べてみます。
1print('model_1:',updown(y_test, model_1.predict(X_test)))
2print('model_2:',updown(y_test, model_2.predict(X_test[:,:,np.newaxis])))
なんと、これでも model_1 に敗北してしまいました!
しかし、正解率が「0.5」を超えないようでは、実際に使用することは出来ません…。
改善案その1
どうにかして稼げるモデルを作れないでしょうか?
まずは「改善案その1」として、Topix と他の株価を合わせて入力してみます。
使用したデータは以下の通りです。
期間は先ほどと同じで、Topix の他に 267社の株価を用います!
モデルの構築
では、モデルを構築します。
1model_2 = models.Sequential()
2model_2.add(LSTM(100,
3 dropout=0.2,
4 recurrent_dropout=0.2,
5 return_sequences=True,
6 input_shape=(20, 268)))
7model_2.add(LSTM(40,
8 dropout=0.2,
9 recurrent_dropout=0.2))
10model_2.add(Dense(10, activation='relu'))
11model_2.add(Dropout(0.5))
12model_2.add(Dense(1, activation='linear'))
13model_2.summary()
14model_2.compile(optimizer=Adam(lr=1e-3),
15 loss='mse',
16 metrics=['mae'])
LSTM層で return_sequence=True とすることで、リターンのシーケンスがそのまま出力されます
これによって、LSTM層をつなげることが出来ます。
今回のように、入力の3次元目が大きくなる場合に使用すると良いでしょう。
学習した結果
この後は、先ほどと同様の流れでモデルを学習します。
さて、結果はどうなるでしょうか?
なんだか悪化したような…。
価格の上げ下げの正解率は、0.44 となりこちらも改善は見られませんでした…。
改善案その2
稼げるモデルを目指すため、目的関数を変更してみます!
先ほどは、株価を予想する(つまり回帰)モデルを構築しました。
「改善案その2」では、株価を予想するのではなく、価格の上げ下げを予想するモデルに変更してみます。
データは、Topix のみのものを使用します。
モデルの構築
まず、モデルは以下のようになります。
1model_2 = models.Sequential()
2model_2.add(LSTM(10,
3 dropout=0.2,
4 recurrent_dropout=0.2,
5 input_shape=(20,1)))
6model_2.add(Dense(5, activation='relu'))
7model_2.add(Dropout(0.5))
8model_2.add(Dense(1, activation='sigmoid'))
9model_2.summary()
10model_2.compile(optimizer='adam',
11 loss='binary_crossentropy',
12 metrics=['acc'])
モデルの変更点は、最後の Dense層の activetion を ’sigmoid’ にしたこと。
そして、compileメソッドの引数loss を、’binaryf_crossentroppy’ としたことです。
このようにすることで、2値分類(価格が上がるか下がるか)を実行できるモデルが構築できます。
出力ラベルの変更
モデルに合わせて、出力ラベルを変更することも忘れずに行いましょう。
出力ラベルを、次の日Topixの値が上がったら「1」、次の日Topixの値が下がったら「0」とする関数は、以下のようになります。
1def getInputLabel(data, period=20):
2 period = period
3 input_tensor = []
4 label_tensor = []
5 for i in range(0, len(data) - period, 1):
6 input_tensor.append(data.values[i:i + period, 0])
7 label_tensor.append(np.diff(data.values[:,0])[i + period -1])
8 input_tensor = np.array(input_tensor)
9 label_tensor = np.sign(np.array(label_tensor))
10 label_tensor[label_tensor<0] = 0
11 return input_tensor, label_tensor
モデルを学習
先ほどと同様の手順で、モデルを学習していきます。
今回は2値分類なので、結果は図示できません。
テストデータの正解率
代わりに、テストデータの正解率を見てみましょう。
1model_2.evaluate(X_test[:,:,np.newaxis], y_test)
evaluate メソッドは、「与えられた入力データ」と「その正解データ」に対してモデルを適用し、(loss, metrics) のタプルを返す関数です。
metrics=’acc’ と指定しているため、metrics の値は正解率となります。
ですので、このモデルは 0.56 の正解率を達成することが出来たことになります!
やはり「目的に合ったモデルを構築する」ということが何よりも大切だということが分かりますね。
さいごに
ディープラーニングを用いて株価予想をしてみました。
「絶対当たる夢のようなモデル」はやはり一筋縄ではいかないようです。
ですが今回の検証で、最終的には56%の正解率にたどり着いたことを考えると、「8割方当たるモデル」というのは工夫次第で実現可能かもしれませんね…!
もっとも、そのようなモデルが発見され広まれば、全員がそのモデルを使用するので、当たらないモデルになってしまいますね(笑)
こちらの記事もオススメ!
2020.07.28機械学習 特集知識編人工知能・機械学習でよく使われるワード徹底まとめ!機械学習の元祖「パーセプトロン」とは?【人工知能】ニューラルネ...
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit