Files
Robot_Arm/ml/apg/apg_finetune.py
T
Hark 2a0a2e55ad 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。
2026-10-18 06:40:23 +08:00

142 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""APG 可微微调(方案 §7.1):按部署节奏可微展开策略,直接对"走得到、不碰、平滑"求梯度;不需要老师。
python -m ml.apg.apg_finetune --init ml/runs/base/best.pt --samples ml/data/samples --out ml/runs/apg [--pool ml/runs/mine/pool.jsonl]
每条展开固定一个噪声 z(确定性可微映射);每次推理提交 3 步(已提交前缀 3 步,与部署一致);
截断反传:每 seg_plans 次推理(=3·seg_plans 步)反传一次并截断状态梯度。
损失:段末到达项 + Σ碰撞铰链 ReLU(margin_train − 间隙)(逐胶囊对)+ 平滑(速度²+加速度²)+ 行程 + BC 锚定(MeanFlow 损失)。"""
import argparse, json, math, os, random, time
import torch
from ml.common.kin_torch import Kin
from ml.common.features import features, STEP, H, wrap180
from ml.data import Samples, load_tasks
from ml.model.policy import build, wta_loss
from ml.model.meanflow import meanflow_loss
from ml.sim.closed_loop import evaluate, tasks_to_tensors, goal_err
def load_pool(paths):
out = []
for p in paths or []:
if os.path.exists(p):
out += [json.loads(l) for l in open(p, encoding='utf-8') if l.strip()]
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--init', required=True); ap.add_argument('--samples', nargs='+', default=['ml/data/samples'])
ap.add_argument('--pool', nargs='*', default=[]); ap.add_argument('--out', default='ml/runs/apg')
ap.add_argument('--config', default='ml/configs/apg.json'); ap.add_argument('--steps', type=int, default=None)
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu'); ap.add_argument('--seed', type=int, default=0)
a = ap.parse_args()
cfg = json.load(open(a.config)); C = cfg['apg']
if a.steps: C['steps'] = a.steps
dev = a.device; torch.manual_seed(a.seed); random.seed(a.seed)
os.makedirs(a.out, exist_ok=True)
ck = torch.load(a.init, map_location=dev)
mcfg = ck['cfg']
model = build(mcfg).to(dev); model.load_state_dict(ck['model'])
kin = Kin(device=dev)
data = Samples(a.samples, device=dev, split='train')
all_tasks = load_tasks(a.samples)
train_tasks = [t for t in all_tasks if t['split'] == 'train'] + load_pool(a.pool)
val_tasks = [t for t in all_tasks if t['split'] == 'val'][:C['eval_tasks']]
w = [C['cat_weights'].get(t.get('cat', 'A'), 1.0) * (2.0 if t.get('hard') else 1.0) for t in train_tasks]
opt = torch.optim.AdamW(model.parameters(), lr=C['lr'], betas=(0.9, 0.95), weight_decay=0.0)
amp = dev.startswith('cuda') and C.get('amp') == 'bf16'
step = torch.tensor(STEP, device=dev)
log = open(os.path.join(a.out, 'log.jsonl'), 'a')
base, _ = evaluate(model.eval(), kin, val_tasks, dev)
print('EVAL0', json.dumps(base), flush=True); log.write(json.dumps({'eval': base, 'step': 0}) + '\n')
best = base['success']
torch.save({'model': model.state_dict(), 'cfg': mcfg, 'step': 0, 'fp': kin.fp, 'eval': base}, os.path.join(a.out, 'best.pt'))
M = C['margin_train_mm']; t0 = time.time()
for it in range(C['steps']):
model.train()
batch = random.choices(train_tasks, weights=w, k=C['batch'])
q0, T, R, ex, tr = tasks_to_tensors(batch, dev)
B = q0.shape[0]
z = torch.randn(B, H, 6, device=dev)
q, qp = q0.clone(), q0.clone()
queue = torch.zeros(B, 0, 6, device=dev)
subs = torch.zeros(B, 2, 6, device=dev); nsub = torch.zeros(B, dtype=torch.long, device=dev); sub_t = torch.zeros(B, device=dev)
opt.zero_grad(set_to_none=True)
stats = {'col': 0.0, 'tgt': 0.0, 'smooth': 0.0}
nseg = math.ceil(C['plans'] / C['seg_plans'])
seg_loss = torch.zeros((), device=dev); prev_d = torch.zeros(B, 6, device=dev)
for p in range(C['plans']):
tnow = p * 3 * 0.02
r = queue.shape[1]
pre = torch.zeros(B, 3, 6, device=dev); pre[:, :r] = queue[:, :r]
qe = q + queue.sum(1)
with torch.no_grad():
if (nsub > 0).any():
cur = subs[:, 0]; dd = torch.where(kin.cont, wrap180(cur - qe), cur - qe).abs().max(-1).values
adv = (nsub > 0) & ((dd < 5) | (tnow - sub_t > 1.5))
subs = torch.where(adv[:, None, None], torch.cat([subs[:, 1:], subs[:, 1:]], 1), subs)
nsub = torch.where(adv, nsub - 1, nsub); sub_t = torch.where(adv, torch.full_like(sub_t, tnow), sub_t)
feat = features(kin, q, qp, T, R, pre, torch.full((B,), r, device=dev), subs[:, 0], (nsub > 0).float())
with torch.autocast('cuda', dtype=torch.bfloat16, enabled=amp):
act, val, sub = model.sample_k(feat, 1, z)
act = act[:, 0].float()
if p == 0:
with torch.no_grad():
need = sub[:, 12].float() > 0.5
subs = q.detach()[:, None] + sub[:, :12].float().view(B, 2, 6) * 180
nsub = torch.where(need, torch.full_like(nsub, 2), torch.zeros_like(nsub))
inc = act.clamp(-1, 1) * step # [B,16,6]
commit = inc[:, r:r + 3]
if r == 0: # 冷启动:先提交一块,下一轮才开始执行
queue = commit; continue
# 执行队列中的 3 步(上一轮提交的)
for k in range(3):
d = queue[:, k]
qp = q; q = kin.clamp_box(q + d)
G = kin.gaps(q, M)
hinge = torch.relu(M - G['gap'])
col = torch.zeros(B, device=dev).index_add(0, G['b'], hinge)
sm = ((d / step) ** 2).sum(-1) + (((d - prev_d) / step) ** 2).sum(-1)
prev_d = d
seg_loss = seg_loss + C['w_col'] * col.mean() + C['w_smooth'] * sm.mean() + C['w_len'] * d.abs().sum(-1).mean()
stats['col'] += col.mean().item(); stats['smooth'] += sm.mean().item()
queue = commit
if (p + 1) % C['seg_plans'] == 0 or p == C['plans'] - 1:
xf = kin.cum_xf(q)
pe = kin.fk(q, xf); Rq = kin.orient(q, xf).reshape(B, 9)
pose = (R.abs().sum(-1) > 0).float()
lt = ((T - pe) / 100).pow(2).sum(-1) + C['w_rot'] * pose * (Rq - R).pow(2).sum(-1)
seg_loss = seg_loss + C['w_tgt'] * lt.mean()
stats['tgt'] += lt.mean().item()
(seg_loss / nseg).backward()
seg_loss = torch.zeros((), device=dev)
q, qp, queue, prev_d = q.detach(), qp.detach(), queue.detach(), prev_d.detach()
# BC 锚定
X, Y, V, SG = data.batch(C['batch'] * 4)
with torch.autocast('cuda', dtype=torch.bfloat16, enabled=amp):
mem = model.encode(X)
l_bc = wta_loss(model, mem, Y)[0] if model.head == 'wta' else meanflow_loss(model, mem, Y)[0]
v, s = model.heads(mem)
l_bc = l_bc + 0.1 * (v.float() - V).abs().mean() + 0.1 * (s.float() - SG).abs().mean()
(C['w_bc'] * l_bc).backward()
gn = torch.nn.utils.clip_grad_norm_(model.parameters(), C['grad_clip'])
opt.step()
with torch.no_grad():
e, oe = goal_err(kin, q, T, R)
rec = {'it': it, 'err_mm_mean': e.mean().item(), 'reach_frac': ((e < 1) & (oe < 0.5)).float().mean().item(), 'bc': l_bc.item(),
'gnorm': gn.item(), **stats, 'min': (time.time() - t0) / 60}
if it % 10 == 0: print(json.dumps(rec), flush=True)
log.write(json.dumps(rec) + '\n'); log.flush()
if (it + 1) % C['eval_every'] == 0 or it == C['steps'] - 1:
res, _ = evaluate(model.eval(), kin, val_tasks, dev)
res['it'] = it + 1
print('EVAL', json.dumps(res), flush=True); log.write(json.dumps({'eval': res}) + '\n'); log.flush()
sd = {'model': model.state_dict(), 'cfg': mcfg, 'step': it + 1, 'fp': kin.fp, 'eval': res}
torch.save(sd, os.path.join(a.out, 'last.pt'))
if res['success'] > best + 1e-4:
best = res['success']; torch.save(sd, os.path.join(a.out, 'best.pt'))
print(f'done, best success {best:.4f}', flush=True)
if __name__ == '__main__':
main()