DH. AI

[딥러닝] 트랜스포머(Transformer) 어텐션, 인코더, 셀프 어텐션 본문

[딥러닝]/[Transformer]

[딥러닝] 트랜스포머(Transformer) 어텐션, 인코더, 셀프 어텐션

도환 2022. 8. 7. 03:41

* 트랜스포머가 처음이라면 이 글을 보기전에 이 링크를 클릭하세요. 

 

[딥러닝] 트랜스포머(Transformer) 하이퍼파라미터, 인코더와 디코더, 포지셔널 인코딩

"Attention is all you need"에서 나온 모델로 기존의 seq2seq의 구조인 인코더-디코더를 따르면서도, 논문의 이름처럼 어텐션(Attention)만으로 구현한 모델입니다. 이 모델은 RNN을 사용하지 않고, 인코더-

dohwai-ai.tistory.com

5. 어텐션(Attention)

트랜스포머의 세가지 어텐션

 

첫번째 그림인 셀프 어텐션은 인코더에서 이루어지지만, 두번째 그림인 Masked 셀프 어텐션과 세번째 그림인 인코더-디코더 어텐션은 디코더에서 이루어집니다. 셀프 어텐션은 본질적으로 Query, Key, Value가 동일한 경우를 말합니다. 주의할 점은 여기서 Query, Key 등이 같다는 것은 벡터의 값이 같다는 것이 아니라 벡터의 출처가 같다는 의미입니다.

반면, 세번째 그림 인코더-디코더 어텐션에서는 Query가 디코더의 벡터인 반면에 Key와 Value가 인코더의 벡터이므로 셀프 어텐션이라고 부르지 않습니다.

각 어텐션의 위치

6. 인코더(Encoder)

인코더의 구조

트랜스포머는 하이퍼파라미터인 num_layers 개수의 인코더 층을 쌓습니다. (논문에서는 6개의 층 사용)

하나의 인코더 층은 크게 총 2개의 서브층(sublayer)으로 나뉘어집니다.  (셀프 어텐션피드 포워드 신경망)

위의 그림에서 멀티 헤드 셀프 어텐션은 셀프 어텐션을 병렬적으로 사용하였다는 의미고, 포지션 와이즈 피드 포워드 신경망은 우리가 알고있는 일반적인 피드 포워드 신경망입니다. 셀프 어텐션 먼저 알아봅시다.

7. 인코더의 셀프 어텐션

기존 어텐션과 셀프 어텐션이 무엇이 다른지 알아야합니다. 기존 어텐션의 이해가 부족하다면 이 링크를 클릭해주세요.

7 - (1) 셀프 어텐션의 의미와 이점

어텐션

어텐션 함수는 주어진 '쿼리(Query)'에 대해서 모든 '키(Key)'와의 유사도를 각각 구합니다. 그리고 구해낸 이 유사도를 가중치로 하여 키와 맵핑되어있는 각각의 '값(Value)'에 반영해줍니다. 그리고 유사도가 반영된 '값(Value)'을 모두 가중합하여 최종적으로 어텐션 값을 리턴합니다. 여기까지는 앞서 배운 어텐션의 개념입니다. 그런데 어텐션 중에서는 셀프 어텐션(self-attention)이라는 것이 있습니다. 어텐션을 자기 자신에게 수행한다는 의미입니다.

기존 어텐션  Q, K, V

기존에는 디코더 셀의 은닉 상태가 Q이고 인코더 셀의 은닉 상태가 K라는 점에서 Q와 K가 서로 다른 값을 가지고 있었습니다. 그런데 셀프 어텐션에서는 Q, K, V가 전부동일합니다. 트랜스포머의 셀프 어텐션에서의 Q, K, V는 아래와 같습니다.

셀프 어텐션 Q, K, V

셀프 어텐션에 대한 구체적인 사항을배우기 전에 셀프 어텐션을 통해 얻을 수 있는 대표적인 효과에 대해서 이해해봅시다.

트랜스포머에 대한 구글 AI 블로그 포스트의 그림

문장을 번역하면 '그 동물은 길을 건너지 않았다. 왜냐하면 그것은 너무 피곤하였기 때문이다.' 라는 의미가 됩니다. 그런데 여기서 그것(it)에 해당하는 것은 과연 길(street)일까요? 동물(animal)일까요? 우리는 피곤한 주체가 동물이라는 것을 아주 쉽게 알 수 있지만 기계는 그렇지 않습니다. 하지만 셀프 어텐션은 입력 문장 내의 단어들끼리 유사도를 구하므로서 그것(it)이 동물(animal)과 연관되었을 확률이 높다는 것을 찾아냅니다. 이제 셀프 어텐션의 동작 원리를 알아봅시다.

7 - (2) Q, K, V 벡터 얻기

앞서 셀프 어텐션은 입력 문장의 단어 벡터들을 가지고 수행한다고 하였는데, 사실 셀프 어텐션은 인코더의 초기 입력인 d_model의 차원을 가지는 단어 벡터들을 사용하여 셀프 어텐션을 수행하는 것이 아니라 우선 각 단어 벡터들로부터 Q벡터, K벡터, V벡터를 얻는 작업을 거칩니다. 초기 입력인 d_model의 차원을 가지는 단어 벡터들보다 더 작은 차원을 가지는데, 논문에서는 512차원의 단어 벡터를 64의 차원을 가지는 Q벡터, K벡터, V벡터로 변환합니다.

64라는 값은 트랜스포머의 또 다른 하이퍼파라미터인 num_heads로 인해 결정되는데, 트랜스포머는 d_model num_heads로 나눈 값을 각 Q벡터, K벡터, V벡터의 차원으로 결정합니다. 논문에서는 num_heads를 8로하였습니다.

예문 중 student라는 단어 벡터를 Q, K, V의 벡터로 변환하는 과정을 보겠습니다.

기존의 벡터로부터 더 작은 벡터는 가중치 행렬을 곱하므로서 완성됩니다. 각 가중치 행렬은 d_model ×                          (d_model/num_heads) 의 크기를 가집니다. 이 가중치 행렬은 훈련 과정에서 학습됩니다. 즉, 논문과 같이 dmodel=512이고 num_heads=8라면, 각 벡터에 3개의 서로 다른 가중치 행렬을 곱하고 64의 크기를 가지는 Q, K, V 벡터를 얻어냅니다.

의 그림은 단어 벡터 중 student 벡터로부터 Q, K, V 벡터를 얻어내는 모습을 보여줍니다. 모든 단어 벡터에 위와 같은 과정을 거치면 I, am, a, student는 각각의 Q, K, V 벡터를 얻습니다.

7 - (3) 스케일드 닷 - 프로덕트 어텐션(Scaled dot - product Attention)

Q, K, V 벡터를 얻었다면 지금부터는 기존에 배운 어텐션 메커니즘과 동일합니다. 각 Q벡터는 모든 K벡터에 대해서 어텐션 스코어를 구하고, 어텐션 분포를 구한 뒤에 이를 사용하여 모든 V벡터를 가중합하여 어텐션 값 또는 컨텍스트 벡터를 구하게 됩니다. 그리고 이를 모든 Q벡터에 대해서 반복합니다.

트랜스포머에서는 어텐션 챕터에 사용했던 내적만을 사용하는 어텐션 함수 score(q,k)=q⋅k가 아니라, score(q,k)=q⋅k/sqrt(n) 을 사용합니다.

아래 그림에서 어텐션 스코어는 각각 단어 I가 단어 I, am, a, student와 얼마나 연관되어 있는지를 보여주는 수치입니다. 트랜스포머에서는 두 벡터의 내적값을 스케일링하는 값으로 K벡터의 차원을 나타내는 dk에 루트를 씌운 값을 사용했습니다.

I에 대해서 어텐션 값을 구하는 과정

어텐션 스코어에 소프트맥스 함수를 사용하여 어텐션 분포(Attention Distribution)을 구하고, 각 V벡터와 가중합하여 어텐션 값(Attention Value)을 구합니다. 이 과정을 각각의 Q벡터마다 따로 계산할 필요없이 행렬 연산이 가능합니다.

7 - (4) 행렬 연산으로 일괄 처리하기

사실 각 단어에 대한 Q, K, V 벡터를 구하고 스케일드 닷-프로덕트 어텐션을 수행하였던 위의 과정들은 벡터 연산이 아니라 행렬 연산을 사용하면 일괄 계산이 가능합니다. 우선, 각 단어 벡터마다 일일히 가중치 행렬을 곱하는 것이 아니라 문장 행렬에 가중치 행렬을 곱하여 Q행렬, K행렬, V행렬을 구합니다.

행렬 연산을 통해 어텐션 스코어는 어떻게 구할 수 있을까요? 여기서 Q행렬을 K행렬을 전치한 행렬과 곱해준다고 해봅시다. 이렇게 되면 각각의 단어의 Q벡터와 K벡터의 내적이 각 행렬의 원소가 되는 행렬이 결과로 나옵니다.

다시 말해 위의 그림의 결과 행렬의 값에 전체적으로 dk를 나누어주면 이는 각 행과 열이 어텐션 스코어 값을 가지는 행렬이 됩니다. 예를 들어 I 행과 student 열의 값은 I의 Q벡터와 student의 K벡터의 어텐션 스코어 값입니다. (행: Q, 열: K)

어텐션 스코어 행렬을 구하였다면 남은 것은 어텐션 분포를 구하고, 이를 사용하여 모든 단어에 대한 어텐션 값을 구하는 일입니다. 이는 간단하게 어텐션 스코어 행렬에 소프트맥스 함수를 사용하고, V행렬을 곱하는 것으로 해결됩니다. 이렇게 되면 각 단어의 어텐션 값을 모두 가지는 어텐션 값 행렬이 결과로 나옵니다.

어텐션 행렬에서 행별로 softmax가 취해진값(어텐션 스코어)과 V가 곱해진다고 생각하면 된다.

위의 그림은 행렬 연산을 통해 모든 값이 일괄 계산되는 과정을 식으로 보여줍니다. 해당 식은 실제 트랜스포머 논문에 기재된 아래의 수식과 정확하게 일치하는 식입니다. (어텐션 값: 4개의 단어 X 2크기의 Q, K, V 의 차원 : 4X2 행렬)

위의 행렬 연산에 사용된 행렬의 크기를 모두 정리해봅시다. 우선 입력 문장의 길이를 seq_len이라고 해봅시다. 그렇다면 문장 행렬의 크기는 (seq_len, d_model)입니다. 여기에 3개의 가중치 행렬을 곱해서 Q, K, V 행렬을 만들어야 합니다.

 

- 개인적으로 행렬 계산 할때 차원의 이해가 필요하다고 생각하기 때문에 아래 내용을 꼭 한번씩 따라가보면서 이해하는 것을 추천합니다.

 

우선 행렬의 크기를 정의하기 위해 행렬의 각 행에 해당되는 Q벡터와 K벡터의 차원을 d_k라고 하고, V벡터의 차원을 d_v라고 해봅시다. 그렇다면 Q행렬과 K행렬의 크기는 (seq_len, d_k)이며, V행렬의 크기는 (seq_len, d_v)가 되어야 합니다. 그렇다면 문장 행렬과 Q, K, V 행렬의 크기로부터 가중치 행렬의 크기 추정이 가능합니다. W_Q W_K (d_model, d_k)의 크기를 가지며, W_V (d_model, d_v)의 크기를 가집니다. 단, 논문에서는 d_k d_v의 차원은 d_model/num_heads와 같습니다. 즉, d_model/num_heads=d_k=d_v입니다.

7 - (6) 멀티 헤드 어텐션(Multi-head Attention)

앞서 배운 어텐션에서는 d_model의 차원을 가진 단어 벡터를 num_heads로 나눈 차원을 가지는 Q, K, V벡터로 바꾸고 어텐션을 수행하였습니다. 논문 기준으로는 512의 차원의 각 단어 벡터를 8로 나누어 64차원의 Q, K, V 벡터로 바꾸어서 어텐션을 수행한 셈인데, 이제 num_heads의 의미와 왜 d_model의 차원을 가진 단어 벡터를 가지고 어텐션을 하지 않고, 차원을 축소시킨 벡터로 어텐션을 수행하였는지 이해해보겠습니다.

트랜스포머 연구진은 한 번의 어텐션을 하는 것보다 여러번의 어텐션을 병렬로 사용하는 것이 더 효과적이라고 판단하였습니다. 그래서 d_model의 차원을 num_heads개로 나누어 d_model/num_heads의 차원을 가지는 Q, K, V에 대해서 num_heads개의 병렬 어텐션을 수행합니다. 논문에서는 하이퍼파라미터인 num_heads의 값을 8로 지정하였고, 8개의 병렬 어텐션이 이루어지게 됩니다. 어텐션을 병렬로 수행하여 다른 시각으로 정보들을 수집하겠다는 겁니다.

이때 각각의 어텐션 값 행렬을 어텐션 헤드라고 부릅니다. 이때 가중치 행렬 W_Q, W_K, W_V의 값은 8개의 어텐션 헤드마다 전부 다릅니다. 

 

멀티헤드 어텐션으로 병렬 처리 하는것의 장점을, 예를 들어보겠습니다. 앞서 사용한 예문 '그 동물은 길을 건너지 않았다. 왜냐하면 그것은 너무 피곤하였기 때문이다.'를 상기해봅시다. 단어 그것(it)이 쿼리였다고 해봅시다. 즉, it에 대한 Q벡터로부터 다른 단어와의 연관도를 구하였을 때 첫번째 어텐션 헤드는 '그것(it)'과 '동물(animal)'의 연관도를 높게 본다면, 두번째 어텐션 헤드는 '그것(it)'과 '피곤하였기 때문이다(tired)'의 연관도를 높게 볼 수 있습니다. 각 어텐션 헤드는 전부 다른 시각에서 보고있기 때문입니다.

병렬 어텐션을 모두 수행하였다면 모든 어텐션 헤드를 연결(concatenate)합니다. 모두 연결된 어텐션 헤드 행렬의 크기는 (seq_len, d_model)가 됩니다.

지금까지 그림에서는 책의 지면상의 한계로 4차원을 d_model=512로 표현하고, 2차원을 dv=64로 표현해왔기 때문에 위의 그림의 행렬의 크기에 혼동의 있을 수 있으나 8개의 어텐션 헤드의 연결(concatenate) 과정의 이해를 위해 이번 행렬만 예외로 위와 같이 d_model의 크기를 dv의 8배인 16차원으로 표현하였습니다. 아래의 그림에서는 다시 d_model를 4차원으로 표현합니다.

위 그럼 처럼, 어텐션 헤드를 모두 연결한 행렬은 또 다른 가중치 행렬 Wo을 곱하게 되는데, 이렇게 나온 결과 행렬이 멀티-헤드 어텐션의 최종 결과물입니다. 위의 그림은 어텐션 헤드를 모두 연결한 행렬이 가중치 행렬 Wo과 곱해지는 과정을 보여줍니다. 이때 결과물인 멀티-헤드 어텐션 행렬은 인코더의 입력이었던 문장 행렬의 (seq_len, dmodel) 크기와 동일합니다. 다시 말해 인코더의 첫번째 서브층인 멀티-헤드 어텐션 단계를 끝마쳤을 때, 인코더의 입력으로 들어왔던 행렬의 크기가 아직 유지되고 있음을 기억해둡시다.

트랜스포머는 동일한  구조의 인코더 층을 6개 쌓은 구조이기 때문에, 첫번째 멀티 헤드 어테션 단계와, 두번째 서브층인 포지션 와이즈 피드 포워드 신경망을 지나면서 인코더의 입력으로 들어올 때의 행렬의 크기는 계속 유지되어야 합니다. 인코더에서의 입력의 크기가 출력에서도 동일 크기로 계속 유지되어야만 다음 인코더에서도 다시 입력이 될 수 있습니다.

 

* 7- (7) 멀티 헤드 어텐션(Multi-head Attention) 구현하기는 맨 아래 링크에서 구현해보세요.

7 - (8) 패딩 마스크(Padding Mask)

패딩 마스크는 입력 문장에 <PAD> 토큰이 있을 경우 어텐션에서 사실상 제외하기 위한 연산입니다. 예를 들어 <PAD>가 포함된 입력 문장의 셀프 어텐션의 예제를 봅시다. 이에 대해서 어텐션을 수행하고 어텐션 스코어 행렬을 얻는 과정은 다음과 같습니다.

그런데 사실 단어 <PAD>의 경우에는 실질적인 의미를 가진 단어가 아닙니다. 그래서 트랜스포머에서는 Key의 경우에 <PAD> 토큰이 존재한다면 이에 대해서는 유사도를 구하지 않도록 마스킹(Masking)을 해주기로 했습니다. 여기서 마스킹이란 어텐션에서 제외하기 위해 값을 가린다는 의미입니다. 어텐션 스코어 행렬에서 행에 해당하는 문장은 Query이고, 열에 해당하는 문장은 Key입니다. 그리고 Key에 <PAD>가 있는 경우에는 해당 열 전체를 마스킹을 해줍니다.

마스킹을 하는 방법은 어텐션 스코어 행렬의 마스킹 위치에 매우 작은 음수값을 넣어주는 것입니다. 여기서 매우 작은 음수값이라는 것은 -1,000,000,000과 같은 -무한대에 가까운 수라는 의미입니다. 현재 어텐션 스코어 함수는 소프트맥스 함수를 지나지 않은 상태입니다. 앞서 배운 연산 순서라면 어텐션 스코어 함수는 소프트맥스 함수를 지나고, 그 후 Value 행렬과 곱해지게 됩니다. 그런데 현재 마스킹 위치에 매우 작은 음수 값이 들어가 있으므로 어텐션 스코어 행렬이 소프트맥스 함수를 지난 후에는 해당 위치의 값은 0이 되어 단어 간 유사도를 구하는 일에 <PAD> 토큰이 반영되지 않게 됩니다.

위 그림은 소프트맥스 함수를 지난 후를 가정하고 있습니다. 소프트맥스 함수를 지나면 각 행의 어텐션 가중치의 총 합은 1이 되는데, 단어 <PAD>의 경우에는 0이 되어 어떤 유의미한 값을 가지고 있지 않습니다.

 

 

 

-딥 러닝을 이용한 자연어 처리 입문 참고하였습니다. -