从零实现CBOW模型的词嵌入矩阵:训练过程详解与最佳实践

1次阅读
没有评论

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

image.webp

词嵌入的核心价值与模型选型

词嵌入是 NLP 领域的基石技术,它将离散的词语映射到连续的向量空间。这种表示不仅能捕捉词语间的语义关系(如 ’ 国王 - 女王≈男人 - 女人 ’),还能让机器学习模型更好地处理文本数据。相较于传统的 one-hot 编码,词嵌入解决了维度灾难和语义鸿沟两大难题。

在 Word2Vec 框架下,CBOW 和 Skip-gram 是两种经典模型:

  • CBOW:通过上下文预测中心词,训练速度快,适合小型数据集
  • Skip-gram:通过中心词预测上下文,擅长处理低频词,适合大规模语料

从零实现 CBOW 模型的词嵌入矩阵:训练过程详解与最佳实践

词嵌入矩阵的数学表示

词嵌入矩阵本质是一个查找表,其中每行对应一个词的向量表示。数学上可表示为:

$$ \mathbf{E} \in \mathbb{R}^{V \times D} $$

  • $V$:词汇表大小
  • $D$:嵌入维度(通常 50-300)

PyTorch 实现基础架构

矩阵初始化

import torch
import torch.nn as nn

class CBOW(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super().__init__()
        # 正态分布初始化,均值 0,标准差 0.1
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        nn.init.normal_(self.embeddings.weight, mean=0, std=0.1)

        # 投影层将上下文向量求和后映射回词汇空间
        self.projection = nn.Linear(embedding_dim, vocab_size)

上下文窗口处理

def get_context(window_size, text_ids):
    """
    text_ids: 已转换为 ID 序列的文本
    返回: (中心词, 上下文词) 对生成器
    """
    for i in range(window_size, len(text_ids)-window_size):
        center = text_ids[i]
        context = torch.cat([text_ids[i-window_size:i], 
            text_ids[i+1:i+window_size+1]
        ])
        yield center, context

训练优化关键技术

负采样加速训练

import math

def negative_sampling(weights, num_samples):
    """
    weights: 词频统计的权重列表
    实现 log-uniform 分布的负采样
    """
    z = 0.5  # 平滑系数
    probs = torch.tensor([math.log(w + z) for w in weights])
    probs = probs / probs.sum()
    return torch.multinomial(probs, num_samples, replacement=True)

学习率策略对比

# 余弦退火比线性衰减更适合词嵌入训练
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100, eta_min=0.0001)

可视化验证

from sklearn.manifold import TSNE

def visualize_embeddings(embeddings, words):
    """用 TSNE 降维展示词向量空间"""
    tsne = TSNE(n_components=2)
    reduced = tsne.fit_transform(embeddings)

    plt.figure(figsize=(12,8))
    for i, word in enumerate(words):
        plt.scatter(reduced[i,0], reduced[i,1])
        plt.annotate(word, (reduced[i,0], reduced[i,1]))

生产环境实战技巧

OOV 词处理三剑客

  1. UNK 令牌 :为低频词设置统一标记
  2. 子词单元 :采用 BPE 或 WordPiece 分解单词
  3. 字符级嵌入 :组合字符特征生成词向量

百万词汇内存优化

  • 哈希分桶 :用哈希函数减少矩阵维度
  • 混合精度训练 :FP16 节省显存
  • 梯度检查点 :时间换空间

多 GPU 训练要点

model = nn.DataParallel(model)  # 包装模型
# 梯度会自动在 GPU 间同步
loss.backward()
optimizer.step()

思考与延伸

课后思考题

  1. 如何设计实验验证词向量确实捕捉到了 ” 国王 - 男人≈女王 - 女人 ” 这类关系?
  2. 当遇到像 ” 苹果 ”(水果 / 公司)这样的多义词时,单一向量表示存在什么缺陷?ELMo 等上下文嵌入模型如何解决这个问题?

实践建议

建议读者在 20newsgroups 等小数据集上先跑通流程,再尝试维基百科等大规模语料。关键是要观察:

  • 随着训练进行,近义词是否在向量空间中逐渐靠近
  • 不同维度下词相似度的变化规律
  • 负采样数量对训练速度和效果的影响

最后提醒:词嵌入只是 NLP 大厦的第一块砖,后续可以尝试结合 LSTM/Transformer 等更复杂的架构。

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