어텐션 메커니즘과 transfomer(self-attention)

platfarm tech team
mojitok
Published in
14 min readMar 10, 2019

Attention Mechanism

어텐션 메커니즘은 자연어 기계 번역을 위한 Seq2Seq 모델에 처음 도입되었습니다. 어텐션 메커니즘은 NLP 태스크 뿐만 아니라, 도메인에 관계 없이 다양하게 쓰이고 있습니다. 현재의 SOTA NLP모델들은 대부분 어텐션 메커니즘을 적용하고 있으니 최근 논문을 이해함에 있어 이해하고 넘어가야 하는 부분입니다.

코드는 이곳(https://github.com/graykode/nlp-tutorial)을 참고해주세요 .

1. Seq2Seq (링크)
2. Seq2Seq with Attention (링크)
3. Bi-LSTM with Attention (링크)
4. Transformer (링크)

1. Seq2Seq 모델에서의 어텐션 메커니즘

Seq2Seq 모델은 대중적이므로 가볍게 짚고만 넘어가겠습니다. Seq2Seq 모델에 대한 자세한 설명들은 ratsgo님의 블로그를 참조하면 볼 수 있습니다. 더불어 자세한 내용은 원작 논문인 Neural Machine Translation by Jointly Learning to Align and Translate을 참고하셔도 좋습니다. 위 논문은 Attention 메커니즘이 처음 등장한 논문이자 조경현 교수님이 쓰신 논문입니다.

번역모델(Seq2Seq) 어텐션 메커니즘의 핵심은 디코더의 특정 time-step의 output 인코더의 모든 time-step의 output 중 어떤 time-step가장 연관이 있는가입니다. 이를 수식으로 다음과 같이 나타냅니다.

  • e_ij는 스칼라 값이며, 디코더의 특정 timestep (i-1)의 아웃풋이 인코더의 특정 timestep (j)의 아웃풋과 얼마나 유사한지 나타내는 값입니다. get_att_score의 함수의 return 값은 scalar 값으로 위 식의 e_ij값을 나타냅니다.
get_att_score 함수
  • 함수 a는 s_i-1(디코더의 전 time-step의 hidden state)과 인코더의 h_j(output)를 연관짓는 alignment model이고, 이를 나타내는 함수 수식은 다양합니다. 위의 코드는 a함수를 enc_output에 linear 곱한 것을 dec_output와 내적해서 구현했습니다.
  • alpha_ij는 e_ij 벡터에 대한 Softmax를 취한 벡터로 역시 스칼라 값입니다. 현재 디코더의 아웃풋 time-step(i)이 어떤 인코더의 아웃풋 time-step(j)의 연관성을 실수로 나타냅니다.
get_att_weight 함수
  • T_x는 인코더의 총 time-step 길이를 나타냅니다.
  • F는 enc_outputs를 나타냅니다.
  • alpha_i 벡터는 위 alpha_ij 값을 encoder의 모든 time-step 만큼 가지고 있는 값입니다. 변수 attn_weights에 해당됩니다.
  • c_i 벡터는 encoder의 output(F 행렬)과 위의 alpha_i 벡터를 내적한 값으로 context = attn_weights.bmm(enc_outputs.transpose(0, 1)) 부분에 해당합니다.

2. Classification의 어텐션 메커니즘(bi-LSTM with Attention)

Seq2Seq는 encoder의 output과 decoder output의 관계로 attention을 봤다면 LSTM에서는 LSTM를 거친 모든 outputs(contextual matrix)와 LSTM의 최종 state(query)간의 Attention을 본다는 차이가 있습니다.

위의 그림은 1층 layer로 이루어진 Classification을 위한 Bi-LSTM Attention을 나타냅니다. Classification에서의 Attention은 번역 모델(Seq2Seq)와 다르게 LSTM hidden cell의 마지막 Hidden State이 어떤 time에 영향을 많이 받았는지가 포인트입니다.

bi-lstm attention
  • attn_output, attention = self.attention_net(output, final_hidden_state)을 통해 지금까지의 LSTM output과 LSTM State의 마지막 상태(final_state)를 어텐션 시킴을 알 수 있습니다.
  • context = torch.bmm(lstm_output.transpose(1, 2), soft_attn_weights.unsqueeze(2)).squeeze(2)를보면 soft_attn_weights(lstm_output과 hidden를 내적 한 후 Softmax)와 lstm_output의 내적을 통해 context 벡터를 만듭니다.
  • context 벡터와 nn.Linear(n_hidden * 2, num_classes)를 곱해, Classification을 하는데 이용하고 이를 통해 학습을 진행합니다. 이를 통해 어떤 단어(time-step)가 Classification 할때 더 많이 어텐션을 주었는지 학습 할 수 있습니다.

3. Transformer

Transformer는 RNN, LSTM없이 time 시퀀스 역할을 하는 모델입니다. RNN, LSTM 셀을 일체 사용하지 않았으나, 자체만으로 time 시퀀스 역할을 해줄 수 있는 굉장히 novel한 논문입니다. 원본 논문은 Attention Is All You Need(2017)이고, 이 논문 이후 현재의 모든 SOTA 모델들이 Transformer 구조에 기반하여 구현되었습니다. (BERT, openAI-GPT 등등) 원본 논문은 꼭 한번 읽어보시기 바랍니다. 해당 논문에 대한 요약은 (링크)에 잘 나와있습니다. 또한 일부 내용도 해당 블로그에서 참조하였음을 밝힙니다. 전체적인 Transformer 모델은 아래와 같습니다.

일반적인 Seq2Seq-Attention 모델에서의 번역 태스크의 문제는 원본 언어(Source Language), 번역된 언어(Target Language)간의 어느정도 대응 여부는 어텐션을 통해 찾을 수 있었으나, 각 자신의 언어만에 대해서는 관계를 나타낼수 없었습니다. 예를 들면 I love tiger but it is scare나는 호랑이를 좋아하지만 그것(호랑이)는 무섭다 사이의 관계는 어텐션을 통해 매칭이 가능했지만 it이 무엇을 나타내는지?와 같은 문제는 기존 Encoder-Decoder 기반의 어텐션 메커니즘에서는 찾을 수 없었습니다.

Transformer 모델을 세분화 하면 다음과 같은 구조를 가집니다.

위 그림에서 빨간색이 인코더, 파란색이 디코더를 가르키고, 자세한 설명은 다음과 같습니다.

  • 주황색 : 인코더에서 Self-Attention이 일어나는 부분
  • 하늘색 : 디코더에서 Self-Attention이 일어나는 부분
  • 노란색 : 인코더와 디코더의 Attention이 일어나는 부분

Transformer가 학습하는 전반적인 그림은 다음과 같습니다.

3–1. Positional Encoding

문장은 일반적인 임베딩과 Positional Encoding더하여 Encoder의 Layer로 들어갑니다.

예를 들어 I love you but not love him이라는 문장이라면 앞의 love와 뒤의 love은 일반적인 임베딩만을 거쳤을 때 동일한 값을 가지게 됩니다.(이는 일반적인 Word2Vec과 같은 임베딩의 문제이기도 합니다.) 하지만 Positional Encoding 이라는 주기함수에 의한 위치에 따른 다른 임베딩을 거치면 같은 단어여도 문장에서 쓰인 위치에 따라 다른 임베딩 값을 가지게 됩니다.

Positional Encoding은 get_sinusoid_encoding_table이라는 주기 함수로 구현됩니다.

이 값이 동일하게 매 Layer마다 Q, K, V로 복사되어 Encoder_Layer로 들어가 MultiHeadAttention를 거치게 됩니다.

3–2. Multi-Head Attention

MultiHeadAttention

d_model은 임베딩을 하기 위한 차원으로 보통 512를 사용하고, d_kd_v는 64를 사용합니다. 그리고 위 논문의 multi-head attention 그림에서의 h는 n_heads(number of head)를 나타내며 보통 8을 사용합니다. 이는 64 * 8 = 512이기 때문입니다.

동일한 [batch_size x len_q x d_model] shape의 동일한 Q, K, V를 만들어 [d_model, d_model] linear를 곱한 후 임베딩 차원을 8등분하여 Scaled Dot Product Atttention으로 넘겨줍니다.

Multi-Head Attention의 역할은 1문장을 여러 head로 Self-Attention 시킴에 있습니다.

“Je suis étudiant”라는 문장의 임베딩 벡터가 512차원이라면 8개 head로 나눠 64개의 벡터를 한 Scaled Dot Attention이 맡아 처리하는 것입니다. 이는 동일한 문장도 8명(8 heads)이 각각의 관점에서 보고 추후에 합치는 과정이라고도 볼 수 있습니다.

3–3. Scaled-Dot Product Attention

Scaled Dot Product Attention은 Self-Attention이 일어나는 부분입니다. 위에서 한 head당 Q(64), K(64), V(64)씩 가져가게 되는데 Self-Attention은 다음과 같습니다.

여기서 Mask(opt.) 부분은 Decoder의 Masked MultiHead Attention과 다른 Masking이고 Optional 한 부분입니다. 단순히 아래와 같은 코드를 통해 입력 dimension(=512) 중 word이 아닌 경우를 구분하는 역할을 합니다(word 입력이 끝난 후 padding 처리와 동일)

get_attn_pad_mask 함수

Q와 transposed K를 내적한후 Scaled with Softmax 함으로써 Self-Attention 시킵니다. 후 동일한 encoder에서 나온 V를 곱합니다. 위 Mask에 (Opt.)는 Optional의 약자로 구현 디테일을 의미합니다. 하지만 decoder에서의 self-attention 시에는 time 시퀀스와 같이 적용되므로 반드시 masking을 해야합니다.(Masked Multi-Head Attention) 이는 decoder시 설명하도록 하겠습니다.

Seq2Seq attention에서 F(enc_output)와 alpha_vector를 내적해서 attention의 결과인 context vector로 사용했던것 처럼, 여기서는 V와 Q와 K_T의 내적의 softmax를 내적하여 context vector로 사용합니다.

Scaled Dot Product라는 의미는 Q와 K 사이를 내적하여 어텐션을 Softmax를 통해 구하고, 그 후에 V를 내적하여 중요한 부분(Attention)을 더 살린다는 메커니즘이 내포되어있습니다.

이렇게 8개의 head(여러 관점)으로 본 것을 다시 concate하고 PoswiseFeedForwardNet시킵니다.

더불어 처음의 Q를 ResNet의 Residual Shortcut와 같은 컨셉으로 더해줍니다. 이는 층이 깊어지는 것을 더 잘 학습 시키기 위함입니다.

3–4. PoswiseFeedForwardNet

Multi Head Attention에서 각 head가 자신의 관점으로만 문장을 Self-Attention 하게 된다면 각 head에 따라 Attention이 치우쳐질 것입니다. PoswiseFeedForwardNet은 각 head가 만들어낸 Self-Attention을 치우치지 않게 균등하게 섞는 역할을 합니다. BERT 톺아보기라는 글을 보면 PoswiseFeedForwardNet의 논문 구현 방식과 최근 트렌드의 구현 방식에 대해 설명하고 있습니다.

convolution

While the linear transformations are the same across different positions, they use different parameters from layer to layer. Another way of describing this is as two convolutions with kernel size 1.

저도 conv 2개를 사용해서 kernel size 1로 구현했으며 이부분은 구현 디테일 이라고 생각합니다.

3–5. Decoder

디코더는 인코더와 동일하지만, Self-Attention시 Masked-Multi-Head Attention을 쓴다는 점이 다릅니다. Masked를 쓰는 이유는 Self-Attention시 자신의 time step 이후 word는 가려 Self-Attention 되는 것을 막는 역할을 합니다. 아래 코드와 같이 `np.triu`를 통해 한번 삼각형 행렬을 만들어 줍니다. 1이 Mask 역할을 합니다.

마지막으로 Encoder의 K와 V, Decoder의 Q를 서로 Attention 시켜 위의 Seq2Seq 모델과 동일하게 Encoder와 Decoder 사이 관계도 Attention 시켜 줍니다.

노란색 Box에서 왼쪽 2개 화살표가 Encoder의 K,V 오른쪽 화살표가 Self-Attention을 거친 Decoder의 Q입니다.

이렇게 나온 logistic을 일반적인Teacher Forcing을 통해 학습을합니다.

3–6. Inference

일반적인 Seq2Seq 모델과 동일하게 Inference시 Encoder의 들어오는 문장(번역할 문장)은 정확히 알지만, Decoder에 들어오는 문장(번역되어지는 문장)은 알지 못합니다. 따라서 시작 표시를 나타내는 <S>를 사용해서 Seq2Seq와 동일하게 Inference 합니다.

이때 Encoder은 일반 학습시의 Encoder와 동일하고, Decoder만 순차적으로 진행하기 위해 Greedy Decoder 혹은 Beam Search를 사용합니다. Greedy Decoder는 위의 그림과 같이 한 word를 넣으면 그 word와 inference를 거친 다음 word를 붙여나가는 식으로 inference를 거칩니다.

greedy_decoder 함수

위의 그림과 같이 inference word가 <E>(end point)이면 inference를 멈춥니다.

감사합니다.

참고자료

ratsgo’s blog: Sequence-to-Sequence 모델로 뉴스 제목 추출하기

Neural Machine Translation by Jointly Learning to Align and Translate

Attention Is All You Need

The Illustrated Transformer

BERT 톺아보기

안녕하세요! 플랫팜 인턴 Jeff(정태환/Tae Hwan Jung, nlkey2022@gmail.com)입니다. NLP (Sentiment Analysis) task를 진행하고 있습니다😀 NLP에 관련된 여러 논문들을 읽어보고 그 중 최근 SOTA인 Attention에 대해 글을 작성해볼 기회가 생겨 되도록 쉽게 설명해보았습니다.

--

--