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:
Hark
2026-10-17 15:48:19 +00:00
parent 7e70c4e811
commit 8bfe9bc083
50 changed files with 2677 additions and 172 deletions
+6 -26
View File
@@ -1,27 +1,7 @@
# ml/gen — M1 离线数据生成(在高性能机器上跑)
# ml/gen — 离线老师数据
依赖:Node ≥ 18,仓库根目录执行。输出写到 `ml/data/`(已 gitignore)。
## 1. 老师一致性门禁(先跑,决定是否需要共识过滤)
```
node ml/gen/consistency.cjs 300 4242 > ml/data/consistency.json
```
单核约 25–30 分钟(300 任务 × 6 次老师求解)。看 `consistentByGateRule`(pos 任务忽略 J6):
- ≥ 0.90 → 生成时不加 `--consensus`
- < 0.90 → 生成时加 `--consensus 4`(过滤与邻居绕法不一致的样本)
## 2. 生成数据
```
bash ml/gen/run_parallel.sh <每进程条数> <进程数> [起始种子] [额外参数]
# 例:16 核、5 万条、不共识:
bash ml/gen/run_parallel.sh 3125 16
# 例:16 核、5 万条、共识 4 邻居:
bash ml/gen/run_parallel.sh 3125 16 1 "--consensus 4"
```
耗时估算(沙箱实测每条每核约 2.1 s;高性能 CPU 单核通常快 1.5–2 倍):
- 不共识:约 30 核·时 → 16 核约 1–2 小时
- 共识 4:约 150 核·时 → 16 核约 5–9 小时
## 3. 回传给我
- `ml/data/stats.json`、`ml/data/consistency.json`(必须)
- `ml/data/shard_*.jsonl`(训练数据,体积约 2.8 MB/500 条 → 5 万条约 280 MB,可压缩后放网盘或直接留在本机用于 M3/M5)
完整流程与命令见 `ml/README.md`(第 3、4 步)。这里的脚本:
- `consistency.cjs`:老师一致性门禁(目标 ±5 mm 邻居的同伦类一致率)
- `gen_dataset.cjs`:采样 → 确定性老师 → 距离下界认证 → 时间律;`--consensus k` 共识过滤;`--tasks f.jsonl` 按给定任务出解(挖错 E2)
- `run_parallel.sh`:多进程生成 + 统计
- `stats.cjs`:汇总 `stats.json`
+2 -42
View File
@@ -1,43 +1,3 @@
/* 距离下界认证(2026-10-17,方案 §8.3 / §4.4)
* 关节直线段 a→b 安全 ⇔ 对每个组对 (gA,gB)(gB ≥ gA+2):
* gap_ab(a) + gap_ab(b) > Σ_{k=gA}^{gB-1} r_k·|Δθ_k|
* gap 取 S.gapMatrix(距离 − need,含 margin),r_k 为 S.leverBounds 的每轴杠杆上界(mm/°)。
* 不满足就二分,子段 < MIN_DEG 仍不过则判不安全。连续轴按 unwrapTo 口径展开后再插值。 */
/* 距离下界认证:实现放在 webapp/js/ik-policy.js(部署安全层与离线过滤共用同一份代码)。 */
'use strict';
module.exports = function makeCert(S, opt) {
opt = opt || {};
const CAP = opt.cap || 40, MIN_DEG = opt.minDeg || 0.002;
const R = opt.lever || S.leverBounds(4000);
const L = S.LIM, O = S.ORDER, w = S.wrap180;
const memo = new Map();
const key = q => q.map(v => v.toFixed(5)).join(',');
let queries = 0;
const gm = q => { const k = key(q); let M = memo.get(k); if (!M) { M = S.gapMatrix(q, CAP); queries++; if (memo.size > 50000) memo.clear(); memo.set(k, M); } return M; };
const minGap = M => { let m = Infinity; for (let a = 0; a < 7; a++) for (let b = a + 2; b < 7; b++) m = Math.min(m, M[a * 7 + b]); return m; };
const inBox = q => O.every((id, i) => L[id].cont || (q[i] >= L[id].lo - 1e-6 && q[i] <= L[id].hi + 1e-6));
function seg(a, b, depth) {
const Ma = gm(a), Mb = gm(b);
const dq = a.map((v, i) => Math.abs(b[i] - v));
let ok = true;
for (let ga = 0; ga < 7 && ok; ga++) for (let gb = ga + 2; gb < 7; gb++) {
const lhs = Ma[ga * 7 + gb] + Mb[ga * 7 + gb];
let rhs = 0; for (let k = ga; k < gb; k++) rhs += R[k] * dq[k];
if (!(lhs > rhs)) { ok = false; break; }
}
if (ok) return true;
if (minGap(Ma) <= 0 || minGap(Mb) <= 0) return false;
if (Math.max.apply(null, dq) < MIN_DEG || depth > 40) return false;
const m = a.map((v, i) => 0.5 * (v + b[i]));
return seg(a, m, depth + 1) && seg(m, b, depth + 1);
}
/* 整条折线:返回 {ok, queries, minGap, failAt} */
function path(pts) {
const q0 = queries; let mg = Infinity;
const P = [pts[0].slice()];
for (let i = 1; i < pts.length; i++) P.push(P[i - 1].map((v, k) => L[O[k]].cont ? v + w(pts[i][k] - v) : pts[i][k]));
for (let i = 0; i < P.length; i++) { if (!inBox(P[i])) return { ok: false, queries: queries - q0, minGap: mg, failAt: i, why: 'box' }; mg = Math.min(mg, minGap(gm(P[i]))); }
for (let i = 1; i < P.length; i++) if (!seg(P[i - 1], P[i], 0)) return { ok: false, queries: queries - q0, minGap: mg, failAt: i, why: 'cert' };
return { ok: true, queries: queries - q0, minGap: mg, unwrapped: P };
}
return { seg: (a, b) => seg(a, b, 0), path, lever: R, gapMin: q => minGap(gm(q)) };
};
module.exports = (S, opt) => require('../../webapp/js/ik-policy.js').makeCert(S, opt);
+8 -6
View File
@@ -13,16 +13,18 @@ const sampler = require('./sampler.cjs')(S, SEED * 7919 + 13);
const teach = require('./teacher.cjs')(S, ArmIK);
const { sameClass } = require('./pathdev.cjs');
const K_CONS = +args.consensus || 0;
const LEVER = S.leverBounds(4000);
const cert = require('./cert.cjs')(S, { lever: LEVER });
/* --tasks file.jsonl:按给定任务(q0/T/R/mode/cat)请老师,不采样(挖错循环 E2 补路径用) */
const GIVEN = args.tasks ? fs.readFileSync(args.tasks, 'utf8').split('\n').filter(Boolean).map(l => JSON.parse(l)) : null;
const NT = GIVEN ? GIVEN.length : N;
const cert = require('./cert.cjs')(S, {});
const { timeParam } = require('./timelaw.cjs');
const fp = S.modelFingerprint ? S.modelFingerprint() : null;
fs.mkdirSync(path.dirname(OUT), { recursive: true });
const fd = fs.openSync(OUT, 'w');
const r3 = v => Math.round(v * 1000) / 1000;
const t00 = performance.now();
for (let i = 0; i < N; i++) {
const task = sampler.sample();
for (let i = 0; i < NT; i++) {
const task = GIVEN ? Object.assign({ cat: 'E2' }, GIVEN[i], { mode: GIVEN[i].R ? 'pose' : 'pos' }) : sampler.sample();
const t = teach(task);
const rec = { id: 's' + String(SEED).padStart(4, '0') + '_' + String(i).padStart(6, '0'), cat: task.cat, mode: task.mode,
q0: task.q0.map(r3), T: task.T.map(r3), R: task.R, fp };
@@ -51,7 +53,7 @@ for (let i = 0; i < N; i++) {
if (t.poseRelaxed && task.mode === 'pose') { rec.mode = 'pos'; rec.R = null; rec.demoted = true; rec.reached = t.err < 1; }
if (rec.keep) { const tp = timeParam(t.pts); rec.traj = tp.traj.map(q => q.map(r3)); rec.exec_s = r3(tp.steps * 0.02); }
fs.writeSync(fd, JSON.stringify(rec) + '\n');
if ((i + 1) % 25 === 0) process.stderr.write(`[seed ${SEED}] ${i + 1}/${N} ${((performance.now() - t00) / 1000 / (i + 1)).toFixed(2)} s/条\n`);
if ((i + 1) % 25 === 0) process.stderr.write(`[seed ${SEED}] ${i + 1}/${NT} ${((performance.now() - t00) / 1000 / (i + 1)).toFixed(2)} s/条\n`);
}
fs.closeSync(fd);
process.stderr.write(`[seed ${SEED}] done ${N} in ${((performance.now() - t00) / 1000).toFixed(0)} s\n`);
process.stderr.write(`[seed ${SEED}] done ${NT} in ${((performance.now() - t00) / 1000).toFixed(0)} s\n`);
+3 -3
View File
@@ -19,15 +19,15 @@ function timeParam(P, opt) {
const u = segT[i] > 0 ? (tau - cum[i]) / segT[i] : 1;
return P[i].map((v, j) => v + (P[i + 1][j] - v) * u);
};
const out = [P[0].slice()]; let tau = 0, v = 0, guard = 0;
const out = [P[0].slice()], taus = [0]; let tau = 0, v = 0, guard = 0;
while (tau < total - 1e-9 && guard++ < 100000) {
const rem = total - tau;
const v1 = Math.min(1, v + a * dt, Math.sqrt(2 * a * Math.max(0, rem - 0.5 * v * dt)) + 1e-9);
let step = 0.5 * (v + v1) * dt;
if (step <= 1e-9) step = Math.min(rem, a * dt * dt); // 防止末端速度掉到 0 卡住
tau = Math.min(total, tau + step); v = v1;
out.push(at(tau));
out.push(at(tau)); taus.push(tau);
}
return { traj: out, total, steps: out.length - 1 };
return { traj: out, taus, cum, total, steps: out.length - 1 };
}
module.exports = { timeParam, VMAX, A, DT };