Home 트랜스포머-인코더
Post
Cancel

트랜스포머-인코더

트랜스포머는 자연어 처리에서 주로 사용하는 딥러닝 아키텍처 중 하나다. 트랜스포머가 추현한 뒤로, 다양한 태스크에 활용되었던 순환신경망(RNN)과 장단기 메모리(LSTM)는 트랜스포머로 대체된다. BERT, GPT, T5 등과 같은 다양한 자연어 처리(NLP)모델에 트랜스포머 아키텍처가 적용됐다.

이번 장에서는 트랜스포머의 기본적인 의미부터 이해해볼 것이다. 그 다음으로 언어 번역 태스크를 통해 인코더-디코더 형태로 어떻게 사용되는지 배운다. 이후에는 각 인코더의 구성요소가 어떻게 동작하는지 자세히 짚어볼 예정이다. 인코더에 대한 전반적인 내용을 다룬 다음 디코더를 살펴보고 디코더의 각 구성요소가 어떻게 작동하는지 알아본다. 마지막으로 인코더와 디코더를 결합해 트랜스포머의 전체 작동 흐름을 확인하자.

1.1 트랜스포머 소개

RNN과 LSTM 네트워크는 다음 단어 예측, 기계 번역, 텍스트 생성 등의 순차적 태스크에서 널리 사용된다. 하지만 이 네트워크는 장기 의존성 문제가 있다. 이런 RNN의 한계점을 극복하려고 “Attention Is All You Need”논문에서 “트랜스포머”라는 아키텍처를 제안한다. 트랜스포머는 현재 자연어 과제에서 최신 기술로 사용된다. 트랜스포머가 출현함으로써 자연어 처리 분야는 획기적으로 발전했으며 BERT, GPT-3, T5 등과 같은 혁명적인 아키텍처가 발전하는 기반이 마련되었다.

트랜스포머는 RNN에서 사용한 순환방식을 사용하지 않고 순수하게 어텐션만 사용한 모델이다. 트랜스포머는 self attention이라는 특수한 형태의 어텐션을 사용한다. 다음 절에서 셀프어텐션이 어떻게 작동하는지 자세히 다룬다.

기계번역 과제를 통해 트랜스포머가 어떤 원리로 작동하는지 살펴보자. 트랜스포머는 인코더-디코더로 구성된 모델이다. 먼저 인코더에 입력 문장 (원문)을 입력하면 인코더는 입력 문장의 표현 방법을 학습시키고 그 결과를 디코더로 보낸다. 디코더는 인코더에서 학습한 표현 결과를 입력받아 사용자가 원하는 문장을 생성 한다.

그림 1-1 트랜스포머의 인코더-디코더 구조

어떻게 작동하는 것일까? 트랜스포머의 인코더-디코더는 영어 문장 (입력 문장)을 어떻게 프랑스어 문장(타깃 문장)으로 변형하는 것일까? 인코더와 디코더는 어떤 원리로 작동할까? 인코더와 디코더를 자세히 알아보자.

1.2 트랜스포머의 인코더 이해하기

트랜스포머는 N개의 인코더가 쌓인형태다. 인코더의 결괏값은 그 다음 인코더의 입력값으로 들어간다. [그림 1-2]는 인코더가 N개로 쌓인 형태를 보여준다. 각 인코더의 결괏값은 그 위에 있는 인코더의 입력값으로 들어간다. 가장 마지막에 있는 인코더의 결괏값이 입력값의 최종 표현 결과가 된다. 최초 인코더에 대한 입력값으로 입력 문장을 넣게 되고, 최종 인코더의 결괏값으로 입력 문장에 따르는 표현 결과를 얻는다.

그림 1-2 N개로 누적된 인코더

트랜스포머 관련 논문인 “Attention Is All You Need”를 보면 $N=6$으로 되어있다. 이는 인코더 6개를 누적해서 쌓아 올린 형태를 표현한 것이다. 하지만 $N$을 다양한 값으로 지정해 인코더의 형태를 바꿀 수있다. 예를 들어 $N=2$인 경우는 다음과 같이 표현한다.

그림 1-3 N=2 인코더

인코더는 어떤 원리로 작동할까? 입력 문장으로 어떤 결괏값을 생성하는가? 이를 이해하려면 우선 인코더의 각 구성요소를 이해해야 한다. 인코더의 세부 구성 요소를 표현하면 다음과 같다.

그림 1-4 인코더의 구성 요소

인코더의 구성 요소를 보면 모든 인코더 블록은 형태가 동일하다. 또한 인코더 블록은 두 가지 요소로 구성된다.

  • 멀티 헤드 어텐션 (multi-head attention)
  • 피드포워드 네트워크 (feedforward network)

두 가지 요소가 어떻게 작동하는지 자세히 알아보자. 멀티 헤드 어텐션을 이해하려면 먼저 셀프 어텐션이 어떤 원리로 작동하는지 이해해야 한다. 셀프 어텐션의 작동 원리에 대해 살펴보도록 하자.

1.2.1 셀프 어텐션의 작동 원리

예제를 활용해 셀프 어텐션의 작동 원리를 이해해보자. 다음과 같은 문장이 있다고 가정한다.

A dog ate the food because it was hungry.

이 문장에서 ‘it’은 ‘dog’나 ‘food’를 의미할 수 있다. 하지만 문장을 자세히 살펴보면 ‘it’은 ‘food’가 아닌 ‘dog’를 의미한다는 것을 쉽게 알 수 있다. 위와 같은 문장이 주어질 경우 모델은 ‘it’이 ‘food’가 아닌 ‘dog’라는 것을 어떻게 알 수 있을까? 이때 셀프 어텐션이 필요하다.

이 문장이 입력되었을 때, 모델은 가장 먼저 단어 ‘A’의 표현을, 그 다음으로 단어 ‘dog’의 표현을 계산한 다음 ‘ate’라는 단어의 표현을 계산한다. 각각의 단어를 계산하는 동안 각 단어의 표현들은 문장 안에 있는 다른 모든 단어의 표현과 연결해 단어가 문장 내에서 갖는 의미를 이해한다.

예를 들어 ‘it’이라는 단어의 표현을 계산하는 동안 모델에서는 ‘it’이라는 단어의 의미를 이해하기 위해 문장 안에 있는 모든 단어와 ‘it’이라는 단어를 연결하는 작업을 수행한다.

[그림 1-5]는 ‘it’이라는 단어의 표현을 계산하기 위해 ‘it’을 문장의 모든 단어와 연결하는 작업을 보여준다. 이와 같은 연결 작업으로 모델은 ‘it’이 ‘food’가 아닌 ‘dog’와 관련이 있다는 것을 학습한다. ‘dog’를 잇는 선이 다른 단어보다 두껍게 표시되었다. 이는 주어진 문장 내에서 ‘it’이라는 단어가 ‘food’가 아닌 ‘dog’와 관련이 있다는 것을 보여준다.

그림 1-5 셀프 어탠션 예제

셀프 어텐션은 내부적으로 어떤 원리로 작동할까? 이제 셀프 어텐션 내부 작동 원리를 자세히 알아보자.

입력 문장이 ‘I am good’이라고 가정해보자. 이 문장을 기준으로 각 단어의 임베딩을 추출한다. 여기서 임베딩이란 각각의 단어를 표현하는 벡터값을 의미하며, 임베딩값은 모델 학습 과정에서 같이 학습된다.

$x_1$ 을 ‘I’, $x_2$를 ‘am’, $x_3$를 ‘good’에 대한 임베딩값이라고 하자. 각각의 값을 표현하면 다음과 같다.

  • 단어 ‘I’에 대한 임베딩: $x_1=[1.76, \ 2.22, \ \cdots, \ 6.66]$
  • 단어 ‘am’에 대한 임베딩: $x_2=[7.77, \ 0.631, \ \cdots, \ 5.35]$
  • 단어 ‘good’에 대한 임베딩: $x_3=[11.44, \ 10.10, \ \cdots, \ 3.33]$

이제 입력 문장 “I am good”을 [그림 1-6]과 같이 입력 행렬 $X$(임베딩 행렬 또는 입력 임베딩)로 표현할 수 있다.이 입력 행렬 $X$(임베딩 행렬 또는 입력 임베딩)로 표현할 수 있다.

그림 1-6 입력 행렬

행렬 $X$에서 첫 번째 행은 ‘I’의 임베딩, 두 번째 행은 ‘am’의 임베딩, 세 번째 행은 ‘good’의 임베딩을 의미한다. 이때 행렬 $X$의 차원은 [문장 길이 x 임베딩 차원]의 형태가 된다. 위 문장에서 단어의 수(문장의 길이)는 3이고 임베딩 차원은 512라고 가정하면 입력 행렬 (입력 임베딩)의 차원은 [3 x 512]가 된다.

이제 입력 행렬 $X$로 부터 쿼리 $Q$ 행렬, 키 $K$ 행렬, 밸류 $V$ 행렬을 생성한다. 이 행렬은 무엇이고, 왜 필요할까? 이 세가지 행렬은 셀프 어텐션에서 사용된다. 이 행렬들을 어떻게 사용하는지 살펴보자.

우선 쿼리, 키, 밸류 행렬을 어떻게 만드는지 알아보자. 행렬을 생성하기 위해서는 $W^Q, W^K, W^V$라는 3개의 가중치 행렬을 생성한 다음 이 가중치 행렬을 입력행력 $X$에 곱해 $Q, K,V$를 생성한다.

이때 가중치 행렬 $W^Q, W^K, W^V$는 처음에 임의의 값을 가지며, 학습 과정에서 최적값을 얻는다. 학습을 통해 최적의 가중치 행렬값이 생성되면 더욱 정확한 쿼리값, 키값, 밸류값을 얻게 된다.

[그림1-7]에서 볼 수 있듯이 입력 행렬값에서 가중치 행렬 $W^Q, W^K, W^V$를 곱하면 쿼리, 키, 밸류값을 얻을 수 있다.

그림 1-7 쿼리, 키, 밸류 행렬 생성

[그림 1-7]을 통해 다음과 같은 내용을 알 수 있다.

  • 쿼리, 키, 밸류의 첫 번째 행인 $q_1,k_1, v_1$은 단어 ‘I’에 대한 쿼리, 키, 밸류 벡터를 의미한다.
  • 쿼리, 키, 밸류의 첫 번째 행인 $q_2,k_2, v_2$은 단어 ‘am’에 대한 쿼리, 키, 밸류 벡터를 의미한다.
  • 쿼리, 키, 밸류의 첫 번째 행인 $q_3,k_3, v_3$은 단어 ‘good’에 대한 쿼리, 키, 밸류 벡터를 의미한다.

이때 쿼리, 키, 밸류 벡터의 차원이 64라고 가정하면 쿼리, 키, 밸류 행렬의 차원은 [문장 길이 x 64] 가 된다. 예제 문장의 세 가지 단어에 대한 쿼리, 키, 밸류 행렬의 차원은 [3 x 64]가 된다.

그런데 왜 이런 형태로 계산해야 할까? 쿼리, 키, 밸류 행렬은 어떻게 사용되는가? 이와 같은 방법의 장점은 무엇인가? 자세히 알아보도록 하자.

셀프 어텐션의 작동 원리 이해하기

앞에서 $Q, K, V$ 행렬 계산 방법과 해당 행렬을 입력 행렬로 얻을 수 있다는 것을 배웠다. 이제 쿼리, 키, 밸류 행렬을 셀프 어텐션에 어떻게 사용하는지 보자.

앞서 단어의 표현을 계산하기 위해 셀프 어텐션은 각 단어를 기준으로 주어진 문장에 있는 모든 단어와 연결하는 과정을 수행하다는 사실을 배웠다. ‘I am good’이라는 문장을 예로 들어 보자. 단어 ‘I’의 표현을 계산하려면, [그림 1-8]처럼 단어 ‘I’와 전체 문장에 있는 단어와 연결하는 과정을 수행한다.

그림 1-8 셀프 어텐션 예제

위와 같은 방법을 적용하는 이유는 무엇일까? 특정 단어와 문장 내에 있는 모든 단어가 어떤 연관이 있는지를 이해하면 좀 더 좋은 표현을 학습시키는데 도움이 된다. 이제 셀프 어텐션이 쿼리, 키, 밸류 값을 사용해 특정 단어와 문장 내에 있는 모든 단어를 연결하는 방법을 알아본다. 셀프 어텐션은 총 4단계로 이루어져 있다. 각 단계를 하나씩 짚어보자.

1단계

셀프 어텐션의 첫 번째 단계는 $Q$ 행렬과 $K^T$행렬의 내적 연산을 수행한다.

그림 1-9 쿼리, 키 행렬

쿼리 행렬 $Q$와 키 행렬 $K^T$의 내적 연산 결과는 다음과 같다.

그림 1-10 쿼리, 키 행렬 내적 연산 결과

이때 쿼리와 키 행렬 사이 내적을 계산하는 이유는 무엇일까? 연산 결과는 정확히 무엇을 의미하는가? $Q\cdot K^T$의 결과에 대해 알아보자.

[그림 1-11]에서 $Q\cdot K^T$의 첫 번째 행을 보자. 첫 번째 행은 쿼리 벡터 $q_1(I)$과 키 벡터 $k_1(I), k_2(am), k_3(good)$의 내적을 계산한다는 것을 알 수 있다. 두 벡터 사이의 내적을 계산하면 두 벡터가 얼마나 유사한지를 알 수 있다.

즉, 쿼리 벡터 $(q_1)$과 키 벡터 $(k_1, k_2, k_3)$ 사이의 내적을 계산하는 것은 쿼리 벡터 $q_1(I)$와 키 벡터 $k_1(I), k_2(am), k_3(good)$ 사이의 유사도를 계산한 것이다. $Q\cdot K^T$ 행렬의 첫 번째 행을 보면 단어 ‘I’는 단어 ‘am’과 ‘good’보다 자신 (I)과 연관성이 더 높은 것을 알 수 있다. $q_1\cdot k_1$의 내적값이 $q_1\cdot k_2, q_1\cdot k_3$ 보다 높기 때문이다.

그림 1-11 쿼리벡터와 키 벡터의 내적 계산 결과

2단계

다음 단계는 $Q\cdot K^T$ 행렬의 키 벡터 차원의 제곱근 값으로 나눈 것이다. 이와 같은 방법을 적용하면 안정적인 경삿값(gradient)을 얻을 수 있다.

$d_k$를 키 벡터의 차원(dimension)이라고 하자. 그러면 $Q\cdot K^T$를 $\sqrt{d_k}$로 나누면 된다. 위의 예제에서 키 벡터의 차원은 64이다. 64의 제곱근인 8로 $Q\cdot K^T$를 나눈다.

그림 1-14 d_k의 제곱근으로 나누기

3단계

이전 단계에서 계산한 유사도 값은 비정규화된 형태(unnormalized form)다. 따라서 소프트맥스 함수를 사용해 정규화 작업을 진행한다. 소프트 맥스 함수를 적용하면 전체 값의 합은 1이되면 각각 0과 1사이의 값을 갖는다. 2단계의 결과에 소프트맥스 함수를 적용하면 다음과 같은 결과를 얻는다.

그림 1-15 소프트맥스 함수 적용 결과

이러한 행렬을 스코어 행렬이라고 한다. 위 점수를 바탕으로 문장 내에 있는 각 단어가 문장에 있는 단어와 얼마나 연관되어 있는지 알 수 있다. 예를 들어 스코어 행렬의 첫 행을 보면 단어 ‘I’는 자기 자신과 90%, 단어 ‘am’과는 10%, 단어 ‘good’과는 3% 관련되어 있다는 것을 알 수 있다.

4단계

지금까지 쿼리, 키 행렬에 대해 내적을 계산하고, 소프트맥스 함수를 사용해 내적값에 대한 정규화 작업을 진행했다. 그 다음 과정은 어텐션 행렬 $Z$를 계산하는 것이다.

어텐션 행렬은 문장의 각 단어의 벡터값을 갖는다. 앞에서 계산한 스코어 행렬인 $softmax({QK^T\over \sqrt{d_k}})$에 밸류 행렬 $V$를 곱하면 어텐션 행렬 $Z$를 구할 수 있다. 다음은 어텐션 행렬을 계산한 결과이다.

어텐션 행렬 $Z$는 각 점수를 기준으로 가중치가 부여된 벡터의 합으로 계산한다. 계산 결과를 한 줄 씩 살펴보자. 첫 마지막으로

\[z_1=0.9 \cdot v_1(I)+0.07\cdot v_2(am)+0.03\cdot v_3(good)\]

위 식처럼 단어 ‘I’의 셀프어텐션 $z_1$은 각 밸류 벡터값의 가중치 합으로 계산된다. 즉 $z_1$의 값은 밸류 벡터 $v_1(I)$의 90% 값과 밸류 벡터 $v_2(am)$의 7% 값과 밸류 백터 $v_3(good)$의 3%값의 합으로 구한다.

이런 계산방법의 장점은 무엇일까? “A dog ate the food because it was hungry”를 예로 들어 답해보자. 여기서 단어 ‘it’은 ‘dog’를 의미한다. 이때 단어 ‘it’에 대해 위의 방법으로 셀프 어텐션을 계산해 다음과 같은 결과를 얻었다고 가정해보자.

\[z_n=0.0\cdot v_1(A)+1.0\cdot v_2(dog)+\cdots +0.0\cdot v_9(hungry)\]

단어 ‘it’의 셀프 벡터값은 밸류 벡터 $v_2(dog)$가 100% 반영된 결과로 볼 수 있다. 이는 모델에서 ‘it’이 ‘food’가 아닌 ‘dog’와 관련이 있다는 것을 알 수 있다. 셀프 어텐션 방법을 적용하면 단어가 문장 내에 있는 다른 단어와 얼마나 연관성이 있는지를 알 수 있다.

어텐션 행렬은 문장 내에 있는 단어의 셀프 백터 값으로 구성된 것을 알 수 있으며 다음 식으로 그 값을 구할 수 있다.

\[Z=softmax({QK^T\over \sqrt{d_k}})V\]

지금까지 살펴본 셀프 어텐션의 단계를 요약하면 다음과 같다.

  1. 쿼리 행렬과 키 행렬 간의 내적을 계산하고($Q\cdot K^T)$ 유사도 값을 산출한다.
  2. $Q\cdot K^T$를 키 행렬 차원의 제곱근$(\sqrt{d_k})$로 나눈다.
  3. 스코어 행렬에 소프트맥스 함수를 적용해 정규화 작업을 진행한다. $(softmax({QK^T\over \sqrt{d_k}})).$
  4. 마지막으로 스코어 행렬에 밸류 행렬을 곱해 어텐션 행렬 $Z$를 산출한다.

그림으로 표현하자면 다음과 같다.

그림 1-22 셀프 어텐션 메커니즘

셀프 어텐션은 쿼리와 키 벡터의 내적을 계산한 다음 $\sqrt{d_k}$로 나누기 때문에 scale dot product 어텐션이라고도 부른다.

1.2.2 멀티 헤드 어텐션 원리

어텐션을 사용할 때 헤드 한 개만 사용한 형태가 아닌 헤드 여러 개를 사용한 어텐션 구조도 사용할 수 있다. 앞에서 어텐션 행렬 $Z$ 행렬을 계산하는 방법을 배웠다. 단일 어텐션 행렬 $Z$이 아닌 다중 어텐션 행렬을 계산해보자. 이런 계산법은 어떤 이점이 있을까?

“All is well”이라는 문장으로 예를 들어 보자. 단어 “well”의 셀프 어텐션을 계산한다고 하자. 계산 결과 다음과 같은 결과를 얻었다고 가정하자.

\[Z_{well}=0.6\cdot v_1(All) + 0.0\cdot v_2(is) + 0.4\cdot v_3(well)\]

위 식에서 볼 수 있듯이 단어 “well”의 셀프 백터값은 가중치를 적용한 각 단어의 벡터값의 합임을 알 수있다. 위의 내용을 좀 더 자세히 들여다보면 단어 “well’의 벡터값은 단어 “All”이 가장 우세하게 작용하는 것을 알 수 있다.

하지만 문장 내에서 단어의 의미가 모호한 경우 역시 발생할 수 있다. 다음 문장을 예를 들어 보자.

A dog ate the food becuase it was hungry.

이 예제에서 단어 ‘it’의 셀프 어텐션을 계산해서 다음과 같은 결과를 얻었다고 가정하자.

\[z_{it}=0.0\cdot v_1(A)+1.0\cdot v_2(dog)+\cdots + 0.0\cdot v_9(hungry)\]

이 때 단어 ‘it’의 의미는 ‘dog’ 또는 ‘food’가 될 수 있는데 위의 결과는 단어의 의미가 잘 연결된 경우다.

따라서 문장 내에서 모호한 의미를 가진 단어가 있을 경우에 앞의 예와 같이 적절한 의미를 가진 단어의 벡터값이 잘 할당되었을 경우에는 문장의 의미를 이해하는 데 좊은 영향을 줄 수있다. 하지만 그 반대의 경우, 즉, 의미가 맞지 않는 단어의 벡터값이 높을 경우에는 문장읭 의미가 잘 못 해석될 수 있다. 그래서 어텐션 결과의 정확도를 높이기 위해서 단일 헤드 어텐션 행렬이 아닌 멀티 헤드 어텐션을 사용한 후 그 결괏값을 더하는 형태로 진행한다. 이와 같은 방법을 사용하는데는 단일 헤드 어텐션을 사용하는 것보다 멀티 헤드 어텐션을 사용하면 좀 더 명확하게 문장의 의미를 이해할 수 있다는 가정이 깔려있다. 이제 멀티 헤드 어텐션에 대해 좀 더 자세히 알아보자.

$h$개의 어텐션 행렬을 구할 수 있을 것이다. 8개의 어텐션 행렬을 구한다고 하면 해당 행렬을 계산한 후에 그 결과를 연결한 후 새로운 가중치 행렬 $W^O$를 곱하면 최종적으로 우리가 원하는 행렬곱을 구할 수 있다.

\[mha = concatenate(Z_1, Z_2, \cdots, Z_8)\cdot W_0\]

1.2.3 위치 인코딩으로 위치 정보 학습

“I am good”이라는 입력 문장이 있다고 가정하자. RNN에서는 단어 단위로 네트워크에 문장을 입력한다. 처음에 “I”라는 단어가 입력으로 전달된 다음에 “am”이라는 단어가 전달된다. 네트워크는 문장을 완전히 이해하기 위해 문장을 단어 단위로 나누어서 입력한다. 하지만 트랜스포머 네트워크에서는 위와 같은 순환구조를 따르지 않는다. 단어 단위로 문장을 입력하는 대신에 문장 안에 있는 모든 단어를 병렬 형태로 입력한다. 병렬로 단어를 입력하는 것은 학습 시간을 줄이고 RNN의 장기 의존성 문제를 해결하는 데 도움이 된다.

하지만 트랜스포머에서 단어를 병렬로 입력하면 한 가지 문제가 발생한다. 그것은 바로 단어의 순서 정보가 유지되지 않은 상태에서 문장의 의미를 어떻게 이해할 수 있느냐는 점이다.

문장의 의미를 이해하기 위해서는 단어의 순서 (문장의 단어의 위치 정보)가 매우 중요하다. 문장의 의미를 명확히 이해하려면 단어의 위치 정보를 반드시 이해해야 한다.

“I am good”이라는 문장으로 돌아가서, 처음에 문장 안의 각 단어에 대해 임베딩값을 얻는다. 이때 임베딩의 차원을 $d_{model}$이라고 한다. 여기서 임베딩 차원의 값을 4라고 한다면 문장에 대한 입력 행의 차원은 [문장 길이 x 임베딩 차원] = [3 x 4]가 된다.

“I am good” 이라는 문장을 입력행렬(임베딩 행렬)로 표현하면 다음과 같다.

그림 1-25 입력 행렬

입력 행렬을 트랜스포머에 바로 입력하면 단어의 순서 정보를 이해할 수 없다. 입력 행렬을 트랜스포머에 직접 전달하는 대신 네트워크에서 문장의 의미를 이해할 수 있도록 단어의 순서를 표현하는 정보를 추가로 제공해야 한다. 이를 위해 위치 인코딩이라는 새로운 방법을 활용한다. 위치 인코딩이라는 이름에서 알 수 있듯이 문장에서 단어의 위치를 나타내는 인코딩이다.

위치 인코딩 행렬 $P$의 차원은 입력행렬 $X$의 차원과 동일하다. 이제 입력 행렬을 트랜스포머에 직접 입력하기 전에 위치 인코딩을 포함한다. 즉 입력 행렬 $X$에 위치 인코딩 $P$를 더한 후 네트워크에 입력하는 것이다. 이제 입력 행렬은 단어의 임베딩 뿐 아니라 문장의 단어 위치 정보도 포함한다.

그림 1-26 입력 행렬에 위치 인코딩 행렬값 추가

그렇다면 위치 인코딩은 어떻게 계산되는 것일까? “Attention Is All You Need” 논문의 저자는 위치 인코딩을 계산하는데 사인파 함수를 사용했다.

\[P(pos, 2i)=\sin({pos\over 1000^{2i/d_{model}}}) \\ P(pos, 2i+1)=\cos({pos\over 1000^{2i/d_{model}}})\]

위의 식에서 $pos$는 문장에서의 단어의 위치를 의미하고, $i$는 해당 위치의 임베딩을 의미한다. 예제를 통해 해당 식의 의미를 이해해보자. 위의 식을 사용해 위치 인코더 행렬을 표현하면 다음과 같다.

\[P=\begin{bmatrix} \sin({pos\over10000^0}) \ \cos({pos\over10000^0}) \ \sin({pos\over10000^{2/4}}) \ \cos({pos\over10000^{2/4}}) \\ \\ \sin({pos\over10000^0}) \ \cos({pos\over10000^0}) \ \sin({pos\over10000^{2/4}}) \ \cos({pos\over10000^{2/4}}) \\ \\ \sin({pos\over10000^0}) \ \cos({pos\over10000^0}) \ \sin({pos\over10000^{2/4}}) \ \cos({pos\over10000^{2/4}}) \end{bmatrix}\]

위치 인코딩 $P$를 계산한 후 임베딩 행렬 $X$에 요소별 합 (element-wise addition)을 수행한 후 인코더의 입력 행렬로 입력한다.

인코더의 아키텍처를 다시 살펴보자. [그림 1-31]의 단일 인코더 블록을 보면 인코더에 입력 데이터는 입력 임베딩을 구한 다음 위치 인코딩을 합한 후 인코더에 입력하는 것을 알 수 있다.

그림 1-31 단일 인코더 블록

1.2.4 피드포워드 네트워크

인코더 블록 내 서브레이어에서 피드포워드 네트워크는 다음과 같이 표현할 수 있다.

그림 1-32 인코더 블록

피드포워드 네트워크는 2개의 전결합층(dense layer)과 ReLU 활성화 함수로 구성된다. 피드포워드 네트워크의 변수(parameter)는 문장의 다른 위치에서는 동일하고 인코더 블록에서는 다르게 나타난다.

1.2.5 add와 norm 요소

인코더에서 중요한 요소 중 하나는 바로 add와 norm관련 부분이다. 이 부분은 서브레이어의 입력과 출력 부분에 연결되어 있다. 이는 [그림 1-33]에서 점선으로 표시된 부분이다. 이 부분을 자세히 보면 다음과 같이 구성됐다는 것을 알 수있다.

  • 서브레이어에서 멀티 헤드 어텐션의 입력값과 출력값을 서로 연결한다.
  • 서브레이어에서 피드포워드의 입력값과 출력값을 서로 연결한다.

그림 1-33 add와 norm이 추가된 인코더 블록

add 와 norm 요소는 기본적으로 레이어 정규화 (layer normalization)과 잔차 연결(residual connection)이다. 레이어 정규화는 각 레이어 값이 크게 변화하는 것을 방지해 모델을 더 빠르게 학습할 수 있게 한다.

1.2.6 모든 인코더 구성 요소 통합

[그림 1-34]는 인코더 2개가 누적된 상태를 보여준다. 간결하게 표현하기 위해 인코더 1만 자세하게 표현했다.

그림 1-34 인코더 1을 확장한 인코더 누적

위의 예제처럼 인코더를 $N$개 누적해서 생성한다. 최상위 인코더의 출력값(인코더 표현)은 주어진 인코더에 대한 표현값이 된다. 최종 인코더에서 얻은 인코더 표현을 $R$이라고 하자.

$R$은 디코더의 입력값으로 들어간다. 디코더는 인코더 표현을 입력값으로 사용하고 타깃 문장을 생성한다.

This post is licensed under CC BY 4.0 by the author.

적대적 공격에 저항하는 딥러닝 모델을 향하여 (Towards Deep Learning Models Resistant to Adversarial Attacks - Aleksnader Madry, Aleksandar Makelov, Ludwig Schmidt, Dimitris Tsipras, Adrian Vladu)

트랜스포머-디코더