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:
+18
-4
@@ -1,4 +1,6 @@
|
||||
"""门禁(方案 §7.2 第 6 步):新模型须 ①门禁集到达率不降 ②A 类降幅 ≤ 0.5 个百分点 ③上一轮修好的错题 95% 仍对。
|
||||
"""门禁(方案 §7.2 第 6 步):新模型须 ①门禁集到达率不降 ②A 类降幅 ≤ 0.5 个百分点 ③上一轮修好的错题 95% 仍对
|
||||
④R7:B/C 类(需绕行)左右比例变化 ≤ 5 个百分点 ⑤R7:两者都到达的题行程中位数比 ≤ 1.05。
|
||||
门禁集为无老师题,到达按定长 6 s 口径(见 closed_loop.NOREF_SEC)。
|
||||
python -m ml.mining.gate --old cur.pt --new cand.pt --tasks ml/runs/gate_tasks.jsonl --out gate.json [--fixed prev_fixed.jsonl]
|
||||
通过则把 cand 复制为 --current(默认 ml/runs/current.pt)。gate.json 含 accept / gain_pp,loop.sh 据此决定继续或停止。"""
|
||||
import argparse, json, shutil
|
||||
@@ -38,15 +40,27 @@ def main():
|
||||
if ft:
|
||||
r2, p2 = evaluate(load(a.new, a.device), kin, ft, a.device)
|
||||
keep = sum(per_task_success(p2)) / len(ft)
|
||||
accept = gain >= 0 and a_drop <= 0.5 and (keep is None or keep >= 0.95)
|
||||
out = {'old': ro, 'new': rn, 'gain_pp': gain, 'A_drop_pp': a_drop, 'fixed_kept': keep, 'accept': accept}
|
||||
# R7:左右比例与行程
|
||||
cat = [t['cat'] for t in tasks]
|
||||
so_side = torch.cat([p['side'] for _, p in po]); sn_side = torch.cat([p['side'] for _, p in pn])
|
||||
det = torch.tensor([c in ('B', 'C') for c in cat])
|
||||
left = lambda sd, ok: (((sd > 0) & det & ok).sum() / ((det & ok).sum().clamp(min=1))).item()
|
||||
ok_o, ok_n = torch.tensor(so, dtype=torch.bool), torch.tensor(sn, dtype=torch.bool)
|
||||
lr_shift = abs(left(sn_side, ok_n) - left(so_side, ok_o)) * 100
|
||||
both = ok_o & ok_n
|
||||
tro = torch.cat([p['travel'] for _, p in po]); trn = torch.cat([p['travel'] for _, p in pn])
|
||||
travel_ratio = (trn[both].median() / tro[both].median().clamp(min=1e-6)).item() if both.any() else None
|
||||
r7 = lr_shift <= 5.0 and (travel_ratio is None or travel_ratio <= 1.05)
|
||||
accept = gain >= 0 and a_drop <= 0.5 and (keep is None or keep >= 0.95) and r7
|
||||
out = {'old': ro, 'new': rn, 'gain_pp': gain, 'A_drop_pp': a_drop, 'fixed_kept': keep,
|
||||
'R7_left_share_shift_pp': lr_shift, 'R7_travel_median_ratio': travel_ratio, 'R7_ok': r7, 'accept': accept}
|
||||
# 本轮新修好的题(旧错新对)留给下一轮做"仍对"检查
|
||||
fixed_now = [t for t, x, y in zip(tasks, so, sn) if (not x) and y]
|
||||
with open(a.out.replace('.json', '_fixed.jsonl'), 'w') as f:
|
||||
for t in fixed_now: f.write(json.dumps(t) + '\n')
|
||||
json.dump(out, open(a.out, 'w'), indent=1)
|
||||
if accept: shutil.copy(a.new, a.current)
|
||||
print(json.dumps({k: out[k] for k in ('gain_pp', 'A_drop_pp', 'fixed_kept', 'accept')}))
|
||||
print(json.dumps({k: out[k] for k in ('gain_pp', 'A_drop_pp', 'fixed_kept', 'R7_left_share_shift_pp', 'R7_travel_median_ratio', 'accept')}))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+9
-7
@@ -1,29 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# 后训练主循环(方案 §7):APG 首轮 → 挖错 3–6 轮(每轮:挖错 → [E2 老师补路径] → APG 微调 → 门禁/回滚)
|
||||
# bash ml/mining/loop.sh [轮数=6] [每轮 APG 步数=1000] [每轮出题数=50000]
|
||||
# 2026-10-18:APG 采样池只用本轮错题(此前累积所有历史轮,后期分布偏向老错题);老错题的保持由门禁"上轮修好仍对"负责。
|
||||
# 统一 $PYTHON;门禁加 R7(左右比例 / 行程)。
|
||||
set -e
|
||||
cd "$(dirname "$0")/../.."
|
||||
PY=${PYTHON:-python}
|
||||
ROUNDS=${1:-6}; STEPS=${2:-1000}; N=${3:-50000}
|
||||
mkdir -p ml/runs/mine
|
||||
[ -f ml/runs/gate_tasks.jsonl ] || python -m ml.mining.mine_round --make-gate 2000 --seed 777 --out ml/runs
|
||||
[ -f ml/runs/gate_tasks.jsonl ] || $PY -m ml.mining.mine_round --make-gate 2000 --seed 777 --out ml/runs
|
||||
[ -f ml/runs/current.pt ] || cp ml/runs/apg/best.pt ml/runs/current.pt
|
||||
SAMPLES="ml/data/samples"; FIXED=""; small=0
|
||||
for ((r=1; r<=ROUNDS; r++)); do
|
||||
D=ml/runs/mine/r$r; mkdir -p $D
|
||||
python -m ml.mining.mine_round --ckpt ml/runs/current.pt --seed $((1000+r)) --n $N --out $D
|
||||
$PY -m ml.mining.mine_round --ckpt ml/runs/current.pt --seed $((1000+r)) --n $N --out $D
|
||||
# E2(局部极小)请老师补少量路径(唯一保留的后训练老师用途)
|
||||
if [ -s $D/e2_tasks.jsonl ]; then
|
||||
node ml/gen/gen_dataset.cjs --tasks $D/e2_tasks.jsonl --seed $((5000+r)) --out ml/data/e2/shard_e2_r$r.jsonl
|
||||
node ml/prep/build_dataset.cjs ml/data/e2/shard_e2_r$r.jsonl ml/data/samples_e2
|
||||
SAMPLES="ml/data/samples ml/data/samples_e2"
|
||||
fi
|
||||
POOLS=$(ls ml/runs/mine/r*/pool.jsonl)
|
||||
python -m ml.apg.apg_finetune --init ml/runs/current.pt --samples $SAMPLES --pool $POOLS --out ml/runs/apg_r$r --steps $STEPS
|
||||
python -m ml.mining.gate --old ml/runs/current.pt --new ml/runs/apg_r$r/best.pt --tasks ml/runs/gate_tasks.jsonl --out $D/gate.json $FIXED
|
||||
$PY -m ml.apg.apg_finetune --init ml/runs/current.pt --samples $SAMPLES --pool $D/pool.jsonl --out ml/runs/apg_r$r --steps $STEPS
|
||||
$PY -m ml.mining.gate --old ml/runs/current.pt --new ml/runs/apg_r$r/best.pt --tasks ml/runs/gate_tasks.jsonl --out $D/gate.json $FIXED
|
||||
FIXED="--fixed $D/gate_fixed.jsonl"
|
||||
gain=$(python -c "import json;print(json.load(open('$D/gate.json'))['gain_pp'])")
|
||||
gain=$($PY -c "import json;print(json.load(open('$D/gate.json'))['gain_pp'])")
|
||||
echo "round $r gain ${gain} pp"
|
||||
if python -c "import sys;sys.exit(0 if $gain < 0.5 else 1)"; then small=$((small+1)); else small=0; fi
|
||||
if $PY -c "import sys;sys.exit(0 if $gain < 0.5 else 1)"; then small=$((small+1)); else small=0; fi
|
||||
if [ $small -ge 2 ]; then echo "连续 2 轮提升 < 0.5 pp,停止"; break; fi
|
||||
done
|
||||
echo "最终模型:ml/runs/current.pt"
|
||||
|
||||
+12
-5
@@ -1,20 +1,27 @@
|
||||
"""无老师出题(PyTorch):A 均匀 / B 直线被挡 / C 贴立柱 / D 工作空间外;pos/pose 各半。"""
|
||||
"""无老师出题(PyTorch):A 均匀 / B 直线被挡 / C 贴立柱 / D 工作空间外;pos/pose 各半。
|
||||
2026-10-18:无老师题没有参考时长与行程,exec_s/travel 置 None(此前写死 3.0/0.0 造成假时长预算);
|
||||
闭环仿真对这类题按定长 6 s 口径判到达,time_ok_1p3 / travel_ratio 只在有老师参考的题上统计。"""
|
||||
import math
|
||||
import torch
|
||||
|
||||
from ml.common.features import wrap180
|
||||
|
||||
|
||||
def min_gap_chunked(kin, q, cap=0.5, chunk=512):
|
||||
"""分块算最小间隙:随机位形每个约 2800 个幸存胶囊对、峰值约 1.5 MB/位形,一次 4 万位形会爆内存/显存(2026-10-18 实测出题 OOM),每块 512"""
|
||||
return torch.cat([kin.gaps(q[i:i + chunk], cap)['gm'].min(-1).values for i in range(0, q.shape[0], chunk)])
|
||||
|
||||
|
||||
def rand_q(kin, n, g):
|
||||
u = torch.rand(n, 6, device=kin.lo.device, generator=g)
|
||||
return kin.lo + u * (kin.hi - kin.lo)
|
||||
|
||||
|
||||
def free_q(kin, n, g, chunk=4096):
|
||||
def free_q(kin, n, g, chunk=1024):
|
||||
out = []
|
||||
while sum(x.shape[0] for x in out) < n:
|
||||
q = rand_q(kin, chunk, g)
|
||||
ok = kin.gaps(q, 0.5)['gm'].min(-1).values > 0
|
||||
ok = min_gap_chunked(kin, q) > 0
|
||||
out.append(q[ok])
|
||||
return torch.cat(out)[:n]
|
||||
|
||||
@@ -23,7 +30,7 @@ def straight_blocked(kin, a, b, n=24):
|
||||
d = torch.where(kin.cont, wrap180(b - a), b - a)
|
||||
s = torch.linspace(0, 1, n, device=a.device)
|
||||
pts = (a[:, None] + s[None, :, None] * d[:, None]).reshape(-1, 6)
|
||||
return (kin.gaps(pts, 0.5)['gm'].min(-1).values <= 0).view(a.shape[0], n).any(-1)
|
||||
return (min_gap_chunked(kin, pts) <= 0).view(a.shape[0], n).any(-1)
|
||||
|
||||
|
||||
def column_gap(kin, q):
|
||||
@@ -44,7 +51,7 @@ def sample_tasks(kin, n, seed, mix=(0.3, 0.3, 0.3, 0.1)):
|
||||
Rb = kin.orient(b, xf).reshape(-1, 9)
|
||||
for i in range(q0.shape[0]):
|
||||
tasks.append({'cat': cat, 'mode': 'pose' if pose[i] else 'pos', 'q0': q0[i].tolist(), 'T': Tb[i].tolist(),
|
||||
'R': Rb[i].tolist() if pose[i] else None, 'exec_s': 3.0, 'travel': 0.0})
|
||||
'R': Rb[i].tolist() if pose[i] else None, 'exec_s': None, 'travel': None})
|
||||
if want[0]:
|
||||
emit('A', free_q(kin, want[0], g), free_q(kin, want[0], g))
|
||||
for cat, k in (('B', want[1]), ('C', want[2])):
|
||||
|
||||
Reference in New Issue
Block a user