深度学习11——Tokenization、embedding、位置编码

1. Tokenization 与 Embedding

文本进入 Transformer 前,通常经历以下过程:

text 复制代码
文本
-> Tokenizer
-> Token
-> Token ID
-> Embedding
-> Token 向量

Tokenization

Tokenizer 负责:

  1. 将文本切分成 token。
  2. 根据词表把每个 token 转换成整数 ID。

例如:

text 复制代码
我喜欢猫
-> ["我", "喜欢", "猫"]
-> [2, 3, 4]

词表可以理解为一张映射表:

text 复制代码
<PAD> -> 0
<UNK> -> 1
我     -> 2
喜欢   -> 3
猫     -> 4
狗     -> 5

Token 不一定是完整的字或单词,也可能是子词。具体如何切分,取决于 Tokenizer 使用的算法和词表。

Token ID 只是 token 在词表中的编号,没有大小、距离等数学含义。


Embedding

Embedding 根据 Token ID 查询对应的向量,本质上是一张可训练的向量表:

text 复制代码
Embedding.weight.shape = [vocab_size, d_model]

其中:

text 复制代码
vocab_size = 词表中的 token 数量
d_model    = 每个 token 的向量维度

例如:

python 复制代码
embedding = nn.Embedding(
    num_embeddings=10000,
    embedding_dim=768
)

Embedding 内部有一个形状为:

text 复制代码
[10000, 768]

的参数矩阵。

输入:

python 复制代码
input_ids = torch.tensor([2, 3, 4])
# shape: [3]

经过 Embedding:

python 复制代码
x = embedding(input_ids)
# shape: [3, 768]

相当于查询:

python 复制代码
x = embedding.weight[input_ids]

也就是取出参数矩阵的第 2、3、4 行。

对于 Batch 输入:

text 复制代码
input_ids: [B, S]

经过 Embedding:

text 复制代码
[B, S] -> [B, S, D]

其中:

text 复制代码
B = batch_size
S = seq_len
D = d_model

Tokenizer 与 Embedding 的区别

项目 Tokenizer Embedding
输入 原始文本 Token ID
输出 Token ID 浮点向量
主要操作 文本切分和词表映射 根据 ID 查询向量
是否属于神经网络参数 通常不是
是否通过反向传播更新 通常不会

Tokenizer 的词表和切分规则可能在模型训练前通过语料构建,但正式训练和使用模型时通常是固定的。


2. 为什么不能直接把 Token ID 输入神经网络

Token ID 是类别编号,不是具有实际数值意义的特征。

假设:

text 复制代码
猫   -> 4
狗   -> 5
苹果 -> 6

这些编号只是词表中的位置,不能说明:

text 复制代码
狗比猫大
苹果比狗大
猫和狗的语义距离是 1
猫和苹果的语义距离是 2

如果直接把 ID 当作普通数字输入神经网络,模型可能错误地把 ID 的大小和差值当成有效特征,例如错误地认为:

text 复制代码
编号 4 和编号 5 比较接近
所以"猫"和"狗"一定相似

但词表编号可以任意调整:

text 复制代码
猫   -> 800
狗   -> 12
苹果 -> 13

编号改变后,数值距离也会改变,但词语含义并没有改变。

因此正确流程是:

text 复制代码
Token ID
-> Embedding 查表
-> 具有多个特征维度的浮点向量
-> 神经网络

Embedding 让每个 token 拥有独立的向量表示,模型不需要使用 Token ID 的数值大小。


3. Embedding 为什么是可学习参数

Embedding 本质上是一个参数矩阵:

text 复制代码
Embedding.weight: [vocab_size, d_model]

模型刚初始化时,每个 token 的向量通常是随机的:

text 复制代码
猫 -> [随机数, 随机数, ...]
狗 -> [随机数, 随机数, ...]

此时这些向量还没有明确含义。

训练过程中:

text 复制代码
前向传播
-> 计算预测结果
-> 计算 Loss
-> 反向传播
-> 更新模型参数

Embedding 的 weight 也参与反向传播,因此会被优化器更新:

python 复制代码
loss.backward()
optimizer.step()

如果输入中出现了"猫",与"猫"对应的 Embedding 向量就可能根据 Loss 获得梯度并被调整。


为什么不把 Embedding 固定住

不同任务需要模型关注不同的信息:

text 复制代码
情感分析:更关注"喜欢""讨厌""开心"等情绪信息
机器翻译:更关注不同语言之间的语义对应关系
文本生成:更关注 token 在上下文中的使用方式
代码模型:更关注变量、语法和程序结构

让 Embedding 可学习,模型就可以根据训练目标自动调整每个 token 的初始表示。

经常出现在相似上下文中的 token,其向量可能逐渐接近。例如:

text 复制代码
我喜欢猫
我喜欢狗
我养了一只猫
我养了一只狗

"猫"和"狗"在这些句子中的使用方式相似,训练时可能获得相似的更新方向,因此向量可能逐渐接近。

不过这种接近不是人工规定的,也不保证每个相似词都一定相邻。它表示的是:

text 复制代码
模型认为这些 token 在当前训练任务中的使用方式相似

Token Embedding 与上下文表示的区别

Embedding 表中每个 Token ID 对应一个固定的初始向量。

例如两个句子:

text 复制代码
苹果很好吃
苹果发布了新手机

两个"苹果"查询到的初始 Token Embedding 相同。

但是经过 Transformer 后,模型会结合上下文产生不同的 Hidden State:

text 复制代码
苹果很好吃       -> 更接近水果含义
苹果发布了新手机 -> 更接近公司含义

因此:

text 复制代码
Token Embedding:与 Token ID 对应的初始表示
Hidden State:经过上下文处理后的动态表示

一个词的多种含义主要由 Transformer 根据上下文进一步区分,而不是只依赖初始 Embedding。


4. 输入 Embedding 与输出 LM Head

语言模型的完整处理流程通常是:

text 复制代码
文本
-> Tokenizer
-> Token ID
-> Token Embedding
-> Transformer
-> Hidden State
-> LM Head
-> 每个 Token 的预测分数
-> Softmax
-> 下一个 Token 的概率

输入 Embedding

输入 Embedding 把 Token ID 转换成向量:

text 复制代码
input_ids: [B, S]
-> Embedding
x: [B, S, D]

它的参数形状是:

text 复制代码
Embedding.weight: [V, D]

其中:

text 复制代码
V = vocab_size
D = d_model

输出 LM Head

Transformer 最后输出:

text 复制代码
hidden_states: [B, S, D]

语言模型需要预测词表中的下一个 token,因此必须把每个 D 维 Hidden State 转换成 V 个词表分数:

python 复制代码
lm_head = nn.Linear(D, V, bias=False)

形状变化:

text 复制代码
[B, S, D] -> [B, S, V]

输出的:

text 复制代码
logits[b, s, :]

表示第 b 个样本、第 s 个位置对整个词表的预测分数。

计算过程可以写成:

text 复制代码
logits = hidden_states @ lm_head.weight.T

其中:

text 复制代码
hidden_states:    [B, S, D]
lm_head.weight.T: [D, V]
logits:           [B, S, V]

为什么输入 Embedding 和 LM Head 可以共享权重

输入 Embedding 的参数形状是:

text 复制代码
[V, D]

LM Head 的权重形状也是:

text 复制代码
[V, D]

两者形状相同,因此可以共享同一个参数矩阵:

python 复制代码
lm_head.weight = token_embedding.weight

这种做法叫:

text 复制代码
Weight Tying,权重绑定或权重共享

输入时,这个矩阵用于:

text 复制代码
Token ID -> 查询对应的 Token 向量

输出时,这个矩阵用于:

text 复制代码
Hidden State -> 与所有 Token 向量计算匹配分数

可以理解为:

text 复制代码
输入阶段:取出某个 token 的表示
输出阶段:判断当前 Hidden State 最接近哪个 token 的表示

共享权重的主要好处:

  1. 减少模型参数量。
  2. 让输入和输出使用一致的 token 表示空间。
  3. 提高同一组参数的利用率。
  4. 在很多语言模型中有助于训练和泛化。

例如:

python 复制代码
token_embedding = nn.Embedding(vocab_size, d_model)
lm_head = nn.Linear(d_model, vocab_size, bias=False)

lm_head.weight = token_embedding.weight

权重共享是常见设计,但不是所有语言模型都必须采用。


5. 为什么 Transformer 需要位置编码

Self-Attention 根据 token 内容计算它们之间的关系,但它天然不知道 token 的顺序。

例如:

text 复制代码
小明喜欢小红
小红喜欢小明

两句话包含相似的 token,但顺序不同,含义也不同。

如果没有位置编码,Self-Attention 可以知道序列中有哪些 token,却无法天然表示:

text 复制代码
哪个 token 在前
哪个 token 在后
两个 token 相距多远

因此需要把位置信息加入模型。

常见方法包括:

方法 主要思想
Sinusoidal Positional Encoding 使用固定的正弦、余弦函数表示绝对位置
Learnable Positional Embedding 为每个绝对位置学习一个向量
Relative Position Encoding 表示两个 token 之间的相对距离
RoPE 根据位置旋转 Q、K
ALiBi 给注意力分数添加距离偏置

6. 正弦、余弦位置编码

原始 Transformer 使用固定的 Sinusoidal Positional Encoding:

text 复制代码
PE(pos, 2i)   = sin(pos / 10000^(2i / d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i / d_model))

其中:

text 复制代码
pos     = token 在序列中的位置
i       = 维度编号
d_model = Token Embedding 的维度

基本规则:

text 复制代码
偶数维度使用 sin
奇数维度使用 cos
不同维度使用不同频率

正弦位置编码不是训练参数,而是通过公式预先计算出来的固定向量。


为什么不同维度使用不同频率

不同频率可以让位置编码同时表达不同范围的位置变化:

text 复制代码
高频维度:变化较快,适合区分相邻位置
低频维度:变化较慢,适合表示较长距离的位置关系

可以把它类比为使用多种时间单位记录时间:

text 复制代码
秒、分钟、小时、日期

只使用一种频率,能够表达的位置模式比较单一;使用多种频率组合,可以让每个位置拥有更容易区分的编码。


为什么可以与 Token Embedding 直接相加

Token Embedding 和位置编码具有相同的最后一维:

text 复制代码
Token Embedding:    [B, S, D]
Position Encoding: [1, S, D]

通过广播:

text 复制代码
[B, S, D] + [1, S, D]
-> [B, S, D]

相加后,每个 token 的向量同时包含:

text 复制代码
这个 token 是什么
这个 token 在什么位置

相加确实会把内容和位置信息混合在同一个向量中,但模型可以通过训练逐渐学会如何使用这些信息。

使用相加而不是拼接,还有一个重要好处:

text 复制代码
相加:[D] + [D] -> [D]
拼接:[D] 和 [D] -> [2D]

相加不会扩大模型维度,也不需要修改后续 Linear 层的输入大小。


7. 可学习位置 Embedding 与相对位置编码

可学习的绝对位置 Embedding

可学习位置 Embedding 为每个位置准备一个可训练向量:

python 复制代码
position_embedding = nn.Embedding(
    max_seq_len,
    d_model
)

参数矩阵形状:

text 复制代码
[max_seq_len, d_model]

例如:

text 复制代码
位置 0 -> 一个可学习向量
位置 1 -> 一个可学习向量
位置 2 -> 一个可学习向量

使用时:

text 复制代码
Token Embedding + Position Embedding

与固定正弦编码相比,它可以根据训练任务自动调整,但通常受到 max_seq_len 限制,超出已定义的位置范围时不能直接查询。


相对位置编码

正弦位置编码和可学习位置 Embedding主要表示绝对位置:

text 复制代码
这是序列中的第 10 个 token

相对位置编码关注两个 token 的距离和方向:

text 复制代码
另一个 token 在当前 token 前面 2 个位置
另一个 token 在当前 token 后面 1 个位置

例如:

text 复制代码
A B C D

B 为当前 token:

text 复制代码
A 相对 B 的位置:-1
B 相对 B 的位置: 0
C 相对 B 的位置:+1
D 相对 B 的位置:+2

语言中的很多关系更依赖相对距离,而不是 token 的绝对编号。


8. RoPE 与 ALiBi

RoPE

RoPE 全称是:

text 复制代码
Rotary Position Embedding
旋转位置编码

RoPE 不把位置向量直接加到 Token Embedding 上,而是根据 token 的位置旋转 Q 和 K:

text 复制代码
Q -> 按位置旋转后的 Q
K -> 按位置旋转后的 K
V -> 通常不旋转

多头注意力中的形状通常是:

text 复制代码
Q、K、V: [B, H, S, d]

RoPE 会在最后一个维度上将特征两两分组,并使用不同角度旋转:

text 复制代码
[x0, x1]
[x2, x3]
[x4, x5]

旋转不会改变张量形状:

text 复制代码
[B, H, S, d] -> [B, H, S, d]

为什么 RoPE 作用于 Q 和 K

注意力分数由 Q 和 K 的内积决定:

text 复制代码
scores = Q @ K.T

RoPE 根据位置分别旋转 Q 和 K。两个旋转后向量的内积,会受到它们位置差的影响:

text 复制代码
位置 m 的 Q
与
位置 n 的 K

计算得到的注意力分数能够反映:

text 复制代码
相对位置 n - m

因此 RoPE 不是简单告诉模型"这是第几个位置",而是直接让 Q 和 K 的匹配分数包含相对位置信息。

V 主要负责传递被关注 token 的内容,不参与注意力分数 Q @ K.T 的计算,所以通常不需要旋转 V。


ALiBi

ALiBi 不修改输入 Embedding,也不旋转 Q 和 K,而是直接给注意力分数添加一个与距离有关的偏置:

text 复制代码
scores = Q @ K.T + distance_bias

通常 token 距离越远,加入的惩罚越大:

text 复制代码
距离近 -> 惩罚较小
距离远 -> 惩罚较大

不同注意力头可以使用不同的距离斜率,从而学习不同范围的注意力关系。

现阶段可以简单记住:

text 复制代码
正弦位置编码:把固定位置向量加到输入上
可学习位置 Embedding:把可训练位置向量加到输入上
相对位置编码:关注 token 之间的相对距离
RoPE:旋转 Q 和 K,让注意力内积包含相对位置
ALiBi:给注意力分数加入与距离有关的偏置
相关推荐
正和视觉教育1 小时前
深圳CCD视觉培训:快速掌握机器视觉检测核心技术
人工智能·python·计算机视觉·视觉检测
Old Uncle Tom1 小时前
银行用户画像 -- 约束与负面偏好
人工智能·金融·手机银行
有点小帅得平哥哥1 小时前
基于飞书生态的 AI 智能体
人工智能·飞书
爬楼的猪2 小时前
2026 年 7 月工业 AI 与机器人产业雷达(1)
人工智能·机器人
电手2 小时前
微软用AI修复622个漏洞,Win 11七月更新又翻车了
人工智能·microsoft
牛企老板俱乐部2 小时前
企跃龙门 GEO 智能优化系统正式发布 抢占 AI 搜索时代企业获客新赛道
大数据·人工智能
声讯电子2 小时前
AU-60 全功能语音处理模组:一款“万能接入”的AI音频前端方案解析
人工智能·音视频·语音识别·ai降噪·usb接口·回音消除
donoot3 小时前
PaddleOCR + PyMuPDF 生成【全兼容双层 PDF】完整实操指南
人工智能·算法·pymupdf·paddleocr·双层pdf
lpfasd1238 小时前
2026年第30周科技社区趋势周报:开放权重之争与AI Agent的破局
人工智能·科技