diag: H 段(老师 oracle 走 rollout / 前缀消融 / 子目标越过顶点规则 / 分轴与分类别指标);训练:EMA、选模不落在 p_neq 预热、闭环分数选模、24000 步余弦、p_neq 0.5 + p_edge 0.1、子目标头切断抄输入、前缀/dq 增广;仿真:last 记真正提交的候选、碰撞与到达按每题时限、回放门禁读 pass+指纹并走 rollout;数据:动作标签按 1.25·STEP 归一化(act_scale 随 meta/ckpt/ONNX 传递,旧模型 1.0)、收回亏空结转、DAgger 带 qPrev 与起步速度、打包签名变更自动重打;ik-policy.js 同步(actScale、子目标规则、DLS 时清 lastPlan);本机执行清单与 README 更新
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
其余项读已有文件(缺文件记为 missing,不算通过):
|
||||
ml/runs/policy_bench.json 部署形态:各集合穿模帧 = 0;near 30/30(pos、pose);route 全部到达;nc1000 0 失败;刹停率 < 2%;R8 恢复 ≥ 95%、活锁 = 0
|
||||
ml/data/r2.json R2 近邻标签 std < 10% 步长
|
||||
ml/runs/bench_*/log.jsonl R10 吞吐 ≥ 5000 样本/s(≈ 60000 步 ≤ 3.5 h)
|
||||
ml/runs/bench_*/log.jsonl R10 吞吐 ≥ 5000 样本/s(24000 步 batch 1024 ≈ 1.4 h)
|
||||
ml/runs/m5_gate_*.json M5 头门禁
|
||||
ml/runs/mine/r*/gate.json R7 最后一轮左右比例 / 行程门禁
|
||||
ml/runs/r4.json R4 浏览器推理 batch=8 P99 < 15 ms(r4 探针)
|
||||
@@ -37,8 +37,8 @@ def main():
|
||||
items.append({'item': name, 'value': value, 'pass': (None if ok is None else bool(ok)), 'rule': rule})
|
||||
|
||||
# 1) held-out 闭环(仿真安全层)
|
||||
ck = torch.load(a.ckpt, map_location=a.device)
|
||||
model = build(ck['cfg']).to(a.device); model.load_state_dict(ck['model']); model.eval()
|
||||
from ml.common.ckpt import load_policy # 2026-10-19:EMA 权重 + act_scale
|
||||
model, ck = load_policy(a.ckpt, a.device)
|
||||
kin = Kin(device=a.device)
|
||||
val = [t for t in load_tasks([a.samples]) if t['split'] == 'val']
|
||||
reach = [t for t in val if t.get('reached') and t['cat'] != 'D']
|
||||
@@ -97,7 +97,7 @@ def main():
|
||||
for f in sorted(glob.glob('ml/runs/bench_*/log.jsonl')):
|
||||
recs = [json.loads(l) for l in open(f) if '"samples_per_s"' in l]
|
||||
if recs: sps = max(sps or 0, recs[-1]['samples_per_s'])
|
||||
add('R10 训练吞吐(样本/s)', sps, sps is not None and sps >= 5000, '≥ 5000(≈ 60000 步 ≤ 3.5 h)')
|
||||
add('R10 训练吞吐(样本/s)', sps, sps is not None and sps >= 5000, '≥ 5000(24000 步 ≈ 1.4 h)')
|
||||
m5 = [jload(f) for f in sorted(glob.glob('ml/runs/m5_gate_*.json'))]
|
||||
m5 = [m for m in m5 if m]
|
||||
add('M5 头门禁', [{'head': m['head'], 'collision_rate': m['policy_only']['collision_rate']} for m in m5] or 'missing',
|
||||
|
||||
+3
-2
@@ -60,14 +60,15 @@ def measure(d, a, pool):
|
||||
# 与池无关的口径:全部有效近邻对(取距离较近的一半,排除远处非线性段)上回归 std ~ a + b·dist,截距 a
|
||||
dd = best_d[fin]; jj = best_j[fin]; qf = torch.nonzero(fin).squeeze(1)
|
||||
half = dd <= torch.quantile(dd, 0.5)
|
||||
Ya, Yb = d.Y[qi][qf[half], :3].float(), d.Y[pi][jj[half], :3].float()
|
||||
# 2026-10-19:Y 以 act_scale·STEP 为单位(DATA_VER 2 起 1.25),乘回 act_scale 让判据仍是「STEP 的比例」,与旧数据可比
|
||||
Ya, Yb = d.Y[qi][qf[half], :3].float() * d.act_scale, d.Y[pi][jj[half], :3].float() * d.act_scale
|
||||
sa = (Ya - Yb).pow(2).mean(-1).mean(-1).sqrt() / math.sqrt(2)
|
||||
A = torch.stack([torch.ones_like(dd[half]), dd[half]], 1)
|
||||
coef = torch.linalg.lstsq(A.cpu(), sa.cpu()[:, None]).solution.squeeze(1)
|
||||
std_norm = max(0.0, coef[0].item())
|
||||
thr = torch.quantile(best_d[fin], a.frac)
|
||||
m = fin & (best_d <= thr)
|
||||
Yq, Yp = d.Y[qi][m, :3].float(), d.Y[pi][best_j[m], :3].float()
|
||||
Yq, Yp = d.Y[qi][m, :3].float() * d.act_scale, d.Y[pi][best_j[m], :3].float() * d.act_scale
|
||||
rms = (Yq - Yp).pow(2).mean(-1).mean(-1).sqrt() # 每对、6 轴 × 3 步平均
|
||||
std = (rms / math.sqrt(2))
|
||||
return {'pairs': int(m.sum()), 'pool': P, 'queries': Q, 'nn_dist_threshold': thr.item(),
|
||||
|
||||
+217
-69
@@ -1,26 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
"""闭环失败定位(只读,手动运行,约 5–8 分钟;训练进行中也可以另开终端跑,显存约 2–3 GB)。
|
||||
|
||||
2026-10-19 新增:第二次预训练(979a581)开环已经正常(候选0 末端误差 ~6 mm、K 中最好 ~2.9 mm、多样性 0.65),
|
||||
闭环却仍 100% 碰撞、0% 到达。开环用的是 JS 生成的特征 X,闭环用的是 Python features() 现算的特征,
|
||||
两者之间只要有一处口径不同,模型在闭环里看到的就是没见过的输入。本脚本回答三件事:
|
||||
A. 特征口径:val 任务起点(非增广、老师状态)上,Python features() 与数据集 X 的逐组最大差;
|
||||
B. 同一噪声下,用两份特征得到的动作差(16 步末端关节差,度)——A 有差但 B 很小说明差异无害;
|
||||
C. 闭环失败形态:首次碰撞时刻分布、碰撞步数、首次碰撞的组对、全程最小目标误差、目标误差进度比;
|
||||
以及同一批题开安全层(safety=True,近似部署)的到达率 / 刹停次数。
|
||||
G. 候选选择与采样段数:一致性打分错位修正后,1 段/4 段采样 × consist/value/first 三种选法,以及 4 段 + 安全层的闭环对照。
|
||||
用法(仓库根目录):
|
||||
"""闭环失败定位(只读,手动运行,n=256 约 35–50 分钟,显存约 3–6 GB)。
|
||||
PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json
|
||||
"""
|
||||
import argparse, json, os
|
||||
任务 = val 集前 n 题;老师轨迹按任务 id 从 ml/data/shard_*.jsonl 取(取不到的题不进 H 段,数量记在 H_tasks_missing_teacher)。
|
||||
模型经 ckpt.load_policy 载入(有 EMA 用 EMA;act_scale 随 checkpoint,旧 checkpoint = 1.0)。
|
||||
|
||||
段落:
|
||||
A. 特征口径:val 任务起点(非增广、老师状态)上,Python features() 与数据集 X 的逐组最大差 / 平均差。
|
||||
B. 同一噪声下两份特征得到的动作差(16 步末端关节差,度)。
|
||||
C. 旧部署口径(1 段采样、一致性选择、子目标旧规则 timeout)的失败形态:首撞时刻、碰撞步数、首撞组对、最小目标误差、
|
||||
目标误差进度比、开安全层的到达 / 碰撞 / 刹停。到达 = 时限内到达,碰撞只计时限内(2026-10-19 起)。
|
||||
H(2026-10-19,三份审查 review_closedloop / review_model_train / review_data_actions 的决定性实验):
|
||||
H1 老师 oracle 走完整 rollout()(closed_loop.TeacherOracle;select='first'、use_sub=False):
|
||||
H1_oracle:额外含 oracle 字段——DLS 接管前与老师轨迹的最大关节偏差、到达 / 碰撞、完成时刻 − 老师时长;
|
||||
H1_oracle_noise_<σ>(σ = 0.05 / 0.1 / 0.2,STEP 为单位,每次规划重采):闭环对残差噪声的敏感度。
|
||||
H2 前缀消融:H2_open_loop(r=3 的 val 行,前缀 47:68 原样 vs 置零,执行窗口 3..5 的误差);
|
||||
H2_replan_empty_s{1,4}:队列空了才规划(每次 r=0、提交 3 步),其余同部署。
|
||||
H3 子目标:H3_headsub_cross_s{1,4}(子目标头 + 越过顶点规则 = 新部署默认)、H3_headsub_timeout_s{1,4}(旧规则 = 旧部署)、
|
||||
H3_teachersub_cross_s{1,4} / H3_teachersub_timeout_s{1,4}(老师真实途经点全表)、H3_nosub_s{1,4}(不用子目标)。
|
||||
H4 指标(上面每个闭环变体都输出同一组键,见 metrics());H4_open_loop:按前缀长度 r=0..3 的执行窗口误差。
|
||||
_s1 / _s4 = MeanFlow 1 段(部署)/ 4 段采样。"""
|
||||
import argparse, json, math, os
|
||||
|
||||
import torch
|
||||
|
||||
from ml.common.ckpt import load_policy
|
||||
from ml.common.kin_torch import Kin
|
||||
from ml.common.features import features, STEP, H
|
||||
from ml.data import Samples, load_tasks
|
||||
from ml.model.policy import build
|
||||
from ml.sim.closed_loop import rollout, goal_err, DT
|
||||
from ml.data import Samples, load_tasks, read_meta
|
||||
from ml.sim.closed_loop import rollout, goal_err, DT, TeacherOracle
|
||||
from ml.eval.replay_teacher import load_teacher, pad_traj, oracle_raw, oracle_summary
|
||||
|
||||
# 83 维特征分组(与 features.py 拼接顺序一致)
|
||||
GROUPS = [('sincos', 0, 12), ('joint_norm', 12, 15), ('dq', 15, 21), ('p', 21, 24), ('Rq', 24, 30), ('T', 30, 33),
|
||||
@@ -29,26 +37,134 @@ GROUPS = [('sincos', 0, 12), ('joint_norm', 12, 15), ('dq', 15, 21), ('p', 21, 2
|
||||
|
||||
|
||||
PROG_T = (0.5, 1.0, 2.0) # 目标误差 / 起点误差 的取样时刻(s)
|
||||
K1, K2 = int(round(1.0 / DT)), int(round(2.0 / DT)) # 1 s / 2 s 对应的步数
|
||||
|
||||
|
||||
def pct(v, p):
|
||||
v = sorted(v)
|
||||
v = sorted(x for x in v if x is not None and x == x)
|
||||
return v[min(len(v) - 1, int(len(v) * p))] if v else None
|
||||
|
||||
|
||||
def qt(t, p):
|
||||
t = t[torch.isfinite(t)]
|
||||
return round(float(t.kthvalue(max(1, min(len(t), int(math.ceil(p * len(t)))))).values), 4) if len(t) else None
|
||||
|
||||
|
||||
def rnd(x, n=4):
|
||||
if isinstance(x, list): return [rnd(v, n) for v in x]
|
||||
return None if x is None or x != x else round(float(x), n)
|
||||
|
||||
|
||||
def pad_len(tr, n):
|
||||
"""轨迹 [B,T,6] 末帧重复补到至少 n 帧(到达后冻结,补帧与冻结等价)。"""
|
||||
if tr.shape[1] >= n: return tr
|
||||
return torch.cat([tr, tr[:, -1:].expand(-1, n - tr.shape[1], -1)], 1)
|
||||
|
||||
|
||||
def batch_stats(kin, ch, per, TR):
|
||||
"""一批的逐题量(CPU)。窗口:幅度 / 净位移 / TCP 用前 1 s,变号率用前 2 s 的 3 步块。"""
|
||||
dev = TR.device
|
||||
stp = torch.tensor(STEP, device=dev)
|
||||
tr = pad_len(per['traj'].to(dev), K2 + 1); te = pad_len(TR, K2 + 1)
|
||||
B = tr.shape[0]
|
||||
amp = lambda x: (x[:, 1:K1 + 1] - x[:, :K1]).abs().sum(1) # [B,6] 前 1 s 关节行程
|
||||
net = lambda x: (x[:, K1] - x[:, 0]).abs() # [B,6] 前 1 s 净位移
|
||||
def tcp(x):
|
||||
p = kin.fk(x[:, :K1 + 1].reshape(-1, 6)).view(B, K1 + 1, 3)
|
||||
return (p[:, 1:] - p[:, :-1]).norm(dim=-1).sum(1), (p[:, -1] - p[:, 0]).norm(dim=-1)
|
||||
def flips(x):
|
||||
idx = torch.arange(0, K2 + 1, 3, device=dev)
|
||||
b = x[:, idx[1:]] - x[:, idx[:-1]] # [B,nb,6] 3 步块增量
|
||||
big = b.abs() > 0.1 * stp
|
||||
v = big[:, 1:] & big[:, :-1]
|
||||
f = v & (torch.sign(b[:, 1:]) != torch.sign(b[:, :-1]))
|
||||
return f.sum(1).float(), v.sum(1).float()
|
||||
tl_p, tn_p = tcp(tr); tl_t, tn_t = tcp(te)
|
||||
fp_, vp_ = flips(tr); ft_, vt_ = flips(te)
|
||||
ex = torch.tensor([t.get('exec_s') or float('nan') for t in ch])
|
||||
return {'ok': per['ok'], 'col': per['col'] > 0, 'isD': torch.tensor([t.get('cat') == 'D' for t in ch]),
|
||||
'pose': torch.tensor([bool(t.get('R')) for t in ch]), 'key': [t.get('cat', '?') + '/' + t.get('mode', '?') for t in ch],
|
||||
'r2': per['goal_ratio_2s'], 'err': per['err'], 'first_hit': per['first_hit'], 'brakes': per['brakes'],
|
||||
'tratio': per['t_done'] / ex,
|
||||
'amp_p': amp(tr).cpu(), 'amp_t': amp(te).cpu(), 'net_p': net(tr).cpu(), 'net_t': net(te).cpu(),
|
||||
'tl_p': tl_p.cpu(), 'tl_t': tl_t.cpu(), 'tn_p': tn_p.cpu(), 'tn_t': tn_t.cpu(),
|
||||
'fl_p': fp_.cpu(), 'fv_p': vp_.cpu(), 'fl_t': ft_.cpu(), 'fv_t': vt_.cpu()}
|
||||
|
||||
|
||||
def metrics(stats):
|
||||
"""H4 指标(每个闭环变体同一组键):
|
||||
reach / collision:cat≠D、时限内(D 类目标不可达,单列在 D);
|
||||
goal_ratio_2s_p50:2 s 时目标误差 / 起点误差;end_goal_err_mm_p50;first_hit_s_p50(撞了的题);t_done_over_teacher_p50(到达的题);
|
||||
joint_amp_ratio[6]:前 1 s 各轴关节行程 Σ|Δq| 策略 / 老师(全体题合计;J6 只算 pose 题,pos 题老师冻结 J6);
|
||||
joint_net_ratio[6]:前 1 s 各轴净位移 |q(1s) − q(0)| 策略 / 老师(同上);j6_pos_amp_deg_p50:pos 题策略 J6 前 1 s 行程(度,老师为 0);
|
||||
tcp_path_ratio_p50 / tcp_net_ratio_p50:前 1 s TCP 路程 / 净位移 策略 / 老师(逐题比,老师 > 1 mm 的题);
|
||||
sign_flip_rate[6] / sign_flip_rate_teacher[6]:前 2 s 相邻 3 步块增量变号的比例(两块都 > 0.1·STEP 才计);
|
||||
by_cat_mode:各 cat/mode 的 n、reach、collision、goal_ratio_2s_p50(不含 D);D:n、collision、end_goal_err_mm_p50、goal_ratio_2s_p50。"""
|
||||
c = {k: (sum((s[k] for s in stats), []) if isinstance(stats[0][k], list) else torch.cat([s[k] for s in stats])) for k in stats[0]}
|
||||
keys = c['key']
|
||||
m = ~c['isD']; pose = c['pose']
|
||||
def jr(a, b):
|
||||
out = []
|
||||
for j in range(6):
|
||||
sel = m & pose if j == 5 else m
|
||||
den = float(b[sel, j].sum()); out.append(float(a[sel, j].sum()) / den if den > 1e-6 else None)
|
||||
return rnd(out)
|
||||
tl_ok = m & (c['tl_t'] > 1); tn_ok = m & (c['tn_t'] > 1)
|
||||
out = {'n': int(m.sum()),
|
||||
'reach': rnd(c['ok'][m].float().mean()) if m.any() else None,
|
||||
'collision': rnd(c['col'][m].float().mean()) if m.any() else None,
|
||||
'goal_ratio_2s_p50': qt(c['r2'][m], .5), 'end_goal_err_mm_p50': qt(c['err'][m], .5),
|
||||
'first_hit_s_p50': qt(c['first_hit'][m], .5), 't_done_over_teacher_p50': qt(c['tratio'][m & c['ok']], .5),
|
||||
'brakes_p50': qt(c['brakes'][m], .5),
|
||||
'joint_amp_ratio': jr(c['amp_p'], c['amp_t']), 'joint_net_ratio': jr(c['net_p'], c['net_t']),
|
||||
'j6_pos_amp_deg_p50': qt(c['amp_p'][m & ~pose, 5], .5),
|
||||
'tcp_path_ratio_p50': qt(c['tl_p'][tl_ok] / c['tl_t'][tl_ok], .5), 'tcp_net_ratio_p50': qt(c['tn_p'][tn_ok] / c['tn_t'][tn_ok], .5),
|
||||
'sign_flip_rate': rnd((c['fl_p'][m].sum(0) / c['fv_p'][m].sum(0).clamp(min=1)).tolist()),
|
||||
'sign_flip_rate_teacher': rnd((c['fl_t'][m].sum(0) / c['fv_t'][m].sum(0).clamp(min=1)).tolist())}
|
||||
by = {}
|
||||
for i, k in enumerate(keys):
|
||||
if not bool(m[i]): continue
|
||||
by.setdefault(k, []).append(i)
|
||||
out['by_cat_mode'] = {k: {'n': len(ix), 'reach': rnd(c['ok'][ix].float().mean()), 'collision': rnd(c['col'][ix].float().mean()),
|
||||
'goal_ratio_2s_p50': qt(c['r2'][ix], .5)} for k, ix in sorted(by.items())}
|
||||
d = c['isD']
|
||||
out['D'] = {'n': int(d.sum()), 'collision': rnd(c['col'][d].float().mean()) if d.any() else None,
|
||||
'end_goal_err_mm_p50': qt(c['err'][d], .5), 'goal_ratio_2s_p50': qt(c['r2'][d], .5)}
|
||||
return out
|
||||
|
||||
|
||||
def teacher_subs(recs, device):
|
||||
"""老师真实途经点全表(path 去掉起终点)→ (subs [B,M,6], n [B]),供 rollout(sub_override=...)。"""
|
||||
vs = [r['path'][1:-1] for r in recs]
|
||||
M = max(1, max(len(v) for v in vs))
|
||||
subs = torch.zeros(len(vs), M, 6, device=device); n = torch.zeros(len(vs), dtype=torch.long, device=device)
|
||||
for i, v in enumerate(vs):
|
||||
if v:
|
||||
x = torch.tensor(v, dtype=torch.float32, device=device); subs[i, :len(v)] = x; subs[i, len(v):] = x[-1]; n[i] = len(v)
|
||||
return subs, n
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--ckpt', required=True); ap.add_argument('--samples', default='ml/data/samples')
|
||||
ap.add_argument('--shards', default='ml/data/shard_*.jsonl')
|
||||
ap.add_argument('--n', type=int, default=256); ap.add_argument('--bs', type=int, default=64)
|
||||
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu'); ap.add_argument('--out', default='ml/runs/diag_cl.json')
|
||||
a = ap.parse_args()
|
||||
dev = a.device
|
||||
ck = torch.load(a.ckpt, map_location=dev)
|
||||
model = build(ck['cfg']).to(dev); model.load_state_dict(ck['model']); model.eval()
|
||||
model, ck = load_policy(a.ckpt, dev)
|
||||
kin = Kin(device=dev)
|
||||
out = {'ckpt': a.ckpt, 'step': ck.get('step')}
|
||||
astep = torch.tensor(STEP, device=dev) * model.act_scale # 动作 → 度
|
||||
data_scale = float(read_meta(a.samples).get('act_scale', 1.0))
|
||||
out = {'ckpt': a.ckpt, 'step': ck.get('step'), 'weights': 'ema' if ck.get('ema') is not None else 'model',
|
||||
'act_scale_ckpt': model.act_scale, 'act_scale_data': data_scale}
|
||||
tasks = [t for t in load_tasks([a.samples]) if t['split'] == 'val'][:a.n]
|
||||
by_tid = {t['tid']: t for t in tasks}
|
||||
def save():
|
||||
s = json.dumps(out, indent=1, ensure_ascii=False)
|
||||
os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True)
|
||||
open(a.out + '.tmp', 'w', encoding='utf-8').write(s); os.replace(a.out + '.tmp', a.out)
|
||||
return s
|
||||
|
||||
# ---------- A / B:起点特征口径 ----------
|
||||
vd = Samples(a.samples, device=dev, split='val')
|
||||
@@ -67,35 +183,69 @@ def main():
|
||||
T = torch.tensor([t['T'] for t in tc], dtype=torch.float32, device=dev)
|
||||
R = torch.tensor([t['R'] if t.get('R') else [0.] * 9 for t in tc], dtype=torch.float32, device=dev)
|
||||
B = qc.shape[0]
|
||||
qpc = torch.tensor([t.get('qp0') or t['q0'] for t in tc], dtype=torch.float32, device=dev)
|
||||
pre = Xc[:, 47:65].view(B, 3, 6) * torch.tensor(STEP, device=dev) # 前缀 / 子目标直接取数据集里的,只比状态相关部分
|
||||
pl = Xc[:, 65:68].sum(-1).round()
|
||||
sf = Xc[:, 74]
|
||||
sub = qc + Xc[:, 68:74] * 180
|
||||
Fp = features(kin, qc, qc, T, R, pre, pl, sub, sf)
|
||||
Fp = features(kin, qc, qpc, T, R, pre, pl, sub, sf)
|
||||
out['A_maxdiff'] = {g: (Fp[:, s:e] - Xc[:, s:e]).abs().max().item() for g, s, e in GROUPS}
|
||||
out['A_meandiff'] = {g: (Fp[:, s:e] - Xc[:, s:e]).abs().mean().item() for g, s, e in GROUPS}
|
||||
with torch.no_grad():
|
||||
z = torch.randn(B * 8, H, 6, device=dev, generator=torch.Generator(device=dev).manual_seed(7))
|
||||
a1, _, _ = model.sample_k(Xc, 8, z); a2, _, _ = model.sample_k(Fp, 8, z)
|
||||
stepv = torch.tensor(STEP, device=dev)
|
||||
d = ((a1.clamp(-1, 1) - a2.clamp(-1, 1)) * stepv).sum(2).abs().max(-1).values # [B,K] 16 步末端关节差(度)
|
||||
mag = (a1.clamp(-1, 1) * stepv).sum(2).abs().max(-1).values
|
||||
d = ((a1.clamp(-1, 1) - a2.clamp(-1, 1)) * astep).sum(2).abs().max(-1).values # [B,K] 16 步末端关节差(度)
|
||||
mag = (a1.clamp(-1, 1) * astep).sum(2).abs().max(-1).values
|
||||
out['B_act_diff_deg'] = {'mean': d.mean().item(), 'p90': d.flatten().kthvalue(max(1, int(0.9 * d.numel()))).values.item(),
|
||||
'ref_motion_deg_mean': mag.mean().item()}
|
||||
|
||||
# ---------- C:闭环失败形态 ----------
|
||||
# ---------- H4 / H2 开环:执行窗口误差(val 行,老师状态) ----------
|
||||
# 闭环稳态 r=3,真正执行的是 act[r:r+3];原来的 ol_fk_mm 是 16 步终点误差,前缀那几步几乎不会错,看不出执行窗口。
|
||||
with torch.no_grad():
|
||||
pl_all = vd.X[:, 65:68].sum(-1).round().long()
|
||||
gsel = torch.Generator(device=dev); gsel.manual_seed(321)
|
||||
def rows_with(r, n=1024):
|
||||
ix = (pl_all == r).nonzero().flatten()
|
||||
return ix[torch.randperm(len(ix), device=dev, generator=gsel)[:n]] if len(ix) else ix
|
||||
def win_err(X, Y, r, steps, k0=None):
|
||||
k0 = r if k0 is None else k0
|
||||
z = torch.randn(X.shape[0] * 8, H, 6, device=dev, generator=torch.Generator(device=dev).manual_seed(11))
|
||||
act, _, _ = model.sample_k(X, 8, z, steps=steps); act = act.float().clamp(-1, 1)
|
||||
e = ((act[:, :, k0:k0 + 3] - Y[:, None, k0:k0 + 3]) * astep).sum(2).abs() # [N,K,6] 窗口累计误差(度)
|
||||
emax = e.max(-1).values.flatten()
|
||||
res = {'n_rows': int(X.shape[0]), 'deg_mean': rnd(emax.mean()), 'deg_p90': qt(emax, .9), 'per_joint_deg_mean': rnd(e.mean((0, 1)).tolist())}
|
||||
if r > 0 and k0 == r:
|
||||
ce = ((act[:, :, :r] - Y[:, None, :r]) * astep).abs().max(-1).values.max(-1).values.flatten()
|
||||
res['prefix_copy_err_deg_mean'] = rnd(ce.mean())
|
||||
return res
|
||||
ol4, ol2 = {}, {}
|
||||
for steps in (1, 4):
|
||||
for r in range(4):
|
||||
ix = rows_with(r)
|
||||
if len(ix): ol4['s%d_r%d' % (steps, r)] = win_err(vd.X[ix].float(), vd.Y[ix].float(), r, steps)
|
||||
ix = rows_with(3)
|
||||
if len(ix):
|
||||
X3 = vd.X[ix].float(); Y3 = vd.Y[ix].float(); X3z = X3.clone(); X3z[:, 47:68] = 0
|
||||
ol2['s%d_prefix_orig' % steps] = win_err(X3, Y3, 3, steps)
|
||||
ol2['s%d_prefix_zeroed' % steps] = win_err(X3z, Y3, 0, steps, k0=3)
|
||||
out['H4_open_loop'] = ol4; out['H2_open_loop'] = ol2
|
||||
del vd
|
||||
if dev.startswith('cuda'): torch.cuda.empty_cache()
|
||||
|
||||
# ---------- C:旧部署口径的闭环失败形态(子目标旧规则,便于与前几轮对比) ----------
|
||||
first_t, col_steps, pairs, min_err, reach = [], [], {}, [], 0
|
||||
prog = {ts: [] for ts in PROG_T}
|
||||
reach_s, brakes_s, col_s = 0, [], 0
|
||||
for i in range(0, len(tasks), a.bs):
|
||||
ch = tasks[i:i + a.bs]
|
||||
with torch.no_grad():
|
||||
_, per = rollout(model, kin, ch, dev, K=8, safety=False, seed=i, return_traj=True)
|
||||
_, per = rollout(model, kin, ch, dev, K=8, safety=False, seed=i, return_traj=True, sub_rule='timeout')
|
||||
tr = per['traj'].to(dev) # [B,Tn,6]
|
||||
B, Tn, _ = tr.shape
|
||||
flat = tr.reshape(-1, 6) # 分块 ≤1024 点(同 dagger/collect.py 的显存修复)
|
||||
gm = torch.cat([kin.gaps(flat[s:s + 1024], 0.5)['gm'] for s in range(0, flat.shape[0], 1024)]).view(B, Tn, 49)
|
||||
hit = gm.min(-1).values <= 0 # [B,Tn]
|
||||
inlim = (torch.arange(Tn, device=dev)[None] * DT) <= per['limit'].to(dev)[:, None] + 1e-6
|
||||
hit = (gm.min(-1).values <= 0) & inlim # [B,Tn] 只计时限内
|
||||
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)
|
||||
e, _ = goal_err(kin, tr.reshape(-1, 6), Tt.repeat_interleave(Tn, 0), Rt.repeat_interleave(Tn, 0))
|
||||
@@ -103,16 +253,15 @@ def main():
|
||||
for b in range(B):
|
||||
for ts in PROG_T:
|
||||
k = min(Tn - 1, int(round(ts / DT))); prog[ts].append((e[b, k] / e[b, 0].clamp(min=1e-3)).item())
|
||||
# 到达后轨迹被冻结(d=0),之后不再计
|
||||
for b in range(B):
|
||||
if per['done'][b]: reach += 1
|
||||
if per['ok'][b]: reach += 1
|
||||
min_err.append(e[b].min().item()); col_steps.append(int(per['col'][b]))
|
||||
h = hit[b].nonzero().flatten()
|
||||
if len(h):
|
||||
k = int(h[0]); first_t.append(k * DT)
|
||||
pr = int(gm[b, k].argmin()); key = '%d-%d' % (pr // 7, pr % 7); pairs[key] = pairs.get(key, 0) + 1
|
||||
rs, ps = rollout(model, kin, ch, dev, K=8, safety=True, seed=i)
|
||||
reach_s += int(ps['done'].sum()); col_s += int((ps['col'] > 0).sum()); brakes_s += ps['brakes'].tolist()
|
||||
rs, ps = rollout(model, kin, ch, dev, K=8, safety=True, seed=i, sub_rule='timeout')
|
||||
reach_s += int(ps['ok'].sum()); col_s += int((ps['col'] > 0).sum()); brakes_s += ps['brakes'].tolist()
|
||||
n = len(tasks)
|
||||
out['C'] = {'n': n, 'reach': reach / n, 'collided': len(first_t) / n,
|
||||
'first_hit_s': {'p10': pct(first_t, .1), 'p50': pct(first_t, .5), 'p90': pct(first_t, .9),
|
||||
@@ -122,46 +271,45 @@ def main():
|
||||
'min_goal_err_mm': {'p10': pct(min_err, .1), 'p50': pct(min_err, .5), 'p90': pct(min_err, .9)},
|
||||
'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 = 在靠近目标
|
||||
print(save(), flush=True)
|
||||
|
||||
# ---------- 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]
|
||||
# ---------- H:闭环变体(同一批 val 题,老师轨迹按 id 取) ----------
|
||||
teach = load_teacher(a.shards, [t['id'] for t in tasks])
|
||||
tH = [t for t in tasks if t['id'] in teach]
|
||||
out['H_tasks'] = len(tH); out['H_tasks_missing_teacher'] = len(tasks) - len(tH)
|
||||
|
||||
def variant(oracle_sigma=None, teacher_sub=False, **kw):
|
||||
stats, raws = [], []
|
||||
for i in range(0, len(tH), a.bs):
|
||||
ch = tH[i:i + a.bs]; recs = [teach[t['id']] for t in ch]
|
||||
TR = pad_traj([r['traj'] for r in recs], dev)
|
||||
kw2 = dict(kw)
|
||||
if oracle_sigma is not None:
|
||||
kw2.update(oracle=TeacherOracle(TR, act_scale=data_scale, sigma=oracle_sigma), select='first', use_sub=False)
|
||||
if teacher_sub: kw2['sub_override'] = teacher_subs(recs, dev)
|
||||
with torch.no_grad():
|
||||
_, 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))); 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()
|
||||
# 前 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)
|
||||
_, per = rollout(None if oracle_sigma is not None else model, kin, ch, dev, K=8, seed=i, return_traj=True, **kw2)
|
||||
stats.append(batch_stats(kin, ch, per, TR))
|
||||
if oracle_sigma is not None: raws.append(oracle_raw(per, TR, [len(r['traj']) for r in recs], ch))
|
||||
del per
|
||||
res = metrics(stats)
|
||||
if oracle_sigma is not None and oracle_sigma == 0: res['oracle'] = oracle_summary(raws)
|
||||
if dev.startswith('cuda'): torch.cuda.empty_cache()
|
||||
return res
|
||||
|
||||
plan = [('H1_oracle', dict(oracle_sigma=0.0))]
|
||||
plan += [('H1_oracle_noise_%g' % s, dict(oracle_sigma=s)) for s in (0.05, 0.1, 0.2)]
|
||||
for st in (1, 4):
|
||||
plan += [('H3_headsub_cross_s%d' % st, dict(steps=st, sub_rule='cross')),
|
||||
('H3_headsub_timeout_s%d' % st, dict(steps=st, sub_rule='timeout')),
|
||||
('H3_teachersub_cross_s%d' % st, dict(steps=st, sub_rule='cross', teacher_sub=True)),
|
||||
('H3_teachersub_timeout_s%d' % st, dict(steps=st, sub_rule='timeout', teacher_sub=True)),
|
||||
('H3_nosub_s%d' % st, dict(steps=st, use_sub=False)),
|
||||
('H2_replan_empty_s%d' % st, dict(steps=st, sub_rule='cross', replan='empty'))]
|
||||
for name, kw in plan:
|
||||
out[name] = variant(**kw)
|
||||
save(); print('[diag] %s 完成' % name, flush=True)
|
||||
print(save(), flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -43,10 +43,8 @@ def main():
|
||||
from ml.model.policy import build
|
||||
from ml.sim.closed_loop import rollout, goal_err
|
||||
|
||||
ck = torch.load(a.ckpt, map_location=a.device)
|
||||
model = build(ck['cfg']).to(a.device)
|
||||
model.load_state_dict(ck['model'])
|
||||
model.eval()
|
||||
from ml.common.ckpt import load_policy # 2026-10-19:EMA 权重 + act_scale
|
||||
model, ck = load_policy(a.ckpt, a.device)
|
||||
kin = Kin(device=a.device)
|
||||
if ck.get('fp') and ck['fp'] != kin.fp:
|
||||
sys.exit('checkpoint 的碰撞模型指纹 %s 与当前 kin.json %s 不符' % (ck['fp'], kin.fp))
|
||||
|
||||
+106
-11
@@ -1,19 +1,90 @@
|
||||
"""老师轨迹回放自检(2026-10-18 首次正式预训练后新增;train 阶段开训前自动跑,约 1 分钟):
|
||||
把 gen 写出的老师 50 Hz 轨迹(keep=true)直接放进闭环仿真用的 PyTorch 运动学 / 碰撞 / 到达判据里检查。
|
||||
老师轨迹在 JS 侧已认证无碰、已到达;如果这里判出碰撞或未到达,说明仿真口径(kin.json、间隙、到达门槛)和数据对不上,
|
||||
那么闭环指标本身不可信,训练多少步都没用 —— 直接停下(rc 5)。
|
||||
"""老师轨迹回放自检(train 阶段开训前自动跑,约 1–3 分钟):
|
||||
python -m ml.eval.replay_teacher --n 512 --out ml/runs/replay_teacher.json
|
||||
判据:到达率(cat≠D)≥ 0.95,碰撞率 ≤ 0.01。"""
|
||||
import argparse, glob, json, os, sys
|
||||
两部分,都过才 pass(pipeline.sh 读 pass 字段与指纹,不过就停 rc 5):
|
||||
1. 运动学口径(2026-10-18):gen 写出的老师 50 Hz 轨迹(keep=true、cat≠D)直接放进 PyTorch 运动学 / 碰撞 / 到达判据:
|
||||
到达率 ≥ 0.95、碰撞率 ≤ 0.01。验证的是「仿真口径(kin.json、间隙、到达门槛)与 JS 认证过的数据一致」。
|
||||
2. 滚动口径(2026-10-19,审查 review_closedloop F1/D1、review_data_actions C1):上面那条完全不经过 rollout(),
|
||||
队列 / 前缀 / 提交 3 步 / qe / 标签 clamp / DLS / done / 时限上的错位它都发现不了。这里让老师当策略(closed_loop.TeacherOracle:
|
||||
每次规划从当前时间下标取老师 16 步增量,按标签口径 clamp)原样走 rollout()(select='first'、use_sub=False):
|
||||
到达(时限内)≥ 0.95、碰撞 ≤ 0.01、DLS 接管前与老师轨迹的最大关节偏差 p99 ≤ 0.1°(shard 轨迹按 1e-3° 取整,标签 clamp 会截掉取整的余量)。
|
||||
输出另带 fp(当前 kin.json)、data_fp(shard 里记录的碰撞模型指纹)、ver=2;pipeline 三者都核对。
|
||||
load_teacher / pad_traj / oracle_raw / oracle_summary 供 ml/eval/diag_cl.py 的 H1 段复用。"""
|
||||
import argparse, glob, json, math, os, sys
|
||||
import torch
|
||||
|
||||
from ml.common.kin_torch import Kin
|
||||
from ml.sim.closed_loop import goal_err
|
||||
from ml.sim.closed_loop import goal_err, rollout, TeacherOracle, DT
|
||||
|
||||
DEV_TOL_DEG = 0.1
|
||||
|
||||
|
||||
def load_teacher(shards, ids):
|
||||
"""按任务 id 从 shard jsonl 取老师记录(traj、path、exec_s…)。只解析 id 命中的行。"""
|
||||
want, out = set(ids), {}
|
||||
for f in sorted(glob.glob(shards)):
|
||||
for line in open(f, encoding='utf-8'):
|
||||
k = line.find('"id":"')
|
||||
if k < 0: continue
|
||||
rid = line[k + 6:line.find('"', k + 6)]
|
||||
if rid in want and rid not in out:
|
||||
r = json.loads(line)
|
||||
if r.get('traj'): out[rid] = r
|
||||
if len(out) == len(want): break
|
||||
return out
|
||||
|
||||
|
||||
def pad_traj(trajs, device, extra=20):
|
||||
"""[B,Tmax+extra,6]:各题老师轨迹末帧重复补齐(oracle 在轨迹末尾之后给零增量)。"""
|
||||
Tm = max(len(t) for t in trajs) + extra
|
||||
out = torch.zeros(len(trajs), Tm, 6, device=device)
|
||||
for i, t in enumerate(trajs):
|
||||
x = torch.tensor(t, dtype=torch.float32, device=device)
|
||||
out[i, :len(t)] = x; out[i, len(t):] = x[-1]
|
||||
return out
|
||||
|
||||
|
||||
def oracle_raw(per, TR, lens, tasks):
|
||||
"""oracle 滚动的逐题量:DLS 接管前(且到达前、老师轨迹结束前)与老师的最大关节偏差 mdev(度)、时限内到达 ok、
|
||||
时限内碰撞 col、是否计入到达统计(cat≠D)、完成时刻 − 老师时长 dt(s)、是否用过 DLS、2 s 目标误差比。"""
|
||||
tr = per['traj'].to(TR.device); B, Tn, _ = tr.shape
|
||||
L = min(Tn, TR.shape[1])
|
||||
dev = (tr[:, :L] - TR[:, :L]).abs().max(-1).values # [B,L]
|
||||
k = torch.arange(L, device=TR.device)[None]
|
||||
kd = torch.tensor(lens, device=TR.device)[:, None] - 1
|
||||
dls = per['dls_it'].to(TR.device)[:, None]
|
||||
td = per['t_done'].to(TR.device)
|
||||
kdone = torch.where(torch.isfinite(td), (td / DT).round(), torch.full_like(td, 1e9)).long()[:, None]
|
||||
valid = (k <= kd) & ((dls < 0) | (k <= dls)) & (k <= kdone)
|
||||
ex = torch.tensor([t.get('exec_s') or float('nan') for t in tasks])
|
||||
return {'mdev': torch.where(valid, dev, torch.zeros_like(dev)).max(-1).values.cpu(), 'ok': per['ok'], 'col': per['col'] > 0,
|
||||
'reach_m': torch.tensor([t.get('cat') != 'D' for t in tasks]), 'dt': per['t_done'] - ex,
|
||||
'dls': per['dls_it'] >= 0, 'r2': per['goal_ratio_2s']}
|
||||
|
||||
|
||||
def _q(v, p):
|
||||
v = v[torch.isfinite(v)]
|
||||
return float(v.kthvalue(max(1, min(len(v), int(math.ceil(p * len(v)))))).values) if len(v) else None
|
||||
|
||||
|
||||
def oracle_summary(raws):
|
||||
c = {k: torch.cat([r[k] for r in raws]) for k in raws[0]}
|
||||
m = c['reach_m']; ok = c['ok']
|
||||
dt = c['dt'][ok & m]
|
||||
return {'n': int(len(m)), 'n_reach_tasks': int(m.sum()),
|
||||
'reach': float(ok[m].float().mean()) if m.any() else None,
|
||||
'collision': float(c['col'][m].float().mean()) if m.any() else None,
|
||||
'D': {'n': int((~m).sum()), 'collision': float(c['col'][~m].float().mean()) if (~m).any() else None},
|
||||
'max_dev_deg_before_dls': {'p50': _q(c['mdev'], .5), 'p99': _q(c['mdev'], .99), 'max': float(c['mdev'].max())},
|
||||
'dls_used': float(c['dls'].float().mean()),
|
||||
'finish_minus_teacher_s': {'p50': _q(dt, .5), 'p90': _q(dt, .9), 'max': float(dt.max()) if len(dt) else None},
|
||||
'goal_ratio_2s_p50': _q(c['r2'], .5)}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--shards', default='ml/data/shard_*.jsonl'); ap.add_argument('--n', type=int, default=512)
|
||||
ap.add_argument('--samples', default='ml/data/samples', help='读打包 meta 的 act_scale(oracle 按同一标签口径 clamp);没有就按 1.0')
|
||||
ap.add_argument('--bs', type=int, default=128)
|
||||
ap.add_argument('--out', default='ml/runs/replay_teacher.json')
|
||||
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu')
|
||||
a = ap.parse_args()
|
||||
@@ -26,6 +97,7 @@ def main():
|
||||
if len(recs) >= a.n: break
|
||||
if len(recs) >= a.n: break
|
||||
if not recs: sys.exit('没有可回放的老师轨迹(先跑 gen)')
|
||||
# ---- 1. 运动学口径
|
||||
reach = col = 0; worst_gap = []
|
||||
for r in recs:
|
||||
q = torch.tensor(r['traj'], dtype=torch.float32, device=a.device)
|
||||
@@ -37,11 +109,34 @@ def main():
|
||||
e, oe = goal_err(kin, q[-1:], T, R)
|
||||
reach += bool((e < 1).item() and (oe < 0.5).item())
|
||||
n = len(recs)
|
||||
res = {'n': n, 'reach': reach / n, 'collision': col / n, 'min_gap_mm_p10': sorted(worst_gap)[n // 10],
|
||||
'pass': reach / n >= 0.95 and col / n <= 0.01, 'fp': kin.fp}
|
||||
kin_ok = reach / n >= 0.95 and col / n <= 0.01
|
||||
# ---- 2. 滚动口径:老师 oracle 走完整 rollout()
|
||||
try:
|
||||
from ml.data import read_meta
|
||||
act_scale = float(read_meta(a.samples).get('act_scale', 1.0)) if os.path.isdir(a.samples) else 1.0
|
||||
except SystemExit:
|
||||
act_scale = 1.0
|
||||
tasks = [{'q0': r['q0'], 'T': r['T'], 'R': r.get('R'), 'exec_s': r.get('exec_s') or (len(r['traj']) - 1) * DT,
|
||||
'travel': r.get('travel_deg'), 'cat': r.get('cat')} for r in recs]
|
||||
raws = []
|
||||
for i in range(0, n, a.bs):
|
||||
tb, rb = tasks[i:i + a.bs], recs[i:i + a.bs]
|
||||
TR = pad_traj([r['traj'] for r in rb], a.device)
|
||||
_, per = rollout(None, kin, tb, a.device, K=1, select='first', use_sub=False, return_traj=True, seed=i,
|
||||
oracle=TeacherOracle(TR, act_scale=act_scale))
|
||||
raws.append(oracle_raw(per, TR, [len(r['traj']) for r in rb], tb))
|
||||
orc = oracle_summary(raws); orc['act_scale'] = act_scale
|
||||
orc_ok = (orc['reach'] or 0) >= 0.95 and (orc['collision'] or 0) <= 0.01 and orc['max_dev_deg_before_dls']['p99'] <= DEV_TOL_DEG
|
||||
data_fps = {r.get('fp') for r in recs}
|
||||
data_fp = data_fps.pop() if len(data_fps) == 1 else 'mixed'
|
||||
res = {'ver': 2, 'n': n, 'reach': reach / n, 'collision': col / n, 'min_gap_mm_p10': sorted(worst_gap)[n // 10], 'kin_pass': kin_ok,
|
||||
'oracle': orc, 'oracle_pass': orc_ok, 'fp': kin.fp, 'data_fp': data_fp,
|
||||
'pass': bool(kin_ok and orc_ok and data_fp in (None, kin.fp))}
|
||||
os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True)
|
||||
json.dump(res, open(a.out, 'w', encoding='utf-8'), indent=1)
|
||||
print('老师轨迹回放:到达 %.1f%%、碰撞 %.1f%%(%d 条)' % (100 * res['reach'], 100 * res['collision'], n), flush=True)
|
||||
json.dump(res, open(a.out, 'w', encoding='utf-8'), indent=1, ensure_ascii=False)
|
||||
print('老师轨迹回放:到达 %.1f%%、碰撞 %.1f%%(%d 条);oracle 走 rollout:到达 %.1f%%、碰撞 %.1f%%、DLS 前最大偏差 p99 %.4f°;指纹 %s' % (
|
||||
100 * res['reach'], 100 * res['collision'], n, 100 * orc['reach'], 100 * orc['collision'],
|
||||
orc['max_dev_deg_before_dls']['p99'], '一致' if data_fp in (None, kin.fp) else '不符'), flush=True)
|
||||
sys.exit(0 if res['pass'] else 5)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user