关注

Transformer 小白指南:从零开始理解注意力机制(Attention Is All you Need)

Transformer 小白指南:从零开始理解注意力机制

📚 专为零基础小白打造的 Transformer 教程

🎯 目标:用最简单的语言,讲清楚最前沿的深度学习技术

⚡ Transformer 是什么?它是当今 AI 领域最重要的技术,支撑着 ChatGPT、GPT-4、BERT、图像生成等几乎所有最先进的 AI 应用

📅 最后更新:2025年11月


📋 目录


1. 为什么要学习 Transformer?

1.1 Transformer 改变了世界

如果你关注过近几年的 AI 新闻,你会发现几乎所有震撼世界的 AI 应用都基于 Transformer:

应用基于的技术发布时间
ChatGPTGPT-3.5/GPT-4 (Transformer)2022年11月
DALL-E 2Transformer + Diffusion2022年4月
MidjourneyTransformer 架构2022年
GitHub CopilotCodex (Transformer)2021年
Google 翻译Transformer2017年后
BERT(搜索引擎)Transformer2018年
AlphaFold 2改进的 Transformer2020年

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)

为什么用三角函数?

  1. 周期性:sin/cos 有周期性,可以表示任意长度的序列
  2. 相对位置:位置 k 和 k+Δ 之间的编码可以通过线性变换得到
  3. 不需要训练:直接计算,不占用参数
可视化位置编码
位置编码的模式(热力图):

位置     维度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 模型对比

模型架构训练方式适合任务代表应用
BERTEncoder-OnlyMasked LM理解(分类、问答)Google搜索
GPTDecoder-Only自回归 LM生成(续写、对话)ChatGPT
T5Encoder-DecoderText-to-Text通用(所有NLP任务)Google Translate
BARTEncoder-DecoderDenoising摘要、翻译文本摘要

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 的三大创新

  1. Self-Attention(自注意力):建立全局依赖
  2. 并行计算:训练速度提升 10-100 倍
  3. 可扩展性:模型越大,效果越好

核心组件

  • 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)
└─ 参与开源项目

推荐资源

论文
  1. Attention is All You Need (2017) - 原始 Transformer 论文
  2. BERT (2018) - Google 的里程碑
  3. GPT-2/GPT-3 (2019/2020) - OpenAI 的突破
  4. 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

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--