16. 判据 5 过线 + 「照抄屏幕」被治好(第 3 轮对照)

★★★ 3.6k 字 · 源文件 PROJECT.md 第 3172 行起 · goal ruler optim read external

#16.1 判据 5 第一次过 50%

跑次β=0 正确率认字留出字体emitvs 纯模仿 30.2%
DE(旧)13.5%36.5%—0.8544.7%
s1(无内驱力 + 成本 0.15)12.5%54.2%—0.1741.4%
s2(成本 0.15)15.6%55.2%29.2%0.1651.7% ✅
s3(成本 0.40)2.1%34.4%—0.087.0%

★ s3 说明 0.40 的开门成本压过头了(门塌到 0.08、正确率掉到 2.1%)。 成本要温和;0.15 是当前最优点,但还没扫过 0.25。

#16.2 ★★ 病:它是拿屏幕当外部内存

diag_parroting.py 在 s2 权重上关掉回显(屏幕只剩目标字):

条件整串正确率认字demit
基线22.7%60.9%1.590.17
回显关3.1%60.9%4.910.82
目标字第 4 刻消失22.7%60.9%1.590.17
回显关 + 目标字消失0.0%60.9%9.430.85
空白屏0.0%8.6%10.660.94

准确说法不是"只会复述屏幕内容",而是:它把「我打到哪儿了」放在屏幕上,不放状态里。 回显一关,22.7% → 3.1%,距离 1.59 → 4.91。

★ 三个"复述"必须分开(严重程度差一个数量级):

#形式是不是问题
①复述目标字(看到「中」打 zhong)就是任务本身,没有别的做法
②复述自己的输出(每刻重看屏幕续写)是偷懒通路(上面这个病)
③复述老师(纯行为克隆,老师一走就崩)最严重,正是"拟合陷阱"的定义

#16.3 治②:训练期随机关回显(--echo-drop)

四个权重的统一对照(diag_parroting.py,n=96,同一把尺子):

权重drop基线整串基线认字无回显整串抹掉目标字换成别的字
s20.020.8%59.4%2.1% ❌11.5%10.4%
e300.323.4%55.5%25.0% ✅6.2%10.9%
e500.521.9%51.0%22.9% ✅5.2%6.2%
e700.716.7%40.6%16.7%6.2%7.3%

★ 第 4 列才是分水岭(部署时本来就要关回显):2.1% -> 25.0%,12 倍。 ★ 第 5、6 列是探针生效的指纹:四个权重全都低(5~12%)—— 说明"抹掉/替换目标字"真的把那个字从屏幕上拿掉了。

训练期读数(2500 步,β=0 时):

跑次drop正确率认字d门(该开/该关)留出字体
s20.015.6%55.2%1.58—29.2%
e300.320.8%53.1%1.440.08/0.32—
e500.522.9%50.0%1.530.09/0.34—
e700.721.9%46.9%1.750.06/0.25—

#★★ 结论:echo-drop 是一个权衡,不是纯改进

指标随 drop 上升的变化
无回显正确率↑ 大幅(2.1% -> 25.0%)—— 这是治好的病
认字能力↓ 单调(59.4% -> 55.5% -> 51.0% -> 40.6%)—— 这是代价
有回显正确率先升(15.6 -> 22.9),drop > 0.5 后回落

为什么"认字"会掉:回显本身是目标字之外的第二个视觉信号, 关掉它的那些回合里,编码器少了一份"这里有字"的监督。drop 越大, 这种"信息更少"的回合越多 -> 认字能力被稀释。

取舍点:0.3 ~ 0.5。 0.3 保住认字(55.5%),0.5 把无回显分数推到最高(25.0%)。 0.7 两个都受损,不要用。

★ 还有一个同向的好处:门的时机第一次分开了(该开 0.08 / 该关 0.32)。 s2 那一列的门是常开(0.85/0.94)。

为什么"关回显"不是"剥夺信息":按内外分离契约,本体感觉(82 维)里一直有 它自己上一刻发出的键。所以这是拿掉一条偷懒通路,不是把它眼睛蒙上。

#16.4 ⚠️⚠️ 这一节最贵的一个 bug(一个字符,毁掉整份诊断)

evaluate(blank_target=True) 我第一版写成了:

python
e._show_char = None if blank_target else (...)      class="tok-com"># ✗ 错

而 render() 里的约定是:

text
_show_char is None  -> 用默认值(画目标字)      ← 我以为是"不画"
_show_char == ""    -> 不画                      ← 这才是"不画"
_show_char == 别的字 -> 画那个字

于是"目标字始终不出现"那一行什么都没改,报出 55.5% 的认字率 —— 屏幕上看不见字却认得出,与同一张表里"空白屏 6.2%"自相矛盾。

★★ 是靠这个矛盾发现的,不是靠检查代码。 教训:一张表里两个读数互相矛盾时,先怀疑工具,不要先怀疑结论。 我当时的第一反应是"它靠先验",第二反应是"上一回合的回显漏进来了", 都是错的;正确答案是"我的探针没生效"。

★ 配套教训:三态开关(默认 / 关 / 换)必须让"关"和"默认"取不同的值。 用 None 表示"关"、同时用"没有这个属性"表示"默认",就是给这个 bug 留门。 正确写法是显式三分支(见 evaluate 现在的实现),并且加一条自检: 探针生效时像素数必须变(diag_parroting 的判据 ② 就是干这个的)。

#16.5 三条可以复用的方法论

  1. "关掉某条通路"是比"看相关性"强得多的判别手段。 关回显、抹目标字、换目标字 —— 三个都是干预,不是观察。 认字 那一列在三个条件下的分裂(55.5 / 6.2 / 10.9)一眼就能读。

  2. 判别器必须在同一张表里同时报"该高的高"和"该低的低"。 只报"关回显掉了多少",就读不出"它到底看没看目标字"。

  3. 一个探针要有"它没生效"的指纹。 这里的指纹是 目标字不出现 == 空白屏(两个都该是 6.2%)。两个本该相同的条件给出不同读数, 就是探针失效的信号。


← 返回《PROJECT.md》目录