반응형


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]

...


반응형