乐于分享
好东西不私藏

Word2Vec训练流程详解:词向量是如何计算出来的?

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/