ml: 全流程代码与运行手册(数据打包/可微运动学与对拍/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:
@@ -0,0 +1,141 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user