Simple RNN/LSTM 이해하기

Yelim Kim·2021년 8월 25일
0

Machine_Learning

목록 보기
11/44
post-thumbnail

이 글에서도 데이터를 가지고 실습을 하긴 했지만, 이 글에서는 보다 자세한 이해를 위해 설명하려고 한다.

우선 RNN과 LSTM을 테스트하기 위한 임의의 입력을 만들어주자.

Packages

import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import SimpleRNN, GRU, LSTM, Bidirectional

Input

train_X = [[0.1, 4.2, 1.5, 1.1, 2.8], [1.0, 3.1, 2.5, 0.7, 1.1], [0.3, 2.1, 1.5, 2.1, 0.1], [2.2, 1.4, 0.5, 0.9, 1.1]]
print(np.shape(train_X))


이전 글에서도 언급했다시피, 오른쪽으로 갈 수록 안쪽 차원이 된다.
Simple RNN에서는 이렇게도 말할 수 있다:
4번의 시점(timesteps)이 존재하고, 각 시점마다 5차원의 단어 벡터가 입력으로 사용된다.
이전 글에서 X와 Y의 shape은 모두 3차원이었다. RNN은 3D텐서를 입력받아 병렬로 계산하기 때문에 배치크기 1을 추가해줘야한다.

train_X = [[[0.1, 4.2, 1.5, 1.1, 2.8], [1.0, 3.1, 2.5, 0.7, 1.1], [0.3, 2.1, 1.5, 2.1, 0.1], [2.2, 1.4, 0.5, 0.9, 1.1]]]
train_X = np.array(train_X, dtype=np.float32)
print(train_X.shape)

  • batch_size = 1 #RNN이 한 번에 학습하는 데이터의 양
  • timesteps = 4
  • input_dim = 5

에 해당하는 3D 텐서가 생성되었다.

Simple RNN

위에서 만든 input을 가지고 SimpleRNN모델에 넣어 출력값을 확인해보자.
우선, hidden_state를 3으로 지정하고, return_sequencesreturn_state를 모두 False로 하고 출력해보자.

rnn = SimpleRNN(3, return_sequences=False, return_state=False)
hidden_state = rnn(train_X)

print('hidden state : {}, shape : {}'.format(hidden_state, hidden_state.shape))

아래와 같이 결과가 나온다.

출력결과를 해석해보자.

  • 1은 input.shape = 1이다.
  • 우리는 return_sequencesreturn_state를 모두 False로 했기 때문에 마지막 결과만 출력한 것이다. 즉 마지막 스텝의 히든 스테이트만 가져왔기 때문에 (1,3)에서 3이 나온 것이다.
    그렇다면 return_sequences만 =true로 설정하면 어떻게 될까?

    모든 시점(4개)에서 출력하게 된다.
    그렇다면 return_state만 =true로 설정하면 어떻게 될까?
    여기서 중요한 부분이 있다. return_state =true를 하는 이유는 앞의 sequences가 참인지 거짓인지에 관계없이 마지막 히든 스테이트를 한번 더 리턴하게 되는데 (이 값을 가지고 무언가 하기 위하여) 그래서 받는 인자를 한개 더 만들어줘야 한다. 코드를 조금 수정해보자.
rnn = SimpleRNN(3, return_sequences=False, return_state=True)
hidden_state, last = rnn(train_X)

print('hidden state : {}, shape: {}'.format(hidden_state, hidden_state.shape))
print('last:',last)

이렇게 last라는 인자를 만들어줬고 마지막에 한번 더 프린트 해 보았다. 결과를 확인해보자.

두개의 출력 모두 마지막 시점의 은닉 상태를 출력한다.
그렇다면 이제 감이 올 것이다. 둘다 True로 만들어주자.

위에 두 결과를 합한 것과 같다.
사실 실제로 SimpleRNN이 사용되는 경우는 거의 없다고 한다.
SimpleRNN보다 주로 사용하는 LSTM에 대해 알아보자.

LSTM

lstm = LSTM(3, return_sequences=False, return_state=False)
hidden_state= lstm(train_X)

print('hidden state : {}, shape: {}'.format(hidden_state, hidden_state.shape))

둘다 False부터 시작해보자.

마지막 시점의 은닉 상태를 출력한다.

return_sequences=False, return_state=True상태를 출력해보자.

lstm = LSTM(3, return_sequences=False, return_state=True)
hidden_state, last_state, last_cell_state = lstm(train_X)

print('hidden state : {}, shape: {}'.format(hidden_state, hidden_state.shape))
print('last hidden state : {}, shape: {}'.format(last_state, last_state.shape))
print('last cell state : {}, shape: {}'.format(last_cell_state, last_cell_state.shape))


우선 return_sequences=False이므로 hidden_state으로 마지막 시점의 은닉 상태를 반환한다.
return_state=True이므로 다시한번 반환해야 하는데, LSTM은 SimpleRNN과 다르게 다시한번 hidden state와 동시에 cell state까지 반환한다는 것이다.

마지막으로 둘다 True로 바꿔보자.

lstm = LSTM(3, return_sequences=True, return_state=True)
hidden_states, last_hidden_state, last_cell_state = lstm(train_X)

print('hidden states : {}, shape: {}'.format(hidden_states, hidden_states.shape))
print('last hidden state : {}, shape: {}'.format(last_hidden_state, last_hidden_state.shape))
print('last cell state : {}, shape: {}'.format(last_cell_state, last_cell_state.shape))


return_sequences=True이므로 모든 시점의 은닉 상태가 출력된다.

BiLSTM


LSTM의 양방향이다.
여기서 return_sequences=True인 경우와 return_sequences=False인 경우의 이 어떻게 바뀌는지 확인해보기 위해 출력값을 고정해주자.

k_init = tf.keras.initializers.Constant(value=0.1)
b_init = tf.keras.initializers.Constant(value=0)
r_init = tf.keras.initializers.Constant(value=0.1)

둘다 False인 경우,

bilstm = Bidirectional(LSTM(3, return_sequences=False, return_state=False, kernel_initializer=k_init, bias_initializer=b_init, recurrent_initializer=r_init))
hidden_state = bilstm(train_X)

print('hidden states : {}, shape: {}'.format(hidden_state, hidden_state.shape))


역시 하나의 인자면 된다. 마지막 은닉 상태가 출력된다.
여기서 출력값의 크기가 (1,6)인것에 주목하자. 이는 아래 그림과 같이 정방향 LSTM의 마지막 시점의 은닉 상태와 역방향 LSTM의 첫번째 시점의 은닉 상태가 연결된 채 반환되기 때문이다.

return_sequences=False, return_state=True상태를 출력해보자.

bilstm = Bidirectional(LSTM(3, return_sequences=False, return_state=True, 
                            kernel_initializer=k_init, bias_initializer=b_init, recurrent_initializer=r_init))
hidden_states, forward_h, forward_c, backward_h, backward_c = bilstm(train_X)

print('hidden states : {}, shape: {}'.format(hidden_states, hidden_states.shape))
print('forward state : {}, shape: {}'.format(forward_h, forward_h.shape))
print('backward state : {}, shape: {}'.format(backward_h, backward_h.shape))

이번에는 무려 5개의 인자가 필요하다. 마지막 은닉 상태와 셀 상태가 한번 더 출력되는데, 여기서 정방향 LSTM의 은닉 상태와 셀 상태, 역방향 LSTM의 은닉 상태와 셀 상태 4가지를 반환하기 때문이다.
이 경우에 forward_h는 정방향 시점의 은닉상태와 backward_h는 역방향 LSTM의 첫번째 은닉 상태값이다. 이 두 값을 연결한 값이 hidden_states에 출력되는 값이다.

이 두 값을 출력해보자.

그리고 return_sequences=True, return_state=True일때 값과 비교해보자.

bilstm = Bidirectional(LSTM(3, return_sequences=True, return_state=True, \
                            kernel_initializer=k_init, bias_initializer=b_init, recurrent_initializer=r_init))
hidden_states, forward_h, forward_c, backward_h, backward_c = bilstm(train_X)

print('hidden states : {}, shape: {}'.format(hidden_states, hidden_states.shape))
print('forward state : {}, shape: {}'.format(forward_h, forward_h.shape))
print('backward state : {}, shape: {}'.format(backward_h, backward_h.shape))


이제 모든 시점에서의 은닉 상태를 출력했다. 역방향 LSTM의 첫번째 시점의 은닉 상태는 더 이상 정방향 LSTM의 마지막 시점의 은닉 상태와 연결되는 것이 아니라 정방향 LSTM의 첫번째 시점의 은닉 상태와 연결된다.

여기서 설명을 마치겠다.

profile
뜬금없지만 세계여행이 꿈입니다.

0개의 댓글