Transformer 小白指南:从零开始理解注意力机制
📚 专为零基础小白打造的 Transformer 教程
🎯 目标:用最简单的语言,讲清楚最前沿的深度学习技术
⚡ Transformer 是什么?它是当今 AI 领域最重要的技术,支撑着 ChatGPT、GPT-4、BERT、图像生成等几乎所有最先进的 AI 应用
📅 最后更新:2025年11月
📋 目录
- 1. 为什么要学习 Transformer?
- 2. Transformer 之前的世界
- 3. Transformer 的核心思想
- 4. 注意力机制详解
- 5. Transformer 架构解析
- 6. 位置编码的奥秘
- 7. 多头注意力机制
- 8. 前馈神经网络
- 9. Transformer 的训练过程
- 10. Transformer 的变种
- 11. 实战代码示例
- 12. 常见问题解答
1. 为什么要学习 Transformer?
1.1 Transformer 改变了世界
如果你关注过近几年的 AI 新闻,你会发现几乎所有震撼世界的 AI 应用都基于 Transformer:
| 应用 | 基于的技术 | 发布时间 |
|---|---|---|
| ChatGPT | GPT-3.5/GPT-4 (Transformer) | 2022年11月 |
| DALL-E 2 | Transformer + Diffusion | 2022年4月 |
| Midjourney | Transformer 架构 | 2022年 |
| GitHub Copilot | Codex (Transformer) | 2021年 |
| Google 翻译 | Transformer | 2017年后 |
| BERT(搜索引擎) | Transformer | 2018年 |
| AlphaFold 2 | 改进的 Transformer | 2020年 |
1.2 为什么 Transformer 这么强?
在 Transformer 出现之前(2017年),深度学习面临几个大问题:
❌ 训练慢:循环神经网络(RNN)必须一个词一个词处理,无法并行
❌ 记不住长文本:处理长句子时,前面的信息会"遗忘"
❌ 计算效率低:GPU 的强大算力无法充分利用
Transformer 一举解决了这些问题:
✅ 可以并行训练:所有词同时处理,训练速度提升 10-100 倍
✅ 长距离依赖:任意两个词之间都能直接建立联系
✅ 可扩展性强:模型越大效果越好(从 GPT-1 到 GPT-4)
1.3 学完这个教程你能做什么?
- 🔍 理解原理:彻底搞懂 Transformer 的工作机制
- 💻 读懂论文:能看懂 “Attention is All You Need” 等经典论文
- 🛠️ 动手实践:用 PyTorch 从零实现一个简单的 Transformer
- 🚀 应用开发:为使用 GPT、BERT 等预训练模型打下基础
- 🎓 面试加分:Transformer 是 AI 岗位必问的知识点
2. Transformer 之前的世界
2.1 深度学习简史
在讲 Transformer 之前,我们先看看 AI 是怎么一步步发展的:
时间线:深度学习的进化史
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1980s-2000s ┃ 传统机器学习
┃ 决策树、SVM、朴素贝叶斯
┃ 问题:需要人工设计特征,效果一般
┃
2012 ┃ ★ AlexNet (CNN 卷积神经网络)
┃ 图像识别准确率大幅提升
┃ 问题:只适合图像,不适合文本
┃
2014-2016 ┃ RNN/LSTM (循环神经网络)
┃ 可以处理文本、语音等序列数据
┃ 问题:训练慢,记不住长文本
┃
2017 ┃ ★★★ Transformer (注意力机制)
┃ "Attention is All You Need" 论文发布
┃ 革命性突破!
┃
2018 ┃ BERT (基于 Transformer)
┃ Google 搜索引擎升级
┃
2018-2020 ┃ GPT-1/GPT-2/GPT-3
┃ 语言生成能力惊人
┃
2022 ┃ ChatGPT (GPT-3.5)
┃ 人工智能走入千家万户
┃
2023-现在 ┃ GPT-4, Claude, Gemini...
┃ 多模态大模型时代
2.2 RNN/LSTM 的问题
在 Transformer 之前,处理文本主要用 循环神经网络(RNN)。
RNN 是怎么工作的?
类比:想象你在读一本书,从左往右一个字一个字读:
输入句子:"我爱北京天安门"
RNN 处理过程:
步骤1: 读"我" → 记住"我"
步骤2: 读"爱" → 记住"我爱"
步骤3: 读"北" → 记住"我爱北"
步骤4: 读"京" → 记住"我爱北京"
步骤5: 读"天" → 记住"我爱北京天"
步骤6: 读"安" → 记住"我爱北京天安"
步骤7: 读"门" → 记住整句话
RNN 的三大致命问题
问题1:训练太慢(无法并行)
- RNN 必须按顺序处理,步骤2必须等步骤1完成
- 就像排队过安检,一次只能过一个人
- 即使你有 100 个 GPU,也只能干等着
问题2:梯度消失(记忆力差)
看这个例子:
句子:"那个在公园里和穿红衣服的女孩说话的男人是我的老师"
问题:主语"男人"和谓语"是"之间隔了很多词
RNN处理:
我 → 的 → 老 → 师 → 是 → ...记不清前面是什么了
当句子很长时,前面的信息会被"遗忘",这叫梯度消失问题。
问题3:不知道重点在哪
句子:"虽然天气很冷,但是我还是出门了"
关键词应该是:"但是"、"出门"
RNN 却会把所有词平等对待,分不清重点
LSTM 的改进
LSTM(长短期记忆网络)在 RNN 基础上改进了记忆能力,但依然:
- ❌ 无法并行训练
- ❌ 长文本处理还是吃力
- ❌ 计算复杂度高
3. Transformer 的核心思想
3.1 革命性突破:抛弃循环
Transformer 最大胆的创新:完全抛弃了循环结构!
RNN 的方式(顺序处理):
我 → 爱 → 北 → 京 → 天 → 安 → 门
| | | | | | |
▼ ▼ ▼ ▼ ▼ ▼ ▼
隐 隐 隐 隐 隐 隐 隐
藏 藏 藏 藏 藏 藏 藏
状 状 状 状 状 状 状
态 态 态 态 态 态 态
1 2 3 4 5 6 7
Transformer 的方式(并行处理):
我 爱 北 京 天 安 门
| | | | | | |
▼ ▼ ▼ ▼ ▼ ▼ ▼
━━━━━━━━━━━━━━━━━━━━━━━
同时处理所有词!
通过"注意力"建立联系
━━━━━━━━━━━━━━━━━━━━━━━
3.2 核心机制:自注意力(Self-Attention)
什么是注意力?
类比人类阅读:
当你读到"他"这个字时,你会自动回想前面提到的人是谁:
句子:"小明买了一个苹果。他很开心。"
读到"他"时,你的注意力会自动关联到"小明"
Self-Attention 就是让 AI 也学会这种能力!
3.3 Transformer 的三大优势
优势1:并行计算
# RNN:必须顺序执行
for word in sentence:
process(word) # 一次只能处理一个
# Transformer:可以同时处理
process_all(sentence) # 所有词一起处理!
结果:训练速度提升 10-100 倍!
优势2:长距离依赖
句子:"电影虽然很长,但是剧情很精彩,演员演技也很好,所以我还是推荐大家去看。"
Transformer 可以让"推荐"直接关联到前面的"精彩"和"好"
RNN 处理到"推荐"时,前面的信息已经很模糊了
优势3:可解释性
Transformer 可以可视化"注意力权重",看出模型关注了哪些词:
翻译:"The cat sat on the mat"
模型在翻译"坐"时的注意力分布:
The(5%) cat(10%) sat(70%) on(10%) the(3%) mat(2%)
^^^^
重点关注!
4. 注意力机制详解
4.1 什么是注意力(Attention)?
日常生活类比
场景1:在嘈杂的咖啡馆
环境噪音:
- 咖啡机声音:████░░░░░░
- 聊天声:████████░░
- 音乐声:█████░░░░░
- 朋友说话:██████████ ← 你的注意力集中在这!
你的大脑会自动"放大"朋友的声音,"屏蔽"其他噪音
场景2:找钥匙
视野中的物品:
- 桌子 → 关注度 20%
- 椅子 → 关注度 10%
- 钱包 → 关注度 15%
- 钥匙 → 关注度 55% ← 注意力最高!
你的眼睛会自动聚焦到"像钥匙"的物体上
注意力机制的本质:在一堆信息中,自动找到最重要的部分!
4.2 Self-Attention 的数学原理
三个核心概念:Q、K、V
Self-Attention 用三个矩阵来实现:
- Q (Query):查询,“我想找什么”
- K (Key):键,“我是什么”
- V (Value):值,“我的内容是什么”
生活类比:
场景:在图书馆找书
你(Query): "我想找关于机器学习的书"
书架标签(Key): "数学"、"物理"、"计算机"、"文学"
书的内容(Value):"xxx的具体内容"
过程:
1. 你的查询 Q 和每个标签 K 计算相似度
2. 发现"计算机"标签最匹配(注意力分数高)
3. 从"计算机"区域提取书籍内容 V
数学公式(小白版解释)
Attention(Q, K, V) = softmax(Q·K^T / √d_k) · V
分解理解:
步骤1:计算相似度 (Q·K^T)
# 假设有 3 个词:["我", "爱", "你"]
Q = ["我"的查询向量] # 例如:[0.2, 0.8, 0.1]
K = [
"我"的键向量, # [0.3, 0.7, 0.2]
"爱"的键向量, # [0.5, 0.4, 0.6]
"你"的键向量 # [0.1, 0.9, 0.3]
]
# 计算"我"与每个词的相似度(点积)
相似度 = Q · K^T
= [0.2, 0.8, 0.1] · [[0.3], [0.7], [0.2]]
[[0.5], [0.4], [0.6]]
[[0.1], [0.9], [0.3]]
# 结果("我"对每个词的关注程度):
与"我"的相似度 = 0.64
与"爱"的相似度 = 0.78
与"你"的相似度 = 0.82
步骤2:归一化 (softmax)
# 原始分数:[0.64, 0.78, 0.82]
# 除以 √d_k(防止数值过大)
d_k = 3 # 向量维度
scaled = [0.64/√3, 0.78/√3, 0.82/√3]
= [0.37, 0.45, 0.47]
# softmax 归一化(转换为概率分布)
attention_weights = softmax([0.37, 0.45, 0.47])
= [0.31, 0.34, 0.35] # 总和为1
# 解释:处理"我"这个词时:
# - 对"我"本身的关注度:31%
# - 对"爱"的关注度:34%
# - 对"你"的关注度:35%
步骤3:加权求和 (·V)
V = [
"我"的值向量, # [内容1]
"爱"的值向量, # [内容2]
"你"的值向量 # [内容3]
]
# 用注意力权重加权平均
输出 = 0.31 × [内容1] + 0.34 × [内容2] + 0.35 × [内容3]
= 融合了三个词信息的新表示!
4.3 Self-Attention 可视化示例
示例1:代词消歧
句子:"The animal didn't cross the street because it was too tired."
问题:"it" 指的是什么?
Self-Attention 的注意力分布:
The animal didn't cross the street because it was too tired
处理 "it" 时:5% 70% 3% 5% 3% 8% 3% - 2% 1% 0%
^^^^
注意力集中在 "animal"!
结论:模型知道 "it" = "animal"(因为动物累了)
示例2:长距离依赖
句子:"The law that the government passed yesterday was controversial."
问题:"was" 的主语是什么?
Self-Attention 的注意力分布:
The law that the government passed yesterday was controversial
处理 "was" 时:3% 65% 5% 3% 8% 4% 7% - 5%
^^^^
跨越多个词,直接关联到主语 "law"!
RNN:处理到 "was" 时,早就忘了 "law" 了
Transformer:可以直接建立长距离联系!
4.4 为什么叫 Self-Attention?
普通 Attention:查询(Query)和键(Key)来自不同序列
例如:机器翻译中,英文句子关注对应的中文词
Self-Attention:Query、Key、Value 都来自同一个句子
例如:"我爱你" 中,"爱" 同时作为:
- Query:我要找相关的词
- Key: 我是"爱"这个动词
- Value:我的语义内容
Self = 自己跟自己的词建立联系
5. Transformer 架构解析
5.1 整体架构图
┌─────────────────────────────────────────────────────────┐
│ Transformer 完整架构 │
└─────────────────────────────────────────────────────────┘
输入:我 爱 机器 学习
↓
┌─────────────────────┐ ┌─────────────────────┐
│ Encoder (编码器) │───→│ Decoder (解码器) │
│ 理解输入语义 │ │ 生成输出语句 │
└─────────────────────┘ └─────────────────────┘
↓ ↓
编码表示 输出:I love machine learning
5.2 Encoder(编码器)详解
Encoder 的任务:理解输入句子的含义
┌──────────────────────────────────────┐
│ 单个 Encoder 层 │
├──────────────────────────────────────┤
│ │
│ 输入:词向量 (带位置信息) │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Multi-Head Attention │ ← 自注意力:理解词与词的关系
│ │ (多头自注意力) │ │
│ └──────────────────────┘ │
│ ↓ │
│ [Add & Norm] ← 残差连接 + 层归一化 │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Feed Forward │ ← 前馈网络:特征变换
│ │ (前馈神经网络) │ │
│ └──────────────────────┘ │
│ ↓ │
│ [Add & Norm] ← 残差连接 + 层归一化 │
│ ↓ │
│ 输出:编码后的语义表示 │
│ │
└──────────────────────────────────────┘
完整 Encoder = 堆叠 6 层这样的结构
Encoder 的处理流程示例
输入句子:"我爱机器学习"
【第1步:词嵌入 + 位置编码】
我 → [0.2, 0.3, ..., 0.1] + 位置1的编码
爱 → [0.5, 0.1, ..., 0.4] + 位置2的编码
机器 → [0.3, 0.8, ..., 0.2] + 位置3的编码
学习 → [0.1, 0.4, ..., 0.7] + 位置4的编码
【第2步:Multi-Head Attention】
计算每个词对其他词的注意力:
- "我" 关注 "爱"(60%)、"学习"(30%)、自己(10%)
- "爱" 关注 "我"(40%)、"机器"(35%)、"学习"(25%)
- ...
【第3步:Add & Norm】
输入 + Attention输出,然后归一化
【第4步:Feed Forward】
通过全连接网络进一步变换
【第5步:重复 6 次】
经过 6 层 Encoder,得到最终的语义表示
5.3 Decoder(解码器)详解
Decoder 的任务:根据编码结果生成输出
┌──────────────────────────────────────┐
│ 单个 Decoder 层 │
├──────────────────────────────────────┤
│ │
│ 输入:已生成的词 (带位置信息) │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Masked Multi-Head │ ← 掩码自注意力:只能看到前面的词
│ │ Attention │ │
│ └──────────────────────┘ │
│ ↓ │
│ [Add & Norm] │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Cross-Attention │ ← 交叉注意力:关注 Encoder 的输出
│ │ (编码器-解码器注意力) │ │
│ └──────────────────────┘ │
│ ↓ │
│ [Add & Norm] │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Feed Forward │ ← 前馈网络
│ │ (前馈神经网络) │ │
│ └──────────────────────┘ │
│ ↓ │
│ [Add & Norm] │
│ ↓ │
│ 输出:下一个词的概率分布 │
│ │
└──────────────────────────────────────┘
完整 Decoder = 堆叠 6 层这样的结构
Decoder 的生成过程(自回归)
任务:翻译 "我爱机器学习" → "I love machine learning"
Step 1: Encoder 处理输入
输入:"我爱机器学习"
输出:编码表示 [h1, h2, h3, h4]
Step 2: Decoder 逐词生成
【时间步1】
已生成:<start>
Decoder 输出:"I" (概率最高)
状态:<start> I
【时间步2】
已生成:<start> I
Decoder 输出:"love" (概率最高)
状态:<start> I love
【时间步3】
已生成:<start> I love
Decoder 输出:"machine" (概率最高)
状态:<start> I love machine
【时间步4】
已生成:<start> I love machine
Decoder 输出:"learning" (概率最高)
状态:<start> I love machine learning
【时间步5】
已生成:<start> I love machine learning
Decoder 输出:"<end>" (结束标志)
最终结果:"I love machine learning"
5.4 Masked Attention(掩码注意力)
为什么需要掩码?
问题场景:训练翻译模型
错误做法(不用掩码):
输入:"我爱机器学习"
目标:"I love machine learning"
生成 "love" 时,模型可以偷看到后面的 "machine learning"
→ 模型学会作弊!测试时没有答案可看,就不会了
正确做法(使用掩码):
生成 "love" 时,只能看到 "<start> I"
→ 强迫模型真正学会预测下一个词
掩码的实现:
注意力矩阵(掩码前):
<start> I love machine learning
<start> ✓ ✓ ✓ ✓ ✓
I ✓ ✓ ✓ ✓ ✓
love ✓ ✓ ✓ ✓ ✓
machine ✓ ✓ ✓ ✓ ✓
learning ✓ ✓ ✓ ✓ ✓
注意力矩阵(掩码后):
<start> I love machine learning
<start> ✓ ✗ ✗ ✗ ✗ 只能看自己
I ✓ ✓ ✗ ✗ ✗ 只能看到前面
love ✓ ✓ ✓ ✗ ✗
machine ✓ ✓ ✓ ✓ ✗
learning ✓ ✓ ✓ ✓ ✓
✓ = 可以关注
✗ = 被掩码遮住(设为 -∞,softmax 后变成 0)
5.5 完整数据流示例
任务:中文 → 英文翻译
【输入】
源句子:"机器学习很有趣"
目标句子:"Machine learning is interesting"(训练时已知)
【Encoder 处理】
1. 词嵌入:
机器 → [v1]
学习 → [v2]
很 → [v3]
有趣 → [v4]
2. 加上位置编码:
[v1] + [pos1]
[v2] + [pos2]
[v3] + [pos3]
[v4] + [pos4]
3. 经过 6 层 Encoder:
→ 编码表示 [h1, h2, h3, h4]
【Decoder 处理】
1. 输入:<start>
→ 通过 Masked Self-Attention(只看自己)
→ 通过 Cross-Attention(关注 Encoder 输出 [h1,h2,h3,h4])
→ 预测:P(Machine) = 0.85 ← 概率最高
→ 输出:"Machine"
2. 输入:<start> Machine
→ Masked Self-Attention(只看前两个词)
→ Cross-Attention(关注 [h1,h2,h3,h4])
→ 预测:P(learning) = 0.92
→ 输出:"learning"
3. 输入:<start> Machine learning
→ ...
→ 预测:P(is) = 0.88
→ 输出:"is"
4. 输入:<start> Machine learning is
→ ...
→ 预测:P(interesting) = 0.91
→ 输出:"interesting"
5. 输入:<start> Machine learning is interesting
→ ...
→ 预测:P(<end>) = 0.95
→ 结束
【最终输出】
"Machine learning is interesting"
6. 位置编码的奥秘
6.1 为什么需要位置编码?
问题:Transformer 同时处理所有词,会丢失词的顺序信息!
句子1:"狗咬了人"
句子2:"人咬了狗"
如果不加位置信息:
Transformer 看到的都是:{狗, 咬, 了, 人}(无序集合)
→ 无法区分这两句话的意思!
解决方案:给每个词加上"位置标签"
句子1:"狗咬了人"
加标签:狗[位置1] 咬[位置2] 了[位置3] 人[位置4]
句子2:"人咬了狗"
加标签:人[位置1] 咬[位置2] 了[位置3] 狗[位置4]
现在可以区分了!
6.2 位置编码的设计
原始论文使用三角函数编码(Sinusoidal Positional Encoding):
# 公式
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
# 其中:
# pos = 位置索引 (0, 1, 2, 3, ...)
# i = 维度索引 (0, 1, 2, ..., d_model/2)
# d_model = 词向量维度(通常是512)
为什么用三角函数?
- 周期性:sin/cos 有周期性,可以表示任意长度的序列
- 相对位置:位置 k 和 k+Δ 之间的编码可以通过线性变换得到
- 不需要训练:直接计算,不占用参数
可视化位置编码
位置编码的模式(热力图):
位置 维度0 维度1 维度2 维度3 ... 维度511
0 ████ ░░░░ ████ ░░░░ ████
1 ███░ ░░█░ ███░ ░░█░ ███░
2 ██░░ ░██░ ██░░ ░██░ ██░░
3 █░░░ ███░ █░░░ ███░ █░░░
4 ░░░░ ████ ░░░░ ████ ░░░░
5 ░░█░ ███░ ░░█░ ███░ ░░█░
...
特点:
- 不同位置的编码不同
- 低频信号(左边)→ 高频信号(右边)
- 可以区分不同位置
6.3 位置编码的实现
import numpy as np
def get_positional_encoding(max_len, d_model):
"""
生成位置编码
参数:
max_len: 最大序列长度
d_model: 词向量维度
"""
# 创建位置编码矩阵
pe = np.zeros((max_len, d_model))
# 位置索引
position = np.arange(0, max_len).reshape(-1, 1)
# 维度索引
div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
# 偶数维度用 sin
pe[:, 0::2] = np.sin(position * div_term)
# 奇数维度用 cos
pe[:, 1::2] = np.cos(position * div_term)
return pe
# 示例:生成前10个位置的编码(维度=512)
pe = get_positional_encoding(10, 512)
print("位置0的编码(前10维):", pe[0, :10])
print("位置5的编码(前10维):", pe[5, :10])
输出示例:
位置0的编码(前10维): [0.000, 1.000, 0.000, 1.000, 0.000, ...]
位置5的编码(前10维): [0.959, 0.284, 0.598, 0.801, 0.099, ...]
6.4 词嵌入 + 位置编码
# 完整的输入表示
词向量 = Embedding(词ID) # 例如:"机器" → [0.2, 0.3, 0.5, ...]
位置编码 = PE(位置) # 例如:位置2 → [0.1, 0.9, 0.2, ...]
最终输入 = 词向量 + 位置编码
# 示例
句子 = "我爱机器学习"
最终输入表示 = [
Embedding("我") + PE(0),
Embedding("爱") + PE(1),
Embedding("机器") + PE(2),
Embedding("学习") + PE(3)
]
7. 多头注意力机制
7.1 什么是多头注意力(Multi-Head Attention)?
单头注意力的局限:
句子:"The animal didn't cross the street because it was too tired."
单头注意力在处理 "it" 时,可能只关注到一个方面:
- 可能只关注 "animal"(指代关系)
- 或者只关注 "tired"(状态描述)
但理想情况是同时关注多个方面!
多头注意力的思想:
就像你同时用不同角度看一个问题:
头1:关注"是什么"(词性、实体)
头2:关注"做什么"(动作、关系)
头3:关注"怎么样"(状态、情感)
头4:关注"在哪里"(位置、上下文)
...
每个头学习不同的语义关系!
7.2 多头注意力的架构
┌─────────────────────────────────────────────────┐
│ Multi-Head Attention (8个头) │
├─────────────────────────────────────────────────┤
│ │
│ 输入:X (词向量矩阵) │
│ │ │
│ ├──→ Linear → Q1, K1, V1 → Attention → head1 │
│ ├──→ Linear → Q2, K2, V2 → Attention → head2 │
│ ├──→ Linear → Q3, K3, V3 → Attention → head3 │
│ ├──→ Linear → Q4, K4, V4 → Attention → head4 │
│ ├──→ Linear → Q5, K5, V5 → Attention → head5 │
│ ├──→ Linear → Q6, K6, V6 → Attention → head6 │
│ ├──→ Linear → Q7, K7, V7 → Attention → head7 │
│ └──→ Linear → Q8, K8, V8 → Attention → head8 │
│ │ │
│ Concat (拼接) │
│ │ │
│ Linear (线性变换) │
│ │ │
│ 输出 │
│ │
└─────────────────────────────────────────────────┘
7.3 多头注意力的计算过程
步骤1:分头计算
# 假设输入维度 d_model = 512,8 个头
d_model = 512
num_heads = 8
d_k = d_model // num_heads = 64 # 每个头的维度
# 输入矩阵 X: [batch_size, seq_len, d_model]
X = [
[词1的向量 (512维)],
[词2的向量 (512维)],
[词3的向量 (512维)],
...
]
# 对每个头:
for i in range(8):
# 线性变换得到 Q, K, V
Q_i = X @ W_Q_i # [batch, seq_len, 64]
K_i = X @ W_K_i # [batch, seq_len, 64]
V_i = X @ W_V_i # [batch, seq_len, 64]
# 计算注意力
head_i = Attention(Q_i, K_i, V_i) # [batch, seq_len, 64]
步骤2:拼接多个头
# 拼接 8 个头的输出
MultiHead = Concat(head1, head2, ..., head8)
= [batch, seq_len, 8*64]
= [batch, seq_len, 512]
步骤3:最终线性变换
# 通过一个线性层融合
Output = MultiHead @ W_O
= [batch, seq_len, 512]
7.4 多头注意力的可视化
不同头学到的模式
示例句子:"The quick brown fox jumps over the lazy dog"
【Head 1:句法关系】
The → quick(0.8), fox(0.2) # 修饰关系
quick → brown(0.6), fox(0.4) # 形容词链
fox → jumps(0.9) # 主谓关系
jumps → over(0.7), fox(0.3) # 动词-介词
【Head 2:语义关联】
fox → dog(0.6), animal-related # 同类概念
quick → fast(0.5), speed-related # 语义相似
brown → color-related # 属性关联
【Head 3:位置关系】
over → fox(0.4), dog(0.6) # 空间关系
The → 前面的词(0.0), 后面(1.0) # 顺序依赖
【Head 4:指代消解】
(在更复杂的句子中)
it → 前面的名词(0.9) # 代词指代
they → 前面的复数名词(0.8) # 复数指代
7.5 多头注意力的优势
优势1:捕获不同层次的信息
单头 Attention:只能学到一种模式
多头 Attention:
- 头1 学习短距离依赖(相邻词关系)
- 头2 学习长距离依赖(句首句尾关联)
- 头3 学习句法结构(主谓宾)
- 头4 学习语义关系(同义词、反义词)
- ...
优势2:增强表达能力
参数量:
单头: 3 × d_model × d_model ≈ 0.75M 参数
8头: 8 × 3 × (d_model/8) × (d_model/8) × 8 ≈ 0.75M 参数
参数量相同,但表达能力更强!
因为每个头专注于不同的特征空间
优势3:训练更稳定
梯度流动:
单头:梯度集中在一条路径,容易梯度消失
多头:8条并行路径,梯度更稳定
8. 前馈神经网络
8.1 Feed Forward Network (FFN)
在每个 Attention 层之后,都有一个前馈神经网络(FFN):
┌────────────────────────────────┐
│ Feed Forward Network (FFN) │
├────────────────────────────────┤
│ │
│ 输入: x (维度 512) │
│ ↓ │
│ 线性层1: W1·x + b1 │
│ 维度: 512 → 2048 (扩大4倍) │
│ ↓ │
│ 激活函数: ReLU(x) │
│ ReLU(x) = max(0, x) │
│ ↓ │
│ 线性层2: W2·x + b2 │
│ 维度: 2048 → 512 (恢复原维度) │
│ ↓ │
│ 输出: y (维度 512) │
│ │
└────────────────────────────────┘
8.2 FFN 的作用
为什么需要 FFN?
Attention 的作用:
- 建立词与词之间的关系
- 信息融合(加权求和)
- 但只是线性变换!
FFN 的作用:
- 引入非线性(ReLU激活函数)
- 特征变换和提取
- 增强模型表达能力
类比理解:
Attention:像是一个"信息路由器"
- 决定哪些信息该传给谁
- 但不改变信息本身的内容
FFN:像是一个"信息处理器"
- 对信息进行加工和提炼
- 提取更高层次的特征
8.3 FFN 的数学表达
def feed_forward(x):
"""
前馈神经网络
x: 输入,形状 [batch, seq_len, d_model=512]
"""
# 第一层:扩展维度
hidden = W1 @ x + b1
# hidden: [batch, seq_len, d_ff=2048]
# 激活函数
hidden = ReLU(hidden)
# ReLU(x) = max(0, x) 负数变0,正数不变
# 第二层:恢复维度
output = W2 @ hidden + b2
# output: [batch, seq_len, d_model=512]
return output
# 原论文参数:
# d_model = 512
# d_ff = 2048(中间层维度,通常是 4 × d_model)
8.4 为什么要"先扩大再缩小"?
输入维度:512
中间维度:2048(扩大4倍)
输出维度:512(恢复)
原因:
1. 增加表达能力:更高维的空间可以学习更复杂的模式
2. 非线性变换:ReLU 在高维空间中更有效
3. 特征提取:类似于 CNN 中的"先扩展通道,再压缩"
类比:
就像你整理房间:
- 先把所有东西摊开(扩展到2048维)
- 仔细整理分类(ReLU 筛选特征)
- 再整齐地放回去(压缩回512维)
8.5 残差连接和层归一化
每个子层(Attention 和 FFN)后面都有:
┌────────────────────────────────┐
│ 残差连接 + 层归一化 │
├────────────────────────────────┤
│ │
│ 输入: x │
│ │ │
│ ├─────→ SubLayer (子层) │
│ │ │ │
│ │ ↓ │
│ │ 输出: F(x) │
│ │ │ │
│ └─────→ + (残差连接) │
│ │ │
│ ↓ │
│ x + F(x) │
│ │ │
│ ↓ │
│ LayerNorm (层归一化) │
│ │ │
│ ↓ │
│ 输出 │
│ │
└────────────────────────────────┘
残差连接(Residual Connection)
# 没有残差连接
output = SubLayer(input)
# 有残差连接
output = LayerNorm(input + SubLayer(input))
为什么需要残差连接?
问题:深层网络容易梯度消失
残差连接的作用:
1. 梯度可以直接流回(跳过中间层)
2. 防止网络退化
3. 加快训练收敛
类比:
就像走迷宫:
- 没有残差:只能一步步往前走
- 有残差:可以"传送",跳过某些路径
层归一化(Layer Normalization)
def layer_norm(x):
"""
对每个样本的特征维度进行归一化
"""
mean = x.mean(dim=-1, keepdim=True)
std = x.std(dim=-1, keepdim=True)
return (x - mean) / (std + eps)
# 示例
输入: [0.5, 2.0, 3.5, 1.0]
mean = 1.75
std = 1.19
归一化后: [-1.05, 0.21, 1.47, -0.63] # 均值0,标准差1
为什么需要层归一化?
作用:
1. 稳定训练:防止数值过大或过小
2. 加快收敛:每层的输入分布稳定
3. 减少对初始化的依赖
对比 Batch Normalization:
BatchNorm:对一个 batch 的同一个特征归一化
LayerNorm:对一个样本的所有特征归一化
Transformer 用 LayerNorm 的原因:
- 序列长度可变,BatchNorm 不适合
- LayerNorm 对 batch size 不敏感
9. Transformer 的训练过程
9.1 训练任务示例:机器翻译
训练数据:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
输入(中文) 输出(英文)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
我爱机器学习 → I love machine learning
今天天气很好 → The weather is nice today
你好,世界 → Hello, world
...
9.2 训练流程
Step 1: 数据准备
# 1. 分词
源句子:"我爱机器学习"
→ ["我", "爱", "机器", "学习"]
目标句子:"I love machine learning"
→ ["<start>", "I", "love", "machine", "learning", "<end>"]
# 2. 转为ID
源ID:[34, 21, 456, 789]
目标ID:[1, 78, 234, 567, 890, 2]
# 3. 构造训练对
输入: ["我", "爱", "机器", "学习"]
输出: ["<start>", "I", "love", "machine", "learning"]
标签: ["I", "love", "machine", "learning", "<end>"]
^^^^
每次预测下一个词
Step 2: 前向传播
# Encoder 处理源句子
encoder_input = ["我", "爱", "机器", "学习"]
encoder_output = encoder(encoder_input) # 编码表示
# Decoder 处理目标句子
decoder_input = ["<start>", "I", "love", "machine", "learning"]
decoder_output = decoder(decoder_input, encoder_output)
# 预测概率分布
predictions = softmax(decoder_output)
# Shape: [batch, seq_len, vocab_size]
# 示例:预测第2个位置的词
位置1的输入:"<start>"
模型预测:
"I" → 0.85 ← 最高概率
"The" → 0.08
"A" → 0.03
"We" → 0.02
...其他词 → 很小
Step 3: 计算损失
# 交叉熵损失(Cross Entropy Loss)
# 每个位置计算损失
位置1:
正确答案:"I"
预测概率:P("I") = 0.85
损失:-log(0.85) = 0.16
位置2:
正确答案:"love"
预测概率:P("love") = 0.92
损失:-log(0.92) = 0.08
位置3:
正确答案:"machine"
预测概率:P("machine") = 0.78
损失:-log(0.78) = 0.25
...
# 总损失 = 所有位置损失的平均值
total_loss = (0.16 + 0.08 + 0.25 + ...) / 句子长度
Step 4: 反向传播和参数更新
# 计算梯度
loss.backward()
# 更新参数(使用 Adam 优化器)
optimizer.step()
# 清空梯度
optimizer.zero_grad()
9.3 训练技巧
技巧1:Teacher Forcing
训练时的策略:
【Teacher Forcing(强制使用正确答案)】
时间步1:输入 "<start>" → 预测 "I"
时间步2:输入 "<start> I" → 预测 "love" ← 用正确的"I"
时间步3:输入 "<start> I love" → 预测 "machine"
...
如果不用 Teacher Forcing(用自己的预测):
时间步1:输入 "<start>" → 预测 "I"
时间步2:输入 "<start> I" → 预测 "enjoy" ← 预测错了!
时间步3:输入 "<start> I enjoy" → 预测会越来越错...
优点:训练快,收敛稳定
缺点:训练和测试不一致(Exposure Bias)
技巧2:Learning Rate Warmup
# 学习率调度策略
def get_learning_rate(step, d_model=512, warmup_steps=4000):
"""
Transformer 论文中的学习率策略
"""
step = max(step, 1)
lr = (d_model ** -0.5) * min(
step ** -0.5,
step * (warmup_steps ** -1.5)
)
return lr
# 学习率曲线:
#
# lr
# │
# │ ╱╲
# │ ╱ ╲
# │ ╱ ╲___
# │╱ ╲____
# └─────────────────→ step
# ↑
# warmup 先增后减
为什么需要 Warmup?
原因:
1. 模型初始时参数随机,梯度不稳定
2. 大学习率可能导致训练崩溃
3. Warmup 让模型慢慢"热身"
类比:
就像汽车启动:
- 不能一开始就全速(学习率太大)
- 先慢慢加速(warmup)
- 然后再逐渐减速(decay)
技巧3:Label Smoothing
# 标签平滑
# 原始标签(one-hot)
真实标签 = [0, 0, 1, 0, 0] # 第3个词是正确答案
100% 确定
# 平滑后的标签
平滑标签 = [0.02, 0.02, 0.92, 0.02, 0.02] # 90% 确定
给其他词也分配一点点概率
# 好处:
# 1. 防止过拟合(不要太自信)
# 2. 提高泛化能力
# 3. 训练更稳定
技巧4:Dropout
# 随机丢弃(Dropout)
# 在训练时:
x = [0.5, 0.8, 0.3, 0.9, 0.2]
dropout(x, rate=0.1) = [0.5, 0, 0.3, 0.9, 0.2] # 随机丢弃10%
↑
被丢弃的元素
# 在测试时:
不使用 dropout,保留所有元素
# 作用:
# 1. 防止过拟合
# 2. 增强鲁棒性
# 3. 相当于训练多个子网络的集成
9.4 训练监控
# 训练过程打印示例
Epoch 1/100
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Batch 100/5000 Loss: 5.234 LR: 0.0001 Time: 2.3s
Batch 200/5000 Loss: 4.876 LR: 0.0002 Time: 2.1s
Batch 300/5000 Loss: 4.523 LR: 0.0003 Time: 2.2s
...
Epoch 1 Avg Loss: 4.123 Val Loss: 4.567
Epoch 2/100
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Batch 100/5000 Loss: 3.987 LR: 0.0004 Time: 2.1s
...
# 关键指标:
# - Loss:损失值(越低越好,但不要过拟合)
# - Perplexity:困惑度 = exp(loss),越低越好
# - BLEU:翻译质量评估(0-100,越高越好)
10. Transformer 的变种
10.1 Encoder-Only 模型
BERT (Bidirectional Encoder Representations from Transformers)
架构:只用 Encoder(12层或24层)
┌──────────────┐
│ Input │ "我[MASK]机器学习"
└──────────────┘
↓
┌──────────────┐
│ Encoder │ × 12 层
│ Encoder │
│ Encoder │
└──────────────┘
↓
┌──────────────┐
│ Output │ 预测 [MASK] = "爱"
└──────────────┘
特点:
✅ 双向理解(同时看前后文)
✅ 适合理解任务(分类、问答、命名实体识别)
❌ 不能直接生成文本
应用:
- Google 搜索引擎
- 文本分类
- 问答系统
- 情感分析
BERT 的训练任务:
任务1:Masked Language Modeling (MLM)
输入:"我[MASK]机器学习"
目标:预测 [MASK] = "爱"
任务2:Next Sentence Prediction (NSP)
输入:
句子A:"我喜欢机器学习"
句子B:"深度学习很有趣"
目标:判断 B 是否是 A 的下一句(是/否)
10.2 Decoder-Only 模型
GPT (Generative Pre-trained Transformer)
架构:只用 Decoder(无 Cross-Attention 部分)
┌──────────────┐
│ Input │ "我爱机器"
└──────────────┘
↓
┌──────────────┐
│ Decoder │ × 12/24/96 层
│ Decoder │ (GPT-3: 96层)
│ Decoder │
└──────────────┘
↓
┌──────────────┐
│ Output │ 预测下一个词 = "学习"
└──────────────┘
特点:
✅ 自回归生成(逐词生成)
✅ 适合生成任务(续写、对话、代码生成)
✅ 单向理解(只看前文,不看后文)
应用:
- ChatGPT
- GitHub Copilot
- 文本生成
- 对话系统
GPT 的训练方式:
训练任务:Language Modeling (自回归语言模型)
输入:"我爱机器"
目标:预测下一个词 = "学习"
然后:
输入:"我爱机器学习"
目标:预测下一个词 = "。"
就这样不断预测下一个词!
10.3 Encoder-Decoder 模型
T5 (Text-to-Text Transfer Transformer)
架构:完整的 Encoder-Decoder(和原始 Transformer 一样)
输入:任何任务都转换为"文本到文本"
示例任务:
【翻译】
输入:"translate English to German: That is good."
输出:"Das ist gut."
【摘要】
输入:"summarize: <长文本>"
输出:"<摘要>"
【问答】
输入:"question: What is the capital of France? context: Paris is..."
输出:"Paris"
【分类】
输入:"sentiment: This movie is great!"
输出:"positive"
特点:
✅ 统一框架(所有任务用同一个模型)
✅ 灵活性强
10.4 模型对比
| 模型 | 架构 | 训练方式 | 适合任务 | 代表应用 |
|---|---|---|---|---|
| BERT | Encoder-Only | Masked LM | 理解(分类、问答) | Google搜索 |
| GPT | Decoder-Only | 自回归 LM | 生成(续写、对话) | ChatGPT |
| T5 | Encoder-Decoder | Text-to-Text | 通用(所有NLP任务) | Google Translate |
| BART | Encoder-Decoder | Denoising | 摘要、翻译 | 文本摘要 |
10.5 模型大小的进化
模型参数量的爆炸式增长:
2018 BERT-Base 110M (1.1亿)
2018 BERT-Large 340M (3.4亿)
2018 GPT-1 117M (1.2亿)
2019 GPT-2 1.5B (15亿)
2020 GPT-3 175B (1750亿) ← 质变!
2023 GPT-4 ~1.8T (约1.8万亿) [据传]
2024 Claude 3 未公开
Gemini Ultra 未公开
趋势:参数越多,能力越强(但成本也越高)
训练成本:
- GPT-3:约 460万美元
- GPT-4:据估计超过 1亿美元
11. 实战代码示例
11.1 从零实现 Self-Attention
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class SelfAttention(nn.Module):
"""
自注意力机制实现
"""
def __init__(self, d_model):
"""
参数:
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, mask=None):
"""
参数:
x: 输入,shape [batch, seq_len, d_model]
mask: 掩码,shape [batch, seq_len, seq_len]
返回:
输出,shape [batch, seq_len, d_model]
"""
# 计算 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]
# 计算注意力分数
# Q·K^T
scores = torch.matmul(Q, K.transpose(-2, -1)) # [batch, seq_len, seq_len]
# 缩放(除以 √d_k)
scores = scores / math.sqrt(self.d_model)
# 应用掩码(如果有)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# Softmax 归一化
attention_weights = F.softmax(scores, dim=-1) # [batch, seq_len, seq_len]
# 加权求和
output = torch.matmul(attention_weights, V) # [batch, seq_len, d_model]
return output, attention_weights
# 使用示例
if __name__ == "__main__":
# 参数
batch_size = 2
seq_len = 5
d_model = 64
# 创建随机输入(模拟词向量)
x = torch.randn(batch_size, seq_len, d_model)
# 创建 Self-Attention 层
attention = SelfAttention(d_model)
# 前向传播
output, weights = attention(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print(f"注意力权重形状: {weights.shape}")
print("\n第一个样本的注意力权重矩阵:")
print(weights[0].detach().numpy())
11.2 多头注意力实现
class MultiHeadAttention(nn.Module):
"""
多头注意力机制
"""
def __init__(self, d_model, num_heads):
"""
参数:
d_model: 词向量维度
num_heads: 头的数量
"""
super().__init__()
assert d_model % num_heads == 0, "d_model 必须能被 num_heads 整除"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads # 每个头的维度
# 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)
# 输出的线性变换
self.W_o = nn.Linear(d_model, d_model)
def split_heads(self, x):
"""
分割成多个头
输入: [batch, seq_len, d_model]
输出: [batch, num_heads, seq_len, d_k]
"""
batch_size, seq_len, d_model = x.size()
return x.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
def forward(self, x, mask=None):
"""
前向传播
"""
batch_size = x.size(0)
# 线性变换
Q = self.W_q(x) # [batch, seq_len, d_model]
K = self.W_k(x)
V = self.W_v(x)
# 分割成多个头
Q = self.split_heads(Q) # [batch, num_heads, seq_len, d_k]
K = self.split_heads(K)
V = self.split_heads(V)
# 计算注意力
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attention_weights = F.softmax(scores, dim=-1)
# 加权求和
output = torch.matmul(attention_weights, V)
# output: [batch, num_heads, seq_len, d_k]
# 合并多个头
output = output.transpose(1, 2).contiguous()
# output: [batch, seq_len, num_heads, d_k]
output = output.view(batch_size, -1, self.d_model)
# output: [batch, seq_len, d_model]
# 最终线性变换
output = self.W_o(output)
return output, attention_weights
# 使用示例
if __name__ == "__main__":
batch_size = 2
seq_len = 10
d_model = 512
num_heads = 8
x = torch.randn(batch_size, seq_len, d_model)
mha = MultiHeadAttention(d_model, num_heads)
output, weights = mha(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print(f"注意力权重形状: {weights.shape}")
11.3 位置编码实现
class PositionalEncoding(nn.Module):
"""
位置编码
"""
def __init__(self, d_model, max_len=5000):
"""
参数:
d_model: 词向量维度
max_len: 最大序列长度
"""
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) # 奇数维度
# 添加 batch 维度
pe = pe.unsqueeze(0) # [1, max_len, d_model]
# 注册为 buffer(不参与训练)
self.register_buffer('pe', pe)
def forward(self, x):
"""
参数:
x: 输入,shape [batch, seq_len, d_model]
"""
# 添加位置编码
seq_len = x.size(1)
x = x + self.pe[:, :seq_len, :]
return x
# 可视化位置编码
import matplotlib.pyplot as plt
import numpy as np
def visualize_positional_encoding():
"""
可视化位置编码
"""
d_model = 128
max_len = 100
pe = PositionalEncoding(d_model, max_len)
# 提取位置编码矩阵
encoding = pe.pe.squeeze(0).numpy()
# 绘制热力图
plt.figure(figsize=(12, 6))
plt.imshow(encoding, cmap='RdBu', aspect='auto')
plt.xlabel('Embedding Dimension')
plt.ylabel('Position')
plt.title('Positional Encoding Visualization')
plt.colorbar()
plt.tight_layout()
plt.savefig('positional_encoding.png', dpi=150)
print("位置编码可视化已保存到 positional_encoding.png")
# 运行可视化
if __name__ == "__main__":
visualize_positional_encoding()
11.4 完整的 Transformer 层
class TransformerEncoderLayer(nn.Module):
"""
单个 Transformer Encoder 层
"""
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
"""
参数:
d_model: 词向量维度
num_heads: 多头注意力的头数
d_ff: 前馈网络的隐藏层维度
dropout: Dropout 比率
"""
super().__init__()
# 多头自注意力
self.self_attention = MultiHeadAttention(d_model, num_heads)
# 前馈网络
self.feed_forward = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.ReLU(),
nn.Linear(d_ff, d_model)
)
# 层归一化
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
# Dropout
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
"""
前向传播
"""
# 多头自注意力 + 残差连接 + 层归一化
attn_output, _ = self.self_attention(x, mask)
x = self.norm1(x + self.dropout(attn_output))
# 前馈网络 + 残差连接 + 层归一化
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_output))
return x
# 使用示例
if __name__ == "__main__":
# 参数
batch_size = 4
seq_len = 20
d_model = 512
num_heads = 8
d_ff = 2048
# 创建输入
x = torch.randn(batch_size, seq_len, d_model)
# 创建 Transformer 层
encoder_layer = TransformerEncoderLayer(d_model, num_heads, d_ff)
# 前向传播
output = encoder_layer(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print(f"参数量: {sum(p.numel() for p in encoder_layer.parameters()):,}")
11.5 使用 PyTorch 内置的 Transformer
import torch
import torch.nn as nn
# PyTorch 提供了内置的 Transformer 模块
model = nn.Transformer(
d_model=512, # 词向量维度
nhead=8, # 多头注意力的头数
num_encoder_layers=6, # Encoder 层数
num_decoder_layers=6, # Decoder 层数
dim_feedforward=2048, # 前馈网络维度
dropout=0.1
)
# 创建输入
src = torch.randn(10, 32, 512) # [seq_len, batch, d_model]
tgt = torch.randn(20, 32, 512) # [seq_len, batch, d_model]
# 前向传播
output = model(src, tgt)
print(f"源序列形状: {src.shape}")
print(f"目标序列形状: {tgt.shape}")
print(f"输出形状: {output.shape}")
print(f"模型参数量: {sum(p.numel() for p in model.parameters()):,}")
12. 常见问题解答
Q1: Transformer 为什么比 RNN 快?
答:
RNN 的处理方式(串行):
时间1: 处理词1 → 隐藏状态1
时间2: 处理词2 + 隐藏状态1 → 隐藏状态2
时间3: 处理词3 + 隐藏状态2 → 隐藏状态3
...
必须等前一步完成才能进行下一步,无法并行!
Transformer 的处理方式(并行):
同时处理所有词:
词1, 词2, 词3, ..., 词N
全部同时计算注意力!
速度对比:
- RNN:O(n) 时间复杂度(n = 序列长度)
- Transformer:O(1) 时间复杂度(理论上,GPU 并行)
- 实际提速:10-100 倍!
Q2: Self-Attention 的计算复杂度是多少?
答:
Self-Attention 的复杂度:O(n²·d)
其中:
- n = 序列长度
- d = 词向量维度
计算量分解:
1. Q·K^T:[n, d] × [d, n] = [n, n] → O(n²·d)
2. Softmax:对 n×n 矩阵 → O(n²)
3. 注意力·V:[n, n] × [n, d] = [n, d] → O(n²·d)
总复杂度:O(n²·d)
问题:
当序列很长时(n > 1000),计算量会非常大!
解决方案:
- Sparse Attention(稀疏注意力)
- Linformer:降到 O(n·d)
- Longformer:O(n·w·d),w 是窗口大小
Q3: 为什么要除以 √d_k?
答:
原因:防止点积结果过大
数学解释:
假设 Q 和 K 的元素是均值0、方差1的随机变量
那么 Q·K^T 的方差 = d_k(维度)
例如:
d_k = 64,点积的方差 = 64
d_k = 512,点积的方差 = 512
如果不缩放:
点积结果 = [-50, -30, 10, 45, 80, ...](差距很大)
Softmax 后 ≈ [0, 0, 0, 0, 1](几乎所有概率集中在一个词上)
→ 梯度很小(梯度消失)
除以 √d_k 后:
点积结果 = [-7, -4, 1, 6, 11, ...](方差恢复到1)
Softmax 后 ≈ [0.05, 0.1, 0.2, 0.3, 0.35](概率分布平滑)
→ 梯度正常
Q4: Transformer 能处理多长的序列?
答:
理论上:无限长(位置编码可以扩展)
实际上:受限于显存
原因:注意力矩阵是 n×n 的
- 序列长度 512:需要 512×512 = 262K 个元素
- 序列长度 1024:需要 1024×1024 = 1M 个元素
- 序列长度 4096:需要 4096×4096 = 16M 个元素
显存占用(float32):
- 512 长度:~1 MB
- 1024 长度:~4 MB
- 2048 长度:~16 MB
- 4096 长度:~64 MB(仅注意力矩阵!)
解决方案:
- 截断长文本
- 滑动窗口(只关注局部)
- 稀疏注意力(不是所有词都相互关注)
- Flash Attention(优化显存占用)
Q5: Transformer 能处理图像吗?
答:
可以!Vision Transformer (ViT)
方法:把图像分割成小块(patch)
例如:224×224 的图像
1. 分成 16×16 的小块
2. 得到 (224/16) × (224/16) = 14×14 = 196 个 patch
3. 每个 patch 看作一个"词"
4. 输入到 Transformer
架构:
图像 → 分块 → 线性映射 → 位置编码 → Transformer → 分类
应用:
- ViT: 图像分类
- DALL-E: 文本生成图像
- Swin Transformer: 目标检测
Q6: 为什么 GPT 用 Decoder-Only,BERT 用 Encoder-Only?
答:
设计哲学不同:
【BERT(Encoder-Only)】
目标:理解文本
任务:分类、问答、实体识别
特点:
- 双向理解(可以看前后文)
- [MASK] 训练:预测被遮住的词
- 适合需要"完整理解"的任务
【GPT(Decoder-Only)】
目标:生成文本
任务:续写、对话、创作
特点:
- 单向生成(只能看前文)
- 自回归训练:预测下一个词
- 适合需要"逐步生成"的任务
类比:
BERT = 阅读理解(先读完整篇文章再回答)
GPT = 即兴创作(一边想一边说)
Q7: Transformer 的缺点是什么?
答:
1. 计算量大
- 注意力:O(n²)
- 长序列处理慢
2. 需要大量数据
- 参数多,容易过拟合
- BERT 用了 16GB 文本训练
- GPT-3 用了 45TB 文本!
3. 缺乏归纳偏置(Inductive Bias)
- CNN 有"局部性"假设
- RNN 有"时序性"假设
- Transformer 靠数据学习所有东西
→ 需要更多数据
4. 位置编码的局限
- 外推性差(训练时见过512长度,测试时1024可能不行)
- 相对位置不如绝对位置清晰
5. 不适合实时任务
- 自回归生成慢(逐词生成)
- 推理延迟高
Q8: 如何选择 Transformer 的超参数?
答:
常用配置参考:
【小模型(适合个人/实验)】
- d_model: 256-512
- num_heads: 4-8
- num_layers: 4-6
- d_ff: 1024-2048
- 参数量:~10M-50M
- 显存需求:4-8GB
【中等模型(BERT-Base 级别)】
- d_model: 768
- num_heads: 12
- num_layers: 12
- d_ff: 3072
- 参数量:~110M
- 显存需求:16GB+
【大模型(GPT-3 级别)】
- d_model: 12288
- num_heads: 96
- num_layers: 96
- d_ff: 49152
- 参数量:175B
- 显存需求:数百GB
经验法则:
1. num_heads 能整除 d_model
2. d_ff = 4 × d_model(通常)
3. 层数越多越好(但训练越慢)
4. 先小模型试验,再放大
🎓 总结
核心要点回顾
✅ Transformer 的三大创新:
- Self-Attention(自注意力):建立全局依赖
- 并行计算:训练速度提升 10-100 倍
- 可扩展性:模型越大,效果越好
✅ 核心组件:
- Multi-Head Attention:多角度理解文本
- Position Encoding:记住词的顺序
- Feed Forward Network:特征变换
- Residual + LayerNorm:稳定训练
✅ 三种架构:
- Encoder-Only(BERT):理解任务
- Decoder-Only(GPT):生成任务
- Encoder-Decoder(T5):翻译、摘要
✅ 应用领域:
- NLP:翻译、问答、对话、摘要
- CV:图像分类、目标检测、生成
- 音频:语音识别、音乐生成
- 生物:蛋白质结构预测
学习路径建议
第1阶段:理论基础(1-2周)
├─ 理解注意力机制
├─ 掌握 Transformer 架构
└─ 阅读原论文 "Attention is All You Need"
第2阶段:代码实践(2-3周)
├─ 从零实现 Self-Attention
├─ 实现完整的 Transformer
└─ 用 PyTorch 训练简单模型
第3阶段:应用开发(4-8周)
├─ 使用预训练模型(BERT、GPT)
├─ Fine-tune 到具体任务
└─ 部署到生产环境
第4阶段:深入研究(持续)
├─ 阅读最新论文
├─ 研究变种模型(Linformer、Reformer)
└─ 参与开源项目
推荐资源
论文
- Attention is All You Need (2017) - 原始 Transformer 论文
- BERT (2018) - Google 的里程碑
- GPT-2/GPT-3 (2019/2020) - OpenAI 的突破
- Vision Transformer (2020) - Transformer 用于图像
教程
- The Illustrated Transformer - Jay Alammar(强烈推荐!)
- Harvard NLP Annotated Transformer - 带注释的代码
- d2l.ai - 动手学深度学习(中文)
- Hugging Face Course - 实战教程
代码库
- Hugging Face Transformers - 最流行的预训练模型库
- Fairseq - Facebook 的序列建模工具
- Tensor2Tensor - Google 的深度学习库
🎉祝你天天开心!!🎉
点关注,不迷路,谢谢大家!
最后更新:2025年11月
作者:Echo
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/m0_74779543/article/details/155039168



