01. Recurrent Models

Transformer 기반의 모델이 등장하기 전까지 각종 NLP task에서 널리 사용되었던 recurrent models (RNN, LSTM, GRU)를 설명합니다.

Recurrent Models: Motivation

Recurrent Models: Motivation

기존에 사용되던 multi layer perceptron은 fully connected layer를 여러 개 쌓아서 input vector를 output vector로 mapping해주는 형태의 함수라고 볼 수 있습니다. 고정된 크기의 input을 통해 실수 값을 예측하는 regression 문제나, 여러 개의 class로 분류해주는 classification 문제에 적합한 구조입니다.

그러나 이러한 구조의 모델들은 natural language, audio, video 등 시간에 따른 temporal context를 고려해야 하는 경우에는 적합하지 않습니다. 단순하게 생각해서 어떤 하나의 문장을 실수 값의 벡터로 인코딩한 후, MLP에 집어넣어서 output 문장을 생성한다고 하면 이는 단어들 사이의 순서 관계를 완전히 무시하는 결과가 되고 높은 성능을 내기 힘듭니다. 또한 input, output size가 고정되어 있으므로 scalability의 관점에서도 좋지 않습니다.

Recurrent Neural Networks

Handling variable sequences

여기서 핵심은 바로 network architecture가 input sequence에 존재하는 ‘context’를 반영하게 하는 것입니다. 이를 위해서 RNN에서는 hidden state tt를 두고 input이 들어올 때마다 이를 업데이트해주는 방법을 사용합니다. 특정 시점 tt까지 들어온 input들을 어떤 방식으로든 hidden state에 반영하여 특정 시점에서의 output generation에 이를 사용하겠다는 것입니다. 모델이 loop를 돌면서 input sequence에 대한 memory(experience)를 유지하고 활용할 수 있게 된 것입니다.

Hidden state update

구체적으로 보면 input이 들어올 때마다 이전의 hidden state에 weight를 곱해주고, 현재 들어온 input에 또 다른 weight를 곱한 뒤 더해주고 activation function을 통과시켜서 새로운 hidden state를 만들어내게 됩니다.

Output Generation

또한 many to many 아키텍쳐의 경우에는 input이 들어올 때마다 그에 해당하는 output을 만들어내야 합니다. 이는 앞서 업데이트된 hidden state hth_t를 또 다른 weight에 곱해서 만들어내면 됩니다. 여기에서 input과 output의 차원이 같게 나와있지만 다르게 만들 수도 있습니다.

Language Modeling Example

조금 더 구체적인 예제를 살펴보겠습니다. RNN을 이용하여 “ybigta”라는 sequence를 modeling하는 예제입니다. 모델에 들어가고 나올 수 있는 character가 6개로 단순하므로 one-hot encoding을 해주었습니다.

각각의 시점에 input vector xtx_t를 weight WxhW_{xh}와 곱해주고, 이전 시점의 hidden state인 ht1h_{t - 1}을 weight WhhW_{hh}와 곱해준 뒤 elementwise하게 더해줍니다. 그리고 이를 tanhtanh 함수에 통과시켜주면 새로운 hidden state인 hth_t를 구할 수 있게 됩니다.

Hidden state hth_t를 구하고 나면 이를 weight WhyW_{hy}와 곱해서 output vector를 만들어낼 수 있습니다. 마지막 character까지 집어넣고 나면 최종 loss를 구한 뒤 이를 backpropagate하여 loss를 최소화하는 방향으로 weight update를 해주면 됩니다.

실제로 language modeling을 할 때는 character 단위로 하기보다는 word 단위로 해야 하므로 word embedding 내용이 포함됩니다. 또한 test time에는 모델이 문장이 언제 끝날지를 알 수 없으므로 EOS (End of Sequence) token을 예측하는 방식으로 training이 진행됩니다.

Backpropagation Through Time (BPTT)

Neural network에서 loss function을 정하고 나면 loss function의 값을 최소화하는 방향으로 weight update를 해주면 training loss가 점점 줄어들게 됩니다. 이를 위해 loss function을 weight matrix로 편미분하는 과정이 필요한데, RNN에서는 tt라는 하나의 차원이 추가되었기 때문에 살짝 복잡합니다.

우선 loss를 LL이라고 하고, weight는 단순화를 위해 hidden state에 곱해지는 WhhW_{hh}만을 고려해 봅시다. Many to many model을 가정하면 각 시점마다 loss가 존재할 것이고, 이를 LtL_t라고 해주면 total loss는 LtL_t들의 합으로 표현될 수 있습니다. 이를 편미분하는 것은 단순하게 LtL_t를 각각 편미분해서 더하는 것과 같습니다.

LW=t=1TLtW{\partial L \over \partial W} = \sum_{t=1}^T {\partial L_t \over \partial W}

따라서 이제 LtW\partial L_t \over \partial W만 구해주면 total loss를 weight에 대해서 편미분한 값을 얻게 됩니다. 그런데 RNN의 구조상 하나의 weight WW를 모든 시점에서 공유하므로 이를 살짝 바꾸면 모든 hidden state가 바뀌게 됩니다. 따라서 chain rule을 이용하여 모든 시점으로 쪼개서 전개해줘야 합니다.

LtW=Lthththt1...h1W{\partial L_t \over \partial W} = {\partial L_t \over \partial h_t} {\partial h_t \over \partial h_{t - 1}} ... {\partial h_1 \over \partial W}

이를 RNN의 hidden state update 식을 이용하여 조금 더 계산해주면 아래와 같습니다.

LW=t=1TLtW{\partial L \over \partial W} = \sum_{t=1}^T{\partial L_t \over \partial W}
LTW=t=1TLThththt1...h1W=LThT(t=2Ttanh(Whhht1+Wxhxt))WhhT1h1W{\partial L_T \over \partial W} = \sum_{t=1}^T{\partial L_T \over \partial h_t} {\partial h_t \over \partial h_{t - 1}} ... {\partial h_1 \over \partial W} = {\partial L_T \over \partial h_T} (\prod_{t=2}^Ttanh'(W_{hh}h_{t-1} + W_{xh}x_t))W_{hh}^{T - 1}{\partial h_1 \over \partial W}

결과적으로 보자면 TT가 커질수록 똑같은 WhhW_{hh}를 여러 번 곱하게 됩니다. 똑같은 스칼라 값을 계속 곱하면 절댓값이 1보다 큰지 작은지에 따라서 0에 가까워지거나, 아니면 절댓값이 무한히 커지게 됩니다. Matrix의 경우에도 유사하나, 가장 큰 singular value 또는 eigenvalue의 크기에 따라서 gradient가 매우 커지고 작아지는 문제가 나타납니다.

이를 exploding / vanishing gradient 문제라고 하며 RNN을 training할 때 아주 큰 문제가 됩니다. 모델이 long term dependency를 포착하지 못하여 아주 긴 input sequence에 대해서 제대로 동작하지 않는 현상이 나타납니다. Exploding gradient의 경우에는 gradient clipping이라는 방법으로 어느정도 해결이 되는데 vanishing gradient 문제는 모델의 아키텍쳐를 바꿔야 합니다. 이를 위해서 등장한 모델이 LSTM입니다.

LSTM

LSTM은 RNN의 long-term dependency 문제를 해결하기 위해서 등장한 아키텍쳐입니다. LSTM 모델의 key idea는 모델이 기억할 내용과 까먹을 내용을 선택하여 중요한 정보만을 오래 기억할 수 있도록 하겠다는 것입니다. 이를 위해서 gate를 두고 정보의 흐름을 조절하게 됩니다.

  1. Input gate: 현재 시점의 정보를 LSTM cell에 얼마나 입력해줄지 (얼마나 중요한지)를 반영하여 셀에 기록하게 됩니다.

  2. Forget gate: 과거의 정보를 얼마나 잊어버릴지를 결정하게 됩니다. 현재 시점의 input과 hidden state에 가중치를 곱하여 더하고, sigmoid 함수를 통과시켜서 cell state에 곱해주게 됩니다. Sigmoid 함수의 범위가 0에서 1 사이이므로 0일 때는 과거 정보를 잊어버리고 1에 가까울 때에는 많이 기억하게 됩니다.

  3. Output gate: 다음 layer로 전달해줄 hidden state를 만들어내는 부분입니다.

앞서 vanishing gradient 문제는 똑같은 matrix를 여러 번 곱하는 데에서 발생하는 문제였습니다. 여기에서는 gradient flow가 multiplicative하지 않고 additive한 특성을 가지므로 vanishing gradient 문제를 어느정도 해결할 수 있습니다.

GRU

LSTM은 parameter가 많아서 연산 비용이 많이 듭니다. GRU에서는 input gate와 forget gate를 합쳐서 update gate를 만든 뒤 reset gate를 추가하였습니다.

  1. Reset gate: 과거의 정보를 적당하게 reset시켜주는 gate입니다. 이전 hidden layer의 값과 현재 input에 weight를 곱한 뒤 더해주고 sigmoid 함수를 통과시켜 줍니다. 이를 이전 hidden state에 곱해주면 됩니다.

  2. Update gate: 과거 현재 정보를 어떤 비율로 반영할지를 결정하게 됩니다.

Last updated