References
- Paper: Efficient Estimation of Word Representations in Vector Space, Mikolov et al., 2013.
- Lecture: CS224N Lecture 1.
1. Basic Idea
Word2Vec 是一种通过预测上下文来学习词向量的模型.
Core idea:
- 拥有一个大型文本 corpus.
- 模型扫描文本, 每次选取一个中心词 和其上下文词 .
- 通过最大化 “在 的上下文中 出现的概率” 来优化词向量.

Word2Vec 有两种实现方式:
- Skip-gram: given the center word, predict context words. This is more commonly used.
- CBOW (Continuous Bag of Words): given context words, predict the center word.
2. Skip-gram Model
2.1 Intuition
Given sentence “the quick brown fox jumps over the lazy dog”:
- Center word: fox.
- Context words with window size 2: quick, brown, jumps, over.
Goal. Maximize the probability of context words and minimize the probability of non-context words.

2.2 Model Definition
Notation:
- Text position .
- Word (token) at position , where the word vector is introduced separately below as (center) / (context), each in with often to .
- Context window size , usually to .
Likelihood:
Negative log-likelihood:
2.3 Probability Calculation
Dual vector representation. 为计算方便, 对同一词汇使用两种向量:
- : vector when is the center word.
- : vector when is the context word.
Conditional probability with softmax:
其中 为词汇表. 内积 衡量两个词向量的相似性.
Cross-entropy-style loss:
Equivalently:
3. Gradient Derivation
3.1 Gradient Descent
Initialization:
Update rule:
3.2 Gradient Calculation
For center word and context word , calculate :
First term:
Second term:
3.3 Final Result
Intuition:
- : observed context word vector.
- : expected context word vector under the current model.
- Gradient = observed - expected.
- 通过梯度下降, 使词向量接近观测到的上下文, 远离预期的上下文.
4. Problem with Softmax
The computational bottleneck is the denominator:
- 词汇表 通常很大, often more than 100k.
- 每次更新都需要 complexity.
- Solution: Negative Sampling, which approximates the full vocabulary with a small number of negative samples.
5. Neural Network Perspective
Skip-gram 模型本质上是一个浅层神经网络.

5.1 Network Structure
| Layer | Shape | Description |
|---|---|---|
| Input layer | One-hot vector of the center word | |
| Hidden layer | Word embedding vector | |
| Output layer | Prediction scores or logits |
5.2 Forward Propagation
- Input to hidden layer, i.e. embedding lookup:
- : center-word embedding matrix.
- One-hot multiplication is equivalent to taking row .
- Hidden layer to output layer, i.e. linear transformation:
- : context-word embedding matrix.
- : score of word .
- Softmax / Sigmoid:
- Full softmax: .
- Negative sampling: or .
5.3 Training Process
1. Forward: one-hot -> embedding -> logits -> softmax/sigmoid
2. Loss: cross-entropy / negative sampling loss
3. Backward: calculate gradients and update W_in, W_out
4. Repeat: sample (center, context) pairs and iterateKey insights:
- 无隐藏层激活函数, 即恒等映射.
- 本质是两层线性变换 + softmax/sigmoid.
- 模型参数就是两个嵌入矩阵.
6. Matrix Factorization Perspective
6.1 Full Softmax Matrix Form
- Take the center-word vector:
- Calculate scores for all words:
- Apply softmax:
- Cross-entropy loss:
6.2 Negative Sampling Matrix Form
- Positive sample score:
- Negative sample scores:
Here is the embedding matrix for negative samples.
- Loss:
7. PyTorch Implementation
7.1 Data Preparation
import torch
import random
import numpy as np
from collections import Counter
torch.manual_seed(42)
random.seed(42)
np.random.seed(42)
# Example corpus
corpus = [
"i like to eat apples and bananas",
"i like to watch movies and cartoons",
"the cat likes to eat fish",
"john loves to read books about python",
]
# Tokenization
tokenized_sentences = [sent.lower().split() for sent in corpus]
# Build vocabulary
all_tokens = [t for sent in tokenized_sentences for t in sent]
word_counter = Counter(all_tokens)
vocab = sorted(word_counter.keys())
word2idx = {w: i for i, w in enumerate(vocab)}
idx2word = {i: w for w, i in word2idx.items()}
vocab_size = len(vocab)
print(f"Vocabulary size = {vocab_size}")7.2 Generate Skip-gram Training Samples
def make_skipgram_data(tokenized_sentences, word2idx, window_size=2):
"""Generate (center_idx, outside_idx) pairs."""
pairs = []
for tokens in tokenized_sentences:
token_ids = [word2idx[w] for w in tokens]
length = len(token_ids)
for i, center_id in enumerate(token_ids):
start = max(i - window_size, 0)
end = min(i + window_size + 1, length)
for j in range(start, end):
if j != i:
pairs.append((center_id, token_ids[j]))
return pairs
skipgram_pairs = make_skipgram_data(tokenized_sentences, word2idx, window_size=2)
print(f"Total skip-gram pairs: {len(skipgram_pairs)}")7.3 Model Definition with Negative Sampling
import torch.nn as nn
class SkipGramNegSample(nn.Module):
def __init__(self, vocab_size, embed_dim, num_negatives=5):
super().__init__()
self.vocab_size = vocab_size
self.embed_dim = embed_dim
self.num_negatives = num_negatives
# Two embedding matrices
self.in_embed = nn.Embedding(vocab_size, embed_dim) # center word
self.out_embed = nn.Embedding(vocab_size, embed_dim) # context word
# Negative sampling distribution: word frequency^0.75
word_freq = np.array([word_counter[idx2word[i]] for i in range(vocab_size)], dtype=np.float32)
word_freq = word_freq ** 0.75
self.neg_sampling_dist = word_freq / word_freq.sum()
# Initialization
nn.init.uniform_(self.in_embed.weight, -0.5, 0.5)
nn.init.uniform_(self.out_embed.weight, -0.5, 0.5)
def forward(self, center_ids, outside_ids):
batch_size = center_ids.size(0)
# 1. Lookup embeddings
center_embed = self.in_embed(center_ids) # (batch, embed_dim)
outside_embed = self.out_embed(outside_ids) # (batch, embed_dim)
# 2. Positive sample score and loss
pos_scores = torch.sum(center_embed * outside_embed, dim=1)
pos_loss = -torch.log(torch.sigmoid(pos_scores) + 1e-8)
# 3. Sample negative examples
neg_samples = np.random.choice(
range(self.vocab_size),
size=(batch_size, self.num_negatives),
p=self.neg_sampling_dist,
)
neg_samples = torch.LongTensor(neg_samples)
# 4. Negative sample scores and loss
neg_embed = self.out_embed(neg_samples) # (batch, num_neg, embed_dim)
center_expand = center_embed.unsqueeze(1) # (batch, 1, embed_dim)
neg_scores = torch.bmm(neg_embed, center_expand.transpose(1, 2)).squeeze()
neg_loss = -torch.log(torch.sigmoid(-neg_scores) + 1e-8)
# 5. Total loss
total_loss = (pos_loss + neg_loss.sum(1)).mean()
return total_loss7.4 Training Loop
embed_dim = 8
num_negatives = 4
model = SkipGramNegSample(vocab_size, embed_dim, num_negatives)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
num_epochs = 3
pairs_list = skipgram_pairs[:]
for epoch in range(num_epochs):
random.shuffle(pairs_list)
total_loss = 0.0
for (center_id, outside_id) in pairs_list:
center_tensor = torch.LongTensor([center_id])
outside_tensor = torch.LongTensor([outside_id])
loss = model(center_tensor, outside_tensor)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(pairs_list)
print(f"Epoch {epoch+1}/{num_epochs}, Avg Loss={avg_loss:.4f}")7.5 Validate Results
def get_embedding(model, word):
idx = word2idx[word]
return model.in_embed.weight[idx].detach().numpy()
def cosine_sim(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)
def most_similar_words(model, query_word, top_k=3):
query_emb = get_embedding(model, query_word)
sims = []
for w in vocab:
if w == query_word:
continue
sim_score = cosine_sim(query_emb, get_embedding(model, w))
sims.append((w, sim_score))
sims.sort(key=lambda x: x[1], reverse=True)
return sims[:top_k]
# Test
for w in ["eat", "movies", "python"]:
if w in word2idx:
print(f"\n[Most similar to '{w}']")
for candidate, score in most_similar_words(model, w):
print(f" {candidate:<10} cos_sim = {score:.4f}")8. Summary
| Perspective | Core Idea |
|---|---|
| Neural network | Two linear transformations: one-hot to embedding to logits |
| Matrix factorization | Implicitly factorizes a PMI matrix |
| Probabilistic model | Maximizes the conditional probability of context words |
Key points:
- 以可微方式从输入映射到输出.
- 通过反向传播更新参数.
- 本质是学习两个嵌入矩阵 .