Transformer

Michael·2023년 11월 12일

DeepLearning

목록 보기
2/2
post-thumbnail

트랜스포머는 2017년 구글이 발표한 논문인 "Attention is all you need"에서 나온 모델이다.

이전 포스팅에서 확인한 seq2seq에서 attention은 Encoder에서 하나의 고정된 벡터로 압축하는 과정에서 일부의 정보만이 저장되는 단점을 보정하기 위해 attention 기법을 사용한 것이었다. 하지만 Attention기법만을 이용하여 Encoder와 Decoder을 구현하면 어떨까?

Transformer

Transformer의 Parameter

dmodel=512d_{model} = 512

트랜스포머의 encoder와 decoder에서 정해진 입력과 출력의 크기이다. 임베딩 벡터의 차원 또한 dmodeld_{model}이며, 각 encoder와 decoder가 다음층으로 보낼때에도 동일한 차원을 유지한다. 논문에서는 512로 설정하고 있다.

num_layer=6num\_layer = 6

encoder와 decoder의 층의 갯수를 정의한다. 논문에서는 각각의 층을 6개로 쌓았다.

num_heads=8num\_heads = 8

트랜스포머에서는 여러개의 헤드에서 병렬적으로 Attention연산을 수행한다. 이후 결과값을 하나로 합친다.

dff=2048d_{ff} = 2048

트랜스포머 내부에 피드 포워드 신경망이 존재하며 해당 신경망의 은닉층의 갯수를 의미한다. 피드 포워드의 input layer와 output layer의 크기는 dmodeld_{model}이다.

Transformer

트랜스포머에서는 seq2seq와 유사하게 Encoder와 Decoder의 구조를 채택한다. 위의 그림에서는 num_layernum\_layer의 갯수로 설정한 6개의 encoder와 decoder가 존재하는 것을 확인할 수 있다.


Decoder에서 출력값을 만들어낼때 <sos> start of sequence를 시작으로 <eos> end of sequence가 나올때 까지 연산을 진행한다.

Positional Encoding

Natural Language Processing (자연어 처리)에서 RNN이 많이 사용됐었던 이유는 무엇일까? 바로 순차적인 입력을 바탕으로 필요한 정보를 저장하였기 때문이다. 즉 각 단어의 position information(위치 정보)를 저장한 것이다.
하지만 Transformer는 순차적으로 입력을 받는 것이 아닌, 한번에 받기에 위치에 대한 정보를 각 단어의 임베딩 벡터에 위치 정보를 더하여 모델의 입력으로 사용해야 한다.
이때 사용하는 이러한 과정을 Positional Encoding이라 한다.

Transformer 의 Position Encoding은 각 단어의 위치에 따라 고유한 벡터를 생성한다.
따라서 그림과 같이 Encoder와 Decoder의 input layer에 들어가기전에, Positional Encoding이 되어서 들어가는 것을 확인할 수 있다.

PE(pos,2i)=sin(pos/100002i/dmodel)PE_{(pos, 2i)} = sin(pos/10000^{2i/d_{model}})
PE(pos,2i+1)=cos(pos/100002i/dmodel)PE_{(pos, 2i+1)} = cos(pos/10000^{2i/d_{model}})

positional은 입력 문장에서 임베딩 위치를 나타내며, i는 임베딩 벡터 내의 차원 인덱스를 의미한다.


sin함수와 cos함수를 사용하는 과정을 통하여 각 단어에 순서 정보를 더해준다. Positional Encoding의 경우 임베딩 벡터와 positional encoding matrix와의 덧셈을 통해 결과가 만들어진다.
그림에서는 dmodeld_{model}의 차원을 4로 표현하였지만 실제 논문에서는 512의 값을 사용한다.

위와 같이 positional encoding방법을 사용하면 순서가 정보가 포함된다.

그러면 도대체 왜 coscos함수와 sinsin함수를 사용하는 것일까?
우리가 기본적으로 생각하는 좌표를 더해주는 것은 벡터가 좌표만큼 더해지는 것인데, 이는 상당히 많은 왜곡을 일으킨다. 게다가 아무리 좌표를 적게 둔다고 하더라도, input의 길이(sequence length)가 매우 길어지면, 좌표가 매우 커지게 된다.
따라서 좌표를 주기함수에 대입하여 주파수를 바꾼다. 그러면 모든 좌표에 대해 다른 포지션이 만들어진다.

Quary, Key, Value란

우리가 구글에서 무엇인가를 검색하려고 할때 검색어로 사용하는 것을 QUARY라 하고, 제목으로 표시되는 것을 KEY라 한다. 그 상세 내용을 VALUE라 한다. 따라서 Quary와 가장 유사한 것을 찾기 위해서는 Key값과 유사성을 잘 파악해야 한다. 쿼리와 키 사이에 유사성을 구하기 위해서는 보통 코싸인 유사도를 사용한다.

Attention(어텐션)

트랜스포머에서 사용되는 Attention method는 총 3가지이다.

첫번째 그림의 self-attention은 Encoder에서 이루어지며, 두번째 그림의 Self-Attention과, 세번째 그림의 Attention은 Decoder에서 이루어진다.

앞의 두개의 Self Attention은 Query, Key, Value가 모두 출처가 같지만, 3번째 Attention의 경우 Query는 Decoder이지만, Key와 Value는 Encoder이다.

기존 Attention의 방식

기존 Attention함수는 주어진 QUERY에 대해서 모든 KEY값과 유사도를 비교한다. 그리고 나서 유사도를 가중치로 하여 각각의 VALUE 값과 곱하여 가중합을 반환하였다.

기존의 Attention의 과정을 조금더 자세하게 설명하면 다음과 같다.
1. encoder의 hidden state와 Decoder의 hidden state와의 Attention Score을 계산한다.
2. 각각의 attention score에 대하여 Softmax를 취하여 Attention Distribution을 구한다.
3. Attention Distribution와 hidden state를 가중합하여 Attention Value구하기
4. Decoder의 hidden state와 Attention Value를 concat한다.
5. 출력층의 Input이 되는 st~\tilde{s_t}를 계산한다.
6. st~\tilde{s_t} 을 출력층의 input으로 계산하여 yty_t를 계산한다.

QUERY : t시점의 디코더 셀에서의 은닉상태
KEY : 모든 시점의 인코더 셀에서의 은닉상태들
VALUE : 모든 시점의 인코더 셀에서의 은닉상태들

Self Attention

Self Attention의 경우 기존 Attention과 Query, Key와 Value값에 대해서 동일한 벡터를 가진다.

그러면 왜 우리는 3개의 동일한 임베딩 레이어를 사용하는 것일까?

다음 그림의 예시를 보면 it이 어떤 것을 가리키고 있는지를 예측하기 위해서 사용된다.
각 encoder와 decoder에 입력과 출력은 dmodeld_{model}차원이 들어오게 된다(논문에서는 512값 사용). 하지만 self attention은 각 단어 벡터들을 64차원을 가지는 QUERY, KEY, VALUE벡터를 사용하였다. 64차원의 벡터를 가지게 된 이유는 num_heads=8num\_heads=8로 설정하였기 때문이다.

다음의 그림과 같이 positional embedding을 거친 Positional-aware Embedding 1×41\times 4에 대하여 4×24\times 2의 Linear Layer을 거치면서 1×21\times 2를 가지는 벡터를 만든다. 이때 곱해지는 Linear Layer은 서로 다르다.

Scaled dot-product Attention

Query, Key, Value값을 구했다면, 나머지 연산은 기존 Attention연산과 동일하다. 각 Query에 대하여 Key값과 Attention Score을 구한 뒤에, 모든 Value값과 Attention Score을 가중합 하여 Attention Value(Context Vector)을 구한다.

Transformer에서는 기존 seq2seq에서 사용한 Attention function인 score(q,k)=q×kscore(q,k) = q \times k가 아닌 scaling을 추가한 Scaled dot-product Attention score인 score(q,k)=q×knscore(q,k) = \frac{q \times k}{\sqrt{n}} 를 사용한다.

다음 그림은 Query I가 I am a student와 얼마나 관련이 있는지를 보여주는 수치이다. dkd_k로는 64값을 가지게 된다. (dmodel=512,num_heads=8\because d_{model}=512, num\_heads=8)

이후 다음과 같이 Softmax함수를 취해 Attention Distribution 을 구한다. 위의 그림에서는 I(key) student(key)와 attention distribution이 0.4로 높은 것을 확인 할 수 있다. 이후 각각의 Value값과 가중합을 구한다.

하지만 위와 같이 연산을 진행할 경우 모든 Query에 대해서 반복적으로 연산을 해주어야 한다 이를 해결하기 위해 행렬 연산 을 이용하여 한번에 계산한다.

Softmax함수는 0 근처에서 gradient가 가장 크기때문에, 해당 함수를 scaling을 하여 학습을 훨씬 빠르게 할 수 있다고 논문에서 말하고 있다.


다음과 같이 Positional-aware Embedding된 input들이 들어오면, 각각의 component에 맞는 Linear Layer을 곱하여 QUERY Matrix, KEY Matrix, VALUE matrix를 구한다.

이후 Attention Value를 구하기 위해 다음과 같이 QUERYQUERYKEYTKEY^T를 행렬곱 연산을 해주면 Attention Score 값이 나오게 된다. 우리는 Scaled dot product Attention을 사용하기에, dk\sqrt{d_k}를 구해야 한다.
마지막으로 Attention Distribution을 취한 뒤, SoftMax를 구한 뒤, 이를 바탕으로 Attention Value값을 구한다.

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})\cdot V

따라서 최종 수식은 다음과 같이 나온다.

Final Output Size

Input sequence : seq_len×dmodelseq\_len \times d_{model}
QUERY : seq_len×dkseq\_len \times d_k
KEY : seq_len×dkseq\_len \times d_k
VALUE : seq_len×dvseq\_len \times d_v
WQW^Q : dmodel×dkd_{model} \times d_k
WKW^K : dmodel×dkd_{model} \times d_k
WVW^V : dmodel×dvd_{model} \times d_v
dmodelnum_heads=dk=dv\frac{d_{model}}{num\_heads} = d_k = d_v

Multi-head Attention

우리가 Self Attention에서 진행한 Attention 연산을 확인해보면, QUERY, KEY, VALUE의 차원이 dmodeld_{model}이 아닌 dkd_k이었다. 논문에서는 dmodel=512d_{model}=512num_heads=8num\_heads=8로 나눈 dk=64d_{k}=64로 진행하였다. model의 차원을 8개로 나누어 각각의 attention연산을 병렬적으로 처리했다고 볼 수 있다.
각각의 연산이 수행될 때 Attention Value는 다르다. Linear Layer인 WQ,WK,WVW^Q, W^K, W^V는 각각의 Attention head마다 다르다.

이를 바탕으로 여러 시각에서 문장을 해석할 수 있다. 예를들어 "그 동물은 길을 건너지 않았다, 왜냐하면 그것은 피곤했기 때문이다."문장이 있으면 그(it)는 동물에 집중할수도, 피곤하다에 집중할 수도 있다. 즉 여러개의 시야로 해당 문장을 분석했다는 것이다.

이후 각각의 attention head에서 수행한 연산을 concatenate한다. 따라서 다시 원래 size인 seq_len×dmodelseq\_len \times d_{model}이 된다.


이후 concatenate한 matrix에 Weight를 곱하여 최종 결과물을 낸다.

즉 가장 중요한 점은 한 층의 encoding layer가 끝났을때 형태가 여전히 seq_len×dmodelsseq\_len \times d_{models}크기로 유지된다.

Padding Mask

앞에서보면 Scaled dot-product Attention에서 Attention 함수 내부를 보면 mask라는 값을 인자로 받아 mask값에 -1e9라는 아주 작은 값을 곱한다.

다음의 그림을 보면, <PAD>는 실질적으로 특정한 의미를 가지고 있는 단어가 아니다. 따라서 <PAD>가 존재한다면, masking을 하여 의미를 파악하는데 방해하지 않도록 한다. masking을 하는 방법으로는 아주 작은 값을 곱하여 Attention score의 값을 매우 작게 한다.
이후 Softmax를 이용하여 Attention Distribution을 구할때 해당 위치의 값이 0이 되어 <PAD>가 반영되지 않도록 한다.

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

Position-wise FFNN은 encoder와 decoder가 모두 가지고 있는 서브층이다.

FFNN(x)=MAX(0,xW1+b1)W2+b2FFNN(x) = MAX(0, xW_1+b_1)W_2+b_2

FFNN의 수식은 다음과 같다.
1. 가중치를 곱한다.
2. ReLU activation function을 취한다.
3. 가중치를 곱한다.

결과의 output은 다음과 같다.

x=seq_len×dmodelx=seq\_len\times d_{model}
W1=dmodel×dffW_1=d_{model}\times d_{ff}
W2=dff×dmodelW_2=d_{ff} \times d_{model}

dffd_{ff}는 논문에서 미리 저장한 값인 2048의 크기를 가진다.
W1W_1, W2W_2는 서로 encoder층 내에서 다른 값을 가진다.

즉 다음과 같이 동일한 형태로 다음 Encoder에 들어가게 된다.

Residual-Connection(잔차 연결) & Layer Normalization(층 정규화)

Transformer에서는 두개의 sub layer을 가진 Encoder에 추가적으로 진행하는 것이 있는데, 이러한 과정이 논문에 Add & Norm 되어있다.

Residual-Connection


잔차연결의 의미를 이해하기 위해서는 H(x)H(x)를 이해해야 한다.
Transformer의 Encoder 구조에서는 Input와 output의 구조가 동일하다는 점에 있다. 따라서 Multi-head Attention 과정 이후의 결과는 size:input_seq×dmodelsize : input\_seq \times d_{model}이고, input size또한 동일하다. 따라서 두 행렬을 더할 수 있다.
따라서 input과, sublayer의 연산을 수행한 값을 더할 수 있다. 이를 수식적으로 표현하면 다음과 같다.
H(x)=x+MultiheadAttentionH(x) = x + Multi-head Attention

이를 그림으로 표현하면 다음과 같다.
Multi-head Attention에 들어가기 전의 값과 덧셈연산을 진행한다.

Layer Noramlization

Residual-Connection을 진행한 뒤에는 Layer Normalization을 진행한다.
두가지 연산을 수행했을때의 연산 결과는 다음과 같다.
LN=LayerNorm(x+Sublayer(x))LN = LayerNorm(x + Sublayer(x))

일단 가장 먼저 정규화를 하기 위해 각 층에 대한 평균과 분산을 구한다. 또한 각 층에 해당하는 화살표 벡터를 xix_i라 한다.
각 층의 정규화가 끝난 뒤에 벡터 xix_ilniln_i라는 벡터로 정의된다.
kk는 각차원을 의미하고, 평균인 μi\mu_i와 분산 σ2\sigma^2scalascala값이다.
x^i,k=xi,kμiσi2+ϵ\hat{x}_{i,k} = \frac{x_{i,k}-\mu_i}{\sqrt{\sigma_i^2+\epsilon}}
여기서 ϵ\epsilon(epsilon)이 하는 역할은 분모가 0이 되는 것을 방지한다.

이후 γ\gammaβ\beta를 도입한 층을 이용하며, γ\gammaβ\beta는 학습이 가능한 parameter이다.
lni=γx^i+β=LayerNorm(xi)ln_i=\gamma \hat{x}_i + \beta = LayerNorm(x_i)
를 통하여 layer normalizationlayer\ normalization을 끝낸 결과를 얻을 수 있다.

Layer NormalizationLayer\ Normalization을 진행하는 이유는 훈련 과정에서 데이터 분포가 변하는 것을 방지하기 위함이다. 이는 시간적으로 연속적인 데이터에서 발생하는 변동성을 효과적으로 관리한다. 이를 통하여 훈련속도를 개선시키고, 더 높은 학습률을 사용할 수 있게 한다.

Decoder

Encoder에서는 num_layersnum\_layers만큼 반복하여 encoding을 진행한다. 이후의 Encoder의 정보를 바탕으로 Decoder의 연산을 진행한다.

Self-Attention Mask & Look-ahead Mask

디코더에서는 문장을 한번에 전달 받기에 순차적으로 예측을 진행하지 못한다. 이에 따라 Look_ahead Mask를 이용하여 미리보기를 방지하는 마스크를 도입한다.

Look-ahead Attention mask는 디코더의 첫번째 층에서 진행된다.

다음과같이 자기 자신과 그 이전의 단어들에 대해서만 Attention Score을 구할 수 있도록 하였다. Look-ahead mask에서는 패딩 마스크를 포함하여 구현한다.

Encoder-Decoder Attention

Encoder-Decoder Attention layer은 decoder의 두번째 sub층이다.
여기서 진행되는 Attention의 QUERY, KEY, VALUE 다음과 같이 정의된다.

QUERY : 디코더 행렬
KEY : 인코더 행렬
VALUE : 인코더 행렬


따라서 다음과 같이 Attention Score Matrix를 구할 수 있다.

Reference

https://wikidocs.net/31379
https://velog.io/@jhbale11/%EC%96%B4%ED%85%90%EC%85%98-%EB%A7%A4%EC%BB%A4%EB%8B%88%EC%A6%98Attention-Mechanism%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

profile
Mulsanne

0개의 댓글