1 Transform基础
Transformer 是谷歌 2017 年提出、完全基于自注意力机制(Self‑Attention)的深度学习基础架构,抛弃了 RNN/LSTM 的循环串行结构,支持输入序列全局并行计算,能够直接捕捉文本、图像等数据里长距离元素之间的依赖关系;依靠多头注意力、位置编码、编码器‑解码器结构,既可以理解上下文语义,也能生成输出,现在几乎所有大语言模型(GLM、Qwen、GPT 系列)、多模态模型都是以 Transformer 为底座迭代而来。
Transformer的核心原理,先把 Transformer 里那些吓人的公式全部扔掉,只记住一句话:
Transformer 的核心就是:一句话里的每个词,都去看看其他词,判断“谁跟我最相关”,然后把相关信息拿过来。
这就是Attention(注意力)。
1. 先看一句话
比如:
小明把苹果给了小红,因为她饿了。
人看到“她”,马上会想:
她 = 谁? 小明? ❌ 苹果? ❌ 小红? ✅Transformer 干的事情本质上也差不多。
处理“她”这个词的时候,它会看看前面的所有词:
小明 0.1 苹果 0.0 小红 0.8 ← 最相关 因为 0.1于是:
她 │ 看其他所有词 │ ┌────────┼────────┐ ↓ ↓ ↓ 小明 苹果 小红 0.1 0.0 0.8 ↑ 重点看这就是Self-Attention。
2. 但是电脑不认识“苹果”
电脑不能直接处理:
苹果所以第一步,要把文字变成数字。
例如:
小明 → [0.2, 0.7, 0.1, ...] 苹果 → [0.8, 0.1, 0.3, ...] 小红 → [0.3, 0.6, 0.2, ...]这个数字向量叫:
Embedding
你可以粗暴理解成:
把一个词/token变成一串数字。
实际上现代 LLM 通常先做 Tokenization:
"Transformer很好用" ↓ Tokenizer ["Trans", "former", "很", "好", "用"] ↓ Embedding 一堆数字向量所以更准确地说,Transformer处理的是Token,不一定是完整的“词”。
3. 最重要的 Q、K、V 到底是什么?
这是 Transformer 最容易把人搞晕的地方。
其实可以用搜索来理解。
假设当前处理:
她
Transformer 给每个 token 生成三个东西:
Q = Query K = Key V = Value可以把它们理解成:
Q:我想找什么? K:我是什么/我能被怎么匹配? V:我真正携带的信息是什么?比如:
“她” Q: “我要找她指的是哪个人?” │ ▼ 拿这个 Q 去匹配所有人的 K │ ┌────┼────┐ ↓ ↓ ↓ 小明 苹果 小红 K K K ↑ 最匹配匹配出来:
Q(她) × K(小明) → 0.1 Q(她) × K(苹果) → 0.0 Q(她) × K(小红) → 0.8于是 Transformer 说:
小红最重要。
然后真正拿的是V:
小明的 V × 0.1 + 苹果的 V × 0.0 + 小红的 V × 0.8最后得到一个新的:
“她”的表示里面已经混入了大量“小红”的信息。
4. 所以 Attention 其实就干三件事
把所有数学细节先隐藏以后:
① 我想找什么? ↓ Q ② 谁跟我最匹配? ↓ Q × K ③ 把重要的信息拿过来 ↓ 权重 × V就这么回事。
数学上那个著名公式:
Attention(Q,K,V) = softmax(QKᵀ / √d) V现在其实也没那么可怕了。
逐块看:
QKᵀ ↓ 看看谁跟谁相关 softmax(...) ↓ 变成权重 例如: 小明 10% 苹果 2% 小红 88% × V ↓ 按照权重取信息所以你甚至可以把整个公式翻译成人话:
“算相关性 → 变成百分比 → 按百分比拿信息。”
5. Multi-Head Attention 又是什么?
这名字也很唬人。
其实就是:
不要只用一种角度判断关系,同时派几个人去看。
例如:
小明把苹果给了小红,因为她饿了。
Head 1 可能关注:
她 → 小红 人物关系Head 2:
给 → 苹果 动作和物体Head 3:
饿 → 她 状态关系Head 4:
小明 → 给 → 小红 动作关系所以:
一句话 │ ┌─────────┼─────────┐ ↓ ↓ ↓ Head1 Head2 Head3 人物 动作 语义 │ │ │ └─────────┼─────────┘ ↓ 合起来这就是:
Multi-Head Attention = 多组 Attention 并行观察不同关系。
6. FFN 又是什么?
Attention 做完之后,还有:
FFN / Feed Forward Network
可以先粗暴理解:
Attention ↓ 从别人那里收集信息 FFN ↓ 自己再加工一下这些信息所以一个 Transformer Block 最核心的部分其实就是:
输入 │ ▼ Attention │ │ “看看别人” ▼ FFN │ │ “自己加工” ▼ 输出实际还有两个非常重要的东西:
Residual Connection LayerNorm所以更完整一点:
┌──────────────┐ │ │ ▼ │ Multi-Head Attention │ │ │ + ◀────────────┘ │ LayerNorm │ ▼ FFN │ + │ LayerNorm │ ▼ OutputResidual 可以理解成:
别把原来的信息扔掉,把新结果加回原来的信息。
7. 一个 Transformer 为什么那么强?
因为不是只做一次。
而是:
Token ↓ Embedding ↓ ┌─────────────────┐ │ Transformer │ │ Attention + FFN │ └─────────────────┘ ↓ ┌─────────────────┐ │ Transformer │ │ Attention + FFN │ └─────────────────┘ ↓ ┌─────────────────┐ │ Transformer │ │ Attention + FFN │ └─────────────────┘ ↓ ... 几十/上百层第一层可能学到:
词和词的简单关系往后逐渐形成更复杂的表示:
语法 ↓ 指代 ↓ 语义 ↓ 上下文 ↓ 复杂关系当然实际神经网络内部并没有这么整齐地“一层负责一种能力”,这只是方便理解。
8. 那 ChatGPT 为什么能一个字一个字往外蹦?
这又是另外一个关键点。
GPT 的任务其实非常朴素:
根据前面的 Token,预测下一个 Token。
比如:
中国的首都是模型计算:
北京 95% 上海 2% 南京 1% 深圳 0.2% ...选择:
北京现在输入变成:
中国的首都是北京继续预测:
。 80% 市 10% , 5% ...于是:
输入 ↓ Transformer ↓ 预测下一个 Token ↓ 加入输入 ↓ Transformer ↓ 再预测一个 ↓ ...所以你看到的回答其实是:
我 ↓ 我认为 ↓ 我认为这个 ↓ 我认为这个问题 ↓ 我认为这个问题可以 ↓ ……一个 Token 一个 Token 生成。
9. 那它怎么知道不能偷看后面的答案?
GPT 使用一个东西叫:
Causal Mask
例如:
我 今天 去 吃 饭 “我” 只能看:我 “今天” 可以看:我 今天 “去” 可以看:我 今天 去 “吃” 可以看:我 今天 去 吃不能:
“今天”偷偷看到后面的“吃饭”所以 Attention 矩阵相当于被遮住一半:
我 今天 去 吃 饭 我 ✓ X X X X 今天 ✓ ✓ X X X 去 ✓ ✓ ✓ X X 吃 ✓ ✓ ✓ ✓ X 饭 ✓ ✓ ✓ ✓ ✓这就是 GPT 里的Masked/Causal Self-Attention。
10. 最后把 Transformer 压缩成一张图
你只要记住这个:
文字 │ ▼ Tokenizer │ ▼ Embedding “文字变成数字” │ ▼ ┌────────────────┐ │ Attention │ │ │ │ 谁跟谁有关系? │ │ │ │ Q → 我要找谁 │ │ K → 我是谁 │ │ V → 我的信息 │ └───────┬────────┘ │ ▼ FFN “加工信息” │ ▼ 重复很多很多层 │ ▼ 得到上下文表示 │ ▼ 预测下一个 Token所以,如果你只准备记4 句话:
1. Embedding:把 Token 变成数字。
2. Attention:每个 Token 看其他 Token,找跟自己最相关的信息。
3. Q/K/V:Q 去匹配 K,然后按照匹配程度拿 V。
4. GPT:重复很多层 Transformer,然后不断预测下一个 Token。
掌握到这里,你其实已经理解了 Transformer 最核心的骨架。
下一步最值得搞懂的不是更多名词,而是拿一句只有 3 个词的话,手算一遍 Q、K、V → Attention 分数 → Softmax → 最终输出。只要亲手算一次,Transformer 基本就从“玄学”变成普通矩阵运算了。
参考:
初学者怎么入门大语言模型(LLM)? - 知乎
2 手算例子
我们故意把数字设计得特别简单,不追求真实模型的维度,只追求把原理算明白。
假设一句话只有 3 个 token:
我 爱 苹果
并且每个 token 只用2 个数字表示。
1. 先得到 Embedding
假设 Embedding 是:
我 = [1, 0] 爱 = [1, 1] 苹果 = [0, 1]写成矩阵:
真实模型可能每个 token 有几千维,我们这里只用 2 维方便手算。
2. 从 X 生成 Q、K、V
Transformer 里面有三个训练出来的矩阵:
WQ,WK,WV
然后
为了让计算最简单,我们假设:
也就是单位矩阵。
所以:
Q K V 我 [1,0] [1,0] [1,0] 爱 [1,1] [1,1] [1,1] 苹果 [0,1] [0,1] [0,1]当然真实 Transformer 中三个 W 通常完全不同。
3. 现在只看“爱”这个 token
我们问:
“爱”应该关注谁?
它自己的 Query:
Q爱=[1,1]
然后拿这个 Q 去和所有 token 的 K 做点积。
和“我”比较
[1,1]⋅[1,0]
就是:
1×1+1×0=1
所以:
爱 → 我 = 1和“爱”自己比较
[1,1]⋅[1,1]
得到:
1+1=2
所以:
爱 → 爱 = 2和“苹果”比较
[1,1]⋅[0,1]
得到:
0+1=1
所以:
爱 → 苹果 = 1Attention Score 就出来了:
我 爱 苹果 “爱”的分数 1 2 1也就是:
Q 负责“查询”,K 负责“被匹配”。
4. 除以 √d
标准 Attention 还要:
这里:
d = 2所以:
2≈1.414
刚才:
[1, 2, 1]除以 1.414:
[0.707, 1.414, 0.707]为什么要除这个?
现在先简单理解:
防止维度很大的时候点积数字太大。
5. Softmax:把分数变成百分比
现在:
我 0.707 爱 1.414 苹果 0.707经过 Softmax。
Softmax 做的事情可以先粗暴理解为:
把这些分数变成总和为 1 的权重。
实际计算:
e0.707≈2.03
e1.414≈4.11
e0.707≈2.03
总和:
2.03+4.11+2.03=8.17
于是:
我 2.03 / 8.17 ≈ 0.248 爱 4.11 / 8.17 ≈ 0.503 苹果 2.03 / 8.17 ≈ 0.248也就是:
“爱”的注意力 我 █████ 24.8% 爱 ██████████ 50.3% 苹果 █████ 24.8%到这里,Attention 最重要的东西已经出来了:
“爱”认为每个 token 对自己有多重要。
6. 最后用这些权重取 V
还记得三个东西:
Q → 我要找什么 K → 用来匹配 V → 真正的信息刚才只是用 Q 和 K 算出了:
我 24.8% 爱 50.3% 苹果 24.8%现在真正拿信息。
三个 V:
V我 = [1,0] V爱 = [1,1] V苹果 = [0,1]按照刚才的百分比加权:
Output爱=0.248V我+0.503V爱+0.248V苹果
代进去:
=0.248[1,0]+0.503[1,1]+0.248[0,1]
分别算:
我: 0.248 × [1,0] = [0.248, 0] 爱: 0.503 × [1,1] = [0.503, 0.503] 苹果: 0.248 × [0,1] = [0, 0.248]加起来:
[0.248,0]+[0.503,0.503]+[0,0.248]
得到:
[0.751, 0.751]
这就是“爱”经过 Self-Attention 之后的新向量。
7. 最关键的地方来了
Attention 之前:
“爱” [1, 1]它只是“爱”自己的信息。
Attention 之后:
“爱” [0.751, 0.751]这个新向量实际上是:
24.8% “我”的信息 + 50.3% “爱”的信息 + 24.8% “苹果”的信息也就是说:
Attention 让一个 token 的表示,融合了上下文中其他 token 的信息。
这才是 Transformer 真正厉害的地方。
8. 其他两个 token 也做同样的事
刚才只算:
爱 → 看 我/爱/苹果实际上:
我 ↓ 也看 我/爱/苹果 爱 ↓ 也看 我/爱/苹果 苹果 ↓ 也看 我/爱/苹果所以整体实际上是:
我们这里:
那么:
这张矩阵特别重要:
被关注的 K 我 爱 苹果 ┌────────────── Q 我 │ 1 1 0 爱 │ 1 2 1 ← 刚才算的 苹果 │ 0 1 1每一行就是一个 token 在看所有其他 token。
9. 现在那个著名公式应该能看懂了
Transformer 最核心的公式:
Attention(Q,K,V)=softmax(dkQKT)V
现在逐个翻译:
QKᵀ ↓ “谁和谁相关?”÷ √d ↓ “把数值控制一下”softmax ↓ “变成注意力百分比”× V ↓ “按照百分比把信息拿过来”所以整条链其实就是:
我 爱 苹果 ↓ Embedding ↓ X ↓ ┌──┼──┐ ↓ ↓ ↓ Q K V │ │ └─×┘ ↓ 相关性分数 ↓ Softmax ↓ 注意力权重 ↓ × V ↓ 融合上下文后的新向量如果你只记一句:
Q 和 K 决定“看谁、看多少”,V 决定“真正拿什么信息”。
这句话搞懂,Self-Attention 的核心就基本搞懂了。
3 程序实现例子
程序例子
# -*- coding: utf-8 -*- import sys # 解决 Windows 终端中文乱码问题(强制 UTF-8 输出) if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") """ 最简单的 Transformer 模型示例(学习用) ======================================== 本文件包含两个版本: 1. 手写版:从零实现一个极简 Transformer(便于理解原理) 2. 现成版:使用 PyTorch 自带的 nn.Transformer(实际工程中用这个) 运行方式: pip install torch python transformer_simple.py """ import math import torch import torch.nn as nn import torch.nn.functional as F # ============================================================ # 第一部分:手写一个极简 Transformer(理解原理用) # ============================================================ class SelfAttention(nn.Module): """单头自注意力机制(Scaled Dot-Product Attention) 核心公式: Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) * V Q: Query(查询) - "我要找什么" K: Key(键) - "我是什么" V: Value(值) - "我提供什么内容" """ def __init__(self, d_model): super().__init__() self.d_model = d_model # 三个线性层,把输入分别变成 Q、K、V 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) def forward(self, x): # x 形状: (batch, seq_len, d_model) Q = self.W_q(x) K = self.W_k(x) V = self.W_v(x) # 1. 计算 Q 和 K 的点积,得到注意力分数 # (batch, seq_len, d_model) @ (batch, d_model, seq_len) # -> (batch, seq_len, seq_len) scores = torch.matmul(Q, K.transpose(-2, -1)) # 2. 除以 sqrt(d_k) 防止数值过大(这就是 "Scaled" 的含义) scores = scores / math.sqrt(self.d_model) # 3. softmax 归一化成概率(每行加起来等于 1) attn_weights = F.softmax(scores, dim=-1) # 4. 用注意力权重加权求和 V # (batch, seq_len, seq_len) @ (batch, seq_len, d_model) # -> (batch, seq_len, d_model) output = torch.matmul(attn_weights, V) return output class FeedForward(nn.Module): """前馈网络:每个位置独立做两次线性变换 + ReLU 作用:给模型增加非线性表达能力 """ def __init__(self, d_model, d_ff): super().__init__() self.fc1 = nn.Linear(d_model, d_ff) self.fc2 = nn.Linear(d_ff, d_model) def forward(self, x): return self.fc2(F.relu(self.fc1(x))) class TransformerBlock(nn.Module): """一个 Transformer 编码器块 = 自注意力 + 前馈网络 + 残差连接 + LayerNorm 结构: x -> [LayerNorm -> SelfAttention] -> + x (残差) -> [LayerNorm -> FeedForward] -> + x (残差) """ def __init__(self, d_model, d_ff): super().__init__() self.attention = SelfAttention(d_model) self.ffn = FeedForward(d_model, d_ff) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) def forward(self, x): # 残差连接:把输入 x 加回去,防止梯度消失 x = x + self.attention(self.norm1(x)) x = x + self.ffn(self.norm2(x)) return x class SimpleTransformer(nn.Module): """极简 Transformer 编码器(用于分类任务) 流程:输入 -> 词嵌入 + 位置编码 -> N 个 TransformerBlock -> 池化 -> 分类 """ def __init__(self, vocab_size, d_model=64, d_ff=128, num_layers=2, num_classes=2): super().__init__() self.d_model = d_model # 词嵌入:把单词 ID 变成向量 self.embedding = nn.Embedding(vocab_size, d_model) # 堆叠 N 个 Transformer 块 self.blocks = nn.ModuleList([ TransformerBlock(d_model, d_ff) for _ in range(num_layers) ]) # 分类头 self.classifier = nn.Linear(d_model, num_classes) def forward(self, x): # x 形状: (batch, seq_len),内容是单词 ID batch, seq_len = x.shape # 1. 词嵌入 x = self.embedding(x) # (batch, seq_len, d_model) # 2. 位置编码(Transformer 没有顺序概念,必须手动加位置信息) # 用正弦/余弦函数生成位置编码,这是原论文的做法 position = torch.arange(seq_len, device=x.device).unsqueeze(1) div_term = torch.exp(torch.arange(0, self.d_model, 2, device=x.device) * (-math.log(10000.0) / self.d_model)) pe = torch.zeros(seq_len, self.d_model, device=x.device) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) x = x + pe.unsqueeze(0) # 广播到 batch 维度 # 3. 经过所有 Transformer 块 for block in self.blocks: x = block(x) # 4. 取所有位置的平均作为整个句子的表示(简单池化) x = x.mean(dim=1) # (batch, d_model) # 5. 分类 return self.classifier(x) # ============================================================ # 第二部分:使用 PyTorch 自带的 nn.Transformer(工程中用这个) # ============================================================ class PyTorchTransformer(nn.Module): """用 nn.Transformer 实现一个完整的编码器-解码器 Transformer 适合做翻译等"序列到序列"任务。 """ def __init__(self, src_vocab, tgt_vocab, d_model=64, nhead=4, num_encoder_layers=2, num_decoder_layers=2): super().__init__() self.src_embedding = nn.Embedding(src_vocab, d_model) self.tgt_embedding = nn.Embedding(tgt_vocab, d_model) # PyTorch 自带的 Transformer(内部已包含位置编码、多头注意力、残差等) self.transformer = nn.Transformer( d_model=d_model, nhead=nhead, # 多头注意力的头数 num_encoder_layers=num_encoder_layers, num_decoder_layers=num_decoder_layers, dim_feedforward=128, batch_first=True, # 输入形状用 (batch, seq, dim) ) self.fc_out = nn.Linear(d_model, tgt_vocab) def forward(self, src, tgt): # src: 源语言句子 (batch, src_len) # tgt: 目标语言句子 (batch, tgt_len) src_emb = self.src_embedding(src) tgt_emb = self.tgt_embedding(tgt) # 注意:nn.Transformer 内部自带位置编码,不需要手动加 output = self.transformer(src_emb, tgt_emb) return self.fc_out(output) # ============================================================ # 第三部分:演示如何使用 # ============================================================ def demo_handwritten(): """演示手写版 Transformer 的完整训练流程""" print("=" * 60) print("演示 1:手写版 Transformer(文本分类)") print("=" * 60) torch.manual_seed(42) # 造一点假数据:10 个单词的词汇表,句子长度 5 # 规则:句子中单词 ID 之和为偶数 -> 类别 0,奇数 -> 类别 1 vocab_size = 10 seq_len = 5 num_samples = 2000 X = torch.randint(0, vocab_size, (num_samples, seq_len)) y = (X.sum(dim=1) % 2).long() # 标签 model = SimpleTransformer(vocab_size=vocab_size, num_classes=2) optimizer = torch.optim.Adam(model.parameters(), lr=0.0003) loss_fn = nn.CrossEntropyLoss() # 训练 300 轮(学习率调低,收敛更稳) for epoch in range(300): optimizer.zero_grad() logits = model(X) loss = loss_fn(logits, y) loss.backward() optimizer.step() if (epoch + 1) % 10 == 0: acc = (logits.argmax(dim=1) == y).float().mean().item() print(f"Epoch {epoch+1:3d} | Loss: {loss.item():.4f} | Acc: {acc:.2%}") # 测试几个句子 test_cases = [ ([1, 2, 3, 4, 5], 1), # 和为 15(奇数)-> 类别 1 ([2, 2, 2, 2, 2], 0), # 和为 10(偶数)-> 类别 0 ([1, 1, 1, 1, 1], 1), # 和为 5(奇数)-> 类别 1 ] for sentence, expected in test_cases: test_x = torch.tensor([sentence]) pred = model(test_x).argmax(dim=1).item() ok = "✓" if pred == expected else "✗" print(f"测试句子 {sentence} 预测类别: {pred}(期望 {expected}){ok}") def demo_pytorch(): """演示 PyTorch 自带 Transformer 的用法""" print() print("=" * 60) print("演示 2:PyTorch 自带 nn.Transformer(机器翻译)") print("=" * 60) torch.manual_seed(42) # 造假数据:模拟"翻译"任务 # 源语言:随机单词序列;目标语言:源序列反转(当作"翻译结果") src_vocab = 20 tgt_vocab = 20 src_len, tgt_len = 6, 6 batch_size = 32 model = PyTorchTransformer(src_vocab=src_vocab, tgt_vocab=tgt_vocab) optimizer = torch.optim.Adam(model.parameters(), lr=0.001) loss_fn = nn.CrossEntropyLoss(ignore_index=0) # 0 当作 padding for epoch in range(30): # 随机生成一批数据 src = torch.randint(1, src_vocab, (batch_size, src_len)) tgt_in = torch.randint(1, tgt_vocab, (batch_size, tgt_len)) # 解码器输入 tgt_out = torch.flip(tgt_in, dims=[1]) # 目标输出 optimizer.zero_grad() logits = model(src, tgt_in) # (batch, tgt_len, tgt_vocab) loss = loss_fn(logits.reshape(-1, tgt_vocab), tgt_out.reshape(-1)) loss.backward() optimizer.step() if (epoch + 1) % 10 == 0: print(f"Epoch {epoch+1:3d} | Loss: {loss.item():.4f}") if __name__ == "__main__": demo_handwritten() demo_pytorch() print() print("完成!建议阅读顺序:") print(" 1. SelfAttention 类(注意力机制核心)") print(" 2. TransformerBlock 类(残差 + LayerNorm)") print(" 3. SimpleTransformer 类(整体流程 + 位置编码)") print(" 4. PyTorchTransformer 类(工程实践)")运行结果
============================================================ 演示 1:手写版 Transformer(文本分类) ============================================================ Epoch 10 | Loss: 0.6908 | Acc: 52.20% Epoch 20 | Loss: 0.6892 | Acc: 54.10% Epoch 30 | Loss: 0.6863 | Acc: 54.30% Epoch 40 | Loss: 0.6839 | Acc: 55.70% Epoch 50 | Loss: 0.6809 | Acc: 58.15% Epoch 60 | Loss: 0.6774 | Acc: 58.10% Epoch 70 | Loss: 0.6730 | Acc: 58.50% Epoch 80 | Loss: 0.6678 | Acc: 58.80% Epoch 90 | Loss: 0.6620 | Acc: 59.05% Epoch 100 | Loss: 0.6554 | Acc: 59.45% Epoch 110 | Loss: 0.6465 | Acc: 60.50% Epoch 120 | Loss: 0.6349 | Acc: 63.15% Epoch 130 | Loss: 0.6218 | Acc: 64.40% Epoch 140 | Loss: 0.6065 | Acc: 65.75% Epoch 150 | Loss: 0.5876 | Acc: 67.95% Epoch 160 | Loss: 0.5651 | Acc: 70.40% Epoch 170 | Loss: 0.5435 | Acc: 72.05% Epoch 180 | Loss: 0.5197 | Acc: 74.25% Epoch 190 | Loss: 0.4919 | Acc: 76.00% Epoch 200 | Loss: 0.4582 | Acc: 79.75% Epoch 210 | Loss: 0.4247 | Acc: 81.15% Epoch 220 | Loss: 0.3856 | Acc: 83.45% Epoch 230 | Loss: 0.3472 | Acc: 85.15% Epoch 240 | Loss: 0.3016 | Acc: 87.85% Epoch 250 | Loss: 0.2520 | Acc: 90.10% Epoch 260 | Loss: 0.2052 | Acc: 92.15% Epoch 270 | Loss: 0.1616 | Acc: 94.20% Epoch 280 | Loss: 0.1265 | Acc: 96.10% Epoch 290 | Loss: 0.0974 | Acc: 97.30% Epoch 300 | Loss: 0.0733 | Acc: 98.20% 测试句子 [1, 2, 3, 4, 5] 预测类别: 1(期望 1)✓ 测试句子 [2, 2, 2, 2, 2] 预测类别: 1(期望 0)✗ 测试句子 [1, 1, 1, 1, 1] 预测类别: 1(期望 1)✓ ============================================================ 演示 2:PyTorch 自带 nn.Transformer(机器翻译) ============================================================ Epoch 10 | Loss: 2.8657 Epoch 20 | Loss: 2.8201 Epoch 30 | Loss: 2.6242 完成!建议阅读顺序: 1. SelfAttention 类(注意力机制核心) 2. TransformerBlock 类(残差 + LayerNorm) 3. SimpleTransformer 类(整体流程 + 位置编码) 4. PyTorchTransformer 类(工程实践)说明
程序整体结构
transformer_simple.py是一个学习用的极简 Transformer 示例,包含三个部分:
部分 内容 目的 第一部分 手写版 Transformer(4 个类) 理解原理 第二部分 PyTorch 自带 nn.Transformer工程实践 第三部分 两个演示函数 看怎么用 第一部分:手写版(理解原理)
1.
SelfAttention—— 注意力机制核心这是整个 Transformer 的灵魂,对应原论文公式:
Attention(Q, K, V) = softmax(QKᵀ / √d_k) · VQ/K/V 的比喻(代码注释里也写了):
- Q(Query 查询):"我要找什么"
- K(Key 键):"我是什么"
- V(Value 值):"我提供什么内容"
forward里的 4 步(第 47-68 行):
- 三个线性层把输入分别变成 Q、K、V
Q @ Kᵀ计算两两之间的相似度分数 → 形状(batch, seq_len, seq_len)- 除以
√d_k防止数值过大(这就是 "Scaled" 的含义)softmax归一化成概率,再乘 V 加权求和关键理解:注意力就是"每个词看其他所有词一眼,决定自己该关注谁"。比如"苹果"这个词,在"我喜欢吃苹果"里应该多关注"吃"。
2.
FeedForward—— 前馈网络fc1(升维) → ReLU → fc2(降维回原维度)每个位置独立做两次线性变换,给模型增加非线性表达能力。注意力负责"词与词之间交流",前馈网络负责"每个词自己思考"。
3.
TransformerBlock—— 一个完整的编码器块结构(第 99-103 行):
x → [LayerNorm → SelfAttention] → + x(残差连接) → [LayerNorm → FeedForward] → + x(残差连接)两个关键设计:
- 残差连接(
x + ...):把输入加回去,防止深层网络梯度消失- LayerNorm:在注意力/前馈之前做归一化,让训练更稳定
4.
SimpleTransformer—— 整体流程
forward的 5 步(第 123-148 行):输入单词ID → 词嵌入(Embedding) → + 位置编码 → N个TransformerBlock → 池化 → 分类位置编码(第 130-138 行)是重点:Transformer 没有 RNN 那种天然的顺序概念,所以必须手动把位置信息加进去。原论文用正弦/余弦函数生成,偶数维度用
sin,奇数维度用cos,这样不同位置有唯一编码,且能表达相对位置关系。第二部分:PyTorch 自带版(工程实践)
PyTorchTransformer直接调用nn.Transformer,一行代码就包含了手写版的所有东西(多头注意力、位置编码、残差、LayerNorm、编码器-解码器结构)。适合做机器翻译等序列到序列任务。对比:手写版是"编码器-only"(适合分类),自带版是"编码器+解码器"(适合翻译)。
第三部分:演示
demo_handwritten—— 文本分类
- 造假数据:10 个单词的词汇表,规则是"单词 ID 之和为偶数 → 类别 0,奇数 → 类别 1"
- 训练 300 轮后准确率98.2%,说明模型真的学会了这个规则
- 测试句子
[2,2,2,2,2](和为 10,偶数)预测为 1 是错的——因为模型学的是统计规律而非精确算术,这是神经网络的特点,也说明"奇偶判断"对 Transformer 来说不是天然擅长的任务
demo_pytorch—— 机器翻译
- 造假数据:目标语言 = 源序列反转(模拟翻译)
- 演示了
nn.Transformer的完整训练循环建议阅读顺序
- 先看
SelfAttention—— 注意力是核心,看懂这个就懂了一半- 再看
TransformerBlock—— 残差 + LayerNorm 的组装- 然后看
SimpleTransformer—— 整体流程和位置编码- 最后看
PyTorchTransformer—— 知道工程上怎么用现成的运行方式:
python transformer_simple.py(已安装 torch 2.13.0 CPU 版和 numpy)。程序结构(三个部分)
第一部分:手写版 Transformer(理解原理)
SelfAttention:注意力核心,实现softmax(QKᵀ/√d_k)·V,用 Q/K/V 三个线性层计算词与词之间的关注度FeedForward:前馈网络,升维→ReLU→降维,增加非线性表达能力TransformerBlock:自注意力 + 前馈 + 残差连接 + LayerNorm 的组装SimpleTransformer:整体流程 = 词嵌入 → 位置编码 → N 个块 → 池化 → 分类第二部分:PyTorch 自带版(工程实践)
PyTorchTransformer:直接调用nn.Transformer,适合机器翻译等序列到序列任务第三部分:两个演示
demo_handwritten:文本分类,训练 300 轮后准确率 98.2%demo_pytorch:机器翻译演示关键学习点
- 注意力机制:Q 是"我要找什么",K 是"我是什么",V 是"我提供什么",通过 QKᵀ 计算相似度再加权求和
- 位置编码:Transformer 没有顺序概念,用正弦/余弦函数手动加位置信息
- 残差连接 + LayerNorm:防止梯度消失、稳定训练
- 手写版 vs 自带版:手写版是编码器-only(分类),自带版是编码器+解码器(翻译)
运行方式:
python transformer_simple.py(已安装 torch 2.13.0 CPU 版和 numpy)。