共计 2938 个字符,预计需要花费 8 分钟才能阅读完成。
背景痛点
在自然语言处理(NLP)领域,词嵌入是一项基础而关键的技术。然而,许多开发者在实现 CBOW(Continuous Bag of Words)模型时,常常会遇到以下几个问题:

- 窗口大小选择困难:窗口大小直接影响模型的训练效果,过大或过小都会导致词向量质量下降。
- 低频词处理不佳:低频词由于出现次数少,往往难以获得高质量的向量表示。
- 训练速度慢:尤其是在大规模语料上训练时,CBOW 模型的训练时间可能会非常长。
这些问题不仅影响了模型的性能,也增加了开发者的调试成本。因此,深入理解 CBOW 的原理和实现细节,对于构建高质量的词嵌入模型至关重要。
技术对比
CBOW 和 Skip-gram 是两种常见的词嵌入模型,它们各有优缺点:
- CBOW:
- 优点:训练速度快,适合处理大规模语料。
-
缺点:对低频词的处理不如 Skip-gram。
-
Skip-gram:
- 优点:对低频词的处理较好,生成的词向量质量通常更高。
- 缺点:训练速度较慢,尤其是在大规模语料上。
开发者可以根据具体需求选择合适的模型。如果目标是快速训练大规模语料,CBOW 可能是更好的选择;如果更关注词向量的质量,尤其是低频词的表现,Skip-gram 可能更合适。
核心实现
数学原理
CBOW 模型的核心思想是通过上下文词预测中心词。其前向传播过程可以表示为:
-
将上下文词的 one-hot 向量输入 Embedding 层,得到词向量表示:
$$ \mathbf{v}_c = \mathbf{E} \mathbf{x}_c $$
其中,$\mathbf{E}$ 是词嵌入矩阵,$\mathbf{x}_c$ 是上下文词的 one-hot 向量。 -
对上下文词向量求平均:
$$ \mathbf{v}{avg} = \frac{1}{C} \sum_c $$
其中,$C$ 是窗口大小。}^C \mathbf{v -
将平均向量输入全连接层,得到预测结果:
$$ \mathbf{z} = \mathbf{W} \mathbf{v}_{avg} + \mathbf{b} $$
其中,$\mathbf{W}$ 和 $\mathbf{b}$ 是全连接层的权重和偏置。 -
通过 softmax 函数计算概率分布:
$$ p(w_o | w_{c1}, …, w_{cC}) = \frac{\exp(\mathbf{z}{o})}{\sum $$
其中,$V$ 是词汇表大小。}^V \exp(\mathbf{z}_i)
反向传播过程则通过梯度下降法更新词嵌入矩阵和全连接层的参数。
PyTorch 实现
以下是 CBOW 模型的 PyTorch 实现代码,包含数据预处理、网络结构定义和训练循环:
import torch
import torch.nn as nn
import torch.optim as optim
from collections import Counter
import numpy as np
# 数据预处理
def build_vocab(corpus, min_freq=5):
word_counts = Counter(corpus)
vocab = {word: idx for idx, (word, count) in enumerate(word_counts.items()) if count >= min_freq}
vocab['<unk>'] = len(vocab) # 未知词
return vocab
# 网络结构定义
class CBOW(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(CBOW, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.linear = nn.Linear(embedding_dim, vocab_size)
def forward(self, inputs):
embeds = self.embeddings(inputs)
avg_embeds = torch.mean(embeds, dim=1)
out = self.linear(avg_embeds)
return out
# 训练循环
def train_cbow(model, data_loader, epochs=10, lr=0.001):
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
for epoch in range(epochs):
total_loss = 0
for batch in data_loader:
context, target = batch
optimizer.zero_grad()
output = model(context)
loss = criterion(output, target)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f'Epoch {epoch}, Loss: {total_loss}')
优化实践
负采样
负采样是一种高效的训练技巧,通过随机采样负例来加速训练。以下是负采样的实现代码:
def negative_sampling(target, num_negatives, vocab_size):
negatives = []
for _ in range(num_negatives):
neg = np.random.randint(0, vocab_size)
while neg == target:
neg = np.random.randint(0, vocab_size)
negatives.append(neg)
return torch.tensor(negatives)
学习率调度
学习率调度可以帮助模型更快收敛。可以使用 PyTorch 的 ReduceLROnPlateau 调度器:
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=2)
词向量评估
词向量的质量可以通过相似度计算和可视化来评估:
from sklearn.metrics.pairwise import cosine_similarity
def word_similarity(word1, word2, embeddings, vocab):
idx1, idx2 = vocab[word1], vocab[word2]
vec1, vec2 = embeddings[idx1], embeddings[idx2]
return cosine_similarity([vec1], [vec2])[0][0]
避坑指南
- 维度灾难:词向量维度不宜过高,通常选择 50-300 维。
- OOV 处理:为未知词(OOV)设置特殊标记,如
<unk>。 - 训练速度慢:使用负采样或层次 softmax 加速训练。
- 低频词处理:增加低频词的采样概率,或使用 Subword 信息。
延伸思考
- 结合 BERT 等现代模型:如何将 CBOW 的词向量与 BERT 等预训练模型结合?
- 多语言词嵌入:如何扩展 CBOW 模型以支持多语言词嵌入?
通过本文的介绍,希望开发者能够掌握 CBOW 词嵌入的核心原理和实现技巧,从而在实际项目中构建高质量的词向量模型。
