共计 1969 个字符,预计需要花费 5 分钟才能阅读完成。
背景介绍
CBOW(Continuous Bag of Words)是 Word2Vec 的一种经典模型,它通过上下文单词预测中心词来学习词向量。相比 Skip-gram,CBOW 更适合处理小型数据集,计算效率更高。理解其手动计算过程能帮助开发者:

- 掌握词嵌入的核心数学原理
- 调试模型时快速定位问题
- 针对特定任务进行定制化改进
核心计算步骤
1. 词向量初始化
词向量通常随机初始化为小数值(如 0.1~0.5 范围),假设词汇表大小 V =5,嵌入维度 d =3:
import numpy as np
np.random.seed(42)
embedding_dim = 3
vocab = {"apple":0, "banana":1, "orange":2, "eat":3, "fruit":4}
# 初始化嵌入矩阵(V x d)W = np.random.uniform(-0.5, 0.5, (len(vocab), embedding_dim))
print("初始嵌入矩阵:\n", W)
2. 上下文窗口处理
以句子 ”I eat apple” 为例,设窗口大小 C =2(前后各 2 个词):
上下文窗口: [I, eat, _, apple]
中心词: eat
3. 前向传播
- 获取上下文词向量:假设上下文索引为 [0,3](”I” 和 ”apple”)
- 计算平均向量:
context_indices = [0, 3] context_vectors = W[context_indices] # 形状 (2,3) h = np.mean(context_vectors, axis=0) # 平均向量 (3,) - 计算分数向量:
scores = np.dot(W, h) # 形状 (5,) - Softmax 概率化:
def softmax(x): exp_x = np.exp(x - np.max(x)) return exp_x / exp_x.sum() probs = softmax(scores)
4. 损失计算
使用交叉熵损失,真实中心词为 ”eat”(索引 3):
y_true = np.zeros(len(vocab))
y_true[3] = 1
loss = -np.sum(y_true * np.log(probs))
5. 梯度反向传播
这是最关键的数学部分,涉及三个梯度:
- 输出层梯度:
grad_scores = probs - y_true # 形状 (5,) - 嵌入矩阵梯度:
grad_W = np.outer(grad_scores, h) # 外积 (5,3) - 上下文向量梯度:
grad_h = np.dot(W.T, grad_scores) # (3,) grad_context = grad_h / len(context_indices) # 平均分配
完整 Python 实现
class CBOW:
def __init__(self, vocab_size, embedding_dim):
self.W = np.random.uniform(-0.5, 5, (vocab_size, embedding_dim))
def forward(self, context_indices):
context_vectors = self.W[context_indices]
h = np.mean(context_vectors, axis=0)
scores = np.dot(self.W, h)
return softmax(scores), h
def backward(self, context_indices, grad_scores, h, lr=0.01):
# 更新输出矩阵
grad_W = np.outer(grad_scores, h)
self.W -= lr * grad_W
# 更新上下文向量
grad_h = np.dot(self.W.T, grad_scores)
grad_context = grad_h / len(context_indices)
for idx in context_indices:
self.W[idx] -= lr * grad_context
性能考量
- 计算复杂度分析:
- 前向传播:O(Cd + Vd)
-
反向传播:O(Vd)
-
优化建议:
- 使用层次 Softmax 或负采样降低 V 的影响
- 小批量训练替代单样本更新
- 采用学习率衰减策略
避坑指南
- 梯度爆炸问题:
- 症状:损失值突然变为 NaN
-
解决:梯度裁剪(
np.clip(grad, -5, 5)) -
训练停滞:
- 检查初始化范围是否过小
-
尝试 Xavier/Glorot 初始化
-
上下文窗口处理:
- 边界词要特殊处理(如句首 / 句末)
- 考虑加权平均而非简单平均
总结与延伸
通过手动实现 CBOW,我们深入理解了:
– 词向量如何通过上下文信息更新
– 梯度传播的数学本质
– 计算效率的瓶颈所在
扩展到大规模数据时建议:
1. 使用 gensim 或 PyTorch 内置实现
2. 结合 Subword 信息处理罕见词
3. 尝试与 BERT 等现代模型对比效果
手动实现虽然效率不高,但对理解词嵌入原理至关重要。建议读者尝试用同样的方法实现 Skip-gram 模型,对比两者的梯度计算差异。
正文完
