目录
[一、one-hot 独热编码](#一、one-hot 独热编码)
[二、Embedding 层与词嵌入](#二、Embedding 层与词嵌入)
[三、Demo 实战:基于 PyTorch 从零实现 Word2Vec-CBOW 模型](#三、Demo 实战:基于 PyTorch 从零实现 Word2Vec-CBOW 模型)
[3、滑动窗口构建 CBOW 训练数据集](#3、滑动窗口构建 CBOW 训练数据集)
[6、CBOW 模型网络结构定义](#6、CBOW 模型网络结构定义)
摘要
文本属于符号数据,计算机无法直接读懂文字。本章从 one-hot 独热编码入手,讲解词嵌入(Embedding)的思想,再动手实现 Word2Vec 的 CBOW 模型,用 PyTorch 完成完整训练 Demo,直观理解 "用上下文预测中心词" 的词向量学习原理。
一、one-hot 独热编码
自然语言是人类的符号系统,但是计算机只认识数字。我们首先要做的,就是把单词转换成数字向量,one-hot 就是最简单的转换方案。
原理
假设词表一共有个单词,每个单词对应一个长度为
的向量。只有单词对应的下标位置为 1,其余全部为 0。我们用完整句子举一个贴合本次实战的实例:
现有简单句子:"I", "love", "NLP",去重后词表、索引、one-hot 对应关系如下:
- 词表索引:I → 0,对应 one-hot:1, 0, 0
- 词表索引:love → 1,对应 one-hot:0, 1, 0
- 词表索引:NLP → 2,对应 one-hot:0, 0, 1
整个句子通过 One-Hot 编码后,会生成一组稀疏向量序列,完整句子向量表示为:
\[1,0,0\]、\[0,1,0\]、\[0,0,1\]
简单来说,句子由多个独立的 one-hot 向量拼接组成。这种编码方式下,所有单词向量相互正交、没有任何关联,完全体现不出单词之间的语义关联,这也是 one-hot 编码最大的短板之一。
当句子扩展为 I love NLP very much 时,每个单词都会生成一个维度等于总词表数量的稀疏向量,句子越长、词表越大,向量冗余和存储开销就越夸张,这也是 one-hot 编码无法适配真实 NLP 任务的核心原因。
one-hot 的缺陷
- 向量极度稀疏,词库越大,向量维度越高,存储开销巨大;
- 词与词之间没有语义关系,任意两个单词向量的内积都是 0,无法表达 "苹果" 和 "水果" 这种相近语义。
|------------------------------------------------------|
| 所以我们需要一种更好的表达方式:词嵌入 Embedding ,也就是 Word2Vec 的核心。 |
二、Embedding 层与词嵌入
词嵌入(Word Embedding),简单来说:把高维稀疏的 one-hot 向量,映射成一个低维、稠密的实数向量。语义相近的单词,它们的向量在空间上距离也更近。
词向量的映射本质就是**稀疏向量与嵌入矩阵的矩阵乘法运算**,我们结合前文的句子案例,直观演示整个映射过程:
继续沿用前文句子词表 "I", "love", "NLP",总词表大小。我们设定 Embedding 希望输出的稠密向量维度为 2,也就是把原本 3 维的稀疏 One-Hot 向量,压缩映射为 2 维语义向量。
模型内部会初始化一个可训练的嵌入矩阵,矩阵形状为:。
假设初始嵌入矩阵如下:
以单词 I 为例,其 One-Hot 向量为 1,0,0,两者进行矩阵相乘:
最终得到 I 对应的2维稠密词向量 。同理,love、NLP 也会通过矩阵运算,得到各自专属的低维向量。
整个过程的核心价值:抛弃无效的稀疏结构,用少量浮点参数承载单词特征。随着 CBOW 模型不断训练、反向传播更新嵌入矩阵权重,语义相近的单词,其向量数值会越来越接近,最终实现「向量空间表征语义」的效果。
Word2Vec 有两种经典训练架构:
- CBOW(Continuous Bag-of-Words):用上下文单词,预测中心目标词(本次代码实现的模型)
- Skip-Gram:用中心单词,预测周围上下文
以完整句子 People create programs to direct processes 为例,设定上下文窗口大小为2,选取中心词 programs 演示两种 Word2Vec 训练逻辑:
1. CBOW(本文实现模型)
输入左右上下文:create, to,模型通过上下文语义特征,预测输出中心词:programs。核心逻辑:上下文 → 中心词。
2. Skip-Gram
输入中心词:programs,模型反向推理、预测周边的上下文单词:create, to。核心逻辑:中心词 → 上下文。
nn.Embedding本质就是一个可训练的权重矩阵:矩阵行数 = 词表大小,列数 = 词向量维度。输入单词索引,直接取出对应向量。这个矩阵,就是我们最后学习出来的词向量。
CBOW 思路:
给定上下文窗口,拿窗口内的上下文词,输入网络,预测中间的中心词。在训练的过程中不断更新 Embedding 权重,最终学到每个单词的向量表示。
三、Demo 实战:基于 PyTorch 从零实现 Word2Vec-CBOW 模型
实战任务说明
本次实战基于一段英文短句语料,完成 Word2Vec-CBOW 模型完整训练。核心任务:设定固定上下文窗口,利用目标词的左右周边单词,推理、预测中心核心单词,从而通过自监督任务学习到稳定、具备语义信息的词向量。
任务核心思路:
1、CBOW 无监督学习:不靠人工标签,依靠「上下文→中心词」的预测任务自动学习语义;
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 # 进度条展示
import numpy as np
# 超参数:上下文窗口大小(左右各取2个词,总上下文4个词)
CONTEXT_SIZE = 2
导入模型训练所需的深度学习、优化、工具库,定义核心超参数 CONTEXT_SIZE=2,代表每个中心词选取左右各2个单词作为上下文,是 CBOW 模型的窗口核心配置。
2、加载语料并构建词表与索引映射
python
# 原始训练语料
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 = list(vocab)
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)}
加载并切分原始文本,通过集合去重得到唯一词表,建立「单词→索引、索引→单词」双向映射。神经网络无法识别字符串,只能通过数字索引查找对应单词,是文本建模的前置基础。
3、滑动窗口构建 CBOW 训练数据集
python
data = []
# 遍历文本,舍弃首尾不足窗口的位置
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
# 拼接左右两侧上下文单词
context = (
[raw_text[i - (2-j)] for j in range(2)]
+ [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]
)
target = raw_text[i] # 中心目标词
data.append((context, target))
采用滑动窗口遍历语料,规避文本首尾无完整上下文的无效位置。每一组数据由「四周上下文 + 中心目标词」组成,严格贴合 CBOW「上下文预测中心词」的自监督训练任务。
4、工具函数:文本转模型索引张量
python
def make_context_vector(context, word_to_ix):
# 单词列表 --> 索引列表
idxs = [word_to_ix[w] for w in context]
# 转为模型可识别的LongTensor
return torch.tensor(idxs, dtype=torch.long)
# 测试输出样例
print(make_context_vector(data[0][0], word_to_idx))
通用数据转换函数,将字符串格式的上下文单词批量转为整型索引张量,适配 PyTorch 模型输入格式,同时通过打印样例验证数据转换正常。
5、自动训练设备适配
python
# 优先cuda,其次mps(苹果芯片),最后cpu
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
自动识别本机硬件设备,优先使用 NVIDIA GPU 加速训练,兼容苹果 M 系列芯片,无加速设备时默认 CPU,适配多平台运行。
6、CBOW 模型网络结构定义
python
class CBOW(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super().__init__()
# 词嵌入层:查表得到词向量
self.embedding = nn.Embedding(vocab_size, embedding_dim)
# 特征投影层 & 输出预测层
self.proj = nn.Linear(embedding_dim, 128)
self.output = nn.Linear(128, vocab_size)
def forward(self, inputs):
# 上下文词向量取平均,融合全局语义
embeds = self.embedding(inputs).mean(dim=0).view(1, -1)
# 特征变换
out = F.relu(self.proj(embeds))
# 预测输出 + 对数softmax
out = self.output(out)
nll_prob = F.log_softmax(out, dim=-1)
return nll_prob
搭建极简 CBOW 网络,核心分为三层。嵌入层负责生成低维稠密词向量,对上下文向量求均值融合语义,再通过全连接层特征变换,最后输出词表维度的对数概率分布,适配后续损失计算。
7、模型、优化器与损失函数初始化
python
# 词向量维度设置为10
model = CBOW(vocab_size, embedding_dim=10).to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)
loss_function = nn.NLLLoss()
losses = []
初始化 CBOW 模型并迁移至对应设备,设置 Adam 优化器更新网络参数,选用 NLLLoss 对数似然损失,匹配模型末端的 log_softmax 输出,同时创建列表保存每轮损失值。
8、模型迭代训练流程
python
model.train()
# 训练100轮
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(f"epoch {epoch}, total_loss: {total_loss}")
开启训练模式,迭代100轮完整训练。逐样本执行前向预测、损失计算、梯度清零、反向传播、参数更新全套流程,累加每轮总损失,直观监控模型收敛状态。
9、模型推理测试
python
# 自定义测试上下文
context = ['People','create','to','direct']
context_vector = make_context_vector(context, word_to_idx).to(device)
# 评估模式,关闭梯度
model.eval()
with torch.no_grad():
predict = model(context_vector)
# 取概率最大的词作为预测结果
max_idx = predict.argmax(dim=1).item()
print("预测中心词:", idx_to_word[int(max_idx)])
10、提取并保存训练好的词向量
python
# 提取训练完成的嵌入权重,转为numpy格式并保存
W = model.embedding.weight.cpu().detach().numpy()
# 保存词向量与词表映射,方便后续复用、可视化与下游任务
np.savez('word2vec_cbow_embedding.npz',
embedding=W,
word_to_idx=word_to_idx,
idx_to_word=idx_to_word)
print("词向量权重已成功保存!")
模型训练结束后,Embedding 层权重就是我们最终学到的词向量矩阵。本段代码将网络权重从计算图中剥离、转为 CPU 数据并转换为 Numpy 格式,同时配套保存词表映射关系,方便后续进行词向量相似度计算、降维可视化、迁移至其他NLP任务使用。
四、总结
- one-hot 编码解决文字数字化,但稀疏、无语义;词嵌入 Embedding,把单词映射到低维稠密向量,捕捉语义信息;
- Word2Vec 的 CBOW 核心思想:上下文预测中心词,通过这个预测任务,自动学习词向量;
- 词向量不是人工定义,是模型在预测任务中自动学到;语义相近的词,向量空间距离更近;
- 本次 Demo 属于极简教学版本,仅使用少量语料完成基础训练;在真实工业项目中,通常会采用大规模海量语料,并结合负采样、分层softmax等优化策略,进一步提升词向量的语义表征精度与泛化能力。