回顾: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、作业

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

相关推荐
九狼13 分钟前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS21 分钟前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区2 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈2 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang2 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx
shengjk13 小时前
NanoClaw 深度剖析:一个"AI 原生"架构的个人助手是如何运转的?
人工智能
西门老铁5 小时前
🦞OpenClaw 让 MacMini 脱销了,而我拿出了6年陈的安卓机
人工智能
恋猫de小郭6 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
是一碗螺丝粉6 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain