본문 바로가기

AI

[AI 논문] Attention Is All You Need (2017)

이번 시간에는 레전드 논문 'Attention Is All You Need'를 공부해보겠다.

확실히 공부한 걸 블로그로 작성하는 일은 시간은 걸리더라도 long-term memory에 각인되는 것 같다. 

공부한 흔적 그 이상의 가치를 가지는 것 같아서 앞으로도 귀찮더라도 꾸준히 포스팅을 해야겠다.

 

출처

- Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł., & Polosukhin, I. (2017). Attention is all you need. Advances in Neural Information Processing Systems, 30, 5998-6008.
- Umar Jamil Youtube: https://www.youtube.com/watch?v=bCz4OMemCcA
- 동빈나 Youtube: https://www.youtube.com/watch?v=AA621UofTUA&t=3916s

 

 

사전 지식

 

 

위 그림에서 보듯, RNN과 LSTM이 등장한 후 이를 기계번역에 사용하고자 Encoder-Decoder 구조로 이루어진 Seq2Seq2 모델이 탄생했었다.

하지만 기존 Seq2Seq2 모델의 한계점은 마지막 hidden vector를 하나의 고정된 크기의 context vector로 사용하려 해서 병목 현상이 나타날 수 있다는 점이었다.

 

그래서 이후 2015년에는 Seq2Seq 모델에 Attention 메커니즘이라는 새로운 방식을 접목했다.

이는 디코더가 인코더의 모든 출력을 동적으로 참고하여 매번 인코더의 모든 출력 중에서 어떤 정보가 중요한지 계산하는 방식이다. 이를 통해 decoder는 필요한 정보에 집중하여 더 정확한 출력을 생성할 수 있다. 

요약하자면, 출력 시퀀스가 입력 시퀀스 중 주목해야할 단어를 찾는 것이다! 

 

근데 2017년, 'Attention Is All You Need'에서는 Seq2Seq와 다르게 RNN이나 LSTM을 사용하지 않고 오로지 attention 메커니즘만 사용하는 파격적인 방식을 제안하였다.

 

이 새로운 아키텍처가 도대체 어떻게 구성되어있길래 이전보다 매우 뛰어난 성능을 보여줬을까?

이제부터 하나씩 살펴보자.

 

 

Transformer의 큰 그림

 

 

 

예전에 나는 저 그림을 보면 항상 지레 겁을 먹었다.

근데 공부를 하고 보니 생각보다 별 거 아니라는 걸 깨달았다.

처음 보는 사람이라면, 구조의 복잡성보다 이것을 이루는 각 블록들을 자세히 관찰해보길 바란다.

같거나 비슷한 블럭들이 여러 번 나오지 않는가?

 

각 블럭들을 하나씩 살펴보기 전에, 일단 큰 숲을 보자면

Transformer는 크게 encoder(왼쪽)와 decoder(오른쪽)로 구성되어 있다.

 

만약 '나는 고양이를 좋아한다'를 'I like cat'으로 번역하는 task를 수행한다고 가정해보자.

 

실제 번역 시에 Inputs에는 '나는 고양이를 좋아한다'가 들어가고 Outputs에는 '<sos>'가 들어간다.

이때, <sos>는 문장의 시작을 명시하는 토큰이다.

그러면 디코더의 출력으로 'I'가 나올테고, 이 'I'가 다시 Outputs 뒤로 들어가서 계속 뒤에 올 단어를 예측하는 일을 수행한다.

 

훈련할 때에는 약간 다른 양상을 띤다.

Inputs에는 동일하게 '나는 고양이를 좋아한다'가 들어가지만 Outputs에는 '<sos> I like'가 들어간다.

훈련할 때는 모델이 <sos> 뒤에 나오는 값을 예측해도 그 예측 값을 가져오는 것이 아니라 미리 준비된 'I'를 쓴다고 이해하면 편하다.

 

그렇다면 이제 encoder와 decoder 각각의 구성요소를 하나씩 자세하게 살펴보자. 

 

Encoder

Encoder

 

1. Input Embedding

 

맨 처음에 original sentence를 token으로 나눈다(보통 단어 기준).

그 뒤 token을 input ID로 바꿔주는데, 이는 학습하는 동안 바뀌지 않는 고유한 숫자다.

Input embedding은 각 token을 벡터로 표현한 것이다. 이때 vector의 size를 512라고 하자.

논문에서는 이를 d-model이라고 한다.

 

만약 token의 개수가 6개라면, input embedding의 행렬 크기는 (6,512)가 되겠지?

 

 

2. Positional Encoding

 

서론에서 말했듯이 Transformer는 RNN이나 LSTM를 사용하지 않고 병렬적으로 token을 넣어주기 때문에 문장 안에서의 token의 순서가 무시될 수 있다.

이를 해결하기 위해 Positional Encoding이라는 기법을 쓴다.

위 사진의 예시를 들면 'CAT'이라는 단어가 한 문장에서 2번 나오는데 input embedding 값은 같으므로 이를 다르게 바꿔 줄 필요가 있다.

 

 

 

이를 위해 위와 같은 공식을 쓴다고 한다(수학적인 설명은 일단 생략하겠다).

왜 positional encoding을 쓰는지 개념만 이해하고 일단 넘어가자.

positional encoding과 input embedding을 더하면 비로서 encoder input이 된다.

PE는 처음에 계산이 한 번 수행되고 이후 train이 되도 바뀌지 않는다.

또한, PE의 각 벡터의 크기도 512이기 때문에 encoder input의 행렬 크기도 아까의 예시를 가지고 온다면 그대로 (6,512)로 보존된다.

 

3. Multi-Head Attention

 

Transformer의 가장 핵심이 되는 파트이다.

일단 Single-Head Attention의 개념부터 알아보자.

가장 핵심은, Transformer의 Attention은 Seq2Seq의 Attention과 달리 입력 시퀀스 안에서의 단어들 간의 관계성을 주목한다!

이를 self-attention이라고도 한다.

 

 

 

위의 그림을 보면 Q, K, V라는 새로운 용어가 나온다.

이 셋은 단지 encoder input을 똑같이 복제한 것에 불과하다.

Q의 각 행은 6개의 단어를 하나씩 담고 있는데, K를 전치해서 곱하면 모든 단어가 자신을 포함한 문장 내의 다른 단어들과 곱해져서 6x6 행렬이 나온다. 

'내적'했으므로 값이 클수록 '유사도'가 크다고 해석할 수 있다.

여기서 설명을 빠뜨린 게 하나 있는데, dmodel의 루트 값으로 나눠주고 softmax를 취해서 확률 값을 표현해주어야 한다.

 

 

 

그 다음 (6,6) 행렬을 다시 (6,512) value랑 곱하여 최종적으로 (6,512) 크기의 Attention을 얻는다. 

이 일련의 계산은 별도의 parameter가 필요하지 않다.

 

지금까지의 self-attention은 'single-head'에 대한 설명이었고, 이제 본격적으로 'multi-head'에 대해 살펴보자.

 

 

논문에 나온 그림을 보면 '여러 개의 V, K, Q를 써서 multi-head인가?'라는 유추를 해볼 수 있다.

 

 

하나씩 살펴보자.

Input으로부터 동일한 세 개의 Q, K, V 행렬을 생성한다.

그리고 이것을 각각 다른 가중치로 곱해주면 Q', K', V'이 생성된다.

이걸 바로 attention(Q,K,V)하는 것이 아니라, head의 개수만큼 Q', K', V'을 열 방향으로 쪼개준다.

그리고 그림과 같이 각각에 대하여 attention 계산을 해주고 이를 이어붙인다.

마지막으로 이를 Wo와 곱하여 최종적으로 (6,512) 크기의 MultiHead Attention 값을 얻는다.

 

왜 이렇게 head를 나누는 걸까?

아직 내가 이해한 것이 정확하진 않을 수 있겠지만, multi-head로 계산했을 때 각 문장을 다른 관점에서 볼 수 있다고 생각하면 문장의 context를 더 잘 학습하지 않을까? (we wants each head to watch different aspects)

 

이러한 기법은 Seq2Seq와 다르게 입력 문장의 단어를 하나하나 처리하지 않고 모든 단어를 한 번에 병렬적으로 처리할 수 있기 때문에 속도가 빠르고, 긴 문장이라 하더라도 각각의 모든 단어들 간의 관계를 차별 없이 주의를 계산할 수 있다는 장점이 있다!

 

4. Add & Normalization

 

일단 Add는 Resnet에서 도입되었떤 skip connection 개념과 비슷하다.

Multi-Head Attention과 처음의 Encoder Input을 더해서 기존 정보도 어느 정도 보존하면서 새로운 정보를 가미하는 효과를 보인다고 생각하면 된다.

 

이렇게 합한 후, 0과 1 사이의 값으로 normalization해준다!

 

5. Feed Forward

 

Feed Forward 레이어는 활성화 함수를 Relu()를 쓰는 fully-connected layer라고 생각하면 된다. 

Relu()함수를 쓰면 음수 값은 0이 된다. 그리고 이 과정을 2번 수행한다. 비선형성을 증사키시는 과정이라고 생각하면 된다.

 

이후 마찬가지로, Add & Norm 과정을 한 번 더 거치면 비로서 Encoder가 끝난다!

 

 

Decoder

Decoder

 

1. Masked Multi-Head Attention

 

Decoder의 Multi-Head Attention은 Encoder와 다르게 앞에 'Masked'라는 단어가 붙는다. 

 

 

 

위 그림과 같이, 행렬의 대각선 위는 (- 무한대)로 처리해서 softmax 계산 시 0이 되도록 처리한다.

왜 decoder에서만 masking을 수행할까? 이는 decoder의 목적을 떠올려 보면 당연하다.

Encoder는 입력 문장 전체의 의미를 파악하는 것이 중요했지만, decoder는 출력 단어의 시퀀스를 생성하는 것이 목적이기 떄문이다!

 

2. Cross-Attention

 

그림에서는 'Multi-Head Attention'이라고 나오지만 Q, K, V의 값을 Encoder와 Decoder 두 군데 모두로부터 쓰기 때문에 Cross-Attention이라고도 한다. 

Encoder에서 나온 입력값 임베딩을 Q와 K로 쓰고, Decoder에서 방금 계산한 값을 V로 써서 계산한다.

 

그 다음 과정은 Encoder와 동일하기 때문에 설명을 생략하겠다.

 

3. Linear & Softmax

 

마지막으로 Linear 계층으로부터 (seq, dmodel) 행렬을 (seq, vocab_size)로 바꿔주고,

이를 softmax를 취해 확률값을 계산하고 Cross-Entropy Loss를 손실함수로 쓴다.

 

 


 

사실 나의 머리 속에서 이해하고 있는 개념을 두서 없이 쓴 것 같아서 불완전한 포스팅이 된 것 같지만...

나중에 시간이 흐르고 다시 보았을 때 이상한 점이 보이면 수정하면 되니깐 너무 처음부터 완벽하게 쓰려는 마음은 놓고자 한다.

 

다음 공부할 논문은 ViT이다!