课程0基础Agent开发课 / LLM基础 / 注意力机制的数学直觉-QKV矩阵在做什么
— 16 min read

注意力机制的数学直觉-QKV矩阵在做什么

> **[进阶选读]** 本文适合想深入理解 LLM 内部机制的读者。深入理解 Q/K/V 矩阵计算的具体过程,适合想从数学角度理解自注意力的读者。路径 A 的读者可以跳过。

注意力机制的数学直觉:Q/K/V 矩阵在做什么

[进阶选读] 本文适合想深入理解 LLM 内部机制的读者。深入理解 Q/K/V 矩阵计算的具体过程,适合想从数学角度理解自注意力的读者。路径 A 的读者可以跳过。


[进阶选读] 本篇从数学直觉角度深入解析注意力机制的计算过程,适合想彻底理解 Transformer 内部原理的读者。如果你的目标是应用开发(路径 A),可以跳过本篇,不影响后续学习。

第 04 篇用图书馆类比解释了自注意力的直觉:Query 在问,Key 在介绍,Value 在给答案。但有一个关键问题被跳过了——Query 和 Key "匹配"这件事,具体是怎么计算的?"相似度分数"从哪里来?为什么要除以一个奇怪的系数?为什么需要多个头?

本篇从数学直觉出发,逐层拆解这些问题。目标不是推导公式,而是让每一步计算都有直觉支撑。


1.1 先把类比建清楚

把图书馆检索系统的类比精确化:

线性投影

输入 x

× W_Q

× W_K

× W_V

Q 矩阵
查询意图

K 矩阵
可检索标签

V 矩阵
实际内容

Q · K^T
匹配相似度

÷ √d_k
缩放

SoftMax
归一化权重

权重 × V
加权提取

注意力输出
当前词的新表示

图 6.20:Q/K/V 注意力计算完整流程图——三个矩阵的角色与计算步骤

假设你走进一家图书馆,想找一本讲 Python 入门的书。你在搜索框输入:"适合零基础学习的 Python 编程入门书"。这就是你的 Query(查询词)

图书馆里每本书都有一套标签卡片——"Python、编程、入门、零基础"。这就是那本书的 Key(索引键)

你的查询词和每本书的标签进行匹配,得出一个相关度分数。相关度高的书排在前面。

当你真正打开这本书,看到里面的知识内容时,读到的才是 Value(内容值)

Q、K、V 为什么需要是三个不同的矩阵变换?

一个词在句子中可以同时扮演不同的角色。以"苹果"为例:当它作为搜索者(Query)时,它在问"我周围有什么相关信息?";当它作为被搜索对象(Key)时,它在说"我是一个食物/品牌名词,在这些语境下与我相关";当它作为信息提供者(Value)时,它在说"关于我,你可以知道这些语义内容"。

把这三个角色用三个不同的投影矩阵分开,让模型有最大的灵活性:每个角色可以强调词的不同语义方面。W_Q 让词形成最有效的"查询意图",W_K 让词形成最有效的"可检索标签",W_V 让词提供最有用的"实际内容"。

整个注意力计算的过程就是:

  1. 用 Query 和所有书的 Key 做匹配,得到每本书的相关度分数
  2. 把相关度分数归一化成"从这本书取多少信息"的权重
  3. 按权重从每本书的 Value 里取信息,加权混合,得到最终结果

在 Transformer 里,这里的"书"是句子里的每一个词。处理当前词时,它的 Query 去和序列里所有词的 Key 做匹配,权重决定"从哪些词借鉴多少信息",最后从 Value 里加权提取,得到当前词的新表示。


1.2 向量点积:相似度的计算方式

1.2.1 几何直觉:方向相似意味着语义相关

在向量空间中,"方向"是一个有意义的概念。如果把两个词的向量想象成从原点出发的箭头,方向相似意味着这两个词在语义上有关联。

为什么点积能衡量方向相似度?点积 = |A| × |B| × cos(θ),其中 θ 是两个向量的夹角。两个向量方向完全相同时 cos(θ) = 1,完全相反时 cos(θ) = -1,垂直时 cos(θ) = 0。

这意味着:Query 向量和 Key 向量的方向越接近,它们的点积越大,说明这个词对当前词提供的信息越相关——这正是注意力权重应该表达的含义。

Query 和 Key 都是向量(Vector)——可以理解为一串数字,比如 [0.8, 0.3, 0.9, 0.1],用于表示词语的语义特征。

两个向量的相似度,用点积(Dot Product)来衡量。点积的计算方式:把两个向量对应位置的数字两两相乘,再把所有乘积加起来。

不需要记住这个计算过程,只需要理解点积的直觉含义:

两个向量方向越相近,点积越大。

向量 A
[0.8, 0.9]

点积计算
0.8×0.7 + 0.9×0.8 = 1.28

向量 B
[0.7, 0.8]

结果:1.28
方向相近,点积大

向量 C
[0.8, 0.9]

点积计算
0.8×(-0.9) + 0.9×0.1 = -0.63

向量 D
[-0.9, 0.1]

结果:-0.63
方向相反,点积小

用人的类比来说:假设每个人的兴趣爱好可以用一个数字向量表示,喜欢编程的人对应 [1.0, 0.0],喜欢音乐的人对应 [0.0, 1.0],两者都感兴趣的人对应 [0.7, 0.7]。兴趣相似的两个人做点积,得到一个大数;兴趣差异大的两个人做点积,得到一个小数甚至负数。这个大数或小数,就是"相似度分数"。

在注意力机制里,Query 向量代表"我在寻找什么类型的信息",Key 向量代表"我提供什么类型的信息"。Q 和 K 点积越大,说明这个词提供的信息正是当前词需要的,注意力权重就越高。


1.3 Softmax:把分数变成权重

点积计算之后,得到一组原始分数,比如:

  • "animal" 对当前词的分数:3.2
  • "street" 对当前词的分数:0.4
  • "tired" 对当前词的分数:1.8
  • 其余词的分数:各种数值

这些分数的问题是:它们是原始数字,有正有负,量级不一。需要把它们转换成"权重"——每个权重在 0 到 1 之间,并且所有权重加起来等于 1。这样才能用来做加权平均。

Softmax(软最大化)就是做这件事的函数。

Softmax 的直觉:先对每个分数取指数(e 的该数次方),让大的数变得更大、小的数变得更小,然后除以所有指数的总和做归一化。

以上面的分数为例(简化示意):

原始分数 经过 Softmax 后的权重
animal 3.2 0.72
tired 1.8 0.22
street 0.4 0.06
其余词 更低 约 0.00

权重之和 = 0.72 + 0.22 + 0.06 + ... = 1.0

Softmax 有一个重要特性:大的分数会被放大,小的分数会被压缩。原始分数差距不大时,Softmax 后的权重差距会被拉大,模型的注意力会更聚焦于最相关的词。


1.4 缩放因子:为什么要除以 √d_k

注意力公式里有一步常常被初学者跳过:在做 Softmax 之前,先把点积除以一个数——√d_k,其中 d_k 是 Key 向量的维度(维度可以理解为向量的长度,即向量里数字的个数)。

为什么需要这个除法?

随着向量维度 d_k 变大,点积的值也会随之变大。比如维度是 4 时,点积最大可能是几;维度是 512 时,点积可能高达几百。

点积非常大时,Softmax 会进入一个危险区域:极大的值经过 Softmax 之后,概率几乎全部集中在最大的那一项(接近 1.0),其余全部接近 0。这种极端的概率分布会导致梯度消失(Vanishing Gradient)——梯度消失指的是训练时的学习信号变得极弱,模型停止更新。

把点积除以 √d_k,是一种简单的归一化手段,把点积的数值范围控制在合理区间,让 Softmax 的输出不那么极端,梯度保持健康。

这个除法本身不复杂,但缺少它,注意力机制在大维度下会严重失效。


1.5 完整的 Q/K/V 计算流程

把前面所有步骤串起来:

输入词序列
(已转为向量)

三个线性变换矩阵
W_Q、W_K、W_V

Q 矩阵
每个词的查询向量

K 矩阵
每个词的索引键向量

V 矩阵
每个词的内容值向量

Q × K^T
所有词对的点积矩阵

÷ √d_k
缩放,防止梯度消失

Softmax
转换为注意力权重
每行加和 = 1

权重矩阵 × V
加权求和

输出:每个词融合了
上下文信息的新表示

这幅图对应的就是注意力机制的标准公式:

code
Attention(Q, K, V) = Softmax(Q × K^T ÷ √d_k) × V

不需要记住公式,理解每一步在做什么就够了:Q 和 K 算相似度 → 除以 √d_k 缩放 → Softmax 归一化 → 加权提取 V。


1.6 Q/K/V 矩阵从哪里来

一个细节值得关注:Q、K、V 并不是输入向量本身,而是输入向量分别乘以三个不同的权重矩阵(W_Q、W_K、W_V)之后得到的。

这三个矩阵是训练中学习到的参数。为什么要做这个变换?

因为同一个词,在不同的上下文里,应该提供不同的"查询意图"(Q)和不同的"信息标签"(K)。通过三个可学习的矩阵,模型能把同一个词的原始向量投影到三个不同的语义空间里,分别扮演好 Query、Key、Value 三个角色。

类比:同一个人(输入向量),在图书馆里作为读者(扮演 Query 角色)时说的话,和作为图书管理员(扮演 Key 角色)时说的话,内容完全不同。W_Q、W_K、W_V 就是"角色切换"的变换矩阵。


1.7 多头注意力:为什么需要多个头

理解了单头注意力之后,多头注意力(Multi-Head Attention)就很直观了。

核心问题:一组 Q/K/V 只能学到一种类型的关联关系。但自然语言里,词与词之间的关系是多维度的:

  • 语法关系:"猫"和"追"(主谓关系)
  • 语义关系:"猫"和"喵喵叫"(语义相关)
  • 指代关系:"它"和"猫"(指代同一对象)
  • 位置关系:相邻词之间的局部依赖

一个注意力头,一次只能关注一种关系。如果只有一个头,它必须在这些关系之间做妥协,所有关系的学习效果都会打折扣。

解决方案:并行使用多组 Q/K/V,每组独立地学习一类关系,最后把所有头的结果拼接起来。

输入向量

头1
W_Q1, W_K1, W_V1
→ 关注语法关系

头2
W_Q2, W_K2, W_V2
→ 关注语义关系

头3
W_Q3, W_K3, W_V3
→ 关注指代关系

头h
W_Qh, W_Kh, W_Vh
→ 关注其他关系

拼接所有头的输出

线性变换 W_O
(整合多头信息)

最终输出
(融合多种关系的词表示)

多头注意力 vs 单头注意力的对比:

维度 单头注意力 多头注意力
关系类型 学习一种综合关系 每个头学习一种关系类型
表达能力 有限,关系之间相互竞争 强,各类关系并行学习
计算量 较低 每个头维度缩小,总计算量相近
可解释性 难以分析学了什么 不同头可分析出不同关注模式
典型应用 简化实验场景 所有主流 Transformer 模型

GPT-3 使用 96 个注意力头,每个头的维度是 128。这 96 个头并行工作,分别关注语言的不同维度,最终拼接成一个维度为 12288 的向量。这是 GPT-3 强大理解能力的结构基础之一。


1.8 代码示例:NumPy(Python科学计算基础库,提供高效的多维数组运算)实现简化版注意力

以下为代码示例,非程序员可跳过代码,重点看文字说明。

代码展示了从输入向量到注意力输出的完整计算过程,步骤和前面讲的完全对应:

python
import numpy as np

def softmax(x):
    """
    Softmax 函数:把一组数字转换为概率分布。
    先减去最大值(防止数值溢出),再做指数归一化。
    """
    # axis=-1 表示沿最后一个维度做 softmax(对每行做归一化)
    e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
    return e_x / e_x.sum(axis=-1, keepdims=True)


def scaled_dot_product_attention(Q, K, V):
    """
    缩放点积注意力(Scaled Dot-Product Attention)

    参数:
        Q: Query 矩阵,形状 (序列长度, d_k)
        K: Key 矩阵,形状 (序列长度, d_k)
        V: Value 矩阵,形状 (序列长度, d_v)

    返回:
        output: 注意力输出,形状 (序列长度, d_v)
        attention_weights: 注意力权重矩阵,用于可视化
    """
    d_k = Q.shape[-1]  # Key 向量的维度

    # 步骤 1:计算 Q 和 K 的点积
    # (seq_len, d_k) × (d_k, seq_len) → (seq_len, seq_len)
    # 结果矩阵的 [i][j] 表示第 i 个词对第 j 个词的原始相似度分数
    scores = np.matmul(Q, K.T)

    # 步骤 2:缩放——除以 √d_k,防止点积过大导致梯度消失
    scores = scores / np.sqrt(d_k)

    # 步骤 3:Softmax——把分数转换为注意力权重(每行加和=1)
    attention_weights = softmax(scores)

    # 步骤 4:用注意力权重对 Value 矩阵加权求和
    # (seq_len, seq_len) × (seq_len, d_v) → (seq_len, d_v)
    output = np.matmul(attention_weights, V)

    return output, attention_weights


# ---- 示例演示 ----
# 假设句子有 4 个词,每个词的向量维度是 8

np.random.seed(42)
seq_len = 4   # 序列长度(词的数量)
d_model = 8  # 词向量维度
d_k = 4       # Q 和 K 的投影维度

# 模拟输入词向量(实际来自词嵌入层)
X = np.random.randn(seq_len, d_model)

# 三个可训练的投影矩阵(实际通过梯度下降学习,这里随机初始化)
W_Q = np.random.randn(d_model, d_k)
W_K = np.random.randn(d_model, d_k)
W_V = np.random.randn(d_model, d_k)

# 计算 Q、K、V 矩阵
Q = X @ W_Q   # (4, 8) × (8, 4) → (4, 4)
K = X @ W_K
V = X @ W_V

# 执行注意力计算
output, weights = scaled_dot_product_attention(Q, K, V)

print("输入序列形状:", X.shape)          # (4, 8)
print("Q/K/V 形状:", Q.shape)            # (4, 4)
print("注意力权重矩阵:\n", np.round(weights, 3))
# 每行代表一个词对所有词的注意力分布,每行加和等于 1
print("输出形状:", output.shape)         # (4, 4)

# 验证:注意力权重每行加和 = 1
print("每行权重之和:", weights.sum(axis=1))  # 全部约等于 1.0

文字说明:

  • scores = Q @ K.T 这一行就是"Query 和 Key 做点积",得到每个词对其他所有词的原始相似度分数
  • / np.sqrt(d_k) 就是"除以 √d_k",缩放防止梯度消失
  • softmax(scores) 就是把分数转换为权重,每行加和等于 1
  • weights @ V 就是"加权提取 Value",得到每个词融合了上下文信息的新表示

实际的 Transformer 里,还会有掩码(Mask)操作——在解码阶段,第 i 个词不应该看到第 i+1 及之后的词(因为那些词还没有生成),通过把对应位置的分数设置为负无穷,Softmax 之后权重就变成 0,实现了屏蔽效果。


1.9 从直觉到理解的完整路径

把本篇的内容串起来,形成一条完整的理解链:

  1. Q/K/V 从哪来:输入向量分别经过三个可训练矩阵(W_Q、W_K、W_V)投影得到
  2. Q 和 K 怎么匹配:做点积,得到相似度分数,方向越相近分数越大
  3. 为什么要缩放:防止维度大时点积过大,导致 Softmax 极化,梯度消失
  4. Softmax 做什么:把相似度分数变成加和为 1 的注意力权重
  5. 权重怎么用:对 V 矩阵做加权求和,提取每个词应该借鉴的信息
  6. 为什么多头:不同头并行学习不同类型的关系(语法、语义、指代……),拼接后表达能力更强

这些就是注意力机制的数学直觉全貌。没有遗漏任何重要概念,也没有真正用到超出初中数学的知识——所有"数学"最后都归结为"越相似点积越大"这一条直觉。

下一篇讲 Transformer 里另一个核心组件的工作原理:MoE(混合专家架构),解释 DeepSeek 为什么又快又便宜。

本页目录