fix(closed_loop): 一致性打分错位一步(对齐 last[3]→last[4],原来每次重规划奖励比上一计划慢一步的候选;Python 闭环与 ik-policy.js 同步修正);rollout 加 select;diag_cl 删 F 段、加 G 段(1/4 段采样 × consist/value/first、4 段+安全层);本机执行清单更新
This commit is contained in:
+29
-30
@@ -8,7 +8,7 @@
|
||||
B. 同一噪声下,用两份特征得到的动作差(16 步末端关节差,度)——A 有差但 B 很小说明差异无害;
|
||||
C. 闭环失败形态:首次碰撞时刻分布、碰撞步数、首次碰撞的组对、全程最小目标误差、目标误差进度比;
|
||||
以及同一批题开安全层(safety=True,近似部署)的到达率 / 刹停次数。
|
||||
F. 一步采样残留噪声:开环比较 1/2/4/8 段采样与零噪声的幅度、抖动、残差与噪声相关;闭环用 4 段采样、零噪声各跑一遍。
|
||||
G. 候选选择与采样段数:一致性打分错位修正后,1 段/4 段采样 × consist/value/first 三种选法,以及 4 段 + 安全层的闭环对照。
|
||||
用法(仓库根目录):
|
||||
PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json
|
||||
"""
|
||||
@@ -123,43 +123,42 @@ def main():
|
||||
'safety_on': {'reach': reach_s / n, 'collided': col_s / n, 'brakes_p50': pct(brakes_s, .5)}}
|
||||
out['C']['goal_err_ratio_p50'] = {'%.1fs' % ts: pct(v, .5) for ts, v in prog.items()} # <1 = 在靠近目标
|
||||
|
||||
# ---------- F:一步采样残留噪声(2026-10-19 加) ----------
|
||||
# 前几轮:子目标、抄速度、起步都已排除;但动作幅度在开环、起点、闭环三处一致是老师的 1.4–1.6 倍,
|
||||
# 而开环 16 步末端 FK 误差只有 ~6 mm。幅度大、终点却准 = 每步动作里叠着来回抵消的抖动。
|
||||
# 嫌疑:MeanFlow 一步生成 x = z − u(z,0,1) 没把噪声 z 去干净(只有 25% 样本学区间平均速度、14k 步),
|
||||
# 闭环每次只执行前 3 步,抖动不抵消,表现为「动得多、走得少」,再挤进碰撞。
|
||||
# F0:开环 val 随机 4096 行,比较 1/2/4/8 段采样和零噪声(z=0):幅度比、抖动比、残差与 z 的相关、前 3 步与 16 步末关节误差
|
||||
# F1:同一批闭环题,用 4 段采样、零噪声各跑一遍,对比 C 的基线
|
||||
stepv = torch.tensor(STEP, device=dev)
|
||||
ri = torch.randperm(vd.X.shape[0], generator=torch.Generator().manual_seed(3))[:4096].to(dev)
|
||||
Xr, Yr = vd.X[ri].float(), vd.Y[ri].float()
|
||||
zr = torch.randn(len(ri), 1, H, 6, device=dev, generator=torch.Generator(device=dev).manual_seed(11))
|
||||
ymag = Yr.abs().mean().item(); yjit = (Yr[:, 1:] - Yr[:, :-1]).abs().mean().item()
|
||||
f0 = {'teacher_amp': ymag, 'teacher_jitter': yjit}
|
||||
for tag, steps, zs in (('1step', 1, 1.0), ('2step', 2, 1.0), ('4step', 4, 1.0), ('8step', 8, 1.0), ('1step_z0', 1, 0.0)):
|
||||
with torch.no_grad(): ac, _, _ = model.sample_k(Xr, 1, zr * zs, steps=steps)
|
||||
ac = ac[:, 0]; acc = ac.clamp(-1, 1); res = ac - Yr
|
||||
zc = zr[:, 0] - zr[:, 0].mean(); rc_ = res - res.mean()
|
||||
corr = ((zc * rc_).sum() / (zc.pow(2).sum().sqrt() * rc_.pow(2).sum().sqrt()).clamp(min=1e-9)).item() if zs > 0 else None
|
||||
e3 = ((acc[:, :3] - Yr[:, :3]) * stepv).sum(1).abs().max(-1).values
|
||||
e16 = ((acc - Yr) * stepv).sum(1).abs().max(-1).values
|
||||
f0[tag] = {'amp_ratio': acc.abs().mean().item() / ymag, 'jitter_ratio': (acc[:, 1:] - acc[:, :-1]).abs().mean().item() / yjit,
|
||||
'resid_corr_z': corr, 'first3_err_deg_p50': pct(e3.tolist(), .5), 'end16_err_deg_p50': pct(e16.tolist(), .5)}
|
||||
out['F0_openloop_sampler'] = f0
|
||||
for tag, kw in (('F1_4step', {'steps': 4}), ('F1_z0', {'z_scale': 0.0})):
|
||||
rc = cc_ = 0; me = []; r2 = []
|
||||
# ---------- G:候选选择与采样段数(2026-10-19 加) ----------
|
||||
# 第四轮 F 段:一步采样残留噪声真实(抖动 42×、残差-噪声相关 −0.86),4 段采样闭环碰撞 99.6%→93%,但到达仍 0%;
|
||||
# 零噪声(8 条候选完全相同,选择不起作用)0%/100%——均值样本本身偏老师 ~4°(多峰平均)。
|
||||
# 复查闭环发现一致性打分错位一步(对齐 last[3],应为 last[4]),等于每次重规划奖励「比上一计划慢一步」的候选;
|
||||
# 已在 closed_loop.py 与 ik-policy.js 修正,上面 C 段即修正后的一步采样基线。
|
||||
# G 段在同一批题上比较:4 段采样 × 三种选法(consist 部署口径 / value 每次价值前瞻 / first 不选),以及 4 段 + 安全层。
|
||||
# 若 consist≈first,说明选择不是主因;若 value 明显好,说明一致性选法本身把闭环带偏;三者都 0% 则问题在策略本身(转向协变量偏移)。
|
||||
def variant(**kw):
|
||||
rc = cc_ = 0; me, r2, fh, sp, br = [], [], [], [], []
|
||||
for i in range(0, len(tasks), a.bs):
|
||||
ch = tasks[i:i + a.bs]
|
||||
with torch.no_grad():
|
||||
_, pp = rollout(model, kin, ch, dev, K=8, safety=False, seed=i, return_traj=True, **kw)
|
||||
_, pp = rollout(model, kin, ch, dev, K=8, seed=i, return_traj=True, **kw)
|
||||
tr = pp['traj'].to(dev); B, Tn, _ = tr.shape
|
||||
k2 = min(Tn - 1, int(round(2.0 / DT)))
|
||||
k2 = min(Tn - 1, int(round(2.0 / DT))); k1 = min(Tn - 1, int(round(1.0 / DT)))
|
||||
Tt = torch.tensor([t['T'] for t in ch], dtype=torch.float32, device=dev)
|
||||
Rt = torch.tensor([t['R'] if t.get('R') else [0.] * 9 for t in ch], dtype=torch.float32, device=dev)
|
||||
e0, _ = goal_err(kin, tr[:, 0], Tt, Rt); e2, _ = goal_err(kin, tr[:, k2], Tt, Rt)
|
||||
r2 += (e2 / e0.clamp(min=1e-3)).tolist()
|
||||
rc += int(pp['done'].sum()); cc_ += int((pp['col'] > 0).sum()); me += pp['err'].tolist()
|
||||
out[tag] = {'reach': rc / n, 'collided': cc_ / n, 'goal_err_ratio_2s_p50': pct(r2, .5), 'end_goal_err_mm_p50': pct(me, .5)}
|
||||
# 前 1 s 关节行程速度 / 老师平均速度(travel/exec_s)
|
||||
v1 = (tr[:, 1:k1 + 1] - tr[:, :k1]).abs().sum((1, 2)) / (k1 * DT)
|
||||
sp += [float(v1[b]) / max(1e-6, ch[b]['travel'] / ch[b]['exec_s']) for b in range(B) if ch[b].get('travel') and ch[b].get('exec_s')]
|
||||
flat = tr.reshape(-1, 6)
|
||||
gm = torch.cat([kin.gaps(flat[s0:s0 + 1024], 0.5)['gm'] for s0 in range(0, flat.shape[0], 1024)]).view(B, Tn, 49)
|
||||
hit = gm.min(-1).values <= 0
|
||||
for b in range(B):
|
||||
h = hit[b].nonzero().flatten()
|
||||
if len(h): fh.append(int(h[0]) * DT)
|
||||
rc += int(pp['done'].sum()); cc_ += int((pp['col'] > 0).sum()); me += pp['err'].tolist(); br += pp['brakes'].tolist()
|
||||
return {'reach': rc / n, 'collided': cc_ / n, 'goal_err_ratio_2s_p50': pct(r2, .5), 'end_goal_err_mm_p50': pct(me, .5),
|
||||
'first_hit_s_p50': pct(fh, .5), 'speed_ratio_1s_p50': pct(sp, .5), 'brakes_p50': pct(br, .5)}
|
||||
out['G_1step_consist'] = variant(steps=1, select='consist')
|
||||
out['G_4step_consist'] = variant(steps=4, select='consist')
|
||||
out['G_4step_value'] = variant(steps=4, select='value')
|
||||
out['G_4step_first'] = variant(steps=4, select='first')
|
||||
out['G_4step_consist_safety'] = variant(steps=4, select='consist', safety=True)
|
||||
s = json.dumps(out, indent=1, ensure_ascii=False); print(s)
|
||||
os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True)
|
||||
open(a.out, 'w', encoding='utf-8').write(s)
|
||||
|
||||
+10
-5
@@ -65,8 +65,8 @@ def dls_steps(kin, q, T, R, n=3, scale=0.3, lam=0.5):
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, return_traj=False, use_sub=True, sub_override=None, qp0=None, steps=1, z_scale=1.0):
|
||||
"""use_sub=False:不用子目标(sf=0);sub_override=(subs [B,2,6] 绝对关节角, need [B] bool):冷启动时用给定途经点代替子目标头;qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放(0 = 只取「均值」样本)。均为诊断用"""
|
||||
def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, return_traj=False, use_sub=True, sub_override=None, qp0=None, steps=1, z_scale=1.0, select='consist'):
|
||||
"""use_sub=False:不用子目标(sf=0);sub_override=(subs [B,2,6] 绝对关节角, need [B] bool):冷启动时用给定途经点代替子目标头;qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放(0 = 只取「均值」样本);select:consist(部署口径:冷启动价值前瞻、之后一致性)/ value(每次都价值前瞻)/ first(不选,取第 0 条)。均为诊断用"""
|
||||
g = torch.Generator(device=device); g.manual_seed(seed)
|
||||
q0, T, R, ex, tr = tasks_to_tensors(tasks, device)
|
||||
B = q0.shape[0]
|
||||
@@ -113,7 +113,9 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
if not use_sub: need = torch.zeros_like(need)
|
||||
nsub = torch.where(need, torch.full_like(nsub, 2), torch.zeros_like(nsub)); sub_t.fill_(tnow)
|
||||
fut = qe[:, None, None] + torch.cumsum(inc[:, :, r:], 2) # [B,K,H-r,6]
|
||||
if last is None:
|
||||
if select == 'first':
|
||||
score = torch.zeros(B, K, device=device); score[:, 0] = 1
|
||||
elif last is None or select == 'value':
|
||||
# 短前瞻:候选末态的价值
|
||||
qa = fut[:, :, -1].reshape(B * K, 6); qb = fut[:, :, -2].reshape(B * K, 6)
|
||||
f2 = features(kin, qa, qb, T.repeat_interleave(K, 0), R.repeat_interleave(K, 0), torch.zeros(B * K, 3, 6, device=device),
|
||||
@@ -121,9 +123,12 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
v2, _ = model.heads(model.encode(f2))
|
||||
score = -v2.view(B, K)
|
||||
else:
|
||||
n = min(fut.shape[2], last.shape[1] - 3)
|
||||
# 2026-10-19 修错位:last[i] = 上一计划从 qe_old 走 i 步后的位置;提交 3 步后 qe_new = last[3],
|
||||
# 新候选 fut[0] 是 qe_new 再走 1 步 = 对应 last[4]。原来对齐 last[3],等于奖励「比上一计划慢一步」的候选,
|
||||
# 每次重规划都挑最拖的那条,闭环越走越慢。JS 部署(ik-policy.js)同一处同样修正。
|
||||
n = min(fut.shape[2], last.shape[1] - 4)
|
||||
w = 0.85 ** torch.arange(n, device=device, dtype=torch.float32)
|
||||
d2 = (((fut[:, :, :n] - last[:, None, 3:3 + n]) / step) ** 2).sum(-1)
|
||||
d2 = (((fut[:, :, :n] - last[:, None, 4:4 + n]) / step) ** 2).sum(-1)
|
||||
score = -(d2 * w).sum(-1)
|
||||
order = score.argsort(-1, descending=True)
|
||||
pick = order[:, 0]
|
||||
|
||||
Reference in New Issue
Block a user