
내가 보유한 작고 소중한 주식 테슬라, 현대자동차, 삼성전자의 주식을 LSTM을 이용해 예측해보겠다.
제발...제발...오르게해주세요...간절한 염원을 담아 고고씽
✅ LSTM이란?
LSTM (Long Short Term Memory)은 기존의 RNN에서 출력과 멀리 있는 정보를 기억할 수 없다는 단점을 보완하여 장/단기 기억을 가능하게 설계한 신경망의 구조이다. 주로 시계열 처리나, 자연어 처리에 사용된다.
- 구조
1) 입력 게이트 : 새로운 정보의 반영 방법 결정 -- 시그모이드 함수, 탄젠트 함수
2) 망각 게이트 : 셀 상태의 정보를 지울 것인지 말 것인지 결정
3) 출력 게이트 : cell state 값에 함수를 적용한 값을 사용
4) Cell state : 이전 상태에서 현재 상태까지 유지되는 정보의 흐름
--> 입력 값과 이전 상태에 따라 값을 업데이트하고 새로운 상태를 출력하며, 셀의 값을 얼마나 기억할지 결정 가능한 게이트를 가지고 있어서 필요한 정보만 기억하도록 제어할 수 있다.
Understanding LSTM Networks -- colah's blog
Posted on August 27, 2015 <!-- by colah --> Humans don’t start their thinking from scratch every second. As you read this essay, you understand each word based on your understanding of previous words. You don’t throw everything away and start thinking
colah.github.io
https://ctkim.tistory.com/entry/LSTMLong-short-time-memory-%EA%B8%B0%EC%B4%88-%EC%9D%B4%ED%95%B4
LSTM(Long short time memory) : 기초 이해
LSTM(Long Short-Term Memory)은 시계열 데이터의 예측, 자연어 처리, 음성 인식, 이미지 분류 등에서 중요한 역할을 하는 모델 중 하나입니다. 이 글에서는 LSTM의 개념, 동작 원리 등에 대해 상세히 알아
ctkim.tistory.com
✅ 데이터 수집
토이프로젝트니까 주식 웹사이트 크롤링 말고, 야후 주식 사이트에서 데이터를 다운받는 방법을 택했다.
historical Data 탭으로 가서 5년간의 데이터를 csv 형식으로 다운 받았다.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dropout, Dense, Activation
from tensorflow.keras.optimizers import Adam
import datetime
data1 = pd.read_csv('C:/Users/TSLA.csv')
data2 = pd.read_csv('C:/Users/samsung.csv')
data3 = pd.read_csv('C:/Users/hyundai.csv')

지금 블로그를 정리하며 다시 생각해보니, Close를 이용하여 예측하면 내가 최종적으로 원하는 방향을 잡을 수 있을 것 같다는 생각이 든다.
✅ 데이터 전처리
- 고가, 저가, 고가와 저가의 중간 값, 고가와 저가의 80% 값을 만들어 주었다. 근데 80% 값은 사용 안함..
# 테슬라
high_price1 = data1['High'].values
low_price1 = data1['Low'].values
mid_price1 = (high_price1 + low_price1)/2
eight_price1 = (high_price1 + low_price1)*0.8
# 삼성전자
high_price2 = data2['High'].values
low_price2 = data2['Low'].values
mid_price2 = (high_price2 + low_price2)/2
eight_price2 = (high_price2 + low_price2)*0.8
# 현대자동차
high_price3 = data3['High'].values
low_price3 = data3['Low'].values
mid_price3 = (high_price3 + low_price3)/2
eight_price3 = (high_price3 + low_price3)*0.8
- window size 설정
window size를 설정해야 한다. 이 프로젝트를 하는 이유가 사실 지금 다른 프로젝트를 하면서 이 개념을 공부해보려고니까..
일단, window size를 30일로 설정할 것이다.
최근 30일 간의 데이터를 통해 내일의 주가를 예측하는 의미이다.
이 window size는 주식 종류, 데이터 특징 등에 따라 다르게 설정해야 하며, window size 설정을 위한 여러가지 방법론이 있지만 여기서는 30으로 통일할 예정이다.
# window size : 최근 30일
seq_len = 30
sequence_length = seq_len + 1
# 테슬라
result1_tsla = []
for index in range(len(mid_price1) - sequence_length):
result1_tsla.append(mid_price1[index: index + sequence_length])
result2_tsla = []
for index in range(len(eight_price1) - sequence_length):
result2_tsla.append(eight_price1[index: index + sequence_length])
# 삼성전자
result1_ss = []
for index in range(len(mid_price2) - sequence_length):
result1_ss.append(mid_price2[index: index + sequence_length])
result2_ss = []
for index in range(len(eight_price2) - sequence_length):
result2_ss.append(eight_price2[index: index + sequence_length])
# 현대자동차
result1_hd = []
for index in range(len(mid_price3) - sequence_length):
result1_hd.append(mid_price3[index: index + sequence_length])
result2_hd = []
for index in range(len(eight_price3) - sequence_length):
result2_hd.append(eight_price3[index: index + sequence_length])
✅ 정규화
# 테슬라
normal_data1 = []
for window in result1_tsla:
normal_window1 = [((float(p) / float(window[0]))-1) for p in window]
normal_data1.append(normal_window1)
result1 = np.array(normal_data1)
# split train set, test set
row1 = int(round(result1.shape[0]*0.9))
train1 = result1[:row1, :]
np.random.shuffle(train1)
x_train1 = train1[:, :-1]
x_train1 = np.reshape(x_train1, (x_train1.shape[0], x_train1.shape[1], 1))
y_train1 = train1[:, -1]
x_test1 = result1[row1:, :-1]
x_test1 = np.reshape(x_test1, (x_test1.shape[0], x_test1.shape[1], 1))
y_test1 = result1[row1:, -1]
x_train1.shape, x_test1.shape
# shape
((1105, 30, 1), (123, 30, 1))
# 삼성전자
normal_data2 = []
for window in result1_ss:
normal_window2 = [((float(p) / float(window[0]))-1) for p in window]
normal_data2.append(normal_window2)
result2 = np.array(normal_data2)
# split train set, test set
row2 = int(round(result2.shape[0]*0.9))
train2 = result2[:row2, :]
np.random.shuffle(train2)
x_train2 = train2[:, :-1]
x_train2 = np.reshape(x_train2, (x_train2.shape[0], x_train2.shape[1], 1))
y_train2 = train2[:, -1]
x_test2 = result2[row2:, :-1]
x_test2 = np.reshape(x_test2, (x_test2.shape[0], x_test2.shape[1], 1))
y_test2 = result2[row2:, -1]
x_train2.shape, x_test2.shape
# shape
((1082, 30, 1), (120, 30, 1))
# 현대자동차
normal_data3 = []
for window in result1_hd:
normal_window3 = [((float(p) / float(window[0]))-1) for p in window]
normal_data3.append(normal_window3)
result3 = np.array(normal_data3)
# split train set, test set
row3 = int(round(result3.shape[0]*0.9))
train3 = result3[:row3, :]
np.random.shuffle(train3)
x_train3 = train3[:, :-1]
x_train3 = np.reshape(x_train3, (x_train3.shape[0], x_train3.shape[1], 1))
y_train3 = train3[:, -1]
x_test3 = result3[row3:, :-1]
x_test3 = np.reshape(x_test3, (x_test3.shape[0], x_test3.shape[1], 1))
y_test3 = result3[row3:, -1]
x_train3.shape, x_test3.shape
# shape
((1082, 30, 1), (120, 30, 1))
삼성전자와 현대자동차가 왜 shape이 같게 나왔는지 의문이다.
shape에 대해 설명하자면,
테슬라는 1105일의 데이터로 학습한 후, 123일의 주식 가격을 예측하는 것이고
삼성전자랑 현대자동차는 1082일의 데이터로 학습한 후, 120일의 주식 가격을 예측하는 것이다.
그리고 window size는 30일!!로 통일.
✅ LSTM 모델링
모델링은 이것저것 실험을 해보았다.
대표적인 몇 개만 적겠다.
1) MSE, rmsprop 사용
# 테슬라
# model = Sequential()
# model.add(LSTM(30, return_sequences=True, input_shape=(30,1)))
# model.add(LSTM(64, return_sequences=False))
# model.add(Dense(1, activation='linear'))
# model.compile(loss='mse', optimizer='rmsprop')
# model.summary()
2) MSE, Adam 0.01 사용
model = Sequential()
model.add(LSTM(30, return_sequences=True, input_shape=(30, 1)))
model.add(LSTM(64, return_sequences=False))
model.add(Dense(1, activation='linear'))
model.compile(loss='mse', optimizer=Adam(0.01))
model.summary()
3) MSE, Adam 0.01 사용
- 최종적으로 복잡성을 적당히 증가시킨 이 모델을 사용했는데..2번째 모델 결과가 잘 나온거 같다. 좀 이따 결과 사진을 첨부하겠습니당..
model = Sequential()
model.add(Input(shape=(30, 1)))
model.add(LSTM(50, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(100, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(100, return_sequences=False))
model.add(Dropout(0.2))
model.add(Dense(1, activation='linear'))
model.compile(loss='mse', optimizer=Adam(0.001)) # 학습률 0.001
model.summary()
✅ 모델 훈련 및 예측
1) 테슬라
model.fit(x_train1, y_train1,
validation_data=(x_test1, y_test1),
batch_size=10, epochs=50)
pred1 = model.predict(x_test1)
# 그래프를 보장
fig = plt.figure(facecolor='white', figsize=(12, 6))
ax = fig.add_subplot(111)
ax.plot(y_test1, label='Real Stock', linewidth=2)
ax.plot(pred1, label='Prediction', linewidth=2)
ax.legend(fontsize='large')
ax.xaxis.set_major_locator(plt.MaxNLocator(30))
ax.yaxis.set_major_locator(plt.MaxNLocator(30))
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
얼추 비슷한거 같기도...? 파란색 선이 실제 주가이고, 주황색 선이 내가 예측한 주가이다.
근데 여기서 의문 아까 두 번째 모델로 학습했을 때가 더 나은 것 같다는 말을 했었는데...흠...이건 epoch도 30으로 준거고 모델도 안복잡한거였는데ㅠ

2) 삼성전자
pred2 = model.predict(x_test2)
fig = plt.figure(facecolor='white', figsize=(12, 6))
ax = fig.add_subplot(111)
ax.plot(y_test2, label='Real Stock', linewidth=2)
ax.plot(pred2, label='Prediction', linewidth=2)
ax.legend(fontsize='large')
ax.xaxis.set_major_locator(plt.MaxNLocator(30))
ax.yaxis.set_major_locator(plt.MaxNLocator(30))
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
3) 현대자동차
pred3 = model.predict(x_test3)
fig = plt.figure(facecolor='white', figsize=(12, 6))
ax = fig.add_subplot(111)
ax.plot(y_test3, label='Real Stock', linewidth=2)
ax.plot(pred3, label='Prediction', linewidth=2)
ax.legend(fontsize='large')
ax.xaxis.set_major_locator(plt.MaxNLocator(30))
ax.yaxis.set_major_locator(plt.MaxNLocator(30))
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
참고사항으로 x축 y축을 설명하자면,
- x축 : 시간의 흐름에 따른 데이터 포인트의 인덱스
- y축 : 주가의 정규화된 값
--> "정규화된 값" : 원래의 주가 데이터에서 특정 기준값으로 나누고 1을 빼서 계산된 비율로, 모든 데이터 포인트들을 동일한 기준으로 비교할 수 있도록 한 것임
그래프에서 각 포인트가 예측 모델에 입력된 연속적인 30일 주가 데이터의 마지막 날에 해당하는 실제 주가와 예측 주가를 나타내고자 하였다.
정리하면? x축의 각 인덱스는 test셋에서의 연속된 기간을 나타내며, y축의 값은 해당 기간의 마지막 날의 실제 주가와 모델이 예측한 주가의 상대적인 변화이다. 예를 들어, x축의 20은 test셋의 21번째 기간을 의미하며, 이 기간의 마지막 날의 실제 주가와 예측 주가가 어떠했는지 y축에서 볼 수 있다.
그런데! 내가 추가적으로 보고싶었던거는!!
2024년 4월 25일 close 주가 : ###
2024년 4월 26일 close 주가 : ###
2024년 4월 27일 close 주가 : ###
.
.
.
이다.
✅ 주가 예측 값 수치로 보기 : 보완 예정
last_batch = x_test1[-1:]
current_batch = last_batch
predicted_prices = []
# 30일간의 주가 예측하기
for i in range(30): # 다음 30일간 예측
current_prediction = model.predict(current_batch)[0]
predicted_prices.append(current_prediction)
current_batch = np.append(current_batch[:, 1:, :], [[current_prediction]], axis=1)
predicted_prices
이거 아니야! 퇴짜
시도 2)
predicted_changes = [
-0.1478745, -0.12135213, -0.0926108, -0.06260584, -0.03236606,
-0.00242374, 0.02693642, 0.05552468, 0.08318421, 0.10977934,
0.13520195, 0.15937923, 0.18227798, 0.20390186, 0.22428292,
0.24347362, 0.26153845, 0.27854663, 0.29456806, 0.30967018,
0.3239146, 0.33735818, 0.3500523, 0.36204264, 0.37337056,
0.38407362, 0.39418563, 0.40373752, 0.41275832, 0.42127517
]
start_date = pd.to_datetime('2024-04-24')
# 마지막 Close 주가 : 2024.04.23일
last_close_price = 144.679993
predicted_prices = [(change + 1) * last_close_price for change in predicted_changes]
predicted_dates = pd.date_range(start=start_date, periods=len(predicted_prices))
predicted_prices_with_dates = [f"{date.strftime('%Y-%m-%d')} Close Price: {price:.2f}" for date, price in zip(predicted_dates, predicted_prices)]
predicted_prices_with_dates
왜 선형성을 가지는거야..........
뭔가 주식시장의 여러가지 변수 요인들은 코드로 구현하기 힘드니까라는 생각을 가지고 챗지피티한테 이유를 물어봤다.

네! 알겠습니다!
본 프로젝트는 LSTM을 이용해서만 진행하려고 했던 토이 프로젝트였는데, 원하는 결과를 얻으려면 디벨롭해야 하고, 그럼 더 많은 시간과 공부가 필요하다는 당연한 사실을ㅎㅎ...
주식 데이터는 진짜 예측하기 어려운 변동성이 많으니까
지금 하고있는 MLOps 프로젝트를 어느정도 마무리하면 디벨롭 해보겠습니다!