学习率 5 档 × 轮数 4 档扫一遍: 0.01 要 800 轮才磨到下限;1.50 直接卡在 0.934 来回跳,比「全猜一半」的 0.6931 还差。 最优不是「损失最小」,是最快到达下限还不震荡——0.20 的 50 轮。 注意那个下限 0.4212 不是 0: 标签本身有噪声。
上一节把数据传上去了。这一节是动手前的最后一步:三个超参数怎么定,以及为什么它们不能分开调。
一、两个参数是耦合的
学习率决定「每步走多远」,轮数决定「走多少步」。 同样的距离,步子大就少走几步,步子小就多走几步——它们本来就是一个东西的两面。 下面这张表是 5 档学习率 × 4 档轮数的验证损失(越小越好):
| 学习率 | 50 轮 | 150 轮 | 400 轮 | 800 轮 |
|---|---|---|---|---|
| 0.01 | 0.4643 | 0.4275 | 0.4213 | 0.4212 |
| 0.05 | 0.4223 | 0.4212 | 0.4213 | 0.4213 |
| 0.20 | 0.4213 | 0.4213 | 0.4213 | 0.4213 |
| 0.60 | 0.4213 | 0.4213 | 0.4213 | 0.4213 |
| 1.50 | 0.9343 | 0.9344 | 0.9344 | 0.9344 |
| 读法: | ||||
| 0.01 那一行要 800 轮才追上下限;0.20 和 0.60 在 50 轮就到了;1.50 那一行永远到不了。 | ||||
| 这就是耦合——同一份数据,学习率放大一档,到达下限所需的轮数就少一截。 | ||||
| 所以分开调是无效的:你把学习率定在 0.05,扫出来的「最优轮数」是 150; | ||||
| 再拿 150 回去扫学习率,最优变成 0.2;再拿 0.2 去扫轮数,最优变成 50。 | ||||
| 每一轮都在不同的坐标上找最优点,永远不会收敛。正确做法是网格一起扫。 |
二、学习率太大,为什么会「停在半路」
1.50 那一行最值得看:四档轮数全在 0.934 附近,既不降也不炸,就那么卡着。 这比发散更难理解。 原因是它进入了一个极限环:参数在最优解两侧来回跳,每一步都跨过头,下一步又跨回来。 轮数再多也只是跳更多圈,位置不变,所以损失不变。 更糟的是 0.9344 比「全猜一半」的 0.6931 还高。 意思是这个模型学得还不如不学——而它的损失曲线会是「平的一条」,看起来像「已经收敛」。 这就是为什么要盯验证损失、还要跟基线比。 只跟自己比,你会以为它训完了;跟瞎猜比,才知道它训坏了。 实战里的判断标准很简单: 学习率从 1e-5 起,按 3 倍往上试。微调的学习率通常比预训练小一个数量级—— 因为参数已经在好位置上了,步子太大会把它推出去。
三、下限不是 0,这是正常的
表里最好的格子是 0.4212,不是 0。很多人卡在这里,然后不断加轮数、加数据,想把损失压下去。 原因是标签里有噪声:Demo 造数据时给每个样本的标签加了随机扰动, 于是存在一个「再怎么学也降不下去」的下限。真实数据里的噪声来源更多——标注歧义、 样本本身模棱两可、特征不够。 所以正确的做法是先找到这个下限:用一个小学习率训足够多轮,看损失停在哪。 停住之后再加轮数,只是在拟合噪声。 这也是「早停」的意义所在:它的作用不是省钱,是防止把模型训成「只记得训练集」。 盯验证损失, 连续两三轮不降就停。
四、三个参数,按重要性排
① 轮数 epochs。 决定「看几遍数据」。微调最常见的是 3 到 5 轮。 ② 学习率 lr。 决定「每步走多远」。微调通常用预训练的十分之一。 ③ 批大小 batch。 决定「一次看几条」。 主要影响显存占用和梯度稳定性,对最终效果的影响比前两个小。 三个参数之外还有一个不用设但必须有的:早停的耐心值。通常设 2 到 3, 意思是连续两三轮的验证损失没改善就停。 最后一条经验:第一版别调参。用默认的 3 轮、平台推荐的学习率先跑一版, 拿到验证损失和抽检结果再决定往哪个方向调。没有基线就开始扫参,扫出来的「最优」没有参照物。
五、动手 Demo:学习率 1.50 卡在 0.934:比全猜一半还差
造 400 条带标签噪声的二维分类数据,扫 5 档学习率 × 4 档轮数,每格跑一次全量梯度下降, 记验证集的对数损失。再拿「全猜一半」的 0.6931 当基线,看哪些格子比基线还差。
X = rg.normal(0, 1, (n, 3)) * 3.0 # 特征量纲放大,压低学习率上限
y = ((X[:, 0] + 0.6 * X[:, 1] + rg.normal(0, 3.0, n)) > 0).astype(int)
for lr in (0.01, 0.05, 0.2, 0.6, 1.5):
for ep in (50, 150, 400, 800):
W, b = np.zeros((3, 2)), np.zeros(2)
for _ in range(ep):
p = softmax(Xtr @ W + b)
g = (p - np.eye(2)[ytr]) / len(ytr)
W -= lr * (Xtr.T @ g)
b -= lr * g.sum(0)
pv = softmax(Xva @ W + b)
z = pv[np.arange(len(yva)), yva]
loss = -np.mean(np.log(np.clip(z, 1e-12, 1.0)))
实跑输出:
--------------------------------------------------------------------
超参数:学习率和轮数合起来扫,别分开调
--------------------------------------------------------------------
学习率 50 轮 150 轮 400 轮 800 轮
-----------------------------------------
0.01 0.4643 0.4275 0.4213 0.4212
0.05 0.4223 0.4212 0.4213 0.4213
0.20 0.4213 0.4213 0.4213 0.4213
0.60 0.4213 0.4213 0.4213 0.4213
1.50 0.9343 0.9344 0.9344 0.9344
表里是**验证集损失**,越小越好。全猜一半的损失是 0.6931,
比这个数还高的格子,等于学得还不如瞎猜。看三个方向:
**学习率太小(0.01)**:50 轮才走到 0.4643,得靠 800 轮慢慢磨。
**学习率太大(1.50)**:四档全卡在 0.93 附近不动——它在最优解两侧来回跳,
一步就跨过头,轮数再多也回不去,还比瞎猜更差。
**刚刚好的那一档**:学习率 0.20、50 轮就到了下限 0.4212。
这就是为什么**两个参数要一起扫**:学习率放大一档、到达下限的轮数就少一截,
两个数是耦合的。**分开调的结果,是在「学习率最好那档」和「轮数最好那档」
之间来回跳,永远不收敛。**
另外注意下限是 0.4212 而不是 0:**标签本身有噪声,损失降不到 0 是正常的**,
盯着「怎么还没降到 0」去加轮数,只会把模型训成记住噪声。
三个参数的实际含义,按重要性排:
① 轮数 epochs 决定「看几遍数据」。最常见的是 3 到 5 轮
② 学习率 lr 决定「每步走多远」。微调通常用预训练的十分之一
③ 批大小 batch 决定「一次看几条」。主要影响显存和稳定性
**微调的学习率一定要比预训练小**——参数已经在好位置上了,
步长太大就会把它推出去,那就是上一节说的灾难性遗忘。
还有一个必须设的参数:**早停**。
盯验证损失,连续两三轮不降就停。**它的作用不是省钱,是防止把模型训成「只记得训练集」。**
done -> _out_39g.txt
表里 20 个格子,三种形态:左下角(0.01 那一行)慢慢磨、中间三行早早到位、1.50 那一行整体卡死。 0.01 的 50 轮是 0.4643,800 轮才到 0.4212。它没有学错,只是走得慢。这种情况多给轮数就能救。 1.50 的四格是 0.9343 到 0.9344,几乎一模一样。这就是极限环的特征:轮数加多少都在原地跳。 这种情况加轮数救不了,只能调小学习率。 0.9344 比 0.6931(全猜一半)还高,这一条最重要。 「损失不降」和「损失很高」是两个不同的病:前者是学习率太小或轮数不够,后者是步长失控。 最后那个 0.4212 的下限也值得记:标签里有噪声时,损失有地板。盯着「为什么降不到 0」去加轮数, 只会把模型训成记住噪声。
30 秒实验
- 把学习率那一列再加一档 0.03,看它落在「慢慢磨」和「早早到位」之间的哪里。 学习率的有效区间往往只有一个数量级宽。
- 把标签噪声去掉(
y直接由特征线性决定),再看下限变成多少。 没有噪声时损失能压到很低——这就证明了 0.4212 是噪声的地板。 - 把轮数上限从 800 改到 2000,看 1.50 那一行会不会动。极限环不会因为圈数变多而离开原地。
常见误区
- 「先调学习率,再调轮数」。两者耦合,分开调会在两个坐标之间来回跳。必须网格一起扫。
- 「验证损失越低越好,压到 0 才停」。标签有噪声时损失有下限,继续训就是在拟合噪声。 先找到地板。
- 「损失不降就加轮数」。如果它是卡在极限环上,加轮数完全无效。先看曲线是「慢」还是「平」。
自测三问(点开对答案)
1. 为什么学习率和轮数不能分开调? —— 因为两者耦合:学习率放大一档,到达下限的轮数就少一截。 分开调会在两个坐标之间来回跳,永远不收敛。 2. 学习率太大时,为什么损失会「停住不动」? —— 因为进了极限环:参数在最优解两侧来回跳,每步都跨过头,位置不变所以损失不变。加轮数也没用。 3. 验证损失停在 0.42 降不下去,说明什么? —— 说明碰到了噪声地板。继续加轮数只会拟合噪声,该做的是检查标签质量或补特征。第五章到这里结束:从「微调是什么」到「超参怎么定」,一条线走完了。 第六章换一个方向——不用云端平台,在自己电脑上把语言模型跑起来。
- ☐ (本章完,下一节 6.1.1 配置文本嵌入模型)