深入解析CBOW模型算法:从词嵌入矩阵到手算实例

1次阅读
没有评论

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

image.webp

背景介绍

词嵌入(Word Embedding)是自然语言处理(NLP)中的基础技术,它将词汇映射到低维连续向量空间,使得语义相似的词在向量空间中距离相近。CBOW(Continuous Bag of Words)模型是词嵌入的经典算法之一,由 Mikolov 等人在 2013 年提出。与 Skip-gram 模型相比,CBOW 通过上下文预测中心词,更适合处理高频词汇和小型数据集。

深入解析 CBOW 模型算法:从词嵌入矩阵到手算实例

CBOW 在 NLP 中有广泛的应用场景,例如:

  • 文本分类
  • 机器翻译
  • 信息检索
  • 情感分析

CBOW vs. Skip-gram

CBOW 和 Skip-gram 是两种主要的词嵌入模型,各有优缺点:

  • CBOW
  • 优点:训练速度快,对高频词表现更好
  • 缺点:对低频词捕捉能力较弱

  • Skip-gram

  • 优点:对低频词表现更好,适合大型数据集
  • 缺点:训练速度较慢

CBOW 的数学原理

CBOW 模型的核心思想是通过上下文词汇预测中心词。假设我们有一个句子:”the quick brown fox jumps”,窗口大小为 2,则中心词为 ”brown” 时,上下文词汇为 [“the”, “quick”, “fox”, “jumps”]。

模型结构

  1. 输入层 :将上下文词汇的 one-hot 向量输入模型。
  2. 投影层 :将 one-hot 向量与词嵌入矩阵 $W_{V \times N}$ 相乘,得到词向量。
  3. 隐藏层 :对上下文词向量求平均。
  4. 输出层 :通过 softmax 计算每个词作为中心词的概率。

数学表达式如下:

  1. 上下文词向量的平均值:
    $$ h = \frac{1}{C} \sum_{c=1}^C W^T x_c $$

  2. 输出层计算:
    $$ u_j = W’^T_j h $$
    $$ p(w_j|context) = y_j = \frac{exp(u_j)}{\sum_{j’=1}^V exp(u_{j’})} $$

  3. 损失函数(交叉熵):
    $$ L = -\sum_{j=1}^V y_j \log(\hat{y}_j) $$

手算实例

假设我们有一个极简的词汇表:[“I”, “love”, “NLP”],词嵌入维度为 2。随机初始化词嵌入矩阵 $W$ 和 $W’$:

W = np.array([[0.1, 0.2],  # I
              [0.3, 0.4],  # love
              [0.5, 0.6]]) # NLP

W_prime = np.array([[0.7, 0.8],  # I
                   [0.9, 1.0],   # love
                   [1.1, 1.2]])  # NLP

假设上下文为 [“I”, “NLP”],中心词为 ”love”:

  1. 计算上下文词向量平均值 $h$:
    $$ h = \frac{1}{2}([0.1, 0.2] + [0.5, 0.6]) = [0.3, 0.4] $$

  2. 计算输出分数 $u_j$:
    $$ u = [0.3, 0.4] \times W’^T = [0.30.7 + 0.40.8, 0.30.9 + 0.41.0, 0.31.1 + 0.41.2] = [0.53, 0.67, 0.81] $$

  3. 计算 softmax 概率:
    $$ y = \frac{exp([0.53, 0.67, 0.81])}{sum(exp([0.53, 0.67, 0.81]))} \approx [0.30, 0.33, 0.37] $$

  4. 计算损失:
    真实分布为 [0, 1, 0](中心词是 ”love”),所以:
    $$ L = -\log(0.33) \approx 1.11 $$

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, center_index):
        # 计算隐藏层
        h = np.mean(self.W[context_indices], axis=0)

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

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

        # 计算损失
        loss = -np.log(y[center_index])

        return loss, h, y

    def backward(self, context_indices, center_index, h, y, learning_rate=0.01):
        # 计算梯度
        grad_y = y.copy()
        grad_y[center_index] -= 1

        # 更新参数
        grad_W_prime = np.outer(h, grad_y)
        grad_h = np.dot(self.W_prime, grad_y)

        # 平均梯度到每个上下文词
        grad_W = np.zeros_like(self.W)
        for idx in context_indices:
            grad_W[idx] += grad_h / len(context_indices)

        # 参数更新
        self.W -= learning_rate * grad_W
        self.W_prime -= learning_rate * grad_W_prime

性能优化建议

负采样技术

原始 CBOW 的 softmax 计算复杂度为 $O(V)$,当词汇表很大时效率极低。负采样(Negative Sampling)通过只更新少数负样本的参数来加速训练:

def negative_sampling_loss(self, context_indices, center_index, k=5):
    h = np.mean(self.W[context_indices], axis=0)

    # 正样本得分
    pos_score = np.dot(h, self.W_prime[:, center_index])
    pos_loss = -np.log(sigmoid(pos_score))

    # 负样本
    neg_loss = 0
    neg_indices = np.random.choice([i for i in range(self.vocab_size) if i != center_index], 
        size=k, 
        replace=False)

    for neg_idx in neg_indices:
        neg_score = np.dot(h, self.W_prime[:, neg_idx])
        neg_loss += -np.log(sigmoid(-neg_score))

    total_loss = pos_loss + neg_loss
    return total_loss

矩阵运算优化

使用批处理(batch processing)和向量化运算可以大幅提升训练速度:

def batch_forward(self, batch_contexts, batch_centers):
    # batch_contexts: (batch_size, context_size)
    # batch_centers: (batch_size,)

    # 向量化计算
    h = np.mean(self.W[batch_contexts], axis=1)  # (batch_size, embedding_dim)
    u = np.dot(h, self.W_prime)  # (batch_size, vocab_size)

    # softmax
    exp_u = np.exp(u - np.max(u, axis=1, keepdims=True))
    y = exp_u / np.sum(exp_u, axis=1, keepdims=True)

    # 交叉熵损失
    losses = -np.log(y[np.arange(len(batch_centers)), batch_centers])
    return np.mean(losses)

避坑指南

  1. 参数设置误区
  2. 窗口大小:一般 2 -5,太大会引入不相关词汇
  3. 学习率:建议从 0.025 开始,随训练逐渐减小
  4. 词向量维度:50-300,太大会导致过拟合

  5. 小数据集过拟合

  6. 使用早停(early stopping)
  7. 增加 L2 正则化
  8. 采用预训练词向量初始化

思考题

如何将 CBOW 应用于特定领域(如医疗、法律)文本?

  1. 领域词汇扩充:收集领域术语,扩展词汇表
  2. 迁移学习:在通用词向量基础上进行微调
  3. 领域语料预处理:保留领域特有的短语和缩写

通过本文的讲解和代码示例,相信读者已经掌握了 CBOW 模型的核心原理和实现方法。在实际应用中,建议先从简单实现开始,逐步加入优化技巧,并根据具体任务调整模型参数。

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