19. 第 4/5 轮的最终读数:预测头有效,大模型(8.7M)训崩了

★★★ 7.4k 字 · 源文件 PROJECT.md 第 3473 行起 · ruler arch read external

#19.1 三个跑次的对照(同一预算:1000 步 × batch 128 = 205 万刻)

跑次参数量预测头正确率认字d全字集空白屏门(该开/该关)
s2(旧基线,B16×2500)1.31M无15.6%55.2%1.58—0.0%—
g_small(B128)1.31M无52.1%77.1%0.7554.2%0.0%0.13/0.23
p_small(B128)1.31M有62.5%79.2%0.4751.0%2.1%0.11/0.24
g_big(B128)8.71M无0.0%0.0%3.300.0%0.0%—/0.00

两条独立结论

#① ★★★ 把 batch 从 16 提到 128:15.6% -> 52.1%(3.3 倍)

总刻数还更多(205 万 vs 64 万)。也就是说:

§16 那些读数全都在"喂不饱"的区间里测的 —— 之前一半的算力是浪费的。

★ 门的时机也第一次真正分开了(该开 0.13 / 该关 0.23,s2 那会儿是 0.85/0.94)。

#② ★★★ 自监督预测头:52.1% -> 62.5%(+10.4pp,相对 +20%)

g_small(无预测头)p_small(有)
正确率52.1%62.5%
认字77.1%79.2%
平均距离 d0.750.47
预测误差 curve—1.0 -> 0.476

★★ "自主学习能力"第一次转化成了可测的更强表现。 而且它不是用别的东西换来的:认字、距离、门时机全部同时变好。

读法:预测下一帧迫使循环核把"世界/我正在打什么"压进状态, 于是认字任务本身也受益 —— 两份目标共享同一个状态。

#19.2 ⚠️⚠️ g_big(8.7M 参数)训练到一半静默崩溃

步正确率认字entemit门
60031.2%76.0%0.820.500.46/0.56
7002.1%14.6%1.400.770.93/0.71
8000.0%1.0%0.000.00nan/0.00
9000.0%1.0%0.000.00nan/0.00

ent=0.00 且 emit=0.00 且 loss 打印成 +0.000。全程没有任何报错。

★★ 所以 §15.4 那条猜想"加参数是正收益"只说对了一半:

"能跑得动"不等于"能训得动"。 §15.2 那张表量的是每步耗时(前向+反传的墙钟),它说明算力有富余; 但它没有量训练稳定性。8.7M 参数在 lr 2e-3 下崩了。

已加 NaN/Inf 闸门(exp31.update() 里):参数或 loss 出现 NaN/Inf 立刻抛异常, 并在错误信息里带上三条最可能的原因。现场指纹写进了注释: ent=0 且 emit=0 = 策略输出成了常数。

★ 为什么之前没有它:clip_grad_norm_ 不会发现参数已经变成 NaN —— 它只裁剪范数。而 NaN 参数会让后续 loss 全变 NaN,打印出来像"收敛到 0"。 这类"看起来像好事"的失效最贵。

#19.3 待验证:崩溃是不是学习率太大

tools/run_H_bigLR.ps1:同配置,lr ∈ {2e-4, 5e-4, 1e-3, 2e-3},700 步 (停在崩溃点 ~750 之前,这样扫描便宜且可比),--clip 0.5。

判据(跑之前写死):

  • 若 2e-4 / 5e-4 不崩且分数接近 g_small -> 是 lr 问题,大模型可用,只要调 lr
  • 若全都崩 -> 不是 lr,回去查 4096 units / backbone 256 的结构问题

#19.4 ★★★★ 判据 5 的分母重测:纯模仿到 97.9% —— 问题被彻底重新定义

exp31_imitation_b128:配置与 g_small 完全相同,只把 β 全程钉在 1(纯模仿,无 RL)。

步正确率认字d无回显模仿对门(该开/该关)
30019.8%57.3%1.7822.9%71.5%0.13/0.48
40052.1%81.2%0.9239.6%90.0%—/—
50085.4%96.9%0.2679.2%99.3%0.06/0.23
60097.9%100.0%0.0397.9%99.3%0.00/0.22
70097.9%100.0%0.02100.0%100.0%0.00/0.23
800100.0%100.0%0.00100.0%100.0%0.00/0.22

(还在跑)

#★★★★ 这推翻了 §16.1,也推翻了 §19.4 的前一版结论

我先后以为实测
判据 5 的分母30.2%100.0%
RL 模型(p_small)62.5%62.5%
"判据 5 完成度"207%(过线)→ 73%(没过)62.5%(没过)

★★★★ "认字"这件事它已经全会了。

纯模仿:正确率 100%、认字 100%、关掉回显也 100%、平均距离 0.00、 门的时机 0.00/0.22(该开时不开、该关时关,完全正确)。

而且它连"拿屏幕当前缀内存"的毛病都没有 —— 无回显 100% 说明 --echo-drop 0.5 把它治好了,而且治得很彻底。

★ 真正坏掉的是 β: 1→0(放手)那一段。 它不是"学得慢",是在忘掉已经会的东西:从 100% 掉到 52~62%。

#这个重新定义为什么重要

之前几个月的工作都建立在"判据 5 = 撤掉老师后还剩 50%"这个框架上, 而那个框架默认了"纯模仿是不完整的、RL 要补上剩下的"。

现在数据显示反了:

text
纯模仿(β=1 全程):100%     ← 已经够了
放手(β:1→0)    :52~62%   ← RL 在破坏
只加监督 + 预测头 :62.5%    ← 比纯模仿差

★★ 所以当前唯一的关键问题不是"怎么让它学得更多", 而是"为什么一放手它就忘"。

§19.5 的 E1/E4 实验就是为这个问题设计的,而且现在是最高优先级。

#一条副产品:--echo-drop 0.5 的效果比 §16.3 里看到的强得多

§16.3 里 drop=0.5 时无回显 22.9%(对比 drop=0 的 2.1%),看着只是"修好了一点"。 现在在训练充分的条件下,drop=0.5 的模型无回显 100% —— 和回显开时完全一样。

★ 说明:"照抄屏幕"这个病不是治不好,而是当时训练不够。 §16.3 那个"权衡"(认字随 drop 下降)也应该在训练充分后重测 —— 当时的所有读数都还在"欠训练"区间里。

#19.4b ★★★ 真正的瓶颈浮出来了:不是认字,是字体泛化

把所有跑次并排(tools/compare_runs.py,这一版把「留出专项」列修出来了):

跑次β正确率认字d留出字体无回显预测误差空白屏
imitation_b1281.00100.0%100.0%0.0029.2%100.0%—0.0%
g_small0.0052.1%77.1%0.7520.8%50.0%—0.0%
p_small0.0062.5%79.2%0.4720.8%58.3%0.222.1%
p_big(8.7M)0.0851.0%80.2%1.1427.1%6.2%0.150.0%
s2_emitc15(旧)0.0015.6%55.2%1.58————
DE(最旧)0.0013.5%36.5%2.28————

#★★ 两件事同时成立

① 训练字符集已经饱和到 100%,但「留出字体」一直卡在 20~29%。

text
所有跑次:见过的字体  ~100%     (imitation_b128 就是 100.0%)
所有跑次:没见过的字体  20~29%   (楷体 / 等线 / 微软正黑)

★★ "认字"这个任务本身已经解决了。剩下的是"换一种字体还认不认得出"。

而且这条瓶颈不在编码器上 —— §12.9 里视觉预热(编码器端)的留出字体是 90.5%。 是策略端没把编码器的泛化能力用起来。 这才是下一步该攻的地方。

判据:--held-eval 就是为它准备的,只是我之前一直没把它当主指标看。

② p_big(8.7M)这一次没有崩,而且有两个亮点

  • 预测误差 0.15(p_small 是 0.22)—— 参数多确实让世界模型更准
  • 留出字体 27.1%,高于 g_small / p_small 的 20.8%

★ 但它的 无回显 只有 6.2%(p_small 58.3%)—— 说明它还没学会不靠回显, 这和"训练步数不够 / β 还没降完"一致(它最后一行 β=0.08)。

★ 回顾 §19.2:g_big 那次崩在 lr 2e-3;这次 p_big 也是 lr 2e-3 却没崩 (步 600 时正确率 51%)。所以崩溃不是"大模型必然",而是"大模型 + 某个 lr 下的不稳定" —— run_H_bigLR.ps1 的 lr 扫描会给出边界。

★ tools/compare_runs.py 的一个 bug 也记一笔:它认的是「留出字体」, 而日志里打的是「留出专项」—— 于是这一列一直是「—」, 我因此漏看了这条主线瓶颈整整几轮。正则已改成两个名字都收。

#19.4c ★★ loss「规律起伏」的确定答案(用 975 步的逐步曲线算出来的)

tools/loss_curve.py results/logs/curve_imitation_b128.json(975 步,逐步记录):

text
① 自相关(**已去趋势**,lag1..9):+0.91 +0.83 +0.72 +0.62 +0.52 +0.42 +0.33 +0.25 +0.18
   参考:去趋势前 lag1 = +0.99  ← 那个数是**趋势**造成的,不是周期
② 扩池那几步的跳变:均值 1.950
   池不变时的 |Δloss|:均值 0.038
   ★ 跳变 / 常态波动 = **52 倍**
③ 趋势(滑动平均 w=48):5.518 -> 0.020;平滑后下降步数占比 81%

★★ 起伏的规律性来自课程扩池(52 倍于常态波动)。 这是设计方案,不是 bug,不需要修。 趋势明确向下(5.518 -> 0.020),看趋势就行,别看逐步。

★ 分析本身的三个坑(都已修,写下来免得重踩):

  1. 自相关必须先去掉趋势。 第一版直接对原序列算,得到 lag1 = +0.99 并据此报"有强周期" —— 而那个 0.99 完全是 loss 单调下降造成的。 去趋势后真正的自相关是 +0.91/+0.83/... (仍在衰减,但那是平滑造成的)。

  2. np.diff 比原序列短 1,掩码要跟着对齐(否则报 boolean index did not match: 949 vs 950)。

  3. loss 是「当前这一批字」的拟合误差,不是「整个字集」的误差。 池不变时换一批字,loss 自然抖 —— 这不是噪声,是任务的真实难度差异。

#19.4d ⚠️ 一个必须先说清的现状:认字任务已经被"做完了",但它太简单

值
训练字符集正确率100.0%
认字(首键)100.0%
平均编辑距离0.00
模仿对100.0%
训练 loss0.020
留出字体29.2%

★ "在训练字符集上认字"这件事没有任何剩余空间了。 剩下的全部空间在字体泛化(29.2%)—— 那是唯一还没解决的部分。

结论:下一步的优化目标必须从"提高认字率"改成"提高留出字体", 否则任何实验都会顶在 100% 的天花板上,读不出差别。 这也是为什么 §19.4b 那条列修好之后,"留出专项"应该成为主指标。

#19.4e ★★★ 收尾:imitation_b128 跑满 1000 步的完整曲线

步训练字符集认字d无回显留出字体模仿对
60097.9%100.0%0.0397.9%25.0%99.3%
70097.9%100.0%0.02100.0%25.0%100.0%
800100.0%100.0%0.00100.0%29.2%100.0%
900100.0%100.0%0.00100.0%39.6%100.0%
1000100.0%100.0%0.00100.0%33.3%100.0%

#★★★ 三条同时成立,且互不矛盾

  1. 训练字符集:100%,距离 0.00 —— 这个任务在训练分布上已经完全解决。
  2. 无回显:100% —— 它完全不依赖屏幕上的回显在打字。 (--echo-drop 0.5 治好了 §16.2 那个"拿屏幕当前缀内存"的病,而且治得彻底。)

  3. 留出字体:25% -> 39.6% -> 33.3%,没有上升趋势,一直在抖 —— 这是唯一剩下的瓶颈。

★★★ 一句话:它把见过的 4 种字体"背下来"了,没有真正学会"认字形"。

而编码器端(§12.9 视觉预热)的留出字体是 90.5% —— 泛化能力在编码器里有,策略端没用起来。

★ 下一步的主指标因此必须换成留出字体。在训练字符集上已经 100%, 任何不改这个指标上限的实验都读不出差别(这解释了为什么 §16/§19 那些跑次"看起来都差不多")。


← 返回《PROJECT.md》目录