self-attention

self-attention

Posted by Junvate on May 22, 2025

自注意力机制

我们来详细讲解一下自注意力机制(Self-Attention Mechanism)。这是 Transformer 架构(DeBERTa、BERT 等模型的基础)的核心部件,也是让这些模型能够深刻理解文本上下文的关键。

核心思想:

想象一下你在阅读一个句子时,为了理解某个词的意思,你的大脑会不自觉地关注句子中其他相关的词。比如,“河边的银行 நேற்று塌陷了”(The bank by the river collapsed yesterday),你会把“银行”和“河边”联系起来,从而知道它指的是河岸,而不是金融机构。

自注意力机制就是模仿这个过程:在处理一个词(或Token)时,模型能够动态地、有选择地关注输入序列中所有其他词(包括它自己),并根据相关性(重要性)来计算这个词的新表示。这里的“自”(Self)指的是注意力发生在输入序列内部,而不是关注外部信息。 运作方式(概念性解释): 准备工作 (生成 Q, K, V): 对于输入序列中的每一个词(Token),模型首先会根据它当前的嵌入向量(embedding)生成三个不同的向量: 查询向量 (Query, Q): 代表当前词“主动去查询”相关信息的意图。可以想象成这个词在问:“谁和我相关?” 键向量 (Key, K): 代表当前词“能提供什么信息”的标识或标签。可以想象成这个词在说:“这是我的信息标签,想找相关信息的可以来看。” 值向量 (Value, V): 代表当前词实际包含的“内容”或“意义”。如果一个词被认为相关,它的值向量就会被用来更新查询词的表示。 这三个向量是通过将原始词嵌入乘以三个不同的、可学习的权重矩阵(WQ, WK, WV)得到的。模型在训练中会学习这些矩阵,使得生成的 Q, K, V 最有利于任务。 计算注意力分数 (Attention Scores): 要计算一个词(比如词 A)的新表示,模型会拿着词 A 的 查询向量 (QA),去和序列中所有词(包括词 A 自己)的 键向量 (K1, K2, …, Kn) 进行计算。 最常用的计算方式是点积 (Dot Product)。QA 与每个 Ki 的点积结果,就代表了词 i 与词 A 的原始相关性分数。分数越高,初步认为相关性越强。 归一化分数 (Normalization using Softmax): 直接使用原始分数可能不稳定,所以模型会使用 Softmax 函数 对上一步得到的所有分数进行归一化。 Softmax 的作用是将这些分数转换成一组概率值(注意力权重),这些概率值的总和为 1。每个概率值代表了对应词对于理解当前词(词 A)的重要性权重。权重越接近 1,表示越重要;越接近 0,表示越不重要。 加权求和 (Weighted Sum of Values): 模型将上一步得到的注意力权重,分别乘以序列中每个词对应的值向量 (V1, V2, …, Vn)。 然后,将所有这些加权后的值向量求和。 这个最终的和向量,就是词 A 经过自注意力机制计算后得到的新表示。这个新表示融合了整个序列中所有词的信息,并且是以“注意力权重”为指导、有侧重地融合的。 多头注意力 (Multi-Head Attention): 为了让模型能同时关注来自不同表示子空间的信息(比如,有的注意力头关注语法关系,有的关注语义关系),实际应用中通常使用多头注意力。 这意味着上述的 Q, K, V 生成、注意力计算过程会并行地进行多次(比如 8 次或 12 次),每次使用不同的权重矩阵(WQ, WK, WV)。 得到多个输出向量后,通常会将它们拼接起来,再通过一个线性层进行整合。 自注意力机制与您的模型 ([CLS] Token) 的关系: 在您的模型描述中,提到“通过自注意力机制让 [CLS] 标记逐步融合全文本信息”。这是如何发生的呢? [CLS] 标记和其他普通词一样,也有自己的 Q, K, V 向量。 在模型的每一层自注意力计算中,[CLS] 的 Q 向量会与所有词(包括它自己和其他实际文本词)的 K 向量计算相关性,得到注意力权重。 然后,[CLS] 的新表示是通过加权求和所有词的 V 向量得到的。 因为这个过程在模型的多个层中重复进行,[CLS] 标记就能一层层地、通过自注意力机制不断地“吸收”和“融合”来自整个输入文本 response_a 或 response_b 的信息。 最终,在 DeBERTa 模型的顶层输出处,[CLS] 标记对应的嵌入向量 embed_a 或 embed_b 就成为了一个能够代表整个输入文本序列信息的聚合表示(句子嵌入)。 总结: 自注意力机制允许模型在处理序列中的每个元素时,都能考虑到序列中所有其他元素的影响,并动态地判断哪些元素更重要。这是它能够捕捉长距离依赖关系、理解复杂上下文的关键。在您的模型中,它使得 [CLS] 标记能够有效地“阅读”整个输入响应,并生成一个浓缩了其整体含义的嵌入向量。

QKV三个矩阵

好的,我们来详细解释一下在 Transformer 模型(尤其是其核心的注意力机制中)Query (Q),Key (K) 和 Value (V) 这三个矩阵是如何生成的。

核心思想:线性变换

Q, K, V 三个矩阵的生成本质上是通过对输入数据进行线性变换得到的。这意味着我们将输入向量乘以三个不同的、可学习的权重矩阵。

输入是什么?

在解释 QKV 生成之前,我们首先要明确输入是什么。

  1. 词嵌入 (Word Embeddings): 文本序列中的每个词或子词单元首先会被转换成一个固定维度的向量,即词嵌入。
  2. 位置编码 (Positional Encodings): 由于 Transformer 的自注意力机制本身不包含序列顺序的信息,我们需要向词嵌入中加入位置编码,以表示词在序列中的位置。

所以,注意力机制的实际输入是词嵌入与位置编码相加后得到的向量序列。我们设这个输入序列为 X,其中 X = [x_1, x_2, …, x_n],x_i 是序列中第 i 个词的嵌入向量(已经包含了位置信息),其维度通常被称为 d_model。

Q, K, V 的生成过程

对于输入序列中的每一个向量 x_i,我们通过以下方式生成对应的 q_i, k_i, v_i 向量:

  1. Query (Q) 向量: q_i = x_i * W_Q 这里 W_Q 是一个可学习的权重矩阵,其维度是 d_model × d_k。 q_i 是第 i 个词的查询向量,维度为 d_k。 作用:Query 代表当前词,它要去“查询”或“关注”序列中的其他词,看看哪些词与自己更相关。

  2. Key (K) 向量: k_i = x_i * W_K 这里 W_K 是另一个可学习的权重矩阵,其维度也是 d_model × d_k。 k_i 是第 i 个词的键向量,维度为 d_k。 作用:Key 代表序列中(包括当前词自身)的每个词的“标识”或“特征”。Query 会与所有的 Key 进行比较(通常通过点积)来计算注意力得分。

  3. Value (V) 向量: v_i = x_i * W_V 这里 W_V 是第三个可学习的权重矩阵,其维度是 d_model × d_v。 v_i 是第 i 个词的值向量,维度为 d_v。 作用:Value 代表序列中每个词实际携带的信息或内容。一旦通过 Q 和 K 计算出注意力权重,这些权重就会作用于 Value 向量,对它们进行加权求和,得到最终的输出。

维度说明:

d_model:输入词嵌入的维度。 d_k:Query 和 Key 向量的维度。 d_v:Value 向量的维度。 在原始的 Transformer 论文中,通常有 d_k = d_v = d_model / h,其中 h 是注意力头的数量(见下文“多头注意力”)。但 d_k 和 d_v 也可以独立设置。

矩阵形式:

如果我们将整个输入序列 X 看作一个矩阵 (形状为 sequence_length × d_model),那么 Q, K, V 也可以表示为矩阵形式:

Q = X * W_Q (形状为 sequence_length × d_k) K = X * W_K (形状为 sequence_length × d_k) V = X * W_V (形状为 sequence_length × d_v)

权重矩阵 W_Q, W_K, W_V 的特性:

  • 可学习 (Learnable): 这些权重矩阵的参数是在模型训练过程中通过反向传播算法学习得到的。模型会学习如何将输入映射到合适的 Q, K, V 表示,以便更好地捕捉词之间的依赖关系。
  • 独立性: W_Q, W_K, W_V 是三个独立的矩阵,它们有各自的参数。
  • 目标驱动: 它们学习的目标是使得 Q 和 K 的点积能够准确反映词之间的相关性,并且使得 V 能够提供有用的信息进行加权聚合。

多头注意力 (Multi-Head Attention) 中的 QKV 生成:

在实际的 Transformer 模型中,通常使用多头注意力机制。这意味着我们不仅仅有一组 W_Q, W_K, W_V,而是有 h 组独立的权重矩阵,对应 h 个“注意力头”。

对于每一个头 j (其中 j 从 1 到 h):

Q_j = X * W_Q^j K_j = X * W_K^j V_j = X * W_V^j

这里 W_Q^j, W_K^j, W_V^j 是第 j 个注意力头的权重矩阵。每个头会学习输入数据的不同子空间表示,从而能够从不同角度捕捉信息。

通常,每个头的 d_k 和 d_v 会设置成 d_model / h,这样在将所有头的输出拼接起来时,总维度仍然接近 d_model。

偏置项 (Bias):

在实际实现中,上述线性变换通常还会包含一个偏置项 (bias term),尽管有时为了简化会被忽略。

q_i = x_i * W_Q + b_Q k_i = x_i * W_K + b_K v_i = x_i * W_V + b_V

这些偏置项 b_Q, b_K, b_V 也是可学习的参数。

不同类型的注意力机制中的 QKV:

  • 自注意力 (Self-Attention): 这是编码器(Encoder)和解码器(Decoder)内部主要使用的注意力类型。 Q, K, V 都来自于同一个输入序列。例如,在编码器的一个自注意力层中,X 是前一层的输出。
  • 交叉注意力 (Cross-Attention) 或 编码器-解码器注意力: 主要用在解码器中,连接编码器和解码器。
    • Q 来自于解码器前一层的输出 (decoder’s previous hidden state)。
    • K 和 V 来自于编码器的最终输出 (encoder’s output sequence)。
    • 即:Q_decoder = X_decoder * W_Q
    • K_encoder = X_encoder * W_K
    • V_encoder = X_encoder * W_V
    • 这里的 W_Q, W_K, W_V 仍然是这个特定交叉注意力层独立的、可学习的权重矩阵。

总结:

Q, K, V 三个矩阵是通过将输入序列(词嵌入+位置编码)分别乘以三个独立的、可学习的权重矩阵 (W_Q, W_K, W_V) 生成的。 这些矩阵使得模型能够动态地计算序列中不同部分之间的相关性(通过 Q 和 K),并根据这些相关性聚合信息(通过 V),这是 Transformer 模型强大能力的核心机制之一。 在多头注意力中,这个过程会并行地执行多次,每次使用不同的权重矩阵集。

我们用一个非常简化的例子来解释 Q, K, V 矩阵是如何通过数字计算生成的。

1. 假设我们的输入

  • 一句话很短: “你好” (Hello you)
  • 词嵌入 + 位置编码后的向量 (输入 X): 假设经过词嵌入和位置编码后,我们得到每个词的向量表达(维度 d_model)。为了简单,我们设 d_model = 4。
    • x_1 (“你”) = [1, 0, 1, 0]
    • x_2 (“好”) = [0, 1, 0, 1]

    所以,输入矩阵 X (形状是 sequence_length × d_model = 2 × 4) 是:

      X = [[1, 0, 1, 0],   # 你
           [0, 1, 0, 1]]   # 好
    

2. 假设我们的权重矩阵 (这些是模型学习到的)

  • Q, K 的维度 d_k: 我们设 d_k = 3
  • V 的维度 d_v: 我们设 d_v = 2 (V 的维度可以和 Q, K 不同)

  • 权重矩阵 W_Q (维度 d_model × d_k = 4 × 3):
      W_Q = [[1, 2, 0],
             [0, 1, 1],
             [1, 0, 1],
             [2, 1, 0]]
    
  • 权重矩阵 W_K (维度 d_model × d_k = 4 × 3):
      W_K = [[0, 1, 1],
             [1, 0, 2],
             [2, 1, 0],
             [0, 2, 1]]
    
  • 权重矩阵 W_V (维度 d_model × d_v = 4 × 2):
      W_V = [[1, 0],
             [0, 2],
             [2, 1],
             [1, 1]]
    

3. 计算 Q, K, V 矩阵

核心公式是: Q = X * W_Q K = X * W_K V = X * W_V

计算 Q 矩阵 (形状 2 × 3): Q = X * W_Q [[1, 0, 1, 0], [[1, 2, 0], [0, 1, 0, 1]] * [0, 1, 1], [1, 0, 1], [2, 1, 0]]

  • q_1 (“你”的Query向量) = x_1 * W_Q = [1, 0, 1, 0] * W_Q = [(1*1 + 0*0 + 1*1 + 0*2), (1*2 + 0*1 + 1*0 + 0*1), (1*0 + 0*1 + 1*1 + 0*0)] = [ (1+0+1+0), (2+0+0+0), (0+0+1+0) ] = [2, 2, 1]

  • q_2 (“好”的Query向量) = x_2 * W_Q = [0, 1, 0, 1] * W_Q = [(0*1 + 1*0 + 0*1 + 1*2), (0*2 + 1*1 + 0*0 + 1*1), (0*0 + 1*1 + 0*1 + 1*0)] = [ (0+0+0+2), (0+1+0+1), (0+1+0+0) ] = [2, 2, 1]

所以,Q 矩阵是: Q = [[2, 2, 1], # 你 的 Query [2, 2, 1]] # 好 的 Query

计算 K 矩阵 (形状 2 × 3): K = X * W_K [[1, 0, 1, 0], [[0, 1, 1], [0, 1, 0, 1]] * [1, 0, 2], [2, 1, 0], [0, 2, 1]]

  • k_1 (“你”的Key向量) = x_1 * W_K = [1, 0, 1, 0] * W_K = [(1*0 + 0*1 + 1*2 + 0*0), (1*1 + 0*0 + 1*1 + 0*2), (1*1 + 0*2 + 1*0 + 0*1)] = [ (0+0+2+0), (1+0+1+0), (1+0+0+0) ] = [2, 2, 1]

  • k_2 (“好”的Key向量) = x_2 * W_K = [0, 1, 0, 1] * W_K = [(0*0 + 1*1 + 0*2 + 1*0), (0*1 + 1*0 + 0*1 + 1*2), (0*1 + 1*2 + 0*0 + 1*1)] = [ (0+1+0+0), (0+0+0+2), (0+2+0+1) ] = [1, 2, 3]

所以,K 矩阵是: K = [[2, 2, 1], # 你 的 Key [1, 2, 3]] # 好 的 Key

计算 V 矩阵 (形状 2 × 2): V = X * W_V [[1, 0, 1, 0], [[1, 0], [0, 1, 0, 1]] * [0, 2], [2, 1], [1, 1]]

  • v_1 (“你”的Value向量) = x_1 * W_V = [1, 0, 1, 0] * W_V = [(1*1 + 0*0 + 1*2 + 0*1), (1*0 + 0*2 + 1*1 + 0*1)] = [ (1+0+2+0), (0+0+1+0) ] = [3, 1]

  • v_2 (“好”的Value向量) = x_2 * W_V = [0, 1, 0, 1] * W_V = [(0*1 + 1*0 + 0*2 + 1*1), (0*0 + 1*2 + 0*1 + 1*1)] = [ (0+0+0+1), (0+2+0+1) ] = [1, 3]

所以,V 矩阵是: V = [[3, 1], # 你 的 Value [1, 3]] # 好 的 Value

总结一下我们得到的 Q, K, V 矩阵:

  • Q 矩阵:
      [[2, 2, 1],
       [2, 2, 1]]
    
  • K 矩阵:
      [[2, 2, 1],
       [1, 2, 3]]
    
  • V 矩阵:
      [[3, 1],
       [1, 3]]
    

这些 Q, K, V 接下来用来做什么?

这些矩阵是注意力机制的直接输入。简单来说,下一步是:

  1. 计算注意力得分: 对于每个词的 Query (Q 中的一行),它会和所有词的 Key (K 中的所有行) 进行点积运算,然后进行缩放 (除以 sqrt(d_k) ),再通过 Softmax 函数得到权重。
    • 例如,”你” (q_1) 对 “你” (k_1) 的关注度,”你” (q_1) 对 “好” (k_2) 的关注度。
    • 同样,”好” (q_2) 对 “你” (k_1) 的关注度,”好” (q_2) 对 “好” (k_2) 的关注度。
  2. 加权求和 Value: 将计算出来的注意力权重分别乘以对应的 Value 向量 (V 中的行),然后相加,得到该 Query 词的最终输出向量。

关键点:

  • W_Q, W_K, W_V 这三个权重矩阵是模型在训练过程中学习到的。它们一开始是随机初始化的,通过大量数据训练后,模型会学会如何设置这些权重,使得 Q, K, V 能够有效地捕捉词与词之间的关系。
  • 这个例子非常简化,实际的维度会大得多 (例如 d_model=512, d_k=d_v=64)。
  • 多头注意力 (Multi-Head Attention): 实际中,这个过程会并行地进行多次(比如8次,即8个“头”),每次使用不同组的 W_Q, W_K, W_V 权重矩阵。每个“头”可以关注输入序列的不同方面。然后将所有“头”的输出结果拼接起来再进行一次线性变换。这样做可以增强模型的表达能力。在多头注意力中,每个头的 d_k 和 d_v 通常是 d_model 除以头的数量。

希望这个带数字的例子能帮助你更好地理解 Q, K, V 是如何从输入通过可学习的线性变换(矩阵乘法)生成的!