Phase 05 - Lesson 04
GloVe, FastText e Embeddings de Subpalavras
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
O Word2Vec treinou um embedding por palavra. O GloVe fatorou a matriz de coocorrência. O FastText embutiu os pedaços. O BPE fez a ponte para os transformers.
Tipo: Build Linguagens: Python Pré-requisitos: Fase 5 · 03 (Word2Vec do Zero) Tempo: ~45 minutos
O Problema
O Word2Vec deixou duas questões em aberto.
Primeiro, havia uma linha paralela de pesquisa que fatorava a matriz de coocorrência diretamente (LSA, HAL) em vez de fazer atualizações skip-gram online. A abordagem iterativa do Word2Vec era fundamentalmente melhor, ou a diferença era um artefato de como os dois métodos tratavam as contagens? O GloVe respondeu isso: fatoração de matriz com uma função de perda cuidadosamente escolhida iguala ou supera o Word2Vec, e custa menos para treinar.
Segundo, nenhum dos métodos tinha uma solução para palavras que nunca havia visto. Zoomer-approved, dogecoin, qualquer nome próprio cunhado na semana passada, toda forma flexionada de uma raiz rara. O FastText resolveu isso embutindo n-gramas de caracteres: uma palavra é a soma de suas partes, incluindo morfemas, então mesmo palavras fora do vocabulário recebem um vetor sensato.
Terceiro, quando os transformers chegaram, a questão mudou novamente. Vocabulários em nível de palavra atingem o limite em torno de um milhão de entradas; a linguagem real é mais aberta que isso. A codificação por pares de bytes (BPE) e suas variantes resolveram isso aprendendo um vocabulário de unidades de subpalavras frequentes que cobre tudo. Todo tokenizador moderno de todo LLM moderno é um tokenizador de subpalavras.
Esta lição percorre os três e depois explica qual usar e quando.
O Conceito
GloVe (Global Vectors). Construa a matriz de coocorrência palavra-palavra X onde X[i][j] é a frequência com que a palavra j aparece no contexto da palavra i. Treine vetores de modo que v_i · v_j + b_i + b_j ≈ log(X[i][j]). Pondere a perda para que pares frequentes não dominem. Pronto.
FastText. Uma palavra é a soma de seus n-gramas de caracteres mais a própria palavra. where se torna <wh, whe, her, ere, re>, <where>. O vetor da palavra é a soma desses vetores componentes. Treine como Word2Vec. Benefício: palavras não vistas (whereupon) se compõem a partir de n-gramas conhecidos.
BPE (Byte-Pair Encoding). Comece com um vocabulário de bytes (ou caracteres) individuais. Conte todo par adjacente no corpus. Faça merge do par mais frequente em um novo token. Repita por k iterações. Resultado: um vocabulário de k + 256 tokens onde sequências frequentes (ing, tion, the) são tokens únicos e palavras raras são quebradas em pedaços familiares. Toda frase se tokeniza em algo.
Construa
GloVe: fatore a matriz de coocorrência
import numpy as np
from collections import Counter
def build_cooccurrence(docs, window=5):
pair_counts = Counter()
vocab = {}
for doc in docs:
for token in doc:
if token not in vocab:
vocab[token] = len(vocab)
for doc in docs:
indexed = [vocab[t] for t in doc]
for i, center in enumerate(indexed):
for j in range(max(0, i - window), min(len(indexed), i + window + 1)):
if i != j:
distance = abs(i - j)
pair_counts[(center, indexed[j])] += 1.0 / distance
return vocab, pair_counts
def glove_train(vocab, pair_counts, dim=16, epochs=100, lr=0.05, x_max=100, alpha=0.75, seed=0):
n = len(vocab)
rng = np.random.default_rng(seed)
W = rng.normal(0, 0.1, size=(n, dim))
W_tilde = rng.normal(0, 0.1, size=(n, dim))
b = np.zeros(n)
b_tilde = np.zeros(n)
for epoch in range(epochs):
for (i, j), x_ij in pair_counts.items():
weight = (x_ij / x_max) ** alpha if x_ij < x_max else 1.0
diff = W[i] @ W_tilde[j] + b[i] + b_tilde[j] - np.log(x_ij)
coef = weight * diff
grad_W_i = coef * W_tilde[j]
grad_W_tilde_j = coef * W[i]
W[i] -= lr * grad_W_i
W_tilde[j] -= lr * grad_W_tilde_j
b[i] -= lr * coef
b_tilde[j] -= lr * coef
return W + W_tilde
Duas engrenagens em movimento que vale nomear. A função de ponderação f(x) = (x/x_max)^alpha reduz o peso de pares muito frequentes (como (the, and)) para que não dominem a perda. O embedding final é a soma das tabelas W (centro) e W_tilde (contexto). Somar ambas é um truque publicado que tende a superar o uso de apenas uma.
FastText: embeddings cientes de subpalavras
def char_ngrams(word, n_min=3, n_max=6):
wrapped = f"<{word}>"
grams = {wrapped}
for n in range(n_min, n_max + 1):
for i in range(len(wrapped) - n + 1):
grams.add(wrapped[i:i + n])
return grams
>>> char_ngrams("where")
{'<where>', '<wh', 'whe', 'her', 'ere', 're>', '<whe', 'wher', 'here', 'ere>', '<wher', 'where', 'here>'}
Cada palavra é representada por seu conjunto de n-gramas (tipicamente de 3 a 6 caracteres). O embedding da palavra é a soma de seus embeddings de n-gramas. Para o treinamento skip-gram, encaixe isto onde o Word2Vec usava um único vetor.
def fasttext_vector(word, ngram_table):
grams = char_ngrams(word)
vecs = [ngram_table[g] for g in grams if g in ngram_table]
if not vecs:
return None
return np.sum(vecs, axis=0)
Para uma palavra não vista, você ainda obtém um vetor desde que alguns de seus n-gramas sejam conhecidos. whereupon compartilha <wh, her, ere e <where com where, então as duas ficam próximas uma da outra.
BPE: vocabulário de subpalavras aprendido
def learn_bpe(corpus, k_merges):
vocab = Counter()
for word, freq in corpus.items():
tokens = tuple(word) + ("</w>",)
vocab[tokens] = freq
merges = []
for _ in range(k_merges):
pair_freq = Counter()
for tokens, freq in vocab.items():
for a, b in zip(tokens, tokens[1:]):
pair_freq[(a, b)] += freq
if not pair_freq:
break
best = pair_freq.most_common(1)[0][0]
merges.append(best)
new_vocab = Counter()
for tokens, freq in vocab.items():
new_tokens = []
i = 0
while i < len(tokens):
if i + 1 < len(tokens) and (tokens[i], tokens[i + 1]) == best:
new_tokens.append(tokens[i] + tokens[i + 1])
i += 2
else:
new_tokens.append(tokens[i])
i += 1
new_vocab[tuple(new_tokens)] = freq
vocab = new_vocab
return merges
def apply_bpe(word, merges):
tokens = list(word) + ["</w>"]
for a, b in merges:
new_tokens = []
i = 0
while i < len(tokens):
if i + 1 < len(tokens) and tokens[i] == a and tokens[i + 1] == b:
new_tokens.append(a + b)
i += 2
else:
new_tokens.append(tokens[i])
i += 1
tokens = new_tokens
return tokens
>>> corpus = Counter({"low": 5, "lower": 2, "newest": 6, "widest": 3})
>>> merges = learn_bpe(corpus, k_merges=10)
>>> apply_bpe("lowest", merges)
['low', 'est</w>']
A primeira iteração faz merge do par adjacente mais comum. Depois de iterações suficientes, substrings frequentes (low, est, tion) tornam-se tokens únicos e palavras raras quebram de forma limpa.
Os tokenizadores reais de GPT / BERT / T5 aprendem de 30 mil a 100 mil merges. Resultado: qualquer texto se tokeniza em uma sequência de comprimento limitado de IDs conhecidos, nunca há OOV.
Use
Na prática, você raramente treina qualquer um destes por conta própria. Você carrega checkpoints pré-treinados.
import fasttext.util
fasttext.util.download_model("en", if_exists="ignore")
ft = fasttext.load_model("cc.en.300.bin")
print(ft.get_word_vector("whereupon").shape)
print(ft.get_word_vector("zoomerapproved").shape)
Para tokenização de subpalavras no estilo BPE na era dos transformers:
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("gpt2")
print(tok.tokenize("unbelievably tokenized"))
['un', 'bel', 'iev', 'ably', 'Ġtoken', 'ized']
O prefixo Ġ marca os limites de palavra (uma convenção do GPT-2). Todo tokenizador moderno é uma variante de BPE, WordPiece (BERT) ou SentencePiece (T5, LLaMA).
Quando escolher qual
| Situação | Escolha |
|---|---|
| Vetores de palavra pré-treinados de propósito geral, sem necessidade de tolerância a OOV | GloVe 300d |
| Vetores de palavra pré-treinados de propósito geral, precisa lidar com erros de digitação / neologismos / línguas morfologicamente ricas | FastText |
| Qualquer coisa que entre em um transformer (treinamento ou inferência) | Qualquer tokenizador que veio com o modelo. Nunca troque. |
| Treinar seu próprio modelo de linguagem do zero | Treine primeiro um tokenizador BPE ou SentencePiece no seu corpus |
| Classificação de texto em produção com um modelo linear | Ainda TF-IDF. Lição 02. |
Entregue
Salve como outputs/skill-tokenizer-picker.md:
---
name: tokenizer-picker
description: Pick a tokenization approach for a new language model or text pipeline.
version: 1.0.0
phase: 5
lesson: 04
tags: [nlp, tokenization, embeddings]
---
Given a task and dataset description, you output:
1. Tokenization strategy (word-level, BPE, WordPiece, SentencePiece, byte-level). One-sentence reason.
2. Vocabulary size target (e.g., 32k for an English-only LM, 64k-100k for multilingual).
3. Library call with the exact training command. Name the library. Quote the arguments.
4. One reproducibility pitfall. Tokenizer-model mismatch is the single most common silent production bug; call out which pair must be used together.
Refuse to recommend training a custom tokenizer when the user is fine-tuning a pretrained LLM. Refuse to recommend word-level tokenization for any model targeting production inference. Flag non-English / multi-script corpora as needing SentencePiece with byte fallback.
Exercícios
- Fácil. Execute
char_ngrams("playing")echar_ngrams("played"). Calcule a sobreposição de Jaccard dos dois conjuntos de n-gramas. Você deve ver pedaços compartilhados substanciais (pla,lay,play), que é o motivo pelo qual o FastText transfere bem entre variantes morfológicas. - Médio. Estenda
learn_bpepara acompanhar o crescimento do vocabulário. Plote os tokens-por-caractere-de-corpus em função do número de merges. Você deve ver compressão rápida no início, com assíntota perto de ~2-3 caracteres por token. - Difícil. Treine um BPE de mil merges sobre as obras completas de Shakespeare. Compare a tokenização de palavras comuns com a de nomes próprios raros. Meça a média de tokens por palavra antes e depois. Escreva o que te surpreendeu.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Matriz de coocorrência | Tabela de frequência palavra-palavra | X[i][j] = a frequência com que a palavra j aparece em uma janela ao redor da palavra i. |
| Subpalavra | Pedaço de uma palavra | Um n-grama de caracteres (FastText) ou token aprendido (BPE/WordPiece/SentencePiece). |
| BPE | Byte-pair encoding | Merge iterativo dos pares adjacentes mais frequentes até o vocabulário atingir o tamanho-alvo. |
| OOV | Fora do vocabulário | Palavra que o modelo nunca viu. Word2Vec/GloVe falham. FastText e BPE lidam com isso. |
| BPE em nível de byte | BPE sobre bytes brutos | O esquema do GPT-2. O vocabulário começa com 256 bytes, então nada nunca é OOV. |
Leitura Adicional
- Pennington, Socher, Manning (2014). GloVe: Global Vectors for Word Representation — o artigo do GloVe, sete páginas, ainda a melhor derivação da função de perda.
- Bojanowski et al. (2017). Enriching Word Vectors with Subword Information — FastText.
- Sennrich, Haddow, Birch (2016). Neural Machine Translation of Rare Words with Subword Units — o artigo que introduziu o BPE no NLP moderno.
- Resumo de tokenizadores da Hugging Face — como BPE, WordPiece e SentencePiece realmente diferem na prática.