评价体系与基线
状态:草案 · 最后更新 2026-11-08 · 可执行配置见
evals/· 日志格式见 evals/log-format.md
比基线表本身更重要的是:我们允许自己被什么结果推翻。 所以下面每条主张都对应一个可以测出"我错了"的指标。
先分清:主张配置 vs 消融基线
本系统自己的配置是 full(什么也不关,见 sim/baseline.py)。 下面四条 yaml 基线都是它的消融版本——所以正确的对照是 full vs B1、full vs B2, 而不是拿 B1 当"我们的系统"去比。早期版本把 B1 误当全开,已在 ADR-0003 纠正。
四条基线
| 编号 | 名称 | 行为决策来源 | 是否依赖 LLM | 用途 |
|---|---|---|---|---|
| B0 | LLM 中心决策 | 全部经 LLM | 是,断线即停 | 对照组:当前主流做法 |
| B0′ | 行为树基线 | 传统行为树 | 否 | 对照组:工业界现状 |
| B1 | 无情绪调制 | L1/L2/L4 保留,移除 L3 调制 | L4 用 | 检验"情绪调制"是否真的有用 |
| B2 | 保留需求生成但移除 L1 抢占 | L1 生成需求但不抢占 | L4 用 | 检验"抢占权"是否真的必要 |
配置在 evals/baselines/,字段含义见 evals/README.md。
为什么是这四条
- 缺 B0,就无法回答"和现在的做法比好在哪";
- 缺 B0′,就无法回答"比不用 LLM 的传统方案好在哪";
- 缺 B1/B2,就无法回答"情绪和抢占是不是多余的装饰"——这是本项目最容易被攻击的两点, 所以它们必须能被单独关掉。
G1-full:人形基线(2026-11-08 新增)
上面四条都是胶囊上的消融。g1-full 不是第五条消融,它是换身体的参照配置: 开关逐项等于 full,身体换成 Unitree G1(29-DOF)。配置在 evals/baselines/g1-full.yaml; 为什么另立一档、代价是什么,见 ADR-0008。
口径
| 项 | 值 |
|---|---|
| 命令 | server/.venv/Scripts/python.exe -m sim.main --body g1 --baseline g1-full --duration 30 --no-render |
| 身体 / 场景 | G1(29-DOF)· stage1 静态房间(9 件家具,没有会动的东西) |
| 时长 / 频率 | 30 s × 100 Hz;时间基相对误差上限 1%(超了 exit 2) |
| 日志核对点 | .meta.json 里的 baseline_file / body / baseline_sha256 |
| 可比性 | 不可与胶囊数字直接比:出生点、drive_toward 的物理含义(推力 vs 命令速度)、推通道、充电桩全不一样 |
跑法(n≥5,不许报单次值)
for s in 7 8 9 10 11; do
server/.venv/Scripts/python.exe -m sim.main --body g1 --baseline g1-full --duration 30 --no-render --seed $s
donen=5 实测分布(2026-11-08 · seed 7–11 各 1 次 · 同一份 g1-full.yaml)
| 指标 | mean | min | max | 标准差(n−1) | n |
|---|---|---|---|---|---|
| 平均速度(m/s) | 0.2178 | 0.2120 | 0.2230 | 0.0051 | 5 |
| 净位移(m) | 3.1388 | 3.0730 | 3.1720 | 0.0387 | 5 |
| z_min(m,pelvis 最低点;≤0.50 判摔) | 0.7122 | 0.7100 | 0.7148 | 0.0017 | 5 |
| L2 否决次数 | 1.0 | 1 | 1 | 0.0 | 5 |
| L4 决策数 | 2.2 | 2 | 3 | 0.4472 | 5 |
逐次数字、日志 sha256、冻结的原始 JSONL 见 evals/g1-full/README.md。
两条口径必须一起看
同一初始状态 + 默认 seed 连跑 5 次,5 份日志的 sha256 全同(逐位相同)。所以「n≥5」在这里 不是「取平均消噪声」,而是「用跨 seed 的扰动看离散度」:--seed 只喂 mock LLM 的延迟抽签, 抽签决定 plan 在哪一刻被采纳,轨迹与站桩时长跟着变;判据、阈值、场景参数一个没动。
30 s 步行对初始坐标浮点敏感(出生点挪 1e-9 m,walk_30s 从 0.5711 变 0.5752 m/s)。 所以跨版本只允许比「机制是否生效」(否决次数、决策数、有没有摔), 不允许比 0.00x m/s 的快慢——那个量级落在分布里,不构成证据。
三处语义不成立(照写 TBD,不伪造信号)
L1.charging:房间没有充电桩,靠近C.CHARGER_POS那个地面点就被判成「在充电」 —— 实测 1471 个控制周期(14.71 s / 30 s)被判充电,电量最终 100.0%,这条回升没有物理依据。L3.hold_charge:由第 1 条生出来的动机(没有桩就没有「保持充电」)。L2.push:人形没有「推底盘」通道,否决在main.py接成速度层后退; 所以overrides=1的那一次没有测到 L2 的推力通路(push_ignored=0,push 分支没被调用)。
与本节「统计要求」的 ≥20 次仍有差距:n=5 是下限,够报分布、不够报区间(n<8 一律不给 95% 区间)。 想要更硬的结论得做出生点/参数扫描——那属于场景变更版本,未做。
指标
| 指标 | B0 | B0′ | B1 | B2 | 测量方法 |
|---|---|---|---|---|---|
| LLM 断线存活时长 | 0(所有行为经 LLM) | 高(不依赖 LLM) | 同 B0′ | 同 B0′ | 固定任务集,断 LLM 后计时 |
| 需求驱动行为占比 | 0 | 0 | 0 | 保留生成,不触发抢占 | 分母 = 全部行为决策数 |
| 情绪调制可观测性 | 不适用 | 不适用 | 无显著差异 | 同 B1 | 同一任务改变情绪向量,统计检验行为参数 |
| 异常自恢复率 | 低 | 中 | 同 B0′ | 同 B0′ | 注入失败,记录无人工干预修正成功率 |
| 抢占正确率 | 不适用 | 不适用 | 不适用 | 无抢占(对照) | 事后规则判定"该抢"比例 |
| 发育曲线 | 平坦 | 平坦 | 平坦 | 平坦 | 长期运行中同类任务成功率/耗时趋势 |
表中的"高/中/低/平坦"是预期方向,不是已测得的数值。真实数值待 v0.1 仿真产出。 任何把预期写成实测的文档,都算数据造假。
关键判定规则
"该抢"怎么判:抢占事件发生后,若无抢占则 1 s 内会发生力超阈值 / 碰撞 / 人进入安全距离, 则判定"该抢"。这条规则是事后判定的,所以必须有人工复核的抽样比例(TBD)。
"抢占正确率"现在有实现了:sim/counterfactual.py 按本节的"若没抢则 1 s 内会撞"做 反事实推演(克隆物理状态、假设没听见否决、跑 1 秒),日志里留 CF counterfactual 记录。 早期版本的"1 - 碰撞/否决"只是代理,已废弃。
"需求驱动行为占比"怎么算:分母为全部行为决策;分子为 L1 稳态偏离触发且非外部指令触发的决策。 依赖 goal_candidate.source_need 字段(见 interfaces.md)——这就是为什么它必填。
"LLM 断线存活"怎么测:固定任务集 + 固定断线时点 + 计时到系统进入安全稳态。 任务集与时长定义在 evals/README.md,改任务集必须同步改基线版本号。
统计要求
| 项 | 要求 | 状态 |
|---|---|---|
| 每项指标重复次数 | ≥ 20 次(草案) | TBD |
| 显著性检验方法 | 参数/非参数待定;情绪调制建议配对检验 | TBD |
| 报告格式 | 均值 + 置信区间 + 原始日志链接 | 见 log-format |
我们不打算做的评价
- 不做 demo 视频评分。好看不等于对。
- 不做单一任务的 SOTA 比较。这是架构提案,不是 benchmark 选手。
- 不把阶段 2 指标当近期承诺。v0.1–v0.3 只留观测钩子。
待补
- [ ] 固定任务集的具体清单与版本化方式
- [ ] 各指标的通过线(不是"提高",而是"高于多少算成立")
- [ ] 失败注入的注入器设计(断电、打滑、遮挡、人闯入)
- [ ] 抢占正确率的人工复核协议与抽样率