RNN 기반 모델이 가진 구조적 단점은 단어가 순차적으로 들어온다는 점입니다.
그렇기 때문에 처리해야 하는 시퀀스가 길수록 연산 시간이 길어지는데요.
트랜스포머는 이런 문제를 해결하기 위해 등장한 모델입니다.
모든 토큰을 동시에 입력받아 병렬 처리하기 때문에 GPU 연산에 최적화되어 있습니다.
아래는 트랜스포머의 구조를 단순하게 시각화한 그림입니다.
Encoder, Decoder로 표현된 사각형을 각각 인코더 블록과 디코더 블록이라고 합니다.
트랜스포머는 인코더 블록과 디코더 블록이 6개씩 모여있는 구조를 하고 있습니다.
왼쪽은 인코더 블록 하나를 나타내고 오른쪽은 디코더 블록 하나를 나타냅니다.
인코더 블록은 크게 2개의 sub-layer ▶️ [Multi-Head (Self) Attention
, Feed Forward
] 로 나눌 수 있습니다.
디코더 블록은 3개의 sub-layer ▶️ [Masked Multi-Head (Self) Attention
, Multi-Head (Encoder-Decoder) Attention
, Feed Forward
] 로 나눌 수 있습니다.
트랜스포머의 주요 메커니즘인 Self-Attention 에 대해 알아보도록 하겠습니다.
아래와 같은 문장이 있다고 해보겠습니다.
The animal didn't cross the street because it was too tired
위 문장을 제대로 번역하려면 "it" 과 같은 지시대명사가 어떤 대상을 가리키는지 알아야 합니다.
그렇기 때문에 트랜스포머에서는 번역하려는 문장 내부 요소의 관계를 잘 파악하기 위해서, 문장 자신에 대해 어텐션 메커니즘을 적용합니다.
이를 Self-Attention 이라고 합니다.
아래는 "it" 이 어떤 단어와 가장 연관되어 있는 지를 시각화한 그림입니다.
Self-Attention은 어떤 과정이길래 단어 사이의 관계를 알아낼 수 있을까요?
Self-Attention에서도 쿼리(Query)-키(Key)-밸류(Value)의 아이디어가 동일하게 등장합니다.
각각의 벡터가 어떤 역할을 하는지 알아보겠습니다.
Self-Attention은 세 가지 가중치 벡터를 대상으로 어텐션을 적용합니다.
적용하는 방식은 기존 Attention 메커니즘과 거의 동일합니다.
먼저, 특정 단어의 쿼리(q) 벡터와 모든 단어의 키(k) 벡터를 내적합니다. 내적을 통해 나오는 값이 Attention 스코어(Score)가 됩니다.
트랜스포머에서는 이 가중치를 q,k,v 벡터 차원 의 제곱근인 로 나누어줍니다.
계산값을 안정적으로 만들어주기 위한 계산 보정으로 생각해주시면 됩니다.
다음으로 Softmax를 취해줍니다.
이를 통해 쿼리에 해당하는 단어와 문장 내 다른 단어가 가지는 관계의 비율을 구할 수 있습니다.
마지막으로 밸류(v) 각 단어의 벡터를 곱해준 후 모두 더하면 Self-Attention 과정이 마무리됩니다.
(아래 그림에서는 로 나누어 준 뒤에 Softmax를 취해주는 과정은 생략되었습니다.)
실제로 각 벡터는 행렬(Q, K, V)로 한꺼번에 계산됩니다. 는 학습 과정에서 갱신되는 파라미터로 이루어진 행렬입니다.
세 행렬과 단어 행렬을 내적하여 쿼리, 키, 밸류 행렬(Q, K, V)를 만들어냅니다.
위에서 살펴본 바와 같이
먼저 쿼리 행렬(Q)과 키 행렬(K)을 내적합니다.
결과로 나오는 행렬의 요소를 로 나누어 줍니다.
행렬의 각 요소에 소프트맥스(Softmax)를 취해줍니다.
마지막으로 밸류 행렬(V)과 내적하면 최종 결과 행렬(Z)이 반환됩니다.
다음으로 Multi-Head Attention 에 대해 알아보겠습니다.
Multi-Head Attention 을 적용하면 여러 개의 Attention 메커니즘을 동시에 병렬적으로 실행합니다.
각 Head 마다 다른 Attention 결과를 내어주기 때문에 앙상블과 유사한 효과를 얻을 수 있으며, 병렬화 효과를 극대화 할 수 있습니다.
논문에서는 8개의 Head를 사용하였는데요.
8개의 Attention 메커니즘을 동시에 실행하여 각각의 출력 행렬 을 만들어냅니다.
출력된 행렬 은 이후에 이어붙여집니다(Concatenate).
그리고 또 다른 파라미터 행렬인 와의 내적을 통해 Multi-Head Attention의 최종 결과인 행렬 를 만들어지지요.
여기서 행렬 의 요소 역시 학습을 통해 갱신되는 가중치입니다.
최종적으로 생성된 행렬 는 어텐션 메커니즘에 입력되는 행렬 와 동일한 형태(Shape)을 가지고 있습니다.
트랜스포머의 모든 sub-layer에서 출력된 벡터는 Layer normalization과 Skip connection을 거치게 됩니다.
Layer normalization의 효과는 Batch normalization과 유사합니다. 학습이 훨씬 빠르고 잘 되도록 합니다.
Skip connection(혹은 Residual connection)은 역전파 과정에서 정보가 소실되지 않도록 합니다.
Sprint 3에서 배울 ResNet의 주요 메커니즘이므로 해당 부분에서 더욱 자세하게 다룰 예정입니다.
다음으로 FFNN(Feed forward neural network) 로 들어갑니다.
은닉층의 차원이 늘어났다가 다시 원래 차원으로 줄어드는 단순한 2층 신경망입니다.
활성화 함수(Activation function)으로 ReLU를 사용합니다.
Masked Self-Attention은 디코더 블록에서 사용하기 위해서 마스킹 과정이 포함된 Self-Attention입니다.
언어 모델에서 디코더가 단어를 생성할 때에는 Auto-Regressive(왼쪽 단어를 보고 오른쪽 단어를 반복하여 예측)하게 진행되었습니다.
RNN을 사용한 번역 모델에서도 생성하려는 단어 이후의 왼쪽에 있는 단어 정보만을 고려하여 단어를 생성하였습니다.
이러한 방법은 트랜스포머에서도 유지해주게 되는데요.
단어가 순차적으로 입력되는 RNN과 달리 트랜스포머에서는 타깃 문장 역시 한 번에 입력되기 때문에
해당 위치 타깃 단어 뒤에 위치한 단어는 Self-Attention에 영향을 주지 않도록 마스킹(Masking)을 해주게 됩니다.
아래 그림을 통해 Masked Self-Attention에 대해 알아보도록 하겠습니다.
Self-Attention (without Masking) vs Masked Self-Attention
Self-Attention 메커니즘은 쿼리 행렬(Q)와 키 행렬(K)의 내적합니다.
결과로 나온 행렬을 차원의 제곱근 로 나누어 준 다음,
Softmax를 취해주고 밸류 행렬(V)과 내적하였습니다.
Masked Self-Attention 에서는 Softmax를 취해주기 전, 가려주고자 하는 요소에만 에 해당하는 매우 작은 수를 더해줍니다.
아래 코드 예시에서는 -10억(=-1e9
)을 더해주었습니다.
이 과정을 마스킹(Masking)이라고 합니다.
마스킹된 값은 Softmax를 취해 주었을 때 0이 나오므로 Value 계산에 반영되지 않습니다.
디코더에서 Masked Self-Attention 층을 지난 벡터는 Encoder-Decoder Attention 층으로 들어갑니다.
좋은 번역을 위해서는 번역할 문장과 번역된 문장 간의 관계 역시 중요합니다.
번역할 문장과 번역되는 문장의 정보 관계를 엮어주는 부분이 바로 이 부분입니다.
이 층에서는 디코더 블록의 Masked Self-Attention으로부터 출력된 벡터를 쿼리(Q) 벡터로 사용합니다.
키(K)와 밸류(V) 벡터는 최상위(=6번째) 인코더 블록에서 사용했던 값을 그대로 가져와서 사용합니다.
Encoder-Decoder Attention 층의 계산 과정은 Self-Attention 했던 것과 동일합니다.
아래는 Encoder-Decoder Attention 가 진행되는 순서를 나타낸 이미지입니다.
디코더의 최상층을 통과한 벡터들은 Linear 층을 지난 후 Softmax를 통해 예측할 단어의 확률을 구하게 됩니다.