본문 바로가기
딥러닝관련/기초 이론

Transformer 정리

by 머리올리자 2021. 11. 9.

 

 

 

 

 

 

 

 

2021.11.08 - [딥러닝관련/기초 이론] - seq2seq, attention 정리

 

seq2seq, attention 정리

최근에 transformer 관련된 이야기가 많이 나와 한 번 정리해보고자 한다. Transformer를 알기 위해서는 그 전에 Attention 메커니즘이 무엇인지 알아야 한다. (computer vision 관련 분야가 메인인데 자연어까

better-tomorrow.tistory.com

 

 

위 글에 이어 transformer를 정리해보고자 한다.

(내가 아는 트랜스포머는 마이클베이의 트랜스포....)

출처 : https://ww.namu.la/s/332758c033a53cbba59c9be7f283f979a9bd9e47d15d0b32a303f3e623ae8f94e74f7a7f1368840b85c08e80e56e7446d5cb25031f26a2212a52e81333c2af271dc159c04da5bb0d79dfbc528e4d9844df1a25c5a64305599fb80c027ab5cd8e

 

Transformer가 나온 논문은 아래와 같다고 한다.

https://arxiv.org/abs/1706.03762

 

Attention Is All You Need

The dominant sequence transduction models are based on complex recurrent or convolutional neural networks in an encoder-decoder configuration. The best performing models also connect the encoder and decoder through an attention mechanism. We propose a new

arxiv.org

[모델 구조]

- seq2seq 구조

- encoder-decoder

- attention만으로 모델 구현

- RNN 사용하지 않음

 


[seq2seq 모델의 한계]

 

- Encoder가 입력 sequence를 하나의 vector로 압축하는 과정에서 정보가 일부 손실된다. (문제점)

(-> 이를 해결하기 위해 attention이 사용됨)

 

-> 그런데 attention을 RNN의 보정을 위한 용도가 아니라 아예 어텐션으로 encoder와 decoder를 만들면?

 

 


[transformer의 주요 하이퍼파라미터]


- encoder와 decoder에서의 정해진 입력과 출력의 크기

- embedding 차원도 이에 해당

- 각 encoder와 decoder가 다음 층의 encoder와 decoder로 값을 보낼 때도 이 차원을 유지

 


- encoder decoder의 layer 수

 


- attention 사용 시, 1번 하는 것 보다 여러 개로 분할해서 병렬로 attention을 수행

- 결과값을 다시 하나로 합치는 방식

- 이 때 병렬의 개수

 


- hidden layer의 크기


[transformer]

출처 : https://wikidocs.net/images/page/31379/transformer1.PNG

- seq2seq와 같이 encoder-decoder 구조.

 

다른점

 

- encoder, decoder 단위가 N개 존재할 수 있다는 점.

 

이전 seq2seq는 encoder-decoder에서 각각 하나의 RNN이 t개의 시점을 가지는 구조라면,

 

트랜스포머는 encoder-decoder라는 단위가 N개로 구성되는 구조

 

아래를 보면 encoder-decoder를 6개씩 사용

 

출처 : https://wikidocs.net/images/page/31379/transformer2.PNG

 

아래는 encoder로부터 정보를 받아 decoder가 출력을 만들어내는 트랜스포머 구조

출처 : https://wikidocs.net/images/page/31379/transformer4_final_final_final.PNG

단순하게 보면 RNN이 없는 encoder-decoder와 같다.

 


[Positional Encoding]

 

RNN이 자연어 처리에서 유용했던 이유

 

- 단어의 위치에 따라 단어를 순차적으로 입력받아서 처리하는 RNN의 특성으로 인해

 

position information을 가질 수 있다는 점

 

 

하지만 transformer는 단어 입력을 순차적으로 받는 방식이 아니다.

 

따라서 단어의 위치 정보를 다른 방식으로 알려줄 필요가 있다.

 

transformer는 단어의 위치 정보를 얻기 위해서,

각 단어의 embedding vector에 위치 정보들을 더하여 모델의 입력으로 사용

 

이를 positional encoding

 

출처 : https://wikidocs.net/images/page/31379/transformer5_final_final.PNG

위 그림은,

 

입력으로 사용되는 embedding vector들이

 

transformer의 입력으로 사용되기 전에 

 

positional encoding 값이 더해지는 것을 보여줌

 

 

출처 : https://wikidocs.net/images/page/31379/transformer6_final.PNG

위는 encoder의 입력으로 들어가기 전 positional encoding과 embedding vector가 더해지는 과정

 

 

그렇다면 positional encoding 값들은 어떤 값?

출처 : https://wikidocs.net/31379

 

positional encoding은 위의 함수를 사용

 

사인 함수와 코사인 함수의 값을 embedding vector에 더해주므로서 단어의 순서 정보를 더하여 준다.

 

 

위 함수를 이해하려면 embedding vector와 positional encoding의 덧셈은 

 

embedding vector가 모여 만들어진 문장 vector matrix와

 

positional encoding matrix의 덧셈 연산을 통해 이루어진다는 점을 이해

 

 

출처: https://wikidocs.net/images/page/31379/transformer7.PNG

 

pos : embedding vector의 위치

i : embedding vector 내의 차원 index

 

앞서 언급한 사인 코사인 식에 따르면, 

 

i가

짝수(pos, 2i)인 경우에는 사인 함수의 값을 사용

홀수(pos, 2i+1)인 경우에는 코사인 함수를 사용

 

 

$$d_{model}$$

- transformer 모든 layer의 output dimension을 의미하는 transformer의 hyper-parameter

- embedding vector의 차원으로도 쓰임(실제 논문은 512차원이라고 함)

 

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

 

예를 들어,

 

embedding vector에 positional encoding 값을 더하면

 

같은 단어라고 하더라도 문장 내의 위치에 따라서,

 

transformer의 입력으로 들어가는 embedding vector의 값이 달라진다.

 

-> transformer의 입력은 순서 정보가 고려된 embedding vector

 


[Attention]

transformer에서 사용하는 세 가지의 attention

 

출처 : https://wikidocs.net/images/page/31379/attention.PNG

 

출처 후 수정 : https://wikidocs.net/images/page/31379/attention.PNG

Self Attention : Query, Key, Value가 동일한 경우(encoder에서 이뤄짐)

 

Encoder-Decoder Attention : Query(decoder vector)와 Key, Value(encoder vector)가 다름

(Self attention이라고 부르지 않음)

 

-> Query, Key, Value가 같다는 것은 값이 아닌 vector의 출처가 같다는 의미

 

이전 글에서 아래와 같이 언급하였다.

 

Query: t 시점의 decoder 셀에서의 hidden state

Keys: 모든 시점의 encoder 셀의 hidden state

Values: 모든 시점의 encoder 셀의 hidden state

 

더 쉽게 생각하면 

 

Query : 영향을 받는 단어

Key : 영향을 주는 단어

Value : 영향에 대한 가중치

 

라고 볼 수 있다.

 

Encoder Self-attention

Query = Key = Value 

 

Masked Decoder Self-attention

Query = Key = Value 

 

Encoder-Decoder attention

Query : 디코더 벡터 / Key = Value : 인코더 벡터

 

출처 : https://wikidocs.net/images/page/31379/transformer_attention_overview.PNG

위 이미지는 각각 transformer의 구조에서 세 가지 attention이 각각 어디에서 이루어지는지

 

Multi-head : attention을 병렬적으로 수행하는 방법

 


[Encoder]

출처 : https://wikidocs.net/images/page/31379/transformer9_final_ver.PNG

 

- num_lyaers 수 만큼 encoder의 layer을 쌓는다.

 

- 하나의 layer는 두 개의 sub layer로 나뉘어진다.

1. Multi-head self attention

(self attention을 병렬로)

 

2. Position-wise Feed Forward Neural Network

(일반적인 feed-forward 신경망)

 


[Encoder self-attention]


1) Self attention의 의미와 이점

 

Attention 함수

- 주어진 '쿼리(Query)'에 대해서 모든 '키(Key)'와의 유사도 계산

- 구해낸 이 유사도를 weight로 하여 key와 mapping되어 있는 각각의 '값(Value)'에 반영

 

유사도가 반영된 '값(Value)'을 모두 weighted-sum 하여 리턴

 

출처 : https://wikidocs.net/images/page/22893/%EC%BF%BC%EB%A6%AC.PNG

 

그렇다면 self-attention 이란

-> 말그대로 자기 자신에게 attention을 진행한다는 의미

 

기존의 attention


Query : t 시점의 decoder 셀에서의 hidden state

Keys : 모든 시점의 encoder 셀의 hidden states

Values : 모든 시점의 encoder 셀의 hidden states

 

 

 

그런데 t시점이라는 것은 계속 변화하면서 반복적으로 쿼리를 수행

이는 전체 시점에 대해서 일반화


Querys : 모든 시점의 decoder 셀에서의 hidden states

Keys : 모든 시점의 encoder 셀의 hidden states

Values : 모든 시점의 encoder 셀의 hidden states

 

 

위와 같이 기존에는 decoder 셀의 hidden state가 Q이고,

encoder 셀의 hidden state가 K라는 점에서 Q와 K가 서로 다른 값을 가지고 있다.

 

하지만 이것이 self attention에서는 Q, K, V가 모두 동일한 값을 가지고 있다.

 


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

 

self-attention의 효과

출처 : https://wikidocs.net/images/page/31379/transformer10.png

 

- 우리는 오른쪽의 it이 animal인 것을 쉽게 알 수 있지만 기계는 그렇지 않다.

 

- self-attention은 입력 문장 내의 단어들끼리 유사도를 구하므로써

it이 animal과 연관되었을 확률이 높다는 것을 찾아냄.

 


2) Q, K, V vector 얻기

 

Self-attention은 Q, K, V에 대해서 초기 입력인 d_model의 차원을 가지는 단어 vector보다 더 작은 차원을 가진다.

 

논문에서는 d_model = 512 차원을 가졌던 각 단어 vector들을 64의 차원을 가지는 Q, K, V 벡터로 변환

 

64라는 값은 트랜스포머의 다른 하이퍼파라미터인 num_heads로 결정

 

 

출처 : https://wikidocs.net/images/page/31379/transformer11.PNG

위는 student라는 단어 vector를 Q, K, V vector로 변환하는 과정.

 

기존 vector * 작은 weight vector = 작은 vector

 

weight vector는 d_model x (d_model / num_heads)의 크기를 가진다

 

 

출처 : https://wikidocs.net/images/page/31379/transformer11.PNG

위 weight matrix는 훈련 과정에서 학습된다.

 

d_model = 512

num_heads = 8

 

Q, K, V channel = 512 / 8 ---> 64


3) Scaled dot-product attention

 

위에서 Q, K, V vector를 얻었다면 나머지는 기존의 attention mechanism과 동일

 

Q vector는 모든 K vector에 대하여 attention score를 구한 후,

 

attention distribution을 구한 뒤에,

 

이를 사용하여 모든 V vector를 weighted sum하여 attention value 아니면 context vector를 구함.

 

이를 모든 Q에 대해서 반복.

 

 

Transformer에서는 간단한 dot product를 사용하는 것이 아닌,

 

여기에 특정값으로 나눠준 attention 함수인 아래 식 사용

 

(기존 dot product attention에서 값을 스케일링한 효과)

 

--> Scaled dot-product attention

 

출처 : https://wikidocs.net/images/page/31379/transformer13.PNG

I am a student 라는 단어가 있다고 가정

 

윈는 단어 I에 대한 Q vector는 모든 K vector에 대해서 attention score를 구하는 것을 보여줌

 

위 그림에서 attention score는 각각 단어 I가 단어 I, am, a, student와 얼마나 연관이 있나 보여주는 수치

 

Transformer에서는 두 vector의 dot-product를 scaling하는 값으로,

 

K vector의 dimension을 나타내는 d_k에 루트를 씌운 형태

 

d_k = d_model / num_heads

-> 64

 

-> 루트 d_k

->8

 

출처 : https://wikidocs.net/images/page/31379/transformer14_final.PNG

이제 attention score에 softmax 함수를 사용하여 Attention distribution을 구한 후,

 

각 V vector와 weighted sum하여 attention value를 구한다.

 

--> 이를 단어 I에 대한 attention value 또는 단어 I에 대한 context vector라고 부름.

 

--> 이제 I가 끝났으니, am, a student에 대해 모두 수행해야 한다.

 


4) 행렬 연산으로 일괄처리

 

한 단어씩 하지 말고 행렬 연산을 통해 일괄적으로 연산을 수행할 수 있다.

 

출처 : https://wikidocs.net/images/page/31379/transformer12.PNG

 

Attention score 계산

출처 : https://wikidocs.net/images/page/31379/transformer15.PNG

 

위의 결과 행렬의 값에 전제척으로 루트 d_k를 나누어주면,

 

각 행과 열이 attention score 값을 가지는 행렬.

 

Attention matrix를 구했다면 attention 분포를 구하고, 이를 이용하여 모든 단어에 대한 attention score를 구하는 일.

 

score 행렬에 softmax 함수를 사용하고, v matrix 을 곱함

 

이렇게 되면 각 단어의 attention 값을 모두 가지는 attention value 행렬이 결과

 

출처 : https://wikidocs.net/images/page/31379/transformer16.PNG

 행렬 연산을 통해 모든 값이 일괄 계산되는 과정을 식

 

 

입력 문장의 길이를 seq_len이라고 가정

 

문장 행렬의 크기 : (seq_len, d_model)

 

-> weight matrix를 곱해 Q, K, V를 만들어야 함

 

Q, K matrix 크기 (seq_len, d_k)

V matrix 크기 (seq_len, d_v)

 

W^q, W^k는 -> (d_model, d_k)

W^v -> (d_model, d_v)의 크기를 가짐

 

단, 논문에서 d_k와 d_v의 크기는 d_model / num heads과 같음.

 

결과적으로 위 softmax 수식을 적용하여 나오는 attention value 행렬 a의 크기는 (seq_len, d_v)

 


5. Scaled dot-product attention 구현

 

# source code from https://wikidocs.net/31379

def scaled_dot_product_attention(query, key, value, mask):
  # query 크기 : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
  # key 크기 : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
  # value 크기 : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
  # padding_mask : (batch_size, 1, 1, key의 문장 길이)

  # 1. Q와 K의 곱. 어텐션 스코어 행렬.
  matmul_qk = tf.matmul(query, key, transpose_b=True)

  # 2. 스케일링
  # dk의 루트값으로 나눠준다.
  depth = tf.cast(tf.shape(key)[-1], tf.float32)
  logits = matmul_qk / tf.math.sqrt(depth)

  # 마스킹. 어텐션 스코어 행렬의 마스킹 할 위치에 매우 작은 음수값을 넣는다.
  # 매우 작은 값이므로 소프트맥스 함수를 지나면 행렬의 해당 위치의 값은 0이 된다.
  if mask is not None:
    logits += (mask * -1e9)

  # 3. 소프트맥스 함수는 마지막 차원인 key의 문장 길이 방향으로 수행된다.
  # attention weight : (batch_size, num_heads, query의 문장 길이, key의 문장 길이)
  attention_weights = tf.nn.softmax(logits, axis=-1)

  # 4. output : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
  output = tf.matmul(attention_weights, value)

  return output, attention_weights
# 임의의 Query, Key, Value인 Q, K, V 행렬 생성 ---> 입력
np.set_printoptions(suppress=True)
temp_k = tf.constant([[10,0,0],
                      [0,10,0],
                      [0,0,10],
                      [0,0,10]], dtype=tf.float32)  # (4, 3)

temp_v = tf.constant([[   1,0],
                      [  10,0],
                      [ 100,5],
                      [1000,6]], dtype=tf.float32)  # (4, 2)
temp_q = tf.constant([[0, 10, 0]], dtype=tf.float32)  # (1, 3) ---> temp_k의 두 번쨰 값과 일치
# 함수 실행
temp_out, temp_attn = scaled_dot_product_attention(temp_q, temp_k, temp_v, None)
print(temp_attn) # 어텐션 분포(어텐션 가중치의 나열)
print(temp_out) # 어텐션 값
tf.Tensor([[0. 1. 0. 0.]], shape=(1, 4), dtype=float32)
tf.Tensor([[10.  0.]], shape=(1, 2), dtype=float32)

Query는 4개의 Key값 중 두번째 값과 일치하므로 어텐션 분포는 [0, 1, 0, 0]의 값

 

value의 두 번째 값인 [10, 0] 이 출력

 

 

 

Query값 [0, 0, 10]로 했을 때

 

Key의 세번째 값과, 네번째 값 두 개의 값 모두와 일치하는 값

 

temp_q = tf.constant([[0, 0, 10]], dtype=tf.float32)
temp_out, temp_attn = scaled_dot_product_attention(temp_q, temp_k, temp_v, None)
print(temp_attn) # 어텐션 분포(어텐션 가중치의 나열)
print(temp_out) # 어텐션 값
tf.Tensor([[0.  0.  0.5 0.5]], shape=(1, 4), dtype=float32)
tf.Tensor([[550.    5.5]], shape=(1, 2), dtype=float32)

Query의 값은 Key의 세번째 값, 네번째 값

모두 유사하다는 의미에서

attention 분포는 [0, 0, 0.5, 0.5]

 

결과적으로 나오는 값

[550, 5.5]

--> [100, 5] * 0.5 + [1000, 6] * 0.5

(weighted sum)

 

 

입력이 여러개라면

 

temp_q = tf.constant([[0, 0, 10], [0, 10, 0], [10, 10, 0]], dtype=tf.float32)  # (3, 3)
temp_out, temp_attn = scaled_dot_product_attention(temp_q, temp_k, temp_v, None)
print(temp_attn) # 어텐션 분포(어텐션 가중치의 나열)
print(temp_out) # 어텐션 값
tf.Tensor(
[[0.  0.  0.5 0.5]
 [0.  1.  0.  0. ]
 [0.5 0.5 0.  0. ]], shape=(3, 4), dtype=float32)
tf.Tensor(
[[550.    5.5]
 [ 10.    0. ]
 [  5.5   0. ]], shape=(3, 2), dtype=float32)

 


6. Multi-head attention

 

위에서 input vector(d_models)에 대해서

num_heads로 나눈 차원을 가지는 Q. K, V vector로 바꾸고 attention 수행

 

그러면 왜 차원을 축소 시켰는지 알아봄

 

출처 : https://wikidocs.net/images/page/31379/transformer17.PNG

 

 

transformer는 한 번의 attention을 하는 것보다

 

여러번의 어텐션을 병렬로 사용하는 것이 효과적이라고 판단.

 

따라서 d_model의 차원을 num_heads로 나누어

 

d_model/num_heads의 차원을 가지는 Q, K, V에 대해서 num_heads 개의 

 

병렬 attention을 수행

 

num_heads가 8이면 8개의 병렬 attention이 수행.

 

이때 각각의 attention value matrix를 attention head.

 

W^Q, W^K, W^V의 값은 8개의 attention head마다 전부 다르다.

 

 

 

병렬 attention의 효과

-> 다른 시각으로 정보 수집

 

ex)  '그 동물은 길을 건너지 않았다. 왜냐하면 그것은 너무 피곤하였기 때문이다.'

 

첫번째 attention head는 '그것(it)'과 '동물(animal)'의 연관도를 높게 본다

두번째 어텐션 헤드는 '그것(it)'과 '피곤하였기 때문이다(tired)'의 연관도를 높게 본다

 

출처 : https://wikidocs.net/images/page/31379/transformer18_final.PNG

 

병렬 attention을 수행하면 위와 같이 모든 attention head를 concatenate 한다.

 

모두 연결된 attention matrix의 행렬 크기는 (seq_len, d_model)

 

출처 : https://wikidocs.net/images/page/31379/transformer19.PNG

(detail한 차원은 논문과 다르기 때문에 동작 방식만 보아라.)

 

Attention head를 모두 연결한 행렬(concatednated matrix)는 또 다른 weight matrix W^0을 곱한다.

 

이렇게 나온 결과가 multi-head attention의 최종 결과물.

 


7. Multi-head attention 구현

 

Multi-head attention에는 두 종류의 weight matrix 행렬이 있음.

 

1. Q, K, V 행렬을 만들기 위한 weight matrix인 WQ, WK, WV matrix

 

2. Attention head들을 concatenation 후에 곱해주는 WO 행렬

 

Multi-head attention 구현 크게 다섯 가지 파트로 구성.

1. WQ, WK, WV에 해당하는 d_model 크기의 Dense layer을 지나게한다.
2. 지정된 헤드 수(num_heads)만큼 split.
3. scaled dot-product attention.
4. 나눠졌던 헤드들을 concatenatetion한다.
5. WO에 해당하는 Dense layer을 지나게 한다.

 

# 2021-11-10 code originated from https://wikidocs.net/31379

class MultiHeadAttention(tf.keras.layers.Layer):

  def __init__(self, d_model, num_heads, name="multi_head_attention"):
    super(MultiHeadAttention, self).__init__(name=name)
    self.num_heads = num_heads
    self.d_model = d_model

    assert d_model % self.num_heads == 0

    # d_model을 num_heads로 나눈 값.
    # 논문 기준 : 64
    self.depth = d_model // self.num_heads

    # WQ, WK, WV에 해당하는 밀집층 정의
    self.query_dense = tf.keras.layers.Dense(units=d_model)
    self.key_dense = tf.keras.layers.Dense(units=d_model)
    self.value_dense = tf.keras.layers.Dense(units=d_model)

    # WO에 해당하는 밀집층 정의
    self.dense = tf.keras.layers.Dense(units=d_model)

  # num_heads 개수만큼 q, k, v를 split하는 함수
  def split_heads(self, inputs, batch_size):
    inputs = tf.reshape(
        inputs, shape=(batch_size, -1, self.num_heads, self.depth))
    return tf.transpose(inputs, perm=[0, 2, 1, 3])

  def call(self, inputs):
    query, key, value, mask = inputs['query'], inputs['key'], inputs[
        'value'], inputs['mask']
    batch_size = tf.shape(query)[0]

    # 1. WQ, WK, WV에 해당하는 밀집층 지나기
    # q : (batch_size, query의 문장 길이, d_model)
    # k : (batch_size, key의 문장 길이, d_model)
    # v : (batch_size, value의 문장 길이, d_model)
    # 참고) 인코더(k, v)-디코더(q) 어텐션에서는 query 길이와 key, value의 길이는 다를 수 있다.
    query = self.query_dense(query)
    key = self.key_dense(key)
    value = self.value_dense(value)

    # 2. 헤드 나누기
    # q : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
    # k : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
    # v : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
    query = self.split_heads(query, batch_size)
    key = self.split_heads(key, batch_size)
    value = self.split_heads(value, batch_size)

    # 3. 스케일드 닷 프로덕트 어텐션. 앞서 구현한 함수 사용.
    # (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
    scaled_attention, _ = scaled_dot_product_attention(query, key, value, mask)
    # (batch_size, query의 문장 길이, num_heads, d_model/num_heads)
    scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])

    # 4. 헤드 연결(concatenate)하기
    # (batch_size, query의 문장 길이, d_model)
    concat_attention = tf.reshape(scaled_attention,
                                  (batch_size, -1, self.d_model))

    # 5. WO에 해당하는 밀집층 지나기
    # (batch_size, query의 문장 길이, d_model)
    outputs = self.dense(concat_attention)

    return outputs

8. Padding Mask

 

앞에 scaled_dot_product_attention 구현한 함수를 보면 mask 값에 대해서 작은 값을 더해준다.

 

def scaled_dot_product_attention(query, key, value, mask):
... 중략 ...
    logits += (mask * -1e9) # 어텐션 스코어 행렬인 logits에 mask*-1e9 값을 더해주고 있다.
... 중략 ...

이는 입력 문장에 <PAD> 토큰이 있을 경우 attention에서 제외하기 위한 연산.

 

<PAD>가 포함된 입력 문장의 self-attention

 

출처 : https://wikidocs.net/images/page/31379/pad_masking11.PNG

 

<PAD>는 실질적인 의미를 가진 단어가 아니다.

 

Transformer에서는 Key의 경우에 <PAD> token이 존재한다면

 

이에 대해서는 유사도를 구하지 않도록 masking을 해줌

 

masking의 이미 : attention에서 제외하기 위해 값을 가린다는 의미

 

Attention score matrix에서

 

행에 해당하는 문자는 query,

 

열에 해당하는 문자는 key,

 

Key에 <pad>가 있는 경우 열 전체에 masking

 

출처 : https://wikidocs.net/images/page/31379/pad_masking2.PNG

masking : attention score matrix의 masking 위치에 매우 작은 음수값을 넣어주는 것

 

[연산]

위 attention score matrix는 softmax를 거치기 전.

 

현재 masking 위치에 매우 작은 음수 값이 있으므로,

 

attention score matrix가 softmax를 거치면 0에 매우 가까웃 숫자가 나옴.

 

유사도 구하는 일에 <pad>토큰이 반영되지 않음

 

출처 : https://wikidocs.net/images/page/31379/softmax.PNG

 

padding mask를 구현하는 방법

 

입력된 정수 시퀀스에서 패딩 토큰의 인덱스인지, 아닌지를 판별하는 함수를 구현

 

아래 함수는 정수 시퀀스에서 0인 경우 1로 변환, 그렇지 않은 경우 0으로 변환

 

def create_padding_mask(x):
  mask = tf.cast(tf.math.equal(x, 0), tf.float32)
  # (batch_size, 1, 1, key의 문장 길이)
  return mask[:, tf.newaxis, tf.newaxis, :]

 

아래의 입력을 넣었을 때

print(create_padding_mask(tf.constant([[1, 21, 777, 0, 0]])))

결과

tf.Tensor([[[[0. 0. 0. 1. 1.]]]], shape=(1, 1, 1, 5), dtype=float32)

 

1의 값을 가진 위치의 열을 어텐션 스코어 행렬에서 마스킹하는 용도로 사용할 수 있음

 


[Position-wise FFNN]

- encoder와 decoder에서 공통적으로 가지고 있는 sub layer

- Fully conntected Feed Forward Neural Network로 볼 수 있다.

위 식을 그림으로 표현하면 아래와 같다.

x : multi head attention의 결과 (seq_len, d_model)의 크기를 가지는 행렬

W_1 : (d_model, d_ff) 의 크기

W_2 : (d_ff, d_model) 의 크기

 

d_ff : 2,048(hyper parameter)

 

W_1, b_1, W_2, b_2 : 하나의 encoder layer 내에서는 다른 문장, 단어들마다 정확하게 동일하게 사용

 

하지만 encoder layer마다는 다른 값을 가짐.

 

출처 : https://wikidocs.net/images/page/31379/transformer20.PNG

위쪽 왼편 이미지는,

 

encoder의 입력을 vector 단위로 보았을 때,

 

각 vector들이 multi-head attention이라는 encoder 내 sub layer를 지나 FFNN을 통과하는 것

(Position-wise FFNN)

 

출처 : https://wikidocs.net/images/page/31379/transformer20.PNG

실제로는 위 이미지가 입력으로 들어감

 

두 번재 sub layer를 지난 encoder의 최종 출력은

 

여전히 encoder의 입력 크기였던 (seq_len, d_model) 이 유지

 


[Residual connection 과 layer normalization]

출처 : https://wikidocs.net/images/page/31379/transformer21.PNG

 

- Add & norm 이 추가됨

출처 : https://wikidocs.net/images/page/31379/transformer21.PNG

- Add : residual connection

- Norm : layer normalization

 


Residual Connection

 

출처 : https://wikidocs.net/images/page/31379/transformer22.PNG

 

- F(x) : sub layer에 해당

 

Residual connection

 

출처 : https://wikidocs.net/images/page/31379/residual_connection.PNG

위는 residual connection의 과정

 


Layer Normalization

 

Residual connection 이후 layer normalization을 거침

Normalization하는 과정

 

d_model에 해당하는 차원에 대해서 normalization

 

출처 : https://wikidocs.net/images/page/31379/layer_norm_new_1_final.PNG

 

평균과 분산을 구한 뒤 화살표 방향 vector를 x라고 명명하면

 

출처 : https://wikidocs.net/images/page/31379/layer_norm_new_2_final.PNG

 

layer normalization 후 vector x는 ln이라는 vector로 normalization 된다.

layer normalization의 수식

 

1. 평균과 분산을 통한 normalization

2. Gamma, beta를 도입하는 것

 

k차원

분모 0 방지를 위해 epsilon

 

1과 0으로 초기화된 gamma, beta vector 준비

 

gamma와 beta를 학습 가능한 파라미터

출처 : https://wikidocs.net/images/page/31379/%EA%B0%90%EB%A7%88%EB%B2%A0%ED%83%80.PNG

 

최종 수식

 

 


[Encoder 구현]

# source code is originated from https://wikidocs.net/31379

def encoder_layer(dff, d_model, num_heads, dropout, name="encoder_layer"):
  inputs = tf.keras.Input(shape=(None, d_model), name="inputs")

  # 인코더는 패딩 마스크 사용
  padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")

  # 멀티-헤드 어텐션 (첫번째 서브층 / 셀프 어텐션)
  attention = MultiHeadAttention(
      d_model, num_heads, name="attention")({
          'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
          'mask': padding_mask # 패딩 마스크 사용
      })

  # 드롭아웃 + 잔차 연결과 층 정규화
  attention = tf.keras.layers.Dropout(rate=dropout)(attention)
  attention = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(inputs + attention)

  # 포지션 와이즈 피드 포워드 신경망 (두번째 서브층)
  outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention)
  outputs = tf.keras.layers.Dense(units=d_model)(outputs)

  # 드롭아웃 + 잔차 연결과 층 정규화
  outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
  outputs = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(attention + outputs)

  return tf.keras.Model(
      inputs=[inputs, padding_mask], outputs=outputs, name=name)

 


[Encoder 쌓기]

def encoder(vocab_size, num_layers, dff,
            d_model, num_heads, dropout,
            name="encoder"):
  inputs = tf.keras.Input(shape=(None,), name="inputs")

  # 인코더는 패딩 마스크 사용
  padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")

  # 포지셔널 인코딩 + 드롭아웃
  embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
  embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
  embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
  outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)

  # 인코더를 num_layers개 쌓기
  for i in range(num_layers):
    outputs = encoder_layer(dff=dff, d_model=d_model, num_heads=num_heads,
        dropout=dropout, name="encoder_layer_{}".format(i),
    )([outputs, padding_mask])

  return tf.keras.Model(
      inputs=[inputs, padding_mask], outputs=outputs, name=name)

[From Encoder to Decoder]

출처 : https://wikidocs.net/images/page/31379/transformer_from_encoder_to_decoder.PNG

 

 

참고

https://wikidocs.net/31379

 

1) 트랜스포머(Transformer)

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

wikidocs.net

https://blog.pingpong.us/transformer-review/

 

Transformer - Harder, Better, Faster, Stronger

Transformer 구조체와 이 구조를 향상시키기 위한 기법들을 같이 알아봅시다.

blog.pingpong.us