共计 2781 个字符,预计需要花费 7 分钟才能阅读完成。
词嵌入的核心价值与模型选型
词嵌入是 NLP 领域的基石技术,它将离散的词语映射到连续的向量空间。这种表示方法的神奇之处在于:语义相似的词在向量空间中距离相近,甚至能捕获 ” 国王 - 男人 + 女人≈女王 ” 这样的语义关系。对于初学者来说,CBOW(Continuous Bag-of-Words)是理想的入门选择——它通过上下文预测中心词,比 Skip-gram 训练更快,尤其在数据量较少时表现更稳定。Skip-gram 则更适合处理低频词,但需要更多训练资源。

CBOW 模型实现详解
词嵌入矩阵的数学本质
词嵌入矩阵本质上是一个查找表,其数学表示为:
$$ \mathbf{E} \in \mathbb{R}^{V \times D} $$
其中 $V$ 是词汇表大小,$D$ 是嵌入维度。当输入词的 one-hot 向量 $\mathbf{x}_i$ 与矩阵相乘时:
$$ \mathbf{e}_i = \mathbf{x}_i \mathbf{E} $$
这个运算等效于直接选取矩阵的第 $i$ 行。
PyTorch 初始化实战
import torch
import torch.nn as nn
vocab_size = 10000 # 词汇表大小
embed_dim = 300 # 嵌入维度
# 使用 Xavier 正态分布初始化
embedding_layer = nn.Embedding(vocab_size, embed_dim)
nn.init.xavier_normal_(embedding_layer.weight)
# 查看初始化的数值分布
print(f'均值: {embedding_layer.weight.mean().item():.4f}')
print(f'标准差: {embedding_layer.weight.std().item():.4f}')
上下文窗口处理技巧
def get_context_words(text, target_idx, window_size=2):
"""
获取目标词周围的上下文词索引
:param text: 词索引列表 [3, 7, 12, 4, ...]
:param target_idx: 目标词位置
:param window_size: 单侧窗口大小
:return: 上下文词索引列表
"""
start = max(0, target_idx - window_size)
end = min(len(text), target_idx + window_size + 1)
context = [text[i] for i in range(start, end) if i != target_idx]
return context
# 示例:处理句子边界
sample_text = [10, 20, 30, 40, 50]
print(get_context_words(sample_text, 0)) # 输出: [20]
print(get_context_words(sample_text, 2)) # 输出: [20, 40, 50]
训练优化关键技术
负采样实现
import math
def negative_sampling(distribution, num_samples):
"""
log-uniform 负采样
:param distribution: 词频分布
:param num_samples: 负采样数量
:return: 负样本索引列表
"""
# 计算 log 频率
log_freq = torch.log(distribution + 1e-10)
# 构建采样分布
samples = torch.multinomial(log_freq, num_samples, replacement=True)
return samples.tolist()
# 示例:模拟的词频分布
freq_dist = torch.tensor([0.01, 0.5, 0.2, 0.05, 0.24])
print(negative_sampling(freq_dist, 3)) # 可能输出: [1, 4, 1]
学习率策略对比
-
线性衰减:
scheduler = torch.optim.lr_scheduler.LambdaLR( optimizer, lr_lambda=lambda epoch: 1 - epoch / total_epochs ) -
余弦退火:
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=total_epochs )
实验表明,余弦退火在词嵌入训练中通常能获得更平滑的收敛。
嵌入可视化
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
def visualize_embeddings(embeddings, words, n_display=100):
"""
使用 TSNE 降维可视化词嵌入
:param embeddings: 词向量矩阵
:param words: 对应的单词列表
:param n_display: 展示数量
"""
tsne = TSNE(n_components=2, perplexity=15)
reduced = tsne.fit_transform(embeddings[:n_display])
plt.figure(figsize=(12, 8))
for i, word in enumerate(words[:n_display]):
plt.scatter(reduced[i, 0], reduced[i, 1])
plt.annotate(word, xy=(reduced[i, 0], reduced[i, 1]))
plt.show()
# 示例调用
visualize_embeddings(embedding_layer.weight, vocab_list)
生产环境实战经验
OOV 词处理方案
- 特殊 UNK 标记:为所有罕见词分配统一标记
- 子词嵌入:使用 BPE 等算法拆分未登录词
- 动态扩展:运行时遇到 OOV 时动态扩展嵌入矩阵
百万级词汇优化
- 矩阵分块:将大矩阵拆分为多个 GPU 上的小块
- 混合精度训练:使用 FP16 减少内存占用
- 梯度检查点:牺牲计算时间换取内存节省
多 GPU 训练要点
model = nn.DataParallel(model) # 包装模型
# 确保在 loss 计算后执行同步
torch.distributed.all_reduce(loss, op=torch.distributed.ReduceOp.SUM)
思考与进阶
- 语义关系验证 :可以通过计算
cos(king-man+woman, queen)等向量运算的相似度,或构建词类比任务测试集 - 一词多义处理:可以考虑:
- 上下文感知的 ELMo 式嵌入
- 为每个词维护多个嵌入向量
- 使用注意力机制动态混合不同语义
通过这个完整的实现流程,我们不仅理解了 CBOW 的内部机制,更掌握了工业级应用的优化技巧。词嵌入作为深度学习时代的词语 DNA,其训练过程充满工程智慧与数学美感。
正文完
