機械学習で株式予測AIの「精度を上げる」コツ ー インスタでのブラジルからのリクエストに応えます

「LSTMモデルの精度を上げてくれないか?」とDMが来たので

実際の精度を上げる方法を解答していきます。

インスタでのブラジル人の方からのリクエスト

要するに

ヤフーファイナンスでブラジル・ボルサ・バルカオという会社の株式予測をしているのだが

変化率の条件分岐を増やすごとに精度が下がってしまうのでモデルを考えてほしい

という要望です。

では、実際に精度を上げる方法を見ていきましょう。

予測精度が出ないときにやること

前にも、記事にしたことがあるのですが

精度を上げるにも基本というものがあります。

だいたい、予測精度が悪いときに試すことは以下になります。

オーバーフィッティングのとき

・トレーニングデータをもっと集める

・特徴量 n を減らす

・正則化の係数 λ を大きくする

アンダーフィッティングのとき

・特徴量 n を増やす

・高次の特徴を追加する

・正則化の係数 λ を小さくする

環境

・WSL2

・Jupyter Notebook

※送ってきた方はColab環境だったのでColabでも問題なさそうです

送られてきたソースコード

予測精度を上げてほしいということなので

実際のソースコードが送られてきました。

そのソースコードをまず解読していきましょう。

情報はオープンのもので、勉強用とのことなので開示しても問題ないと思います。

↓ここから

データの準備

パッケージをインストールします

!pip install tensorflow-gpu
!pip install yfinance --upgrade --no-cache-dir
!pip install pandas_datareader

インポートします

import pandas_datareader.data as web
from datetime import date, timedelta
import pandas as pd
from numpy import array
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
import matplotlib.pyplot as plt 
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import Dropout
import yfinance as yf
yf.pdr_override()

2000年から2022年までのブラジル・ボルサ・バルカオの株価を取得します

b3sa3 = web.get_data_yahoo("B3SA3.SA",start="2000-01-01", end="2022-12-31",interval="1d")

テストデータと境界をセッティングします

hj=date.today()
td0 = timedelta(1)
td1 = timedelta(800)
td2 = timedelta(21)
end = hj-td2
start = end-td1
yesterday=hj-td0
print(start, end, hj)
b3sa3_test = web.get_data_yahoo("B3SA3.SA",start=start, end=end,interval="1d")
b3sa3_hj = web.get_data_yahoo("B3SA3.SA",start=yesterday, end=hj,interval="1d")
b3sa3_test = b3sa3_test.tail(365)

テストデータ

b3sa3_test

分類における決定境界

b3sa3_hj

最初の5行のデータ確認

b3sa3.head()

最後の5行のデータ確認

b3sa3.tail()

nullチェック

b3sa3.isnull().sum()

データ情報確認

b3sa3.info()

2クラス分類の場合

モデル

# split a univariate sequence into samples
def split_sequence(sequence, n_steps, avanco):
 X, y = list(), list()
 for i in range(len(sequence)):
 # find the end of this pattern
  end_ix = i + n_steps
  z=end_ix-1
 # check if we are beyond the sequence
  if end_ix+avanco > len(sequence)-1:
    break
 # gather input and output parts of the pattern
  seq_x, seq_y = sequence[i:end_ix], sequence[end_ix+avanco]
  e=seq_y
  f=seq_x[-1]
  faixa = ((e/f)-1)*100
  if faixa <0:
    faixa=-1
  else:
    faixa=1
  X.append(seq_x)
  y.append(faixa)
 return array(X), array(y)
# define input sequence
raw_seq = b3sa3['Close']
# choose a number of time steps
n_steps = 365
avanco = 21
# split into samples
X, y = split_sequence(raw_seq, n_steps,avanco)
# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X2 = X.reshape((X.shape[0], X.shape[1], n_features))

2クラス分類されたバイナリ配列に変換します

num_classes = 2
y2 = keras.utils.to_categorical(y, num_classes)

フィッティングします

# define model
model2 = Sequential()
model2.add(LSTM(100, activation='tanh'))
model2.add(Dense(2,activation='sigmoid'))
model2.compile(optimizer='adam', loss="categorical_crossentropy", metrics=['accuracy'])
checkpoint_filepath = '/checkpoint'

# fit model
model2.fit(X2, y2, validation_split=0.2, epochs=10)

結果

Epoch 1/10
85/85 [==============================] - 11s 119ms/step - loss: 0.0246 - accuracy: 0.9926 - val_loss: 3.0749e-04 - val_accuracy: 1.0000
Epoch 2/10
85/85 [==============================] - 10s 118ms/step - loss: 1.7007e-04 - accuracy: 1.0000 - val_loss: 1.6888e-04 - val_accuracy: 1.0000
Epoch 3/10
85/85 [==============================] - 10s 120ms/step - loss: 9.2416e-05 - accuracy: 1.0000 - val_loss: 1.0388e-04 - val_accuracy: 1.0000
Epoch 4/10
85/85 [==============================] - 10s 121ms/step - loss: 5.6772e-05 - accuracy: 1.0000 - val_loss: 7.2730e-05 - val_accuracy: 1.0000
Epoch 5/10
85/85 [==============================] - 10s 121ms/step - loss: 3.9265e-05 - accuracy: 1.0000 - val_loss: 5.5672e-05 - val_accuracy: 1.0000
Epoch 6/10
85/85 [==============================] - 10s 122ms/step - loss: 2.9215e-05 - accuracy: 1.0000 - val_loss: 4.4618e-05 - val_accuracy: 1.0000
Epoch 7/10
85/85 [==============================] - 10s 115ms/step - loss: 2.2800e-05 - accuracy: 1.0000 - val_loss: 3.6798e-05 - val_accuracy: 1.0000
Epoch 8/10
85/85 [==============================] - 10s 116ms/step - loss: 1.8407e-05 - accuracy: 1.0000 - val_loss: 3.0964e-05 - val_accuracy: 1.0000
Epoch 9/10
85/85 [==============================] - 10s 118ms/step - loss: 1.5248e-05 - accuracy: 1.0000 - val_loss: 2.6502e-05 - val_accuracy: 1.0000
Epoch 10/10
85/85 [==============================] - 10s 118ms/step - loss: 1.2893e-05 - accuracy: 1.0000 - val_loss: 2.3007e-05 - val_accuracy: 1.0000

4クラス分類の場合

モデル

# split a univariate sequence into samples
def split_sequence(sequence, n_steps, avanco):
 X, y  =list(), list()
 for i in range(len(sequence)):
 # find the end of this pattern
  end_ix = i + n_steps
  z=end_ix-1
 # check if we are beyond the sequence
  if end_ix+avanco > len(sequence)-1:
    break
 # gather input and output parts of the pattern
  seq_x, seq_y = sequence[i:end_ix], sequence[end_ix+avanco]
  e=seq_y
  f=seq_x[-1]
  faixa = ((e/f)-1)*100
  if faixa <-5:
    faixa=-2
  elif faixa <0:
    faixa=-1
  elif faixa <5:
    faixa=1 
  elif faixa >5:
    faixa=2  
  X.append(seq_x)
  y.append(faixa)
 return array(X), array(y)
# define input sequence
raw_seq = b3sa3['Close']
# choose a number of time steps
n_steps = 365
avanco = 21
# split into samples
X, y = split_sequence(raw_seq, n_steps,avanco)
# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X4 = X.reshape((X.shape[0], X.shape[1], n_features))

4クラス分類されたバイナリ配列に変換します

num_classes = 4
y4 = keras.utils.to_categorical(y, num_classes)

フィッティングします

# define model

model4 = Sequential()
model4.add(LSTM(100, activation='tanh'))
model4.add(Dense(4,activation='sigmoid'))
model4.compile(optimizer='adam', loss="categorical_crossentropy", metrics=['accuracy'])
# fit model
model4.fit(X4, y4, validation_split=0.2, epochs=10)

結果

Epoch 1/10
85/85 [==============================] - 11s 121ms/step - loss: 0.9769 - accuracy: 0.5952 - val_loss: 0.9037 - val_accuracy: 0.6356
Epoch 2/10
85/85 [==============================] - 10s 118ms/step - loss: 0.9510 - accuracy: 0.6063 - val_loss: 0.9116 - val_accuracy: 0.6356
Epoch 3/10
85/85 [==============================] - 10s 116ms/step - loss: 0.9541 - accuracy: 0.6063 - val_loss: 0.9150 - val_accuracy: 0.6356
Epoch 4/10
85/85 [==============================] - 10s 117ms/step - loss: 0.9476 - accuracy: 0.6063 - val_loss: 0.9061 - val_accuracy: 0.6356
Epoch 5/10
85/85 [==============================] - 10s 115ms/step - loss: 0.9522 - accuracy: 0.6063 - val_loss: 0.9117 - val_accuracy: 0.6356
Epoch 6/10
85/85 [==============================] - 10s 114ms/step - loss: 0.9493 - accuracy: 0.6063 - val_loss: 0.9010 - val_accuracy: 0.6356
Epoch 7/10
85/85 [==============================] - 10s 114ms/step - loss: 0.9497 - accuracy: 0.6063 - val_loss: 0.9049 - val_accuracy: 0.6356
Epoch 8/10
85/85 [==============================] - 10s 118ms/step - loss: 0.9449 - accuracy: 0.6063 - val_loss: 0.9087 - val_accuracy: 0.6356
Epoch 9/10
85/85 [==============================] - 10s 119ms/step - loss: 0.9478 - accuracy: 0.6063 - val_loss: 0.9058 - val_accuracy: 0.6356
Epoch 10/10
85/85 [==============================] - 10s 119ms/step - loss: 0.9452 - accuracy: 0.6063 - val_loss: 0.9055 - val_accuracy: 0.6356

6クラス分類の場合

モデル

def split_sequence(sequence, n_steps, avanco):
 X, y = list(), list()

 for i in range(len(sequence)):
 # find the end of this pattern
  end_ix = i + n_steps
  z=end_ix-1
 # check if we are beyond the sequence
  if end_ix+avanco > len(sequence)-1:
    break
 # gather input and output parts of the pattern
  seq_x, seq_y = sequence[i:end_ix], sequence[end_ix+avanco]
  e=seq_y
  f=seq_x[-1]
  faixa = ((e/f)-1)*100
  if faixa <-15:
    faixa=-3
  elif faixa <-5:
    faixa=-2
  elif faixa <0:
    faixa=-1    
  elif faixa <5:
    faixa=1 
  elif faixa <15:
    faixa=2  
  elif faixa >15:
    faixa=3 
  X.append(seq_x)
  y.append(faixa)
 return array(X), array(y)
# define input sequence
raw_seq = b3sa3['Close']
# choose a number of time steps
n_steps = 365
avanco = 21
# split into samples
X, y = split_sequence(raw_seq, n_steps,avanco)
# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X6 = X.reshape((X.shape[0], X.shape[1], n_features))

6クラス分類されたバイナリ配列に変換します

num_classes = 6
y6 = keras.utils.to_categorical(y, num_classes)

フィッティングします

# define model
model6 = Sequential()
model6.add(LSTM(100, activation='tanh'))
model6.add(Dense(6,activation='sigmoid'))
model6.compile(optimizer='adam', loss="categorical_crossentropy", metrics=['accuracy'])
# fit model
model6.fit(X6, y6, validation_split=0.2, epochs=10)

結果

85/85 [==============================] - 11s 113ms/step - loss: 1.5843 - accuracy: 0.2848 - val_loss: 1.6382 - val_accuracy: 0.1689
Epoch 2/10
85/85 [==============================] - 9s 111ms/step - loss: 1.5677 - accuracy: 0.2800 - val_loss: 1.6800 - val_accuracy: 0.1689
Epoch 3/10
85/85 [==============================] - 9s 111ms/step - loss: 1.5623 - accuracy: 0.2941 - val_loss: 1.6359 - val_accuracy: 0.1689
Epoch 4/10
85/85 [==============================] - 9s 111ms/step - loss: 1.5513 - accuracy: 0.2963 - val_loss: 1.6746 - val_accuracy: 0.1748
Epoch 5/10
85/85 [==============================] - 9s 111ms/step - loss: 1.5483 - accuracy: 0.3078 - val_loss: 1.6489 - val_accuracy: 0.2356
Epoch 6/10
85/85 [==============================] - 9s 110ms/step - loss: 1.5505 - accuracy: 0.2952 - val_loss: 1.6026 - val_accuracy: 0.2193
Epoch 7/10
85/85 [==============================] - 9s 111ms/step - loss: 1.5391 - accuracy: 0.3200 - val_loss: 1.6281 - val_accuracy: 0.2504
Epoch 8/10
85/85 [==============================] - 10s 114ms/step - loss: 1.5384 - accuracy: 0.3230 - val_loss: 1.6312 - val_accuracy: 0.1911
Epoch 9/10
85/85 [==============================] - 10s 117ms/step - loss: 1.5185 - accuracy: 0.3330 - val_loss: 1.6505 - val_accuracy: 0.2430
Epoch 10/10
85/85 [==============================] - 10s 116ms/step - loss: 1.5229 - accuracy: 0.3274 - val_loss: 1.6121 - val_accuracy: 0.2533

↑ここまで

ご覧の通り、クラスが多くなる度に精度が低くなっていることがわかります

これらを100%に近づけたいです

実際に精度を上げる

まずは、6クラス分類のパターンでやってみて

精度があがったら4クラス分類の方にも反映しましょう

色々試しましたが、答えは単純明快でした。

試したこととその結果は以下

・単純にデータ量を増やしてみる → 2007年からの株式データしかないのでそもそもデータを増やせませんでした

・特徴量を増やしてみる(’Close’以外に’Open’, ‘High’, ‘Low’も含める) → 精度は変わらずでした

・n_stepsを増やしてみる → 精度は変わらずでした

・avanco “パラメーター”の異なる値を実験してみる → 精度は変わらずでした

・RNNを使用 → 精度は下がりました

・epoch数を増やす → 時間はかかりましたが精度は0.2533から0.4655まで上がりました

・そもそも他クラス分類なのでDenseのactivationはsigmoidではなくsoftmaxに変更しました → なぜか精度は変わらずでした

結果

その他、色々試しましたが6クラス分類で0.4655が限界です。

しかしながら、同じepoch数とモデルで4クラス分類を試したところ精度は0.6063から0.7104まで上がりました

0.8以上あると良いとされていますが

株式という不規則な分野であるため

分析データとしては十分な精度なのではないでしょうか。

最終的な変更点

・Dropoutで過学習を防ぐ

・他クラス分類なのでDenseのactivationをsigmoidからsoftmaxに変更する

・epoch数を10増やす

Before

model = Sequential()
model.add(LSTM(100, activation='tanh'))
model.add(Dense(num_classes, activation='sigmoid'))
model.compile(optimizer='adam', loss="categorical_crossentropy", metrics=['accuracy'])
model6.fit(X, y, validation_split=0.2, epochs=10)

After

model = Sequential()
model.add(LSTM(units=128, input_shape=(n_steps, n_features)))
model.add(Dropout(0.2))
model.add(Dense(units=num_classes, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(X6, y6, batch_size=32, epochs=100, validation_split=0.2)

まとめ

今回は、単純にepoch数を増やすことで精度が上がりました。




機械学習といえど

人間が手を加えるパフォーマンスやクオリティーは欠かせない部分になります

精度はそれを最も評価できる指標でしょう




また、精度を上げるために

もっと良いアルゴリズムを探したり

探究心や好奇心もエンジニアには必要です。

リクエスト募集中

皆さまのリクエストや質問も募集しています