共计 2193 个字符,预计需要花费 6 分钟才能阅读完成。
为什么需要词嵌入?
自然语言处理(NLP)中,计算机需要一种方式来表示和理解词语。传统方法如 one-hot 编码存在维度灾难和语义缺失的问题。词嵌入(Word Embedding)通过将词语映射到低维连续向量空间,不仅降低了维度,还能捕捉词语之间的语义关系。

CBOW(Continuous Bag-of-Words)模型是 Word2Vec 的一种实现方式,它通过上下文词语预测目标词语来学习词向量。CBOW 适用于数据量较小、计算资源有限的场景,因为它的训练速度通常比 Skip-gram 更快。
CBOW 模型的计算流程
1. 输入层到隐藏层
假设我们的词汇表大小为 V,词向量维度为 N。对于上下文窗口大小为 C 的 CBOW 模型,输入层接收 C 个 one-hot 编码的上下文词向量 {x₁, x₂, …, x_c}。
隐藏层 h 的计算公式为:
$$h = \frac{1}{C}W^T(x_1 + x_2 + … + x_c)$$
其中 W 是 V×N 维的输入权重矩阵。
2. 隐藏层到输出层
输出层通过另一个 N×V 维的权重矩阵 W ’ 计算得分:
$$u = W’^T h$$
然后通过 softmax 函数得到概率分布:
$$P(w_j|context) = y_j = \frac{exp(u_j)}{\sum_{k=1}^V exp(u_k)}$$
梯度计算和参数更新
损失函数
使用交叉熵损失函数:
$$E = -\log P(w_O|context) = -u_j^* + \log\sum_{k=1}^V exp(u_k)$$
其中 j * 是目标词的索引。
梯度计算
- 输出层梯度:
$$\frac{\partial E}{\partial W’} = h(y – t)^T$$
其中 t 是目标词的 one-hot 向量。
- 隐藏层梯度:
$$\frac{\partial E}{\partial h} = W'(y – t)$$
- 输入层梯度:
$$\frac{\partial E}{\partial W} = \frac{1}{C} \sum_{c=1}^C x_c (\frac{\partial E}{\partial h})^T$$
参数更新
使用随机梯度下降(SGD)更新参数:
$$W = W – \eta \frac{\partial E}{\partial W}$$
$$W’ = W’ – \eta \frac{\partial E}{\partial W’}$$
其中 η 是学习率。
Python 实现示例
import numpy as np
# 超参数设置
V = 10000 # 词汇表大小
N = 300 # 词向量维度
learning_rate = 0.01
window_size = 2 # 单侧窗口大小
# 初始化权重矩阵
W = np.random.randn(V, N) * 0.01 # 输入权重
W_prime = np.random.randn(N, V) * 0.01 # 输出权重
# 模拟输入数据
context_indices = [10, 20, 30, 40] # 上下文词索引
target_index = 50 # 目标词索引
# 前向传播
# 1. 输入层到隐藏层
context_vectors = [np.zeros(V) for _ in context_indices]
for i, idx in enumerate(context_indices):
context_vectors[i][idx] = 1
h = np.zeros(N)
for vec in context_vectors:
h += np.dot(W.T, vec)
h /= len(context_vectors)
# 2. 隐藏层到输出层
u = np.dot(W_prime.T, h)
y = np.exp(u) / np.sum(np.exp(u))
# 损失计算
t = np.zeros(V)
t[target_index] = 1
loss = -np.log(y[target_index])
# 反向传播
# 1. 输出层梯度
delta_output = y - t
dW_prime = np.outer(h, delta_output)
# 2. 隐藏层梯度
delta_hidden = np.dot(W_prime, delta_output)
# 3. 输入层梯度
dW = np.zeros_like(W)
for vec in context_vectors:
dW += np.outer(vec, delta_hidden)
dW /= len(context_vectors)
# 参数更新
W -= learning_rate * dW
W_prime -= learning_rate * dW_prime
实际训练注意事项
- 学习率选择 :
- 通常从 0.01 开始尝试
- 可以使用学习率衰减策略
-
太大会导致震荡,太小收敛慢
-
负采样技巧 :
- 原始 softmax 计算成本高
- 负采样只更新少数负样本的权重
-
显著提高训练速度
-
向量维度影响 :
- 维度太低:表达能力不足
- 维度太高:容易过拟合
- 一般 100-300 维效果较好
思考题
- CBOW 和 Skip-gram 在梯度计算上有什么主要差异?
- CBOW 是多个上下文词贡献梯度
-
Skip-gram 是一个中心词贡献多个梯度
-
如何验证手算结果的正确性?
- 使用数值梯度检验
- 对比不同实现的输出
-
检查损失是否单调下降
-
为什么 CBOW 通常比 Skip-gram 训练更快?
- CBOW 每次更新考虑更多上下文信息
- Skip-gram 需要处理更多样本对
总结
通过手动实现 CBOW 模型,我们深入理解了词嵌入的生成过程。虽然实际应用中我们会使用优化过的库,但这种底层实现的理解对于调试模型和解决实际问题非常有帮助。建议读者尝试扩展这个基础实现,加入负采样、学习率调整等优化策略,观察模型性能的变化。
