CBOW模型词嵌入手算指南:从梯度计算到向量生成

1次阅读
没有评论

共计 2193 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

为什么需要词嵌入?

自然语言处理(NLP)中,计算机需要一种方式来表示和理解词语。传统方法如 one-hot 编码存在维度灾难和语义缺失的问题。词嵌入(Word Embedding)通过将词语映射到低维连续向量空间,不仅降低了维度,还能捕捉词语之间的语义关系。

CBOW 模型词嵌入手算指南:从梯度计算到向量生成

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 * 是目标词的索引。

梯度计算

  1. 输出层梯度:

$$\frac{\partial E}{\partial W’} = h(y – t)^T$$

其中 t 是目标词的 one-hot 向量。

  1. 隐藏层梯度:

$$\frac{\partial E}{\partial h} = W'(y – t)$$

  1. 输入层梯度:

$$\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

实际训练注意事项

  1. 学习率选择
  2. 通常从 0.01 开始尝试
  3. 可以使用学习率衰减策略
  4. 太大会导致震荡,太小收敛慢

  5. 负采样技巧

  6. 原始 softmax 计算成本高
  7. 负采样只更新少数负样本的权重
  8. 显著提高训练速度

  9. 向量维度影响

  10. 维度太低:表达能力不足
  11. 维度太高:容易过拟合
  12. 一般 100-300 维效果较好

思考题

  1. CBOW 和 Skip-gram 在梯度计算上有什么主要差异?
  2. CBOW 是多个上下文词贡献梯度
  3. Skip-gram 是一个中心词贡献多个梯度

  4. 如何验证手算结果的正确性?

  5. 使用数值梯度检验
  6. 对比不同实现的输出
  7. 检查损失是否单调下降

  8. 为什么 CBOW 通常比 Skip-gram 训练更快?

  9. CBOW 每次更新考虑更多上下文信息
  10. Skip-gram 需要处理更多样本对

总结

通过手动实现 CBOW 模型,我们深入理解了词嵌入的生成过程。虽然实际应用中我们会使用优化过的库,但这种底层实现的理解对于调试模型和解决实际问题非常有帮助。建议读者尝试扩展这个基础实现,加入负采样、学习率调整等优化策略,观察模型性能的变化。

正文完
 0
评论(没有评论)