> 排序后聚类去找"两个模态",会把一次回退当成模态然后放行
硬件是双峰的(见 [同一台机器上 6/9 个指标是双峰的——进程启动时二选一,然后整轮不变](/notes/bimodal-hardware-breaks-the-effect-size-bar/)),所以回退门禁需要先判断 "这个序列是不是两个模态"。第一版的实现是最自然的那个:把序列排序,然后聚类。
结论先行¶
硬件是双峰的(见 同一台机器上 6/9 个指标是双峰的——进程启动时二选一,然后整轮不变),所以回退门禁需要先判断 "这个序列是不是两个模态"。第一版的实现是最自然的那个:把序列排序,然后聚类。
它静默地废掉了整个门禁。因为——
一次回退本身也是两个簇:前面快,后面慢。
把一个植入的 +20% 阶跃喂给它,它报告"两个模态相距 20.6%,无法判定",然后放行了构建。
区分这两种情况的不是分散度,是排列: 真模态是交替出现的;阶跃是一整段接另一整段。排序把排列信息整个扔掉了。
修法:把聚类标签按原始顺序做 Wald–Wolfowitz runs 检验。 交替模态下标签序列像抛硬币(runs 数约 n/2);阶跃下是两个长 run。 z ≤ −2 表示这个划分可以被时间解释,于是判定为位移而不是模态混合。
为什么¶
两件事值得单独记住。
一、"排序"是统计里最容易丢信息的一步,而且丢的常常正是你要找的那个。 任何"先排序再看分布"的检验都对时间顺序失明。凡是要区分 "内在多模态"和"随时间变化"的场合,都必须有一个基于顺序的统计量。
二、两个方向各需要一个测试,只有一个会漏。这个项目钉了两条:
test_a_step_is_not_mistaken_for_two_modes
test_interleaved_modes_are_not_gated
只写第一条,会倾向于把真模态判成阶跃,于是门禁在每台双峰机器上天天误报; 只写第二条,就是原来那个 bug——什么都判成模态,门禁永不触发。 这是 用 any() 写的回归测试是假保证——它只要求最容易过的那个夹具通过 的同一个形状:一个方向的断言不构成一个检验。
还有一句值得抄下来:
永不触发的门禁和永远触发的门禁,在绿色构建上看起来完全一样。 而这一个已经悄悄变成了前者。
怎么用¶
写任何"自动判断这批数据能不能判"的逻辑时:
- 先植入一个你知道答案的信号(这里是 +20% 阶跃),看检测器会不会把它 归类成"无法判定"。无法判定是最危险的类别——它既不红也不漏,它放行。
- 检查你的检测器是否用到了顺序。 如果它的输入是排序后的值或一个直方图, 那它不可能区分"混合"和"变化"。
- 两个方向各写一个测试,并确认去掉任一个都会让另一个 bug 复活。
- 让"无法判定"这个结论自带数字,不要只给一个标签—— 报告"两个模态相距 20.6%"的时候,20.6% 这个数本身就足以让人起疑。
出处¶
~/Engineering/High-Performance-Hybrid-Trading-System/docs/REGRESSION.md"The bug that made the gate useless" 一节(含被植入的 +20%update_level阶跃、 那句"two modes 20.6% apart, cannot gate"、Wald–Wolfowitz runs 的替代方案与 z ≤ −2 判据、 以及两个方向的测试名)- 相关:测不出失败的门禁比没有门禁更糟——它在制造信心、用 any() 写的回归测试是假保证——它只要求最容易过的那个夹具通过