본문 바로가기
ML & DL/tensorflow

[tensorflow] RNN에 사용되는 layer

by 별준 2020. 12. 22.

(tensorflow v2.4.0)

 

RNN 모델에 사용하는 tensorflow의 layer에 대해서 알아보도록 하겠습니다.

import numpy as np
import tensorflow as tf

1. Simple RNN layer

tensorflow에서 Simple RNN은 아래의 API로 사용할 수 있습니다. 

tf.keras.layers.SimpleRNN

이번글에서 파라미터로는 units, activation, return_sequences를 사용할 예정이며,

units은 output의 차원이며, return_sequences는 RNN에서 마지막 output 시퀀스에서만 결과를 출력할 지, 아니면 모든 시퀀스에서 결과를 출력할 지에 대한 여부를 나타냅니다. 주로 여러 개의 RNN layer를 쌓을 때 사용됩니다.

 

간단한 예제를 통해서 살펴보도록 하겠습니다.

시퀀스를 구성하는 4개의 숫자가 주어졌을 때, 다음 숫자를 예측하는 간단한 '시퀀스 예측 모델'을 SimplyRNN 모델을 통해서 구현해보도록 하겠습니다.

예를 들어, [0.0, 0.1, 0.2, 0.3]이라는 연속된 숫자가 입력으로 주어질 때, [0.4]를 예측하는 네트워크를 만드는 것이 목표입니다.

 

데이터 생성

import tensorflow as tf
import numpy as np

X = []
Y = []
for i in range(6):
    lst = list(range(i, i+4))
    X.append(list(map(lambda c: [c/10], lst)))
    Y.append((i+4)/10)

X = np.array(X)
Y = np.array(Y)

for i in range(len(X)):
    print(X[i], Y[i])

다음으로 SimpleRNN layer를 사용한 모델을 정의합니다. 

model = tf.keras.Sequential([
    tf.keras.layers.SimpleRNN(units=10, return_sequences=False, input_shape=[4,1]),
    tf.keras.layers.Dense(1)
])

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

여기서 input_shape는 [4, 1] 인데, 각각 time step과 input_dim을 나타냅니다.

 

SimpleRNN의 파라미터의 수는 총 130개인데, 아래와 같이 파라미터가 존재합니다.

input에 의한 파라미터 weight = (1, 10), bias = (10, )

activation에 의한 파라미터 weight = (10, 10)

(output을 위한 파라미터도 존재할 줄 알았는데, SimpleRNN에서는 없는 것으로 확인됩니다.)

 

그럼 100 epochs로 학습을 하고, 예측값이 어떻게 나오는지 확인해보겠습니다.

model.fit(X, Y, epochs=100, verbose=0)
print(model.predict(X))

얼추 Y의 값과 유사하게 예측하고 있는 것 같습니다.

 

그렇다면 학습하지 않은 새로운 데이터를 가지고 어떤 결과를 도출하는지 살펴보겠습니다.

print(model.predict(np.array([[[0.6],[0.7],[0.8],[0.9]]])))
print(model.predict(np.array([[[-0.1],[0.0],[0.1],[0.2]]])))

1.0을 예측하기를 원한 데이터는 0.8479315를, 0.3을 예측하기를 원한 데이터의 출력은 0.17500487이 나왔습니다. 

결과를 개선하기 위해서는 아마 더 많은 Training Data를 사용하는 것이 좋을 것 같습니다. 

 

SimpleRNN layer는 RNN에서 가장 간단한 형태이면서 빠르게 모델을 만들어볼 때 유용합니다. 하지만 실무에서는 SimpleRNN의 단점을 개선한 LSTM이나 GRU layer 등이 많이 사용됩니다.

 

2. LSTM layer

SimpleRNN layer는 Long-Term Dependency(장기의존성) 문제가 존재하며, 입력 데이터와 출력 데이터 사이의 길이가 멀어질수록 연관 관계가 적어집니다. 즉, 현재 예측값을 얻기 위해 과거의 정보에 의존하지만, 과거 시점이 너무 멀어지면 문제를 해결하기 어렵다는 것입니다.

 

이 문제를 해결하기 위한 구조로 LSTM이 제안되었는데, RNN보다 복잡한 구조를 가지고 있습니다. LSTM에는 Output외에도 LSTM cell 사이에서만 공유되는 Cell State를 가지고 있습니다.

자세한 구조는 아래 게시글을 참조바랍니다.

2020/12/21 - [Coursera 강의/Deep Learning] - Recurrent Neural Networks 2 (GRU, LSTM, BRNN)

2020/12/21 - [Coursera 강의/Deep Learning] - [실습] Building a RNN step by step(Basic RNN, LSTM)

 

LSTM의 학습 능력을 확인하기 위해서 LSTM을 처음 제안한 논문에 나온 실험 중 곱셈 문제(Multiplication problem)을 예제로 살펴보겠습니다.

말 그대로 실수에 대해 곱셈을 하는 문제인데, 100개의 연속된 숫자 중에서 1로 마킹된 두 개의 숫자만 곱해야하는 문제입니다.(나머지는 0으로 마킹되어 있습니다)

 

데이터 준비

random한 숫자 100개를 만들어주고, 마킹할 숫자 2개의 인덱스를 선택한 후에, 그 인덱스만 1인 마커를 만들어줍니다.

그리고, 두 숫자를 합쳐줍니다.

X = []
Y = []
for i in range(3000):
    lst = np.random.rand(100)
    idx = np.random.choice(100, 2, replace=False)
    zeros = np.zeros(100)
    zeros[idx] = 1
    X.append(np.array(list(zip(zeros, lst))))
    Y.append(np.prod(lst[idx]))

print(X[0], Y[0])

첫번째 문제의 value

이 과정을 3000번 반복해서 총 3000개의 training sample을 생성했습니다. 논문에서는 2560개의 문제를 생성했지만, 3000개의 문제를 생성하고, 2560개는 학습용으로 사용하고 나머지는 검증용으로 사용하겠습니다.

 

비교를 위해서 SimpleRNN으로 정의한 모델과 LSTM으로 정의한 모델을 모두 학습해서 비교해보도록 하겠습니다.

 

Simple RNN 모델 정의

model = tf.keras.Sequential(
    [tf.keras.layers.SimpleRNN(units=30, return_sequences=True, input_shape=(100,2)),
     tf.keras.layers.SimpleRNN(units=30),
     tf.keras.layers.Dense(1)]
)
model.compile(optimizer='adam', loss='mse')
model.summary()

모델에서 SimpleRNN layer를 쌓아주기 위해서 첫번째 SImpleRNN의 return_sequences 값이 True로 설정된 것을 확인할 수 있습니다. 이렇게 설정되면 레이어의 출력을 다음 레이어로 그대로 넘겨주게 됩니다.

 

그럼 학습을 진행해보도록 하겠습니다. RNN은 CNN보다 학습시간이 오래 걸리는 편이기 때문에 GPU 사용이 거의 필수인 것 같습니다...(저는 Colab으로 돌렸습니다.)

X = np.array(X)
Y = np.array(Y)

history = model.fit(X, Y, epochs=100, validation_split=0.2)

학습의 전체적인 경향을 보기 위해 그래프로 살펴보도록 하겠습니다.

import matplotlib.pyplot as plt
plt.plot(history.history['loss'], 'b-', label='loss')
plt.plot(history.history['val_loss'], 'r--', label='val_loss')
plt.xlabel('Epoch')
plt.legend()
plt.show()

학습 Loss는 점차 감소하고 있지만, 검증 Loss인 val_loss는 증가하는 모습을 보여주고 있습니다. 전형적인 과적합의 그래프를 보여주고 있습니다. 

 

정확도를 살펴보도록 하겠습니다. 논문에서는 오차가 0.04 이상이면 오답으로 처리했는데, 그대로 이 조건을 가지고 정확도를 측정해보겠습니다.

model.evaluate(X[2560:], Y[2560:])
prediction = model.predict(X[2560:2560+5])

for i in range(5):
    print(Y[2560+i], '\t', prediction[i][0], '\tdiff:', abs(prediction[i][0] - Y[2560+i]))

prediction = model.predict(X[2560:])
fail = 0
for i in range(len(prediction)):
    if abs(prediction[i][0] - Y[2560+i] > 0.04):
        fail += 1
print('correctness: ', ((440 - fail)/440) * 100, '%')

validation set으로 evaluate한 결과 0.0647의 loss가 나왔습니다. 이는 100 epoch의 training loss보다 3배가량 높은 값으로, 모델이 새로운 데이터에 잘 예측하지 못하는 모습을 보여주고 있습니다.

5개의 sample에 대해서 오차가 다양하게 나오고 있으며, 정확도는 55%가 나왔습니다.

(책에서는 9.32%가 나왔는데, 꽤 높은 값이 나와서 당황했습니다.)

 

LSTM Layer 모델 정의

다음으로 LSTM으로 구성된 RNN 모델을 정의해보겠습니다.

model = tf.keras.Sequential(
    [tf.keras.layers.LSTM(units=30, return_sequences=True, input_shape=(100,2)),
     tf.keras.layers.LSTM(units=30),
     tf.keras.layers.Dense(1)]
)
model.compile(optimizer='adam', loss='mse')
model.summary()

단순히 SimpleRNN layer만 LSTM으로 변경되었습니다.

 

학습을 진행해보도록 하겠습니다.

history = model.fit(X, Y, epochs=100, validation_split=0.2)

처음에는 loss와 val_loss가 잘 줄어들지 않는 듯하다가, 어느 순간 두 값 모두 매우 작아지고 있습니다.

 

그래프로 확인해보겠습니다.

plt.plot(history.history['loss'], 'b-', label='loss')
plt.plot(history.history['val_loss'], 'r--', label='val_loss')
plt.xlabel('Epoch')
plt.legend()
plt.show()

정확도는 다음과 같습니다.

model.evaluate(X[2560:], Y[2560:])
prediction = model.predict(X[2560:2560+5])

for i in range(5):
    print(Y[2560+i], '\t', prediction[i][0], '\tdiff:', abs(prediction[i][0] - Y[2560+i]))

prediction = model.predict(X[2560:])
fail = 0
for i in range(len(prediction)):
    if abs(prediction[i][0] - Y[2560+i] > 0.04):
        fail += 1
print('correctness: ', ((440 - fail)/440) * 100, '%')

validation set의 loss가 거의 0에 가까운 값이 나오고, 5개의 sample의 loss도 0에 가깝습니다.

무엇보다 validation set에서의 정확도가 97.5%로 SimpleRNN보다 훨씬 높은 정확도를 보여주고 있습니다.

 

3. GRU layer

GRU layer는 LSTM layer와 유사하지만 구조가 더 간단하여 연산량이 더 적고, 어떤 문제에서는 LSTM보다 더 좋은 성능을 보이기도 합니다.

 

모델을 정의해보겠습니다.

model = tf.keras.Sequential(
    [tf.keras.layers.GRU(units=30, return_sequences=True, input_shape=(100,2)),
     tf.keras.layers.GRU(units=30),
     tf.keras.layers.Dense(1)]
)
model.compile(optimizer='adam', loss='mse')
model.summary()

파라미터의 수가 더 줄어든 것을 볼 수 있습니다.

history = model.fit(X, Y, epochs=100, validation_split=0.2)

LSTM을 사용한 것과 유사하게 loss가 감소한 것을 볼 수 있습니다.

plt.plot(history.history['loss'], 'b-', label='loss')
plt.plot(history.history['val_loss'], 'r--', label='val_loss')
plt.xlabel('Epoch')
plt.legend()
plt.show()

LSTM을 사용했을 때는 30 epoch 쯤에서 loss가 가파르게 감소했지만, GRU을 사용했을 때에는 18 epoch 정도에서 가파르게 loss가 감소하고 있고, 값의 변화도 조금 더 안정적인 것 같습니다.

 

정확도는 다음과 같습니다.

model.evaluate(X[2560:], Y[2560:])
prediction = model.predict(X[2560:2560+5])

for i in range(5):
    print(Y[2560+i], '\t', prediction[i][0], '\tdiff:', abs(prediction[i][0] - Y[2560+i]))

prediction = model.predict(X[2560:])
fail = 0
for i in range(len(prediction)):
    if abs(prediction[i][0] - Y[2560+i] > 0.04):
        fail += 1
print('correctness: ', ((440 - fail)/440) * 100, '%')

정확도는 98.86%로 거의 99%에 가까운 값이 나왔습니다. LSTM layer보다 조금 더 높은 수치로 GRU에서 조금 더 잘 풀리는 것으로 추측됩니다. 다만 딥러닝 특성상 시행할 때마다 결과가 달라질 수 있기 때문에 정확한 결과를 얻으려면 여러 번 수행해서 평균을 낸 값을 사용하는 것이 좋습니다.

 

 

- 참조

시작하세요! 텐서플로 2.0 프로그래밍

http://bit.ly/2TbNFtQ 

 

댓글