본문 바로가기
Deep Learning/Recurrent Neural Networks

RNN Tensorflow + Keras

by YJJo 2019. 12. 7.

이번 포스팅은 Tensorflow의 keras를 이용하여 RNN의 다양한 구조를 구현해보는 것이 목표입니다. RNN에는 크게 세 가지 방법이 있는데 simple RNN, LSTM, GRU가 있습니다. 이번 실습은 simple RNN을 이용하여 many-to-one, many-to-many, stacked many-to-one, stacked many-to-many 네 가지를 살펴보겠습니다. simple RNN을 이용하여 설명하지만 단순히 아래 등장할 코드에서 simple RNN을 LSTM, GRU로 바꾸어 주면 내용은 같기때문에 응용하는데 큰 문제는 없을 것 입니다.

import numpy as np
import tensorflow as tf
from tensorflow import keras

Dimension for RNN models in Tensorflow

Tensorflow+Keras로 RNN 모형을 구현할 때 가장 핵심이 되는 부분은 데이터의 구조입니다. 기본적인 입력 데이터 (input data)의 구조는 다음과 같습니다. $$\left(\text{batch size}, \text{tims steps}, \text{input lenth}\right)$$ 앞으로 등장할 one, many의 의미는 위 데이터 형태에서 time steps과 관련이 있습니다. 예를 들어, many-to-one은 앞은 many고 뒤는 one인데 이는 입력 데이터의 time step이 1보다 크다는 것을 의미하고, 출력데이터의 time steps이 1이라는 것을 의미합니다.

Many-to-One

Many-to-One의 Input data의 형태는 $$\left(\text{batch size}, t, \text{input length}\right), t>1$$이고, Output data의 형태는 $$\left(\text{batch size}, 1, 1\right)$$가 됩니다. 다음에 등장할 숫자를 맞추는 many-to-one을 구현해보겠습니다. $$\begin{align*}[1,2,3]&\rightarrow[4] \\ [2,3,4]&\rightarrow[5] \\ [3,4,5]&\rightarrow [6]\end{align*}$$

Figure 1: Many-to-One RNN

기본 데이터를 준비해보겠습니다.

## x & y
x = np.array([[[1],[2],[3]],[[2],[3],[4]],[[3],[4],[5]]], dtype=np.float32)
y = np.array([[4],[5],[6]], dtype=np.float32)

## print
print('[ Shape ]')
print('x: {}, y: {}'.format(x.shape, y.shape))

# [ Output ]
# [ Shape ]
# x: (3, 3, 1), y: (3, 1)

이어서 Tensorflow의 keras를 이용하여 모형 선언을 해주겠습니다. SimpleRNN 함수를 이용하여 은닉 노드가 100개인 RNN Cell을 쉽게 구축할 수 있습니다. RNN에서 100개의 노드가 출력되면 이를 Dense를 이용하여 하나로 묶어줍니다. 이어서 학습을 위한 손실 함수와 최적화기는 각각 mean squared errror와 adam을 적용해줍니다.

# Make Model
layer_input  = keras.Input(shape=(3, 1), name='input')
layer_rnn    = keras.layers.SimpleRNN(100, name='RNN')(layer_input)
layer_output = keras.layers.Dense(1, name='output')(layer_rnn)

model = keras.Model(layer_input, layer_output)
print(model.summary())

# Complier
model.compile(loss = 'mse', optimizer='adam')
model._name = 'many_to_one'

# [ Output ]
# Model: "many_to_one"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input (InputLayer)           [(None, 3, 1)]            0         
# _________________________________________________________________
# RNN (SimpleRNN)              (None, 100)               10200     
# _________________________________________________________________
# output (Dense)               (None, 1)                 101       
# =================================================================
# Total params: 10,301
# Trainable params: 10,301
# Non-trainable params: 0
# _________________________________________________________________

다음으론 위에서 선언한 모형을 학습하고 학습데이터와 [4, 5, 6]을 검증 데이터 (test data)를 적용해보겠습니다. 예측값은 predict 메소드를 이용하면 구할 수 있습니다. (데이터가 적고, 최적화된 구조도 아니라서 결과가 좋진 않습니다. Many-to-One 구조를 이렇게 구현할 수 있구나.. 정도만 알고 가셔도 충분할 것 같습니다.)

# Learn model
model.fit(x, y, epochs=100, batch_size=1, verbose=0)

print('[Result for Training Data]')
print(model.predict(x))
print('')
print('[Result for Test Data]')
print(model.predict(np.array([[[4],[5],[6]]], dtype=np.float32)))

# [ Output ]
# [Result for Training Data]
# [[3.9197853]
#  [5.1760707]
#  [5.897519 ]]

# [Result for Test Data]
# [[6.289039]]

Many-to-Many

Many-to-Many의 Input data과 Output 데이터 형태 모두 $$\left(\text{batch size}, t, \text{input length}\right), t>1$$가 됩니다. 다음에 등장할 숫자를 맞추는 many-to-one을 구현해보겠습니다. $$\begin{align*}[1,2,3]&\rightarrow[2,3,4] \\ [2,3,4]&\rightarrow[3,4,5] \\ [3,4,5]&\rightarrow [4,5,6]\end{align*}$$

Figure 2: Many-to-Many RNN

기본 데이터를 준비합니다.

## x & y
x = np.array([[[1],[2],[3]], [[2],[3],[4]], [[3],[4],[5]]], dtype=np.float32)
y = np.array([[[2],[3],[4]], [[3],[4],[5]], [[4],[5],[6]]], dtype=np.float32)

## print
print('[ Shape ]')
print('x: {}, y: {}'.format(x.shape, y.shape))

# [ Output ]
# [ Shape ]
# x: (3, 3, 1), y: (3, 3, 1)

이제 keras를 이용하여 은닉 노드가 100개인 RNN을 만들어 주도록 하겠습니다. 위 Many-to-One과 크게 다르지 않고 딱 두 가지 차이가 있습니다.

  • 첫 번째는 return-sequences입니다. return_sequences의 default는 False로 설정되어있으며, 해당 경우 time step의 마지막에서만 아웃풋을 출력합니다. 반면 지금처럼 True로 설정한 경우 모든 time step에서 아웃풋을 출력합니다.
  • 두 번째는 TimeDistributed입니다. TimeDistributed를 이용하면 각 time에서 출력된 아웃풋을 내부에 선언해준 레이어와 연결시켜주는 역할을 합니다. 아래 예제에서는 Dense(unit=1)로 연결을 했고, 이는 RNN Cell의 가중치와 마찬가지로 모든 step step에서 가중치를 공유합니다.
# Make Model
layer_input  = keras.Input(shape=(3, 1), name='input')
layer_rnn    = keras.layers.SimpleRNN(100, return_sequences=True, name='RNN')(layer_input)
layer_output = keras.layers.TimeDistributed(keras.layers.Dense(1), name='output')(layer_rnn)

model = keras.Model(layer_input, layer_output)
model._name = 'many_to_many'
print(model.summary())

# Complier
model.compile(loss = 'mse', optimizer='adam')

# [ Output ]
# Model: "many_to_many"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input (InputLayer)           [(None, 3, 1)]            0         
# _________________________________________________________________
# RNN (SimpleRNN)              (None, 3, 100)            10200     
# _________________________________________________________________
# output (TimeDistributed)     (None, 3, 1)              101       
# =================================================================
# Total params: 10,301
# Trainable params: 10,301
# Non-trainable params: 0
# _________________________________________________________________

다음으론 위에서 선언한 모형을 학습하고 학습데이터와 [4, 5, 6]을 검증 데이터 (test data)를 적용해보겠습니다. 예측값은 predict 메소드를 이용하면 구할 수 있습니다. Many-to-One 보다는 조금 나은 결과를 보입니다. (현 예제에서 Many-to-One이 나은 결과를 보인다는 의미로 항상 그렇다는 뜻이 아님에 유의하시길 바랍니다.)

# Learn Model
model.fit(x, y, epochs=100, batch_size=1, verbose=0)

print('[Result for Training Data]')
print(model.predict(x))
print('')
print('[Result for Test Data]')
print(model.predict(np.array([[[4],[5],[6]]], dtype=np.float32)))

# [ Output ]
# [Result for Training Data]
# [[[2.0261364]
#   [2.9813964]
#   [3.9668841]]

#  [[3.0722613]
#   [4.117426 ]
#   [5.0680556]]

#  [[3.926321 ]
#   [4.9281354]
#   [5.971689 ]]]

# [Result for Test Data]
# [[[4.5858283]
#   [5.5066338]
#   [6.646799 ]]]

Stacked Many-to-One

Stacked RNNs은 모형을 구축할 때 RNN cell을 적층하는 구조입니다. 2층의 Many-to-One RNN 구조는 아래와 같이 구성됩니다.

Figure 3: Stacked Many-to-One

직전에 Many-to-One에서 적용한 다음 숫자 맞추기 데이터를 그대로 사용하겠습니다.

## x & y
x = np.array([[[1],[2],[3]],[[2],[3],[4]],[[3],[4],[5]]], dtype=np.float32)
y = np.array([[4],[5],[6]], dtype=np.float32)

## print
print('[ Shape ]')
print('x: {}, y: {}'.format(x.shape, y.shape))

# [ Output ]
# [ Shape ]
# x: (3, 3, 1), y: (3, 1)

이어서 keras로 stacked many-to-one을 구성해주겠습니다. 위 many-to-one가 다른 점은 첫 번째 층의 RNN cell은 return_sequences=True로 설정 해주는 것이 유일합니다. 이렇게 설정해주는 이유는 윗층의 RNN cell로 은닉층 계산 결과를 전달해주기 위함입니다.

# Make Model
layer_input  = keras.Input(shape=(3, 1), name='input')
layer_rnn0   = keras.layers.SimpleRNN(100, return_sequences=True, name='RNN_cell_0')(layer_input)
layer_rnn1   = keras.layers.SimpleRNN(100, name='RNN_cell_1')(layer_rnn0)
layer_output = keras.layers.Dense(1, name='output')(layer_rnn1)

model = keras.Model(layer_input, layer_output)
model._name = 'stacked_many_to_one'
print(model.summary())

# Complier
model.compile(loss = 'mse', optimizer='adam')

# [ Output ]
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input (InputLayer)           (None, 3, 1)              0         
# _________________________________________________________________
# RNN_cell_0 (SimpleRNN)       (None, 3, 100)            10200     
# _________________________________________________________________
# RNN_cell_1 (SimpleRNN)       (None, 100)               20100     
# _________________________________________________________________
# output (Dense)               (None, 1)                 101       
# =================================================================
# Total params: 30,401
# Trainable params: 30,401
# Non-trainable params: 0
# _________________________________________________________________

단순 Many-to-one과 같이 위에서 선언한 모형을 학습하고 학습데이터와 [4, 5, 6]을 검증 데이터 (test data)를 적용해보겠습니다. RNN cell을 적층한 결과가 단순 Many-to-One보다 좋아진 것을 확인할 수 있습니다.

# Learn model
model.fit(x, y, epochs=100, batch_size=1, verbose=0)

print('[Result for Training Data]')
print(model.predict(x))
print('')
print('[Result for Test Data]')
print(model.predict(np.array([[[4],[5],[6]]], dtype=np.float32)))

# [ Output ]
# [Result for Training Data]
# [[3.953749 ]
#  [5.0304537]
#  [5.947592 ]]

# [Result for Test Data]
# [[6.611109]]

Stacked Many-to-Many

Stacked RNNs은 모형을 구축할 때 RNN cell을 적층하는 구조입니다. 2층의 Many-to-One RNN 구조는 아래와 같이 구성됩니다.

Figure 4: Stacked Many-to-Many

비교를 위해 직전에 Many-to-Many에서 활용한 데이터를 다시 사용하도록 하겠습니다.

# Make Model
layer_input  = keras.Input(shape=(3, 1), name='input')
layer_rnn0   = keras.layers.SimpleRNN(100, return_sequences=True, name='RNN_cell_0')(layer_input)
layer_rnn1   = keras.layers.SimpleRNN(100, return_sequences=True, name='RNN_cell_1')(layer_rnn0)
layer_output = keras.layers.TimeDistributed(keras.layers.Dense(1), name='output')(layer_rnn1)

model = keras.Model(layer_input, layer_output)
model._name = 'stacked_many_to_many'
print(model.summary())

# Complier
model.compile(loss = 'mse', optimizer='adam')

# [ Output ]
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input (InputLayer)           (None, 3, 1)              0         
# _________________________________________________________________
# RNN_cell_0 (SimpleRNN)       (None, 3, 100)            10200     
# _________________________________________________________________
# RNN_cell_1 (SimpleRNN)       (None, 3, 100)            20100     
# _________________________________________________________________
# output (TimeDistributed)     (None, 3, 1)              101       
# =================================================================
# Total params: 30,401
# Trainable params: 30,401
# Non-trainable params: 0
# _________________________________________________________________

다음으론 위에서 선언한 모형을 학습하고 학습데이터와 [4, 5, 6]을 검증 데이터 (test data)를 적용해보겠습니다. 단순 Many-to-Many와 거의 비슷한 결과를 보입니다.

# Learn model
model.fit(x, y, epochs=100, batch_size=1, verbose=0)
print('[Result for Training Data]')
print(model.predict(x))
print('')
print('[Result for Test Data]')
print(model.predict(np.array([[[4],[5],[6]]], dtype=np.float32)))

# [ Output ]
# [Result for Training Data]
# [[[1.9391257]
#  [2.9446127]
#  [3.96884  ]]

#  [[3.1007104]
#   [4.093822 ]
#   [5.0487785]]

#  [[3.947911 ]
#   [4.928792 ]
#   [5.9456053]]]

# [Result for Test Data]
# [[[4.541132 ]
#   [5.516348 ]
#   [6.5795174]]]

마치며

이번 포스팅에서는 Many-to-One, Many-to-Many, stacked Many-to-One, stacked Many-to-many RNN 구조를 tensorflow+keras로 구현하는 방법을 살펴보았습니다. SimpleRNN을 타겟으로 작성되었지만 LSTM, GRU 메소들 이용하면 동일한 방법으로 위 python코드를 확장할 수 있습니다. 감사합니다:)

[ 원하는 딥러닝 코드가 있다면 언제든지 댓글이나 방명록을 통해 남겨주시면 반영해서 올려드리겠습니다 :) ]

 

 

댓글