ml/gen: 确定性老师(碰撞次数计时 + resetSession)、pos 任务冻结 J6、--consensus 邻域共识过滤、Dr 类、README(高性能机器运行说明)
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# ml/gen — M1 离线数据生成(在高性能机器上跑)
|
||||
|
||||
依赖: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)
|
||||
@@ -8,6 +8,8 @@ const N = +process.argv[2] || 100, SEED = +process.argv[3] || 4242;
|
||||
const { S, ArmIK } = require('../../tests/route_bench_setup.cjs')();
|
||||
const sm = require('./sampler.cjs')(S, SEED);
|
||||
const teach = require('./teacher.cjs')(S, ArmIK);
|
||||
const PD = require('./pathdev.cjs');
|
||||
const fz = (r, t) => (t.mode === 'pos' && r.ptsJ6Frozen) ? r.ptsJ6Frozen : r.pts; // 与 gen 同口径:pos 任务冻结 J6(gen 里认证不过才回退)
|
||||
const { VMAX } = require('./timelaw.cjs');
|
||||
function resample(P, n) {
|
||||
const seg = []; for (let i = 1; i < P.length; i++) { let m = 0; for (let j = 0; j < 6; j++) m = Math.max(m, Math.abs(P[i][j] - P[i - 1][j]) / VMAX[j]); seg.push(m); }
|
||||
@@ -22,16 +24,16 @@ const axisHist = [0, 0, 0, 0, 0, 0]; let sameNoJ6 = 0;
|
||||
let tasks = 0, pairs = 0, same = 0, detSame = 0, detN = 0; const devs = [], byCat = {};
|
||||
while (tasks < N) {
|
||||
const t = sm.sample(); if (t.cat !== 'B' && t.cat !== 'C') continue;
|
||||
const base = teach(t); if (!base.ok || !base.reached) continue;
|
||||
const base = teach(t); if (!base.ok || !base.reached) continue; base.pts = fz(base, t);
|
||||
tasks++;
|
||||
const again = teach(t); if (again.ok) { detN++; if (dev(base.pts, again.pts) < 1e-6) detSame++; }
|
||||
const again = teach(t); if (again.ok) { detN++; if (dev(base.pts, fz(again, t)) < 1e-6) detSame++; }
|
||||
for (let k = 0; k < 4; k++) {
|
||||
const u = [sm.rnd() - 0.5, sm.rnd() - 0.5, sm.rnd() - 0.5], n = Math.hypot(...u);
|
||||
const T2 = t.T.map((v, i) => v + 5 * u[i] / n);
|
||||
const r = teach(Object.assign({}, t, { T: T2 }));
|
||||
if (!r.ok || !r.reached) continue;
|
||||
const da = devAx(base.pts, r.pts), d = Math.max(...da); devs.push(d); pairs++;
|
||||
if (Math.max(...da.slice(0, 5)) < 30) sameNoJ6++;
|
||||
const da = devAx(base.pts, fz(r, t)), d = Math.max(...da); devs.push(d); pairs++;
|
||||
if (PD.sameClass(base.pts, fz(r, t), t.mode)) sameNoJ6++;
|
||||
if (d >= 30) axisHist[da.indexOf(d)]++;
|
||||
const c = byCat[t.cat] = byCat[t.cat] || { pairs: 0, same: 0 }; c.pairs++;
|
||||
if (d < 30) { same++; c.same++; }
|
||||
@@ -42,4 +44,4 @@ devs.sort((a, b) => a - b);
|
||||
console.log(JSON.stringify({ tasks, pairs, consistentRate: +(same / pairs).toFixed(3), gate: same / pairs >= 0.9 ? 'PASS' : 'FAIL',
|
||||
devP50: +devs[devs.length >> 1].toFixed(1), devP90: +devs[Math.floor(devs.length * 0.9)].toFixed(1),
|
||||
byCat: Object.fromEntries(Object.entries(byCat).map(([k, v]) => [k, +(v.same / v.pairs).toFixed(3)])),
|
||||
consistentIgnoringJ6: +(sameNoJ6 / pairs).toFixed(3), worstAxisOfInconsistent: axisHist, deterministic: detSame + '/' + detN }));
|
||||
consistentByGateRule: +(sameNoJ6 / pairs).toFixed(3), gateByRule: sameNoJ6 / pairs >= 0.9 ? 'PASS' : 'FAIL', worstAxisOfInconsistent: axisHist, deterministic: detSame + '/' + detN }));
|
||||
|
||||
+20
-2
@@ -1,5 +1,7 @@
|
||||
/* M1 数据生成器(2026-10-17,方案 §4)
|
||||
* node ml/gen/gen_dataset.cjs --n 500 --seed 1 --out ml/data/shard_0001.jsonl
|
||||
* node ml/gen/gen_dataset.cjs --n 500 --seed 1 --out ml/data/shard_0001.jsonl [--consensus 4]
|
||||
* --consensus k:邻域共识过滤。起点不变、目标位置各扰动 5 mm 造 k 个邻居跑老师,原任务路径若与多数邻居
|
||||
* 不在同一绕行类(pathdev.sameClass),整条样本 keep=false(why=consensus)。成本约 (1+k) 倍。
|
||||
* 每行一条任务:采样 → 老师 → 距离下界认证过滤 → 局部时间律(50 Hz)→ 写出。
|
||||
* 认证不过的样本也写出(keep=false + 原因),供 stats 统计丢弃率;训练只用 keep=true。 */
|
||||
'use strict';
|
||||
@@ -9,6 +11,8 @@ const N = +args.n || 100, SEED = +args.seed || 1, OUT = args.out || 'ml/data/sha
|
||||
const { S, ArmIK } = require('../../tests/route_bench_setup.cjs')();
|
||||
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 });
|
||||
const { timeParam } = require('./timelaw.cjs');
|
||||
@@ -24,12 +28,26 @@ for (let i = 0; i < N; i++) {
|
||||
q0: task.q0.map(r3), T: task.T.map(r3), R: task.R, fp };
|
||||
if (!t.ok) { rec.keep = false; rec.why = t.why; fs.writeSync(fd, JSON.stringify(rec) + '\n'); continue; }
|
||||
const c0 = performance.now();
|
||||
const c = cert.path(t.pts);
|
||||
let c = null;
|
||||
if (t.ptsJ6Frozen) { const cf = cert.path(t.ptsJ6Frozen); if (cf.ok) { t.pts = t.ptsJ6Frozen; t.travel = t.travelOf(t.pts); c = cf; rec.j6Frozen = true; } }
|
||||
if (!c) c = cert.path(t.pts);
|
||||
if (task.cat === 'D' && t.reached) rec.cat = 'Dr'; // "不可达"采样其实可达:单独记类,不当停靠样本
|
||||
Object.assign(rec, { path: t.pts.map(q => q.map(r3)), reached: t.reached, err: r3(t.err), oerr: r3(t.oerr), travel_deg: r3(t.travel),
|
||||
plan_ms: Math.round(t.ms), blocked: t.blocked, poseRelaxed: t.poseRelaxed, detour: t.detour,
|
||||
cert: { ok: c.ok, queries: c.queries, minGap: r3(c.minGap), ms: Math.round(performance.now() - c0), why: c.why || null, failAt: c.failAt == null ? null : c.failAt } });
|
||||
/* 训练可用:认证通过;D 类允许未到达(停靠),其余类必须到达;poseRelaxed 的 pose 样本降为 pos */
|
||||
rec.keep = c.ok && (t.reached || task.cat === 'D');
|
||||
if (rec.keep && K_CONS > 0 && t.reached) {
|
||||
let agree = 0, valid = 0;
|
||||
for (let k = 0; k < K_CONS; k++) {
|
||||
const u = [sampler.rnd() - 0.5, sampler.rnd() - 0.5, sampler.rnd() - 0.5], n = Math.hypot(u[0], u[1], u[2]);
|
||||
const nb = teach(Object.assign({}, task, { T: task.T.map((v, j) => v + 5 * u[j] / n) }));
|
||||
if (!nb.ok || !nb.reached) continue;
|
||||
valid++; if (sameClass(t.pts, nb.ptsJ6Frozen && rec.j6Frozen ? nb.ptsJ6Frozen : nb.pts, task.mode)) agree++;
|
||||
}
|
||||
rec.consensus = { agree, valid };
|
||||
if (valid >= 2 && agree * 2 < valid) { rec.keep = false; rec.why = 'consensus'; }
|
||||
}
|
||||
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');
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/* 路径偏差(同伦类判定用):两条展开折线按巡航时间 τ 各重采样 n 点,逐轴取最大差(°)。
|
||||
* 判定口径:pos 任务忽略 J6(位置任务里 J6 是冗余自由度),max < 30° 视为同一绕行类。 */
|
||||
'use strict';
|
||||
const { VMAX } = require('./timelaw.cjs');
|
||||
function resample(P, n) {
|
||||
const seg = []; for (let i = 1; i < P.length; i++) { let m = 0; for (let j = 0; j < 6; j++) m = Math.max(m, Math.abs(P[i][j] - P[i - 1][j]) / VMAX[j]); seg.push(m); }
|
||||
if (!seg.length) return Array.from({ length: n }, () => P[0].slice());
|
||||
const tot = seg.reduce((a, b) => a + b, 0) || 1; const out = [];
|
||||
for (let k = 0; k < n; k++) { let tau = tot * k / (n - 1), i = 0; while (i < seg.length - 1 && tau > seg[i]) { tau -= seg[i]; i++; }
|
||||
const u = seg[i] > 0 ? Math.min(1, tau / seg[i]) : 1; out.push(P[i].map((v, j) => v + (P[i + 1][j] - v) * u)); }
|
||||
return out;
|
||||
}
|
||||
function devAx(A, B, n) { n = n || 32; const a = resample(A, n), b = resample(B, n); const m = [0, 0, 0, 0, 0, 0]; for (let k = 0; k < n; k++) for (let j = 0; j < 6; j++) m[j] = Math.max(m[j], Math.abs(a[k][j] - b[k][j])); return m; }
|
||||
const SAME_DEG = 30;
|
||||
function sameClass(A, B, mode) { const d = devAx(A, B); return Math.max.apply(null, mode === 'pos' ? d.slice(0, 5) : d) < SAME_DEG; }
|
||||
module.exports = { resample, devAx, sameClass, SAME_DEG };
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# 并行生成:bash ml/gen/run_parallel.sh <每进程条数> <进程数> [起始种子=1]
|
||||
# 并行生成:bash ml/gen/run_parallel.sh <每进程条数> <进程数> [起始种子=1] ["--consensus 4"]
|
||||
# 例:8 核机器 5 万条 → bash ml/gen/run_parallel.sh 6250 8
|
||||
set -e
|
||||
N=${1:-500}; P=${2:-2}; S0=${3:-1}
|
||||
N=${1:-500}; P=${2:-2}; S0=${3:-1}; EXTRA=${4:-}
|
||||
cd "$(dirname "$0")/../.."
|
||||
mkdir -p ml/data
|
||||
for ((i=0;i<P;i++)); do s=$((S0+i)); node ml/gen/gen_dataset.cjs --n $N --seed $s --out ml/data/shard_$(printf %04d $s).jsonl 2> ml/data/log_$s.txt & done
|
||||
for ((i=0;i<P;i++)); do s=$((S0+i)); node ml/gen/gen_dataset.cjs --n $N --seed $s --out ml/data/shard_$(printf %04d $s).jsonl $EXTRA 2> ml/data/log_$s.txt & done
|
||||
wait
|
||||
node ml/gen/stats.cjs ml/data/shard_*.jsonl > ml/data/stats.json
|
||||
cat ml/data/stats.json
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ const q = a => { a.sort((x, y) => x - y); return a.length ? { p50: +a[a.length >
|
||||
const plan = [], certMs = [], travel = [], exec = [], gaps = [];
|
||||
for (const f of files) for (const line of fs.readFileSync(f, 'utf8').split('\n')) {
|
||||
if (!line) continue; const r = JSON.parse(line); total++;
|
||||
const k = r.cat + '/' + r.mode; const b = by[k] = by[k] || { n: 0, reached: 0, certFail: 0, keep: 0, demoted: 0 };
|
||||
b.n++; if (r.reached) b.reached++; if (r.cert && !r.cert.ok) b.certFail++; if (r.demoted) b.demoted++;
|
||||
const k = r.cat + '/' + r.mode; const b = by[k] = by[k] || { n: 0, reached: 0, certFail: 0, keep: 0, demoted: 0, consensusDrop: 0, j6Frozen: 0 };
|
||||
b.n++; if (r.reached) b.reached++; if (r.cert && !r.cert.ok) b.certFail++; if (r.demoted) b.demoted++; if (r.why === 'consensus') b.consensusDrop++; if (r.j6Frozen) b.j6Frozen++;
|
||||
if (r.plan_ms != null) plan.push(r.plan_ms); if (r.cert) certMs.push(r.cert.ms); if (r.cert) gaps.push(r.cert.minGap);
|
||||
if (r.keep) { b.keep++; keep++; travel.push(r.travel_deg); exec.push(r.exec_s);
|
||||
const n = r.traj.length - 1; steps += n; const s = Math.floor(n / 3) + 1; samples += s;
|
||||
|
||||
+20
-9
@@ -1,14 +1,19 @@
|
||||
/* 老师(方案 §4.3):solveDyn 松手口径——有离线路网时内部走 PRM 路网 Lazy A* + 捷径平滑(种子由起点决定,确定性),
|
||||
* 路网失败再落到 RRT-Connect。返回展开后的折线与到达判据。 */
|
||||
/* 老师(方案 §4.3):solveDyn 松手口径——有离线路网时内部走 PRM 路网 Lazy A* + 捷径平滑,路网失败再落到 RRT-Connect。
|
||||
* 离线确定性(2026-10-17,M1 一致性门禁 78% 未过后加):
|
||||
* - 每次求解前 resetSession():清空路网增广/坏边/已验段等跨调用状态,结果与样本顺序无关;
|
||||
* - setDeterministic:毫秒预算改按碰撞判定次数计(msPerCheck 默认 0.5,即 300 ms 预算 ≈ 600 次判定),与机器负载无关;
|
||||
* - pos 任务冻结 J6:位置任务里 J6 不影响 TCP,路径上 J6 恒取起点值(认证不过才保留老师原路径,由 gen 决定)。 */
|
||||
'use strict';
|
||||
module.exports = function makeTeacher(S, ArmIK) {
|
||||
module.exports = function makeTeacher(S, ArmIK, opt) {
|
||||
opt = opt || {};
|
||||
const L = S.LIM, O = S.ORDER, w = S.wrap180;
|
||||
if (S.setDeterministic) S.setDeterministic(true, opt.msPerCheck || 0.5);
|
||||
return function teach(task) {
|
||||
const opt = { tool: 0, step: 0.5, ref: task.q0, from: task.q0.slice() };
|
||||
if (task.mode === 'pose') { opt.pose = { R: task.R }; opt.priority = 'pos'; }
|
||||
if (S.rtReset) S.rtReset();
|
||||
const o = { tool: 0, step: 0.5, ref: task.q0, from: task.q0.slice() };
|
||||
if (task.mode === 'pose') { o.pose = { R: task.R }; o.priority = 'pos'; }
|
||||
if (S.resetSession) S.resetSession(); else if (S.rtReset) S.rtReset();
|
||||
const t0 = performance.now();
|
||||
const r = S.solveDyn(task.T, opt);
|
||||
const r = S.solveDyn(task.T, o);
|
||||
const ms = performance.now() - t0;
|
||||
if (!r || !r.q) return { ok: false, ms, why: 'noq' };
|
||||
const pts = [task.q0.slice()].concat(r.via || [], [r.q]);
|
||||
@@ -17,7 +22,13 @@ module.exports = function makeTeacher(S, ArmIK) {
|
||||
const err = Math.hypot(tcp[0] - task.T[0], tcp[1] - task.T[1], tcp[2] - task.T[2]);
|
||||
const oerr = task.mode === 'pose' ? ArmIK.rotErrDeg(S.orient(qf), task.R) : 0;
|
||||
const reached = err < 1 && oerr < 0.5 && !r.blocked;
|
||||
let travel = 0; for (let i = 1; i < pts.length; i++) for (let k = 0; k < 6; k++) travel += Math.abs(pts[i][k] - pts[i - 1][k]);
|
||||
return { ok: true, pts, reached, err, oerr, travel, ms, blocked: !!r.blocked, poseRelaxed: !!r.poseRelaxed, unsafe: !!r.unsafe, detour: r.detour || null };
|
||||
const travelOf = P => { let t = 0; for (let i = 1; i < P.length; i++) for (let k = 0; k < 6; k++) t += Math.abs(P[i][k] - P[i - 1][k]); return t; };
|
||||
const out = { ok: true, pts, reached, err, oerr, travel: travelOf(pts), ms, blocked: !!r.blocked, poseRelaxed: !!r.poseRelaxed, unsafe: !!r.unsafe, detour: r.detour || null };
|
||||
if (task.mode === 'pos') {
|
||||
const j6 = task.q0[5];
|
||||
if (pts.some(p => Math.abs(p[5] - j6) > 1e-6)) out.ptsJ6Frozen = pts.map(p => { const c = p.slice(); c[5] = j6; return c; });
|
||||
}
|
||||
out.travelOf = travelOf;
|
||||
return out;
|
||||
};
|
||||
};
|
||||
|
||||
+14
-2
@@ -463,6 +463,14 @@ function make(cfg) {
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
function setDeterministic(on, msPerCheck) { VCLK = on ? { base: 0, per: msPerCheck || 0.5, tick: 0 } : null; }
|
||||
/* 清空所有跨调用状态(路网增广、坏边/死边/已验段缓存、实时路径、全量 IK 缓存),使下一次求解与历史无关 */
|
||||
function resetSession() {
|
||||
RMX.Q.length = 0; RMX.adj.length = 0; RMX.adjR = null;
|
||||
BADE.clear(); DEAD.clear(); FINE.clear();
|
||||
RT = null; RTSTALL.key = null; RTSTALL.n = 0;
|
||||
FULLIK.key = null; FULLIK.cur = null; FULLIK.T = null; FULLIK.at = 0;
|
||||
}
|
||||
/* 全胶囊对最小间隙(mm,= 距离 − need,负值为干涉;≥cap 一律记 cap,下界、保守)(2026-10-17,
|
||||
ml/gen 距离下界认证用):不走 pair2d 查表、不提前退出,比 collideFree 慢,只给离线/安全层用。 */
|
||||
function clearance(q, cap) {
|
||||
@@ -769,7 +777,11 @@ function make(cfg) {
|
||||
for (let i = 0; i < 6; i++) { const w = W ? W[i] : 1; t += Math.abs(wrap180(q[i] - ref[i])) * w; }
|
||||
return t + 1e-6 * c;
|
||||
};
|
||||
const nowMs = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
|
||||
/* 确定性时钟(2026-10-17,ml/gen 离线老师用):开启后各处毫秒预算改按"碰撞判定次数 × msPerCheck"计,
|
||||
另每次读时钟 +1 µs 防纯计算循环不前进。规划结果只取决于输入,不随机器快慢/负载变化。默认关闭。 */
|
||||
let VCLK = null;
|
||||
const nowMs = () => VCLK ? VCLK.base + PST.n * VCLK.per + (VCLK.tick += 0.001)
|
||||
: (typeof performance !== 'undefined' ? performance.now() : Date.now());
|
||||
|
||||
/* 姿态求解原语工厂:位置闭式支把 θ1..θ3 吸收掉之后,姿态任务只剩「工具轴指向 =
|
||||
目标轴」这 2 个方程、2 个未知数 (θ4,θ5),解是孤立点,滚转再由 θ6 闭式补上。
|
||||
@@ -3163,7 +3175,7 @@ function make(cfg) {
|
||||
solveRoute: solveRoute, routeFrom: routeFrom, transitPt: transitPt, planAround: planAround, edgeFree: edgeFree, PST: PST, unwrapTo: unwrapTo, rtWarm: rtWarm, rtReset: () => { RT = null; }, rmPlan: rmPlan, rmSmooth: rmSmooth, modelFingerprint: modelFingerprint, RM: RM, wd: wd, classGap: classGap, classFingerprint: classFingerprint, P2D: P2D,
|
||||
mixSolve: mixSolve, closest: closest, closest6: closest6, closestOri: closestOri, orient: orient,
|
||||
branch: branch, selfTest: selfTest, poseSelfTest: poseSelfTest,
|
||||
collideFree: collideFree, collideDetail: collideDetail, clearance: clearance, gapMatrix: gapMatrix, leverBounds: leverBounds,
|
||||
collideFree: collideFree, collideDetail: collideDetail, clearance: clearance, gapMatrix: gapMatrix, leverBounds: leverBounds, setDeterministic: setDeterministic, resetSession: resetSession,
|
||||
dynLimit: dynLimit, dynLimits: dynLimits,
|
||||
K: K, LIM: LIM, P: P, D: D, ORDER: ORDER, wrap180: wrap180, costOf: costOf };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user