深度学习:注意力机制(Attention Mechanism)
一、什么是注意力机制?
1.1 定义和概念公式
注意力机制(Attention Mechanism)是深度学习中的一种核心组件,它模拟人类认知过程中的选择性注意能力,允许模型动态地关注输入信息中的相关部分,而忽略无关信息。其核心思想是通过计算权重分布,对输入信息进行加权求和,从而生成一个聚焦的上下文表示。
基本概念
在注意力机制中,通常涉及三个关键组件:
- 查询(Query,QQuery, QQuery,Q):代表当前需要处理的任务或关注点。
- 键(Key,KKey, KKey,K):代表输入信息的标识或索引,用于与查询进行匹配。
- 值(Value,VValue, VValue,V):代表输入信息的实际内容,是加权求和的对象。
数学公式
Attention(Q,K,V)=∑iαi⋅Vi\text{Attention}(Q, K, V) = \sum_{i} \alpha_i \cdot V_iAttention(Q,K,V)=i∑αi⋅Vi
其中,权重 αi\alpha_iαi 通过softmax函数计算:
αi=exp(score(Q,Ki))∑jexp(score(Q,Kj))\alpha_i = \frac{\exp(\text{score}(Q, K_i))}{\sum_j \exp(\text{score}(Q, K_j))}αi=∑jexp(score(Q,Kj))exp(score(Q,Ki))
打分函数score(Q,Ki)\text{score}(Q, K_i)score(Q,Ki)常见的形式有:
- 点积打分:Q⋅KiQ \cdot K_iQ⋅Ki(适用于QQQ和KKK维度相同)
- 缩放点积打分:Q⋅Kidk\frac{Q \cdot K_i}{\sqrt{d_k}}dkQ⋅Ki(避免梯度消失dkd_kdk是KKK的维度)
- 加性打分:vTtanh(WQ+UKi)v^T \tanh(W Q + U K_i)vTtanh(WQ+UKi)(更灵活,但参数更多)
这些公式体现了注意力机制的核心:通过查询与键的相似度计算权重,然后对值进行加权求和,生成一个聚焦的表示。
1.2 例子
通过一个图书馆比喻来理解注意力机制的计算过程和物理含义。想象您走进一个大型图书馆(输入序列),想要找关于"深度学习和注意力机制"的书籍。图书馆管理员(注意力机制)会协助您完成这个过程。
- 查询(Query):您的问题:“我想找关于深度学习和注意力机制的书籍”。
- 键(Key):每本书的索引标签或关键词(如"深度学习"、“机器学习”、“注意力机制”)。
- 值(Value):书籍的实际内容和文化价值。
| 书籍 | 关键词(Key) | 内容价值(Value) |
|---|---|---|
| 书A | 深度学习, 神经网络 | 4.0 |
| 书B | 机器学习, Python | 3.0 |
| 书C | 注意力机制, AI | 4.5 |
| 书D | 计算机视觉, CNN | 3.5 |
| 书E | NLP, Transformer | 4.2 |
计算过程详解
-
第1步:计算匹配度,注意力分数,管理员将您的问题与每本书的关键词进行匹配,计算相似度:
- 书A:匹配"深度学习" → 分数 = 0.7
- 书B:无直接匹配 → 分数 = 0.1
- 书C:匹配"注意力机制" → 分数 = 0.9
- 书D:无直接匹配 → 分数 = 0.2
- 书E:部分相关(AI) → 分数 = 0.6
-
第2步:缩放处理,防止过度匹配:为避免某些书籍过度主导,进行缩放处理(假设平均关键词数dk=3d_k = 3dk=3,3≈1.732\sqrt3 ≈ 1.7323≈1.732):
- 书A:0.7 / 1.732 ≈ 0.404
- 书B:0.1 / 1.732 ≈ 0.058
- 书C:0.9 / 1.732 ≈ 0.520
- 书D:0.2 / 1.732 ≈ 0.115
- 书E:0.6 / 1.732 ≈ 0.346
-
第3步:转化为概率分布(Softmax),管理员将匹配度转化为推荐概率,确保总和为1:
S=exp(0.404)+exp(0.058)+exp(0.520)+exp(0.115)+exp(0.346)≈6.774S = \exp(0.404) + \exp(0.058) + \exp(0.520) + \exp(0.115) + \exp(0.346) \approx 6.774S=exp(0.404)+exp(0.058)+exp(0.520)+exp(0.115)+exp(0.346)≈6.774
wA=exp(0.404)S≈1.4976.774≈0.221(22.1%)w_A = \frac{\exp(0.404)}{S} \approx \frac{1.497}{6.774} \approx 0.221 \quad (22.1\%)wA=Sexp(0.404)≈6.7741.497≈0.221(22.1%)
wB=exp(0.058)S≈1.0606.774≈0.156(15.6%)w_B = \frac{\exp(0.058)}{S} \approx \frac{1.060}{6.774} \approx 0.156 \quad (15.6\%)wB=Sexp(0.058)≈6.7741.060≈0.156(15.6%)
wC=exp(0.520)S≈1.6826.774≈0.248(24.8%)w_C = \frac{\exp(0.520)}{S} \approx \frac{1.682}{6.774} \approx 0.248 \quad (24.8\%)wC=Sexp(0.520)≈6.7741.682≈0.248(24.8%)
wD=exp(0.115)S≈1.1226.774≈0.166(16.6%)w_D = \frac{\exp(0.115)}{S} \approx \frac{1.122}{6.774} \approx 0.166 \quad (16.6\%)wD=Sexp(0.115)≈6.7741.122≈0.166(16.6%)
wE=exp(0.346)S≈1.4136.774≈0.209(20.9%)w_E = \frac{\exp(0.346)}{S} \approx \frac{1.413}{6.774} \approx 0.209 \quad (20.9\%)wE=Sexp(0.346)≈6.7741.413≈0.209(20.9%) -
第4步:加权求和,生成最终输出,管理员根据权重从每本书中提取相关内容,整合成最终答案:
最终输出=0.221×4.0+0.156×3.0+0.248×4.5+0.166×3.5+0.209×4.2=3.927最终输出 = 0.221×4.0 + 0.156×3.0 + 0.248×4.5 + 0.166×3.5 + 0.209×4.2= 3.927最终输出=0.221×4.0+0.156×3.0+0.248×4.5+0.166×3.5+0.209×4.2=3.927
物理含义
- 动态聚焦:模型不是平等对待所有输入信息,而是根据当前任务(查询)动态调整关注点。在图书馆例子中,管理员根据您的具体需求,优先推荐相关书籍。
- 信息融合:最终输出(3.927)不是一个简单的平均值,而是融合了所有相关信息的加权和,既包含了高相关性的书籍(书C),也考虑了部分相关的书籍(书E),避免了信息丢失。
- 可解释性:权重分布(22.1%、15.6%等)清晰显示了模型决策的依据,例如书C权重最高(24.8%),表明它被判断为最相关。
二、软注意力机制(Soft Attention)
| 类型 | 特点 | 应用场景 |
|---|---|---|
| 软注意力 | 对所有输入位置分配连续权重,可微分 | 大多数深度学习任务 |
| 硬注意力 | 只关注一个或几个位置,不可微分 | 需要稀疏关注的场景 |
2.1 软注意力机制的核心思想
软注意力机制(Soft Attention)是注意力机制中最常用的一种形式,它通过计算所有输入信息的加权平均来实现“软性”选择,而不是硬性选择某一个输入。这种机制允许模型以可微的方式学习关注输入序列的不同部分,从而显著提升模型性能,特别是在序列到序列(Seq2Seq)任务中。
与硬注意力(Hard Attention)相比,软注意力的关键优势在于其可微性,使得它可以通过标准梯度下降进行端到端训练,而无需复杂的强化学习技巧。
2.2 传统Encoder-Decoder框架
在传统的Encoder-Decoder框架中,编码器将整个输入序列压缩成一个固定长度的上下文向量C,解码器则基于这个向量生成输出序列。这种方法在处理长序列时存在显著局限性,因为固定向量难以保留所有详细信息,导致信息瓶颈。
核心特点:
- 固定上下文向量:整个输入序列被编码成一个固定的上下文向量
C - 信息瓶颈:无论生成哪个输出词(y1y_1y1, y2y_2y2, y3y_3y3),都使用同一个
C - 序列处理:解码器按顺序生成输出,每个步骤依赖前一个输出
工作流程:
- 编码器(左侧):处理输入序列x1,x2,...,xmx_1,x_2,...,x_mx1,x2,...,xm,生成隐藏状态h1,h2,...,hmh_1,h_2,...,h_mh1,h2,...,hm
- 上下文向量:所有隐藏状态被压缩成固定向量
C - 解码器(右侧):基于
C和前一个输出,逐步生成y1,y2,...,yny_1,y_2,...,y_ny1,y2,...,yn
局限性:
- 长序列信息丢失严重
- 无法动态关注输入的不同部分
- 所有输出词共享相同的上下文信息


2.3 引入注意力机制的Encoder-Decoder框架
软注意力机制革命性地改进了这一框架,它为解码器的每一步生成一个动态的、专注于当前任务的上下文向量CiC_iCi:
Ci=∑j=1TxαijhjC_i = \sum_{j=1}^{T_x} \alpha_{ij} h_jCi=j=1∑Txαijhj
其中:
- TxT_xTx是输入序列长度
- hjh_jhj是编码器在第jjj个时间步的隐藏状态
- αijα_{ij}αij是在生成第iii个输出时对第jjj个输入的注意力权重
软注意力的计算可以分为三个关键步骤:
第一步:计算注意力得分
使用打分函数计算查询(Query)与每个键(Key)的相似度:
eij=score(si−1,hj)e_{ij} = \text{score}(s_{i-1}, h_j)eij=score(si−1,hj)
其中si−1s_{i-1}si−1是解码器上一时刻的隐藏状态(作为Query),hjh_jhj是编码器的隐藏状态(作为Key)。常用的打分函数包括:
- 加性模型:eij=vTtanh(Wsi−1+Uhj)e_{ij} = v^T \tanh(W s_{i-1} + U h_j)eij=vTtanh(Wsi−1+Uhj)
- 点积模型:eij=si−1Thje_{ij} = s_{i-1}^T h_jeij=si−1Thj
- 缩放点积模型:eij=si−1Thjdke_{ij} = \frac{s_{i-1}^T h_j}{\sqrt{d_k}}eij=dksi−1Thj(Transformer中使用)
第二步:Softmax归一化
将注意力得分通过softmax函数转换为概率分布:
αij=exp(eij)∑k=1Txexp(eik) \alpha_{ij} = \frac{\exp(e_{ij})}{\sum_{k=1}^{T_x} \exp(e_{ik})} αij=∑k=1Txexp(eik)exp(eij)
这确保了所有权重和为1,形成一个有效的概率分布。
第三步:加权求和
使用注意力权重对值(Value)进行加权求和,生成上下文向量:
Ci=∑j=1TxαijhjC_i = \sum_{j=1}^{T_x} \alpha_{ij} h_jCi=j=1∑Txαijhj
其中hjh_jhj既作为Key也作为Value(在基础实现中)。
键值对模式与软寻址
更一般地,软注意力机制可以理解为一种软寻址操作,其中输入信息被表示为键值对(Key-Value pairs):
- 键(Key):用于计算注意力分布,相当于信息的地址标签
- 值(Value):存储的实际内容,用于加权求和
- 查询(Query):当前的任务或关注点,用于检索相关信息
这种模式允许Key和Value具有不同的表示,增加了模型的灵活性。公式表示为:
Attention(Q,K,V)=softmax(QKTdk)V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V Attention(Q,K,V)=softmax(dkQKT)V
其中:
- QQQ是查询矩阵
- KKK是键矩阵
- VVV是值矩阵
- dkd_kdk是键的维度(用于缩放)

核心改进:
- 动态上下文向量:为每个输出词生成专属的上下文向量(C1,C2,C3)(C_1, C_2, C_3)(C1,C2,C3)
- 注意力分配:根据当前生成任务动态选择关注的输入部分
- 精准对齐:实现输入与输出之间的细粒度对应
工作流程:
- 编码器:处理输入X1,X2,X3,X4X_1,X_2,X_3,X_4X1,X2,X3,X4,生成丰富的隐藏状态表示
- 注意力计算:在生成每个YiY_iYi时:
- 计算与所有输入XjX_jXj的注意力权重
- 生成专属的上下文向量CiC_iCi
- 解码生成:基于CiC_iCi生成对应的输出YiY_iYi
优势体现:
- 解决信息瓶颈:每个输出词有量身定制的上下文
- 提升长序列性能:避免远距离信息丢失
- 增强可解释性:注意力权重显示对齐关系
三、自注意力机制(Self-Attention)
3.1 自注意力机制的核心思想
自注意力机制(Self-Attention)是注意力机制的一个重要变体,它将注意力机制应用于序列内部,而不是编码器与解码器之间。自注意力机制的核心思想是让序列中的每个元素都能够关注序列中的所有其他元素,从而捕捉序列内部的复杂依赖关系。
与传统的软注意力机制相比,自注意力机制的关键区别在于:
- 软注意力:连接不同序列(如源语言和目标语言)
- 自注意力:在同一个序列内部建立连接
这种机制特别适合处理长距离依赖问题,因为无论两个元素在序列中相距多远,自注意力机制都能直接建立它们之间的连接,而不需要像RNN那样经过多个时间步的传递。
3.2 自注意力机制的计算过程
自注意力机制的计算过程与软注意力类似,但具有一些独特的特点:
基本计算公式
自注意力机制的计算可以表示为:
SelfAttention(X)=Attention(Q,K,V) \text{SelfAttention}(X) = \text{Attention}(Q, K, V) SelfAttention(X)=Attention(Q,K,V)
其中查询(Q)、键(K)、值(V)都来自同一个输入序列X:
Q=XWQ,K=XWK,V=XWV Q = XW^Q, \quad K = XW^K, \quad V = XW^V Q=XWQ,K=XWK,V=XWV
这里WQW^QWQ, WKW^KWK, WVW^VWV是可学习的权重矩阵。
具体计算步骤
- 线性变换:将输入序列X通过三个不同的线性变换得到QQQ、KKK、VVV
- 注意力分数计算:计算每个位置与其他所有位置的相似度
- Softmax归一化:将分数转换为注意力权重
- 加权求和:使用权重对V进行加权,得到输出
数学表达式为:
Attention(Q,K,V)=softmax(QKTdk)V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dkQKT)V
其中dk\sqrt{d_k}dk是缩放因子,防止内积过大导致梯度消失。
3.3 自注意力机制的优势
- 解决长距离依赖问题:在传统的RNN中,信息需要经过多个时间步才能从序列开头传递到末尾,容易造成信息丢失或梯度消失。自注意力机制通过直接连接任意两个位置,完美解决了这个问题。
- 并行计算能力:与RNN的顺序处理不同,自注意力机制的所有计算都可以并行进行,大大提高了计算效率,特别适合GPU加速。
- 捕捉复杂语法结构:自注意力机制能够学习各种复杂的语法关系:
- 主谓一致:学习主语与谓语动词的匹配
- 指代消解:识别代词所指的对象
- 修饰关系:理解形容词与名词的修饰关系
3.4 多头自注意力机制
为了增强模型的表达能力,实践中通常使用多头自注意力(Multi-Head Self-Attention)。多头机制让模型能够同时从不同的角度关注信息:
MultiHead(Q,K,V)=Concat(head1,…,headh)WO \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h)W^O MultiHead(Q,K,V)=Concat(head1,…,headh)WO
其中每个注意力头:
headi=Attention(QWiQ,KWiK,VWiV) \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) headi=Attention(QWiQ,KWiK,VWiV)
多头机制的优势:
- 多样化关注:不同头可以学习关注不同类型的模式
- 增强表达能力:组合多个子空间的表示
- 提高鲁棒性:减少过拟合风险
3.5 自注意力机制计算示例
在句子"The animal didn’t cross the street because it was too tired"中:
- “it"指代"animal”
- 自注意力机制可以直接建立"it"与"animal"的连接,无论它们相距多远

import numpy as np
# ==================== 1. 输入序列表示与预处理 ====================
# 定义句子分词和位置索引
tokens = ["The", "animal", "didn't", "cross", "the", "street", "because", "it", "was", "too", "tired"]
positions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# 使用GloVe词向量(300维)的简化数值,这里随机生成300维的向量
np.random.seed(42) # 设置随机种子确保可重现
X = np.random.randn(len(tokens), 300) # 生成11个词的300维向量
print("输入矩阵 X 的形状:", X.shape) # (11, 300)
print("=" * 50)
# ==================== 2. 多头注意力参数设置 ====================
d_model = 300 # 词向量维度
num_heads = 3 # 使用3个注意力头
d_k = d_model // num_heads # d_k = 100
print(f"多头注意力参数: d_model={d_model}, num_heads={num_heads}, d_k={d_k}")
# ==================== 3. 初始化每个头的权重矩阵 ====================
# 为每个头初始化独立的WQ, WK, WV矩阵
WQ_heads = [] # 存储所有头的WQ矩阵
WK_heads = [] # 存储所有头的WK矩阵
WV_heads = [] # 存储所有头的WV矩阵
for i in range(num_heads):
# 初始化当前头的权重矩阵
WQ = np.random.randn(d_model, d_k) * 0.1 # 形状: (300, 100)
WK = np.random.randn(d_model, d_k) * 0.1 # 形状: (300, 100)
WV = np.random.randn(d_model, d_k) * 0.1 # 形状: (300, 100)
WQ_heads.append(WQ)
WK_heads.append(WK)
WV_heads.append(WV)
print(f"\n头 {i + 1} 权重矩阵形状:")
print(f" WQ_{i + 1}: {WQ.shape}")
print(f" WK_{i + 1}: {WK.shape}")
print(f" WV_{i + 1}: {WV.shape}")
print("=" * 50)
# ==================== 4. 计算每个头的Q, K, V矩阵 ====================
Q_heads = [] # 存储所有头的Q矩阵
K_heads = [] # 存储所有头的K矩阵
V_heads = [] # 存储所有头的V矩阵
for i in range(num_heads):
# 获取当前头的权重矩阵
WQ = WQ_heads[i]
WK = WK_heads[i]
WV = WV_heads[i]
# 计算当前头的Q, K, V
Q = np.dot(X, WQ) # 形状: (11, 100)
K = np.dot(X, WK) # 形状: (11, 100)
V = np.dot(X, WV) # 形状: (11, 100)
Q_heads.append(Q)
K_heads.append(K)
V_heads.append(V)
print(f"\n头 {i + 1} 的Q、K、V矩阵形状:")
print(f" Q_{i + 1}: {Q.shape}")
print(f" K_{i + 1}: {K.shape}")
print(f" V_{i + 1}: {V.shape}")
print("=" * 50)
# ==================== 5. 计算每个头的注意力输出(为所有位置) ====================
# 使用缩放点积注意力机制,为每个位置计算输出
attention_outputs_per_head = [] # 存储每个头的输出矩阵(形状: (11, 100))
for i in range(num_heads):
# 获取当前头的Q, K, V
Q = Q_heads[i] # 形状: (11, 100)
K = K_heads[i] # 形状: (11, 100)
V = V_heads[i] # 形状: (11, 100)
# 计算缩放点积注意力分数
attention_scores = np.dot(Q, K.T) / np.sqrt(d_k) # 形状: (11, 11)
# 应用Softmax得到注意力权重
attention_weights = np.exp(attention_scores - np.max(attention_scores, axis=-1, keepdims=True)) # 数值稳定性
attention_weights /= np.sum(attention_weights, axis=-1, keepdims=True) # 形状: (11, 11)
# 计算当前头的输出:加权求和V
head_output = np.dot(attention_weights, V) # 形状: (11, 100)
attention_outputs_per_head.append(head_output)
print(f"\n头 {i + 1} 输出矩阵形状: {head_output.shape}")
print("=" * 50)
# ==================== 6. 拼接所有头的输出 ====================
# 将所有头的输出拼接在一起(沿特征维度)
multi_head_output = np.concatenate(attention_outputs_per_head, axis=-1) # 形状: (11, 300)
print(f"拼接后多头输出形状: {multi_head_output.shape}")
# ==================== 7. 最终线性变换 ====================
# 应用输出权重矩阵W_O
W_O = np.random.randn(d_model, d_model) * 0.1 # 形状: (300, 300)
final_output = np.dot(multi_head_output, W_O) # 形状: (11, 300)
print(f"最终输出形状: {final_output.shape}")
print("=" * 50)
输入矩阵 X 的形状: (11, 300)
==================================================
多头注意力参数: d_model=300, num_heads=3, d_k=100
头 1 权重矩阵形状:
WQ_1: (300, 100)
WK_1: (300, 100)
WV_1: (300, 100)
头 2 权重矩阵形状:
WQ_2: (300, 100)
WK_2: (300, 100)
WV_2: (300, 100)
头 3 权重矩阵形状:
WQ_3: (300, 100)
WK_3: (300, 100)
WV_3: (300, 100)
==================================================
头 1 的Q、K、V矩阵形状:
Q_1: (11, 100)
K_1: (11, 100)
V_1: (11, 100)
头 2 的Q、K、V矩阵形状:
Q_2: (11, 100)
K_2: (11, 100)
V_2: (11, 100)
头 3 的Q、K、V矩阵形状:
Q_3: (11, 100)
K_3: (11, 100)
V_3: (11, 100)
==================================================
头 1 输出矩阵形状: (11, 100)
头 2 输出矩阵形状: (11, 100)
头 3 输出矩阵形状: (11, 100)
==================================================
拼接后多头输出形状: (11, 300)
最终输出形状: (11, 300)
==================================================
3.6 单头注意力热力图
import matplotlib.pyplot as plt
import seaborn as sns
import torch
from transformers import BertTokenizer, BertModel # 正确的导入方式
def plot_attention_heatmap(attention_weights, tokens_x, tokens_y, title="Attention Heatmap"):
"""
绘制注意力权重热力图
"""
plt.figure(figsize=(10, 8))
sns.heatmap(attention_weights,
xticklabels=tokens_x,
yticklabels=tokens_y,
cmap="YlOrRd",
annot=True,
fmt=".2f",
cbar_kws={"shrink": 0.8})
plt.title(title, fontsize=16, pad=20)
plt.xlabel("Input Tokens", fontsize=12)
plt.ylabel("Output Tokens", fontsize=12)
plt.xticks(rotation=45, ha='right')
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()
def extract_attention_weights(text, layer_idx=0, head_idx=0):
"""
从BERT模型提取注意力权重
"""
# 加载预训练模型和tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased', output_attentions=True)
# Tokenize输入
inputs = tokenizer(text, return_tensors="pt")
# 获取注意力权重
with torch.no_grad():
outputs = model(**inputs)
attentions = outputs.attentions # 所有层的注意力权重
# 提取特定层和头的注意力权重
attention_weights = attentions[layer_idx][0, head_idx].numpy()
# 获取tokens
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
return attention_weights, tokens
# 示例使用
if __name__ == "__main__":
text = "The animal didn't cross the street because it was too tired"
try:
# 提取注意力权重
attention_weights, tokens = extract_attention_weights(text)
# 可视化
plot_attention_heatmap(attention_weights, tokens, tokens, "BERT Attention Weights")
except Exception as e:
print(f"发生错误: {e}")
print("请确保已安装 transformers 库: pip install transformers")
print("如果已安装,请检查网络连接(首次运行需要下载模型)")

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)