《大模型原理通俗讲》系列 04|注意力机制通俗讲:它怎么知道“它”指的是谁

本文通俗讲解大模型中注意力机制的核心原理,阐明其如何通过Q(查询)、K(键)、V(值)三要素实现有选择地聚合上下文信息;详细拆解打分、softmax归一化、加权混合三步计算流程;指出注意力是Transformer架构基石,解决长程依赖、支持并行计算,并澄清常见误解如‘注意力等于理解’或‘权重即解释’等。

04|注意力机制通俗讲:它怎么知道“它”指的是谁

《大模型原理通俗讲》系列第 4 篇,共 16 篇。不需要数学基础,会用大模型聊天就能读。这是整个系列最关键的一篇:读懂它,后面的结构就都顺了。

先看两句话,只差一个字:

  • 奖杯放不进行李箱,因为它太大了。
  • 奖杯放不进行李箱,因为它太小了。

第一句里,“它”是奖杯;第二句里,“它”是行李箱。

你读起来毫不费力。可对模型来说,这是个难题:“它”这个字,两句话里一模一样,光看“它”本身,永远看不出它指的是谁。答案藏在别的词里(“大”“小”,还有前面的“奖杯”“行李箱”)。

所以模型读到“它”的时候,必须有一个本事:回头去前面找线索,而且知道该重点看哪几个词。

这个本事,就叫注意力机制。读完这一篇,你能回答三个问题:

  1. 注意力到底在“注意”什么?它是怎么算出来的?
  2. 经常听到的 Q、K、V 是什么?
  3. 为什么说注意力是大模型最核心的零件?

一、一句话讲清楚注意力

回忆上一篇:每个词查表得到一个向量,但这只是起点,同一个词不管出现在哪句话里,起点都一样。

如果每个词只用自己的向量,“它”永远是同一个“它”,什么都分不清。如果让每个词把前面所有词平均地看一遍,信息又会糊成一团。

注意力的做法是第三种:有选择地看。

每个词读到自己的时候,回头给前面的每个词打一个“看重程度”,再按这个程度,把它们的信息“借”过来,混进自己的向量里。

用我们的例子,读到“它”时是这样的:
在这里插入图片描述

“奖杯”和“行李箱”各被看重了约 4 成,别的词只有零头。“它”的向量因此带上了“奖杯”“行李箱”这类物体的信息。(图里的数字,是后面那段小代码真实算出来的。)


二、拆成三步:打分 → 变百分比 → 混合

“有选择地看”,在计算机里是怎么做到的?只有三步。

先用一个类比:你带着一个问题去图书馆。

  • 你手里有一张写着问题的问题卡;
  • 每本书都有一个书名标签;
  • 每本书里有自己的内容。

你拿问题卡跟每本书的标签对一对,越对得上的书,越多翻几页;最后把几本书里的内容,按“翻了多少”的比例,合成一份笔记。

注意力就是把这件事变成了数字:
在这里插入图片描述

第 ① 步,打分。 “它”拿着自己的问题卡(Q,Query,“我在找什么”),去和每个词的标签(K,Key,“我是什么”)对一对。越匹配,分数越高。(两串数字“对得上”的程度,数学上用“点积”来算,你只需要记住:对得上,分高。)

第 ② 步,变成百分比。 分数有大有小,没法直接当“分配比例”用。于是用一个叫 softmax 的办法,把一排分数变成一排百分比:分数越高,占比越大;每个都不为负,加起来正好是 100%。

第 ③ 步,混合内容。 每个词还有一份“能给你什么”的内容(V,Value)。按上一步的百分比,把大家的内容加权相加,得到的就是“它”的新向量:它已经带上了前文的信息。

这三步,就是注意力的全部。后面不管多复杂的模型,都是在重复它。


三、动手:30 行代码,把三步算一遍

我们做一个迷你版(需要 numpy),句子只有五个词:“奖杯 放不进 行李箱 因为 它”。

先说清楚:这里的数字是我手工设定的,为的是让你看清计算过程。 真实模型里,这些数字是训练出来的,没有这么整齐。为了看得懂,我给每个词的 3 个数起了名字:[是物体, 是动作, 是指代词]。

# mini_attention.py —— 迷你注意力:看清"打分 → 变百分比 → 混合信息"三步
import numpy as np

words = ["奖杯", "放不进", "行李箱", "因为", "它"]
# 每个词的向量,3 个数。为了看得懂,我手工给每一维起了名字:[是物体, 是动作, 是指代词]
x = np.array([[1, 0, 0],     # 奖杯
              [0, 1, 0],     # 放不进
              [1, 0, 0],     # 行李箱
              [0, .3, 0],    # 因为
              [0, 0, 1]])    # 它

# 三个"变换":把同一个向量,分别变成"问题卡 q""标签 k""内容 v"。真实模型里它们是训练出来的,这里手工设定
Wq = np.array([[0, 0], [2, 0], [3, 0]])      # 动作词想找"物体",指代词更想找"物体"
Wk = np.array([[1, 0], [0, 1], [.3, 0]])     # 物体词的标签是"我是物体"
Wv = np.eye(3)                               # 内容:原样带走

q, k, v = x @ Wq, x @ Wk, x @ Wv

scores = q @ k.T / np.sqrt(2)                # ① 打分:问题卡和每个标签对一对(除以 √2 是为了数字别太大)
scores[np.triu_indices(len(words), 1)] = -np.inf   # 接龙:每个词只能看自己和前面的,不许偷看后面
w = np.exp(scores - scores.max(axis=1, keepdims=True))
w = w / w.sum(axis=1, keepdims=True)         # ② 变成百分比:每一行加起来是 1
out = w @ v                                  # ③ 按百分比,把内容混合起来

print("词的编号:" + "  ".join(f"{i + 1}={t}" for i, t in enumerate(words)))
print("\n注意力权重(行 = 谁在看,列 = 看第几个词)")
print(" " * 8 + "".join(f"{i + 1:>6}" for i in range(len(words))))
for t, row in zip(words, w):
    print(t + " " * (8 - 2 * len(t)) + "".join(f"{p:>6.2f}" for p in row))    # 汉字占两格,所以按 2 倍补空格
print("\n『它』看之前:", x[4], "  看完之后:", out[4].round(2))

运行结果(我实际跑过):

词的编号:1=奖杯  2=放不进  3=行李箱  4=因为  5=它

注意力权重(行 = 谁在看,列 = 看第几个词)
             1     2     3     4     5
奖杯      1.00  0.00  0.00  0.00  0.00
放不进    0.80  0.20  0.00  0.00  0.00
行李箱    0.33  0.33  0.33  0.00  0.00
因为      0.30  0.20  0.30  0.20  0.00
它        0.41  0.05  0.41  0.05  0.09

『它』看之前: [0. 0. 1.]   看完之后: [0.81 0.06 0.09]

把这张权重表画出来:
在这里插入图片描述

逐行读一读,你会发现它在做符合直觉的事:

  • “它”这一行: 奖杯 0.41、行李箱 0.41,是最高的两个。因为“它”的问题卡在找“物体”,而这两个词的标签正好是“我是物体”。
  • “放不进”这一行: 奖杯 0.80。动词在找它的“东西”,所以重点看了前面的物体。
  • “它”的向量变了: 看之前是 [0, 0, 1](只是个指代词),看完之后是 [0.81, 0.06, 0.09],“物体”这一维从 0 变成了 0.81。这就是“借信息”。
  • 上三角全是“看不到”: 生成文字是接龙,每个词只能看自己和前面的词,不能偷看后面还没写出来的。这叫“因果遮罩”(第 07 篇会再用到)。所以第一个词“奖杯”只能看自己,权重是 1.00。
  • “行李箱”这一行是平均的: 因为我没给它设“问题”,它对前面一视同仁。这只是我这个迷你例子的结果,不是规律。

一定要说清楚的一点

看“它”这一行:奖杯和行李箱几乎平分了注意力,这一步并没有分出“它”到底是谁。

这没错,而且这正是真实情况。一次注意力不会“解开谜题”,它只是把相关的信息聚到一起。 真正分出“大 → 奖杯,小 → 行李箱”,要靠后面的词(比如“大”)也去回头看,再靠很多层、很多个“头”反复整理(第 05、06 篇讲),以及训练时学到的常识(比如“大的东西放不进小的容器”)。

把注意力想成“聚光灯”,而不是“答案”,会更准确。


四、Q、K、V 是从哪来的

上面反复出现的 Q(问题卡)、K(标签)、V(内容),是三种不同的东西吗?

不是。它们是同一个词的向量,分别经过三个不同的“变换”得到的三个版本:
在这里插入图片描述

  • 一个词的向量 x(上一篇查表得到的);
  • 乘以变换表 Wq,得到 q:“我在找什么”;
  • 乘以变换表 Wk,得到 k:“我是什么”;
  • 乘以变换表 Wv,得到 v:“我能给你什么”。

为什么要三个不同的?因为同一个词在注意力里扮演三种不同的角色:有时候它是“提问的人”,有时候它是“被找的人”,有时候它是“被取走信息的人”。三个角色需要的信息不一样,所以要用三张不同的变换表。

这三张变换表(Wq、Wk、Wv)里的数字,和上一篇的向量表一样,都是训练时学出来的。我们的迷你例子里是手工填的,真实模型里没有人填。


五、为什么说它是大模型最核心的零件

三个理由:

  1. 它解决了“远距离线索”。 回忆第 01 篇:“小明昨天丢了钱包,今天他很……”,线索在很前面。注意力让每个词都能直接看到前面任何位置的词,不管隔得多远。
  2. 它是一次性算完的。 一个句子里每个词的注意力,可以同时算(一张表一次算出来),不用像人读书那样一个字一个字等。这让用大量 GPU 训练成为可能(第 10 篇)。
  3. 大模型的主体结构(Transformer)就是用它搭起来的。 提出这个结构的那篇 2017 年的论文,标题就叫《Attention Is All You Need》(注意力就是你所需要的)。第 06 篇我们会看到整个结构。

当然,它也有代价:每个词要和前面所有词打分,句子长度翻倍,要算的分数大约翻 4 倍。这就是为什么模型一次能看的长度(上下文窗口)有上限,而且很长的输入特别耗资源(第 12 篇会讲)。


六、图书馆这个比喻,到哪里为止

  1. 书名是人起的,Q、K 却是学出来的数字。 真实模型里,q、k 里的每个数没有我们能读懂的含义。“问题卡”“标签”只是帮你建立直觉的名字。
  2. “注意”不是人那种有意识的注意。 它只是一个数学上的“加权平均”:权重大,就多混一点。模型没有“意识到这里是重点”。
  3. 权重高,不等于“它就是这么想的”。 注意力权重很容易画成漂亮的热力图,但学界对“权重能不能当作模型的解释”一直有讨论。看图时,把它当成“信息从哪里流过来”,不要当成“模型的内心想法”。
  4. 一次注意力只“借”一轮信息。 真实模型要借很多轮(很多层),而且每轮同时从好几个角度借(很多个头),这是下一篇的内容。

七、三个常见误解

误解 1:“注意力说明模型理解了。”
不能这么推。注意力只是把相关的信息聚在一起的一种计算。它为什么有效,和模型“懂不懂”,是两个问题。

误解 2:“注意力让模型记住了前面所有内容。”
不是。权重加起来是 1,看重了这个,就少看了那个;而且模型一次能看的长度是有上限的(上下文窗口),超过的部分就看不到了。

误解 3:“只有‘它’这样的代词才需要注意力。”
每个词都在做。“放不进”要找它的物体,“很”要找前面的原因,“钱包”要找“丢了”……每一个位置、每一层,都在回头看。


八、本篇要点

  1. 注意力 = 每个词回头给前面每个词打“看重程度”,再按这个程度把信息“借”过来。
  2. 三步:打分(Q 对 K)→ 变成百分比 → 按百分比混合内容(V)。
  3. Q、K、V 是同一个词的向量,经过三个学出来的变换得到的三个版本。一次注意力是“聚光灯”,不是“答案”。

下一篇预告: 一次注意力,只能从一个角度看。可一句话里,同时有语法关系、指代关系、因果关系……怎么办?而且注意力本身并不分先后:“狗咬人”和“人咬狗”,它怎么分得清?下一篇讲多头注意力和位置编码。

系列目录

在这里插入图片描述

文中的图、代码,欢迎自己动手改一改,比如把 Wq 里的数字改掉,看看“它”会去看谁。有哪里没讲明白,欢迎在评论区告诉我。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

forest_09

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值