返回学习路线
9应用实战

第 9 周:Transformer 架构

Self-Attention、Multi-Head、位置编码

每周 6-10 小时

本周目标

深入理解 Transformer 架构——当前 NLP 和多模态 AI 的基石。掌握 Self-Attention 机制(模型如何"关注"输入中的不同部分)、 Multi-Head Attention(并行关注多种信息)、位置编码(弥补序列位置信息的缺失)。

学习内容

9.1 序列建模基础

9.1.1 序列任务类型:分类、生成、翻译

序列数据在现实世界中无处不在,从文本、语音到时间序列数据,都需要特殊的建模方法。根据任务目标的不同,序列任务可以分为以下三类:

序列分类任务旨在将整个输入序列映射到一个类别标签。典型的应用包括情感分析(判断一段文本的情感倾向)、文本分类(将文档归类到不同主题)和命名实体识别(识别文本中的实体类型)。这类任务的特点是输入为变长序列,输出为固定维度的类别分布。

序列生成任务要求模型根据给定的上下文或条件生成新的序列。语言建模(预测下一个词)、文本摘要(生成简洁的摘要)和对话生成都属于此类任务。生成任务通常采用自回归方式,逐个生成序列中的元素。

序列到序列(Seq2Seq)任务是最具挑战性的序列任务类型,需要将输入序列转换为输出序列,且两者长度可能不同。机器翻译是最典型的 Seq2Seq 任务,例如将英文句子"Hello world"翻译为中文"你好世界"。其他应用包括语音识别(音频到文本)和代码生成(自然语言到程序代码)。

9.1.2 编码器-解码器框架:Seq2Seq 架构

编码器-解码器(Encoder-Decoder)框架是解决 Seq2Seq 问题的经典架构。该框架由两个主要组件构成:

**编码器(Encoder)**负责将变长的输入序列压缩为固定长度的上下文向量(Context Vector)。编码器通常采用循环神经网络(RNN)或其变体(LSTM、GRU),逐词处理输入序列,最终状态编码了整个输入序列的信息。

**解码器(Decoder)**则基于编码器产生的上下文向量生成输出序列。解码器同样采用 RNN 结构,在每个时间步生成一个输出词,并将该词作为下一个时间步的输入,直到生成结束标记。

编码器-解码器框架的计算流程可以形式化表示为:

给定输入序列 X=(x1,x2,...,xn)X = (x_1, x_2, ..., x_n),编码器计算:

ht=f(xt,ht1)h_t = f(x_t, h_{t-1})

其中 ff 是 RNN 单元的非线性变换函数,hth_t 是第 tt 步的隐藏状态。编码器最终状态 hnh_n 作为上下文向量 cc

解码器基于上下文向量生成输出序列 Y=(y1,y2,...,ym)Y = (y_1, y_2, ..., y_m)

st=g(yt1,st1,c)s_t = g(y_{t-1}, s_{t-1}, c)

p(yty<t,X)=softmax(Wsst+bs)p(y_t|y_{<t}, X) = \text{softmax}(W_s s_t + b_s)

其中 sts_t 是解码器在第 tt 步的隐藏状态,gg 是解码器 RNN 单元。

9.1.3 序列到序列学习:对齐问题

传统编码器-解码器架构存在一个根本性缺陷:信息瓶颈。无论输入序列多长,所有信息都被压缩到一个固定维度的上下文向量中。这导致长序列的信息丢失,模型难以准确翻译或生成输出。

对齐问题是 Seq2Seq 学习的核心挑战。在机器翻译中,源语言句子的不同部分与目标语言句子的不同部分存在对应关系。例如,在英译中时,英文的主语通常对应中文的主语,但词序可能不同。传统方法无法显式建模这种软对齐关系。

Bahdanau 等人提出的注意力机制为解决对齐问题提供了有效方案。注意力机制允许解码器在生成每个输出词时"关注"输入序列的不同部分,动态地选择相关信息,而非仅依赖固定的上下文向量。这一思想为后续 Transformer 架构的诞生奠定了基础。

9.2 注意力机制

9.2.1 注意力原理与直观理解:Query-Key-Value

注意力机制的核心思想源于人类视觉注意力:当处理复杂信息时,我们会选择性地关注最相关的部分,而非同时处理所有信息。在神经网络中,注意力机制通过计算查询(Query)与键(Key)之间的相似度,确定应该关注哪些值(Value)。

Query-Key-Value(QKV)框架是注意力机制的数学基础。可以将这一框架类比为数据库查询:

  • Query(查询):表示当前需要关注什么信息
  • Key(键):表示存储信息的位置标识
  • Value(值):表示实际存储的信息内容

注意力计算过程分为三个步骤:

  1. 相似度计算:计算 Query 与每个 Key 的相似度分数
  2. 权重归一化:使用 softmax 函数将分数转换为概率分布
  3. 加权求和:根据权重对 Value 进行加权求和,得到注意力输出

数学上,给定 Query 向量 qq、Key 矩阵 K=[k1,k2,...,kn]K = [k_1, k_2, ..., k_n] 和 Value 矩阵 V=[v1,v2,...,vn]V = [v_1, v_2, ..., v_n],注意力输出为:

Attention(q,K,V)=i=1nαivi\text{Attention}(q, K, V) = \sum_{i=1}^{n} \alpha_i v_i

其中注意力权重 αi\alpha_i 计算为:

αi=exp(score(q,ki))j=1nexp(score(q,kj))\alpha_i = \frac{\exp(\text{score}(q, k_i))}{\sum_{j=1}^{n} \exp(\text{score}(q, k_j))}

常用的相似度评分函数包括:

  • 点积注意力score(q,k)=qTk\text{score}(q, k) = q^T k
  • 缩放点积注意力score(q,k)=qTkdk\text{score}(q, k) = \frac{q^T k}{\sqrt{d_k}}
  • 加性注意力score(q,k)=wTtanh(Wqq+Wkk)\text{score}(q, k) = w^T \tanh(W_q q + W_k k)

9.2.2 自注意力机制:Self-Attention 计算

**自注意力(Self-Attention)**是注意力机制的重要变体,它计算序列中每个位置与其他所有位置的注意力关系。与标准注意力不同,自注意力的 Query、Key、Value 都来自同一个序列,使模型能够捕捉序列内部的依赖关系。

自注意力的计算过程如下:

给定输入序列的嵌入表示 XRn×dX \in \mathbb{R}^{n \times d},首先通过线性变换得到 Q、K、V 矩阵:

Q=XWQ,K=XWK,V=XWVQ = X W_Q, \quad K = X W_K, \quad V = X W_V

其中 WQ,WK,WVRd×dkW_Q, W_K, W_V \in \mathbb{R}^{d \times d_k} 是可学习的投影矩阵。

然后计算缩放点积注意力:

SelfAttention(X)=softmax(QKTdk)V\text{SelfAttention}(X) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V

缩放点积注意力中的缩放因子 1dk\frac{1}{\sqrt{d_k}} 具有重要作用。当 dkd_k 较大时,点积的数值会变得很大,导致 softmax 函数的梯度变得非常小。缩放操作可以稳定梯度,改善训练效果。

下图展示了自注意力权重矩阵的可视化结果:

自注意力权重可视化

图 9-1:句子"The cat sat on the mat"的自注意力权重矩阵。每个单元格表示 Query 位置(行)对 Key 位置(列)的注意力权重。对角线值较高表明词语主要关注自身及其相邻词。

从图中可以观察到以下模式:

  • 对角线元素值较高,表明每个词主要关注自身
  • 相邻词之间存在较强的注意力连接
  • "mat"与"on"、"the"之间存在较强的语义关联
import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class SelfAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads  # 每个头的维度

        # Q、K、V 投影矩阵
        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, D = x.shape  # batch, seq_len, d_model

        # 投影并分头
        Q = self.W_q(x).view(B, L, self.n_heads, self.d_k).transpose(1, 2)
        K = self.W_k(x).view(B, L, self.n_heads, self.d_k).transpose(1, 2)
        V = self.W_v(x).view(B, L, self.n_heads, self.d_k).transpose(1, 2)
        # Q, K, V shape: (B, n_heads, L, d_k)

        # 计算注意力分数
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        # scores shape: (B, n_heads, L, L)

        if mask is not None:
            scores = scores.masked_fill(mask == 0, float("-inf"))

        attn_weights = F.softmax(scores, dim=-1)

        # 加权求和
        output = torch.matmul(attn_weights, V)
        # output shape: (B, n_heads, L, d_k)

        # 拼接多头并投影
        output = output.transpose(1, 2).contiguous().view(B, L, D)
        return self.W_o(output)

# ─── 直觉理解 ───
# "我喜欢吃苹果" 中,"苹果"会关注"吃"(动词-宾语关系)
# "苹果公司发布了新手机" 中,"苹果"会关注"公司"、"发布"(主语-谓语关系)
# 同一个词在不同上下文中,关注的词不同,表示也不同

9.2.3 多头注意力:并行注意力头

**多头注意力(Multi-Head Attention)**是 Transformer 的核心创新之一。其核心思想是:不同的注意力头可以学习不同类型的依赖关系,多个头并行工作可以捕捉更丰富的语义信息。

多头注意力的计算过程如下:

首先,将 Q、K、V 分别投影到 hh 个不同的子空间:

Qi=XWQ(i),Ki=XWK(i),Vi=XWV(i)Q_i = X W_Q^{(i)}, \quad K_i = X W_K^{(i)}, \quad V_i = X W_V^{(i)}

其中 i=1,2,...,hi = 1, 2, ..., hWQ(i),WK(i),WV(i)Rd×dkW_Q^{(i)}, W_K^{(i)}, W_V^{(i)} \in \mathbb{R}^{d \times d_k},通常 dk=d/hd_k = d/h

然后,在每个子空间独立计算注意力:

headi=Attention(Qi,Ki,Vi)=softmax(QiKiTdk)Vi\text{head}_i = \text{Attention}(Q_i, K_i, V_i) = \text{softmax}\left(\frac{Q_i K_i^T}{\sqrt{d_k}}\right) V_i

最后,将所有头的输出拼接并投影:

MultiHead(X)=Concat(head1,...,headh)WO\text{MultiHead}(X) = \text{Concat}(\text{head}_1, ..., \text{head}_h) W_O

其中 WORd×dW_O \in \mathbb{R}^{d \times d} 是输出投影矩阵。

下图展示了 8 个注意力头的权重分布:

多头注意力可视化

图 9-2:多头注意力可视化。不同注意力头学习不同的关注模式:Head 1 主要关注相邻词(局部注意力),Head 2 关注句首词,Head 3 关注名词,Head 4 关注动词,其余头学习更复杂的依赖关系。

多头注意力的优势在于:

  • 表达能力增强:不同头可以专注于不同的 linguistic phenomena(句法、语义、共指等)
  • 并行计算:所有头可以并行计算,充分利用 GPU 加速
  • 稳定性提升:多个头的集成提高了模型的鲁棒性

9.2.4 注意力可视化:注意力权重分析

注意力权重可视化是理解 Transformer 模型行为的重要工具。通过分析注意力矩阵,我们可以洞察模型如何建立词与词之间的关联。

注意力模式类型

  1. 对角线注意力:词语主要关注自身和相邻词,反映局部依赖
  2. 垂直注意力:多个 Query 关注同一个 Key,表明该 Key 包含重要信息
  3. 稀疏注意力:注意力分布稀疏,表明模型关注少数关键位置
  4. 均匀注意力:注意力分布均匀,表明所有位置信息都相关

注意力分析的应用

  • 模型可解释性:理解模型的决策依据
  • 错误分析:定位模型关注错误的原因
  • 知识蒸馏:提取注意力模式指导小模型训练
  • 多语言分析:比较不同语言的注意力模式差异

9.3 Transformer 架构

9.3.1 编码器结构与组件:多头注意力、FFN、LayerNorm

2017 年,Vaswani 等人提出的 Transformer 架构完全摒弃了循环和卷积结构,完全基于注意力机制构建。Transformer 编码器由 NN 个相同的层堆叠而成,每层包含两个子层:

多头自注意力子层

AttentionOutput=MultiHead(X)\text{AttentionOutput} = \text{MultiHead}(X)

位置前馈网络(Position-wise Feed-Forward Network, FFN)

FFN(x)=ReLU(xW1+b1)W2+b2\text{FFN}(x) = \text{ReLU}(x W_1 + b_1) W_2 + b_2

FFN 对每个位置独立应用相同的变换,将输入从 dd 维映射到 dffd_{ff} 维再映射回 dd 维。通常 dff=4dd_{ff} = 4d

层归一化(Layer Normalization)残差连接

每个子层都使用残差连接和层归一化:

Output=LayerNorm(X+Sublayer(X))\text{Output} = \text{LayerNorm}(X + \text{Sublayer}(X))

层归一化对每个样本的所有特征进行归一化:

LayerNorm(x)=γxμσ2+ϵ+β\text{LayerNorm}(x) = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta

其中 μ\muσ2\sigma^2 是特征的均值和方差,γ\gammaβ\beta 是可学习的缩放和平移参数。

残差连接的作用包括:

  • 缓解梯度消失问题,使深层网络可训练
  • 允许信息直接传递,保留低层特征
  • 加速收敛,改善优化效果

9.3.2 解码器结构与组件:掩码注意力、交叉注意力

Transformer 解码器同样由 NN 个相同的层堆叠而成,但每层包含三个子层:

掩码多头自注意力(Masked Multi-Head Self-Attention)

解码器的自注意力需要防止关注未来位置,确保生成第 tt 个词时只能使用已生成的 t1t-1 个词信息。这通过掩码实现:

MaskedAttention(Q,K,V)=softmax(QKTdk+M)V\text{MaskedAttention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + M\right) V

其中掩码矩阵 MM 满足:

Mij={0if ijif i<jM_{ij} = \begin{cases} 0 & \text{if } i \geq j \\ -\infty & \text{if } i < j \end{cases}

这种掩码方式称为因果掩码(Causal Masking)下三角掩码,确保模型只能关注当前位置及之前的位置。

交叉注意力(Cross-Attention)

交叉注意力层允许解码器关注编码器的输出。与自注意力不同,交叉注意力的 Query 来自解码器,而 Key 和 Value 来自编码器:

CrossAttention(Qdec,Kenc,Venc)=softmax(QdecKencTdk)Venc\text{CrossAttention}(Q_{dec}, K_{enc}, V_{enc}) = \text{softmax}\left(\frac{Q_{dec} K_{enc}^T}{\sqrt{d_k}}\right) V_{enc}

交叉注意力是 Seq2Seq 任务的关键,它建立了源序列和目标序列之间的对齐关系。

位置前馈网络:与编码器相同。

9.3.3 位置编码与嵌入:正弦位置编码、可学习位置编码

由于 Transformer 没有循环或卷积结构,它本身无法感知序列中词的位置信息。为了引入位置信息,需要将位置编码添加到词嵌入中。

**正弦位置编码(Sinusoidal Position Encoding)**是 Transformer 原论文采用的方法。对于位置 pospos 和维度 ii,位置编码定义为:

PE(pos,2i)=sin(pos100002i/dmodel)PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)

PE(pos,2i+1)=cos(pos100002i/dmodel)PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)

正弦位置编码的优势:

  • 可以外推到训练时未见过的更长序列
  • 每个位置的编码是唯一的
  • 相对位置可以通过线性变换得到:PEpos+kPE_{pos+k} 可以用 PEposPE_{pos} 线性表示

下图展示了正弦位置编码的可视化:

位置编码可视化

图 9-3:正弦位置编码可视化。左图显示位置编码矩阵的热图,不同维度以不同频率的正弦/余弦函数编码位置信息。右图展示几个代表性维度的波形,低维度(如 Dim 0)变化缓慢,高维度(如 Dim 31)变化快速。

**可学习位置编码(Learned Position Embedding)**是另一种常用方法,直接将位置编码作为可学习的参数。BERT 等模型采用这种方法。可学习位置编码的优势是灵活性高,可以适应特定任务,但无法外推到更长序列。

最终输入表示为词嵌入与位置编码之和:

X=Embedding(tokens)+PositionEncoding(positions)X = \text{Embedding}(tokens) + \text{PositionEncoding}(positions)

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1).float()

        div_term = torch.exp(
            torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)
        )

        pe[:, 0::2] = torch.sin(position * div_term)  # 偶数维度用 sin
        pe[:, 1::2] = torch.cos(position * div_term)  # 奇数维度用 cos
        self.register_buffer("pe", pe.unsqueeze(0))

    def forward(self, x):
        # x shape: (B, L, D)
        return x + self.pe[:, :x.size(1)]

# ─── 为什么用三角函数?───
# 1. 不同维度有不同的"频率",低频捕捉长距离,高频捕捉短距离
# 2. PE(pos+k) 可以表示为 PE(pos) 的线性函数,模型能学到相对位置
# 3. 不需要额外学习参数

9.3.4 完整 Transformer 模型:架构整体理解

完整的 Transformer 模型将编码器和解码器组合在一起,形成端到端的 Seq2Seq 架构。

Transformer 工作流程

  1. 输入嵌入:源序列和目标序列分别通过嵌入层和位置编码
  2. 编码器处理:源序列通过编码器,生成上下文表示
  3. 解码器处理:目标序列通过解码器,交叉注意力层关注编码器输出
  4. 输出生成:解码器输出通过线性层和 softmax,预测下一个词的概率分布

下表对比了 RNN、LSTM 和 Transformer 三种架构的关键特性:

特性RNNLSTMTransformer
核心机制循环连接门控循环自注意力
并行性顺序计算顺序计算完全并行
长距离依赖困难改善直接建模
训练速度
位置信息隐式隐式显式编码
典型应用短序列中等序列长序列

架构对比

图 9-4:RNN、LSTM、Transformer 三种架构的性能对比。左图显示随着序列长度增加,Transformer 保持稳定的性能,而 RNN/LSTM 性能显著下降。中图显示 Transformer 的训练时间随序列长度增长较慢。右图显示 Transformer 在长距离依赖捕获方面具有明显优势。

Transformer 架构的革命性意义在于:

  • 完全并行化:摒弃循环结构,实现完全并行计算
  • 长距离依赖:注意力机制直接建模任意距离的关系
  • 可扩展性:架构简洁,易于扩展到更大规模
class TransformerEncoderLayer(nn.Module):
    def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
        super().__init__()
        self.attention = SelfAttention(d_model, n_heads)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(d_ff, d_model),
            nn.Dropout(dropout)
        )
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # 残差连接 + LayerNorm(Pre-Norm 风格)
        x = x + self.dropout(self.attention(self.norm1(x), mask))
        x = x + self.ffn(self.norm2(x))
        return x

时间分配建议

  • 3h Self-Attention:Q/K/V、注意力计算、可视化注意力权重
  • 2h Multi-Head Attention + 位置编码
  • 2h 手写完整 Transformer Encoder 层
  • 1h 阅读 "Attention Is All You Need" 论文核心部分

里程碑(第 9-10 周联合)

完成一个文本分类小项目。可以选择情感分析、新闻分类或意图识别任务, 使用预训练的 BERT 模型微调,理解迁移学习的威力。

学完你应该能...

  • 从零实现 Self-Attention 计算过程
  • 解释 Multi-Head Attention 的作用和优势
  • 理解位置编码的数学原理和必要性
  • 描述 Transformer 编码器的完整结构