본문 바로가기

수업 정리(개인용)/cs231n

CS231n Lecture 10 : Recurrent Neural Networks

지난 시간에 배웠던 네트워크들을 간단하게 리뷰하고 넘어간다. VGG나 GoogLeNet은 2014년 Batch Normalization이 나오기 전의 모델이다. 이런 깊은 망을 학습시키는 것은 굉장히 어려웠고, VGG는 첫 11 layer들을 먼저 학습시킨 뒤 쌓아가는 식으로 학습했다. GoogLeNet의 auxiliary classifier도 실제로 classification을 위해 붙인게 아니고 gradient vanishing 때문이었다고 한다.

 

ResNet에서는 L2 regularization을 적용해서 weight 값들을 0으로 가게 유도했다고 한다. 첫 아이디어 자체가 이미 잘되는 shallow한 모델 뒤에 identity mapping layer를 쌓으면 적어도 성능이 떨어지지는 않아야 한다는 거였기 때문에, identity mapping을 믿고 그 뒤에 붙은 layer는 weight를 줄여서, 큰 변화는 못 일으키게 하고 residual만 학습하게 하는 것이다.

 

RNN의 다양한 방식과 예시이다.

 

Non-sequence data에도 RNN을 적용해서 좋은 결과를 낸 사례. Classification도 이런 식으로 할 수 있다. 모델이 이미지의 전체를 보는 게 아니라 부분 부분을 sequential하게 본다.

전형적인 RNN term이다.

Output y1...yT에서 각각의 loss를 구한 후 sum해서 최종 loss가 된다. 이 경우는 각 timestep 별로 label이 존재하는 경우이다.

Character-level에서 one hot encoding을 4 dimension으로 한 후 RNN에 넣은 모습이다. Hidden state는 3 dimension이고, output인 y는 input과 demension이 같다. Language modeling에서는 다음 글자를 예측하게 학습하는데, y1에서 4.1이 제일 높으므로 o를 예측했지만 label은 e이므로 softmax loss가 벌을 줄 것임을 알 수 있다. 이어서 e가 들어가고 l을 예측하도록 학습하게 된다. 이 과정에서 모델은 지난 context를 바탕으로 다음에 올 letter를 예측하게 된다. Appl 다음에 e가 오고, ca 다음에 r가 온다는 패턴을 학습하게 되는 것이다.

Test time에서의 generation 과정이다. 여기서는 자신의 이전 output을 그대로 다음 input으로 넣어준다. 이 때, output vector를 argmax해서 넣어줄 수도 있지만, softmax 확률을 바탕으로 randomly sample해서 주면 diversity가 늘어난다.

 

끝까지 forward 한 후 처음으로 다시 backward 하면 너무 느리고 비효율적이므로, truncated backprop을 한다. 대략 한 100번째 문자까지 학습하고 나면 거기서 backprop을 한 번 하고, 기억해둔 101번째 hidden state를 주면서 다시 진행을 하는 식이다. 이건 마치 데이터셋이 너무 크면 mini batch를 사용하는 것과 같은 맥락이다.

 

많은 분들이 애를 먹고 궁금했을 것 같은 부분이다. 여기서 justin이 설명을 약간 오해할 수 있게 해서 그랬을 지도 모른다. Output vector의 한 element를 뽑아서, sequence를 RNN에 태우는 동안 그 scalar element의 magnitude를 색으로 표시했다고 하는데.

 

자칫 위와 같이 잘못 이해할 수 있다. 조금만 생각해보면 우리가 output vector에서 character를 뽑아서 다음 input으로 줄 때 softmax probability를 바탕으로 글자를 추출하는데, 그 element의 activation을 색으로 표시했다는 말이다. 위의 잘못된 예시와 같이 하나의 element를 정해놓고 계속 그 element의 activation을 tracking 하는 것은 의미도 없고 말이 되지 않는다. 선택되지 않은 element는 output으로 출력이 되지도 않았는데 어떻게 글자에 색을 입혀서 보여줄 수 있겠는가? Output이 하나의 문자로만 되어있다면 몰라도. 위의 슬라이드를 보면 문장이 /*  Unpack 으로 시작하는데, 여기 나온 공백 포함 9글자가 모두 서로 다르다. 즉 softmax에서 매 번 다른 문자를 추출한 것이다. 그리고 추출된 그 각각의 element의 magnitude를 색으로 입혀 표시한 것이다.

 

사실 보는 내내 궁금했던 게, 단순히 다음 글자를 예측하게 할 뿐인데 어떻게 rnn이 코딩을 하고, 수학적 증명을 하고, 극 대본을 쓴단 말인가(흉내만 내는 것이지만)? 이 분들도 그게 궁금해서 이런 실험을 한 것이다. 각 timestep별로 hidden state들의 activation을 색으로 표시해서 보면, timestep 초반에는 low level modeling을 하는 것처럼 보인다.

 

다시 한 번 찬찬히 살펴보자. / 이후에 *이 나올 때, activation이 높다. 즉 softmax 값이 높고, 모델이 / 다음에는 *가 온다는 예측을 할 때 confidency가 높다는 말이다. 왜냐하면 모델이 저 코드 데이터를 학습할 때, / 다음에 *가 오는 패턴을 무수히 많이 봤기 때문일 것이라고 추측할 수 있다(주석은 항상 저렇게 시작하니까). 그리고 U 다음에 n이 오는 것도 자신있게 예측하지만, Un 다음에 뭐가 올지는 확신하지 못하는 모습이다. 사실 un은 접두사이기 때문에 많은 단어가 올 수 있어서 사람도 저 뒤에 무슨 글자가 올지는 예측하기 힘들다. 하지만 Unp까지 보면 a는 다시 쉽게 예측하고, 다시 c를 뱉을 때에는 confidence가 떨어진다. 지금은 전체적인 context를 보기 보다는 다음에 무슨 글자가 올지만 보고 있는 것 같다. Low level의 language modeling이라고 할 수 있다.

여기서는 인용구를 인식하고 있는 모습이다.

 

여기서는 line length를 tracking하는 듯 보인다. 한 line의 끝에 갈 수록 confidence가 올라가다가 개행을 한다. 이 쯤 되면 개행을 할 때가 됐다는 확신을 모델이 느끼는 것이라고 할 수 있다. 

 

이렇게 조건문을 인식하기도 하고(if문 뒤의 괄호에서는 어떤 전형적인 패턴이 있기 마련이다)

Indent를 인식하기도 한다. 이 데이터셋에서는 indent를 파고 들어가다 보면 return을 많이 했었나보다.

 

마치 CNN의 filter를 reconstruction해서 보듯이 RNN을 까서 보니까 어떤 식으로 문장을 생성하는지 감은 오는 것 같다. 단순히 다음 문자를 맞추기 위해 학습을 하지만, 다음 문자를 잘 맞추기 위해서는 context 정보를 잘 활용해야 하고, context 정보는 hidden state에 다 누적이 되어 있으니 학습을 계속 하면 마법처럼 잘 된다는 것은 알겠다. 하지만 아직도 궁금한 것은 어떻게 이 하나의 rnn cell이 이렇게 복잡한 패턴을 학습할 수 있는 것일까? 더 공부해볼 문제다.

Image captioning에서는 rnn의 input에 image 정보가 추가되어 총 3개가 된다. Input word x, 이전 cell의 hidden state, 그리고 이미지 정보까지 해서 weight도 3개가 존재하게 된다. CNN의 마지막 feature가 첫 rnn cell에 들어오고, start token을 받게 되면 그 다음부터는 위에서 본 text generation과 비슷하다. 마지막에 end token을 뱉고 생성을 종료한다.

 

이번에는 CNN이 이미지 전체에 대한 summary가 아니라, 이미지의 각 patch에 대한 summary를 제공한다. RNN cell은 vocab의 distribution과 더불어 새로운 a라는 output을 뱉는다. a는 이미지의 위치 정보의 distribution이다. d벡터에서 softmax probability를 바탕으로 글자를 추출하듯이, a벡터에서 위치를 추출해서, 그에 해당하는 patch의 CNN feature map이 다음 RNN cell의 input으로 들어간다. 즉 모델의 input으로 이미지 전체의 feature map이 아닌 한 부분의 feature map이 들어가고, 어느 부분을 집중해서 볼지(=attention)를 모델이 직접 선택하게 하는 것이다.

 

 

모델이 이미지의 각기 다른 부분에 attention을 주고 있는 모습이다. Soft attention에서는 이미지의 위치를 하나만 뽑기보단 weighted sum해서 결국은 전체를 다 이용하고(대신 어떤 부분에 weight를 크게 줘서 더 집중해서 볼 것이다), hard attention은 말 그대로 위에서 설명했듯이 하나의 patch만 주는 것이다. 이렇게 하면 미분이 불가능해져서 일반적인 backprop보다 fancier한 방법이 필요하다고 한다.

 

사진을 보고 질문에 답하는 문제이다. 이미지 feature map과 question embedding이 rnn의 input이다. 위에서 살펴본 RNN의 형태 중 many to one이라고 할 수 있다. 여기에도 attention을 적용할 수 있다.

 

이처럼 RNN을 여러 층 쌓는 것도 가능하고, 보통 아무리 많이 쌓아도 4층 정도라고 한다.

 

 

실제로 RNN 내부를 보면, 두 weight matrix와 두 벡터가 각각 stack돼서 하나의 행렬 곱으로 계산된다.

RNN의 단점. Hidden state는 매 번 같은 W와 곱해진다. 이게 왜 문제가 되는지 간단하게 살펴보면

 

이렇게 생각해보자. 직관적으로 문제를 파악하기 위해 우선 tanh를 제외하고 보면, 결국 t번 째 h를 만들기 위해서는 h0에 같은 weight matrix가 t번 곱해져야 한다. Backprop할 때 h0의 gradient를 구하려면, h1의 gradient와 W_hh의 transpose와의 행렬 곱이다. 그런데 그 h1의 gradient는 h2의 gradient와 W_hh의 transpose와의 행렬 곱이다. 이렇게 h0의 gradient는 하나의 행렬이 계속 곱해진 형태로 나오게 된다. Scalar로 생각해보면 같은 수를 백 번 이렇게 곱하면 1이 아닌 이상 발산하거나 0에 수렴하게 된다. 그래서 gradient clipping도 써보고 했지만, 결국은 더 진보된 LSTM이라는 모델로 넘어가야 한다.

 

x와 h가 concatenate 되어서 커다란 W와 곱해진다. W는 4개의 partition으로 구분할 수 있는데, 각각이 i,f,o,g를 만들게 된다. i,f,o,g,는 sigmoid나 tanh를 거친다. 각각의 역할은 위에 표시해 두었다. LSTM에서는 h 이외에 cell state라는 state가 추가되었고, cell state에서 일부분만 밖으로 보여주게 된다. 

 

Cell state는 f와 element wise로 곱해진 뒤에 덧셈 연산을 한번 하고 그대로 넘겨진다(사실은 cell 내부에 가지고 있다). 여기에는 3가지 장점이 있다.

 

1. 행렬 곱이 아니라 원소 곱이므로 좋다(왜?).

2. c와 곱해지는 f는 매 번 다른 값이다. 우리가 커다란 W와 [x,h]와의 행렬 곱에서 f를 추출했던 것을 기억하자. 그리고 RNN의 문제가 매 번 같은 W와 곱해진다는 것이었음을 기억하자.

3. 그 f는 sigmoid의 output이므로 exploding 문제도 해결할 수 있다.

 

추가적으로, hidden state를 backprop 할 때를 생각해보면, RNN에서는 tanh를 계속 거쳐서 계산된 것이므로 역전파 시에도 tanh를 거꾸로 계속 통과해야 했다. 여기서는 다르다. Cell state는 저렇게 원소 곱과 덧셈 게이트만 통과하다가, 마지막 h_t를 계산해서 그걸로 loss를 구한다. 역전파 시에도 h_t를 만들 때 통과한 tanh를 한 번만 거꾸로 통과하고 나면, gradient highway를 통해서 쭉 쭉 넘어오므로 vanishing이 없다.

 

요약하면 cell state의 gradient를 nice하게 구할 수 있어서 좋다.

 

저 그림을 보고 있으면, c가 마치 skip connection으로 다음 timstep과 연결되어 있는 것 처럼 보이는데, 이건 ResNet의 idea와 유사해 보인다. 이렇게 깊은 RNN을 학습시키는 것과 깊은 CNN을 학습시키는 것에서 오버랩이 많이 일어난다고 한다.

 

Summary이다. Google에서 이런 저런 방법을 다 시도해봤는데, GRU나 LSTM보다 significant하게 좋은 방법은 없었다고 한다. 깊은 모델에서 additive interaction으로 gradient flow를 뚫어주는건 어디서나 볼 수 있는 idea인 것 같다. RNN은 super useful하다. 비록 요즘에는 transformer를 더 많이 쓰는 것 같지만.

 

지난 10개의 강의중에서 손에 꼽을 정도로 어려운 강의였다.