이 포스트는 Do it 딥러닝 교과서 (윤성진 저)를 참고하여 만들어졌음!
RNN
시간 혹 공간적 순서 관계가 있는 데이터를 순차 데이터(sequence data)라고 부른다. 순차 데이터는 시공간의 순서 관계에 의해 문맥 혹은 context를 갖는다. 예를 들어 '나는', '밥이', '먹고싶다' 라는 텍스트 데이터는 '나는 밥이 먹고싶다'의 순서로 진행되는 한 문장이다.
하지만 각 세 데이터를 순방향 신경망에 차례대로 입력하는 경우 순방향 신경망은 세 단어 간 문맥을 파악하지 못하여 한 문장을 완성하지 못한다.
이러한 한계점을 돌파하여 데이터의 순차 구조를 인식하고 기억하도록 개발된 것이 바로 순환 신경망 (RNN: recurrent neural network)이다.
RNN structure
기존 신경망과 다르게 RNN은 데이터를 순차적으로 입력받는다. $x, x_{t-1}, x_t, x_{t+1}, \cdots$
RNN은 입력층$(x)$, 은닉층$(h)$, 출력층$(y)$으로 이루어지는데, 일반적으로 은닉층을 깊게 쌓아도 성능이 크게 향상되지 않으므로 보통 1-2 계층으로 쌓는다. 눈여겨보아야 할 점은 한 계층의 은닉상태 $h_{t-1}$가 다음단계 $h_t$로 연결된다는 것이다.
순환 연산방식
- 새로운 데이터 $x_t$를 입력받는다.
- 이전 은닉상태 $h_{t-1}$와 입력데이터 $x_t$를 합쳐서 $(h_{t-1}, x_t)$로 구성하여 은닉층에 입력한다.
- $f_W(h_{t-1}, x_t)$를 이용하여 은닉층 출력 $h_t$를 얻는다.
- $f_W$는 tanh 등의 함수를 나타내며 RNN 모델 종류에 따라 달라진다. $W$는 은닉층 가중치이다.
- 은닉상태 (은닉층 출력) $h_t$을 뉴런연산 하여 최종 $y_t$를 출력한다.
기본 순환 신경망 (Vanilla RNN)
$$\begin{align} \vec{h}_t &= \text{tanh}(W_{hh}\vec{h}_{t-1} + W_{xh} \vec{x}_t) \\ &=\text{tanh} \bigg( (W_{hh} \; W_{xh} \begin{pmatrix} \vec{h}_{t-1} \\ \vec{x}_t \end{pmatrix} \bigg) \\ &= \text{tanh}\bigg(W \begin{pmatrix} \vec{h}\\ \vec{x}_t \end{pmatrix} \bigg), \; W=(W_{hh} \; W_{xh}) \end{align}$$
- $W_{xh}$ : 입력층-은닉층 연결 가중치
- $W_{hh}$ : 은닉층 피드백 연결 가중치
- $W_{hy}$ : 은닉층-출력층 연결 가중치
입력데이터 $\vec{x}_t$와 이전 은닉층의 출력 $\vec{h}_{t-1}$을 가중치로 조합한 후 tanh에 입력하여 새로운 은닉상태 $\vec{h}_t$를 만든다.
은닉층 입력 $(\vec{h}_{t-1}, \vec{x}_t)$와 은닉층 출력 $\vec{h}_t$
초기 은닉상태 $\vec{h}_0 = \vec{0}$으로 두면 단계별로 진행되는 은닉상태 (은닉층 출력)은 다음과 같이 나타난다.
$$\vec{h}_1 = f_W (\vec{h}_0, \vec{x}_1) \\ \vec{h}_2 = f_W(\vec{h}_1, \vec{x}_2) \\ \cdots \\ \vec{h}_t = f_W(\vec{h}_{t-1}, \vec{x}_t) $$
- 첫 번째 은닉상태 $\vec{h}_1$은 입력으로 $\vec{h}_0, \vec{x}_1$을 받으므로 $\vec{h}_1$은 $\vec{x}_1$이 추상화된 콘텍스트를 나타낸다.
- 두 번째 은닉상태 $\vec{h}_2$은 $(\vec{h}_1, \vec{x}_2)$을 입력받는데, 이때 $\vec{h}_1$은 $\vec{x}_1$의 콘텍스트를 포함하므로 두 번째 은닉상태는 $(\vec{x}_1, \vec{x}_2)$가 추상화된 콘텍스트를 나타낸다.
- $t$번째 단계의 은닉상태 $\vec{h}_t$는 $(\vec{h}_{t-1}, \vec{x}_t)$을 입력받으므로 $(\vec{x}_1, \vec{x}_2, \vec{x}_3, \cdots, \vec{x}_t)$가 추상화된 콘텍스트를 나타낸다.
즉 은닉상태는 현재까지 입력된 모든 데이터를 추상화한다.
$t$번째 단계의 은닉상태는 다음과 같다.
$$\vec{h}_t = f_W(\vec{h}_{t-1}, \vec{x}_t)$$
이는 점화식 형태로 표현이 가능하므로 다음과 같이 다시 쓸 수 있다.
$$\vec{h}_t = f_W(f_W(\cdots f_W(f_W(f_W(\vec{h}_0, \vec{x}_1), \vec{x}_2), \vec{x}_3), \cdots, \vec{x}_{t-1}), \vec{x}_t) $$
따라서 각 단계의 출력 $y_1, y_2, \cdots$은 이제까지 입력된 입력들 $x_1, x_2, \cdots$에 대한 조건부 확률을 표현한다.
가중치 공유
RNN은 세 가중치 $W_{xh}, W_{hh}, W_{hy}$는 모든 계층에서 공유하여 사용된다. 쉽게 말해서 모든 계층에서 같은 가중치를 사용하여 학습이 진행된다는 것이다.
- 하나의 가중치가 모든 데이터의 순차구조를 학습할 수 있다. -> 추론과정에서 특정 순차구조가 어느 위치에 나타나더라도 포착할 수 있음
- 단계를 쉽게 추가할 수 있으므로 가변 길이 데이터를 유연하게 처리할 수 있다.
- 파라미터 수가 매우 절약된다.
RNN 모델종류
다대일 모델 (many-to-one)
입력이 many, 출력이 one인 RNN 구조이다. 입력은 순차열이지만 출력은 순차열이 아닐 때 사용한다. 모든 단계에서 입력을 받지만 출력은 마지막 단계에서만 이루어진다.
주로 감정분석 (sentiment analysis)에서 사용된다. 예를들어 영화리뷰 감정분석에 사용될 때, 영화리뷰를 단어 단위로 분할하여 모든 단계에서 입력한다. 모든 단어를 입력했으면 마지막 단계에서 이것이 positive인지 negative인지를 판단하여 마지막 단계에서 출력한다.
다대다 모델 (many-to-many)
many-to-many 모델은 매 단계에서 데이터를 입력받으며, 동시에 모든 단계에서 결과를 출력한다. 주로 Name entity recognition에 사용되는 형태인데, 한 문장을 단어별로 쪼갠 후 각 단계에 입력했을 때 매 단계마다 입력된 단어가 어떤 name entity인지 판단하여 출력한다.
Teacher forcing
Teacher forcing은 위의 many-to-many 모델의 variation으로 현재 단계의 출력을 다음 단계의 은닉층에 전달하는 방식이다. 현재 단계의 예측결과를 teacher signal으로 사용하여 다음 단계의 은닉층에 전달하여 예측 성능을 높인다. 현재 단계의 출력이 다음 단계의 은닉층에게 방향성을 가이드해주기 때문에 teaching의 의미를 갖는다.
다만 teacher forcing을 사용할 때 주의해야 할 점은, 이 방식을 이용하여 train할 때 사용하는 teacher signal은 한 단계에서의 예측 결과가 아니라 그 단계의 target값이 되어야 한다는 것이다. train 과정에서는 예측결과 $y$가 엉망진창이므로 이것을 teacher signal로 사용하는 것이 불가능하다.
Inference 과정에서는 입력 데이터에 대한 target 값을 알 수 없기 때문에 해당 단계에서의 예측값을 teacher signal으로 사용하여 다음 단계 은닉층에 입력한다.
일대다 모델 (one-to-many)
one-to-many는 입력은 순차열이 아니지만 출력은 순차열일 때 사용한다. 예를들어 이미지를 입력했을 때 이를 설명하는 캡션을 출력하고 싶은 경우, 첫 단계에 이미지를 한 번 입력하고 매 단계에서 이미지를 설명하는 문장의 단어들을 출력한다.
Encoder-Decoder model
모델의 입력과 출력 길이가 서로 다른 순차열일 때 사용한다. 입력데이터를 요약하는 encoder, 요약된 데이터를 가지고 출력데이터를 생성하는 decoder로 구성된다. 순차열(sequence)을 다른 순차열로 변환하기 때문에 Seq2Seq 모델이라고도 부른다. 주로 기계번역을 통해 한 언어를 다른 언어로 변환할 때 사용한다.
Encoder와 Decoder는 은닉층으로만 구성된다. Encoder의 마지막 은닉상태를 context vector $C$로 출력하고, 이를 decoder에 전달한다. 이때 decoder에는 context vector 뿐만 아니라 해당 시점에서 예측된 출력을 같이 입력한다.
Backpropagation through time (BPTT)
순환신경망의 순전파가 시간순서를 갖고 차례로 진행된 것의 반대로, 역전파는 그 시간순서를 따라 반대 방향으로 진행된다.
위와 같은 many-to-many이 있다고 하자. 이때 입력 순차열은 $X=(\vec{x}_1, \vec{x}_2, \vec{x}_3, \vec{x}_4)$이고, target 순차열은 $\vec{t}=(t_1, t_2, t_3, t_4)$라고 할 때 각 단계의 출력 $y_1,y_2,y_3,y_4$과 target을 비교하여 Loss를 구할 수 있다.
이때 loss는 다음과 같이 나타날 수 있다.
$$\begin{align} &L_1 = J(f(\vec{x}_1 ; \vec{\theta}), t_1) \\ &L_2 = J(f(\vec{x}_1, \vec{x}_2 ; \vec{\theta}), t_2) \\ &L_3 = J(f(\vec{x}_1, \vec{x}_2, \vec{x}_3 ; \vec{\theta}), t_3) \\ &L_4=J(f(\vec{x}_1, \vec{x}_2, \vec{x}_3, \vec{x}_4 ; \vec{\theta}), t_4) \end{align}$$
전체 Loss는 그냥 각 단계의 loss를 더한 형태로 나타난다.
$$J(f(X; \vec{\theta}), t) = L_1+L_2+L_3+L_4 $$
순전파에서는 가장 마지막 단계였던 $y_4$로부터 시작하여 역전파가 수행된다. $y_4 \rightarrow h_1$, $y_3 \rightarrow h_1$, $y_2 \rightarrow h_1$ 한 단계씩 차례대로 역전파를 수행할 수도 있지만, 굳이 그런 비효율적인 방법은 사용하지 않는다.
우리는 모든 단계가 한 번에 역전파 되도록 한다. 마지막 단계에서 역전파 된 초록색 화살표가 $h_3$에 도착하면 그 loss와 $y_3$으로부터 도착한 loss를 더하여 $h_2$으로 역전파한다.
$h_3$에서의 역전파 자세히 보기
- $\vec{h}_4, y_3$에서 각각 전역미분 $\frac{\partial J}{\partial \vec{h}_4} \cdot \frac{\partial \vec{h}_4}{\partial \vec{h}_3}$와 $\frac{\partial J}{\partial y_3} \cdot \frac{\partial y_3}{\partial \vec{h}_3}$을 받는다.
- 두 전역미분을 더하면 $\frac{\partial J}{\partial \vec{h}_3}$이 된다. $$\frac{\partial J}{\partial \vec{h}_3} = \frac{\partial J}{\partial \vec{h}_4} \cdot \frac{\partial \vec{h}_4}{\partial \vec{h}_3} + \frac{\partial J}{\partial y_3} \cdot \frac{\partial y_3}{\partial \vec{h}_3} $$
- $\vec{h}_3$에서 역방향으로 가는 지역미분 $\frac{\partial \vec{h}}{\partial W_{xh}}, \frac{\partial \vec{h}_3}{\partial W_{hh}}, \frac{\partial \vec{h}_3}{\partial \vec{h}_2}$를 계산한다. $$\begin{align} &\frac{\partial J}{\partial W_{xh}} = \frac{\partial J}{\partial \vec{h}_3} \cdot \frac{\partial \vec{h}_3}{\partial W_{xh}} \\ &\frac{\partial J}{\partial W_{hh}} = \frac{\partial J}{\partial \vec{h}_3} \cdot \frac{\partial \vec{h}_3}{\partial W_{hh}} \\ &\frac{\partial J}{\partial \vec{h}_2} = \frac{\partial J}{\partial \vec{h}_3} \cdot \frac{\partial \vec{h}_3}{\partial \vec{h}_2} \end{align}$$
- 3번의 첫 번째, 두 번째 식을 이용하여 $W_{xh}$, $W_{hh}$를 업데이트 한다.
- 전역미분 $\frac{\partial J}{\partial \vec{h}_2}$를 $\vec{h}_2$에 전달
Truncated BPTT
RNN에서 역전파는 일단 마지막 단계까지 forward가 이루어진 후에 실행된다.
하지만 만약 답도없이 긴 forward가 있다면 어떨까? 너무 긴 입력에 대해서는 역전파가 불가능하다.
이 경우에는 truncated BPTT를 사용한다. 전체 단계를 적절히 나누어 역전파를 수행하는 것이다. 여기서는 전체 단계를 3 묶음으로 나눈다.
먼저 첫 번째 묶음에 대해 forward, backward를 수행한다. backward를 수행하며 forward 과정에서의 weight 업데이트하여 잃어버리지만, 해당 묶음의 마지막 은닉상태를 기억해두었다가 두 번째 묶음이 시작될 때 이것을 전달해주면 context를 계속 유지할 수 있다.
첫 번째 묶음에 대해 backward를 마친 후, 첫 번째 묶음의 마지막 은닉상태를 두 번째 묶음에 전달하여 두 번째 묶음의 forward, backward를 수행한다. 같은 방식으로 세 번째 묶음에 대해서도 forward, backward가 진행된다.
Truncated BPTT를 이용하면 끝이 없는 순차열도 학습이 가능하다.
'딥러닝' 카테고리의 다른 글
14. LSTM (Long Short Term Memory) 개념 + GRU (0) | 2023.11.15 |
---|---|
13. RNN 코드실습 (0) | 2023.10.31 |
VGG net 논문리뷰 + 실습 (0) | 2023.10.04 |
11. 컨볼루션 신경망, CNN (Convolution neural network) (0) | 2023.09.27 |
9. 초기화 Initialization (0) | 2023.09.21 |