NLP学习笔记09:注意力机制——从 Self-Attention 到 Transformer

作者:Ye Shun
日期:2026-04-18

一、前言

注意力机制(Attention Mechanism)是现代深度学习,尤其是自然语言处理中的核心技术之一。它的灵感来自人类的认知过程:当我们阅读一句话时,并不会平均地处理每一个词,而是会把更多注意力放在当前最相关的部分。

例如在阅读句子:

猫坐在垫子上,因为它很舒服。

理解“它”指代什么时,人不会机械地平均看待所有词,而会自动把注意力集中到与“它”最相关的候选对象上。注意力机制希望让模型也具备这种“动态聚焦”的能力。

它的核心思想可以概括为一句话:

根据输入中不同部分对当前任务的重要性,动态分配不同权重。

这套思想已经成为 Transformer、BERT、GPT、T5 等模型的基础。理解注意力机制,几乎就是理解现代 NLP 的关键起点。

这篇笔记将围绕以下几个问题展开:

  1. 注意力机制到底在解决什么问题
  2. Query、Key、Value 分别是什么意思
  3. 自注意力和多头注意力是怎么工作的
  4. 注意力机制为什么能推动 Transformer 崛起
  5. 实践中如何实现和可视化注意力

二、为什么需要注意力机制

在注意力机制出现之前,处理序列数据的主力模型主要是 RNN、LSTM 和 GRU。它们虽然能处理上下文,但存在一些明显局限。

1. 长距离依赖难题

在长序列中,前后信息相距很远时,RNN 系列模型往往难以稳定捕捉这种依赖。即便 LSTM 和 GRU 已经缓解了问题,也并没有彻底解决。

2. 顺序计算限制

RNN 在时间步之间存在强依赖,因此很难像普通矩阵运算那样充分并行。训练长序列时,效率容易受到影响。

3. 信息压缩瓶颈

在早期 Seq2Seq 模型中,编码器通常需要把整个源句压缩成一个固定长度向量,再交给解码器。这个单一向量很容易丢失细节,尤其是在长句子上。

4. 注意力给出的改进思路

注意力机制的关键改进是:

  • 不要求把所有信息压缩成一个固定向量
  • 在每一步都能动态查看输入中的不同位置
  • 根据当前任务重点重新分配权重

也就是说,模型在做决策时,不再只依赖“一个总摘要”,而是可以“回头查看原文中最相关的部分”。

三、注意力机制的基本概念

注意力机制最经典的数学形式是:

Attention(Q, K, V) = softmax(QK^T / √d_k) V

这是缩放点积注意力(Scaled Dot-Product Attention)的表达式。

1. Q、K、V 分别是什么

这三个量可以理解为:

  • Q(Query):当前要查询什么
  • K(Key):每个位置提供的“索引”或“匹配依据”
  • V(Value):每个位置真正携带的信息

可以把它类比成数据库检索:

  • Query 是检索请求
  • Key 是检索键
  • Value 是对应的数据内容

模型会先用 Q 和所有 K 做匹配,算出“当前应该关注谁”;再用这些权重对所有 V 加权求和,得到最终输出。

2. 为什么要除以 √d_k

如果向量维度很大,QK^T 的点积值容易变得很大,导致 softmax 之后分布过于尖锐,训练不稳定。

所以引入缩放项 √d_k,让数值范围更平衡,梯度更稳定。

3. softmax 的作用

softmax 会把匹配分数转换成一组和为 1 的权重,表示模型对不同位置的关注比例。

这就意味着:

  • 权重越大,说明模型越关注那个位置
  • 权重越小,说明那个位置对当前计算贡献较少

四、自注意力机制(Self-Attention)

自注意力(Self-Attention)是注意力机制中最关键的一种形式。它的特点是:

序列中的每个位置,都可以和序列中的所有其他位置建立联系。

也就是说,在自注意力中:

  • Query 来自输入序列本身
  • Key 来自输入序列本身
  • Value 也来自输入序列本身

1. 工作流程

对于一个输入序列中的某个位置,自注意力通常按以下步骤工作:

  1. 计算当前词和所有词之间的相关性分数
  2. 对分数做 softmax,得到注意力权重
  3. 用这些权重对所有 Value 做加权求和
  4. 得到当前词新的上下文表示

2. 一个简化示例

import torch
import torch.nn.functional as F


def self_attention(query, key, value):
    scores = torch.matmul(query, key.transpose(-2, -1)) / (query.size(-1) ** 0.5)
    weights = F.softmax(scores, dim=-1)
    return torch.matmul(weights, value)

3. 自注意力的优势

全局上下文感知

每个位置都能直接访问整个序列中的所有位置,而不像 RNN 那样必须逐步传递信息。

更适合并行计算

自注意力可以把一个序列中所有位置的相关性计算写成矩阵乘法,因此更适合现代 GPU 并行计算。

需要特别说明的是:

  • 它“更容易并行”
  • 但不代表它在序列长度上的复杂度更低

标准自注意力在序列长度为 n 时,时间和内存开销通常是 O(n^2)。这是它和 RNN 很不一样、也很重要的一个点。

更强的长距离建模能力

两个相距很远的词之间,也可以通过一次注意力计算直接建立联系,而不需要跨越很多时间步传递。

4. 自注意力的一个限制

自注意力本身并不天然编码位置信息。换句话说,如果不额外加入位置编码,它并不知道“哪个词在前,哪个词在后”。

所以在 Transformer 中,自注意力通常要配合位置编码(Positional Encoding)一起使用。

五、多头注意力(Multi-Head Attention)

单头注意力虽然已经很强,但模型在一次注意力计算中,可能只学到某一种关系模式。多头注意力(Multi-Head Attention)就是为了解决这个问题。

它的核心思想是:

把注意力机制并行做很多次,让不同的头去关注不同类型的关系。

1. 多头注意力的结构

多头注意力通常包括:

  • 多组独立的 QKV 线性变换
  • 多个并行注意力头
  • 拼接多个头的输出
  • 再通过一个线性层映射回原维度

2. 为什么多头更强

不同注意力头可能学习到不同模式,例如:

  • 一个头关注主谓关系
  • 一个头关注长距离依赖
  • 一个头关注局部词组搭配
  • 一个头关注指代消解

这会显著增强模型的表达能力。

3. 多头注意力示例

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


class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super().__init__()
        self.d_model = d_model
        self.num_heads = num_heads
        self.d_k = d_model // 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, query, key, value):
        batch_size = query.size(0)

        Q = self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        K = self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        V = self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)

        scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
        weights = F.softmax(scores, dim=-1)
        output = torch.matmul(weights, V)

        output = output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
        return self.W_o(output)

六、注意力机制在 NLP 中的应用

注意力机制已经成为现代 NLP 系统的核心组件,尤其在 Transformer 架构中几乎无处不在。

1. 机器翻译

在经典的 Seq2Seq with Attention 中,解码器在生成每个目标词时,不再只依赖固定长度的编码向量,而是动态关注源句中最相关的部分。

这显著改善了长句翻译质量。

2. 文本摘要

摘要模型需要识别原文中的关键信息。注意力机制可以帮助模型聚焦核心句子或核心片段,提高摘要生成质量。

3. 问答系统

问答任务中,经常需要计算“问题”和“文档”之间的对应关系。交叉注意力(Cross-Attention)可以帮助模型找到最相关的证据片段。

4. 语言模型

GPT 系列模型使用掩码自注意力(Masked Self-Attention),保证每个位置只能看到它前面的内容,从而实现自回归生成。

5. 表示学习模型

BERT 使用双向自注意力,可以同时利用左右上下文,因此在文本理解任务中表现非常强。

七、BERT 中的注意力

BERT 是注意力机制应用得最典型的模型之一。它本质上是由多层 Transformer Encoder 堆叠而成。

1. BERT 的关键特点

  • 使用双向自注意力
  • 每层都有多头注意力
  • 通过多层堆叠不断增强上下文表示

2. 如何获取注意力权重

在 Hugging Face 中,如果想拿到 BERT 的注意力权重,一般要显式设置 output_attentions=True

from transformers import BertModel, BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")

inputs = tokenizer("Hello, my dog is cute", return_tensors="pt")
outputs = model(**inputs, output_attentions=True)

attention = outputs.attentions

这样 outputs.attentions 中才会包含各层、各头的注意力权重。

八、注意力机制的常见变体

随着序列越来越长、任务越来越复杂,研究者提出了很多注意力机制的变体。

1. 缩放点积注意力

这是 Transformer 中最经典的版本:

  • 使用点积计算相似度
  • √d_k 缩放
  • 计算效率高

2. 加法注意力(Additive Attention)

这是更早期的一种注意力形式,常见于 Bahdanau Attention。它通过一个小型前馈网络来计算 Query 和 Key 的兼容性。

相比点积注意力:

  • 计算更灵活
  • 在低维场景下有时更稳定
  • 但效率通常不如点积注意力

3. 局部注意力(Local Attention)

局部注意力只关注输入的一部分,而不是整段序列。这样可以显著减少计算成本。

4. 稀疏注意力(Sparse Attention)

稀疏注意力不会让每个位置都和所有位置建立连接,而是只计算部分位置之间的注意力。例如 Longformer 中的滑动窗口注意力。

这类方法的目标都是一样的:

在尽量保留建模能力的同时,降低标准自注意力的二次复杂度开销。

九、实践练习建议

练习1:实现基础注意力机制

可以先写一个最简单的 attention 模块,输入一组隐藏状态,输出上下文向量和注意力权重:

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


class SimpleAttention(nn.Module):
    def __init__(self, hidden_size):
        super().__init__()
        self.attention = nn.Linear(hidden_size, 1)

    def forward(self, encoder_outputs):
        attention_scores = self.attention(encoder_outputs).squeeze(2)
        attention_weights = F.softmax(attention_scores, dim=1)
        context_vector = torch.bmm(attention_weights.unsqueeze(1), encoder_outputs)
        return context_vector.squeeze(1), attention_weights

这个练习很适合帮助理解:

  • 注意力分数是怎么来的
  • 权重是如何归一化的
  • 上下文向量是如何加权求和得到的

练习2:可视化注意力权重

如果把注意力权重画成热力图,会更直观地看到模型到底在关注什么。

import matplotlib.pyplot as plt
import seaborn as sns


def plot_attention(attention_weights, source_tokens, target_tokens):
    plt.figure(figsize=(10, 8))
    sns.heatmap(
        attention_weights,
        xticklabels=source_tokens,
        yticklabels=target_tokens,
        cmap="YlGnBu"
    )
    plt.xlabel("Source Tokens")
    plt.ylabel("Target Tokens")
    plt.title("Attention Weights Visualization")
    plt.show()

这个练习对理解机器翻译和 Transformer 非常有帮助。

十、学习建议

如果你想真正把注意力机制学透,我建议按下面的顺序来:

  1. 先理解最基本的加权求和思想
  2. 再理解 Query、Key、Value 的角色分工
  3. 然后理解自注意力和多头注意力
  4. 接着理解注意力如何进入 Transformer
  5. 最后再去看 BERT、GPT、T5 等大模型

这样更容易把“公式、结构和应用”串起来。

十一、总结

注意力机制的本质,是让模型在处理输入时,不必平均看待所有信息,而是能够根据当前任务动态聚焦最相关的部分。

从方法角度看:

  • 注意力机制解决了固定向量瓶颈问题
  • 自注意力让序列中任意位置都能直接交互
  • 多头注意力让模型可以并行学习多种关系模式

从应用角度看:

  • 它推动了机器翻译、问答、摘要、语言模型等任务的发展
  • 它也是 Transformer、BERT、GPT 等现代模型的核心基础

理解注意力机制之后,再去看 Transformer,就会更容易明白为什么现代 NLP 会从 RNN 时代走向自注意力时代。

Logo

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

更多推荐