课程简介与 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 图帮你把整体结构串起来。左边是编码器,右边是解码器,核心组件一目了然。

Transformer 架构核心组件 输入 Embedding 位置编码 多头自注意力 h 个头 残差 + LayerNorm 前馈网络 FFN 编码器输出 编码器 × N 层 输出 Embedding 位置编码 Masked 自注意力 防止看到未来 残差 + LayerNorm 交叉注意力 Q 来自解码器 K, V 残差 + LayerNorm FFN 线性 + Softmax 解码器 × N 层 左:编码器,提取输入表示 右:解码器,自回归生成输出 虚线:交叉注意力的 K,V 来自编码器

1.4 训练中的痛点:随机性与不确定性

有了上面的结构基础,我们回到最开始的痛点。训练 Transformer 时,随机性来源太多了:

  • 模型初始化:权重参数随机
  • Dropout:训练时随机丢弃
  • 数据加载:DataLoader shuffle 顺序
  • CUDA 非确定性算法:某些操作(如反向传播的原子加法)在 GPU 上不是确定性的
  • 硬件扰动:浮点运算的舍入差异

我曾经在 Reproduce 一个论文结果时,发现对方只给了 seed=42,但我的机器用了 A100,他用 V100,结果就差了 0.5 个点。折腾了三天才定位到是 torch.backends.cudnn.deterministic=False 导致的。

小技巧: 在 PyTorch 里,固定随机性的标准做法是设置:
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
torch.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 集成
警告: 可重复性不是万能的。如果模型本身对随机种子敏感(比如某些 GAN 或强化学习),你在固定随机性后仍可能出现不稳定。但我可以保证,固定了上面提到的 4 个地方,95% 的论文结果都能跑回来。

好,这一章算是热身。后面我们就要开始动手了——先搞定 PyTorch 里的随机种子管理,再调那一堆看着眼晕的超参数。做好准备,调参路上不迷路。