순환 신경망(RNN) 개념 이해하기
20 Jan 2023 | NLP(Natural Language Processing)
- 순환 신경망(Recurrent Neural Network, RNN)
- RNN에 대한 수식 정의
- 케라스(Keras)로 RNN 구현하기
- 깊은 순환 신경망(Deep Recurrent Neural Network)
- 양방향 순환 신경망(Bidirectional Recurrent Neural Network)
1. 순환 신경망(Recurrent Neural Network, RNN)
RNN(Recurrent Neural Network) 은 입력과 출력을 시퀀스 단위로 처리 하는 시퀀스(Sequence) 모델 입니다. 시퀀스들을 처리하기 위해 고안된 모델들을 시퀀스 모델이라고 하며, RNN은 가장 기본적인 인공신경망 시퀀스 모델입니다.
피드 포워드 신경망(Feed Forward Neural Network) 은 전부 은닉층에서 활성화 함수를 지난 값을 오직 출력층 방향으로만 향했습니다. 하지만 RNN(Recurrent Neural Network) 은 은닉층의 노드에서 활성화 함수를 통해 나온 결과값을 출력층 방향으로도 보내면서, 다시 은닉층 노드의 다음 계산의 입력으로 보내는 특징 을 갖고있습니다.
- $t$ : 현재 시점
- $x$ : 입력층의 입력 벡터
- $y$ : 출력층의 출력 벡터
- 셀(cell)(메모리 셀, RNN 셀)
- RNN에서, 은닉층에서 활성화 함수를 통해 결과를 내보내는 역할을 하는 노드
- 이전의 값을 기억하려고 하는, 일종의 메모리 역할을 수행
은닉층의 메모리 셀 은 각각의 시점(time step) 에서 입력층의 입력 벡터 와 바로 이전 시점에서의 은닉층의 메모리 셀에서 나온 값을 자신의 입력으로 사용 하는 재귀적 활동 을 하고 있습니다. 이는 현재 시점 $t$ 에서의 메모리셀이 갖고있는 값은 과거의 메모리 셀들의 값에 영향을 받은 것임을 의미 합니다.
이때 메모리 셀이 출력층 방향 또는 다음 시점인 $t+1$ 의 자신에게 보내는 값 을 은닉 상태(hidden state) 라고 합니다. 다시말해 $t$ 시점의 메모리 셀은 $t‐1$ 시점의 메모리 셀이 보낸 은닉 상태값을 $t$ 시점의 은닉 상태 계산을 위한 입력값으로 사용합니다.
위 그림은 동일한 그림으로 단지 사이클을 그리는 화살표를 사용하여 표현하였느냐, 시점의 흐름에 따라서 표현하였느냐의 차이일 뿐 둘 다 동일한 RNN 을 표현하고 있습니다.
RNN 에서는 뉴런이라는 단위보다는 입력층과 출력층에서는 각각 입력 벡터 와 출력 벡터, 은닉층에서는 은닉 상태 라는 표현을 주로 사용합니다. 피드 포워드 신경망과의 차이를 비교하기 위해서 RNN 을 뉴런 단위로 시각화해보겠습니다.
RNN(순환 신경망)은 은닉층에 사이클이 존재하는 신경망이라는 점이 FFNN(순방향 신경망)과 다릅니다.
위의 그림은 입력 벡터의 차원이 4, 은닉 상태의 크기가 2, 출력층의 출력 벡터의 차원이 2인 RNN의 시점이 2 일 때의 모습을 보여줍니다. 다시 말해 뉴런 단위로 해석하면 입력층의 뉴런수는 4, 은닉층의 뉴런 수는 2, 출력층의 뉴런 수는 2입니다.
RNN은 입력과 출력의 길이를 다르게 설계 할 수 있으므로 다양한 용도로 사용할 수 있습니다. 위 그림은 입력과 출력의 길이에 따라서 달라지는 RNN의 다양한 형태 를 보여줍니다. RNN 셀의 각 시점의 가장 보편적인 입출력의 단위 는 ‘단어 벡터’ 입니다. 위 구조가 자연어 처리에서 어떻게 사용될 수 있는지 예를 들어보겠습니다.
- 일 대 다(one‐to‐many)
- 하나의 입력에 대해서 여러개의 출력을 의미
- 이미지 캡셔닝(Image Captioning) : 하나의 이미지 입력에 대해서 사진의 제목을 출력. 사진의 제목은 단어들의 나열이므로 시퀀스 출력입니다.
- 다 대 일(many‐to‐one)
- 단어 시퀀스에 대해서 하나의 출력을 의미
- 감성 분류(sentiment classification) : 입력 문서가 긍정적인지 부정적인지를 판별
- 스팸 메일 분류(spam detection) : 메일이 정상 메일인지 스팸 메일인지 판별
- 다 대 다(many‐to‐many)
- 챗봇 : 사용자가 문장을 입력하면 대답 문장을 출력을 의미
- 번역기 : 입력 문장으로부터 번역된 문장을 출력
- 태깅 작업 : 개체명 인식이나 품사 태깅과 같은 작업
2. RNN에 대한 수식 정의
현재 시점 $t$ 에서의 은닉 상태값을 $h_t$ 라고 정의하겠습니다. 은닉층의 메모리 셀은 $h_t$ 를 계산하기 위해서 총 두 개의 가중치 를 가집니다. 하나는 입력층을 위한 가중치 $W_x$ 이고, 하나는 이전 시점 $t‐1$ 의 은닉 상태값인 $h_{t−1}$ 을 위한 가중치 $W_h$ 입니다. 이를 식으로 표현하면 다음과 같습니다.
- 은닉층 : $h_t = tanh(W_x x_t + W_h h_{t−1} + b)$
- 출력층 : $y_t = f(W_y h_t + b)$
- $f$ : 비선형 활성화 함수
자연어 처리에서 RNN의 입력 $x_t$ 는 대부분의 경우 단어 벡터로 간주 할 수 있는데, 단어 벡터의 차원 을 $d$ 라고 하고, 은닉 상태의 크기 를 $D_h$ 라고 하였을 때 각 벡터와 행렬의 크기는 다음과 같습니다.
- $x_t$
- RNN의 입력, 대부분의 경우 단어 벡터로 간주
- $d × 1$
- $W_x$
- 입력층을 위한 가중치
- $D_h × d$
- $W_h$
- 이전 시점 $t‐1$ 의 은닉 상태값인 $h_{t−1}$ 을 위한 가중치
- $D_h × D_h$
- $h_{t−1}$
- 이전 시점 $t-1$ 에서의 은닉 상태값
- $D_h × 1$
- $b$
- $D_h × 1$
- $h_t$
- 현재 시점 $t$ 에서의 은닉 상태값
- $D_h × 1$
배치 크기가 1이고, $d$ 와 $D_h$ 두 값 모두를 4로 가정하였을때, RNN의 은닉층 연산을 그림으로 표현하면 다음과 같습니다.
이때 $h_t$ 를 계산하기 위한 활성화 함수로는 주로 tanh 가 사용됩니다. 위의 식에서 각각의 가중치 $W_x$, $W_h$, $W_y$ 의 값은 하나의 층에서는 모든 시점에서 값을 동일하게 공유 합니다. 하지만 은닉층이 2개 이상일 경우에는 각 은닉층에서의 가중치는 서로 다릅니다.
출력층은 결과값인 $y_t$ 를 계산하기 위한 활성화 함수로는 푸는 문제에 따라서 달라지는데, 예를 들어 이진 분류시 출력층에 시그모이드 함수, 다중 클래스 분류시 출력층에 소프트맥스 함수를 사용할 수 있습니다.
3. 케라스(Keras)로 RNN 구현하기
from tensorflow.keras.layers import SimpleRNN
# RNN층 추가
model.add(SimpleRNN(hidden_units))
# 추가 인자를 사용할때
model.add(SimpleRNN(hidden_units, input_shape=(timesteps, input_dim)))
# 다른 표기
model.add(SimpleRNN(hidden_units, input_length=M, input_dim=N))
RNN 층은 (batch_size, timesteps, input_dim) 크기의 3D 텐서를 입력으로 받습니다.
- hidden_units : $D_h$
- 은닉 상태의 크기를 정의. 메모리 셀의 용량.
- 메모리 셀이 다음 시점의 메모리 셀과 출력층으로 보내는 값의 크기(output_dim)와 동일.
- RNN의 용량(capacity)을 늘린다고 보면 되며, 중소형 모델의 경우 보통 128, 256, 512, 1024 등의 값을 가짐.
- timesteps
- 입력 시퀀스의 길이(input_length) 라고 표현.
- 시점의 수. NLP에서는 보통 문장의 길이가 된다.
- input_dim : $d$
- NLP에서는 보통 단어 벡터의 차원이 된다.
- 입력의 크기.
위 코드는 주로 은닉층으로 간주할 수 있는 하나의 RNN 층에 대한 코드로, 해당 코드가 리턴하는 결과값은 하나의 은닉 상태 또는 정의하기에 따라 여러 개의 시점의 은닉 상태 입니다. 아래의 그림은 전결합층(Fully‐connected layer)을 출력층으로 사용하였을 경우의 인공 신경망 그림과 은닉층까지만 표현한 그림의 차이를 보여줍니다.
RNN 층은 3D 텐서를 입력받아 사용자의 설정에 따라 두 가지 종류의 출력을 내보냅니다. 메모리 셀의 최종 시점의 은닉 상태만을 리턴 하고자 한다면 (batch_size, output_dim) 크기의 2D 텐서를 리턴 합니다. output_dim 은 앞서 코드에서 정의한 hidden_units 의 값으로 설정 됩니다.
하지만, 메모리 셀의 각 시점(time step) 의 은닉 상태값들을 모아서 전체 시퀀스를 리턴 하고자 한다면 (batch_size, timesteps, output_dim) 크기의 3D 텐서를 리턴 합니다. 이는 RNN 층의 return_sequences 매개 변수에 True 를 설정 하여 설정이 가능합니다.
위의 그림은 time step=3 일 때, return_sequences=True 를 설정했을 때와 그렇지 않았을 때 어떤 차이가 있는지를 보여줍니다. return_sequences=True 를 선택하면 메모리 셀이 모든 시점(time step)에 대해서 은닉 상태값을 출력하며, return_sequences=False 로 선택할 경우에는 메모리 셀은 하나의 은닉 상태값만을 출력합니다. 그리고 이 하나의 값은 마지막 시점(time step)의 메모리 셀의 은닉 상태값입니다.
실습을 통해 모델 내부적으로 출력 결과를 어떻게 정의하는지 이해해봅시다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import SimpleRNN
model = Sequential()
model.add(SimpleRNN(3, input_shape=(2, 10)))
# model.add(SimpleRNN(3, input_length=2, input_dim=10))와 동일
model.summary()
[output]
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn (SimpleRNN) (None, 3) 42
=================================================================
Total params: 42
Trainable params: 42
Non-trainable params: 0
_________________________________________________________________
출력값이 (batch_size, output_dim) 크기의 2D 텐서일 때, output_dim 은 hidden_units 의 값인 3입니다. 이 경우 batch_size 를 현 단계에서는 알 수 없으므로 (None, 3) 이 됩니다.
위의 예제의 경우 $D_h = 3$, $t = 2$(RNN의 특성상 모든 시점에 히든 스테이트를 공유하므로, time은 변수의 개수에 관계없음), $d = 10$ 이므로, 아래 계산과정으로 파라미터의 수를 카운팅할 수 있습니다. params = (Dh * Dh) + (Dh * d) + (Dh) = (3 * 3) + (3 * 10) + (3) = 42.
이번에는 batch_size 를 미리 정의해보겠습니다.
model = Sequential()
model.add(SimpleRNN(3, batch_input_shape=(8, 2, 10)))
model.summary()
[output]
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn_1 (SimpleRNN) (8, 3) 42
=================================================================
Total params: 42
Trainable params: 42
Non-trainable params: 0
_________________________________________________________________
batch_size 를 8로 기재하면 출력의 크기가 (8, 3) 이 됩니다. return_sequences 매개 변수에 True 를 기재 하여 출력값으로 (batch_size, timesteps, output_dim) 크기의 3D 텐서를 리턴하도록 모델을 만들어 보겠습니다.
model = Sequential()
model.add(SimpleRNN(3, batch_input_shape=(8, 2, 10), return_sequences=True))
model.summary()
[output]
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn_2 (SimpleRNN) (8, 2, 3) 42
=================================================================
Total params: 42
Trainable params: 42
Non-trainable params: 0
_________________________________________________________________
출력의 크기가 (8, 2, 3) 이 됩니다.
4. 깊은 순환 신경망(Deep Recurrent Neural Network)
RNN도 다수의 은닉층을 가질수 있으며, 위 그림은 순환 신경망에서 은닉층이 1개 더 추가되어 은닉층이 2개인 깊은(deep) 순환 신경망 의 모습을 보여줍니다. 은닉층을 2개 추가하는 경우 코드는 아래와 같습니다.
hidden_size = 8
model = Sequential()
model.add(SimpleRNN(hidden_size, input_length=10, input_dim=5, return_sequences=True))
model.add(SimpleRNN(hidden_size, return_sequences=True))
model.summary()
[output]
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn_3 (SimpleRNN) (None, 10, 8) 112
simple_rnn_4 (SimpleRNN) (None, 10, 8) 136
=================================================================
Total params: 248
Trainable params: 248
Non-trainable params: 0
_________________________________________________________________
첫번째 은닉층은 다음 은닉층이 존재하므로 return_sequences=True 를 설정하여 모든 시점에 대해서 은닉 상태 값을 다음 은닉층으로 보내주고 있습니다.
5. 양방향 순환 신경망(Bidirectional Recurrent Neural Network)
양방향 순환 신경망 은 시점 $t$ 에서의 출력값을 예측할때 이전 시점의 입력뿐만 아니라, 이후 시점의 입력 또한 예측에 기여할 수 있다는 아이디어에 기반 합니다. 빈칸 채우기 문제에 비유하여 보겠습니다.
운동을 열심히 하는 것은[ ]을 늘리는데 효과적이다.
1) 근 육
2) 지 방
3) 스 트 레 스
‘운동을 열심히 하는 것은 [ ] 을 늘리는데 효과적이다.’ 라는 문장에서 문맥 상으로 정답은 ‘근육’ 입니다. 위의 빈칸 채우기 문제를 풀 때 이전에 나온 단어들만으로 빈칸을 채우려고 시도해보면 정보가 부족합니다.
RNN이 풀고자 하는 문제 중에서는 과거 시점의 입력 뿐만 아니라 미래 시점의 입력에 힌트가 있는 경우도 많습니다. 그래서 이전과 이후의 시점 모두를 고려해서 현재 시점의 예측을 더욱 정확하게 할 수 있도록 고안된 것 이 양방향 RNN 입니다.
양방향 RNN은 하나의 출력값을 예측하기 위해 기본적으로 두 개의 메모리 셀을 사용 합니다. 첫번째 메모리 셀 은 앞 시점의 은닉 상태(Forward States) 를 전달받아 현재의 은닉 상태를 계산 합니다. 위의 그림에서는 주황색 메모리 셀에 해당됩니다. 두번째 메모리 셀 은 앞 시점의 은닉 상태가 아니라 뒤 시점의 은닉 상태(Backward States) 를 전달받아 현재의 은닉 상태를 계산 합니다. 입력 시퀀스를 반대 방향으로 읽는 것입니다. 위의 그림에서는 초록색 메모리 셀에 해 당됩니다. 그리고 이 두 개의 값 모두가 현재 시점의 출력층에서 출력값을 예측하기 위해 사용됩니다.
from tensorflow.keras.layers import Bidirectional
hidden_size = 8
timesteps = 10
input_dim = 5
model = Sequential()
model.add(Bidirectional(SimpleRNN(hidden_size, return_sequences=True), input_shape=(timesteps, input_dim)))
model.summary()
[output]
Model: "sequential_4"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
bidirectional (Bidirectiona (None, 10, 16) 224
l)
=================================================================
Total params: 224
Trainable params: 224
Non-trainable params: 0
_________________________________________________________________
양방향 RNN도 다수의 은닉층을 가질 수 있습니다. 아래의 그림은 양방향 순환 신경망에서 은닉층이 1개 더 추가되어 은닉층이 2개인 깊은(deep) 양방향 순환 신경망 의 모습을 보여줍니다.
다른 인공 신경망 모델들도 마찬가지이지만, 은닉층을 무조건 추가한다고 해서 모델의 성능이 좋아지는 것은 아닙니다. 은닉층을 추가하면 학습할 수 있는 양이 많아지지만 반대로 훈련데이터 또한 많은 양이 필요합니다.
- 순환 신경망(Recurrent Neural Network, RNN)
- RNN에 대한 수식 정의
- 케라스(Keras)로 RNN 구현하기
- 깊은 순환 신경망(Deep Recurrent Neural Network)
- 양방향 순환 신경망(Bidirectional Recurrent Neural Network)
1. 순환 신경망(Recurrent Neural Network, RNN)
RNN(Recurrent Neural Network) 은 입력과 출력을 시퀀스 단위로 처리 하는 시퀀스(Sequence) 모델 입니다. 시퀀스들을 처리하기 위해 고안된 모델들을 시퀀스 모델이라고 하며, RNN은 가장 기본적인 인공신경망 시퀀스 모델입니다.
피드 포워드 신경망(Feed Forward Neural Network) 은 전부 은닉층에서 활성화 함수를 지난 값을 오직 출력층 방향으로만 향했습니다. 하지만 RNN(Recurrent Neural Network) 은 은닉층의 노드에서 활성화 함수를 통해 나온 결과값을 출력층 방향으로도 보내면서, 다시 은닉층 노드의 다음 계산의 입력으로 보내는 특징 을 갖고있습니다.
- $t$ : 현재 시점
- $x$ : 입력층의 입력 벡터
- $y$ : 출력층의 출력 벡터
- 셀(cell)(메모리 셀, RNN 셀)
- RNN에서, 은닉층에서 활성화 함수를 통해 결과를 내보내는 역할을 하는 노드
- 이전의 값을 기억하려고 하는, 일종의 메모리 역할을 수행
은닉층의 메모리 셀 은 각각의 시점(time step) 에서 입력층의 입력 벡터 와 바로 이전 시점에서의 은닉층의 메모리 셀에서 나온 값을 자신의 입력으로 사용 하는 재귀적 활동 을 하고 있습니다. 이는 현재 시점 $t$ 에서의 메모리셀이 갖고있는 값은 과거의 메모리 셀들의 값에 영향을 받은 것임을 의미 합니다.
이때 메모리 셀이 출력층 방향 또는 다음 시점인 $t+1$ 의 자신에게 보내는 값 을 은닉 상태(hidden state) 라고 합니다. 다시말해 $t$ 시점의 메모리 셀은 $t‐1$ 시점의 메모리 셀이 보낸 은닉 상태값을 $t$ 시점의 은닉 상태 계산을 위한 입력값으로 사용합니다.
위 그림은 동일한 그림으로 단지 사이클을 그리는 화살표를 사용하여 표현하였느냐, 시점의 흐름에 따라서 표현하였느냐의 차이일 뿐 둘 다 동일한 RNN 을 표현하고 있습니다.
RNN 에서는 뉴런이라는 단위보다는 입력층과 출력층에서는 각각 입력 벡터 와 출력 벡터, 은닉층에서는 은닉 상태 라는 표현을 주로 사용합니다. 피드 포워드 신경망과의 차이를 비교하기 위해서 RNN 을 뉴런 단위로 시각화해보겠습니다.
RNN(순환 신경망)은 은닉층에 사이클이 존재하는 신경망이라는 점이 FFNN(순방향 신경망)과 다릅니다.
위의 그림은 입력 벡터의 차원이 4, 은닉 상태의 크기가 2, 출력층의 출력 벡터의 차원이 2인 RNN의 시점이 2 일 때의 모습을 보여줍니다. 다시 말해 뉴런 단위로 해석하면 입력층의 뉴런수는 4, 은닉층의 뉴런 수는 2, 출력층의 뉴런 수는 2입니다.
RNN은 입력과 출력의 길이를 다르게 설계 할 수 있으므로 다양한 용도로 사용할 수 있습니다. 위 그림은 입력과 출력의 길이에 따라서 달라지는 RNN의 다양한 형태 를 보여줍니다. RNN 셀의 각 시점의 가장 보편적인 입출력의 단위 는 ‘단어 벡터’ 입니다. 위 구조가 자연어 처리에서 어떻게 사용될 수 있는지 예를 들어보겠습니다.
- 일 대 다(one‐to‐many)
- 하나의 입력에 대해서 여러개의 출력을 의미
- 이미지 캡셔닝(Image Captioning) : 하나의 이미지 입력에 대해서 사진의 제목을 출력. 사진의 제목은 단어들의 나열이므로 시퀀스 출력입니다.
- 다 대 일(many‐to‐one)
- 단어 시퀀스에 대해서 하나의 출력을 의미
- 감성 분류(sentiment classification) : 입력 문서가 긍정적인지 부정적인지를 판별
- 스팸 메일 분류(spam detection) : 메일이 정상 메일인지 스팸 메일인지 판별
- 다 대 다(many‐to‐many)
- 챗봇 : 사용자가 문장을 입력하면 대답 문장을 출력을 의미
- 번역기 : 입력 문장으로부터 번역된 문장을 출력
- 태깅 작업 : 개체명 인식이나 품사 태깅과 같은 작업
2. RNN에 대한 수식 정의
현재 시점 $t$ 에서의 은닉 상태값을 $h_t$ 라고 정의하겠습니다. 은닉층의 메모리 셀은 $h_t$ 를 계산하기 위해서 총 두 개의 가중치 를 가집니다. 하나는 입력층을 위한 가중치 $W_x$ 이고, 하나는 이전 시점 $t‐1$ 의 은닉 상태값인 $h_{t−1}$ 을 위한 가중치 $W_h$ 입니다. 이를 식으로 표현하면 다음과 같습니다.
- 은닉층 : $h_t = tanh(W_x x_t + W_h h_{t−1} + b)$
- 출력층 : $y_t = f(W_y h_t + b)$
- $f$ : 비선형 활성화 함수
자연어 처리에서 RNN의 입력 $x_t$ 는 대부분의 경우 단어 벡터로 간주 할 수 있는데, 단어 벡터의 차원 을 $d$ 라고 하고, 은닉 상태의 크기 를 $D_h$ 라고 하였을 때 각 벡터와 행렬의 크기는 다음과 같습니다.
- $x_t$
- RNN의 입력, 대부분의 경우 단어 벡터로 간주
- $d × 1$
- $W_x$
- 입력층을 위한 가중치
- $D_h × d$
- $W_h$
- 이전 시점 $t‐1$ 의 은닉 상태값인 $h_{t−1}$ 을 위한 가중치
- $D_h × D_h$
- $h_{t−1}$
- 이전 시점 $t-1$ 에서의 은닉 상태값
- $D_h × 1$
- $b$
- $D_h × 1$
- $h_t$
- 현재 시점 $t$ 에서의 은닉 상태값
- $D_h × 1$
배치 크기가 1이고, $d$ 와 $D_h$ 두 값 모두를 4로 가정하였을때, RNN의 은닉층 연산을 그림으로 표현하면 다음과 같습니다.
이때 $h_t$ 를 계산하기 위한 활성화 함수로는 주로 tanh 가 사용됩니다. 위의 식에서 각각의 가중치 $W_x$, $W_h$, $W_y$ 의 값은 하나의 층에서는 모든 시점에서 값을 동일하게 공유 합니다. 하지만 은닉층이 2개 이상일 경우에는 각 은닉층에서의 가중치는 서로 다릅니다.
출력층은 결과값인 $y_t$ 를 계산하기 위한 활성화 함수로는 푸는 문제에 따라서 달라지는데, 예를 들어 이진 분류시 출력층에 시그모이드 함수, 다중 클래스 분류시 출력층에 소프트맥스 함수를 사용할 수 있습니다.
3. 케라스(Keras)로 RNN 구현하기
from tensorflow.keras.layers import SimpleRNN
# RNN층 추가
model.add(SimpleRNN(hidden_units))
# 추가 인자를 사용할때
model.add(SimpleRNN(hidden_units, input_shape=(timesteps, input_dim)))
# 다른 표기
model.add(SimpleRNN(hidden_units, input_length=M, input_dim=N))
RNN 층은 (batch_size, timesteps, input_dim) 크기의 3D 텐서를 입력으로 받습니다.
- hidden_units : $D_h$
- 은닉 상태의 크기를 정의. 메모리 셀의 용량.
- 메모리 셀이 다음 시점의 메모리 셀과 출력층으로 보내는 값의 크기(output_dim)와 동일.
- RNN의 용량(capacity)을 늘린다고 보면 되며, 중소형 모델의 경우 보통 128, 256, 512, 1024 등의 값을 가짐.
- timesteps
- 입력 시퀀스의 길이(input_length) 라고 표현.
- 시점의 수. NLP에서는 보통 문장의 길이가 된다.
- input_dim : $d$
- NLP에서는 보통 단어 벡터의 차원이 된다.
- 입력의 크기.
위 코드는 주로 은닉층으로 간주할 수 있는 하나의 RNN 층에 대한 코드로, 해당 코드가 리턴하는 결과값은 하나의 은닉 상태 또는 정의하기에 따라 여러 개의 시점의 은닉 상태 입니다. 아래의 그림은 전결합층(Fully‐connected layer)을 출력층으로 사용하였을 경우의 인공 신경망 그림과 은닉층까지만 표현한 그림의 차이를 보여줍니다.
RNN 층은 3D 텐서를 입력받아 사용자의 설정에 따라 두 가지 종류의 출력을 내보냅니다. 메모리 셀의 최종 시점의 은닉 상태만을 리턴 하고자 한다면 (batch_size, output_dim) 크기의 2D 텐서를 리턴 합니다. output_dim 은 앞서 코드에서 정의한 hidden_units 의 값으로 설정 됩니다.
하지만, 메모리 셀의 각 시점(time step) 의 은닉 상태값들을 모아서 전체 시퀀스를 리턴 하고자 한다면 (batch_size, timesteps, output_dim) 크기의 3D 텐서를 리턴 합니다. 이는 RNN 층의 return_sequences 매개 변수에 True 를 설정 하여 설정이 가능합니다.
위의 그림은 time step=3 일 때, return_sequences=True 를 설정했을 때와 그렇지 않았을 때 어떤 차이가 있는지를 보여줍니다. return_sequences=True 를 선택하면 메모리 셀이 모든 시점(time step)에 대해서 은닉 상태값을 출력하며, return_sequences=False 로 선택할 경우에는 메모리 셀은 하나의 은닉 상태값만을 출력합니다. 그리고 이 하나의 값은 마지막 시점(time step)의 메모리 셀의 은닉 상태값입니다.
실습을 통해 모델 내부적으로 출력 결과를 어떻게 정의하는지 이해해봅시다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import SimpleRNN
model = Sequential()
model.add(SimpleRNN(3, input_shape=(2, 10)))
# model.add(SimpleRNN(3, input_length=2, input_dim=10))와 동일
model.summary()
[output]
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn (SimpleRNN) (None, 3) 42
=================================================================
Total params: 42
Trainable params: 42
Non-trainable params: 0
_________________________________________________________________
출력값이 (batch_size, output_dim) 크기의 2D 텐서일 때, output_dim 은 hidden_units 의 값인 3입니다. 이 경우 batch_size 를 현 단계에서는 알 수 없으므로 (None, 3) 이 됩니다.
위의 예제의 경우 $D_h = 3$, $t = 2$(RNN의 특성상 모든 시점에 히든 스테이트를 공유하므로, time은 변수의 개수에 관계없음), $d = 10$ 이므로, 아래 계산과정으로 파라미터의 수를 카운팅할 수 있습니다. params = (Dh * Dh) + (Dh * d) + (Dh) = (3 * 3) + (3 * 10) + (3) = 42.
이번에는 batch_size 를 미리 정의해보겠습니다.
model = Sequential()
model.add(SimpleRNN(3, batch_input_shape=(8, 2, 10)))
model.summary()
[output]
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn_1 (SimpleRNN) (8, 3) 42
=================================================================
Total params: 42
Trainable params: 42
Non-trainable params: 0
_________________________________________________________________
batch_size 를 8로 기재하면 출력의 크기가 (8, 3) 이 됩니다. return_sequences 매개 변수에 True 를 기재 하여 출력값으로 (batch_size, timesteps, output_dim) 크기의 3D 텐서를 리턴하도록 모델을 만들어 보겠습니다.
model = Sequential()
model.add(SimpleRNN(3, batch_input_shape=(8, 2, 10), return_sequences=True))
model.summary()
[output]
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn_2 (SimpleRNN) (8, 2, 3) 42
=================================================================
Total params: 42
Trainable params: 42
Non-trainable params: 0
_________________________________________________________________
출력의 크기가 (8, 2, 3) 이 됩니다.
4. 깊은 순환 신경망(Deep Recurrent Neural Network)
RNN도 다수의 은닉층을 가질수 있으며, 위 그림은 순환 신경망에서 은닉층이 1개 더 추가되어 은닉층이 2개인 깊은(deep) 순환 신경망 의 모습을 보여줍니다. 은닉층을 2개 추가하는 경우 코드는 아래와 같습니다.
hidden_size = 8
model = Sequential()
model.add(SimpleRNN(hidden_size, input_length=10, input_dim=5, return_sequences=True))
model.add(SimpleRNN(hidden_size, return_sequences=True))
model.summary()
[output]
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn_3 (SimpleRNN) (None, 10, 8) 112
simple_rnn_4 (SimpleRNN) (None, 10, 8) 136
=================================================================
Total params: 248
Trainable params: 248
Non-trainable params: 0
_________________________________________________________________
첫번째 은닉층은 다음 은닉층이 존재하므로 return_sequences=True 를 설정하여 모든 시점에 대해서 은닉 상태 값을 다음 은닉층으로 보내주고 있습니다.
5. 양방향 순환 신경망(Bidirectional Recurrent Neural Network)
양방향 순환 신경망 은 시점 $t$ 에서의 출력값을 예측할때 이전 시점의 입력뿐만 아니라, 이후 시점의 입력 또한 예측에 기여할 수 있다는 아이디어에 기반 합니다. 빈칸 채우기 문제에 비유하여 보겠습니다.
운동을 열심히 하는 것은[ ]을 늘리는데 효과적이다.
1) 근 육
2) 지 방
3) 스 트 레 스
‘운동을 열심히 하는 것은 [ ] 을 늘리는데 효과적이다.’ 라는 문장에서 문맥 상으로 정답은 ‘근육’ 입니다. 위의 빈칸 채우기 문제를 풀 때 이전에 나온 단어들만으로 빈칸을 채우려고 시도해보면 정보가 부족합니다.
RNN이 풀고자 하는 문제 중에서는 과거 시점의 입력 뿐만 아니라 미래 시점의 입력에 힌트가 있는 경우도 많습니다. 그래서 이전과 이후의 시점 모두를 고려해서 현재 시점의 예측을 더욱 정확하게 할 수 있도록 고안된 것 이 양방향 RNN 입니다.
양방향 RNN은 하나의 출력값을 예측하기 위해 기본적으로 두 개의 메모리 셀을 사용 합니다. 첫번째 메모리 셀 은 앞 시점의 은닉 상태(Forward States) 를 전달받아 현재의 은닉 상태를 계산 합니다. 위의 그림에서는 주황색 메모리 셀에 해당됩니다. 두번째 메모리 셀 은 앞 시점의 은닉 상태가 아니라 뒤 시점의 은닉 상태(Backward States) 를 전달받아 현재의 은닉 상태를 계산 합니다. 입력 시퀀스를 반대 방향으로 읽는 것입니다. 위의 그림에서는 초록색 메모리 셀에 해 당됩니다. 그리고 이 두 개의 값 모두가 현재 시점의 출력층에서 출력값을 예측하기 위해 사용됩니다.
from tensorflow.keras.layers import Bidirectional
hidden_size = 8
timesteps = 10
input_dim = 5
model = Sequential()
model.add(Bidirectional(SimpleRNN(hidden_size, return_sequences=True), input_shape=(timesteps, input_dim)))
model.summary()
[output]
Model: "sequential_4"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
bidirectional (Bidirectiona (None, 10, 16) 224
l)
=================================================================
Total params: 224
Trainable params: 224
Non-trainable params: 0
_________________________________________________________________
양방향 RNN도 다수의 은닉층을 가질 수 있습니다. 아래의 그림은 양방향 순환 신경망에서 은닉층이 1개 더 추가되어 은닉층이 2개인 깊은(deep) 양방향 순환 신경망 의 모습을 보여줍니다.
다른 인공 신경망 모델들도 마찬가지이지만, 은닉층을 무조건 추가한다고 해서 모델의 성능이 좋아지는 것은 아닙니다. 은닉층을 추가하면 학습할 수 있는 양이 많아지지만 반대로 훈련데이터 또한 많은 양이 필요합니다.