从 Self-Attention 到 Multi-Head Attention,从位置编码到完整架构——
理解现代 AI 最核心的技术基石
在 Transformer 出现之前,循环神经网络(RNN)及其变体 LSTM(Long Short-Term Memory)是序列建模的主流方法。但它们存在几个根本性的局限:
问题 1:串行计算 — RNN 必须逐步处理序列:先处理第 1 个词,再处理第 2 个词……无法并行化。在 GPU 上,这意味着大量计算资源被浪费。
问题 2:长期依赖衰减 — 当序列很长时,早期信息在反复传递中逐渐"遗忘"。虽然 LSTM 引入了门控机制缓解了这个问题,但在处理超长序列(如整篇文章)时仍然力不从心。
问题 3:梯度消失/爆炸 — 反向传播时梯度在时间步上连乘,容易导致训练不稳定。
词₁ → [RNN] → 词₂ → [RNN] → 词₃ → [RNN] → …
⏱️ 必须等上一步完成才能处理下一步
词₁ ──┐
词₂ ──┼──→ [Self-Attention] → 全部同时处理
词₃ ──┘
⚡ 所有词同时处理,充分利用 GPU
Seq2Seq(Sequence-to-Sequence)模型使用编码器将输入序列压缩成一个固定长度的向量,再用解码器生成输出序列。这个架构虽然突破了定长输入的限制,但固定长度的上下文向量成为信息瓶颈——无论输入多长,都被压缩进同一个向量中,信息不可避免地丢失。
2015 年引入的注意力机制(Bahdanau Attention)允许解码器在生成每个词时"回头看"编码器的不同位置,缓解了信息瓶颈。这为 Transformer 的诞生埋下了伏笔。
出处:Bahdanau, D., Cho, K. & Bengio, Y. (2015). "Neural Machine Translation by Jointly Learning to Align and Translate." ICLR 2015. arXiv:1409.04732017 年 6 月,Google Brain 团队的 8 位研究者发表了论文 "Attention Is All You Need",提出了 Transformer 架构。其核心思想极其大胆:
完全抛弃 RNN 和 CNN,仅用注意力机制来建模序列关系。
论文认为:既然注意力机制已经能解决 RNN 的信息瓶颈问题,为什么不干脆去掉 RNN,让注意力机制独立承担全部工作?实验证明,这样做不仅在翻译任务上达到了当时的最优水平(BLEU 28.4),而且训练速度大幅提升(比当时最优模型快 8 倍)。
标题本身就是一个宣言:我们之前用 RNN、CNN、各种复杂的门控机制来做序列建模,但实际上,注意力机制就已经足够了。你需要的全部(All You Need),就是注意力。这个标题既简洁又充满自信,后来也成为了 AI 领域最著名的论文标题之一。
出处:Vaswani, A. et al. (2017). "Attention Is All You Need." NeurIPS 2017. arXiv:1706.03762原始 Transformer 采用经典的 Encoder-Decoder 架构。编码器负责理解输入,解码器负责生成输出。两者都由 N 个相同结构的层堆叠而成(论文中 N=6)。
数据从输入到输出的完整路径如下:
将每个 token(词/子词)转换为 d_model 维的向量(论文中 d_model=512)。类似词向量(Word Embedding),但维度更高。
在嵌入向量上叠加位置信息。因为自注意力机制本身没有顺序概念,需要显式告诉模型每个词的位置。
输入序列经过 N 层编码器,每层包含:Multi-Head Self-Attention → Add & Norm → Feed-Forward → Add & Norm。输出是编码后的"上下文表示"。
已生成的输出序列经过 N 层解码器,每层额外包含"交叉注意力"——让解码器关注编码器的输出。最后通过线性层 + Softmax 输出概率分布。
| 维度 | RNN / LSTM | Transformer |
|---|---|---|
| 计算方式 | 串行(逐步处理) | 并行(同时处理所有位置) |
| 长距离依赖 | 信息随距离衰减 | 任意两个位置直接交互 |
| 训练速度 | 慢(无法充分并行) | 快(充分利用 GPU 并行) |
| 位置信息 | 隐含在处理顺序中 | 需要额外添加位置编码 |
| 最大路径长度 | O(n),序列越长路径越长 | O(1),任意两个词之间直接连接 |
自注意力机制的核心是三个矩阵:Query(查询)、Key(键)、Value(值)。一个绝佳的类比是图书馆检索系统:
📖 想象你走进图书馆:
Query(查询) = 你心中的问题——"我想找一本关于量子力学的书"
Key(键) = 每本书的标签/标题——"《量子力学导论》《红楼梦》《Python编程》……"
Value(值) = 书的内容本身
你用 Query 去匹配所有 Key,找到最相关的书,然后取出对应的 Value(内容)。
在 Transformer 中,每个词都会生成自己的 Q、K、V:
让我们逐项拆解:
假设句子 "The cat sat on the mat",我们只取前 3 个词来演示。设 dk = 4:
Step 1:输入嵌入矩阵 X(3×4)经过三个线性变换得到 Q、K、V:
Step 2:计算注意力分数(以 "cat" 的 Q 为例):
Step 3:Softmax 归一化:
Step 4:加权求和 Value:
注意力矩阵展示了每个词对其他词的"关注程度"。以 "The cat sat on the mat" 为例:
解读:"cat" 最关注自身(70%)和 "The"(18%),因为它需要确定自己是主语。
"sat" 既关注 "cat"(15%,主语)也关注 "on"(20%,介词),因为动词需要搭配。
这是一个简化示意——实际训练后的注意力模式会更加复杂和有意义。
单个注意力头只能从"一个角度"看待词与词之间的关系。但语言中的关系是多种多样的:
关注语法关系
主语 → 谓语
关注指代消解
it → 前面的名词
关注语义相似性
近义词、同义表达
关注位置关系
相邻词、远距离词
直觉:就像人看一幅画——一个人从色彩角度欣赏,另一个人从构图角度分析,第三个人关注画中的故事。多个"视角"合在一起,才能得到更全面的理解。多头注意力让模型同时从多个子空间学习不同类型的注意力关系。
将 Q、K、V 分别通过 h 组不同的权重矩阵投影到 h 个低维子空间。每个头的维度 dk = dmodel / h(如 512/8=64)。
在 h 个子空间中分别独立计算缩放点积注意力。每个头关注不同方面的关系。
将 h 个头的输出拼接起来,得到 dmodel 维的向量。
通过输出权重矩阵 WO 将拼接结果映射回 dmodel 维,得到最终的 Multi-Head Attention 输出。
论文使用了 h = 8 个注意力头。由于每个头的维度是 dmodel/h = 64,总的计算量与单头注意力(dk = dmodel = 512)几乎相同,但能捕获更丰富的关系模式。
出处:Vaswani, A. et al. (2017). "Attention Is All You Need." NeurIPS 2017. Section 3.2.2.| 类型 | Q 来源 | K,V 来源 | 作用 |
|---|---|---|---|
| Self-Attention | 同一序列 | 同一序列 | 编码器内部 / 解码器内部 |
| Masked Self-Attention | 同一序列 | 同一序列(掩码) | 解码器:防止"看到未来" |
| Cross-Attention | 解码器 | 编码器输出 | 解码器关注输入信息 |
自注意力机制有一个"致命弱点":它本身是排列不变的(Permutation Invariant)。也就是说,把 "猫 吃 鱼" 和 "鱼 吃 猫" 输入自注意力层,如果没有额外的位置信息,模型认为它们的含义完全一样!
RNN 天然通过处理顺序来编码位置信息(先处理的词 = 位置靠前),但 Transformer 的自注意力是并行处理所有词的,丢失了顺序信息。因此需要显式地注入位置信息。
原始 Transformer 使用的是固定的正弦-余弦编码,其优雅之处在于不需要学习参数:
其中:
行 = 位置(pos 0, 1, 2),列 = 维度。
左侧(低频维度)颜色变化明显,能区分不同位置;
右侧(高频维度)颜色相近,提供细微的位置差异。
| 类型 | 说明 | 代表模型 |
|---|---|---|
| 固定正弦-余弦 | 使用三角函数,无需学习参数,天然支持外推到更长序列 | 原始 Transformer (2017) |
| 可学习绝对编码 | 位置编码作为可训练参数,模型自己学习最优的位置表示 | BERT、GPT-2、ViT |
| 相对位置编码 | 编码两个位置之间的"距离"而非绝对位置,泛化能力更强 | T5、Transformer-XL |
| 旋转位置编码 (RoPE) | 通过旋转变换编码相对位置,兼具绝对和相对位置信息 | LLaMA、Qwen、PaLM |
每个注意力层之后都有一个前馈网络(FFN),由两个线性变换 + ReLU 激活组成。作用是"非线性特征变换"——在注意力提取了词与词的关系后,FFN 进一步加工这些信息。
FFN 的隐藏维度通常为 4 × dmodel(如 2048),比输入维度大很多。这相当于在每个位置上独立做了一次"特征扩展→压缩",类似于两个 1×1 卷积。
对每个样本的特征维度做归一化,使其均值为 0、方差为 1。作用:
Transformer 使用了 Pre-LN(先归一化再进入注意力层),这比原始论文的 Post-LN 训练更稳定。
将子层的输入直接加到输出上:output = LayerNorm(x + SubLayer(x))。这是 ResNet 的核心思想:
论文中每层有 2 个残差连接:一个围绕 Multi-Head Attention,一个围绕 FFN。
掩码在 Transformer 中扮演关键角色,主要有两种:
因果掩码使解码器具有自回归特性:生成第 t 个词时只能看到前 t-1 个词。
Transformer 的发布彻底改变了 AI 的格局。以下是基于它的几个里程碑式的工作:
Google 发布 BERT(Bidirectional Encoder Representations from Transformers),只使用 Transformer 的编码器部分。核心创新:
BERT 在 11 项 NLP 基准任务上刷新了记录,奠定了"预训练-微调"范式。
出处:Devlin, J. et al. (2019). "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding." NAACL 2019. arXiv:1810.04805OpenAI 的 GPT(Generative Pre-trained Transformer)系列只使用 Transformer 的解码器部分,采用"从左到右"的自回归方式生成文本:
Google 的 T5(Text-to-Text Transfer Transformer)将所有 NLP 任务统一为"文本到文本"格式:输入一段文本,输出一段文本。翻译、摘要、问答、分类……全部用相同的编码器-解码器架构处理。
ViT 证明了 Transformer 不仅适用于文本,也能在图像识别上达到或超越 CNN 的水平。核心方法极其简单:
ViT 的论文标题 "An Image is Worth 16x16 Words" 是对 "A picture is worth a thousand words" 的巧妙致敬——一张图片 ≈ 16×16 个词。
出处:Dosovitskiy, A. et al. (2021). "An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale." ICLR 2021. arXiv:2010.11929BERT、RoBERTa、ALBERT、DeBERTa
适用:分类、NER、语义理解
GPT 系列、LLaMA、Qwen、DeepSeek
适用:文本生成、对话、推理
T5、BART、mBART、Whisper
适用:翻译、摘要、语音识别
以下代码展示了自注意力机制的完整实现过程,包含详细的数学注释:
import torch import torch.nn as nn import torch.nn.functional as F import math class SelfAttention(nn.Module): """ 自注意力机制的 PyTorch 实现 数学公式: Attention(Q, K, V) = softmax(QK^T / √d_k) V 参数: d_model (int): 模型维度,即每个 token 向量的长度 n_heads (int): 注意力头的数量 """ def __init__(self, d_model=512, n_heads=8): super().__init__() assert d_model % n_heads == 0, "d_model 必须能被 n_heads 整除" self.d_model = d_model self.n_heads = n_heads self.d_k = d_model // n_heads # 每个头的维度: 512/8 = 64 # Q, K, V 的线性投影层 (可学习参数) self.W_q = nn.Linear(d_model, d_model) self.W_k = nn.Linear(d_model, d_model) self.W_v = nn.Linear(d_model, d_model) # 输出投影层 self.W_o = nn.Linear(d_model, d_model) def scaled_dot_product_attention(self, Q, K, V, mask=None): """ 缩放点积注意力 Step 1: QK^T — 查询与键的点积,得到注意力分数 Step 2: / √d_k — 缩放,防止梯度消失 Step 3: softmax — 归一化为概率分布 Step 4: × V — 加权求和 Value """ # Step 1 & 2: 计算注意力分数并缩放 # Q: (batch, n_heads, seq_len, d_k) # K^T: (batch, n_heads, d_k, seq_len) # scores: (batch, n_heads, seq_len, seq_len) scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) # 应用掩码(如因果掩码): 将需要屏蔽的位置设为 -∞ if mask is not None: scores = scores.masked_fill(mask == 0, float('-inf')) # Step 3: softmax 归一化 attention_weights = F.softmax(scores, dim=-1) # Step 4: 用注意力权重加权 Value # output: (batch, n_heads, seq_len, d_k) output = torch.matmul(attention_weights, V) return output, attention_weights def forward(self, x, mask=None): """ 前向传播 输入 x: (batch_size, seq_len, d_model) """ batch_size, seq_len, _ = x.size() # Step 1: 线性投影得到 Q, K, V Q = self.W_q(x) # (batch, seq_len, d_model) K = self.W_k(x) V = self.W_v(x) # Step 2: 拆分为多头 # reshape 为 (batch, n_heads, seq_len, d_k) Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2) K = K.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2) V = V.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2) # Step 3: 计算缩放点积注意力 attn_output, attn_weights = self.scaled_dot_product_attention(Q, K, V, mask) # Step 4: 拼接多头 # (batch, n_heads, seq_len, d_k) → (batch, seq_len, d_model) attn_output = attn_output.transpose(1, 2).contiguous() attn_output = attn_output.view(batch_size, seq_len, self.d_model) # Step 5: 输出线性变换 output = self.W_o(attn_output) return output, attn_weights # ===== 使用示例 ===== if __name__ == "__main__": # 模型参数 d_model = 512 # 模型维度 n_heads = 8 # 注意力头数 seq_len = 10 # 序列长度(如 10 个词) batch_size = 4 # 批量大小 # 创建模型 self_attn = SelfAttention(d_model=d_model, n_heads=n_heads) # 模拟输入: 4 个句子,每句 10 个词,每个词 512 维 x = torch.randn(batch_size, seq_len, d_model) # 创建因果掩码(下三角矩阵,防止看到未来) mask = torch.tril(torch.ones(seq_len, seq_len)) # 前向传播 output, weights = self_attn(x, mask) print(f"输入形状: {x.shape}") # (4, 10, 512) print(f"输出形状: {output.shape}") # (4, 10, 512) print(f"注意力权重: {weights.shape}") # (4, 8, 10, 10) print(f"每个样本有 {n_heads} 个头, 每个头是 {seq_len}×{seq_len} 的注意力矩阵")
代码结构说明:
1. W_q, W_k, W_v — 三个线性层,将输入投影为 Q、K、V(可学习参数)
2. scaled_dot_product_attention — 核心注意力计算:QK^T → 缩放 → softmax → ×V
3. 多头拆分/拼接 — 将 d_model 维度拆为 n_heads × d_k,分别计算后拼接
4. W_o — 输出投影,将拼接后的多头结果映射回 d_model 维
5. 因果掩码 — 下三角矩阵,确保解码器只能看到已生成的内容