Download tests/benchmark_rr_planning.py from logos42/headless: direct link, hf CLI and curl.
- Browser
- Download file 9.18 kB
-
https://huggingface.co/logos42/headless/resolve/main/tests/benchmark_rr_planning.py
- Command line
-
hf download hf://logos42/headless/tests/benchmark_rr_planning.py
-
curl -L -o benchmark_rr_planning.py https://huggingface.co/logos42/headless/resolve/main/tests/benchmark_rr_planning.py
9.18 kB
| # -*- coding: utf-8 -*- | |
| """OaK 的 Model→Planning 层在 KeyDoor 上的基准 —— 指标 = **look-ahead 操作数**。 | |
| 论文(Sutton et al. 2022)用它自己的指标衡量这套机制:一个"让规划更省"的 | |
| 抽象,只有在**规划代价**上才看得见收益(§9.2:"Use the source's own metric | |
| for the mechanism")。本项目已在 gridworld 上复现了论文的双向结果: | |
| 两房间 (36 状态) primitive 1400 / shortest 1750 / **RR 875** | |
| 四房间 (80 状态) primitive 48872 / shortest 53710 / **RR 26200** | |
| 本文件把同一件事搬到 **KeyDoorMDP**(时序结构已证:A ∈ [−1,+1], 33% 显著)。 | |
| ## 消融矩阵(相邻两臂只差**一个**变量) | |
| arm c(cumulant) z(stopping value) 作用 | |
| ───────────── ─────────── ──────────────────────── ────────────────── | |
| primitive — — 下界基线(无 option) | |
| rr-exact R V_main + (w̄−w_i)x_i **机制本体** | |
| rr-zeroV R 0 + (w̄−0 )x_i ← 削掉**价值函数** | |
| rr-nobonus R V_main + (0 )x_i ≡ V_main ← 削掉停止奖励(已知退化) | |
| rr-nocum 0 V_main + (w̄−w_i)x_i ← 削掉**沿途主任务奖励** | |
| rr-nofeat R V_main + (w̄−w_i)x_uniform ← 削掉**特征语义** | |
| bottleneck −1 0 @ 瓶颈 **已知弱(论文点名)** | |
| random R V_main + (w̄−w_i)x_random **随机目标(负对照)** | |
| ★ 判据**在跑之前写死**(写进本文件, 见 `INTERPRETATION_RULES`), 免得数字 | |
| 出来以后往想要的方向读。 | |
| """ | |
| import json | |
| import os | |
| import sys | |
| import numpy as np | |
| sys.path.insert(0, ".") | |
| from hibs_lnn.rr_options import ( # noqa: E402 | |
| KeyDoorTabular, build_bottleneck_library, build_random_goal_library, | |
| build_rr_library, feature_vectors, main_value, | |
| ) | |
| from hibs_lnn.subtask_options import ( # noqa: E402 | |
| build_option_model, ops_to_tolerance, | |
| ) | |
| TOL = 0.01 | |
| MAX_OPS = 400000 | |
| # ── 判据写死在此:跑完**先读这里再读数字** ──────────────────────────── | |
| INTERPRETATION_RULES = [ | |
| "① 内部哨兵: primitive 臂必须复现历史值(两房间 1400 量级的同型行为)。" | |
| "若 primitive 臂与历史不符 -> 本 harness 自身有 bug, 其余数字全部作废。", | |
| "② 已知负向: bottleneck 臂**不得**优于 primitive。若它赢了 -> harness 有 bug" | |
| "(论文明确指出瓶颈子目标是已知弱基线)。", | |
| "③ 机制判据: rr-exact 优于 primitive 才叫'机制有效'。" | |
| "相等等不于'无效', 而是'这个 regime 上无可抽象的结构', 必须换 regime 再说。", | |
| "④ 价值函数假设(用户的猜测): 比较 rr-exact vs rr-zeroV。" | |
| "两者**逐位相同** = 该 regime 下价值函数不影响规划 -> 假设**未被这个试验台检验**" | |
| "(不是'被证伪');两者不同且 rr-exact 更省 = 价值函数是收益来源。", | |
| "⑤ 只在 tol=0.01 与 max_ops=400000 下读数:'打满上限'的臂记为不收敛, 不当作有限值比较。", | |
| "⑥ 单 regime 的结果只是**一个点**; 结论要在多 regime 上一致才成立。", | |
| ] | |
| def arm_libraries(mdl, V_main): | |
| """构造矩阵里每个臂的 option 库。""" | |
| arms = {} | |
| arms["rr-exact"] = build_rr_library(mdl, V_main, bonus_weight=1.0) | |
| arms["rr-zeroV"] = build_rr_library(mdl, V_main, bonus_weight=1.0, | |
| z_mode="zeroV") | |
| arms["rr-nobonus"] = build_rr_library(mdl, V_main, bonus_weight=1.0, | |
| z_mode="nobonus") | |
| arms["rr-nocum"] = build_rr_library(mdl, V_main, bonus_weight=1.0, | |
| cumulant="zero") | |
| # 削掉特征语义: 特征用一个与任务无关的均匀特征(半数为 1) | |
| n = mdl.n_states | |
| x_unif = np.array([1.0 if (s % 2 == 0) else 0.0 for s in range(n)]) | |
| from hibs_lnn.subtask_options import reward_respecting_subtask | |
| st_u = reward_respecting_subtask(mdl, x_unif, V_main, bonus_weight=1.0) | |
| st_u.name = "rr[nofeat]" | |
| st_u.feature = x_unif | |
| st_u.solve(mdl) | |
| arms["rr-nofeat"] = [st_u] | |
| arms["bottleneck"] = build_bottleneck_library(mdl) | |
| arms["random"] = build_random_goal_library(mdl, V_main, seed=0) | |
| return arms | |
| def eval_arm(mdl, lib, v_star, s0=0): | |
| models = {} | |
| ks = [] | |
| for st in lib: | |
| name = st.name | |
| if name in models: # 重名 -> 加后缀 | |
| name = f"{name}#{len(models)}" | |
| M = build_option_model(mdl, st) | |
| models[name] = M | |
| # ★ 时长仪表: ops 指标可能被"option 有多长"主导而不是被"目标选得好不好" | |
| # 主导 —— 一个 k 步的 option 只花 1 次 backup 就传播 k 步的价值。 | |
| # 记录每个 option 的期望终止步数, 才能把这个 confound 读出来。 | |
| for s in range(mdl.n_states): | |
| for ent in M[s]: | |
| ks.append(ent[3]) | |
| ops, errs, ops_curve, err_curve = ops_to_tolerance( | |
| mdl, models, v_star, s0, tol=TOL, max_ops=MAX_OPS) | |
| return {"ops": int(ops), "err": float(errs), "converged": bool(errs < TOL), | |
| "n_options": len(models), "n_sweeps": len(ops_curve), | |
| "ops_per_sweep": (float(ops) / max(1, len(ops_curve))), | |
| "mean_k": float(np.mean(ks)) if ks else 1.0, | |
| "max_k": float(np.max(ks)) if ks else 1.0, | |
| "beta_total": int(sum(int(np.sum(st.beta)) for st in lib))} | |
| def main(): | |
| regimes = [ | |
| (2, 5, 7, "A: key=2 door=5 goal=7 (需全链)"), | |
| (1, 4, 7, "B: key=1 door=4 goal=7 (需全链)"), | |
| (3, 6, 7, "C: key=3 door=6 goal=7 (需全链)"), | |
| (2, 5, 3, "D: goal<door (免钥匙, 门无关)"), | |
| ] | |
| out = {"tol": TOL, "max_ops": MAX_OPS, "rules": INTERPRETATION_RULES, | |
| "regimes": []} | |
| print("=" * 78) | |
| print("KeyDoor 规划基准 | 指标 = look-ahead 操作数到 |V(s0)-V*| < %.2f" % TOL) | |
| print("=" * 78) | |
| for kp, dp, gp, label in regimes: | |
| from hibs_lnn.skill_mdp import KeyDoorMDP | |
| mdp = KeyDoorMDP(n_pos=8, key_pos=kp, door_pos=dp, goal_pos=gp, horizon=30) | |
| mdp.set_regime(kp, dp, gp) | |
| mdl = KeyDoorTabular(mdp, gamma=0.95) | |
| V_main = main_value(mdl) | |
| v_star = float(V_main[0]) | |
| # ── ① 内部哨兵: primitive ──────────────────────────────────── | |
| base = eval_arm(mdl, [], v_star) | |
| row = {"regime": (kp, dp, gp), "label": label, | |
| "v_star": v_star, "primitive": base, "arms": {}} | |
| print(f"\n{label} V*(s0)={v_star:.6f} |goal|={int(mdl.is_goal.sum())}") | |
| print(f" {'arm':12s} {'ops':>7s} {'×prim':>6s} {'conv':>5s} " | |
| f"{'mean_k':>7s} {'max_k':>6s} {'n_opt':>5s} {'|β|':>5s} {'ops/扫':>8s}") | |
| print(f" {'primitive':12s} {base['ops']:7d} {1.0:6.3f} " | |
| f"{str(base['converged']):>5s} {1.0:7.2f} {1.0:6.2f} {'-':>5s} " | |
| f"{'-':>5s} {base['ops_per_sweep']:8.1f}") | |
| for name, lib in arm_libraries(mdl, V_main).items(): | |
| r = eval_arm(mdl, lib, v_star) | |
| r["ratio_vs_primitive"] = r["ops"] / max(1, base["ops"]) | |
| row["arms"][name] = r | |
| print(f" {name:12s} {r['ops']:7d} {r['ratio_vs_primitive']:6.3f} " | |
| f"{str(r['converged']):>5s} {r['mean_k']:7.2f} {r['max_k']:6.2f} " | |
| f"{r['n_options']:5d} {r['beta_total']:5d} {r['ops_per_sweep']:8.1f}") | |
| out["regimes"].append(row) | |
| # ── w̄ 扫描(dose-response: 中间值应当最好, 见配方 §9.3 末) ──────── | |
| print("\n" + "=" * 78) | |
| print("w̄ 扫描(regime A)—— 论文: 中间值最好; w̄→大 退化成 shortest-path") | |
| print("=" * 78) | |
| kp, dp, gp = 2, 5, 7 | |
| from hibs_lnn.skill_mdp import KeyDoorMDP | |
| mdp = KeyDoorMDP(n_pos=8, key_pos=kp, door_pos=dp, goal_pos=gp, horizon=30) | |
| mdp.set_regime(kp, dp, gp) | |
| mdl = KeyDoorTabular(mdp) | |
| V_main = main_value(mdl) | |
| base = eval_arm(mdl, [], float(V_main[0])) | |
| wbar = {} | |
| print(f" primitive: ops={base['ops']}") | |
| for w in (0.0, 0.1, 0.3, 0.5, 0.78, 1.0, 3.0, 10.0, 100.0): | |
| lib = build_rr_library(mdl, V_main, bonus_weight=w) | |
| r = eval_arm(mdl, lib, float(V_main[0])) | |
| wbar[str(w)] = r | |
| print(f" w̄={w:<6} ops={r['ops']:8d} err={r['err']:.2e} " | |
| f"conv={r['converged']} ×prim={r['ops']/max(1,base['ops']):.3f} " | |
| f"|β|={r['beta_total']}") | |
| out["wbar_sweep_regimeA"] = {"primitive_ops": base["ops"], "arms": wbar} | |
| os.makedirs("results", exist_ok=True) | |
| with open("results/rr_planning_keydoor.json", "w") as f: | |
| json.dump(out, f, indent=2, ensure_ascii=False) | |
| print("\n→ results/rr_planning_keydoor.json") | |
| if __name__ == "__main__": | |
| main() | |