이제 데이터를 살펴봤으니,
LSTM 기반의 주가예측 모델을 만들어보자.
우선 지난글에서 불러온대로 데이터를 다시 확인해본다.
[이전글] #1. 주식 데이터 불러오기 (yfinance)
[이전글] #2. 주가 데이터 차트 그리기(matplotlib)
데이터 로드
import yfinance as yf
import pandas as pd
echopro_bm = yf.download("247540.KQ", interval='1h', period='1y')
echopro_bm.head(3)
>> Open High Low Close Adj Close Volume
Datetime
2022-04-06 13:00:00+09:00 412600.0 418600.0 412400.0 417100.0 417100.0 0
2022-04-06 14:00:00+09:00 417100.0 422200.0 416500.0 418800.0 418800.0 51266
다양한 변수들이 있는데, 그 중 종가를 예측하는 모델을 만들기 위해서
'Close' 컬럼을 예측값 y로 만들고, 종가를 제외한 나머지 'Open', 'High', 'Low', 'Volume' 을 독립변수 x로 사용하려고 한다.
데이터 정규화
그 전에, 데이터들 간 범위가 너무 많이 차이나면 학습이 잘 안될 수 있기 때문에, 정규화를 해주는게 좋을 것 같다.
sklearn 라이브러리의 MinMaxScaler를 사용했다.
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
# 스케일을 적용할 column을 정의
scale_cols = ['Open', 'High', 'Low', 'Close', 'Volume']
# 스케일 후 columns
scaled = scaler.fit_transform(echopro_bm[scale_cols])
# 데이터 프레임으로 만들기
s_df = pd.DataFrame(scaled, columns=scale_cols)
s_df.head(3)
>> Open High Low Close Volume
0 0.686291 0.691535 0.692175 0.694588 0.000000
1 0.694242 0.691962 0.697579 0.691581 0.016615
2 0.691448 0.687046 0.696282 0.690507 0.009878
스케일링 후 데이터들이 0~1 사이의 값들로 정규화 되는 모습을 확인 할 수 있다.
학습/테스트 데이터셋 만들기
이제 학습 데이터와 테스트 데이터로 데이터셋을 나눠보자.
sklearn의 train_test_split 함수를 이용하면 간단하게 데이터셋을 나눌 수 있다.
종가를 예측하는 모델을 만들려고 하기 때문에,
y로는 s_df['Close'] 를, x로는 Close 열을 제외한 나머지 컬럼을 사용하기 위해 s_df.drop('Close', 1) 을 넣어주었다.
(s_df.drop('Close', 1) 는 'Close' 열을 제외한 데이터를 불러온다는 의미이며, 1은 axis=1 (열 방향 드롭) 을 간략히 쓴 것)
train 데이터와 test 데이터의 비율은 7:3으로 적용했으며,
그 결과 학습 데이터가 1188개, 테스트 데이터가 298개로 나눠진 것을 확인할 수 있다.
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(s_df.drop('Close', 1), s_df['Close'], test_size=0.2, random_state=0, shuffle=False)
print(x_train.shape, y_train.shape, x_test.shape, y_test.shape)
>> (1188, 4) (1188,) (298, 4) (298,)
그리고 종가 예측에 사용할 데이터를 지정한 윈도우 사이즈에 맞춰 배치로 만들어 주는 함수를 만든다.
이 window_size와 batch_size는 하이퍼파라미터로, 값을 바꿔서 돌려볼 수 있다.
import tensorflow as tf
def windowed_dataset(series, window_size, batch_size, shuffle):
series = tf.expand_dims(series, axis=-1)
ds = tf.data.Dataset.from_tensor_slices(series)
ds = ds.window(window_size + 1, shift=1, drop_remainder=True)
ds = ds.flat_map(lambda w: w.batch(window_size + 1))
if shuffle:
ds = ds.shuffle(1000)
ds = ds.map(lambda w: (w[:-1], w[-1]))
return ds.batch(batch_size).prefetch(1)
WINDOW_SIZE=24
BATCH_SIZE=4
# trian_data는 학습용 데이터셋, test_data는 검증용 데이터셋
train_data = windowed_dataset(y_train, WINDOW_SIZE, BATCH_SIZE, True)
test_data = windowed_dataset(y_test, WINDOW_SIZE, BATCH_SIZE, False)
# 아래의 코드로 데이터셋의 구성을 확인해 볼 수 있음
# X: (batch_size, window_size, feature)
# Y: (batch_size, feature)
for data in train_data.take(1):
print(f'데이터셋(X) 구성(batch_size, window_size, feature갯수): {data[0].shape}')
print(f'데이터셋(Y) 구성(batch_size, window_size, feature갯수): {data[1].shape}')
>> 데이터셋(X) 구성(batch_size, window_size, feature갯수): (4, 24, 1)
데이터셋(Y) 구성(batch_size, window_size, feature갯수): (4, 1)
window_size의 경우 처음에 20으로 설정했는데,
현재 데이터가 1시간 간격으로 가져온거라
6시간 단위 (하루에 장이 열리는 시간이 09-16시까지 6시간)로 끊어주는게 좋을 것 같아서
나중엔 24, 18 등 6의 배수로 실험해봤다.
batch_size는 32부터 16, 8, 4 로 줄여서 돌려봤는데 4가 제일 나았다.
배치사이즈가 작아질수록 학습에 보는 데이터수의 적어져서 그런지 예측하기 직전 데이터들의 트렌드를 더 잘 학습하는 것 같다.
우선 과거의 종가 데이터만 가지고 다음 종가데이터를 예측하도록 해봤다.
이제 LSTM 모델을 만들어봅시다!
LSTM 모델 만들기
import os
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM, Conv1D, Lambda
from tensorflow.keras.losses import Huber
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
model = Sequential([
# 1차원 feature map 생성
Conv1D(filters=32, kernel_size=5,
padding="causal",
activation="relu",
input_shape=[WINDOW_SIZE, 1]),
# LSTM
LSTM(16, activation='tanh'),
Dense(16, activation="relu"),
Dense(1),
])
# Sequence 학습에 비교적 좋은 퍼포먼스를 내는 Huber()를 사용
loss = Huber()
optimizer = Adam(0.0005)
model.compile(loss=Huber(), optimizer=optimizer, metrics=['mse'])
# earlystopping은 10번 epoch통안 val_loss 개선이 없다면 학습을 멈춤
earlystopping = EarlyStopping(monitor='val_loss', patience=10)
# val_loss 기준 체크포인터도 생성
filename = os.path.join('tmp', 'ckeckpointer.ckpt')
checkpoint = ModelCheckpoint(filename,
save_weights_only=True,
save_best_only=True,
monitor='val_loss',
verbose=1)
history = model.fit(train_data,
validation_data=(test_data),
epochs=50,
callbacks=[checkpoint, earlystopping])
model.load_weights(filename)
pred = model.predict(test_data)
결과 시각화
실제 값과 예측값을 시각화해봤다.
plt.figure(figsize=(12, 9))
plt.plot(np.asarray(y_test)[24:], label='actual')
plt.plot(pred, label='prediction')
plt.legend()
plt.show()
배치 사이즈에 따른 예측값 차이
-> 배치 사이즈가 커질수록 스무딩 되는 것 같고, 배치 사이즈가 작아지면 좀 더 변동성에 핏하게 예측하는 듯.
window_size= 24, batxh_size = 8 | window_size= 24, batxh_size = 4 |
최종 선정 모델
window_size =20, batch_size = 4 인 모델을 최종 모델로 선정했다.
예측 결과 시각화
예측값이 후행되는게 아쉽긴 하지만,
그래도 몇번의 파라미터 튜닝 끝에 근접하게 예측하는 모델을 만든 것 같다.
'투자x개발 > 퀀트 투자' 카테고리의 다른 글
[할 수 있다!퀀트 투자] ch.4 듀얼 모멘텀 전략 - 백테스트 (0) | 2023.07.18 |
---|---|
[할 수 있다!퀀트 투자] ch.04 듀얼 모멘텀 전략 - 한국 팩터 듀얼 모멘텀 전략 (0) | 2023.07.18 |
[주가 예측 모델링] #2. 주가 데이터 차트 그리기 (matplotlib) (0) | 2023.04.05 |
[주가 예측 모델링] #1. 주식 데이터 불러오기 (yfinance) (1) | 2023.04.05 |
[국내주식] 2차전지 주요 종목 분석 with ChatGPT (0) | 2023.04.04 |
댓글