注意力机制背后的数学:Q、K 和 V

在这篇文章里,我们会通过一个逐步展开的数值例子,学习注意力机制背后的数学:查询(Query,Q)、键(Key,K)和值(Value,V)。

我们会讲到:

  • 注意力公式
  • 准备工作:从词到向量
  • 创建 Q、K 和 V 矩阵
  • 计算注意力分数(Q x K^T)
  • 缩放分数
  • 应用 softmax
  • 计算最终输出(注意力权重 x V)
  • 把所有步骤串起来

开始吧。

注意力公式

**缩放点积注意力(Scaled Dot-Product Attention)**的核心公式是:

Attention(Q, K, V) = softmax(Q x K^T / sqrt(d_k)) x V

这里:

  • Q 是查询矩阵
  • K 是键矩阵
  • V 是值矩阵
  • K^T 是键矩阵的转置
  • d_k 是键向量的维度
  • sqrt 表示平方根

如果这个公式看起来复杂,不用担心。我们会用真实数字一步步拆开它。读完这篇文章后,这个公式的每一部分都会变得清楚。

思路很简单:我们把每个词想寻找的内容(查询),和其他每个词能提供的内容()进行比较,再根据比较结果,从最相关的词中收集实际信息()。

现在,用真实数字来看一遍。

准备工作:从词到向量

我们取一个只有 3 个词的简单句子:

"I love AI"(我爱 AI)

在 Transformer 里,每个词会先被转换成一个向量(也就是一组数字),称为嵌入(embedding)。为了便于理解,我们会使用非常小的 4 维向量(也就是 d_emb = 4)。

"I"    → [1.0, 0.0, 1.0, 0.0]
"love" → [0.0, 1.0, 0.0, 1.0]
"AI"   → [1.0, 1.0, 0.0, 0.0]

我们把这些向量堆叠成一个形状为 3 x 4输入矩阵 X(3 个词,每个词有 4 个数字):

X = | 1.0  0.0  1.0  0.0 |   ← "I"
    | 0.0  1.0  0.0  1.0 |   ← "love"
    | 1.0  1.0  0.0  0.0 |   ← "AI"

这里,每一行代表一个词,每一列对应嵌入向量中的一个数字。这就是我们的起点。

创建 Q、K 和 V 矩阵

现在,我们需要创建三个单独的矩阵:Q(查询)K(键)V(值)

怎么创建它们?我们把输入矩阵 X 分别乘以三个不同的权重矩阵W_QW_KW_V。这些权重矩阵是在训练过程中学习得到的。

Q = X x W_Q
K = X x W_K
V = X x W_V

在我们的例子里,假设 d_k = 3(也就是我们想让 Q、K、V 向量的大小为 3)。因此,每个权重矩阵的形状都是 4 x 3(输入维度 4,输出维度 3)。

为了便于理解,我们使用下面这些权重矩阵:

W_Q = | 1  0  1 |
      | 0  1  0 |
      | 1  0  0 |
      | 0  1  1 |

W_K = | 0  1  0 |
      | 1  0  1 |
      | 0  0  1 |
      | 1  1  0 |

W_V = | 1  0  0 |
      | 0  1  0 |
      | 0  0  1 |
      | 1  1  0 |

现在,我们来计算 Q、K 和 V。

计算 Q = X x W_Q:

Q = | 1.0  0.0  1.0  0.0 |     | 1  0  1 |
    | 0.0  1.0  0.0  1.0 |  x  | 0  1  0 |
    | 1.0  1.0  0.0  0.0 |     | 1  0  0 |
                               | 0  1  1 |

第一行("I"):[11 + 00 + 11 + 00, 10 + 01 + 10 + 01, 11 + 00 + 10 + 01] = [2, 0, 1]

第二行("love"):[01 + 10 + 01 + 10, 00 + 11 + 00 + 11, 01 + 10 + 00 + 11] = [0, 2, 1]

第三行("AI"):[11 + 10 + 01 + 00, 10 + 11 + 00 + 01, 11 + 10 + 00 + 01] = [1, 1, 1]

Q = | 2  0  1 |   ← "I"
    | 0  2  1 |   ← "love"
    | 1  1  1 |   ← "AI"

计算 K = X x W_K:

第一行("I"):[10 + 01 + 10 + 01, 11 + 00 + 10 + 01, 10 + 01 + 11 + 00] = [0, 1, 1]

第二行("love"):[00 + 11 + 00 + 11, 01 + 10 + 00 + 11, 00 + 11 + 01 + 10] = [2, 1, 1]

第三行("AI"):[10 + 11 + 00 + 01, 11 + 10 + 00 + 01, 10 + 11 + 01 + 00] = [1, 1, 1]

K = | 0  1  1 |   ← "I"
    | 2  1  1 |   ← "love"
    | 1  1  1 |   ← "AI"

计算 V = X x W_V:

第一行("I"):[11 + 00 + 10 + 01, 10 + 01 + 10 + 01, 10 + 00 + 11 + 00] = [1, 0, 1]

第二行("love"):[01 + 10 + 00 + 11, 00 + 11 + 00 + 11, 00 + 10 + 01 + 10] = [1, 2, 0]

第三行("AI"):[11 + 10 + 00 + 01, 10 + 11 + 00 + 01, 10 + 10 + 01 + 00] = [1, 1, 0]

V = | 1  0  1 |   ← "I"
    | 1  2  0 |   ← "love"
    | 1  1  0 |   ← "AI"

现在,Q、K 和 V 矩阵都准备好了。

注意: 在真实模型中,这些权重矩阵不是手工设置的,而是在训练过程中学习得到的。这里为了便于理解,我们使用了简单数字。

到目前为止,我们已经学会了如何从输入创建 Q、K 和 V。现在进入下一步。

计算注意力分数(Q x K^T)

注意力公式里的第一步,是计算 Q x K^T

这个结果表示每个词应该在多大程度上关注其他各个词。计算时,要把每个词的查询向量与其他每个词的键向量相乘。

首先,我们需要 K^T(K 的转置)。转置的意思很简单:交换行和列。第一行变成第一列,第二行变成第二列,依此类推:

K^T = | 0  2  1 |
      | 1  1  1 |
      | 1  1  1 |

现在,计算 Q x K^T

Q x K^T = | 2  0  1 |     | 0  2  1 |
          | 0  2  1 |  x  | 1  1  1 |
          | 1  1  1 |     | 1  1  1 |

"I" 对所有词的注意力分数:

  • "I" → "I":20 + 01 + 1*1 = 1
  • "I" → "love":22 + 01 + 1*1 = 5
  • "I" → "AI":21 + 01 + 1*1 = 3

"love" 对所有词的注意力分数:

  • "love" → "I":00 + 21 + 1*1 = 3
  • "love" → "love":02 + 21 + 1*1 = 3
  • "love" → "AI":01 + 21 + 1*1 = 3

"AI" 对所有词的注意力分数:

  • "AI" → "I":10 + 11 + 1*1 = 2
  • "AI" → "love":12 + 11 + 1*1 = 4
  • "AI" → "AI":11 + 11 + 1*1 = 3
            "I"   "love"   "AI"
Scores = |  1      5       3  |   ← "I"
         |  3      3       3  |   ← "love"
         |  2      4       3  |   ← "AI"

这里可以看到,"I" 给 "love" 的注意力分数最高,为 5,意味着 "I" 认为 "love" 最相关。"love" 给所有词的分数都一样,都是 3。"AI" 给 "love" 的分数最高,为 4

注意力分数的计算就到这里。现在进入缩放步骤。

缩放分数

现在,我们把每个分数都除以 sqrt(d_k)

在这个例子里,d_k = 3(也就是键向量的维度),所以 sqrt(3) = 1.732

问题来了:为什么要缩放?答案是:如果点积的值太大,下一步里的 softmax 函数会产生非常极端的值(接近 0 或 1)。这会让模型更难学习。缩放可以把数值保持在一个更容易处理的范围内。

Scaled Scores = Scores / sqrt(3)
                    "I"      "love"     "AI"
Scaled Scores = |  0.577    2.887     1.732  |   ← "I"
                |  1.732    1.732     1.732  |   ← "love"
                |  1.155    2.309     1.732  |   ← "AI"

这里可以注意到,分数的相对顺序没有改变。"I" 仍然最关注 "love"。但数值变小了,这正是我们想要的。

以后,我计划写一篇文章,解释为什么选择除以 sqrt(d_k),而不是其他因子。

接下来应用 softmax。

应用 softmax

现在,我们对每一行应用 softmax 函数。Softmax 会把分数转换成概率,并且这些概率加起来等于 1。

一行里某个值 x_i 的 softmax 公式是:

softmax(x_i) = e^(x_i) / sum(e^(x_j) for all j in that row)

这里,e 是数学常数(约等于 2.718)。不用太纠结这个公式。我们会一步步算出来。

对 "I" 这一行:[0.577, 2.887, 1.732]

  • e^0.577 = 1.781
  • e^2.887 = 17.940
  • e^1.732 = 5.651
  • 总和 = 1.781 + 17.940 + 5.651 = 25.372

注意力权重:

  • "I" → "I":1.781 / 25.372 = 0.070
  • "I" → "love":17.940 / 25.372 = 0.707
  • "I" → "AI":5.651 / 25.372 = 0.223

对 "love" 这一行:[1.732, 1.732, 1.732]

所有值都相等,所以每个词获得相同的注意力:

  • "love" → "I":0.333
  • "love" → "love":0.333
  • "love" → "AI":0.333

对 "AI" 这一行:[1.155, 2.309, 1.732]

  • e^1.155 = 3.174
  • e^2.309 = 10.063
  • e^1.732 = 5.651
  • 总和 = 3.174 + 10.063 + 5.651 = 18.888

注意力权重:

  • "AI" → "I":3.174 / 18.888 = 0.168
  • "AI" → "love":10.063 / 18.888 = 0.533
  • "AI" → "AI":5.651 / 18.888 = 0.299

最终的注意力权重矩阵是:

                       "I"     "love"    "AI"
Attention Weights = | 0.070    0.707    0.223 |   ← "I"
                    | 0.333    0.333    0.333 |   ← "love"
                    | 0.168    0.533    0.299 |   ← "AI"

这里可以看到:

  • "I" 把 70.7% 的注意力放在 "love" 上,22.3% 放在 "AI" 上,只有 7.0% 放在自己身上。
  • "love" 对三个词的注意力相同(各 33.3%)。
  • "AI" 把 53.3% 的注意力放在 "love" 上,29.9% 放在自己身上,16.8% 放在 "I" 上。

每一行加起来都是 1.0(也就是 100%)。这正是 softmax 所保证的。

现在,我们有了注意力权重。接下来计算最终输出。

计算最终输出(注意力权重 x V)

现在,我们把注意力权重矩阵和值矩阵 V 相乘,得到最终输出。

Output = Attention Weights x V
Output = | 0.070  0.707  0.223 |     | 1  0  1 |
         | 0.333  0.333  0.333 |  x  | 1  2  0 |
         | 0.168  0.533  0.299 |     | 1  1  0 |

对 "I":

  • 0.0701 + 0.7071 + 0.223*1 = 1.000
  • 0.0700 + 0.7072 + 0.223*1 = 1.637
  • 0.0701 + 0.7070 + 0.223*0 = 0.070

对 "love":

  • 0.3331 + 0.3331 + 0.333*1 = 0.999
  • 0.3330 + 0.3332 + 0.333*1 = 0.999
  • 0.3331 + 0.3330 + 0.333*0 = 0.333

对 "AI":

  • 0.1681 + 0.5331 + 0.299*1 = 1.000
  • 0.1680 + 0.5332 + 0.299*1 = 1.365
  • 0.1681 + 0.5330 + 0.299*0 = 0.168
Output = | 1.000  1.637  0.070 |   ← "I"
         | 1.000  1.000  0.333 |   ← "love"
         | 1.000  1.365  0.168 |   ← "AI"

这里可以看到,每个词现在都有了一个新向量,它是所有词的 V 向量的加权组合。"I" 得到的新表示受 "love" 的 V 向量影响很大(因为它对 "love" 的注意力权重最高,为 0.707)。这就是注意力机制的作用。

每个词的输出不再只与这个词本身有关。它现在包含了来自其他所有词的信息,并且这些信息会按相关性加权。这就是模型理解上下文和词与词之间关系的方式。

最终输出的计算就到这里。现在,把所有步骤串起来。

把所有步骤串起来

我们把整个计算过程集中总结一下:

第 1 步: 从输入嵌入向量(X)开始。

第 2 步: 用 X 乘以权重矩阵,得到 Q、K 和 V。

第 3 步: 用 Q 乘以 K 的转置(Q x K^T),计算注意力分数。

第 4 步: 除以 sqrt(d_k),对分数进行缩放。

第 5 步: 应用 softmax,得到注意力权重(概率)。

第 6 步: 用注意力权重乘以 V,得到最终输出。

整个过程可以写成一个公式:

Attention(Q, K, V) = softmax(Q x K^T / sqrt(d_k)) x V

现在,这个公式的每一部分都清楚了。

这就是注意力机制中 Q、K 和 V 背后的数学。

每个大语言模型(LLM)的核心都会用到这套计算。为了便于理解,我们使用的是很小的数字;但在真实模型里,维度会大得多(512、1024 或更高),权重矩阵也是在训练过程中学习得到的。

现在,我们应该已经理解了注意力机制背后的数学:Q、K 和 V。

今天就到这里。

谢谢

Amit Shekhar
Outcome School 创始人

在这里阅读我的所有高质量博客。