回顾:cbow连续词袋与词嵌入

目录

一、先明确核心目标:

二、逐段拆解:代码的每一步,都藏着知识点

第一步:导入依赖库,做好准备工作

第二步:语料预处理------把原始文本变成模型能"看懂"的数据

第三步:生成训练数据------构建"上下文-中心词"样本对

第四步:辅助函数------将上下文转换为模型可输入的张量

第五步:设备选择------优先GPU加速,提升训练效率

第六步:核心环节------用PyTorch定义CBOW模型

[1. __init__方法(模型层定义)](#1. __init__方法(模型层定义))

[2. forward方法(前向传播逻辑)](#2. forward方法(前向传播逻辑))

第七步:模型初始化与训练------从参数优化到模型收敛

第八步:模型验证与词嵌入提取------让训练成果落地

[1. 模型预测](#1. 模型预测)

[2. 词嵌入提取](#2. 词嵌入提取)

[3. 词嵌入保存与加载](#3. 词嵌入保存与加载)

三、运行结果预期与作业

[1. 预期运行结果](#1. 预期运行结果)

2、运行结果部分截图​编辑

3、作业


6.1和8.2是核心理解

一、先明确核心目标:

在看代码之前,我们先理清核心任务,避免"盲目看代码、不懂其意义":

  • 以一段关于"计算过程"的英文语料为训练数据,设定上下文窗口大小为2(即中心词左右各2个词作为上下文);

  • 用PyTorch搭建简易CBOW模型,通过"上下文预测中心词"的辅助任务,训练模型学习词语的语义关联;

  • 训练完成后,从模型中提取词嵌入向量,将其保存为npz文件,方便后续复用(比如文本分类、语义匹配等任务);

  • 用训练好的模型做简单预测,验证模型的训练效果。

简单说,这份代码的核心就是"用PyTorch实战CBOW,最终得到可用的词嵌入",全程贴合工业界基础建模流程,没有多余的复杂操作,新手也能轻松跟上。

二、逐段拆解:代码的每一步,都藏着知识点

接下来,我们跟着代码的执行顺序,逐段拆解,每一段代码都配上详细解读,让每一行代码都有迹可循、有理可依。

第一步:导入依赖库,做好准备工作

代码开头先导入需要的库,都是PyTorch建模的"标配",无需额外安装复杂依赖:

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
from tqdm import tqdm

解读:

  • torch相关库:PyTorch的核心库,用于搭建模型、定义损失函数、优化器等;

  • numpy:用于后续词嵌入向量的保存与加载,处理数值计算;

  • tqdm:用于显示训练进度条,直观观察训练过程,提升开发体验(实战中常用小技巧)。

第二步:语料预处理------把原始文本变成模型能"看懂"的数据

任何NLP任务的第一步都是预处理,计算机无法直接处理字符串,必须将其转换为整数索引,这一步就是完成"原始文本→索引数据"的转换:

python 复制代码
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)
print("长度:",vocab_size)

word_to_idx = {word:i for i,word in enumerate(vocab)}
idx_to_word = {i:word for i,word in enumerate(vocab)}

解读:

  • context_size=2:设定上下文窗口大小为2,意味着每个中心词的上下文由"左边2个词+右边2个词"组成,共4个词;

  • raw_text.split():将原始文本按空格拆分,得到单词列表(英文文本最简单高效的分词方式,中文需用jieba等专门分词工具);

  • vocab=set(raw_text):对单词列表去重,构建词汇表------词汇表是所有不重复单词的集合,vocab_size就是词汇表大小,后续模型的输入输出维度都围绕它展开;

  • word_to_idx与idx_to_word:构建"单词→索引"和"索引→单词"的映射字典,这是NLP建模的基础操作------将字符串单词转换为唯一整数索引,模型才能进行后续计算。

第三步:生成训练数据------构建"上下文-中心词"样本对

CBOW模型的核心任务是"用上下文预测中心词",因此需要将预处理后的单词列表,转换为模型需要的"上下文-中心词"样本对:

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))

解读:

  • 循环范围:range(context_size, len(raw_text)-context_size)------避免越界!开头前2个词和结尾后2个词,无法满足"左右各2个上下文词"的要求,直接跳过;

  • 上下文构建:用列表推导式简洁获取上下文------左边取[i-2, i-1],右边取[i+1, i+2],拼接后得到4个词的上下文,对应中心词raw_text[i];

  • data列表:最终存储的是(上下文单词列表, 中心词)对,比如data[0]就是第一个中心词对应的上下文和目标词,这是模型的核心训练样本。

第四步:辅助函数------将上下文转换为模型可输入的张量

模型输入需要是PyTorch张量(而非列表),因此定义一个辅助函数,将上下文单词列表转换为长整型张量:

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)

解读:

  • 先将上下文单词列表,通过word_to_idx字典转换为整数索引列表;

  • 转换为torch.tensor,且dtype=torch.long------这是nn.Embedding层(词嵌入层)要求的输入类型,必须是长整型,不能用默认的浮点型;

  • 测试:print(make_context_vector(data[0][0], word_to_idx))可打印第一个上下文的索引张量,验证转换是否成功。

第五步:设备选择------优先GPU加速,提升训练效率

实战中,GPU能大幅提升训练速度,尤其是语料量较大时,这一步实现"优先使用GPU,无GPU则用CPU":前提是之前配置了cuda,我们在深度学习的初始就将讲诉了配置方法。

python 复制代码
device="cuda" if torch.cuda.is_available() else "cpu"
print(device)

解读:

  • torch.cuda.is_available():判断当前环境是否有可用的GPU;

  • 后续模型和数据都需要通过.to(device)迁移到指定设备上,保证计算在同一设备进行,避免报错。

第六步:核心环节------用PyTorch定义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.out=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.out(out)
        nll_prob=F.log_softmax(out,dim=1)
        return nll_prob

解读(重点!):

1. __init__方法(模型层定义)

  • super(CBOW, self).init():继承nn.Module(PyTorch所有神经网络模型的基类),获得框架提供的参数管理、模式切换等功能;

  • self.embeddings=nn.Embedding(vocab_size, embedding_dim):词嵌入核心层!对应我们手动实现CBOW时的W1矩阵(词汇表大小×词嵌入维度),作用是将整数索引映射为低维连续的词嵌入向量,权重会在训练中自动优化,无需手动初始化;

  • 这里我们输入的索引向量是一个数值,在embedding层内部会拆解成49维度(vocab_size)大小的独热编码,然后通过矩阵运算转化为10维度的目标词向量。这也就是词嵌入的过程,词嵌入就是指将高维的词向量(独热编码)压缩成低纬度的、用较少的维度来表示的向量(这个较少的向量维度,每一维度都可以理解成一种特征,通过不同的特征描述原来的词汇)。这就解决了独热编码,没有语义相关性,维度灾难的问题,在我们得到的低纬度词嵌入向量,是含有语义的,比如篮球和乒乓球,具有相关语义,在词嵌入向量的空间中,坐标应该比较接近。

  • self.proj=nn.Linear(embedding_dim, 128):额外添加的线性投影层(隐藏层),将词嵌入维度(后续设定为10)映射到128维------比基础CBOW多了这一步,能让模型学习更复杂的语义关联,提升词嵌入质量;

  • self.out=nn.Linear(128, vocab_size):输出层,将128维的隐藏层输出,映射回词汇表大小维度,得到每个单词的预测得分。

2. forward方法(前向传播逻辑)

  • embeds=sum(self.embeddings(inputs)).view(1,-1):CBOW的核心操作!

    • self.embeddings(inputs):将输入的4个上下文索引,转换为4个embedding_dim维的词嵌入向量(输出形状:[4, 10]);

    • sum(...):对4个词嵌入向量求和(等价于求平均,不影响概率排序,简化计算),得到1个10维向量;

    • view(1,-1):将10维向量重塑为[1, 10]的二维张量------线性层要求输入是"批次维度×特征维度",这里批次大小为1(单样本训练)。

  • out=F.relu(self.proj(embeds)):将聚合后的词嵌入输入投影层,通过ReLU激活函数引入非线性,让模型能学习更复杂的关系;

  • out=self.out(out):将隐藏层输出映射到词汇表维度,得到每个单词的原始预测得分;

  • nll_prob=F.log_softmax(out, dim=1):将原始得分转换为"概率分布的对数值",配合后续NLLLoss损失函数计算损失,避免数值溢出。

第七步:模型初始化与训练------从参数优化到模型收敛

这是PyTorch模型训练的标准流程,完整实现了"初始化→循环训练→梯度更新",还加入了进度条显示,贴合实战:

python 复制代码
model=CBOW(vocab_size,10).to(device)
optimizer=optim.Adam(model.parameters(),lr=0.001)
print(model)
losses=[]
loss_function=nn.NLLLoss()
model.train()
for epoch in tqdm(range(100)):
    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=CBOW(vocab_size,10).to(device)------词嵌入维度设定为10(实战中常用50/100),并将模型迁移到指定设备;

  • 优化器:optim.Adam------目前最常用的优化器之一,自适应调整学习率,收敛更快更稳定,lr=0.001是经典初始学习率;

  • 损失函数:nn.NLLLoss(负对数似然损失)------专门配合log_softmax使用,等价于交叉熵损失,计算预测结果与真实目标的差距;

  • model.train():将模型切换到训练模式(虽无Dropout等层,但养成习惯,后续复杂模型必备);

  • 训练循环(重点!):

    • epoch=100:训练100轮,轮数可根据损失收敛情况调整;

    • 单样本训练:遍历每个"上下文-中心词"对,将上下文向量和目标词都迁移到指定设备;

    • 前向传播:train_predict=model(context_vector),得到模型预测结果;

    • 梯度更新三步曲(缺一不可):

      • optimizer.zero_grad():清空上一轮的梯度,避免梯度累积导致参数更新异常;

      • loss.backward():反向传播,计算所有可训练参数(词嵌入层、线性层权重)的梯度;

      • optimizer.step():根据梯度,更新模型参数,让模型不断逼近最优解。

    • losses记录:每轮累计总损失,后续可通过绘制损失曲线,观察模型是否收敛(损失持续下降并平稳,说明模型在有效学习)。

第八步:模型验证与词嵌入提取------让训练成果落地

训练完成后,不是结束,而是要验证模型效果、提取词嵌入并保存,让模型产生实际价值:

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)
print(idx_to_word[max_idx.item()])

# 提取词嵌入权重
print("cbow embedding weight: ",model.embeddings.weight)
W=model.embeddings.weight.cpu().detach().numpy()

# 构建词→词向量字典
word_2_vec={}
for word in word_to_idx.keys():
    word_2_vec[word]=W[word_to_idx[word],:]
print('结束')

# 保存与加载词向量
np.savez("cbow.npz",file1=W)
data=np.load("cbow.npz")
print(data['file1'])

解读:

1. 模型预测

  • model.eval():将模型切换到评估模式,禁用训练模式下的特殊行为,保证预测结果稳定;

  • predict.argmax(1):找到预测概率最大的索引(log_softmax输出中,值越大,概率越高);

  • idx_to_word[max_idx.item()]:将索引转换为对应单词------这里上下文是["People","create","to","direct"],真实中心词是"programs",训练到位的话,模型能准确预测。

2. 词嵌入提取

  • model.embeddings.weight:这就是我们要的词嵌入矩阵!对应手动实现中的W1,形状为[vocab_size, 10],每一行是一个单词的10维词嵌入向量;

  • cpu().detach().numpy():张量转Numpy数组的标准操作:

    • detach():脱离计算图,避免后续操作影响模型梯度(评估阶段无需计算梯度);

    • cpu():将GPU上的张量迁移到CPU(Numpy不支持GPU张量);

    • numpy():转换为Numpy数组,方便后续保存和使用。

  • word_2_vec字典:构建"单词→词嵌入向量"的映射,后续使用时,可直接通过单词获取对应的词向量。

3. 词嵌入保存与加载

  • np.savez("cbow.npz", file1=W):将词嵌入矩阵保存为npz格式(Numpy压缩格式),占用空间小,方便后续复用(无需重新训练);

  • np.load("cbow.npz"):加载保存的词向量,data['file1']就是之前保存的词嵌入矩阵,可直接用于其他NLP任务。

三、运行结果预期与作业

1. 预期运行结果

运行这份代码后,你会看到这些关键输出,证明代码运行正常:

  • 首先打印词汇表大小(约50个左右)和设备类型(cuda或cpu);

  • 训练过程中,tqdm进度条推进,每轮打印总损失,损失会持续下降,最终趋于平稳;

  • 预测阶段输出一个单词(大概率是"programs"),验证模型预测效果;

  • 最后打印词嵌入矩阵和加载后的npz文件内容,形状为[vocab_size, 10],证明词向量保存、加载成功。

2、运行结果部分截图

显然我们得到了正确的预测:program

3、作业

修改代码使得:基于前面四个词预测下一个词。

相关推荐
七夜zippoe1 小时前
大模型低成本高性能演进 从GPT到DeepSeek的技术实战手记
人工智能·gpt·算法·架构·deepseek
Allen_LVyingbo1 小时前
面向70B多模态医疗大模型预训练的工程落地(医疗大模型预训练扩展包)
人工智能·python·分类·知识图谱·健康医疗·迁移学习
一方_self1 小时前
cloudflare AI gateway实战代理任意第三方大模型服务提供商
人工智能·gateway
Deng8723473481 小时前
电脑使用 Gemini出了点问题解决办法
人工智能·python
汗流浃背了吧,老弟!2 小时前
LangChain RAG PDF 问答 Demo
人工智能·深度学习
GJGCY2 小时前
技术拆解:从Manus的通用推理到金智维K-APA的受控执行,企业级AI架构如何选择?
人工智能·架构
上海合宙LuatOS2 小时前
LuatOS socket基础知识和应用开发
开发语言·人工智能·单片机·嵌入式硬件·物联网·开源·php
盖雅工场2 小时前
业务波动适配型排班,破解零售服务业人力失衡难题
大数据·人工智能
人工智能AI技术2 小时前
【Agent从入门到实践】45 与后端系统集成:Agent作为服务,嵌入业务流程
人工智能·python