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:
Hark
2026-10-19 03:42:46 +00:00
parent 0a19b29fd3
commit 0f5b84b919
4 changed files with 50 additions and 80 deletions
+7 -42
View File
@@ -3,32 +3,31 @@
本文件是 Hark 对本机计算的唯一要求清单。按顺序执行,每一步把「回传」里的文件或输出原样交给用户,不要跳步,不要自行改代码。
所有命令在仓库根目录(G:\Robot_v2\机器人手臂)的 Git Bash 里运行。完成的条目由 Hark 在下一次提交里删掉。
## 已知结论(截至 2026-10-19 03:07 回传)
## 已知结论(截至 2026-10-19 03:37 回传)
- 特征口径 Python 与 JS 一致;子目标、抄上一帧速度、起步都已排除(老师轨迹中段带速度起步也 0% 到达、96% 碰撞)。
- gaps 显存已过:三条都 1.05 GiB,与点数无关。
- 稳定现象:策略动作幅度在开环、起点、闭环三处都是老师的 1.4–1.6 倍,开环 16 步末端却准(~6 mm)。幅度大、终点准 = 每步叠着来回抵消的抖动;闭环只执行前 3 步,抖动不抵消,表现为「动得多、走得少」。
- 现在的嫌疑:MeanFlow 一步生成没把噪声去干净。第 1 步的 F 段专门验证。
- 特征口径 Python 与 JS 一致;子目标、抄上一帧速度、起步都已排除;gaps 显存已过。
- 一步采样残留噪声真实(抖动 42×、残差-噪声相关 −0.86),2 段以上基本修掉;4 段闭环碰撞 99.6%→93%、2 s 误差比 0.815→0.610,到达仍 0%。零噪声 0%/100%(均值样本偏老师 ~4°)。
- 新发现的错误:闭环一致性打分错位一步(对齐上一计划的第 3 个点,应为第 4 个),等于每次重规划奖励「比上一计划慢一步」的候选。Python 闭环和浏览器部署已同时修正。第 1 步的 C 段和 G 段都按修正后口径跑。
## 第 0 步:同步代码
```bash
git status --short # 有未提交改动就先 commit,不要丢
git pull --rebase gitea main
git log --oneline -1 # 应是 Hark 的「diag: F 段 …」
git log --oneline -1 # 应是 Hark 的「fix(closed_loop): 一致性打分错位 …」
```
- 训练控制台若开着先点「停止」,确认没有 python 训练进程在跑。
- 不要启动训练 / DAgger / M5,等 Hark 看完第 1 步结果再定。
## 第 1 步:闭环失败定位(含新加的 F 段,约 6–10 分钟)
## 第 1 步:闭环失败定位(含新加的 G 段,约 10–15 分钟)
```bash
PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json
```
- 专用显存超过 22 GB 就 Ctrl+C 并回传终端输出。
- 新增输出:`F0_openloop_sampler`(开环 1/2/4/8 段采样与零噪声:幅度比、抖动比、残差与噪声相关、前 3 步与 16 步末关节误差);`F1_4step`、`F1_z0`(闭环用 4 段采样、零噪声:到达、碰撞、2 s 目标误差比)。E 段已删。
- 新增输出:`G_1step_consist`、`G_4step_consist`、`G_4step_value`、`G_4step_first`、`G_4step_consist_safety`,每项含到达、碰撞、2 s 目标误差比、末端误差、首次碰撞时刻、前 1 s 速度比、刹停次数。F 段已删。
- 回传:`ml/runs/diag_cl.json`。
## 回传汇总
@@ -39,37 +38,3 @@ PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out
| 任一步出错 | 终端最后 30 行输出 |
---
## 执行回传(2026-10-19 04:25,本地 AI)
### 第 0 步:同步代码
pull 前无未提交 tracked 改动;两个 untracked 文件原位保留(旧 `diag_collision.py`、用户个人 html)。log 顶部 = `1bf59e7`「diag: F 段 …」✓。无训练进程。
### 第 1 步:闭环失败定位(F 段;完整 JSON 已更新到 ml/runs/diag_cl.json)
C 段复跑与基线一致:0% 到达 / 99.6% 碰撞 / 2 s 误差比 0.815。
**F0_openloop_sampler**(开环 4096 行;老师 amp 0.319、jitter 0.0084):
| 采样 | 幅度比 | 抖动比 | 残差-噪声相关 | 前 3 步误差 (°) | 16 步末端误差 (°) |
|---|---|---|---|---|---|
| 1step | 1.438 | **41.9** | **−0.861** | 2.12 | 4.97 |
| 2step | 0.987 | 4.18 | 0.284 | 0.21 | 0.92 |
| 4step | 1.011 | 1.92 | −0.034 | 0.17 | 0.86 |
| 8step | 0.998 | 1.67 | 0.108 | 0.11 | 0.56 |
| 1step z=0 | 1.161 | 1.14 | — | 0.82 | 4.01 |
- **残留噪声证实**:一步生成的动作帧间抖动是老师的 **42 倍**、残差与输入噪声相关 **−0.861**(x=z−u 中 z 没去干净)。
- **多段采样有效**:2 段就把抖动降到 4.2、16 步误差 4.97°→0.92°;4 段 1.9 / 0.86°;8 段 1.7 / 0.56°。
- **零噪声**:幅度 1.16、抖动 1.14、16 步末端 4.01°——纯「均值样本」本身离老师样本约 4°。
- 1step 的前 3 步误差(2.12°)比 16 步(4.97°)小一半——抖动误差随步数累积;1.438 幅度比即这一抖动的表现。
**F1 闭环对照**(C 基线 0% / 99.6% / 0.815):
- **F1_4step**(4 段采样):到达 **0%** / 碰撞 **93.0%** / 2 s 误差比 **0.610** / 末端误差 p50 449 mm
- **F1_z0**(零噪声):到达 **0%** / 碰撞 **100.0%** / 2 s 误差比 **0.724** / 末端误差 p50 418 mm
**结论**:一步采样残留噪声真实存在(F0 证据充分),且 4 段采样把闭环碰撞从 99.6% 降到 93.0%、2 s 误差比 0.815→0.610——方向对,但**不是闭环失败的主因**:4 段与零噪声都到不了(0% 到达),零噪声甚至 100% 碰撞(比基线还差)。
### 本轮无出错,无终端报错段。
+29 -30
View File
@@ -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
View File
@@ -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]
+4 -3
View File
@@ -297,10 +297,11 @@ class Runner {
const fut = []; let qq = qe.slice(); for (let s = r; s < H; s++) { qq = qq.map((v, j) => v + inc[s][j]); fut.push(qq); }
let j1 = 0; for (let s = 0; s < H; s++) j1 += inc[s][0];
let score = -2.0 * out.value[k];
if (this.lastPlan) { // 与上一条已选计划的未来重叠步对齐(时间平移 3 步)
if (this.lastPlan) { // 与上一条已选计划的未来重叠步对齐:lastPlan[i] = 旧 qe 走 i 步,新 qe = lastPlan[commit],fut[s] 对应 lastPlan[s+commit+1]
// 2026-10-19 修错位(原对齐 lastPlan[s+commit],奖励比上一计划慢一步的候选;Python closed_loop.py 同步修正)
let c = 0, w = 1;
for (let s = 0; s < fut.length && s + o.commit < this.lastPlan.length; s++, w *= 0.85) {
let d2 = 0; for (let j = 0; j < 6; j++) d2 += Math.pow((fut[s][j] - this.lastPlan[s + o.commit][j]) / STEP[j], 2);
for (let s = 0; s < fut.length && s + o.commit + 1 < this.lastPlan.length; s++, w *= 0.85) {
let d2 = 0; for (let j = 0; j < 6; j++) d2 += Math.pow((fut[s][j] - this.lastPlan[s + o.commit + 1][j]) / STEP[j], 2);
c += w * d2;
}
score -= 0.02 * c;