Transformer拆解06:Self-Attention,一句话里的词怎样互相看见

Transformer拆解06:Self-Attention,一句话里的词怎样互相看见

我是老计。前面三篇把Embedding、QKV和缩放点积公式分别拆开了。现在把它们装起来,回答注意力机制最核心的问题:同一条序列里的每个位置,怎样根据自己的需要,从其他位置取回信息?

这就是Self-Attention,自注意力。

Self表示查询、键和值来自同一组序列表示。它不表示每个位置只看自己,不表示Q、K、V数值相同,也不表示位置之间的关注关系一定对称。

本篇用一个三位置小矩阵走完整个前向过程。数字是人为构造的教学示例,不代表训练后某个真实语言模型一定产生相同权重。

一,Self-Attention的输入和输出

设输入序列经过Embedding和位置表示后为:

text 复制代码
X:[B, S, d_model]

自注意力用同一份X生成:

text 复制代码
Q = XW_Q
K = XW_K
V = XW_V

然后计算:

text 复制代码
A = Softmax(QK^T / sqrt(d_k))
Y = AV

如果不拆多头,形状是:

text 复制代码
Q、K:[B, S, d_k]
V:[B, S, d_v]
A:[B, S, S]
Y:[B, S, d_v]

矩阵 A 的每一行对应一个查询位置,每一列对应一个被查询的键位置。第 i 行表示位置 i 如何在所有允许位置之间分配注意力。

二,手算一个三位置例子

为了让数字可追踪,取3个位置、每个输入3维,并投影到2维Q、K、V:

text 复制代码
X = [[1,0,1],
     [0,1,1],
     [1,1,0]]

投影矩阵选为固定小矩阵。完整NumPy代码如下:

python 复制代码
import numpy as np

np.set_printoptions(precision=4, suppress=True)
X = np.array([[1., 0., 1.],
              [0., 1., 1.],
              [1., 1., 0.]])
WQ = np.array([[1., 0.], [0., 1.], [1., 1.]])
WK = np.array([[1., 1.], [1., 0.], [0., 1.]])
WV = np.array([[1., 0.], [0., 1.], [1., -1.]])

Q, K, V = X @ WQ, X @ WK, X @ WV
scores = Q @ K.T / np.sqrt(Q.shape[-1])
stable = scores - scores.max(axis=-1, keepdims=True)
A = np.exp(stable)
A /= A.sum(axis=-1, keepdims=True)
Y = A @ V

print("attention:\n", A)
print("row sums:", A.sum(axis=-1))
print("output:\n", Y)

运行环境:Python 3,NumPy 1.26.4,CPU。实际输出:

text 复制代码
attention:
 [[0.284  0.14   0.576 ]
 [0.576  0.14   0.284 ]
 [0.4011 0.1978 0.4011]]
row sums: [1. 1. 1.]
output:
 [[ 1.284   0.292 ]
 [ 1.576  -0.292 ]
 [ 1.4011  0.    ]]

第一行到底在说什么

第一行 [0.284, 0.14, 0.576] 属于第一个查询位置。它表示第一个位置从三个V来源取信息时,分别使用约0.284、0.14、0.576的系数。

因此第一个输出为:

text 复制代码
Y_1 = 0.284 V_1 + 0.14 V_2 + 0.576 V_3

它不是选中某一个位置后复制,而是做软加权汇总。第二行和第三行分别属于第二、第三个查询,彼此独立归一化。

每行和为1不代表每列和为1

Softmax沿键位置轴做,因此每一行和为1。列和没有必须等于1的约束。多个查询可以同时高度关注同一个键,也可能几乎没人关注某个键。

这个方向不能反。如果沿查询轴归一化,矩阵可能出现列和为1,但语义变成每个键在不同查询之间分配份额,不是标准自注意力的计算。

三,一个Token怎样"看见"上下文

假设序列是"服务发生严重故障"。某一层中,"故障"位置的Q可以与所有允许位置的K计算分数;"严重"的K若与它兼容,权重会较高;最终"故障"位置把"严重"等位置的V信息混入自己的新表示。

这只是直觉描述。真实模型中的Token切分、投影维度和头功能不会如此整齐,也不能仅凭某个注意力头的高权重,断言模型形成了完整语法关系。

表示是逐层更新的

第1层的QKV来自输入表示,第2层来自第1层输出。一个位置在前一层汇总上下文后,下一层用更新后的表示再次生成QKV。因此多层Transformer不是反复使用一张固定注意力图,而是逐层重新判断关系。

残差连接还会把子层输入沿主路径保留下来。最终表示既包含注意力汇总,也包含原表示和前馈网络加工,不能只用一张A矩阵解释全部输出。

注意力矩阵描述的是某一层、某一头、某次前向中V怎样被加权,不是整台模型的完整思考过程。

四,没有位置表示时,顺序从哪里来

纯自注意力只根据输入内容做相同的线性投影和位置间匹配。如果把输入位置整体重新排列,输出会按同样顺序重新排列。这叫排列等变性。

设排列矩阵为P,忽略Mask:

text 复制代码
Attention(PX) = P Attention(X)

直觉是:交换输入行,会同步交换Q、K、V的行,关系矩阵的行列一起重排,最后输出也跟着重排。模型知道"这些元素怎样互相关联",却没有额外依据区分原先谁在第1位。

用NumPy验证排列等变

python 复制代码
import numpy as np

rng = np.random.default_rng(67)
X = rng.normal(size=(4, 3))
WQ = rng.normal(size=(3, 2))
WK = rng.normal(size=(3, 2))
WV = rng.normal(size=(3, 2))

def attention(x):
    q, k, v = x @ WQ, x @ WK, x @ WV
    scores = q @ k.T / np.sqrt(q.shape[-1])
    scores -= scores.max(axis=-1, keepdims=True)
    weights = np.exp(scores)
    weights /= weights.sum(axis=-1, keepdims=True)
    return weights @ v

perm = np.array([2, 0, 3, 1])
y = attention(X)
y_perm = attention(X[perm])
print("max permutation error:", np.max(np.abs(y_perm - y[perm])))
print("output shape:", y.shape)

实际输出:

text 复制代码
max permutation error: 1.1102230246251565e-16
output shape: (4, 2)

误差处在浮点舍入量级,说明输出只是跟随输入排列。加入绝对位置编码后,若只排列Token而不同时排列位置表示,等变关系会被打破;RoPE则通过QK关系注入相对位置信息。第09篇会细讲。

五,双向自注意力、因果自注意力与Padding

Self-Attention只说明QKV同源,不决定哪些位置允许互相看见。可见范围由Mask和任务定义。

Encoder式双向自注意力

没有因果限制时,每个有效查询可以读取整条输入的有效位置,包括左边和右边。BERT类Encoder常用这种结构,适合理解完整输入。

"可以看见"不等于"权重一定很高"。Mask只规定合法连接,具体权重仍由QK分数决定。

Decoder式因果自注意力

GPT类Decoder要求位置 t 只能读取不晚于 t 的位置。未来位置在Softmax前被Mask成负无穷。训练时整段输入可并行送入,但每行可见范围不同;推理时未来Token尚不存在,仍需逐步生成。

Padding Mask

批次中较短序列补齐后,真实查询不应从Padding键位置取信息。Padding Mask通常遮挡键列。是否还要处理Padding查询行和损失位置,要结合框架实现和任务,不能只看注意力权重一层。

这三种情况可以同时出现。例如Decoder批次既需要Causal Mask防未来,又需要Padding Mask忽略补齐键。

六,自注意力矩阵为什么不必对称

虽然QKV同源,W_Q 和 W_K 通常不同,因此:

text 复制代码
q_i k_j^T generally differs from q_j k_i^T

即使未缩放分数偶然对称,逐行Softmax的归一化分母也可能不同,最终权重不保证对称。

语言关系本来也常有方向。"它"关注前面的实体来确定指代,不要求那个实体以同样强度反向关注"它"。

此外,Causal Mask会显式打破双向连接,未来位置对当前不可见,而后面位置可以看前面,更不可能对称。

七,自注意力的计算与存储成本

标准全局自注意力需要构造或等价计算 S×S 关系。单头分数张量形状为 [B, S, S],多头为 [B, H, S, S]。

序列长度翻倍,关系元素数量变为4倍。训练时还可能保存权重或中间量供反向传播,显存压力明显。现代融合内核可以减少中间数据写回,但标准全局注意力的两两关系算术规模仍是二次的。

自回归推理时,每个新Token只产生一个新查询,对历史所有键做匹配,单步关系数量随历史长度线性增长;生成整个长度的累计工作仍会增长。KV Cache避免重复计算历史K/V,却不取消当前查询读取全部历史键值的需要。

八,从关系图理解可见范围

可以把自注意力看成一张有向图:每个Token位置是节点,允许关注的方向是一条边,注意力权重是边上的动态系数。

双向图、因果图和Padding删边

  • 双向Encoder在有效位置之间通常接近全连接有向图;
  • 因果Decoder只保留从当前位置指向自身和过去的边;
  • Padding Mask删除所有有效查询指向Padding键的边;
  • 局部注意力只保留窗口内边;
  • 稀疏注意力按规则保留部分远距离边。

这个视角能把两个概念分开:Mask决定边存不存在,QK和Softmax决定现有边权重多大。 Mask是硬约束,权重是输入相关的软选择。

如果一条边被Mask,不管QK分数原本多高,它都不应参与汇总;如果边被允许,也不代表模型一定关注它,权重仍可能接近0。

多层如何扩大信息传播

在全局自注意力中,一层就允许任意位置直接交换信息。局部或稀疏注意力中,一层只能沿有限边传播,但堆叠多层后,信息可经过中间节点到达更远位置。

因此讨论"上下文窗口"时,要区分理论可见边、层数传播路径和模型实际学到的有效依赖。一个位置在Mask上可见,不等于模型一定充分利用;不可见则是计算图上明确无法直接读取。

九,注意力输出还不是Block最终输出

单头Self-Attention得到 Y=AV 后,完整Transformer还会经历多头拼接、输出投影、Dropout、残差连接、归一化和前馈网络。不能把Y直接等同于这一层最终表示。

残差为什么影响解释

假设注意力子层输出为 F(X),残差路径会形成类似:

text 复制代码
X + F(X)

即使某个位置的注意力几乎不从自身V取信息,原始位置表示仍可通过残差主路径保留。于是"注意力权重里自身很低"不等于模型丢掉了自身内容。

同样,某个来源V权重高,只说明它对注意力分支贡献大。最终表示还包含残差与后续非线性加工。

批次和头各自独立归一化

带多头后权重形状为 [B,H,S,S]。每个批次、每个头、每个查询行分别做Softmax:

  • 不同样本之间不共享一张权重表;
  • 不同头之间不在同一次Softmax里竞争;
  • 一个头关注局部、另一个头关注远处是允许的;
  • 拼接后还要由输出投影混合各头信息。

第07篇会严格追踪这条形状链。

十,注意力矩阵是一张输入相关的混合表

Softmax后的注意力矩阵每行非负且和为1,可以看成一张行随机矩阵。左乘V时,每个输出位置都从允许的V行中做一次凸组合。

但这不等于整个Transformer只能"平均"已有信息。原因包括:

  • Q、K、V本身是学习到的线性投影,V已经改变了表示空间;
  • 多个头分别混合不同V子空间,拼接后再经过输出投影;
  • 残差把原表示加入结果;
  • 前馈网络提供逐位置非线性变换;
  • 多层堆叠会反复生成新的QKV和新的混合矩阵。

因此,单头的一次 AV 是加权平均,整台Transformer不是一个简单平均器。

注意力权重随输入变化

卷积核权重训练后固定,而注意力混合系数A会随当前输入变化。参数矩阵WQ、WK训练后固定,但不同句子产生不同Q和K,进而产生不同A。这种"参数固定、路由动态"的特点,是注意力能按内容选择信息的重要来源。

当然,动态不等于没有结构约束。Mask规定哪些边不能出现,位置表示影响位置关系,头维度和参数决定可表达空间,训练数据决定模型最终学到什么。

自注意力不是把所有词无差别混在一起,而是为每个输入、每层、每头动态生成一张受Mask约束的信息混合表。

十一,常见误区

误区一:Self表示只关注自己

Self表示QKV同源。没有Mask时,每个位置通常可以关注序列内所有位置。

误区二:Self表示Q等于K等于V

三者使用不同投影矩阵,数值通常不同。

误区三:注意力矩阵一定对称

QK角色、参数和逐行Softmax都不要求对称,因果Mask还会进一步制造方向性。

误区四:权重最高的位置就是唯一答案

输出通常是所有允许V的软加权和,多层残差和前馈还会继续加工。

误区五:自注意力天然知道顺序

纯内容自注意力具有排列等变性,需要位置编码、RoPE或其他机制注入顺序。

小结

Self-Attention让同一序列的每个位置生成Q、K、V。每个查询与所有允许键计算分数,沿键轴Softmax,再加权汇总V。注意力矩阵每行属于一个查询、行和为1,列和不受约束。

Self只表示QKV同源,不表示三者相同、只看自己或矩阵对称。没有位置表示和Mask时,自注意力对输入排列具有等变性;双向、因果和Padding可见范围由不同Mask决定。

排列等变也不等于模型不能处理集合。若任务本来不关心顺序,等变结构反而合理;只有任务语义依赖先后时,才必须引入位置或其他顺序信号。

下一篇把一个头扩展为多个头,严格追踪拆头、转置、并行注意力、拼接和输出投影的每一步形状。

延伸阅读

(本文为技术学习与经验分享,代码用于理解机制。框架与实现可能随版本变化,具体用法请以官方最新文档为准。)

版权声明:本文版权归作者老计个人所有。欢迎个人学习阅读,未经授权,禁止转载、摘编、搬运或用于任何商业用途,违者将追究责任。转载请联系作者Jich。

相关推荐
我喜欢就喜欢1 小时前
RAG 重排序 Rerank 实战:用 bge-reranker 把 Top-K 命中率拉回来
python·大模型·向量检索·rag·rerank
VIP_CQCRE2 小时前
在 VS Code 等编辑器里用 OpenCode 接入 Ace Data Cloud:Provider 配置与 IDE 验证
大模型·ai编程·vs code·opencode·ace data cloud
行走的陀螺仪2 小时前
Super-Agent 架构演进(下):解耦设计与 NestJS 工程落地
架构·大模型·agent
像风一样自由20201 天前
3.Dockerfile如何构建一个Python算法镜像
开发语言·人工智能·python·大模型
猿界零零七1 天前
Agent 开发面试题--Day002【Agent和Workflow的有什么区别】
人工智能·大模型
大鹏的NLP博客1 天前
从格赖斯合作原则看大语言模型与提示词工程
人工智能·自然语言处理·大模型·agent
寻道码路1 天前
AI Agent 工程化开发实战(五):持久化与断点恢复——Checkpointer 与 Store
人工智能·大模型·checkpoint·持久化·ai agent·langgraph·ai工程化
秋秋小事1 天前
transformer基础 RNN + LSTM
rnn·lstm·transformer
虎鲸不是鱼1 天前
AI 自动化逆向(三十六)先找不加密的部分
人工智能·自动化·大模型·逆向·ai逆向