如果你关注 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 TextRNN 看句子 我 → 爱 → 中国 串行像排队传递 Transformer 我 ↔ 爱 ↔ 中国 并行像开会讨论这带来了两个巨大优势✅并行计算所有词同时处理训练速度大幅提升✅长距离依赖无论词和词之间隔多远都能直接建立联系二、Transformer 的总体架构Transformer 由编码器Encoder和解码器Decoder两部分组成Plain Text┌─────────────────────┐ 输入文本 │ Encoder 编码器 │ 我 爱 中国 ──→ │ (理解输入什么意思) │ └──────────┬──────────┘ │ 编码后的语义表示 ↓ ┌─────────────────────┐ │ Decoder 解码器 │ ──→ 输出文本 │ (生成输出) │ I love China └─────────────────────┘GPT 系列只用了解码器所以叫 GPT Generative Pre-trained TransformerBERT 只用了编码器所以适合做理解类任务如分类、问答下面我们重点拆解编码器因为它是所有 Transformer 变体的核心。三、编码器的核心组件逐层拆解3.1 第一层输入嵌入Input Embedding问题计算机不认识中国这两个字它只认识数字。怎么办方案把每个词转换成一个固定长度的向量一串数字比如 512 个浮点数。Pythonimport 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_model512) 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、VPython# 把自注意力想象成搜索引擎 # # QQuery查询你想搜什么 —— 我正在处理哪个词 # KKey键 被搜索的内容标签 —— 其他词的特征是什么 # VValue值 被搜索的内容本身 —— 其他词的实际内容是什么 # # 流程 # 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, maskNone): 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_model512, n_heads8) 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_model512, d_ff2048) sample torch.randn(2, 10, 512) output ff(sample) print(fFFN 输入: {sample.shape} → 输出: {output.shape}) print(f512→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] → AddNorm → [FeedForward] → AddNorm → 输出 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, maskNone): # 第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 层 论文中 N6即堆叠 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, maskNone): # ① 词嵌入 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(fGPT-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 的区别维度BERTGPT用的部分只用 Encoder只用 Decoder看信息的方向双向能看到前后所有词单向只能看到前面的词适合任务理解分类、问答、实体识别生成写文章、对话、翻译训练方式完形填空猜被遮住的词预测下一个词代表模型BERT、RoBERTaGPT-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 的钥匙。希望这篇文章能帮你打开这扇门。