共计 2271 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:从 One-Hot 到词嵌入的进化
传统 NLP 处理文本时最直接的方法是使用 One-Hot 编码,但这种表示方式存在明显的维度灾难问题。假设词表大小为 5 万,每个词就需要一个 5 万维的向量表示,其中只有一位是 1,其余全是 0。这种稀疏表示不仅占用大量内存,更重要的是无法捕捉词与词之间的语义关系。

CBOW(Continuous Bag-of-Words)模型通过预测中心词基于其上下文词的方式,学习得到稠密的低维词向量表示。这些词向量能够通过向量空间中的距离反映词语义相似性,解决了 One-Hot 编码的核心痛点。
技术对比:为什么选择 CBOW 而非 Skip-gram
Word2Vec 家族主要有两种模型:CBOW 和 Skip-gram。虽然它们都用于学习词嵌入,但在不同场景下各有优势:
- CBOW 通过上下文预测当前词,训练速度更快,对高频词的处理效果更好
- Skip-gram 通过当前词预测上下文,在小型数据集上表现更好,特别是对低频词
我们选择 CBOW 来实现词嵌入矩阵,主要基于以下考虑:
- 大多数实际应用中数据量充足
- CBOW 训练效率更高
- 对高频词的更好处理符合多数 NLP 任务需求
核心实现:PyTorch 构建 CBOW 模型
词表构建与预处理
# 构建词表并过滤低频词
min_count = 5 # 最小词频阈值
word_counts = Counter(text_corpus)
vocab = {word: idx for idx, (word, count) in enumerate(word_counts.items()) if count >= min_count}
vocab_size = len(vocab)
# 添加特殊 token
vocab['<unk>'] = len(vocab) # 未知词
vocab['<pad>'] = len(vocab) # 填充
滑动窗口上下文采样
CBOW 的核心思想是使用上下文窗口内的词预测中心词。我们需要实现一个滑动窗口采样器:
def get_context_words(text, window_size=2):
"""
生成上下文 - 中心词对
text: 分词后的文本列表
window_size: 上下文窗口半径
返回: (context_words, center_word) 元组列表
"""
pairs = []
for i in range(window_size, len(text)-window_size):
context = text[i-window_size:i] + text[i+1:i+window_size+1]
center = text[i]
pairs.append((context, center))
return pairs
负采样实现
负采样是提高训练效率的关键技术,替代了计算量大的 softmax:
class NegativeSampler:
def __init__(self, word_counts, power=0.75):
"""
word_counts: 词频统计字典
power: 平滑系数(默认 0.75)
"""
self.word_dist = np.array([count**power for count in word_counts.values()])
self.word_dist = self.word_dist / self.word_dist.sum()
self.words = list(word_counts.keys())
def sample(self, n_samples):
"""采样负样本"""
return np.random.choice(self.words, size=n_samples, p=self.word_dist)
性能优化:关键参数调优
batch_size 与 embedding_dim 的权衡
- 较大的 batch_size 可以提高训练速度,但可能降低模型质量
- embedding_dim 通常选择 128-512 之间,需要平衡表达能力和计算成本
建议从 256 维开始实验,根据下游任务表现调整。
动态学习率策略
# 余弦退火学习率调度
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=100, # 半周期迭代次数
eta_min=1e-5 # 最小学习率
)
避坑指南:实战经验分享
处理 OOV 词的 3 种方案
- 使用特殊
标记表示所有未登录词 - 字符级或子词 (subword) 表示
- 预训练一个通用 OOV 向量
梯度爆炸预防
- 梯度裁剪:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0) - 监控梯度范数:
total_norm = torch.norm(torch.stack([torch.norm(p.grad) for p in model.parameters()]))
生产建议:模型部署与评估
模型持久化方案
# 保存整个模型
torch.save(model, 'cbow_model.pth')
# 只保存词向量(生产环境推荐)
np.save('word_embeddings.npy', model.embedding.weight.detach().numpy())
可视化评估工具
- 使用 t -SNE 或 PCA 降维后可视化
- 推荐工具:TensorBoard Projector
结语与开放问题
通过本文的实践指南,我们实现了从理论到生产的完整 CBOW 词嵌入矩阵构建流程。最后留给大家一个值得思考的问题:如何评估不同维度下词向量的语义捕获能力?是简单的相似度计算,还是需要通过下游任务验证?这或许是词向量研究中最基础也最重要的问题之一。
正文完
