[자연어 처리의 모든 것] 2. 자연어 처리와 딥러닝
1. Recurrent Neural Network (RNN)
이전 강의에서 자연어 처리 필수 개념인 '워드 임베딩(Word Embedding)'를 학습했습니다. 이번 시간에는 본격적으로 자연어 처리를 위한 '딥러닝 모델'을 알아봅니다. 가장 대표적인 딥러닝 모델인 RNN은 언어의 Seqeuntial한 특징을 잘 학습하기 위해 등장했습니다. RNN은 어떠한 특징과 종류를 가지고 있는지 강의를 통해 확인해봅시다.
RNN(Recurrent Neural Network)
- RNN은 현재 타임스텝에 대해 이전 스텝까지의 정보를 기반으로 예측값을 산출하는 구조의 딥러닝 모델입니다.
- 매 타임스텝마다 동일한 파라미터를 가진 모듈을 사용하므로, '재귀적인 호출'의 특성을 보여주어 'Recurrent Neural Network'라는 이름을 가지게 되었습니다.
RNN은 sequence 데이터가 입력으로 혹은 출력으로 주어진 상황에서 각 time step에서 들어오는 입력벡터 x_t와
그 전 time step의 RNN 모듈에서 계산한 hidden state 벡터 h_(t-1)을 입력으로 받아서 현재 time step에서의 h_t를 출력으로 내어주는 구조를 가집니다. 입력이 I study math라는 문장일 때 각 워드는 각 time step의 입력으로 순차적으로 들어가게 됩니다. 매 time step마다 RNN모듈이 그 전 time step까지 계산해둔 hidden state 벡터 h_(t-1)과 현재 들어온 워드 정보 h_t를 결합해서 h_t를 계산합니다.
여기서 중요한 사실은 서로 다른 time step에서 들어오는 입력 데이터를 처리할 때 동일한 파라미터를 가지는 Recurrent neural net 모듈을 동일하게 사용한다는 것입니다. 그림에서 보면 A가 매 time step마다 재귀적으로 호출되면서 A모듈의 출력이 다음 time step의 입력으로 들어가는 형태로 이해할 수 있습니다. 왼쪽의 도식화된 그림은 Rolled 버전의 RNN의 다이어그램입니다. 오른쪽 그림은 Unrolled 버전의 다이어그램입니다.
RNN 계산 방법
- t : 현재 타임스텝(time step) , w : 웨이트(weight)
- h_t-1 : old hidden-state vector
- x_t : input vector at some time step
- h_t : new hidden-state vector
- f_w : RNN function with parameters W
- y_t : output vector at time step t
- 위의 변수들에 대하여, h_t = f_w(h_t-1,x_t) 의 함수를 통해 매 타임스텝마다 hidden state를 다시 구해준다.
- 이 때, W와 입력값( x_t, h_t-1 )으로 tan_h 를 곱해서 h_t 를 구해준다.
- 구해진 h_t, x_t 를 입력으로 y_t 값을 산출하게 된다.
hidden state 벡터는 다음 타임 스텝의 입력으로 쓰임과 동시에 출력값을 계산하는데도 쓰입니다. 아웃풋 y_t는 매 타임 스텝마다 계산할 수도 있고 마지막 타임 스텝에만 계산할 수도 있습니다.
x_t가 3차원이고 h_t-1이 2차원 벡터라고 가정하겠습니다. 여기서 hidden state 벡터의 차원수는 우리가 정의해야하는 하이퍼파라미터입니다. f_w는 RNN모듈이고 이를 생각해보겠습니다. h_t도 h_t-1과 동일한 dimension을 공유해야하기 때문에 이 예제에서는 2여야 합니다. 그 이후에는 h_t를 계산한 후 non-linear인 tanh를 거쳐 계산합니다.
여기서 fully connected layer의 linear transformation matrix를 W라고 나타내면 이처럼 나타낼 수 있습니다. x_t와 h_t-1이 있을때 이 5 dimensional vector를 선형 변환을 통해 나온 값이 h_t라고 할때 행렬 W는 사이즈가 2 * 5로 정의됩니다.
5차원 벡터간의 내적을 보면 빨간색끼리, 초록색끼리 따로 내적해준 후 두개 내적값을 더해주면 h_t의 첫번째 dimension의 값이 됩니다. 앞의 행렬을 w_xh, 뒤의 행렬을 w_hh라고 표시해보겠습니다. 그러면 h_t에 대한 수식은 w_xh*x_t + w_hh*h_t-1 이 됩니다. 이는 h_t를 계산할 때 현재 타임 스텝에서 들어온 x_t를 w_xh와 결합해서 h_t의 일부를 만들고, 두번째 입력이었던 h_t-1을 w_hh와 곱해서 h_t의 일부를 만든다는 것을 알 수 있습니다. 여기서 w_xh는 w_xt를 h_t로 변환해주고 w_hh는 w_h_t-1에서 h_t로 변환합니다.
이렇게 구해진 h_t를 tanh를 통과시켜 최종 hidden state 벡터 h_t를 계산할 수 있습니다. 이 때 task의 예측값이 필요한 경우에는 h_t를 입력으로 해서 추가적인 layer인 output layer를 만들고 여기에 linear transformation matrix은 w_hy를 곱해서 최종 output인 y_t를 구할 수 있습니다. 여기서 w_hy도 w_ht를 y_t로 변환한다고 볼 수 있습니다.
이 output 벡터는 만약 binary classification을 수행한다면 차원은 1차원인 스칼라 값이 되고 여기에 sigmoid를 적용해 확률값을 예측값으로 적용합니다. multi-class classification을 수행한다면 y_t가 클래스 수만큼의 차원을 가지는 벡터가 되고 여기에 softmax를 적용해 분류하고자하는 클래스와 동일한 개수 만큼의 확률 분포를 얻게됩니다.
다양한 타입의 RNN 모델
실제 활용 예시를 통해 다양한 타입의 RNN 모델을 구분해봅시다.
1 | one to one | [키, 몸무게, 나이]와 같은 정보를 입력값으로 할 때, 이를 통해 저혈압/고혈압인지 분류하는 형태의 태스크 |
2 | one to many | '이미지 캡셔닝'과 같이 하나의 이미지를 입력값으로 주면 설명글을 생성하는 태스크 |
3 | many to one | 감성 분석과 같이 문장을 넣으면 긍/부정 중 하나의 레이블로 분류하는 태스크 |
4 | many to many | 기계 번역과 같이 입력값을 끝까지 다 읽은 후, 번역된 문장을 출력해주는 태스크 |
5 | many to many | 비디오 분류와 같이 영상의 프레임 레벨에서 예측하는 태스크 혹은 각 단어의 품사에 대해 태깅하는 POS와 같은 태스크 |
2. Character-level Language Model
자연어 처리의 최신 트렌드를 알아보면, '거대언어모델'이라는 표현을 접하게 됩니다.
오늘은 이때 사용되는 "언어 모델"에 대해서 학습합니다. 그 중에서도 앞 강의에서 배운 RNN을 활용한 언어 모델에 대해서 배우고, 그 예시들을 살펴봅니다.
character-level Language Model
- 언어 모델이란 이전에 등장한 문자열을 기반으로 다음 단어를 예측하는 태스크를 말합니다. 그중에서도 캐릭터 레벨 언어 모델(character-level Language Model)은 문자 단위로 다음에 올 문자를 예측하는 언어 모델입니다.
- 예를 들어, 그림과 같이 맨 처음에 "h"가 주어지면 "e"를 예측하고, "e"가 주어지면 "l"을 예측하고, "l"이 주어지면 다음 "l"을 예측하도록 hidden state가 학습돼야합니다.
- 이때 각 타임스텝별로 output layer를 통해 차원이 4(유니크한 문자의 개수) 벡터를 출력해주는데 이를 logit이라고 부르며, softmax layer를 통과시키면 원-핫 벡터 형태의 출력값이 나오게 됩니다.
예제에서는 하나의 단어인 hello가 주어집니다. 첫번째로 할 일은 사전을 구축하는 것입니다. 중복없이 모으면 사전을 구축할 수 있습니다. 이 다음 각각의 단어는 총 사전의 개수만큼의 dimension을 가지는 원핫벡터로 나타낼 수있습니다.
이를 가지고 언어모델링을 수행하는 경우에는 h e l l 순으로 sequence가 주어지게 되면 첫번째 타임스텝에서 h가 주어졌을 때 e를 예측하고, h와 e가 주어지면 l을 예측하고, h와 e와 l이 주어지면 l을 예측해야 합니다.
h, e, l, l이 원핫벡터 형태로 입력으로 주어지게 되면 RNN 모듈은 매 타임스텝에서 들어오는 입력 벡터와 전 타임스텝에서 주어지는 h_t-1을 선형결합해 얻어지는 h_t를 만들어내게 됩니다. 여기서 h_t-1과 h_t의 dimension을 3이라고 하면 hidden state 벡터는 h_t로 변환하는 선형변환들과 곱하고 추가적으로 b까지 고려하면 기본적인 layer가 됩니다. 여기에 비선형 변환인 tanh를 통과한 후 h_t를 얻어낼 수 있습니다.
h_0는 모두 0인 벡터이고 계산을 수행한 후 h_1, h_2, h_3가 차례로 계산됩니다. 이 때 h_2를 h_3로 만드는 과정에서 w_hh와 w_xh가 관여됨을 알 수 있습니다.
각 타임스텝마다 character를 예측하므로 many-to-many task에 해당하고 output 벡터를 계산하기 위해서 해당 타임스텝에서 구해진 h_t를 output lyaer을 적용해 최종 output을 얻어내게 되는데 output layer에서 적용된 선형변환의 파라미터를 w_hy 라고 한다면 h_t를 w_hy와 곱한 후 b를 고려해 output 벡터를 얻어내게 됩니다.
output layer은 사전에 정의된 크기와 동일한 4차원이 되고 이 output 벡터를 multi-class classification을 수행하기 위해 softmax 층을 통과시키게 되면 해당 확률값이 가장 큰 값으로 나오게 됨을 알 수 있습니다. 여기서 o값이 4.1로 가장 높기 때문에 o로 분류하게되지만 ground truth는 e이기 때문에 두번째 값을 높이도록 학습이 진행되어야 합니다. 따라서 [0,1,0,0] 과 여기서 나온 softmax 벡터가 최대한 가까워지도록 하는 softmax loss를 적용해서 학습하게 됩니다.
w_xh, w_hh, w_hy가 backpropagation에 의해 학습이 진행되고 세번째와 네번째 타임스텝의 입력이 동일하게 l인 상황에서 세번째 타임스텝에서는 l을 예측하고 네번째 타임스텝에서는 o를 예측해야 합니다. RNN이 이러한 task를 수행하기 위해서는 현재 타임스텝의 입력뿐만아니라 전 타임스텝에서 넘어오는 hidden state 정보인 h_t-1 을 이용해야 합니다.
학습을 끝낸 후 추론을 수행할때는 첫번째 문자 h만을 주고 해당 타임스텝에서의 예측값을 얻어낸 후 다음 타입스텝으로의 입력으로 넣어줘 예측값을 얻어내는 과정을 반복합니다.
다양한 언어모델의 예시
- 셰익스피어의 글을 활용해 더 많은 학습이 진행될수록 완전한 형태의 문장을 출력하는 것을 확인할 수 있습니다.
- 전 타임스텝까지의 주식값을 활용하여 다음날 주식값을 예측하는 형태로도 수행할 수 있습니다.
- 그 외에도 인물 별 대사, Latex로 쓰여진 논문, C 프로그래밍 언어와 같은 경우에도 다양한 언어적 특성을 학습하여 텍스트를 생성할 수도 있습니다.
공백이나 쉼표, 줄바꿈의 경우에도 dimension으로 봅니다.
3. Backpropagation through time and Long-Term-Dependency
지난 시간까지 배웠던 RNN은 자연어 처리에서 널리 사용되던 모델이었습니다.
하지만 치명적인 단점이 있었고, 이를 보완한 모델들이 나오기 시작했습니다. LSTM, GRU 그리고 Transformer 까지의 모델들이 나오게 된 이유에 대해 알아봅시다.
RNN 모델이 학습하는 방법 : Truncation , BPTT
- 딥러닝 모델은 forward propagation을 통해 계산된 W를, backward propagation을 지나면서 W를 미분한 값인 gradient를 통해 학습하게 됩니다.
- BPTT란, Backpropagation through time의 줄임말로 RNN에서 타임스텝마다 계산된 weight를 backward propagation을 통해 학습하는 방식을 의미합니다.
매 타임스텝마다 주어진 character가 있을 것이고 여기 발생된 hidden state 벡터를 통해 output layer을 통화시켜 얻은 예측값, 그리고 각 타임스텝에서 나와야 하는 다음 character에 해당하는 ground truth와의 비교를 통한 loss function으로 학습이 진행됩니다.
입력 벡터가 hidden state 벡터로 변환될 때 사용되는 w_xh, 전 타임스텝의 hidden state 벡터가 현재 타임스텝으로 변환될 때 사용되는 w_hh, 그리고 여기서 변환된 h_t가 output 벡터로 변환될 때 사용되는 w_hy 행렬이 backpropagation에 의해 학습됩니다. 이 경우 character의 전체 sequence가 못해서 수만개를 가지는 데이터가 주어졌을 때 한번에 계산해야 하는 양이 매우 길 수 있습니다.
한번에 gpu를 통해 학습해야 하는 sequence가 매우 길고, output에 대한 loss를 적용한 후 backpropagation을 계산하는 길이도 매우 길기 때문에 한정된 자원 내 담기지 못할 수 있어 truncation을 이용합니다.
Truncation이란, 제한된 리소스(메모리) 내에서 모든 시퀀스를 학습할 수 없기때문에 잘라서 학습에 사용하는 것을 말합니다. 이를 통해 정보가 어디에 저장되는지도 알 수 있습니다. RNN에서 필요한 정보를 저장하는 공간은 매 타임스텝마다 업데이트를 수행하는 h_t입니다.
hidden state 벡터가 3차원이라고 생각하면 3개중 어디에 정보가 저장되는지 역추적할 수 있습니다. 각 차원중 하나를 고정하고 그 값이 타임 스텝이 진행됨에 따라 어떻게 변화하는지 분석해서 RNN의 특성을 알 수 있습니다.
- 아래 그림은 특정 hidden state 벡터의 dimension의 값을 시각화한 그림으로, if라는 단어를 인식해 조건문에 해당하는 부분이 값이 큰 형태로 나타냅니다. if에 해당하는 조건문을 기억하는 역할을 담당한다고 볼 수 있습니다.
하지만, 기존의 Vanilla RNN으로는 위와 같이 학습될 수 없습니다.
Vanishing/Exploding Gradient Problem in RNN
그 이유는 gradient가 전파되면서 소실되거나 증폭되면서 멀리까지 학습정보를 잘 전달하지 못하는 Long-Term-Dependency가 발생하기 때문입니다. x_t-1와 h_t-2이 결합되어서 hidden state가 만들어졌을 경우 여기서 나타난 h_t-1 은 다음 타임 스텝으로 넘어갈 때 w_hh를 곱하고 x_t가 w_hh과 곱해진 벡터에 tanh를 추가해 계산합니다. 다음 타임스텝으로 넘어갈때도 동일하게 w_hh를 곱한 후 x_t+1과 결합해 다음 타임스텝으로 넘어갑니다. w_hh가 반복적으로 곱해지므로 등비수열처럼 계산되어 기하급수적으로 커지거나 작아지는 현상이 나타납니다.
h_1으로부터 h_3까지 값이 변환되는 과정은 다음과 같고 맨 아래식처럼 하나의 식으로 나타낼 수 있습니다. gradient가 반영되어 backpropagation이 되어 h_3까지 전달되고 h_1까지 전달되는 과정을 살펴보면 h_3에 대한 h_1의 편미분 값을 계산해주게 됩니다.
합성함수의 미분에 따라 네모의 식을 X로 볼 때 tanh 함수의 접선의 기울기를 의미합니다. 그 미분값에 안의 네모를 새로운 X로 보면 미분값은 3이 됩니다. 또 안의 네모를 보면 tanh 함수의 접선의 기울기가 곱해지고 그 안의 h_1에 대한 편미분값인 3이 또 곱해집니다. 결국 gradient를 계산할 때 타임스텝의 갯수만큼 3이 곱해져 증폭되는 것을 알 수 있습니다. 결국 gradient값이 기하급수적으로 커지거나 작아지는 현상이 나타납니다. 따라서 모델의 학습이 잘 이루어지지 않습니다.
그렇다면 위의 hidden state 시각화는 어떻게 잘 이뤄진걸까요? 바로 RNN의 Long-Term-Dependency 문제를 보완한 LSTM 모델로 학습한 결과이기 때문입니다. LSTM에 대한 자세한 내용은 다음 강의에서 배우도록 하겠습니다.
4. Long Short-Term Memory (LSTM)
이전 강의에서는 RNN에서 발생하는 Long-Term-Dependency 현상에 대해 학습했습니다. 길이가 길어질수록 학습이 잘 이뤄지지 않는다는 문제점이 있는데, 이를 해결하기 위해 LSTM(Long Short-Term Memory)라는 모델이 나오게 됩니다. 이번 강의를 통해 더 발전된 RNN 계열모델인 LSTM 모델에 대해서 살펴보겠습니다.
LSTM : Long Short-Term Memory
- LSTM의 중심 아이디어는 단기 기억으로 저장하여 이걸 때에 따라 꺼내 사용함으로 더 오래 기억할 수 있도록 개선하는 것입니다. 다시 말해 Cell state에는 핵심 정보들을 모두 담아두고, 필요할 때마다 Hidden state를 가공해 time step에 필요한 정보만 노출하는 형태로 정보가 전파됩니다.
RNN에서 h_t = f_w( x_t, h_t-1 ) 이었습니다. LSTM 에서는 각 타임스텝에서 c_t와 h_t가 계삽됩니다. 따라서 {c_t, h_t} = LSTM ( x_t, C_T-1, h_t-1 ) 입니다.
LSTM의 여러 gate 설명
- 위 그림을 살펴보면 input으로 x_t, h_t-1 이 들어오게 되고 이를 W에 곱해주어 선형변환한 후 4개의 벡터로 분할해줍니다. 각각의 벡터에 sigmoid or tanh를 연산해줍니다. 게이트별로 설명은 아래와 같습니다. Ifog 로 불리는 게이트들은 그림에서 순서대로 나타납니다.
- x_t, h_t-1의 각각의 dimension이 h라고 가정하면 두개를 concate하면 2h가 되고 따라서 적용되는 선형변환의 column은 2h의 크기를 갖습니다. 또 4개의 세트를 만들어줘야 하기 때문에 4h에 해당하는 row의 갯수를 갖습니다. sigmoid에 해당하는 3개는 0~1의 크기의 벡터를 가지게 되고 tanh에 해당하는 값은 -1~1의 크기의 벡터를 가지게 됩니다.
- I : Input gate로 불리며, cell 에 쓸 지말지를 결정하는 게이트입니다. 즉, 들어오는 input에 대해서 마지막에 sigmoid를 거쳐 0-1 사이 값으로 표현해줍니다. 이 값은 cell state와 hidden state 두 갈래로 흐르게 됩니다.
- 표현식 : sigmoid(W(xt, ht-1)sigmoid(W(xt,ht−1)
- f : Forget gate 로 불리며, 정보를 어느정도로 지울지를 0~1사이의 값으로 나타냅니다.
- 표현식 : sigmoid(W(xt, ht-1)sigmoid(W(xt,ht−1)
- o : Output gate로 불리며, Cell 정보를 어느정도 hidden state에서 사용해야할 지를 0~1사이 값으로 나타냅니다.
- 표현식 : sigmoid(W(xt, ht-1)sigmoid(W(xt,ht−1)
- g : Gate gate로 불리며, 어느정도로 Cell state에 반영해야할 지를 -1 ~ 1 사이의 값으로 나타냅니다.
- 표현식 : tanh(W(xt, ht-1)tanh(W(xt,ht−1)
여러 gate 들은 c_t-1을 적절히 변환하는데 사용됩니다. forget gate 벡터는 x_t, h_t-1 두개의 벡터를 입력으로 받아 선형결합해 만들어지는 output 벡터에 sigmoid를 통과해 만들어집니다. c_t-1이 3차원 벡터로서 [3,5,-2]의 값을 갖는다면 먼저 forget gate와 곱해져 어느정도 남길지 결정합니다. 예를들어 첫번째 3은 0.7과 곱해서 70%만 남겨 2.1이 됩니다.
그 다음 gate gate 벡터가 적용됩니다. tanh를 적용해 -1~1사이의 값을 갖는 c_t~를 input gate 벡터와 곱해준 값에 forgate gate를 거친 값을 더해 계산합니다. c_t~를 input gate 벡터와 곱해주는 이유는 원하는 값을 더해줄때 특정 비율만큼의 값을 덜어내 만들겠다는 것입니다.
현재 타임스텝의 c_t를 계산완료하면 마지막으로 h_t를 만듭니다. c_t에 tanh를 적용해 -1~1크기의 벡터로 만들어주고 0~1크기인 output gate 벡터를 곱해주어 적절한 비율만큼 값을 적게 만들어 h_t를 만듭니다. 여기서 c_t는 기억해야하는 모든 정보를 담고 있는 벡터이지만 h_t는 현재 타임스텝에서 예측값에 직접적으로 필요한 정보만을 담은, 즉 c_t의 정보를 필터링한 형태로 볼 수 있습니다.
LSTM이 RNN과 다른점
- LSTM의 특징은 각 time step마다 필요한 정보를 단기 기억으로 hidden state에 저장하여 관리되도록 학습하는 것입니다.
- 오차역전파(backpropagation) 진행시 가중치(W)를 계속해서 곱해주는 연산이 아니라, forget gate를 거친 값에 대해 필요로하는 정보를 덧셈을 통해 연산하여 그레디언트 소실/증폭 문제를 방지합니다.
GRU : Gated Recurrent Unit
- LSTM과 전체적인 동작원리는 유사하지만, Cell state, Hidden state를 일원화하여 경량화한 모델입니다.
- GRU에서 사용되는 h_t-1 은 LSTM에서의 c_t 와 비슷한 역할을 합니다.
- forget gate 대신 (1-input gate)를 사용하여 h_t 를 구할때 가중평균의 형태로 계산하게 됩니다. 가중치의 합은 항상 1입니다.
- c_t와 h_t를 h_t-1로 일원화했고 두 독립된 게이트를 하나의 게이트로 계산하게 했습니다.
- 계산량과 메모리 요구량을 LSTM에 비해 줄여준 모델이면서 동시에 성능면에서도 LSTM과 비슷하거나 더 좋은 성능을 내는 모델입니다.
RNN , LSTM , GRU 요약
- RNN은 들어오는 입력값에 대해서, 많은 유연성을 가지고 학습되는 딥러닝 모델입니다.
- RNN에서는 그레디언트 소실/증폭 문제가 있어 실제로 많이 사용되지는 않지만, RNN 계열의 LSTM, GRU 모델은 현재도 많이 사용되고 있습니다.
- LSTM과 GRU 모델은 RNN과 달리 가중치를 곱셈이 아닌 덧셈을 통한 그레디언트 복사로 그레디언트 소실/증폭 문제를 해결했습니다.