* 강의 자료는 아래에 있습니다!!
오늘은, RNN에 대해 알아보겠습니다.
일반적인 신경망들은 'one-to-one' 과 같은 모양을 가진다.
기본 신경망은 input layer, hidden layer, output layer를 거치는 구성을 갖는다.
이 'one-to-one' 모델은 input으로 fixed size인 이미지나 벡터를 입력받는다. 이 하나의 입력은 hidden layer를 거쳐서 fixed size를 가진 output vector로 내보내게 된다.
그러나 RNN에서는 one-to-one의 경우가 아닌, 위와 같은 one-to-many, many-to-one, many-to-many 로도 존재한다.
먼저, one-to-many의 예시로는 image captioning이 있다. 즉, 이 이미지를 묘사하는 단어들의 sequence를 출력해 내는 것이다. (sequence : 순서)
'many-to-one'의 예로는, sentiment classification이 있다. 이는, 감정을 분류해내는 것으로, 단어들로 구성된 sequence (ex, 이메일, 메시지 등) 의 감정이 positive한지, negative한지를 한 개의 class로 분류해 내는 것이다.
'many-to-many'의 예로는 Machine Translation이 있다. 예를 들면, 영어 단어로 구성된 문장이 들어왔을 때, 한국어 단어로 구성된 문장으로 번역을 하는 것이다.
또 다른 'many-to-many'의 예로는 video classification이 있을 수 있다. 비디오에서는 기본적으로, 모든 하나하나의 프레임들을 나누게 되는데, 중요한 점은 예측이 현재 시점에서의 프레임에서만 국한되어서는 안된다는 것이다. 비디오에 있어서 예측이라는 것은 현재의 프레임과 더불어, 현재의 프레임 전에 이미 지나간 모든 프레임들에 대한 함수가 되어야 한다는 것이다.
따라서, RNN의 아키텍쳐의 핵심은 모든 각각의 타임 스텝에서의 예측은 [현재의 프레임 + 지나간 프레임들에 대한 함수]로 이루어지게 된다는 것이다.
cf) one-to-one 신경망일 경우에, input과 output이 기본적으로 sequence(순서)가 아닌 경우이지만, 사실은 이 input과 output 자체가 어떤 fixed size의 형태를 갖고 있기 때문에, 이 fixed size 각각에 대해서 이것들이 sequence하다고 간주하고, sequential한 처리를 해줄 수도 있다.
이것은 fixed input들을 sequence하게 처리하는 것을 보여준다.
이것은 fixed size input이고, 이미지의 숫자가 몇 인지 분류하는 문제이다.
input 이미지의 정답은, 이미지의 여러 부분을 조금씩 살펴본 후에 숫자가 몇 인지를 최종적으로 판단한다.
이처럼 입/출력이 고정되어있다고 하더라도, '가변 과정' 인 경우에 RNN은 유용하다.
여기서는 Train time에서 본 이미지들을 바탕으로 새로운 이미지를 생성해 낸다.
여기서 RNN을 이용할 수 있다. 순차적으로 전체 출력의 일부분씩 생성해내는 것이다. 이 경우에도 전체 output은 fixed size이지만, RNN을 이용해서 일부분씩 순차적으로 처리할 수 있게 된다.
다음으로는, RNN의 동작원리에 대해 살펴볼 것이다.
((일반적으로 RNN은 작은 recurrent core cell(순환하는 성질?)을 가지고 있다. 이 때, input x가 RNN으로 들어가게 된다. ))
input x가 RNN으로 들어가게 된다.
RNN은 내부에 hidden state를 가지고 있다. 이 hidden state는 RNN에 새로운 입력이 들어올 때마다 업데이트 된다. 업데이트된 hidden state는 모델에 피드백 되고, 이후에 또 다른 입력 x가 들어오게 된다.
((먼저, RNN이 존재하고, 시간의 흐름에 따라서 input vector를 입력받게 된다. 즉, 매 time step 마다 input vector가 RNN으로 입력이 되게 된다.
여기서 RNN은 어떠한 상태를 갖게 된다. 그리고 이러한 상태를 어떠한 function으로 변형해 줄 수 있게된다. 이것은 매 time step 마다 input을 받는 것에 대한 function이다. RNN 또한 weight로 구성되게 되며, weight들이 튜닝되어감에 따라서 RNN이 진화되어 나가기 때문에, 새로운 input이 들어올 때마다 다른 반응을 보이게 되는 것이다. ))
RNN이 매 단계마다 값을 출력하는 경우를 생각해보자. 다음과 같은 방식으로 진행이 된다.
1. RNN이 입력을 받는다.
2. hidden state를 업데이트 한다.
3. 출력 값을 내보낸다.
((그러고 나서 우리는 특정 time step에서의 벡터를 예측하는 것을 원한다. ))
다음은 RNN 구조를 수식적으로 표현한 것이다.
RNN은 함수 f로 재귀적인 관계를 연산할 수 있도록 설계된다.
파라미터 W를 가진 함수 f가 있다. 함수 f는 이전 상태의 hidden state인 h_t-1과 현재 상태의 입력인 x_t를 입력으로 받는다. 그리고 h_t를 출력한다. h_t는 '다음 상태의 hidden state'를 의미한다.
그리고 다음 단계에서는 h_t와 x_t+1이 입력이 된다.
// 이 RNN에서 출력 값을 가지려면 h_t를 입력으로 하는 Fully connected layers를 추가해야 한다. Fully connected layer는 매번 업데이트되는 hidden state(h_t)를 기반으로 출력 값을 결정하게 된다.
여기서 중요한 사실은, 함수 f와 파라미터 W는 매번 동일한 값을 가진다는 것이다.
-----------------------
((매 time step마다 입력되는 벡터 x에 대해서 recurrence function을 적용할 수 있게 된다. 이를 적용함으로써 sequence를 처리해 줄 수 있게 되는것이다.
RNN 구조를 수식적으로 표현한 것이다. 새로운 상태를 ht라고 하고, 이것을 파라미터 W에 대한 function에서 바로 직전의 hidden state와 현재의 time step에서의 input vector을 인자로 사용한다.
cf) state는 vector의 collection으로 표현이 된다. recurrence function은 파라미터 W에 대한 function이기 떄문에 W를 변경하게 되면 RNN도 다른 결과를 보이게 될 것이다. 그러므로 우리는 이 RNN이 우리가 원하는 특정 behavior를 가질 수 있도록 weight(파라미터) 값들을 학습시켜 나가게 되는 것이다. ))
((여기서 주의해야할 점은, 매 time step마다 동일한 function과 동일한 파라미터 셋들이 사용되어야 한다는 것이다.
이렇게 함으로써 RNN이 input과 output의 sequence size에 영향을 받지 않고 무관하게 적용이 가능하게 된다. ))
RNN을 가장 간단하게 수식적으로 나타내기위해서 이 vanilla RNN을 사용해보자.
위에 보이는 수식은 이전 슬라이드에 있었던 수식과 동일하다. 이것은 이전 hidden state와 현재 input을 받아서 다음 hidden state를 출력하는 것이다.
이것을 수식적으로 간단하게 표현해보면, 가중치 행렬 W_xh와 입력 x_t의 곱으로 나타낼 수 있다. 가중치 행렬 W_hh는 이전 hidden state와 곱해지는 값이다. 이렇게 두 입력에 대한 행렬 곱 연산이 있고, 이 두 결과 값을 더해준다. 그리고 이 시스템에서 non-linearity(비선형)를 구현하기 위해 tanh를 적용한다.
우리는 이제 매 time step마다 출력 y를 얻고 싶어 한다. 이를 위해서는 hidden state인 h_t를 새로운 가중치 행렬인 W_hy와 곱해주어야 한다.
여기서 매 step에서의 출력 y는 class score가 될 수 있을 것이다.
------------------
(((이러한 recurrence function을 적용한 가장 간단한 사례는 vanilla RNN이다.
이 vanilla RNN에서는 단일한 hidden vector 인 h로만 state가 구성된다.
현재의 state는 직전의 state와 현재의 입력값의 function으로 표현될 수 있는 것이고, 이것을 vanilla RNN에서의 state update로 표현하게 된다면 다음과 같은 식으로 표현 될 것이다. xt 같은 경우에는 weight값이 x에서 hidden layer로 가는 Wxh의 영향을 받기 떄문에 Wxh를 곱해 주었고, 직전의 상태(ht-1)의 경우에는 현재의 hidden layer와 직전의 hidden layer의 영향을 받기 때문에 weight를 Whh를 곱해 주었다. 이 둘을 더한 다음에 tanh로 해준 것이 현재의 state가 되는 것이다. 즉, 현재의 state 라는 것은 어떠한 history와 새로운 입력값에 의해서 상태가 변화한다는 것을 알 수 있다.
y값은 Wx가 되니까 W는 Why가 될 것이고, 여기에 방금 구한 ht를 곱해준 것이 yt가 될 것이다. )))
RNN은 크게 두 가지 방법으로 해석할 수 있다. 하나는 RNN이 hidden state를 가지면서 이것을 재귀적으로 피드백 한다는 것이다. 이렇게 표현하면 헷갈리기 때문에, 많은 time step들을 펼쳐서 보면 이해가 쉬울 것이다. 펼쳐서 보게 된다면 hidden state, 입/출력, 가중치 행렬들 간의 관계를 좀 더 명확하게 이해할 수 있을 것이다.
첫 번째 step에서는 initial hidden state인 h_0이 있다. 대부분에서는 h_0은 0으로 초기화시킨다. 그리고 입력 x_t가 있다. h_0과 x_1이 함수 f_w의 입력으로 들어간다. 그리고 이것들의 출력은 h_1이 된다.
이러한 과정들이 반복된다. h_1과 x_2가 f_w에 들어가서 h_2가 출력으로 나오게 된다.
이러한 과정들을 반복하면서 순차적으로 변하는 입력인 x_t를 받는다.
조금 더 구체적으로 설명하기위해, 행렬 W를 넣어보았다.
여기서 주목할 점은 동일한 가중치 행렬인 W가 매번 사용된다는 것이다. 매번 h와 x는 달라지지만, W는 동일하다.
이 RNN 모델의 backprop을 위한 행렬 W의 gradient(기울기)를 구하려면, 각 스텝에서 W에 대한 gradient를 전부 계산한 뒤에 이 모든 값들을 더해주면 될 것이다.
(backprop : 네트워크 전체에 대해 반복적인 연쇄 법칙을 적용하여 그래디언트를 계산하는 방법 중 하나)
우리는 이 computational graph에 y_t도 넣어볼 수 있다.
RNN의 출력 값인 h_t가 또 다른 네트워크의 입력으로 들어가서 y_t를 만들어내는 것이다.
여기서 y_t는 매 time step의 class score가 될 수도 있을 것이다.
RNN의 Loss도 살펴볼 것이다.
각 스텝마다 모두 output이 나오기 때문에, 각각의 output에서의 loss를 계산할 수 있을 것이다. 여기서 Loss는 softmax loss가 될 수 있을 것이다.
RNN의 최종 Loss는 모든 개별 Loss들의 평균일 것이다.
각 time step에서 Loss가 발생하면, 이를 전부 더해서 평균내어 최종 네트워크 Loss를 계산하는 것이다.
//이 네트워크의 Backprop을 생각해보면, 모델을 학습시키려면 w의 Loss가 필요하다.
//이 Loss flowing은 각 time step에서 이루어진다. 여기서 각 time step마다 가중치 W에 대한 local gradient를 계산할 수 있다. 이렇게 각자 계산된 local gradient를 최종 gradient에 더한다.
그렇다면, 'many-to-one' 이라면 어떨까? 감정분석 같은것 말이다.
이 경우에는 네트워크의 최종 hidden state에서만 결과 값이 나올 것이다.
//왜냐하면, 최종 hidden state가 전체 시퀀스의 내용에 대한 요약이라고 볼 수 있기 때문이다.
one-to-many의 경우에는 fixed size input을 받지만, variably sized output을 가진 네트워크이다.
여기서는 보통 input이 모델의 initial hidden state를 초기화 시키는 용도로 사용된다. 그리고 매 스텝마다 output을 가진다.
'sequence to sequent' 모델이다. 이것은 machine translation에 사용할 수 있는 모델이다. 이것은 'many-to-one' 모델과 'one-to-many' 모델의 결합으로 볼 수 있다. 그렇다면 이 모델은 가변입력과 가변출력을 가질 것이다.
'many-to-one' 모델은 encoder 구조로 볼 수 있다. 여기서는 가변 입력을 받을 수 있다. 그리고 이 모델에서의 마지막 hidden state를 통해서 전체를 요약해 하나의 출력으로 요약할 수 있다.
아까의 출력 한 개는 다시 'one-to-many' 모델의 input으로 들어가게 된다. 그리고 이 모델은 가변 출력을 가진다. 이것은 매 스텝마다 출력을 계속 output해주는 방식이다.
보통 RNN은 Language modeling을 할 때 자주 사용한다. 다음은 그 간단한 예이다.
보통 RNN은 Language modeling을 할 때 자주 사용한다. 다음은 그 간단한 예이다.
이것은 hello라는 단어를 읽으면, 다음에 올 문자를 맞춰야하는 예이다.
단어는 h, e, l, o가 주어져 있고, hello라는 문장을 학습시켜야한다. Train할 때, thello의 각 단어들을 입력으로 넣어주어야한다.
이 hello는 아까 봤던 x_t가 될 것이다.
먼저, 입력은 한 글자씩 넣어야한다. 그렇기 때문에 h, e, l, o를 각각 하나의 벡터로 표현할 수 있다.
먼저, 첫 번째 스텝에서는 입력으로 h가 들어온다. 그리고 이것이 hidden layer로 들어가서 output을 출력한다. 이 output은 입력 h 다음에 어떤 문자가 나올 것인지 예측하는 것이다. 여기서는 우리가 hello라는 결과를 보여야하기때문에, e라는 출력값을 갖는 것이 맞다. 하지만 o 라고 예측하고 있다. (가장 큰 크기인 4.1의 위치를 잘 보자) 이것은 잘못 예측한 경우이다.
이렇게 다음 단어가 입력으로 들어가면서 과정이 반복된다.
다음으로 e 가 input으로 들어가게되면, 이전의 hidden state와 함께 새로운 hidden state를 만들어낸다. 그런 다음, 새로 만들어진 hidden state를 이용해서 또 다시 예측을 하게 된다. 여기서는 output으로 l이 나와야 한다.
이러한 과정을 계속해서 반복해서 학습시킨다. 다양한 문장들로 학습시키게된다면, 결국 이 모델은 이전의 문장들의 문맥을 참고해서 다음 문자가 무엇일지 예측할 수 있을 것이다.
그렇다면, 이 모델의 test time은 어떠한가? 이 학습 모델을 잘 활용하기 위해서는 모델로부터 sampling을 하는 것인데, 이것은 train할 때 모델이 봤을 법한 문장들은 모델이 스스로 생성해낼 수 있게끔하는 것이다.
우선 모델에게 문장의 첫 글자인 h를 input으로 넣어보자. hidden layer를 지나서 output layer에서 각각의 벡터에 대한 스코어를 얻을 수 있다. test time에서는 이 스코어를 다음 글자를 선택하는데 이용한다. 이 스코어를 확률분포로 표현하기 위해서 softmax 함수를 사용해야한다. 이 확률분포는 두 번째 글자를 선택하기 위해 쓰일 것이다.
여기서 e는 뽑힐 확률이 굉장히 작았음에도 불구하고 운이 좋게 e가 샘플링 되었다.
이제 아까의 샘플링으로 나온 e를 다음 input으로 넣어 준다. 이 때, e를 다시 원래 벡터로 만들어주고 넣어야한다.
이 과정을 계속해서 반복한다.
Q. 가장 높은 스코어를 선택하지 않고 왜 굳이 확률분포에서 샘플링하는가?
A. 이 예제의 경우에는 가장 스코어가 높은 값만 사용하게 되면, 올바른 결과가 나오지 않았을 것이다. 확률분포를 사용해서 샘플링했기 때문에 hello를 만들어낼 수 있었다. 실제로는 두 경우 모두 사용할 수 있다. 하지만 확률 분포에서 샘플링 하는 방법을 사용한다면, 일반적으로 모델에서 다양성을 얻을 수 있게 된다. (ex. 항상 h를 첫 input으로 놓는다고 할 때, 확률분포로 샘플링을 하게 된다면 그럴듯한 다양한 문장들을 출력할 수 있고, 이것은 출력의 다양성으로 이어져 좋은 방법이 될 수 있다.)
Q. 다음 입력으로 바꿔주는 one hot vector 대신에 softmax vector를 그대로 넣어줄 수 있는가?
A. 그렇게 되면 두 가지 문제가 발생할 수 있다. 하나는 우리가 원하는 입력값이 달라진다는 점이고, 다른 하나는 입력값으로 한 번도 보지 못한 값을 주게되면 모델이 작동을 잘 못한다는 점이다.
이런 경우에는 매 스텝마다 출력값의 loss를 계산해서 최종적으로 loss를 얻게되는데, 이것을 backpropagation through time이라고 한다. 이 경우에는 정방향이든, 역방향이든 전체 sequence를 가지고 loss를 계산해야한다. 하지만 시퀀스가 너무 길면 문제가 생길 수도 있다.
// (ex. wikipedia 전체 문서로 모델을 학습시킨다고 가정했을 때, 문서가 너무 많기 때문에 학습이 너무 느릴것이다. 메모리 사용량도 많을 것이다. )
그렇기 때문에, truncated(트런케이티드) backpropagation 을 통해서 backprob을 근사시키는 방법을 사용한다. 이 방법은 입력이 너무 길다고 하더라도 train할 때 한 스텝을 일정 단위로 자르고 단계를 진행하기 때문에 시간이 오래걸리는 문제를 해결할 수 있다. 이 한 스텝을 batch라고 볼 수 있겠다.
이런 과정을 반복한다. 다음 스텝에서는 이전에 계산한 hidden state는 계속 유지한채로 반복해야한다. backprop은 현재 스텝만큼만 진행해야한다.
이것은 andrea가 만든 코드이다. RNN 전체 과정을 구현하였다.
다음으로는 셰익스피어의 작품들을 RNN으로 학습시켜볼 것이다.
학습 초기에는 굉장히 의미 없는 문장들만 출력한다. 하지만 train을 계속해서 할 수록 의미있는 문장을 출력하게 된다.
학습을 더 많이 시키게 되면, 훨씬 더 긴 문장들도 만들어낼 수 있다.
위 자료를 RNN에 학습시키게 되면 이러한 결과가 나오게 된다. 모델이 수식도 만들어낼 수 있게 된다.
이번에는 linux kernel의 전체 소스코드를 학습시켜본 것이다.
이렇게 C언어 같은 코드로 나온다.
이것은 강의해주시는 분이 한 것이다. RNN을 특정 데이터셋으로 학습시키고, hidden state를 하나 뽑아서 어떤 값이 들어있는지 보는 것이다.
이것을 해보면, 대부분의 hidden state는 아무 의미 없는 패턴을 지닌다.
//그렇기 때문에 vector를 하나 뽑은 다음, 이 시퀀스를 다시 forward 시켜보았다. 여기에서 각각의 색깔은 시퀀스를 읽는 동안에 앞에서 뽑은 hidden vector 값을 의미한다.
이것은 대부분 해석하기가 어렵다.
그러다가 따옴표를 찾는 벡터를 알아냈다. 이 벡터는 처음에 계속 off 상태이다.(파란색이 off)
그러다가 따옴표를 만나게 되면, 값이 켜지고 이것은 다음 따옴표를 만나기 전까지만 유지가 된다.
이런식으로 우리는 모델이 다음 문자를 예측하도록 학습시켰지만 모델은 더 유용한 것을 학습하게 된다.
이것은 줄 바꿈을 하기 위해 현재 줄의 단어의 갯수를 세는 것이다. 처음엔 파란색으로 시작했다가 줄이 점점 길어지면 빨간색으로 변한다. 그리고 줄바꿈이 되면, 다시 파란색으로 돌아간다.
이것은 linux 소스코드를 학습시킬 때 발견한 것으로, if문의 조건문에서 값이 커지는 cell이다. 이것으로 조건문의 내부와 외부를 구별할 수 있다.
이것은 코멘트를 찾아내는 것이다.
이것은 들여쓰기를 세는 것이다.
Image captioning model은 입력은 이미지이고, 출력은 자연어로 된 caption이다. 이 논문에서는 caption은 가변 길이를 갖고 있기 때문에, caption마다 다양한 시퀀스 길이를 갖는 것을 알 수 있다. 그렇기 때문에 RNN을 쓰는 것이다.
모델에는 입력 이미지를 받기 위한 CNN이 있다. CNN은 이미지 정보가 들어있는 vector를 출력한다. 그리고 이 vector는 RNN의 첫 step에서의 입력으로 들어가게 된다. 그러면 RNN은 caption에 사용할 문자들을 하나씩 생성해낸다.
여기서는 모델을 학습시킨 후에 test time에서 어떻게 동작하는지를 살펴본다.
먼저, input 이미지를 받아서 CNN의 입력으로 넣는다. 다만, softmax score를 사용하지 않고 직전의 vector의 출력(4096)으로 할 것이다.
이전까지의 RNN 모델은 현재 step의 input 값과 이전 step에서의 hidden state 값을 입력으로 받았고, 이것으로 다음 hidden state를 얻었다. 그러나 여기서는 이미지 정보도 더해주어야 한다. 이미지 정보를 추가하는 방법으로 세 번째 가중치 행렬을 추가하는 것이다. 다음 hidden state를 계산할 때마다 모든 스텝에 이 이미지 정보를 추가한다.
이렇게 y_0라는 샘플링 된 단어가 output으로 나오게 되면, 이것을 다시 다음 스텝의 입력으로 넣어준다.
그러면 또 다시 y_1이라는 output을 내보내게 된다.
이렇게 반복하고 모든 스텝이 종료되면 최종적으로 한 문장이 나오게 된다. 여기서 <END>라는 토큰은 문장의 끝을 알려준다. 이 <END>가 샘플링되면, 모델은 더이상 단어를 생성하지 않게 되고, 이미지에 대한 caption이 완성되게 된다.
이러한 image captioning은 지도학습이라고 볼 수 있기 때문에 이 모델을 학습시키려면 이미 caption이 있는 이미지를 가지고 있어야한다.
이것들은 image captioning의 잘 된 결과이다.
하지만 모델을 훈련시킬 때, 보지 못한 데이터에 대해서는 그렇게 잘 동작하지는 않는다.
image captioning에서 더 발전한 모델은 attention이라는 모델이다. 이 모델은 caption을 생성할 때 이미지의 다양한 부분을 집중해서 볼 수 있다.
attention의 동작원리로는 먼저, CNN이 있다. 이 CNN은 벡터 하나를 만들지 않고 각각의 벡터가 갖고 있는 공간정보를 만들어낸다.
이렇게 매 스텝 샘플링을 할 때, 모델이 이미지에서 보고 싶은 위치에 대한 분포도 만든다.
h0는 이미지의 위치에 대한 분포를 계산하고, 그것의 output인 a1이 다시 feature L x D와 연산해서 z1을 생성한다.
이 z1은 다음 스텝의 input으로 들어가게 되고,
두 개의 출력이 나온다. 하나는 단어들의 분포이고, 나머지 하나는 이미지 위치에 대한 분포이다.
이렇게 계속 과정을 반복하면, 매 스텝마다 output이 두 개씩 만들어진다.
train을 마치면, 모델이 caption을 생성하기 위해서 이미지의 attention을 이동시키는 것을 볼 수 있다.
여기서는 A bird flying over a body of water가 만들어졌다.
Attention 모델을 학습시키고 난 후에 caption을 생성한 것을 보면, 실제로 의미있는 부분이 나타나게 된다.
RNN과 attention을 합친 모델은 image captioning뿐만 아니라 더 다양한 것들도 할 수 있다.
여기서는 input으로 이미지와 질문을 같이 준다. 그리고 모델은 네 개의 보기 중에서 정답을 맞추어야 한다.
이 모델도 RNN과 CNN을 통해 만들 수 있다.
이것은 many-to-one을 사용한 경우로, 모델은 질문을 입력으로 받고, RNN이 이것을 하나의 vector로 내보낸다.
지금까지는 hidden state가 하나뿐이었지만, 여기서는 hidden state가 여러개인 모델이다.
입력이 들어가서 첫 번째 hidden state를 만들어낸다. 그리고 RNN을 하나 돌리면 hidden state 시퀀스가 생기고, 이것을 다른 RNN으로 입력을 넣어주는 방식으로 hidden state를 또 만들어낸다.
이렇게 하는 이유는 모델이 깊어질수록 다양한 문제에서 성능이 더 좋아지기 때문이다.
그렇다고 너무 많이 깊은건 사용하지 않고, 보통 3~4개의 layer를 가진 RNN을 사용한다고 한다.
RNN을 사용할 때 문제점이 있다.
이것은 vanilla RNN cell이다. 현재 입력과 이전 hidden state를 입력으로 쌓고, tanh를 씌워서 다음 hidden state를 만든다. 이것이 vanilla RNN의 기본 수식이다.
backward pass를 할 때, h_t에 대한 loss의 미분 값을 얻게 된다. 그 다음 loss에 대한 h_t-1의 미분값을 계산하게 된다. 이렇게 backward pass의 전체과정은 빨간색 순서대로 진행된다.
여기서 문제는, 우리가 h_0에 대한 gradient를 구하려고 하면, 결국 모든 RNN cell을 거쳐야 한다. 하나의 cell을 통과할 떄마다 가중치 행렬 W가 관여하게 된다. h_0의 gradient를 계산하는 식을 써보면, 너무 많은 가중치 행렬들이 개입하게된다.
우선, 가중치를 행렬이라고 생각하지말고 스칼라 값이라고 생각한다면, 이것을 step 개수 만큼 곱해주어야 할 것이다. 만약 곱해지는 값이 1보다 크면 값이 점점 커질 것이고, 1보다 작으면 값이 점점 작아져서 0이 될 것이다. 이 두 가지 문제점이 일어나지 않으려면, 곱해지는 값이 1인 경우가 되어야 한다.
LSTM은 앞에서의 vanishing 과 exploding gradient 문제를 완화시키기위해서 나왔다.
vanilla RNN은 hidden state가 있었고, 매 스텝마다 재귀적인 방식을 통해 hidden state를 업데이트했다.
LSTM에는 한 cell 당 두 개의 hidden state가 있다. 하나는 h_t이고, 나머지 하나는 c_t라는 벡터이다. c_t는 LSTM 내부에만 존재하며 밖으로 나오지 않는다.
LSTM도 RNN과 같은 두 개의 입력을 받고, 4개의 gate를 계산하게 된다. 이 gate들을 c_t를 업데이트하는데 이용하게된다. 그리고 이 c_t로 다음 스텝의 hidden state를 업데이트 한다.
LSTM의 동작 원리를 살펴보겠습니다.
LSTM은 이전 hidden state인 h_t-1와 현재의 입력인 x_t를 입력으로 받는다. 그리고 난 후 4개의 gate 값을 계산하기 위해 가중치 행렬을 곱해준다. 이 각 gate의 output은 hidden state크기와 동일하게된다. 4개의 gate는 i, f, o, g이다. I는 input gate이고, cell에서의 입력인 x_t에 대한 가중치이다. F는 forget gate이다. F는 이전 스텝의 cell의 정보를 얼마나 지울지에 대한 가중치이다. O는 output gate이다. O는 cell을 얼마나 드러낼 것인지에 대한 가중치이다. G는 gate gate라고 부르며 input cell을 얼마나 포함시킬지 경절하는 가중치이다.
i/f/o 는 sigmoid(0~1)를 사용하지만, g는 tanh(-1 ~ +1)를 사용한다.
이것은 LSTM cell이 어떻게 동작하는지 나타낸다.
c_t-1과 h_t1과 x_t를 입력으로 받는다. 이전의 hidden state인 h_t-1과 x_t를 stack하게되고, 가중치 행렬 W를 곱해서 4개의 gate를 만든다. f를 c_t-1과 곱하고, i와 g가 곱해진 후 앞의 것들과 더해서 다음 cell을 만들게된다. c_t는 tanh를 거친 후 o와 곱해져서 다음 hidden state를 만들어낸다.
여기서 보면, gradient는 upsteam gradient와 f의 곱이 된다.
vanilla rnn은 같은 h_t를 계속곱해서 앞선 문제가 있었지만 LSTM은 forget gate가 스텝마다 계속 변해서 이러한 문제를 더 쉽게 해결할 수 있다.
그리고 vanilla rnn의 backward pass에서는 매 스텝마다 gradient가 tanh를 거쳐야 했다. 그러나 LSTM은 h_t를 c_0까지 backprob하려고 할 때 tanh를 한번만 거치면 된다.
따라서 LSTM에서도 vanishing gradient의 위험이 존재하지만 vanilla rnn만큼 심하진 않다. 그 이유는 매 스텝마다 f가 변하게 되기 때문이다.
이것은 RNN아키텍쳐의 변형된 버전으로 유명한 GRU이다.
'NLP' 카테고리의 다른 글
BERT 쉽게 이해하기 (글 쓰는 중...) (0) | 2022.08.29 |
---|---|
[Project 1] Text Classification (0) | 2022.04.04 |