Phase 05 - Lesson 03
Word Embeddings — Word2Vec do zero
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Uma palavra e a companhia que mantem. Treine uma rede rasa com essa ideia e a geometria emerge.
Tipo: Build Linguagens: Python Pre-requisitos: Fase 5 · 02 (BoW + TF-IDF), Fase 3 · 03 (Backpropagation do Zero) Tempo: ~75 minutos
O Problema
O TF-IDF sabe que dog e puppy sao palavras diferentes. Ele nao sabe que elas significam quase a mesma coisa. Um classificador treinado em dog nao consegue generalizar para uma resenha sobre puppy. Voce pode disfarcar isso listando sinonimos, mas isso falha com termos raros, jargao de dominio e todo idioma que voce nao antecipou.
Voce quer uma representacao em que dog e puppy cheguem perto um do outro no espaco. Em que king - man + woman caia perto de queen. Em que um modelo treinado em dog transfira algum sinal de graca para puppy.
O Word2Vec nos deu esse espaco. Rede neural de duas camadas, execucoes de treino com trilhoes de tokens, publicado em 2013. A arquitetura e quase constrangedoramente simples. Os resultados remodelaram o NLP por uma decada.
O Conceito
Hipotese distribucional (Firth, 1957): "Voce conhecera uma palavra pela companhia que ela mantem." Se duas palavras aparecem em contextos similares, provavelmente significam coisas similares.
O Word2Vec vem em dois sabores, ambos explorando essa ideia.
- Skip-gram. Dada uma palavra central, preve as palavras ao redor.
cat -> (the, sat, on)com janela de tamanho 2. - CBOW (continuous bag of words). Dadas as palavras ao redor, preve a central.
(the, sat, on) -> cat.
O skip-gram e mais lento para treinar, mas lida melhor com palavras raras. Tornou-se o padrao.
A rede tem uma camada oculta sem nao linearidade. A entrada e um vetor one-hot sobre o vocabulario. A saida e um softmax sobre o vocabulario. Apos o treino, voce descarta a camada de saida. Os pesos da camada oculta sao os embeddings.
one-hot(center) ── W ──▶ hidden (d-dim) ── W' ──▶ softmax(vocab)
^
this is the embedding
O truque: um softmax sobre 100k palavras e proibitivamente caro. O Word2Vec usa amostragem negativa (negative sampling) para transforma-lo em uma tarefa de classificacao binaria. Preve "esta palavra de contexto apareceu perto desta palavra central, sim ou nao". Amostre um punhado de palavras negativas (que nao co-ocorrem) por par de treino em vez de calcular o softmax sobre o vocabulario inteiro.
Construa
Passo 1: pares de treino a partir de um corpus
def skipgram_pairs(docs, window=2):
pairs = []
for doc in docs:
for i, center in enumerate(doc):
for j in range(max(0, i - window), min(len(doc), i + window + 1)):
if i == j:
continue
pairs.append((center, doc[j]))
return pairs
>>> skipgram_pairs([["the", "cat", "sat", "on", "mat"]], window=2)
[('the', 'cat'), ('the', 'sat'),
('cat', 'the'), ('cat', 'sat'), ('cat', 'on'),
('sat', 'the'), ('sat', 'cat'), ('sat', 'on'), ('sat', 'mat'),
...]
Todo par (central, contexto) dentro de uma janela e um exemplo de treino positivo.
Passo 2: tabelas de embedding
Duas matrizes. W e a tabela de embeddings da palavra central (a que voce mantem). W' e a tabela da palavra de contexto (frequentemente descartada, as vezes mediada com W).
import numpy as np
def init_embeddings(vocab_size, dim, seed=0):
rng = np.random.default_rng(seed)
W = rng.normal(0, 0.1, size=(vocab_size, dim))
W_prime = rng.normal(0, 0.1, size=(vocab_size, dim))
return W, W_prime
Inicializacao aleatoria pequena. Vocabulario de 10k e dim 100 e realista; para ensino, 50 de vocabulario x 16 de dim e suficiente para ver a geometria.
Passo 3: objetivo de amostragem negativa
Para cada par positivo (center, context), amostre k palavras aleatorias do vocabulario como negativas. Treine o modelo para que o produto escalar W[center] · W'[context] seja alto para positivos e baixo para negativos.
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-np.clip(x, -20, 20)))
def train_pair(W, W_prime, center_idx, context_idx, negative_indices, lr):
v_c = W[center_idx]
u_pos = W_prime[context_idx]
u_negs = W_prime[negative_indices]
pos_score = sigmoid(v_c @ u_pos)
neg_scores = sigmoid(u_negs @ v_c)
grad_center = (pos_score - 1) * u_pos
for i, u in enumerate(u_negs):
grad_center += neg_scores[i] * u
W[context_idx] = W[context_idx]
W_prime[context_idx] -= lr * (pos_score - 1) * v_c
for i, neg_idx in enumerate(negative_indices):
W_prime[neg_idx] -= lr * neg_scores[i] * v_c
W[center_idx] -= lr * grad_center
A formula magica: perda logistica no par positivo (queremos sigmoide perto de 1) mais perda logistica nos pares negativos (queremos sigmoide perto de 0). Os gradientes fluem para ambas as tabelas. A derivacao completa esta no artigo original; percorra-a uma vez com lapis e papel se quiser que ela grude.
Passo 4: treine em um corpus de brinquedo
def train(docs, dim=16, window=2, k_neg=5, epochs=100, lr=0.05, seed=0):
vocab = build_vocab(docs)
vocab_size = len(vocab)
rng = np.random.default_rng(seed)
W, W_prime = init_embeddings(vocab_size, dim, seed=seed)
pairs = skipgram_pairs(docs, window=window)
for epoch in range(epochs):
rng.shuffle(pairs)
for center, context in pairs:
c_idx = vocab[center]
ctx_idx = vocab[context]
negs = rng.integers(0, vocab_size, size=k_neg)
negs = [n for n in negs if n != ctx_idx and n != c_idx]
train_pair(W, W_prime, c_idx, ctx_idx, negs, lr)
return vocab, W
Apos epocas suficientes em um corpus grande, palavras que compartilham contextos tem embeddings centrais similares. Em um corpus de brinquedo, voce ve o efeito de forma tenue. Em bilhoes de tokens, voce o ve de forma dramatica.
Passo 5: o truque da analogia
def nearest(vocab, W, target_vec, topk=5, exclude=None):
exclude = exclude or set()
inv_vocab = {i: w for w, i in vocab.items()}
norms = np.linalg.norm(W, axis=1, keepdims=True) + 1e-9
W_norm = W / norms
target = target_vec / (np.linalg.norm(target_vec) + 1e-9)
sims = W_norm @ target
order = np.argsort(-sims)
out = []
for i in order:
if i in exclude:
continue
out.append((inv_vocab[i], float(sims[i])))
if len(out) == topk:
break
return out
def analogy(vocab, W, a, b, c, topk=5):
v = W[vocab[b]] - W[vocab[a]] + W[vocab[c]]
return nearest(vocab, W, v, topk=topk, exclude={vocab[a], vocab[b], vocab[c]})
Sobre os vetores pre-treinados Google News de 300d:
>>> analogy(vocab, W, "man", "king", "woman")
[('queen', 0.71), ('monarch', 0.62), ('princess', 0.59), ...]
king - man + woman = queen. Nao porque o modelo sabe o que e realeza. Porque o vetor (king - man) captura algo como "real", e adiciona-lo a woman cai perto da regiao real-feminina.
Use
Escrever o Word2Vec do zero e didatico. O NLP de producao usa gensim.
from gensim.models import Word2Vec
sentences = [
["the", "cat", "sat", "on", "the", "mat"],
["the", "dog", "ran", "across", "the", "room"],
]
model = Word2Vec(
sentences,
vector_size=100,
window=5,
min_count=1,
sg=1,
negative=5,
workers=4,
epochs=30,
)
print(model.wv["cat"])
print(model.wv.most_similar("cat", topn=3))
Para trabalho real, voce quase nunca treina o Word2Vec por conta propria. Voce baixa vetores pre-treinados.
- GloVe — abordagem de fatoracao de matriz de co-ocorrencia da Stanford. Checkpoints de 50d, 100d, 200d, 300d. Boa cobertura geral. A licao 04 cobre o GloVe especificamente.
- fastText — extensao do Word2Vec do Facebook que embute n-gramas de caracteres. Lida com palavras fora do vocabulario compondo subpalavras. Licao 04.
- Word2Vec pre-treinado no Google News — 300d, vocabulario de 3M de palavras, publicado em 2013. Ainda baixado diariamente.
Quando o Word2Vec ainda vence em 2026
- Recuperacao leve e especifica de dominio. Treine em resumos medicos em uma hora num laptop e obtenha vetores especializados que nenhum modelo geral captura.
- Engenharia de features estilo analogia.
gender_vector = mean(man - woman pairs). Subtraia-o de outras palavras para obter um eixo de genero neutro. Ainda usado em pesquisa de equidade (fairness). - Interpretabilidade. 100d e pequeno o suficiente para plotar via PCA ou t-SNE e realmente ver clusters se formando.
- Em qualquer lugar onde a inferencia precise rodar no dispositivo sem GPU. A busca do Word2Vec e a leitura de uma unica linha.
Onde o Word2Vec falha
O muro da polissemia. bank tem um unico vetor. river bank e financial bank o compartilham. table (planilha vs. movel) o compartilha. Um classificador a jusante nao consegue distinguir os sentidos a partir do vetor.
Embeddings contextuais (ELMo, BERT, todo transformer desde entao) resolveram isso produzindo um vetor diferente para cada ocorrencia da palavra com base no contexto ao redor. Esse e o salto do Word2Vec para o BERT: do estatico ao contextual. A Fase 7 cobre a metade do transformer.
O problema de fora do vocabulario (out-of-vocabulary) e a outra falha. O Word2Vec nunca viu Zoomer-approved se isso nao estava nos dados de treino. Nenhum fallback. O fastText resolve isso com composicao de subpalavras (licao 04).
Entregue
Salve como outputs/skill-embedding-probe.md:
---
name: embedding-probe
description: Inspect a word2vec model. Run analogies, find neighbors, diagnose quality.
version: 1.0.0
phase: 5
lesson: 03
tags: [nlp, embeddings, debugging]
---
You probe trained word embeddings to verify they are working. Given a `gensim.models.KeyedVectors` object and a vocabulary, you run:
1. Three canonical analogy tests. `king : man :: queen : woman`. `paris : france :: tokyo : japan`. `walking : walked :: swimming : ?`. Report the top-1 result and its cosine.
2. Five nearest-neighbor tests on domain-specific words the user supplies. Print top-5 neighbors with cosines.
3. One symmetry check. `similarity(a, b) == similarity(b, a)` to within float precision.
4. One degenerate check. If any embedding has a norm below 0.01 or above 100, the model has a training bug. Flag it.
Refuse to declare a model good on analogy accuracy alone. Analogy benchmarks are gameable and do not transfer to downstream tasks. Recommend intrinsic + downstream evaluation together.
Exercicios
- Facil. Rode o loop de treino em um corpus minusculo (20 frases sobre gatos e cachorros). Apos 200 epocas, verifique se
nearest(vocab, W, W[vocab["cat"]])retornadogem seu top 3. Se nao, aumente as epocas ou o vocabulario. - Medio. Adicione subamostragem de palavras frequentes. Palavras com frequencia acima de
10^-5sao descartadas dos pares de treino com probabilidade proporcional a sua frequencia. Meca o efeito na similaridade de palavras raras. - Dificil. Treine um modelo no corpus 20 Newsgroups. Calcule dois eixos de vies:
he - sheedoctor - nurse. Projete palavras de ocupacao em ambos os eixos. Relate quais ocupacoes tem a maior diferenca de vies. Esse e o tipo de sondagem que pesquisadores de equidade usam.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Word embedding | Palavra como vetor | Uma representacao densa, de baixa dimensao (tipicamente 100-300) aprendida a partir do contexto. |
| Skip-gram | Truque do Word2Vec | Prever palavras de contexto a partir da palavra central. Mais lento que o CBOW, melhor para palavras raras. |
| Amostragem negativa | Atalho de treino | Substituir o softmax sobre o vocabulario completo por classificacao binaria contra k palavras aleatorias. |
| Embedding estatico | Um vetor por palavra | Mesmo vetor independente do contexto. Falha na polissemia. |
| Embedding contextual | Vetor sensivel ao contexto | Vetor diferente para cada ocorrencia com base nas palavras ao redor. O que os transformers produzem. |
| OOV | Fora do vocabulario | Palavra nao vista no treino. O Word2Vec nao consegue produzir um vetor para elas. |
Leitura Complementar
- Mikolov et al. (2013). Distributed Representations of Words and Phrases and their Compositionality — o artigo da amostragem negativa. Curto e legivel.
- Rong, X. (2014). word2vec Parameter Learning Explained — a derivacao mais clara dos gradientes, caso a matematica do artigo original pareca densa.
- Tutorial do Word2Vec no gensim — configuracoes de treino de producao que realmente funcionam.