【零基础学AI】第 26 章——注意力机制怎样工作

第 26 章 注意力机制怎样工作

文章目录

"小猫吃鱼,它饱了。"读到"它"时,前面的"小猫"提供了理解这个代词的重要信息。上一章把词向量取平均,每个词使用相同权重,许多差别会消失。注意力增加了一步,根据当前位置和其他位置的向量计算权重,再按权重汇总信息。

本章只算四个位置、两个维度。需要第 25 章的 Token、向量和余弦知识,以及前面数组与矩阵的基础。句子、切分和参数全是人工教学设定,没有训练模型。我们会逐项计算 Query、Key、Value,核对矩阵形状,代码和运行入口见GitHub仓库。

🌈 关于《AI 零基础 36 讲》

🧠 零基础也能开始学 AI

《AI 零基础 36 讲》是一套面向初学者的 AI 入门教程。课程从认识和使用 AI 开始,逐步学习

Python、数据处理、机器学习与深度学习,再进入大模型应用开发,最后完成个人知识库助手的综合实践。

📖 本篇是正式讲解文章

通过具体例子、原创图示和操作结果讲清概念。涉及编程的章节提供可运行代码,方便你边读边试。

🎯 读完以后,动手检验

建议按专栏顺序学习,并完成对应的课后训练,检查自己能否独立运用本章知识。

博客专栏:《AI 零基础 36 讲》

从一句话到四行输入

为缩小计算,把句子人为分成"小猫、吃鱼、它、饱了"四个 Token,暂时省去标点。输入 X 为四行四列,每个位置用一个不同的独热向量表示,即一行中一个位置为 1,其余为 0。

python 复制代码
import numpy as np
from examples.attention_core import load_data
data = load_data()
x = np.array(data["x"], dtype=float)
print(data["tokens"])
print(x)
print(x.shape)

输出为四个 Token、4×4 单位矩阵和 (4, 4)。行数是 Token 数,列数是输入特征维度。独热向量在这里仅区分位置对应的 Token,不包含学到的语义和额外位置编码。完整模型通常使用训练得到的表示,下一章再把各层连接起来。

一个输入为何要变成 Q、K、V 三份

Query 简写为 Q,是当前位置参与匹配的向量;Key 简写为 K,是每个候选位置用于匹配的向量;Value 简写为 V,是各位置将被汇总的信息向量。

它们来自不同的线性投影。投影是把原向量乘以一组参数矩阵,得到另一种表示。本章用 Q=XWq、K=XWk、V=XWv,三组参数均为 4×2,因此三份结果都是 4×2。这个机制与矩阵乘法直接相连,不需要把网络描述成有人在脑中挑选词语。

下面是第一个头的实际结果。一个头表示一套投影和一次注意力计算,后面再讨论多个头。

Token Q K V
小猫 1,0 2,0 1,0
吃鱼 0,1 0,1 0,1
它 2,0 0,0 0,0
饱了 0,1 1,1 1,1
python 复制代码
from examples.attention_core import load_data, run_heads
heads, concatenated, output = run_heads(load_data())
for name in ["q", "k", "v"]:
    print(name, heads[0][name].shape)
print(heads[0]["q"][2])

三份形状都为 (4, 2),第 2 行 Q 为 [2,0],行编号从 0 开始,正好对应"它"。因为 X 是单位矩阵,本章投影结果恰好等于参数矩阵的对应行,这使数值容易复核。真实输入不必是单位矩阵,Q、K、V 也不必相同。

Q 与 K 决定匹配权重,V 提供汇总内容。如果保持 Q 和 K 不变,仅把所有 V 乘以 2,权重不会变化,但汇总输出会乘以 2。这个差别解释了为什么三组名称不能随意互换。

把"它"这一行的匹配分数算出来

用"它"的 Q [2,0] 分别和四行 K 做点积,得到 [4,0,0,2]。比如"小猫"的分数为 2×2+0×0=4,"饱了"为 2×1+0×1=2。

本章采用缩放点积注意力。点积要除以 K 的维度的平方根,这里是根号 2,得到约 [2.828427,0,0,1.414214]。这是 Transformer 原论文的缩放点积规则。它与上一章的余弦不同,没有再除以 Q、K 各自长度。

缩放的目的涉及分数大小和 Softmax 的训练行为。维度增大时,点积的幅度在一定分布假设下也会增大,过大的分数差容易使 Softmax 集中到很少的位置。

分数怎样变成一组权重

Softmax 对各分数取指数,再除以同一行指数之和。输出为非负数,每行总和为 1。本章"它"这一行的结果如下。

K、V 位置 原点积 缩放后分数 权重
小猫 4 约 2.828427 约 0.734577
吃鱼 0 0 约 0.043418
它 0 0 约 0.043418
饱了 2 约 1.414214 约 0.178588
python 复制代码
from examples.attention_core import load_data, run_heads
head = run_heads(load_data())[0][0]
print(head["raw"][2])
print(head["scaled"][2].round(6))
print(head["weights"][2].round(6))
print(head["weights"].sum(axis=1))

前三行输出对应上表,最后为 [1,1,1,1]。显示时四舍五入可能使打印数值的和略有差异,程序核对使用完整精度。

计算指数时还有一个容易忽略的细节。代码先减去每行最大分数,再做指数,避免很大的正数溢出。所有分数一起减去同一个值,不会改变 Softmax 的比例。例如 [1,2,3] 与 [10001,10002,10003] 得到同样权重,这一点也已实际测试。

权重 0.734577 表示在这次人工计算中,"小猫"对应的 V 被赋予这个系数。它不是代词指代正确率,也没有测量语言模型理解句子的水平。数值较高的原因就在我们给定的 Q、K 中,不能绕过参数来源把它解释成模型自行学会了语法。

用权重汇总 V

"它"这一行的输出为四个 V 的加权和。

text 复制代码
0.734577 × [1,0]
+ 0.043418 × [0,1]
+ 0.043418 × [0,0]
+ 0.178588 × [1,1]
≈ [0.913165, 0.222006]

输出仍为二维,因为每个 V 都是二维。"小猫"贡献第一维,"吃鱼"贡献第二维,"饱了"同时贡献两维。计算过程见逐项贡献表。

连线从当前 Q 位置指向提供 K、V 的位置,粗细表示权重。图用一个位置展开全部计算。要同时处理四个 Q,就把它们排成矩阵,运算写成 softmax(QK.T / sqrt(dk)) @ V,其中 Softmax 按每一行执行。

步骤 矩阵形状
Q、K、V 各 4×2
K 转置 2×4
Q 与 K 转置相乘 4×4
行内 Softmax 4×4
权重矩阵与 V 相乘 4×2

权重矩阵的行是接收信息的位置,列是提供信息的位置。拿某一列求和,不要求等于 1。把 Softmax 做在列上会变成另一种计算,不能与本章公式混用。

两个头为什么能给出不同结果

本章另外给出第二套投影。对同一个"它",第二头权重约为 [0.043418,0.043418,0.178588,0.734577],最大的系数对应"饱了"。两套不同参数对同一输入得到不同匹配关系,这就是多头机制可以展示的核心计算差别。

两个头各自产生 4×2 输出,沿特征方向拼成 4×4,再乘本章给定的 4×4 输出投影 Wo,结果仍为 4×4。拼接保留每个头的输出分量,后面的投影再组合这些分量。原论文的多头注意力部分给出了相应计算结构。

python 复制代码
from examples.attention_core import load_data, run_heads
heads, joined, projected = run_heads(load_data())
print(heads[0]["output"].shape, heads[1]["output"].shape)
print(joined.shape, projected.shape)
print(heads[1]["weights"][2].round(6))

输出两次 (4,2),随后两次 (4,4),最后对应第二头的权重。真实模型会训练各头参数,各头可能获得不同关系,也可能有冗余。不能事先规定第一头永远负责主语、第二头永远负责动作;本章这些差异只是人工设置的可见计算。

生成文字时,为何不能看到后面的位置

前面的计算允许全部位置互相提供信息,称为不加因果限制的自注意力。自注意力表示 Q、K、V 都来自同一输入序列的不同投影。生成下一个 Token 时,则要限制当前计算只使用已经可见的位置。

在方形的因果自注意力矩阵中,将当前行后面位置的分数设为负无穷,再做 Softmax,那些位置的指数变为零。"它"是第 2 行,可以使用"小猫、吃鱼、它",不能使用第 3 行的"饱了"。本章因果版本权重变为约 [0.894285,0.052857,0.052857,0]。

python 复制代码
from examples.attention_core import load_data, run_heads
head = run_heads(load_data(), causal=True)[0][0]
print(head["weights"][2].round(6))
print(head["weights"][0])

第二个输出为 [1,0,0,0],因为第一个位置在这个示例中只能使用自己。屏蔽之后,剩余权重会重新归一化,不能在原权重上直接删除最后一项而不调整其他项。

两个排错点,以及图能说明到哪里

如果直接写 Q @ K,两个 4×2 矩阵无法相乘,程序报维度不匹配。修复版使用 K.T,得到 4×4 分数矩阵。另一个问题是在某行把所有位置都屏蔽,负无穷分数无法产生有效的归一化权重。本章主动报错,修复版保留一个合法位置,得到 [1,0]。两组真实结果见错误与修复。

注意力图是这一步计算的系数图。后面还有 V 的内容、其他头、投影、残差和更多层,不能凭一条粗线就解释最终答案的全部原因。关于把注意力直接当解释的限制,可参考研究论文 Attention is not Explanation。

此外,当前演示未加入位置编码。如果同时重排 Q、K、V 的行,不加因果掩码时,输出会按同样顺序重排。注意力核心计算本身没有自动提供文字先后关系。下一章会把位置表示、残差、归一化和前馈网络接起来。先完成【零基础学AI】第 26 章课后练习与答案,试着修改一个 K,重新算出权重和输出的变化。

相关推荐
lucas_AI10 分钟前
没人教过它化学立体几何,但它悄悄学会了:前沿 LLM 排构象已经超过经典力场
人工智能
硅谷秋水12 分钟前
OpenRUA 方法分析
人工智能·机器学习·语言模型·机器人
Rocky Ding*19 分钟前
【三年面试五年模拟】2026-11-01_字节跳动AI Agent全栈研发一面面经全解析
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·ai agent
Bode_200223 分钟前
大语言模型与运筹规划双闭环共生(LLM-OR):体系架构、理论机理与落地实践
人工智能·语言模型·架构
lucas_AI24 分钟前
给 AI 大脑「动手术」不用老中医把脉了:美国海军研究让机器自己找到该动的位置
人工智能
lucas_AI25 分钟前
给 DeepResearch 打分的尺子是错的:百度发布首个全管线 DeepSearch 奖励框架
人工智能
lucas_AI33 分钟前
50 个大模型 vs 258 个人类心理学实验:AI 的「常识」离人类还有多远?
人工智能
愤怒的代码34 分钟前
上下文窗口:模型的短期记忆
人工智能·llm·agent