
8:Transformer
🎯 欢迎进入AI的"核心反应堆"!
恭喜你!🎉 经过前面所有题目的历练,你已经从AI小白成功进阶。现在,是时候挑战现代AI的核心引擎——Transformer了!
这不仅仅是一道题目,更是一次从入门到精通的华丽蜕变!从GPT到ChatGPT,从BERT到Stable Diffusion,几乎所有震撼世界的AI模型都离不开这个神奇的架构。
🚀 激动人心的事实: 你即将亲手实现支撑整个大语言模型时代的核心技术!
📚 学习目标
✅ 深入理解 Transformer架构的设计哲学 ✅ 掌握注意力机制的数学原理 ✅ MiniGPT实战:理论转化为代码能力 ✅ 培养独立研究论文的能力
💡 学习建议
理论先行: 在写代码之前,确保脑海中有清晰的架构图
循序渐进: 从单头注意力到多头,从简单到复杂
善用调试: 每一个tensor的shape变化都要心中有数
对比分析: 调参数,看变化,做一个"超参数猎人"
延伸思考: 想想如何让你的模型更强更快
⚠️ 重要提醒: 虽然这题很硬核,但完成后你将从入门直达精通!这可能是你编程生涯中最有成就感的时刻之一!同时也是面试中的重点题目喔~
🔥 第一部分:理论基础问答
Attention is all you need!
在深入代码之前,让我们先"拷问"一下Transformer的灵魂!
** 必答题清单:**
Transformer的特点是什么?主要解决什么问题?缺点又是什么?
- 🔍 任务: 在原论文中找到对应位置并标注
Layer Norm vs Batch Norm
- 🤔 这两个"标准化兄弟"有什么恩怨情仇?
位置编码 (Positional Encoding)
深度解析: 什么是位置编码?
Transformer用了怎样的位置编码?
这种设计的优势在哪里?
Encoder & Decoder 架构解析
画图 + 文字: 详细说明两个模块的结构
数据是如何在其中流动的?
🎯 面试重点: 务必搞懂每个细节!
注意力机制核心问题
K, Q, V分别代表什么?如何变化?
Q×K^T的数学内涵是什么?
Scaled Dot-Product Attention vs Additive Attention
自注意力机制的"自"
- 这个"自"字体现在哪里?为什么这么设计?
可学习参数分析
注意力机制中有需要学习的参数吗?
多头注意力中呢?
我们希望这些参数学到什么?
数据流动全解析
文本输入后,各个维度是如何变化的?
从token到embedding到输出的完整过程
BLEU评估指标
- BLEU是什么?在NLP中扮演什么角色?
第二部分:MiniGPT实战挑战
"纸上得来终觉浅,绝知此事要躬行"
准备好撸起袖子,手搓一个迷你版GPT了吗?
🎮 实战环境准备
硬件需求: 强烈建议使用云端GPU(参考第0题白嫖攻略)
工具: Jupyter Lab
1️⃣ 数据集获取
tinyshakespeare数据集:
wget https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txtwget https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt💡 偷懒指南: 数据集很小,直接复制粘贴到txt文件也OK!
2️⃣ 超参数配置
import torch
import torch.nn as nn
from torch.nn import functional as F
import matplotlib.pyplot as plt
## 超参设置(每个参数都有讲究!)
batch_size = 16 # 批处理大小
block_size = 32 # 上下文长度
max_iters = 5000 # 训练轮数
learning_rate = 1e-3 # 学习率
device = 'cuda' if torch.cuda.is_available() else 'cpu'
n_embd = 64 # embedding维度
n_head = 4 # 多头注意力头数
n_layer = 4 # transformer层数
dropout = 0.0 # dropout率
torch.manual_seed(42) # 随机种子,保证可复现import torch
import torch.nn as nn
from torch.nn import functional as F
import matplotlib.pyplot as plt
## 超参设置(每个参数都有讲究!)
batch_size = 16 # 批处理大小
block_size = 32 # 上下文长度
max_iters = 5000 # 训练轮数
learning_rate = 1e-3 # 学习率
device = 'cuda' if torch.cuda.is_available() else 'cpu'
n_embd = 64 # embedding维度
n_head = 4 # 多头注意力头数
n_layer = 4 # transformer层数
dropout = 0.0 # dropout率
torch.manual_seed(42) # 随机种子,保证可复现3️⃣ 数据预处理
# 读取莎士比亚文本
with open('input.txt', 'r', encoding='utf-8') as f:
text = f.read()
print(f"数据集总字符数: {len(text):,}")
print(f"前300个字符预览:\n{text[:300]}")
# 构建词汇表
chars = sorted(list(set(text)))
vocab_size = len(chars)
print(f"词汇表: {''.join(chars)}")
print(f"词汇表大小: {vocab_size}")
# 简单tokenizer实现
stoi = {ch: i for i, ch in enumerate(chars)}
itos = {i: ch for i, ch in enumerate(chars)}
encode = lambda s: [stoi[c] for c in s]
decode = lambda l: ''.join([itos[i] for i in l])
# 测试编解码
print(f"编码测试: {encode('Hello World!')}")
print(f"解码测试: {decode(encode('Hello World!'))}")# 读取莎士比亚文本
with open('input.txt', 'r', encoding='utf-8') as f:
text = f.read()
print(f"数据集总字符数: {len(text):,}")
print(f"前300个字符预览:\n{text[:300]}")
# 构建词汇表
chars = sorted(list(set(text)))
vocab_size = len(chars)
print(f"词汇表: {''.join(chars)}")
print(f"词汇表大小: {vocab_size}")
# 简单tokenizer实现
stoi = {ch: i for i, ch in enumerate(chars)}
itos = {i: ch for i, ch in enumerate(chars)}
encode = lambda s: [stoi[c] for c in s]
decode = lambda l: ''.join([itos[i] for i in l])
# 测试编解码
print(f"编码测试: {encode('Hello World!')}")
print(f"解码测试: {decode(encode('Hello World!'))}")4️⃣ 数据分批处理
def get_batch(split):
"""
生成训练批次数据
你需要实现:
1. 从训练/验证集中随机采样
2. 生成输入序列x和目标序列y
3. 返回正确维度的tensor
"""
###################
# 你的代码在这里
###################
passdef get_batch(split):
"""
生成训练批次数据
你需要实现:
1. 从训练/验证集中随机采样
2. 生成输入序列x和目标序列y
3. 返回正确维度的tensor
"""
###################
# 你的代码在这里
###################
pass5️⃣ 模型架构实现
🔥 这是整个项目的核心!必须手工实现!
class Head(nn.Module):
"""单头自注意力"""
def __init__(self, head_size):
super().__init__()
# QKV投影矩阵
self.key = nn.Linear(n_embd, head_size, bias=False)
self.query = nn.Linear(n_embd, head_size, bias=False)
self.value = nn.Linear(n_embd, head_size, bias=False)
# 下三角mask矩阵(防止看到未来)
self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))
self.dropout = nn.Dropout(dropout)
def forward(self, x):
B, T, C = x.shape
###################
# 注意力计算逻辑
# 1. 计算Q, K, V
# 2. 计算注意力分数
# 3. 应用mask
# 4. 加权求和
###################
pass
class MultiHeadAttention(nn.Module):
"""多头注意力:并行处理多个注意力头"""
def __init__(self, num_heads, head_size):
super().__init__()
self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
self.proj = nn.Linear(n_embd, n_embd)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
###################
# 多头并行计算与合并
###################
pass
class FeedForward(nn.Module):
"""前馈神经网络:简单而强大"""
###################
# 实现两层线性变换+激活
###################
pass
class Block(nn.Module):
"""Transformer Block:communication + computation"""
###################
# 组装完整的transformer块
# LayerNorm + MultiHeadAttention + LayerNorm + FeedForward
###################
pass
class BigramLanguageModel(nn.Module):
"""完整的语言模型"""
def __init__(self):
super().__init__()
# embedding表
self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
self.position_embedding_table = nn.Embedding(block_size, n_embd)
# 堆叠transformer块
self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
self.ln_f = nn.LayerNorm(n_embd)
self.lm_head = nn.Linear(n_embd, vocab_size)
def forward(self, idx, targets=None):
###################
# 完整前向传播
# 1. token + position embedding
# 2. 通过transformer块
# 3. 输出预测
# 4. 计算损失(如果有target)
###################
pass
def generate(self, idx, max_new_tokens):
"""文本生成:让AI说话!"""
for _ in range(max_new_tokens):
# 裁剪到block_size
idx_cond = idx[:, -block_size:]
# 预测下一个token
logits, _ = self(idx_cond)
logits = logits[:, -1, :] # 只要最后一个时间步
probs = F.softmax(logits, dim=-1)
# 采样下一个token
idx_next = torch.multinomial(probs, num_samples=1)
idx = torch.cat((idx, idx_next), dim=1)
return idxclass Head(nn.Module):
"""单头自注意力"""
def __init__(self, head_size):
super().__init__()
# QKV投影矩阵
self.key = nn.Linear(n_embd, head_size, bias=False)
self.query = nn.Linear(n_embd, head_size, bias=False)
self.value = nn.Linear(n_embd, head_size, bias=False)
# 下三角mask矩阵(防止看到未来)
self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))
self.dropout = nn.Dropout(dropout)
def forward(self, x):
B, T, C = x.shape
###################
# 注意力计算逻辑
# 1. 计算Q, K, V
# 2. 计算注意力分数
# 3. 应用mask
# 4. 加权求和
###################
pass
class MultiHeadAttention(nn.Module):
"""多头注意力:并行处理多个注意力头"""
def __init__(self, num_heads, head_size):
super().__init__()
self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
self.proj = nn.Linear(n_embd, n_embd)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
###################
# 多头并行计算与合并
###################
pass
class FeedForward(nn.Module):
"""前馈神经网络:简单而强大"""
###################
# 实现两层线性变换+激活
###################
pass
class Block(nn.Module):
"""Transformer Block:communication + computation"""
###################
# 组装完整的transformer块
# LayerNorm + MultiHeadAttention + LayerNorm + FeedForward
###################
pass
class BigramLanguageModel(nn.Module):
"""完整的语言模型"""
def __init__(self):
super().__init__()
# embedding表
self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
self.position_embedding_table = nn.Embedding(block_size, n_embd)
# 堆叠transformer块
self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
self.ln_f = nn.LayerNorm(n_embd)
self.lm_head = nn.Linear(n_embd, vocab_size)
def forward(self, idx, targets=None):
###################
# 完整前向传播
# 1. token + position embedding
# 2. 通过transformer块
# 3. 输出预测
# 4. 计算损失(如果有target)
###################
pass
def generate(self, idx, max_new_tokens):
"""文本生成:让AI说话!"""
for _ in range(max_new_tokens):
# 裁剪到block_size
idx_cond = idx[:, -block_size:]
# 预测下一个token
logits, _ = self(idx_cond)
logits = logits[:, -1, :] # 只要最后一个时间步
probs = F.softmax(logits, dim=-1)
# 采样下一个token
idx_next = torch.multinomial(probs, num_samples=1)
idx = torch.cat((idx, idx_next), dim=1)
return idx6️⃣ 模型评估 📊
@torch.no_grad()
def estimate_loss():
"""评估训练和验证损失"""
out = {}
model.eval()
###################
# 计算平均损失
###################
model.train()
return out@torch.no_grad()
def estimate_loss():
"""评估训练和验证损失"""
out = {}
model.eval()
###################
# 计算平均损失
###################
model.train()
return out7️⃣ 模型训练
# 实例化模型
model = BigramLanguageModel().to(device)
print(f"模型参数量: {sum(p.numel() for p in model.parameters())/1e6:.2f}M")
# 训练循环
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
train_losses, val_losses = [], []
for iter in range(max_iters):
###################
# 训练循环实现
# 1. 获取batch
# 2. 前向传播
# 3. 计算损失
# 4. 反向传播
# 5. 参数更新
# 6. 定期评估
###################
pass
# 绘制损失曲线
plt.figure(figsize=(10, 5))
plt.plot(train_losses, label='Train Loss', color='#FF6B6B')
plt.plot(val_losses, label='Validation Loss', color='#4ECDC4')
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.title('Training Progress: Loss Curves')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('loss_plot.png', dpi=300, bbox_inches='tight')
# 生成莎士比亚风格文本
print("AI莎士比亚开始创作...")
context = torch.zeros((1, 1), dtype=torch.long, device=device)
generated_text = decode(model.generate(context, max_new_tokens=2000)[0].tolist())
print(generated_text)# 实例化模型
model = BigramLanguageModel().to(device)
print(f"模型参数量: {sum(p.numel() for p in model.parameters())/1e6:.2f}M")
# 训练循环
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
train_losses, val_losses = [], []
for iter in range(max_iters):
###################
# 训练循环实现
# 1. 获取batch
# 2. 前向传播
# 3. 计算损失
# 4. 反向传播
# 5. 参数更新
# 6. 定期评估
###################
pass
# 绘制损失曲线
plt.figure(figsize=(10, 5))
plt.plot(train_losses, label='Train Loss', color='#FF6B6B')
plt.plot(val_losses, label='Validation Loss', color='#4ECDC4')
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.title('Training Progress: Loss Curves')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('loss_plot.png', dpi=300, bbox_inches='tight')
# 生成莎士比亚风格文本
print("AI莎士比亚开始创作...")
context = torch.zeros((1, 1), dtype=torch.long, device=device)
generated_text = decode(model.generate(context, max_new_tokens=2000)[0].tolist())
print(generated_text)8️⃣ 结果分析
回答以下问题:
Validation Loss收敛值是多少?
MiniGPT参数量统计(应该<1M)
显存占用情况(应该<1G)
9️⃣ 挑战性进阶问题
RoPE vs 正弦位置编码
为什么现代GPT使用Rotary Position Embedding?
尝试在MiniGPT中实现RoPE并对比性能!
LayerNorm位置之争
Pre-LN vs Post-LN:为什么前者更稳定?
修改代码实现Pre-LN架构并分析训练稳定性!
计算复杂度分析
当block_size增加到128时会发生什么?
尝试**梯度检查点(Gradient Checkpointing)**减少显存占用!
FlashAttention算法
- 了解这个让Transformer飞起来的神奇算法!
⚠️ 重要提醒
AI工具使用: 本题强烈建议纯手工实现!这可能是你最后一次有机会完全从零手撸这么核心的算法了,好好珍惜这个过程吧!
提交要求
提交内容
代码文件:
ml-8-姓名-学号.py理论答案:
ml-8-姓名-学号.md压缩包提交
提交方式
邮箱:
gimmerml401@163.com主题:
8-姓名-学号
出题人:百事可乐
QQ:2465800571