深入理解CBOW模型的反向传播机制:从理论到代码实现

1次阅读
没有评论

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

image.webp

理解 CBOW 模型的基本架构

CBOW(Continuous Bag of Words)是 Word2Vec 的一种经典模型,它通过上下文词来预测当前词。对于刚接触 NLP 的同学来说,理解它的核心思想很重要:

深入理解 CBOW 模型的反向传播机制:从理论到代码实现

  • 输入层:上下文词的 one-hot 向量(比如窗口大小为 2 时,就是前后各两个词)
  • 隐藏层:所有上下文词向量的平均值(这就是 ”Bag of Words” 的含义)
  • 输出层:通过 softmax 计算目标词的概率分布

前向传播的数学表达很简单:

  1. 将上下文词的 one-hot 向量与词向量矩阵 W 相乘,得到各自的词向量
  2. 对这些词向量取平均,得到隐藏层向量 h
  3. 将 h 与另一个词向量矩阵 W ’ 相乘,得到输出向量 u
  4. 对 u 应用 softmax 函数,得到每个词的概率分布

用公式表示就是:

$$ h = \frac{1}{C} \sum_{c=1}^C W^T x_c $$

$$ u = W’ h $$

$$ y = \text{softmax}(u) $$

反向传播的数学推导

这才是真正的重头戏。我们需要理解误差是如何反向传播并更新词向量的。假设我们的损失函数是交叉熵:

$$ L = -\sum_{j=1}^V y_j \log \hat{y}_j $$

其中 y 是真实标签的 one-hot 向量,ŷ是预测的概率分布。我们需要计算损失对 W 和 W ’ 的梯度。

  1. 首先计算输出层的误差:

$$ \frac{\partial L}{\partial u} = \hat{y} – y $$

  1. 然后计算 W ’ 的梯度:

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

  1. 计算隐藏层的误差:

$$ \frac{\partial L}{\partial h} = W’ (\hat{y} – y) $$

  1. 最后计算 W 的梯度(对每个上下文词 x_c):

$$ \frac{\partial L}{\partial W} = \sum_{c=1}^C x_c \left(\frac{1}{C} W’ (\hat{y} – y) \right)^T $$

这个推导过程可能看起来有点复杂,但其实核心思想就是链式法则。每一步都在计算误差如何从输出层反向传播到输入层。

Python 实现代码

理解了数学原理后,我们来看具体实现。下面是一个简化版的 CBOW 实现:

import numpy as np

class CBOW:
    def __init__(self, vocab_size, embedding_dim):
        # 初始化词向量矩阵
        self.W = np.random.randn(vocab_size, embedding_dim) * 0.01
        self.W_prime = np.random.randn(embedding_dim, vocab_size) * 0.01

    def forward(self, context_indices):
        """
        前向传播
        :param context_indices: 上下文词的索引列表
        :return: 预测的概率分布
        """
        # 获取上下文词向量
        context_vectors = self.W[context_indices, :]

        # 计算隐藏层(取平均)h = np.mean(context_vectors, axis=0)

        # 计算输出
        u = np.dot(h, self.W_prime)

        # softmax
        exp_u = np.exp(u - np.max(u))  # 数值稳定性处理
        y_pred = exp_u / np.sum(exp_u)

        return y_pred, h

    def backward(self, context_indices, target_index, y_pred, h, learning_rate=0.01):
        """
        反向传播
        :param context_indices: 上下文词索引
        :param target_index: 目标词索引
        :param y_pred: 预测的概率分布
        :param h: 隐藏层向量
        :param learning_rate: 学习率
        """
        # 构造真实标签的 one-hot 向量
        y_true = np.zeros_like(y_pred)
        y_true[target_index] = 1

        # 计算输出层误差
        delta = y_pred - y_true

        # 更新 W'
        dW_prime = np.outer(h, delta)
        self.W_prime -= learning_rate * dW_prime

        # 计算隐藏层误差
        delta_h = np.dot(self.W_prime, delta)

        # 更新 W(对每个上下文词)C = len(context_indices)
        for idx in context_indices:
            self.W[idx, :] -= learning_rate * delta_h / C

实现中的常见问题

在实际实现 CBOW 时,新手容易遇到以下几个问题:

  1. 梯度消失:当词向量维度很大或学习率很小时,梯度可能会变得非常小,导致训练缓慢

  2. 解决方案:使用合适的初始化方法(如 Xavier 初始化),调整学习率

  3. 计算效率低:直接实现 softmax 在词汇表很大时计算开销巨大

  4. 解决方案:使用负采样 (negative sampling) 或层次 softmax(hierarchical softmax)

  5. 过拟合:模型可能在训练集上表现很好但在测试集上表现不佳

  6. 解决方案:使用 dropout 或 L2 正则化

避坑指南

根据我的经验,新手在实现 CBOW 时容易犯的错误包括:

  • 忘记对隐藏层向量取平均(这是 CBOW 与 skip-gram 的重要区别)
  • 在计算 softmax 时没有做数值稳定性处理(可能导致数值溢出)
  • 反向传播时没有正确计算梯度(特别是 W ’ 的更新容易出错)
  • 学习率设置不当(太大导致震荡,太小导致收敛慢)

思考与扩展

理解了基础的 CBOW 实现后,你可以尝试以下扩展:

  1. 实现不同窗口大小的 CBOW(比如窗口大小从 2 改为 4)
  2. 添加负采样来提高训练效率
  3. 比较 CBOW 和 skip-gram 在不同数据集上的表现
  4. 尝试用 CBOW 训练的词向量来做一些简单的语义分析任务

CBOW 虽然结构简单,但它很好地体现了词向量的核心思想。希望通过这篇文章,你能对 CBOW 的反向传播机制有更深入的理解。在实际应用中,你可能需要使用更高效的实现(如 gensim 中的 Word2Vec),但理解底层原理对于调参和解决问题非常有帮助。

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