一、注意力机制基本原理

  1. 动机
    传统的 RNN/CNN 在处理长序列时,容易出现“信息遗忘”或“长依赖难以捕捉”的问题。注意力机制通过“动态加权”——在不同位置分配不同“关注度”(权重)——让模型在解码或特征聚合时,将更多注意力放在最相关的输入上,从而:

    • 缓解长距离依赖
    • 提升对关键信息的捕获能力
    • 增强可解释性:权重分布可视化后,可以直观看到模型聚焦的位置
  2. 核心思想
    给定一组“值”VVV和与之对应的“键”KKK,对于每个“查询”QQQ,计算它与所有键的相似度分数(Score),再对这些分数做 Softmax,得到权重向量;最后以权重为系数,对值向量做加权求和,得到注意力输出:
    Attention(Q,K,V)  =  softmax(QK⊤dk) V \mathrm{Attention}(Q,K,V) \;=\; \mathrm{softmax}\Bigl(\tfrac{QK^\top}{\sqrt{d_k}}\Bigr)\,V Attention(Q,K,V)=softmax(dk​​QK⊤​)V
    其中,dk\sqrt{d_k}dk​​ 是缩放因子,用以控制内积数值量级,确保权重是单位方差的,保持 Softmax 梯度稳定。


二、Query、Key、Value 定义与工作流程

组件作用形状(Batch, Seq, Dim)
Query要“查询”的向量集合,一般来自解码器当前步或同一序列(B,Lq,dk)(B, L_q, d_k)(B,Lq​,dk​)
Key用于“匹配”查询的向量集合,一般来自编码器输出或同一序列(B,Lk,dk)(B, L_k, d_k)(B,Lk​,dk​)
Value与 Key 一一对应的“值”向量集合,最终以加权和形式输出(B,Lv,dv)(B, L_v, d_v)(B,Lv​,dv​), 通常 Lk=LvL_k=L_vLk​=Lv​
  1. 投影(Projection)
    通常先用三组线性变换,将同一样本输入 X\mathbf{X}X 映射到三个子空间:
    Q=XWQ,K=XWK,V=XWV Q = XW^Q,\quad K = XW^K,\quad V = XW^V Q=XWQ,K=XWK,V=XWV
    其中 (WQ,WK\in\mathbb{R}^{d_{\text{model}}\times d_k}),(WV\in\mathbb{R}{d_{\text{model}}\times d_v})。

  2. 计算相似度(Score)
    Score(Qi,Kj)  =  Qi⋅Kj(i=1…Lq,  j=1…Lk) \mathrm{Score}(Q_i,K_j) \;=\; Q_i \cdot K_j \quad (i=1\dots L_q,\; j=1\dots L_k) Score(Qi​,Kj​)=Qi​⋅Kj​(i=1…Lq​,j=1…Lk​)
    得到一个 (Lq×Lk)(L_q\times L_k)(Lq​×Lk​) 的打分矩阵。

  3. 缩放与归一化
    Score~=Scoredk,A=softmax(Score~)(沿 K 维度) \widetilde{\mathrm{Score}} = \frac{\mathrm{Score}}{\sqrt{d_k}},\quad A = \mathrm{softmax}\bigl(\widetilde{\mathrm{Score}}\bigr)\quad (\text{沿 }K\text{ 维度}) Score=dk​​Score​,A=softmax(Score)(沿 K 维度)
    使得打分数值不至于过大或过小,保持梯度有效。

  4. 加权求和输出
    Attention(Q,K,V)=A  V,yi=∑j=1LkAij Vj \mathrm{Attention}(Q,K,V) = A\;V,\quad y_i = \sum_{j=1}^{L_k} A_{ij}\,V_j Attention(Q,K,V)=AV,yi​=j=1∑Lk​​Aij​Vj​
    最终输出与 Query 个数相同,每个 yiy_iyi​ 都融合了所有 Value,重点突出了与 QiQ_iQi​ 相关的部分。


三、缩放点乘注意力的数学公式

设单个 Query 向量维度为 dkd_kdk​,则整批计算可写为矩阵形式:
Attention(Q,K,V)=softmax(QK⊤dk) V, \mathrm{Attention}(Q,K,V) = \mathrm{softmax}\Bigl(\tfrac{QK^\top}{\sqrt{d_k}}\Bigr)\,V, Attention(Q,K,V)=softmax(dk​​QK⊤​)V,

  • Q∈RB×Lq×dkQ\in\mathbb{R}^{B\times L_q\times d_k}Q∈RB×Lq​×dk​
  • K∈RB×Lk×dkK\in\mathbb{R}^{B\times L_k\times d_k}K∈RB×Lk​×dk​
  • V∈RB×Lk×dvV\in\mathbb{R}^{B\times L_k\times d_v}V∈RB×Lk​×dv​
  • 输出为 RB×Lq×dv\mathbb{R}^{B\times L_q\times d_v}RB×Lq​×dv​

四、PyTorch 实现示例

下面给出一个简洁版的「缩放点乘注意力」函数,以及如何在多头注意力(Multi‑Head Attention)中使用它。

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

def scaled_dot_product_attention(q, k, v, mask=None):
    """
    q, k, v: 张量形状均为 (B, num_heads, L, d_k)
    mask: 可选张量,形状 (B, 1, L_q, L_k),用于屏蔽不可关注位置
    """
    d_k = q.size(-1)
    # 1. 计算未缩放打分
    scores = torch.matmul(q, k.transpose(-2, -1))  # -> (B, heads, L_q, L_k)
    # 2. 缩放
    scores = scores / math.sqrt(d_k)
    # 3. 可选屏蔽
    if mask is not None:
        scores = scores.masked_fill(mask == 0, float('-inf'))
    # 4. 归一化
    attn = F.softmax(scores, dim=-1)               # -> (B, heads, L_q, L_k)
    # 5. 加权求和
    output = torch.matmul(attn, v)                 # -> (B, heads, L_q, d_k)
    return output, attn

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super().__init__()
        assert d_model % num_heads == 0
        self.d_k = d_model // num_heads
        self.num_heads = num_heads

        self.w_q = nn.Linear(d_model, d_model)
        self.w_k = nn.Linear(d_model, d_model)
        self.w_v = nn.Linear(d_model, d_model)
        self.w_o = nn.Linear(d_model, d_model)

    def forward(self, x, mask=None):
        B, L, _ = x.size()
        # 1. 线性映射并切分多头
        q = self.w_q(x).view(B, L, self.num_heads, self.d_k).transpose(1,2)
        k = self.w_k(x).view(B, L, self.num_heads, self.d_k).transpose(1,2)
        v = self.w_v(x).view(B, L, self.num_heads, self.d_k).transpose(1,2)
        # q,k,v: (B, heads, L, d_k)

        # 2. 点乘注意力
        attn_output, attn_weights = scaled_dot_product_attention(q, k, v, mask)
        # attn_output: (B, heads, L, d_k)

        # 3. 合并多头
        attn_output = attn_output.transpose(1,2).contiguous().view(B, L, -1)
        # 4. 最后一层线性
        output = self.w_o(attn_output)  # (B, L, d_model)
        return output, attn_weights

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐