이 글에서도 데이터를 가지고 실습을 하긴 했지만, 이 글에서는 보다 자세한 이해를 위해 설명하려고 한다.
우선 RNN과 LSTM을 테스트하기 위한 임의의 입력을 만들어주자.
import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import SimpleRNN, GRU, LSTM, Bidirectional
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)
에 해당하는 3D 텐서가 생성되었다.
위에서 만든 input을 가지고 SimpleRNN모델에 넣어 출력값을 확인해보자.
우선, hidden_state를 3으로 지정하고, return_sequences
와 return_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))
아래와 같이 결과가 나온다.
출력결과를 해석해보자.
input.shape = 1
이다.return_sequences
와 return_state
를 모두 False
로 했기 때문에 마지막 결과만 출력한 것이다. 즉 마지막 스텝의 히든 스테이트만 가져왔기 때문에 (1,3)에서 3이 나온 것이다.return_sequences만 =true
로 설정하면 어떻게 될까?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(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
이므로 모든 시점의 은닉 상태가 출력된다.
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의 첫번째 시점의 은닉 상태와 연결된다.
여기서 설명을 마치겠다.