94·进阶主题高级

Transformer 与注意力机制简介

nlptransformer

第94课 - Transformer 与注意力机制简介

所属模块: 进阶主题
难度: Advanced
标签: NLP, Transformer
上一课: NLP 基础:词嵌入、TF-IDF 与 Word2Vec
下一课: 计算机视觉基础:目标检测与语义分割

1. 学习目标

在本课程结束后,你将能够:

  • 理解 RNN/LSTM 在处理长序列时存在的根本问题(长程依赖)。
  • 掌握 自注意力机制(Self-Attention)的基本原理及其在序列建模中的优势。
  • 描述 Transformer 模型的核心架构,包括编码器-解码器结构和多头注意力。
  • 动手实现 一个最基础的自注意力计算模块。

2. 核心概念

2.1 从 RNN 到 Transformer 的演进

在上一课中,我们学习了 Word2Vec 等词嵌入技术,它们解决了将离散词语转化为连续向量的问题。传统上,处理序列数据(如句子)的主流模型是 RNN 及其变体 LSTM/GRU

RNN 的痛点:

  • 顺序计算: 处理序列必须从头到尾一步一步进行,无法并行,计算效率低。
  • 长程依赖困难: 虽然 LSTM 通过门控机制缓解了梯度消失问题,但对于非常长的序列(如长段落、篇章),其捕捉第一个词和最后一个词之间关系的能力仍然有限。

Transformer 的诞生: 2017年,论文《Attention Is All You Need》提出了 Transformer 架构。它摒弃了循环结构,完全基于注意力机制,实现了高度并行化,并能直接建立序列中任意两个位置之间的关联,从根本上解决了长程依赖问题。

2.2 注意力机制:让模型学会“聚焦”

注意力机制的核心思想: 当我们阅读一个句子时,理解当前词(如“它”)的含义,需要回顾句子中其他相关的词(如“动物”、“过马路”)。注意力机制就是让模型学会为句子中的每个词分配一个“关注度权重”,权重越高,表示这个词对理解当前词越重要。

自注意力: 在自注意力中,查询(Query)、键(Key)、值(Value)都来自于同一个输入序列。它让序列中的每个位置都能直接“看到”并“关注”其他所有位置,从而动态地计算每个词的新表示。

2.3 Transformer 架构概览

一个标准的 Transformer 模型包含一个编码器和一个解码器

  • 编码器(Encoder): 由 N 个相同的层堆叠而成。每层包含两个子层:
    1. 多头自注意力层: 让输入序列内部相互关注。
    2. 前馈神经网络层: 对注意力输出进行非线性变换。
  • 解码器(Decoder): 结构类似,但多了“编码器-解码器注意力层”,用于关注编码器的输出。

关键创新点:

  • 位置编码: 由于 Transformer 没有循环或卷积结构,它必须为输入序列添加位置编码,以注入词语顺序信息。
  • 多头注意力: 将注意力机制并行地运行多次(多个“头”),让模型从不同的子空间去关注信息,然后合并结果,增强了模型的表达能力。

3. 代码示例:实现一个简单的自注意力层

下面我们用 PyTorch 实现一个最基本的 缩放点积自注意力

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

class SimpleSelfAttention(nn.Module):
    def __init__(self, embed_dim):
        """
        简单的自注意力层。
        Args:
            embed_dim: 输入词向量的维度。
        """
        super(SimpleSelfAttention, self).__init__()
        self.embed_dim = embed_dim
        # 定义生成Q, K, V的线性变换层
        self.query = nn.Linear(embed_dim, embed_dim)
        self.key = nn.Linear(embed_dim, embed_dim)
        self.value = nn.Linear(embed_dim, embed_dim)

    def forward(self, x):
        """
        前向传播。
        Args:
            x: 输入张量,形状为 (batch_size, seq_len, embed_dim)
        Returns:
            attention_output: 注意力机制输出的上下文向量,形状 (batch_size, seq_len, embed_dim)
            attention_weights: 注意力权重矩阵,形状 (batch_size, seq_len, seq_len)
        """
        batch_size, seq_len, embed_dim = x.shape

        # 1. 通过线性层生成 Q, K, V
        Q = self.query(x)  # (batch_size, seq_len, embed_dim)
        K = self.key(x)    # (batch_size, seq_len, embed_dim)
        V = self.value(x)  # (batch_size, seq_len, embed_dim)

        # 2. 计算注意力分数 (Q * K^T) / sqrt(d_k)
        d_k = embed_dim  # 在此简化,实际多头注意力中 d_k = embed_dim / num_heads
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
        # scores 形状: (batch_size, seq_len, seq_len)

        # 3. 使用 softmax 归一化得到注意力权重
        attention_weights = F.softmax(scores, dim=-1)
        # attention_weights 形状: (batch_size, seq_len, seq_len)

        # 4. 将注意力权重与 V 相乘,得到上下文向量
        context = torch.matmul(attention_weights, V)
        # context 形状: (batch_size, seq_len, embed_dim)

        return context, attention_weights

# 使用示例
if __name__ == "__main__":
    # 假设批次大小为2,序列长度为5,嵌入维度为8
    batch_size, seq_len, embed_dim = 2, 5, 8
    # 创建一个随机输入序列
    input_sequence = torch.randn(batch_size, seq_len, embed_dim)

    # 初始化自注意力层
    self_attn = SimpleSelfAttention(embed_dim)

    # 前向传播
    output, weights = self_attn(input_sequence)

    print(f"输入形状: {input_sequence.shape}")
    print(f"输出(上下文向量)形状: {output.shape}")
    print(f"注意力权重形状: {weights.shape}")
    # 打印第一个样本中,第一个词对其他词的注意力分布
    print(f"样本1,第1个词对序列中每个词的注意力权重: {weights[0, 0, :].data}")

4. 实践练习

练习 1:计算注意力权重

假设我们有一个长度为3的序列,其嵌入维度为4。给定以下 Q, K, V 矩阵(为简化,已省略线性变换):

Q = [[1, 0, 1, 0],
     [0, 1, 0, 1],
     [1, 1, 1, 1]]
K = Q (相同)
V = [[1, 2, 3, 4],
     [5, 6, 7, 8],
     [9,10,11,12]]

请手动计算第一步的注意力分数矩阵(Q * K^T / sqrt(4)),并通过 softmax 得到注意力权重,最终输出第一个词(Q的第一行)的上下文向量。

预期输出: 注意力权重矩阵和最终的上下文向量值。

练习 2:理解位置编码的重要性

修改上面代码示例中的输入 input_sequence,使其三个样本分别代表不同的序列顺序:

  • 样本1: [词A, 词B, 词C, 词D, 词E]
  • 样本2: [词E, 词D, 词C, 词B, 词A] (完全倒序)
  • 样本3: [词A, 词C, 词B, 词E, 词D] (打乱顺序) 观察并解释:如果不加入任何位置信息,三个样本经过同一个 SimpleSelfAttention 层后,它们的输出 context 在理论上是否会完全不同?为什么?

练习 3:扩展到多头注意力

基于上面的 SimpleSelfAttention 类,创建一个 SimpleMultiHeadAttention 类。它接收参数 num_heads,将 embed_dim 分割为 num_heads 份,每份维度为 d_k = embed_dim / num_heads,然后独立计算自注意力,最后将多头结果拼接并线性投影。尝试实现它。

5. 常见错误

  1. 混淆“自注意力”与“注意力”: 自注意力(Self-Attention)特指 Q, K, V 来自同一序列。而更一般的注意力机制(如 seq2seq 中的)中,Q 来自解码器,K 和 V 来自编码器。
  2. 忘记缩放因子 1/sqrt(d_k) 当维度 d_k 较大时,点积结果的数值会很大,导致 softmax 梯度极小,训练困难。缩放可以缓解此问题。
  3. 忽略位置编码: 仅仅堆叠自注意力层,模型对序列顺序是“盲”的(置换不变性),无法区分“猫吃鱼”和“鱼吃猫”。位置编码是 Transformer 能理解序列顺序的关键。
  4. 误以为注意力权重代表“重要性”: 注意力权重是动态计算得到的,它反映了在当前上下文中,模型选择关注哪些信息,其解释性有时并不直观,不应过度解读。

6. 小结

本课我们迈入了现代深度学习 NLP 的核心领域。关键要点如下:

  • Transformer 的革命性在于用全注意力机制替代了循环结构,实现了并行计算强大的长程依赖建模能力。
  • 自注意力机制是 Transformer 的基石,它通过计算序列内部元素间的相关性权重,生成包含全局上下文的上下文表示。
  • 多头注意力前馈网络构成了 Transformer 编码器/解码器的核心模块。位置编码为其提供了至关重要的顺序信息。
  • Transformer 不仅是 BERT、GPT 等大语言模型的底层架构,其思想也已广泛应用于计算机视觉(Vision Transformer)等领域。

理解了 Transformer 的基本原理,你就掌握了打开整个现代 AI 应用宝库的一把关键钥匙。下一课,我们将离开 NLP,探索它在计算机视觉领域的精彩应用。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「计算机视觉基础:目标检测与语义分割」 以巩固所学知识。