课程简介与 Transformer 基础回顾
做深度学习这几年,我调过不少模型,但真正让我头疼的,不是模型选型,也不是数据量不够,而是——同样的代码,跑两次,结果不一样。你是不是也遇到过?今天跑一个指标,明天跑就变了,调了半天超参数,最后发现是随机种子没固定。嗯,这节课我们就从最根本的问题聊起。
这门课叫《Transformer 超参数调优与批次可重复性实践》。说白了,就是两件事:一是把 Transformer 那些关键超参数怎么调、调成啥样才算稳,讲透;二是让你的实验 每次都能复现,别让运气决定论文能不能中。
1.1 为什么超参数调优和可重复性这么重要?
我入行第三年带过一个项目,要做文本摘要。模型选的是 BART,数据量不大,但每次训练完 BLEU 分数能差 2 个点。你说这怎么调?后来我花了整整两周,把随机种子、DataLoader shuffle 顺序、CUDA 确定性都锁死了,结果稳定了,才发现之前以为是调参的问题,其实全是随机性在作怪。
超参数调优本身就够难了——学习率、batch size、warmup steps、dropout……哪个不是牵一发动全身?如果连实验本身都不能重复,那我们调出来的“最优参数”到底是真靠谱,还是碰运气?
所以这门课的核心目标就两个:
- 给出 Transformer 常见超参数的调优指南和实用策略
- 手把手教你怎么在 PyTorch 和 TensorFlow 里实现批次级别的可重复性
1.2 Transformer 核心组件速览
先快速过一下 Transformer 的基本结构。假设你已经了解自注意力机制,这里只捡重点说,后面章节会深入每个模块的超参数影响。
自注意力(Self-Attention)
Transformer 最革命的地方就是把 RNN 的时序依赖换成了注意力矩阵。给定一个序列 X ∈ ℝ^(n×d),我们做三个线性投影得到 Q、K、V,然后算注意力分数:
Attention(Q,K,V) = softmax( Q K^T / sqrt(d_k) ) V
这里 d_k 是每个头的维度。注意那个 sqrt(d_k) —— 我刚开始用的时候少写了它,结果训练直接崩了,梯度爆炸。就因为 softmax 输入太大,概率分布几乎成了 one-hot。这小细节,坑过不少人。
多头注意力(Multi-Head Attention)
不只用一组 QKV,而是并行做 h 组,每组有独立的权重矩阵。然后把 h 个头的输出拼起来再投影一次。为什么用多头?说白了就是让模型从不同子空间去关注信息,类似 CNN 多个卷积核的思路。
关键超参数: 头数 h、每头维度 d_k。总维度 d_model = h × d_k。这里有个常见误区——d_k 太小,每个头学不到东西;d_k 太大,又容易过拟合。我在一个 8 头 512 维的模型上试过,把 d_k 从 64 缩到 32,效果反而更好,因为每个头被迫学更紧凑的表示。
位置编码(Positional Encoding)
自注意力本身是置换等变的——它不知道词序。所以必须加位置信息。Transformer 原文用正余弦函数,后来大家都学可学习的位置嵌入。注意:位置编码的维度要和 d_model 一致,直接相加。
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
这个公式里 10000 是基频,我调过一个文本生成任务,把基频改成 5000,结果长序列的生成质量反而下降了。嗯,不是所有参数都适合乱改。
前馈网络(Feed-Forward Network)
每个注意力层后面接一个两层 MLP,中间用 ReLU(现在更流行 GELU)。隐藏层维度通常设为 d_ff = 4 × d_model。有人问是不是越大越好?我试过 8 倍,模型直接变慢了 2 倍,指标没涨。这个比率是经验值,但别随便翻倍。
层归一化与残差连接
每个子层输出做 LayerNorm(x + Sublayer(x))。残差连接让梯度能流得更深,层归一化稳定训练。自从 Pre-LN(先归一化再进子层)被提出后,训练稳定性好了一大截。我在一个 6 层 Transformer 上对比过,Post-LN 要小心学习率,Pre-LN 可以直接上 1e-3。
1.3 一张图看懂 Transformer 架构
下面这张 SVG 图帮你把整体结构串起来。左边是编码器,右边是解码器,核心组件一目了然。
1.4 训练中的痛点:随机性与不确定性
有了上面的结构基础,我们回到最开始的痛点。训练 Transformer 时,随机性来源太多了:
- 模型初始化:权重参数随机
- Dropout:训练时随机丢弃
- 数据加载:DataLoader shuffle 顺序
- CUDA 非确定性算法:某些操作(如反向传播的原子加法)在 GPU 上不是确定性的
- 硬件扰动:浮点运算的舍入差异
我曾经在 Reproduce 一个论文结果时,发现对方只给了 seed=42,但我的机器用了 A100,他用 V100,结果就差了 0.5 个点。折腾了三天才定位到是 torch.backends.cudnn.deterministic=False 导致的。
torch.manual_seed(seed)torch.cuda.manual_seed_all(seed)torch.backends.cudnn.deterministic = Truetorch.use_deterministic_algorithms(True)注意:确定性算法会牺牲一点性能,但换来可重复性,很值。
1.5 课程内容速览
把后面的路标也给你列举一下,方便你心里有数:
| 章节范围 | 核心主题 |
|---|---|
| 2–5 章 | 学习率、batch size、warmup、dropout 详解 |
| 6–10 章 | 梯度累积、混合精度、权重衰减 |
| 11–15 章 | 批次可重复性原理与 PyTorch/TensorFlow 实现 |
| 16–20 章 | 超参数搜索策略(网格、随机、贝叶斯) |
| 21–25 章 | 实验管理(MLflow、Weights & Biases) |
| 26–30 章 | 生产环境复现与 CI/CD 集成 |
好,这一章算是热身。后面我们就要开始动手了——先搞定 PyTorch 里的随机种子管理,再调那一堆看着眼晕的超参数。做好准备,调参路上不迷路。