[ML] Transformer Encoder 뽀개기

chrisjune
7 min read5 days ago

--

멀티헤드 어텐션 레이어를 활용한 Transformer encoder 구조를 만들어 봅니다.

https://ssoldot.me/news/6461

Attention에 대하여 궁금하신 분은 아래 링크글을 참고해주세요.

Transformer Encoder

멀티헤드 어텐션 레이어를 활용하여 트랜스포머 구조를 만들 수 있습니다.

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

class TransformerEncoder(layers.Layer):
def __init__(self, embed_dim, dense_dim, num_heads, **kwargs):
super().__init__(**kwargs)
self.embed_dim = embed_dim # 입력토큰 크기
self.dense_dim = dense_dim # dense
self.num_heads = num_heads

self.attention = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)
self.dense_proj = keras.Sequential([
layers.Dense(dense_dim, activation='relu'),
layers.Dense(embed_dim),])
self.layernorm_1 = layers.LayerNormalization()
self.layernorm_2 = layers.LayerNormalization()

def call(self, inputs, mask=None):
if mask is not None:
mask = mask[:,tf.newaxis,:]
attention_output = self.attention(
inputs, inputs, attention_mask=mask)
proj_input = self.layernorm_1(inputs + attention_output)
proj_output = self.dense_proj(proj_input)
return self.layernorm_2(proj_input + proj_output)
def get_config(self):
config = super().get_config()
config.update({
"embed_dim": self.embed_dim,
"dense_dim": self.dense_dim,
"num_heads": self.num_heads
})
return config

가장 기본형태의 트랜스포머 인코더입니다.문장의 토큰 임베딩을 입력받고 어텐션레이어를 통해 어텐션 가중치에 의해 조정되고, MLP 레이어와 정규화 레이어를 통과한 결과를 받습니다.

일반적으로 사용하는 BatchNormalization 이 아니라 LayerNormalization을 사용한 이유는 LayerNormalization은 마지막 축 따라서 평균과 분산을 구합니다. 예를 들어 (4,3,2) 형태의 벡터의 평균과 분산은 (4,3)이 되고 이를 활용해 정규화합니다. 각 시퀀스 안에서 데이터를 개별적으로 구하기 때문에 시퀀스 데이터에 적합합니다.

BatchNormalization은 마지막 축을 제외한 다른 축을 따라 평균과 분산을 구합니다. 예를 들어 (4,3,2) 형태의 벡터의 평균과 분산은 (2,) 형태가 되고 이를 활용해 정규화합니다. 보다 많은 데이터를 활용하기 때문에 특성의 평균과 분산에 대한 정확한 통계값을 구할 수 있습니다.

PositionalEmbedding

Transformer Encoder의 문제는 시퀀스에서 토큰의 순서를 바꾸어도 정확히 결과가 동일하다는 것입니다. 셀프어텐션은 원소 쌍 사이의 관계를 계산하기 때문에 순서를 고려하지 않습니다. I ate foodFood ate I 는 다른 문장이기 때문에 순서를 고려할 수단이 필요합니다.

PositionalEmbedding은 시퀀스의 토큰 순서를 인식하기 위하여 시퀀스를 입력받아 String embedding과 위치 embedding을 생성합니다.

위치를 나타내는 방법으로는 시퀀스 특성상 주기를 표현할 수 있는 Cosine으로 -1과 1의 값으로 표현할 수 있습니다. 여기선 간단하게 토큰 순서대로 Incremental하게 숫자를 부여하는 방법을 사용했습니다.

class PositionalEmbedding(layers.Layer):
def __init__(self, sequence_length, input_dim, output_dim, **kwargs):
super().__init__(**kwargs)
self.token_embeddings = layers.Embedding(
input_dim=input_dim, output_dim=output_dim)
self.position_embeddings = layers.Embedding(
input_dim=sequence_length, output_dim=output_dim)
self.sequence_length = sequence_length
self.input_dim=input_dim
self.output_dim=output_dim

def call(self, inputs):
length = tf.shape(inputs)[-1]
positions = tf.range(start=0, limit=length, delta=1)
embedded_tokens = self.token_embeddings(inputs)
embedded_position = self.position_embeddings(positions)
return embedded_position + embedded_tokens

def compute_mask(self, inputs, mask=None):
return tf.math.not_equal(inputs, 0)

def get_config(self):
config = super().get_config()
config.update({
"output_dim": self.output_dim,
"sequence_length": self.sequence_length,
"input_dim": self.input_dim
})
return config

PositionalEmbedding과 TransformerEncoder를 활용하면 아래와 같은 Sequential Layer를 구성할 수 있습니다

from keras import layers, Sequential

Sequential([
layers.InputLayer(input_shape=(None,), dtype=tf.string),
layers.StringLookup(vocabulary=lookup_vocab),
PositionalEmbedding(sequence_length=50, input_dim=len(lookup_vocab) + 1, output_dim=embed_dim),
TransformerEncoder(
embed_dim=embed_dim,
dense_dim=embed_dim,
num_heads=num_heads,
),
layers.GlobalAvgPool1D(),
layers.Flatten()
]

트랜스포머 Decoder과 이를 활용한 Sequence to Sequence 모델은 다른 포스트에서 정리하도록 하겠습니다.

간단하게 트랜스포머 인코더를 이해하시는데 조금이나마 도움이 되었으면 좋겠습니다.

--

--