ml: 按运行手册审查修订——头分目录不互相覆盖(ml/runs/<CFG>、selected_ckpt/CKPT);统一 $PYTHON(Windows Git Bash 可跑);gen 按实测样本/任务自动补量;R10 改终态判据(≥5k 样本/s≈60k 步≤3.5h);m5 纯策略/仿真安全层双口径;无老师题定长 6 s 口径;APG 展开随批时长、端点+中点铰链、余量 3 mm(投影同步);挖错池只用本轮;门禁加 R7;bench 加 R8 对抗集与刹停率、--nc-n/--route-n/--r8-n;新增 check_r2 与 acceptance;export 默认 fp32;出题分块防 OOM;val 为空保护

This commit is contained in:
Hark
2026-10-17 16:36:22 +00:00
parent 0218d7fdd7
commit d3b7c7b5ea
18 changed files with 493 additions and 156 deletions
+31 -9
View File
@@ -17,11 +17,15 @@ def tasks_to_tensors(tasks, device):
q0 = torch.tensor([t['q0'] for t in tasks], dtype=torch.float32, device=device)
T = torch.tensor([t['T'] for t in tasks], dtype=torch.float32, device=device)
R = torch.tensor([t['R'] if t.get('R') else [0.] * 9 for t in tasks], dtype=torch.float32, device=device)
ex = torch.tensor([t.get('exec_s', 3.0) or 3.0 for t in tasks], dtype=torch.float32, device=device)
tr = torch.tensor([t.get('travel', 0.0) or 0.0 for t in tasks], dtype=torch.float32, device=device)
# 无老师参考(挖错/门禁题)时 exec_s/travel 为 None → NaN;调用方据此区分口径
ex = torch.tensor([t['exec_s'] if t.get('exec_s') else float('nan') for t in tasks], dtype=torch.float32, device=device)
tr = torch.tensor([t['travel'] if t.get('travel') else float('nan') for t in tasks], dtype=torch.float32, device=device)
return q0, T, R, ex, tr
NOREF_SEC = 6.0 # 无老师参考题的定长时限(s)
def goal_err(kin, q, T, R):
xf = kin.cum_xf(q)
p = kin.fk(q, xf); Rq = kin.orient(q, xf).reshape(-1, 9)
@@ -64,7 +68,8 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
q0, T, R, ex, tr = tasks_to_tensors(tasks, device)
B = q0.shape[0]
step = torch.tensor(STEP, device=device)
limit = torch.clamp(2 * ex, min=3.0) if max_sec is None else torch.full_like(ex, max_sec)
has_ref = torch.isfinite(ex)
limit = torch.where(has_ref, torch.clamp(2 * ex, min=3.0), torch.full_like(ex, NOREF_SEC)) if max_sec is None else torch.full_like(ex, max_sec)
nmax = int(math.ceil(limit.max().item() / DT))
q, qp = q0.clone(), q0.clone()
queue = torch.zeros(B, 0, 6, device=device)
@@ -73,6 +78,8 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
done = torch.zeros(B, dtype=torch.bool, device=device); t_done = torch.full((B,), float('nan'), device=device)
col = torch.zeros(B, device=device); travel = torch.zeros(B, device=device); brakes = torch.zeros(B, device=device)
traj = [q.clone()] if return_traj else None
n_start = int(round(0.5 / DT)); moved_start = None # R2 起步失败:0.5 s 内关节总行程 < 1°
j1_acc = torch.zeros(B, device=device); j1_max = torch.zeros(B, device=device); j1_min = torch.zeros(B, device=device)
cold = True
for it in range(nmax):
tnow = it * DT
@@ -138,6 +145,8 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
d = torch.where(done[:, None], torch.zeros_like(d), d)
qp = q; q = kin.clamp_box(q + d)
travel += d.abs().sum(-1)
j1_acc = j1_acc + d[:, 0]; j1_max = torch.maximum(j1_max, j1_acc); j1_min = torch.minimum(j1_min, j1_acc)
if it + 1 == n_start: moved_start = travel.clone()
if return_traj: traj.append(q.clone())
mid = qp + 0.5 * (q - qp)
gm = kin.gaps(torch.cat([q, mid], 0), 0.5)['gm'].min(-1).values
@@ -148,26 +157,39 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
t_done = torch.where(newly, torch.full_like(t_done, (it + 1) * DT), t_done); done |= newly
if done.all(): break
ok = done & (t_done <= limit)
res = {'n': B, 'success': ok.float().mean().item(),
'time_ok_1p3': (ok & (t_done <= 1.3 * ex)).float().mean().item(),
if moved_start is None: moved_start = travel.clone()
e_end, _ = goal_err(kin, q, T, R)
start_fail = (moved_start < 1.0) & ~(done & (t_done <= 0.5))
okr = ok & has_ref & torch.isfinite(tr)
res = {'n': B, 'n_ref': int(has_ref.sum()), 'success': ok.float().mean().item(),
'time_ok_1p3': (ok & has_ref & (t_done <= 1.3 * ex)).float().sum().item() / max(1, int(has_ref.sum())) if has_ref.any() else None,
'collision_rate': (col > 0).float().mean().item(), 'collision_steps_mean': col.mean().item(),
'travel_ratio_median': (travel[ok] / tr[ok].clamp(min=1)).median().item() if ok.any() and (tr > 0).any() else None,
'time_mean_s': t_done[ok].mean().item() if ok.any() else None, 'brakes_mean': brakes.mean().item()}
per = {'done': done.cpu(), 't_done': t_done.cpu(), 'col': col.cpu(), 'travel': travel.cpu()}
'travel_ratio_median': (travel[okr] / tr[okr].clamp(min=1)).median().item() if okr.any() else None,
'time_mean_s': t_done[ok].mean().item() if ok.any() else None, 'brakes_mean': brakes.mean().item(),
'start_fail': start_fail.float().mean().item(),
'time_budget': 'teacher×2(≥3 s)' if bool(has_ref.all()) else ('fixed %.0f s' % (max_sec or NOREF_SEC) if not has_ref.any() else 'mixed')}
# 绕行侧:J1 累计位移的正向/负向极值哪个更大(+1 / −1),供 R7 左右比例门禁
side = torch.where(j1_max >= -j1_min, torch.ones_like(j1_max), -torch.ones_like(j1_max))
per = {'done': done.cpu(), 't_done': t_done.cpu(), 'col': col.cpu(), 'travel': travel.cpu(), 'err': e_end.cpu(),
'side': side.cpu(), 'start_fail': start_fail.cpu(), 'brakes': brakes.cpu()}
if return_traj: per['traj'] = torch.stack(traj, 1).cpu()
return res, per
def evaluate(model, kin, tasks, device, K=8, safety=False, bs=256, seed=0):
if not tasks: # 小样本摸底时 val 可能为空
return {'n': 0, 'success': float('nan'), 'collision_rate': float('nan'), 'start_fail': float('nan'), 'brakes_mean': float('nan')}, []
agg, pers = {}, []
for i in range(0, len(tasks), bs):
r, p = rollout(model, kin, tasks[i:i + bs], device, K=K, safety=safety, seed=seed + i)
w = r['n']; pers.append((r, p))
for k, v in r.items():
if k == 'n' or v is None: continue
if k == 'n' or v is None or isinstance(v, str): continue
agg[k] = agg.get(k, 0.0) + v * w
n = sum(r['n'] for r, _ in pers)
out = {k: v / n for k, v in agg.items()}; out['n'] = n
out['n_ref'] = sum(r['n_ref'] for r, _ in pers)
out['time_budget'] = pers[0][0]['time_budget'] if len({r['time_budget'] for r, _ in pers}) == 1 else 'mixed'
return out, pers