exp+fix: try3 反向档位(刹车+93%/到达-6.6pp)与 K16 构成试候选数的单调轴;reverse 指标补动步口径+可手算负对照
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"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": 3,
|
||||
"sel_w": 0.1,
|
||||
"dls_safety": 1,
|
||||
"fb_clear": 0.0,
|
||||
"fb_cmin": 0.3,
|
||||
"sub_refresh": 0,
|
||||
"fb_block_gain": 0.5,
|
||||
"dump": null,
|
||||
"out": "docs/experiment_round39_try3.json",
|
||||
"trace": null,
|
||||
"select": "endpoint",
|
||||
"replan": "empty",
|
||||
"safety": 1,
|
||||
"partial": 1,
|
||||
"inc_cap": 1,
|
||||
"gap_margin": 0.0,
|
||||
"cert_batch": 0,
|
||||
"steps": 1,
|
||||
"z_scale": 1.0,
|
||||
"len_w": 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.45703125,
|
||||
"collision_free_success": 0.45703125,
|
||||
"time_ok_1p3": 0.23828125,
|
||||
"collision_rate": 0.0,
|
||||
"collision_steps_mean": 0.0,
|
||||
"travel_ratio_median": 2.529658794403076,
|
||||
"time_mean_s": 2.3210256099700928,
|
||||
"brakes_mean": 37.75390625,
|
||||
"start_fail": 0.0,
|
||||
"goal_ratio_2s": 0.3564552068710327,
|
||||
"err_fail_med": 220.24862670898438,
|
||||
"err_success_med": 0.23727791011333466,
|
||||
"dls_share": 0.56640625,
|
||||
"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.22442243993282318,
|
||||
"reverse_mean": 0.23038837313652039
|
||||
},
|
||||
"simulation_wall_s": 170.3933407000004
|
||||
}
|
||||
@@ -107,6 +107,22 @@
|
||||
- **对 `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)要读的第一个数。
|
||||
|
||||
## try3 反向档位 + 换向指标的稀释修正(轮次 39 续)
|
||||
|
||||
- `--safety_try 3`(只允许安全层试按分数排序的前 3 条候选,其余不变、仍 `--inc_cap 1`):到达 **0.4570**(对照 0.5234,−6.6 pp)、时限内 0.2383(−2.7 pp)、**刹车 37.75**(对照 19.55,+93%)、行程比 2.530、碰撞仍 0(`docs/experiment_round39_try3.json`)。
|
||||
- 把"允许试几条候选"这条轴摆开,是**单调**的:
|
||||
|
||||
| 试候选数 | 到达 | 时限内 | 刹车/题 |
|
||||
|---|---|---|---|
|
||||
| 3 | 0.4570 | 0.2383 | 37.75 |
|
||||
| 8(对照) | 0.5234 | 0.2656 | 19.55 |
|
||||
| 16 | 0.5625 | 0.2969 | 13.77 |
|
||||
|
||||
⇒ "全拒是没备选而不是真没路"这条假设有**双向**证据(减档更差、加档更好),不只是单点相关。边际收益在递减:3→8 值 +6.6 pp,8→16 只值 +3.9 pp。
|
||||
- **但不能当部署结论**:认证点数与候选数成正比(K=8 每次规划 32 个点 → K=16 是 64 个),而 GPU 周期 P50 已经是 33.5 ms、超 20 ms 预算。所以接下来必须先量 `K=8 vs 16` 的 B=1 独占微基准(`ml/runs/_kcost_probe.py`,已验证调用签名);如果 K=16 把周期推到 45 ms 以上,这条路就只能靠"一次批量调用覆盖全部候选"把常数项压回去(轮次 34 已证批量把最坏 80.2→37.1 ms)。
|
||||
- **换向指标被刹停稀释,已修**:try3 报 `reverse_p50=0.2244`,比对照的离线同窗口值 0.247 **还低**,而它恰恰是站住最多的臂——因为分母 `nact` 把站住的步也算进去了。补了两个口径:`reverse_rate_moving`(只算动过的步)与 `moving_share`(动步占比,=1−站住比例)。
|
||||
配套写了**可手算的负对照**:`+0.9 → 站一拍 → −0.9` 这一条轨迹给 1/18(全步口径)vs 1/12(动步口径)、moving_share=2/3 ⇒ 稀释效应是真的,不是猜的。写夹具时又踩一次自己的坑:`commit_n=1` 时每拍都重新规划、永远只执行候选的第 0 步,序列里的 0 与 −0.9 根本没被执行到(末步报 3.24° 而不是 0°)——夹具必须按"一次规划提交 3 步"来构造。测试 42→43 项全绿。
|
||||
|
||||
## K16:候选数翻倍是本轮第一个正向结果(轮次 39 续)
|
||||
|
||||
- 同一 256 题、同 seed、`--inc_cap 1`、其余配方不变,只把 `K` 从 8 加到 16(`docs/experiment_round39_K16.json`):
|
||||
|
||||
+3
-1
@@ -51,7 +51,9 @@ def run(model, kin, tasks, device, bs=256, **kw):
|
||||
'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()})
|
||||
out.update({'reverse_p50': median(rr), 'reverse_mean': rr.mean().item(),
|
||||
'reverse_moving_p50': median(flat['reverse_rate_moving']), # 只在动了的步上算,不被刹停稀释
|
||||
'moving_share_p50': median(flat['moving_share'])}) # 活动窗内动了的步占比(=1−站住比例)
|
||||
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()
|
||||
|
||||
@@ -203,6 +203,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
# 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)
|
||||
nmov = torch.zeros(B, 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°
|
||||
@@ -479,10 +480,11 @@ 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 倍"主要是同一轴来回抖,不是绕远
|
||||
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_
|
||||
nmov += act_ * (exec_d.sum(-1) > 1e-6).float() # 只在"动了"的步上算换向:站住会稀释分母(刹停越多的臂会显得越不抖)
|
||||
e, oe = goal_err(kin, q, T, R)
|
||||
if it + 1 == k2s: e2 = e.clone()
|
||||
newly = (~done) & (e < 1) & (oe < 0.5)
|
||||
@@ -513,7 +515,9 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
'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(),
|
||||
# 口径与离线分解一致:换向的 (步,轴) 对数 / 活动的 (步,轴) 对数 ∈ [0,1](除以 6 个轴)
|
||||
'reverse_rate': (rev / (6.0 * nact.clamp_min(1.0))).cpu()}
|
||||
'reverse_rate': (rev / (6.0 * nact.clamp_min(1.0))).cpu(),
|
||||
'reverse_rate_moving': (rev / (6.0 * nmov.clamp_min(1.0))).cpu(),
|
||||
'moving_share': (nmov / 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()}
|
||||
|
||||
@@ -824,6 +824,9 @@ class TravelPenaltySelectTest(unittest.TestCase):
|
||||
# 口径 = 换向的(步,轴)对 / 活动的(步,轴)对;匀速条一条都不翻。
|
||||
self.assertAlmostEqual(float(p0['reverse_rate'][0]), 2.0 / 18.0, places=5)
|
||||
self.assertLess(float(p1['reverse_rate'][0]), 1e-6)
|
||||
# 这条夹具全程都在动(没有站住的步),所以"只算动过的步"的口径必须与旧口径相同、动步占比 = 1
|
||||
self.assertAlmostEqual(float(p0['reverse_rate_moving'][0]), float(p0['reverse_rate'][0]), places=6)
|
||||
self.assertAlmostEqual(float(p0['moving_share'][0]), 1.0, places=6)
|
||||
|
||||
def test_waste_penalty_also_separates_them(self):
|
||||
"""换成"白费比例"(1 − 同轴净位移/累计行程)也必须能分开,且罚度按当前误差缩放:
|
||||
@@ -862,5 +865,29 @@ class TravelPenaltySelectTest(unittest.TestCase):
|
||||
torch.testing.assert_close(out[0]['traj'], out[1]['traj'], rtol=0, atol=0)
|
||||
|
||||
|
||||
def test_standing_steps_dilute_the_all_step_version(self):
|
||||
"""负对照(可手算):+0.9 → 站一拍 → −0.9。方向翻转发生在"停住"两侧,
|
||||
全步口径给 1/(3×6)=1/18,只算动过的步给 1/(2×6)=1/12 ⇒ 证明"刹停越多显得越不抖"这个稀释效应是真的。"""
|
||||
class SafeKin:
|
||||
def clamp_box(self, q):
|
||||
return q
|
||||
|
||||
def gaps(self, q, cap):
|
||||
return {'gm': torch.full((len(q), 49), 1.0, device=q.device)}
|
||||
|
||||
seq = [[0.9, 0, 0, 0, 0, 0], [0.0, 0, 0, 0, 0, 0], [-0.9, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 13
|
||||
acts = torch.tensor([seq], dtype=torch.float32) # 只有 1 条候选
|
||||
with patch('ml.sim.closed_loop.features', return_value=torch.zeros(1, 83)), \
|
||||
patch('ml.sim.closed_loop.goal_err',
|
||||
side_effect=lambda kin, q, T, R: (torch.full((q.shape[0],), 10.0), torch.zeros(q.shape[0]))):
|
||||
_, per = rollout(self.StubModel(acts), SafeKin(), [{'q0': [0] * 6, 'T': [10, 0, 0]}], 'cpu',
|
||||
K=1, safety=True, max_sec=0.06, return_traj=True, use_sub=False,
|
||||
select='endpoint', replan='empty', commit_n=3, safety_partial=3)
|
||||
self.assertAlmostEqual(float(per['traj'][0, 3, 0]), 0.0, places=5) # 1.08 → 停 → 回 0
|
||||
self.assertAlmostEqual(float(per['reverse_rate'][0]), 1.0 / 18.0, places=5)
|
||||
self.assertAlmostEqual(float(per['reverse_rate_moving'][0]), 1.0 / 12.0, places=5)
|
||||
self.assertAlmostEqual(float(per['moving_share'][0]), 2.0 / 3.0, places=5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user