Transformer模型深度解析

[!info] 阅读导览
全文 10 节:背景(1.1)→ 解决的主要问题(1.2)→ 总体框架(1.3)→ Encoder 与 Decoder 概览(1.4)→ 输入(1.5:Tokenizer / Embedding)→ Multi-Head Attention(1.6)→ Decoder 结构(1.7)→ 附录完整代码(1.8)→ 参考文献(1.9)→ 扩展阅读(1.10)

flowchart LR
    A[输入文本] --> B[Tokenizer 分词]
    B --> C[Token Embedding + Positional Encoding]
    C --> D[Encoder × 6<br>Multi-Head Self-Attention + FFN]
    D --> E[Encoder-Decoder Attention]
    E --> F[Decoder × 6<br>Masked Self-Attention + FFN]
    F --> G[Linear + Softmax]
    G --> H[输出序列]

1. Transformer 模型深度解析

1.1. 背景

2017 年,谷歌团队撰写了论文《Attention Is All You Need》,恐怕连作者都未曾预料到,这个最初为提升机器翻译效率设计的架构,会在短短八年间成为人工智能的"通用计算引擎"。

从智能手机的实时翻译到科研机构的蛋白质结构预测,从虚拟人的对话生成到金融市场的舆情分析,Transformer 架构正以惊人的渗透力重构人机交互的每个维度。

1.1.1. 架构突破(2017)

原始 Transformer 凭借自注意力机制突破序列建模瓶颈,其并行处理能力使训练速度较 RNN 提升近 10 倍。如同内燃机取代蒸汽机,这种架构迅速在机器翻译领域确立新标准。

1.1.2. 规模爆发(2018-2020)

BERT 与 GPT 分道扬镳:前者通过双向注意力成为语言理解标杆,后者凭借自回归生成开辟创作新大陆。

这一时期,Transformer 的参数规模从 3 亿(GPT-1)激增至 1750 亿(GPT-3),展现出惊人的可扩展性。

1.1.3. 泛化扩张(2021-2025)

随着 DeepSeek-R1 等国产大模型崛起,Transformer 开始突破自然语言边界:

  • 在 AlphaFold 中解析蛋白质 3D 结构,
  • 在 Stable Diffusion 中生成图像,甚至参与数学定理证明。其"注意力即计算"的核心理念正成为多模态智能的基础范式。

当我们站在 2025 年回望,Transformer 已不仅是技术名词,更代表着一种认知范式的转移——它证明通过纯粹的注意力机制,机器可以建立对人类语言乃至复杂世界的深刻理解。

本文将揭示这场革命的技术本质,对 Transformer 模型进行深度解析。

图片

1.2. Transformer 模型解决的主要问题

Transformer 模型主要解决了自然语言处理(NLP)和跨模态任务中的三大核心难题:

  1. 长距离依赖建模:传统 RNN/LSTM 需按序列顺序处理数据,远距离词义关联易丢失。Transformer 的自注意力机制能直接建立任意位置词语间的联系,例如在"猫坐在垫子上,因为它柔软"中精准判断"它"指代"垫子"而非"猫"。

  2. 并行计算瓶颈:RNN 的时序依赖性导致训练效率低下。Transformer 通过自注意力层实现全序列并行处理,训练速度提升 10 倍以上。

  3. 跨模态统一架构:从文本(BERT/GPT)、图像(ViT、Swin Transformer)到多模态任务,Transformer 通过模块化设计实现了不同数据类型的统一处理。

    • 例如翻译的跨语言转换和图文生成均基于此架构。当前在机器翻译、文本生成、蛋白质结构预测等领域的成功,验证了 Transformer 作为基础架构的普适性。

1.3. 总体框架

Transformer 模型采用经典的编码器-解码器(Encoder-Decoder)架构,其中:

  • 编码器由 6 个完全相同的层堆叠而成,每层包含多头自注意力机制和前馈神经网络
  • 解码器同样由 6 层组成,但增加了编码器-解码器注意力层,用于融合源语言信息。这种分层结构使模型能够逐级提取和组合不同层次的语义特征。

图片

把 Transformer 模型看做一个黑盒函数。通过这个黑盒函数,可以完成不同语言之间的翻译。

图片

Transformer 模型主要包含两大部分——编码器(encoding)和解码器(decoding)。

图片

1.4. Encoder 与 Decoder 概览

Transformer 架构由 Encoder 和 Decoder 两个部分组成:其中 Encoder 和 Decoder 都是由 N=6 个相同的层堆叠而成。

图片

Multi-Head Attention 结构是 Transformer 模型的核心结构,由多个 Self-Attention 组成。

部件 结构 层数 主要模块
Encoder 编码器层堆叠 N=6 层 Self-Attention + Feed Forward
Decoder 解码器层堆叠 N=6 层 Self-Attention + Encoder-Decoder Attention + Feed Forward

1.5. 输入

1.5.1. Tokenizer 预处理

在基于 Transformer 架构的大语言模型中,原始输入通常是字符串形式的自然语言文本。由于神经网络模型本身无法直接处理文本数据,因此需要通过 Tokenizer(分词器)进行预处理转换。这一过程主要包含三个关键步骤:

  1. 分词(Tokenization):将输入文本按照预定义的规则切分为离散的词元(token),这些词元可能是完整单词(如 “transformer”)、子词单元或特殊符号(如 “[CLS]”)。

    • 不同的分词策略(如 WordPiece、BPE 等)会影响最终的分词结果。
  2. 词表映射(Vocabulary Mapping):每个 token 会被映射到预训练模型词表中对应的唯一整数 ID。

    • 例如在 BERT 模型中,“the” 可能被映射为 1996,“cat” 被映射为 4937。这一步骤实现了文本到数值的转换。
  3. 向量生成(input_ids 构造):最终输出是一个由 token ID 构成的数值向量(单句输入)或矩阵(多句输入),同时还会生成 attention_mask 等辅助张量。

    • 这些数值化的表示将作为模型的直接输入。

以下是通过 Hugging Face 的 transformers 库实现 BERT 模型预处理的标准代码示例:

from transformers import BertTokenizer, BertModel
import torch

# 1. 加载预训练的 BERT tokenizer 和模型
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

# 2. 输入文本
text = "Attention Is All You Need"

# 3. 分词并映射为 token ID 序列
inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)

# 输出 token IDs
print("Token IDs:", inputs['input_ids'])

# 4. 传入模型,获取输出
outputs = model(**inputs)

# 5. 获取最后一层的隐藏状态表示
last_hidden_states = outputs.last_hidden_state
print("Last hidden states shape:", last_hidden_states.shape)

原始输入文本 “Attention Is All You Need” 经过 Tokenizer 处理后的完整转换流程如下:

Token IDs: tensor([[ 101, 3086, 2003, 2035, 2017, 2342, 102]])
Last hidden states shape: torch.Size([1, 7, 768])

其中:

  • 101: [CLS] 标记
  • 102: [SEP] 标记
  • [3086, 2003, 2035, 2017, 2342]:表示 token 在词表中的索引

[!warning] 常见报错
bert = BertModel.from_pretrained("bert-base-uncased") 出现错误:OSError: We couldn't connect to https://huggingface.co to load this model, couldn't find it in the cached files and it looks like ...。错误原因:无法连接到 https://huggingface.co 来加载此模型。参考 CSDN:Bert 下载和使用(以 bert-base-uncased 为例)。

在基于 Transformer 架构的大语言模型(LLM)处理流程中,input_ids 作为核心输入要素,是由文本经过 Tokenizer 预处理后生成的整数向量。该向量中的每个元素对应原始文本中 token 在预训练词表中的唯一索引位置,完整记录了输入文本的数字化表示。

1.5.2. Embedding 层

在基于 Transformer 架构的大型语言模型(LLM)中,嵌入层(Embedding Layer)作为模型的第一级数据处理单元,承担着将离散的 token ID(如 input_ids 中的整数索引)转换为连续稠密向量空间表示的关键职能。

这种转换不仅实现了文本数据的数值化表征,更通过高维空间的几何关系(如余弦相似度)编码了丰富的语义信息,为后续的自注意力机制等神经网络操作提供了可计算的数学基础。

值得注意的是,现代嵌入层技术已突破单一文本模态的限制:

  • 文本嵌入(Token Embedding):将每个 token ID 映射为固定维度的向量表示。例如在 BERT-base 模型中,每个 token 被转换为 768 维的向量。这种映射关系通过预训练学习得到,能够捕捉词汇间的语义关联。

  • 位置嵌入(Positional Embedding):由于 Transformer 的自注意力机制本身不具备位置信息感知能力,需要通过位置嵌入来编码序列中每个 token 的位置信息。常见的位置编码方式包括:

    • 正弦余弦函数(Sinusoidal):如原始 Transformer 论文提出的固定位置编码
    • 可学习位置编码(Learnable):如 BERT 等模型采用的可训练位置嵌入
  • 跨模态嵌入(Cross-modal Embedding):在多模态 Transformer 中,嵌入层还需要处理图像、音频等非文本输入,通过特定的编码器(如 ViT 的图像分块嵌入)将其转换为与文本嵌入兼容的向量表示。

以下是通过 PyTorch 实现嵌入层的标准代码示例:

import torch
import torch.nn as nn

# 定义嵌入层参数
vocab_size = 30522  # BERT-base 词表大小
hidden_size = 768   # 隐藏层维度
max_position_embeddings = 512  # 最大序列长度

# 1. Token Embedding
token_embedding = nn.Embedding(vocab_size, hidden_size)

# 2. Position Embedding
position_embedding = nn.Embedding(max_position_embeddings, hidden_size)

# 3. 输入示例(batch_size=1, seq_length=10)
input_ids = torch.randint(0, vocab_size, (1, 10))  # 随机生成 token IDs
position_ids = torch.arange(0, 10).unsqueeze(0)      # 位置 ID [0,1,2,...,9]

# 4. 计算嵌入表示
token_embeddings = token_embedding(input_ids)
position_embeddings = position_embedding(position_ids)

# 5. 合并嵌入(简单相加)
final_embeddings = token_embeddings + position_embeddings

print("Token embeddings shape:", token_embeddings.shape)
print("Position embeddings shape:", position_embeddings.shape)
print("Final embeddings shape:", final_embeddings.shape)

输出结果:

Token embeddings shape: torch.Size([1, 10, 768])
Position embeddings shape: torch.Size([1, 10, 768])
Final embeddings shape: torch.Size([1, 10, 768])

嵌入层的数学原理可以形式化表示为:

$$
\text{Embedding}(x_i, p_i) = W_{\text{token}} \cdot x_i + W_{\text{pos}} \cdot p_i
$$

其中:

  • $x_i$ 是第 i 个 token 的 ID
  • $p_i$ 是第 i 个位置的位置 ID
  • $W_{\text{token}}$ 是 token 嵌入矩阵
  • $W_{\text{pos}}$ 是位置嵌入矩阵

嵌入层的输出作为 Transformer 编码器的输入,为后续的多头自注意力机制提供初始的向量表示。这种设计使得模型能够同时考虑词汇的语义信息和在序列中的相对位置关系,为理解自然语言的复杂结构奠定了基础。

1.5.2.1. Token 嵌入(Token Embedding)

自然语言处理中,输入文本通常是以符号形式存在的词汇,而这些离散符号无法直接被神经网络处理。因此需要一个可学习的嵌入矩阵将每个 token 转换为固定维度的向量。

工作原理:

a. 输入是一个形状为 [batch_size, seq_len] 的整数张量,表示每个 token 在词表中的索引;

b. 输出是一个形状为 [batch_size, seq_len, d_model] 的三维张量,其中:

  • d_model 是嵌入维度(如 512 或 768)
  • 每个 token 对应一个 d_model 维的向量

c. 嵌入层权重矩阵大小为 [vocab_size, d_model],参数量为 vocab_size × d_model。

在 PyTorch 中,词嵌入层通常使用 torch.nn.Embedding 模块实现,其作用是将 token 的索引转换为低维语义向量表示。vocab_size 表示词表的大小。

输入与输出说明:

类型 描述
输入 一个整数张量,表示每个 token 在词表中的索引;输入形状 (batch_size, sequence_length)
输出 每个 token 被映射到 embedding_dim 维度的稠密向量;输出形状 (batch_size, sequence_length, embedding_dim)
  • batch_size:批次大小(即一次处理多少条文本)
  • sequence_length:每条文本包含的 token 数量
  • embedding_dim:嵌入向量的维度,也称为词向量维度;它通常被设置为 d_model 或 h,即后续 Transformer 层使用的隐藏层维度(如 512 或 768)

示例代码:构建 Token Embedding 层

from transformers import BertTokenizer
import torch.nn as nn

# 1. 使用 BERT tokenizer 将批量输入的字符串文本序列转化为 input_ids
tokenizer = BertTokenizer.from_pretrained("bert-base-multilingual-cased")
batch_text = ["Attention Is All You Need", "I have a dog and cat"]
inputs = tokenizer(batch_text, return_tensors="pt", truncation=True, padding=True)
input_ids = inputs["input_ids"]

# 2. 创建一个 nn.Embedding 层
vocab_size = tokenizer.vocab_size  # 词表大小取决于你加载的具体 tokenizer 模型
embedding_dim = 512  # 嵌入向量的维度,参考 transformer 论文的大小
embedding_layer = nn.Embedding(vocab_size, embedding_dim)

# 3. 通过 nn.Embedding 层,将输入的 IDs 映射到嵌入向量
embedded_output = embedding_layer(input_ids)

# 4. 输出嵌入向量的形状
print("嵌入向量的形状:", embedded_output.shape)  # (batch_size, sequence_length, embedding_dim) -> torch.Size([2, 8, 512])

# 5. 打印嵌入向量
print(embedded_output)

输出:

嵌入向量的形状: torch.Size([2, 8, 512])
tensor([[[-0.9461,  0.7232,  0.1157,  ...,  0.1310,  1.3179,  0.2536],
         [ 0.7747,  0.8147, -1.0195,  ..., -0.1765, -2.2312, -1.1339],
         [-0.2936,  0.8093,  0.8993,  ..., -0.5816, -2.0731, -0.3793],
         ...,
         [ 0.7904, -0.0937, -0.5136,  ...,  1.6374,  1.6368,  0.8727],
         [ 1.8002,  1.0112,  2.3631,  ..., -0.3558,  0.3920, -2.2043],
         [-2.1555, -0.8592,  1.9316,  ..., -0.0876,  1.1625, -0.0215]],

        [[-0.9461,  0.7232,  0.1157,  ...,  0.1310,  1.3179,  0.2536],
         [-0.8953, -0.4229,  2.4109,  ..., -1.5934,  2.2598,  0.4689],
         [ 1.4073, -1.1110,  1.0428,  ...,  1.1734,  0.6988,  1.8572],
         ...,
         [ 0.0776,  0.3944,  0.6931,  ..., -0.1452,  0.4170, -0.3119],
         [-0.2317,  1.3057,  0.0294,  ..., -2.3177, -0.3012, -0.5404],
         [ 1.8002,  1.0112,  2.3631,  ..., -0.3558,  0.3920, -2.2043]]],
       grad_fn=<EmbeddingBackward0>)

1.5.2.2. 位置嵌入(Positional Encoding)

由于 Transformer 不依赖于 RNN 的顺序性建模方式,它必须显式地引入位置信息,以保留 token 在序列中的位置特征。

为此,Transformer 使用了 Sinusoidal Positional Encoding(正弦/余弦位置编码):

$$
PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right), \quad
PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)
$$

其中:

  • pos:token 在序列中的位置
  • i:维度索引
  • d_model:嵌入维度

Transformer Embedding 层集成

Transformer 输入模块有三个组成部分:文本/提示词、分词器(Tokenizer)和嵌入层(Embeddings)。输入模块的工作流程和代码实现如下所示:

图片

矩阵的每一列表示一个 token 的嵌入向量:

import torch
import torch.nn as nn

class PositionalEncoding(nn.Module):
    """
    正弦位置编码层(Sinusoidal Positional Encoding)
    通过正弦/余弦函数生成包含绝对位置信息的编码矩阵,用于 Transformer 模型
    """

    def __init__(self, d_model, max_len, device):
        """
        初始化位置编码层

        参数:
            d_model: 模型维度/嵌入维度(如 512)
            max_len: 支持的最大序列长度(如 512)
            device: 计算设备(torch.device('cpu') 或 torch.device('cuda'))
        """
        super(PositionalEncoding, self).__init__()

        # 初始化编码矩阵 [max_len, d_model],与输入矩阵同尺寸便于相加
        self.encoding = torch.zeros(max_len, d_model, device=device)
        # 位置编码是固定计算,不需要梯度更新
        self.encoding.requires_grad = False

        # 生成位置序列 [0,1,2,...,max_len-1] 并转为列向量 [max_len, 1]
        pos = torch.arange(0, max_len, device=device).float().unsqueeze(1)

        # 生成维度索引 [0,2,4,...,d_model-2],间隔取偶数索引
        _2i = torch.arange(0, d_model, step=2, device=device).float()

        # 计算正弦编码(填充偶数位置列)
        self.encoding[:, 0::2] = torch.sin(pos / (10000 ** (_2i / d_model)))
        # 计算余弦编码(填充奇数位置列)
        self.encoding[:, 1::2] = torch.cos(pos / (10000 ** (_2i / d_model)))

    def forward(self, x):
        """
        前向传播:根据输入序列长度截取对应位置编码

        参数:
            x: 输入张量 shape=[batch_size, seq_len]
        返回:
            位置编码 shape=[seq_len, d_model]
        """
        _, seq_len = x.size()  # 获取实际序列长度
        return self.encoding[:seq_len, :]  # 动态截取编码


class TokenEmbedding(nn.Embedding):
    """
    支持设备感知的词嵌入层(继承自 nn.Embedding)
    特征:
    1. 自动处理设备迁移(CPU/GPU)
    2. 内置 padding_idx 处理
    """

    def __init__(self, vocab_size, d_model, device=None):
        """
        初始化词嵌入层

        参数:
            vocab_size: 词表大小(如 30000)
            d_model: 嵌入维度(如 512)
            device: 目标设备(可选)
        """
        super(TokenEmbedding, self).__init__(
            vocab_size,
            d_model,
            padding_idx=1  # 约定 1 为填充符索引
        )
        if device is not None:
            self.to(device)  # 初始化时迁移到指定设备

    def forward(self, x):
        """
        前向传播:自动对齐输入与权重的设备

        参数:
            x: 输入词索引 shape=[batch_size, seq_len]
        返回:
            词嵌入向量 shape=[batch_size, seq_len, d_model]
        """
        # 设备对齐检查
        if self.weight.device != x.device:
            x = x.to(self.weight.device)
        return super().forward(x)


class TransformerEmbedding(nn.Module):
    """
    Transformer 组合嵌入层 = 词嵌入 + 位置编码 + Dropout
    功能:
    1. 将离散词索引转为连续向量
    2. 注入位置信息
    3. 正则化处理
    """

    def __init__(self, vocab_size, max_len, d_model, drop_prob, device):
        """
        初始化组合嵌入层

        参数:
            vocab_size: 词表大小
            max_len: 最大序列长度
            d_model: 模型维度
            drop_prob: dropout 概率(如 0.1)
            device: 计算设备
        """
        super(TransformerEmbedding, self).__init__()
        self.tok_emb = TokenEmbedding(vocab_size, d_model, device)  # 词嵌入层
        self.pos_emb = PositionalEncoding(d_model, max_len, device)  # 位置编码层
        self.drop_out = nn.Dropout(p=drop_prob)  # 随机失活层

    def forward(self, x):
        """
        前向传播:组合词嵌入与位置信息

        参数:
            x: 输入词索引 shape=[batch_size, seq_len]
        返回:
            组合嵌入表示 shape=[batch_size, seq_len, d_model]
        """
        tok_emb = self.tok_emb(x)  # 词嵌入 [batch, seq, d_model]
        pos_emb = self.pos_emb(x)  # 位置编码 [seq, d_model]
        # 逐元素相加后应用 dropout
        return self.drop_out(tok_emb + pos_emb)

输入测试:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
vocab_size = 10  # 词表大小
d_model = 512    # 模型维度
max_len = 100    # 最大序列长度
drop_prob = 0.1  # dropout 概率
batch_size = 32  # 批处理大小
seq_len = 50

"""测试 TransformerEmbedding 输出形状是否正确"""
te = TransformerEmbedding(vocab_size, max_len, d_model, drop_prob, device)
dummy_input = torch.randint(0, vocab_size, (batch_size, seq_len))
output = te(dummy_input)
print(output.shape)  # [batch_size, seq_len, d_model]

输出:

torch.Size([32, 50, 512])

1.6. Multi-Head Attention 结构

1.6.1. Self-Attention 机制

自注意力机制(Self-Attention)是 Transformer 架构的核心组件,其核心思想是通过计算序列中每个元素与其他所有元素之间的关联度(注意力权重),来动态地聚合全局上下文信息。这种机制能够有效捕捉长距离依赖关系,解决了传统 RNN/CNN 模型在处理长序列时的信息衰减问题。

自注意力的计算过程可以分解为三个关键步骤:

  1. 线性变换(Linear Projection):将输入向量通过三个不同的权重矩阵($W_Q$, $W_K$, $W_V$)分别映射为查询(Query)、键(Key)和值(Value)向量:

$$
Q = XW_Q, \quad K = XW_K, \quad V = XW_V
$$

其中 $X \in \mathbb{R}^{n \times d}$ 是输入序列矩阵,$n$ 为序列长度,$d$ 为向量维度。

  1. 注意力分数计算(Attention Score):通过查询向量与键向量的点积计算注意力分数,然后通过缩放(scaling)和 Softmax 归一化得到注意力权重:

$$
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$

其中 $d_k$ 是键向量的维度,缩放因子 $\sqrt{d_k}$ 用于防止点积结果过大导致 Softmax 梯度消失。

  1. 加权求和(Weighted Sum):使用注意力权重对值向量进行加权求和,得到每个位置的输出表示。

自注意力机制的数学表达式可以完整写为:

$$
\text{SelfAttention}(X) = \text{softmax}\left(\frac{XW_Q(XW_K)^T}{\sqrt{d_k}}\right)XW_V
$$

这种设计使得每个位置都能够关注到序列中的所有其他位置,从而获得全局的上下文信息。

以下是通过 PyTorch 实现自注意力机制的标准代码示例:

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

class SelfAttention(nn.Module):
    def __init__(self, d_model, d_k, d_v):
        super(SelfAttention, self).__init__()
        self.d_k = d_k
        # 线性变换层
        self.W_q = nn.Linear(d_model, d_k)
        self.W_k = nn.Linear(d_model, d_k)
        self.W_v = nn.Linear(d_model, d_v)

    def forward(self, x):
        # x: [batch_size, seq_len, d_model]
        batch_size, seq_len, d_model = x.shape

        # 计算 Q, K, V
        Q = self.W_q(x)  # [batch_size, seq_len, d_k]
        K = self.W_k(x)  # [batch_size, seq_len, d_k]
        V = self.W_v(x)  # [batch_size, seq_len, d_v]

        # 计算注意力分数
        scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(self.d_k, dtype=torch.float32))

        # Softmax 归一化
        attention_weights = F.softmax(scores, dim=-1)  # [batch_size, seq_len, seq_len]

        # 加权求和
        output = torch.matmul(attention_weights, V)  # [batch_size, seq_len, d_v]

        return output, attention_weights

# 测试示例
d_model = 512
d_k = 64
d_v = 64
seq_len = 10
batch_size = 2

# 创建自注意力层
self_attention = SelfAttention(d_model, d_k, d_v)

# 随机输入
x = torch.randn(batch_size, seq_len, d_model)

# 前向传播
output, attention_weights = self_attention(x)

print("Input shape:", x.shape)
print("Output shape:", output.shape)
print("Attention weights shape:", attention_weights.shape)

输出结果:

Input shape: torch.Size([2, 10, 512])
Output shape: torch.Size([2, 10, 64])
Attention weights shape: torch.Size([2, 10, 10])

自注意力机制的核心优势在于其能够:

  • 并行计算:不同于 RNN 的序列化处理,自注意力可以同时计算所有位置之间的关联
  • 长距离依赖:直接建模任意距离的依赖关系,不受序列长度限制
  • 可解释性:注意力权重可视化可以揭示模型关注的重点信息

1.6.2. Multi-Head Attention

Multi-Head Attention(MHA)是在 Self-Attention 基础上引入的一种增强机制。其核心理念是:将输入向量空间划分为多个子空间,在每个子空间中独立计算 Self-Attention,最后将多个子空间的输出拼接在一起并进行线性变换,从而得到最终的输出。

对于 MHA,之所以需要对 Q、K、V 进行多头(head)划分,其目的是为了增强模型对不同信息的关注。具体来说,多组 Q、K、V 分别计算 Self-Attention,每个头自然就会有独立的 Q、K、V 参数,从而让模型同时关注多个不同的信息,这有些类似 CNN 架构模型的多通道机制。

Multi-Head Attention 的计算过程可以分解为以下步骤:

  1. 线性投影与头分割(Linear Projection & Head Splitting):将输入向量通过多个线性变换层,并将结果分割为多个头:

$$
\text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)
$$

其中 $W_i^Q, W_i^K, W_i^V$ 是第 i 个头的投影矩阵。

  1. 多头注意力计算(Multi-Head Attention):每个头独立计算自注意力:

$$
\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h)W^O
$$

其中 $W^O$ 是输出投影矩阵。

  1. 输出投影(Output Projection):将多个头的输出拼接后进行线性变换。

Multi-Head Attention 的完整数学表达式为:

$$
\text{MultiHeadAttention}(X) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h)W^O
$$

其中每个头的计算为:

$$
\text{head}_i = \text{softmax}\left(\frac{XW_i^Q(XW_i^K)^T}{\sqrt{d_k}}\right)XW_i^V
$$

以下是通过 PyTorch 实现 Multi-Head Attention 的标准代码示例:

import torch
import torch.nn as nn
import math

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        assert d_model % num_heads == 0, "d_model must be divisible by num_heads"

        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, Q, K, V, mask=None):
        batch_size, seq_len, d_model = Q.shape

        # 线性变换
        Q = self.W_q(Q)  # [batch_size, seq_len, d_model]
        K = self.W_k(K)  # [batch_size, seq_len, d_model]
        V = self.W_v(V)  # [batch_size, seq_len, d_model]

        # 分割为多个头
        Q = Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)  # [batch_size, num_heads, seq_len, d_k]
        K = K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
        V = V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)

        # 计算注意力分数
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)  # [batch_size, num_heads, seq_len, seq_len]

        # 应用掩码(如果有)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)

        # Softmax 归一化
        attention_weights = nn.functional.softmax(scores, dim=-1)  # [batch_size, num_heads, seq_len, seq_len]

        # 加权求和
        output = torch.matmul(attention_weights, V)  # [batch_size, num_heads, seq_len, d_k]

        # 拼接多头输出
        output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model)  # [batch_size, seq_len, d_model]

        # 输出投影
        output = self.W_o(output)  # [batch_size, seq_len, d_model]

        return output, attention_weights

# 测试示例
d_model = 512
num_heads = 8
seq_len = 10
batch_size = 2

# 创建 Multi-Head Attention 层
mha = MultiHeadAttention(d_model, num_heads)

# 随机输入
Q = torch.randn(batch_size, seq_len, d_model)
K = torch.randn(batch_size, seq_len, d_model)
V = torch.randn(batch_size, seq_len, d_model)

# 前向传播
output, attention_weights = mha(Q, K, V)

print("Input shape:", Q.shape)
print("Output shape:", output.shape)
print("Attention weights shape:", attention_weights.shape)

输出结果:

Input shape: torch.Size([2, 10, 512])
Output shape: torch.Size([2, 10, 512])
Attention weights shape: torch.Size([2, 8, 10, 10])

Multi-Head Attention 的核心优势在于:

  • 多视角建模:不同头可以关注不同类型的信息(如语法、语义、位置等)
  • 增强表达能力:通过多头机制,模型能够同时捕捉多种依赖关系
  • 参数效率:虽然头数增加,但每个头的维度减小,总体参数量保持稳定

在实际应用中,Multi-Head Attention 通常与残差连接和层归一化结合使用,形成完整的注意力层。

1.6.3. Encoder 结构

Transformer 中的 Encoder 是整个模型中用于编码输入序列的部分。它由 N=6 个相同的 encoder block 堆叠而成。每个 encoder block 主要包含两个子层(sub-layers):多头自注意力机制(Multi-Head Self-Attention)和位置全连接前馈网络(Position-wise Feed Forward Network)。

这两个子层之间都使用了残差连接(Residual Connection)和层归一化(Layer Normalization),以增强训练稳定性和模型表达能力。

Encoder block 的计算过程可总结为下述步骤:

  • 输入张量经过多头自注意力层(Multi-Head Attention),得到注意力输出
  • 将注意力输出与原始输入进行残差连接(Add),然后进行层归一化(Layer Norm)
  • 归一化后的张量经过前馈网络(Feed Forward),得到前馈输出
  • 将前馈输出与归一化后的张量进行残差连接(Add),然后再次进行层归一化(Layer Norm)
  • 最终输出作为下一个 Encoder block 的输入,或者作为整个 Encoder 的输出

以下是通过 PyTorch 实现 Encoder block 的标准代码示例:

import torch
import torch.nn as nn

class EncoderBlock(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
        super(EncoderBlock, self).__init__()
        # 多头自注意力层
        self.self_attention = MultiHeadAttention(d_model, num_heads)
        # 前馈网络层
        self.feed_forward = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model)
        )
        # 层归一化层
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        # Dropout 层
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # 多头自注意力 + 残差连接 + 层归一化
        attention_output, _ = self.self_attention(x, x, x, mask)
        x = self.norm1(x + self.dropout(attention_output))

        # 前馈网络 + 残差连接 + 层归一化
        ff_output = self.feed_forward(x)
        x = self.norm2(x + self.dropout(ff_output))

        return x

# 测试示例
d_model = 512
num_heads = 8
d_ff = 2048
seq_len = 10
batch_size = 2

# 创建 Encoder block
encoder_block = EncoderBlock(d_model, num_heads, d_ff)

# 随机输入
x = torch.randn(batch_size, seq_len, d_model)

# 前向传播
output = encoder_block(x)

print("Input shape:", x.shape)
print("Output shape:", output.shape)

输出结果:

Input shape: torch.Size([2, 10, 512])
Output shape: torch.Size([2, 10, 512])

完整的 Encoder 由多个 Encoder block 堆叠而成:

class TransformerEncoder(nn.Module):
    def __init__(self, num_layers, d_model, num_heads, d_ff, dropout=0.1):
        super(TransformerEncoder, self).__init__()
        self.layers = nn.ModuleList([
            EncoderBlock(d_model, num_heads, d_ff, dropout)
            for _ in range(num_layers)
        ])

    def forward(self, x, mask=None):
        for layer in self.layers:
            x = layer(x, mask)
        return x

# 测试示例
num_layers = 6
encoder = TransformerEncoder(num_layers, d_model, num_heads, d_ff)
output = encoder(x)

print("Encoder output shape:", output.shape)

输出结果:

Encoder output shape: torch.Size([2, 10, 512])

Encoder 结构的核心特点包括:

  • 多层堆叠:通过多个 Encoder block 的堆叠,模型能够学习到不同层次的表示
  • 残差连接:缓解梯度消失问题,促进深层网络的训练
  • 层归一化:提高训练稳定性,加速收敛
  • 位置编码:通过位置编码为模型提供序列位置信息

Encoder 的输出通常作为 Decoder 的输入,用于生成目标序列。

1.7. Decoder 结构

Decoder 是 Transformer 架构中用于生成输出序列的部分。与 Encoder 类似,它由 N=6 个相同的 Decoder block 堆叠而成,但结构更为复杂。

每个 Decoder block 包含三个子层:

  1. Masked Multi-Head Self-Attention:对目标序列进行自注意力计算,使用掩码防止信息泄露
  2. Multi-Head Encoder-Decoder Attention:使用 Encoder 的输出作为 Key 和 Value,Decoder 的中间表示作为 Query
  3. Position-wise Feed Forward Network:与 Encoder 相同的前馈网络

每个子层后都应用残差连接和层归一化。

Decoder 的计算过程可以总结为:

  • 输入:目标序列的嵌入表示(通常右移一位)
  • Masked Self-Attention:计算目标序列的自注意力,使用三角掩码防止看到未来信息
  • Encoder-Decoder Attention:将 Encoder 的输出作为上下文信息
  • Feed Forward Network:进行非线性变换
  • 输出:经过线性层和 Softmax 得到词汇表上的概率分布

以下是通过 PyTorch 实现 Decoder block 的标准代码示例:

import torch
import torch.nn as nn

class DecoderBlock(nn.Module):
    def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
        super(DecoderBlock, self).__init__()
        # Masked 多头自注意力层
        self.masked_self_attention = MultiHeadAttention(d_model, num_heads)
        # Encoder-Decoder 注意力层
        self.encoder_decoder_attention = MultiHeadAttention(d_model, num_heads)
        # 前馈网络层
        self.feed_forward = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model)
        )
        # 层归一化层
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        # Dropout 层
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, encoder_output, src_mask=None, tgt_mask=None):
        # Masked Self-Attention + 残差连接 + 层归一化
        masked_attention_output, _ = self.masked_self_attention(x, x, x, tgt_mask)
        x = self.norm1(x + self.dropout(masked_attention_output))

        # Encoder-Decoder Attention + 残差连接 + 层归一化
        encoder_decoder_output, _ = self.encoder_decoder_attention(x, encoder_output, encoder_output, src_mask)
        x = self.norm2(x + self.dropout(encoder_decoder_output))

        # Feed Forward + 残差连接 + 层归一化
        ff_output = self.feed_forward(x)
        x = self.norm3(x + self.dropout(ff_output))

        return x

# 测试示例
d_model = 512
num_heads = 8
d_ff = 2048
seq_len = 10
batch_size = 2

# 创建 Decoder block
decoder_block = DecoderBlock(d_model, num_heads, d_ff)

# 随机输入(目标序列)
x = torch.randn(batch_size, seq_len, d_model)
# Encoder 输出
encoder_output = torch.randn(batch_size, seq_len, d_model)

# 前向传播
output = decoder_block(x, encoder_output)

print("Input shape:", x.shape)
print("Encoder output shape:", encoder_output.shape)
print("Decoder output shape:", output.shape)

输出结果:

Input shape: torch.Size([2, 10, 512])
Encoder output shape: torch.Size([2, 10, 512])
Decoder output shape: torch.Size([2, 10, 512])

完整的 Decoder 由多个 Decoder block 堆叠而成:

class TransformerDecoder(nn.Module):
    def __init__(self, num_layers, d_model, num_heads, d_ff, dropout=0.1):
        super(TransformerDecoder, self).__init__()
        self.layers = nn.ModuleList([
            DecoderBlock(d_model, num_heads, d_ff, dropout)
            for _ in range(num_layers)
        ])

    def forward(self, x, encoder_output, src_mask=None, tgt_mask=None):
        for layer in self.layers:
            x = layer(x, encoder_output, src_mask, tgt_mask)
        return x

# 测试示例
num_layers = 6
decoder = TransformerDecoder(num_layers, d_model, num_heads, d_ff)
output = decoder(x, encoder_output)

print("Decoder output shape:", output.shape)

输出结果:

Decoder output shape: torch.Size([2, 10, 512])

Decoder 结构的核心特点包括:

  • 自回归生成:逐个生成输出 token,使用之前生成的 token 作为输入
  • 掩码注意力:防止模型在训练时看到未来信息
  • 交叉注意力:利用 Encoder 的上下文信息指导生成过程
  • 多层堆叠:通过多个 Decoder block 学习复杂的生成模式

Decoder 的输出经过线性层和 Softmax 后,得到词汇表上的概率分布,用于预测下一个 token。

1.8. 附录:完整模型代码

基于前面实现的 Encoder 和 Decoder 组件,Transformer 模型的完整代码如下所示:

import torch
import torch.nn as nn

class Transformer(nn.Module):
    def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout=0.1):
        super(Transformer, self).__init__()
        # 嵌入层
        self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
        self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
        # 位置编码
        self.positional_encoding = PositionalEncoding(d_model, max_seq_length)
        # Encoder 和 Decoder
        self.encoder = TransformerEncoder(num_layers, d_model, num_heads, d_ff, dropout)
        self.decoder = TransformerDecoder(num_layers, d_model, num_heads, d_ff, dropout)
        # 输出层
        self.fc_out = nn.Linear(d_model, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

    def forward(self, src, tgt, src_mask=None, tgt_mask=None):
        # Encoder 部分
        src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
        enc_output = self.encoder(src_embedded, src_mask)

        # Decoder 部分
        tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
        dec_output = self.decoder(tgt_embedded, enc_output, src_mask, tgt_mask)

        # 输出
        output = self.fc_out(dec_output)
        return output

# 测试示例
src_vocab_size = 10000
tgt_vocab_size = 8000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100

# 创建模型
model = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length)

# 随机输入
batch_size = 2
src_seq_len = 20
tgt_seq_len = 15
src = torch.randint(0, src_vocab_size, (batch_size, src_seq_len))
tgt = torch.randint(0, tgt_vocab_size, (batch_size, tgt_seq_len))

# 前向传播
output = model(src, tgt)

print("Input shapes:")
print("Source:", src.shape)
print("Target:", tgt.shape)
print("Output shape:", output.shape)

输出结果:

Input shapes:
Source: torch.Size([2, 20])
Target: torch.Size([2, 15])
Output shape: torch.Size([2, 15, 8000])

1.9. 参考文献

[1] Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., … & Polosukhin, I. (2017). Attention is all you need. In Advances in neural information processing systems (pp. 5998-6008).

[2] Devlin, J., Chang, M. W., Lee, K., & Toutanova, K. (2018). Bert: Pre-training of deep bidirectional transformers for language understanding. arXiv preprint arXiv:1810.04805.

[3] Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J., Dhariwal, P., … & Amodei, D. (2020). Language models are few-shot learners. Advances in neural information processing systems, 33, 1877-1901.

[4] Radford, A., Wu, J., Child, R., Luan, D., Amodei, D., & Sutskever, I. (2019). Language models are unsupervised multitask learners. OpenAI blog, 1(8), 9.

[5] Liu, Y., Ott, M., Goyal, N., Du, J., Joshi, M., Chen, D., … & Stoyanov, V. (2019). Roberta: A robustly optimized bert pretraining approach. arXiv preprint arXiv:1907.11692.

[6] Raffel, C., Shazeer, N., Roberts, A., Lee, K., Narang, S., Matena, M., … & Liu, P. J. (2020). Exploring the limits of transfer learning with a unified text-to-text transformer. Journal of Machine Learning Research, 21(140), 1-67.

[7] Dosovitskiy, A., Beyer, L., Kolesnikov, A., Weissenborn, D., Zhai, X., Unterthiner, T., … & Houlsby, N. (2020). An image is worth 16x16 words: Transformers for image recognition at scale. arXiv preprint arXiv:2010.11929.

1.10. 扩展阅读

  • 沐神:Transformer 论文逐段精读【论文精读】
  • CSDN:Transformer 模型结构详解及代码实现
  • 知乎:Transformer 详解
  • jalammar:The Illustrated Transformer

[!success] 核心要点

  • 核心机制:Self-Attention 通过 Q/K/V 线性变换 + 缩放点积 + Softmax 加权求和,直接建模任意位置间的依赖关系;Multi-Head 将输入空间划分为多个子空间并行关注不同信息
  • 架构组成:Encoder(6 层:Multi-Head Self-Attention + FFN,各带残差与 LayerNorm)+ Decoder(6 层:Masked Self-Attention + Encoder-Decoder Attention + FFN)
  • 输入流程:Tokenizer 分词 → token ID 映射 → Token Embedding + Positional Encoding(正弦/可学习)→ 编码器
  • 解决三大难题:长距离依赖、并行计算瓶颈、跨模态统一架构
  • 代码路径:PositionalEncoding / TokenEmbedding → EncoderBlock / DecoderBlock → Transformer 完整模型,输出形状全程可验证([batch, seq, d_model])