트랜스포머의 기본 구조는 '인코더-디코더(Encoder-Decoder)이다.
인코더 : 입력 토큰의 시퀀스를 은닉 상태(Hidden state) 또는 문맥(context)라 부르는 임베딩 벡터의 시퀀스로 변환함
디코더 : 인코더의 은식 상태를 사용해 출력 토근의 시퀀스를 한 번에 하나씩 반복적으로 생성함
[추가 참조]
트랜스포머 인코더는 여러 개의 인코더 층이 서로 쌓여 구성됨
인코더 블록의 각 층은 심층 신경망을 효율적으로 훈련하기 위해 표준 기법인 스킵 연결(skip connection)과 층 정규화(layer normalization)도 사용함
Xi는 모든 xj(1 to n)의 선형 결합으로 나타낼 수 있음
xi에 대한 어텐션 가중치 Wji의 총합은 1(확률)이 되도록 정규화됨
예시
# 1) 텍스트 데이터 토크나이징
from transformers import AutoTokenizer
model_ckpt = "bert-base-uncased"
text = "time flies like an arrow etc"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
"""
tokenizer info : BertTokenizerFast(name_or_path='bert-base-uncased', vocab_size=30522, model_max_length=512, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'}, clean_up_tokenization_spaces=True)
"""
inputs = tokenizer(text, return_tensors="pt", add_special_tokens=False) # add_special_tokens=False <== [CLS], [SEP] 포함
"""
inputs info : {'input_ids': tensor([[ 2051, 10029, 2066, 2019, 8612, 4385]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1]])}
"""
# 2) 텍스트 임베딩
from torch import nn
from transformers import AutoConfig
config = AutoConfig.from_pretrained(model_ckpt)
"""
config info :
BertConfig {
"_name_or_path": "bert-base-uncased",
"architectures": [
"BertForMaskedLM"
],
"attention_probs_dropout_prob": 0.1,
"classifier_dropout": null,
"gradient_checkpointing": false,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-12,
"max_position_embeddings": 512,
"model_type": "bert",
"num_attention_heads": 12,
"num_hidden_layers": 12,
"pad_token_id": 0,
"position_embedding_type": "absolute",
"transformers_version": "4.30.2",
"type_vocab_size": 2,
"use_cache": true,
"vocab_size": 30522
}
"""
token_emb = nn.Embedding(config.vocab_size, config.hidden_size)
token_emb # Embedding(30522, 768)
inputs_embeds = token_emb(inputs.input_ids)
"""
>>> inputs_embeds
tensor([[[-2.3282, -0.0118, -1.9412, ..., 1.5498, -0.9696, 0.5593],
[-0.7488, 0.0046, -0.2549, ..., 0.1123, -0.2114, 0.6592],
[ 1.0944, 1.1721, -0.0549, ..., 1.2206, -1.6383, -0.0529],
[-0.2461, -0.7813, 0.0361, ..., 0.8733, 0.8599, 0.4416],
[-1.0326, 1.4316, -1.0660, ..., 0.3551, -0.6275, 1.8587],
[-1.3137, 1.0480, -1.1594, ..., -1.3812, -1.3219, 0.0499]]],
grad_fn=<EmbeddingBackward0>)
"""
inputs_embeds.size() # torch.Size([1, 6, 768])
# 3) attendtion score 계산하기 및 가중치 update
import torch
from math import sqrt
query = key = value = inputs_embeds
dim_k = key.size(-1) # 768
# key.transpose(1,2).shape == torch.Size([1, 768, 6])
scores = torch.bmm(query, key.transpose(1,2)) / sqrt(dim_k)
scores.size() # torch.Size([1, 6, 6])
# 4) 소프트 맥스로 가중치의 합이 1이 되도록 정규화
import torch.nn.functional as F
weights = F.softmax(scores, dim=-1)
# weights.shape # torch.Size([1, 6, 6])
weights.sum(dim=-1) # tensor([[1., 1., 1., 1., 1., 1.]], grad_fn=<SumBackward1>)
# 5) 위치
attn_outputs = torch.bmm(weights, value)
attn_outputs.shape # torch.Size([1, 6, 768])
def scaled_dot_product_attention(query, key, value):
# self-attention
dim_k = query.size(-1)
scores = torch.bmm(query, key.transpose(1, 2)) / sqrt(dim_k)
weights = F.softmax(scores, dim=-1)
return torch.bmm(weights, value)
class AttentionHead(nn.Module):
# 단일 어텐션
def __init__(self, embed_dim, head_dim):
super().__init__()
self.q = nn.Linear(embed_dim, head_dim)
self.k = nn.Linear(embed_dim, head_dim)
self.v = nn.Linear(embed_dim, head_dim)
def forward(self, hidden_state):
# 셀프어텐션
attn_outputs = scaled_dot_product_attention(
self.q(hidden_state), self.k(hidden_state), self.v(hidden_state))
return attn_outputs
class MultiHeadAttention(nn.Module):
def __init__(self, config):
super().__init__()
embed_dim = config.hidden_size #768
num_heads = config.num_attention_heads # 12
head_dim = embed_dim // num_heads # 64
# 셀프어텐션 초기화 후 모듈리스트에 넣기
self.heads = nn.ModuleList(
[AttentionHead(embed_dim, head_dim) for _ in range(num_heads)]
)
# 최종 W0 값 초기화
self.output_linear = nn.Linear(embed_dim, embed_dim)
def forward(self, hidden_state):
# self-attentiond의 hidden_state를 멀티 헤드 수 만큼 도출하여 concat
x = torch.cat([h(hidden_state) for h in self.heads], dim=-1)
# W0 업데이트
x = self.output_linear(x)
return x
multihead_attn = MultiHeadAttention(config)
attn_output = multihead_attn(inputs_embeds)
attn_output.size() # torch.Size([1, 6, 768])
class FeedForward(nn.Module):
def __init__(self, config):
super().__init__()
self.linear_1 = nn.Linear(config.hidden_size, config.intermediate_size) # (768, 3072)
self.linear_2 = nn.Linear(config.intermediate_size, config.hidden_size) # (3072, 768)
self.gelu = nn.GELU()
self.dropout = nn.Dropout(config.hidden_dropout_prob) # 0.1
def forward(self, x):
x = self.linear_1(x)
x = self.gelu(x)
x = self.linear_2(x)
x = self.dropout(x)
return x
feed_forward = FeedForward(config)
ff_outputs = feed_forward(attn_outputs)
ff_outputs.size() # torch.Size([1, 6, 768])
class TransformerEncoderLayer(nn.Module):
def __init__(self, config):
super().__init__()
self.layer_norm_1 = nn.LayerNorm(config.hidden_size)
self.layer_norm_2 = nn.LayerNorm(config.hidden_size)
self.attention = MultiHeadAttention(config)
self.feed_forward = FeedForward(config)
def forward(self, x):
# Apply layer normalization and then copy input into query, key, value
hidden_state = self.layer_norm_1(x) # pre layer normalize
# Apply attention with a skip connection
x = x + self.attention(hidden_state)
# Apply feed-forward layer with a skip connection
x = x + self.feed_forward(self.layer_norm_2(x))
return x
encoder_layer = TransformerEncoderLayer(config)
inputs_embeds.shape, encoder_layer(inputs_embeds).size()
class Embeddings(nn.Module):
def __init__(self, config):
super().__init__()
self.token_embeddings = nn.Embedding(config.vocab_size,
config.hidden_size)
self.position_embeddings = nn.Embedding(config.max_position_embeddings,
config.hidden_size)
self.layer_norm = nn.LayerNorm(config.hidden_size, eps=1e-12)
self.dropout = nn.Dropout()
def forward(self, input_ids):
# Create position IDs for input sequence
seq_length = input_ids.size(1)
position_ids = torch.arange(seq_length, dtype=torch.long).unsqueeze(0) # index 입력
# Create token and position embeddings
token_embeddings = self.token_embeddings(input_ids)
position_embeddings = self.position_embeddings(position_ids)
# Combine token and position embeddings
embeddings = token_embeddings + position_embeddings #
embeddings = self.layer_norm(embeddings)
embeddings = self.dropout(embeddings)
return embeddings
embedding_layer = Embeddings(config)
class TransformerEncoder(nn.Module):
def __init__(self, config):
super().__init__()
self.embeddings = Embeddings(config)
self.layers = nn.ModuleList([TransformerEncoderLayer(config)
for _ in range(config.num_hidden_layers)])
def forward(self, x):
x = self.embeddings(x)
for layer in self.layers:
x = layer(x)
return x
class TransformerForSequenceClassification(nn.Module):
def __init__(self, config):
super().__init__()
self.encoder = TransformerEncoder(config)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
self.classifier = nn.Linear(config.hidden_size, config.num_labels)
def forward(self, x):
x = self.encoder(x)[:, 0, :] # select hidden state of [CLS] token
x = self.dropout(x)
x = self.classifier(x)
return x
config.num_labels = 3
encoder_classifier = TransformerForSequenceClassification(config)
encoder_classifier(inputs.input_ids).size()
디코더 특징 : 두 개의 어텐션 층이 있음
seq_len = inputs.input_ids.size(-1)
mask = torch.tril(torch.ones(seq_len, seq_len)).unsqueeze(0)
mask[0]
scores.masked_fill(mask == 0, -float("inf")) # 0을 -inf로 치환
=> 0을 -inf(무한대)로 바꾸면 어텐션 헤드가 미래 토큰을 보지 못 함
=> -inf에 소프트맥스 함수를 적용하면 0이 되어 어텐션 가중치가 모두 0이 됨
def scaled_dot_product_attention(query, key, value, mask=None):
dim_k = query.size(-1)
scores = torch.bmm(query, key.transpose(1, 2)) / sqrt(dim_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float("-inf"))
weights = F.softmax(scores, dim=-1)
return weights.bmm(value)
특징 : BERT는 두 가지 목표(MLM, NSP)로 사전 훈련됨
특징 : 메모리 및 시간 개선한 모델
특징 : Bert 대비 모델 성능 개선
NSP 작업을 제외하고, 더 오랫동안 더 큰 배치로 더 많은 훈련 데이터로 모델 훈련
특징 : 다국어 모델을 구축하기 위한 사전 훈련 목표로 크로스-언어 언어 모델(XLM)의 작업의 한 방법
특징 : 다국어 사전 훈련을 대규모로 확장
특징 : 매개변수를 효과적으로 사용
문제상황 : 표준 MLM 사전 훈련 목표의 한 가지 제한은 각 훈련 단계에서 가려진 토큰의 표현만 업데이트되고 다른 입력 토큰은 업데이트되지 않음
특징 : 이 문제를 해결하기 위해 ELECTRA는 두 개의 모델 접근 방식 사용
특징 : 앙상블로 구성된 첫 번째 모델로서 토큰의 절대적 위치 및 상대적 위치 모두를 효과적으로 활용하는 모델
특징 : 시퀀스에서 다음 단어 예측에 뛰어난 성과를 보여 주로 텍스트 생성에 사용됨
특징 : NLP에서 두 가지 핵심 아이디어(트랜스포머 디코더 아키텍처와 전이 학습)의 결합 결과
특징 : 원래 모델에 훈련 데이터 세트를 확장
특징 : GPT-2와 같은 모델은 입력 시퀀스 (또는 프롬프트)를 이어나갈 수 있음
특징 : GPT-2를 100배로 확장하여 1750억 개의 매개변수를 가짐(서로 다른 규모의 언어 모델의 동작을 분석한 결과, 계산량, 데이터셋 크기, 모델 크기, 언어 모델의 성능 간의 관계를 지배하는 간단한 거듭제곱 법칙 발견)
특징 : EleutherAI라는 연구자 집단이 훈련한 GPT와 유사한 모델로 현재 이 모델들은 전체 1750억 개의 매개변수를 가진 모델의 작은 변형이며, 각각 13억 개, 27억 개 및 60억 개의 매개변수를 가짐
특징 : 모든 NLU 및 NLG 작업을 텍스트 대 텍스트 작업으로 변환하여 통합함
특징 : BART는 BERT와 GPT의 사전 훈련 절차를 인코더-디코더 아키텍처 내에서 결합함
=> 입력 시퀀스는 간단한 마스킹에서 문장 순열, 토큰 삭제 및 문서 회전까지 여러 가지 변형 중 하나를 거침.
=> 수정된 입력은 인코더를 통과하고 디코더는 원래의 텍스트를 재구성함
=> 모델은 NLU 및 NLG 작업에 모두 유연하게 사용될 수 있는 모델
특징 : 일반적으로 하나의 언어 쌍과 번역 방향에 대한 번역 모델이 구축됨
특징 : 선형적으로 확장 가능한 희소한 어텐션 형태를 사용하여 메모리 문제(최대 문맥 크기) 해결
=> 이로써 대부분의 BERT 모델에서 512 토큰인 문맥을 BigBird에서 4,096 토큰으로 drastics하게 확장가능해짐
글 재미있게 봤습니다.