共计 2324 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:为什么要手动计算 CBOW 词嵌入?
在自然语言处理(NLP)中,词嵌入(Word Embedding)是许多任务的基础。CBOW(Continuous Bag of Words)模型是最常用的词嵌入方法之一。尽管现成的工具包(如 Gensim、TensorFlow)可以轻松生成词嵌入,但手动计算 CBOW 词嵌入有以下重要意义:

- 理解黑箱模型:许多初学者直接调用现成的 API,但对模型内部的计算过程一知半手算能帮助你真正掌握词嵌入的生成逻辑。
- 调试需求:当模型表现不佳时,手动推导能帮助你定位问题,比如梯度消失或维度选择不当。
- 学术研究:手动实现是理解论文和优化模型的基础。
数学推导:CBOW 模型的逐步计算
1. 输入层:One-Hot 编码
假设我们的词汇表包含 3 个单词:”cat”, “dog”, “bird”。每个单词用一个长度为 3 的 One-Hot 向量表示:
- “cat” = [1, 0, 0]
- “dog” = [0, 1, 0]
- “bird” = [0, 0, 1]
2. 隐藏层:词向量计算
CBOW 的核心思想是通过上下文预测中心词。假设窗口大小为 1(即左右各 1 个词),输入是上下文词的 One-Hot 向量,输出是中心词的概率分布。
隐藏层的计算如下:
$$
\mathbf{h} = \frac{1}{C} \sum_{c=1}^{C} \mathbf{W}_{in}^T \mathbf{x}_c
$$
其中:
– (C)是上下文词的数量(本例中为 2)。
– (\mathbf{W}_{in})是输入权重矩阵(维度为 (V \times N),(V) 是词汇表大小,(N)是词向量维度)。
– (\mathbf{x}_c)是第 (c) 个上下文词的 One-Hot 向量。
3. 输出层:Softmax 前向传播
输出层的计算目标是生成中心词的概率分布:
$$
\mathbf{u} = \mathbf{W}_{out} \mathbf{h}
$$
$$
\mathbf{y} = \text{softmax}(\mathbf{u})
$$
其中:
– (\mathbf{W}_{out})是输出权重矩阵(维度为(N \times V))。
– (\mathbf{y})是预测的概率分布。
– softmax 函数定义为:
$$
\text{softmax}(u_i) = \frac{e^{u_i}}{\sum_{j=1}^{V} e^{u_j}}
$$
4. 反向传播:梯度更新
通过交叉熵损失函数计算误差,并更新权重矩阵:
$$
L = -\sum_{i=1}^{V} y_i^* \log(y_i)
$$
其中 (y_i^*) 是真实标签(One-Hot 向量)。梯度更新公式为:
$$
\mathbf{W}{out} := \mathbf{W}} – \eta \cdot \frac{\partial L}{\partial \mathbf{W{out}}
$$
$$
\mathbf{W}} := \mathbf{W{in} – \eta \cdot \frac{\partial L}{\partial \mathbf{W}
$$}
其中 (\eta) 是学习率。
代码实现:3 个单词的微型 CBOW
以下是 Python 实现,使用 NumPy 避免循环:
import numpy as np
# 词汇表和 One-Hot 编码
vocab = ['cat', 'dog', 'bird']
word_to_idx = {'cat': 0, 'dog': 1, 'bird': 2}
# 初始化权重矩阵(小型示例:N=2)V = len(vocab)
N = 2 # 词向量维度
W_in = np.random.randn(V, N) # 输入权重
W_out = np.random.randn(N, V) # 输出权重
# 训练数据:上下文 -> 中心词
# 示例:(['dog', 'bird'], 'cat')
context = [word_to_idx['dog'], word_to_idx['bird']]
target = word_to_idx['cat']
# 前向传播
x = np.zeros(V)
for idx in context:
x[idx] += 1
x /= len(context) # 平均上下文向量
h = np.dot(x, W_in) # 隐藏层
u = np.dot(h, W_out) # 输出层
y = np.exp(u) / np.sum(np.exp(u)) # softmax
# 损失函数(交叉熵)loss = -np.log(y[target])
# 反向传播
dy = y.copy()
dy[target] -= 1 # 梯度:∂L/∂u
dW_out = np.outer(h, dy) # ∂L/∂W_out
dh = np.dot(W_out, dy) # ∂L/∂h
dW_in = np.outer(x, dh) # ∂L/∂W_in
# 更新权重
learning_rate = 0.1
W_out -= learning_rate * dW_out
W_in -= learning_rate * dW_in
print("更新后的 W_in:", W_in)
print("更新后的 W_out:", W_out)
避坑指南
- 词向量维度选择:
- 太小(如 N =2)会导致信息丢失,太大(如 N =300)可能过拟合。
-
经验值:N=50~300,根据任务调整。
-
处理低频词:
- 对低频词进行下采样(Subsampling)。
-
使用负采样(Negative Sampling)替代 Softmax。
-
梯度消失问题:
- 使用 ReLU 或 GloVe 等替代方案。
- 调整学习率和初始化权重(如 Xavier 初始化)。
延伸思考
- 与 Skip-Gram 的对比:
- Skip-Gram 通过中心词预测上下文,适合小数据集。
-
CBOW 通过上下文预测中心词,训练更快但精度略低。
-
验证手算结果:
- 对比现成工具包(如 Gensim)的输出。
- 检查损失函数是否单调递减。
通过手动计算和代码实现,你可以更深入地理解 CBOW 模型的运作机制,为后续的 NLP 任务打下坚实基础。
