Files
Robot_Arm/docs/Transformer轨迹拟合训练方案.md
T

318 lines
14 KiB
Markdown
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.
# Transformer 拟合避障轨迹规划:训练方案(审查稿)
> 2026-10-17 起草,基于 `main@3c7c814`(贴立柱修复后的规划器)。本文只是方案,代码尚未编写。
> 审查时重点看:§0 定位、§2 数据分布、§3 表示方式、§5 训练配置、§7 验收标准。
---
## 0. 定位与边界
| 项目 | 决定 |
|---|---|
| 模型角色 | **提议器**:给出若干条候选关节轨迹,由现有 `collideFree` + 半格复验把关,全部不合法时回落 `routeRT` / `solveDyn` |
| 不做的事 | 不让模型直接驱动电机;不替代碰撞检测;不学习动力学(速度/加速度仍由 `animTo` 时间参数化) |
| 老师 | 当前 JS 规划器(`webapp/js/ik-solver.js`):松手口径用 `S.solveDyn(T, opt)`,拖拽口径用 `S.solveRoute(T, {quick:true,...})` |
| 收益目标 | 实时拖拽重规划 P99 ≈ 321 ms → **< 50 ms**;首次全量 IK 尖峰(最差约 2 s)→ < 100 ms |
| 底线 | 「模型+验证+回落」整体的到达率、路径合法率**不得低于**现有规划器(任何基准上都不允许退化) |
为什么必须保留验证:碰撞是"硬约束",神经网络只能做到高概率满足。验证器把"偶尔撞一下"变成"偶尔慢一下"。
---
## 1. 训练环境
| 阶段 | 在哪里跑 | 说明 |
|---|---|---|
| 数据生成 | Hark 沙箱(2 vCPU / 7 GB,Node) 或 用户机器 | 纯 JS,无需 GPU;可多进程并行 |
| 训练 | **用户的 GPU 机器** | 沙箱无 GPU、无 PyTorch。最低建议 8 GB 显存(RTX 3060 级别) |
| 部署推理 | 浏览器(onnxruntime-web,WebGPU 优先,wasm 兜底) | 与现有查看器同页运行 |
Python 环境(训练机):
```bash
conda create -n armtf python=3.11 -y && conda activate armtf
pip install torch --index-url https://download.pytorch.org/whl/cu121 # 按本机 CUDA 版本选
pip install numpy pyarrow tqdm tensorboard onnx onnxruntime
```
---
## 2. 第一步:生成数据(规划器当老师)
### 2.1 新增文件
| 文件 | 作用 |
|---|---|
| `ml/gen/gen_dataset.cjs` | 采样任务 → 调用规划器 → 校验 → 写分片 |
| `ml/gen/sampler.cjs` | 起点/目标采样器(三类分布,见 2.3) |
| `ml/gen/run_parallel.sh` | 按 CPU 核数起 N 个进程,每个进程不同随机种子 |
| `ml/data/shard_XXXX.jsonl` | 原始样本分片(每片 5000 条) |
复用 `tests/route_bench_setup.cjs` 装配求解器(关节表、`docs/travel.json`、`docs/collide.json`,无需网格)。
### 2.2 单条样本的字段
```json
{
"id": "s0003_000127",
"mode": "pos" | "pose",
"teacher": "dyn" | "rt",
"q0": [6 个关节角, 度],
"T": [x, y, z], // 目标 TCP 位置, mm
"R": [[3x3]] | null, // 目标姿态(pose 模式)
"tool": 0,
"path": [[6], [6], ...], // 老师给出的折线: q0 → via... → q
"q_goal": [6],
"ok": true, // 到达 (误差<1mm, pose 另需<0.5°) 且 1° 细验无干涉
"poseRelaxed": false,
"travel_deg": 123.4,
"plan_ms": 812.0,
"fp": "collide.json 指纹" // modelFingerprint(),碰撞模型变了旧数据作废
}
```
### 2.3 采样分布(目标 20 万条原始任务)
关节限位(`docs/travel.json`):J1/J3/J6 连续 ±180°;J2 [-92, 9],J4 [-111, 16],J5 [-93, 34]。
| 类别 | 占比 | 采样方式 |
|---|---:|---|
| A 均匀 | 50% | 起点 q0、目标位形 b 均在限位内均匀采样且本身无干涉;目标取 `fk(b)`(pose 模式加 `orient(b)`) |
| B 直线被挡 | 30% | 同 A,但要求 q0→b 关节直线 1° 细验有干涉(route_bench 的生成口径) |
| C 贴立柱 | 15% | b 满足 `nearColumn`(J1/J2/J3 任一 ±4° 即撞立柱),q0 在立柱另一侧(near_column_bench 口径) |
| D 不可达/无安全解 | 5% | 目标在工作空间外或所有 IK 分支都撞;标签为"拒绝",教模型说"到不了" |
- `mode`:pos 与 pose 各 50%。
- 老师:每条任务用 `solveDyn` 求一次(松手口径,质量更高);其中 30% 额外用 `routeRT` 拖拽口径求一次,作为"快速路径"风格的辅助样本(`teacher:"rt"`),训练时可按需过滤。
- **测试集隔离**:用固定种子另生成 2000 条留作测试集,且排除与训练目标距离 < 5 mm 的样本;`route_bench` / `near_column_bench` / `rt_drag_bench` 的案例**不进入训练集**。
### 2.4 质量过滤
1. `ok == false` 的样本:A/B/C 类丢弃;D 类保留为"拒绝"标签。
2. 整条路径 1° 细验(`segOK`)+ 半格复验,任一段干涉即丢弃。
3. `poseRelaxed == true` 的样本单独打标签,训练时只当作 pos 模式样本用(避免模型学到"姿态随便放")。
4. 关节行程超过同任务 `routeRT` 结果 1.5 倍的视为老师绕远,丢弃(可选,先统计比例再决定)。
### 2.5 数据增广(不需要重新规划)
- **后缀切片**:合法路径的任意后半段也是合法路径。从每条路径中按弧长取 2–4 个中间点作为新 q0,得到新样本(目标不变)。约 ×3。
- **连续轴平移**:J1 绕立柱对称性**不成立**(立柱在固定位置),不做 J1 旋转增广;J6 若与碰撞无关可加 ±360° 等价表示,归一化时统一处理。
- 增广后预计约 60 万条训练样本。
### 2.6 耗时与存储估算
- `solveDyn` 约 0.7 s/条,沙箱 2 进程约 1 万条/小时 → 20 万条约 20 小时;用户机器 8 核约 5 小时。
- 每条约 1–2 KB,全部原始数据约 300 MB;转 Parquet 后约 80 MB。
### 2.7 本步检查点(审查时可看)
- [ ] `ml/data/stats.json`:各类别数量、ok 率、平均行程、路径点数分布、规划耗时分布
- [ ] 随机抽 20 条在查看器里回放(新增 `?replay=shard_0000.jsonl#127` URL 参数)
- [ ] 测试集与 3 个基准案例和训练集无重叠
---
## 3. 第二步:预处理(Python)
文件:`ml/prep/build_dataset.py` → 输出 `ml/data/train.parquet`、`val.parquet`(95/5 划分)、`test.parquet`、`norm.json`。
### 3.1 路径重采样
1. 连续轴(J1/J3/J6)先做展开(`unwrapTo` 同口径),保证相邻点差 < 180°。
2. 按**加权关节空间弧长**重采样为固定 **K = 32** 个点,第 0 点 = q0,第 31 点 = q_goal。权重默认各轴 1(可改成按连杆长度加权)。
3. 记录原始弧长 `L`(模型额外回归它,用于早停与排序)。
### 3.2 条件输入(编码器一侧)
| 量 | 维度 | 编码 |
|---|---:|---|
| q0 | 6 | 每轴 (sin, cos) → 12;限位轴再加归一化值 (q−lo)/(hi−lo) → +3 |
| 目标位置 T | 3 | 除以 600 mm 归一化 |
| 目标姿态 R | 6 | 旋转矩阵前两列(6D 表示);pos 模式填 0 |
| 模式 | 2 | one-hot [pos, pose] |
| 工具号 | 1 | 目前恒 0,留位 |
| 起点 TCP | 3 | `fk(q0)` 归一化(给模型一个笛卡尔线索) |
### 3.3 输出表示(解码器一侧)——离散化
- 每个关节值相对 q0 的**增量** Δq = q_k − q0 离散化:连续轴范围 [−360, 360],限位轴范围 [lo−hi, hi−lo],均分 **512 个桶**(约 1.4°/桶)。
- 每个桶再回归一个桶内偏移 ∈ [−0.5, 0.5](细化到亚度级)。
- 序列:K−1 = 31 个路径点 × 6 轴 = **186 个 token**(第 0 点即 q0,不需要预测)。
- 额外 1 个 `[REJECT]/[OK]` 分类 token 放在序列最前:D 类样本只训练这个 token。
**为什么不用直接回归(MSE)**:立柱两侧绕行(左绕/右绕、J3 翻转/不翻转)是多峰分布,MSE 会输出两条路径的平均——恰好穿过立柱。离散化 + 自回归采样能保留多峰。
### 3.4 归一化文件 `norm.json`
记录桶边界、K、权重、碰撞模型指纹 `fp`;部署端必须校验 `fp` 一致,否则拒绝加载模型。
---
## 4. 第三步:模型结构
文件:`ml/model/traj_transformer.py`
```
条件向量 (≈37 维)
└─ MLP → 8 个条件 token (d=256) ← 编码器:4 层 Transformer encoder
目标序列: [BOS][OK] (j1,j2,...,j6)×31
└─ 嵌入 = 桶嵌入 + 轴嵌入(6) + 路径点位置嵌入(32)
└─ 6 层 Transformer decoder(因果自注意力 + 对条件 token 的交叉注意力)
输出头:
├─ 桶分类头 (512 类 / token)
├─ 桶内偏移回归头 (1 / token)
├─ OK/REJECT 头 (2 类)
└─ 路径弧长回归头 (1)
```
| 超参 | 值 |
|---|---|
| d_model | 256 |
| 头数 | 8 |
| FFN | 1024,GELU |
| 编码器/解码器层数 | 4 / 6 |
| Dropout | 0.1 |
| 参数量 | 约 7 M(fp32 约 28 MB,fp16 约 14 MB) |
备选(第一版不做):同一骨干改为 flow matching,直接输出 31×6 连续值,推理 8–16 步;在自回归版精度不够时再试。
---
## 5. 第四步:训练
文件:`ml/train.py`,配置 `ml/configs/base.yaml`。
### 5.1 损失
```
L = CE(桶) 权重 1.0(label smoothing 0.05)
+ L1(桶内偏移) 权重 0.5
+ CE(OK/REJECT) 权重 0.5
+ L1(弧长 L) 权重 0.1
+ 平滑项 ‖q_{k+1} − 2q_k + q_{k−1}‖²(用期望桶值计算) 权重 0.05
+ 终点项 ‖q_31 − q_goal‖(期望值) 权重 0.5
```
### 5.2 训练配置
| 项 | 值 |
|---|---|
| 优化器 | AdamW,β=(0.9, 0.95),weight decay 0.05 |
| 学习率 | 3e-4,前 2000 步线性预热,余弦退火到 3e-6 |
| batch | 256(显存不够用梯度累积) |
| 精度 | bf16/fp16 混合精度 |
| 轮数 | 最多 40 epoch,验证集"合法率"连续 5 次不升即早停 |
| 教师强制 | 全程 teacher forcing;第 20 epoch 后 10% 概率用模型自己的预测(scheduled sampling),减轻曝光偏差 |
| 预计耗时 | RTX 3060:约 1.5–3 小时(估算,跑完第一轮后校正) |
### 5.3 训练中验证(每个 epoch)
在 `val` 抽 1000 条,**自回归采样**生成路径后用 Python 版碰撞检查器评估:
- 用 `ml/eval/collide_py.py`(从 `collide.json` 读胶囊,与 JS 同口径;先用 JS 结果对拍 1 万个位形,误差必须为 0)。
- 指标:合法率(1° 细验无干涉)、终点误差、行程比(模型/老师)、REJECT 准确率。
- 保存最优 checkpoint 依据 = **合法率**,其次终点误差。
### 5.4 一条命令运行
```bash
python ml/prep/build_dataset.py --in ml/data --out ml/data
python ml/train.py --config ml/configs/base.yaml --run runs/v1
tensorboard --logdir runs
```
---
## 6. 第五步:推理与集成
### 6.1 导出
`ml/export_onnx.py`:导出带 KV cache 的解码器(opset 17),fp16 量化;输出 `webapp/models/traj_v1.onnx` + `traj_v1.norm.json`。
### 6.2 推理流程(新增 `webapp/js/ik-learned.js`,在 `routeRT` 之前调用)
1. 编码条件 → 一次 batch 采样 **B = 8** 条(温度 {0, 0.5, 0.8, 1.0} 各 2 条,0 为贪心)。
2. 若 OK 头判 REJECT 概率 > 0.9 → 直接走原规划器(不信任模型的"到不了",只用来省时间排序)。
3. 每条候选:终点用 `solveTo` 做 1–3 次牛顿精修 → 整条 `collideFree` 1° 细验 + 半格复验。
4. 选合法且行程最短的一条;**全部不合法 → 原 `routeRT` / `solveDyn` 回落**。
5. 拖拽中热启动:把上一帧路径剩余部分作为解码前缀,只重新生成后半段。
### 6.3 延迟预算(目标)
| 环节 | 目标 |
|---|---:|
| 模型推理(8 条 × 186 token,WebGPU) | < 15 ms |
| 碰撞验证(8 条) | < 15 ms(已有包围球预筛) |
| 合计(命中时) | < 30 ms |
---
## 7. 第六步:评估与验收
### 7.1 测试集合
| 集合 | 来源 |
|---|---|
| route_bench | `tests/route_bench.cjs`,60 例 |
| near_column_bench | `tests/near_column_bench.cjs`,30 例(pos / pose / rt) |
| rt_drag_bench | `tests/rt_drag_bench.cjs`,12 例 |
| held-out | §2.3 的 2000 条留出测试集 |
新增 `tests/learned_bench.cjs`:对同一批案例分别跑 ①原规划器 ②仅模型 ③模型+验证+回落,输出对比表。
### 7.2 验收标准
| 指标 | 仅模型(参考) | 模型+验证+回落(必须) |
|---|---|---|
| 路径合法率 | ≥ 90% | = 100%(与现状一致) |
| 到达率 | ≥ 95% | ≥ 现状(route 60/60,near pos 30/30,rt 30/30) |
| 关节行程 | ≤ 老师 × 1.1 | ≤ 现状 × 1.05 |
| 拖拽重规划 P99 | — | < 50 ms(现状约 321 ms) |
| 拖拽碰撞帧 | — | 0 |
| 模型命中率(不需回落) | — | ≥ 85% |
不达标的处理:命中率低 → 加数据(重点补失败类别)/ 加大模型;行程差 → 加大行程 loss 或过滤老师绕远样本;延迟不降 → 减少 B 或改 flow matching。
---
## 8. 风险与维护
1. **碰撞模型/立柱变动 = 重训**:`collide.json` 指纹写进数据和模型,不一致时前端自动禁用模型,回落原规划器,不会出错只会变慢。重训流程 = §2 → §5,约半天。
2. **老师的缺陷会被学走**:例如 `ci27` 姿态根枚举漏分支(姿态偏差约 97.5°)。数据里 `poseRelaxed` 样本已隔离;老师修好后再重造数据。
3. **分布外目标**:模型在工作空间边缘可能乱给;有验证+回落兜底,只影响速度。
4. **J3/J6 连续轴展开**:预处理与部署必须同一套 `unwrapTo` 口径,§3 对拍时一并验证。
---
## 9. 目录规划
```
ml/
gen/ gen_dataset.cjs sampler.cjs run_parallel.sh
data/ shard_*.jsonl stats.json train/val/test.parquet norm.json (不入库,.gitignore)
prep/ build_dataset.py
model/ traj_transformer.py
eval/ collide_py.py eval_paths.py
configs/ base.yaml
train.py export_onnx.py README.md
webapp/js/ik-learned.js
webapp/models/traj_v1.onnx traj_v1.norm.json
tests/learned_bench.cjs
```
## 10. 里程碑
| # | 内容 | 产出 | 在哪跑 |
|---|---|---|---|
| M1 | 数据生成器 + 1000 条试跑 + stats | `ml/gen/*`、stats.json | 沙箱 |
| M2 | Python 碰撞检查器对拍 | `collide_py.py`,1 万位形 0 误差 | 沙箱 |
| M3 | 全量 20 万条数据 | Parquet | 沙箱(约 20 h)或用户机器 |
| M4 | 训练工程 + 小数据过拟合测试(1000 条能背下来) | `train.py` 等 | 沙箱 CPU 验证代码可跑 |
| M5 | 正式训练 | checkpoint、TensorBoard | **用户 GPU** |
| M6 | 导出 ONNX + 前端集成 + learned_bench | 对比表 | 沙箱 + 浏览器 |
## 11. 需要用户确认
1. 训练机显卡型号与显存。
2. 定位:只做加速器(本文默认),还是希望最终能脱离规划器。
3. 数据生成在沙箱跑(约 20 h),还是你的机器跑更快。