自然语言处理CBOW模型:基于上下文预测中间词

今天我们进行自然语言处理(NLP)的学习。基于上下文预测中间词的CBOW模型------它是Word2Vec的核心模型之一,也是理解"词嵌入"思想的关键。博文分为三个部分:名词补充介绍,CBOW原理讲解,代码实现CBOW。

一、补充知识:筑牢CBOW学习基础

在学习CBOW模型前,我们先理清几个核心名词的含义,这些都是今日学习中高频接触的关键概念,理解它们才能更好地掌握模型逻辑。

1. Word2Vec

Word2Vec是2013年由Google提出的一套"词向量生成工具",核心目标是将自然语言中的单词,转换为计算机能理解的低维稠密向量(即词嵌入向量)。它包含两个核心模型:

  • CBOW(Continuous Bag-of-Words):连续词袋模型,核心逻辑是"通过上下文单词预测中心词"(也是我们今日重点学习的模型);

  • Skip-gram:跳字模型,逻辑与CBOW相反,是"通过中心词预测上下文单词"。

Word2Vec的核心价值是解决了传统One-Hot向量的"维度灾难"问题,同时让生成的词向量蕴含语义信息(比如"苹果"和"香蕉"的向量相似度高于"苹果"和"汽车")。

2. 词嵌入(Embedding)与Embedding_dim

Embedding层是深度学习中将离散型数据(如单词、类别)转换为连续型向量表示的核心组件。它可以将高维的稀疏表示(如one-hot编码)转换为低维的稠密向量。Embedding_dim表示词嵌入的维度。

3. One-Hot向量(独热编码)

One-Hot向量是早期表示单词的方式,核心是"用词汇表大小作为向量长度,某个单词对应位置记为1,其余为0"。比如词汇表为[We, are, about, to, study],则"are"的One-Hot向量是[0,1,0,0,0]。

缺点很明显:词汇表越大,向量维度越高(比如10万词对应10万维),存在"维度灾难";且向量间是正交关系,无法体现语义关联(比如"苹果"和"香蕉"的相似度为0)。

4. 索引张量(Index Tensor)

CBOW模型无法直接处理单词,需要先将单词转换为数字索引(比如通过word_to_idx字典将"We"映射为0),再将索引组成的列表转换为PyTorch的张量------这种存储单词索引的张量就是"索引张量"。

关键要求:PyTorch的nn.Embedding层(词嵌入层)要求输入的索引张量必须是torch.long(长整型)类型,否则会报错。

5. Log-Softmax与NLLLoss

这是CBOW模型计算损失的核心组合,等价于交叉熵损失(nn.CrossEntropyLoss):

  • Log-Softmax:先通过Softmax将模型输出的原始得分(无范围限制)归一化为0~1的概率分布,再对概率取自然对数,避免单独计算Softmax时的数值溢出;

  • NLLLoss(负对数似然损失):找到目标词索引对应的对数概率,取其负值作为损失------损失越小,说明模型预测目标词的概率越高。

二. CBOW 词嵌入模型原理(核心是 "用上下文预测目标词")

1、模型流程:

训练流程可简化为:① 输入:上下文词的 One-Hot 编码;② 映射:通过V×N矩阵将 One-Hot 转为低维词嵌入向量;③ 聚合:平均多个上下文的词嵌入,得到上下文整体特征;④ 映射回:通过N×V矩阵将低维特征转回词汇表维度;⑤ 预测:Softmax 归一化得概率,选最大概率对应词为预测结果;⑥ 优化:对比真实标签算损失,反向传播调整矩阵参数,提升预测精度。

2、 核心设计逻辑(为什么要映射低维)
  • 降成本:把V×V级别的参数压缩为V×N+N×V(N 远小于 V),降低计算 / 存储压力;
  • 捕语义:让语义相近的词在低维空间中向量更接近;
  • 提泛化:让模型学到词的共性特征,能理解未见过的上下文组合。

三、项目案例:基于CBOW实现语料库预测中间词

1、数据准备:构建训练数据与词汇表

CBOW模型的训练数据是"上下文-中心词"对(),我们先从原始文本中提取这些数据,并构建词汇表和单词-索引映射。

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import numpy as np
# 原始文本(可根据需求替换)
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."""

# 文本分词(按空格分割,简单处理)
raw_text = raw_text.replace(".", "").split()

# 上下文窗口大小:中心词前后各2个词
CONTEXT_SIZE = 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, word in enumerate(vocab)}

遍历原始文本,提取每个中心词对应的上下文,组成训练样本:

python 复制代码
data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
    # 上下文:中心词前后各2个词
    context = [
        raw_text[i - CONTEXT_SIZE], raw_text[i - CONTEXT_SIZE + 1],
        raw_text[i + 1], raw_text[i + 2]
    ]
    # 中心词(目标词)
    target = raw_text[i]
    data.append((context, target))

# 查看前3个训练样本
print("训练样本示例:", data[:3])

定义函数,将单词列表形式的上下文,转为模型所需的torch.long类型索引张量:

python 复制代码
def make_context_vector(context, word_to_idx):
    # 将上下文单词转为索引列表
    idxs = [word_to_idx[word] for word in context]
    # 转为long类型张量(nn.Embedding要求输入为long型)
    return torch.tensor(idxs, dtype=torch.long)
2、定义CBOW模型

CBOW模型结构分为三层:词嵌入层(Embedding)、中间特征层(Linear+ReLU)、输出层(Linear),核心逻辑是"上下文嵌入聚合→特征变换→预测中心词"。

python 复制代码
class CBOW(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(CBOW, self).__init__()
        # 1. 词嵌入层:将索引转为低维嵌入向量
        # 输入维度:vocab_size(词汇表大小),输出维度:embedding_dim(词向量维度)
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        
        # 2. 中间特征层(proj层):增强特征表达能力
        # 输入维度:embedding_dim,输出维度:128(超参数,可调整)
        self.proj = nn.Linear(embedding_dim, out_features=128)
        
        # 3. 输出层:将特征映射到词汇表维度,预测每个单词是中心词的得分
        # 输入维度:128,输出维度:vocab_size
        self.output = nn.Linear(128, vocab_size)

    def forward(self, inputs):
        # 步骤1:获取上下文单词的词嵌入(inputs是索引张量,形状:[4])
        embeds = self.embeddings(inputs)  # 输出形状:[4, embedding_dim]
        
        # 步骤2:聚合上下文嵌入(求和,合并为一个代表上下文语义的向量)
        embeds_sum = sum(embeds).view(1, -1)  # 输出形状:[1, embedding_dim](适配后续层输入)
        
        # 步骤3:特征变换+非线性激活(ReLU引入非线性,拟合复杂语义)
        proj_out = F.relu(self.proj(embeds_sum))  # 输出形状:[1, 128]
        
        # 步骤4:输出预测得分,转为对数概率(供损失计算)
        output = self.output(proj_out)  # 输出形状:[1, vocab_size]
        return F.log_softmax(output, dim=-1)  # dim=-1:在词汇表维度做归一化

词嵌入层操作解释:

CBOW 的逻辑是 "用上下文预测中心词",因此需要把多个上下文单词的嵌入向量合并成一个 "整体特征"------ 求和是最简单的聚合方式(也可以用mean()平均,效果几乎一致,只是缩放系数不同,模型会通过权重补偿)。

sum vs mean :求和(sum)和求平均(mean)都是常见的聚合方式,比如把代码改成torch.mean(self.embeddings(inputs), dim=0).view(1, -1),逻辑完全等价 ------ 求平均只是把求和结果除以单词数,模型训练时会自动调整权重,最终效果无差异。

3、模型训练

配置训练参数,通过"前向传播→计算损失→反向更新"的循环,让模型学习上下文与中心词的关联规律。

初始化模型、损失函数与优化器:

python 复制代码
# 超参数设置
embedding_dim = 10  # 词向量维度(小语料选10即可)
learning_rate = 0.001  # 学习率
epochs = 500  # 训练轮数

# 初始化模型(传入词汇表大小和词向量维度)
model = CBOW(vocab_size, embedding_dim)

# 损失函数:NLLLoss(配合log_softmax使用)
loss_fn = nn.NLLLoss()

# 优化器:Adam(自适应学习率,训练更稳定)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

循环训练:

python 复制代码
for epoch in range(epochs):
    total_loss = 0.0
    # 遍历所有训练样本
    for context, target in data:
        # 1. 准备输入和标签:上下文转索引张量,目标词转索引
        context_tensor = make_context_vector(context, word_to_idx)
        target_idx = torch.tensor([word_to_idx[target]], dtype=torch.long)
        
        # 2. 前向传播:模型预测
        log_prob = model(context_tensor)
        
        # 3. 计算损失
        loss = loss_fn(log_prob, target_idx)
        
        # 4. 反向传播+参数更新
        optimizer.zero_grad()  # 清空上一轮梯度
        loss.backward()  # 反向计算梯度
        optimizer.step()  # 更新模型参数
        
        # 累加总损失
        total_loss += loss.item()
    
    # 每100轮打印一次平均损失(查看训练进度)
    if (epoch + 1) % 100 == 0:
        avg_loss = total_loss / len(data)
        print(f"Epoch [{epoch+1}/{epochs}], Average Loss: {avg_loss:.4f}")
4、输入上下文进行预测接口
python 复制代码
def predict_center_word():
    # 接收用户输入的4个上下文单词(空格分隔)
    user_input = input("请输入4个上下文单词(空格分隔):")
    context = user_input.split()
    
    # 转换为索引张量
    context_tensor = make_context_vector(context, word_to_idx)
    
    # 模型切换到预测模式(关闭梯度追踪,节省资源)
    model.eval()
    with torch.no_grad():
        # 预测对数概率
        predict_log_prob = model(context_tensor)
    
    # 取概率最大的索引(即预测的中心词索引)
    # 注意:预测索引是张量,需用.item()转为Python整数
    pred_idx = predict_log_prob.argmax(1).item()
    
    # 索引转单词,输出结果
    print(f"输入的上下文:{context}")
    print(f"预测的中心词:{idx_to_word[pred_idx]}")

# 调用预测函数
predict_center_word()
5、完整代码运行结果:
相关推荐
摸鱼仙人~1 天前
中国内需市场的战略重构与潜在增长点深度研究报告
大数据·人工智能
jimmyleeee1 天前
人工智能基础知识笔记三十二:向量数据库的查找类型和工作原理
人工智能·笔记
像风一样自由20201 天前
MCP 入门指南:让 AI 连接真实世界
人工智能
尚可签1 天前
怎么降低AI率(文本)?最近发现了非常简单的思路
人工智能
咕噜企业分发小米1 天前
阿里云AI教育产品如何助力企业提升客户粘性?
人工智能·microsoft·阿里云
华如锦1 天前
四:从零搭建一个RAG
java·开发语言·人工智能·python·机器学习·spring cloud·计算机视觉
F_D_Z1 天前
TensorFlow Playground 交互式神经网络可视化工具
人工智能·神经网络·tensorflow
sonadorje1 天前
梯度下降法的迭代步骤
算法·机器学习
杭州泽沃电子科技有限公司1 天前
核电的“热血管”与它的智能脉搏:热转换在线监测如何守护能源生命线
人工智能·在线监测