论文信息:Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Łukasz Kaiser, Illia Polosukhin. "Attention Is All You Need". NeurIPS 2017.
2017年,Google Research 团队发表的《Attention Is All You Need》彻底改写了自然语言处理和深度学习的历史。这篇论文提出了一种名为 Transformer 的新型网络架构,核心思想是:完全摒弃循环神经网络(RNN)和卷积神经网络(CNN),仅基于注意力机制(Attention Mechanism)来处理序列数据。
在此之前,序列建模(如机器翻译、文本生成)几乎被 RNN(LSTM/GRU)统治。RNN 的时序依赖特性使得训练难以并行化,长距离依赖捕获困难,梯度消失/爆炸问题长期困扰着研究者。Transformer 的横空出世解决了这些问题,并迅速成为 NLP 领域的事实标准架构,进而扩展到计算机视觉(ViT)、语音处理、多模态等各个 AI 子领域。
截至2026年,几乎所有主流大语言模型(GPT-4、Claude、Gemini、LLaMA、DeepSeek、GLM、Qwen 等)均基于 Transformer 架构的变体。这篇论文是深度学习史上引用量最高的论文之一,累计引用超过 15 万次。
在 Transformer 之前,机器翻译等序列到序列(Seq2Seq)任务的标准架构是 Encoder-Decoder 框架 + RNN(LSTM/GRU)。其工作流程如下:
然而,RNN 架构存在以下根本性问题:
部分研究者尝试使用 CNN 替代 RNN(如 WaveNet、ConvS2S)。CNN 可以并行计算,但:
针对上述问题,Transformer 提出了三个重大创新:
Transformer 遵循 Encoder-Decoder 架构,但编码器和解码器均由若干相同的 Transformer Block 堆叠而成(原论文使用 )。
输出概率
↑
Softmax
↑
线性层
↑
┌─────────────────────┐
│ 解码器 × N │
│ ┌───────────────┐ │
│ │ Add & Norm │ │
│ │ Feed Forward │ │
│ └───────↑───────┘ │
│ ┌───────↑───────┐ │
│ │ Add & Norm │ │
│ │ Cross-Attn │ │
│ └───────↑───────┘ │
│ ┌───────↑───────┐ │
│ │ Add & Norm │ │
│ │ Masked SA │ │
│ └───────↑───────┘ │
│ ┌───────┴───────┐ │
│ │ Positional │ │
│ │ Encoding │ │
│ └───────↑───────┘ │
└─────────↑─────────────┘
↑
┌─────────┴─────────────┐
│ 编码器 × N │
│ ┌───────────────┐ │
│ │ Add & Norm │ │
│ │ Feed Forward │ │
│ └───────↑───────┘ │
│ ┌───────↑───────┐ │
│ │ Add & Norm │ │
│ │ Self-Attn │ │
│ └───────↑───────┘ │
│ ┌───────┴───────┐ │
│ │ Positional │ │
│ │ Encoding │ │
│ └───────↑───────┘ │
└─────────↑─────────────┘
↑
输入嵌入
输入序列首先通过词嵌入层映射为 维的向量。原论文中 。
对于机器翻译任务,输入和输出使用不同的词嵌入矩阵。两个嵌入矩阵的维度均为 ,词汇表大小约为 37,000(基于 Byte-Pair Encoding 的子词切分)。
由于 Transformer 没有循环结构,模型本身不感知 token 在序列中的位置。位置编码的作用是让模型能够利用序列的顺序信息。
原论文使用了 正弦/余弦位置编码(Sinusoidal Positional Encoding):
其中 是位置索引, 是维度索引。
这种编码方式有几个巧妙之处:
位置编码与 token 嵌入相加后输入编码器:。
Hugo 的实践笔记:后来的研究工作发现,可学习位置编码(Learnable Positional Encoding)效果与正弦编码相当,但更为灵活。BERT、GPT-2 等早期模型使用了可学习位置编码。而 RoPE(Rotary Position Embedding,苏剑林等人提出)在 LLaMA、Mistral 等现代模型中成为主流,它通过在注意力计算中直接旋转 Query 和 Key 向量来编码相对位置,兼具绝对位置编码的简洁性和相对位置编码的表达能力。
这是 Transformer 的核心构建块。给定 Query(查询)、Key(键)和 Value(值),注意力输出是 Value 的加权和,权重由 Query 与 Key 的兼容性决定。
计算公式:
其中:
缩放因子 的作用:
当 较大时, 的点积值的方差会随维度增大而增大(每个维度是均值为 0、方差为 1 的随机变量时,点积结果的方差为 )。这意味着 softmax 的输入会聚集在梯度极小的区域,导致梯度消失。除以 将方差归一化为 1,保持梯度稳定。
自注意力(Self-Attention):当 、、 来自同一个序列时,称为自注意力。在编码器中,每个位置可以关注输入序列的所有位置(包括自身)。
掩码自注意力(Masked Self-Attention):在解码器中,每个位置只能关注它之前(含自身)的位置,以防止信息泄露。这是通过在上三角矩阵中添加 的掩码实现的:
其中 对 (允许关注), 对 (禁止关注未来的 token)。
Hugo 的实践笔记:在实际实现中,掩码矩阵通常用
float('-inf')填充上三角。PyTorch 中可以用torch.triu(torch.full((seq_len, seq_len), float('-inf')), diagonal=1)生成。需要特别注意-inf在softmax中的行为——经过softmax后这些位置的注意力权重变为 0,不会对 Value 加权。
多头注意力将 Query、Key、Value 通过不同的线性投影映射到 个子空间,在每个子空间中独立计算注意力,然后将结果拼接并做线性投影:
其中每个注意力头:
投影矩阵:
原论文设置 ,。每个头的维度降低到 64,计算复杂度与单头注意力()相近,但模型可以从不同角度关注信息。
多头注意力的直觉理解:
不同的注意力头可能学会关注不同类型的关系:
在 Transformer 中,多头注意力在三个位置使用(每种使用 头):
| 位置 | 类型 | 、 来源 | 来源 | 用途 |
|---|---|---|---|---|
| 编码器 Self-Attention | 自注意力 | 编码器输入 | 编码器输入 | 编码输入序列的上下文表示 |
| 解码器 Masked Self-Attention | 自注意力(掩码) | 解码器输入 | 解码器输入 | 解码器自回归生成 |
| 解码器 Cross-Attention | 交叉注意力 | 编码器输出 | 解码器输入 | 解码器关注编码器输出 |
每个 Transformer Block 中的注意力层之后,是一个两层的全连接前馈网络:
内部隐藏层维度为 ,约是 的 4 倍。
"Position-wise" 的含义:这个 FFN 对序列中的每个位置独立且相同地应用,不跨位置交互。也就是说,它是一个逐点(point-wise)的 MLP,参数量为 。
FFN 的作用是给每个位置的表示增加非线性变换能力,让模型能学习到注意力输出之上的复杂特征组合。这可以理解为一个记忆检索与特征交互的过程:注意力层负责"路由"信息,FFN 层负责"处理"信息。
Hugo 的实践笔记:在现代 LLM 中,FFN 层通常使用 SwiGLU(LLaMA 系列)或 GELU 激活函数替代原版的 ReLU。SwiGLU 的公式为 ,其中 是逐元素乘法。这增加了约 50% 的参数量,但通常能获得更好的效果。这也是为什么 LLaMA 的 FFN 维度通常比 GPT 更大。
Transformer 使用了 Pre-Norm 或 Post-Norm 的架构:
原论文(Post-Norm):
即先执行子层操作(注意力或 FFN),然后加残差连接,最后层归一化。
现代实践(Pre-Norm):
即先层归一化,再执行子层操作,最后加残差连接。GPT-2 及之后的模型广泛采用 Pre-Norm,因为它在训练中更稳定,特别是在深层网络中。
层归一化(Layer Normalization):
其中 和 是在特征维度上(而非 batch 维度)计算的均值和标准差。对于形状为 的输入, 和 的形状为 ,即在最后一个维度上归一化。
Dropout:在每个子层的输出上应用 dropout(原论文设置为 0.1),同时对嵌入和位置编码的和也应用 dropout。
编码器( 层,每层结构相同):
输入 → Multi-Head Self-Attention → Add & Norm → FFN → Add & Norm → 输出
每个编码器层接收上一层的输出作为 、、,输出与输入序列等长的表示序列。
解码器( 层,每层包含三个子层):
输入 → Masked Multi-Head Self-Attention → Add & Norm →
Cross-Attention (Q=decoder, K=V=encoder) → Add & Norm →
FFN → Add & Norm → 输出
解码器的交叉注意力(Cross-Attention)将编码器的输出作为 和 ,解码器的前一层的输出作为 。这使得解码器能够在生成每个目标 token 时,关注输入序列中最相关的部分。
最终,解码器输出经过一个线性层(将 映射到词汇表大小)和 softmax 层,得到下一个 token 的概率分布。
使用 Adam 优化器,设置 ,,。
学习率调度:采用预热 + 衰减的策略:
其中 。在前 4000 步,学习率线性增长;之后随步数平方根倒数衰减。
这种调度策略的直觉是:训练初期需要较小的学习率来稳定优化过程;4000 步后模型进入稳定训练阶段,学习率逐渐衰减。
| 任务 | 模型 | BLEU | 训练成本(FLOPs) |
|---|---|---|---|
| WMT 2014 英→德 | BiLSTM + Attention (SOTA) | 26.5 | - |
| WMT 2014 英→德 | ConvS2S (Fb) | 25.2 | 9.6 |
| WMT 2014 英→德 | Transformer (Base) | 27.3 | 3.3 |
| WMT 2014 英→德 | Transformer (Big) | 28.4 | 2.3 |
| WMT 2014 英→法 | ConvS2S + MoE | 40.5 | 32.0 |
| WMT 2014 英→法 | Transformer (Base) | 38.1 | 4.1 |
| WMT 2014 英→法 | Transformer (Big) | 41.8 | 2.3 |
训练成本列是与已有最优模型相比的相对值,数字越小越好。
Transformer (Big) 增加了 、、,并且使用 0.3 的 dropout。
注意力可视化:论文中对编码器的自注意力进行可视化,展示了多头注意力如何关注输入序列的不同位置。例如,在"it"的消歧上,不同注意力头分别关注"it"的语法角色和语义指代对象。
注意力距离:分析表明,解码器下层更关注局部信息(相邻 token),而上层更关注长距离依赖。编码器中也观察到类似的分层特征。
从计算角度比较 Transformer 与 RNN、CNN:
| 层类型 | 每层复杂度 | 序列操作数 | 最大路径长度 |
|---|---|---|---|
| 自注意力 | |||
| 循环网络 | |||
| 卷积 |
其中 是序列长度, 是表示维度, 是卷积核大小。
关键观察:
Transformer 的 时空复杂度是其主要局限。后续研究提出了多种改进:
后来出现了一些重要的架构分叉:
下面是一个简化版 Transformer 编码器层的 PyTorch 实现:
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class ScaledDotProductAttention(nn.Module):
def __init__(self, d_k, dropout=0.1):
super().__init__()
self.d_k = d_k
self.dropout = nn.Dropout(dropout)
def forward(self, q, k, v, mask=None):
# q, k, v: (batch, heads, seq_len, d_k)
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn = self.dropout(F.softmax(scores, dim=-1))
return torch.matmul(attn, v), attn
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, n_heads, dropout=0.1):
super().__init__()
assert d_model % n_heads == 0
self.d_k = d_model // n_heads
self.n_heads = n_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)
self.attention = ScaledDotProductAttention(self.d_k, dropout)
self.dropout = nn.Dropout(dropout)
def forward(self, q, k, v, mask=None):
batch_size = q.size(0)
# Linear projections + split heads
Q = self.w_q(q).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
K = self.w_k(k).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
V = self.w_v(v).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
# Apply attention
x, attn = self.attention(Q, K, V, mask)
# Concatenate heads
x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.n_heads * self.d_k)
return self.w_o(x)
class PositionwiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
self.w_1 = nn.Linear(d_model, d_ff)
self.w_2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
return self.w_2(self.dropout(F.relu(self.w_1(x))))
class EncoderLayer(nn.Module):
def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
self.ffn = PositionwiseFeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Self-attention + residual + layer norm (Pre-Norm style)
x = x + self.dropout(self.self_attn(self.norm1(x), self.norm1(x), self.norm1(x), mask))
# FFN + residual + layer norm
x = x + self.dropout(self.ffn(self.norm2(x)))
return x
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, dtype=torch.float).unsqueeze(1)
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)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0) # (1, max_len, d_model)
self.register_buffer('pe', pe)
def forward(self, x):
return x + self.pe[:, :x.size(1), :]
class SimpleEncoder(nn.Module):
def __init__(self, vocab_size, d_model=512, n_layers=6, n_heads=8, d_ff=2048, max_len=5000, dropout=0.1):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_len)
self.layers = nn.ModuleList([
EncoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)
])
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
x = self.dropout(self.pos_encoding(self.embedding(x)))
for layer in self.layers:
x = layer(x, mask)
return x
Hugo 的实践笔记:在生产环境中,绝对不要自己手写训练用的 Transformer——使用 Hugging Face Transformers 库更安全高效。但手写一个简化版有助于深入理解每个组件的原理。上述代码中我选择了 Pre-Norm 风格,因为现代 LLM 实践中 Pre-Norm 训练更稳定。另外注意
ScaledDotProductAttention中的 mask 处理:masked_fill使用float('-inf')而非一个极小值(如-1e9),这在数值上是更正确的做法。
以下是如何使用 Hugging Face Transformers 加载预训练模型:
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
# 加载一个基于编码器-解码器 Transformer 的翻译模型
tokenizer = AutoTokenizer.from_pretrained("t5-small")
model = AutoModelForSeq2SeqLM.from_pretrained("t5-small")
# 翻译示例
input_text = "translate English to German: The attention mechanism is powerful."
inputs = tokenizer(input_text, return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=40)
translation = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(translation)
# 输出: "Der Aufmerksamkeitsmechanismus ist leistungsstark."
《Attention Is All You Need》不仅是一篇论文,它开启了一个时代:
Transformer 并非完美无缺,了解其局限性有助于合理选型:
Hugo 的实践笔记:在项目选型时,如果任务只是短文本分类(如情感分析、垃圾邮件检测),传统方法(如 BoW + XGBoost)可能更简单高效。Transformer 的优势在长距离依赖、上下文理解和生成任务上才真正体现。不要因为它是时尚就滥用——我见过一个项目用 T5 做商品分类,模型 200M 参数,线上延迟 200ms;换成蒸馏版的 BERT 分类器,10M 参数,延迟 20ms,准确率反而更高。
《Attention Is All You Need》的贡献可以概括为:
2026 年的今天,Transformer 的统治地位依然稳固,但同时也有了潜在的挑战者——Mamba、RWKV 等状态空间模型正在试图在长序列任务上挑战 Transformer。但无论如何,《Attention Is All You Need》在深度学习史上的地位已经不可动摇,它是一篇将"简单即优雅"发挥到极致的经典之作。
此页面为 AI 知识体系 的一部分,内容持续更新中。