2017年,Vaswani等人在论文《Attention Is All You Need》中提出了Transformer架构。这个标题本身就是一个宣言:注意力机制不再需要循环神经网络或卷积神经网络的辅助,它足以独立承担序列建模的全部任务。
七年后的今天,Transformer已经成为大语言模型的基石。GPT系列、BERT、LLaMA、Mistral——所有这些模型的核心都是同一个组件:Self-Attention。理解Self-Attention的计算流程,不只是学术要求,更是理解现代AI工作原理的必经之路。
注意力:从直觉到数学
在深入Self-Attention的具体计算之前,需要先理解"注意力"这个概念的本质。
传统神经网络处理序列时存在一个根本困境:序列中的每个元素应该关注哪些其他元素?在机器翻译中,翻译一个词可能需要参考原文中多个不同的词;在文本理解中,理解一个词的含义需要考虑上下文中的相关词汇。
注意力机制提供了一个优雅的解决方案:让每个位置动态地决定应该"关注"序列中的哪些其他位置。这不是一个硬编码的规则,而是一个可学习的过程。
flowchart LR
subgraph 问题
A[序列处理困境]
end
subgraph 传统方法
B[RNN: 顺序处理]
C[CNN: 局部窗口]
end
subgraph 注意力方案
D[全局注意力]
E[动态权重]
F[并行计算]
end
A --> B
A --> C
A --> D
D --> E --> F
Self-Attention中的"Self"意味着:同一个序列内部的元素相互关注。序列中的每个元素既作为查询者去寻找相关信息,也作为被查询者提供自己的信息。
Query、Key、Value:一个检索系统的隐喻
理解Self-Attention最直观的方式是把它想象成一个检索系统:
- Query (查询):当前元素想要寻找什么样的信息
- Key (键):每个元素能够提供什么样的信息
- Value (值):每个元素实际的内容
当你在图书馆查找资料时,你手中的需求描述就是Query,每本书的标题和摘要就是Key,书的实际内容就是Value。通过比较Query和Key的相似度,你决定要阅读哪些书(Value)。
flowchart TB
subgraph 图书馆隐喻
Q[你的需求<br/>Query]
K1[书A标题<br/>Key]
K2[书B标题<br/>Key]
K3[书C标题<br/>Key]
V1[书A内容<br/>Value]
V2[书B内容<br/>Value]
V3[书C内容<br/>Value]
Match{相似度匹配}
Result[阅读内容]
end
Q --> Match
K1 --> Match
K2 --> Match
K3 --> Match
Match -->|高相似度| V1
Match -->|中相似度| V2
Match -->|低相似度| V3
V1 --> Result
V2 --> Result
V3 --> Result
在Self-Attention中,这三个向量都是通过线性变换从同一个输入向量得到的:
$$Q = XW^Q, \quad K = XW^K, \quad V = XW^V$$其中 $X \in \mathbb{R}^{n \times d}$ 是输入序列(n个token,每个维度为d),$W^Q, W^K \in \mathbb{R}^{d \times d_k}$ 和 $W^V \in \mathbb{R}^{d \times d_v}$ 是可学习的投影矩阵。
Self-Attention的完整计算流程
第一步:输入嵌入与位置编码
假设输入句子是"Life is short",首先需要将每个词转换为向量表示。这个过程分为两步:
词嵌入(Word Embedding):每个词从词表中取出对应的嵌入向量。假设嵌入维度 $d_{model} = 512$,则每个词被表示为一个512维的向量。
位置编码(Positional Encoding):由于Self-Attention本身是位置不变的(交换输入序列中任意两个位置,输出只是相应交换),需要显式注入位置信息。原始Transformer使用正弦和余弦函数:
$$PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d_{model}})$$$$PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d_{model}})$$
位置编码被直接加到词嵌入上:
$$X = \text{Embedding}(\text{tokens}) + PE$$flowchart LR
subgraph 输入处理
Tokens["Life, is, short"]
Embed["词嵌入<br/>[3, 512]"]
PE["位置编码<br/>[3, 512]"]
Add["相加"]
X["输入表示 X<br/>[3, 512]"]
end
Tokens --> Embed --> Add
PE --> Add --> X
第二步:计算Query、Key、Value
对于序列中的每个位置 $i$,通过三个不同的线性变换得到 $q^{(i)}, k^{(i)}, v^{(i)}$:
$$q^{(i)} = W^Q x^{(i)}$$$$k^{(i)} = W^K x^{(i)}$$
$$v^{(i)} = W^V x^{(i)}$$
用矩阵形式表示整个序列:
$$Q = XW^Q, \quad K = XW^K, \quad V = XW^V$$假设序列长度为 $n$,Query和Key的维度为 $d_k$,Value的维度为 $d_v$,则 $Q, K \in \mathbb{R}^{n \times d_k}$,$V \in \mathbb{R}^{n \times d_v}$。
import torch
import torch.nn as nn
d_model = 512
d_k = 64
d_v = 64
# 假设输入序列
X = torch.randn(1, 10, d_model) # batch_size=1, seq_len=10
# Q, K, V投影
W_Q = nn.Linear(d_model, d_k, bias=False)
W_K = nn.Linear(d_model, d_k, bias=False)
W_V = nn.Linear(d_model, d_v, bias=False)
Q = W_Q(X) # [1, 10, 64]
K = W_K(X) # [1, 10, 64]
V = W_V(X) # [1, 10, 64]
第三步:计算注意力分数
这是Self-Attention的核心计算。对于查询位置 $i$ 和键位置 $j$,注意力分数是它们的点积:
$$\omega_{ij} = q^{(i)} \cdot k^{(j)}$$用矩阵形式一次性计算所有位置的注意力分数:
$$\Omega = QK^T$$这里 $\Omega \in \mathbb{R}^{n \times n}$,其中 $\Omega_{ij}$ 表示位置 $i$ 对位置 $j$ 的"关注程度"(归一化前)。
# 计算注意力分数
scores = torch.matmul(Q, K.transpose(-2, -1)) # [1, 10, 10]
flowchart TB
subgraph 注意力分数计算
Q["Query<br/>[n, dk]"]
K["Key<br/>[n, dk]"]
KT["K^T<br/>[dk, n]"]
MM["矩阵乘法<br/>Q × K^T"]
Scores["注意力分数<br/>[n, n]"]
end
Q --> MM
K --> KT --> MM --> Scores
第四步:缩放因子 $\sqrt{d_k}$
在应用Softmax之前,需要将注意力分数除以 $\sqrt{d_k}$:
$$\Omega_{scaled} = \frac{QK^T}{\sqrt{d_k}}$$为什么需要这个缩放?
假设 $Q$ 和 $K$ 的元素是均值为0、方差为1的独立随机变量。两个 $d_k$ 维向量的点积:
$$q \cdot k = \sum_{i=1}^{d_k} q_i k_i$$其方差为 $d_k$(每个 $q_i k_i$ 的方差为1,求和后方差累加)。当 $d_k$ 很大时(如512),点积的绝对值会很大。
Softmax函数对输入的幅度非常敏感:
$$\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}$$当输入值很大时,Softmax会进入饱和区,输出趋近于one-hot向量,梯度趋近于零。这会导致训练困难。
除以 $\sqrt{d_k}$ 将方差归一化回1,使Softmax的输入保持在合理的范围内。
import math
d_k = Q.size(-1)
scaled_scores = scores / math.sqrt(d_k)
flowchart LR
subgraph 缩放的必要性
A["原始点积<br/>方差=dk"]
B["Softmax"]
C["问题:<br/>饱和区/梯度消失"]
D["÷ √dk<br/>方差=1"]
E["稳定训练"]
end
A --> B --> C
A --> D --> E
第五步:应用注意力掩码(可选)
在某些场景下,需要阻止某些位置的注意力:
填充掩码(Padding Mask):序列被填充到相同长度时,填充位置不应参与注意力计算。
因果掩码(Causal Mask):在自回归生成中,位置 $i$ 不应看到位置 $i+1, i+2, ...$ 的信息。
掩码通过将对应位置的分数设为 $-\infty$ 来实现(Softmax后这些位置的权重为0):
# 因果掩码示例(下三角矩阵)
seq_len = 10
causal_mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1).bool()
scaled_scores = scaled_scores.masked_fill(causal_mask, float('-inf'))
flowchart TB
subgraph 因果掩码示例
M["掩码矩阵<br/>上三角为-inf"]
S1["原始分数<br/>[n, n]"]
S2["掩码后分数<br/>[n, n]"]
Note["位置i只能<br/>看到位置1..i"]
end
M --> S2
S1 --> S2
S2 --> Note
第六步:Softmax归一化
将缩放后的分数转换为概率分布:
$$A = \text{softmax}(\Omega_{scaled})$$每一行表示一个查询位置对所有键位置的注意力权重,和为1。
attention_weights = torch.softmax(scaled_scores, dim=-1)
第七步:加权求和
最后,用注意力权重对Value向量进行加权求和:
$$\text{Output}^{(i)} = \sum_{j=1}^{n} A_{ij} v^{(j)}$$矩阵形式:
$$\text{Output} = AV$$output = torch.matmul(attention_weights, V) # [1, 10, 64]
完整公式
将上述步骤合并,得到Self-Attention的完整公式:
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$flowchart LR
subgraph Input
X[输入序列 X]
end
subgraph Projections
WQ[W^Q]
WK[W^K]
WV[W^V]
Q[Query Q]
K[Key K]
V[Value V]
end
subgraph Attention
MM[矩阵乘法 QK^T]
Scale[缩放 ÷√dk]
Mask[掩码可选]
SM[Softmax]
Weighted[加权求和 AV]
end
subgraph Output
Out[输出]
end
X --> WQ --> Q
X --> WK --> K
X --> WV --> V
Q --> MM
K --> MM
MM --> Scale --> Mask --> SM
V --> Weighted
SM --> Weighted --> Out
多头注意力:并行处理多种关系
单个Self-Attention可能无法同时捕捉序列中的多种关系。比如,一个句子中可能同时存在语法依赖、语义关联、指代关系等不同类型的联系。
多头注意力(Multi-Head Attention)通过并行运行多个独立的Self-Attention来解决这个问题:
$$\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, ..., \text{head}_h)W^O$$其中:
$$\text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)$$每个"头"有自己独立的投影矩阵 $W_i^Q, W_i^K, W_i^V$,可以学习关注不同类型的关系。
flowchart TB
subgraph Input
X[输入序列]
end
subgraph Heads
H1[Head 1: 语法关系]
H2[Head 2: 语义关联]
H3[Head 3: 指代关系]
H4[Head h: ...]
end
subgraph Outputs
O1[输出1]
O2[输出2]
O3[输出3]
O4[输出h]
end
subgraph Final
Concat[拼接]
Proj[线性投影 W^O]
Result[最终输出]
end
X --> H1 --> O1
X --> H2 --> O2
X --> H3 --> O3
X --> H4 --> O4
O1 --> Concat
O2 --> Concat
O3 --> Concat
O4 --> Concat
Concat --> Proj --> Result
为什么是"头分割"而不是"完整投影"?
在实现中,通常将 $d_{model}$ 维的输入投影到 $d_{model}$ 维,然后分割成 $h$ 个头,每个头维度为 $d_k = d_{model}/h$:
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.head_dim = d_model // num_heads
# 单个大投影矩阵,后续分割成多个头
self.qkv_proj = nn.Linear(d_model, 3 * d_model)
self.out_proj = nn.Linear(d_model, d_model)
def forward(self, x, mask=None):
batch_size, seq_len, _ = x.size()
# 投影并分割成Q, K, V
qkv = self.qkv_proj(x)
qkv = qkv.reshape(batch_size, seq_len, 3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4) # [3, batch, heads, seq, head_dim]
Q, K, V = qkv[0], qkv[1], qkv[2]
# Scaled Dot-Product Attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.head_dim)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attention = torch.softmax(scores, dim=-1)
# 加权求和
output = torch.matmul(attention, V)
# 拼接所有头
output = output.transpose(1, 2).contiguous()
output = output.view(batch_size, seq_len, self.d_model)
# 最终投影
return self.out_proj(output)
这种设计有一个重要优势:参数量和计算量与单头注意力相同。每个头的维度减少了 $h$ 倍,但头的数量增加了 $h$ 倍,总体保持平衡。
多头注意力的几何直觉
可以把多头注意力理解为:在多个不同的"表示子空间"中并行计算注意力。每个头专注于不同类型的关系:
- 某个头可能专注于相邻词之间的语法关系
- 另一个头可能捕捉长距离的语义关联
- 还有一个头可能关注指代消解
这种多样化的注意力模式是Transformer强大表达能力的重要来源。
残差连接与层归一化:稳定训练的关键设计
Transformer的每个子层(Self-Attention和Feed-Forward)都被包裹在残差连接和层归一化中:
$$\text{Output} = \text{LayerNorm}(x + \text{Sublayer}(x))$$flowchart TB
subgraph Transformer子层结构
X[输入 x]
Sub[子层计算<br/>Self-Attention/FFN]
Drop[Dropout]
Add[残差相加]
LN[Layer Normalization]
Out[输出]
end
X --> Sub --> Drop --> Add --> LN --> Out
X -.->|"恒等映射"| Add
残差连接的作用
残差连接(Skip Connection)最初在ResNet中提出,在Transformer中同样关键:
梯度流动:深层网络面临梯度消失问题。残差连接提供了一条"高速公路",梯度可以直接流向浅层。对于 $L$ 层网络,梯度只需经过 $L$ 次矩阵乘法,而不是 $L$ 次连乘。
信息保留:Self-Attention是位置不变的,位置信息只能通过位置编码注入。残差连接确保原始位置信息不会在第一层就丢失。
训练稳定性:残差连接使每层只需学习"残差"——对输入的修改,而非全新的表示。这降低了学习难度。
层归一化的作用
Batch Normalization在NLP任务中表现不佳,因为:
- 不同句子的词分布差异大
- Batch size通常较小(受GPU内存限制)
- 长序列中稀有词的特征方差更大
Layer Normalization在每个样本内部进行归一化:
$$\text{LN}(x) = \gamma \cdot \frac{x - \mu}{\sigma + \epsilon} + \beta$$其中 $\mu$ 和 $\sigma$ 是该样本在特征维度上的均值和标准差,$\gamma$ 和 $\beta$ 是可学习的缩放和偏移参数。
Layer Normalization确保每个位置的表示在相似的数值范围内,加速训练并提供轻微的正则化效果。
Pre-Norm vs Post-Norm
原始Transformer使用Post-Norm:先计算子层,再加残差,最后归一化。现代大模型(如GPT、LLaMA)普遍使用Pre-Norm:先归一化,再计算子层,最后加残差。
Pre-Norm的优势:
- 训练更稳定,不需要学习率预热
- 梯度流动更平滑
- 可以堆叠更多层
# Post-Norm (原始Transformer)
x = x + dropout(sublayer(layer_norm(x)))
# Pre-Norm (现代大模型)
x = x + dropout(sublayer(layer_norm(x)))
注意力掩码:控制信息流动
注意力掩码是Transformer中一个容易被忽视但极其重要的机制。
flowchart TB
subgraph 掩码类型
Padding["填充掩码<br/>Padding Mask"]
Causal["因果掩码<br/>Causal Mask"]
Combined["组合掩码"]
end
subgraph 应用场景
P1["变长序列处理"]
C1["自回归生成"]
C2["GPT/LLaMA推理"]
end
Padding --> P1
Causal --> C1
Padding --> Combined
Causal --> Combined --> C2
填充掩码(Padding Mask)
变长序列需要填充到相同长度。填充位置不应参与注意力计算:
def create_padding_mask(seq, pad_token=0):
"""seq: [batch_size, seq_len]"""
return (seq != pad_token).unsqueeze(1).unsqueeze(2)
# 返回 [batch_size, 1, 1, seq_len]
因果掩码(Causal Mask)
自回归生成要求:位置 $i$ 只能看到位置 $1, 2, ..., i$:
def create_causal_mask(seq_len):
"""创建下三角掩码矩阵"""
mask = torch.tril(torch.ones(seq_len, seq_len))
return mask.unsqueeze(0).unsqueeze(0) # [1, 1, seq_len, seq_len]
因果掩码确保了:
- 训练时:使用完整的序列,但每个位置只能看到之前的信息
- 推理时:生成是自回归的,每个新token只依赖于之前生成的token
掩码的实现细节
掩码在Softmax之前应用,将需要屏蔽的位置设为 $-\infty$:
scores = scores.masked_fill(mask == 0, float('-inf'))
Softmax后,这些位置的权重为:
$$\text{softmax}(-\infty) = \frac{e^{-\infty}}{\sum_j e^{x_j}} = 0$$复杂度分析:Self-Attention的代价
时间复杂度
Self-Attention的时间复杂度是 $O(n^2 \cdot d)$:
- $QK^T$:$n \times d_k$ 和 $d_k \times n$ 的矩阵乘法 → $O(n^2 \cdot d_k)$
- Softmax:对 $n \times n$ 矩阵的每行归一化 → $O(n^2)$
- $AV$:$n \times n$ 和 $n \times d_v$ 的矩阵乘法 → $O(n^2 \cdot d_v)$
当序列长度 $n$ 远大于嵌入维度 $d$ 时,$O(n^2)$ 成为瓶颈。
空间复杂度
存储注意力矩阵 $A \in \mathbb{R}^{n \times n}$ 需要 $O(n^2)$ 空间。对于长序列(如 $n = 100,000$),这个矩阵需要约 40GB内存(float32)。
flowchart LR
subgraph 复杂度对比
A["Self-Attention<br/>O(n² × d)"]
B["RNN<br/>O(n × d²)"]
C["CNN<br/>O(k × n × d)"]
end
subgraph 适用场景
D["短序列<br/>n < d"]
E["长序列<br/>n > d"]
end
A --> D
B --> E
C --> E
优化方向
FlashAttention通过分块计算和重计算策略,将注意力矩阵的内存需求从 $O(n^2)$ 降到 $O(n)$,同时保持相同的计算复杂度。这是目前处理长序列的标准方案。
稀疏注意力、线性注意力等方法尝试将复杂度降到 $O(n)$ 或 $O(n \log n)$,但通常会牺牲一定的模型性能。
Self-Attention的反向传播
理解Self-Attention的反向传播对于调试和优化至关重要。
flowchart TB
subgraph 前向传播
X[输入 X] --> QKV[Q, K, V]
QKV --> Scores[注意力分数]
Scores --> Attn[注意力权重 A]
Attn --> Out[输出 Y=AV]
end
subgraph 反向传播
dL_dY["∂L/∂Y"]
dL_dA["∂L/∂A = ∂L/∂Y · V^T"]
dL_dV["∂L/∂V = A^T · ∂L/∂Y"]
dL_dScores["∂L/∂Scores<br/>(Softmax梯度)"]
dL_dQKV["∂L/∂Q, ∂L/∂K"]
dL_dX["∂L/∂X"]
end
dL_dY --> dL_dA
dL_dA --> dL_dScores
dL_dScores --> dL_dQKV --> dL_dX
dL_dY --> dL_dV --> dL_dX
注意力权重的梯度
设损失函数为 $L$,输出为 $Y = AV$。根据链式法则:
$$\frac{\partial L}{\partial A} = \frac{\partial L}{\partial Y} \cdot V^T$$Softmax的梯度
Softmax的雅可比矩阵是对角占优的,梯度计算需要考虑所有输出之间的耦合:
$$\frac{\partial L}{\partial \omega_i} = \sum_j \frac{\partial L}{\partial a_j} \cdot a_j (\delta_{ij} - a_i)$$其中 $\delta_{ij}$ 是Kronecker delta,$a_j$ 是Softmax输出。
Q、K、V的梯度
$$\frac{\partial L}{\partial Q} = \frac{\partial L}{\partial \Omega} \cdot K / \sqrt{d_k}$$$$\frac{\partial L}{\partial K} = Q^T \cdot \frac{\partial L}{\partial \Omega} / \sqrt{d_k}$$
$$\frac{\partial L}{\partial V} = A^T \cdot \frac{\partial L}{\partial Y}$$
梯度流动的关键观察:$Q$ 和 $K$ 的梯度来自注意力分数,$V$ 的梯度来自最终输出。这种分离使得模型能够独立学习"关注什么"和"如何使用关注的信息"。
常见面试问题与误区
问题一:为什么使用点积而不是其他相似度度量?
点积有两个优势:
- 计算效率:点积可以通过高度优化的矩阵乘法实现
- 可学习性:通过 $W^Q$ 和 $W^K$ 的学习,点积可以隐式地学习复杂的相似度函数
原始论文还尝试了加性注意力(Additive Attention),但在实践中点积注意力的性能相当且计算更快。
问题二:为什么Query和Key的维度相同?
点积要求两个向量维度相同。但Value的维度可以不同——它决定了输出的维度。
在实际实现中,通常设置 $d_k = d_v = d_{model}/h$,但这不是必须的。
问题三:Self-Attention和Cross-Attention有什么区别?
- Self-Attention:$Q = K = V = X$,同一序列内部的注意力
- Cross-Attention:$Q$ 来自一个序列,$K, V$ 来自另一个序列,用于编码器-解码器架构
Cross-Attention允许解码器"查询"编码器的输出。
flowchart TB
subgraph Self-Attention
X1[序列 X] --> Q1[Q]
X1 --> K1[K]
X1 --> V1[V]
Q1 --> Attn1[注意力计算]
K1 --> Attn1
V1 --> Attn1
end
subgraph Cross-Attention
X2[解码器输出] --> Q2[Q]
X3[编码器输出] --> K2[K]
X3 --> V2[V]
Q2 --> Attn2[注意力计算]
K2 --> Attn2
V2 --> Attn2
end
问题四:注意力权重可以直接解释吗?
这是一个常见的误解。注意力权重表示"模型关注了什么",但不一定表示"模型为什么关注它"。
2020年的论文《Attention is not Explanation》表明:不同的注意力分布可能产生相同的预测结果。注意力权重更多是模型内部的计算中间结果,而非人类可理解的决策解释。
问题五:为什么需要缩放因子?
面试中常见的回答是"防止点积过大"。更精确的解释涉及统计特性:
假设 $q_i, k_i \sim N(0, 1)$ 独立同分布,则 $q \cdot k = \sum_{i=1}^{d_k} q_i k_i$ 的方差为 $d_k$。
标准差为 $\sqrt{d_k}$,所以除以 $\sqrt{d_k}$ 将点积的方差归一化为1。
问题六:多头注意力和单头注意力参数量相同吗?
是的。如果单头注意力的投影矩阵维度为 $d \times d$,则 $h$ 头注意力的每个头维度为 $d/h$,参数量为 $h \times d \times (d/h) = d^2$。
计算量也相同,因为每个头的计算量减少,但头数增加。
从理论到实践:调试Self-Attention
常见问题诊断
注意力权重全为均匀分布:可能的原因
- 学习率过大导致训练不稳定
- 输入嵌入未正确归一化
- 缩放因子被错误移除
某些头总是产生相同的注意力模式:可能的原因
- 初始化问题
- 正则化不足
- 数据中的偏差
梯度消失或爆炸:检查
- 残差连接是否正确实现
- LayerNorm是否应用在正确的位置
- 是否使用了合适的初始化方法
实用调试技巧
# 可视化注意力权重
def visualize_attention(attention_weights, tokens):
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(10, 8))
sns.heatmap(attention_weights,
xticklabels=tokens,
yticklabels=tokens,
cmap='viridis')
plt.xlabel('Keys')
plt.ylabel('Queries')
plt.show()
# 检查注意力分布的熵
def check_attention_entropy(attention_weights):
entropy = -torch.sum(attention_weights * torch.log(attention_weights + 1e-9), dim=-1)
print(f"平均熵: {entropy.mean():.4f}, 最大可能熵: {math.log(attention_weights.size(-1)):.4f}")
总结
Self-Attention是Transformer架构的核心,其计算流程可以概括为七个步骤:
- 输入嵌入与位置编码
- 计算Query、Key、Value投影
- 计算注意力分数 $QK^T$
- 缩放 $\frac{1}{\sqrt{d_k}}$
- 应用掩码(可选)
- Softmax归一化
- 加权求和得到输出
多头注意力通过并行处理多种关系增强了模型的表达能力。残差连接和层归一化确保了深层网络的训练稳定性。注意力掩码控制了信息流动,支持填充处理和自回归生成。
理解这些机制不仅是掌握Transformer的关键,也是深入理解现代大语言模型工作原理的基础。无论是面试准备还是实际工程开发,对Self-Attention的深入理解都会带来显著的优势。
参考文献
- Vaswani, A., et al. “Attention Is All You Need.” NeurIPS 2017.
- Devlin, J., et al. “BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.” NAACL 2019.
- Radford, A., et al. “Language Models are Unsupervised Multitask Learners.” OpenAI 2019.
- Dao, T., et al. “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness.” NeurIPS 2022.
- Joulin, A. “Gradient Flow in Transformers.” arXiv 2024.
- Tay, Y., et al. “Efficient Transformers: A Survey.” ACM Computing Surveys 2022.
- Bahdanau, D., et al. “Neural Machine Translation by Jointly Learning to Align and Translate.” ICLR 2015.
- He, K., et al. “Deep Residual Learning for Image Recognition.” CVPR 2016.
- Ba, J.L., et al. “Layer Normalization.” arXiv 2016.
- Jain, S., et al. “Attention is not Explanation.” NAACL 2019.