HOME
home
About
home

Transformer

Transformer Architecture

트랜스포머란?

트랜스포머(Transformer)는 2017년 구글이 발표한 논문인 “Attention is all you need”에서 나온 모델로 기존의 seq2seq의 구조인 인코더-디코더를 따르면서도, 논문의 이름처럼 어텐션(Attention)만으로 구현한 모델입니다. 이 모델은 RNN을 사용하지 않고, 인코더-디코더 구조를 설계하였음에도 성능도 RNN보다 우수하다는 특징을 갖고있습니다.

기존의 seq2seq 모델의 한계

트랜스포머에 대해서 배우기 전에 기존의 seq2seq를 상기해보면, 기존의 seq2seq모델은 인코더-디코더 구조로 구성되어져 있습니다. 여기서 인코더는 입력 시쿼스를 하나의 벡터 표현으로 압축하고, 디코더는 이 벡터 표현을 통해서 출력 시쿼스를 만들어냈습니다. 하지만 이러한 구조는 인코더가 입력 시쿼스를 하나의 벡터로 압축하는 과정에서 입력 시쿼스의 정보가 일부 손실된다는 단점이 있었고, 이를 보정하기 위해 어텐션이 사용되었습니다. 그런데 어텐션을 RNN의 보정을 위한 용도가 아니라 아예 어텐션으로 인코더와 디코더를 만들어보면 어떨까요?

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

시작에 앞서 트랜스포머의 하이퍼파라미터를 정의하고자 합니다. 각 하이퍼파라미터의 의미에 대해서는 뒤에서 설명하기로하고, 여기서는 트랜스포머에는 이러한 하이퍼파라미터가 존재한다는 정도로만 이해해보도록 하겠습니다. 아래에서 정의하는 수값은 트랜스포머를 제안한 논문에서 사용한 수값으로 하이퍼파라미터는 사용자가 모델 설계시 임의로 변경할 수 있는 값들입니다.
dmodel=512d_{model} = 512 트랜스포머의 인코더와 디코더에서의 정해진 입력과 줄력의 크기를 의미합니다. 임베딩 벡터의 차원 또한 이며, 각 인코더와 디코더가 다음 층의 인코더와 디코더로 값을 보낼 때에도 이 자원을 유지합니다. 논문에서는 512 입니다.
num_layers = 6 트랜스포머에서 하나의 인코더와 디코더를 층으로 생각하였을 때, 트랜스포머 모델에서 인코더와 디코더가 총 몇층으로 구성되었는지를 의미합니다. 논문에서는 인코더와 디코더를 각각 총 6개 쌓았습니다.
numheads = 8 트랜스포머에서는 어텐션을 사용할 때, 1 번 하는 것 보다 여러 개로 분할해서 병렬로 어텐션을 수행하고 결과값을다시 하냐로 합치는 방식을 택했습니다. 이때 이 병렬의 개수를 의미합니다.
dffd_{ff} =2048 트랜스포머 내부에는 피드 포워드 신경망이 존재합니다. 이때 은닉층의 크기를 의미합니다. 피드 포워드 신경망의 입력층과 줄력층의 크기는 입니다.

트랜스포머 구조

‘나’ 위의 그림은 인코더로부터 정보를 전달받아 디코더가 출력 결과를 만들어내는 트랜스포머 구조를 보여줍니다. 디코더는 마지 기존의 seq2seq 구조처럼 시작 심볼 <sos>를 입력으로 받아 종료 심볼 <eos>가 나올 때까지 연산을 진행합니다. 이는 RNN은 사용되지 않지만 여전히 인코더-디코더의 구조는 유지되고 였음을 보여줍니다. 이제 트랜스포머의 내부 구조를 조금씩 확대해가는 방식으로 트랜스포머를 이해해봅시다. 우선 인코더와 디코더의 구조를 이해하기 전에 트랜스포머의 입력에 대해서 이해해보겠습니다. 트랜스포머의 인코더와 디코더는 단순히 각 단어의 임베딩 벡터들을 입력받는 것이 아니라 임베딩 벡터에서 조정된 값을 입력받는데 이에 대해서 알아보기 위해 입력 부분을 확대해보겠습니다.

포지셔널 인코딩(Positional Encoding)

트랜스포머의 내부를 이해하기 전에 우선 트랜스포머의 입력에 대해서 알아보겠습니다. RNN 이 자연어 저리에서 유용했던 이유는 단어의 위치에 따라 단어를 순차적으로 입력받아서 처리하는 RNN의 특성으로 인해 각 단어의 위치 정보(position information)를 가질 수 있다는 점에 였었습니다. 하지만 트랜스포머는 단어 입력을 순차적으로 받는 방식이 아니므로 단어의 위지 정보를 다른 방식으로 알려줄 필요가 있습니다. 트랜스포머는 단어의 위치 정보를 얻기 위해서 각 단어의 임베딩 벡터에 위지 정보들을 더하여 모델의 입력으로 사용하는데, 이를 포지셔널 인코딩(positional encod ing)이라고합니다.
그림은 입력으로 사용되는 임베딩 벡터들이 트랜스포머의 입력으로 사용되기 전에 포지셔널 인코딩값이 더해지는 것을 보여줍니다. 임베딩 벡터가 인코더의 입력으로 사용되기 전에 포지셔널 인코딩값이더해지는 과정을 시각화하면 아래와 같습니다.
포지셔널 인코딩 값들은 어떤 값이기에 위치 정보를 반영해줄 수 있는 것일까요? 트랜스포머는 위지 정보를 가진 값을 만들기 위해서 아래의 두 개의 함수를 사용합니다.
PE(pos,2i)=sin(pos100002idmodel )PE(pos,2i+1)=cos(pos100002idmodel )P E_{(p o s, 2 i)}=\sin \left(\frac{p o s}{10000^{\frac{2 i}{d_{\text {model }}}}}\right) \quad P E_{(p o s, 2 i+1)}=\cos \left(\frac{p o s}{10000^{\frac{2 i}{d_{\text {model }}}}}\right)
사인 함수와 코사인 함수의 그래프를 상기해보면 요동치는 값의 형태를 생각해볼 수 있는데, 트랜스포머는 사인 함수와 코사인 함수의 값을 임베딩 벡터에 더해주므로서 단어의 순서 정보를 더하여 줍니다. 그런데 위의 두 함수에는 pospos, ii, dmodeld_{model} 등의 생소한 변수들이 였습니다. 위의 함수를 이해하기 워해서는 위에서 본 임베딩 벡터와 포지셔널 인코덩의 덧샘은 사실 임베딩 벡터가 모여 만들어진 문장 벡터-행렬과 포지셔널 인코덩 행렬의 덧샘 연산을 통해 이루어진다는 점을 이해해야 합니다.
CODE

어텐션 (Attention)

트랜스포머에서 사용되는 세 가지의 어텐션에 대해서 간단히 정리해봅시다. 지금은 큰 그럼을 이해하는것에만집중합니다.
첫번째 그림인 셀프 어텐션은 인코더에서 이루어지지만, 두번째 그림인 셀프 어텐션과 세번째 그림인 인코더-디코더 어텐션은 디코더에서 이루어집니다. 셀프 어텐션은 본질적으로 Query, Key, Value가 동일한 경우를 말합니다. 반면, 세번째 그림 인코더-디코더 어텐션에서는 Query가 디코더의 벡터인 반면에 Key와 Value가 인코더의 벡터이므로 셀프 어텐션이라고 부르지 않습니다.
위 그림은 트랜스포머의 아키텍처에서 세 가지 어텐션이 각각 어디에서 이루어지는지를 보여줍니다. 세 개의 어텐션에 추가적으로 멀티 헤드'라는 이름이 붙어있습니다. 뒤에서 설명하겠지만, 이는 트랜스포머가 어텐션을 병렬적으로 수행하는 방법을 의미합니다.

인코더(Encoder)

트랜스포머는 하이퍼파라미터인 num_layers 개수의 인코더 층을 쌓습니다. 논문에서는 총 6개의 인코더층을 사용하였습니다. 인코더를 하나의 층이라는 개념으로 생각한다면, 하나의 인코더 층은 크게 총 2개의 서브층(sublayer) 으로 나뉘어집니다. 바로 셀프 어텐션과 피드 포워드 신경망입니다. 위의 그림에서는 멀티 헤드 셀프 어텐션과 포지션 와이즈 피드 포워드 신경망이라고 적혀있지만, 멀티 헤드셀프 어텐션은 셀프 어텐션을 병렬적으로 사용하였다는 의미고, 포지션 와이즈 피드 포워드 신경망은 우리가 알고있는 일반적인 피드 포워드 신경망입니다. 우선 셀프 어텐션에 대해서 알아봅시다.

셀프 어텐션의 의미와 이점

어텐션 함수는 주어진 쿼리(Query)’에 대해서 모든 '키 (Key)’와의 유사도를 각각 구합니다. 그리고 구해낸 이 유사도를 가중치로 하여 키와 맵핑되어있는 각각의 ’값 (Value)’에 반영해줍니다. 그리고 유사도가 반영된 ’값(Value)’을 모두 가중합하여 리턴합니다.
Q = Query : t 시점의 디코더 셀에서이 은닉 상태 K = Keys : 모든 시점으| 인코더 셀의 은닉 상태들 V = Values : 모든 시점의 인코더 셀의 은닉 상대들
그런데 사실 t 시점이라는 것은 계속 변화하면서 반복적으로 쿼리를 수행하므로 결국 전제 시점에 대해서 일반화를 할 수도 있습니다.
Q = Querys : 모든 시점의 디코너 셀에서의 은닉 상태들 K = Keys : 모든 시점의 인코너 셀의 은닉 상태들 V = Values : 모든 시점의 인코더 셀의 은닉 상대들
이처럼 기존에는 디코더 셀의 은닉 상태가 Q이고 인코더 셸의 은닉 상태가 K라는 점에서 Q와 K가 서로 다른 값을 가지고 있었습니다. 그런데 셀프 어텐션에서는 Q, K, V가 전부 동일합니다. 트랜스포머의 셀프 어텐션에서의 Q, K, V는 아래와 같습니다.
Q = Querys : 모든 시점의 디코더 셀에서의 은닉 상태들 K = Keys : 모든 시점의 인코더 셀의 은닉 상대급 V = Values : 모든 시점의 인코더 셀의 은닉 상대들
셀프 어텐션에 대한 구제적인 사항을 배우기 전에 셀프 어텐션을 통해 얻을 수 있는 대표적인 효과에 대해서 이해해봅시다.
위의 그림은 트랜스포머에 대한 구글 AI 불로그 포스트에서 가져왔습니다. 위의 예시 문장을 번역하면 '그 동물은 길을 건너지 않았다. 왜냐면 그것은 너무 피곤하였기 때문이다.’ 라는 의미가 됩니다. 그런데 여기서 그것(it)에 해당하는 것은 과연 길 (street)일까요? 동물 (anima|) 일까요? 우리는 피곤한 주제가 동물이라는 것을 아주 쉽게 알 수 있지만 기계는 그렇지 않습니다. 하지만 셀프 어텐션은 입력 문장 내의 단어들끼리 유사도를 구하므로서 그것(it)이 동물(animal) 과 연관되었을 확률이 높다는 것을 찾아냅니다.

Q, K, V 벡터 얻기

앞서 셀프 어텐션은 입력 문장의 단어 벡터들을 가지고 수행한다고 하였는데, 사실 셀프 어텐션은 인코더의 초기 입력인 d_model 의 자원을 가지는 단어 벡터들을 사용하여 셀프 어텐션을 수행하는 것이 아니라 우선 각 단어 벡터들로부터 Q벡터, K 벡터, V벡터를 얻는 작업을 거칩니다. 이때 이 Q벡터, K 벡터, V벡터들은 초기 입력인 d_model 의 자원을 가지는 단어 벡터들보다 더 작은 차원을 가지는데, 논문에서는 dmodeld_{model}= 512 의 차원을 가졌던 각 단어 벡터들을 64의 차원을 가지는 Q벡터, K벡터, v벡터로 변완하였습니다. 64라는 값은 트랜스포머의 또 다른 하이퍼파라미터인 num_heads 로 인해 결정되는데, 트랜스포머는 dmodeld_{model} 을 num_heads로 나눈 값을 각 Q벡터, K벡터, V벡터의 차원으로 결정합니다. 논문에서는 num_heads 를 8로하였습니다. 이제 그림을 통해 이해해봅시다. 예를 들어 여기서 사용하고 였는 예문 중 student라는 단어 벡터를 Q,K,VQ, K, V의 벡터로 변완하는 과정 을 보겠습니다.
기존의 벡터로부터 더 작은 벡터는 가중지 행렬을 곱하므로서 완성됩니다. 각 가중지 행렬은 dmodeld_{model} ×(dmodelnum heads)\times\left(\frac{d_{model}} {\text{num heads}}\right)의 크기를 가집니다. 이 가중지 행렬은 훈련 과정에서 학습됩니다. 즉, 논문과 갈이 dmodeld_{model} = 512 이고 num_heads = 8라면, 각 벡터에 3개의 서로 다른 가중지 행렬을 곱하고 64의 크기를 가지는 Q,K,VQ, K, V 벡터를 얻어냅니다. 위의 그림은 단어 벡터 중 student 벡터로부터 Q,K,VQ, K, V 벡터를 얻어내는 모습을 보여줍니다. 모든 단어 벡터에 위와 같은 과정을 거치면 I, am, a, student는 각각의 Q,K,VQ, K, V 벡터를 얻습니다.

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

Q,K,VQ, K, V 벡터를 얻었다면 지금부터는 기존에 배운 어텐선 메커니즘과 동일합니다. 각 QQ 벡터는 모든 KK 벡터에 대해서 어텐션 스코어를 구하고, 어텐션 분포를 구한 뒤에 이를 사용하여 모든 VV 벡터를 가중합하여 어텐션 값 또는 컨텍스트 벡터를 구하게 됩니다. 그리고 이를 모든 Q 백터에 대해서 반복합니다. 그런데 앞서 어텐션 챕터에서 어텐션 함수의 종류는 다양하다고 언급한 바 있습니다. 트랜스포머에서는 어텐션 챕터에 사용했던 내적만을 사용하는 어텐션 함수 score(q,k)score(q, k) = qkq • k 가 아니라 여기에 특정값으로 나눠준 어텐션 함수인 score(q,k)score( q, k) = q • k/ ../n 를 사용합니다. 이러한 함수를 사용하는 어텐션을 어텐선 챕터에서 배운 닷-프로덕트 어텐션 (dot-product attention) 에서 값을 스케일링하는 것을 주가하였다고 하여 스케일드 닷-프르덕트 어텐션(Scaled dot-product Attention) 이라고 합니다. 이제 그림을 통해 이해해봅시 다.
우선 단어 II 에 대한 QQ 벡터를 기준으로 설명해보겠습니다. 지금부터 설명하는 과정은 am에 대한 QQ벡터, a에 대한 Q벡터, student에 대한 Q벡터에 대해서도 모두 동일한 과정을 거집니다. 위의 그림은 단어 I에 대한 Q 벡터가 모든 K 벡터에 대해서 어텐션 스코어를 구하는 것을 보여줍니다. 위의 128과 32는 저자가 임의로 가정한 수지로 신경쓰지않아도좋습니다. 위의 그림에서 어텐선 스코어는 각각 단어 II 가 단어 I, am, a, student와 얼마나 연관되어 있는지를 보여주는 수치입니다. 트랜스포머에서는 두 벡터의 내적값을 스케일링하는 값으로 KK 벡터의 자원을 나타내는 dkd_k에 루트를 씌운 값사용하는 것을 택했습니다. 앞서 언급하였듯이 논문에서 dkd_k 는 라는 식에 따라서 64의 값을 가지므로 dk\sqrt{d_{k}} 는 8의 값을가집니다.

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

사실 각 단어에 대한 Q,K,VQ, K, V 벡터를 구하고 스케일드 닷-프로덕트 어텐선을 수행하였던 위의 과정들은 벡터 연산이 아니라 행렬 연산을 사용하면 일괄 계산이 가능합니다. 지금까지 벡터 연산으로 설명하였던 이유는 이해를 돕기 위한 과정이고, 실제로는 행렬 연산으로 구현됩니다. 위의 과정을 벡터가 아닌 행렬 연산으로 이해해봅시다. 우선, 각 단어 벡터마다 일일히 가중치 행렬을 곱하는 것이 아니라 문장 행렬에 가중치 행렬을 곱하여 QQ행렬, KK행렬, VV행렬을 구합니다.
이제 행렬 연산을 통해 어텐선 스코어는 어떻게 구할 수 있을까요? 여기서 Q 행렬을 K 행렬을 전치한 행렬과 곱해준다고 해봅시다. 이렇게 되면 각각의 단어의 Q 벡터와 K 벡터의 내적이 각 행렬의 원소가 되는 행렬이 결과로 나옵니다.
다시 말해 위의 그림의 결과 행렬의 값에 전제적으로 J를 나누어주면 이는 각 행과 열이 어텐선 스코어값을 가지는 행렬이 됩니다. 예를 들어 I 행과 student 열의 값은 I 의 Q벡터와 student의 K 벡터의 어텐션 스코어와 동일한행렬이 된다는 것입니다. 즉, 어텐선 스코어 행렬입니다. 어텐선 스코어 행렬을 구하였다면 남은 것은 어텐션 분포를구하고, 이를 사용하여 모든 단어에 대한 어텐션 값을 구하는 일입니다. 이는 간단하게 어텐선 스코어 행렬에 소프트맥스 함수를 사용하고, V 행렬을 곱하는 것으로 해결됩니다. 이렇게 되면 각 단어의 어텐션 값을 모두 가지는 어텐션 값 행렬이 결과로 나옵니다.
위의 그림은 행렬 연산을 통해 모든 값이 일괄 계산되는 과정을 식으로 보여줍니다. 해당 식은 실제 트랜스포머 논문에 기재된 아래의 수식과 정확하게 일치하는 식입니다.
Attention(Q,K,V)=softmax(QKTdk)V\operatorname{Attention}(Q, K, V)=\operatorname{softmax}\left(\frac{Q K^{T}}{\sqrt{d_{k}}}\right) V
위의 행렬 연산에 사용된 행렬의 크기를 모두 정리해봅시다. 우선 입력 문장의 길이를 seq_len이라고 해봅시다. 그렇다면 문장 행렬의 크기는 ((seq_len,dmodeld_{model})) 입니다. 여기에 3개의 가중지 행렬을 곱해서 Q,K,VQ, K, V 행렬을 만들어야합니다.
우선 행렬의 크기를 정의하기 워해 행렬의 각 행에 해당되는 QQ 벡터와 KK 벡터의 크기를 dkd_k라고 하고, VV 벡터의 크기를 dvd_v 라고 해봅시다. 그렇다면 QQ 행렬과 KK 행렬의 크기는 (seq_len, dkd_k) 이며, VV행렬의 크기는 (seq_len, dvd_v) 가 되어야 합니다.
그렇다면 문장 행렬과 Q,K,VQ, K, V 행렬의 크기로부터 가중치 행렬의 크기 추정이 가능합니다. WQW^QWKW^K는 (dmodeld_{model}, dkd_k) 의 크기를 가지며, WVW^V 는 (dmodeld_{model}, dvd_v) 의 크기를 가집니다. 단, 논문에서는 dkd_kdvd_v 의 크기는 dmodeld_{model} 와 같습니다.
즉, (dmodelnum heads)\left(\frac{d_{model}} {\text{num heads}}\right) = dkd_k = dvd_v 입니다.
결과적으로
softmax(QKTdk)V\operatorname{softmax}\left(\frac{Q K^{T}}{\sqrt{d_{k}}}\right) V식을 적용하여 나오는 어텐션 값 행렬 a의 크기는 (seq_len, dvd_v)이 됩니다.
def scaled_do t_product _attention(query, key, value, mask): matmul_ qk = tf .matmul(query, key, transpose_b=True) depth = tf.cast(tf.shape(key)[-1], tf.float 32) logits = matmul_ qk / tf .math.sqrt(depth) if mask is not None: logits += (mask * -le9) attention_weights = tf.nn.softmax(logits, axis=-1) output = tf.matmul(attention_weights, value) return output, attention_weights
Python
복사
코드는 위의 내용을 이해했다면 어렵지 않습니다. QQ행렬과 KK행렬을 전치한 행렬을 곱하고, 소프트맥스 함수를 사용하여 어텐션 분포 행렬을 얻은 뒤에 VV 행렬과 곱합니다. 코드에서 mask가 사용되는 if문은 아직 배우지 않은 내용으로 지금은 무시 scaled_dot_p roduct _attention 함수가 정상 작동하는지 테스트를 해보겠습니다. 우선 temp_q, temp_k, temp_v라는 임의의 Query, Key, Value 행렬을 만들고, 이를 scaled_dot_p roduct_attention 함수에 입력으로 넣어 함수가 리턴하는 값을 출력해볼 겁니다.
여기서 주목할 점은 Query에 해당하는 tem p_q의 값 [O, 10, 이은 Key에 해당하는 tem p_k 의 두번째 값 [0, 10, 0]과 일치한다는 점입니다. 그렇다면 어텐선 분포와 어텐선 값은 과연 어떤 값이 나올까요?
Query는 4개의 Key값 중 두번째 값과 일치하므로 어텐션 분포는 [O, 1, 0, 0] 의 값을 가지며 결과적으로 Value의 두번째 값인 [10, 0] 이 줄력되는 것을 왁인할 수 있습니다. 이번에는 Query의 값만 다른 값으로 바꿔보고 함수를 실행해봅시다. 이번에 사용할 Query값 [0, 0, 10] 은 Key의 세번째 값과, 네번째 값 두 개의 값 모두와 일치하는 값입니다.
Query의 값은 Key의 세번째 값과 네번째 값 두 개의 값과 모두 유사하다는 의미에서 어텐선 분포는 [0, 0, 0.5, 0.5)의 값을 가집니다. 곁과적으로 나오는 값 [550, 5.5] 는 Value의 세번째 값 [100, 5] 에 0.5를 곱한 값과 네번째 값 [1000, 6] 에 0.5 를 곱한 값의 원소별 합입니다. 이번에는 하나가 아닌 3개의 Query의 값을 함수의 입력으로 사용해보겠습니다.

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

앞서 배운 어텐선에서는 dmodeld_{model} 의 자원을 가진 단어 벡터를 num_heads로 나눈 자원을 가지는 Q,K,VQ, K, V 벡터로 바꾸고 어텐선율 수행하였습니다. 논문 기준으로는 512 의 자원의 각 단어 벡터를 8로 나누어 64차원의 Q,K,VQ, K, V 벡터로 바꾸어서 어텐션을 수행한 샘인데, 이제 num_heads 의 의미와 왜 dmodel 의 자원을 가진 단어 벡터를 가지고 어텐션을 하지 않고 자원을 축소시킨 벡터로 어텐션을 수행하였는지 이해해보겠습니다.
트랜스포머 연구진은 한 번의 어텐션을 하는 것보다 여러번의 어텐션을 병렬로 사용하는 것이 더 효과적이라고 판단하였습니다. 그래서 dmodeld_{model}의 자원을 num_heads개로 나누어 dmodeld_{model}/num_heads 의 자원을 가지는 Q,K,VQ, K, V에 대해서 num_heads 개의 병렬 어텐션을 수행합니다. 논문에서는 하이퍼파라미터인 num_heads 의 값을 8로 지정하였고, 8개의 병렬 어텐션이 이루어지게 됩니다. 다시 말해 위에서 설명한 어텐션이 8개로 병렬로 이루어지게 되는데,이때 각각의 어텐션 값 행렬을 어텐선 헤드라고 부릅니다. 이때 가중치 행렬 WQW^Q, WKW^K, WVW^V 의 값은 8개의 어텐션 헤드마다전부다릅니다.
병렬 어텐선으로 얻을 수 있는 효과는 무엇일까요? 그리스로마신화에는 머리가 여러 개인 괴물 히드라나 케로베로스가 나옵니다. 이 괴물들의 특징은 머리가 여러 개이기 때문에 여러 시점에서 상대방을 불 수 있다는 겁니다. 이렇게되면 시각에서 놓치는 게 별로 없을테니까 이런 괴물들에게 기습을 하는 것이 굉장히 힘이 들겁니다. 멀티 헤드 어텐션도 똑같습니다. 어텐션을 병렬로 수행하여 다른 시각으로 정보들을 수집하겠다는 겁니다. 예를 들어보겠습니다. 앞서 사용한 예문 '그 동물은 길을 건너지 않았다. 왜냐하면 그것은 너무 피곤하였기 때문이다.를 상기해봅시다. 단어 그것(it)이 쿼리였다고 해봅시다. 즉, it에 대한 Q벡터로부터 다른 단어와의 연관도를 구하였을 때 첫번째 어텐선 헤드는 '그것(it)’과 '동물(anima|)’ 의 연관도를 높게 본다면, 두번째 어텐선 헤드는 '그것 (it)’과 '피곤하였기 때문이다(tired)' 의 연관도를 높게 불 수 있습니다. 각 어텐션 헤드는 전부 다른 시각에서 보고있기 때문입니다.

멀티 헤드 어텐션 (Multi -head Attention) 구현하기

멀티 헤드 어텐선에서는 크 게 두 종류의 가중치 행렬이 L~습니다. 바로 Q, K, V 행렬을 만들기 위한 가중치 행렬인 WO,WK,WVW^O, W^K, W^V 행렬과 바로 어텐션 헤드들을 연결 (conca tenation) 후에 곱해주는 WOW^O 행렬입니다. 가중지 행렬을 곱하는 것을 구현 상에서는 입력을 밀집층(Dense layer)를 지나게 하므로서 구현합니다. 케라스 코드 상으로 지금까지 즐기자게 사용해왔던 Dense()에 해당됩니다.
멀티 헤드 어텐션의 구현은 크게 다섯 가지 파트로 구선 1. WQ,WK,WVW^Q, W^K, W^V에 해당하는 dmodeld_{model} 크기의 밀집층(Dense layer}을 지나게한다 2. 지정된 헤드 수(num_heads)관큼 나눈다(split). 3. 스케일드 닷 프로덕트 어텐선. 4. 나눠졌던 헤드들을 연결 (concatenatetion) 한다. 5. WOW^O에 해당하는 밀집층을 지나게 한다.
코드

패딩 마스크 (Padding Mask)

스케일드 닷 프로덕트 어텐선 함수 내부를 보면 mask라는 값을 인자로 받아서, 이 mask값에다가 -le9라는 아주 작온 음수값을 곱한 후 어텐션 스코어 행렬에 더해주고 있습니다. 이 연산의 정제는 무엇일까요?
이는 입력 문장에 <PAD> 토큰이 있을 경우 어텐션에서 사실상 제외하기 위한 연산입니다. 예를 들어 <PAD>가 포함된 입력 문장의 셀프 어텐션의 예제를 봅시다. 이에 대해서 어텐션을 수행하고 어텐션 스코어(Attention Score) 행렬을 얻는 과정은 다음과같습니다.
그런데 사실 단어 <PAD>의 경우에는 실질적인 의미를 가진 단어가 아닙니다. 그래서 트랜스포머에서는 Key의 경우에 <PAD> 토큰이 존재한다면 이에 대해서는 유사도를 구하지 않도록 마스킹 (Masking)을 해주기로 했습니다. 여기서 마스킹이란 어텐선에서 제외하기 위해 값을 가린다는 의미입니다. 어텐선 스코어 행렬에서 행에 해당하는 문장은 Query이고, 열에 해당하는 문장은 Key입니다. 그리고 Key에 <PAD>가 있는 경우에는 해당 열 전제를 마스킹을 해줍니다.
마스킹을 하는 방법은 어텐션 스코어 행렬의 마스킹 위지에 매우 작은 음수값을 넣어주는 것입니다. 여기서 매우 작은 음수값이라는 것은 -1,000,000,000과 같은 -무한대에 가까운 수라는 의미입니다. 현재 어텐션 스코어 함수는 소프트맥스 함수를 지나지 않은 상태입니다. 앞서 배운 연산 순서라면 어텐션 스코어 함수는 소프트맥스 함수를 지나고, 그 후 Value 행렬과 곱해지게 됩니다. 그런데 현재 마스킹 위지에 매우 작은 음수 값이 들어가 있으므로 어텐선스코어 행렬이 소프트맥스 함수를 지난 후에는 해당 위지의 값은 0에 굉장히 가까운 값이 되어 단어 간 유사도를 구하는 일에 <PAD> 토큰이 반영되지 않게 됩니다.
위 그림은 소프트맥스 함수를 지난 후를 가정하고 있습니다. 소프트맥스 함수를 지나면 각 행의 어텐선 가중지의 총합은 1 이 되는데, 단어 <PAD> 의 경우에는 0 이 되어 어떤 유의미한 값을 가지고 있지 않습니다. 패덩 마스크를 구현하는 방법은 입력된 정수 시퀀스에서 패딩 토큰의 인덱스인지, 아닌지를 판별하는 함수를 구현하는 것입니다. 아래의 함수는 정수 시퀀스에서 0인 경우에는 1 로 변완하고, 그렇지 않은 경우에는 0으로 변완하는 함수입니다.
임의의 정수 시퀀스 입력을 넣어서 어떻게 변완되는지 보겠습니다.
prinit(create_padding_mask(tf.constant([[1,21,777,0,0]])))
Python
복사
위 벡터를 통해서 1 의 값을 가진 위치의 열을 어텐션 스코어 행렬에서 마스킹하는 용도로 사용할 수 있습니다. 위 벡터를 스케일드 닷 프로덕트 어텐션의 인자로 전달하면, 스케일드 닷 프로덕트 어텐선에서는 위 벡터에다가 매우 작은 음수값인 -le9를 곱하고, 이를 행렬에 더해주어 해당 열을 전부 마스킹하게 되는 것입니다. 첫번째 서브층인 멀티 헤드 어텐션을 구현해보았습니다. 앞서 인코더는 두 개의 서브 서브층(subla yer)으로 나뉘어진다고 언급한 적이 있는데, 이제 두번째 서브층인 포지선-와이즈 피드 포워드 신경망에 대해서 알아보겠습니다.

포지션-와이즈 피드 포워드 신경망

지금은 인코더를 설명하고 있지만, 포지선 와이즈 FFNN 은 인코더와 디코더에서 공통적으로 가지고 있는 서브층입니다. 포지선-와이즈 FFNN는 쉽게 말하면 완전 연결FFNN(Full y-connected FFNN)이라고 해석할 수 있습니다. 앞서 인공 신경망은 결국 벡터와 행렬 연산으로 표현될 수 있음을 배웠습니다. 아래는 포지션 와이즈 FFNN 의 수식을보여줍니다
FFNN(x)=MAX(O,xW1+b1)W2+b2FFNN(x) = MAX(O, xW_1 + b_1)W_2 + b_2
식을 그림으로 표현하면 아래와 같습니다.
여기서 xx 는 앞서 멀티 헤드 어텐선의 결과로 나온 (seq_len,dmodeld_{model}) 의 크기를 가지는 행렬을 말합니다. 가중지 행렬W1W_1 은 (dmodel,dffd_{ff})의 크기를 가지고, 가중지 행렬 W2W_2 은 (dffd_{ff},dmodeld_{model}) 의 크기를 가집니다. 논문에서 은닉층의 크기인 dttd_{tt}는 앞서 하이퍼파라미터를 정의할 때 언급했듯이 2,048의 크기를 가집니다. 여기서 매개변수 W1,b1,W2,b2W_1,b_1,W_2,b_2 는 하나의 인코더 층 내에서는 다른 문장, 다른 단어들마다 정확하게 동일하게 사용됩니다. 하지만 인코더 층마다는 다른 값을 가집니다.
위의 그림에서 좌측은 인코더의 입력을 벡터 단위로 봤을 때, 각 벡터들이 멀티 헤드 어텐션 층이라는 인코더 내 첫번째 서브 층을 지나 FFNNFFNN 을 통과하는 것을 보여줍니다. 이는 두번째 서브층인 Position-wise FFNN을 의미합니다 물론, 실제로는 그림의 우측과 같이 행렬로 연산되는데, 두번째 서브층을 지난 인코더의 조1 종 줄력은 여전히 인코더의 입력의 크기였던 (seq_len, dmoded_{mode}l) 의 크기가 보존되고 있습니다. 하나의 인코더 층을 지난 이 행렬은 다음인코더 층으로 전달되고, 다음 층에서도 동일한 인코더 연산이 반복됩니다.

잔차 연결과 층 정규화

인코더의 두 개의 서브층에 대해서 이해하였다면 인코더에 대한 설명은 거의 다왔습니다! 트랜스포머에서는 이러한 두 개의 서브층을 가진 인코더에 추가적으로 사용하는 기법이 있는데, 바로 Add & Norm 입니다. 더 정확히는 잔차연결 (residual connection)과 층 정규화(layer normalization) 를 의미합니다. 위의 그림은 앞서 Positi on-w ise FFNN 를 설명할 때 사용한 앞선 그림에서 화살표와 Add & Norm(잔자 연결과 정규화 과정)을 주가한 그림입니다. 추가된 화살표들은 서브층 이전의 입력에서 시작되어 서브층의 줄력 부분을 향하고 있는 것에 주목합시다. 추가된 화살표가 어떤 의미를 갖고 있는지는 잔자 연결과 층 정규화를 배우고 나면 이해할 수있습니다.

잔자 연결 (Residual connection)

잔자 연결 (residual connection) 의 의미를 이해하기 위해서 어떤 함수 에 대한 이야기를 해보겠습니다.
H(x)=x+F(x)H(x) = x + F(x)
위 그림은 입력 xxxx에 대한 어떤 함수 F(x) 의 값을 더한 함수 H(x)H(x) 의 구조를 보여줍니다. 어떤 함수 F(x)F(x) 가 트랜스포머에서는 서브층에 해당됩니다. 다시 말해 잔자 연결은 서브층의 입력과 줄력을 더하는 것을 말합니다. 앞서 언급했듯이 트랜스포머에서 서브층의 입력과 줄력은 동일한 자원을 갖고 있으므로, 서브층의 입력과 서브층의 줄력은 덧셈 연산을 할 수 있습니다. 이것이 바로 위의 인코더 그림에서 각 화살표가 서브층의 입력에서 줄력으로 향하도록그려졌던 이유입니다. 잔자 연결은 컴퓨터 비전 분야에서 주로 사용되는 모델의 학습을 돕는 기법입니다.
이를 식으로 표현하면 x+Sublayer(x)x + Sublayer(x) 라고 할 수 있습니다.
가령, 서브층이 멀티 헤드 어텐션이었다면 잔자 연결 연산은 다음과 같습니다.
H(x)=x+MultiheadAttention(x)H(x) = x + Multi — head Attention(x)
위 그림은 멀티 헤드 어텐선의 입력과 멀티 헤드 어텐선의 결과가 더해지는 과정을 보여줍니다.

층 정규화(Layer Normalization)

잔차 연결을 거진 결과는 이어서 층 정규화 과정을 거지게됩니다. 잔차 연결의 입력을 x, 잔차 연결과 층 정규화 두 가지 연산을 모두 수행한 후의 결과 행렬을 LN 이라고 하였을 때, 잔차 연결 후 층 정규화 연산을 수식으로 표현하자면 다음과같습니다.
LNLN = LLayerNorm(x+Sublayer(x)) (x + Sublayer(x))
이제 층 정규화를 하는 과정에 대해서 이해해봅시다. 층 정규화는 텐서의 마지막 자원에 대해서 평균과 분산을 구하고, 이를 가지고 어떤 수식을 통해 값을 정규화하여 학습을 돕습니다. 여기서 텐서의 마지막 dmodeld_{model}차원이란 것은 트랜스포머에서는 dmodeld_{model}차원을 의미합니다. 아래 그림은 자원의 방향을 화살표로 표현하였습니다.
층 정규화를 위해서 우선, 화살표 방향으로 각각 평균 μ과 분산 σ\sigma을 구합니다. 각 화살표 방향의 벡터를 xix_i라고 명명 해봅시다
층 정규화를 수행한 후에는 벡터 xix_ilniln_i 라는 벡터로 정규화가 됩니다.
lniln_i = LayerNormLayerNorm(xix_i)
이제 층 정규화의 수식을 알아봅시다. 여기서는 층 정규화를 두 가지 과정으로 나누어서 설명하겠습니다.
첫번째는 평균과 분산을 통한 정규화, 두번째는 감마(γ\gamma)와 베타(β\beta)를 도입하는 것입니다. 우선, 평균과 분산을 통해 벡터
x^i,k=xi,kμiσi2+ϵ\hat{x}_{i, k}=\frac{x_{i, k}-\mu_{i}}{\sqrt{\sigma_{i}^{2}+\epsilon}}
€(입실론)은 분모가 0 이 되는 것을 방지하는 값입니다. 이제 γ\gamma(감마)와 β\beta(베타)라는 벡터를 준비합니다. 단, 이들의 초기값은 각각 1 과 0 입니다.
γ\gamma 와 B를 도입한충정규화의 최종수식은 다음과 같으며,γ\gammaβ\beta는 학습 가능한 파라미터입니다.
lniln_i = γx^i+β\gamma\hat{x}_{i} + \beta = LayerNormLayerNorm(xix_i)
인코더 / 디코더 구현
소스 참조
참고자료