首页 / PyTorch 入门教程 / NLP 入门:文本张量化与词嵌入

PyTorch 入门教程

NLP 入门:文本张量化与词嵌入

本教程共 60 篇 · 第 41 篇 · 更新于 2026-08-17 · 约 4 分钟阅读

PyTorchNLP词嵌入nn.Embeddingone-hot预训练词向量文本张量化

本节目标:搞懂文本是怎么变成张量的,理解词嵌入(Word Embedding)解决了 one-hot 编码的什么问题,并会用 nn.Embedding 完成词到向量的转换。

前面聊的输入都是图像,像素天生是数字。文本不一样。计算机不认识「你好」,只认数字。自然语言处理(Natural Language Processing,NLP)的第一步,就是把文字变成张量。

从词到索引

最直接的办法:给每个词发一个编号。先统计语料里出现过的所有词,做成词表(Vocabulary),每个词对应唯一的整数 ID。

比如词表是 {“我”: 0, “爱”: 1, “PyTorch”: 2},「我爱 PyTorch」就变成了 [0, 1, 2]。切分文本的过程叫分词(Tokenization),分出来的单元叫词元(Token)。

看着简单,这步却是所有 NLP 模型的起点。词表大小一般用 V 表示,后面处处要用。

中文分词比英文麻烦:词与词之间没有空格。实际项目常用 jieba 这类工具分词,英文按空格切就行。本章示例都用英文,先讲清原理。

one-hot:能用,但很浪费

有了索引,最直白的编码是 one-hot 编码(独热编码):每个词用一个 V 维向量表示,自己那一维是 1,其余全是 0。

比如「我」是 [1, 0, 0],「爱」是 [0, 1, 0]。能用,但两个毛病很明显:

  1. 太占空间。词表一万个词,每个词就是一万维向量,大部分位置是 0。
  2. 词与词毫无关联。任意两个词的相似度都是 0,「猫」和「狗」与「猫」和「汽车」没区别。

可语义上「猫」和「狗」明明更接近。one-hot 把这层信息全丢了。

词嵌入:把语义装进向量

词嵌入(Word Embedding)换个思路:用低维稠密向量表示每个词,数值通过训练学出来,语义相近的词在向量空间里距离更近。依据是分布式假设(Distributional Hypothesis):出现在相似上下文里的词,语义也相似。

打个比方:one-hot 是每人发一个随机编号,编号之间没含义;词嵌入是每人填一张性格表,「开朗」维度上外向的人分数都高。看表上的数字,就能比较远近。

远近怎么量?常用余弦相似度(Cosine Similarity):两个向量夹角的余弦,越接近 1 越相似。经典例子「国王 - 男人 + 女人 ≈ 女王」就是这种语义运算的直观展示。

嵌入矩阵的形状是 (V, D):V 是词表大小,D 是嵌入维度。每个词 ID 查一行,拿到自己的向量。词嵌入是模型参数,跟着训练自动调整,不用人设计。

用 nn.Embedding 查表

PyTorch 把「词表 + 查表」封装成 nn.Embedding 层:

import torch
import torch.nn as nn

word_to_ix = {"hello": 0, "world": 1}

embedding = nn.Embedding(num_embeddings=2, embedding_dim=5)
print(embedding.weight.shape)  # torch.Size([2, 5])

ids = torch.tensor([word_to_ix["hello"]], dtype=torch.long)
print(embedding(ids).shape)    # torch.Size([1, 5]),一个 5 维向量

两个注意点:输入必须是整数张量(torch.long),传浮点数直接报错;输出最后一维是 D,前面的形状和输入一致。

Note

嵌入层参数量 = 词表大小 × 嵌入维度。词表一万、维度 256,就是 256 万个参数。NLP 模型里,嵌入层常常占掉一大块参数量。

句子长短不一,模型要求形状统一,短句就得补零到一样长。补出来的填充符(Padding)不该参与语义计算。

nn.Embeddingpadding_idx 参数就是干这个的:指定索引的向量恒为零,填充内容不会干扰模型。

embedding = nn.Embedding(10000, 128, padding_idx=0)
print(embedding(torch.tensor([0])).sum())  # tensor(0.) 恒为零
Tip

预处理阶段留一个专属索引当填充位,比如 0。短句统一补 0,模型自动忽略它们。

预训练词嵌入与训练

词嵌入可以随机初始化从头学,也可以加载别人在大语料上训好的:Word2Vec、GloVe、FastText 都是常见选择。数据少的时候,预训练词向量能明显拉高效果。

加载用 nn.Embedding.from_pretrained

pretrained = torch.randn(10000, 300)  # 假设是下载好的词向量矩阵
embedding = nn.Embedding.from_pretrained(pretrained, freeze=True)

freeze=True 表示冻结:训练时这些向量不动,适合数据少、只训上层模型的场景。想微调就设 freeze=False

Note

预训练词向量处理不了未登录词(OOV,Out-of-Vocabulary):词表里没有的词直接查不到。FastText 用子词信息缓解了这个问题,现代大模型则用 Byte-Pair Encoding 这类子词分词从根上绕开。

这些向量不是谁设计的,是训练的「副产品」。经典做法 Word2Vec 有两个思路:

  1. 连续词袋模型(CBOW):拿一个词前后的上下文词,预测中间这个词。
  2. Skip-gram:反过来,拿中间词预测周围的上下文词。

预测对了,词向量就被迫编码语义:出现在类似上下文里的词,向量自然靠拢。GloVe 走的是另一条路,统计词与词的共现次数来训练,效果类似。这类预训练向量通常有 50、100、200、300 维的版本,下载即用。

维度怎么选?数据少选小维度(50~100),不容易过拟合;数据充足可以上 300。训练过程中词向量还能继续微调,适配具体任务。

一条完整链路

把这一章串起来,文本进模型的完整流水线是:

  1. 清洗:去标点、统一大小写、转 Unicode 为 ASCII。
  2. 分词:把句子切成词元。
  3. 建词表:统计词频,给每个词分配索引。
  4. 转索引:句子变成整数序列。
  5. 查表:nn.Embedding 把整数序列变成向量序列。
  6. 进模型:向量序列交给 RNN 或 Transformer。

实际项目里分词和建词表常用 Hugging Face 的 tokenizers / transformers 库,开箱即用。早年常被推荐的 torchtext 已经停止维护(2023 年 9 月停更),别用在新项目里。本章先手写一遍,原理就通了。

拿到词的向量,后面就能接各种结构:RNN/LSTM 按顺序读,Transformer 用注意力并行看。位置信息 RNN 天然就有,Transformer 得额外加位置编码(第 23 章讲过)。如果你的建模单位不是词而是字符或子词,思路完全一样:建表、转索引、查嵌入。第 42 章的字符级 RNN 就是这么开始的。

常见坑

  1. 索引越界:输入 ID 超出 num_embeddings 会直接报错。新句子里的新词要映射到专属的 OOV 索引,不能硬塞。
  2. dtype 不对:浮点索引报错信息会提示 indices 必须是 Long 或 Int。见多了就认识了。
  3. 填充配合掩码:padding_idx 的向量恒为零,梯度也是零,但模型其他部分(如 RNN)也要配合掩码,才不会受填充位干扰。

本章一句话:文本先变索引,再查表变稠密向量,语义相近的词在向量空间里靠得近。这张「词表」,是所有 NLP 模型的入口。