9강 리뷰
1) AlexNet
2) VGGNet
- filter 개수를 줄여 depth를 더 쌓음
3) GoogLeNet
- inception module, bottleneck layer
4) ResNet
- residual mapping
Recurrent Neural Network
지금까지 배운 아키텍쳐들은 ONE-TO-ONE(Vanilla Neural Network)의 모양이라고 할 수 있다. 이미지 또는 벡터를 입력으로 받아, 입력 하나가 hidden layer을 거쳐 하나의 출력을 내보내는 방식이다.
classification문제라면 카테고리가 될 것이다.
하지만, 머신러닝의 관점에서 생각해보면 모델이 다양한 입력을 처리할 수 있도록 유연해질 필요가 있다. 그렇기에 RNN은 네트워크가 다양한 입/출력을 다룰 수 있는 여지를 제공해준다.
- One to Many
입력은 이미지와 같은 “단일 입력”이지만, 출력은 caption
과 같은 “가변 출력”이다. caption에 따라 단어의 수가 천차 만별이다.
- Many to One
입력이 문장과 같은 “가변 입력”이다. 이 문장의 감정을 분류한다. 긍정적인 문장인지 부정적인 문장인지 구별하는 것이다.
- Many to Many
computer vision의 경우, 입력이 비디오라고 하면, 비디오에 따라 전체 프레임 수가 다양하다. 따라서 전체 비디오를 읽으려면 가변 길이의 입력을 받아야 한다.
비디오를 입력받아서 비디오에 나타나는 action을 분류한다면, 이 경우는 입/출력 모두 가변이어야 한다.
번역을 할 때도, 입력이 영어 문장이면 가변 입력이 될 것이고, 출력은 번역 결과인 문장이 되니 출력도 가변 출력이 된다.
비디오의 매 프레임마다 classification을 해야 한다면 이 또한, 입/출력이 둘다 가변이 된다.
Recurrent Neural Network는 가변 길이의 데이터를 다루기 위해 필요한 일반적인 방법이다.
RNN은 위의 다양한 상황들에 대해 잘 처리할 수 있도록 해준다.
- Sequential Processing of Non-Sequence Data
입/출력은 고정이지만, 연속적인 프로세싱이 요구되는 경우가 있다.
위의 이미지가 있다고 치면, 이는 고정 입력이다. 그리고 이미지의 숫자가 몇인지 분류하는 문제이다. 입력 이미지의 정답을 전체를 보지 않고, 이미지의 여러 부분을 조금씩 살펴본다. 다 살펴본 후 숫자가 몇인지 판단한다.
이처럼 입/출력이 고정된 길이라고 해도 가변 과정인 경우에도 RNN은 상당히 유용하다.
그렇다면, RNN은 어떻게 동작하는 것일까?
일반적으로 RNN은 작은 “Recurrent Core Cell”을 가지고 있다.
입력 x가 RNN으로 들어가면 RNN에는 내부에 hidden state가 있을 것이다. hidden state는 RNN이 새로운 입력을 학습할 때마다 매번 업데이트된다.
RNN이 매 단계마다 값을 출력하는 경우
- RNN이 입력을 받는다.
- hidden state를 업데이트
- 출력값을 내보낸다.
RNN의 구조를 수식적으로 나타낸다면 아래와 같다.
초록색 RNN Block은 함수f를 통해 재귀적인 관계를 연산할 수 있도록 설계된다.
파라미터 w를 가진 함수f가 있다고 할 때, 함수 f는 이전 상태의 hidden state인 h_t-1과 현재 상태의 입력인 x_t를 입력으로 받는다. 그리고 h_t를 출력한다. h_t는 다음 사애의 hidden state이다.
다음 단계에서는 h_t와 x_t+1이 입력이 된다.
RNN에서 출력값 y를 가지려면 h_t를 입력으로 하는 FC-Layer을 추가해야 한다.
FC Layer은 매번 업데이트되는 hidden state(h_t)를 기반으로 출력 값을 결정한다. 이때, 함수 f와 파라미터 w는 항상 동일하다.
단순한 vanilla RNN을 예시로 보자.
이전 hidden state와 현재 입력을 받아서 다음 hidden state를 출력한다.
좀 더 자세하게 보자면, 이전 hidden state인 h_t-1와 곱해지는 가중치 행렬 w_hh와 현재 입력 x_t와 곱해지는 가중치 행렬 w_xh이 있다. 그리고 비선형을 위해 tanh를 적용한다.
이제 이를 통해 매 스텝마다 출력 y를 얻고자 한다. 이를 위해서는 h_t를 새로운 가중치 행렬 w_hy와 곱해준다. 매 스텝에 출력 y는 class score가 될 수 있을 것이다.
RNN의 두 가지 방법의 해석
- RNN이 hidden state를 가지며 재귀적으로 피드백한다는 것
첫 스텝에서 initial hidden state인 h0가 있다. 대부분은 h0는 0으로 한다. 입력 x_t와 h0를 fw의 입력으로 받아 h1을 출력한다. 이 과정을 반복해 h2,h3,,, h_T가 될 것이다. 여기서 주의해야 할 것은 동일한 가중치 행렬 W를 매번 사용한다는 점이다. h와 x만 달라진다.
그리고 위의 그림에서 y_t는 매 스텝의 class score이라 할 수 있다. y_t를 통해 실제 값과 y_t의 차이를 통해 loss 값을 구한다. 최종적인 loss는 각 개별 loss들의 합이 된다.
모델을 학습시키기 위해 dL/dw를 구해야 한다.
동일한 node를 여러 번 사용할 때 backward pass시에 dL/dw를 계산하려면 w의 gradient를 전부 더해준다. 따라서 RNN 모델의 backprop을 위한 행렬 w의 gradient를 구하려면 각 스텝에서의 w에 대한 gradient를 전부 계산한 뒤에 이 값을 모두 더해주면 된다.
many to many가 아닌 many to one의 경우는 어떻게 될까?
이 경우 최종 hidden state에서만 결과 값이 나온다.
one to many의 경우에는
고정 입력을 받지만, 가변 출력인 네트워크이다. 이 경우 대체적으로 고정 입력은 모델의 initial hidden state를 초기화시키는 용도로 사용한다.
RNN의 경우는 모든 스텝에서 출력값을 가진다.
sequence to sequence
가변 입력(sequence)과 가변 출력(sequence)을 가지는 모델로 기계 번역에 사용될 수 있는 모델이다.
many to one 과 one to many 모델의 결합이라 할 수 있다. 즉, encoder과 decoder 구조를 갖는다. encoder은 가변 입력을 받는다. english sentence가 될 수 있을 것이다.
encoder의 final hidden state를 통해 전체 sentence를 요약한다. 가변 입력을 하나의 벡터로 요약한 것이다.
반면, decoder은 가변 출력을 출력한다. 한국어로 번역된 문장을 출력한다고 할 수 있을 것이다. 가변 출력은 매 스텝 적절한 단어를 내뱉는다.
output의 각 losses를 합쳐서 backpropagation을 수행한다.
더 구체적인 예를 살펴보자. 대부분 RNN은 language modeling에 자주 사용된다. 즉, 어떻게 자연어를 만들어 내는가의 모델로서 사용된다. 예를 들어 문자(character)를 내뱉는 모델이라면 매 스텝 어떻게 문자를 생성해낼지를 해결해야 한다.
문자열 시퀀스를 읽어들이고, 현재 문맥에서 다음 문자를 예측해야 하는 네트워크이다. 리스트 [h,e,l,o]가 있고, hello 라는 단어를 만들고자 한다. train time에서는 training sequence인 hello의 각 단어를 입력으로 넣어줘야 한다.
hello가 입력 x_t가 된다. 우선 입력은 한 글자씩한다. 일반적으로 입력을 넣어주는 방법이 있는데, 우선 vocabulary는 총 h,e,l,o로 4가지이다. 각 글자는 하나의 벡터로 표현할 수 있다. 해당 글자의 위치에 해당하는 위치에만 1로 표시한다. 이 예제는 4개 글자이므로 4d 벡터로 표현가능하다.
forward pass에서의 동작을 나타낸 것이다.
우선 첫 스텝으로 입력 문자 h가 들어오면, 네트워크는 y_t를 출력한다. y_t는 어떤 문자가 h 다음에 나올 것 같은지를 예측한 값이다. 이 예제에서는 e를 예측해야 정답이다.
softmax를 통해 이 예측값이 얼마나 정답에 가까운지 값을 출력할 것이고, 다음 스텝도 동일하게 진행된다.
하지만 다른 점은, 이전 hidden state도 함께 계산이 이루어진다는 것이다. 이전 hidden state와 함께 새로운 hidden state를 만들어낸다.
모델에 다양한 문장을 학습시키면 결국 모델은 이전 문장들의 문맥을 참고해서 다음 문자가 무엇일지를 예측한다.
test time에서는 train time에 모델이 봤을 법한 문장을 모델 스스로 생성해내도록 한다.
우선 모델에게 문장의 첫 글자만 제공하여 각 문자에 대한 스코어(output)를 얻는다. 스코어를 softmax 함수를 활용하여 확률분포로 만들고, 이를 이용해 다음 글자를 선택한다.
이렇게 뽑힌 글자를 다음 스텝의 입력으로 넣어준다. 이 문자를 벡터화 할 것이고, 입력으로 넣어 네트워크의 두번째 출력을 만들어낸다.
가장 높은 스코어가 아닌 확률분포를 사용하는 이유는 가장 높은 스코어를 사용한다고 해서 올바른 결과를 얻을 수 없다. 어떤 경우는 argmax probasbility만 사용할 수 있다. 하지만 확률분포로 샘플링하는 것이 모델에게 다양성을 줄 수 있다.
또, test time에서 softmax vector 대신 one hot vector을 사용하게 되면 입력이 train time에서 보지 못한 입력 값을 주게 되면 대게 모델이 아무 기능을 못한다. 또, 실제의 vocabulary는 매우 크기 때문에, one hot 대신 softmax를 사용한다.
실제로 one hot vectore를 sparse vectore 처리하는 방법도 있다. 큰 단어가 들어오면 연산량이 엄청나기 때문이다. 이런 모델의 경우 스텝마다 출력값이 존재한다. 이 출력값들의 loss를 계산해서 final loss를 얻는데 이를 backpropagation through time이라 한다.
forward pass의 경우 전체 시퀀스가 끝날 때까지 출력값이 생긴다. 반대로 backward pass에서도 각 sequence를 통한 loss를 계산해야 한다. 이 경우 문장이 아주 길면 문제가 될 수 있다. gradient를 계산하려면 모든 것들을 다 거쳐야 하기 떄문이다.
따라서 truncated backpropagation through time을 통해 backpropagation을 근사시키는 기법을 사용한다.
비록 입력 sequence가 무한대일지라도 train time에 한 스텝을 100정도의 일정 단위로 자른다.
100 스텝만 forward pass하고 이 서브 시퀀스의 loss를 계산한다. 그리고 gradient step을 진행한다. 이 과정을 반복한다.
다음 batch의 forward pass를 계산할 때는 이전 hidden state를 이용한다. 하지만, gradient step은 현재 batch에서만 진행한다.
Q. RNN이 Markov Assumption을 따르는가? 그렇지 않다. RNN은 이전 hidden state를 계속해서 앞으로 가져오기 때문이다. hidden state를 조건으로 마르코비아 가정을 하고 있다면, hidden state가 미래를 예언하는 시퀀스의 우리가 필요로 하는 모든 것이기 때문에 시간을 통한 역전파를 시켜야 한다.
RNN 코드
min-char-rnn이라는 코드가 있다. 112 줄의 코드로 RNN 전체 과정을 구현했다. 여기서 truncated backporpagation을 사용한다.
이 모델은 학습과정 동안에 시퀀스 데이터의 숨겨진 구조(latent structure)을 알아서 학습한다. hidden vector이 있고 이 vector이 계속 업데이트된다.
vector이 어떤 의미를 하는 것인지 알아보기 위해 실험을 진행했다. hidden state는 아무 의미 없는 패턴이었지만, vector하나를 뽑은 다음 이 시퀀스를 다시 한번 forward시켜보았다.
위의 각 색깔은 시퀀스를 읽는 동안 앞서 뽑은 hidden vector의 값을 의미한다. 대부분의 cell은 해석하기 어려웠다. 대부분은 다음에 어떤 문자가 와야할지 알아내기 위한 low level 정도였다.
하지만, 따옴표를 찾는 벡터를 알아냈다. 처음에는 계속 off(파란색)이지만, 따옴표를 만나면 값이 켜진다(빨간색). 그리고는 따옴표가 닫히기 전까지 유지되다가 마침 따옴표를 만나면 다시 값이 꺼진다.
또, 줄바꿈을 위해 현재 줄의 단어 갯수를 세는 듯해보이는 cell도 있었다. 처음에는 0으로 시작하다가 줄이 점점 길어지면 점점 빨간색으로 변했다. 줄바꿈이 이루어지면 다시 0으로 리셋되었다. 이를 통해, 모델이 언제 새로운 라인을 필요로 할지 지속적으로 추적하는 역할을 한 것이다.
Image Captioning
다시 이미지 모델로 돌아와, image captioning은 입력이 이미지이고 출력이 자연어로 된 caption이다.
caption은 가변길이다. caption마다 다양한 시퀀스 길이를 가지고 있다. 여기에 RNN Language model이 아주 잘 어울린다.
모델에 CNN이 들어있는데, CNN은 요약된 이미지 정보가 들어있는 vector를 출력한다. 이 vector은 RNN의 초기 step의 입력으로 들어간다. 그러면 RNN은 caption에 사용할 문자들을 하나씩 만들어낸다.
그렇다면 test time에서는 어떻게 동작하는지 알아보자
입력 이미지를 받아 CNN의 입력으로 넣는다. 다만 softmax score을 사용하지 않고, 직전의 4096차원의 vector을 출력한다. 이 벡터는 전체 이미지 정보를 요약하는데 사용된다.
RNN에 이 벡터를 초기 입력값으로 입력한다.
- 모델에게 “여기 이미지 정보가 있으니 이 조건에 맞는 문장을 만들어줘” 라고 시작해야 한다. 이전까지의 모델에서는 RNN 모델이 두 개의 가중치 행렬을 입력으로 받았다. 하나는 현재 스텝의 입력, 다른 하나는 이전 스텝의 hidden state였다. 이 둘을 조합하여 다음 hidden state를 얻었다. 하지만 이제는 이미지 정보도 더해줘야 한다. 가장 쉬운 방법이 세번째 가중치 행렬을 추가하는 것이다. 다음 hidden state를 계산할 때마다 모든 스텝에 이 이미지 정보를 추가한다.
- vocabulary의 모든 스코어들에 대한 분포를 계산한다. 여기서 vocabulary는 엄청 크다. 그 분포에서 샘플링하고 그 단어를 다음 스텝의 입력으로 다시 넣어준다.
모든 스텝이 종료되면 결국 한 문장이 만들어진다. train time에는 모든 caption의 종료지점에 end 토큰을 삽입한다. 네트워크가 학습하는 동안 스퀀스의 끝에 end토큰을 넣어야 한다는 것을 알려줘야 하기 때문이다.
학습이 끝나고 test time에는 모델이 문장 생성을 끝마치면 end 토큰을 샘플링한다.
이 모델은 완전한 supervised learning(지도학습)으로 학습시킨다. 이 모델을 학습시키기 위해서는 natural language model과 CNN을 동시에 backprop시켜야 한다.
하지만, 위의 모델은 보지 못한 데이터에 대해서는 잘 동작하지 않는다.
따라서 더 발전된 모델인 Attention이라는 모델을 개발했다. 이 모델은 caption을 생성할 때 이미지의 다양한 부분을 집중해서(attention) 볼 수 있다.
간단하게만 보자면 CNN이 있는데, CNN으로 벡터 하나를 만드는게 아니라 각 벡터가 공간정보를 가지고 있는 grid of vector(LxD)을 만들어낸다.
forward pass할 때, 매 스텝 vocabulary에서 샘플링을 할 때 모델이 이미지에서 보고싶은 위치에 대한 분포도 같이 만든다. 이미지의 각 위치에 대한 분포는 train time에 모델이 어느 위치를 봐야하는지에 대한 attention이라 할 수 있다.
첫번째 hidden state(h0)는 이미지의 위치에 대한 분포를 계산한다. 그리고 분포(a1)를 다시 벡터 집합(LxD)와 연산하여 이미지 attention(z1)을 생성한다. 이 벡터는 RNN의 다음 스텝의 입력으로 들어간다.
그러면 두 개의 출력(a2,d1)이 생성된다. d1은 vocabulary의 각 단어들의 분포, a2는 이미지 위치에 대한 분포이다.
이 과정을 반복하면 매 스텝마다 값 두 개가 계속 만들어질 것이다.
caption을 만들 때 마다 이미지 내에 다양한 곳들에 attention을 주는 것도 볼 수 있다.
attention 모델로 caption을 생성하면 의미있는 부분에 집중되어 있다는 것을 알 수 있다.
RNN + Attention
RNN+Attention 조합은 image captioning 뿐만 아니라 VQA(Visual Question Answering) 과 같은 곳에도 사용될 수 있다.
VQA에서는 입력이 두 가지다. 하나는 이미지, 다른 하나는 이미지에 대한 질문이다.
예를 들어, 첫번째 그림과 같이 Q: 트럭에 그려져 있는 멸종위기 동물은 무엇인가? 와 같이 Q와 이미지가 같이 입력으로 들어가야 한다. 그리고 모델은 네 개의 보기 중에서 정답을 맞춰야 한다.
이 경우는 many to one의 경우에 해당된다. 모델은 자연어 문장(질문)을 입력으로 받아야 한다. 이는 RNN을 통해 RNN이 질문을 vector로 요약하고, 이미지 요약을 위해 CNN을 사용한다.
간혹 VQA 문제를 풀기 위해 soft special attention 알고리즘을 적용하는 경우도 있다.
이 예시를 보면 모델이 정답을 결정하기 위해 이미지에 대한 attention을 만들어낸다.
Multilayer RNN
이때까지는 RNN 레이어를 단일로 사용했다. hidden state도 하나 뿐이었다. 하지만 더 자주 보게 될 모델은 Multilayer RNN이다.
3 layer RNN이 있다. 입력은 첫번째 RNN으로 들어가서 첫번째 hidden state를 만들어낸다. RNN 하나를 돌리면 hidden states 시퀀스(첫번째 초록색 줄)가 생긴다. 이렇게 만들어진 hidden state 시퀀스를 다른 RNN의 입력으로 넣어줄 수 있다. 그러면 RNN layer가 만들어내는 또 다른 hidden state 시퀀스가 생겨난다.
이런식으로 RNN Layer을 쌓아올린다. 이렇게 하는 이유는 모델이 깊어질수록 다양한 문제들에 대해 성능이 좋아지기 때문이다. 대부분 2~4 layer RNN을 사용한다.
RNN을 사용할 때 문제점이 있다.
우리가 봐오던 일반적인(vanilla) RNN Cell이 있다. 입력은 현재 입력 x_t 와 이전 hidden state h_t-1이다. 이 두 입력을 쌓는다(stack).
그리고 가중치 행렬 w와 행렬 곱연산을 하고 tanh를 씌워 다음 hidden state(h_t)를 만든다.
이 아키텍처의 backward pass에서 gradient 계산과정은 어떻게 될까?
우선 backward pass시 h_t에 대한 loss의 미분값을 얻는다. 그 다음 loss에 대한 h_t-1의 미분값을 계산하게 된다.
backward pass는 빨간색 화살표를 따르게 될 것이다. 우선 gradient가 tanh gate를 탄 후 matmul gate를 통과한다.
matmul gate의 backprop는 이 가중치 행렬 W을 곱하게 된다.
이는 매번 cell을 하나 통과할 때마다 가중치 행렬의 일부를 곱하게 된다는 것을 의미한다.
하지만 이는 우리가 h_0에 대한 gradient를 구하고자 한다면 결구 모든 RNN cell을 거쳐야 한다는 것이다. 즉, 아주 많은 가중치 행렬들이 개입하며 이는 안좋은 방법이다.
가중치를 행렬이 아닌 스칼라로 생각해보자. 만약 곱해지는 값이 1보다 크면 점점 값이 커지고, 1보다 작다면 점점 작아져 0이 될 것이다. 이 두 상황이 일어나지 않으려면 곱해지는 값이 1인 경우밖에 없다. 실제로 1이 되기는 매우 어렵다.
스칼라와 동일하게 행렬도 마찬가지다. 행렬의 특이값(singular value)가 엄청나게 크다면, 역시 h_0의 gradient는 엄청 커진다. 이를 exploding gradient problem이라고 한다. backprop시 레이어가 깊어질수록 gradient가 기하급수적으로 증가하는 현상이다.
반대로 특이값이 1보다 작다면 기하급수적으로 작아져 vanishing gradient problem이 발생한다.
그래서 사람들은 gradient clipping이라는 기법을 사용하곤 한다. gradient를 계산하고, gradient의 L2 norm이 임계값보다 큰 경우 gradient가 최대 임계값을 넘지 못하도록 조정해준다. 좋은 방법은 아니지만, 많은 사람들이 RNN학습에 활용한다.
exploding을 막는데는 효과적이나, vanishing을 다루려면 좀 더 복잡한 RNN 아키텍처가 필요하다.
Long Short Term Memory
이는 LSTM(Long Short Term Memory)이 관한 것이다. LSTM은 vanishing과 exploding gradient 문제를 완화시키기 위해 디자인되었다. gradient가 잘 전달되도록 아키텍처 자체를 디자인 한 것이다.
한 cell 당 1개의 hidden state만 가지던 vanilla RNN과 달리 Cell 당 두 개의 hidden state를 가진다. 하나는 Vanilla RNN에 있던 것과 유사한 개념인 h_t가 있고, cell state라고 하는 c_t
라는 vector이 더 존재한다. c_t는 LSTM 내부에만 존재하여 밖에 노출되지 않는 변수이다.
LSTM도 두 개의 입력(h_t-1, x_t)을 받는다. 그리고 i,f,o,g라는 4개의 gates를 계산한다. 이 gates를 c_t를 업데이트하는데 이용한다. 그리고 c_t로 다음 스텝의 hidden state를 업데이트한다.
동작하는 방식으로는, 우선 이전 hidden state h_t와 입력 x_t를 입력으로 받는다. vanilla RNN의 경우 두 입력을 cancat하고 행렬곱 연산으로 hidden state를 직접 구했다.
하지만, LSTM에서는 이전 hidden state와 입력을 받아 쌓는다. 그리고 네 개의 gate의 값을 계산하기 위한 커다란 가중치 행렬을 곱해준다. 각 gate의 출력은 hidden state의 크기와 동일하다. 다른 크기로 디자인할 수도 있긴 하다.
gate의 출력 중
- i는 input gate이다. i는 cell에서의 입력 x_t에 대한 가중치다.
- f는 forget gate로 이전 스텝의 cell의 정보를 얼마나 지울지에 대한 가중치다.
- o는 output gate로 cell state인 c_t를 얼마나 밖에 드러내 보일지에 대한 가중치다.
- g는 gate gate라 하는데, input cell을 얼마나 포함시킬지 결정하는 가중치다.
중요한 것은 각 gate에 사용하는 non-linearity가 다 다르다는 점이다. input/forget/output gate는 sigmoid를 사용한다. gate의 값이 0~1 이라는 의미다. 반면 gate gate는 tanh를 사용한다. -1~+1 값을 갖기 위함이다.
forget gate=0이면 이전 cell state를 잊는다는 것이고 1이라면 이전 cell state를 기억한다는 것이다.
i=0 or 1이므로 이 cell state를 사용하고 싶으면 1, 쓰지 않으려면 0이 된다.
c_t의 수식을 보면 이전 cell state(c_t-1)을 계속 기억할지 말지를 결정한다(f*c_t-1). 즉 cell state의 각 요소는 줄었다 늘었다 할 수 있다.
cell state를 계산한 후에는 hidden state를 업데이트할 차례다. h_t는 실제로 밖에 보여지는 값이다. 그렇기에 cell state는 counters의 개념으로 최대 1 또는 -1씩 세는 것이다. 이 값이 tanh를 통과하여 output gate와 곱해진다.
output gate 또한 sigmoid에서 나온 값으로 0~1 값을 가진다. output gate는 각 스텝에서 다음 hidden state를 계산할 때 cell state를 얼마나 노출시킬지를 결정한다.
이는 LSTM 동작 다이어그램이다.
- 우선 cell state c_t-1과 hidden state h_t-1을 입력으로 받는다. 현재 입력 x_t도 있다.
- 이전 hidden state h_t-1과 현재 입력 x_t를 쌓는다.
- 가중치 행렬 w와 곱해서 4개의 gates를 만든다.
- forget gate를 이전 cell state c_t-1 과 곱한다.
- input/gate gate를 곱한 후 cell state 와 곱해서 다음 cell state를 만든다.
- c_t는 tanh를 거쳐 output gate와 곱해져 다음 hidden state(h_t)를 만든다.
그렇다면 backward pass는 어떨까?
앞서 배운 Vanilla RNN의 경우에는 가중치 행렬 W가 계속해서 곱해지는 문제가 있었다. 하지만 LSTM에서는 cell state의 gradient를 계산해주는 backward경로를 살펴보면 addition operation(+)가 있다. additino에서는 gradient가 그저 복사된다.
따라서 gradient는 upstream gradient * forget gate 이다.
이 특성으로 인해 Vanilla RNN보다 좋은 점이 두 가지 있다.
첫번째로는 forget gate와 곱해지는 연산이 matrix multiple 이 아닌 element-wise이다.
두번째로는 element wise multiplication을 통해 매 스텝 다른 값의 forget gate와 곱해질 수 있다는 것이다. 이를 통해 exploding/vanishing gradient 문제를 해결할 수 있다. 앞서 vaniila RNN의 경우는 동일한 가중치 행렬(h_t)만 계속 곱했다. 이때문에, exploding/vanishing 문제가 발생한 것이다.
그리고, forget gate는 sigmoid의 출력이므로 0~1이다. 이는 forget gate를 반복적으로 곱한다고 했을 때 더 좋은 수치적 특성을 보일 수 있다.
gradient 계산을 쭉 보면, 모델의 종단인 loss에서 가장 처음 cell state(c0)까지 방해가 별로 없다.
Q. W를 업데이트해야 할텐데, W에 대한 gradient는 어떻게 되나? 가중치 w에 대한 local gradient는 해당 스텝에서의 cell/hidden state로부터 전달된다.
vanilla RNN의 경우 각 스텝의 가중치 행렬 w들이 서로 영향을 미쳤다. 하지만 LSTM의 경우 아주 긴 시퀀스가 있다고 했을때, w의 local gradient는 cell/hidden state로부터 흘러온다,
lstm에서는 cell state가 gradient를 잘 전달해주기 때문에 w에 대한 local gradient도 훨씬 더 잘 전달된다.
Q. non-linearity로 인한 vinishing gradient 문제는 어떻게 되나? f의 경우 출력이 0~1이니 항상 1보다 작아져서 gradient가 점점 감소할 수 있다.
이를 해결하기 위해 f의 bias를 양수로 초기와시키는 방법이 있다. 이를 위해 학습 초기에 forget gate의 값을 1에 가깝게 설정한다.
그렇게 학습이 진행되면 f의 bias가 적절한 자기 자리를 찾아간다.
물론 LSTM에서도 vanishing gradient문제가 있다. 하지만 vanilla RNN보다 덜 민감한 첫번째 이유는 매 스텝마다 f가 변하기 때문이고, 두번째는 LSTM에서는 element wise multiplication을 수행하기 때문이다.
LSTM을 보면 ResNet과 유사하게 생겼다. ResNet에서도 backward pass에서 고속도로 역할인 identity connection이 유용했다.
LSTM에서도 Cell state의 element wise multiple이 gradient 고속도로 역할을 수행한다.
GRU
LSTM 다음으로 유명한 것이 GRU(Gated Recurrent Unit)이다. vanishing gradient를 해결하기 위한 element wise mult gate가 있다.
Reference
- http://cs231n.stanford.edu/2017/syllabus.html
- https://velog.io/@cha-suyeon/CS231n-Lecture-10-%EA%B0%95%EC%9D%98-%EC%A0%95%EB%A6%AC