2026年9月15日
基于PyTorch实现CBOW词向量模型
一、前言
在自然语言处理(NLP)任务中,文字无法直接被计算机识别与运算,词向量(Word Embedding)是连接自然语言与深度学习模型的核心桥梁。Word2Vec是经典的轻量级词向量训练算法,主要包含两种模型:**CBOW(连续词袋模型)**和Skip-Gram模型。
其中CBOW模型的核心思想十分简单:通过某个单词的上下文词汇,预测当前中心单词,在训练过程中不断迭代更新权重,最终学习到每一个单词的高维语义向量。
二、CBOW模型核心原理
2.1 核心逻辑
给定固定大小的上下文窗口(本文设置左右各2个单词,共4个上下文词),将上下文单词作为模型输入,中心单词作为预测标签。模型通过学习上下文与中心词的关联关系,将语义相近的单词映射到高维空间中距离相近的向量。
2.2 模型结构
-
嵌入层(Embedding):将离散的单词索引转换为连续稠密的词向量,是模型的核心权重层,最终训练结果即为该层参数。
-
隐藏层:通过全连接层对词向量进行特征变换,搭配ReLU激活函数提升模型非线性拟合能力。
-
输出层:将隐藏层特征映射为词表维度的概率分布,通过log_softmax归一化处理,配合负对数似然损失函数完成训练。
三、环境依赖准备
本次实战所需依赖库均为深度学习常用工具,提前安装即可正常运行代码:
pip install numpy tqdm torch
核心依赖说明:
-
torch:搭建、训练深度学习模型核心框架;
-
numpy:数据处理与矩阵运算;
-
tqdm:训练过程进度条可视化。
四、完整代码实现与详细解析
4.1 导入依赖库
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from tqdm import tqdm
import numpy as np
4.2 数据集构建
本次使用英文短文作为原始语料,通过固定窗口划分上下文与中心词,构建CBOW训练数据集。同时构建单词与索引的双向映射字典,实现文字与模型可识别数字的转换。
# 定义上下文窗口大小:左右各2个单词
CONTEXT_SIZE = 2
# 原始训练语料
raw_text = """We are about to study the idea of a computational process.
Computational processes are abstract beings that inhabit computers.
As they evolve, processes manipulate other abstract things called data.
The evolution of a process is directed by a pattern of rules
called a program. People create programs to direct processes. In effect,
we conjure the spirits of the computer with our spells.""".split()
# 构建词表(去重)
vocab = set(raw_text)
vocab_size = len(vocab)
# 单词-索引双向映射字典
word_to_idx = {word: i for i, word in enumerate(vocab)}
idx_to_word = {i: word for i, word in enumerate(vocab)}
# 构建训练数据集:(上下文词列表, 中心目标词)
data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
# 拼接左右上下文单词
context = (
[raw_text[i - (2-j)] for j in range(2)]
+ [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]
)
target = raw_text[i]
data.append((context, target))
# 上下文单词转索引张量工具函数
def make_context_vector(context, word_to_idx):
idxs = [word_to_idx[w] for w in context]
return torch.tensor(idxs, dtype=torch.long)
4.3 设备自适应配置
自动识别设备硬件,优先使用GPU加速训练,无GPU时自动适配CPU,提升代码通用性。
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"当前训练设备: {device}")
4.4 搭建CBOW模型
继承PyTorch核心模型基类,定义嵌入层、全连接隐藏层、输出层,实现前向传播逻辑。通过上下文词向量求和聚合语义特征,完成中心词预测。
class CBOW(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(CBOW, self).__init__()
# 词嵌入层:词表大小+词向量维度
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
# 隐藏层:词向量映射至128维特征
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)
# 非线性特征提取
out = F.relu(self.proj(embeds))
# 输出预测分数并归一化对数概率
out = self.output(out)
nll_prob = F.log_softmax(out, dim=-1)
return nll_prob
4.5 模型训练配置与训练循环
配置模型参数、优化器、损失函数,通过多轮迭代完成模型训练,实时记录每轮训练的总损失,监控模型收敛效果。
# 初始化模型:词向量维度设置为10
model = CBOW(vocab_size, 10).to(device)
# Adam优化器,学习率0.001
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 损失函数:负对数似然损失,适配log_softmax输出
loss_function = nn.NLLLoss()
# 存储每轮损失值
losses = []
# 模型训练模式
model.train()
# 迭代200轮训练
for epoch in tqdm(range(200), desc="训练进度"):
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)
# 前向传播预测
predict = model(context_vector)
# 计算损失
loss = loss_function(predict, target)
# 反向传播与参数更新
optimizer.zero_grad() # 梯度清零,避免累积
loss.backward() # 反向传播求梯度
optimizer.step() # 更新模型权重
total_loss += loss.item()
losses.append(total_loss)
# 输出训练损失变化
print("每轮训练总损失:", losses)
4.6 模型测试与词向量提取
训练完成后切换模型为评估模式,通过自定义上下文预测中心单词,验证模型效果,同时提取训练完成的词向量矩阵。
# 模型评估测试
if __name__ == "__main__":
# 自定义测试上下文:原句 People create programs to direct
test_context = ['People', 'create', 'to', 'direct']
test_vec = make_context_vector(test_context, word_to_idx).to(device)
# 切换评估模式,关闭训练专属参数
model.eval()
# 模型预测
predict_result = model(test_vec)
# 取概率最大值对应的单词索引
max_idx = predict_result.argmax(1).item()
# 索引还原为单词
pred_word = idx_to_word[max_idx]
print(f"上下文词汇:{test_context}")
print(f"模型预测中心词:{pred_word}")
# 提取训练完成的词向量矩阵(核心成果)
word_embedding = model.embeddings.weight.cpu().detach().numpy()
print(f"\n词向量矩阵维度:{word_embedding.shape}")
print("词向量矩阵数据:")
print(word_embedding)
五、核心代码关键点解析
5.1 损失函数搭配逻辑
代码中使用 F.log_softmax + nn.NLLLoss() 组合,效果等价于交叉熵损失函数。先对模型输出做对数概率归一化,再通过负对数似然计算预测值与真实值的误差,是多分类任务的经典搭配。
5.2 训练与评估模式区别
-
model.train():开启训练模式,启用梯度计算、参数更新,适用于模型迭代训练阶段;
-
model.eval():开启评估模式,关闭梯度计算、dropout等训练专属机制,保证预测结果稳定。
5.3 词向量提取逻辑
模型的**embeddings.weight** 即为最终训练的词向量矩阵,维度为[词表数量, 词向量维度]。通过**cpu()、detach()、numpy()**三步处理,将GPU张量转为可直接使用的numpy数组,方便后续保存、可视化和二次运算。
六、实验结果分析
-
损失收敛:随着训练轮次增加,每轮总损失持续下降并逐渐趋于平稳,说明模型有效学习到上下文与中心词的语义关联,拟合效果良好。
-
预测效果 :输入上下文
['People', 'create', 'to', 'direct'],模型可准确预测出中心词programs,符合原始语料语义。 -
词向量特征:最终得到的词向量将语义相近的单词映射为相似向量,可用于文本分类、语义匹配、聚类等下游NLP任务。
七、总结与拓展方向
本文实现了极简版CBOW词向量训练模型,完整复现了Word2Vec的核心逻辑,帮助理解词嵌入的训练原理。相较于传统one-hot编码,CBOW得到的稠密词向量解决了维度爆炸、语义缺失的问题,是NLP入门的核心知识点。