ml: 数据生成 + 可微运动学对拍 + MeanFlow/WTA 策略与闭环仿真 + APG/挖错/ONNX 导出与运行手册

- M1 数据生成器:采样/老师/距离下界认证/时间律/统计/一致性门禁;确定性老师(碰撞次数计时 + resetSession)、pos 任务冻结 J6、--consensus 邻域共识过滤、Dr 类。
- 全流程代码与运行手册:数据打包/可微运动学与对拍/MeanFlow+WTA 模型/闭环仿真/APG/挖错循环/ONNX 导出;webapp 策略运行时 ik-policy.js、?policy=1 接入、tests/policy_bench.cjs;ik-solver 增 pairList/clusterExport/criticalPairs/pairGap/leverTable。
This commit is contained in:
Hark
2026-10-18 06:40:23 +08:00
committed by cbb
parent cc43b3d3be
commit 2a0a2e55ad
56 changed files with 2990 additions and 97 deletions
+78
View File
@@ -0,0 +1,78 @@
"""无老师挖错一轮(方案 §7.2):出题 → 闭环作答(关安全层)→ 验算 → 错题分类 → 错题+邻居进 APG 采样池。
python -m ml.mining.mine_round --ckpt ml/runs/apg/best.pt --seed 1001 --n 50000 --out ml/runs/mine/r1
造固定门禁集(只做一次):python -m ml.mining.mine_round --make-gate 2000 --seed 777 --out ml/runs
输出:pool.jsonl(错题与 ±5 mm/±3° 邻居,E1/E6 ×2)、e2_tasks.jsonl(卡在局部极小,可离线请老师补路径)、report.json/.md
错题类:E1 撞 / E2 没到且离目标 > 20 mm(局部极小)/ E3 用时 > 4 s / E4 没到但 < 20 mm(末端精度)/ E5 J1 换向 ≥ 3 次(摇摆)"""
import argparse, json, os
import torch
from ml.common.kin_torch import Kin
from ml.model.policy import build
from ml.sim.closed_loop import rollout, goal_err, tasks_to_tensors
from ml.mining.tasks import sample_tasks, neighbours
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--ckpt'); ap.add_argument('--seed', type=int, default=1001); ap.add_argument('--n', type=int, default=50000)
ap.add_argument('--out', required=True); ap.add_argument('--bs', type=int, default=512); ap.add_argument('--neigh', type=int, default=10)
ap.add_argument('--make-gate', type=int, default=0)
ap.add_argument('--mix', default='0.3,0.3,0.3,0.1', help='A,B,C,D 占比;贴立柱门禁集用 0,0,1,0')
ap.add_argument('--name', default='gate_tasks.jsonl')
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu')
a = ap.parse_args()
os.makedirs(a.out, exist_ok=True)
kin = Kin(device=a.device)
if a.make_gate:
ts = sample_tasks(kin, a.make_gate, a.seed, mix=tuple(float(x) for x in a.mix.split(',')))
with open(os.path.join(a.out, a.name), 'w') as f:
for t in ts: f.write(json.dumps(t) + '\n')
print('gate tasks', len(ts)); return
ck = torch.load(a.ckpt, map_location=a.device)
model = build(ck['cfg']).to(a.device); model.load_state_dict(ck['model']); model.eval()
tasks = sample_tasks(kin, a.n, a.seed)
g = torch.Generator(); g.manual_seed(a.seed)
cls = {k: [] for k in ('E1', 'E2', 'E3', 'E4', 'E5', 'ok')}
for i in range(0, len(tasks), a.bs):
tb = tasks[i:i + a.bs]
_, per = rollout(model, kin, tb, a.device, K=8, safety=False, seed=a.seed + i, max_sec=6.0, return_traj=True)
q0, T, R, _, _ = tasks_to_tensors(tb, a.device)
tr = per['traj']
e, _ = goal_err(kin, tr[:, -1].to(a.device), T, R)
d1 = tr[:, 1:, 0] - tr[:, :-1, 0]
sg = torch.sign(torch.where(d1.abs() > 0.05, d1, torch.zeros_like(d1)))
sw = torch.zeros(len(tb))
for b in range(len(tb)):
s = sg[b][sg[b] != 0]
sw[b] = (s[1:] != s[:-1]).sum() if s.numel() > 1 else 0
for b, t in enumerate(tb):
if per['col'][b] > 0: cls['E1'].append(t)
elif not per['done'][b]: (cls['E2'] if e[b] > 20 else cls['E4']).append(t)
elif per['t_done'][b] > 4.0: cls['E3'].append(t)
elif sw[b] >= 3: cls['E5'].append(t)
else: cls['ok'].append(t)
pool = []
for k, lst in cls.items():
if k == 'ok': continue
rep = 2 if k == 'E1' else 1
for t in lst:
t = dict(t); t['hard'] = True; t['err'] = k
for _ in range(rep): pool.append(t); pool += neighbours(t, a.neigh, g)
with open(os.path.join(a.out, 'pool.jsonl'), 'w') as f:
for t in pool: f.write(json.dumps(t) + '\n')
with open(os.path.join(a.out, 'e2_tasks.jsonl'), 'w') as f:
for t in cls['E2'][:2000]: f.write(json.dumps(t) + '\n')
n = len(tasks)
rep = {'n': n, **{k: len(v) / n for k, v in cls.items()}, 'pool': len(pool)}
bycat = {}
for k, lst in cls.items():
for t in lst: bycat.setdefault(t['cat'], {}).setdefault(k, 0); bycat[t['cat']][k] += 1
rep['by_cat'] = bycat
json.dump(rep, open(os.path.join(a.out, 'report.json'), 'w'), indent=1)
md = ['# 挖错报告', '', f'任务 {n},错题池 {len(pool)}', '', '| 类 | 占比 |', '|---|---:|'] + [f'| {k} | {len(v) / n:.2%} |' for k, v in cls.items()]
open(os.path.join(a.out, 'report.md'), 'w').write('\n'.join(md) + '\n')
print(json.dumps(rep, indent=1))
if __name__ == '__main__':
main()