从零实现CBOW模型的词嵌入矩阵:手写矩阵训练过程详解

1次阅读
没有评论

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

image.webp

背景介绍

词嵌入是自然语言处理(NLP)中的一项基础技术,它将词语映射到低维连续向量空间中,使得语义相似的词在向量空间中距离相近。这种表示方法克服了传统 one-hot 编码的高维稀疏问题,为后续的文本分类、情感分析等任务提供了更好的特征表示。

从零实现 CBOW 模型的词嵌入矩阵:手写矩阵训练过程详解

在众多词嵌入模型中,CBOW(Continuous Bag-of-Words)因其简单高效而广受欢迎。它通过上下文词预测中心词的方式学习词向量,适合处理大规模语料库。

CBOW 模型原理解析

CBOW 模型的核心思想可以用以下数学公式表示:

给定一个中心词 $w_t$ 和其上下文窗口大小为 $m$,模型目标是最大化:

$$P(w_t|w_{t-m},…,w_{t-1},w_{t+1},…,w_{t+m})$$

模型结构主要包含三个部分:

  1. 输入层:将上下文词的 one-hot 表示拼接起来
  2. 隐藏层:通过词嵌入矩阵 $W_{V×N}$ 将高维稀疏向量转换为低维稠密向量(V 是词表大小,N 是嵌入维度)
  3. 输出层:通过另一个矩阵 $W’_{N×V}$ 计算每个词作为中心词的概率
graph LR
    A[上下文词 one-hot] --> B[词嵌入矩阵 W]
    B --> C[上下文向量平均]
    C --> D[输出矩阵 W']
    D --> E[softmax 概率]

手写实现步骤

数据预处理与上下文窗口构建

首先我们需要准备训练数据。以句子 ”the quick brown fox jumps” 为例:

  1. 构建词汇表并分配索引
  2. 定义窗口大小(通常 2 -5)
  3. 生成 (上下文, 中心词) 训练对

对于窗口大小 2,生成的训练样本为:
– ([the, quick], brown)
– ([quick, brown], fox)
– ([brown, fox], jumps)

初始化词嵌入矩阵

词嵌入矩阵 $W$ 的维度为 $V×N$,其中 V 是词表大小,N 是嵌入维度(通常 50-300)。初始化方法:

embedding_dim = 100
vocab_size = len(vocab)
W = np.random.randn(vocab_size, embedding_dim) * 0.01  # 小随机数初始化
W_prime = np.random.randn(embedding_dim, vocab_size) * 0.01

前向传播计算

  1. 将上下文词的 one-hot 向量与 $W$ 相乘得到嵌入向量
  2. 对上下文向量取平均
  3. 与 $W’$ 相乘得到输出分数
  4. 通过 softmax 计算概率分布

数学表达式:

$$h = \frac{1}{C} \sum_{c=1}^C W^T x_c$$
$$u = W’^T h$$
$$y = \text{softmax}(u)$$

损失函数定义

使用交叉熵损失函数:

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

其中 $y$ 是真实分布(one-hot),$\hat{y}$ 是预测分布。

反向传播与矩阵更新

通过链式法则计算梯度并更新参数:

  1. 计算输出层误差:$\delta_{out} = \hat{y} – y$
  2. 更新 $W’$:$\Delta W’ = h \cdot \delta_{out}^T$
  3. 计算隐藏层误差:$\delta_{hidden} = W’ \cdot \delta_{out}$
  4. 更新 $W$:对每个上下文词 $x_c$,$\Delta W += x_c \cdot \delta_{hidden}^T$

完整 Python 代码实现

import numpy as np
from collections import defaultdict

class CBOW:
    def __init__(self, vocab_size, embedding_dim, window_size=2):
        self.vocab_size = vocab_size
        self.embedding_dim = embedding_dim
        self.window_size = window_size

        # 初始化参数
        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):
        """前向传播"""
        h = np.mean(self.W[context_indices, :], axis=0)
        u = np.dot(self.W_prime.T, h)
        y_pred = self._softmax(u)
        return y_pred, h

    def backward(self, context_indices, target_index, y_pred, h, learning_rate=0.01):
        """反向传播"""
        # 计算输出层误差
        delta_out = y_pred.copy()
        delta_out[target_index] -= 1

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

        # 计算隐藏层误差并平均分配到上下文词
        delta_hidden = np.dot(self.W_prime, delta_out) / len(context_indices)

        # 更新 W
        for idx in context_indices:
            self.W[idx, :] -= learning_rate * delta_hidden

    def _softmax(self, x):
        """数值稳定的 softmax 实现"""
        e_x = np.exp(x - np.max(x))
        return e_x / e_x.sum()

# 示例用法
corpus = ["the", "quick", "brown", "fox", "jumps"]
vocab = {word:i for i, word in enumerate(set(corpus))}
model = CBOW(len(vocab), embedding_dim=10)

# 训练一个样本
context = [vocab["the"], vocab["quick"]]
target = vocab["brown"]
y_pred, h = model.forward(context)
model.backward(context, target, y_pred, h)

性能优化建议

  1. 向量化计算:避免循环,使用矩阵运算
  2. 负采样:替代计算量大的 softmax
  3. 学习率调整:使用 Adam 等自适应优化器
  4. 批量训练:积累多个样本的梯度后统一更新
  5. 高频词降采样:平衡常见词和罕见词的影响

常见问题与解决方案

梯度消失问题
– 使用 ReLU 等非饱和激活函数
– 合适的参数初始化(如 Xavier)

稀疏矩阵处理
– 使用稀疏矩阵存储 one-hot 向量
– 只更新实际用到的词向量

训练不稳定
– 梯度裁剪
– 学习率 warmup

延伸思考

要将此方法扩展到更大语料库,可以考虑:

  1. 使用 Huffman 树或负采样加速 softmax
  2. 分布式训练框架(如 TensorFlow/PyTorch)
  3. 加入 subword 信息处理 OOV 问题
  4. 结合字符级特征增强表示

进一步学习资源

  1. Mikolov et al. 2013 论文《Efficient Estimation of Word Representations in Vector Space》
  2. Stanford CS224N 课程讲义
  3. Gensim 库的 Word2Vec 实现
  4. TensorFlow 官方 Word2Vec 教程

通过本文的手写实现,相信你对 CBOW 模型的内部机制有了更深入的理解。词嵌入作为 NLP 的基础组件,掌握其原理对后续学习 BERT 等先进模型大有裨益。建议读者尝试在更大数据集上实践,并比较不同超参数对结果的影响。

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