AIML/딥러닝 최신 트렌드 알고리즘

[ 딥러닝 논문 리뷰 - PRMI Lab ] - Transformer: Attention is all you need (NIPS, 2017)

Hyunseo😊 2023. 7. 25. 16:39

이번에는 DeTR을 읽기 위해 "Attention Is All You Need (NIPS, 2017)" 에서 소개된 Transformer에 대해 사전 지식으로서 알아가 보도록 하겠습니다.

먼저 논문과 함께 아래와 같은 자료들을 참고했습니다.

https://wikidocs.net/31379

 

16-01 트랜스포머(Transformer)

* 이번 챕터는 앞서 설명한 어텐션 메커니즘 챕터에 대한 사전 이해가 필요합니다. 트랜스포머(Transformer)는 2017년 구글이 발표한 논문인 Attention i…

wikidocs.net

How faster?

논문에서는 기존에 RNN모델을 사용하지 않음으로써 Transformer를 기존의 자연어 처리 분야에서 더 뛰어난 성능을 내게 할 수 있었다고 합니다. 즉 어떻게 RNN을 사용하지 않고, encoder-decoder 구조를 설계하였음에도 번역 성능에서도 RNN보다 우수한 성능을 냈는지 알아보겠습니다.

Parallelization

Transformer를 한마디로 말하자면, Parallelization(병렬화)라고 할 수 있습니다. 쉽게 말해서 Transformer는 일을 최대한 한 번에 처리한다는 것입니다. 기존에 seq2seq + attention 방법에서는 번역할 때마다 하나하나씩 encoder를 갱신해 나가면서 번역을 진행했었습니다, transformer는 이러한 과정을 한번에 하겠다는 것이죠.

 

트랜스포머 (Transformer)

이전 seq2seq 구조에서는 encoder-decoder에서 각각 하나의 RNN이 t개의 시점을 가지는 구조였다면 이번에는 encoder-decoder라는 단위가 아래와같이 N개로 구성되는 구조입니다. 본 "Attention is all you need"라는 논문에서는 해당 구조의 개수를 6개 사용했습니다.

 

Transformer model

위 2번째 그림에서는 encoder로부터 정보를 전달받아 decoder가 출력 결과를 만들어 내는 Transformer 구조를 보여줍니다. decoder는 기존의 seq2seq 구조처럼 심볼 <sos>를 입력으로 받아 종료 심볼 <eos>가 나올 때까지 연산을 진행하게 됩니다.

 

Transformer의 encoder-decoder는 단순히 각 단어의 임베딩 벡터를 입력받는 것이 아니라 임베딩 벡터에서 조정된 값을 입력받는데 이에 대해 알아보기 위해 입력 부분을 자세히 알아보겠습니다.

 

포지셔널 인코딩 (Positional Encoding)

RNN이 자연어 처리에서 유용했던 이유는 단어의 위치에 따라 단어를 순차적으로 입력받아서 처리하는 특성으로 인해 각 단어의 위치 정보를 가질 수 있다는 점에 있었습니다.

 

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

 

tranformer with positional encoding layer

위의 그림에서 포지셔닝 벡터가 임베딩 벡터에 더해지는 과정을 볼 수 있습니다. 이가 어떤 값이기에 위치 정보를 반영해줄 수 있는 것인지 두개의 함수를 보겠습니다.

 

positional encoding에 사용되는 sin, cos 함수

sin, cos함수의 그래프를 보면 요동치는 값의 형태를 생각할 수 있습니다. Transformer는 이로한 값을 임베딩 벡터에 더해주므로서 단어의 순서 정보를 더하여 줍니다.

위그림에서 (pos, i)가 무엇인지 나타나있습니다. $pos$는 임베딩 벡터의 위치, $i$는 임베딩 벡터 내의 차원의 인덱스를 의미합니다. 위의 수식에서 $(pos, 2i)$일 때는 사인 함수를 사용하고, $(pos, 2i + 1)$일 때는 코사인 함수를 사용하고 있음에 주목해야 합니다.

 

그리고 $d_{model}$은 트랜스포머의 모든 층의 출력 차원을 의미하는 Transformer의 하이퍼파라미터입니다. 임베딩 벡터 또한 $d_{model}$의 차원을 가지는데 위의 그림에서는 마치 4로 표현되어 있지만 실제 논문에서는 512의 값을 가집니다.

 

위와같은 포지셔널 인코딩 방법을 사용하면 순서 정보가 보존되는데, 예를 들어 각 임베딩 벡터에 포지셔널 인코딩의 값을 더하면 같은 단어라고 하더라도 문장 내의 위치에 따라서 트랜스포머의 입력으로 들어가는 임베딩 벡터의 값이 달라집니다. 

 

어텐션 (Attention)

Transformer에서 사용되는 3가지의 어텐션 방법입니다. 첫번째 그림인 Self-Attention은 encoder에서 이루어지지만, 두번째 그림인 Masked Decoder Self-Attention과 세번째 그림인 encoder-decoder Attention은 decoder에서 이루어집니다. 

 

 

Self-attention은 본질적으로 Query, Key, Value가 동일한 경우를 말합니다. 반면, 세번째 그림 encoder-decoder Attention에서는 Query가 디코더의 벡터인 반면에 Key와 Value가 인코더의 벡터이므로 셀프 어텐션이라고 부르지 않습니다.

Encoder-Decoder Attention에서는 당연히 치팅을 하지 않기 위해 앞선 단어 벡터들만을 참조합니다.

 

인코더의 셀프 어텐션 : Query = Key = Value
디코더의 마스크드 셀프 어텐션 : Query = Key = Value
디코더의 인코더-디코더 어텐션 : Query : 디코더 벡터 / Key = Value : 인코더 벡터

Transformer 구조

 

위 그림은 Transformer의 아키텍처에서 위에서 소개한 세가지 Attention이 각각 어디에서 이루어지는지를 보여줍니다. 세 개의 어텐션에 추가적으로 '멀티 헤드' 라는 이름이 붙혀져 있습니다. 이는 Transformer가 어텐션을 병렬적으로 수행하는 방법을 말합니다.

 

CNN 과의 비교

CNN

CNN이 문장을 어떻게 인코딩하는지를 나타낸 것입니다. 합성곱 필터가 단어를 하나씩 넘기면서 차례대로 읽어 들이는 걸 알 수 있습니다.  위와같이 CNN은 합성곱 필터 크기를 넘어서는 문맥을 읽어내기 어렵다는 단점이 있습니다.

 

RNN 과의 비교 

RNN역시 아래와 같이 소스 언어 시퀀스를 인코딩 할 때, 차례대로 처리합니다.

RNN

이와같은 RNN은 시퀀스의 길이가 길어질 수록 정보 압축의 문제가 발생합니다. 오래 전에 입력된 단어는 잊어버리거나, 특정 단어 정보를 과도하게 반영해 전체 정보를 왜곡하는 경우가 자주 생긴다는 것입니다.

 

어텐션과 비교

아래 그림을 보면, 어텐션이 왜 좋고 transformer에서 왜 사용하는지 직관적으로 캐치할 수 있을 겁니다. cafe에 대응하는 소스 언어의 단어는 카페입니다. 이는 소스 시퀀스의 초반부에 등장하는데, cafe라는 단어를 디코딩해야 할 때 카페를 반드시 참조해야 합니다. 어텐션이 없는 단순 RNN을 사용하면 모델이 잊었을 가능성이 크고, 이 때문에 번역 품질이 낮아질 수 있습니다.

 

CAFE의 어텐션

 

어텐션은 이러한 문제점을 해결합니다. 디코더 쪽 RNN에 어텐션을 추가하는 방식으로 말이죠. 어텐션은 디코더가 타깃 시퀀스를 생성할 때 소스 시퀀스 전체에서 어떤 요소에 주목해야 할지 알려주므로 카페가 소스 시퀀스 초반에 등장하거나 소스 시퀀스의 길이가 길어지더라도 번역 품질이 떨어지는 것을 막을 수 있습니다.

 

어텐션의 특징 및 장점

셀프 어텐션은 자기 자신에 수행하는 어텐션입니다. 

'거기'의 셀프 어텐션

잘 학습된 셀프 어텐션 모델이라면 거기에 대응하는 장소는 카페라는 사실을 알아챌 수 있을 겁니다. 그뿐만 아니라 거기는 갔었어와도 연관이 있음을 확인할 수 있습니다. 

 

'카페'의 셀프 어텐션

위도 마찬가지 입니다. 

 

셀프 어텐션은 입력 시퀀스 전체에 대응해서 수행합니다. 이처럼 개별 단어와 전체 입력 시퀀스를 대상으로 어텐션 계산을 수행해 문맥 전체를 고려하기 때문에 지역적인 문맥만 보는 CNN대비 강점이 있습니다. 그리고 단어들 서로가 서로를 1:1로 바라보게 해서 시퀀스의 길이가 길어지더라도 정보를 잊거나 왜곡할 염려가 없어 RNN의 한계를 극복했다고 할 수 있습니다.

 

인코더 (Encoder)

트랜스포머는 num_layers개수의 인코더 층을 쌓습니다. 논문에서는 총 6개개의 인코더 층을 사용합니다. 그리고 하나의 인코더 층은 위와같이 총 2개의 서브층으로 나뉩니다. 

 

Self-Attention과 FFNN 신경망입니다. 위의 그림에서는 Multi-head Self-Attention과 Position-wise FFNN이라고 적혀있지만, Muli-head Self-Attention은 Self-Attention을 병렬적으로 사용하였다는 의미이고, Position-wise FFNN은 우리가 알고있는 일반적인 피드포워드 신경망입니다.

 

인코더의 셀프 어텐션

어텐션 함수는 쿼리(Query)에 대해서 모든 키(Key)와의 유사도를 구합니다. 그리고 구해낸 이 유사도를 가중치로 하여 키와 매핑되어 있는 각각의 값(Value)에 반영해줍니다. 

 

기존에 seq2seq에서 어텐션을 사용할 경우 Q, K, V의 정의는 아래와 같습니다.

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

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

 

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

셀프 어텐션을 통해 아래와 같은 효과를 가질 수도 있습니다.

advantage of self-attention

'그 동물은 길을 건너지 않았다. 왜냐하면 그것은 너무 피곤하였기 때문이다' 라는 의미의 문장이 있습니다. 그런데 여기서 그것(it)에 해당하는 것은 길(street)인지, 동물(animal)인지 헷갈립니다. 우리는 피곤한 주체가 동물이라는 것을 아주 쉽게 알 수 있지만, 기계는 그렇지 않습니다. 하지만 셀프 어텐션은 입력 문장 내의 단어들끼리의 유사도를 구하므로서 그것(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로 하였습니다. 

 

Q, K, V벡터 생성 과정

기존의 벡터로부터 더 작은 벡터는 가중치 행렬을 곱함으로서 완성됩니다. 각 가중치 행렬은 $d_{model} x (d_{model} / num_heads)$의 크기를 가지게 됩니다. 이 가중치 행렬은 훈련 과정에서 학습됩니다. 위 그림은 단어 벡터 중 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.k$가 아니라 여기에 특정값으로 나눠준 어텐션 함수인 $score(q,k) = q.k/sqrt(n)$을 사용합니다. 이러한 함수를 사용하는 것이 닷-프로덕트에서 값을 스케일링하는 것을 추가하였다고 해서 스케일드 닷-프로덕트 어텐션이라고 합니다.

스케일드 닷-프로덕트 어텐션

위의 그림에서 어텐션 스코어는 각각 단어 I가 단어 I, am, a, student와 얼마나 연관되어 있는지를 보여주는 수치입니다. 트랜스포머에서는 두 벡터의 내적값을 스케일링하는 값으로 K벡터의 차원을 나타내는 $d_{k}$에 루트를 씌운 $sqrt(d_{k})$를 사용하는 것을 선택했습니다. 이는 $d_{model} / num_heads$이므로 64의 값을 가지므로 $sqrt(d_{k})$는 8의 값을 가지게 됩니다.

 

attention distribution

이제 어텐션 스코어에 소프트맥스 함수를 사용하여 어텐션 부포(Attention Distribution)을 구하고, 각 V벡터와 가중합하여 어텐션 값(Attention Value)를 구합니다. 이를 단어 I에 대한 컨텍스트 벡터(context-vector)라고 할 수 있습니다. 근데 이를 굳이 Q벡터마다 일일히 따로 연산해줄 필요가 있을까요??

 

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

이는 각 단어에 대한 Q, K, V벡터를 구하고 스케일드 닷-프로덕트 어텐션을 수행하였던 위의 과정들은 벡터 연산이 아니라 행렬 연산을 사용하여 일괄 계산이 가능해집니다.

행렬 연산으로 이루어짐

Q행렬을 K행렬을 전치한 값과 곱해준다고 해 보겠습니다. 이렇게 되면 각각의 Q벡터가 K벡터의 내적이 각 행렬의 원소가 되는 행렬이 결과로 나오게 됩니다.

다시 말해 위의 그림의 결과 행렬의 값에 전체적으로 $sqrt(d{k})$를 나누어주면 이는 각 행과 열이 어텐션 스코어를 가지는 행렬이 됩니다. 이제 남은건 어텐션 분포를 구하고, 이를 사용하여 모든 단어에 대한 어텐션 값을 구하는 과정입니다.

위의 과정은 행렬 연산을 통해 모든 값이 일괄 계싼 되는 과정을 식으로 보여주는 것입니다. 실제 트랜스포머 논문에 기재된 아래의 수식과 정확히 일치합니다.

 

행렬의 크기를 모두 정리해보겠습니다. 우선 입력 문장의 길이를 seq_len이라고 하겠습니다. 문장 행렬의 크기는 (seq_len, $d_{model}$)입니다. 여기에 3개의 가중치 행렬을 곱해서 Q, K, V행렬을 만들어야 합니다.

 

Q, K벡터의 차원을 $d_{k}$라고 하고, V벡터의 차원을 (seq_len, $d_{v}$)가 되어야 합니다. Q, K, V행렬의 크기로부터 가중치 행렬의 크기 추정이 가능해집니다. $W^{Q}$와 $W^{K}$는 (${d_model}, $d_{k}$)의 값을 가지며, $W^{K}$는 (${d_model}, $d_{v}$)의 크기를 가집니다. 단 논문에서 $d_{k}$와 $d_{v}$의 차원은 $d_{mode} / num_heads$와 같습니다. 

 

즉 위 식을 거쳐 나오는 어텐션 행렬 a의 크기는 (seq_len, $d_{v})$가 됩니다. 

 

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

num_heads의 의미와 왜 $d_{model}$의 차원을 가진 단어 벡터를 가지고 어텐션을 하지 않고 차원을 축소시킨 벡터로 어텐션을 수행하였는지 이해해보겠습니다.

트랜스포머 연구진은 한 번의 어텐션을 하는 것보다 여러번의 어텐션을 병렬로 사용하는 것이 더 효과적으로 판단하였습니다. 그래서 $d_{model}$의 차원을 num_heads로 나누어 해당 수의 차원을 가지는 Q, K, V에 대해서 num_heads개의 병렬 어텐션을 수행합니다. 다시 말해 위에서 설명한 어텐션이 8개로 병렬로 이루어지게 되는데, 이때 각각의 어텐션 값 행렬을 어텐션 헤드라고 부릅니다. 이때 가중치 행렬 $W^{Q}, W^{K}, W^{V}$의 값은 8개의 어텐션 헤드마다 전부 다릅니다.

 

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

 

concatenate

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

 

그림상과는 다르게 실제 논문에서는 4차원을 $d_{model}$=512로 표현하고, 2차원을 $d_{v}$=64로 표현해왔기 때문에 위의 그림의 행렬의 크기에 혼동이 있을 수 있습니다. 위에서만 그냥 $d_{model}$의 크기를 8배인 16차원으로 표현하였습니다. 아래서는 다시 $d_{model}$을 4차원으로 표현합니다.

최종적으로 모두 concatenate한 행렬은 가중치 행렬 $W^{o}$을 곱하게 됩니다. 이떄 결과물인 멀티-헤드 여탠션 행렬은 인코더의 입력이였던 문장 행렬의 (seq_len, $d_{model}$)크기와 동일합니다. 

 

이전에 인코더가 6개가 실제로 논문상에서 쌓여있다고 말했는데, 이러한 인코더층의 특성을 이용해서 인코더의 출력이 다시 다른 인코더의 입력으로서 동작할 수 있게 됩니다.

multi-head attention (동빈나)

위와같은 식을 통해 multi-head attention을 진행할 수 있습니다. 그리고 여기서 input과 output의 dimension을 같도록 맞추어줍니다. 또한 당연히 softmax에 scaling을 적용해 주는걸 잊지 마시길 바랍니다.

 

mask matrix (동빈나)

그리고 위의 attention 구조에서 보았지만, mask matrix를 통해서 Attention Energies행렬과 element wise 곱하기 연산을 통해서 음의 무한대로 설정해주어서 특정 단어는 무시할 수 있도록 할 수 있습니다. 

 

패딩 마스크(Padding Mask)

padding mask

패딩 마스크는 입력 문장에 <PAD> 토큰이 있을 경우 어텐션에서 사실상 제외를 하기 위한 연산입니다. 위는 <PAD>포함된 입력 문장의 셀프 어텐션의 예제입니다. 이에 대해서 어텐션을 수행하고 어텐션 스코어 행렬을 얻는 과정은 위와 같습니다. 여기서 어텐션 스코어 행렬에서 행에 해당 하는 문장은 Query이고, 열에 해당하는 문장은 Key입니다. 그리고 Key에 <PAD>가 있는 경우에는 해당 열 전체를 마스킹해줍니다. 

마스킹 하는 방법은 위와같이 -1,000,000,000와 같은 -무한대에 가까운 수를 곱해주면 됩니다. 그러면 어텐션 스코어 행렬이 소프트맥스 함수를 지난 후에는 해당 위치의 값은 0이 되어 단어 간 유사도를 구하는 일에 <PAD>토큰이 반영되지 않게 됩니다.

 

포지션-와이즈 피드 포워드 신경망(Position-wise FFNN)

지금은 인코더를 설명하지만, 포지션-와이즈 FFNN은 인코더와 디코더에서 공통적으로 가지고 있는 서브층입니다. 앞선 인공 신경망은 결국 모두 벡터와 행렬 연산으로 표현될 수 있음을 배웠습니다.

여기서 $x$는 앞서 멀티 헤드 어텐션의 결과로 나온 (seq_len, $d_{model}$)의 크기를 가지는 행렬을 말합니다. 가중치 행렬 $W_{1}$은 ($d_{model}$, $d_{ff}$)의 크기를 가지고, 가중치 행렬 $W_{2}$은 ($d_{ff}$, $d_{model}$)의 크기를 가집니다. 논문에서 은닉층의 크기인 $d_{ff}$는 2,048의 크기를 가집니다.

 

encoder-decoder 층에서의 하이퍼파라미터

위의 그림에서 좌측은 인코더의 입력을 벡터 단위로 봤을 떄, 각 벡터들이 멀티 헤드 어텐션 층이라는 인코더 내 첫번쨰 서브 층을 지나 FFNN을 통과하는 것을 보여줍니다. 물론 오른쪽처럼 행렬로 계산이 진행되는데, 두번째 서브층을 지난 인코더의 최종 출력은 여전히 인코더의 입력 크기였던 ($seq_len$, $d_{model}$)의 크기로 보존되고 있습니다. 

 

잔차 연결(Residual connection)과 층 정규화(Layer Normalization)

위의 그림은 Position-wise FFNN을 설명할 때 앞선 그림에서 화살펴와 Add & Norm(잔차연결 과 정규화 과정)을 추가한 그림입니다. 추가된 활사표들은 서브층 이전의 입력에서 시작되어 서브층의 출력 부분을 향하고 있는 것이 주목해야합니다. 

 

잔차 연결은 ResNet에서 많이 봤으므로 생략하겠습니다. 그리고 층 정규화도 말 그대로 아래와 같이 잔차 연결의 입력을 $x$, 잔차 연결과 층 정규화 두가지 연산을 모두 수행한 후의 결과 행렬을 $LN$이라고 하였을 때, 수식을 표현하자면 아래와 같습니다. 

층 정규화를 위해서 화살표 방향으로 평균과 분산을 구하고 정규화를 합니다. 여기서 활용되는거는 $\gamma$와 $\beta$라는 학습가능한 파라미터입니다.

 

이와같이 생긴 행렬이며 이를 도입한 층 정규화의 최종 수식은 아래와 같습니다.

 

이와같은 층을 토대로 인코더를 많이 쌓으면 인코더 완성입니다.

 

인코더에서 디코더로(From Encoder To Decoder)

위에서는 인코더에 대해 정리했습니다. 이렇게 구현된 인코더는 num_layers만큼의 층 연산을 순차적으로 한 후에 마지막 층의 인코더의 출력을 디코더에게 전달합니다. 디코더도 시작되어 num_layers만큼의 연산을 하는데, 이때마다 인코더가 보낸 출력을 각 디코더 층 연산에 사용합니다.

 

디코더의 첫번째 서브층: 셀프 어텐션과 룩-어헤드 마스크

위 그림과 같이 디코더도 인코더와 동일하게 임베딩 층과 포지셔널 인코딩층을 거친 후의 문장 행렬이 입력됩니다. 트랜스포머 또한 seq2seq와 마찬가지로 교사 강요(Teacher Forcing)을 사용하여 훈련되므로 학습과정에서 디코더는 번역할 문장에 해당하는 문장행렬을 한 번에 입력받습니다. 그리고 디코더는 이 문장 행렬로부터 각 시점의 단어를 예측하도록 훈련됩니다.

 

여기서 맨 앞에서 말했던 문제점이 있습니다. 바로 seq2seq의 디코더에 사용되는 RNN 계열의 신경망은 입력 단어를 매 시점마다 순차적으로 입력받으므로 다음 단어 예측에 현재 시점을 포함한 이전 시점에 대한 단어들만 참고할 수 있었습니다. 반면, 트랜스포머는 문장 행렬로 한 번에 받으므로 현재 시점의 단어를 예측하고자 할 때, 입력 문장 행렬로부터 미래 시점의 단어까지도 참고할 수 있는 문제점이 발생합니다. 

 

이를 위해 트랜스포머의 디코더에서는 현재 시점의 예측에서 현재 시점보다 미래에 있는 단어들을 참고하지 못하도록 룩-어헤드 마스크(look-ahead mask)를 도입했습니다.

 

atten score matrix

이를 위해서 위의 Attention Score Matrix에 Mask Matrix를 마스킹해주면 됩니다.

마스킹 된 후의 어텐션 스코어 행렬의 각 행을 보면 자기 자신과 그 이전 단어들만을 참고할 수 있음을 볼 수 있습니다. 그 외에는 근본적으로 셀프 어텐션이라는 점과, 멀티 헤드 어텐션을 수행한다는 점에서 인코더의 첫번째 서브층과 같습니다.

 

디코더의 두번째 서브층: 인코더-디코더 어텐션

디코더의 두번쨰 서브층입니다. 디코더의 두번째 서브층은 멀티 헤드 어텐션을 수행한다는 점에서는 이전의 어텐션들(인코더와 디코더의 첫번째 서브층)과는 공통점이 있으나 이번에는 셀프 어텐션이 아닙니다.

 

셀프 어텐션은 Q, K, V가 같은 경우를 말하는데, 인코더-디코더 어텐션은 Query가 디코더인 행렬인 반면, Key와 Value은 인코더 행렬이기 때문입니다.

위와같이 디코더의 두번째 서브층을 확대해보면, 위와같이 인코더로부터 두 개의 화살표가 그려져 있습니다.

 

두 개의 화살표는 각각 Key와 Value를 의미하며 이는 인코더의 마지막 층에서 온 행렬로부터 얻습니다. 반면 Query는 디코더의 첫번째 서브층의 결과 행렬로부터 얻는다는 점이 다릅니다. 

 

위와같은 형태를 띕니다.