RNN이란
RNN(순환신경망, Recurrent Neural Network)는 주로 시계열 분석, 자연어 처리 등 순서가 있는 데이터에 사용하면 좋은 결과가 있는 모델이다. 실제로 주식, 파생상품, 기상, 전력량 등을 계산할 때 매우 유효하고 캐글이나 해커톤 등의 대회에서 기계열 문제를 다룰 때 많이 사용한다.
위 그림을 보면 x는 1부터 5까지로 구성되어 있고, y는 아직 모른다.
물론 경험상 6이 나올 거라는 예상은 되지만, 머신은 아직 모른다.
우선 x1에 1이 입력되고 훈련이 시작된다. 첫 번째 노드에서 h(hypersis)와 w(가중치)를 구한다.
이를 다음 노드에 전달하고 x2로 2가 입력된다. 이때, 이전 노드에서 받은 w와 h 그리고 이번에 받아들인 2를 같이 연산하여 다시 w와 h를 구한다. 이렇게 계속 진행하여 마지막 x5에 5를 입력하고, 그전에 받은 w와 h를 같이 연산하면 마지막에 y를 구한다. 연산이 잘 된다면 6이 나올 것이다. 여기까지가 RNN의 1회 연산이다.
SimpleRNN
가장 기분적인 RNN이다.
#1. 데이터
import numpy as np
x_train = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
y_train = np.array([6,7,8])
print("x_train.shape : ", x_train.shape)
print("y_train.shape : ", y_train.shape)
결과
x_train.shape : (3, 5)
y_train.shape : (3,)
x_train은 1에서 5, 2에서 6, 3에서 7로 총 3개의 리스트이다.
shape는 (3,5) 이다. y_train은 x_train에 대응하는 3개짜리 벡터로 구성되어 있다. shape는 (3,)이다.
여기서 x_train의 컬럼과 y_train의 벡터의 크기를 맞추기 위해 x_train을 reshape하겠다.
#1. 데이터
import numpy as np
x_train = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
y_train = np.array([6,7,8])
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], 1)
print("x_train.shape : ", x_train.shape)
print("y_train.shape : ", y_train.shape)
결과
x_train.shape : (3, 5, 1)
y_train.shape : (3,)
여기서 주의 할 것은 x의 데이터 구조이다. (3행, 5열, 1feature)
[[[1],[2],[3],[4],[5]],
[[2],[3],[4],[5],[6]],
[[3],[4],[5],[6],[7]]]
y의 벡터는 행의 개수와 대응한다. 그래서 x의 행의 수 = y의 벡터 크기와 같다. 그래서 1,2,3,4,5를 연산해서 6이 나오고 2,3,4,5,6을 해서 7이 나오는 식의 매치가 된다.
이제 x_train의 행의 수와 y_train의 벡터의 크기가 일치한다.
1부터 5까지의 인풋으로 6이 나오고, 2부터 6까지의 인풋으로 7이 아웃풋, 마지막으로 3부터 7까지 인풋으로 8이 나오는 모델을 훈련시킨다고 생각하면 된다.
모델을 구성하자. 텐서플로 2.0(케라스)에서는 이미 RNN에 대한 모델이 모두 구현되어 있다.
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, SimpleRNN
model = Sequential()
model.add(SimpleRNN(7, input_shape = (5, 1), activation = 'relu'))
model.add(Dense(4))
model.add(Dense(1))
model.summary()
SimpleRNN이 추가로 import 되었다. 바로 우리가 필요로 하는 RNN 모델이다. 케라스에서는 이미 이렇게 구현되어 있다. 사용법도 간단하다. 처음에 아웃풋 노드의 개수(7) 그리고 input_shape를 명시해준 다음, 모델에 add해주면 된다.
그런데 이전과 다른점이 있다. input_shape가 2차원이다. 이전 Dense모델을 할 때까지만 해도 input_shape는 1차원이었고 컬럼의 개수만 맞춰주면 되었는데 RNN에서는 뜬금없이 2차원이다.
모델 구성에서 가장 중요한 것은 shape이다. 입력의 모양만 알고 shape만 정확히 명시하면 지구상 어떤 데이터로 어떤 모델이든지 만들 수 있다. 가장 많이 실수하는 부분이 shape다.
이것만 명확히 이해하면 쉽다.
input_shpae=(5,1)
여기서 5는 x의 열(컬럼)의 수이다. 물론 y의 벡터 자리 수와도 동일하다. 그렇다면 1은 무엇일까?
1부터 5까지의 연산을 할 때 몇 개씩 데이터를 연산을 했을까?
x1과 w와 h를 연산해서 x2에 전달
x2과 w와 h를 연산해서 x3에 전달
..
x5과 w와 h를 연산해서 y에 전달
1개씩 잘라서 연산을 했다. 1개씩 묶어서 작업을 했다고도 할 수 있다.
RNN의 input_shape를 행과 열로 몇 개씩 자르는지로 shape를 설명할 수 있다.
정식 용어로 하면 sampes, time steps, feature라고 한다.
이제 (5,1)의 뜻을 파악했다. 하지만 현재 x의 shape는 (5,3)이다. 우리는 앞에서 회귀 모델을 설명하면서 input_shape를 구성할 때 중요한점 하나를 배웠다. 바로 '행 무시'라는 부분이다.
입력에서 중요한 것은 열(컬럼)이다. 그래서 실제로 (5,1)의 의미는 (None, 5,1)이다. 여기서 None은 행, 5는 컬럼, 1은 앞에서 말했듯이 몇 개씩 잘랐는지 이다.
그렇다면 원 소스에서 x 데이터를 현재 shape의 모델에 맞게 reshape해주어야 한다.
(3,5)->(3,5,1)
이렇게 변경하면 모델의 input_shape에서 인식할 때 행은 무시하고 (None, 5,1)의 형식으로 인식하고 모델이 정확하게 돌아간다.
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], 1)
그래서 위 한 줄을 추가했다.
x_train.shape[0]는 기존 x_train의 행(=3)이고 , x_train.shape[1]은 기존 x_train의 열(=5)이다. 이로써 x_train의 shape가 (3,5,1)이 되었다.
Total params: 100 Trainable params: 100 Non-trainable params: 0
_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
simple_rnn (SimpleRNN) (None, 7) 63
_________________________________________________________________
dense (Dense) (None, 4) 32
_________________________________________________________________
dense_1 (Dense) (None, 1) 5 =================================================================
input_shape가 (5,1)이므로 첫 번째 인풋은 (None,5,1)이고 이후 각 레이어당 아웃풋은 (None,7),(None,4),(None,1)이다.
그런데 첫 번째 SimpleRNN의 레어어의 Param이 63이다. 우리가 Dense로 했을 경우 통상 20 정도((인풋노드수 + 바이어스) * 아웃풋노드수 = (5+1) * 7 = 42) 인데, 63이나 된다. 인풋과 아웃풋이 더 커질수록 RNN의 param 수는 훨씬 더 증가합니다. 그만큼 단순 DNN보다 더 많은 연산이 된다고 이해하면 된다.
#1. 데이터
import numpy as np
x_train = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
y_train = np.array([6,7,8])
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], 1)
print("x_train.shape : ", x_train.shape) #(3,5,1)
print("y_train.shape : ", y_train.shape) #(3,)
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, SimpleRNN
model = Sequential()
model.add(SimpleRNN(7, input_shape = (5, 1), activation = 'relu'))
model.add(Dense(4))
model.add(Dense(1))
model.summary()
#3. 훈련
model.compile(loss='mse', optimizer='adam', metrics=['mse'])
model.fit(x_train, y_train, epochs=100, batch_size=1)
#4. 예측
x_predict = np.array([[4,5,6,7,8]])
print(x_predict.shape) #(1,5)
x_predict = x_predict.reshape(x_predict.shape[0],x_predict.shape[1], 1)
print("x_predict.shape : ", x_predict.shape) # (1,5,1)
y_predict = model.predict(x_predict)
print("예측값 :", y_predict)
test갑을 만들지 않으므로 평가는 생략하고 새로운 값으로 예측을 하였다.
예측용 x_predict
4,5,6,7,8을 예측용 데이터로 이 훈련된 RNN모델에 넣었다.
input_shape에 맞도록 (1,5)로 reshape해서 (1,5,1)로 맞췄다. 이 데이터의 행은 한가지이므로 1, 열은 동일하게 5, feature는 1이다. 예측용 데이터의 shape는 (1,5,1)이 된다.
y_predict = model.predict(x_predict)
print("예측값 : ", y_predict)
결과
예측값 : x_predict.shape : (1, 5, 1)
예측값 : [[9.587747]]
LSTM
RNN에서 가장 많이 사용하는 모델이라 RNN이라고하면 그냥 LSTM으로 통용되는 경우가 종종 있다. 원리는 거의 동일하지만 SimpleRNN보다 파라미터의 수가 월등히 많아지고 성능이 좋다.
#1. 데이터
import numpy as np
x_train = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
y_train = np.array([6,7,8])
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], 1)
print("x_train.shape : ", x_train.shape) #(3,5,1)
print("y_train.shape : ", y_train.shape) #(3,)
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, LSTM
model = Sequential()
model.add(LSTM(7, input_shape = (5, 1), activation = 'relu'))
model.add(Dense(4))
model.add(Dense(1))
model.summary()
#3. 훈련
model.compile(loss='mse', optimizer='adam', metrics=['mse'])
model.fit(x_train, y_train, epochs=100, batch_size=1)
#4. 예측
x_predict = np.array([[4,5,6,7,8]])
print(x_predict.shape) #(1,5)
x_predict = x_predict.reshape(x_predict.shape[0],x_predict.shape[1], 1)
print("x_predict.shape : ", x_predict.shape) # (1,5,1)
y_predict = model.predict(x_predict)
print("예측값 :", y_predict)
SimpleRNN 대신 LSTM을 import 하였고 첫 번째 히든 레이어에 SimpleRNN 대신 LSTM을 사용했다. 사용법이 똑같다.
_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
lstm (LSTM) (None, 7) 252
_________________________________________________________________
dense_14 (Dense) (None, 4) 32
_________________________________________________________________
dense_15 (Dense) (None, 1) 5 =================================================================
Total params: 289 Trainable params: 289 Non-trainable params: 0
예측값 : [[10.7888155]]
Summary 결과를 보면 첫 번째 히든 레이어에서 63이었으나, LSTM에서는 252이다. Total params의 경우에도
100에서 289로 증가하였다. 같은 시계열 모델임에도 LSTM이 simpleRNN에 비해 약 5배 정도 연산을 더 하는 셈이다.
연산을 많이 한다고 무조건 좋은 것은 아니다.
실행하면서 느겼을지 모르지만 SimpleRNN에 비해 LSTM이 느리다. 그렇지만 성능은 약간 더 좋다.
GRU
뉴욕대학교의 조경현 교수가 만든 모델이다. LSTM을 보완하였는데, 사실 GRU는 LSTM의 아웃풋 게이트를 두지 않고 LSTM을 간단하게 변경한 구조이다. 약간 축소했다고 생각하면 된다. 그래서 LSTM보다 약간 빨리지고, 성능은 비슷하거나 약간 낮은 걸로 알려있다.
#1. 데이터
import numpy as np
x_train = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
y_train = np.array([6,7,8])
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], 1)
print("x_train.shape : ", x_train.shape) #(3,5,1)
print("y_train.shape : ", y_train.shape) #(3,)
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, GRU
model = Sequential()
model.add(GRU(7, input_shape = (5, 1), activation = 'relu'))
model.add(Dense(4))
model.add(Dense(1))
model.summary()
#3. 훈련
model.compile(loss='mse', optimizer='adam', metrics=['mse'])
model.fit(x_train, y_train, epochs=100, batch_size=1)
#4. 예측
x_predict = np.array([[4,5,6,7,8]])
print(x_predict.shape) #(1,5)
x_predict = x_predict.reshape(x_predict.shape[0],x_predict.shape[1], 1)
print("x_predict.shape : ", x_predict.shape) # (1,5,1)
y_predict = model.predict(x_predict)
print("예측값 :", y_predict)
Total params: 247 Trainable params: 247 Non-trainable params: 0
_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
gru (GRU) (None, 7) 210
_________________________________________________________________
dense_16 (Dense) (None, 4) 32
_________________________________________________________________
dense_17 (Dense) (None, 1) 5 =================================================================
예측값 : [[9.977119]]
SimpleRNN 대신 GRU을 import 하였고 첫 번째 히든 레이어에 SimpleRNN 대신 GRU을 사용했다. 사용법이 SimpleRNN이나 LSTM과 똑같다.
param의 개수가 SimpleRNN 63, LSTM 252이였는데 GRU에서는 247이다.
결과 값이 다를 수 있다. 왜냐하면 하이퍼파라미터 튜닝을 잘 하지 않았고, 훈련의 횟수도 100회로 적다. 이런 경우 결과값을 기대하지 말자.
Bidirectional
RNN에서 우리는 실행의 방향이 한 방향이었고 시간의 순서대로 진행했다.
예를 들어 1,2,3,4,5라는 데이터에서 1부터 5까지 순차적으로 RNN하는 방식이었다.
하지만 이를 역으로 생각해 보면 5,4,3,2,1이라는 데이터 역시 순차적 데이터가 될 수 있다. 여기서 착안한 것이 Bidirectional이다. 우선 RNN으로 진행한 후 역으로 다시 훈련을 시키는 방법이다. 1개의 데이터 셋으로 두번 훈련 시키는 효과를 누리는 셈이다.
#1. 데이터
import numpy as np
x_train = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
y_train = np.array([6,7,8])
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], 1)
print("x_train.shape : ", x_train.shape) #(3,5,1)
print("y_train.shape : ", y_train.shape) #(3,)
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, LSTM, Bidirectional
model = Sequential()
model.add(Bidirectional(LSTM(7, activation = 'relu'), input_shape = ( 5,1)))
model.add(Dense(4))
model.add(Dense(1))
model.summary()
#3. 훈련
model.compile(loss='mse', optimizer='adam', metrics=['mse'])
model.fit(x_train, y_train, epochs=100, batch_size=1)
#4. 예측
x_predict = np.array([[4,5,6,7,8]])
print(x_predict.shape) #(1,5)
x_predict = x_predict.reshape(x_predict.shape[0],x_predict.shape[1], 1)
print("x_predict.shape : ", x_predict.shape) # (1,5,1)
y_predict = model.predict(x_predict)
print("예측값 :", y_predict)
모델 부분에 Bidirectional을 import하자
그리고 LSTM 레이어를 수정하자.
_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
bidirectional_2 (Bidirection (None, 14) 504
_________________________________________________________________
dense_24 (Dense) (None, 4) 60
_________________________________________________________________
dense_25 (Dense) (None, 1) 5 =================================================================
Total params: 569 Trainable params: 569 Non-trainable params: 0
예측값 : [[8.939632]]
첫번째 히든 레이어의 파라미터가 LSTM에서 252였던 것에 비해 정확히 2배의 파라미터인 504개이다. 그리고 2배의 파라미터를 잡기 때문에 약 2배 정도 느려진다.
LSTM의 2배의 연산이 꼭 좋은 결과를 나오게 한다고 판단 할 수 없다. 중요한 것은 두배 걸린다는 것이다.
LSTM 레이어 연결
현재까지는 RNN 모델 다음에 바로 Dense층을 연결했다. 하지만 LSTM도 2개 이상을 연결 할 수 있다.
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, LSTM
model = Sequential()
model.add(LSTM(7, input_shape = (5, 1), activation = 'relu'))
model.add(LSTM(8))
model.add(Dense(4))
model.add(Dense(1))
ValueError: Input 0 of layer lstm_5 is incompatible with the layer: expected ndim=3, found ndim=2. Full shape received: [None, 7]
lstm_5 계층이 호환이 되지 않으며 Ndim은 3을 기대했지만 발견된 ndim은 2라는 의미이다.
측 dimention또는 shape가 맞지 않는 다는 말이다. lstm은 3개의 차원이 필요하다.
samples, time steps, feature
LSTM은 입력의 디멘션은 3이다. (행, 열, 피쳐 = None , 5, 1)하지만 출력의 디멘션은 2이다. (None,7)
그래서 shape가 맞지 않아 에러발생한다. 두 번째 레이어에 차원을 맞춰주려면 케라스의 LSTM에서는 return_sequence라는 파라미터를 지원하는데 이를 사용하면 된다. 한마디로 이전 차원을 그대로 유지해주겠다는 뜻이다.
return_sequences=True 를 추가하자.
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, LSTM
model = Sequential()
model.add(LSTM(7, input_shape = (5, 1), activation = 'relu', return_sequences=True))
model.add(LSTM(8))
model.add(Dense(4))
model.add(Dense(1))
#1. 데이터
import numpy as np
x_train = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
y_train = np.array([6,7,8])
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], 1)
print("x_train.shape : ", x_train.shape) #(3,5,1)
print("y_train.shape : ", y_train.shape) #(3,)
#2. 모델구성
from keras.models import Sequential
from keras.layers import Dense, LSTM
model = Sequential()
model.add(LSTM(7, input_shape = (5, 1), activation = 'relu', return_sequences=True))
model.add(LSTM(8))
model.add(Dense(4))
model.add(Dense(1))
model.summary()
#3. 훈련
model.compile(loss='mse', optimizer='adam', metrics=['mse'])
model.fit(x_train, y_train, epochs=100, batch_size=1)
#4. 예측
x_predict = np.array([[4,5,6,7,8]])
print(x_predict.shape) #(1,5)
x_predict = x_predict.reshape(x_predict.shape[0],x_predict.shape[1], 1)
print("x_predict.shape : ", x_predict.shape) # (1,5,1)
y_predict = model.predict(x_predict)
print("예측값 :", y_predict)
_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
lstm_6 (LSTM) (None, 5, 7) 252
_________________________________________________________________
lstm_7 (LSTM) (None, 8) 512
_________________________________________________________________
dense_26 (Dense) (None, 4) 36
_________________________________________________________________
dense_27 (Dense) (None, 1) 5 =================================================================
Total params: 805 Trainable params: 805 Non-trainable params: 0
예측값 : [[7.0832467]]
lstm_6과 lstm_7이 연결된 것을 확인 할 수 있다.
예측값이 잘 맞지 않는다. lstm을 무조건 2개 이상 엮는다고 좋아지지 않는다. 특히 이렇게 적은 데이터는 더욱 그렇다.
많은 데이터를 사용할 경우 한 번씩 사용해보면서 필요한 순간을 찾자.
물론 이 모델의 경우에는 하이퍼파라미터를 튜닝할 경우 훨씬 더 좋은 성능이 나올 수 있다. 결국 선능 판단은 최종 acc나 지표로 확인하는 것이 좋고, 하이퍼파라미터 튜닝을 최대한 많이 해보는 것이 중요하다.
3개 이상을 lstm을 연결하려면 동일하게 return_sequence로 연결하면 된다.
'AI > DeepLearning' 카테고리의 다른 글
RNN용 데이터 자르기 (0) | 2020.10.06 |
---|---|
케라스 모델의 파라미터들과 기타 기법들 (0) | 2020.10.06 |
회귀 모델 정리 - 앙상블 및 기타 모델 (0) | 2020.07.13 |
회귀 모델 정리 - 함수형 모델 (0) | 2020.07.11 |
회귀 모델 정리 - 순차모델 (0) | 2020.07.07 |