用 PyTorch 实现 CBOW 模型

一、词向量与 CBOW 模型

在自然语言处理(NLP)领域,词向量是将离散单词转化为连续数值向量的核心技术,它能让计算机理解单词的语义信息(如语义相似的单词向量距离更近)。而CBOW(Continuous Bag-of-Words,连续词袋模型) 是 Word2Vec 的经典实现之一,核心思想是通过上下文单词预测中心目标单词,训练过程中学习到的嵌入层参数就是我们需要的词向量,兼具实现简单、训练高效的特点。

二、语料预处理

原始文本通常包含标点、空值、多余空格等噪声,无法直接用于模型训练,语料预处理的目标是将原始文本转化为干净、标准化的单词序列

2.1 核心处理步骤

  1. 读取文本语料:使用 Pandas 读取本地文本文件,处理空值

  2. 文本拼接与降噪:将多行文本拼接为单行,去除标点符号(逗号、句号等)

  3. 单词分割:按空格分割为单词列表,兼容 1 个 / 多个空格,自动过滤空字符串

  4. 构建词汇表:生成唯一单词集合,建立单词与索引的双向映射(方便后续数值化)

代码:

python 复制代码
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
import pandas as pd

# 超参数:上下文窗口大小(左右各4个单词)
CONTEXT_SIZE = 4

# 1. 读取语料并处理空值
raw_text = pd.read_table(r'.\AI报道.txt', header=None)
total_text = raw_text.iloc[:, 0].dropna().str.cat(sep=' ')

# 2. 去除标点,标准化文本
total_text = total_text.replace(',', '').replace('.', '')

# 3. 分割为单词列表(兼容多空格,过滤空值)
total_text = total_text.split()

# 4. 构建词汇表与双向映射
vocab = set(word for word in total_text if word.strip())  # 唯一单词集合
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)}  # 索引→单词
  • dropna() 必须保留:避免原始文本中的空行导致后续处理报错

  • 无参数split():相比split(' '),能自动处理多个连续空格,且不会生成空字符串元素

三、构造训练数据集

python 复制代码
# 构造(上下文, 目标词)训练样本
data = []
for i in range(CONTEXT_SIZE, len(total_text) - CONTEXT_SIZE):
    # 提取上下文:目标词左侧4个单词
    context = [total_text[j + i - 4] for j in range(CONTEXT_SIZE)]
    # 提取目标词
    target = total_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)  # 强制转为长整型张量(Embedding层要求)

# 测试:输出第一个样本的上下文张量
print(make_context_vector(data[0][0], word_to_idx))
  • 遍历边界:range(CONTEXT_SIZE, len(total_text)-CONTEXT_SIZE) 确保上下文不会超出单词列表范围,避免索引越界;

  • 张量类型:dtype=torch.long 是必须的,PyTorch 的nn.Embedding层仅接受长整型(LongTensor)索引输入;

  • 样本格式:data列表中每个元素为(['单词1','单词2',...,'单词8'], '目标词'),是 CBOW 模型的标准训练样本。

四、定义 CBOW 模型 ------ 基于 PyTorch 的神经网络实现

CBOW 模型的网络结构简单且固定,核心由嵌入层、线性层、激活层、输出层组成,训练过程中嵌入层的权重会被不断优化,最终成为我们需要的词向量。

5.1 CBOW 模型核心结构

  1. 嵌入层(Embedding):核心层,将单词索引(one-hot 向量等价)压缩为低维连续向量(词向量),输入为vocab_size(词汇表大小),输出为embedding_dim(词向量维度,超参数)

  2. 投影层(Linear1):将词向量映射到隐藏层(128 维),增加模型拟合能力;

  3. 激活层(ReLU):引入非线性,解决线性模型表达能力不足的问题;

  4. 输出层(Linear2):将隐藏层特征映射回词汇表大小,输出每个单词为目标词的概率;

  5. LogSoftmax:对输出层结果做归一化,配合 NLLLoss 计算损失。

python 复制代码
class CBOW(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(CBOW, self).__init__()  # 继承父类nn.Module的初始化
        # 嵌入层:vocab_size→embedding_dim,核心词向量层
        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):
        # 1. 嵌入层:索引→词向量,求和后重塑形状(适配线性层)
        embeds = sum(self.embeddings(inputs)).view(1, -1)
        # 2. 投影层+ReLU激活
        out = F.relu(self.proj(embeds))
        # 3. 输出层:隐藏层→词汇表维度
        out = self.output(out)
        # 4. LogSoftmax归一化,dim=-1表示最后一维归一化
        nll_prob = F.log_softmax(out, dim=-1)
        return nll_prob

view(1, -1):将求和后的一维张量重塑为(1, embedding_dim)的二维张量,因为 PyTorch 线性层要求输入为二维(batch_size, feature_dim)

五、模型训练

模型训练是优化嵌入层权重(词向量)的核心过程,需要完成设备配置、模型实例化、优化器与损失函数选择、训练循环,同时通过损失值变化监控训练效果

5.1设备、模型、优化器、损失函数

python 复制代码
# 1. 自动选择训练设备(GPU→MPS→CPU),提升训练速度
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"训练设备:{device}")

# 2. 实例化CBOW模型并移至指定设备,embedding_dim=10为词向量维度(超参数)
model = CBOW(vocab_size, 10).to(device)

# 3. 选择优化器:Adam优化器,学习率lr=0.001(超参数)
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 4. 选择损失函数:NLLLoss(配合LogSoftmax使用,适合多分类任务)
loss_function = nn.NLLLoss()

# 5. 模型设为训练模式:开启梯度追踪、批量归一化/ dropout生效
model.train()

# 存储每轮损失值,监控训练效果
losses = []

5.2训练循环

python 复制代码
# 训练200轮,tqdm显示进度条
for epoch in tqdm(range(200)):
    total_loss = 0  # 累计每轮总损失
    # 遍历所有训练样本
    for context, target in data:
        # 1. 上下文数值化并移至指定设备
        context_vector = make_context_vector(context, word_to_idx).to(device)
        # 2. 目标词数值化并移至指定设备(转为张量,适配损失函数)
        target = torch.tensor([word_to_idx[target]]).to(device)
        
        # 3. 前向传播:预测目标词概率
        train_predict = model(context_vector)
        # 4. 计算损失:预测值与真实值的差距
        loss = loss_function(train_predict, target)
        
        # 5. 反向传播与参数更新(核心三步)
        optimizer.zero_grad()  # 梯度清零:避免上一轮梯度累积
        loss.backward()        # 反向传播:计算所有参数的梯度
        optimizer.step()       # 参数更新:根据梯度调整权重
        
        # 累计本轮损失
        total_loss += loss.item()
    
    # 存储本轮平均损失,监控训练趋势
    losses.append(total_loss / len(data))
    print(f"第{epoch+1}轮损失:{losses[-1]:.4f}")
  • 设备迁移:所有张量和模型必须移至同一设备,否则会报设备不匹配错误

  • 梯度清零:optimizer.zero_grad() 是训练循环的核心,PyTorch 会自动累积梯度,若不清零会导致梯度计算错误

  • 损失累加:loss.item() 将张量类型的损失值转为 Python 数值,避免张量累积导致的内存泄漏

  • 模型训练模式:model.train() 开启梯度追踪,若使用 dropout/batchnorm 层,该方法会让其按训练模式工作

  • 损失监控:若损失值持续下降并趋于稳定,说明模型训练有效;若损失值不下降,需调整学习率、词向量维度等超参数。

六、模型测试与词向量生成

6.1 模型测试:输入前四个单词预测第五个词

python 复制代码
# 测试上下文(需来自训练语料的词汇表)
test_context = ['AI','Revolutionizing','Industries','and']
# 上下文数值化并移至设备
context_vector = make_context_vector(test_context, word_to_idx).to(device)

# 模型设为测试模式:关闭梯度追踪、dropout/batchnorm固定
model.eval()
# 前向传播预测
with torch.no_grad():  # 关闭梯度计算,提升速度并节省内存
    predict = model(context_vector)

# 提取预测结果:取概率最大的索引,转为单词
max_idx = predict.argmax(1).cpu().item()  # 设备迁移+转为Python整数
predict_word = idx_to_word[max_idx]
print(f"模型预测的目标单词:{predict_word}")
  • model.eval():将模型转为测试模式,关闭梯度追踪,若有 dropout/batchnorm 层,会按测试模式工作(如 dropout 不随机丢弃神经元)

  • torch.no_grad():上下文管理器,临时关闭梯度计算,避免测试过程中产生无用梯度,提升计算速度并节省内存

  • argmax(1):按行取概率最大的索引(dim=1),对应词汇表中概率最高的目标词

  • cpu().item():将设备上的张量索引转为 Python 整数,才能通过idx_to_word映射为单词

6.2词向量提取与字典生成

python 复制代码
# 提取嵌入层权重(词向量矩阵),并做设备/梯度/格式转换
# cpu():移至CPU;detach():脱离计算图,停止梯度追踪;numpy():转为Numpy数组
word_embedding_weight = model.embeddings.weight.cpu().detach().numpy()

# 生成词向量字典:{单词:对应的词向量}
word_2_vec = {}
for word in word_to_idx.keys():
    # 按单词索引取权重矩阵的对应行,即为该单词的词向量
    word_2_vec[word] = word_embedding_weight[word_to_idx[word], :]

# 打印示例:查看某个单词的词向量
print(f"单词'AI'的词向量:{word_2_vec['AI']}")
print(f"词向量维度:{word_2_vec['AI'].shape}")
  • detach() 必须使用:嵌入层权重是模型的可训练参数,处于计算图中,detach() 可脱离计算图并停止梯度追踪,避免后续操作报错

  • 权重矩阵形状:(vocab_size, embedding_dim),即词汇表中每个单词对应一个embedding_dim维的词向量

  • 词向量字典的意义:将数值化的词向量与单词一一对应,可直接用于后续语义分析、文本分类等 NLP 任务

相关推荐
Deepoch2 小时前
Deepoc具身模型开发板:让农业采摘机器人智能化升级更简单
人工智能·科技·农业·采摘机器人·农业机器人·deepoc·具身模型开发板
北巷`2 小时前
大模型应用的模型架构和核心技术原理-以DeepSeek对话助手为例分析
人工智能
CDA数据分析师干货分享2 小时前
【干货】CDA一级知识点拆解3:《CDA一级商业数据分析》第3章 商业数据分析框架
大数据·人工智能·数据挖掘·数据分析·cda证书·cda数据分析师
Coding茶水间2 小时前
基于深度学习的花朵识别系统演示与介绍(YOLOv12/v11/v8/v5模型+Pyqt5界面+训练代码+数据集)
开发语言·人工智能·深度学习·yolo·目标检测·机器学习
GAOJ_K2 小时前
滚柱导轨润滑周期预测
人工智能·科技·自动化·制造
致Great2 小时前
Kimi K2.5技术报告解读:视觉-文本联合训练与并行智能体框架
人工智能
阿杰学AI2 小时前
AI核心知识82——大语言模型之AI Value Alignment(简洁且通俗易懂版)
人工智能·ai·语言模型·自然语言处理·aigc·机械学习·ai价值观对齐
小镇cxy2 小时前
OpenSpec 规范开发
人工智能·ai
北京地铁1号线2 小时前
4.2 幻觉抑制策略
大数据·人工智能·深度学习·大语言模型