一句话总结:词嵌入(Embedding)表示"这是什么词",位置编码(Positional Encoding)表示"这个词在哪里"。Transformer 把它们直接相加而不是拼接,因为相加既能保留两种信息,又不增加维度,还让模型更容易学习。
14.1 回顾:输入处理的两个步骤
在第 4 章和第 5 章,我们分别学习了:
| 章节 | 组件 | 作用 |
|---|---|---|
| 第 4 章 | Embedding | 把 Token ID 变成向量(语义信息) |
| 第 5 章 | Positional Encoding | 给向量添加位置信息 |
这一章,我们来深入理解:为什么这两个信息要通过"相加"来结合?
14.2 两种信息的本质
14.2.1 词嵌入:语义信息
回顾第 4 章的 Embedding 查找表:
每个词有一个对应的向量,这个向量包含了词的语义信息:
- "国王"和"王后"的向量接近(都是皇室)
- "国王"和"苹果"的向量很远(没有关系)
Embedding 回答的问题是:这个词是什么意思?
14.2.2 位置编码:位置信息
回顾第 5 章的位置编码:
位置编码是一组正弦/余弦函数,每个位置有唯一的编码:
- 位置 0 有自己的向量
- 位置 1 有不同的向量
- ...
位置编码回答的问题是:这个词在句子的什么位置?
14.2.3 两种信息都很重要
考虑这两个句子:
- "我爱你"
- "你爱我"
它们包含完全相同的词,但意思完全不同。位置改变了含义。
所以 Transformer 需要同时知道:
- 每个词是什么(Embedding)
- 每个词在哪里(Positional Encoding)
14.3 结合方式:相加 vs 拼接
14.3.1 两种选择
有两种直观的方式来结合 Embedding 和 Positional Encoding:
方式一:拼接(Concatenation)
输入向量 = [Embedding; Positional Encoding]
维度:d_model + d_model = 2 × d_model
方式二:相加(Addition)
输入向量 = Embedding + Positional Encoding
维度:d_model(不变)
Transformer 选择了相加。为什么?
14.3.2 相加的优势
1. 维度不增加
拼接会让维度翻倍:
- 原本 d_model = 512
- 拼接后变成 1024
这意味着所有后续层的参数量都要翻倍!
相加保持维度不变:
- 原本 d_model = 512
- 相加后还是 512
2. 信息可以共存
两个向量相加后,信息并没有丢失,而是"叠加"在一起:
embedding = [0.5, 0.3, -0.2, 0.8, ...] # 语义
position = [0.1, 0.0, 0.1, -0.1, ...] # 位置
combined = [0.6, 0.3, -0.1, 0.7, ...] # 包含两种信息
3. 模型可以学会分离
神经网络有很强的表达能力,它可以学会:
- 从 combined 中提取语义相关的部分
- 从 combined 中提取位置相关的部分
14.3.3 一个直观的类比
想象你在一个派对上:
- 你的外表(Embedding):告诉别人"你是谁"
- 你站的位置(Positional Encoding):告诉别人"你在哪"
这两个信息是独立的,但可以同时存在。别人看到你,既知道你是谁,也知道你在哪里。
14.4 计算方式示例
14.4.1 具体计算
看这张图,我们来追踪一个具体的计算:
假设:
old_embedding_value = 0.9 # 某个词的 embedding 中的一个值
positional_encoding = 0.1 # 对应位置的位置编码值
输入阶段(相加):
combined = embedding + positional_encoding
= 0.9 + 0.1
= 1.0
训练阶段(反向传播更新 embedding):
new_embedding_value = old_embedding_value - lr * error_gradient
= 0.9 - 0.1 * (-0.4)
= 0.9 + 0.04
= 0.94
下一次前向传播:
new_combined_value = new_embedding_value + positional_encoding
= 0.94 + 0.1
= 1.04
14.4.2 关键观察
注意这个过程中:
- Embedding 是可学习的:在训练过程中不断更新
- Positional Encoding 是固定的:不参与训练(使用正弦/余弦函数)
- 相加发生在每次前向传播:不是一次性的
14.5 深层逻辑:为什么这种设计有效?
14.5.1 向量空间的视角
在高维向量空间中,方向代表含义,位置代表不同的概念。
相加可以理解为:
- Embedding 向量指向"语义方向"
- Positional Encoding 向量提供"位置偏移"
最终的向量既有语义信息,又被"偏移"到了正确的位置。
14.5.2 正交性假设
一个重要的假设是:语义信息和位置信息在某种程度上是正交的。
正交意味着:
- 语义变化不会影响位置信息
- 位置变化不会影响语义信息
虽然这不是严格成立的,但神经网络可以学会近似这种分离。
14.5.3 Attention 如何利用这两种信息
在 Attention 计算中:
Q = (Embedding + PE) @ Wq
K = (Embedding + PE) @ Wk
Wq 和 Wk 可以学习到:
- 某些维度主要关注语义相似度
- 某些维度主要关注位置关系
例如:
- "sat" 和 "cat" 的语义相关(动词+主语)
- 相邻位置的词可能语法相关
Attention 可以同时捕捉这两种关系。
14.6 变体:不同的位置编码方法
14.6.1 原始方法:相加固定编码
input = Embedding(token_ids) + PositionalEncoding(positions)
这是原始 Transformer(2017)使用的方法,采用正弦余弦固定编码。
注意:GPT-2 虽然也是"相加",但使用的是可学习的位置编码,而非固定的正弦编码。
14.6.2 可学习位置编码
GPT 系列实际上使用的是可学习的位置编码:
# 代码示例
class LearnablePositionalEncoding(nn.Module):
def __init__(self, max_len, d_model):
super().__init__()
# 位置编码也是一个可学习的 Embedding
self.pe = nn.Embedding(max_len, d_model)
def forward(self, x):
positions = torch.arange(x.size(1), device=x.device)
return x + self.pe(positions)
可学习位置编码的优势:
- 让模型自己学习最优的位置表示
- 不需要手动设计正弦/余弦函数
14.6.3 RoPE:旋转位置编码
更现代的方法是 RoPE(Rotary Position Embedding),它不是简单地相加,而是通过旋转来编码位置:
Q_rotated = rotate(Q, position)
K_rotated = rotate(K, position)
RoPE 的优势:
- 相对位置信息更明确
- 可以外推到更长的序列
LLaMA、GPT-NeoX 等现代模型都使用 RoPE。这个话题我们在第 25 章详细讨论。
14.7 实际代码:输入处理完整流程
14.7.1 PyTorch 实现
# 代码示例
class TransformerInput(nn.Module):
def __init__(self, vocab_size, d_model, max_len, dropout=0.1):
super().__init__()
# 词嵌入
self.token_embedding = nn.Embedding(vocab_size, d_model)
# 位置编码(可学习)
self.position_embedding = nn.Embedding(max_len, d_model)
# Dropout
self.dropout = nn.Dropout(dropout)
# 缩放因子
self.scale = d_model ** 0.5
def forward(self, x):
# x: [batch_size, seq_len] 的 token IDs
# 1. 词嵌入
token_emb = self.token_embedding(x) # [batch, seq, d_model]
# 2. 缩放(可选,有些实现会做)
token_emb = token_emb * self.scale
# 3. 位置编码
positions = torch.arange(x.size(1), device=x.device)
pos_emb = self.position_embedding(positions) # [seq, d_model]
# 4. 相加
combined = token_emb + pos_emb # [batch, seq, d_model]
# 5. Dropout
return self.dropout(combined)
14.7.2 维度追踪
输入 token_ids: [4, 16] # 4个句子,每句16个token
Token Embedding:
查表: token_embedding([4, 16])
输出: [4, 16, 512] # 每个token变成512维向量
Position Embedding:
位置: [0, 1, 2, ..., 15]
查表: position_embedding([16])
输出: [16, 512] # 每个位置有512维向量
广播: [4, 16, 512] # 广播到batch维度
相加:
[4, 16, 512] + [4, 16, 512] = [4, 16, 512]
输出: [4, 16, 512] # 包含语义+位置信息的向量
14.8 常见问题
14.8.1 相加会不会让信息混淆?
不会,因为:
- 向量空间是高维的(512维),有足够的"空间"容纳两种信息
- Attention 的 Wq、Wk、Wv 可以学会提取需要的信息
- 多层 Transformer 可以逐步分离和处理这些信息
14.8.2 为什么位置编码的幅度要小?
如果位置编码太大,会"淹没"词嵌入的信息:
embedding = [0.5, 0.3, -0.2] # 语义信息
position = [10, 20, -15] # 位置信息(太大了!)
combined = [10.5, 20.3, -15.2] # 主要是位置信息
所以正弦/余弦位置编码的值在 [-1, 1] 之间,和 embedding 的量级相当。
14.8.3 可学习 vs 固定位置编码哪个好?
| 类型 | 优点 | 缺点 |
|---|---|---|
| 固定(正弦/余弦) | 理论上可外推到任意长度 | 不一定是最优的 |
| 可学习 | 能学到任务特定的位置模式 | 只能处理见过的长度 |
实践中,可学习位置编码在多数任务上表现更好,所以 GPT 系列都用可学习的。
14.9 本章总结
14.9.1 核心概念
| 信息类型 | 来源 | 表示什么 | 是否可学习 |
|---|---|---|---|
| 词嵌入 | Embedding 查表 | 词的语义 | 是 |
| 位置编码 | 位置 Embedding/正弦函数 | 词的位置 | 取决于实现 |
| 输入向量 | 两者相加 | 语义 + 位置 | - |
14.9.2 为什么相加而不是拼接
- 维度不变:不增加计算量
- 信息叠加:高维空间可以容纳两种信息
- 可分离:Attention 可以学会分别利用两种信息
14.9.3 核心认知
词嵌入和位置编码通过简单的相加结合在一起,这种设计既高效又有效。词嵌入提供"是什么"的语义信息,位置编码提供"在哪里"的位置信息。Transformer 的 Attention 机制可以从这个混合向量中提取需要的信息,实现对语言的深度理解。
本章交付物
学完这一章,你应该能够:
- 解释词嵌入和位置编码分别表示什么
- 说出为什么选择相加而不是拼接
- 理解相加后信息如何被 Attention 利用
- 知道可学习位置编码和固定位置编码的区别
下一章预告
现在我们理解了输入是如何准备的:词嵌入 + 位置编码 → 相加 → 输入向量。
下一章,我们将把所有组件串起来,追踪数据从输入到输出的完整前向传播过程。这是理解 Transformer 如何工作的最后一块拼图!