본문 바로가기
DeepLearning Framework & Coding/Tensorflow 2.X

[코드로 이해하는 딥러닝 2-11] - RNN(Recurrent Neural Network)/LSTM(Long-Short-Term-Memory)

by 노마드공학자 2021. 1. 16.

[코드로 이해하는 딥러닝 0] - 글연재에 앞서 https://limitsinx.tistory.com/27

[코드로 이해하는 딥러닝 1] - Tensorflow 시작 https://limitsinx.tistory.com/28 

[코드로 이해하는 딥러닝 2] - Tensorflow 변수선언 https://limitsinx.tistory.com/29

[코드로 이해하는 딥러닝 3] - Tensorflow placeholder변수 https://limitsinx.tistory.com/30

[코드로 이해하는 딥러닝 4] - 선형회귀(Linear Regression) https://limitsinx.tistory.com/31

[코드로 이해하는 딥러닝 5] - 다중선형회귀(Multiple Linear Regression) https://limitsinx.tistory.com/32

[코드로 이해하는 딥러닝 6] - 회귀(Regression)에 대한 다른 접근 https://limitsinx.tistory.com/33

[코드로 이해하는 딥러닝 7] - .txt(.csv)파일 불러오기 https://limitsinx.tistory.com/34

[코드로 이해하는 딥러닝 8] - Logistic Regression(sigmoid) https://limitsinx.tistory.com/35

[코드로 이해하는 딥러닝 9] - Softmax Regression(multiple classification) https://limitsinx.tistory.com/36

[코드로 이해하는 딥러닝 10] - MNIST 데이터 분류/One hot encoding https://limitsinx.tistory.com/37

[코드로 이해하는 딥러닝 11] - Deep Neural Network/XOR https://limitsinx.tistory.com/38

[코드로 이해하는 딥러닝 11-EX] - MNIST를 DNN으로 학습해보기/Adam optimizer https://limitsinx.tistory.com/39

[코드로 이해하는 딥러닝 12] - RELU(Rectified Linear Unit) https://limitsinx.tistory.com/40

[코드로 이해하는 딥러닝 13] - .txt(.csv)파일로 저장하기 https://limitsinx.tistory.com/44

[코드로 이해하는 딥러닝 14] - Drop out https://limitsinx.tistory.com/45

[코드로 이해하는 딥러닝 15] - 초기화(Initialization)의 중요성 https://limitsinx.tistory.com/46

[코드로 이해하는 딥러닝 16] - CNN(Convolutional Neural Network) https://limitsinx.tistory.com/47

 

※이 전글에서 정리한 코드/문법은 재설명하지 않으므로, 참고부탁드립니다

※해당 글은 PC에서 보기에 최적화 되어있습니다.

 

 

 

"RNN(Recurrent Neural Network)?"

 

 

RNN, 출처 : https://towardsdatascience.com/understanding-rnn-and-lstm-f7cdf6dfc14e

 

한때 CNN과 더불어 시대를 풍미하던 모델인, RNN에 대해 정리해보도록 하겠습니다.

시계열(Time Sequential)데이터를 학습하는 경우, 필요한 모델인데요. 제가 주로 다루는 모델입니다.

 

이제까지 General한 Neural Network부터 CNN까지, 모든 데이터들은 Time과 연관되어있지 않았습니다.

즉, 이미지 수만장(MNIST) 혹은 데이터들을 학습시켰을뿐이지, 이게 시간적인 연관성을 전혀 가지진 않았다는 말이죠

 

예를들면..

 

"H E L L O" 라는 단어를 자동검색어에 뜨도록 학습시키고 싶다고 가정을 해보시죠

그럼 H라는 단어를 쳤을때 E L L O라고 다음 값이 나올 수 있도록 학습이 되어야겠죠

 

그럼 기존의 학습방법은 input 한 알파벳을 치면, 그 다음 알파벳이 나오도록 학습을 하는것입니다. 아래처럼요

 

 

H -> E

E -> L

L -> L

L -> O

 

 

이렇게 1개의 input을 주었을때 다음 output으로는 그다음 알파벳이 나오도록만 학습을 해주는것입니다.

그런데 이상한게 보이시죠??

 

똑같은 Input을 주었는데 다른 output이 나와야하는 경우가 발생하는 것입니다.

 

즉, "L이 2번연속나왔을때는 O라는 output을 주도록해라~" 라는 새로운 학습조건이 필요하게 된거죠

 

이게바로 Time Sequential Data(시계열데이터) 입니다. 이전 과거의 값이 현재의 값에 영향을 미치는거죠

 

이런 데이터들을 학습시키기 위한 것이 바로 RNN 입니다.

 

현재 state에서의 x_t(input)과 h_y(hypothesis)로 y_t를 만들어내는것이, 이제까지의 Neural Network 였다면,

과거의 x_t-1, h_t-1, y_t-1이 현재의 y_t를 구성하는데까지 모두 영향을 주는것이라는 거죠

 

출처 : https://medium.com/analytics-vidhya/in-depth-tutorial-of-recurrent-neural-network-rnn-and-long-short-term-memory-lstm-networks-3a782712a09f

 

손으로 하나씩 공책에 적어가면서 따라 가다보면 이해가 훨씬 쉽습니다.

 

개념적인 부분에 대해 정리를 하기위해 Notation을 정리해보겠습니다.

 

[Notation]

x_(t-1) : 과거의 input 데이터

y_(t-1) : 과거의 output 데이터

h_(t-1) : 과거의 hypothesis

W_(t-1) : 과거의 Weighting

b_h : 과거의 Bias

W_hh : 과거의 Hypothesis를 현재로 넘길때 곱해지는 Weighting

 

x_t : 현재의 input 데이터

y_t : 현재의 output 데이터

h_t : 현재의 hypothesis

W_xh : 현재의 Weighting

W_hy : 현재의 State와 과거의 Hypothesis를 통해 y_t를 얻기위해 곱해지는 Weighting

b_t : 현재의 Bias

 

tanh : activation function(기존의 ReLU, sigmoid ...와 같은 활성함수)

 

[개념정리]

과거의 데이터부터 정리해보겠습니다.

h_(t-1) = W_(t-1)*x_(t-1) + b_(t-1) 입니다.

 

y_(t-1)은 해당 State에서의 Weighting과 Bias값들이 더해진것이므로, 이 다음 State필요한것은 y_(t-1)이 아닌, 좀더 본질적인 값인 h_(t-1)입니다.

 

따라서, h_(t-1)은 W_hh와 곱해져 다음 State로 넘어가게 됩니다.

이부분이 아래 빨간 네모박스 부분의 동작개요 입니다.

 

출처 : i2 tutorials, 네모박스는 본인이 침

 

이것으로 과거데이터의 값은 모두 정리되었습니다. (W_hh * h_(t-1) + b_h)

현재 State로 가보겠습니다.

 

h_t = W_xh * x_t + b_t 입니다.

여기에 과거의 값을 더해줌으로써 최종적인 현재 state에서의 h_t가 완성됩니다.

 

h_t = W_xh * x_t + W_hh * h_(t-1)

 

이것을 Activation Function인 tanh에 통과시킨후, W_hy를 곱하고 bias를 더해주면 최종적으로 현재 state에서의 y값인 y_t 가얻어집니다!

 

y_t=W_hy * tanh(h_t) + b_t

 

출처 : i2 tutorials, 네모박스는 본인이 침

 

즉, 빨간 네모박스 친 부분이, 위에서 현재 state를 기준으로 정리한 값들입니다.

 

이렇게하면 감이 조금 오는것이, 기존에는 x_t와 관계된 y_t만이 나왔는데

 

x_(t-1)을 토대로 연산한 h_(t-1)이 관여된, y_t를 얻게 되죠!

즉, 과거의 데이터가 현재의 상태에 영향을 미치게 되는것으로, 이것을 "RNN"이라고 부릅니다.

 

 

"LSTM(Long Short Term Memory)"

 

 

RNN은 과거의 데이터값들을 기억하지만, Layer가 Deep해질수록 점점 과거의 값들이 "희석"되는 문제가 발생합니다.

 

즉, 일반적인 Neural Network에서 발생했던 "Vanishing Gradient"와 같은 문제가 생기는거죠

 

예를들면, t-1의 값들은 t 상태에서 잘 가지고 있지만, t-100의 값은 거의 영향력이 없게 될것입니다.

 

따라서, 과거의 데이터들이 희석되는 문제점을 고쳐준 것이 바로 "LSTM(Long Short Term Memory"입니다.

 

출처 : https://ratsgo.github.io/natural%20language%20processing/2017/03/09/rnnlstm/

상기 그림의 첫번째 이미지가 RNN 두번째가 LSTM인데요

 

RNN은 위에서 설명한바와 같이, h_(t-1) + W_hx*x_t 를 tanh에 통과시킨채로 해당 Layer에서의 연산이 종료됩니다.

 

하지만, LSTM에서는 과거의 값을 좀 더 잘 보존할 수 있도록 내부적으로 여러가지 연산장치들을 추가해놓았습니다.

 

- 하기 이미지는 LSTM의 "Cell" 내부를 수식화해서 정리해놓은 것입니다.

 

출처 : https://www.researchgate.net/figure/Structure-of-the-LSTM-cell-and-equations-that-describe-the-gates-of-an-LSTM-cell_fig5_329362532

 

출처 : https://ratsgo.github.io/natural%20language%20processing/2017/03/09/rnnlstm/

 

그럼 이번에는 코드로

 

"hi hello" 라는 문장을 학습시키고, "hi hell"까지 input으로 주었을때 "hi hello"전체를 순서대로 정렬할 수 있는지 구현해보겠습니다.

 

 

[코드 전문]

 

import numpy as np

import tensorflow as tf

 

idx2char = ['h''i''e''l''o']

# Teach hello: hihell -> ihello

# x_data = [[0, 1, 0, 2, 3, 3]]  # hihell

y_data = [[102334]]  # ihello

 

num_classes = 5

input_dim = 5  # one-hot size, same as hidden_size to directly predict one-hot

sequence_length = 6  # |ihello| == 6

learning_rate = 0.1

 

x_one_hot = np.array([[[10000],    # h 0

                       [01000],    # i 1

                       [10000],    # h 0

                       [00100],    # e 2

                       [00010],    # l 3

                       [00010]]],  # l 3

                     dtype=np.float32)

 

y_one_hot = tf.keras.utils.to_categorical(y_data, num_classes=num_classes)

print(x_one_hot.shape)

print(y_one_hot.shape)

 

tf.model = tf.keras.Sequential()

 

# make cell and add it to RNN layer

# input_shape = (1,6,5) => number of sequence (batch), length of sequence, size of input dim

cell = tf.keras.layers.LSTMCell(units=num_classes, input_shape=(sequence_length, input_dim))

tf.model.add(tf.keras.layers.RNN(cell=cell, return_sequences=True))

 

# single LSTM layer can be used as well instead of creating LSTMCell

# tf.model.add(tf.keras.layers.LSTM(units=num_classes, input_shape=(sequence_length, input_dim), return_sequences=True))

 

# fully connected layer

tf.model.add(tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(units=num_classes, activation='softmax')))

 

tf.model.compile(loss='categorical_crossentropy'optimizer=tf.keras.optimizers.Adam(lr=learning_rate),

                 metrics=['accuracy'])

 

# train

tf.model.fit(x_one_hot, y_one_hot, epochs=50)

tf.model.summary()

 

predictions = tf.model.predict(x_one_hot)

for i, prediction in enumerate(predictions):

    print(prediction)

    # print char using argmax, dict

    result_str = [idx2char[c] for c in np.argmax(prediction, axis=1)]

    print("\tPrediction str: "''.join(result_str))

 

 

[코드 분석-1]

① idx2char = [ 'h', 'i', 'e', 'l', 'o']

: index to characteristic으로, 각각의 알파벳에 대해 one hot encoding을 하기위한 코드입니다.

 

② y_data = [[1, 0, 2, 3, ,3, 4]]

: 1 = i,  0 = h, 2 = e, 3 = l, 4 = o로써, 각각의 값들에 대해  매핑을 해주고, y_data값을 불러보면

"ihello" 라고 볼수있죠

즉, x_data를 밑에서 선언해줄텐데 "hihell"을 학습시키면, "ihello"가 나오도록해라~~라고 학습데이터를 선언해주는것입니다.

 

③ num_classes = 5

: h,i,e,l,o  5개의 classification을 하는것입니다.

 

④ x_one_hot = np.array([[ ... ]]

: 학습하기 위한 x_data를 one_hot_encoding한 값으로 정해주는 모습입니다.

"hi hell"을 x_data로 넣어주는 코드입니다.

 

 

[코드 분석-2]

① y_one_hot = tf.keras.utils.to_categorical(y_data, num_classes = num_classes)

: y_data인 1 0 2 3 3 4 를 one hot encoding 해주는 코드입니다.

 

즉, 1 0 2 3 3 4 는 i h e l l o 에 대응되는데

i= [0 0 0 0 0]

h = [0 0 0 0 1]

e = [0 0 1 0 0]

...

 

이런식으로 지정해주는 코드입니다.

 

② cell = tf.keras.layers.LSTMCell(units = num_classes, input_shape = (sequence_length,input_dim))

: LSTM으로, 5개의 classification에 대해, 6행 5열로 input data를 정렬해주는것을 의미합니다.

(6개의 x_data, 5개의 classification)

 

③ tf.model.add(tf.keras.layers.RNN(cell=cell, return_sequences=True))

: RNN으로 LSTM의 cell을 지정해주고, Sequences=True라는 명령어로, Time sequential한 데이터임을 선언해줍니다.

 

 

[코드 분석-3]

① tf.model.add(tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(units=num_classes, activation='softmax')))

: 모델의 layer를 설정해주는 부분으로, 기존에는 tf.keras.layers(~~) 이런식으로만 정리되었는데

TimeDistributed를 추가시켜주었습니다.

classification을 5개로 선언하고, activation function은 softmax를 사용한 코드인데요, 

 

 

"RNN/LSTM은 ReLU를 더이상 사용하지 않습니다!!"

 

 

출처 : AI wiki, activation function
출처 : https://medium.com/@shrutijadon10104776/survey-on-activation-functions-for-deep-learning-9689331ba092

 

 

왜일까요??

 

RNN은 Time Sequential하게 과거의 데이터를 지속적으로 꺼내서 사용합니다.

LSTM은 더하죠!

 

 

즉, 과거의 값들이 끊임없이 재귀(반복적으로)로 사용되기때문에, -1~1 사이로 normalizing이 필요합니다.

 

 

ReLU는 0이상의 값에서는 y=x가 나오게 되죠, 즉 1이상의 값이 나오게 되는경우, 발산할 확률이 아주아주 높아지게 됩니다. (대부분 발산합니다.)

 

따라서 output layer의 값을 -1~1 사이로 normalizing해줄 수 있는 softmax 혹은 tanh가 주로 사용됩니다.

 

ReLU로 activation function을 바꿔서 진행해보시면 말도 안되는 값이 나오는것을 확인하실 수 있습니다.

 

 

 

[결과값(softmax)]

결과값입니다.

x_data = "Hi Hell"을 넣었을때 y값으로 어떤걸 예측할까?에 대한 답으로

 

"i Hello"라고 정확하게 나오는 모습입니다.

 

[결과값(sigmoid)]

동일한 조건에서 activation function만 sigmoid로 바꾸었는데도 일단 예측은 잘 하고있습니다. 

Normalizing이 0~1로 되기때문에, -1~1까지 조금더 넓지만, normalizing이 가능한 softmax가 더 좋아보이네요

 

[결과값(relu)]

네.. ReLU로 돌려보니 역시 맛탱이가 가버립니다.

1이상의 값이 1번이라도 나오면 점점 눈덩이처럼 커져서 발산해버리게 되죠

 

예측값도 "hhhhh"로 전혀 학습이 안된모습입니다..

 

 

Summary

 

RNN과 LSTM에대해 간략히 정리해보고 코드로 돌려보았습니다.

 

이번 글에서 중요한 부분은,

 

CNN및 DNN에서 주로 사용했던 ReLU는 더이상 RNN에서는 사용하지 않는다!

 

Vanishing Gradient 문제를 잡기 위해 사용했던 ReLU는 RNN에서는 LSTM및 tanh로 해결했으므로, 굳이 발산할 가능성이 있는 ReLU를 쓸필요가 없고, 쓰면 발산한다!

 

입니다.

 

 

댓글