본문 바로가기

AI/CS231N

Lecture 10 : Recurrent Neural Networks

해당 게시물은 Standford 2017 CS231n 강의를 들으며 정리한 내용이며 슬라이드를 바탕으로 작성되었습니다.

이번 강의는 Recurrent Neural Networks에 관한 내용입니다.

위 그림은 RNN을 이용해서 만들 수 있는 다양한 모델에 대한 다이어그램입니다. 제일 왼쪽에 있는 one to one 모델부터 many to many까지 간단하게 설명해보겠습니다.

  • one-to-one : ‘Vanilla’ Neural Network의 모양을 띄고 있습니다. 입력으로 하나의 이미지나 벡터를 받아서 하나의 출력을 만들어내는 모델입니다. 하지만 Machine Learning의 관점으로 볼 때 모델이 다양한 입력을 처리할 수 있도록 유연해질 필요가 있기 때문에 RNN이 만들어졌습니다.
  • one-to-many : 입력으로는 이미지와 같은 단일 입력을 받지만 출력으로는 Caption과 같은 가변 출력을 만들어냅니다.
  • many-to-one : 가변 입력을 받아서 단일 출력을 만들어내는 모델입니다. NLP에서 이러한 모델이 활용되는 부분은 입력으로 text를 받아서 출력으로 그 text에 대한 감성 분석을 진행하는 경우가 있을 수 있습니다. CV에서는 입력으로 비디오(프레임 수가 다양함)를 받게 되고 출력으로는 전체 비디오의 activity or action을 분류하는 모델을 예로 들 수 있습니다.
  • many-to-many : 가변 입력을 받아서 가변 출력을 만들어내는 모델입니다. 예를 들면 Machine translation모델이 있습니다. 입력으로 영어 문장을 받아서 출력으로 번역된 프랑스어 문장을 만들어내는 모델이 many-to-many 모델이라고 볼 수 있습니다.
  • many-to-many(각 스텝마다 출력을 뽑아냄) : 같은 many-to-many지만 앞의 모델과의 차이점은 step이 진행됨에 따라 각 step마다 출력을 만든다는 차이가 있습니다.

이처럼 RNN은 다양한 모양으로 만들 수 있고 입・출력이 고정이지만 Sequential processing이 요구되는 경우 RNN이 아주 유용합니다.

위 그림은 RNN을 간략하게 나타낸 그림입니다. 그림의 초록색 박스 Recurrent Core Cell 안에는 hidden state가 들어있습니다.

  • hidden state는 새로운 입력 x가 들어올 때마다 업데이트됩니다. hidden state는 업데이트된 후 모델에 feed back되고 이후 다시 새로운 입력 x가 들어오는 구조입니다.
  • RNN이 매 단계마다 값을 출력하는 경우
    1. Input값 X 들어옴
    2. Hidden state업데이트
    3. y출력 순서로 진행됩니다.

이 전 단계의 hidden state($h_{t-1}$)이 다음 단계의 hidden state($h_t$)로 업데이트될 때는 위의 과정을 거쳐서 업데이트됩니다.

  • RNN이 출력 값(y)을 가지려면 $h_t$를 입력으로 하는 FC-Layer를 추가해야 하며 이때 FC-Layer에서는 매번 업데이트되는 hidden state를 기반으로 출력 값을 결정하게 됩니다.
  • 함수 f, 파라미터 W는 진행되는 매 스텝마다 항상 동일해야 합니다.

조금 더 이해하기 쉽게 써보면 $h_{t-1}$에 h에 대한 가중치 $W_{hh}$가 곱해지고 $x_t$에 x에 대한 가중치 $W_{xh}$가 곱해집니다. 그리고 그 둘을 합해주고 tanh를 거쳐 $h_t$로 업데이트됩니다. 만약 출력 값 $y_t$를 뽑고 싶으면 출력 값을 뽑는 가중치 $W_{hy}$를 $h_t$에 곱해주면 됩니다.

RNN을 해석할 때는 크게 두 가지로 해석합니다.

  1. RNN이 hidden state를 가지며 이를 재귀적으로 feedback 한다. ← 이렇게 해석하면 좀 헷갈릴 수 있습니다.
  2. Multiple time steps를 Unrolling 하여 hidden state, 입•출력, 가중치 행렬 간의 관계를 이해한다. ← 이런 방식으로 이해하는 것이 조금 더 명확하게 이해 가능합니다.

위 그림으로 RNN이 어떻게 진행되는지를 살펴보면

  1. 먼저 최초의 hidden state($h_0$)가 있고, 입력 $x_1$이 들어옵니다. 보통 $h_0$는 0으로 초기화합니다.
  2. 입력과 hidden state가 가중치 W와 계산되어 hidden state를 업데이트하여 $h_1$으로 업데이트 되게 됩니다.
  3. 이 과정을 계속 거쳐가면서 hidden state를 업데이트해나갑니다.

여기서 h와 x는 매번 달라지지만 W는 변하지 않고 동일한 가중치 행렬이 매번 사용된다는 것을 기억해야 합니다.

앞서 computational graph에서 동일한 node를 여러 번 사용할 때 back propagation flow가 어떤지에 대해 배웠습니다. backward pass시에 $dL\over dW$을 계산하려면 행렬 W의 gradient를 전부 더해줍니다. 따라서 RNN 모델의 backprop을 위한 행렬 W의 gradient를 구하려면 각 스텝에서의 W에 대한 gradient를 전부 계산한 뒤에 모두 더해줍니다.

 

  • 위 다이어그램은 Many-to-Many의 진행 과정을 나타내고 있습니다. 이 전의 그림과 다른 점은 RNN의 Output인 $h_t$가 또 다른 네트워크의 입력으로 들어가서 $y_t$를 만들어냅니다.
  • 각 시퀀스마다 Ground Trueth Label이 있다고 가정하면 스텝마다 개별적으로 $y_t$에 대한 loss를 구할 수 있습니다. 최종 Loss는 개별 loss의 합으로 구할 수 있습니다.
  • 여기서의 backprop을 생각해보면 모델 학습을 위해서는 $dL\over dW$을 구해야 합니다. Loss flowing은 각 스텝에서 이루어지게 되고, 이 경우 각 Step마다 W에 대한 local gradient를 계산 가능합니다. 계산된 개별 local gradient를 최종 gradient에 더해주는 방법으로 backprop을 진행할 수 있습니다.

 

Many-to-One의 진행 과정을 나타낸 다이어그램입니다. 여기서 볼 수 있는 것은 최종 hidden state에서만 결괏값이 나올 것이고 이 말은 최종 hidden state가 전체 시퀀스 내용에 대한 일종의 요약이라고 볼 수 있습니다.

 

One-to-Many에서는 고정 입력을 모델의 Initial hidden state를 초기화시키는 용도로 사용하고, 시퀀스를 진행하면서 모든 step에서 출력 값을 가집니다.

 

Sequence to Sequence : Many-to-one + one-to-many

Sequence to Sequence 모델은 2 Stage 모델로 encoder와 decoder가 합쳐진 모델입니다.

  • Encoder
    • 입력 x를 가변 입력으로 받습니다.
    • endocer의 final hidden state를 통해 전체 sentence를 요약합니다.
    • 쉽게 말해 Many-to-One을 수행하여 가변 입력을 하나의 벡터로 요약해주는 역할을 합니다.
  • Decoder
    • One-to-Many과정을 수행하게 됩니다.
    • 입력으로 들어오는 것은 앞서 encoder에서 요약한 하나의 벡터입니다.
    • 매 스텝마다 적절한 단어를 뽑아내며 가변 출력을 내뱉습니다. (예를 들면 번역된 문장이 있습니다.)
    • Backprop을 진행할 때는 Output sentence의 각 losses를 합해서 진행하게 됩니다.

보통 RNN은 NLP에서 많이 사용합니다. 예를 들어 문자열 시퀀스를 읽고, 다음 문자를 예측해주는 Character-level Language Model이 있습니다.

 

Character-level Language Model

이 모델을 훈련할 때, training sequence의 각 문자들을 입력으로 넣어주는데 일반적으로 입력을 넣어주는 방법이 있습니다.

  • 예를 들어 training sequence가 ‘hello’라고 한다면 먼저 독립적인 문자 ‘h,e,l,o’를 생각합니다.
  • 각 문자를 하나의 벡터로 표현해줍니다.(One-hot encoding)
  • 이번 예에서는 문자가 4개 이므로 문자를 표현한 벡터는 4d 벡터가 될 것입니다.

  • 위에서 표현한 단어의 벡터들을 RNN의 입력으로 넣어주게 되고 위의 식처럼 어떠한 방법을 통해 hidden state를 업데이트합니다.

  • 그리고 각각의 스텝에서 입력으로 들어온 문자 다음에 어떤 문자가 올지에 대한 예측을 output으로 뱉어냅니다.
  • 제일 처음 step을 살펴보면 ‘h’에 해당하는 벡터가 입력으로 들어오고 그에 대한 hidden state를 업데이트합니다. 그리고 그 hidden state가 또 다른 네트워크의 입력으로 들어가서 다음에 올 문자에 대한 예측을 뱉어냅니다.
  • 그리고 다음 스텝은 입력으로 ‘e’에 대항하는 벡터가 들어오고, 이 전의 hidden state(’h’를 입력으로 받아서 업데이트했던)와 입력 ‘e’를 이용해 새로운 hidden state를 업데이트하게 됩니다.
  • 이런 방식으로 모델이 학습을 이어나가게 됩니다.

이렇게 학습시킨 모델을 활용하는 방법에 대해 알아보겠습니다.

위의 방법으로 학습시킨 모델을 활용할 수 있는 방법 중 하나는 모델로부터 sampling을 하는 것입니다. 이 말은 모델이 훈련 과정에서 봤을법한 문장을 스스로 생성해 내는 것을 의미합니다.

  1. 먼저 문장의 첫 글자만 입력으로 줍니다. 이 경우에는 ‘h’를 입력으로 주었습니다.
  2. 모델은 배운 대로 ‘h’에 대한 output layer를 생성합니다.
  3. 테스트 시에는 여기서 한 가지를 더 하는데 다음으로 올 문자에 대한 확률을 예측하는 것입니다.
  4. 그렇게 확률을 예측해서 다음 문자를 예측했다고 해보겠습니다. 위의 예에서는 ‘e’가 비록 확률은 낮지만 운이 좋게 뽑혔다고 생각하겠습니다.

모델이 다음 문자로 ‘e’가 올 것이라 예측했기 때문에 다음 스텝의 입력으로 ‘e’에 해당하는 벡터를 넣어주게 됩니다. 그리고 전과 같은 방식으로 다음 문자에 대한 확률을 예측한 후 문자를 뽑게 됩니다. 다음으로는 이러한 과정의 반복입니다.

 

전체의 과정을 한 번 보면 모델은 전체 문장을 만들어내기 위해 타임 스텝마다 확률 분포에서 문자를 하나씩 뽑아내게 됩니다.

  • 왜 굳이 확률 분포를 이용해서 문자를 뽑아낼까요? 그냥 score가 높은 문자를 뽑아내면 되지 않을까요?
    • 이 예제만 봐도 만약 score만 사용했다면 올바른 결과가 나오지 않았을 것입니다. 실제로는 두 경우 모두 사용 가능하고, 어떤 경우는 argmax를 사용하기도 합니다. 하지만 확률 분포를 이용하면 모델에서 다양성을 확보 가능합니다. 예를 들어 ‘h’로 시작하는 여러 개의 단어들을 학습시켰다면 확률에 의해 ‘h’로 시작하는 여러 다양한 단어들을 만들어 낼 수도 있을 것입니다.
  • Test시에 그냥 softmax vector를 one-hot vector대신 다음 입력으로 쓰면 안 될까요?
    • 만약 그렇게 된다면 2가지의 문제가 발생합니다.
      1. 다음 스텝의 입력이 훈련 과정에서 본 것과 다릅니다. 이렇게 되면 모델이 제대로 예측을 할 수가 없게 됩니다.
      2. 실제로 vacabularies가 아주 큽니다. 엄청난 양의 단어가 있기 때문에 실제로는 one-hot vector를 sparse vector operation으로 처리하게 됩니다.

그럼 이러한 모델 구조에서 gradient를 계산하는 방법에 대해서 보겠습니다. 먼저 모델의 모든 스텝마다 출력이 존재하게 되고 각 출력을 이용해 final loss를 얻는데 이를 ‘Backpropagation through time’이라고 합니다.

forward pass의 경우 전체 시퀀스 끝까지 출력 값을 생성하면서 loss를 계산합니다. 그리고 backward시에도 똑같이 전체 시퀀스를 이용해 gradient를 계산하게 됩니다. 이러한 방법은 시퀀스가 매우 길면 문제가 될 수 있습니다.

 

이러한 문제 때문에 실제로는 Truncated backpropagation을 통해 backpropagation을 근사 시키는 방법을 사용하게 됩니다.

  1. 먼저 무한대의 Input이 있더라도 훈련 시 스텝을 일정 단위로 자릅니다.(대략 100스텝 정도)
  2. 앞에서 자른 100스텝만 forward하고 이렇게 잘린 sub sequence의 loss를 계산하고 gradient step을 진행하게 됩니다. gradient step은 항상 현재 batch(step)에서만 진행하게 됩니다.
  3. 2의 과정을 반복하게 됩니다. 하지만 이전 batch에서 계산한 hidden state는 계속 유지합니다.

어떻게 보면 위 과정들은 SGD(Stochastic Gradient Descent)의 시퀀스 데이터 버전이라 볼 수 있습니다.

 

이러한 RNN이 활용되는 분야는 정말 많습니다. 수학 책을 만들어주거나, c-code를 직접 작성하는 등 다양한 방법이 있습니다. 그중에서 Image Captioning에 대해서 한 번 보겠습니다.

 

Image Captioning

위 그림은 image captioning에 대한 대략적인 다이어그램입니다. 이미지를 input으로 받아 cnn을 거쳐 이미지 정보에 대한 요약을 rnn으로 넣어주게 되고 사진에 대한 설명을 출력하게 되는 구조입니다.

 

예를 한 번 들어보겠습니다.

먼저 input image가 들어오고 cnn을 거치게 됩니다. 여기서 중요한 것은 마지막 softmax층을 사용하지 않는다는 점입니다. 직전의 4096-dim vector를 출력으로 이용하여 전체 이미지를 요약하는 역할을 하는 vector를 뽑아냅니다.

 

  • 다음으로 이미지의 정보를 요약한 벡터가 어떤 가중치와 곱해져서 RNN에 들어가게 되고 hidden state를 업데이트하게 됩니다. 위의 식을 보면 이 전의 RNN에서는 가중치가 x, h에 관한 가중치 2개였지만 여기서는 이미지에 대한 정보를 추가해주기 위해 새로운 가중치 $W_{ih}$가 사용됩니다.
  • 그리고 RNN의 시작 부분은 <START> 토큰을 입력으로 주게 되면서 모델에게 예측을 시작해라고 알려줘야 합니다.

그러면 모델은 앞서 배운 것처럼 sampling을 진행하게 됩니다.

 

계속 sampling을 반복하면서 진행하다가 어느 시점에 <END> 토큰이 sampling 된다면 모델은 더 이상 단어를 생성하지 않으며 이미지에 대한 caption이 생성되게 됩니다.

 

아래 사진들은 Image Captioning을 이용한 예제들입니다.

생각보다 이미지에 대한 요약을 잘 만들어내는 것 같지만 문제점이 존재하고 이러한 문제점을 해결하기 위한 방법으로 Attention을 이용한 Image captioning 방법이 있습니다.

 

Image Captioning with Attention

Attention을 이용한 모델은 Caption을 생성할 때 이미지의 다양한 부분을 집중해서 볼 수 있습니다. 그럼 Attention이 적용된 모델의 작동 과정을 보겠습니다.

 

먼저 모델에 이미지가 들어옵니다. 그 이미지는 CNN을 거치게 되고 출력으로 벡터 하나를 만드는 게 아니라 각 벡터가 공간적인 정보를 가지고 있는 grid of vector를 만들어 냅니다.(L x D)

 

그런 다음 $h_0$에서 위치에 대한 분포($a_1$)를 만들고, 만들어진 분포를 벡터 집합과 연산해 이미지($z_1$)를 생성합니다.

이렇게 모델은 Froward 하면서 매 step vocabulary에서 샘플링할 때 모델이 이미지에서 보고 싶은 위치에 대한 분포를 만들어내고, 이때 위치에 대한 분포는 훈련 과정에서 모델이 이미지의 어느 위치를 봐야 하는지에 대한 attention입니다.

 

그렇게 만들어진 이미지($z_1$)와 첫 번째 단어($y_1$), 그리고 전 단계의 hidden state($h_0$)을 이용해 hidden state를 업데이트 하고($h_1$) 또다시 이미지의 위치에 대한 분포($a_2$)를 만들어냅니다. 그리고 이번에는 vocabulary의 각 단어들의 분포($d_1$)도 만들어냅니다.

 

이러한 과정을 반복적으로 진행하면서 모델이 Captioning을 진행하게 됩니다.

훈련을 마치면 모델이 Caption을 생성하기 위해 이미지의 attention을 이동시키는 것을 볼 수 있습니다.

 

 

Visual Question Answering

RNN과 Attention을 이용한 모델 중에 Visual Question Answering을 수행하는 모델이 있는데, 이 모델은 이미지와 이미지에 대한 질문을 받으면 그에 따른 답을 만들어내는 모델입니다.

 

https://arxiv.org/pdf/1505.00468.pdf

이 모델의 작동 원리는 다음과 같습니다.

  1. 먼저 모델이 질문을 input으로 받습니다.
  2. RNN을 통해 질문을 vector로 요약합니다.
  3. 또한, 이미지 정보 요약을 위해 CNN을 사용합니다.
  4. CNN, RNN에서 나온 벡터를 조합하여 질문에 대한 분포를 예측할 수 있을 것입니다.
    • 간혹 VQA문제를 해결하기 위해 soft special attention을 적용하는 경우도 있습니다.
  • VQA모델에서 encoded image, question(요약된 이미지, 질문 벡터)를 어떻게 조합하나요?
    • 두 벡터를 조합하는 가장 쉬운 방법을 그냥 concat을 이용해 이어 붙여서 FC-layer의 입력으로 만드는 방법이 있습니다. 간혹 강력한 함수를 만들기 위해 두 vector 간의 복잡한 조합을 만드는 경우도 있습니다.

 

Multilayer RNNs

지금까지는 단일 레이어를 사용한 모델들만 봤습니다.(hidden state가 하나뿐) 하지만 앞으로 자주 보게 될 것은 multi-layer RNN입니다. 위 그림은 그런 multy-layer RNN에 대한 다이어그램입니다.

multi-layer RNN의 작동 순서는 다음과 같습니다

  1. 입력이 첫 번째 RNN으로 들어가서 첫 hidden layer를 만들게 됩니다.
  2. RNN을 한 번 돌리면 hidden states 시퀀스가 생깁니다.(제일 아래쪽 초록색 레이어)
  3. 2에서 생성된 hidden states를 다른 RNN의 입력으로 넣고 한 번 돌려서 또 다른 hidden states 시퀀스를 생성합니다.(밑에서 두 번째 초록색 레이어)
  4. 이런 방식으로 계속 hidden state 시퀀스를 만들어줍니다. 이렇게 모델이 깊어지게 되면 다양한 문제에서 성능이 좋아집니다. 하지만 너무 깊게 모델을 설계하지는 않습니다.

Vanilla RNN Gradient Flow

지금까지는 다양한 RNN을 이용한 모델들이 forward pass시에 어떻게 동작하는지에 대해서 집중해서 다뤘습니다. 지금부터는 훈련 시 Gradient를 계산하는 데 있어서 어떠한 문제들이 있는지 알아보겠습니다.

위 그림은 Vanilla RNN이 hidden state를 업데이트하는 과정을 나타낸 것입니다.

아래 그림은 back propagation을 진행할 때의 gradient flow에 대해 나타낸 그림입니다.

 

이러한 구조는 backward pass에서 gradient를 계산할 때 어떤 일이 발생할까요?

  1. 먼저 upstream gradient가 tanh gate를 타고 흘러 들어갑니다.
  2. 다음으로 mat mul gate를 통과합니다.( 정확히는 가중치 행렬의 전치($W^T$)를 곱하게 됩니다.
  3. → 이는 vanilla RNN cell을 하나 통과할 때마다 가중치 행렬의 일부를 곱하게 되는 것을 의미합니다.

RNN구조의 특성상 여러 RNN cell을 쌓아 올린다는 사실을 고려하면 gradient가 RNN의 layer를 통해 어떤 방식으로 전달되는지 생각해볼 수 있습니다.

만약에 이런 식으로 제일 처음 hidden state($h_0$)의 gradient를 구하려면 결국 모든 cell을 거쳐야 하고 가중치 행렬과의 계산을 아주 많이 진행해야 함을 의미합니다.

 

위 그림은 $h_0$까지의 gradient flow에 대한 전체적인 다이어그램입니다. 단계를 거치면 거칠수록 W가 많이 관여를 하게 되고 크게 두 가지의 문제가 발생하게 됩니다.

  1. gradient 행렬의 특이값이 1보다 큰 경우 : Exploding gradients
  2. gradient 행렬의 특이값이 1보다 작은 경우 : Vanishing gradients

1의 문제를 해결하기 위해서 Gradient clipping을 사용하게 됩니다.

  • Gradient clipping : gradient의 L2 norm을 계산하고, 만약 그 값이 임계값보다 크다면 최대 임계를 넘지 못하도록 조정

2의 문제를 해결하기 위해선 좀 더 복잡한 RNN구조가 필요합니다.

 

LSTM(Long Short Term Memory)

LSTM은 RNN의 업그레이드된 버전입니다. vanishing/exploding문제 해결을 위해 디자인되었습니다.

  • LSTM은 먼저 이 전의 hidden state($h_{t-1}$)와 현재의 입력값($x_t$), 총 2개의 input을 받습니다.
  • 입력을 받아서 총 4개의 gates를 계산합니다. 이렇게 계산된 gates는 Cell state의 업데이트에 이용됩니다.
  • 이렇게 업데이트된 Cell state($c_t$)로 다음 hidden state($h_t$)를 업데이트합니다.

LSTM은 하나의 Cell당 두 개의 hidden state가 존재합니다.

  1. $h_t$ (vanilla RNN과 유사)
  2. $c_t$ ( Cell state) : LSTM 내부에만 존재하며 밖에 노출되지 않습니다.

Vanilla RNN의 경우 $h_{t-1}, x_t$를 concat 하고 행렬곱 연산으로 hidden state를 직접 구합니다. 하지만 LSTM에서는 조금 다릅니다. LSTM에서는 $h_{t-1}$, $x_t$를 쌓아 놓고 4개의 게이트(i, f, o, g)를 계산하기 위한 커다란 가중치 행렬을 곱해줍니다.(각 gates 출력은 hidden state size와 동일합니다.)

계산된 4개의 게이트(i, f, o, g)는 각각 의미가 다릅니다.

  • $i$ : Input gate로 Cell에서 입력 $x_t$에 대한 가중치입니다. (0~1)
  • $f$ : Forget gate로 이전 스텝의 Cell 정보를 얼마나 망각(forget)할지에 대한 가중치입니다.(0~1)
  • $o$ : Output gate로 Cell state를 얼마나 드러내 보일지에 대한 가중치입니다.(0~1)
  • $g$ : Gate gate로 Input Cell을 얼마나 포함시킬지를 결정하는 가중치입니다.(-1~1) → 이 부분이 좀 이상해 보일 수 있지만 사실은 이치에 더 맞습니다.

위 그림에서 만약 gate들의 값이 binary라고 가정하고 $c_t$가 업데이트되는 식을 한 번 살펴보겠습니다.

($i, f, o = 0 or 1$ $g = 1 or -1$)

$c_t = f \odot c_{t-1} + i \odot g$

  • $i$는 1 또는 0 값을 갖습니다. cell state의 각 element에 대해 이 cell state를 사용하고 싶으면 1이 됩니다.
  • $g$는 -1 또는 1 값을 갖습니다.
  • → $i, g$를 element-wise multiplication을 진행해주고 나오는 결괏값에 따라 이 cell state를 사용할지에 대한 판단을 하게 됩니다. (사용하고 싶으면 1, 아니면 0)
  • $f, c_{t-1}$ 사이에서도 $f$는 값이 0 또는 1이므로 element-wise multiplication을 통해 이전 cell state를 잊을지 말지에 대한 판단을 하게 됩니다. (0이면 잊고, 1이면 기억)

$c_t$는 현재 스텝에서 사용될 수 있는 후보라고 할 수 있습니다. $c_t$를 계산하는 전체 수식은 두 개의 독립적인 scalar값 ($f, i$)에 의해 조정됩니다. 각 값은 1까지 증가하거나 감소합니다.

$c_t$에 대한 수식을 해석해보면 우선 이전 Cell state($c_{t-1}$)을 기억할지 말지를 결정해준 다음($f \odot c_{t-1}$) 각 스텝마다 1까지 Cell state의 각 요소를 증가시키거나 감소시킬 수 있습니다($i \odot g$). 즉, cell state의 각 요소는 scalar integar counter처럼 값이 줄었다 늘었다 하는 것으로 볼 수 있습니다.

여기서 중요한 점은 각 gate에서 사용하는 non-linearity가 각양각색이라는 점입니다.

  • Input, Output, Forget → sigmoid 사용
  • Gate → tanh 사용

이렇게 계산된 $c_t$와 $o$를 통해 다음 hidden state($h_t$)를 결정합니다.

Cell state를 계산했으면 이젠 hidden state를 계산해야 합니다. $h_t$는 실제 밖으로 보이는 값입니다. 때문에 cell state는 Counters의 개념으로 해석할 수 있습니다(각 스텝마다 최대 1 또는 -1로 세는 것).

이 값은 tanh를 통과하고 최종적으로 output gate과 곱해집니다. output게이트의 값도 sigmoid를 통과해서 나온 0~1의 값입니다. output gate는 각 스텝에서 다음 hidden state를 계산할 때 cell state를 얼마나 노출시킬지 결정합니다.

 

위 그림은 LSTM을 조금 더 이해하기 쉽게 표현한 그림입니다.

 

그럼 단계별로 LSTM을 살펴보겠습니다.

LSTM의 첫 단계로는 전 cell state($c_{t-1}$)로부터 어떤 정보를 버릴지를 정하는 것으로, sigmoid layer에 의해 결정됩니다. 이 단계를 forget gate layer라고 부릅니다. 이 단계에서는 $h_{t-1}$과 $x_t$를 받아서 0~1 값을 $c_{t-1}$에 보냅니다. 그 값이 1이면 ‘모든 정보를 보존해라’가 되고, 0이면 ‘모든 정보를 버려라’가 됩니다.

 

다음 단계는 새로운 정보 중 어떤 것을 Cell state에 저장할지 정하는 것입니다. 먼저 input gate layer라고 불리는 sigmoid layer가 어떤 값을 업데이트할지 정합니다. 그 다음에 tanh layer(gate gate)가 새로운 후보 값들인 $\tilde{C_t}$ 라는 vector를 만들고, cell state에 더할 준비를 합니다. 이렇게 두 단계에서 나온 정보를 합쳐서 cell state를 업데이트할 재료를 만들게 됩니다.

 

이제 $C_{t-1}$를 업데이트해서 새로운 $C_t$를 만들 것입니다. 이미 이전 단계에서 어떤 값을 얼마나 업데이트해야 할 지 다 정해놨으니 여기선 실행만 하면 됩니다.

 

우선 이전 $C_{t-1}$에 $f_t$를 곱해서 가장 첫 단계에서 잊어버리기로 정했던 것들을 잊어버립니다. 그러고 나서 $i_t * \tilde{C_t}$를 더합니다. 이 더하는 값은 두 번째 단계에서 업데이트하기로 한 값을 얼마나 업데이트할지 정한 만큼 scale 한 값이 됩니다.

 

마지막으로 무엇을 output으로 내보낼지 정하는 일이 남았습니다. 이 output은 cell state를 바탕으로 필터 된 값이 될 것입니다. 먼저 sigmoid layer에 input 데이터를 태우고 cell state의 어느 부분을 output으로 내보낼지를 정합니다. 그러고 나서 cell state를 tanh layer에 태워 -1~1 사이의 값을 받은 뒤에 방금 전에 계산한 sigmoid gate의 output과 곱해줍니다. 그렇게 하면 우리가 output으로 보내고자 하는 부분만 내보낼 수 있게 됩니다.

 

아래 그림은 LSTM에서의 Gradient Flow를 나타낸 그림입니다.

빨간 선으로 표시된 게 LSTM에서 Cell state의 gradient를 계산하는 경로입니다. 경로를 보면 덧셈 노드를 거치고 element-wise multiply로 직접 전달하여 다음 Cell state로 흘러갑니다. 따라서 gradient는 upstream gradient와 forget gate의 element wise곱으로 계산됩니다.

이러한 특성이 Vanilla RNN보다 좋은 점은 2가지가 있습니다.

  1. forget gate와의 연산이 행렬 연산이 아닌 element-wise라는 점
  2. element-wise를 통해 매 스텝 다른 값의 forget gate와 곱해질 수 있다는 점(Vanilla RNN의 경우 동일한 가중치 행렬만을 계속 곱해줬음)
  3. → 이러한 특징 때문에 exploding/vanishing gradient 문제를 해결할 수 있습니다(forget gate 값이 계속 변하기 때문).

위 내용뿐만 아니라 또 한 가지 명심해야 할 점은 Vanilla RNN은 backward 스텝마다 gradient가 tanh를 거쳐야만 했습니다. LSTM에서도 $h_t$를 출력($y_t$) 계산에 사용합니다. 만약 최종 $h_t$를 가장 첫 Cell state($c_0$)까지 backpropagation을 하는 경우를 생각해보면 RNN처럼 매 번 tanh를 거치는 게 아니라 tanh를 마지막 한 번만 거치면 됩니다.

 

전체적인 흐름을 보게 되면 Cell state를 통한 backpropagation은 gradient를 위한 고속도로입니다. → gradient가 흘러가면서 방해를 덜 받습니다.

  • 하지만 결국 W를 업데이트해야 하는 건 똑같은데, W에 대한 gradient는 어떻게 되는지?
    • W에 대한 local gradient는 해당 스텝에서의 cell/hidden state로부터 전달됩니다. LSTM은 cell state C가 gradient를 잘 전달해주기 때문에 W에 대한 local gradient도 훨씬 깔끔하게 전달됩니다.
  • RNN과 비슷하게 LSTM에도 여전히 non-linearities가 있으므로(sigmoid) vanishing gradient문제에 민감하지 않은지
    • 그럴지도 모릅니다. forget gate의 경우 출력이 항상 1보다 작으므로(0~1) gradient가 점점 감소할 수 있습니다. 그래서 사람들은 forget gate의 biases를 양수로 초기화시키기도 합니다. 이걸 이용해서 forget gate의 값이 학습 초기에 1에 가깝게 만들어 줍니다. 이렇게 되면 적어도 학습 초기에는 gradient흐름이 원활할 것입니다. 학습이 진행되면서 forget gate의 biases는 적절한 자기 자리를 찾아가게 될 것입니다.

 

Other RNN Variants

LSTM 외에도 RNN을 변형시킨 여러 모델이 있습니다. 특히 GRU는 LSTM과 유사하게 생겼습니다.

GRU는 forget gate와 input gate를 하나의 ‘update gate’로 합쳤고, cell state와 hidden state를 합쳤습니다.

이후로 많은 연구가 있었지만 전부 LSTM과 GRU에 비해 크게 좋다 할 정도가 아니었습니다. 이를 통해 LSTM과 GRU가 아무렇게나 만들어진 수식이 아니라는 사실을 알 수 있습니다.

 

 


  • 참고

https://dgkim5360.tistory.com/entry/understanding-long-short-term-memory-lstm-kr

 

Long Short-Term Memory (LSTM) 이해하기

이 글은 Christopher Olah가 2015년 8월에 쓴 글을 우리 말로 번역한 것이다. Recurrent neural network의 개념을 쉽게 설명했고, 그 중 획기적인 모델인 LSTM을 이론적으로 이해할 수 있도록 좋은 그림과 함께

dgkim5360.tistory.com

https://medium.com/analytics-vidhya/lstms-explained-a-complete-technically-accurate-conceptual-guide-with-keras-2a650327e8f2

 

LSTMs Explained: A Complete, Technically Accurate, Conceptual Guide with Keras

I know, I know — yet another guide on LSTMs / RNNs / Keras / whatever. There are SO many guides out there — half of them full of false…

medium.com

 

'AI > CS231N' 카테고리의 다른 글

Lecture 9 : CNN Architecture  (0) 2022.09.06
Lecture 11 : Detection and Segmentation  (0) 2022.09.03