Keras를 통한 LSTM 구현
/* 2017.8.29 by. 3months */
- LSTM에 대한 개념 학습
http://blog.naver.com/kmkim1222/221069000196
https://www.youtube.com/watch?v=6niqTuYFZLQ&list=PL3FW7Lu3i5JvHM8ljYj-zLfQRF3EO8sYv
- 코드 및 데이터 출처 -https://github.com/Dataweekends/zero_to_deep_learning_udemy
LSTM 개념
LSTM은 기존 vanilla RNN을 개선한 모델로써 데이터의 long-term dependency를 학습하는데 효과적인 모델입니다. 또한 내부적으로 구성요소간에 additive, elementwise interaction을 통해 backpropagation 할 때, gradient explosion, vanishing 문제를 기존 RNN에 비해 매우 줄인 것이 중요한 개선점입니다.
코드 실습을 통해 LSTM을 학습하기 위해서는 어떠한 데이터 구조가 필요한지, 즉, INPUT은 어떤 모양이어야 하고, OUTPUT이 어떻게 생성되며, 네트워크 구조를 코드 상으로 어떻게 정의하는지 알아보겠습니다.
사용할 데이터
cansim-0800020-eng-6674700030567901031.csv
데이터 임포트
데이터를 임포트합니다. 데이터는 날짜별로 특정한 값이 있는 데이터입니다. Unadjusted이 Raw 값이고, Seaonally adjusted는 Season의 효과를 보정한 값입니다. 보통 주식과 같은 시계열 데이터는 Season에 의한 효과가 있는데, Seasonally adjusted는 이 Season에 의한 효과를 보정하여 전체적인 추세만 보고자할 때 사용하는 변수입니다. 우리가 학습할 변수는 Season에 의한 효과를 보정하지 않은 Unadjusted입니다.
import pandas as pd import numpy as np %matplotlib inline import matplotlib.pyplot as plt df = pd.read_csv('data/cansim-0800020-eng-6674700030567901031.csv', skiprows=6, skipfooter=9, engine='python') df.head()
Adjustments | Unadjusted | Seasonally adjusted | |
---|---|---|---|
0 | Jan-1991 | 12588862 | 15026890 |
1 | Feb-1991 | 12154321 | 15304585 |
2 | Mar-1991 | 14337072 | 15413591 |
3 | Apr-1991 | 15108570 | 15293409 |
4 | May-1991 | 17225734 | 15676083 |
전처리
from pandas.tseries.offsets import MonthEnd df['Adjustments'] = pd.to_datetime(df['Adjustments']) + MonthEnd(1) df = df.set_index('Adjustments') print(df.head()) df.plot()
우선 문자열로된 Adjustments 변수를 datetime 객체로 만들어주고, MonthEnd(1)을 더하면 해당 월의 맨 끝 날짜가 지정되게 됩니다. 그리고 이 날짜를 index로 만들어줍니다. 이후에 plot을 하면 날짜를 index로 해서 Unadjusted와 Seasonally adjusted 변수의 그래프를 그릴 수 있습니다.
Unadjusted | Seasonally adjusted | |
---|---|---|
Adjustments | ||
1991-01-31 | 12588862 | 15026890 |
1991-02-28 | 12154321 | 15304585 |
1991-03-31 | 14337072 | 15413591 |
1991-04-30 | 15108570 | 15293409 |
1991-05-31 | 17225734 | 15676083 |
인덱스가 0,1,2,3 ... 에서 날짜로 변했음을 알 수 있습니다. (가장 왼쪽열. 또한 index의 이름이 Adjustements가 되었음을 알 수 있음.)
우리가 학습할 변수는 파란색 선 Unadjusted 입니다.
트레이닝셋 테스트셋 SPLIT
2011/1/1 을 기준으로 트레이닝셋과 테스트셋으로 구분합니다. 그리고 학습과 테스트에 사용할 Unadjusted 변수만 남겨놓습니다.
split_date = pd.Timestamp('01-01-2011') # 2011/1/1 까지의 데이터를 트레이닝셋. # 그 이후 데이터를 테스트셋으로 한다. train = df.loc[:split_date, ['Unadjusted']] test = df.loc[split_date:, ['Unadjusted']] # Feature는 Unadjusted 한 개 ax = train.plot() test.plot(ax=ax) plt.legend(['train', 'test'])
변수 Scaling
MinMax Scaling을 통해 변수를 Scaling합니다. MinMax Scaling : http://3months.tistory.com/167
그러면 아래와 같이 Scaling 된 것을 볼 수 있고, scaling의 결과는 2차원의 numpy ndarray 타입으로 변환이 되게 됩니다.
from sklearn.preprocessing import MinMaxScaler sc = MinMaxScaler() train_sc = sc.fit_transform(train) test_sc = sc.transform(test) train_sc
(240, 1) [[ 0.01402033] [ 0. ] [ 0.0704258 ] [ 0.09531795] [ 0.16362761] [ 0.13514108] [ 0.12395846] [ 0.12617398]
......
Pandas Dataframe으로 변환
이를 다시 pandas dataframe 데이터 타입으로 변환해봅시다. Dataframe에서 작업을 해야지 Window를 만들기 유용하기 때문입니다. Window는 RNN을 트레이닝 하기위한 단위로 Window size는 Timestep과 같다고 생각하시면 됩니다.
train_sc_df = pd.DataFrame(train_sc, columns=['Scaled'], index=train.index) test_sc_df = pd.DataFrame(test_sc, columns=['Scaled'], index=test.index) train_sc_df.head()
Scaled | |
---|---|
Adjustments | |
1991-01-31 | 0.014020 |
1991-02-28 | 0.000000 |
1991-03-31 | 0.070426 |
1991-04-30 | 0.095318 |
1991-05-31 | 0.163628 |
pandas shift를 통해 Window 만들기
shift는 이전 정보 다음 row에서 다시 쓰기 위한 pandas의 함수입니다. 이를 통해 아래와 같이 과거의 값들을 shift_s 와 같은 형태로 저장할 수 있습니다. 과거값은 총 12개를 저장하며, timestep은 12개가 됩니다. 우리의 목적은 과거값 shift1~12를 통해 현재값 Scaled를 예측하는 것입니다.
for s in range(1, 13): train_sc_df['shift_{}'.format(s)] = train_sc_df['Scaled'].shift(s) test_sc_df['shift_{}'.format(s)] = test_sc_df['Scaled'].shift(s) train_sc_df.head(13)
Scaled | shift_1 | shift_2 | shift_3 | shift_4 | shift_5 | shift_6 | shift_7 | shift_8 | shift_9 | shift_10 | shift_11 | shift_12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Adjustments | |||||||||||||
1991-01-31 | 0.014020 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1991-02-28 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1991-03-31 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1991-04-30 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1991-05-31 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1991-06-30 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1991-07-31 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN | NaN | NaN |
1991-08-31 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN | NaN |
1991-09-30 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN | NaN |
1991-10-31 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN | NaN |
1991-11-30 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 | NaN | NaN |
1991-12-31 | 0.200913 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.01402 | NaN |
1992-01-31 | 0.030027 | 0.200913 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.00000 | 0.01402 |
트레이닝셋과 테스트셋 만들기
dropna를 통해 NaN이 있는 데이터를 제거하고, shift_1 ~ shift_12는 X로 Scaled는 Y로 지정을 합니다.
X_train = train_sc_df.dropna().drop('Scaled', axis=1) y_train = train_sc_df.dropna()[['Scaled']] X_test = test_sc_df.dropna().drop('Scaled', axis=1) y_test = test_sc_df.dropna()[['Scaled']]
최종 트레이닝셋과 테스트셋의 모양은 아래와 같습니다.
X_train.head()
shift_1 | shift_2 | shift_3 | shift_4 | shift_5 | shift_6 | shift_7 | shift_8 | shift_9 | shift_10 | shift_11 | shift_12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Adjustments | ||||||||||||
1992-01-31 | 0.200913 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 | 0.014020 |
1992-02-29 | 0.030027 | 0.200913 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 | 0.000000 |
1992-03-31 | 0.019993 | 0.030027 | 0.200913 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 | 0.070426 |
1992-04-30 | 0.065964 | 0.019993 | 0.030027 | 0.200913 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 | 0.095318 |
1992-05-31 | 0.109831 | 0.065964 | 0.019993 | 0.030027 | 0.200913 | 0.131738 | 0.111395 | 0.092309 | 0.126174 | 0.123958 | 0.135141 | 0.163628 |
y_train.head()
Scaled | |
---|---|
Adjustments | |
1992-01-31 | 0.030027 |
1992-02-29 | 0.019993 |
1992-03-31 | 0.065964 |
1992-04-30 | 0.109831 |
1992-05-31 | 0.149130 |
다시 ndarray로 변환하기
데이터구조를 다시 ndarray로 변환시켜줍니다. 실제 deep learning 모델의 트레이닝과 테스트로 사용되는 데이터는 일반적으로 numpy의 ndarray입니다. 아래와 같이 .values를 통해 ndarray 값을 얻을 수 있습니다. 트레이닝 데이터는 timestep=12, size = 228 입니다.
X_train = X_train.values X_test= X_test.values y_train = y_train.values y_test = y_test.values
print(X_train.shape) print(X_train) print(y_train_shape) print(y_train)
(228, 12) [[ 0.20091289 0.13173822 0.11139526 ..., 0.0704258 0. 0.01402033] [ 0.03002688 0.20091289 0.13173822 ..., 0.09531795 0.0704258 0. ] [ 0.01999285 0.03002688 0.20091289 ..., 0.16362761 0.09531795 0.0704258 ] ..., [ 0.79916654 0.81439355 0.86398323 ..., 0.92972161 0.71629034 0.77368724] [ 0.80210057 0.79916654 0.81439355 ..., 0.59734863 0.92972161 0.71629034] [ 0.81482896 0.80210057 0.79916654 ..., 0.53166512 0.59734863 0.92972161]] (228, 1) [[ 0.03002688] [ 0.01999285] [ 0.06596369] [ 0.10983126] [ 0.14912986]
....
최종 트레이닝셋과 테스트셋의 X 만들기
이부분이 중요한데, keras에서는 RNN 계열의 모델을 트레이닝할 대 요구되는 데이터의 형식이 있습니다. 바로 3차원 데이터여야하며 각각의 차원은 (size, timestep, feature) 을 순서대로 나타내주어야하는 것입니다. 따라서 이 형태로 데이터를 reshape 해주어야합니다. 일반적인 MLP 모델에서는 size와 feature만 있기 때문에 2차원이지만, RNN에서는 "시간" 이라는 개념이 있기 때문에 차원이 한 차원 늘어나게 된 것입니다. 합리적인 데이터 구조라고 볼 수 있습니다.
아래 코드를 통해 Training set과 Test set의 X를 RNN 학습에 맞는 형태로 reshape 해줍니다.
X_train_t = X_train.reshape(X_train.shape[0], 12, 1) X_test_t = X_test.reshape(X_test.shape[0], 12, 1)
print("최종 DATA") print(X_train_t.shape) print(X_train_t) print(y_train)
최종 DATA (228, 12, 1) [[[ 0.20091289] [ 0.13173822] [ 0.11139526] ..., [ 0.0704258 ] [ 0. ] [ 0.01402033]] [[ 0.03002688] [ 0.20091289] [ 0.13173822] ..., [ 0.09531795] [ 0.0704258 ] [ 0. ]]
LSTM 모델 만들기
아래와 같이 keras를 통해 LSTM 모델을 만들 수 있습니다. input_shape=(timestep, feature)으로 만들어줍니다. size는 모델 설계시에는 중요하지 않으므로, feature, timestep만 모델에 알려주면 됩니다. 또 예측하고자하는 target의 갯수가 1이므로 마지막에 Dense(1)을 하나 추가해줍니다. 또한 실제 연속적인 값을 예측하는 것이기 때문에 loss function은 mean squared error가 됩니다. 또한 일반적으로 optimizer는 adam을 자주 사용합니다.
from keras.layers import LSTM from keras.models import Sequential from keras.layers import Dense import keras.backend as K from keras.callbacks import EarlyStopping K.clear_session() model = Sequential() # Sequeatial Model model.add(LSTM(20, input_shape=(12, 1))) # (timestep, feature) model.add(Dense(1)) # output = 1 model.compile(loss='mean_squared_error', optimizer='adam') model.summary()
모델 Fitting
모델을 Fitting한다는 것은 Training data set으로 optimization 과정을 통해 모델의 weight를 찾는 것입니다. early stopping 객체를 이용해 epoch마다 early stopping을 체크합니다. 아래와 같은 결과를 내면서 트레이닝이 잘 되는 것을 확인할 수 있습니다.
early_stop = EarlyStopping(monitor='loss', patience=1, verbose=1) model.fit(X_train_t, y_train, epochs=100, batch_size=30, verbose=1, callbacks=[early_stop])
Epoch 1/100 228/228 [==============================] - 0s - loss: 0.3396 Epoch 2/100 228/228 [==============================] - 0s - loss: 0.2958 Epoch 3/100 228/228 [==============================] - 0s - loss: 0.2551 Epoch 4/100 228/228 [==============================] - 0s - loss: 0.2175
...
학습된 모델을 통해 테스트셋 Test 하기
테스트셋은 2011/1/1 이후의 데이터를 나타냅니다. 트레이닝에는 1991년~ 2010년 까지 데이터가 이용되었기 때문에, 이 실습은 1991년~2010년 데이터를 통해 2011년 이후의 데이터를 예측하는 것으로 볼 수 있습니다.
테스트셋의 모양은 아래와 같이 생겼습니다. 당연한 이야기지만 트레이닝셋과 구조가 같습니다.
print(X_test_t)
[[[ 1.06265011] [ 0.87180554] [ 0.84048091] [ 0.86220767] [ 0.88363094] [ 0.89302107] [ 0.92552046] [ 0.89993326] [ 0.83505683] [ 0.77259579] [ 0.56926634] [ 0.61423187]]
....
model.predict를 통해 테스트셋의 X에 대한 예측값 y_hat을 얻을 수 있습니다.
y_pred = model.predict(X_test_t) print(y_pred)
[[ 0.82410765] [ 0.85221326] [ 0.88795143] [ 0.90008551] [ 0.90281236] [ 0.90063977] [ 0.89379823] [ 0.88957471] [ 0.88779473]
...
'Tools > Keras' 카테고리의 다른 글
Keras- Tensorflow Backend에서 특정 디바이스 사용법 (1) | 2017.11.01 |
---|---|
Keras - CNN ImageDataGenerator 활용하기 (11) | 2017.10.28 |
Keras - 모델 저장하고 불러오기 (3) | 2017.07.19 |
Keras와 Tensorflow 사용할 때 유용한 아나콘다 가상환경 (0) | 2017.07.01 |
Keras - Backend 설정하기 (Theano, Tensorflow) (2) | 2017.07.01 |