Word2Vec训练流程详解:词向量是如何计算出来的?
目标读者:已经了解词向量基本概念,但想深入理解 Word2Vec 训练过程的人
核心问题:Word2Vec 到底是如何把词变成向量的?训练流程是什么?词向量从哪里来?
一、整体流程概览
1.1 一句话概括
Word2Vec 通过训练一个浅层神经网络来完成”预测上下文”的任务,训练完成后,我们丢弃输出层,只保留隐藏层的权重矩阵 —— 这个矩阵就是我们要的词向量。
1.2 训练流程图
准备语料
↓
构建词表(大小 V)
↓
初始化两个矩阵:
– W (V×N):输入矩阵(词向量查找表)← 最终要的
– W’ (N×V):输出矩阵(辅助矩阵)← 训练完丢弃
↓
对每个训练样本 (中心词, 上下文词):
├─ 前向传播:计算预测概率
├─ 计算损失:预测 vs 真实
└─ 反向传播:更新 W 和 W’
↓
重复数百万次(多个 epoch)
↓
训练完成!
↓
提取 W 矩阵 → 这就是词向量查找表
二、详细训练流程(Skip-gram 为例)
2.1 准备阶段
Step 1:准备语料
Python
复制
# 原始语料
corpus = [
“我 爱 自然 语言 处理”,
“自然 语言 处理 很 有趣”,
…
]
# 分词后(假设已经分好)
sentences = [
[“我”, “爱”, “自然”, “语言”, “处理”],
[“自然”, “语言”, “处理”, “很”, “有趣”],
…
]
Step 2:构建词表
Python
复制
# 统计所有出现的词,构建词到 ID 的映射
vocab = {
“我”: 0,
“爱”: 1,
“自然”: 2,
“语言”: 3,
“处理”: 4,
…
}
V = len(vocab) # 词表大小,例如 10,000
Step 3:生成训练样本
使用滑动窗口(例如窗口大小=2)生成 (中心词, 上下文词) 对:
Python
复制
# 对句子 [“我”, “爱”, “自然”, “语言”, “处理”]
# 窗口大小=2,生成训练样本:
中心词=”我” → 上下文词=[“爱”, “自然”]
中心词=”爱” → 上下文词=[“我”, “自然”, “语言”]
中心词=”自然” → 上下文词=[“我”, “爱”, “语言”, “处理”]
…
# 最终得到训练样本列表
training_samples = [
(1, 0), # (“爱”, “我”)
(1, 2), # (“爱”, “自然”)
(2, 1), # (“自然”, “爱”)
(2, 3), # (“自然”, “语言”)
…
]
2.2 初始化参数
两个核心矩阵:
W (输入矩阵, V×N): 词向量查找表
– 每一行对应一个词的向量
– 训练完成后,这就是我们要的词向量
– 例如 W[1] = “爱” 的词向量
W’ (输出矩阵, N×V): 输出查找表
– 用于计算输出得分
– 训练完成后丢弃
初始化:用随机数填充这两个矩阵
Python
复制
import numpy as np
V = 10000 # 词表大小
N = 300 # 词向量维度
# 随机初始化
W = np.random.randn(V, N) * 0.01 # (V×N)
W_prime = np.random.randn(N, V) * 0.01 # (N×V)
2.3 前向传播(Forward Propagation)
以一个训练样本 (中心词="爱", 上下文词="我") 为例:
Step 1:输入层 —— One-hot 编码
Python
复制
# “爱” 的词表 ID = 1
x = [0, 1, 0, 0, …] # (1×V) One-hot 向量
Step 2:隐藏层 —— 查找词向量
Python
复制
# 矩阵乘法:h = x · W
# 因为 x 是 One-hot,所以 h = W[1] (第1行)
h = W[1] # (1×N) “爱” 的词向量
# 例如:h = [0.1, 0.4, -0.2, …, 0.3] (300维)
关键洞察:隐藏层的输出就是中心词的词向量!
Step 3:输出层 —— 计算得分
Python
复制
# 矩阵乘法:u = h · W’
u = np.dot(h, W_prime) # (1×V) 得分向量
# 例如:
# u[0] = “我” 的得分 = 0.8
# u[1] = “爱” 的得分 = 0.2
# u[2] = “自然” 的得分 = 0.5
# …
Step 4:Softmax —— 转换为概率
Python
复制
# 对得分做 Softmax,得到概率分布
p = np.exp(u) / np.sum(np.exp(u)) # (1×V)
# 例如:
# p[0] = P(“我” | “爱”) = 0.15
# p[1] = P(“爱” | “爱”) = 0.05
# p[2] = P(“自然” | “爱”) = 0.10
# …
2.4 计算损失(Loss Calculation)
Python
复制
# 真实答案是 “我” (ID=0)
# 我们希望 p[0] 尽可能高
# 损失函数:交叉熵
loss = -log(p[0]) # 只关心真实上下文词的概率
# 如果 p[0] = 0.15,loss = -log(0.15) ≈ 1.90
# 如果 p[0] = 0.01,loss = -log(0.01) ≈ 4.61(损失很大)
2.5 反向传播(Backward Propagation)
目标:调整 W 和 W’,使得损失降低
Step 1:计算梯度
Python
复制
# 对输出矩阵 W’ 的梯度
# ∂Loss/∂W’ = h^T · (p – y)
# 其中 y 是真实的 One-hot 向量
y = [1, 0, 0, …] # “我” 是真实答案
grad_W_prime = np.outer(h, p – y) # (N×V)
# 对输入矩阵 W 的梯度
# ∂Loss/∂W = x^T · (W’ · (p – y))
grad_W = np.outer(x, np.dot(W_prime, p – y)) # (V×N)
Step 2:更新参数
Python
复制
learning_rate = 0.025
# 更新 W’
W_prime = W_prime – learning_rate * grad_W_prime
# 更新 W(只更新中心词对应的行)
# 因为 x 是 One-hot,所以只更新 W[1]
W[1] = W[1] – learning_rate * grad_W[1]
2.6 负采样加速(Negative Sampling)
问题:原始 Softmax 需要计算整个词表的概率(V 可能 = 100,000),太慢!
解决方案:把多分类问题变成二分类问题
Python
复制
# 对每个正样本 (中心词, 上下文词),采样 K 个负样本
positive_sample = (“爱”, “我”) # 真实上下文对
negative_samples = [(“爱”, “自然”), (“爱”, “处理”), …] # 随机词对
# 训练目标:
# – 正样本:预测为 1(是真的上下文对)
# – 负样本:预测为 0(不是真的上下文对)
# 损失函数(简化版):
loss = -log(sigmoid(v_”我” · v_”爱”)) # 正样本,希望得分高
-Σ log(sigmoid(-v_neg · v_”爱”)) # 负样本,希望得分低
效果:计算量从 O(V) 降到 O(K+1),K 通常 = 5~20
三、数值例子:从头到尾走一遍
3.1 设定
词表:[“我”, “爱”, “你”, “自然”, “语言”] (V = 5)
词向量维度:N = 3 (真实场景是 100~300)
训练样本:(中心词=”爱”, 上下文词=”我”)
3.2 初始化
输入矩阵 W (5×3):
W = [
[0.2, -0.1, 0.3], # “我” 的向量
[0.1, 0.4, -0.2], # “爱” 的向量 ← 中心词
[-0.3, 0.2, 0.1], # “你” 的向量
[0.4, -0.2, 0.1], # “自然” 的向量
[-0.1, 0.3, -0.4], # “语言” 的向量
]
输出矩阵 W’ (3×5):
W_prime = [
[0.1, -0.2, 0.3], # 发送给 “我” 的权重
[0.2, 0.1, -0.1], # 发送给 “爱” 的权重
[-0.1, 0.3, 0.2], # 发送给 “你” 的权重
[0.4, -0.1, -0.2], # 发送给 “自然” 的权重
[-0.3, 0.2, 0.1], # 发送给 “语言” 的权重
]
3.3 前向传播
Step 1:输入层
Python
复制
x = [0, 1, 0, 0, 0] # “爱” 的 One-hot (ID=1)
Step 2:隐藏层
Python
复制
h = W[1] = [0.1, 0.4, -0.2] # “爱” 的当前词向量
Step 3:输出层
Python
复制
# 计算得分:u = h · W’
u = np.dot(h, W_prime)
= [0.1, 0.4, -0.2] · W’
= [0.08, 0.05, 0.11, 0.02, -0.01] # 简化计算
# Softmax
p = softmax(u) = [0.21, 0.20, 0.22, 0.20, 0.17]
问题:真实答案是 “我” (ID=0),但模型认为 “你” (ID=2) 的概率最高!
3.4 反向传播
Python
复制
# 计算梯度(简化版)
grad_W_prime = np.outer(h, p – y)
= np.outer([0.1, 0.4, -0.2], [0.21-1, 0.20-0, 0.22-0, …])
= …
# 更新 W[1](”爱” 的词向量)
W[1] = W[1] – learning_rate * grad_W[1]
= [0.1, 0.4, -0.2] – 0.025 * [-0.8, 0.3, 0.2]
= [0.12, 0.39, -0.21]
关键:经过这次更新,”爱” 的词向量变得更接近 “我” 的向量!
3.5 训练完成后
经过数百万次这样的更新:
Python
复制
# W 矩阵中的每个词向量都会被调整
# 使得经常出现在相似上下文的词,向量也相似
# 例如:
W[1] # “爱” 的最终向量
W[?] # “喜欢” 的最终向量(假设 ID=?)
# 这两个向量会很接近,因为她们经常出现在相似上下文
四、词向量如何被使用?
4.1 提取词向量
Python
复制
# 训练完成后,W 的每一行就是一个词的向量
word_vector = W[word_id]
# 例如获取 “爱” 的向量
love_vector = W[1] # [0.12, 0.39, -0.21, …, 0.05]
4.2 计算词相似度
Python
复制
from scipy.spatial.distance import cosine
# 计算 “爱” 和 “喜欢” 的相似度
similarity = 1 – cosine(W[1], W[like_id])
print(f”相似度: {similarity:.3f}”) # 例如 0.85
4.3 向量算术
Python
复制
# 经典例子:king – man + woman ≈ queen
result = W[king_id] – W[man_id] + W[woman_id]
# 找最相似的词
most_similar = find_most_similar(result, W)
print(most_similar) # 应该输出 “queen”
五、CBOW 流程对比
5.1 CBOW 是什么?
CBOW (Continuous Bag of Words) 是 Word2Vec 的另一种训练方式:
Skip-gram:输入中心词 → 预测上下文词
CBOW: 输入上下文词 → 预测中心词
5.2 CBOW 训练流程
以训练样本 (上下文词=["我", "自然"], 中心词="爱") 为例:
Python
复制
# Step 1:输入层(多个 One-hot)
x_我 = [1, 0, 0, 0, 0]
x_自然 = [0, 0, 0, 1, 0]
# Step 2:隐藏层(平均词向量)
h = (W[0] + W[3]) / 2 # 上下文词向量的平均值
# Step 3:输出层(预测中心词)
u = np.dot(h, W_prime)
p = softmax(u)
# Step 4:计算损失
loss = -log(p[1]) # 希望 p[“爱”] 最高
# Step 5:反向传播,更新 W[0], W[3], W[1]
5.3 Skip-gram vs CBOW
特性
Skip-gram
CBOW
输入
中心词(1个)
上下文词(多个)
输出
上下文词(多个)
中心词(1个)
训练速度
慢(每个样本做 C 次更新)
快(每个样本只做 1 次更新)
适合场景
大语料、罕见词
小语料、常见词
词向量质量
罕见词好
常见词好
六、总结:词向量是如何计算出来的?
6.1 核心答案
关键洞察:词向量不是”计算”出来的,而是”训练”出来的!
初始化(随机) → 前向传播(预测) → 计算损失 → 反向传播(更新) → 重复数百万次
↓
最终得到:W 矩阵(词向量查找表)
6.2 关键步骤回顾
1. 初始化:随机初始化 W 矩阵(V×N)
2. 训练:通过预测任务,不断调整 W 矩阵
3. 完成:W 的每一行就是一个词的向量
6.3 为什么这样能工作?
因为训练目标是”预测上下文” → 为了做好预测,模型必须让语义相似的词有相似的向量!
“猫” 和 “狗” 经常出现在相似上下文 → 它们的向量必须相似 → 才能同时预测好各自的上下文
七、代码示例:完整训练流程
下面是一个完整的 Word2Vec Skip-gram 训练代码示例(简化版,无负采样):
Python
复制
import numpy as np
# 1. 数据准备
corpus = [
[“我”, “爱”, “自然”, “语言”],
[“我”, “爱”, “编程”],
[“自然”, “语言”, “很”, “有趣”],
[“编程”, “很”, “有趣”],
]
# 构建词表
vocab = {}
for sentence in corpus:
for word in sentence:
if word not in vocab:
vocab[word] = len(vocab)
V = len(vocab) # 词表大小
N = 10 # 词向量维度(简化,真实场景用 100~300)
Python
复制
# 生成训练样本(Skip-gram)
training_samples = []
window_size = 2
for sentence in corpus:
for i, center_word in enumerate(sentence):
center_id = vocab[center_word]
# 获取上下文词
start = max(0, i – window_size)
end = min(len(sentence), i + window_size + 1)
for j in range(start, end):
if j != i:
context_id = vocab[sentence[j]]
training_samples.append((center_id, context_id))
print(f”训练样本数: {len(training_samples)}”)
print(f”前5个样本: {training_samples[:5]}”)
Python
复制
# 2. 初始化参数
np.random.seed(42)
W = np.random.randn(V, N) * 0.01 # 输入矩阵(词向量查找表)
W_prime = np.random.randn(N, V) * 0.01 # 输出矩阵
# 3. 训练(简化版,无负采样)
def softmax(x):
exp_x = np.exp(x – np.max(x)) # 数值稳定性
return exp_x / np.sum(exp_x)
learning_rate = 0.025
num_epochs = 1000
for epoch in range(num_epochs):
total_loss = 0
for center_id, context_id in training_samples:
# Step 1: 前向传播
h = W[center_id] # (N,)
u = np.dot(h, W_prime) # (V,)
p = softmax(u) # (V,)
# Step 2: 计算损失
loss = -np.log(p[context_id] + 1e-10)
total_loss += loss
# Step 3: 反向传播(简化版)
y = np.zeros(V)
y[context_id] = 1
# 计算梯度
grad_W_prime = np.outer(h, p – y) # (N, V)
grad_W_center = np.dot(W_prime, p – y) # (N,)
# 更新参数
W_prime -= learning_rate * grad_W_prime
W[center_id] -= learning_rate * grad_W_center
if epoch % 100 == 0:
print(f”Epoch {epoch}, Loss: {total_loss:.4f}”)
Python
复制
# 4. 提取词向量
print(“\n训练完成!词向量:”)
for word, word_id in vocab.items():
vector = W[word_id]
print(f”{word} (ID={word_id}): {vector[:5]}…”) # 只打印前5维
# 5. 计算词相似度
def cosine_similarity(v1, v2):
return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
print(“\n词相似度:”)
words = list(vocab.keys())
for i in range(len(words)):
for j in range(i+1, len(words)):
sim = cosine_similarity(W[vocab[words[i]]], W[vocab[words[j]]])
print(f”{words[i]} vs {words[j]}: {sim:.3f}”)
八、常见问题解答
❓ Q1: 词向量是”计算”出来的还是”学习”出来的?
A: 是学习出来的!不是通过某种公式算出来的,而是通过训练过程,让模型自己调整出来的。
❓ Q2: 为什么训练完成后要丢弃 W’?
A: 因为 W’ 只是训练过程中的”辅助工具”,用于帮助计算预测概率。我们只需要 W(词向量查找表)。
❓ Q3: 词向量的维度 N 怎么选?
A:
– 小语料:50~100 维
– 中等语料:100~300 维
– 大语料:300~500 维
– 维度越高,表达能力越强,但计算量也越大
❓ Q4: 负采样为什么能加速训练?
A: 原始 Softmax 需要计算整个词表的概率(O(V)),负采样只需要计算 K+1 个样本(O(K+1)),K 通常 = 5~20。
❓ Q5: 如何判断词向量训练得好不好?
A:
1. 词相似度测试:相似的词应该有高相似度
2. 词类比测试:king – man + woman ≈ queen
3. 下游任务性能:用在其他 NLP 任务上,看效果
Word2Vec 的核心思想:通过训练一个”假任务”(预测上下文)来学习词向量。
词向量不是计算出来的,而是训练出来的!
下期预告:从 Word2Vec 到 BERT —— 为什么静态词向量不够用?上下文相关的词表示如何工作?
参考资料:
– [Mikolov et al. 2013a] Efficient Estimation of Word Representations in Vector Space
– [Mikolov et al. 2013b] Distributed Representations of Words and Phrases and their Compositionality
– [Jay Alammar] The Illustrated Word2vec: https://jalammar.github.io/illustrated-word2vec/
– [Chris McCormick] Word2Vec Tutorial: http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
夜雨聆风