본문 바로가기

Deep Learning(강의 및 책)/CS231

Lecture 10. Recurrent Neural Networks

728x90

이번 강의는 RNN에 대한 내용입니다.

 

위 사진을 보면 RNN 구조들이 나와 있습니다. 이 전까지 본 one to one과 같은 구조를 vanilla neural network라 합니다. input과 output 모두 1개인 경우입니다. one to many의 경우 input은 1개이지만 output은 가변 출력입니다. image caption과 같은 경우가 될 수 있고 caption에 따라 단어의 수가 천차만별이기 때문에 output은 가변 출력입니다. many to one 구조는 input이 가변 입력입니다. 문장 같은 경우가 input으로 들어가면 가변 입력입니다. 문장의 감정을 분류하는 모델의 경우 many to one과 같은 구조가 적절합니다. 마지막으로 many to many가 있습니다. computer vision task model을 예로 보겠습니다. video를 input으로 넣어주겠습니다. 비디오에 따라 전체 프레임 수는 다양합니다. 그래서 전체 video를 읽기 위해선 input은 가변 길이가 돼야 합니다. 비디오에 나타나는 activity 또는 action을 분류하는 model이라면 output도 가변 출력이어야 합니다. 문장 번역기도 input과 output 모두 가변 길이인 model입니다. 마지막 many to many의 경우 각 input마다 output이 있는 경우입니다. video 프레임마다 classification을 해야 하는 모델은 이와 같은 형태를 보일 것입니다.

 

일반적으로 RNN은 작은 Recurrent Core Cell을 가지고 있습니다. input x가 RNN으로 들어가고 RNN 내부에는 hidden state을 가지고 있습니다. hidden state은 RNN이 새로운 input을 불러들일 때 마다 매번 update 되는 값입니다. hidden state은 모델에 feedback 된 후에 다시 input x를 받습니다.

 

위 식으로 RNN 구조에 대한 설명을 할 수 있습니다. f는 재귀적인 관계를 연산할 수 있도록 구현되어 있는 함수입니다. x는 input이 되고 h_t-1은 이전 단계의 state이 됩니다. 출력으로 새로운 h_t가 나옵니다. RNN의 출력인 y도 가질 수 있는데 이러기 위해선 h_t를 input으로 하는 FC-Layer를 추가하면 됩니다. 이러한 모델은 f와 parameter W가 매 step마다 동일합니다.

 

이전 hidden state와 곱해지는 가중치 W_hh랑 현재 input x_t와 곱해지는 가중치 W_xh가 있습니다. 이렇게 두 입력(h, x)에 대한 행렬 곱 연산이 있고 두 결과 값을 더해줍니다. 그 다음 non-linearity를 위해 tanh를 적용합니다. output y를 얻기 위해 h_t와 다른 가중치 행렬 W_hy를 곱합니다. 

 

위 그래프를 통해 RNN 구조를 알아볼 수 있습니다. 가중치 W는 매 step마다 동일한 W가 들어갑니다. input x와 h_t는 계속 변경되지만 W는 계속 같은 것이 사용됩니다. 각 step마다 output y가 있고 이를 이용해 Loss들을 구할 수 있습니다. 최종 Loss는 loss들의 합이 되면 됩니다. RNN 모데의 backprop의 경우 W의 gradient는 각 step마다 구해지고 이 값들을 전부 계산해 모두 더하면 W에 대한 gradient가 됩니다.

 

이번에는 Many to One의 그래프입니다.

 

 

이건 one to many의 그래프입니다. 고정 길이 input x를 받고 출력은 가변 길이인 모습입니다.

 

many to one 과 one to many를 합친 구조도 존재합니다. 이 경우 sequence to sequence 문제를 해결하는 model이라 볼 수 있습니다. 가변 input과 가변 output을 가지는 모델이고 이는 두 개의 stage(encode, decode)로 볼 수 있습니다. encoder는 가변 input을 받는데 예를 들어 영어문장이 될 수 있습니다. encoder의 final hidden state를 통해 전체 sentence의 요약을 얻습니다. Decdoer는 one to many를 수행하는데 앞서 요약된 단일 vector를 input으로 받아 가변 출력을 뱉습니다. 영어문장을 받고 한글 문장으로 번역하는 경우 이렇게 encoder와 decoder의 구조로 구현할 수 있습니다. decoder에서 매 step마다 적절한 단어(output y_t)를 출력합니다. output sentence의 각 loss들을 합해 backprop을 진행합니다.

 

위 그림은 아주 간단한 예시입니다. vocabulary에 [h, e, l, o]가 있고 input sentence는 "hello"가 되고 각 글자들을 input으로 넣어줍니다. input에 사용되는 글자들은 vocabulary에 존재하는 글자 vector로 표현합니다. input으 hidden layer에 들어가 출력 y_t를 출력합니다. y_t는 그 다음 글자가 무엇일지 예측한 값이 됩니다. 첫 input "h"의 경우를 보겠습니다. output layer에서 점수가 가장 높은 값은 4.1로 이는 o를 의미합니다. 하지만 정답은 e입니다. 이렇게 오차를 이용해 점점 o가 아닌 e를 예측할 수 있도록 학습합니다.

 

 

지금까지 본 RNN의 모델에서 forward pass를 할 때 전체 sequence가 끝날 때까지 출력값이 생성됩니다. backward pass에서도 전체 sentence를 가지고 Loss를 계산해야 합니다. 만약 sentence가 엄청 긴 경우 이는 문제가 될 수 있습니다. wikipedia 전체 문서로 모델을 학습시킨다고 해보면 학습은 매우 느릴 것입니다. gradient 계산도 매우 오래 걸릴 것이고 문서 전체에 대한 gradient를 계산해야 gradient update가 1회 수행됩니다

 

이러한 문제를 해결하기 위해 truncated backpropagation이 등장합니다. train step을 100회 정도로 자릅니다. 100 step만 forward pass 진행하고 이 sub sequence의 loss를 계산합니다. 그리고 gradient step을 진행합니다. 이 과정을 반복해 전체 step을 계산합니다. 다만 이전 batch에서 계산한 hidden state은 계속 유지합니다. 그래야 다음 batch의 forward pass를 계산할 때 이전 hidden state를 이용할 수 있기 때문입니다. backprop도 같은 방식으로 batch만큼 잘라 진행합니다.

사실 사용자는 model에게 다음 등장할 글자가 무엇인지만 학습을 시켰습니다. 하지만 잘 학습된 RNN model은 학습과정 동안에 문장 데이터의 숨겨진 구조(작가의 스타일이 될 수 있습니다)를 알아서 학습합니다.

 

Image Caption의 경우 input이 image고 output은 자연어로 이루어진 caption입니다. 이 caption은 가변 길이이며 여기에 RNN Language model이 아주 잘 어울립니다. model에는 input image를 받아 처리를 위한 CNN이 존재합니다. CNN은 요약된 이미지 정보 vector를 만들어 내고 RNN에 input으로 넣어줍니다. RNN은 caption에 사용되는 문자들을 출력합니다.

 

위 그림을 보면 image caption 모델 구조를 볼 수 있습니다. 이 전까지 봤던 RNN 모델은 두 개의 가중치 행렬 곱을 input으로 받았습니다. 그리고 hidden state를 얻었는데 이제는 image 정보도 추가로 넣어줘야 합니다. 사람마다 모델의 이미지 정보를 추가하는 방식이 다르겠지만 가장 쉬운 방식은 위처럼 세 번째 가중치 행렬을 추가해주는 방법입니다. 다음 hidden state를 계산할 때마다 모든 step에 이 이미지 정보를 추가해주면 됩니다. 한 step에서 나온 단어를 그다음 step의 input으로 넣어줍니다. 이를 반복하면 caption이 완성됩니다.

 

model의 마지막에 <END>라는 special token이 나옵니다. 이는 문장의 끝을 알려주는 역할을 하고 이 token이 sampling되면 더 이상 단어를 생성하지 않으며 image에 대한 caption이 종료됩니다. train time에는 모든 caption의 종료지점에 token을 삽입합니다. 네트워크가 학습하는 동안에 sequence의 끝에 <END>라는 token이 있어야 한다는 것을 학습해야 하기 때문입니다. 학습이 끝나고 test time에는 모델이 문장 생성을 끝내면 token을 sampling 할 것입니다. 이 model을 학습시키기 위해서는 CNN과 RNN을 동시에 backprop 해야 합니다.

 

이번에는 image caption에 attention까지 이용한 구조입니다. forward pass에서 매 step마다 vocabulary를 만들 때 모델이 이미지에서 어디를 보고 sampling하는지에 대한 분포도 만들어냅니다. 이미지의 각 위치에 대한 분포는 train time에 모델이 어느 위치를 봐야 하는지에 대한 attention이라 할 수 있습니다. 첫 번째 hidden state는 이미지의 위치에 대한 분포를 계산합니다. 이 분포 a1을 다시 vecotr 집합(LxD)과 연산하여 이미지 attention(z1)을 수행합니다.

출력으로 a2, d1이 나오는데 d1은 단어 분포를 의미하고 a2는 이미지 위치에 대한 분포입니다.

 

 

학습을 마친 모델은 caption을 생성할 때 의미있는 부분에 attention 하는 것을 볼 수 있습니다.

 

vanilla RNN gradient flow를 보여준 모습입니다. backward pass시 h_t에 대한 loss의 미분 값을 얻습니다. 그다음 loss에 대한 h_t-1의 미분 값을 구하게 되는데 우선 gradient가 tanh gate를 타고 흘러갑니다. 그다음 mat mul gate를 통과하는데 이 gate의 backprop은 transpose(가중치 행렬)을 곱하게 되는데 이는 매번 진행됩니다.

 

RNN의 특성상 여러 sequence의 cell을 쌓아 올립니다. 이 경우 h_0에 대한 gradient를 구하려면 모든 RNN cell을 거쳐야 합니다. cell 하나를 통과할 때마다 cell의 행렬 W transpose factor가 관여합니다. h_0의 gradient를 계산하는 식을 써보면 아주 많은 가중치 행렬들이 개입하게 되고 그 결과 연산량은 상당히 많아지고 1보다 큰 값은 계속 커지고 1보다 작은 값은 계속 작아져 0이 됩니다. 이를 해결하기 위해 gradient clipping을 사용합니다. gradient가 L2 norm의 임계값보다 큰 경우 gradient가 최대 임계값을 넘지 못하도록 조정하는 방법입니다. 그다지 좋은 방법은 아니며 이를 해결하는 새로운 RNN 구조가 등장합니다.

 

LSTM(Long Short Term Memory)이라는 모델인데 RNN을 더 발전시킨 구조입니다. RNN의 경우 hidden state가 step마다 update됩니다. LSTM에는 한 cell 당 두 개의 hidden state가 있습니다. h_t와 cell state인 c_t인데 c_t는 LSTM 내부에만 존재하고 외부에 노출되지 않는 변수입니다. LSTM도 input으로 h_t-1, x_t를 받습니다. 그리고 4개의 gate를 계산합니다. i, f, o, g가 gate들이고 cell state update에 사용됩니다. 그리고 c_t로 다음 step의 hidden state를 update 합니다.

 

LSTM에 들어온 H_t-1, x_t를 쌓아놓습니다. 그리고 네 개의 gate의 값을 계산하기 위한 커다란 가중치 행렬을 곱합니다. 각 gate출력은 hidden state의 크기와 동일하고 gate 중 i는 input gate, i는 cell에서의 x_t에 대한 가중치, F는 forget gate이고 이전 step의 cell의 정보를 얼마나 forget 할 지에 대한 가중치, O는 c_t를 얼마나 밖에 드러내 보일지에 대한 가중치입니다. 각 gate에서 사용하는 non linearity는 다양하며 여기선 i, f, o에서 sigmoid를 이용하고 g는 tanh를 사용합니다. g는 -1에서 1 사이의 값을 출력할 것이고 다른 gate들은 0에서 1 사이의 값을 출력합니다. c_t는 forget gate의 element-wise multiplication 합니다. 결과는 0에서 1 사이의 값이 나옵니다. forget gate가 0에 가까울수록 이전 cell state를 잊습니다. 반면 1에 가까울수록 cell state를 잘 기억합니다. i와 g를 element-wise multiplication 합니다. i의 경우 input을 잘 기억하고 싶으면 1이 나오고 아니면 0에 가까운 값이 나옵니다. gate는 tanh이므로 -1에서 1 사이의 값을 출력하는데 이 값과 곱해지기 때문에 c_t가 감소되기도 합니다. 마지막 h_t는 output gate와 연산을 통해 얼마나 output으로 내보낼지 결정됩니다.

 

먼저 x_t와 h_t-1이 input으로 들어와 쌓고 가중치 W와 곱해 4개의 gate를 만듭니다. 각 gate들은 각자 위치에 맞게 연산이 진행됩니다. 모든 연산을 거친 후 c_t와 h_t가 나옵니다.

 

이런 LSTM의 backprop의 경우 gradient의 flow를 보여준 그림입니다. LSTM은 addition operation의 backprop을 진행합니다. addition은 그저 두 갈래로 gradient가 복사됩니다. 따라서 gradient는 upstream gradient와 forget gate의 element wise 곱이 됩니다. cell state의 backprop은 매우 간단합니다. full maxtix multiplication이 아니라 element wise multiplication이기 때문에 연산량은 더 적습니다. 매 step마다 다른 값의 forget gate가 곱해지고 forget gate는 0에서 1 사이의 값이기 때문에 더 좋은 수치적 특성을 보일 수 있습니다. W에 대한 local gradient는 cell/hidden state를 통해 전달받는데 cell state자체가 매우 잘 전달되기 때문에 W에 대한 gradient도 잘 전달됩니다.

사실 gorgaet gate에 sigmoid를 쓰기 때문에 vanishing문제는 여전하지 않나??

그럴 수도 있습니다. 그래서 사람들은 forget gate의 bias를 양수로 초기화합니다. 이렇게 하면 학습 초기에 forget gate의 값이 1에 가깝도록 됩니다. 1에 가까운 값이기 때문에 적어도 초기 학습에서는 gradient의 흐름이 원활합니다. 학습이 진행되면 forget gate의 bias가 적절한 자리를 다시 찾아갑니다. LSTM에도 vanishing이 존재하지만 Full matmul이 아닌 element wise multiplication이기 때문에 덜 심합니다.