Transformer 到底是什么?——从零理解大模型的核心基石引言
如果你关注 AI,一定听过这些词:GPT-4、ChatGPT、BERT、Claude、LLaMA。这些模型名字各不相同,但它们的底层架构都指向同一个东西——Transformer。
2017 年,Google 团队在论文《Attention Is All You Need》中首次提出 Transformer。当时没人能预料到,这篇论文会彻底改变人工智能的走向。ChatGPT 能"听懂人话"、Midjourney 能"画图"、Sora 能"生成视频",底层都离不开 Transformer 的思想。
这篇文章会带你从零理解 Transformer 的每一个组件,并用 Python 代码亲手实现一个迷你版 Transformer。
一、Transformer 之前,世界是什么样?
1.1 RNN 的困境
在 Transformer 出现之前,处理文本主要靠 RNN(循环神经网络) 及其变体 LSTM、GRU。
Python
# RNN 的工作方式:一个词一个词地"啃"
"""
输入句子:"我 爱 中国"
RNN 处理流程:
第1步:读 "我" → 产生隐藏状态 h1
第2步:读 "爱" + h1 → 产生隐藏状态 h2
第3步:读 "中国" + h2 → 产生隐藏状态 h3
"""
RNN 的致命缺陷:
Python
# 问题1:无法并行计算
# 必须等第1个词处理完,才能处理第2个词
# 一个 1000 词的句子,要串行跑 1000 步
# 问题2:长距离依赖丢失
# 句子开头的信息,传到句子末尾时已经"衰减"得差不多了
sentence = "这只猫,它毛色纯白、眼睛湛蓝、性格温顺、喜欢晒太阳、爱吃三文鱼...(中间100个词)...非常可爱"
# RNN 读到"非常可爱"时,已经很难记住主语是"猫"了
1.2 Transformer 的革命性改进
Transformer 的核心思想就一句话:让每个词都能直接"看到"句子中所有其他词。
Plain Text
RNN 看句子: 我 → 爱 → 中国 (串行,像排队传递)
Transformer: 我 ↔ 爱 ↔ 中国 (并行,像开会讨论)
这带来了两个巨大优势:
- ✅ 并行计算:所有词同时处理,训练速度大幅提升
- ✅ 长距离依赖:无论词和词之间隔多远,都能直接建立联系
二、Transformer 的总体架构
Transformer 由 编码器(Encoder) 和 解码器(Decoder) 两部分组成:
Plain Text
┌─────────────────────┐
输入文本 │ Encoder 编码器 │
"我 爱 中国" ──→ │ (理解输入什么意思) │
└──────────┬──────────┘
│ 编码后的语义表示
↓
┌─────────────────────┐
│ Decoder 解码器 │ ──→ 输出文本
│ (生成输出) │ "I love China"
└─────────────────────┘
- GPT 系列只用了解码器(所以叫 GPT = Generative Pre-trained Transformer)
- BERT 只用了编码器(所以适合做"理解"类任务,如分类、问答)
下面我们重点拆解编码器,因为它是所有 Transformer 变体的核心。
三、编码器的核心组件:逐层拆解
3.1 第一层:输入嵌入(Input Embedding)
问题:计算机不认识"中国"这两个字,它只认识数字。怎么办?
方案:把每个词转换成一个固定长度的向量(一串数字,比如 512 个浮点数)。
Python
import torch
import torch.nn as nn
import math
# ============================================
# 1. 输入嵌入层:把词变成数字向量
# ============================================
class InputEmbedding(nn.Module):
"""
将每个词映射为一个稠密向量。
比如:"中国" → [0.12, -0.85, 0.33, ..., 0.67](512维)
"""
def __init__(self, vocab_size: int, d_model: int):
"""
vocab_size: 词汇表大小(比如 50000 个词)
d_model: 每个词对应的向量维度(论文中是 512)
"""
super().__init__()
# nn.Embedding 就是一个查找表:
# 输入:词的编号(比如 3721)
# 输出:这个词对应的 512 维向量
self.embedding = nn.Embedding(vocab_size, d_model)
self.d_model = d_model
def forward(self, x):
# x 的形状: (batch_size, seq_len)
# 比如: (32, 100) → 32个句子,每句100个词
# 输出形状: (batch_size, seq_len, d_model)
# 比如: (32, 100, 512) → 每个词都变成了512维向量
# 论文中把嵌入向量乘以 sqrt(d_model),为了让数值稳定
return self.embedding(x) * math.sqrt(self.d_model)
# 测试一下
vocab_size = 10000 # 假设词汇表有 10000 个词
d_model = 512 # 每个词表示成 512 维向量
embedding_layer = InputEmbedding(vocab_size, d_model)
# 模拟输入:2 个句子,每句 10 个词(每个词用数字编号表示)
sample_input = torch.randint(0, vocab_size, (2, 10))
embedded = embedding_layer(sample_input)
print(f"输入形状: {sample_input.shape}") # torch.Size([2, 10])
print(f"嵌入后形状: {embedded.shape}") # torch.Size([2, 10, 512])
print(f"解释:2个句子,每句10个词,每个词变成512维向量 ✅")
3.2 第二层:位置编码(Positional Encoding)
关键问题:Transformer 是并行处理所有词的,它不知道"我爱你"和"你爱我"有什么区别!
方案:给每个位置(第1个词、第2个词...)加上一个独特的"位置签名"。
Python
# ============================================
# 2. 位置编码:告诉模型每个词在句子的第几个位置
# ============================================
class PositionalEncoding(nn.Module):
"""
使用正弦和余弦函数生成位置编码。
每个位置都有一个独特的"位置签名"向量,
直接加到词向量上,让模型知道词的顺序。
"""
def __init__(self, d_model: int, max_len: int = 5000):
"""
d_model: 嵌入维度(512)
max_len: 支持的最大句子长度
"""
super().__init__()
# 创建一个 (max_len, d_model) 的矩阵,存放所有位置编码
pe = torch.zeros(max_len, d_model)
# position: 每个位置的编号 [0, 1, 2, ..., max_len-1]
position = torch.arange(0, max_len).unsqueeze(1).float()
# div_term: 用于控制正弦/余弦的频率
# 偶数维度用 sin,奇数维度用 cos
div_term = torch.exp(
torch.arange(0, d_model, 2).float()
* (-math.log(10000.0) / d_model)
)
# 偶数维度:sin
pe[:, 0::2] = torch.sin(position * div_term)
# 奇数维度:cos
pe[:, 1::2] = torch.cos(position * div_term)
# 增加 batch 维度,方便后续直接相加
pe = pe.unsqueeze(0) # (1, max_len, d_model)
# 注册为 buffer(不参与训练,但会保存到模型中)
self.register_buffer('pe', pe)
def forward(self, x):
"""
x 形状: (batch_size, seq_len, d_model)
把位置编码加到词向量上
"""
# x + pe 就是"含有位置信息的词向量"
# 比如:"我"在位置0的向量 = 原始"我"的向量 + 位置0的编码
x = x + self.pe[:, :x.size(1), :]
return x
# 测试位置编码
pos_encoder = PositionalEncoding(d_model=512)
sample = torch.zeros(2, 10, 512) # 2个句子,每句10个词
pos_encoded = pos_encoder(sample)
print(f"位置编码后形状: {pos_encoded.shape}") # torch.Size([2, 10, 512])
print(f"位置编码已加入,不同位置的词有了不同的'位置签名' ✅")
3.3 第三层:自注意力机制(Self-Attention)—— 核心中的核心
这才是 Transformer 的"灵魂"。
直观理解
读这句话:"它太可爱了,我忍不住想摸它"
人类读的时候,大脑会自动把"它"和前面的"猫"联系起来。自注意力机制就是让模型学会这种"关联"。
三个关键概念:Q、K、V
Python
# 把自注意力想象成"搜索引擎":
#
# Q(Query,查询):你想搜什么? —— "我正在处理哪个词?"
# K(Key,键): 被搜索的内容标签 —— "其他词的特征是什么?"
# V(Value,值): 被搜索的内容本身 —— "其他词的实际内容是什么?"
#
# 流程:
# Q 和每个 K 计算相似度 → 得到"注意力分数"
# 注意力分数 × V → 加权求和 → 得到"这个词应该关注哪些其他词"
代码实现
Python
# ============================================
# 3. 自注意力机制:Transformer 的灵魂
# ============================================
class SelfAttention(nn.Module):
"""
缩放点积注意力(Scaled Dot-Product Attention)
公式:Attention(Q, K, V) = softmax(Q·K^T / sqrt(d_k)) · V
"""
def __init__(self, d_model: int, n_heads: int):
"""
d_model: 模型维度(512)
n_heads: 多头注意力的头数(8)
"""
super().__init__()
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_heads # 每个头的维度 = 512 / 8 = 64
# 三个线性变换层:把输入分别映射成 Q、K、V
self.W_q = nn.Linear(d_model, d_model) # 生成 Query
self.W_k = nn.Linear(d_model, d_model) # 生成 Key
self.W_v = nn.Linear(d_model, d_model) # 生成 Value
# 最终的输出映射
self.W_o = nn.Linear(d_model, d_model)
def forward(self, x, mask=None):
"""
x 形状: (batch_size, seq_len, d_model)
返回: (batch_size, seq_len, d_model)
"""
batch_size, seq_len, _ = x.shape
# ===== 第1步:生成 Q、K、V =====
# 把输入 x 通过三个不同的线性变换,得到 Q、K、V
Q = self.W_q(x) # (batch, seq_len, d_model)
K = self.W_k(x) # (batch, seq_len, d_model)
V = self.W_v(x) # (batch, seq_len, d_model)
# ===== 第2步:拆成多头 =====
# 把 d_model 拆成 n_heads 个头,每个头独立计算注意力
# (batch, seq_len, d_model) → (batch, n_heads, seq_len, d_k)
Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
K = K.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
V = V.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
# ===== 第3步:计算注意力分数 =====
# Q 和 K 做点积,得到"每个词对其他词的关注度"
# Q @ K^T → (batch, n_heads, seq_len, seq_len)
attention_scores = torch.matmul(Q, K.transpose(-2, -1))
# 缩放!除以 sqrt(d_k) 防止点积结果太大,导致 softmax 梯度消失
attention_scores = attention_scores / math.sqrt(self.d_k)
# 如果有 mask(比如 padding 的位置),把对应位置设为 -∞
# 这样 softmax 之后这些位置的权重就是 0
if mask is not None:
attention_scores = attention_scores.masked_fill(
mask == 0, float('-inf')
)
# ===== 第4步:softmax 得到注意力权重 =====
# 每个词对各个词的关注度,加起来等于 1
attention_weights = torch.softmax(attention_scores, dim=-1)
# ===== 第5步:加权求和 =====
# 用注意力权重 × V,得到"包含上下文的词表示"
# (batch, n_heads, seq_len, d_k)
output = torch.matmul(attention_weights, V)
# ===== 第6步:合并多头 =====
# (batch, n_heads, seq_len, d_k) → (batch, seq_len, d_model)
output = output.transpose(1, 2).contiguous().view(
batch_size, seq_len, self.d_model
)
# ===== 第7步:最终线性映射 =====
output = self.W_o(output)
return output
# 测试自注意力
attention = SelfAttention(d_model=512, n_heads=8)
sample = torch.randn(2, 10, 512) # 2个句子,每句10个词,512维
output = attention(sample)
print(f"输入形状: {sample.shape}") # torch.Size([2, 10, 512])
print(f"输出形状: {output.shape}") # torch.Size([2, 10, 512])
print(f"每个词都"看"到了句子中所有其他词,并融合了上下文信息 ✅")
直观可视化:词与词之间学到了什么
Python
# 假设句子是:"我 爱 中国"
# 注意力矩阵可能是这样的(每个值表示"行"对"列"的关注度):
"""
我 爱 中国
我 0.30 0.20 0.50 ← "我" 最关注 "中国"("我"是谁?我是"中国人")
爱 0.25 0.15 0.60 ← "爱" 最关注 "中国"(爱什么?爱"中国")
中国 0.40 0.35 0.25 ← "中国" 最关注 "我"(什么跟"中国"相关?"我")
"""
3.4 多头注意力(Multi-Head Attention)
为什么要多头?
一个头可能只关注"语法关系",另一个头关注"语义关系",还有一个关注"位置关系"。 多个头 = 从多个角度同时理解句子。
Python
# 前面的 SelfAttention 类已经实现了多头注意力
# 关键代码就是这一行:
# Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
# 理解:把 512 维的向量切成 8 份,每份 64 维
# 每个头独立计算注意力,最后再把 8 个头的输出拼回 512 维
# 这就是论文标题的含义:
# "Attention Is All You Need" = 注意力机制就是全部你需要的东西
3.5 前馈网络(Feed-Forward Network)
注意力层让词之间互相"交流",前馈网络则让每个词"独立思考"。
Python
# ============================================
# 4. 前馈网络:每个词独立思考,增加模型的表达能力
# ============================================
class FeedForward(nn.Module):
"""
两层全连接网络 + ReLU 激活函数
公式:FFN(x) = ReLU(x·W1 + b1)·W2 + b2
先升维(d_model → d_ff,比如 512 → 2048),
再降维(d_ff → d_model),
中间用 ReLU 激活。
这个"先膨胀再收缩"的结构,让模型有更强的表达能力。
"""
def __init__(self, d_model: int, d_ff: int, dropout: float = 0.1):
"""
d_model: 模型维度(512)
d_ff: 前馈网络内部维度(2048,论文中设为 4 倍 d_model)
"""
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff) # 512 → 2048,升维
self.linear2 = nn.Linear(d_ff, d_model) # 2048 → 512,降维
self.dropout = nn.Dropout(dropout) # 防止过拟合
def forward(self, x):
# x: (batch, seq_len, d_model)
x = self.linear1(x) # 升维到 2048
x = torch.relu(x) # 非线性激活
x = self.dropout(x) # 随机丢弃一些神经元
x = self.linear2(x) # 降维回 512
return x
# 测试
ff = FeedForward(d_model=512, d_ff=2048)
sample = torch.randn(2, 10, 512)
output = ff(sample)
print(f"FFN 输入: {sample.shape} → 输出: {output.shape}")
print(f"512→2048→512,先扩展再压缩,增强了模型表达能力 ✅")
3.6 残差连接 + 层归一化(Add & Norm)
层层叠加,够深才够强——但越深越难训练。残差连接就是来解决这个问题的。
Python
# ============================================
# 5. 残差连接 + 层归一化:确保深层网络能稳定训练
# ============================================
class AddAndNorm(nn.Module):
"""
Add(残差连接):把输入和输出加起来
→ 就像"抄近道",让梯度能直接传回浅层,防止梯度消失
Norm(层归一化):把数值稳定在一个范围内
→ 让训练更稳定、更快收敛
"""
def __init__(self, d_model: int, dropout: float = 0.1):
super().__init__()
self.norm = nn.LayerNorm(d_model) # 层归一化
self.dropout = nn.Dropout(dropout)
def forward(self, x, sublayer_output):
"""
x: 原始输入(残差)
sublayer_output: 子层输出(注意力层或前馈网络层)
"""
# 残差连接:原始输入 + 子层输出,让信息绕过子层直达后面
# 然后做层归一化
return self.norm(x + self.dropout(sublayer_output))
3.7 组装:一个完整的 Encoder 层
Python
# ============================================
# 6. 组装一个 Encoder 层
# ============================================
class EncoderLayer(nn.Module):
"""
一个 Encoder 层 = 多头自注意力 + 前馈网络
每个子层后面都有残差连接和层归一化
结构:
x → [Self-Attention] → Add&Norm → [FeedForward] → Add&Norm → 输出
"""
def __init__(self, d_model: int, n_heads: int, d_ff: int, dropout: float = 0.1):
super().__init__()
self.attention = SelfAttention(d_model, n_heads) # 多头自注意力
self.feed_forward = FeedForward(d_model, d_ff, dropout) # 前馈网络
self.add_norm1 = AddAndNorm(d_model, dropout) # 注意力后的残差+归一化
self.add_norm2 = AddAndNorm(d_model, dropout) # 前馈后的残差+归一化
def forward(self, x, mask=None):
# 第1步:自注意力 → 残差连接 → 层归一化
attn_output = self.attention(x, mask)
x = self.add_norm1(x, attn_output)
# 第2步:前馈网络 → 残差连接 → 层归一化
ff_output = self.feed_forward(x)
x = self.add_norm2(x, ff_output)
return x
# ============================================
# 7. 组装完整的 Encoder(多个 Encoder 层堆叠)
# ============================================
class Encoder(nn.Module):
"""
完整的编码器 = 输入嵌入 + 位置编码 + N 个 Encoder 层
论文中 N=6,即堆叠 6 层
"""
def __init__(
self,
vocab_size: int, # 词汇表大小
d_model: int, # 模型维度(512)
n_layers: int, # Encoder 层数(6)
n_heads: int, # 注意力头数(8)
d_ff: int, # 前馈网络维度(2048)
max_len: int, # 最大句子长度
dropout: float = 0.1
):
super().__init__()
self.embedding = InputEmbedding(vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model, max_len)
# 堆叠 n_layers 个 Encoder 层
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.embedding(x)
# ② 位置编码
x = self.positional_encoding(x)
# ③ 随机丢弃(正则化)
x = self.dropout(x)
# ④ 逐层传递
for layer in self.layers:
x = layer(x, mask)
return x
# ============================================
# 8. 跑一个完整的 Encoder
# ============================================
def test_transformer_encoder():
"""测试迷你 Transformer Encoder"""
vocab_size = 10000 # 词汇表 10000 个词
d_model = 512 # 每个词用 512 维向量表示
n_layers = 6 # 6 层 Encoder(论文标准配置)
n_heads = 8 # 8 个注意力头
d_ff = 2048 # 前馈网络 2048 维
max_len = 100 # 最长 100 个词
encoder = Encoder(vocab_size, d_model, n_layers, n_heads, d_ff, max_len)
# 模拟输入:2 个句子,每句 20 个词
sample_input = torch.randint(0, vocab_size, (2, 20))
output = encoder(sample_input)
print(f"输入形状: {sample_input.shape}") # torch.Size([2, 20])
print(f"输出形状: {output.shape}") # torch.Size([2, 20, 512])
print()
print("Encoder 工作流程:")
print(" ① 词嵌入:每个词 → 512维向量")
print(" ② 位置编码:加入位置信息")
print(" ③ 6层Encoder:每层包含自注意力+前馈网络")
print(" ④ 输出:每个词都融合了整句话的上下文信息 ✅")
print()
# 计算参数量
total_params = sum(p.numel() for p in encoder.parameters())
print(f"总参数量: {total_params:,}")
print(f"(GPT-3 有 1750 亿参数,我们的迷你版只有 {total_params:,} 个)")
test_transformer_encoder()
四、一张图总结 Transformer 的数据流动
Plain Text
输入:"我 爱 中国"
│
├─→ ① 词嵌入:每个词 → 512维向量
│ "我" → [0.12, -0.85, 0.33, ...]
│ "爱" → [0.67, 0.21, -0.44, ...]
│ "中国" → [-0.33, 0.78, 0.15, ...]
│
├─→ ② 位置编码:加上位置签名
│ "我" 位置0 → 加上 sin/cos 编码
│ "爱" 位置1 → 加上 sin/cos 编码
│ "中国" 位置2 → 加上 sin/cos 编码
│
└─→ ③ 6层 Encoder 处理:
┌─────────────────────────────────────┐
│ 第1层:Self-Attention → FFN │
│ 第2层:Self-Attention → FFN │
│ ... │
│ 第6层:Self-Attention → FFN │
└─────────────────────────────────────┘
↓
输出:每个词都融合了整句话的上下文
"中国" 的向量里,包含了"我"和"爱"的信息
五、GPT 和 BERT 的区别
| 维度 | BERT | GPT |
|---|---|---|
| 用的部分 | 只用 Encoder | 只用 Decoder |
| 看信息的方向 | 双向(能看到前后所有词) | 单向(只能看到前面的词) |
| 适合任务 | 理解:分类、问答、实体识别 | 生成:写文章、对话、翻译 |
| 训练方式 | 完形填空(猜被遮住的词) | 预测下一个词 |
| 代表模型 | BERT、RoBERTa | GPT-3、GPT-4、ChatGPT |
六、为什么 Transformer 是"革命性"的?
Python
# 对比总结
"""
Transformer 之前(RNN/LSTM 时代):
❌ 串行处理:1000个词要跑1000步,太慢了
❌ 长距离遗忘:句子太长,开头的信息就丢了
❌ 难以扩展:层数深了训练不稳定
Transformer 之后:
✅ 并行处理:所有词同时计算,速度飞起
✅ 任意距离:第1个词和第1000个词直接建立联系
✅ 可无限堆叠:层数越多效果越好(GPT-3 有 96 层)
✅ 通用架构:能处理文本、图像、语音、视频、代码...
"""
结语
Transformer 的本质,就是让机器学会"读句子时关注哪些词"。这看似简单的想法,却引发了 AI 领域的革命。
从 2017 年的一篇论文,到 2023 年 ChatGPT 横空出世,再到今天 AI 能写代码、画图、做视频,这一切的起点都是——Attention Is All You Need。
理解 Transformer,就是拿到了理解现代 AI 的钥匙。希望这篇文章能帮你打开这扇门。
更多推荐

所有评论(0)