통상의 피드포워드네트워크는 each words vectors들의 순서(sequence 정보)를 반영하기 어렵습니다. 통상 한 도큐먼트의 squence에 해당하는 워드벡터 정보를 average하는 정도로 구현되는 것이 최선입니다.
이로 인해 "Dog bites man"과 "Man bites dog"라는 명백히 다른 문장은 동일한 벡터로 표현되고 같은 의미로 해석되어 뉴럴넷을 타게 됩니다.
이런 문제를 해결하기 위해 순서정보를 반영하는 RNN이 등장합니다.
RNN의 기본 컨셉은 간단합니다.
뉴럴넷의 각 셀(Unit)들은 통상적인 Feed-forward Net에서는 이전 셀들의 output만을 입력으로 받았지만 RNN에서는 이전셀의 output및 자기 자신의 이전 상태를 입력으로, 즉 2가지 입력을 받아 냅니다.
구체적으로는 자신의 이전 time step에 대한 output입니다.
하나의 은닉층을 가지는 simple RNN 예시를 통해 개념을 이해해봅시다.
다음과 같은 document가 입력되었습니다.
"The dog looked guilty"
이 document는 "The", "dog", "looked", "guilty"로 이루어진 하나의 시퀀스가 되어 뉴럴넷으로 입력됩니다.
먼저 squence의 첫번째 워드인 "The"가 임베딩 레이어를 거쳐 특정 벡터 값 으로 입력되면 hidden layer에서는 이를 에 자체적인 weight을 입혀 으로 출력하게 됩니다. 이때 은 다음 레이어(출력층)으로 전달함과 동시에 동일 유닛의 다음 타임스텝으로도 동시에 전달합니다.
이후 두번째 "dog"가 입력되면() 이는 은닉층에서 이전 타임 스텝의 은닉층 출력 값인 과 이번 타임스텝의 시퀀스 입력 값 을 동시에 입력으로 받아들이고 다시 다음 타입스텝의 은닉층과 자신의 타임스텝인 출력층으로 출력값을 전달합니다.
이렇게 반복하여 마지막 타임스텝에서는 "guilty"라는 입력값 ()와 이전 은닉층 출력값 를 바탕으로 계산하여 output layer로 출력합니다. 그리고 output layer를 거쳐 모든 시퀀스의 정보를 담고 있는 최종 가 출력되게 됩니다.
여기서 RNN의 형태에 따라 각 타임스텝마다 출력층으로도 전달된 은닉층의 출력값이 계산되어 의 값도 출력하는 경우도 있습니다.
수식을 이용해 표현해보겠습니다.
입력층을 지난 임베딩 벡터는 , 은닉층 출력 값은 , 출력층 출력값은 로 하고 은닉층 유닛들의 weight 매트릭스 , 출력층 유닛들의 weight 매트릭스 라고 하겠습니다. 앞선 weight 매트릭스는 피드포워드 포스트에서 다루었듯 "입력 레이어의 유닛" x "출력 레이어의 유닛"으로 dimension이 정해지나 선형변환의 형태로 표기하기 위해서 입력 벡터를 열벡터로 표시하고 매트릭스를 앞에 곱해주면서 "출력 레이어의 유닛" x "입력 레이어의 유닛" 형태로 트랜스포즈를 취해서 표기하는 편이 더 많습니다. 이번 포스트에서는 그렇게 사용해보도록 하겠습니다.
이렇게 는 x 차원을 지닌 매트릭스, 는 x 차원을 지닌 매트릭스가 됩니다.
여기에 추가로 RNN은 이전 타임스텝의 은닉층 출력 에서 현재 타임스텝의 은닉층으로 입력될 경우의 weight들을 표현하는 매트릭스가 추가로 필요합니다. 이는 x 크기를 가질 겁니다.
먼저 출력층 출력값 부터 살펴 봅시다.
각 타입스텝의 최종 출력층 출력값 는 은닉층의 출력값인 을 자신의 weight vector 집합 와 매트릭스곱을 진행하여 activation function 를 거쳐 최종 도출됩니다.
여기서 는 자신의 출력층 유닛의 개수(차원)()을 row로 이전 은닉층 유닛의 개수 ()을 col로 가지는 매트릭스가 되도록 Transpose를 취해줍니다.
그래야 x 의 차원을 가지는 열벡터가 x 의 차원을 가진 로 선형변환되어 x 이 차원을 가진 출력층 출력값 가 도출될 수 있기 때문입니다.
이를 수식으로 표현하면 다음과 같습니다. hat이 붙은 이유는 역전파를 수행할 예측치이기 때문입니다.
은닉층 출력값은 어떨까요?
이전 타임스텝의 출력값 에 weight 를 곱하고, 입력값 에 weight 를 곱한 수식의 합으로 구성됩니다.
물론 별도의 활성함수가 취해져야하므로 이를 라 하면 다음과 같은 수식으로 나타납니다.
RNN에서 중요한 점 중 하나는 각 타임스텝을 거치더라도 weight matrix , , 는 공유해서 사용한다는 점입니다.
RNN에서의 역전파(backpropagation)을 계산하는 방법은 기존 FNN(Feedforward Neural Network)과 약간 다릅니다. 기존 FNN에서는 마지막 출력값의 실제 라벨값과의 오차를 기반으로 역전파를 계산했다면 RNN에서는 각 타임스텝별 출력값 들의 각각의 loss를 라고 먼저 계산하고 평균값을 다음과 같이 최종 loss로 표현합니다. 은 타임스텝의 크기입니다.
업데이트되어야할 weight matrix는 simple RNN을 기준으로 , , 세가지 이므로 다음과 같은 세가지 gradient를 구해야 합니다.
먼저 출력층 부터 살펴봅시다. 출력층은 recurrent한 파트와 독립적으로 구성되기에 간단하게 계산됩니다.
각 타임스텝별 오차의 gradient는 모두 에 영향을 미치기 때문에 합으로 구해져야합니다.
이후 하나의 는 FNN처럼 다음과 같이 chain rule을 통해 풀어서 표현할 수 있습니다. 여기서 는 를 의미합니다.
다음은 이전 타임스텝에서 현재의 타임스텝으로 를 전달할 경우의 weight matrix인 입니다. 타임스텝별로 각각 gradient를 계산하고 합하는 형태는 동일합니다.
이후 형태는 이전과 유사합니다.
다만 문제는 수식에서 부분입니다.
는 이전 타임스텝 에도 영향을 받기때문에 이를 고려해야 합니다.
수식을 다시 살펴보면 다음과 같았습니다.
이에 gradient는 다음과 같이 이전 단계의 변화량을 고려해야합니다.
이는 모든 타임스텝에서 동일한 weight matrix 를 공유하기 때문에 사용할 수 있는 방법입니다.
그리고 는 또한 그 전 타임스텝 의 변화량에 영향을 받습니다. 그리고 또한 변화량에 영향을 받아 출력된 것이죠.
다음과 같이 재귀적으로 최초의 타임스텝까지 거슬러 올라가게 됩니다.
이 때문에 RNN의 역전파 이름이 BPTT(Backpropagation through time)이 된 것입니다.
마지막으로 역시 모든 타임스텝에 공유되며 Recurrent 과정에 영향을 미치므로 와 동일하게 타임스텝을 거슬러 올라가는 방식으로 표현됩니다.
[1] nlpdemystified, https://www.nlpdemystified.org/