博主头像
小雨淅沥

Some things were meant to be.

D2L:10 注意力机制

注意力机制

1 Attention 机制

1.1 注意力机制

我们把线索分为 随意线索和不随意线索,随意线索指的是“你想要做什么”,是注意力的重点,不随意线索指的是环境的一些东西,例如你自然而然的一些注意到的东西,和主观无关

  • Query: 一个随意线索
  • Value: 输入的一个数值
  • Key: 不随意线索

1.2 注意力池化

Attention Pooling 会有偏向性地选择某一些输入,例如一个很经典的核回归函数

$$ f(x) = \sum_{i=1}^{n} \frac{K(x - x_i)} {\sum_{j=1}^{n} K(x - x_j)} \, y_i $$

在这里面 $w_i(x) = \frac{K(x-x_i)}{\sum_j K(x-x_j)}$ 代表权重,核心思想是基于相似度的加权平均,越接近$x$ (查询值)的 $x_i$ , 其对应的 $y_i$ 会有更高的权重

如果应用高斯核 $K(u) = \frac{1}{\sqrt{2\pi}} \exp\left(-\frac{u^2}{2}\right)$ 则最后可以得到

$$ f(x) = \sum_{i=1}^{n} \operatorname{softmax}\!\left( -\frac{1}{2}(x - x_i)^2 \right) \, y_i $$

为了应用到机器学习,我们要加入一个权重让这个操作可以学习,于是就得到了:

$$ f(x) = \sum_{i=1}^{n} \operatorname{softmax}\!\left( -\frac{1}{2}\big((x - x_i)w\big)^2 \right) \, y_i $$

2 注意力分数

2.1 一维

$$ f(x) = \sum_i \alpha(x, x_i) y_i = \sum_{i=1}^{n} \operatorname{softmax}\!\left(-\frac{1}{2}(x - x_i)^2\right) y_i $$

对于这个公式,其中 $\alpha(x, x_i)$ 是注意权重,$-\frac{1}{2}(x - x_i)^2$ 表示的是注意力分数

正如这张图所展示的,将 Query 扔进来,和每一个 Key 计算一次注意力分数函数,在上边的例子里是 $-\frac{1}{2}(x - x_i)^2$

之后放进 Softmax 计算为权重,最后和 Values 相乘计算加权和,得到最终的输出

2.2 向量化

继续将 Query 升维得到一个向量 $\mathbf{q} \in \mathbb{R}^{q}$ , 另有 $m$ 个键值对 $\left(\right. \mathbf{k}_{1} , \mathbf{v}_{1} \left.\right) , \ldots , \left(\right. \mathbf{k}_{m} , \mathbf{v}_{m} \left.\right)$ , 其中 $\mathbf{v}_{i} \in \mathbb{R}^{v}$ ,那么注意力汇聚函数会变成

$$ f(\mathbf{q}, (\mathbf{k}_1, \mathbf{v}_1), \ldots, (\mathbf{k}_m, \mathbf{v}_m)) = \sum_{i=1}^{m} \alpha(\mathbf{q}, \mathbf{k}_i)\,\mathbf{v}_i \in \mathbb{R}^{v} $$

其中查询$\mathbf{q}$和键$\mathbf{k}_i$的注意力权重(标量)是通过注意力评分函数 $a$ 将两个向量映射成标量,再经过 softmax 运算得到的:

$$ \alpha(\mathbf{q}, \mathbf{k}_i) = \mathrm{softmax}(a(\mathbf{q}, \mathbf{k}_i)) = \frac{\exp(a(\mathbf{q}, \mathbf{k}_i))}{\sum_{j=1}^m \exp(a(\mathbf{q}, \mathbf{k}_j))} \in \mathbb{R}. $$

这里的 $a$ 就是注意力评分函数,用于计算 attention score

下面是两种最常见的分数计算公式

2.3 加性注意力

给定查询 $\mathbf{q} \in \mathbb{R}^q$ 和键 $\mathbf{k} \in \mathbb{R}^k$,加性注意力(additive attention)的评分函数为

$$ a(\mathbf q, \mathbf k) = \mathbf w_v^\top \text{tanh}(\mathbf W_q\mathbf q + \mathbf W_k \mathbf k) \in \mathbb{R}, $$

其中可学习的参数是 $\mathbf W_q\in\mathbb R^{h\times q}$ 、$\mathbf W_k\in\mathbb R^{h\times k}$和$\mathbf w_v\in\mathbb R^{h}$。这里的 $\text{tanh}$ 相当于一个激活函数,这个有点像一个输出大小为 1 的单隐藏层 MLP

这个注意力的优点是 Key Value Query 可以是任意的长度,

为什么叫加性:因为是线性变换后执行相加

完整的逻辑链,这里面有一个 num_hiddens,这个值是隐藏的中间处理值 :

$q,k \rightarrow W_q,W_k \rightarrow num\_hiddens 维空间 \rightarrow \tanh \rightarrow W_v \rightarrow 标量分数 \rightarrow softmax \rightarrow 概率 \rightarrow 加权 V$

2.4 缩放点积注意力

Scaled Dot-Product Attention 要求 Q 和 K 都是同样长度 ,此时可以有

$$ a \left(\right. \mathbf{q} , \mathbf{k} \left.\right) = \mathbf{q}^{\top} \mathbf{k} / \sqrt{d} $$

这个时候就是没有学习的参数了,是最简单的注意力公式

另外也有向量化版本,例如 $Q \in \mathbb{R}^{n \times d} \quad $ $K \in \mathbb{R}^{m \times d} \quad $ $V \in \mathbb{R}^{m \times v}$

此时计算的注意力分数为 $a(Q, K) = \frac{QK^{T}}{\sqrt{d}} \in \mathbb{R}^{n \times m}$

这里 $\sqrt{d}$ 是 Key 的向量维度,作用是防止点积结果数值过大,导致 softmax 梯度消失(方差值计算)

对应注意力池化层 $f = \mathrm{softmax}\big(a(Q, K)\big) V \in \mathbb{R}^{n \times v}$ ,这里 softmax 对每一行做一次,最后得到一个 $n \times m$ 的概率分布

3 Bahdanau 注意力

在机器翻译中,每一个生成的词可能来自源句子中的不同的词,但是 seq2seq 就无法做到这个

seq2seq 的上下文实际上是 RNN 的最后一个时刻状态,但是现实翻译句子的上下文并不一定是最后一个的状态,

Attention 中把 encoder 中对于每一个词的输出作为一个 KV 对(某一个词的 RNN 输出)

Query 指的是 decoder 上一个时刻的 hidden state,用来对 encoder 的所有 hidden states 执行查询

最后 Attention 的输出和当前 decoder hidden state 合并在一起进入 FC 层

4 多头注意力

指的是将多个注意力模块全部都整合到一起,每一个注意力负责不同的学习

实现流程是:原始输入 → 不同投影空间 → 每个空间单独做 attention → 拼接

对于每一个头有计算公式: $\mathbf{h}_{i} = f \left(\right. \mathbf{W}_{i}^{\left(\right. q \left.\right)} \mathbf{q} , \mathbf{W}_{i}^{\left(\right. k \left.\right)} \mathbf{k} , \mathbf{W}_{i}^{\left(\right. v \left.\right)} \mathbf{v} \left.\right) \in \mathbb{R}^{p_{v}}$ , 其中 $\mathbf W_i^{(q)}\in\mathbb R^{p_q\times d_q}$、$\mathbf W_i^{(k)}\in\mathbb R^{p_k\times d_k}$和$\mathbf W_i^{(v)}\in\mathbb R^{p_v\times d_v}$ 都是可学习参数(注意他们的维度)

$f$ 是注意力汇聚函数,是 $f(\mathbf{q}, \{(\mathbf{k}_i, \mathbf{v}_i)\}) = \sum_{i=1}^{m} \alpha(\mathbf{q}, \mathbf{k}_i)\mathbf{v}_i$ 这个公式

多头注意力使用一个线性变换来整合所有的头的输出,这个线性变换也是一个可学习参数是$\mathbf W_o\in\mathbb R^{p_o\times h p_v}$:

$$ \mathbf W_o \begin{bmatrix}\mathbf h_1\\\vdots\\\mathbf h_h\end{bmatrix} \in \mathbb{R}^{p_o}. $$

5 自注意力

自注意力指的是 $\text{x}_i$ 同时充当 QKV 三种角色,$\mathbf{y}_{i} = f \left(\right. \mathbf{x}_{i} , \left(\right. \mathbf{x}_{1} , \mathbf{x}_{1} \left.\right) , \ldots , \left(\right. \mathbf{x}_{n} , \mathbf{x}_{n} \left.\right) \left.\right) \in \mathbb{R}^{d}$ 也就是这个公式,给定一个序列就可以直接执行输出,可以不用 encode 和 decode

不论是 CNN RNN 还是 Attention 实际上都可用来处理序列,如下图可明显的看出区别

CNN 的优势是可以并行计算;RNN 相应的并行度很差,信息传递很慢,但是记忆性序列很强(顺序上)

Attention 的计算复杂度非常高(和序列长度指数相关),计算代价很高,但是并行度和信息传播速度很好

为什么说可以并行,因为首先矩阵运算本身是可以并行了,也没有时间上的递归状态

5.1 位置编码

self-attention 是不会记录位置信息的,所以需要位置编码将信息注入到输入中(不是模型中!)

假设输入表示 $\mathbf{X} \in \mathbb{R}^{n \times d}$包含一个序列中 $n$ 个词元的$d$维嵌入表示。位置编码使用相同形状的位置嵌入矩阵$\mathbf{P} \in \mathbb{R}^{n \times d}$输出$\mathbf{X} + \mathbf{P}$,矩阵第$i$行、第$2j$列和$2j+1$列上的元素为:

$$ \begin{aligned} p_{i, 2j} &= \sin\left(\frac{i}{10000^{2j/d}}\right),\\p_{i, 2j+1} &= \cos\left(\frac{i}{10000^{2j/d}}\right).\end{aligned} $$

编码的周期为 2 使用相位(正余弦变化)不同编码周期之间使用正余弦的频率来区分

越往后频率的变化越小(可以理解为 随着 $f$ 变大导致这个频率上的编码越来越不明显)

在绝对位置编码中,一个二进制数从 0 ~ 8 的第 0 位的变化的 $T = 2$,第 1 位就变成了 $T = 4$ 直接翻倍了,越往后的变化越慢

位置编码中的绝对位置信息也是类似道理,越往后的列,行之间的变化对编码值本身影响也会逐渐变小;这表现在模型中也就是:前面的维度频率高,对相邻的 token 非常敏感,后面的维度则没那么敏感

位置编码之所以使用这种正弦的公式,另一个原因是相对位置信息:这是因为对于任何确定的位置偏移$\delta$,位置$i + \delta$处
的位置编码可以线性投影位置$i$处的位置编码来表示。

这种投影的数学解释是,令$\omega_j = 1/10000^{2j/d}$,对于任何确定的位置偏移$\delta$
$(p_{i, 2j}, p_{i, 2j+1})$都可以线性投影到 $(p_{i+\delta, 2j}, p_{i+\delta, 2j+1})$:这里的投影矩阵与 $i$ 无关,是一个相对位置性

$$ \begin{aligned} &\begin{bmatrix} \cos(\delta \omega_j) & \sin(\delta \omega_j) \\ -\sin(\delta \omega_j) & \cos(\delta \omega_j) \\ \end{bmatrix} \begin{bmatrix} p_{i, 2j} \\ p_{i, 2j+1} \\ \end{bmatrix}\\ =&\begin{bmatrix} \cos(\delta \omega_j) \sin(i \omega_j) + \sin(\delta \omega_j) \cos(i \omega_j) \\ -\sin(\delta \omega_j) \sin(i \omega_j) + \cos(\delta \omega_j) \cos(i \omega_j) \\ \end{bmatrix}\\ =&\begin{bmatrix} \sin\left((i+\delta) \omega_j\right) \\ \cos\left((i+\delta) \omega_j\right) \\ \end{bmatrix}\\ =& \begin{bmatrix} p_{i+\delta, 2j} \\ p_{i+\delta, 2j+1} \\ \end{bmatrix}, \end{aligned} $$

$2\times 2$投影矩阵不依赖于任何位置的索引$i$。

6 Transformer

Transformer 是一个完全的 self-attention 架构,没有借助 RNN,使用了 encoder 和 decoder 的结构

多头注意力中使用了掩码,指的是注意力中可以查看到完整序列,这在编码的时候是没有问题的,但是在解码的时候不可行的,解码器对一个序列的元素输出的时候,不应该考虑这个元素之后的元素

因此使用一个有掩码的 softmax

Transformer 架构
Transformer 架构

6.1 基于位置的前馈网络

Positionwise FFN,将一个输入形状从 $(b, n, d)$ 转化为 $(bn, d)$, 作用两个 FC 层(输入一个变化,输出一个变化),等价于两个 1x1 的卷积层

为什么叫 Positionwise?因为对于一个 X.shape = (batch_size, seq_len, d_model) 输入的三维矩阵,执行操作是对每一个 token 的向量单独做同一个 MLP,而不是在 token 之间交互

主要的作用就是在 attention 之后,对每个 token 单独做一个共享参数的两层非线性映射,用来提升特征表达能力。

6.2 残差连接 + 层归一化

Add & norm 顾名思义就是 add 和 norm 两个操作

Add 指的是将 进入多头注意力的输入X 以及多头注意力的输出Y两者相加,其中Y会额外执行一个 dropout

Norm 中的 norm 指的是 Layer Normalization,Batch Normalization 对每一个特征/通道的元素执行归一化,但是这个不适合对序列长度会变的 NLP

Layer Normalization 会对每一个样本里的元素执行归一化

6.3 信息传递

Encoder 到 Decoder 中传递的值,是编码器的输出 $y_i$ 传递过来的,他们作为上图中的 Transformer 块中的多头注意力的 Key 和 Value,而 Query 来自于目标序列(注意看解码器最下面的那个多头注意力用的是 self-attention)

这意味着编码器和解码器输出维度都是一样的

6.4 预测

当尝试预测 t + 1 输出时,解码器中输入前 t 个预测值,在 self-attention 中前 t 个值作为 KV,第 t 个值作为 Q

6.5 Decoder 数据流

当前 token → embedding → X

DecoderBlock:
    attention1: # 这个是self attention,使用 KV cache
        Q = 当前 X
        K/V = 历史缓存 + 当前 X

    attention2:    # encoder 一次性输出完毕构造完 KV
        Q = attention1输出
        K/V = enc_output (固定)

为什么可以用 KV cache?

因为有因果 mask 的存在,计算 token1 的时候永远只会有 1 的数据,就算计算 token3 的时候重新计算 token1 也是同样的结果;另外 Decoder 的 mask 是下三角矩阵,未来的值不会影响到当前的值

本质的解释就是 attention 是非递归的,不像 RNN 依赖于过去的状态,这就导致 self attention 可以直接用缓存写入最新的 token 就行了

D2L:10 注意力机制
https://www.rainerseventeen.cn/index.php/Deep-Learning/68.html
本文作者 Rainer
发布时间 2026-03-01
许可协议 CC BY-NC-SA 4.0
发表新评论