본문 바로가기
딥러닝/트랜스포머

트랜스포머 시리즈 4편 트랜스포머의 구조 - 인코더

by 객잔주인 2024. 2. 5.

트랜스포머의 구조 (좌) 인코더 (우) 디코더

이번 포스팅과 다음 포스팅에서는 트랜스포머의 구조에 대해 알아보겠습니다

 

트랜스포머는 2017년 구글에서 발표한 논문 Attention Is All You Need에서 등장한 개념입니다

 

현재 자연어 처리 분야에서 사용되는 많은 모델들이 이 아키텍쳐에 기반을 두고 있으며

 

비전 분야에서도 트랜스포머에 기반한 ViT를 사용하기도 합니다

 

그러나 재미있는(?) 점은 논문의 저자들도 이게 왜 잘 되는지에 대한 설명을 하지 못한다는 겁니다

 

'이렇게 해보니 잘 되더라'라는 식의 내용이 많고 아직 제대로된 깔끔한 해석이 나오지 않은 상황입니다 (앞으로도 그럴 것 같습니다)

 

따라서 트랜스포머의 구조를 보시면서 '왜 이 레이어를 넣었지?', '왜 이렇게 했지?', '왜 이 연산을 한거지?' 라는 의문이 드시더라도

 

일단은 그 의문들을 잠시 넣어두시고 '트랜스포머가 이렇게 생겼구나' 정도로 넘어가시면 좋을 것 같습니다

 

트랜스포머는 인코더와 디코더 부분으로 나뉘는데 오늘은 인코더 부분만 먼저 살펴보도록 하겠습니다

 

인코더 블록

트랜스포머의 인코더 블록

 

트랜스포머의 인코더를 이루는 핵심 요소는 어텐션 블록입니다. 어텐션 블록은 멀티헤드 어텐션, Add & Norm, Feed Forawrd라는 하위 구성을 가지고 있는 큰 단위입니다. 또한 그림상에선 하나만 표시되어있지만 여러개의 인코더가 사용될 수 있습니다. 먼저 인코더 블록의 하위 층 중 하나인 멀티헤드 어텐션에 대해 알아보겠습니다.

 

멀티헤드 어텐션 (Multi-head attention)

인코더 블록의 멀티헤드 어텐션은 이전 포스팅에서 설명했던 셀프 어텐션이 사용된 부분입니다. '멀티헤드'라는 단어가 붙은 이유는 셀프 어텐션이 여러 개 사용되었기 때문입니다. 셀프 어텐션을 여러 개 사용하는 이유는 셀프 어텐션을 하나만 사용할 경우 소프트 맥스가 유사도 중 하나에만 초점을 맞추는 경향이 생기기 때문입니다. 그래서 셀프 어텐션을 여러 개 사용해서 동시에 여러 곳에 집중(어텐션) 하겠다는 의도입니다.

 

트랜스포머에서는 셀프 어텐션 중에서도 스케일드 내적 셀프 어텐션(scaled dot-product self attention)을 사용했습니다. 기존 셀프 어텐션과 크게 다르지 않습니다. 식을 보면 그 차이를 쉽게 확인하실 수 있습니다:

 

기존 셀프 어텐션: $$Attention(Q, K, V) = Softmax(QK^{T})V$$

스케일드 내적 셀프 어텐션: $$Attention(Q, K, V) = Softmax(\frac{QK^{T}}{\sqrt{d_{k}}})V$$

 

보이시나요? $Q$와 $K$의 내적 결과를 $\sqrt{d_{k}}$로 나누어 준 것이 전부입니다. $d_{k}$는 키 벡터의 원소의 개수를 의미하는데 그것에 제곱근을 씌운 결과로 나누어준 것입니다. 이렇게 $d_{k}$로 나누어 주었을 때 학습이 더 안정적으로 진행되고 모형의 성능이 더 좋아진다고 합니다. (이유는 논문 저자도 모릅니다)

 

스케일드 어텐션을 여러 개 사용하면 멀티헤드 어텐션이라고 했습니다. 그래서 여러 개의 어텐션 결과를 이어 붙이는 concatenate을 해주면 멀티헤드 어텐션의 결과를 얻을 수 있습니다만... 논문에서는 여기에 몇 가지 가공을 조금 했습니다. 아래 식을 보시죠:

$$\begin{align} MultiHead(Q, K, V) = Concat(head_{1}, ..., head_{h})W^{O} \\ where\ head_{i} = Attention(QW_{i}^{Q}, KW_{i}^{K}, VW_{i}^{V})\end{align}$$

 

아랫줄을 먼저 보시면 기존의 어텐션과는 다르게 $Q, K, V$ 각각에 가중치 행렬을 곱해준 것을 확인할 수 있습니다. (이것을 선형 변환한다고 합니다) 각 헤드에서 선형변환된 $Q, K, V$를 이용한 스케일드 어텐션 결과를 얻어 concat 해준 후 다시 한번 $W^{O}$로 선형변환 해주면 멀티헤드 어텐션의 결과를 얻을 수 있습니다

 

이렇게 얻어진 멀티헤드 어텐션의 결과는 Add & Norm 모듈에 입력됩니다

 

Add & Norm

Add & Norm 층에서는 멀티헤드 어텐션 층이 출력하는 값과 인코더 블럭에 입력되는 값(멀티헤드 어텐션 층에 입력되는 임베딩)을 더한(Add) 후 계층 정규화(Layer Normalization)을 수행합니다. 이 과정을 수행하면 경사 소실(gradient descent) 문제를 줄여 학습이 더 안정적으로 진행된다고 합니다

 

여기서 나온 결과는 다시 피드 포워드(Feed Forward) 네트워크로 입력됩니다

 

피드 포워드 (Feed Forward)

피드 포워드 네트워크는 두 개의 선형 층으로 구성된 완전 연결 신경망입니다. 두 개의 층 중 첫 번째 층에서는 입력 차원이 네 배로 뻥튀기 되고 두 번째 차원에서 다시 원래 크기의 차원으로 되돌립니다. 이러한 과정을 거치면 모델의 표현력이 상승한다고 합니다

 

입력 임베딩

지금까지는 인코더 블록에 입력된 정보가 어떻게 가공되는지에 대해 알아봤다면 이 섹션에서는 인코더 블록에 어떤 정보가 입력되는지를 알아보겠습니다

인코더 블록에 입력되기 전 임베딩의 가공과정

위 그림을 보시면 Positional Encoding이라는 부분이 보이는데요, 이 부분은 임베딩 벡터에 위치 정보를 씌워주는 과정입니다. 위치 정보는 왜 심어줄까요? 아래 예시를 한 번 보겠습니다:

 

문장1. 나는 백화점에 갔다가 집에 도착했다

 

문장2. 나는 집에 갔다가 백화점에 도착했다

 

문장1과 문장2는 같은 단어로 구성된 다른 문장입니다. 그러나 단어의 위치가 바뀌면 문장이 가지는 의미도 달라지게 됩니다. 위치 인코딩은 문장의 각 단어(혹은 토큰)에 위치 정보를 각인해주는 과정입니다. 이런 과정이 필요한 이유는 트랜스포머가 모든 단어를 한 번에 입력받기 때문에 문장 내 각 단어의 선후 관계가 꼬일 수 있기 때문입니다.

 

위치 임베딩 벡터

위치 임베딩 벡터는 벡터의 각 위치에 대한 정보를 가지는 벡터입니다. 이 정보를 각 단어의 임베딩 벡터와 더해주면 인코더 블록에 입력할 최종 임베딩 벡터를 얻을 수 있게됩니다

 

위치를 인코딩하는 방법은 여러가지가 있는데 Attention Is All You Need 논문에서는 $sin$함수와 $cos$함수를 사용하는 방법을 택했습니다.

 

$$\begin{align} PE_{(pos, i)} = sin(\frac{pos}{10000^{i / d_{model}}}) \\ PE_{(pos, i)} = cos(\frac{pos}{10000^{i-1 / d_{model}}}) \end{align}$$

 

위 식에서

$pos$는 문장 내에서 단어의 위치를 나타냅니다. 예를 들어, I love you라는 문장이 있으면 I의 pos는 0, love의 pos는 1, you의 pos는 2가 됩니다.

 

$i$는 단어의 임베딩 벡터 내에서 요소의 위치를 나타냅니다. 예를 들어 love의 임베딩 벡터 $Embed_{love} = [0.5, 0.6, 0.7]$이라고 할 때 $0.5 (i=0), 0.6 (i=1), 0.7 (i=2)$ 입니다.

 

$d_{model}$은 단어의 임베딩 차원을 뜻합니다. 위치 임베딩 벡터를 만들 때에 i가 짝수일 때는 $sin$함수를, 홀수일 때는 $cos$함수를 이용합니다. 살짝 복잡한데 아래에 예시를 나타내 보았습니다

 

I love you라는 문장의 위치 임베딩은 다음과 같습니다. 단어의 임베딩 차원(열의 개수)은 4차원이라고 가정하겠습니다.

 

\begin{bmatrix} sin(\frac{0}{10000^{0 / 4}}) & cos(\frac{0}{10000^{0 / 4}}) & sin(\frac{0}{10000^{1 / 4}}) &  cos(\frac{0}{10000^{1 / 4}})\\ sin(\frac{1}{10000^{0 / 4}})& cos(\frac{1}{10000^{0 / 4}}) & sin(\frac{1}{10000^{1 / 4}}) & cos(\frac{1}{10000^{1 / 4}})\\ sin(\frac{2}{10000^{0 / 4}}) & cos(\frac{2}{10000^{0 / 4}}) & sin(\frac{2}{10000^{1 / 4}}) &  cos(\frac{2}{10000^{1 / 4}}) \end{bmatrix}

 

이제 이 위치 임베딩 행렬과 원래의 임베딩 행렬을 더해주면 인코더 블럭에 입력될 준비가 끝나게 됩니다


오늘은 트랜스포머의 구조 중에서도 인코더 부분에 대해 살펴봤습니다

 

다음 포스팅에서는 트랜스포머의 디코더 부분에 대해 알아보겠습니다

 

감사합니다