논문리뷰 4. 주가 예측(1)
주가 예측 논문에는 시계열 데이터를 예측하는 쪽과 분류분석으로 주가의 증가, 감소를 예측하는 쪽이 있다.
논문을 쓰기위해 코드를 정리하며 과거의 리뷰 코드들을 전부 돌려보았다.
그 중 시계열 데이터를 예측하는 논문들을 정리해보려고 한다.
1. The optimization of share price prediction model based on support vector machine, 2011
import yfinance as yf
import numpy as np
import pandas as pd
from sklearn.svm import SVR
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import MinMaxScaler
import train_test as tt
필요한 패키지를 불러온다. 여기서 train_test는 내가 만든 py 파일이다.(github에 가면 있을 것이다.)
def MAPE(y_test, y_pred):
return np.mean(np.abs((y_test - y_pred) / y_test)) * 100
평가방법은 RMSE, MSE, MAE, MAPE를 사용할 것이다.
price_data = yf.download("BESTEAST.BO",start = '2010-09-01', end = "2010-12-18")
price_data = price_data[["Open","High","Low","Close","Volume"]]
lag_n = 3
price_data[["Open","High","Low","Volume"]] = price_data[["Open","High","Low","Volume"]].shift(lag_n)
price_data = price_data[lag_n:len(price_data)]
위 논문은 Best East Hotel의 주가를 예측했다. 그러므로 나도 동일한 주가를 예측해보았다.
기간도 동일하게 설정하였고, 고가, 저가, 종가, 시가, 거래량까지 사용한다.
lag_n은 오늘의 고가, 저가, 시가, 거래량을 가지고 3일 후의 종가를 예측할 예정이므로 3으로 설정하고 shift를 이용해 데이터를 맞춰준다.
x = price_data[["Open","High","Low","Volume"]]
y = price_data[["Close"]]
scaler_x = MinMaxScaler()
scaler_y = MinMaxScaler()
x = pd.DataFrame(scaler_x.fit_transform(x))
y = pd.DataFrame(scaler_y.fit_transform(y))
train_n = 60
train_x = x[0:train_n]
train_y = y[0:train_n]
test_x = x[train_n:len(price_data)]
test_y = y[train_n:len(price_data)]
input은 시가, 고가, 저가, 거래량이고, output은 종가이다.
정규화는 MinMaxScaler을 사용했다. 정규화를 시켜주고 데이터 프레임으로 변경한다.
여기서 논문과의 차이점이 나타나는데
논문에서는 총 70개의 데이터로 60:10으로 나눴다. 하지만 나는 71개의 데이터가 있었다. 그래서 60:11로 나누었다.
model = SVR(kernel = 'rbf', C=102.2, gamma = 0.0625, epsilon= 0.01)
model.fit(train_x, train_y)
이 모델은 SVM 모델 중 회귀분석을 이용한 SVR 모델을 사용하였다.
사용하는 커널은 RBF이다. SVR에서는 RBF를 사용하는 경우를 많이 보았다.
C : 패널티이다. 모델에 맞지 않으면 패널티를 준다. 값이 클수록 모델이 엄격해진다. 즉, 오버피팅이 발생할 위험이 있다.
gamma : 커널의 영향 폭이다. 값이 작을 수록 하나의 샘플이 미치는 영향이 커진다. 그래서 값이 크면 모델이 복잡해진다.
epsilon : 허용 범위이다. epsilon의 범위 안에 있으면 패널티를 받지 않는다.
모델을 학습한다.
svr_pred = np.reshape(model.predict(test_x),(len(model.predict(test_x)),1))
svr_pred = scaler_y.inverse_transform(svr_pred)
test_y = scaler_y.inverse_transform(test_y)
svr_mae = mean_absolute_error(test_y, svr_pred)
svr_mse = mean_squared_error(test_y, svr_pred)
svr_rmse = np.sqrt(mean_squared_error(test_y, svr_pred))
svr_mape = MAPE(test_y, svr_pred)
predict 함수를 이용해 test 샘플을 예측하고 shape을 (11,1)이 되도록 지정해준다.
그리고 정규화했던 값을 다시 돌려준다.
그리고 MAE, MSE, RMSE, MAPE를 계산해준다.
논문에서 나온 값은 MAPE 기준 0.0008이다. 하지만 나는 값이 11.8로 훨씬 컸다.
# price data, window = 1
dir = "/home/whfhrs3260/csv_data/price_data_score_10years.csv"
stock = "^DJI"
variable = ["before_close","Score"]
window_size = 1
start_date = "2012-01-01"
end_date = "2022-04-30"
scaler_x, scaler_y, x, y, x_t, y_t = tt.train_test_result(dir, stock, variable, window_size, start_date, end_date)
x = x.reshape(len(x),window_size*len(variable))
y = y.reshape(len(y),1)
x_t = x_t.reshape(len(x_t),window_size*len(variable))
y_t = y_t.reshape(len(y_t),1)
model = SVR(kernel = 'rbf', C=102.2, gamma = 0.0625, epsilon= 0.01)
model.fit(x, y)
#예측값
svr_pred = np.reshape(model.predict(x_t),(len(model.predict(x_t)),1))
svr_pred = scaler_y.inverse_transform(svr_pred)
y_t = scaler_y.inverse_transform(y_t)
svr_mae = mean_absolute_error(y_t, svr_pred)
svr_mse = mean_squared_error(y_t, svr_pred)
svr_rmse = np.sqrt(mean_squared_error(y_t, svr_pred))
svr_mape = MAPE(y_t, svr_pred)
나의 NBC 뉴스의 감성분석 score와 종가를 이용해 계산한 결과와 논문 재현에서 종목을 Dow Jones로만 바꾼 결과를 비교해보았다.
MAPE 기준 전자는 1.021, 후자는 3.18로 감성분석 score을 사용한 게 좋았다. MAE 기준도 동일했지만 RMSE, MSE 기준으로 후자가 결과가 좋았다.
2. Stock price prediction using BERT and GAN, 2017
import yfinance as yf
import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
from statsmodels.tsa.arima.model import ARIMA
import train_test as tt
패키지를 불러온다.
dir = "/home/whfhrs3260/csv_data/price_data_score_10years.csv"
stock = "AAPL"
variable = ["before_close"]
window_size = 1
start_date = "2010-07-01"
end_date = "2020-07-31"
scaler_x, scaler_y,x, y, x_t, y_t = tt.train_test_result(dir, stock, variable, window_size, start_date, end_date)
x = x.reshape(len(x),window_size*len(variable))
y = y.reshape(len(y),1)
x_t = x_t.reshape(len(x_t),window_size*len(variable))
y_t = y_t.reshape(len(y_t),1)
train = pd.DataFrame(x)
test = pd.DataFrame(x_t)
train_x = train.squeeze()
test_x = test.squeeze()
# 주가데이터
history = [i for i in train_x]
# 예측데이터
arima_pred = list()
for t in range(len(test_x)):
model = ARIMA(history, order=(4,1,0))
model_fit = model.fit()
forecast_test = model_fit.forecast()
yhat = forecast_test[0]
arima_pred.append(yhat)
obs = test_x[t]
history.append(obs)
논문에서 ARIMA 모델은 종가 하나만으로 예측을 하였다.
논문과 시기, 종목을 동일하게 설정한다.
내 train_test_result는 하루 전날 종가로 오늘의 종가를 예측할 수 있도록 input과 output을 구성해줄 수 있는 함수이다.
데이터가 딥러닝 모델에 바로 넣을 수 있도록 구성이 되어있으므로 shape을 바꿔준다.
squeeze 함수를 이용해 차원을 하나 줄여준다. 여기서는 dataframe을 series 형식으로 바꿔주게 된다.
history는 1~30일까지의 종가를 넣고 31일을 예측했다면 append 함수를 이용해 31일의 종가를 넣고 다시 1~31일까지의 종가를 가지고 32일의 종가를 예측하기 위한 것이다.
그래서 처음엔 train만 넣고 test의 첫번째 날을 예측을 했다면 (train+test의 첫째날)로 test의 둘째날을 예측한다.
예측한 데이터는 arima_pred에 넣을 것이다.
for문을 돌려 test의 하루하루를 예측해본다. ARIMA 함수를 사용했고 4,1,0으로 파라미터를 지정하였다.(논문에서 최적의 파라미터라고 했다.)
fit으로 모델을 생성하고, forecast 함수로 하루를 예측한다. 그리고 그 값을 arima_pred에 넣는다.
arima_pred = pd.DataFrame(arima_pred)
arima_pred.index = test.index
arima_pred = scaler_y.inverse_transform(arima_pred)
y_t = scaler_y.inverse_transform(y_t)
arima_mae = mean_absolute_error(y_t, arima_pred)
arima_mse = mean_squared_error(y_t, arima_pred)
arima_rmse = np.sqrt(mean_squared_error(y_t, arima_pred))
arima_mape = MAPE(y_t, arima_pred)
arima_rmse, arima_mse, arima_mae, arima_mape
예측된 값은 평가를 해야하기 때문에 형식을 데이터프레임으로 바꿔주고 index도 지정해준다.
결과를 보면 RMSE 기준 논문은 18.247, 나의 재현은 4.264로 결과가 더 좋아졌다.
Dow Jones 데이터로 예측을 해보면 스케일이 달라 APPLE과 Dow Jones의 예측값을 비교하기는 힘들지만 MAPE 기준 APPLE을 예측한 재현은 7.091, Dow Jones를 예측한 재현은 1.190이다. 다른 평가방법은 스케일이 달라 비교하기 어려울 정도로 차이가 크게 APPLE이 좋다.
둘 다 동일하게 종가만 사용하였고, 차이는 종목만 차이가 있다.
3. Stock price prediction using BERT and GAN, 2017
import yfinance as yf
import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
import tensorflow as tf
from tensorflow.keras.layers import Dense, LSTM, Dropout, Bidirectional
from tensorflow.keras.models import Sequential
from keras import optimizers
from keras import backend as K
import random
import train_test as tt
import os
def MAPE(y_test, y_pred):
return np.mean(np.abs((y_test - y_pred) / y_test)) * 100
def root_mean_squared_error(y_true, y_pred):
return K.sqrt(K.mean(K.square(y_pred - y_true)))
필요한 패키지와 함수를 불러온다.
def seed_everything(seed: int = 42):
random.seed(seed)
np.random.seed(seed)
os.environ["PYTHONHASHSEED"] = str(seed)
tf.random.set_seed(seed)
seed_everything(42)
재현성을 위해서 seed를 고정해준다. 하지만 실행해본 결과 dropout 때문인지 다른 값이 고정이 안된 건지 값이 완전히 동일하지 않았다. 자료조사 결과 이부분은 어쩔 수 없다고 한다.
dir = "/home/whfhrs3260/csv_data/price_data_score_10years.csv"
stock = "AAPL"
variable = ["before_close","Score"]
window_size = 3
start_date = "2010-07-01"
end_date = "2020-07-31"
scaler_x, scaler_y,x, y, x_t, y_t = tt.train_test_result(dir, stock, variable, window_size, start_date, end_date)
bi_lstm_model = Sequential()
bi_lstm_model.add(Bidirectional(LSTM(128, input_shape = (x.shape[1],x.shape[2]))))
bi_lstm_model.add(Dense(64))
bi_lstm_model.add(Dense(1,activation="tanh"))
adam = optimizers.Adam(learning_rate=0.0012)
bi_lstm_model.compile(optimizer=adam, loss = "mse")
history = bi_lstm_model.fit(x, y, epochs=150, batch_size=64, validation_split=0.2)
bi_lstm_pred = bi_lstm_model.evaluate(x_t, y_t)
논문에서 사용한 변수의 개수는 훨씬 많다. 하지만 재현해보았을 때, 개수가 맞지않아 메일을 보냈지만 답을 받을 수 없었다. 그래서 종가와 감성분석 score로 재현을 해보았다. 시기, 모델과 APPLE 주가를 사용한 것만 동일하다.
window_size는 3으로 설정되어있었고, Bi-LSTM 128 units, Dense 64 units, Dense 1 units, loss = MSE, 150 epochs, 64 batch size, 0.0012 learning rate, Adam optimizer 이 적혀있는 조건이었다.
bi_lstm_pred = bi_lstm_model.predict(x_t)
bi_lstm_pred = scaler_y.inverse_transform(bi_lstm_pred)
y_t = scaler_y.inverse_transform(y_t)
root_mean_squared_error(y_t, bi_lstm_pred), mean_squared_error(y_t, bi_lstm_pred), mean_absolute_error(y_t, bi_lstm_pred), MAPE(y_t, bi_lstm_pred)
RMSE 기준 논문은 2.939이었지만 나의 재현 결과는 5.126이었다. 변수가 상당히 많이 빠져있으므로 그 영향을 많이 받은 듯하다.
동일한 내용이지만 Bi-LSTM이 아닌 LSTM을 사용한 결과는 4.981이었다. 양방향 LSTM보단 단방향 LSTM이 결과가 더 좋았다.
같은 논문에서 GRU도 실행을 하였다.
gru_model = Sequential()
gru_model.add(GRU(128, input_shape = (x.shape[1],x.shape[2]),return_sequences=True))
gru_model.add(GRU(64))
gru_model.add(Dense(32))
gru_model.add(Dense(1))
adam = optimizers.Adam(lr=0.0005)
gru_model.compile(optimizer=adam, loss = 'mse')
history = gru_model.fit(x, y, epochs=50, batch_size=128, validation_split=0.2)
gru_pred = gru_model.evaluate(x_t, y_t)
모델은 window size = 3, GRU 128 units, GRU 64 units, Dense 32 units, Dense 1 units, loss = MSE, Adam optimizer, learning rate 0.0005, batch size 128, 50 epochs 로 설정이 되어있었다.
결과는 RMSE 기준 논문에서는 2.96, 재현 결과는 1.977으로 재현 결과가 더 좋았다.
Bi-LSTM을 사용했을 땐 변수의 개수가 더 작고 달라서 결과가 더 안좋나 싶었는데 GRU의 결과는 나의 결과가 더 좋다.
변수가 중요한 것보다 최적의 변수 + 모델을 찾는 게 관건일 것 같다는 생각을 했다.
같은 모델도 변수가 달라지면 값이 크게 달라지므로 주가 예측에 최적의 변수가 있는 것도 최적의 모델이 있는 것도 아닌 최적의 변수 + 모델이 있을 것이라고 생각한다.
그리고 그 변수 + 모델도 종목에 따라 상당히 달라질 것이라고 생각한다.