Transformers: Atenção é Tudo que Você Precisa
Sobre este Tutorial
Este tutorial apresenta uma exploração completa e didática da arquitetura Transformer, introduzida no artigo seminal "Attention Is All You Need" (Vaswani et al., 2017). Vamos examinar cada componente desta arquitetura revolucionária que mudou fundamentalmente o Processamento de Linguagem Natural (NLP) e além.
Referência: Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L.,
Gomez, A. N., Kaiser, Ł., & Polosukhin, I. (2017). Attention is all you need.
In Advances in neural information processing systems (pp. 5998-6008).
arXiv:1706.03762
1. Introdução aos Transformers
1.1 Por que Transformers?
Durante anos, as Redes Neurais Recorrentes (RNNs) e suas variantes (LSTM, GRU) dominaram tarefas de modelagem de sequências e transdução. No entanto, essas arquiteturas tinham limitações fundamentais:
- Processamento Sequencial: Não podem processar tokens em paralelo, limitando o uso de GPUs
- Dependências de Longo Alcance: Dificuldade em capturar relações entre posições distantes
- Gradientes Desvanecentes: Desafios de treinamento com sequências longas
- Tempo de Treinamento: Lento devido à natureza sequencial inerente
- Restrições de Memória: Estado oculto se torna gargalo para sequências longas
Conceito-Chave
O Transformer elimina completamente a recorrência, confiando apenas em mecanismos de atenção para capturar dependências globais entre entrada e saída. Isso possibilita:
- Paralelização completa durante o treinamento
- Modelagem direta de dependências independente da distância
- Comprimento de caminho constante entre quaisquer duas posições
- Tempo de treinamento significativamente reduzido
Resultados Principais do Artigo (WMT 2014)
O artigo original demonstrou resultados estado-da-arte:
- Inglês para Alemão: 28.4 BLEU (melhorando o melhor anterior em mais de 2 BLEU)
- Inglês para Francês: 41.8 BLEU (novo estado-da-arte para modelo único)
- Tempo de Treinamento: 3.5 dias em 8 GPUs (fração do custo de modelos anteriores)
- Generalização: Aplicado com sucesso à análise sintática do inglês
1.2 A Revolução em NLP
O paper "Attention Is All You Need" (2017) introduziu os Transformers e mudou completamente o panorama da IA:
2. Limitações das RNNs
2.1 Processamento Sequencial
Em uma RNN, cada token deve ser processado em ordem sequencial:
Onde $h_t$ depende de $h_{t-1}$, impedindo paralelização
Problema
Esta dependência sequencial significa que não podemos usar GPUs eficientemente, pois precisamos esperar cada passo ser computado antes de processar o próximo.
2.2 Dependências de Longo Prazo
Mesmo com LSTMs e GRUs, capturar relações entre tokens distantes é desafiador:
Exemplo
Considere a frase: "O gato que estava dormindo no sofá da sala acordou."
A RNN precisa propagar informação através de múltiplos passos para conectar "O gato" com "acordou". A cada passo, alguma informação é perdida.
| Aspecto | RNNs/LSTMs | Transformers |
|---|---|---|
| Processamento | Sequencial | Paralelo |
| Dependências Longas | Difícil | Fácil (atenção direta) |
| Complexidade | O(n) por timestep | O(1) operações (paralelas) |
| Memória | Estado oculto fixo | Atenção sobre toda sequência |
3. Mecanismo de Atenção
3.1 Intuição da Atenção
A Ideia Central
Atenção permite que o modelo "olhe" para diferentes partes da entrada ao processar cada elemento, decidindo dinamicamente quais partes são mais relevantes para o contexto atual.
Exemplo Intuitivo
Ao traduzir "The cat sat on the mat" para português, ao gerar a palavra "sentou":
- O modelo "presta atenção" principalmente em "sat"
- Mas também considera "cat" (para concordância de gênero)
- E pode olhar para "mat" (contexto completo)
A atenção calcula quanto "peso" dar para cada palavra da entrada.
3.2 Self-Attention (Autoatenção)
No self-attention, a sequência "olha para si mesma" - cada token pode prestar atenção em todos os outros tokens da mesma sequência.
3.2.1 Query, Key e Value
O self-attention utiliza três conceitos fundamentais:
Definições
- Query (Q): "O que estou procurando?" - representa o token atual fazendo a consulta
- Key (K): "O que eu ofereço?" - representa o conteúdo de cada token
- Value (V): "O que eu transmito?" - representa a informação que será extraída
Cada embedding de entrada $x_i$ é transformado em três vetores através de matrizes de peso:
Analogia: Biblioteca
Imagine uma biblioteca:
- Query: Sua pergunta/busca ("Preciso de livros sobre IA")
- Key: Índice/etiqueta de cada livro ("Este livro é sobre IA", "Este é sobre culinária")
- Value: Conteúdo real dos livros que você leva
Você compara sua Query com as Keys de todos os livros, encontra os mais relevantes, e pega seus Values (conteúdo).
3.2.2 Fórmula da Atenção com Produto Escalar Escalonado
O mecanismo central de atenção é a Atenção com Produto Escalar Escalonado:
Vamos entender cada componente e por que funciona:
Computação Passo a Passo
1. Calcular Scores de Compatibilidade (QKT):
- Multiplicação matricial entre consultas (queries) e chaves (keys)
- Mede similaridade entre cada par query-key
- Resulta em logits de atenção de forma (seq_len, seq_len)
- Valores altos indicam forte relevância
2. Escalar por √dk:
- Crítico para manter o fluxo de gradiente
- Para dk grande, produtos escalares crescem em magnitude
- Empurra softmax para regiões com gradientes pequenos
- O fator de escala √dk contrabalança este efeito
Por que o Escalonamento Importa
Para ilustrar: assuma que q e k são variáveis aleatórias independentes com média 0 e variância 1. Seu produto escalar tem média 0 e variância dk. Sem escalonamento, para dk grande, a variância dos produtos escalares se torna grande, empurrando as saídas do softmax para 0 ou 1 (regiões saturadas com gradientes desvanecentes).
3. Aplicar Softmax:
- Converte scores em distribuição de probabilidade
- Garante que os pesos de atenção somem 1 para cada query
- Cria padrões de atenção suaves e diferenciáveis
4. Soma Ponderada dos Valores:
- Combina valores de acordo com os pesos de atenção
- Produz representação contextualizada para cada posição
- Saída tem mesma dimensionalidade que os valores
Análise de Complexidade
Conforme o artigo original, a complexidade computacional é:
- Complexidade de Tempo: O(n² · d) onde n é comprimento da sequência, d é dimensão
- Operações Sequenciais: O(1) - completamente paralelizável
- Comprimento Máximo do Caminho: O(1) - constante para qualquer par de posições
Compare com RNNs: O(n) operações sequenciais, O(n) comprimento máximo do caminho, tornando Transformers superiores para sequências longas e computação paralela.
Exemplo Numérico Simples
Considere a frase: "O gato dorme"
Ao processar "gato", a atenção pode calcular:
- Atenção para "O": 0.1 (baixa relevância)
- Atenção para "gato": 0.5 (alta - o próprio token)
- Atenção para "dorme": 0.4 (alta - ação relacionada)
A representação final de "gato" será: 0.1 × VO + 0.5 × Vgato + 0.4 × Vdorme
3.3 Atenção Multi-Cabeça
Em vez de realizar uma única função de atenção, o Transformer emprega atenção multi-cabeça com h camadas de atenção paralelas (cabeças).
Por que Múltiplas Cabeças?
A atenção multi-cabeça permite que o modelo atenda conjuntamente à informação de diferentes subespaços de representação em diferentes posições. Com uma única cabeça de atenção, a média inibe essa capacidade.
- Cabeça 1: Pode focar em relações sintáticas (concordância sujeito-verbo)
- Cabeça 2: Pode capturar similaridades semânticas
- Cabeça 3: Pode identificar dependências de longo alcance
- Cabeça 4+: Aprendem padrões diversos relevantes para a tarefa
Onde cada cabeça é computada como:
$$\text{head}_i = \text{Attention}(QW^Q_i, KW^K_i, VW^V_i)$$Detalhes Matemáticos
Cada cabeça tem matrizes de projeção aprendidas:
- $W^Q_i \in \mathbb{R}^{d_{model} \times d_k}$ - Projeção de Query para cabeça i
- $W^K_i \in \mathbb{R}^{d_{model} \times d_k}$ - Projeção de Key para cabeça i
- $W^V_i \in \mathbb{R}^{d_{model} \times d_v}$ - Projeção de Value para cabeça i
- $W^O \in \mathbb{R}^{h \cdot d_v \times d_{model}}$ - Projeção de saída
Hiperparâmetros do Artigo:
- Número de cabeças: h = 8
- Dimensão do modelo: dmodel = 512
- Dimensão Key/Query: dk = dv = dmodel/h = 64
Devido à dimensão reduzida de cada cabeça, o custo computacional total é similar à atenção de cabeça única com dimensionalidade completa, enquanto fornece os benefícios de múltiplos subespaços de representação.
💻 Multi-Head Attention em PyTorch
import torch
import torch.nn as nn
import math
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
# Matrizes de projeção para Q, K, V
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
# Matriz de saída
self.W_o = nn.Linear(d_model, d_model)
def scaled_dot_product_attention(self, Q, K, V, mask=None):
# Q, K, V: (batch, num_heads, seq_len, d_k)
# Calcula scores de atenção
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# Aplica máscara (se fornecida)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# Aplica softmax
attention_weights = torch.softmax(scores, dim=-1)
# Multiplica por V
output = torch.matmul(attention_weights, V)
return output, attention_weights
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# Projeções lineares
Q = self.W_q(Q) # (batch, seq_len, d_model)
K = self.W_k(K)
V = self.W_v(V)
# Divide em múltiplas cabeças
# (batch, seq_len, d_model) -> (batch, seq_len, num_heads, d_k)
Q = Q.view(batch_size, -1, self.num_heads, self.d_k)
K = K.view(batch_size, -1, self.num_heads, self.d_k)
V = V.view(batch_size, -1, self.num_heads, self.d_k)
# Transpõe para (batch, num_heads, seq_len, d_k)
Q = Q.transpose(1, 2)
K = K.transpose(1, 2)
V = V.transpose(1, 2)
# Calcula atenção
output, attention_weights = self.scaled_dot_product_attention(Q, K, V, mask)
# Concatena cabeças
# (batch, num_heads, seq_len, d_k) -> (batch, seq_len, d_model)
output = output.transpose(1, 2).contiguous()
output = output.view(batch_size, -1, self.d_model)
# Projeção final
output = self.W_o(output)
return output, attention_weights
4. Arquitetura do Transformer
4.1 Visão Geral
O Transformer completo é uma arquitetura encoder-decoder composta por:
Componentes Principais
- Encoder: Processa a sequência de entrada
- Decoder: Gera a sequência de saída
- Positional Encoding: Adiciona informação de posição
- Multi-Head Attention: Captura relações entre tokens
- Feed-Forward Networks: Transforma representações
- Layer Normalization: Estabiliza treinamento
- Residual Connections: Facilita propagação do gradiente
4.2 Encoder
O encoder é composto por uma pilha de N camadas idênticas (geralmente N=6). Cada camada tem dois sub-componentes:
Estrutura de uma Camada do Encoder
1. Multi-Head Self-Attention:
- Permite que cada posição atenda todas as posições
- Captura relações e dependências
2. Feed-Forward Network:
- Duas camadas lineares com ReLU no meio
- Aplicada independentemente para cada posição
Ambos têm:
- Residual Connection: adiciona entrada à saída
- Layer Normalization: normaliza após a adição
💻 Encoder Layer em PyTorch
class EncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
# Multi-Head Attention
self.self_attention = MultiHeadAttention(d_model, num_heads)
# Feed-Forward Network
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model)
)
# Layer Normalization
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
# Dropout
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Self-Attention com residual connection
attn_output, _ = self.self_attention(x, x, x, mask)
x = self.norm1(x + self.dropout1(attn_output))
# Feed-Forward com residual connection
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout2(ff_output))
return x
class Encoder(nn.Module):
def __init__(self, num_layers, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
# Pilha de camadas encoder
self.layers = nn.ModuleList([
EncoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
def forward(self, x, mask=None):
# Passa por todas as camadas
for layer in self.layers:
x = layer(x, mask)
return x
4.3 Decoder
O decoder também é uma pilha de N camadas, mas com três sub-componentes:
Estrutura de uma Camada do Decoder
1. Masked Multi-Head Self-Attention:
- Similar ao encoder, mas com máscara
- Impede que posições atendam a posições futuras
- Necessário para treinamento autoregressivo
2. Multi-Head Cross-Attention:
- Queries vêm do decoder
- Keys e Values vêm do encoder
- Permite que o decoder "olhe" para a entrada
3. Feed-Forward Network:
- Mesma estrutura do encoder
Masking no Decoder
Durante o treinamento, o decoder vê toda a sequência alvo de uma vez, mas precisamos impedir que ele "trapaceie" olhando tokens futuros. A máscara garante que a predição na posição i só pode depender das posições anteriores (< i).
💻 Decoder Layer em PyTorch
class DecoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
# Masked Self-Attention
self.self_attention = MultiHeadAttention(d_model, num_heads)
# Cross-Attention (com encoder)
self.cross_attention = MultiHeadAttention(d_model, num_heads)
# Feed-Forward
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model)
)
# Layer Norms
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
# Dropouts
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
self.dropout3 = nn.Dropout(dropout)
def forward(self, x, encoder_output, src_mask=None, tgt_mask=None):
# Masked Self-Attention
attn_output, _ = self.self_attention(x, x, x, tgt_mask)
x = self.norm1(x + self.dropout1(attn_output))
# Cross-Attention com encoder
attn_output, _ = self.cross_attention(
x, encoder_output, encoder_output, src_mask
)
x = self.norm2(x + self.dropout2(attn_output))
# Feed-Forward
ff_output = self.feed_forward(x)
x = self.norm3(x + self.dropout3(ff_output))
return x
4.4 Codificação Posicional
Como o Transformer não contém recorrência ou convolução, ele não tem noção inerente de ordem ou posição dos tokens. Codificações posicionais injetam informação sobre a posição relativa ou absoluta dos tokens na sequência.
Por que Informação Posicional é Crítica
Sem codificação posicional, "O gato persegue o rato" e "O rato persegue o gato" teriam representações idênticas! A ordem das palavras é crucial na linguagem natural.
O artigo usa funções senoidais de diferentes frequências para codificação posicional:
Onde:
- pos = posição na sequência (0 até seq_len - 1)
- i = índice da dimensão (0 até dmodel/2 - 1)
- Dimensões pares usam seno, dimensões ímpares usam cosseno
Propriedades da Codificação Senoidal
Esta formulação foi escolhida por várias razões:
1. Transformações Lineares:
- Para qualquer deslocamento fixo k, PEpos+k pode ser representado como função linear de PEpos
- O modelo pode facilmente aprender a atender por posições relativas
2. Codificação Única:
- Cada posição recebe um vetor de codificação único
- Diferentes dimensões têm comprimentos de onda formando progressão geométrica de 2π a 10000·2π
3. Extrapolação:
- Pode generalizar para comprimentos de sequência maiores que os exemplos de treinamento
- Embeddings posicionais aprendidos mostraram desempenho similar mas sem garantia de extrapolação
4. Valores Limitados:
- Todos os valores no intervalo [-1, 1]
- Estável e bem comportado durante o treinamento
Intuição: Progressão de Comprimento de Onda
Pense na codificação posicional como um relógio com múltiplos ponteiros:
- Dimensões rápidas (alta frequência): Mudam rapidamente, codificam posição detalhada
- Dimensões lentas (baixa frequência): Mudam lentamente, codificam posição grosseira
- Juntas, fornecem um "timestamp" único para cada posição
As primeiras dimensões completam muitos ciclos sobre a sequência (como segundos em um relógio), enquanto dimensões posteriores completam menos ciclos (como horas).
💻 Positional Encoding em PyTorch
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
# Cria matriz de positional encoding
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
# Calcula divisor
div_term = torch.exp(
torch.arange(0, d_model, 2).float() *
(-math.log(10000.0) / d_model)
)
# Aplica seno para posições pares
pe[:, 0::2] = torch.sin(position * div_term)
# Aplica cosseno para posições ímpares
pe[:, 1::2] = torch.cos(position * div_term)
# Adiciona dimensão batch
pe = pe.unsqueeze(0)
# Registra como buffer (não é parâmetro treinável)
self.register_buffer('pe', pe)
def forward(self, x):
# Adiciona positional encoding ao input
# x: (batch, seq_len, d_model)
x = x + self.pe[:, :x.size(1), :]
return x
4.5 Feed-Forward Networks
Cada camada contém uma rede feed-forward totalmente conectada que é aplicada independentemente a cada posição:
Características:
- Duas transformações lineares com ReLU no meio
- Dimensão interna tipicamente 4× maior (d_ff = 4 × d_model)
- Mesmos pesos para todas as posições, mas diferentes entre camadas
- Adiciona capacidade de transformação não-linear
5. Implementação Prática
5.1 Implementação Completa em PyTorch
Vamos juntar todos os componentes em um Transformer completo:
💻 Transformer Completo
class Transformer(nn.Module):
def __init__(
self,
src_vocab_size,
tgt_vocab_size,
d_model=512,
num_heads=8,
num_encoder_layers=6,
num_decoder_layers=6,
d_ff=2048,
dropout=0.1,
max_len=5000
):
super().__init__()
# Embeddings
self.src_embedding = nn.Embedding(src_vocab_size, d_model)
self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)
# Positional Encoding
self.positional_encoding = PositionalEncoding(d_model, max_len)
# Encoder e Decoder
self.encoder = Encoder(
num_encoder_layers, d_model, num_heads, d_ff, dropout
)
self.decoder = Decoder(
num_decoder_layers, d_model, num_heads, d_ff, dropout
)
# Camada final de projeção
self.output_projection = nn.Linear(d_model, tgt_vocab_size)
# Dropout
self.dropout = nn.Dropout(dropout)
# Fator de escala para embeddings
self.d_model = d_model
def make_src_mask(self, src):
# Cria máscara para padding no source
# src: (batch, src_len)
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
# (batch, 1, 1, src_len)
return src_mask
def make_tgt_mask(self, tgt):
# Cria máscara para padding e look-ahead no target
# tgt: (batch, tgt_len)
batch_size, tgt_len = tgt.shape
# Máscara de padding
tgt_pad_mask = (tgt != 0).unsqueeze(1).unsqueeze(2)
# (batch, 1, 1, tgt_len)
# Máscara look-ahead (triangular inferior)
tgt_sub_mask = torch.tril(
torch.ones((tgt_len, tgt_len), device=tgt.device)
).bool()
# (tgt_len, tgt_len)
# Combina máscaras
tgt_mask = tgt_pad_mask & tgt_sub_mask
# (batch, 1, tgt_len, tgt_len)
return tgt_mask
def forward(self, src, tgt):
# src: (batch, src_len)
# tgt: (batch, tgt_len)
# Cria máscaras
src_mask = self.make_src_mask(src)
tgt_mask = self.make_tgt_mask(tgt)
# Embeddings com scaling
src_embedded = self.dropout(
self.positional_encoding(
self.src_embedding(src) * math.sqrt(self.d_model)
)
)
tgt_embedded = self.dropout(
self.positional_encoding(
self.tgt_embedding(tgt) * math.sqrt(self.d_model)
)
)
# Encoder
encoder_output = self.encoder(src_embedded, src_mask)
# Decoder
decoder_output = self.decoder(
tgt_embedded, encoder_output, src_mask, tgt_mask
)
# Projeção final
output = self.output_projection(decoder_output)
return output
class Decoder(nn.Module):
def __init__(self, num_layers, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.layers = nn.ModuleList([
DecoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
def forward(self, x, encoder_output, src_mask=None, tgt_mask=None):
for layer in self.layers:
x = layer(x, encoder_output, src_mask, tgt_mask)
return x
5.2 Exemplo de Uso
💻 Treinando um Transformer
# Hiperparâmetros
src_vocab_size = 10000
tgt_vocab_size = 10000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
dropout = 0.1
batch_size = 32
max_len = 100
# Cria modelo
model = Transformer(
src_vocab_size=src_vocab_size,
tgt_vocab_size=tgt_vocab_size,
d_model=d_model,
num_heads=num_heads,
num_encoder_layers=num_layers,
num_decoder_layers=num_layers,
d_ff=d_ff,
dropout=dropout,
max_len=max_len
)
# Move para GPU se disponível
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
# Otimizador e loss
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
criterion = nn.CrossEntropyLoss(ignore_index=0) # Ignora padding
# Exemplo de dados (toy example)
src = torch.randint(1, src_vocab_size, (batch_size, 50)).to(device)
tgt = torch.randint(1, tgt_vocab_size, (batch_size, 50)).to(device)
# Forward pass
model.train()
output = model(src, tgt[:, :-1]) # Teacher forcing
# Calcula loss
loss = criterion(
output.reshape(-1, tgt_vocab_size),
tgt[:, 1:].reshape(-1)
)
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'Loss: {loss.item():.4f}')
# Inferência (geração)
model.eval()
with torch.no_grad():
# Começa com token de início
generated = torch.tensor([[1]]).to(device) # [BOS]
for _ in range(max_len):
output = model(src[:1], generated)
# Pega próximo token
next_token = output[:, -1, :].argmax(dim=-1, keepdim=True)
generated = torch.cat([generated, next_token], dim=1)
# Para se gerar token de fim
if next_token.item() == 2: # [EOS]
break
print(f'Generated sequence: {generated}')
Dicas de Treinamento
- Learning Rate Scheduling: Use warmup seguido de decay
- Label Smoothing: Melhora generalização
- Dropout: Importante para evitar overfitting
- Gradient Clipping: Estabiliza treinamento
- Batch Size: Quanto maior, melhor (se couber na memória)
6. Aplicações e Variantes
6.1 BERT (Bidirectional Encoder Representations from Transformers)
Características do BERT
- Arquitetura: Apenas Encoder (sem Decoder)
- Treinamento: Masked Language Modeling (MLM) + Next Sentence Prediction (NSP)
- Bidirectional: Vê contexto completo (esquerda e direita)
- Uso: Principalmente para tarefas de compreensão (classificação, NER, Q&A)
Masked Language Modeling
Entrada: "O [MASK] está no [MASK]"
Objetivo: Prever "gato" e "telhado"
O modelo aprende representações contextuais ricas ao tentar prever palavras mascaradas usando contexto bidirecional.
💻 Usando BERT com Hugging Face
from transformers import BertTokenizer, BertModel
import torch
# Carrega tokenizer e modelo pré-treinado
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
# Texto de entrada
text = "O Transformer revolucionou o NLP"
inputs = tokenizer(text, return_tensors='pt')
# Forward pass
with torch.no_grad():
outputs = model(**inputs)
# Obtém representações
last_hidden_states = outputs.last_hidden_state
print(f'Shape: {last_hidden_states.shape}')
# Shape: [1, sequence_length, hidden_size]
6.2 GPT (Generative Pre-trained Transformer)
Características do GPT
- Arquitetura: Apenas Decoder (sem Encoder)
- Treinamento: Causal Language Modeling (prever próxima palavra)
- Unidirecional: Vê apenas contexto à esquerda
- Uso: Principalmente para geração de texto
Evolução do GPT
- GPT-1 (2018): 117M parâmetros
- GPT-2 (2019): 1.5B parâmetros
- GPT-3 (2020): 175B parâmetros
- GPT-4 (2023): Multimodal, parâmetros não divulgados
Cada versão demonstrou capacidades emergentes impressionantes com escala crescente.
💻 Geração de Texto com GPT-2
from transformers import GPT2LMHeadModel, GPT2Tokenizer
# Carrega modelo e tokenizer
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
model = GPT2LMHeadModel.from_pretrained('gpt2')
# Prompt inicial
prompt = "Transformers são redes neurais que"
inputs = tokenizer(prompt, return_tensors='pt')
# Gera texto
outputs = model.generate(
inputs['input_ids'],
max_length=100,
num_return_sequences=1,
temperature=0.7,
top_k=50,
top_p=0.95,
do_sample=True
)
# Decodifica resultado
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(generated_text)
6.3 Vision Transformers (ViT)
Transformers não são limitados a texto! Vision Transformers aplicam a mesma arquitetura a imagens.
Como Funciona o ViT
- Divide a imagem em patches: Ex: 224×224 → 196 patches de 16×16
- Lineariza cada patch: Flatten e projeção linear
- Adiciona positional embeddings: Para manter informação espacial
- Adiciona token [CLS]: Para representação global
- Aplica Transformer Encoder: Como em BERT
- Classifica usando [CLS]: MLP na saída do [CLS] token
Outras Aplicações de Transformers
- Áudio: Whisper (transcrição de áudio)
- Multimodal: CLIP (visão + linguagem)
- Proteínas: AlphaFold (estrutura de proteínas)
- Código: Codex/Copilot (geração de código)
- Jogos: Decision Transformers (RL)
🎯 Resumo dos Conceitos Principais
✨ O que Aprendemos
- Self-Attention: Permite que tokens atendam uns aos outros em paralelo
- Multi-Head Attention: Múltiplas perspectivas de atenção simultaneamente
- Positional Encoding: Adiciona informação de posição sem recorrência
- Encoder-Decoder: Arquitetura poderosa para sequence-to-sequence
- Paralelização: Processamento eficiente aproveitando GPUs
- Escalabilidade: Funciona incrivelmente bem com mais dados e parâmetros
🚀 Por que Transformers são Revolucionários
Os Transformers mudaram fundamentalmente a IA por permitirem:
- Modelos maiores e mais poderosos (GPT-3, GPT-4)
- Transfer learning efetivo (fine-tuning de modelos pré-treinados)
- Aplicação além de NLP (visão, áudio, multimodal)
- Emergência de capacidades com escala
"Attention is All You Need" - realmente era tudo que precisávamos! 🎉