Transformer原理详解
注意力就是一切的架构 — Attention Is All You Need
Transformer
Self-Attention
Multi-Head Attention
Positional Encoding
Encoder-Decoder
LayerNorm
FFN
一、Transformer架构总览
2017年,Google在论文《Attention Is All You Need》中提出了Transformer架构,彻底改变了自然语言处理(NLP)领域的格局。Transformer摒弃了传统的循环神经网络(RNN)和卷积神经网络(CNN),完全基于注意力机制(Attention Mechanism)来建模序列数据。其核心思想在于:序列中任意两个位置之间的距离都是常数 ,这从根本上解决了RNN难以捕获长程依赖的痛点。
Transformer的原始架构采用了编码器-解码器(Encoder-Decoder) 结构。编码器由N个完全相同的层(Layer)堆叠而成(原论文中N=6),每一层包含两个子层:多头自注意力子层(Multi-Head Self-Attention)和逐位置前馈神经网络子层(Position-wise Feed-Forward Network)。每个子层周围都使用了残差连接(Residual Connection) 和层归一化(Layer Normalization) ,公式表达为:LayerNorm(x + Sublayer(x)) 。解码器同样由N个层堆叠,每个解码器层包含三个子层:掩码多头自注意力(Masked Multi-Head Self-Attention)、编码器-解码器注意力(Encoder-Decoder Attention)和前馈网络。
架构核心特点
完全基于注意力: 没有任何循环或卷积操作,全部依赖注意力机制捕获依赖关系
并行计算: 输入序列的所有位置可以同时进行计算,训练效率远高于RNN
全局感受野: 每个位置可以直接关注到整个序列的所有其他位置
多层堆叠: 通过N层堆叠逐步构建从低级到高级的语义表示
残差连接: 缓解深层网络中的梯度消失问题,使信息流动更加顺畅
1.1 编码器结构
编码器(Encoder)负责将输入序列映射为连续的表示序列。每个编码器层包含两个子层:第一个子层是多头自注意力机制,允许每个位置关注序列中的所有其他位置;第二个子层是逐位置前馈网络,对每个位置的表示独立地进行非线性变换。两个子层均有残差连接和层归一化。输入序列首先经过词嵌入(Token Embedding) 和位置编码(Positional Encoding) 后送入编码器。
1.2 解码器结构
解码器(Decoder)以编码器的输出和已生成的目标序列为输入,逐步生成输出序列。每个解码器层包含三个子层:第一个子层是掩码多头自注意力,确保在预测当前位置时只能看到之前的位置(因果约束);第二个子层是编码器-解码器注意力,以编码器输出作为Key和Value,以解码器自注意力输出作为Query,实现源序列到目标序列的信息传递;第三个子层是前馈网络。
# Transformer编码器层 PyTorch实现框架
class TransformerEncoderLayer(nn.Module):
def __init__(self, d_model, nhead, dim_feedforward, dropout=0.1):
super().__init__()
# 多头自注意力子层
self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
# 前馈网络子层
self.feed_forward = nn.Sequential(
nn.Linear(d_model, dim_feedforward),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(dim_feedforward, d_model)
)
# 层归一化
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
# Dropout
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, mask=None):
# 子层1: 多头自注意力 + 残差连接 + LayerNorm
attn_out = self.self_attn(x, x, x, attn_mask=mask)[0]
x = self.norm1(x + self.dropout1(attn_out))
# 子层2: 前馈网络 + 残差连接 + LayerNorm
ff_out = self.feed_forward(x)
x = self.norm2(x + self.dropout2(ff_out))
return x
二、自注意力机制 Self-Attention
自注意力机制(Self-Attention)是Transformer最核心的组件。其基本思想是:对于输入序列中的每个元素,通过计算该元素与序列中所有其他元素之间的注意力权重 ,来聚合全局信息。这种机制使得模型能够动态地关注输入序列中最相关的部分。
2.1 QKV矩阵推导
自注意力机制的核心操作涉及三个矩阵:Query(查询) 、Key(键) 和Value(值) 。对于输入序列 X ∈ R^(n×d_model),通过三个不同的权重矩阵 W_Q、W_K、W_V 进行线性变换得到 Q、K、V:
Q = X · W_Q W_Q ∈ R^(d_model × d_k)
K = X · W_K W_K ∈ R^(d_model × d_k)
V = X · W_V W_V ∈ R^(d_model × d_v)
其中 d_k = d_v = d_model / h,h 为注意力头的数量。Query 表示当前位置"想要查找什么",Key 表示其他位置"拥有什么信息",Value 表示其他位置"提供什么信息"。注意力得分通过 Query 与 Key 的点积计算,然后经过 Softmax 归一化得到权重,最后对 Value 进行加权求和。
2.2 缩放点积注意力 Scaled Dot-Product Attention
注意力计算的核心公式为:
Attention(Q, K, V) = softmax(Q · K^T / √d_k) · V
其中 Q·K^T 计算所有位置对之间的相似度得分,除以 √d_k 进行缩放(Scale)以防止点积值过大导致Softmax梯度饱和。缩放因子 √d_k 的选取基于以下考量:当 d_k 较大时,点积的方差也会变大(约为 d_k),导致Softmax函数的输入落入梯度极小的区域。通过除以 √d_k 可以将方差控制为1,保持梯度的稳定性。
缩放因子的数学解释
假设 q 和 k 是均值为0、方差为1的独立随机向量,则点积 q·k = Σ(q_i·k_i) 的均值为0,方差为 d_k。这意味着点积值的标准差为 √d_k。通过除以 √d_k,使得注意力得分的方差恢复为1,避免Softmax进入饱和区。
# 缩放点积注意力的PyTorch实现
import torch
import torch.nn as nn
import torch.nn.functional as F
def scaled_dot_product_attention(Q, K, V, mask=None):
"""
Q: (batch_size, ..., seq_len_q, d_k)
K: (batch_size, ..., seq_len_k, d_k)
V: (batch_size, ..., seq_len_k, d_v)
mask: (batch_size, ..., seq_len_q, seq_len_k)
"""
d_k = Q.size(-1)
# 计算QK^T / sqrt(d_k)
scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5)
# 掩码处理(将需要屏蔽的位置设为负无穷)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# Softmax归一化得到注意力权重
attn_weights = F.softmax(scores, dim=-1)
# 加权求和得到输出
output = torch.matmul(attn_weights, V)
return output, attn_weights
2.3 多头注意力 Multi-Head Attention
多头注意力机制(Multi-Head Attention)是自注意力的扩展。它将模型分为 h 个独立的注意力头,每个头在不同的表示子空间中并行地执行缩放点积注意力。这样模型可以从不同的角度关注序列中的不同特征,捕捉更加丰富的关系。
MultiHead(Q, K, V) = Concat(head_1, head_2, ..., head_h) · W_O
其中 head_i = Attention(Q · W_Q_i, K · W_K_i, V · W_V_i)
每个注意力头的维度为 d_k = d_v = d_model / h。各头的输出拼接后通过输出投影矩阵 W_O 映射回 d_model 维度。原论文中 d_model=512,h=8,因此每个头的维度 d_k=64。
# 多头注意力机制的完整PyTorch实现
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, nhead, dropout=0.1):
super().__init__()
assert d_model % nhead == 0
self.d_model = d_model
self.nhead = nhead
self.d_k = d_model // nhead
# 定义QKV线性变换矩阵
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) # 输出投影
self.dropout = nn.Dropout(dropout)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# 1. 线性变换并分割成多个头
# Q: (batch, seq_len, d_model) -> (batch, seq_len, nhead, d_k) -> (batch, nhead, seq_len, d_k)
Q = self.W_Q(Q).view(batch_size, -1, self.nhead, self.d_k).transpose(1, 2)
K = self.W_K(K).view(batch_size, -1, self.nhead, self.d_k).transpose(1, 2)
V = self.W_V(V).view(batch_size, -1, self.nhead, self.d_k).transpose(1, 2)
# 2. 缩放点积注意力
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn_weights = F.softmax(scores, dim=-1)
attn_weights = self.dropout(attn_weights)
# 3. 加权求和
attn_out = torch.matmul(attn_weights, V) # (batch, nhead, seq_len, d_k)
# 4. 拼接所有头并投影
attn_out = attn_out.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
output = self.W_O(attn_out)
return output, attn_weights
2.4 注意力头数的选择
注意力头数 h 是Transformer中重要的超参数。原论文中 d_model=512,h=8。实际应用中的常见配置如下:
模型
d_model
nhead
d_k
参数量(单层)
Transformer Base
512
8
64
~1.05M
Transformer Big
1024
16
64
~4.19M
BERT-Base
768
12
64
~2.36M
BERT-Large
1024
16
64
~4.19M
GPT-2
768
12
64
~2.36M
GPT-3
12288
96
128
~453M
选择 h 的核心原则是保持 d_k = d_model / h 为合理值(通常为64或128)。过小的 d_k 会限制每个头的表达能力,过大的 d_k 会使缩放点积注意力中 Softmax 梯度易于饱和。
三、位置编码 Positional Encoding
由于自注意力机制本身不具备序列顺序感知能力(它对输入序列的排列是置换等变的),必须显式地在输入中注入位置信息。Transformer 原论文提出了正弦余弦位置编码(Sinusoidal Positional Encoding) ,这是一种不需要学习的确定性编码方式。
PE_(pos, 2i) = sin(pos / 10000^(2i / d_model))
PE_(pos, 2i+1) = cos(pos / 10000^(2i / d_model))
其中 pos 表示位置索引,i 表示维度索引(0 ≤ i < d_model/2)。这种编码方式的精妙之处在于:对于任意固定偏移量 k,PE(pos+k) 可以表示为 PE(pos) 的线性函数,这使得模型能够轻易地学习到相对位置关系。
# 正弦余弦位置编码的PyTorch实现
import torch
import math
def sinusoidal_positional_encoding(max_len, d_model):
"""生成正弦余弦位置编码
Args:
max_len: 最大序列长度
d_model: 模型维度
Returns:
pe: (max_len, d_model)
"""
pe = torch.zeros(max_len, d_model)
# 位置索引: (max_len, 1)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
# 频率项: 1/10000^(2i/d_model)
div_term = torch.exp(torch.arange(0, d_model, 2).float()
* (-math.log(10000.0) / d_model))
# 偶数维度使用sin,奇数维度使用cos
pe[:, 0::2] = torch.sin(position * div_term) # 2i
pe[:, 1::2] = torch.cos(position * div_term) # 2i+1
return pe
# 使用示例
max_len = 512
d_model = 512
pe = sinusoidal_positional_encoding(max_len, d_model)
# pe[pos] 可直接与token embedding相加
3.1 可学习位置编码
除了正弦余弦编码,可学习位置编码(Learned Positional Encoding) 是另一种常见方案。BERT和GPT系列模型均采用此方法。它将位置编码视为可训练的参数矩阵,通过反向传播学习适合特定任务的位置表示。可学习编码的优点是灵活性高,能自适应地学习位置模式,缺点是无法处理超过训练时最大长度的序列。
# 可学习位置编码
class LearnedPositionalEncoding(nn.Module):
def __init__(self, max_len, d_model, dropout=0.1):
super().__init__()
self.dropout = nn.Dropout(dropout)
# 位置编码参数: (1, max_len, d_model)
self.position_embeddings = nn.Parameter(
torch.randn(1, max_len, d_model)
)
def forward(self, x):
# x: (batch, seq_len, d_model)
seq_len = x.size(1)
# 添加位置编码
x = x + self.position_embeddings[:, :seq_len, :]
return self.dropout(x)
3.2 相对位置编码
绝对位置编码(无论是正弦余弦还是可学习)存在一个局限:它难以捕捉位置之间的相对关系 。例如,模型需要理解"单词A在单词B左边第3个位置"这类相对信息。为此,Shaw等人提出了相对位置编码(Relative Positional Encoding) ,该编码方式在注意力得分计算中引入位置偏差:
score(i, j) = (Q_i · K_j) + (Q_i · a_(i-j))
其中 a_(i-j) 是位置i和j之间的相对位置嵌入
相对位置编码的关键优势:平移不变性 ——无论"动词-名词"关系出现在句子的开头还是结尾,相对位置模式都是一致的。Transformer-XL、T5等模型广泛采用了相对位置编码。XLNet和Transformer-XL更进一步,将相对位置信息同时融入注意力得分计算和值映射中。
三种位置编码对比
正弦余弦编码: 无需学习,可外推到任意长度,但表达能力固定
可学习编码: 灵活性强,但无法处理超长序列,需大量数据学习
相对位置编码: 捕获相对位置关系,具有平移不变性,计算复杂度略高
四、前馈神经网络 FFN
Transformer中的逐位置前馈神经网络(Position-wise Feed-Forward Network) 对每个位置的表示独立地应用相同的非线性变换。它本质上是一个两层的多层感知机(MLP),包含一个隐藏层扩展和一个输出层压缩:
FFN(x) = W_2 · GELU(W_1 · x + b_1) + b_2
其中 W_1 ∈ R^(d_ff × d_model)、W_2 ∈ R^(d_model × d_ff)。隐藏层维度 d_ff 通常设置为 d_model 的4倍(原论文中 d_model=512,d_ff=2048)。这种"先扩展后压缩"的设计使得模型能够在高维空间中学习更丰富的特征表示。
# 标准FFN实现(GELU激活)
class FeedForward(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff)
self.linear2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# 激活函数使用GELU
x = F.gelu(self.linear1(x))
x = self.dropout(x)
x = self.linear2(x)
return x
# 实例化:d_model=512, d_ff=2048
ffn = FeedForward(d_model=512, d_ff=2048, dropout=0.1)
4.1 激活函数的选择
Transformer原论文使用ReLU激活函数。后续研究发现,GELU(Gaussian Error Linear Unit) 在许多任务上表现更好,已成为BERT和GPT系列模型的事实标准。GELU定义为:
GELU(x) = x · Φ(x) = x · 0.5 · [1 + erf(x / √2)]
其中 Φ(x) 是标准高斯分布的累积分布函数。GELU可以看作ReLU的平滑版本,在输入较小时有非零梯度,有利于训练稳定性。
4.2 SwiGLU变体
近年来的研究表明,将FFN中的激活函数替换为SwiGLU(Swish-Gated Linear Unit) 可以显著提升模型性能。SwiGLU由Google在PaLM模型中推广,它引入了一个门控机制(Gating Mechanism):
SwiGLU(x) = (Swish(x · W_gate) ⊙ (x · W_up)) · W_down
其中 Swish(x) = x · sigmoid(βx),⊙ 表示逐元素相乘。SwiGLU将单个线性层拆分为门控分支和上投影分支,通过门控机制控制信息流动。需要注意的是,使用SwiGLU时,隐藏层维度通常从 4×d_model 调整为 8/3×d_model 以保持参数量一致(因为SwiGLU有三个权重矩阵而非两个)。LLaMA、GPT-J等现代大语言模型广泛采用了SwiGLU。
# SwiGLU实现
class SwiGLU(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
# SwiGLU需要三个权重矩阵
self.W_gate = nn.Linear(d_model, d_ff) # 门控分支
self.W_up = nn.Linear(d_model, d_ff) # 上投影分支
self.W_down = nn.Linear(d_ff, d_model) # 下投影
self.dropout = nn.Dropout(dropout)
def forward(self, x):
gate = F.silu(self.W_gate(x)) # SiLU = Swish(β=1)
up = self.W_up(x)
x = gate * up # 门控乘法
x = self.dropout(x)
x = self.W_down(x)
return x
# LLaMA风格配置: d_ff = int(8/3 * d_model)
llama_ffn = SwiGLU(d_model=4096,
d_ff=int(8/3 * 4096)) # ≈ 10923
五、层归一化 Layer Normalization
层归一化(Layer Normalization, LayerNorm) 是Transformer中稳定训练的关键技术。与批归一化(Batch Normalization)沿着批次维度计算统计量不同,LayerNorm沿着特征维度计算均值和方差,因此不受批次大小的影响,特别适合序列模型。
LayerNorm(x) = γ · (x - μ) / √(σ² + ε) + β
其中 μ = (1/d) · Σx_i, σ² = (1/d) · Σ(x_i - μ)²
其中 γ 和 β 是可学习的缩放参数和偏移参数,ε 是为数值稳定性添加的小常数。LayerNorm确保每一层的输出具有稳定的均值和方差,缓解了内部协变量偏移问题。
5.1 Pre-Norm vs Post-Norm
LayerNorm在Transformer中的放置位置经历了重要的演进,形成了两种主流方案:
对比维度
Post-Norm(原始Transformer)
Pre-Norm(现代主流方案)
计算顺序
残差连接 → 子层 → LayerNorm
LayerNorm → 子层 → 残差连接
公式表达
output = LayerNorm(x + Sublayer(x))
output = x + Sublayer(LayerNorm(x))
训练稳定性
深层训练不稳定,需warmup
深层训练稳定,可无warmup
典型模型
Transformer原论文、BERT
GPT系列、LLaMA、ViT
梯度流动
经过LayerNorm反向传播
残差捷径直达,梯度畅通
Post-Norm (原始论文方案):将LayerNorm放在残差连接之后。表达式为 LayerNorm(x + Sublayer(x))。这种方案下,梯度需要流经LayerNorm层,在深层网络中可能导致梯度消失。因此Post-Norm通常需要学习率预热(warmup)和更细致的调参。
Pre-Norm (现代主流方案):将LayerNorm放在子层之前。表达式为 x + Sublayer(LayerNorm(x))。优势在于残差连接提供了无损的梯度捷径,模型可以在没有warmup的情况下直接训练深层网络。当前绝大多数Transformer模型(GPT、LLaMA、ViT等)都采用Pre-Norm方案。
# Post-Norm vs Pre-Norm对比实现
class EncoderLayer_PostNorm(nn.Module):
"""Post-Norm方案(原始Transformer)"""
def __init__(self, d_model, nhead, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, nhead, dropout)
self.ffn = FeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Post-Norm: 先残差,后归一化
x = self.norm1(x + self.dropout1(self.self_attn(x, x, x, mask)[0]))
x = self.norm2(x + self.dropout2(self.ffn(x)))
return x
class EncoderLayer_PreNorm(nn.Module):
"""Pre-Norm方案(现代主流)"""
def __init__(self, d_model, nhead, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, nhead, dropout)
self.ffn = FeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Pre-Norm: 先归一化,后残差
x = x + self.dropout1(self.self_attn(self.norm1(x), self.norm1(x),
self.norm1(x), mask)[0])
x = x + self.dropout2(self.ffn(self.norm2(x)))
return x
六、掩码机制 Masking
Transformer中使用不同类型的掩码来约束注意力机制的行为。主要分为两大类:填充掩码(Padding Mask) 和因果掩码(Causal Mask / Look-Ahead Mask) 。
6.1 填充掩码 Padding Mask
在批处理中,不等长的序列需要填充(Padding)到相同长度。填充位置的Token不应参与注意力计算,因为它们是人为添加的无意义标记。填充掩码将填充位置的注意力得分设为负无穷(-∞),使得Softmax后对应位置的注意力权重趋近于0。
# 填充掩码生成
def create_padding_mask(seq, pad_token_id=0):
"""
seq: (batch_size, seq_len)
return: (batch_size, 1, 1, seq_len)
"""
mask = (seq != pad_token_id).float()
return mask.unsqueeze(1).unsqueeze(2)
6.2 因果掩码 Causal Mask
因果掩码(也称为前瞻掩码 Look-Ahead Mask)用于解码器的自注意力层,确保在生成位置i的预测时,只能看到位置 < i 的已生成Token,而看不到未来位置的Token。如果不加此掩码,解码器会"作弊"地看到正确答案。
因果掩码是一个上三角矩阵,上三角部分(未来位置)填充为负无穷,下三角部分(当前和历史位置)为0:
# 因果掩码生成
def create_causal_mask(seq_len):
"""
return: (seq_len, seq_len)
下三角为0(可见),上三角为-inf(不可见)
"""
mask = torch.triu(torch.ones(seq_len, seq_len) * float('-inf'),
diagonal=1)
return mask
# 示例: seq_len=4 的因果掩码
# tensor([[0., -inf, -inf, -inf],
# [0., 0., -inf, -inf],
# [0., 0., 0., -inf],
# [0., 0., 0., 0.]])
6.3 三种注意力类型的对比
注意力类型
使用位置
Q、K、V来源
掩码类型
作用
Self-Attention (自注意力)
编码器
Q=K=V=编码器输入
Padding Mask
捕获输入序列内部的长程依赖关系
Masked Self-Attention (掩码自注意力)
解码器(第一子层)
Q=K=V=解码器输入
Causal Mask + Padding Mask
确保自回归生成中只看历史位置
Cross-Attention (交叉注意力)
解码器(第二子层)
Q=解码器输出 K=V=编码器输出
Padding Mask(基于编码器)
将编码器的语义信息传递给解码器
理解Encoder-Decoder Attention
在编码器-解码器注意力中,Query来自解码器的前一子层(掩码自注意力输出),Key和Value来自编码器的最终输出。这样,解码器的每个位置都可以"关注"编码器输出的所有位置,从源序列中获取相关信息。这种设计使得解码器在生成目标序列时,能够参考源序列的完整语义信息。例如,在机器翻译中,解码器生成英文单词时,可以关注源语言句子中的所有单词。
七、Transformer的并行训练优势
Transformer相比RNN的最显著优势在于并行计算能力 。RNN的每个时间步依赖前一个时间步的隐状态,必须顺序执行,无法并行。而Transformer的自注意力机制在计算每个位置的表示时,可以同时计算所有位置,充分利用GPU的大规模并行计算能力。
7.1 vs RNN的序贯计算
对比维度
Transformer
RNN / LSTM
计算方式
完全并行
顺序串行
时间复杂度
O(n²·d) (可并行化)
O(n·d²) (不可并行)
训练速度(n=512)
~快10-100倍
慢(单步依赖)
最大路径长度
O(1) — 任意位置直接相连
O(n) — 需逐时间步传递
长程依赖捕获
优秀(全局注意力)
困难(梯度消失/爆炸)
位置感知
需显式位置编码
天然具备(时间步序贯)
硬件利用率
极高(充分利用GPU矩阵计算)
低(矩阵-向量运算)
7.2 长程依赖捕获
在RNN中,两个位置之间的信息传递路径长度等于它们之间的距离。对于长序列,信息需要经过多个时间步的传递,极易受到梯度消失或梯度爆炸的影响。而在Transformer中,自注意力机制将任意两个位置之间的路径长度缩短为O(1) ——无论两个词相距多远,注意力计算都直接建立连接。这种特性使得Transformer在捕获长程依赖关系方面具有天然优势,也是其在文本、代码、DNA序列等长程依赖任务中表现优异的重要原因。
计算复杂度分析
自注意力的时间复杂度为 O(n²·d),其中 n 为序列长度,d 为隐层维度。当 n 较大时,二次复杂度成为瓶颈。针对这一问题,后续研究提出了多种高效注意力变体:
稀疏注意力(Sparse Attention): 限制每个位置只关注局部和少数全局位置(如Longformer、BigBird)
线性注意力(Linear Attention): 将Softmax注意力近似为核函数,复杂度降至 O(n)(如Performer、Linear Transformer)
闪存注意力(Flash Attention): 通过分块计算和内存优化,在不降低精度的情况下加速注意力计算
# 训练速度的直观对比(伪代码示意)
import time
import torch.nn as nn
# RNN: 必须顺序执行
def train_rnn(model, x):
# x: (seq_len, batch, d_model)
h = torch.zeros(batch, d_model)
for t in range(seq_len):
h = model.rnn_cell(x[t], h) # 第t步必须等t-1步完成
return h
# 总时间 ≈ seq_len × 单步时间
# Transformer: 完全并行
def train_transformer(model, x):
# x: (batch, seq_len, d_model)
h = model.transformer(x) # 所有位置同时计算
return h
# 总时间 ≈ 单次前向传播时间(与seq_len弱相关)
八、完整Transformer模型实现
下面整合前述所有组件,构建一个完整的Transformer模型。
# 完整Transformer模型实现
class Transformer(nn.Module):
def __init__(self, vocab_size, d_model=512, nhead=8,
num_layers=6, d_ff=2048, max_len=512, dropout=0.1):
super().__init__()
# 词嵌入 + 位置编码
self.token_embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = sinusoidal_positional_encoding(max_len, d_model)
self.dropout = nn.Dropout(dropout)
# 编码器:N层堆叠
self.encoder_layers = nn.ModuleList([
EncoderLayer_PreNorm(d_model, nhead, d_ff, dropout)
for _ in range(num_layers)
])
# 解码器:N层堆叠
self.decoder_layers = nn.ModuleList([
DecoderLayer_PreNorm(d_model, nhead, d_ff, dropout)
for _ in range(num_layers)
])
# 输出投影
self.output_projection = nn.Linear(d_model, vocab_size)
self.d_model = d_model
def forward(self, src, tgt, src_mask=None, tgt_mask=None):
# src, tgt: (batch, seq_len)
# 编码器
src_emb = self.token_embedding(src) * math.sqrt(self.d_model)
src_emb = src_emb + self.pos_encoding[:src.size(1), :].to(src.device)
src_emb = self.dropout(src_emb)
enc_output = src_emb
for layer in self.encoder_layers:
enc_output = layer(enc_output, src_mask)
# 解码器
tgt_emb = self.token_embedding(tgt) * math.sqrt(self.d_model)
tgt_emb = tgt_emb + self.pos_encoding[:tgt.size(1), :].to(tgt.device)
tgt_emb = self.dropout(tgt_emb)
dec_output = tgt_emb
for layer in self.decoder_layers:
dec_output = layer(dec_output, enc_output,
src_mask, tgt_mask)
# 输出投影
logits = self.output_projection(dec_output)
return logits
class DecoderLayer_PreNorm(nn.Module):
"""解码器层(Pre-Norm方案)"""
def __init__(self, d_model, nhead, d_ff, dropout=0.1):
super().__init__()
self.masked_attn = MultiHeadAttention(d_model, nhead, dropout)
self.cross_attn = MultiHeadAttention(d_model, nhead, dropout)
self.ffn = FeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
self.dropout3 = nn.Dropout(dropout)
def forward(self, x, enc_output, src_mask=None, tgt_mask=None):
# 子层1: 掩码自注意力
x = x + self.dropout1(
self.masked_attn(self.norm1(x), self.norm1(x),
self.norm1(x), tgt_mask)[0]
)
# 子层2: 编码器-解码器交叉注意力
x = x + self.dropout2(
self.cross_attn(self.norm2(x), enc_output,
enc_output, src_mask)[0]
)
# 子层3: 前馈网络
x = x + self.dropout3(self.ffn(self.norm3(x)))
return x
九、核心要点总结
注意力机制的本质: Self-Attention通过QKV计算让序列中任意两个位置直接交互,路径长度为O(1),从根本上解决了长程依赖问题
多头注意力的优势: 将模型分割为h个独立的表示子空间,每个头关注不同的语义特征,拼接后通过投影融合,提升了模型的表达能力
位置编码的必要性: 自注意力不具备序列顺序感知能力,必须通过正弦余弦编码或可学习编码显式注入位置信息。相对位置编码更能适应不同长度的序列
Pre-Norm vs Post-Norm: Pre-Norm方案梯度流动更顺畅,深层训练更稳定,已成为现代Transformer模型的主流选择
三种注意力各司其职: Self-Attention编码源序列语义、Masked Self-Attention保证自回归因果性、Cross-Attention传递源序列信息到解码器
FFN的非线性变换: 逐位置前馈网络通过"扩展-激活-压缩"的流程为模型引入非线性,SwiGLU等变体通过门控机制进一步增强了表达能力
并行训练的革命性突破: Transformer完全可并行化的计算模式使得训练速度比RNN提升数十倍,同时自注意力的全局感受野使得长程依赖捕获远超传统序列模型
从Transformer到预训练模型: 基于Transformer的Encoder-only(BERT)、Decoder-only(GPT)和Encoder-Decoder(T5)架构衍生出了当今最强大的预训练语言模型,推动NLP进入大模型时代
十、进一步思考
Transformer架构自2017年提出以来,已经走过了近九年的发展历程。期间涌现了大量改进和变体:从BERT的预训练-微调范式,到GPT系列的大规模自回归语言模型,再到ViT将Transformer应用于计算机视觉,以及最近的混合架构(如Mamba、RWKV等状态空间模型)。Transformer的影响力已经远远超出了NLP领域,成为深度学习中最具影响力的架构之一。
当前研究热点与未来方向
高效注意力: Flash Attention、多查询注意力(MQA)、分组查询注意力(GQA)等降低注意力计算和内存开销的技术
长上下文建模: 通过RoPE、ALiBi、Ring Attention等技术将Transformer的上下文窗口扩展到百万级Token
混合架构: Transformer与状态空间模型(State Space Models)的融合,在保持并行训练优势的同时降低二次复杂度
多模态Transformer: 统一处理文本、图像、视频、音频等多模态数据的Transformer架构
推理优化: 投机解码(Speculative Decoding)、KV-Cache优化等技术提升Transformer推理速度
"We propose a new simple network architecture, the Transformer, based solely on attention mechanisms, dispensing with recurrence and convolutions entirely." — Vaswani et al., 2017
理解Transformer的原理是掌握现代深度学习的基石。从注意力机制的数学推导到实际代码实现,从Encoder-Decoder的完整结构到各种改进变体,每一步都体现了深度学习研究者的智慧。随着大语言模型的持续演进,Transformer架构也在不断进化,但其核心思想——注意力就是一切 ——始终没有改变。
本学习笔记基于论文《Attention Is All You Need》(Vaswani et al., 2017) 及相关研究整理总结
本学习笔记为本人学习资料,不得转载
免责声明: 本学习笔记仅供学习参考,如有错误欢迎指正。文中涉及的所有模型架构和算法均为公开研究成果。