이번 포스팅은 standford university의 cs231 lecture 10을 공부하고, 강의와 슬라이드를 바탕으로 정리한 글임을 밝힙니다.
💻 유튜브 강의: Lecture 10 | Recurrent Neural Networks
💻 한글 강의: cs231n 10강 RNN, LSTM
📑 slide: PDF
Machine Learning 관점에서 생각해보면 모델이 다양한 입력을 처리할 수 있도록 유연해질 필요가 있습니다.
Vanlilla Neural Networks의 한계는 고정된 크기의 vector를 입력으로 받아들이고 고정 크기의 vector를 출력합니다. 고정된 양의 계산 단계를 사용하여 mapping을 수행하는데요.
RNN은 vectro sequence에 대해 연산을 수행할 수 있고, 더 구체적으로 만들 수 있다는 점에서 차이가 있습니다.
해당 그림에서 각 사각형은 vector이며 화살표는 function을 의미합니다.
빨간색 box는 input vector이며, 파란색 box는 output vector, 녹색 box는 RNN state입니다.
one to one
RNN이 없는 Vanilla mode이며, fixed-sized input을 fixed-sized output을 처리합니다.
one to many
Sequence output (e.g. Image Captioning, image -> sequence of words)
many to one
Sequence Unit (e.g. Sentiment Classification, sequence of words -> sentiment)
many to many
Sequence input and sequence output (e.g. Machine Translation, seq of words in English -> seq of words in French)
many to many
Synced sequence input and output (e.g. Video Classification on frame level)
fixed-sized의 input과 output이 필요한 상황에서도 RNN은 쓰일 수 있습니다. 이를 sequential processing이 요구 되는 경우라고도 설명합니다.
위 이미지는 fixed input입니다. 이미지의 숫자가 몇인지 분류하는 문제이고, 입력 이미지의 label을 feed-forward pass 한 번으로 결정하는 것이 아니라 RNN network는 이미지의 여러 부분을 조금씩 살펴봅니다.
이미지를 살펴본 후 숫자가 몇 인지를 최종적으로 판단할 수 있습니다.
위의 예제처럼 입출력이 고정된 길이라고 해도 "가변 과정(processing)"인 경우에 RNN은 유용하게 쓰입니다. 일부분씩 순차적으로 처리할 수 있기 때문입니다.
일반적으로 RNN은 작은 Recurrent Core Cell을 갖고 있습니다.
입력 가 RNN으로 들어가면, RNN은 새로운 state vector를 만들어내기 위해 fixed function을 사용하여 input vector와 output vector를 결합합니다.
RNN 내부의 hidden state에서 새로운 입력을 받아들여 update 되는 방식입니다.
이런 방식으로 RNN은 네트워크가 다양한 입출력을 다룰 수 있는 여지를 제공해 줍니다.
위의 수식을 통해 RNN이 어떻게 동작하는 지 알 수 있습니다.
RNN에서 출력 값을 가지려면 를 입력으로 하는 FC-Layer를 추가해야 합니다. FC-Layer는 매번 업데이트 되는 Hidden_state를 기반으로 출력 값을 정합니다.
❗ Notice: 함수 와 parameter 는 매 step에서 동일합니다.
가중치 행렬 와 입력 의 곱으로 나타낼 수 있습니다.
또한, 가중치 행렬 도 있는데요. 이는 이전 hidden state 값인 와 곱해집니다.
이렇게 두 입력 에 대한 행렬 곱 연산이 있고, 두 연산 결과 값을 더해줍니다.
그리고 여기서 non-linearity를 구현하기 위해 를 사용합니다.
RNN은 hidden state를 가지며 Recurrently feed back하는 특징을 갖고 있습니다.
RNN의 Multiple time steps을 unrolling해서 보려고 합니다. unrolling하면 hidden states, 입출력, 가중치 행렬들 간의 관계를 이해하는데 도움이 될 겁니다!
initial hidden state인 가 있습니다. 대부분의 경우 은 0으로 초기화 시킵니다. 그리고 입력값 가 있습니다. 과 이 함수 의 입력으로 들어갑니다.
의 출력은 입니다. 이 과정이 계속 반복됩니다.
이번에는 이 다음 입력으로 들어옵니다. 의 출력은 가 됩니다.
이 과정을 반복하면서 가변 입력 를 받습니다.
이제 더 구체적으로 보기 위해 행렬 가 추가됩니다.
여기서 알아야 할 점은 동일한 가중치 행렬 가 매번 사용된다는 점입니다!
매번 와 는 달라지지만 는 매번 동일합니다.
앞의 강의에서 Computational graph에서는 동일한 node를 여러 번 사용할 때 backprop gradient의 흐름이 어떤지를 배웠습니다.
backward pass시 를 계산하려면 의 gradient를 전부 더해줬습니다.
따라서 이 RNN 모델의 backprop을 위한 행렬 의 gradient를 구하려면 각 step에서의 에 대한 gradient를 전부 계산한 뒤에 이 값들을 모두 더해주면 됩니다.
Computational graph에 도 넣어볼 수 있습니다. RNN의 출력값 가 또 다른 네트워크의 입력으로 들어가서 를 만들어 냅니다. 그래서 는 매 스텝의 class score라 할 수 있습니다.
RNN의 Loss도 살펴보겠습니다. 각 시퀀스마다 Ground truth label이 있다고 가정하면, 각 스텝마다 개별적으로 에 대한 Loss를 계산할 수 있습니다.
여기서 Loss는 softmax loss입니다. RNN의 최종 Loss는 각 개별 loss들의 합이 됩니다.
각 단계에서 발생한 loss를 모두 더하여 최종 loss를 계산합니다. 이 네트워크의 Bakprop을 생각해보면 모델을 학습 시키기 위해 를 구해야 합니다.
Loss flowing은 각 스텝에서 이루어집니다. 이 경우에는 각 time stpe마다 가중치 에 대한 local gradient를 계산할 수 있습니다.
이렇게 개별로 계산된 local gradient를 최종 gradient에 더합니다.
감정 분석(sentiment analysis)에 쓰이는 many to one의 경우를 살펴보겠습니다.
이 경우에는 네트워크의 최종 hidden state에서만 결과 값이 나올 것입니다. 최종 hidden state가 전체 시퀀스 내용에 대한 일종의 요약이기 때문입니다.
그렇다면 one to many의 경우는 어떨까요?
fix sized input을 받지만, variably sized output인 network입니다.
이 경우에는 대부분 고정 입력은 모델의 initial hidden state을 초기화 시키는 용도로 사용하고, RNN은 모든 step에서 output을 가집니다.
고정 입력을 받지만 가변 출력인 네트워크입니다. 이 경우에는 대체적으로 고정 입력은 모델의 initial hidden state를 초기화 시키는 용도로 사용합니다.
그리고 RNN은 모든 스텝에서 출력 값을 가집니다.
이렇게 가변 출력을 가지는 경우에도 그래프를 Unroll 할 수 있습니다.
machine translation에 쓰이는 Sequence to Sequence 모델에 대해서 알아보겠습니다.
이것은 두 개의 스테이지로 구성됩니다.
구조입니다.
encoder은 가변 입력을 받습니다. 예를 들면, English sentence가 될 수 있습니다.
encoder의 final hidden state를 통해 전체 sentence를 요약합니다.
encoder에서는 many to one을 수행합니다. 가변 입력을 하나의 vector로 요약합니다.
decoder은 one to many를 수행합니다. 입력은 앞서 요약한 하나의 vector가 되었는데요. decoder는 가변 출력을 내뱉습니다.
예를 들여, 아까 입력 받은 문장을 다른 언어로 번역된 문장이 될 수 있습니다.
가변 출력은 매 step마다 적절한 단어를 출력합니다.
그럼 전체 Computational graph를 풀어서 전체 학습 과정을 해석해보면 Output sentence의 각 loss들을 합해 Backprob을 진행합니다.
이제 더 구체적인 예를 살펴보겠습니다.
RNN은 Language modeling에서 자주 사용합니다.
Language modeling의 문제에서 해결하고자 하는 목표는 바로 어떻게 자연어(natural language)를 만들어 낼 지 입니다.
Character-level Language Model의 예를 들어 보겠습니다.
네트워크는 문자열 sequence를 읽어 오고, 현재 문맥에서 다음 문제를 예측해야 합니다.
'hello'
이 예시에서는 'hello'의 단어를 제시합니다.
이를 list로 표현하면
[h,e,l,o]
입니다.
Train time에서는 training sequence(hello)의 각 단어를 입력으로 넣어줘야 합니다.
각 글자는 하나의 vector로 표현이 가능하며, 이 vector는 1이 하나 있고 나머지는 0인 vector입니다. vector에서 해당 글자의 위치에서만 1로 표시를 해주는 것입니다.
Forward pass에서 네트워크의 동작은 첫 번째 step에서는 입력 문자 h가 들어오고, 첫 번째 RNN cell로는 'h'가 들어갑니다.
이 네트워크는 를 출력하고 이 는 어떤 문자가 'h' 다음에 나올 것 같은 지를 예측한 값입니다.
이 과정을 반복하며 모델을 다양한 문장으로 학습 시킨다면 결국 모델은 이전 문장의 문맥을 참고하여 다음 문자가 무엇인지를 학습해야 할 것입니다.
그러면 test time은 어떻게 될까요?
이렇게 training하면 모델을 활용할 수 있는 방법들 중 하나는 model로부터 sampling하는 것입니다.
다시 말해, train time에 모델이 봤을 법한 문장을 모델 스스로 생성해 내는 것입니다.
test time에서는 이 결과(score)를 sampling에 이용합니다. score를 확률 분포로 표현하기 위해 softmax 함수를 사용할 수 있습니다.
확률 분포에서 'e'가 나왔고, 이를 다음 스텝의 네트워크 입력으로 넣어줍니다. 'e'를 다시 vector [0,1,0,0]으로 만들어주고 그 다음 input으로 넣어주면 네트워크는 두 번째 출력을 만들어냅니다.
이렇게 학습된 모델만 가지고 새로운 문장을 만들어 내기 위해 이 과정을 반복합니다.
전체 문장을 만들기 위해 time step마다 확률 분포에서 문자를 하나씩 뽑아냅니다.
RNN 모델의 경우 sequence step마다 출력값이 존재합니다.
이 출력값들의 loss를 계산해 final loss를 얻는데 이를 backpropagation through time이라 합니다.
forward pass의 경우, 전체 sequence가 끝날 때까지 출력값이 생깁니다.
반대로 backward pass에서도 전체 sequence를 갖고 Loss를 계산해야 합니다.
이 경우에서 sequence가 아주 길다면 문제가 될 수 있습니다.
만약 위키디피아에 있는 모든 문서를 바탕으로 학습을 한다면 아주 느릴 것입니다.
gradient를 계산하려면 전체 문서를 다 거쳐야 하기 때문이죠.
이 전체 문서에 대한 gradient를 계산하고 나면 gradient updata가 1회 수행된 꼴입니다.
그렇다면 다시 반복하는 과정은 아주 느릴 것이고, 메모리 부족의 문제로도 이어집니다.
실제로는 Truncated Backpropagaiton이라는 방법을 통해 backprop을 근사시키는 기법을 사용합니다!
이 방법의 아이디어는 비록 input sequence가 엄청나게 길어서 무한대라고 할 지라도 train time에 한 step을 일정 단위로 자릅니다.
대략 100정도로 자른다고 가정해보겠습니다.
100 step만 forward pass하고 이 sub sequence의 loss를 계산합니다. 그리고 gradient step을 진행합니다.
이 전체 과정을 반복합니다. 다만 이전 batch(=100)에서 계산한 hidden states는 계속 유지합니다.
다음 Batch의 forward pass를 계산할 때는 이전 hidden state를 이용합니다. 그리고 gradient step은 현재 Batch에서만 진행합니다.
이 방법을 Truncated Backpropagaiton through time이라 합니다.
이 과정을 계속 반복합니다.
이전 batch에서 hidden states를 가져와 forward pass를 수행하고 backpropb은 현재 batch만큼만 진행합니다.
이 방법은 Stocastic gradient descent의 sequence data 버전이라 볼 수 있습니다.
RNN Language model로 아주 재밌는 것을 학습 시킬 수 있습니다. RNN으로는 어떤 문장이든 학습 시킬 수 있습니다.
여기서는 셰익스피어의 소설을 예로 들고 있는데요.
학습 초기에는 의미없는 문장만 뱉어내다가 학습을 시킬 수록 의미 있는 문장을 만들어 냅니다. 학습이 끝나면 셰익스피어 느낌을 내는 문장을 만들어 냅니다.
더 학습을 길게 시키면 훨씬 더 긴 문장도 만들어 낼 수 있습니다. 이것은 셰익스피어의 느낌을 따라하는 것 뿐만 아니라 문장의 구조를 완벽하게 익히는 과정과도 비슷합니다.
여기서 알아야 할 점은 우리가 모델에게 요청한 것이 무엇인가? 입니다.
그건 Sequence의 다음 문자를 예측하라는 것이었는데요.
모델은 학습 과정 속에서 시퀀스 데이터의 숨겨진 구조(latent structure)을 알아서 학습합니다.
Andrej란 사람이 모델이 어떤 방식으로 학습하는지 궁금해서 연구 후 논문을 발표했다고 합니다.
아래 내용이 해당 내용입니다.
모델이 도대체 어떤 방식으로 학습할까요?
RNN에는 hidden vector가 있고 이 vector가 계속 업데이트 됩니다.
그 vector를 추출해보면 해석 가능한 의미 있는 것들이 올 수도 있지 않을까하는 추측에서 출발합니다.
RNN Language model을 특정 dataset으로만 학습시키고, hidden vector을 하나 뽑아서 어떤 값이 들어있는지 살펴보았습니다.
서로 다른 hidden states가 도대체 어떤 것을 중요시하게 보고 있는지 알아보기 위함이었습니다.
vector를 하나 뽑은 다음에 이 시퀀스를 한 번 forward 시켜보는 것. 여기서 각 색깔은 시퀀스를 읽는 동안에 앞서 뽑은 hidden vector의 값을 의미합니다.
대부분의 cell은 의미를 파악하기 어렵지만, 따옴표(quote)를 찾는 벡터를 알아냈습니다. 잘 보면 따옴표가 시작하는 부분 " " 이 각각 다른 색깔의 블록으로 표시되어 있음을 알 수 있습니다.
이런 식으로 그저 모델이 다음 문자를 예측하도록 학습 시켰을 뿐이지만 모델은 더 유용한 것을 학습하고 있습니다.
또한, 이곳에서는 줄바꿈을 위해 현재 줄의 단어 갯수를 세는 듯해 보이는 Cell도 발견했습니다.
처음에는 0으로 시작했다가 줄이 점점 길어지면 점점 빨간색으로 변하는데요.
점점 길어지다 줄바꿈이 이루어지면 다시 0으로 리셋됩니다.
이 cell은 언제 모델이 new line characters을 필요로 할 지 지속적으로 추적하는 역할을 수행합니다.
이것은 정말 놀라운 일이라고 합니다!
그저 다음 문자를 예측해달라는 모델을 학습했을 뿐인데 결국 모델은 입력 데이터의 구조를 학습하게 되었기 때문입니다.
약간 모델한테 숙제를 주었더니 선행 학습까지 해버리는 클래스를 보여주고 있습니다.
이 수업은 Computer Vision에 대한 수업이기 때문에 다시 Image 예시로 돌아가서 설명을 시작합니다.
이 모델의 입력은 이미지이고 출력은 자연어로 된 Caption입니다. Caption은 가변 길이입니다.
Caption마다 다양한 시퀀스 길이를 갖고 있습니다. 여기에 RNN Language Model이 잘 어울립니다.
모델에는 입력 이미지를 받기 위한 CNN이 있습니다. CNN은 요약된 이미지 정보가 들어 있는 Vector를 출력합니다.
이 Vector는 RNN의 초기 step의 입력으로 들어갑니다.
그러면 RNN은 Caption에 사용할 문자들을 하나씩 만들어 냅니다.
이제 전체 과정을 살펴보겠습니다.
입력 이미지를 받아 CNN의 입력으로 넣습니다.
다만 sofemax scores를 사용하지 않고 직전의 4,096-dim vector를 출력합니다.
이 vector는 전체 이미지 정보를 요약하는데 사용됩니다.
초깃값 입력
RNN Language models에서 배웠듯이 모델이 문장을 생성해 내기 위해 앞서 초기 값을 넣어줍니다.
여기서는 이제 이미지를 주고 문장을 만들어달라는요구로 시작합니다.
before
now
이전까지의 모델에서는 RNN 모델이 두 개의 가중치 행렬을 입력으로 받았습니다.
하나는 현재 스텝의 입력이고 다른 하나는 이전 스텝의 Hidden state였습니다.
그리고 이 둘을 조합해서 다음 Hidden state를 얻었습니다.
하지만 이제는 이미지 정보도 추가를 해줘야 하는데요. 가장 쉬운 방법은 세 번째 가중치 행렬을 추가하는 방법입니다.
이렇게 하면 다음 hidden state를 계산할 때마다 모든 스텝에서 이미지 정보를 추가하게 됩니다.
이 문제에서 Vocabulary는 위키디피아의 전체 문서보다 더 크다고 생각하면 됩니다. 그 분포에서 sampling을 하고 그 단어를 다음 스텝의 입력으로 다시 넣어줍니다.
sampling 된 가 들어가면 다시 vocab에 대한 분포를 추정하고 다음 단어를 만들어 냅니다.
모든 스텝이 종료되면 한 문장이 만들어집니다.
이때 <'END'>라는 특별한 토큰이 있는데 이는 문장의 끝을 알려주는 토큰입니다.
END가 sampling되면 모델은 더 단어를 생성하지 않고 이미지에 대한 caption을 완성합니다.
Train time에는 모든 caption의 종료 지점에 토큰을 삽입합니다. 네트워크가 학습하는 동안 시퀀스의 끝에 토큰을 넣어야 한다는 것을 알려줘야 하기 때문입니다.
학습이 끝나고 Test time에는 모델이 문장 생성을 끝마치고 토큰을 샘플링합니다. 이 Task를 위한 가장 큰 데이터 셋은 Microsoft COCO 데이터셋이 있습니다. 이 모델을 학습시키기 위해서 natural language model과 CNN을 동시에 backprop 할 수 있습니다.
이 모델을 학습한 후 나온 결괏값입니다. 잘 만든 것도 잘 못 만든 문장도 있습니다.
이 모델은 상당히 powerful합니다. 이미지 묘사를 정확히하고 있으며 비교적 복잡해 보이는 captions도 만들어냈습니다.
당연히 항상 완벽하지 않습니다. 잘못 예측하는 경우는 Train time에서 잘 발견하지 못한 데이터이기 때문인데요.
모델은 Train time에서 본 사실만 배우기 때문입니다.
조금 더 발전된 Attention이란 모델이 있습니다. 이 수업에서는 상세히 다루지는 않습니다.
CNN으로 벡터 하나를 만드는 것이 아닌 공간 정보를 가지고 있는 grid of vector를 만들어 냅니다.
Forward pass시 매 스텝 voca에서 샘플링 할 때, 모델이 이미지에서 보고 싶은 위치에 대한 분포를 만들어냅니다.
이미지의 각 위치에 대한 분포는 Train time에 모델이 어느 위치를 봐야하는 지에 대한 'Attention'이라 할 수 있습니다.
Train을 끝마치면 모델이 caption을 생성하기 위해 이미지의 attention을 이동시키는 모습을 볼 수 있습니다.
이 caption을 만들어 내기 위해 이미지 내에 다양한 곳들에 attention을 주는 것을 확인할 수 있습니다.
위의 예시를 보아도, 여자가 원반을 던진다 라는 caption을 만들어 냈고, 이미지에서 원반에 attention하고 있습니다.
모델에서 매 스텝마다 어디를 보라고 말해준 적이 없는데도 잘 생성합니다.
모델 스스로가 Train time에서 알아냈습니다. 모델이 원반 영역에 집중하는 것이 가장 올바른 일이라는 걸 스스로 알아낸 것입니다.
모델 전체가 미분이 가능하기 때문에 soft attention 또한 backprop이 가능합니다.
이 조합은 Image captioning 뿐만 아니라 더 다양한 것들을 할 수 있습니다.
Visual Question Answering이 있습니다. 여기서는 입력이 두 개입니다. 하나는 이미지와 하나는 이미지에 관련된 질문입니다!
이 모델 또한 RNN과 CNN으로 만들 수 있습니다.
이 경우는 "many to one"의 경우입니다. 모델은 자연어 문장(질문)을 입력으로 받아야 합니다. RNN으로 구현할 수 있고, RNN이 질문을 vector로 요약합니다. 그리고 여기서는 이미지 요약이 필요하기 때문에 CNN이 필요합니다.
RNN/CNN에서 나온 벡터를 조합하면 질문에 대한 분포를 예측할 수 있습니다.
지금까지는 RNN 레이어를 단일로 사용했습니다. hidden state가 하나 뿐이었습니다. 하지만 더 자주 보게 될 모델들은 Multilayer RNN입니다.
위 이미지에는 3-Layer RNN이 있습니다. 입력이 첫 번째 RNN으로 들어가서 첫 번째 hidden state를 만들어 냅니다.
RNN 하나를 돌리면 hidden state 시퀀스가 생깁니다. 이렇게 만들어진 hidden state 시퀀스를 다른 RNN의 입력으로 넣어줄 수 있습니다.
그러면 두 번째 RNN layer가 만들어내는 또 다른 hidden states 시퀀스가 생겨납니다. 이런 식으로 RNN layer를 쌓아 올릴 수 있을 것입니다.
이렇게 하는 이유는 모델이 깊어질수록 다양한 문제들에서 성능이 더 좋아지기 때문입니다. 이 방법이 RNN에서도 적용되고, 많은 경우 3~4 layer RNN을 사용합니다.
하지만 보통 엄청나게 깊은 RNN 모델을 사용하지는 않습니다. 일반적으로 2~4 layer RNN이 적절합니다.
RNN 사용할 때 문제점이 있는데요. 현재 입력 x_t, h_t-1(이전 hidden state)가 있고 이 두 입력을 쌓습니다(stack). 이렇게 두 입력을 쌓고 가중치 행렬 W와 행렬 곱 연산을 하고 tanh를 씌어 다음 hidden state(h_t)를 만듭니다.
이게 일반적인 RNN의 수행인데요. 그러면 이 아키텍처는 backward pass에 그레디언트를 계산하는 과정에서 어떤 일이 발생할까요?
일단 Backward pass시 h_t에 대한 loss의 미분값을 얻습니다. 그 다음 Loss에 대한 h_t-1의 미분값을 계산하게 됩니다. Backward pass의 전체 과정은 빨간색 통로를 따라가면 됩니다.
우선 그레디언트가 tanh gate를 타고 흘러가고, Mat mul gate를 통과합니다. Mat mul gate의 back prop은 결국 이 transpose(가중치 행렬)을 곱하게 됩니다.
이는 매번 vanilla RNN cells를 하나 통과할 때마다 가중치 행렬의 일부를 곱하게 된다는 의미인데요. RNN의 특성 상, RNN이 여러 시퀀스의 Cell을 쌓아 올리는 사실을 고려하면 그레디언트가 RNN 모델의 Layers 시퀀스를 통해 어떤 방식으로 전달되는지 생각해 볼 수 있습니다.
여기서 이상한 낌새가 발견됩니다. 만약 우리가 h_0에 대한 그레디언트를 구하고자 한다면 결국 모든 RNN Cells를 거쳐야 합니다.
이는 cell이 하나를 통과할 때마다 각 cell의 행렬 W transpose factors가 관여하고, h_0의 그레디언트를 계산하는 식을 써보면 아주 많은 가중치 행렬이 개입하게 되며 안 좋습니다.
일단 가중치가 행렬이 아니라 스칼라라고 생각해봅니다. 이것들이 스칼라 값이고 이 값들을 계속해서 곱한다고 했을 때, 값들을 수백 번 곱해야 할 수도 있습니다.
만약 곱해지는 값이 1보다 큰 경우라면 점점 값이 커질 것이고 1보다 작은 경우라면 점점 작아져서 0이 됩니다.
만약 두 상황이 일어나지 않으려면 곱해지는 값이 1이여야 합니다.
만약 스칼라가 엄청 크다면? h_0의 그레디언트는 아주 커지게 됩니다. 이를 Exploding gradients라고 합니다. backprop시 레이어가 깊어질 수록 그레디언트가 기하급수적으로 증가하는 현상입니다.
행렬의 특이값이 1보다 작은 경우라면 정반대의 상황이 발생합니다. 그레디언트가 기하급수적으로 작아집니다. 이를 Vanising gradients라고 합니다.
그래서 사람들은 gradient clipping 이라는 기법을 사용합니다. gradient clipping는 그레디언트를 계산하고 그레디언트의 L2 norm이 임계값보다 큰 경우 그레디언트가 최대 임계값을 넘지 못하도록 조정합니다.
이 방법은 그닥 좋은 방법은 아니지만 많이 사용한다고 합니다.
하지만 반대로 Vanising gradients를 다루려면 더 복잡한 RNN 아케틱처가 필요합니다. 이는 LSTM에 관한 것입니다.
LSTM은 Exploding gradients, Vanising gradients 문제를 완화시키기 위해 디자인 되었습니다.
LSTM은 아주 재밌게 생겼습니다!(🤔) vanillia RNN은 hidden state가 있었습니다. 그리고 매 스텝마다 재귀적 방법으로 hidden state를 업데이트 했었죠.
LSTM에는 한 cell 당 두 개의 Hidden state가 있습니다. 하나는 h_t이고, 하나는 c_t라는 두 번째 벡터입니다. 이것은 'Cell state'를 말합니다.
Cell state는 LSTM 내부에만 존재하며 밖에 노출되지 않는 변수입니다. LSTM도 두 개의 입력을 받습니다.(h_t-1, x_t) 그리고 4개의 gates를 계산합니다. [i, f, o, g]
이 gates를 cell_states, c_t를 업데이트하는데 이용합니다. 그리고 c_t로 다음 스텝의 hidden state를 업데이트합니다.
우선 LSTM에서는 이전 hidden state인 h_t와 현재의 입력인 x_t를 입력으로 받습니다. vanillia RNN과 비슷한데 이는 두 입력을 concat하고 행렬 곱 연산으로 hidden state를 직접 구하는 방식이었습니다.
LSTM의 경우는 조금 다릅니다. 이전 hidden state를 입력 받아 쌓아두고, 네 개의 gates의 값을 계산하기 위해 커다란 가중치 행렬을 곱해줍니다.
G는 input cell을 얼마나 포함시킬지 결정하는 가중치입니다. gate의 gate를 결정하는 게이트인 것이죠.
중요한 것은 gate에서 사용하는 non-linearity가 각양각색이라는 점인데요. i, f, o는 sigmoid를 사용합니다. 이건 값이 [0, 1] 이라는 의미겠죠. 하지만 g는 tanh를 사용합니다. 따라서 g는 [-1, 1] 의 값을 갖습니다.
이전 스텝의 Cell state(C_t-1)은 forget gate와 element-wise multiplication합니다. 결과 벡터(f*c_t-1)는 0 또는 1 일 것입니다.
따라서 forget gate=0인 element는 이전 cell state를 잊습니다. forget gate=1이면 element는 이전 cell state를 기억합니다. forget gate는 이전 cell state의 on/offf를 결정했습니다.
그 다음 i를 보겠습니다. cell state의 각 element에 대해 이 cell state를 사용하고 싶으면 1이 됩니다. 반면 쓰고 싶지 않으면 i=0이 되겠죠.
cell state를 계산하는 전체 수식을 살펴봅니다. 이 수식은 두 개의 독립적인 scaler 값 (f,i)에 의해 조정됩니다.
c_t의 수식은 이전 cell state(c_t-1)을 계속 기억할 지 말지를 결정합니다. (f*c_t-1)
즉, cell state의 각 요소는 scaler integer counters처럼 값이 줄었다 늘었다 하는 것을 볼 수 있습니다.
Cell state를 계산한 후 이제는 hidden state를 업데이트 할 차례입니다. h_t는 실제로 밖으로 보여지는 값입니다.
그렇기 때문에 cell state는 counters의 개념으로 해석할 수 있습니다. 각 스텝마다 최대 1 또는 -1씩 셉니다. 그리고 이 값은 tanh를 통과합니다. 최종적으로 output gate와 곱해집니다.
output gate 물론 sigmoid에서 나온 값이기 때문에 0~1 값을 가지며 output gate는 hidden gate를 계산할 때 cell state를 얼마나 노출시킬지를 결정합니다.
왼쪽을 보면 이전의 의 입력값이 있네요. 현재 입력 도 있습니다. 우선 이 현재 입력 를 쌓습니다. 그리고 여기서 가중치 행렬 4개를 곱해 gate를 만드네요.
그리고 f는 이전 와 곱합니다. 그리고 i, g가 element wise로 곱한 후 cell state와 곱해 다음 을 만듭니다.
는 를 거친 후 o와 곱해져서 다음 hidden state()를 만들어냅니다.
각 gate는 각 gate마다 곱해지는 가중치 행렬을 갖고 있습니다. 우선 x와 h를 쌓으면 전체 행렬이 됩니다. 그리고 가중치 행렬의 크기는 입니다. 이 가중치 행렬은 행렬 4개를 서로 합쳐 놓은 것이라 보면 됩니다. 각 4개의 행렬은 서로 가능 gate를 계산합니다.
하지만 결국은 모두 두 개의 입력를 쌓고 행렬 곱 연산을 수행하는 것입니다!
이제 LSTM의 Backward pass를 보겠습니다. 앞서 vanilla RNN의 경우 Backward pass의 문제점이 무엇이었습니까?
하지만 LSTM에서는 상황이 많이 달라집니다.
cell state에서 내려오는 upstream gradient를 살펴 봅니다. 우선 addition operation의 Backprob이 있습니다.
그레디언트는 upstream gradient와 forget gate의 element wise입니다. 그래서 upstream gradient * forget gate가 됩니다.
이게 vanilla RNN 보다 좋은 점이 2가지 있습니다.
앞서 vanilla RNN의 경우에는 동일한 가중치 행렬(h_t)만을 계속 곱했습니다. 이는 exploding/vanishing gradient 문제를 일으켰었는데, 반면 LSTM에서는 f가 스텝마다 계속 변합니다!
따라서 LSTM은 exploding/vanishing gradient 문제를 더 쉽게 해결합니다.
그리고 f는 sigmoid에서 나온 값이므로 element wise multply가 0~1 사이의 값입니다.
f를 반복적으로 곱한다고 했을 때 더 좋은 수치적인 특성으로 변합니다.
또 한 가지 명심해야 할 점!
vanilla RNN의 backward pass에서는 매 스텝에서 그레디언트가 어디를 거쳤죠? tanh를 거쳤습니다. LSTM에서도 hidden state h_t를 출력 y_t를 계산하는데 사용합니다.
만약 LSTM의 최종 hidden state h_t를 가장 첫 cell state(c_0)까지 backprop하는 걸 생각해보면, RNN처럼 매 스텝마다 tanh를 거칠 필요가 앖으며 단 한 번만 tanh를 거치면 됩니다.
LSTM의 전체적인 모습을 그려보니 cell state를 통한 backprop은 그레디언트를 위한 고속도로처럼 보입니다.
그레디언트가 모델의 종단인 Loss에서 가장 처음 cell state(C_0)까지 흘러가며 방해를 덜 받는 것입니다.
이 예제의 경우 가장 score가 높은 값만 사용하면 올바른 결과를 낼 수 없었습니다.
아까 'h' 다음 'e'를 출력한 것도 확률상 13% 낮은 비율이었기 때문입니다.
따라서 확률분포에서 sampling을 했기 때문에 'hello'를 만들 수 있었던 것입니다.
어떤 경우는 argmax probability(가장 높은 값을 취하는 방법)만 사용할 수 있습니다. 이게 더 안정적인 방법일 수 있습니다.
하지만 확률분포에서 sampling하는 방법을 사용하면 일반적으로 모델에서 다양성을 얻을 수 있습니다!
sampling 방법은 다양한 출력을 얻을 수 있다는 관점에서 좋은 방법이 될 수 있습니다.
이럴 경우 두 가지 문제가 발생합니다.
입력이 Train time에서 본 입력과 달라집니다.
모델에게 Train time에서 보지 못한 입력 값을 주게 되면 대게는 모델이 아무 기능을 하지 못합니다.
실제로 vocabularies가 아주 크다는 것입니다.
예제에서는 vocab이 4개 밖에 없었습니다([h,e,l,o]).
그러나 한 step마다 단어(word)를 생성하는 모델이라고 보면 voca 안에는 이 세상의 모든 영어 단어가 들어가야 합니다.
voca는 수만 개 이상의 요소를 가지게 될 수 있습니다.
실제로는 one hot vector을 sparse vector operation으로 처리합니다.
만약 10,000 dimension을 가진 softmax vector가 들어오면 연산량이 어마어마 할 것입니다. 그래서 test time에서도 one hot을 사용합니다.
그렇지 않습니다. RNN은 이전 hidden state를 계속해서 앞으로 가져가기 때문입니다.
hidden state를 조건으로 마르코비아 가정을 하고 있다면, hidden state가 미래를 예언하는 시퀀스의 우리가 필요로 하는 모든 것이기 때문에. 시간을 통한 역전파를 시켜야 합니다.
Truncated Backprop은 very large sequence data의 gradient를 근사시키는 방법입니다.
이렇게 요약된(encoded) 질문 벡터와 요약된 이미지를 어떻게 조합할 수 있는지에 대한 질문입니다.
가장 쉬운 방법은 하나의 concat으로 붙여서 FC-Layer의 입력으로 만드는 방법입니다.
가중치 W에 대한 Local gradient는 해당 스텝에 해당하는 현재의 cell/hidden state로부터 전달됩니다. vanilla RNN의 경우 각 스텝의 가중치 행렬 W들이 서로 영향을 미쳤습니다.
반면 LSTM의 경우에는 가령 아주 긴 시퀀스가 있고 그레디언트가 맨 끝에서부터 전달되기 시작된다고 했을 때, 이 시퀀스의 backprop 과정에서 각 스텝마다 w에 대한 locla gradient가 있을 것입니다.
W의 local gradient는 cell/hidden state로부터 흘러옵니다. LSTM의 경우 cell state가 그레디언트를 잘 전달해주기 때문에 w에 대한 local gradient도 훨씬 더 깔끔하게 전달됩니다.
그럴 지도 모릅니다. 가령 f의 경우 출력이 0~1이니 항상 1보다 작으므로 그레디어트가 점점 감소할 수 있습니다.
그래서 해결 방법으로 f의 biases를 양수로 초기화 시키는 방법이 있습니다. 이 방법을 이용해서 학습 초기에 forget gate의 값이 1에 가깝도록 해줍니다. 1에 가까운 값이기 때문에 적어도 학습 초기에는 그레디언트의 흐름이 비교적 원활합니다.
그리고 학습이 진행되면 f의 biases가 적절한 자기 자리를 찾아갑니다.
물론 LSTM에서도 vanishing gradient 문제가 발생할 수 있습니다. 하지만 vanilla RNN 보다 덜 민감한 이유는 매 스텝마다 f가 변하기 때문이고, 두 번째 이유는 LSTM에서는 Full Mat-mul이 아닌 element-wise multiplication을 수행하기 때문입니다.
Resnet의 Backward pass에서 identity connection이 아주 유용했습니다. Identity mapping이 ResNet 그레디언트를 위한 고속도로 역할을 했습니다.
LSTM도 동일한 intuition입니다. LSTM의 Cell state의 element-wise mult가 그레디언트를 위한 고속도로 역할을 하기 때문입니다.
오늘 내용을 요약하자면