自然语言处理之PyTorch实现词袋CBOW模型
在自然语言处理(NLP)领域,词向量(Word Embedding)是将文本转换为数值向量的核心技术。它能让计算机“理解”词语的语义关联,例如“国王”和“女王”的向量差可能与“男人”和“女人”的向量差相似。而Word2Vec作为经典的词向量训练模型,其核心思想是通过上下文预测目标词(或反之)。本文将以 --CBOW(连续词袋模型)为例,带你从代码到原理,一步步实现一个简单的词向量训练过程。
一、CBOW模型简介
CBOW(Continuous Bag-of-Words)是Word2Vec的两种核心模型之一。其核心思想是:给定目标词的上下文窗口内的所有词,预测目标词本身。例如,对于句子“We are about to study”,若上下文窗口大小为2(即目标词左右各取2个词),则当目标词是“about”时,上下文是“We, are, to, study”,模型需要根据这4个词预测出“about”。
CBOW的优势在于通过平均上下文词向量来预测目标词,计算效率高;缺点是对低频词不友好。本文将实现的CBOW模型包含词嵌入层、投影层和输出层,最终输出目标词的概率分布。
二、环境准备与数据预处理
2.1 进度条库安装
pip install torch numpy tqdm
2.2 语料库与基础设置
我们使用一段英文文本作为语料库,并定义上下文窗口大小(CONTEXT_SIZE=2
,即目标词左右各取2个词):
CONTEXT_SIZE = 2 # 上下文窗口大小(左右各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() # 按空格分割成单词列表
2.3 构建词汇表与映射
为了将文本转换为模型可处理的数值,需要先构建词汇表(所有唯一词),并为每个词分配唯一索引:
vocab = set(raw_text) # 去重后的词汇表(集合)
vocab_size = len(vocab) # 词汇表大小(本文示例中为49)# 词到索引的映射(如:"We"→0,"are"→1)
word_to_idx = {word: i for i, word in enumerate(vocab)}
# 索引到词的反向映射(如:0→"We",1→"are")
idx_to_word = {i: word for i, word in enumerate(vocab)}
三、生成训练数据:上下文-目标词对
CBOW的训练数据是“上下文词列表”与“目标词”的配对。例如,若目标词是raw_text[i]
,则上下文是[raw_text[i-2], raw_text[i-1], raw_text[i+1], raw_text[i+2]]
(假设窗口大小为2)。
3.1 数据生成逻辑
通过遍历语料库,跳过前CONTEXT_SIZE
和后CONTEXT_SIZE
个词(避免越界),生成上下文-目标词对:
data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):# 左上下文:取i-2, i-1(j从0到1,2-j对应2,1)left_context = [raw_text[i - (2 - j)] for j in range(CONTEXT_SIZE)]# 右上下文:取i+1, i+2(j从0到1,i+j+1对应i+1, i+2)right_context = [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]context = left_context + right_context # 合并上下文(共4个词)target = raw_text[i] # 目标词(当前中心词)data.append((context, target))
3.2 示例验证
以i=2
为例:
- 左上下文:
i-2=0
(“We”),i-1=1
(“are”)→["We", "are"]
- 右上下文:
i+1=3
(“to”),i+2=4
(“study”)→["to", "study"]
- 上下文合并:
["We", "are", "to", "study"]
- 目标词:
raw_text[2]
(“about”)
四、CBOW模型实现(PyTorch)
4.1 模型结构设计
CBOW模型的核心是通过上下文词的词向量预测目标词。模型结构包含三层:
- 词嵌入层(Embedding):将词的索引映射为低维稠密向量(如10维)。
- 投影层(Linear):将拼接后的词向量投影到更高维度(如128维),增加非线性表达能力。
- 输出层(Linear):将投影后的向量映射回词汇表大小,通过Softmax输出目标词的概率分布。
import torch
import torch.nn as nn
import torch.nn.functional as Fclass 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)) # nn.relu() 激活层out = self.output(out)nll_prob = F.log_softmax(out, dim=-1) # softmax交叉熵return nll_prob
五、模型训练与优化
5.1 初始化模型与超参数
设置词向量维度(embedding_dim=10
)、学习率(lr=0.001
)、训练轮数(epochs=200
),并初始化模型、优化器和损失函数:
vocab_size = 49
model = CBOW(vocab_size, 10).to(device)
optimizer=torch.optim.Adam(model.parameters(),lr=0.001)
losses = []# 存储损失的集合 losses: []
loss_function = nn.NLLLoss() #NLLLoss损失函数(当分类列表非常多的情况),将多个类
5.2 训练循环逻辑
遍历每个训练轮次(epoch),对每个上下文-目标词对进行前向传播、损失计算、反向传播和参数更新:
model.train()
for epoch in tqdm(range(200)):#开始训练total_loss = 0for 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) # 可以不写forward,torch的内置功能,loss = loss_function(train_predict, target) # 计算损失# 反向传播optimizer.zero_grad() # 梯度值清零loss.backward() # 反向传播计算得到每个参数的梯度optimizer.step() # 根据梯度更新网络参数total_loss += loss.item()
六、词向量提取与应用
训练完成后,模型的词嵌入层(model.embeddings.weight
)中存储了每个词的向量表示。我们可以将其提取并保存,用于后续任务(如文本分类、相似度计算)。
6.1 提取词向量
# 将词向量从GPU移至CPU,并转换为NumPy数组
W = model.embeddings.weight.cpu().detach().numpy()
print("词向量矩阵形状:", W.shape) # (vocab_size, embedding_dim) → (49, 10)
6.2 生成词-向量映射字典
word_2_vec = {}
for word, idx in word_to_idx.items():word_2_vec[word] = W[idx] # 每个词对应词向量矩阵中的一行
print("示例词向量('process'):", word_2_vec["process"])
6.3 保存词向量
使用np.savez
保存词向量矩阵,方便后续加载使用:
import numpy as np
np.savez('word2vec实现.npz', word_vectors=W) # 保存为npz文件# 加载验证
data = np.load('word2vec实现.npz')
loaded_vectors = data['word_vectors']
print("加载的词向量形状:", loaded_vectors.shape) # 应与原始矩阵一致