기본 환경: IDE: VS code, Language: Python
1. Project 개요
1.1. Project 배경
기준일 | KOSPI 시가 총액 (천억원) | 삼성전자 시가 총액 (천억원) | 비중 (%) |
2023-01-27 | 1,966.56 | 385.65 | 약 19.61% |
23년 01월 27일자 기준 KOSPI 시가 총액 대비 삼성전자 시가 총액이 약 20%를 차지
우리나라의 경제 지표이자 투자 지표를 의미하는 KOSPI의 5분의 1을 차지하는 삼성전자 주가 예측의 필요성
1.2. Project 목표
2023년 01월 30일자 삼성전자 시가 예측
2. 데이터 분석
2.1. 데이터 설명
일자: 2015.01 ~ 2023.01의 데이터
시가: 개장 후 최초로 체결된 거래 가격
고가: 장중 기록되는 가장 높은 거래 가격
저가: 장중 기록되는 가장 낮은 거래 가격
종가: 개장 후 최종으로 체결된 거래 가격
전일비: 전일 대비 종가의 등락폭
등락률: (전일비/당일종가)*100
거래량: 장중 기록되는 주식 거래량
금액(백만): 장중 기록되는 주식 거래 금액
신용비: 신용 공여를 통해서 매수된 거래 금액
개인: 장중 기록되는 개인 순매매 거래량
기관: 장중 기록되는 기관 순매매 거래량
외인(수량): 장중 기록되는 외국인 순매매 거래량
외국계: 장중 기록되는 외국계 증권사를 이용하는 주체의 순매매 거래량
프로그램: 장중 기록되는 프로그램 순매매 거래량
외인비: 외국인의 상장 주식수 대비 보유 비율
2.2. 데이터 전처리
2.2.1. 2018년 05월 04일자 기준 삼성전자 주가가 50:1로 액면분할 되었으므로 주가가 큰 폭으로 차이나므로 그 이전값은 데이터로 활용하지 않음
2.2.2. 삼성전자의 '시가' 예측을 목표로 하고 있으므로 다양한 데이터 중에서 '시가'열과 가장 유사한 추세선을 그리는 특징 5개를 선택하여 모델 구축
(단, 최신 데이터의 적극 반영을 위해 특징 선별 시, 최근 2년 데이터만을 활용)
-> 시가, 저가, 고가, 종가, 외인비
2021.01.04 ~ 2023.01.27 시가, 고가, 저가, 종가 추세선
2021.01.04 ~ 2023.01.27 시가, 외인비 추세선
* 시가, 외인비 그래프의 경우, 시가를 1400으로 나눠준 값으로 조정함
2.2.3. 외인비의 경우, 다른 x값과 비교할 때 수치가 배우 낮으므로 비슷한 수치를 맞춰주기 위해 추가 연산 진행
외인비(%) -> 외인비(1400%)
2.2.4. RNN 모델 이용 시, 최근 데이터가 영향력을 높일 수 있도록 과거순으로 재정렬
2.2.5. 특정일의 매매에 따라 그 다음날 시가에 영향을 미치는 것을 상정하고 있으므로 '시가'와 다른 데이터 특징과는 1일의 차이가 존재하는 것을 고려(하기 이미지 참고)
2.2.6. 삼성전자와 관련없는 타사 주가를 통해 훈련된 모델을 병합하므로써 성능이 우수해지는지 판별해보기 위해 아모레퍼시픽 주가자료 활용
아모레 퍼시픽 주가자료 또한 삼성전자 데이터와 마찬가지로 전처리 과정 동일하게 진행 - 액면분할로 인하여 2015년 05월 08일 이전 자료 사용 X
-> Ensemble Model 구현을 위해 동일한 크기의 datasets 필요
-> 삼성전자 액면 분할 시기와 동일하게 2018년 05월 04일자 자료부터 사용
- 특성으로는 삼성전자와 동일하게 시가, 고가, 저가, 종가, 외인비 사용
- 외인비 수치 재조정 : 외인비 * 5700
- RNN Model 구축 시, 최신 데이터 영향력을 높이기 위해 인덱스 기준 내림차순 정렬
- 훈련 데이터와 예측 데이터 일자 구분
훈련 데이터 | x | 2015.05.08 - 2023.01.26 | 예측 데이터 | x | 2023.01.27 |
시가, 고가, 저가, 종가, 외인비 | 시가, 고가, 저가, 종가, 외인비 | ||||
y | 2015.05.09 - 2023.01.27 | y | 2023.01.30 | ||
시가 | 시가 |
3. 모델링 및 분석 결과
# samsung_predict_open_stock_price.py
# For Detail: https://hj0216.tistory.com/74
import pandas as pd
import numpy as np
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Input, concatenate, SimpleRNN, LSTM, Dropout, GRU, Bidirectional, Flatten
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from sklearn.model_selection import train_test_split
path = './keras/'
# path = 현재 dir 아래 keras dir로 경로 지정
# 1.1. Data Preprocessing(Samsung)
samsung = pd.read_csv(path+'stock_samsung.csv', encoding='CP949', nrows=1166, usecols=[1,2,3,4,16], header=0)
# 한글 데이터의 경우, pandas의 read_cvs 사용 시 깨짐 현상 발생 -> encoding='CP949' 추가
# 액면 분할로 인한 주가 차이가 약 50배 이상 차이나므로 액면 분할 이후 데이터만 수집 -> nrows 사용
# 훈련 데이터로 특성 5개만 추출: 1(시가), 2(고가), 3(저가), 4(종가), 16(외인비) -> usecols 사용
# 첫번쩨 행: col name -> header=0 지정
samsung['시가'] = samsung['시가'].str.replace(',', '').astype('float')
samsung['고가'] = samsung['고가'].str.replace(',', '').astype('float')
samsung['저가'] = samsung['저가'].str.replace(',', '').astype('float')
samsung['종가'] = samsung['종가'].str.replace(',', '').astype('float')
# csv 파일에서 형식이 회계 또는 통화로 되어있을 경우 ',' 때문에 oject로 인식되므로 str(,) 삭제 후 형변환
samsung['외인비'] = samsung['외인비']*1400
# 외인비의 수치가 다른 특성에 비해 과도하게 낮으므로 1400을 곱하여 다른 특성과 비슷한 수치를 만들어줌
samsung = samsung.sort_index(ascending=False)
# samsung 출력 시, sort_index가 적용되지 않은 상태로 출력되므로 변수를 재선언하여 정렬이 적용된 내용을 담아줘야 함
# samsung = samsung.sort_values(ascending=True, by=['일자'])
'''
print(samsung[:1165].tail())
samsung datasets 마지막 행 제외 하위 5개 목록 출력: 2023.01.18-26
시가 고가 저가 종가 외인비
5 60,700 61,000 59,900 60,400 70126.0
4 60,500 61,500 60,400 61,500 70210.0
3 62,100 62,300 61,100 61,800 70238.0
2 63,500 63,700 63,000 63,400 70392.0
1 63,800 63,900 63,300 63,900 70476.0
'''
samsung_open = samsung['시가'][1:]
# 전일 데이터를 기반으로 익일 데이터를 예측하는 것으므로 y data로 사용할 시가 데이터는 첫 날(2018년 5월 4일) 데이터를 제외함
'''
print(samsung_open.head())
samsung_open datasets 상위 5개 목록 출력: 2015.05.08-14
1164 52,600
1163 52,600
1162 51,700
1161 52,000
1160 51,000
'''
x1_train, x1_test, y_train, y_test = train_test_split(
samsung[:1165], samsung_open,
shuffle=True,
train_size=0.7,
random_state=123
)
# samsung[:1165]: 훈련용 데이터에서 예측에 필요한 01월 27일자 데이터 제외
print(x1_train.shape, x1_test.shape) # (815, 5) (350, 5)
print(y_train.shape, y_test.shape) # (815,) (350,)
x1_train = x1_train.to_numpy()
x1_test = x1_test.to_numpy()
# reshape을 위한 DataFrame -> Numpy
x1_train = x1_train.reshape(815, 5, 1)
x1_test = x1_test.reshape(350, 5, 1)
# 1.2. Data Preprocessing(Amore)
amore = pd.read_csv(path+'stock_amore.csv', encoding='CP949', nrows=1166, usecols=[1,2,3,4,16])
# nrows=1902 (amore 액면분할 기준): Make sure all arrays contain the same number of samples.
# -> nrows=1166 (samsung 액면분할 기준 data shape과 맞춤)
amore['시가'] = amore['시가'].str.replace(',', '').astype('float')
amore['고가'] = amore['고가'].str.replace(',', '').astype('float')
amore['저가'] = amore['저가'].str.replace(',', '').astype('float')
amore['종가'] = amore['종가'].str.replace(',', '').astype('float')
amore['외인비'] = amore['외인비']*5700
amore = amore.sort_index(ascending=False)
x2_train, x2_test = train_test_split(
amore[:1165],
shuffle=True,
train_size=0.7,
random_state=123
)
print(x2_train.shape, x2_test.shape) # (815, 5) (350, 5)
# 2. Model Construction
# 2-1. Model_1(Samsung)
input1 = Input(shape=(5,1))
lstm1_1 = LSTM(units=64, return_sequences=True,
input_shape=(5,1))(input1)
# gru1_2 = GRU(64, activation='relu')(lstm1_1)
dense1_2 = Dense(32, activation='relu')(lstm1_1)
dense1_3 = Dense(16, activation='relu')(dense1_2)
flatten1_4 = Flatten()(dense1_3)
# A `Concatenate` layer requires inputs with matching shapes except for the concatenation axis.
output1 = Dense(16, activation='relu')(flatten1_4)
# 2-2. Model_2(Amore)
input2 = Input(shape=(5,))
dense2_1 = Dense(64, activation='relu')(input2)
dense2_2 = Dense(32, activation='linear')(dense2_1)
dropout2_3 = Dropout(0.1)(dense2_2)
dense2_4 = Dense(16, activation='linear')(dropout2_3)
output2 = Dense(8, activation='relu')(dense2_4)
# 2-3. Model_merge
merge3 = concatenate([output1, output2])
merge3_1 = Dense(64, activation='relu')(merge3)
merge3_2 = Dense(32, activation='relu')(merge3_1)
last_output = Dense(1)(merge3_2)
model = Model(inputs=[input1, input2], outputs=last_output)
model.summary()
# 3. compile and train
model.compile(loss='mse', optimizer='adam')
earlyStopping = EarlyStopping(monitor='val_loss', mode='min', patience=64, restore_best_weights=True, verbose=1)
modelCheckPoint = ModelCheckpoint(monitor='val_loss', mode='auto', verbose=1,
save_best_only=True,
filepath='./_save/MCP/samsumg_open_MCP.hdf5')
model.fit([x1_train, x2_train], y_train,
validation_split=0.2,
callbacks=[earlyStopping, modelCheckPoint],
epochs=256,
batch_size=64)
# 4. evaluate and predict
loss = model.evaluate([x1_test, x2_test], y_test)
result = model.predict([samsung[1165:].to_numpy().reshape(1,5,1),amore[1165:]])
# train data shape과 predict data shape 맞추기
print("Samsung Electronics market price prediction : ", result)
'''
Result
Samsung Electronics market price prediction : [[65331.867]]
'''
3.1. 모델 구축
3.1.1. 4년 6개월 삼성전자 주가 및 관련 자료를 기반으로 한 훈련 모델1 구축
3.1.2. 4년 6개월 아모레 퍼시픽 주가 및 관련 자료를 기반으로 한 훈련 모델2 구축
3.1.3. 모델1, 모델2 병합을 통한 새로운 모델3을 구축하여 01월 30일 월요일 삼성전자 시가 예측
3.2. 훈련 및 예측
Samsung Electronics market price prediction : [[65331.867]]
3.3. 평가
(실제) 01월 30일자 삼성전자 시가: 64,900원
(예측) 01월 30일자 삼성전자 시가: 65,331원
(차이): + 431원
3.4. 시사점 및 한계점
3.4.1. 시사점
서로 연관이 없는 주식간에도 모델을 구축해서 훈련을 시킬 때, 현재 주가에서 크게 벗어나지 않는 결과값, 즉 유의미한 결과값을 도출할 수 있음
3.4.2. 한계점
크게 벗어나지 않을 예정이기에 해당 근처값을 목표로 모델을 훈련시킴
예상되는 값이 나올 때까지 Hyper-parameter tuning*을 반복하며 훈련 및 예측을 반복하였음
현재 주가와 비슷한 데이터가 나올 경우, 로스값이 매우 높게 나와서 모델 구성과 훈련의 적합성에 대한 의문이 존재함
-> 최소의 Loss와 최적의 weight를 만들기위해 튜닝을 진행할 경우, 로스는 낮아지지만 현재 주가와는 차이가 커져서 오히려 모델의 성능을 하향 조정하면서 훈련 진행함
* Hyper-parameter Tuning 내용
Model
- 삼성전자 dataset: Bidirectional(LSTM) + GRU model을 구현
예상 주가가 5만원대에 머물러 LSTM 단방향으로 진행
return_sequences=True -> Ouput_shape을 Input과 동일하게 유지하고 Flatten() 진행
-> RNN model로 다뤄질 dataset이 시계열 자료가 아닌 경우, 오히려 저성능을 보일 수 있음
Bidirectional(LSTM) Oput data가 시계열 자료인지 확인 필요
- 아모레 dataset: DNN model 구성
- Merge model: DNN model 구성
- Model Layer의 node를 일괄적으로 128로 시작했으나, 예상 주가가 5만원대로 도출되어 64로 하향 조정
- Model Layer 수를 dropout 포함 5~7 층으로 구성하였으나 6만원 초반이상으로 주가가 오르지 않음
-> Dropout은 예측과 동일하게 사용 시, 기대되는 시가 가격에서 멀어짐
Dropout은 과적합 문제를 해결하기 위해 사용되는데 Dataset의 크기가 1200이면 작은 편에 속하는 것인지 과적합 해결보다는 오히려 성능이 하향됨
- 모든 모델의 activation function은 relu/linear로 진행
-> 입출력값이 모두 비슷하고 양수이기에 두 함수의 차이에 크게 의미를 두지 않음
Training
- Earlystopping을 사용하여 훈련의 종료가 fit의 epoch보다 적게 이뤄지므로 튜닝 대상을 epoch보다는 patience로 지정
- EarlyStopping, ModelCheckPoint 기준을 validation loss로 지정하기 위해 훈련 데이터의 20%를 validation data로 지정
- EarlyStopping argument 중 restore_best_weights=True로 지정하여, restore 지점을 훈련 종료 시가 아닌 best weight 지점으로 수정
- batch size(1 epoch 당 함께 훈련될 data의 개수): 16~64 정도의 수치에서 1월 27일자 주가와 유사하게 도출됨
4. 프로젝트 수행 소감
인공지능 과정을 배우고 처음으로 데이터 전처리와 더불어 모델 구축을 해보았는데, 구현하고자 하는 바와 구현할 수 있는 실력의 차이를 많이 느낄 수 있었습니다.
- matplotlib.plot() 선 그래프를 활용하여 데이터 특성간의 유사도를 찾아보고 싶었으나, 그래프 사이즈가 일정 크기 이상으로 증가하지 않아 최종적으로는 엑셀을 활용하여 판단였습니다.
- 시간 상의 문제로 원본 데이터 이외의 자료를 이용하지 못하여 아쉬움이 남습니다.
- 주말 이후에 처음 거래되는 월요일 시가 예측이므로 다른 요일과 다른 특징이 있는지 확인해기 위해 요일 자료를 따로 추출해서 사용하고 싶었습니다. 그러나 Python에 대한 기본 문법을 모르는 상황에서 요일을 추출해내고 해당 자료만 추출해서 사용하기에는 시간이 부족하다고 판단하여 넘어간 것이 아쉬움으로 남습니다.
그럼에도 불구하고 이번 주가 예측 모델을 구축하면서 큰 성취감도 느낄 수 있었습니다.
- 무엇보다 포기하지 않고 마무리를 지을 수 있게되어 기쁜 마음이 큽니다. 약 12시간동안 다양한 오류에도 포기하지 않고 현재 삼성전자 주가(64,600원)와 비슷한 예측치를 만들 수 있게 되었습니다.
- Dacon에서의 서울시 따릉이 수요 예측, Kaggle에서의 Bike 수요 예측을 진행할 때에는 수업에서 함께 모델을 구축하였기 때문에 어떤 데이터 특성을 넣고 결측치 처리는 어떻게 할 것인지에 대한 고민을 해보지 못했습니다. 그러나 삼성전자 시가 예측 프로젝트를 통해서 데이터 전처리 방법에 대해 스스로 생각해보고 그간 배웠던 지식으로 더 좋은 성능을 만들기 위해서 다양한 Hyper-Parameter Tuning을 진행하며 loss값을 줄이기 위한 노력이 기억에 많이 남습니다.
- 지식적인 측면에서도 데이터를 처리하는 과정에서 중요한 것은 데이터의 타입과 형태라는 것을 다시 한 번 배우게 되었습니다.
데이터 전처리 과정을 약 6시간 정도 소요하고, 모델 구현부터는 빠르게 진행할 수 있을 것이라고 생각하였는데 csv 파일 형식에서 data type이 통화나 회계로 입력될 경우, string 처리되어 모델을 구현할 수 없는 문제가 발생한다는 것을 알게되었으며 해결 방법 또한 찾을 수 있었습니다.
또한 데이터의 타입(pandas, numpy 등)에 따라 사용할 수 있는 메서드들이 상이하다는 점을 RNN 구현을 위한 reshpae 과정에서 배울 수 있게되었습니다.
- nrows, usecols 등 일부 데이터만 활용하기 위해 다양한 메서드들을 배울 수 있게되었으며, 사용하는 것에 어려움을 느끼던 data split의 방법 중 하나인 slicing을 이번 프로젝트를 통해 원할하게 사용할 수 있게 되었습니다.
- 자주했던 실수이기에 잊지 않고자 다시금 적어보자면 training data set shape과 predict data set shape은 언제나 일치해야합니다. 간단하게 이야기를 하자면 데이터를 특정한 형태로 훈련을 수행했다면 예측도 훈련과 동일한 환경에서 이뤄질 수 있도록 준비가 되어야 한다는 뜻입니다.
향후에는 Python 공부 및 심층신경망을 활용한 인공지능 과정에 대해 스스로 공부하면서 부족한 점이 많았을 이번 프로젝트를 다시 한 번 진행해보고 싶습니다. 또한 이번 프로젝트를 진행하면서 모델을 온전히 구현할 수 있었던 것은 인터넷 상에서 다양한 지식을 공유해주신 많은 분들의 도움 덕분이라고 생각합니다. 다시 한 번 감사한 마음을 담아 저 또한 제가 배우고 알게된 지식들을 정확히 또 꾸준히 공유해가도록 하겠습니다.
마지막으로 간략하게 소감과 더불어 제가 그리는 개발자의 모습에 대해 적도록 하겠습니다.
엑셀을 통해서 간단하게 진행하던 작업을 Python언어로 코드를 작성하며 많은 어려움을 느꼈습니다. 간단하게는 덧셈, 곱셈 등 단순 연산에서부터 그래프 그리기까지 검색한 결과와 동일하게 코드를 작성해도 오류가 나기 부지기수였습니다. 어제와 오늘 이러한 과정을 계속해서 반복하며 사용자에게 편리한 기능을 제공하기 위해 얼마나 많은 개발자분들의 노력이 필요했을지 생각을 해볼 수 있게 되었습니다.
이를 통해 저는 막연하게 개발자가 되겠다는 다짐에서 '어떤' 개발자가 되겠다는 결심을 할 수 있었습니다. 저의 삶의 태도처럼 적어도 받는 만큼은 베풀기 위해, 개발자로서 사용자분들께 다양한 편의를 제공해드리고 제가 배운 것과 알게된 것을 필요한 분들을 위해 꾸준히 공유해나가고 싶습니다.
이상 장장 12시간의 삼성전자 시가 예측 모델 구현 프로젝트에 대한 포스팅을 마치도록 하겠습니다.
감사합니다.
➕ ModelCheckPoint model load 시, 가중치 변화 문제
Save ModelCheckPoint 시, Result
Load ModelCheckPoint 시, Result
Predict 확인 후, MCP로 .hdf5 file로 가중치를 저장하였으나 load 할 때 Predict가 틀어지는 문제 발생
-> 당시 시가와 유사한 예측값을 도출한 후, 모델 훈련을 진행하지 않아 근본적인 원인은 찾지 못하였응, 그러나 모델을 저징한 후에 load 과정을 통해서 확인을 해봐야한다는 것을 배움
(+ CPU가 pentium임에도 불구하고 열심히 모델을 돌려준 제 오래된 노트북 LG 그램에게도 감사한 마음을 전합니다,
LG 전자 파이팅..!)
➕ Python을 활용한 그래프 그리기
# 시가의 추세선과 유사한 모양을 그리는 특성을 찾기 위한 시각화
import pandas as pd
import matplotlib.pyplot as plt
path = './keras/'
samsung = pd.read_csv(path+'stock_samsung.csv', encoding='CP949', nrows=1166, usecols=[1,2,3,4,16])
start = samsung['시가']
high = samsung['고가']
low = samsung['저가']
end = samsung['종가']
trading_vol = samsung['거래량']
transaction_amnt = samsung['금액(백만)']
retail_i = samsung['개인']
institutional_i = samsung['기관']
foreign_i = samsung['외인(수량)']
foreign_institutional_i = samsung['외국계']
program = samsung['프로그램']
foreign_i_ratio = samsung['외인비']
date = samsung['일자']
plt.figure(figsize=(500, 50)) # length, height
plt.plot(date, start)
plt.plot(date, high)
plt.plot(date, low)
plt.plot(date, end)
plt.plot(date, trading_vol)
plt.plot(date, transaction_amnt)
plt.plot(date, retail_i)
plt.plot(date, institutional_i)
plt.plot(date, foreign_i)
plt.plot(date, foreign_institutional_i)
plt.plot(date, program)
plt.plot(date, foreign_i_ratio)
plt.gca().axes.xaxis.set_visible(False) # x 축값 생략
plt.gca().axes.yaxis.set_visible(False) # y 축값 생략
plt.show()
➕ Pandas/Python Data Type
Pandas dtype | Python type | 비고 |
object | str | 문자열(char 포함) |
int64 | int | 숫자 |
float64 | float | 부동 소수점 |
bool | bool | True or False |
datetime64 | datetime | 시간, 날짜 값 |
소스 코드
🔗 HJ0216/TIL
📚 참고 자료
한국 거래소 전체 지수 시세 통계 자료
삼성전자 증권 정보
[파이썬] pandas로 cvs에서 특정 값을 가진 행 찾기
판다스(Pandas) df.head(), df.tail()
01. 데이터 값을 기준으로 데이터 정렬: sort_values()
파이썬 matplotlib 그래프 축 없애기
판다스(Pandas)에서 엑셀, CSV 파일의 일부만 불러오기 데이터 타입과 포맷 지정하기
[파이썬 pandas] CSV 파일의 숫자를 문자열(string)로 읽어 들이는 방법(dtype 옵션 사용방법)
[Python pandas] DataFrame에서 천 단위 숫자의 자리 구분 기호 콤마(',')를 없애는 방법
[Python] Pandas DataFrame을 numpy 배열로 변환하는 방법
'Naver Clould with BitCamp > Aartificial Intelligence' 카테고리의 다른 글
LSTM, Bidirectional, Conv1D (0) | 2023.01.30 |
---|---|
[Warning] Allocation of ... exceeds 10% of free system memory (0) | 2023.01.29 |
RNN Model Construction (0) | 2023.01.27 |
CNN Model Construction2 (0) | 2023.01.26 |
Save model and weights (0) | 2023.01.24 |