Skip to content

Self-Attention 是什么

Self-Attention 是 Transformer 的核心机制。

它的作用可以先用一句话理解:

text
让序列中的每个位置,根据当前内容,动态决定应该参考哪些其他位置。

经典的 Attention 公式是:

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q, K, V) = softmax\left(\frac{QK^T}{\sqrt{d_k}}\right)V

这个公式可以先粗略拆成三步:

  • QKTQK^T:计算每个位置和其他位置的匹配程度。
  • softmaxsoftmax:把匹配分数变成注意力权重。
  • 乘以 VV:按照注意力权重读取并汇总信息。

后面的小节会逐步解释这里的 Q、K、V、softmax 和因果 mask。

在 Decoder-only 模型里,它通常还会加上因果限制:只能看自己和前面的 token,不能看未来 token。

为什么需要 Self-Attention

假设输入是:

text
一只 / 蓝色 / 怪兽 / 走进 / 森林

如果只看每个 token 自己:

text
一只:数量
蓝色:颜色
怪兽:角色或生物
走进:动作
森林:地点

模型很难理解它们合起来是:

text
一只蓝色怪兽走进森林

Self-Attention 的作用,就是让每个位置可以参考前文中相关的 token。比如“怪兽”可以参考“一只”和“蓝色”,“走进”可以参考“怪兽”,“森林”可以参考“走进”和前面的主角,从而形成更完整的上下文状态。

输入输出形状

假设输入有 10 个 token,每个 token 是 128 维向量:

text
输入:10×128

Self-Attention 输出通常仍然是:

text
输出:10×128

形状不变,但每一行向量都可能融合了其他位置的信息。

在 Decoder-only 模型中,第 (i) 个位置只能融合第 (0) 到第 (i) 个位置的信息。

Q、K、V 是什么

Self-Attention 会先把每个输入向量变成三种向量:

text
Q:Query,表示“我想找什么信息”
K:Key,表示“我这里有什么信息可被匹配”
V:Value,表示“如果别人关注我,我实际提供什么内容”

可以用一个查资料的比喻:

text
Query:我提出的问题
Key:每份资料的标签
Value:资料的正文内容

某个位置会拿自己的 Query 去和其他位置的 Key 比较,看哪些位置更相关,然后再按相关程度读取那些位置的 Value。

Q/K/V 如何得到

Q、K、V 通常由输入矩阵 (X) 通过线性变换得到:

Q=XWQQ = XW_Q

K=XWKK = XW_K

V=XWVV = XW_V

其中:

text
X:输入向量矩阵
W_Q, W_K, W_V:模型训练出来的权重矩阵

如果输入是 10×12810 \times 128,并且 Q/K/V 维度也是 128,那么:

text
X:10×128
Q:10×128
K:10×128
V:10×128

真实模型里常常会把这些向量拆成多个 head,也就是 Multi-Head Attention。这里先只看单个 head 的直觉。

注意力分数:Query 和 Key 的匹配程度

有了 Q 和 K 后,模型会计算每个位置对其他位置的关注程度。

常见做法是点积:

scoreij=QiKjscore_{ij} = Q_i \cdot K_j

含义是:

text
第 i 个位置的 Query
和第 j 个位置的 Key
有多匹配

所有位置两两比较后,会得到一个分数矩阵:

text
10×10

比如第 10 行表示:

text
第 10 个位置分别想关注第 1 到第 10 个位置的程度。

用“一只 / 蓝色 / 怪兽 / 走进 / 森林”做一个例子,分数矩阵可能长这样:

Query \ Key一只蓝色怪兽走进森林
一只2.4-0.60.2-1.1-0.8
蓝色0.82.71.9-0.40.1
怪兽1.43.13.50.20.4
走进0.50.73.42.91.8
森林0.20.62.33.63.0

这里每一行表示“当前位置想关注其他位置的程度”。

比如最后一行是 Query: 森林,它表示“森林”这个位置在看前文时,对几个位置的匹配分数:

text
和“一只”的匹配分数:0.2
和“蓝色”的匹配分数:0.6
和“怪兽”的匹配分数:2.3
和“走进”的匹配分数:3.6
和“森林”自己的匹配分数:3.0

分数越高,说明当前位置越倾向于从那个位置读取信息。这个例子里,“森林”比较关注“走进”和自己,也会关注“怪兽”,因为“谁走进了森林”对当前位置很重要。

缩放和 softmax

实际计算中,attention 分数通常会除以一个缩放因子:

score=QKTdkscore = \frac{QK^T}{\sqrt{d_k}}

其中 (d_k) 是 Key 向量的维度。

缩放的目的是让分数范围更稳定,避免点积结果太大。

然后模型会对分数做 softmax:

weights=softmax(score)weights = softmax(score)

softmax 会把分数变成概率一样的权重。

比如某个位置可能得到:

text
关注“怪兽”:25%
关注“走进”:45%
关注“森林”:30%

这些权重表示:当前位置应该从哪些 token 那里读更多信息。

加权求和:读取 Value

得到 attention weights 后,模型会用这些权重去加权求和 Value:

output=weightsVoutput = weights \cdot V

直观理解:

text
更相关的位置,Value 占比更高。
不相关的位置,Value 占比更低。

对于“一只 / 蓝色 / 怪兽 / 走进 / 森林”来说,“森林”这个位置可能会比较关注:

text
怪兽
走进
森林

于是它的新向量会融合这些位置的 Value 信息,形成更完整的“怪兽走进森林”这一上下文状态。

因果 Mask:不能看未来

Decoder-only 模型生成文本时,不能偷看未来 token。

如果输入是:

text
t0, t1, t2, t3

第 2 个位置只能看:

text
t0, t1, t2

不能看:

text
t3

这通常通过 causal mask 实现。

可以把允许看的范围写成:

text
第 0 个位置:t0
第 1 个位置:t0, t1
第 2 个位置:t0, t1, t2
第 3 个位置:t0, t1, t2, t3

继续用“一只 / 蓝色 / 怪兽 / 走进 / 森林”的例子,因果 mask 可以画成这样:

Query \ Key一只蓝色怪兽走进森林
一只10000
蓝色11000
怪兽11100
走进11110
森林11111

也就是说:

text
1 表示可以看。
0 表示不能看。

“一只”这个位置只能看自己。
“蓝色”这个位置可以看“一只”和自己。
“怪兽”这个位置可以看“一只”“蓝色”和自己。
“走进”这个位置可以看“一只”“蓝色”“怪兽”和自己。
“森林”这个位置可以看前面全部 token 和自己。

需要注意:这个 1/0 表格只是表示“可见性”。在真正计算 attention 时,通常不是简单把不能看的分数改成 0,而是把不能看的位置改成 -inf 或一个非常小的数,比如 -1e9

原因是后面要经过 softmax。如果把不能看的位置设成 0,它仍然可能分到概率:

text
scores = [2.0, 0.0, 0.0]
softmax(scores) ≈ [0.79, 0.10, 0.10]

这两个 0 分位置仍然有注意力权重。

如果把不能看的位置设成 -inf

text
scores = [2.0, -inf, -inf]
softmax(scores) = [1.0, 0.0, 0.0]

这样不能看的位置经过 softmax 后,注意力权重才会真正变成 0。

这样模型训练和推理时都符合“从左到右生成”的规则。

小结

Self-Attention 的流程可以概括为:

text
输入向量
-> 生成 Q / K / V
-> 用 Q 和 K 计算匹配分数
-> softmax 得到注意力权重
-> 用权重加权求和 V
-> 输出融合上下文后的向量

它的核心价值是:

text
让每个位置动态选择应该参考哪些 token。

在 Decoder-only 模型中,它还会受到因果 mask 限制,只能看自己和前面的 token。