본문 바로가기

문돌이 존버/데이터 분석

트랜스포머(Transformer) 간단히 이해하기 (1)

반응형
본 글은 "딥 러닝을 이용한 자연어 처리 입문"을 학습하며 작성한 것입니다. 중간중간 제가 이해한 내용을 좀 더 풀어서 썼습니다. 문제가 된다면 비공개 처리하겠습니다.

트랜스포머는 2017년 구글이 발표한 논문인 Attention is all you need에서 나온 모델로 기존의 seq2seq의 구조인 인코더-디코더를 따르면서도 어텐션으로만 구현만 모델입니다. 본 모델은 RNN을 사용하지 않고 인코더-디코더 구조를 설계했음에도 성능이 RNN보다 우수하다고 합니다.

기존의 seq2seq 모델의 한계

기존 seq2seq 모델은 인코더-디코더 구조로 구성되어 있습니다. 인코더는 입력 시퀀스를 하나의 벡터 표현으로 압축하고, 디코더는 이 벡터 표현을 통해 출력 시퀀스를 만들어냈습니다. 하지만 인코더가 입력 시퀀스를 하나의 벡터로 압축하는 과정에서 입력 시퀀스 정보가 일부 손실된다는 단점이 있고, 이를 보완하는 것이 어텐션이 사용되었죠. 이때 RNN 보정 용도가 아닌 아예 어텐션으로 인코더와 디코더를 만들어보는 것을 생각해봅시다.

트랜스포머의 주요 하이퍼파라미터

먼저 하이퍼파라미터를 간단하게 알아보고자 합니다. 아래에서 정의한 수치값은 트랜스포머를 제안한 논문에서 사용한 것으로 사용자의 입맛에 따라 임의로 변경할 수 있습니다.

1. $d_{model} = 512$
트랜스포머의 인코더와 디코더에서의 정해진 입력과 출력의 크기를 의미합니다. 임베딩 벡터의 차원 또한 $d_{model}$이며, 각 인코더와 디코더가 다음 층의 인코더와 디코더로 값을 보낼 때에도 이 차원을 유지합니다.

2. $num\_layers = 6$
트랜스포머에서 하나의 인코더와 디코더를 층으로 생각했을 때, 트랜스포머 모델에서 인코더와 디코더가 총 몇 층으로 구성되었는지를 의미합니다. 논문에서는 인코더와 디코더를 각각 6층을 쌓았습니다.

3. $num\_heads = 8$
트랜스포머에서는 어텐션을 사용할 때, 1번 하는 것보다 여러 개로 분할해서 병렬로 어텐션을 수행하고 결과값을 다시 하나로 합치는 방식을 택했습니다. 이때 이 병렬의 개수를 의미합니다.

4. $d_{ff} = 2048$
트랜스포머 내부에는 피드 포워드 신경망이 존재합니다. 이때 은닉층의 크기를 의미합니다. 피드 포워드 신경망의 입력층과 출력층의 크기는 $d_{model}$입니다.

트랜스포머

<출처: 딥 러닝을 이용한 자연어 처리 입문>

트랜스포머는 RNN을 사용하지 않지만 기존의 seq2seq처럼 인코더에서 입력 시퀀스를 입력받고, 디코더에서 출력 시퀀스를 출력하는 인코더-디코더 구조를 유지합니다. 다른 점은 인코더와 디코더라는 단위가 N개 존재할 수 있다는 점입니다.

이전 seq2seq는 인코더와 디코더에서 각각 하나의 RNN이 t개의 시점(time-step)을 가지는 구조였다면, 이번엔 인코더와 디코더라는 단위가 N개로 구성되는 구조입니다.

출처: https://hyen4110.tistory.com/40

이제 인코더와 디코더가 각각 여러 개 쌓였다는 의미를 표현하기 위해 알파벳 s를 붙여 encoders, decoders라고 합니다.

<출처: 딥 러닝을 이용한 자연어 처리 입문>

위 그림의 Decoders는 기존의 seq2seq처럼 시작 심볼 <sos>를 입력으로 받아 종료 심볼 <eos>가 나올 때까지 연산을 진행합니다. 이는 RNN은 사용되지 않지만 여전히 인코더-디코더 구조는 유지되는 것입니다.

트랜스포머의 인코더와 디코더는 단순히 각 단어의 임베딩 벡터들을 입력받는 것이 아니라 임베딩 벡터에서 조정된 값을 입력받는데 이를 이해하기 위해 입력 부분을 확대해서 살펴보겠습니다.

포지셔널 인코딩(Positional Encoding)

RNN이 자연어 처리에서 유용했던 이유는 단어의 위치에 따라 단어를 순차적으로 입력받아 처리하는 RNN 특성으로 인해 각 단어의 위치 정보(position information)를 가질 수 있기 때문입니다. 다시 말해 "I am a student"의 경우, student라는 명사는 be 동사 뒤에 오는 것처럼 위치적 정보가 있다는 의미입니다.

하지만 트랜스포머는 단어 입력을 순차적으로 받는 방식이 아니므로 단어의 위치 정보를 다른 방식으로 알려줄 필요가 있습니다. 이를 위해 각 단어의 임베딩 벡터에 위치 정보들을 더해 모델의 입력으로 사용하는데, 이를 포지셔널 인코딩이라고 합니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

위 그림을 보면 입력으로 사용되는 임베딩 벡터들이 트랜스포머 입력으로 사용되기 전 포지셔널 인코딩값이 더해집니다. 임베딩 벡터가 인코더 입력으로 사용되기 전 포지셔널 인코딩값이 더해지는 과정을 시각화하면 아래와 같습니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

트랜스포머는 위치 정보를 가진 값을 만들기 위해 아래 2개의 함수를 사용합니다.

$PE_{(pos, 2i)} = sin(pos/10000^{2i/d_{model}})$

$PE_{(pos, 2i+1)} = cos(pos/10000^{2i/d_{model}})$

2개 함수에는 $pos,\ i,\ d_{model}$ 등 변수들이 있습니다. 함수를 이해하기 위해선 임베딩 벡터와 포지셔널 인코딩의 덧셈은 사실 임베딩 벡터가 모여 만들어진 문장 벡터 행렬포지셔널 인코딩 행렬덧셈 연산을 통해 이루어진다는 점을 알아야 합니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

pos는 입력 문장에서의 임베딩 벡터 위치를 나타내며, $i$는 임베딩 벡터 내 차원의 인덱스를 의미합니다. 임베딩 벡터 내 각 차원의 인덱스가 짝수인 경우($pos,\ 2i$) 사인 함수 값을 이용하고 홀수인 경우($pos,\ 2i+1$) 코사인 함수 값을 사용합니다.

$d_{model}$은 트랜스포머의 모든 층의 출력 차원을 의미하는 하이퍼파라미터입니다. 이렇게 포지셔널 인코딩 방법을 사용하면 순서 정보가 보존되는데, 예를 들어 각 임베딩 벡터에 포지셔널 인코딩값을 더하면 같은 단어라 하더라도 문장 내 위치에 따라서 트랜스포머 입력으로 들어가는 임베딩 벡터값이 달라집니다. 결국 트랜스포머 입력은 순서 정보가 고려된 임베딩 벡터인 것입니다.

어텐션

지금은 큰 그림을 이해하는 데 집중하겠습니다. 아래는 트랜스포머에서 사용되는 3가지 어텐션입니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

1번 그림의 셀프 어텐션은 인코더에서 이루어지지만, 2번 그림의 셀프 어텐션 및 3번 그림의 인코더-디코더 어텐션은 디코더에서 이루어집니다. 셀프 어텐션은 본질적으로 Query, Key, Value가 동일한 경우를 말합니다. 반면, 3번 그림에선 Query가 디코더의 벡터인 반면 Key, Value는 인코더의 벡터이므로 셀프 어텐션이라 하지 않습니다.

(주의!!)
Query, Key 등이 같다는 것은 벡터 값이 같다는 것이 아니라 벡터의 출처(인코더, 디코더)가 같다는 의미
정리
1. 인코더의 셀프 어텐션: Query = Key = Value
2. 디코더의 마스크드 셀프 어텐션: Query = Key = Value
3. 디코더의 인코더-디코더 어텐션: Query: 디코더 벡터 <-> Key = Value: 인코더 벡터

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

위 그림은 트랜스포머 아키텍처에서 3가지 어텐션이 각각 어디에서 이루어지는지를 보여줍니다. 추가적으로 멀티 헤드(Multi-head) 이름이 붙었는데, 이는 뒤에서 설명할 트랜스포머가 어텐션을 병렬적으로 수행하는 방법을 의미합니다.

인코더

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

트랜스포머는 하이퍼파라미터인 $num\_layers$ 개수의 인코더 층을 쌓습니다. 인코더를 하나의 층이란 개념으로 생각하면 하나의 인코더 층은 크게 2개의 서브층(sublayer)으로 나뉩니다. 바로 셀프 어텐션피드 포워드 신경망입니다.  

인코더의 셀프 어텐션

셀프 어텐션의 의미와 이점

어텐션 함수는 주어진 Query에 대해 모든 Key와의 유사도를 각각 구합니다. 이후 해당 유사도를 가중치로 하여 Key와 매핑되어 있는 각각의 Value에 반영해줍니다. 마지막으로 유사도가 반영된 Value를 모두 가중합하여 리턴합니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

여기까지가 앞서 배운 어텐션의 개념입니다. 셀프 어텐션은 단지 어텐션을 자기 자신에게 수행한다는 의미입니다. 아래는 기존 seq2seq에서 어텐션을 사용할 경우의 Q, K, V의 정의입니다.

Q = Query : t 시점의 디코더 셀에서의 은닉 상태
K = Keys : 모든 시점의 인코더 셀의 은닉 상태들
V = Values : 모든 시점의 인코더 셀의 은닉 상태들

사실 t 시점이라는 것은 계속 변화하면서 반복적으로 쿼리를 수행하므로 결국 전체 시점에 대해 일반화를 할 수도 있습니다.

Q = Querys : 모든 시점의 디코더 셀에서의 은닉 상태들
K = Keys : 모든 시점의 인코더 셀의 은닉 상태들
V = Values : 모든 시점의 인코더 셀의 은닉 상태들

이처럼 기존에는 디코더 셀의 은닉 상태가 Q이고, 인코더 셀의 은닉 상태가 K라는 점에서 Q와 K는 서로 다른 값을 가지고 있습니다. 하지만 셀프 어텐션에선 Q, K, V가 전부 동일합니다.

Q: 입력 문장의 모든 단어 벡터들
K: 입력 문장의 모든 단어 벡터들
V: 입력 문장의 모든 단어 벡터들

우선 셀프 어텐션을 통해 얻을 수 있는 대표적인 효과에 대해서 이해해봅시다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

위 예시 문장을 번역하면 "그 동물은 길을 건너지 않다. 왜냐하면 그것은 너무 피곤했기 때문이다" 입니다. 여기서 그것(it)에 해당하는 것은 사람 눈에는 당연히 동물(animal)이겠지만, 기계는 길(street)인지, 동물인지 알기 쉽지 않습니다. 셀프 어텐션은 입력 문장 내의 단어들끼리 유사도를 구함으로써 그것(it)이 동물(animal)과 연관되었을 확률이 높다는 것을 찾아냅니다.

Q, K, V 벡터 얻기

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

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

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

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

기존의 벡터로부터 더 작은 벡터는 가중치 행렬을 곱하여 완성됩니다. 각 가중치 행렬은 $d_{model} \times (d_{model}/num\_heads)$의 크기를 가집니다. 이 가중치 행렬은 훈련 과정에서 학습됩니다. 즉 논문과 같이 $d_{model}=512$이고 $num\_heads=8$라면, 각 벡터에 3개의 서로 다른 가중치 행렬을 곱하고 64의 크기를 가지는 Q, K, V벡터를 얻어냅니다. 위의 그림은 단어 벡터 중 student 벡터로부터 Q, K, V벡터를 얻어내는 모습을 보여줍니다. 모든 단어 벡터에 위와 같은 과정을 거치면 I, am, a, student는 각각의 Q, K, V벡터를 얻습니다.

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

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

어텐션 함수의 종류는 다양한데, 트랜스포머에선 내적만을 사용하는 어텐션 함수 $score(q, k) = q \times k$가 아니라 여기에 특정값으로 나눠준 어텐션 함수인 $score(q, k) = q \times k / \sqrt n$를 사용합니다. 이렇게 스케일링을 추가했기 때문에 스케일드 닷-프로덕트 어텐션이라고 합니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

위의 그림은 단어 I에 대한 Q벡터가 모든 K벡터에 대해서 어텐션 스코어를 구하는 것을 보여줍니다.

어텐션 스코어는 단어 I가 I, am, a, student 각 단어와 얼마나 연관되어 있는지를 보여주는 수치입니다. 트랜스포머에서는 두 벡터의 내적값을 스케일링하는 값으로 K벡터의 차원을 나타내는 $d_k$에 루트를 씌운 $\sqrt d_k$사용합니다. 앞에도 설명했지만 $d_k$는 $d_{model}/num\_heads$라는 식에 따르므로 $d_k$는 8입니다.

128, 32 수치는 저자가 임의로 가정한 것이라고 합니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

이제 어텐션 스코어에 소프트맥스 함수를 사용하여 어텐션 분포(Attention Distribution)를 구하고, 각 V벡터와 가중합하여 어텐션 값(Attention Value)을 구합니다. 이를 단어 I에 대한 어텐션 값(=컨텍스트 벡터)이라고 부릅니다. 이제 am에 대한 Q벡터, a에 대한 Q벡터, student에 대한 Q벡터에 대해서도 모두 동일한 과정을 반복하여 각각에 대한 어텐션 값을 구합니다. 이때 단어마다 연산을 별도로 진행하지 않고 행렬 연산으로 일괄 처리하는 방법이 있습니다.

행렬 연산으로 일괄 처리하기

위의 과정을 벡터가 아닌 행렬 연산으로 이해해봅시다. 우선, 각 단어 벡터마다 일일히 가중치 행렬을 곱하는 것이 아니라 문장 행렬에 가중치 행렬을 곱하여 Q 행렬, K 행렬, V행렬을 구합니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

여기서 Q 행렬을 K 행렬을 전치한 행렬과 곱해준다고 해봅시다. 이렇게 되면 각각의 단어의 Q벡터와 K벡터의 내적이 각 행렬의 원소가 되는 행렬이 결과로 나옵니다.

&amp;amp;lt;출처: 딥 러닝을 이용한 자연어 처리 입문&amp;amp;gt;

다시 말해 위 그림의 결과 행렬의 값에 전체적으로 $d_k$를 나누어주면 이는 각 행과 열이 어텐션 스코어값을 가지는 행렬이 됩니다. 예를 들어 I 행과 student 열의 값은 I의 Q벡터와 student의 K 벡터의 어텐션 스코어와 동일한 행렬이 된다는 것입니다. 즉, 어텐션 스코어 행렬입니다. 어텐션 스코어 행렬을 구했다면 남은 것은 어텐션 분포를 구하고, 이를 사용하여 모든 단어에 대한 어텐션 값을 구하는 일입니다. 이는 간단하게 어텐션 스코어 행렬에 소프트맥스 함수를 사용하고, V 행렬을 곱하는 것으로 해결됩니다. 이렇게 하면 각 단어의 어텐션 값을 모두 가지는 어텐션 값 행렬이 결과로 나옵니다.

다음 편에 계속됩니다.

참조
딥 러닝을 이용한 자연어 처리 입문(https://wikidocs.net/31379)
728x90
반응형