连续词袋模型(Continuous Bag-of-Words,CBOW)是Word2Vec的两种经典实现方式之一。它的核心思想是通过上下文词来预测当前词,这与人类理解语言的方式非常相似——当我们看到"猫喜欢吃__"这样的句子时,很容易联想到"鱼"这个空缺词。
CBOW模型的架构特点:
与传统N-gram模型相比,CBOW的优势在于:
CBOW模型的核心计算过程可以表示为:
h = (1/C) * Σ(W^T * x_i)
y = softmax(U^T * h)
其中:
提示:在实际实现中,我们通常使用负采样(Negative Sampling)来优化softmax计算,但本文示例保持原始实现以便理解。
python复制def make_context_vector(context, word_to_idx):
idxs = [word_to_idx[w] for w in context]
return torch.tensor(idxs, dtype=torch.long)
这个函数完成了以下关键转换:
常见问题处理:
python复制class CBOW(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(CBOW, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.proj = nn.Linear(embedding_dim, 128)
self.output = nn.Linear(128, vocab_size)
def forward(self, inputs):
embeds = sum(self.embeddings(inputs)).view(1, -1)/len(inputs)
out = F.relu(self.proj(embeds))
out = self.output(out)
nll_prob = F.log_softmax(out, dim=-1)
return nll_prob
关键组件解析:
| 组件 | 作用 | 参数说明 |
|---|---|---|
| nn.Embedding | 词嵌入层 | vocab_size×embedding_dim矩阵 |
| nn.Linear(proj) | 中间投影层 | 提升表征能力 |
| nn.Linear(output) | 输出层 | 输出词汇表大小的概率分布 |
| F.relu | 激活函数 | 引入非线性 |
| F.log_softmax | 损失计算 | 配合NLLLoss使用 |
python复制model = CBOW(vocab_size, 10).to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)
loss_function = nn.NLLLoss()
for epoch in tqdm(range(200)):
total_loss = 0
for context, target in data:
context_vector = make_context_vector(context, word_to_idx).to(device)
target = torch.tensor([word_to_idx[target]]).to(device)
optimizer.zero_grad()
train_prediction = model(context_vector)
loss = loss_function(train_prediction, target)
loss.backward()
optimizer.step()
total_loss += loss.item()
losses.append(total_loss)
训练技巧:
训练完成后,可以通过降维技术(如t-SNE)可视化词向量:
python复制from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
tsne = TSNE(n_components=2)
vectors_2d = tsne.fit_transform(W)
plt.figure(figsize=(10,8))
for i, word in enumerate(vocab):
plt.scatter(vectors_2d[i,0], vectors_2d[i,1])
plt.annotate(word, xy=(vectors_2d[i,0], vectors_2d[i,1]))
plt.show()
python复制# 替换原始softmax输出层
self.output = nn.LogSigmoid() # 用于负采样
层次softmax:通过霍夫曼树减少计算复杂度
子词信息:引入FastText的字符n-gram特征
多任务学习:同时训练CBOW和Skip-gram目标
python复制torch.quantization.quantize_dynamic(
model, {nn.Linear}, dtype=torch.qint8)
python复制torch.onnx.export(model, dummy_input, "cbow.onnx")
修改数据构建方式:
python复制data = []
for i in range(len(raw_text) - CONTEXT_SIZE):
context = raw_text[i:i + CONTEXT_SIZE] # 历史输入
target = raw_text[i + CONTEXT_SIZE] # 待预测词
data.append((context, target))
关键优化点:
python复制def get_similar_words(word, top_k=5):
vec = model.embeddings(torch.tensor([word_to_idx[word]]))
similarities = torch.matmul(model.embeddings.weight, vec.T)
_, indices = torch.topk(similarities, k=top_k+1)
return [idx_to_word[i.item()] for i in indices[1:]]
将CBOW词向量作为特征输入分类器:
python复制class TextClassifier(nn.Module):
def __init__(self, embedding_layer, num_classes):
super().__init__()
self.embedding = embedding_layer
self.classifier = nn.Linear(embedding_dim, num_classes)
def forward(self, text):
embedded = self.embedding(text).mean(dim=1)
return self.classifier(embedded)
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 损失不下降 | 学习率过高/低 | 调整lr(0.0001-0.01) |
| 梯度爆炸 | 未做归一化 | 添加LayerNorm |
| 过拟合 | 模型复杂度过高 | 增加Dropout层 |
| 词义混淆 | 窗口大小不当 | 调整上下文窗口(2-10) |
python复制import torch.sparse
sparse_input = context_vector.to_sparse()
python复制scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
python复制model = nn.DataParallel(model)
在实际项目中,我发现embedding_dim设置在100-300之间通常能取得较好平衡。对于小型语料库,可以适当降低维度防止过拟合;而大型语料库则需要更高维度捕捉细粒度语义。另一个实用技巧是在训练初期冻结embedding层,先优化上层参数,待loss平稳后再解冻进行联合训练。