深入理解 Transformer 架构:从注意力机制到 GPT

2217 字
11 分钟
深入理解 Transformer 架构:从注意力机制到 GPT

1. 注意力机制(Attention Mechanism)#

1.1 什么是注意力机制?#

注意力机制的核心思想是:在处理序列数据时,模型应该”关注”与当前任务最相关的部分

想象你在阅读一段文字时,虽然你会看到所有词,但你真正”聚焦”的只有少数几个与理解当前句子最相关的词。注意力机制正是模拟了这种人眼的注意力分配方式。

1.2 缩放点积注意力(Scaled Dot-Product Attention)#

Transformer 使用的是缩放点积注意力机制。给定查询(Query)、键(Key)和值(Value)三个矩阵:

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

其中 dkd_k 是键向量的维度。为什么要除以 dk\sqrt{d_k}

原因:当 dkd_k 较大时,点积的值会变得很大,导致 softmax 函数进入饱和区域,梯度变得非常小。通过缩放,可以保持点积结果的方差稳定。

import torch
import torch.nn.functional as F
import math
def scaled_dot_product_attention(Q, K, V, mask=None):
"""
Q: (batch, seq_len, d_k)
K: (batch, seq_len, d_k)
V: (batch, seq_len, d_v)
"""
d_k = Q.size(-1)
# 计算点积
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
# 应用掩码(用于防止看到未来信息)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# softmax 归一化
attention_weights = F.softmax(scores, dim=-1)
# 加权求和
output = torch.matmul(attention_weights, V)
return output, attention_weights

1.3 注意力权重示例#

假设我们有句子 “The cat sat on the mat”,当模型处理 “cat” 这个词时,注意力权重可能如下分布:

词 Token注意力权重
The0.12
cat0.35
sat0.18
on0.08
the0.15
mat0.12

可以看到,模型在处理 “cat” 时,会给自身(0.35)和相邻的 “sat”(0.18)、“the”(0.15)较高的注意力权重,这符合语法和语义上的关联。

温度(Temperature)的影响

  • 温度越高 → 注意力分布越均匀(更”软”的注意力)
  • 温度越低 → 注意力分布越尖锐(更”硬”的注意力,接近 one-hot)

1.4 为什么需要 Query、Key、Value?#

这是一个非常巧妙的设计:

  • Query(查询):当前位置想要查找的信息
  • Key(键):每个位置的”索引”,用于匹配 Query
  • Value(值):每个位置的实际内容

类比搜索引擎:

  • Query 是你的搜索词
  • Key 是网页的标题/关键词
  • Value 是网页的实际内容

2. 多头注意力(Multi-Head Attention)#

2.1 为什么需要多头?#

单个注意力头只能学习一种类型的关联关系,而多头注意力让模型能够同时关注不同类型的信息

例如,在翻译任务中,一个头可能关注语法结构,另一个头关注语义关联,还有一个头关注词汇对应关系。

2.2 多头注意力的计算#

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

其中每个头的计算:

headi=Attention(QWiQ,KWiK,VWiV)\text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)
import torch
import torch.nn as nn
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
assert d_model % num_heads == 0
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 split_heads(self, x):
"""将最后一个维度分割成 num_heads 个头"""
batch_size, seq_len, _ = x.size()
x = x.view(batch_size, seq_len, self.num_heads, self.d_k)
return x.permute(0, 2, 1, 3) # (batch, heads, seq_len, d_k)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# 线性变换并分割成多个头
Q = self.split_heads(self.W_q(Q))
K = self.split_heads(self.W_k(K))
V = self.split_heads(self.W_v(V))
# 计算注意力
attn_output, _ = scaled_dot_product_attention(Q, K, V, mask)
# 合并多头
attn_output = attn_output.permute(0, 2, 1, 3).contiguous()
attn_output = attn_output.view(batch_size, -1, self.d_model)
# 最终线性变换
output = self.W_o(attn_output)
return output

3. 位置编码(Positional Encoding)#

3.1 为什么需要位置编码?#

Transformer 本身没有循环结构,无法直接获取序列中元素的位置信息。但词的顺序在语言中至关重要(“狗咬人” vs “人咬狗”)。

为了解决这个问题,Transformer 使用位置编码(Positional Encoding)来注入位置信息。

3.2 正弦和余弦位置编码#

论文中使用正弦和余弦函数来生成位置编码:

PE(pos,2i)=sin(pos100002i/dmodel)PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)PE(pos,2i+1)=cos(pos100002i/dmodel)PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)
import torch
import torch.nn as nn
import math
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_seq_len=5000):
super().__init__()
# 创建位置编码矩阵
pe = torch.zeros(max_seq_len, d_model)
# 位置向量
position = torch.arange(0, max_seq_len, dtype=torch.float).unsqueeze(1)
# 频率向量
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)
pe[:, 1::2] = torch.cos(position * div_term)
# 添加批次维度
pe = pe.unsqueeze(0) # (1, max_seq_len, d_model)
# 注册为不优化的缓冲区
self.register_buffer('pe', pe)
def forward(self, x):
"""
x: (batch_size, seq_len, d_model)
"""
# 将位置编码添加到输入
x = x + self.pe[:, :x.size(1), :]
return x

3.3 位置编码可视化#

3.4 位置编码的特性#

正弦/余弦位置编码具有以下优点:

  1. 不同位置的编码唯一:每个位置都有独特的编码
  2. 相对位置可推导PE(pos+k)PE(pos + k) 可以表示为 PE(pos)PE(pos) 的线性函数
  3. 支持任意长度的序列:可以泛化到训练时未见过的序列长度
进阶阅读

近年来,研究者提出了多种改进的位置编码方式:

  • 旋转位置编码(RoPE):LLaMA 采用,在注意力计算中融入位置信息
  • 相对位置编码:直接建模 token 之间的相对距离
  • ALiBi:一种不需要位置编码的方法,通过注意力分数的线性偏置实现

4. Transformer 整体架构#

4.1 编码器(Encoder)#

每个编码器层包含两个子层:

  1. 多头自注意力层:对输入序列进行自注意力计算
  2. 前馈神经网络(FFN):两层线性变换,中间有 ReLU 激活

每个子层周围都有残差连接层归一化

LayerNorm(x+Sublayer(x))\text{LayerNorm}(x + \text{Sublayer}(x))

4.2 解码器(Decoder)#

解码器在编码器的基础上增加了一个掩码多头注意力层,确保预测时只能看到当前词之前的词(不能”看到未来”)。

4.3 架构可视化#

4.3 编码器-解码器架构图#

┌─────────────────────────────────────────────────────────────────┐
│ 完整 Transformer 架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 输入嵌入 │ │
│ │ + 位置编码 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 编码器 (N 层) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ Multi-Head Self-Attention │ │ │
│ │ │ Add & Norm │ │ │
│ │ │ Feed Forward Network │ │ │
│ │ │ Add & Norm │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └──────────────────┬────────────────────────┘ │
│ │ │
│ │ 编码器输出 │
│ │ (作为 K, V) │
│ ┌───────────┴───────────┐ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 输出嵌入 │ │ 解码器输入 │ │
│ │ + 位置编码 │ │ (右移) │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 解码器 (N 层) │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ Masked Self-Attention │ │ │
│ │ │ Add & Norm │ │ │
│ │ │ Cross-Attention (使用编码器 K/V) │ │ │
│ │ │ Add & Norm │ │ │
│ │ │ Feed Forward Network │ │ │
│ │ │ Add & Norm │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └──────────────────┬────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 线性层 │ │
│ │ + Softmax │ │
│ └──────┬──────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 输出概率 │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘

图例说明

  • 编码器:处理输入序列,捕捉双向上下文信息
  • 解码器:自注意力确保只看到当前词之前的词,交叉注意力使用编码器输出的 K/V
  • 残差连接:每个子层周围都有 Skip Connection 和 LayerNorm

5.1 BERT:双向编码器#

BERT(Bidirectional Encoder Representations from Transformers)仅使用 Transformer 的编码器部分,通过掩码语言模型(MLM)任务实现双向上下文建模。

5.2 GPT:生成式预训练#

GPT 系列(GPT-2、GPT-3、GPT-4)仅使用 Transformer 的解码器部分,通过下一个词预测任务进行训练,展现了强大的文本生成能力。

5.3 T5:编码器-解码器统一#

T5 将所有 NLP 任务统一建模为 Text-to-Text 任务,使用完整的编码器-解码器结构。

5.4 现代大模型架构#

模型架构特点
GPT-4解码器超大规模,RLHF 微调
LLaMA解码器开源,高效
ChatGLM解码器中文优化,对齐微调
Mistral解码器Sparse MoE,高效
Claude解码器Constitutional AI

6. 核心公式总结#

  1. 缩放点积注意力
Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
  1. 多头注意力
MultiHead(Q,K,V)=Concat(head1,,headh)WO\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h)W^O
  1. 层归一化 + 残差连接
Output=LayerNorm(x+Sublayer(x))\text{Output} = \text{LayerNorm}(x + \text{Sublayer}(x))

7. 总结#

Transformer 的核心创新在于:

  1. 完全基于注意力机制:摒弃了 RNN 的顺序计算,实现了真正的并行化
  2. 多头注意力:允许模型关注不同类型的关联关系
  3. 位置编码:解决了序列顺序建模的问题
  4. 可扩展性:可以通过增加层数和维度来提升模型容量

这些设计使得 Transformer 能够高效处理长距离依赖问题,并为现代大语言模型的发展奠定了基础。

参考资料#

  1. Vaswani, A., et al. (2017). “Attention Is All You Need.” NeurIPS.
  2. Devlin, J., et al. (2018). “BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.” NAACL.
  3. Radford, A., et al. (2019). “Language Models are Unsupervised Multitask Learners.” OpenAI Technical Report.
  4. Touvron, H., et al. (2023). “LLaMA: Open and Efficient Foundation Language Models.” Meta AI.

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

深入理解 Transformer 架构:从注意力机制到 GPT
https://aiattnstudio.link/posts/transformer-architecture/
作者
Federico
发布于
2026-07-14
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author

Federico

AI Research Lab

Hello, I'm Federico.

关于实验室 / About
公告

欢迎来到Federico的个人博客

分类
标签
站点统计
57文章
7分类
404标签