自然语言处理是人工智能领域的核心分支之一,而词向量(Word2Vec)作为 NLP 任务的基础表征技术,解决了传统独热编码高维稀疏、无法表达语义关联的痛点。Word2Vec 包含两种经典模型:跳元模型(Skip-Gram)与连续词袋模型(CBOW)。本文将结合完整可运行的 PyTorch 代码,从原理剖析、数据集构建、网络搭建、模型训练到结果保存,全方位讲解CBOW 模型的手动实现流程,同时解析代码细节与踩坑优化技巧,帮助零基础读者彻底理解词向量的训练逻辑,全文结合实战代码,兼具理论深度与工程落地性。
一、研究背景与理论基础
1.1 词向量的发展痛点
在早期自然语言处理任务中,研究者常使用One-Hot 独热编码表征单词。假设词汇表包含 1000 个单词,每个单词会被编码为长度 1000 的向量,仅对应索引位置为 1,其余全为 0。这种编码方式存在两大致命缺陷:第一,维度灾难。词汇量越大,向量维度越高,计算成本呈指数级增长;第二,语义隔离。任意两个单词的独热向量内积恒为 0,无法体现 "computer" 与 "program" 这类语义相近单词的关联关系。
为解决上述问题,Word2Vec 词嵌入技术应运而生。它通过神经网络训练,将单词映射到低维稠密向量空间,语义相似的单词在向量空间中距离更近,完美实现语义量化表达。
1.2 CBOW 模型核心原理
Word2Vec 两大模型中,CBOW(Continuous Bag of Words,连续词袋模型) 的核心逻辑是:通过上下文单词预测中心单词 。本文代码中设置CONTEXT_SIZE = 2,代表中心词左右各取 2 个单词作为上下文窗口,单个样本共 4 个上下文词汇。模型训练时,输入上下文单词的向量表征,经过嵌入层映射、全连接层特征提取后,输出词汇表中每个单词的概率分布,不断缩小预测概率与真实中心词的误差,最终收敛得到稳定的词嵌入矩阵。
对比 Skip-Gram 模型(中心词预测上下文),CBOW 训练速度更快,适合小型语料训练,非常适合入门学习词向量原理,也是本文实战实现的核心模型。
1.3 CBOW 网络结构拆解
标准简易 CBOW 网络分为三层:
- 嵌入层 Embedding:将单词索引转换为低维稠密向量,是词向量存储的核心层;
- 隐藏全连接层:聚合上下文向量特征,通过激活函数增强非线性表达能力;
- 输出分类层:映射到词汇表维度,经 Log Softmax 输出单词预测概率,配合 NLLLoss 损失函数完成训练优化。
二、数据集预处理完整流程
2.1 原始语料定义
本文采用一段英文科普文本作为小型训练语料,涵盖计算机基础概念短句,语料长度适中,适合轻量化模型快速收敛训练:
python
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."""
小型封闭语料可以避免生僻词干扰,降低模型训练难度,方便初学者观察损失下降与词向量收敛过程。
2.2 词汇表与索引映射构建
首先提取语料中所有独立单词构建词汇表,再建立单词→索引 与索引→单词双向映射字典,这是深度学习文本任务的标准预处理步骤:
python
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 in enumerate(vocab)}
集合去重保证词汇唯一性,双向字典方便后续编码转换与预测结果还原,是 CBOW 数据输入的基础铺垫。
2.3 上下文与中心词样本构建
设置上下文窗口大小CONTEXT_SIZE = 2,遍历整个文本序列,逐个提取中心词与对应上下文:
python
data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
context =(
[raw_text[i - (2-j)] for j in range(CONTEXT_SIZE)]
+ [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]
)
target = raw_text[i]
data.append((context, target))
遍历范围规避首尾边界(无完整上下文),每个样本格式为(上下文列表, 中心词),批量构建训练数据集,为后续迭代训练提供样本支撑。
2.4 上下文向量编码优化函数
自定义make_context_vector函数,将上下文单词转换为模型可识别的张量索引,同时加入工程优化:小写统一转换 + 未知词兜底:
python
def make_context_vector(context, word_to_ix):
idxs = [word_to_ix.get(w.lower(), 0) for w in context]
return torch.tensor(idxs, dtype=torch.long)
原生代码仅简单索引映射,优化后通过get方法设置默认索引 0,避免陌生单词导致键值报错;小写统一消除大小写差异干扰,提升数据集鲁棒性,是实战开发中必备的容错技巧。
三、设备配置与 CBOW 网络模型搭建
3.1 自适应训练设备选择
深度学习训练优先使用 GPU 加速,代码自动兼容 CUDA、Apple MPS 与 CPU 三类设备,跨平台适配性极强:
python
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(device)
NVIDIA 显卡自动启用 CUDA 加速,Mac 苹果芯片调用 MPS 框架,无加速设备则默认 CPU 运行,兼顾不同运行环境的兼容性。
3.2 CBOW 神经网络类定义
基于 PyTorch 的 nn.Module 自定义 CBOW 模型,严格遵循三层结构设计:
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)
out = F.relu(self.proj(embeds))
out = self.output(out)
nll_prob = F.log_softmax(out, dim=-1)
return nll_prob
网络层详解:
- 嵌入层 nn.Embedding:输入词汇总量与词向量维度(本文设置 10 维),自动初始化词嵌入权重矩阵,是训练后词向量的存储载体;
- 特征投影层 Linear:将聚合后的上下文向量映射到 128 维隐藏空间,ReLU 激活函数引入非线性特征;
- 输出分类层:映射回词汇表维度,Log Softmax 归一化输出概率分布,适配 NLLLoss 损失函数计算误差。
前向传播逻辑:
上下文多个单词嵌入向量求和聚合,重塑维度后逐层前向计算,最终输出中心词预测对数概率,完全贴合 CBOW 上下文预测中心词的核心逻辑。
四、模型训练全过程解析
4.1 初始化模型、优化器与损失函数
python
model = CBOW(vocab_size, 10).to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)
loss_function = nn.NLLLoss()
losses = []
- 模型实例化:词汇总量 + 10 维词向量,迁移至训练设备;
- 优化器选择 Adam:自适应学习率,收敛速度优于 SGD,适合小型模型快速训练;
- 损失函数 NLLLoss:配合网络末端的 Log Softmax,是分类任务经典组合;
- 损失列表:记录每轮总损失,用于观察模型收敛趋势。
4.2 迭代训练核心循环
设置 200 轮训练 epoch,搭配 tqdm 进度条可视化训练过程,逐样本梯度更新:
python
model.train()
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)
# 前向传播预测
train_predict = model(context_vector)
loss = loss_function(train_predict, target)
# 反向传播与参数更新
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
losses.append(total_loss)
print(losses)
训练关键步骤拆解:
- 训练模式锁定 model.train ():启用 Dropout、BN 等训练专用层(本文模型轻量化,为规范写法保留);
- 梯度清零:每轮样本必须执行
zero_grad(),避免梯度累积干扰参数更新; - 损失反向传播:
loss.backward()自动计算所有权重梯度; - 参数迭代:优化器 step () 根据梯度更新嵌入层与全连接层权重;
- 损失累加:统计每 epoch 总损失,打印观察模型是否稳步收敛。
随着训练轮次增加,总损失会持续下降并逐渐趋于平稳,代表模型学习到上下文与中心词的语义关联,词向量权重逐步优化完成。
五、模型推理与词向量结果保存
5.1 自定义上下文推理预测
训练完成后输入自定义上下文['People', 'create', 'to', 'direct'],预测最可能的中心单词:
python
context = ['People', 'create', 'to', 'direct']
context_vector = make_context_vector(context, word_to_idx).to(device)
model.eval()
predict = model(context_vector)
max_idx = predict.argmax(1)
切换model.eval()推理模式,关闭梯度计算与训练专用层,通过 argmax 取概率最大值索引,还原预测中心词,验证模型语义学习效果。
5.2 提取并查看词嵌入权重
嵌入层权重矩阵就是最终训练完成的 Word2Vec 词向量,直接提取解析:
python
print("CBOW embeddings'weight=", model.embeddings.weight)
W = model.embeddings.weight.cpu().detach().numpy()
print(W)
将 GPU 张量迁移至 CPU,脱离计算图转换为 numpy 数组,方便后续离线存储与数据分析,权重矩阵形状为[词汇总数, 10],每行对应一个单词的 10 维稠密向量。
5.3 构建单词 - 向量映射字典
封装所有单词与对应词向量,方便后续快速查询调用:
python
word_2_vec = {}
for word in word_to_idx.keys():
word_2_vec[word] = W[word_to_idx[word], :]
字典结构化存储,实现输入单词快速检索对应向量,适配下游 NLP 任务调用需求。
5.4 词向量离线持久化保存
通过 numpy 压缩保存训练好的词向量文件,实现模型结果复用:
python
np.savez('word2vec实现.npz', file_1=W)
data = np.load('word2vec实现.npz')
print(data.files)
保存为 npz 压缩格式,体积小巧读取便捷,后续无需重新训练,直接加载文件即可调用训练好的词向量,提升工程复用效率。
六、总结
本文基于 PyTorch 完整实现了 CBOW 词向量训练全流程,从理论原理、语料预处理、网络搭建、迭代训练到结果保存,层层拆解代码逻辑。CBOW 通过上下文预测中心词的训练方式,让神经网络自动学习单词的语义关联,将离散单词映射为低维稠密向量,解决了传统独热编码的语义缺陷。
全套代码轻量化易运行,适配入门级深度学习硬件,同时加入工程容错优化,兼顾学习理解与实战落地。通过 200 轮迭代训练,损失稳步收敛,最终提取并离线保存词嵌入矩阵,完成 Word2Vec 核心功能实现。掌握 CBOW 手动实现原理,不仅能深入理解词嵌入训练机制,也为后续 Transformer、大语言模型等进阶 NLP 学习打下坚实基础。