diff --git a/docs/experiment_round51_k16pre.json b/docs/experiment_round51_k16pre.json new file mode 100644 index 0000000..36a6511 --- /dev/null +++ b/docs/experiment_round51_k16pre.json @@ -0,0 +1,110 @@ +{ + "config": { + "ckpt": "ml/runs/base_dag2_dag1/best.pt", + "samples": "ml/data/samples", + "tasks": null, + "split": "val", + "n": 256, + "K": 16, + "commit": 2, + "fb": 2.2, + "ki": 0.0, + "fb_scale": 0.0, + "fb_near": 0.2, + "fb_fallback": 0.0, + "kdecay": 0.9, + "kimax": 0.5, + "fb_every": 1, + "safety_try": 0, + "sel_w": 0.1, + "dls_safety": 1, + "fb_clear": 0.0, + "fb_cmin": 0.3, + "sub_refresh": 0, + "fb_block_gain": 0.5, + "dump": "ml/runs/round51_k16pre_rows.json", + "out": "docs/experiment_round51_k16pre.json", + "trace": null, + "select": "pre", + "replan": "empty", + "safety": 1, + "partial": 1, + "inc_cap": 1, + "gap_margin": 0.0, + "cert_batch": 1, + "steps": 1, + "z_scale": 1.0, + "len_w": 0.0, + "tie": 0, + "pre_h": 0, + "pre_near": 0.0, + "backoff": 0, + "max_sec": null, + "seed": 0, + "dls": "analytic", + "device": "cuda" + }, + "batch_size": 256, + "batch_seed_rule": "same seed per batch", + "checkpoint_sha256": "f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1", + "tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772", + "kin_fp": "2ebcbf17", + "act_scale": 1.0, + "metrics": { + "n": 256, + "n_ref": 256, + "success": 0.56640625, + "collision_free_success": 0.56640625, + "time_ok_1p3": 0.359375, + "collision_rate": 0.0, + "collision_steps_mean": 0.0, + "travel_ratio_median": 2.414156198501587, + "time_mean_s": 2.265103578567505, + "brakes_mean": 14.1953125, + "start_fail": 0.0, + "goal_ratio_2s": 0.13815215229988098, + "err_fail_med": 184.40463256835938, + "err_success_med": 0.2881776690483093, + "dls_share": 0.64453125, + "time_budget": "teacher×2(≥3 s)", + "speed_ratio_p50": 1.0000016689300537, + "speed_ratio_p95": 1.0000101327896118, + "speed_ratio_max": 1.0000101327896118, + "over_vmax_rate": 0.0, + "accel_ratio_p95": 1.0000016689300537, + "accel_ratio_max": 1.0000101327896118, + "reverse_p50": 0.2373737394809723, + "reverse_mean": 0.2401755303144455, + "reverse_moving_p50": 0.27848100662231445, + "moving_share_p50": 0.9851852059364319, + "brake_rate_mean": 0.07279647886753082, + "brake_rate_p50": 0.0, + "brake_rate_p90": 0.27435898780822754, + "stuck_share": 0.171875, + "n_plans_mean": 195.0, + "success_reachable": 0.596707820892334, + "by_cat": { + "A": [ + 128, + 100 + ], + "B": [ + 74, + 26 + ], + "C": [ + 32, + 12 + ], + "D": [ + 13, + 0 + ], + "Dr": [ + 9, + 7 + ] + } + }, + "simulation_wall_s": 486.8652108999959 +} diff --git a/docs/experiment_round52_ctl_ex.json b/docs/experiment_round52_ctl_ex.json new file mode 100644 index 0000000..570154a --- /dev/null +++ b/docs/experiment_round52_ctl_ex.json @@ -0,0 +1,110 @@ +{ + "config": { + "ckpt": "ml/runs/base_dag2_dag1/best.pt", + "samples": "ml/data/samples", + "tasks": null, + "split": "val", + "n": 256, + "K": 8, + "commit": 2, + "fb": 2.2, + "ki": 0.0, + "fb_scale": 0.0, + "fb_near": 0.2, + "fb_fallback": 0.0, + "kdecay": 0.9, + "kimax": 0.5, + "fb_every": 1, + "safety_try": 0, + "sel_w": 0.1, + "dls_safety": 1, + "fb_clear": 0.0, + "fb_cmin": 0.3, + "sub_refresh": 0, + "fb_block_gain": 0.5, + "dump": "ml/runs/round52_ctl_ex_rows.json", + "out": "docs/experiment_round52_ctl_ex.json", + "trace": null, + "select": "endpoint", + "replan": "empty", + "safety": 1, + "partial": 1, + "inc_cap": 1, + "gap_margin": 0.0, + "cert_batch": 1, + "steps": 1, + "z_scale": 1.0, + "len_w": 0.0, + "tie": 0, + "pre_h": 0, + "pre_near": 0.0, + "backoff": 0, + "max_sec": null, + "seed": 0, + "dls": "analytic", + "device": "cuda" + }, + "batch_size": 256, + "batch_seed_rule": "same seed per batch", + "checkpoint_sha256": "f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1", + "tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772", + "kin_fp": "2ebcbf17", + "act_scale": 1.0, + "metrics": { + "n": 256, + "n_ref": 256, + "success": 0.5234375, + "collision_free_success": 0.5234375, + "time_ok_1p3": 0.265625, + "collision_rate": 0.0, + "collision_steps_mean": 0.0, + "travel_ratio_median": 2.615795850753784, + "time_mean_s": 2.486567258834839, + "brakes_mean": 19.5546875, + "start_fail": 0.0, + "goal_ratio_2s": 0.27920001745224, + "err_fail_med": 204.71360778808594, + "err_success_med": 0.21751396358013153, + "dls_share": 0.61328125, + "time_budget": "teacher×2(≥3 s)", + "speed_ratio_p50": 1.0000016689300537, + "speed_ratio_p95": 1.0000101327896118, + "speed_ratio_max": 1.0000101327896118, + "over_vmax_rate": 0.0, + "accel_ratio_p95": 1.0000016689300537, + "accel_ratio_max": 1.0000101327896118, + "reverse_p50": 0.24741201102733612, + "reverse_mean": 0.25991496443748474, + "reverse_moving_p50": 0.32581019401550293, + "moving_share_p50": 0.9587156176567078, + "brake_rate_mean": 0.10028044879436493, + "brake_rate_p50": 0.0051282052882015705, + "brake_rate_p90": 0.3153846263885498, + "stuck_share": 0.234375, + "n_plans_mean": 195.0, + "success_reachable": 0.5514403581619263, + "by_cat": { + "A": [ + 128, + 96 + ], + "B": [ + 74, + 20 + ], + "C": [ + 32, + 11 + ], + "D": [ + 13, + 0 + ], + "Dr": [ + 9, + 7 + ] + } + }, + "simulation_wall_s": 296.3142095999938 +} diff --git a/docs/探索日志.md b/docs/探索日志.md index e60c5d5..7a48b42 100644 --- a/docs/探索日志.md +++ b/docs/探索日志.md @@ -20,7 +20,8 @@ - **当前最好(n=256 val,同 seed 同批)**:`ml/runs/base_dag2_dag1/best.pt` + `K=8, select=endpoint, replan=empty, commit_n=2, fb=2.2, fb_block_gain=0.5, dls_safety=1, safety=1` → **到达 57.4%、无碰撞到达 57.4%、时限内(≤1.3×老师) 39.1%、刹停 19.25/题、行程比 2.445、2 s 误差比 0.210**(轮次 33 base)。 - **但这组数含超速**:`speed_ratio_p50=1.660`、`over_vmax_rate=1.000` ⇒ 每轴实际最大速度是 VMAX 的 1.66 倍(反馈叠在 ±1 限幅之后,轮次 30 定位)。按合法限速 `--inc_cap 1` 重测是 **到达 52.3% / 时限内 26.6%**(轮次 33 cap)。**验收要引用后者**,两者的差就是"以前的分数有一部分是超速换来的"。 - n=512 复核在轮次 28–30 给过 50.8–52.1%;当前配方(含 `inc_cap`)尚未在 n=512 重跑。 - - **合法限速口径下的新最好时限读数(轮次 46–47)**:同配方把打分地平线换成被执行前缀 `select=pre` → **到达 54.3%、时限内 33.2%(控制 26.6%)、2 s 误差比 0.170(控制 0.279)、行程比 2.445、碰撞 0、超 VMAX 0**;seed=1 复现 54.3%/34.0%。到达相对控制只 +5/+3 题(McNemar p=0.42/0.68 ⇒ 噪声),**显著的是时限内与进度**,代价是到达题末态误差 0.22→0.42 mm(判据壳 1 mm 内,余量 4.6×→2.4×)。所以它刷新的是"时限内"这一栏,不是"到达"这一栏。 + - **合法限速口径下的当前最好格(轮次 51,2×2 补全后)**:`K=16 + select=pre + commit_n=2 + fb=2.2 + inc_cap=1 + cert_batch=1` → **到达 56.6%、时限内 35.9%、2 s 误差比 0.138、行程比 2.414、刹停 14.20/题、碰撞 0、超 VMAX 0**。相对历史控制(K8 endpoint 0.5234/0.2656)逐题赢 20 丢 9 = 净 +11 题,**McNemar p=0.061 ⇒ 到达仍未确立**;`goal_ratio_2s` 201:55(p=1.0e−20)是这条线最硬的证据。两条杠杆**在时限上可加(无交互)、在到达上饱和**(K16 之上再加 `pre` 只净 +1 题,p=1.0)。 + - **口径提醒(对所有历史数字生效)**:`ok = done & (t_done ≤ max(2×老师用时, 3 s))` ⇒ 本链引用的"到达率"本身就带 2× 时限,`time_ok_1p3` 才是 §9.2 的 1.3× 口径,两道门不能混着引用。 - **实时性(GPU,B=1、K=8、一步)**:整周期 P50 33.5 ms、P99 64.7 ms;批量认证把最坏情况从 80.2 → 37.1 ms(轮次 34)。仍未进 §9.2 的 P99 < 20 ms,瓶颈是几何查询不是推理(推理 7.2 ms)。 - **安全层口径**:线上只做**端点 + 中点**点检(`gaps(...).min(-1) > 0`)。轮次 30 那条"认证通过仍碰撞"(−0.149 mm)已被连续段认证抓住(`certify_uniform` n≥8、二分 5 次查询),但它进不了实时预算(轮次 31–32)。 - **失败结构(轮次 38,147 到达 / 109 未到)**:**63 题是走一半被冻住**(离目标 >100 mm 且刹车 >30 次)、23 题方向错、位置差 20~100 mm 15 题、差 5~20 mm 5 题、姿态没到位 3 题、起步失败 0;未到组活动窗内 37% 的步是零运动。 @@ -113,6 +114,49 @@ - **对 `backoff` 的预期与判据**:全拒意味着"连第 1 步的端点或中点已经穿进障碍",缩步能不能救取决于**穿得有多深**(穿 0.1 mm 救得回,穿 3 mm 救不回)。这个数转储里没记(只记被选中那条),所以直接由 `backoff_mean`/`reject_all_mean` 来量: `reject_all_mean ≈ 原全拒次数`(=backoff_mean+brakes_mean,与轮次 33 cap 的 19.55 可比),`backoff_mean/(backoff_mean+brakes_mean)` 就是"缩步能救回的比例"。这是补跑(#22)要读的第一个数。 +## 轮次 52:补上逐题老师用时这把尺子之后,整条"打分地平线"线的结论要翻面——1.3× 口径上的时限收益是显著的 + +- **仪表缺的是什么**:`--dump` 的逐题行里没有老师用时,所以 §9.2 的 `time_ok_1p3`(≤1.3×老师)此前**只能报整批比例,做不了逐题配对**。本轮把 `t_exp`(老师用时)与 `tr_ref`(老师行程)写进每一行——它们是**题目属性、与臂无关**,所以只要有一条带新字段的控制臂落盘,就能按题号 join 给**所有历史臂**补上配对能力。 +- **改了落盘代码必须先自证没动行为**:重跑同配方控制臂 ⇒ **22/22 老键逐位相同**(`docs/experiment_round52_ctl_ex.json` vs `round45_tie0`)。顺带核对 `limit = 2×t_exp`(3.48 s ⇒ 6.96 s),把轮次 51 那句"到达自带 2× 时限"从推断变成实测。 +- **逐题配对(vs K8 `endpoint` 控制,n=256、seed=0;判据 = ok 且 `t_done ≤ 1.3·t_exp`)**: + + | 臂 | 新增达标 | 失去达标 | 净 | 精确 McNemar p | + |---|---|---|---|---| + | **K16 `pre`(最好格)** | **27** | **3** | **+24** | **8.4e−6** | + | K8 `pre` | 22 | 5 | +17 | **0.0015** | + | 门控 5 mm | 23 | 6 | +17 | 0.0023 | + | 门控 20 mm | 22 | 6 | +16 | 0.0037 | + | K8 `pre` h=4 | 15 | 5 | +10 | 0.041 | + | K16 `endpoint` | 17 | 9 | +8 | 0.169 | + | K8 `pre` h=8 | 11 | 7 | +4 | 0.481 | + | `tie=1`(轮次 45) | 3 | 0 | +3 | 0.250 | + +- **这条表纠正的是我自己的判读,不是数据**:轮次 46–51 我一直拿 `success`(自带 2× 预算的那道门)做逐题配对,于是写成"到达与时限收益都在噪声内"。换成 §9.2 真正的 1.3× 口径,`pre` 系列的收益是 **p=0.0015 与 8.4e−6**——整条线最硬的一条证据。⇒ **"到达率没显著变化"仍然成立**(2× 那道门上是净 +1/+5/+11、p≥0.06),但**"时限内到达"这道门是明确被买到的**。两栏必须分开写,之前混过一次。 +- **同时被这张表重新定位的两条旧结论**:`K16+endpoint` 的时限收益(+8 题)其实不显著(p=0.169)——轮次 39 报的"+3.1 pp 时限"当时没有配对检验;h=8 那臂在 1.3× 口径下也是 p=0.48 ⇒ 轮次 48 说 h=8 是坏点,现在有了第二重证据。`tie=1` 在 1.3× 上只有 +3 题(p=0.25)且到达净 −5 ⇒ 判掉不变。 +- **待办**:seed=1 的 `t_exp` 正在补落盘(轮次 53),以便把轮次 47/50 那句"整批 +4.3 pp / +0.39 pp"也换成配对 p 值。 +- **口径纪律**:`--pre_near`、`--pre_h`、`--tie` 都是可选口径,默认仍是 `endpoint`。§9.2 的验收栏仍按"到达未确立、时限内已确立(seed0 配对)"来写,等 s1 配对完成再决定是否给到达之外的第二栏正式换配方。 + +## 轮次 51:2×2 补全(K∈{8,16} × select∈{endpoint,pre})——两条正向杠杆在时限上可加、在到达上饱和 + +- **为什么补这格**:`K=16`(#27/#1,轮次 39/44 验证)与 `select=pre`(#29,轮次 46 验证)是链上仅有的两条正向杠杆,但从未同时测过。§9.2 要的是**同一份配方**同时满足到达与时限,所以可加性必须直接量,不能各自报喜。 +- **四格(全部 n=256、seed=0、同配方、同批口径;缺的那格是本轮新跑)**: + + | 格 | 到达 | 时限内(≤1.3×老师) | 2 s 误差比 | 到达题末态误差 | 刹停/题 | 行程比 | 碰撞 / 超 VMAX | + |---|---|---|---|---|---|---|---| + | K8 `endpoint`(历史控制) | 0.5234 | 0.2656 | 0.2792 | 0.2175 | 19.55 | 2.6158 | 0 / 0 | + | K8 `pre` | 0.5430 | 0.3320 | 0.1702 | 0.4151 | 19.84 | 2.4449 | 0 / 0 | + | K16 `endpoint` | 0.5625 | 0.2969 | 0.2800 | 0.1595 | 13.77 | 2.6243 | 0 / 0 | + | **K16 `pre`(新最好格)** | **0.5664** | **0.3594** | **0.1382** | 0.2882 | 14.20 | **2.4142** | **0 / 0** | + +- **主效应(这就是可加性的算法)**: + - 时限:`pre` 在 K8 上 **+6.64 pp**、在 K16 上 **+6.25 pp**;`K16` 在 endpoint 上 +3.13 pp、在 pre 上 +2.73 pp ⇒ **四个方向同量级、无交互**。时限这块确实是两个独立瓶颈(候选容量、打分口径)各自买一份。 + - 到达:`pre` 在 K8 上 +1.95 pp、在 K16 上只 **+0.39 pp**(逐题 16 赢 15 丢,净 +1,**McNemar p=1.000**)⇒ **到达饱和**:K16 已经把"有活路可选"这件事买完了,再把打分口径改对,救不回新题。 + - 最好格 vs 历史控制(K8 endpoint)逐题:赢 20 丢 9,净 **+11 题**(0.5234→0.5664),**McNemar p=0.061** ⇒ 这是整条"选择/认证"线上**最接近显著**的一次到达收益,但按规矩 p=0.061 就是"尚未确立",要在 n=512 或第二个 seed 上复现才算。翻转按类别:B +8/−2、A +8/−4、C +3/−2、Dr +1/−1 ⇒ 增益仍然主要在 B(直线被挡)。 + - `goal_ratio_2s` 逐题:201 好 / 55 差,**p = 1.0e−20** ⇒ 全程进度是这条线最硬的证据(比任何单臂都强)。 +- **一个所有历史数字都被我漏说的口径**:`ok = done & (t_done ≤ limit)`,而 `limit = time_limit(ex) = max(2×老师用时, 3 s)`。**⇒ 本链引用的"到达率"本来就带 2× 时限**,不是"最终到没到"。`time_ok_1p3` 才是 §9.2 的 1.3× 口径。两者是两道门,引用时不能再混。轮次 52 已把 `t_exp/tr_ref` 落盘,之后 1.3× 口径能做逐题配对。 +- **状态**:合法口径(碰撞 0、超 VMAX 0)下的当前最好配方更新为 **K=16 + select=pre + commit_n=2 + fb=2.2 + inc_cap=1 + cert_batch=1** → 到达 56.6%、时限内 35.9%。§9.2 的缺口仍然是**到达 −41.4 pp**(时限 −64.1 pp 是另一栏)。 +- **判据没被放松的证据**:四格 `collision_rate` 与 `over_vmax_rate` 全 0,`n_plans_mean` 全 195.0 ⇒ 规划次数不变;K16 的额外成本只在每颗规划的候选数(认证与打分的批量宽度),这条**必须用独占微基准量**才能进部署账(#16/#28 仍未结)。 + ## 轮次 50:门控 20 mm 的 seed=1 复现——方向对、幅度不稳:时限收益塌了,精度代价只是减小没消除 - **一条臂就够**(控制臂复用 `round47_ep_s1`,同 seed 同配方,已在盘)。 @@ -202,6 +246,7 @@ - **统计口径(这条线真正的证据在哪)**: - `goal_ratio_2s` 逐题符号检验:s0 **192 好 / 64 差(p=4.9e−16)**,s1 **191 好 / 65 差(p=1.5e−15)**。⇒ 显著、且两个 seed 的计数几乎一样。 - 到达:s0 逐题丢 10 赢 15(净 +5,McNemar **p=0.424**)、s1 丢 10 赢 13(净 +3,**p=0.678**)。⇒ **到达率的提升在两个 seed 上都只是噪声**。诚实的写法是"时限内到达显著变多、总到达未见显著变化",即它把路上的时间买回来了,没有把通路买回来。 + **【轮次 52 补验】**这句里"时限内显著变多"当时还只是整批读数(0.2656→0.3320),没有逐题配对。补上每题老师用时后:**K8 `pre` 在 1.3× 口径上逐题 22 赢 5 丢,净 +17,精确 p=0.0015**;`K16+pre` 是 27/3、p=8.4e−6。⇒ 那句话现在有据了,而且比原来写的更强。注意 `success` 那道门自带 **2×** 预算(`limit = max(2·t_exp, 3 s)`),所以"到达 p=0.42"与"时限内 p=0.0015"是两道不同门的读数,不矛盾。 - 末态误差逐题:s0 130 好 / 126 差(p=0.85)、s1 121/135(p=0.42)⇒ 全程不显著;但**到达题组内**的中位误差两 seed 都涨(0.22→0.42、0.22→0.34 mm)。这是"打分只看 2 步"的必然代价:近目标处不再为最后那几毫米承诺路径。判据仍未违反(到达定义是 `e<1 mm 且 oe<0.5°`,最差的 0.42 mm 也在壳内),但**这条余量从 4.6 倍缩到 2.4 倍**。 - **按类别(两个 seed 方向一致)**:A 类 −1,**B 类 +2、C 类 +3/+2**。单个切片在这个分母上都不显著,但方向与轮次 44 的"#27:候选容量救不了 B/C"**不矛盾**——那条测的是"多给同分布候选",这条改的是"用哪个终点评价候选"。真正的判据留给下一轮的地平线曲线。 - **成本**:两臂墙钟 317.6/319.2 s(s0)与 315.1/314.4 s(s1)——同量级;但**墙钟差不构成成本结论**,要说"零额外开销"得用独占微基准把 `goal_err` 在 `B×K` 个点上的两次调用分开量(记为待办)。 diff --git a/ml/eval/eval_cl.py b/ml/eval/eval_cl.py index 2a4fd73..93ea2a4 100644 --- a/ml/eval/eval_cl.py +++ b/ml/eval/eval_cl.py @@ -160,8 +160,13 @@ def main(): keys = ['done', 'ok', 't_done', 'limit', 'col', 'brakes', 'err', 'first_hit', 'dls_it', 'goal_ratio_2s', 'brake_rate', 'n_plans', 'tie_opp', 'tie_changed', 'tie_opp_rate', 'tie_changed_rate'] flat = {k: torch.cat([p[k] for _, p, _ in pers]).tolist() for k in keys} + _, _, _, ex_t, tr_t = tasks_to_tensors(tasks, 'cpu') # run() 里的同名量是局部变量,这里必须自取 for i, t in enumerate(tasks): - rows.append({**t, 'i': i, **{k: flat[k][i] for k in keys}}) + row = {**t, 'i': i, **{k: flat[k][i] for k in keys}} + # 老师用时/行程是题目属性(与臂无关),落盘后任何一臂都能给历史臂补上"1.3×老师"的逐题配对 + row['t_exp'] = float(ex_t[i]) if torch.isfinite(ex_t[i]) else None + row['tr_ref'] = float(tr_t[i]) if torch.isfinite(tr_t[i]) else None + rows.append(row) Path(a.dump).write_text(json.dumps(json_finite(rows), allow_nan=False), encoding='utf-8') print('dump', a.dump, flush=True) if a.trace: