CBOW模型手算词嵌入步骤详解:从梯度计算到实现优化

1次阅读
没有评论

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

image.webp

背景介绍

CBOW(Continuous Bag of Words)是 Word2Vec 的一种经典模型,它通过上下文单词预测中心词来学习词向量。相比 Skip-gram,CBOW 更适合处理小型数据集,计算效率更高。理解其手动计算过程能帮助开发者:

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. 前向传播

  1. 获取上下文词向量:假设上下文索引为 [0,3](”I” 和 ”apple”)
  2. 计算平均向量:
    context_indices = [0, 3]
    context_vectors = W[context_indices]  # 形状 (2,3)
    h = np.mean(context_vectors, axis=0)  # 平均向量 (3,)
  3. 计算分数向量:
    scores = np.dot(W, h)  # 形状 (5,)
  4. 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. 梯度反向传播

这是最关键的数学部分,涉及三个梯度:

  1. 输出层梯度:
    grad_scores = probs - y_true  # 形状 (5,)
  2. 嵌入矩阵梯度:
    grad_W = np.outer(grad_scores, h)  # 外积 (5,3)
  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

性能考量

  1. 计算复杂度分析:
  2. 前向传播:O(Cd + Vd)
  3. 反向传播:O(Vd)

  4. 优化建议:

  5. 使用层次 Softmax 或负采样降低 V 的影响
  6. 小批量训练替代单样本更新
  7. 采用学习率衰减策略

避坑指南

  1. 梯度爆炸问题:
  2. 症状:损失值突然变为 NaN
  3. 解决:梯度裁剪(np.clip(grad, -5, 5))

  4. 训练停滞:

  5. 检查初始化范围是否过小
  6. 尝试 Xavier/Glorot 初始化

  7. 上下文窗口处理:

  8. 边界词要特殊处理(如句首 / 句末)
  9. 考虑加权平均而非简单平均

总结与延伸

通过手动实现 CBOW,我们深入理解了:
– 词向量如何通过上下文信息更新
– 梯度传播的数学本质
– 计算效率的瓶颈所在

扩展到大规模数据时建议:
1. 使用 gensim 或 PyTorch 内置实现
2. 结合 Subword 信息处理罕见词
3. 尝试与 BERT 等现代模型对比效果

手动实现虽然效率不高,但对理解词嵌入原理至关重要。建议读者尝试用同样的方法实现 Skip-gram 模型,对比两者的梯度计算差异。

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