From 3e015b7485545fe49afbf4e9ddd6a43579b81131 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Tue, 20 Oct 2026 22:26:34 +0800 Subject: [PATCH] =?UTF-8?q?feat+test:=20=E6=8D=A2=E5=90=91=E7=8E=87=20reve?= =?UTF-8?q?rse=5Frate=20=E8=BF=9B=E9=97=AD=E7=8E=AF=E6=8C=87=E6=A0=87?= =?UTF-8?q?=EF=BC=88=E6=8C=89=E8=BD=B4=E6=96=B9=E5=90=91=E7=BF=BB=E8=BD=AC?= =?UTF-8?q?/=E6=B4=BB=E5=8A=A8=E6=AD=A5=EF=BC=8C=E7=AB=99=E4=BD=8F?= =?UTF-8?q?=E4=B8=8D=E9=87=8D=E7=BD=AE=EF=BC=89=EF=BC=8C=E5=B8=A6=E6=AD=A3?= =?UTF-8?q?=E5=AF=B9=E7=85=A7=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/探索日志.md | 7 +++++++ ml/eval/eval_cl.py | 3 +++ ml/sim/closed_loop.py | 8 +++++++- tests/test_eval_cl.py | 3 +++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/探索日志.md b/docs/探索日志.md index 32a4977..1007a94 100644 --- a/docs/探索日志.md +++ b/docs/探索日志.md @@ -52,6 +52,13 @@ 1. 新加的 `backoff` 指标一度在产物里根本没有——`ml/eval/eval_cl.py:run()` 的 `out` 是**自己显式列键**的,不是从 `rollout` 返回的字典通抄,所以我往 `res` 加的 `backoff_mean` 被无声丢掉。已改成从逐题 `per['backoff']` 汇总(`backoff_mean` + `reject_all_mean = backoff+brakes`,后者才是与"原来全拒次数"可比的量)。**教训:加指标要顺着输出链路走到底,别只改生产端。** 2. 停后台 GPU 作业时,`TaskStop` 之后 `bash` 脚本链会活下来并继续拉起下一个 python;而 `nvidia-smi --query-compute-apps` 在这台机器上列不出这些 python(只列 ShellHost/wallpaper32 之类),据此判断"卡已空"会误判。我第一次按 PID 杀子进程后,链条 30 秒后又起了一个新的 eval。第二次我按进程树杀,但**把父子关系读反了**(Windows 的 PPID 在父进程已死时是陈旧值),结果误杀了新链的第一臂。正确做法:重启前用命令行正则列出全量候选、数 python 个数确认单租户,重启后再数一次;杀之前先按 `StartTime` 判新旧。backoff 那一臂因此要补跑。 +## 换向率进了评估指标(轮次 39 续) + +- 分解既然指到"来回抖",每个臂都要直接报这个数,不能每次回头读转储:`rollout` 里按轴记方向,`reverse_rate` = 同轴方向翻转的步数 / 活动步数(站住的步不重置方向,所以"停了再同向走"不算换向);`eval_cl` 报 `reverse_p50`/`reverse_mean`。 +- **正对照**(`tests/test_eval_cl.py`,42 项全绿):抖动轨迹报 >0.4、匀速轨迹报 ≈0——指标本身要证明有灵敏度,不然等于没测。 +- 待交叉验证:新指标在对照臂上应复现转储离线算的 **23.5%**(p50);对不上就是两个口径有一个错。 +- 生产端改了、输出端没改=没改:上一次 `backoff_mean` 就是这么丢的,这次加指标同时改了 `closed_loop.per` 与 `eval_cl` 的显式键列表。 + ## 行程超出的成因分解(轮次 39 续,纯 CPU 读轮次 33 base 转储) - 口径先对齐:只在"有老师参考且到达"的题上算,自测 **2.425** 与 eval 报的 2.445 同量级(差别在我按 `t_done/DT` 取窗端,eval 逐步累加),后面的数都在这个口径上。 diff --git a/ml/eval/eval_cl.py b/ml/eval/eval_cl.py index d48c722..d3587e6 100644 --- a/ml/eval/eval_cl.py +++ b/ml/eval/eval_cl.py @@ -49,6 +49,9 @@ def run(model, kin, tasks, device, bs=256, **kw): out.update({'speed_ratio_p50': median(v), 'speed_ratio_p95': v.quantile(0.95).item(), 'speed_ratio_max': v.max().item(), 'over_vmax_rate': (v > 1.001).float().mean().item(), 'accel_ratio_p95': acc.quantile(0.95).item(), 'accel_ratio_max': acc.max().item()}) + if 'reverse_rate' in flat: # 换向步占比:行程超出主要是同一轴来回抖(轮次 39 分解),所以每个臂都要直接报它 + rr = flat['reverse_rate'] + out.update({'reverse_p50': median(rr), 'reverse_mean': rr.mean().item()}) if 'backoff' in flat and flat['backoff'].any(): # backoff=缩步顶上的次数;brakes 现在只记连最小档都不安全的 out['backoff_mean'] = flat['backoff'].mean().item() # ⇒ 原来的"全拒"事件数 = 两者之和 out['reject_all_mean'] = (flat['backoff'] + flat['brakes']).mean().item() diff --git a/ml/sim/closed_loop.py b/ml/sim/closed_loop.py index f91d095..05830fd 100644 --- a/ml/sim/closed_loop.py +++ b/ml/sim/closed_loop.py @@ -202,6 +202,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, first_hit = torch.full((B,), float('nan'), device=device); dls_it = torch.full((B,), -1, dtype=torch.long, device=device) # 2026-10-20(轮次33):速度/加速度按「实际执行位移」算,不看指令——第30轮证明反馈是在限幅之后叠加的 vratio = torch.zeros(B, device=device); aratio = torch.zeros(B, device=device); prev_exec = torch.zeros(B, 6, device=device) + rev = torch.zeros(B, device=device); nact = torch.zeros(B, device=device); prev_sgn = torch.zeros(B, 6, device=device) traj = [q.clone()] if return_traj else None plans = {k: [] for k in ('it', 'r', 'qe', 'commit', 'points', 'midpoints', 'gaps', 'certified')} if return_traj else None n_start = int(round(0.5 / DT)); moved_start = None # R2 起步失败:0.5 s 内关节总行程 < 1° @@ -478,6 +479,10 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, vratio = torch.maximum(vratio, (exec_d / step).max(-1).values * act_) # 实际速度 / VMAX aratio = torch.maximum(aratio, ((exec_d - prev_exec) / step).max(-1).values * act_) # 每控制步的速度变化 / VMAX prev_exec = exec_d + sgn = torch.sign(q - qp) # 换向率:轮次 39 的分解证明"行程超出 2.4 倍"主要是同一轴来回抖,不是绕远 + rev += ((sgn != 0) & (prev_sgn != 0) & (sgn == -prev_sgn)).float().sum(-1) * act_ + prev_sgn = torch.where(sgn != 0, sgn, prev_sgn) # 零步不重置方向(站住再走同向不算换向) + nact += act_ e, oe = goal_err(kin, q, T, R) if it + 1 == k2s: e2 = e.clone() newly = (~done) & (e < 1) & (oe < 0.5) @@ -506,7 +511,8 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, side = torch.where(j1_first != 0, j1_first, torch.where(j1_max >= -j1_min, torch.ones_like(j1_max), -torch.ones_like(j1_max))) per = {'done': done.cpu(), 'ok': ok.cpu(), 't_done': t_done.cpu(), 'limit': limit.cpu(), 'col': col.cpu(), 'travel': travel.cpu(), 'err': e_end.cpu(), 'side': side.cpu(), 'start_fail': start_fail.cpu(), 'brakes': brakes.cpu(), 'backoff': bo.cpu(), 'first_hit': first_hit.cpu(), 'dls_it': dls_it.cpu(), - 'goal_ratio_2s': ratio2.cpu(), 'speed_ratio': vratio.cpu(), 'accel_ratio': aratio.cpu()} + 'goal_ratio_2s': ratio2.cpu(), 'speed_ratio': vratio.cpu(), 'accel_ratio': aratio.cpu(), + 'reverse_rate': (rev / nact.clamp_min(1.0)).cpu()} if return_traj: per['traj'] = torch.stack(traj, 1).cpu() per['plans'] = {k: torch.tensor(v) if k in ('it', 'r') else torch.stack(v, 1).cpu() for k, v in plans.items()} diff --git a/tests/test_eval_cl.py b/tests/test_eval_cl.py index a1ac3ca..ddcef9b 100644 --- a/tests/test_eval_cl.py +++ b/tests/test_eval_cl.py @@ -820,6 +820,9 @@ class TravelPenaltySelectTest(unittest.TestCase): self.assertAlmostEqual(p0['traj'][0, 1, 0].item(), 0.9 * 1.2, places=5) p1 = self.run1(1.0) # 加惩罚:行程 10.8 vs 3.6 ⇒ 选匀速那条 self.assertAlmostEqual(p1['traj'][0, 1, 0].item(), 0.225 * 1.2, places=5) + # 换向率指标要有灵敏度:抖动轨迹必须报出高换向,匀速轨迹必须接近 0(正对照,不然等于没测) + self.assertGreater(float(p0['reverse_rate'][0]), 0.4) + self.assertLess(float(p1['reverse_rate'][0]), 1e-6) def test_waste_penalty_also_separates_them(self): """换成"白费比例"(1 − 同轴净位移/累计行程)也必须能分开,且罚度按当前误差缩放: