注意力机制

注意力机制的引入

在生物学中,注意力受到非自主性提示恶化自主性提示有选择地引导。

非自主性提示是基于环境中物体的突出性和易见性。比如,对于五个物品:一份报纸、一篇研究论文、一杯咖啡、一本笔记和一本书,所有纸制品都是黑白印刷的,但咖啡杯是红色的。这个咖啡杯在这种视觉环境中是显眼和突出的,人本的注意力被不由自主地被吸引,所以我们会把视力最敏锐的地方放到咖啡上。

如果我们的主观意愿是读一本书,那么此时我们会自主地将视线注意力放到书本上,

在深度学习中,我们将非自主性提示称为Key(键),自主性提示成为Query(查询),而所有的信息内容称为Value(值)

以机器翻译为例,比如英译中,Key是源语言(英语)中每个词的“特征索引”,Query是目标语言(中文)生成当前位置内容的需求,Value是源语言中每个词的实际语义。

在深度学习中,”特征索引“、”需求“、”实际语义“并不是由人们显示提取的,而是通过模型自主学习到的:

比如,\(\mathbf{X}\)表示经过Embedding后的小批量文本数据,维度为[batch_size, seq_length, embed_size]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch
import torch.nn as nn

batch_size = 1
seq_len = 3
embed_size = 512

X = torch.randn(batch_size, seq_len, embed_size)

# 定义权重矩阵
d_k = 64
d_q = 64
d_v = 64
W_k = nn.Linear(embed_size, d_k, bias=False)
W_q = nn.Linear(embed_size, d_q, bias=False)
W_v = nn.Linear(embed_size, d_v, bias=False)

# 生成Key, Value, Query
K = W_k(X)
Q = W_q(X)
V = W_v(X)

由此计算出来的K的维度为[batch_size, seq_length, d_k]Q的维度为[batch_size, seq_length, d_q]V的维度为[batch_size, seq_length, d_v]

对于一个样本,对于第\(\tau\)个时间步的查询\(\mathbf{q}\in\mathbb{R}^{1\times d_q}\),该样本的Key为\(\mathbf{k}\in\mathbb{R}^{m\times d_k}\),Value为\(\mathbf{v}\in\mathbb{R}^{m\times d_v}\),其中\(m\)表示seq_length,也就是序列长度,对于第\(i\)个时间步的key和value:\(\mathbf{k}_i\in\mathbb{R}^{1\times d_k}\)\(\mathbf{v}_i\in\mathbb{R}^{1\times d_v}\),则注意力机制可以表示为函数: \[ f(\mathbf{q}) = \sum_{i=1}^m \alpha(\mathbf{q}, \mathbf{k}_i)\mathbf{v}_i \] 其中,\(f(\mathbf{q})\in\mathbb{R}^{1\times d_v}\)

\(\alpha(\mathbf{q}, \mathbf{k}_i)\)的公式为: \[ \alpha(\mathbf{q}, \mathbf{k}_i) = \mathrm{softmax}\Big(a(\mathbf{q}, \mathbf{k}_i)\Big) = \frac{\exp(a(\mathbf{q}, \mathbf{k}_i))}{\sum_{j=1}^m \exp(a(\mathbf{q}, \mathbf{k}_j))}\in\mathbb{R} \] \(a(\mathbf{q}, \mathbf{k}_i)\)是注意力评分函数,\(\alpha(\mathbf{q}, \mathbf{k}_i)\)则是注意力评分函数经过softmax运算变成概率得到的

注意力评分函数可以认为是当前位置的查询\(\mathbf{q}\),对不同时间步的信息进行打分,评价不同时间步信息的重要程度,而注意力机制就是根据注意力评分函数对所有的信息\(\mathbf{v}\)进行加权平均。

注意力评分函数

加性注意力

一般来说,当查询和键是不同长度的向量时,可以使用加性注意力作为评分函数。给定查询\(\mathbf{q}\in\mathbb{R}^{q}\)和键\(\mathbf{k_i}\in\mathbb{R}^{k}\),加性注意力(additive attention)的评分函数为 \[ a(\mathbf{q}, \mathbf{k}_i) = \mathbf{w}_v^\top\tanh(\mathbf{W}_q\mathbf{q} + \mathbf{W}_k\mathbf{k}_i)\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\)。即将查询和键连结起来后输入到一个多层感知机(MLP)中,感知机包含一个隐藏层,其隐藏单元数是一个超参数\(h\)。通过使用\(\tanh\)作为激活函数,并且禁用偏置项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class AdditiveAttention(nn.Module):
def __init__(self, key_size, query_size, hidden_size, dropout, **kwargs):
super().__init__()
self.W_k = nn.Linear(key_size, hidden_size, bias=False)
self.W_q = nn.Linear(query_size, hidden_size, bias=False)
self.w_v = nn.Linear(hidden_size, 1, bias=False)
self.tanh = nn.Tanh()
self.dropout = dropout

def forward(self, queries, keys, values):
# queries: [batch_size, query_size]
# keys: [batch_size, seq_len, key_size]
# values: [batch_size, seq_len, value_size]

queries = self.W_q(queries).unsqueeze(1)
# queries: [batch_size, 1, hidden_size],增加一个维度是为了与keys维度对齐,方便使用广播机制
keys = self.W_k(keys)
# keys: [batch_size, seq_len, hidden_size]

# 计算分数
scores = self.w_v(self.tanh(queries + keys)).squeeze(-1)
# scores: [batch_size, seq_len],删去了最后一个维度
alpha = torch.softmax(scores, dim=-1) # alpha: [batch_size, seq_len]

# 加权求和
context = torch.bmm(alpha.unsqueeze(1), values).squeeze(1)
# 将alpha维度变为[batch_size, 1, seq_len],便于和values进行批量矩阵乘法,变成[batch_size, 1, value_size]
# 该批量矩阵乘法实现了对v每个量进行加权平均的计算
# 然后降维,context维度变为[batch_size, value_size]
return context, alpha

上面代码中的queries是第\(\tau\)个时间步的小批量的查询,因此返回的注意力context维度为[batch_size, value_size]

缩放点积注意力

使用点积可以得到计算效率更高的评分函数,但是点积操作要求查询和键具有相同的长度\(d\)。假设查询和键的所有元素都是独立的随机变量,并且都满足零均值和单位方差,那么两个向量的点积的均值为0,方差为\(d\)。为了确保无论向量长度如何,点积的方差在不考虑向量长度的情况下仍然是1,我们再将点积除以\(\sqrt{d}\),则缩放点积注意力评分函数为: \[ a(\mathbf{q}, \mathbf{k}_i) = \mathbf{q}^\top\mathbf{k}_i/\sqrt{d} \] 在实践中,我们通常从小批量的角度来考虑提高效率(此处的小批量是只查询的小批量,依旧只有一个文本序列),例如基于\(n\)个查询和\(m\)个键-值对计算注意力(\(m\)即时间步的数量),其中查询和键的长度为\(d\),值的长度为\(v\)。查询\(\mathbf{Q}\in\mathbb{R}^{n\times d}\)、键\(\mathbf{K}\in\mathbb{R}^{m\times d}\)和值\(\mathbf{V}\in\mathbb{R}^{m\times v}\)的缩放点积注意力是: \[ \mathrm{softmax}\left(\frac{\mathbf{Q}\mathbf{K}^\top}{\sqrt{d}}\right)\mathbf{V}\in\mathbb{R}^{n\times v} \] 其中 \[ \frac{\mathbf{Q}\mathbf{K}^\top}{\sqrt{d}} = \left[ \begin{matrix} a(\mathbf{q}_1, \mathbf{k}_1)&a(\mathbf{q}_1, \mathbf{k}_2)&\cdots&a(\mathbf{q}_1, \mathbf{k}_m)\\ a(\mathbf{q}_2, \mathbf{k_1})&a(\mathbf{q}_2, \mathbf{k}_2)&\cdots&a(\mathbf{q}_2, \mathbf{k}_m)\\ \vdots&\vdots& &\vdots\\ a(\mathbf{q}_n, \mathbf{k_1})&a(\mathbf{q}_n, \mathbf{k}_2)&\cdots&a(\mathbf{q}_n, \mathbf{k}_m) \end{matrix} \right] \] 因此,\(\mathrm{softmax}\)是对每行的数据进行处理,需要加入参数dim=-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch
import torch.nn as nn
import torch.nn.functional as F

clss ScaledDotProductAttention(nn.Module):
def __init__(self, d_k, d_v):
super().__init__()
self.d_k = d_k
self.d_v = d_v

def forward(self, Q, K, V, mask=None):
# Q: [batch_size, n, d_k]
# K: [batch_size, m, d_k]
# V: [batch_size, m, d_v]
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5) # [batch_size, n, m]
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)

attn_weights = F.softmax(scores, dim=-1) # [batch_size, n, m]
output = torch.matmul(attn_weights, V)
# output: [batch_size, n, d_v]
return output, attn_weights

使用缩放点积注意力的优点:

  1. 高效:无额外参数,计算速度优于加性注意力。
  2. 可并行化:矩阵乘法适合 GPU 加速。
  3. 理论保障:缩放因子有效缓解梯度消失问题

与加性注意力机制对比:

特性 缩放点积注意力 加性注意力
计算方式 直接点积 + 缩放 神经网络融合 Query 和 Key
参数数量 无额外参数(仅需 \(\mathbf{Q}\),\(\mathbf{K}\),\(\mathbf{V}\) 的投影矩阵) 需额外参数(\(\mathbf{W}_q\),\(\mathbf{W}_k\),\(\mathbf{w}_v\)
计算复杂度 \(O(n⋅m⋅d_k)\) \(O(n⋅m⋅h)\)\(h\)为隐藏层大小)
适用场景 Query 和 Key 维度相同且较大时高效 Query 和 Key 维度不同或需非线性交互时更灵活
常用模型 Transformer、BERT、GPT Seq2Seq(如早期机器翻译)

上面代码中mask的作用:

在注意力机制中,mask的作用是控制模型对输入序列不同位置的访问权限,常用于屏蔽无效位置(如填充符<PAD>)或防止未来信息泄露(如自回归生成)。

Mask的两种主要类型:

  1. Padding Mask(填充掩码)

    • 场景:处理变长序列时,短序列会被填充符(如<PAD>)补齐到相同长度
    • 作用:防止模型关注填充符(无效位置)
    • 实现:将填充符位置的注意力分数设为极小数(如-1e9),使Softmax后权重趋近于0

    示例:

    输入序列为["I", "love", "nlp", "<pad>", "<pad>"],则对应的mask[1, 1, 1, 0, 0],表示前三个位置有效,后两个位置需要屏蔽。

  2. Sequence Mask(序列掩码):

    • 场景:自回归任务(如机器翻译的解码器),防止当前位置关注到未来位置。
    • 作用:确保生成第\(t\)个词时,仅依赖前\(t-1\)个词
    • 实现:使用上三角矩阵(对角线及以上为0,下三角为1)屏蔽未来位置

    mask的维度应该为[batch_size, n, m],如果维度为[batch_size, 1, m],会自动广播到对应维度

    示例:

    输入序列为[["I", "love", "deep", "learning", "<pad>"]],则对于\(\mathbf{q}_1, \cdots, \mathbf{q}_5\)

    1
    2
    3
    4
    5
    6
    7
    [
    [1, 0, 0, 0, 0],
    [1, 1, 0, 0, 0],
    [1, 1, 1, 0, 0],
    [1, 1, 1, 1, 0],
    [1, 1, 1, 1, 0]# 忽略填充<pad>
    ]

注意力机制
https://blog.shinebook.net/2025/04/19/人工智能/理论基础/深度学习/注意力机制/
作者
X
发布于
2025年4月19日
许可协议