基于PyTorch的CBOW模型实现与词向量生成

文章目录

  • [一. CBOW模型详解](#一. CBOW模型详解)
    • [1.1 Word2Vec与分布式表示](#1.1 Word2Vec与分布式表示)
    • [1.2 CBOW模型原理](#1.2 CBOW模型原理)
    • [1.3 网络架构详解](#1.3 网络架构详解)
    • [1.4 训练目标与优化](#1.4 训练目标与优化)
    • [1.5 CBOW 与 Skip-gram 比较](#1.5 CBOW 与 Skip-gram 比较)
    • [1.6 词向量的应用与提取](#1.6 词向量的应用与提取)
  • [二. 数据准备与预处理](#二. 数据准备与预处理)
    • [2.1 语料库与基本参数设置](#2.1 语料库与基本参数设置)
    • [2.2 构建词汇表](#2.2 构建词汇表)
    • [2.3 构建训练数据集](#2.3 构建训练数据集)
    • [2.4 上下文向量化函数](#2.4 上下文向量化函数)
  • [三. CBOW模型构建](#三. CBOW模型构建)
    • [3.1 模型类定义](#3.1 模型类定义)
  • [四. 模型训练](#四. 模型训练)
    • [4.1 设备配置与模型初始化](#4.1 设备配置与模型初始化)
    • [4.2 训练循环](#4.2 训练循环)
  • [五. 模型测试与词向量提取](#五. 模型测试与词向量提取)
    • [5.1 模型测试](#5.1 模型测试)
    • [5.2 词向量提取与保存](#5.2 词向量提取与保存)

一. CBOW模型详解

1.1 Word2Vec与分布式表示

Word2Vec是Google在2013年提出的一种高效学习词向量的模型,它包含两种主要架构:连续词袋模型(CBOW)和跳字模型(Skip-gram)。这两种模型都基于一个核心思想:"一个词的含义可以通过它周围出现的词来定义",这被称为分布式假设。

在代码实现中,我们通过构建(context, target)训练对来体现这一思想:

python 复制代码
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))  # 构建训练样本

与传统one-hot编码相比,Word2Vec生成的词向量具有以下优势:

  • 低维稠密 :代码中设置embedding_dim=10,将49维的稀疏one-hot压缩为10维稠密向量
  • 语义信息:相似的词在向量空间中距离较近
  • 数学运算:支持向量运算,如"国王 - 男人 + 女人 ≈ 女王"

1.2 CBOW模型原理

CBOW模型的核心思想是通过上下文词汇预测中心词。具体来说:

输入 :上下文窗口中所有词的one-hot表示(或索引)
输出 :中心词的概率分布
目标:最大化正确中心词的对数似然

数学表达

给定上下文词序列 C = { w t − 2 , w t − 1 , w t + 1 , w t + 2 } C = \{w_{t-2}, w_{t-1}, w_{t+1}, w_{t+2}\} C={wt−2,wt−1,wt+1,wt+2},CBOW模型试图预测中心词 w t w_t wt:

P ( w t ∣ C ) = exp ⁡ ( v w t T ⋅ v ˉ C ) ∑ w ∈ V exp ⁡ ( v w T ⋅ v ˉ C ) P(w_t | C) = \frac{\exp(v_{w_t}^T \cdot \bar{v}C)}{\sum{w \in V} \exp(v_w^T \cdot \bar{v}_C)} P(wt∣C)=∑w∈Vexp(vwT⋅vˉC)exp(vwtT⋅vˉC)

其中:

  • v ˉ C \bar{v}_C vˉC 是上下文词向量的平均(或求和)
  • v w v_{w} vw是词 w w w 的输出向量
  • V V V是词汇表

在代码中,这一原理体现在CBOW模型的forward方法:

python 复制代码
def forward(self, inputs):
    embeds = sum(self.embeddings(inputs)).view(1, -1)  # 对上下文词向量求和
    out = F.relu(self.proj(embeds))
    out = self.output(out)
    nl_l_prob = F.log_softmax(out, dim=-1)  # 计算对数概率
    return nl_l_prob

1.3 网络架构详解

CBOW模型通常包含以下三层:

  1. 输入层 :上下文词的索引表示,代码中使用make_context_vector函数转换
  2. 投影层(隐藏层):共享的嵌入矩阵,将索引映射为稠密词向量
  3. 输出层:线性层将隐藏层表示映射回词汇表空间

代码中的网络层说明:

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.output = nn.Linear(128, vocab_size)  # 输出层

1.4 训练目标与优化

CBOW的训练目标是最大化给定上下文时正确中心词的条件概率:

L = ∑ t = 1 T log ⁡ P ( w t ∣ w t − m , . . . , w t − 1 , w t + 1 , . . . , w t + m ) \mathcal{L} = \sum_{t=1}^{T} \log P(w_t | w_{t-m}, ..., w_{t-1}, w_{t+1}, ..., w_{t+m}) L=∑t=1TlogP(wt∣wt−m,...,wt−1,wt+1,...,wt+m)

在代码中,我们使用负对数似然损失(NLLLoss):

python 复制代码
loss_function = nn.NLLLoss()  # 负对数似然损失

# 在训练循环中
train_predict = model(context_vector)
loss = loss_function(train_predict, target)  # 计算损失

训练过程采用反向传播和Adam优化器:

python 复制代码
optimizer = optim.Adam(model.parameters(), lr=0.001)  # Adam优化器

# 反向传播
optimizer.zero_grad()  # 梯度清零
loss.backward()  # 反向传播
optimizer.step()  # 参数更新

1.5 CBOW 与 Skip-gram 比较

特性 CBOW Skip-gram 代码体现
训练速度 更快 较慢 forward中求和操作计算效率高
输入输出 多对一 一对多 多个上下文词预测一个中心词
上下文利用 求和/平均 独立处理 使用sum()聚合上下文信息

1.6 词向量的应用与提取

训练完成后,可以提取词向量用于各种NLP任务。代码中通过以下方式提取和保存词向量:

python 复制代码
# 获取Embedding层的权重矩阵
W = model.embeddings.weight.cpu().detach().numpy()

# 构建词向量字典
word_2vec = {}
for word in word_to_idx.keys():
    word_2vec[word] = W[word_to_idx[word], :]

# 保存词向量
np.savez(r'word2vec实现.npz', file_1=W)

这些词向量可以应用于:

  1. 语义相似度计算:通过余弦相似度比较词向量
  2. 文本分类:作为特征输入分类器
  3. 命名实体识别:提供上下文语义信息

二. 数据准备与预处理

2.1 语料库与基本参数设置

首先,需要定义上下文窗口的大小并准备训练语料:

python 复制代码
import torch
import torch.nn as nn  # 神经网络
import torch.nn.functional as F
import torch.optim as optim  #
from tqdm import tqdm, trange  # 显示进度条
import numpy as np

# 设置词左边和右边选择的个数(即上下文词汇个数)
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()
# 中文的语句,你可以选择分词,也可以选择分字

代码分析

  • CONTEXT_SIZE = 2:定义上下文窗口大小为2,即每个中心词考虑左右各2个上下文词
  • raw_text:示例英文语料库,使用.split()方法按空格分词
  • 注释提示对于中文语料,可以选择分词或分字处理

2.2 构建词汇表

python 复制代码
vocab = set(raw_text)  # 集合、词库,里面内容去重
vocab_size = len(vocab)

word_to_idx = {word: i for i, word in enumerate(vocab)}  # for循环的复合写法,第1次循环,i得到的索引号,word 第1个单词
idx_to_word = {i: word for i, word in enumerate(vocab)}

代码分析

  • vocab = set(raw_text):创建词汇集合,自动去除重复单词
  • vocab_size = len(vocab):获取词汇表大小
  • word_to_idx:创建单词到索引的映射字典
  • idx_to_word:创建索引到单词的映射字典,用于后续的反向查找

2.3 构建训练数据集

python 复制代码
data = []
# 获取上下文词,将上下文词作为输入,目标词作为输出。构建训练数据集。
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):  # (2, 60)
    context = (
            [raw_text[i - (2 - j)] for j in range(CONTEXT_SIZE)]
            + [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]
    )  # 获取上下文词 (['we', 'are', 'to', 'study'])
    target = raw_text[i]  # 获取目标词'about'
    data.append((context, target))  # 将上下文词和目标词保存到data中[((['we', 'are', 'to', 'study']), 'about']

代码分析

  • 循环从第3个词开始到倒数第3个词结束(索引从0开始),确保每个中心词都有完整的上下文
  • context列表推导式:前部分获取左侧上下文词,后部分获取右侧上下文词
  • target:当前中心词
  • 最终data列表包含多个(context, target)元组

2.4 上下文向量化函数

python 复制代码
def make_context_vector(context, word_to_ix):  # 将上下文词转换为one-hot
    idxs = [word_to_ix[w] for w in context]
    return torch.tensor(idxs, dtype=torch.long)  # 强制类型的转换,将列表

print(make_context_vector(data[0][0], word_to_idx))  # 示例

代码分析

  • make_context_vector函数:将单词列表转换为对应的索引列表
  • 返回PyTorch张量,数据类型为torch.long,适合作为Embedding层的输入
  • 打印示例:展示如何将上下文词转换为索引张量

三. CBOW模型构建

3.1 模型类定义

python 复制代码
class CBOW(nn.Module):  # 神经网络
    def __init__(self, vocab_size, embedding_dim):
        super(CBOW, self).__init__()  # 父类的初始化
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)  # vocab_size:词嵌入的one-hot大小,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)  # cnn
        out = F.relu(self.proj(embeds))  # nn.relu()激活层
        out = self.output(out)
        nl_l_prob = F.log_softmax(out, dim=-1)  # softmax交叉熵。
        return nl_l_prob

代码分析

初始化方法__init__参数

  • vocab_size:词汇表大小,即one-hot向量的维度
  • embedding_dim:词嵌入的维度,将高维one-hot向量压缩到此维度

网络层说明

  • self.embeddings:Embedding层,将单词索引映射为稠密向量
  • self.proj:线性投影层,将词向量维度从embedding_dim映射到128维
  • self.output:输出层,将128维特征映射回词汇表大小维度

前向传播forward方法

  • inputs:上下文词的索引张量
  • sum(self.embeddings(inputs)):对上下文词的词向量求和,体现CBOW的核心思想
  • .view(1, -1):调整张量形状为[1, embedding_dim]
  • F.relu:ReLU激活函数,引入非线性
  • F.log_softmax:log_softmax函数,计算对数概率,与NLLLoss配合使用

四. 模型训练

4.1 设备配置与模型初始化

python 复制代码
# 模型在cuda训练
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(device)
model = CBOW(vocab_size, embedding_dim=10).to(device)  # 语料库中一共有49个单词,[000000...1]49->[ ... ]300

optimizer = optim.Adam(model.parameters(), lr=0.001)  # 优化器

代码分析

  • device:自动检测可用设备(CUDA、MPS或CPU)
  • model:创建CBOW模型实例,词嵌入维度设为10
  • optimizer:使用Adam优化器,学习率为0.001

4.2 训练循环

python 复制代码
losses = []  # 存储损失的集合
loss_function = nn.NLLLoss()  # NLLLoss损失函数(当分类列表非常多的情况),将多个类别分别分成0、1两个类别。这里和log_softmax合在一起就是一个

model.train()  # 不代表开始训练,模型具备训练的能力,设置一个可写的权限??
for epoch in tqdm(range(200)):  # 开始训练
    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)  # 可以不写forward,torch的内置功能,
        loss = loss_function(train_predict, target)  # 计算真实值和预测值之间的差距
        # 反向传播
        optimizer.zero_grad()  # 梯度值清零
        loss.backward()  # 反向传播计算得到每个参数的梯度值
        optimizer.step()  # 根据梯度更新网络参数

        total_loss += loss.item()
    losses.append(total_loss)
    print(losses)

代码分析

  • losses:记录每个epoch的总损失
  • loss_function:负对数似然损失,与log_softmax配合使用
  • model.train():设置模型为训练模式,启用dropout和batch normalization
  • tqdm(range(200)):使用tqdm包装循环,显示训练进度条
  • 内部循环:遍历所有训练样本
  • make_context_vector:将上下文转换为索引张量
  • torch.tensor([word_to_idx[target]]):将目标词转换为索引张量
  • model(context_vector):前向传播获取预测值
  • loss.backward():反向传播计算梯度
  • optimizer.step():更新模型参数

五. 模型测试与词向量提取

5.1 模型测试

python 复制代码
# 测试
# context = ['People', 'create', 'to', 'direct']  # People create programs to direct
context = ["spirits", "of", "the", "computer"]  # spirits of the computer
context_vector = make_context_vector(context, word_to_idx).to(device)
# 预测的值
model.eval()  # 进入到测试模式
predict = model(context_vector)
max_idx = predict.argmax(1)  # dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号

代码分析

  • model.eval():设置模型为评估模式,禁用dropout和batch normalization
  • predict.argmax(1):获取预测概率最大值的索引,即预测的中心词

5.2 词向量提取与保存

python 复制代码
# 获取词向量,这个Embedding就是我们需要的问题。他只是一个模型的一个中间过程
print("CBOW embedding weight=", model.embeddings.weight)  # GPU
W = model.embeddings.weight.cpu().detach().numpy()  # .detach(); 这个方法会创建一个新的Tensor,它和原来的Tensor共享数据,但是不会追踪梯度。
# 这意味着这个新的Tensor不会参与梯度的反向传播,这对于防止在计算梯度时意外修改某些参数很有用。

print(W)
# 生成词嵌入字典,即{单词1:词向量1,单词2:词向量2...}的格式
word_2vec = {}

for word in word_to_idx.keys():
    # 词向量矩阵中某个词的索引所对应的那一列即为所该词的词向量
    word_2vec[word] = W[word_to_idx[word], :]

print("end")

# 保存训练后的词向量为npz文件'''numpy W 处理矩阵的速度非常快,方便后期其他人项目,要继续使用
np.savez(r'word2vec实现.npz', file_1=W)
data = np.load(r'word2vec实现.npz')
print(data.files)

代码分析

  • model.embeddings.weight:获取Embedding层的权重矩阵,即所有词的词向量
  • .cpu().detach().numpy():将张量转移到CPU,脱离计算图,转换为numpy数组
  • word_2vec字典:构建{单词: 词向量}的映射关系
  • np.savez:将词向量矩阵保存为npz格式,便于后续加载和使用
相关推荐
王夏奇17 小时前
python在汽车电子行业中的应用1-基础知识概念
开发语言·python·汽车
He_Donglin17 小时前
Python图书爬虫
开发语言·爬虫·python
天远Date Lab17 小时前
Python金融风控实战:集成天远多头借贷行业风险版API实现共债预警
大数据·python
Python极客之家17 小时前
基于深度学习的刑事案件智能分类系统
人工智能·python·深度学习·机器学习·数据挖掘·毕业设计·情感分析
Arvin_Zhang201617 小时前
使用python实现从PDF格式的control mapping获取gross die数量
python·pdf
CodeCraft Studio18 小时前
国产化PDF处理控件Spire.PDF教程:使用Python批量自动化将PDF转换为黑白(灰度)
python·pdf·自动化·spire.pdf·文档自动化·pdf开发组件·国产化文档组件
一棵开花的树,枝芽无限靠近你18 小时前
【Pytorch】(一)使用 PyTorch 进行深度学习:60 分钟速成
人工智能·pytorch·深度学习
web3.088899918 小时前
1688商品详情API接口深度解析
开发语言·python
幻云201018 小时前
Python机器学习:从入门到资深
人工智能·python