diff --git a/docs/reports/actpred_action_norm_weighting_plan_20260814.md b/docs/reports/actpred_action_norm_weighting_plan_20260814.md new file mode 100644 index 0000000..c52071c --- /dev/null +++ b/docs/reports/actpred_action_norm_weighting_plan_20260814.md @@ -0,0 +1,101 @@ +# actpred 动作归一化与逐列权重开发方案 + +> 文档类型:开发方案 +> 模块:actpred(联动 pybot 数据契约) +> 作者:自动生成 +> 日期:2026-08-14 +> 前置实验:`pybot_actpred_dataset_training_eval_20260814.md` + +--- + +## 1. 问题陈述 + +闭环评估(R4)暴露:z-score 归一化把 18 列方差拉平,隐式实现"全列等权",导致**舵机精度 ↔ spk.F 频率跟踪的权衡**——未归一化模型舵机准但 spk.F 崩(21k MSE),归一化模型 spk.F 改善 3× 但简单动作恶化 1.3-25×。 + +本质:**缩放方式(数值稳定性)与列重要性(优化方向)是两个正交维度**,当前被隐式耦合。方案目标:固定物理范围归一化(数值稳定、免 stats 文件)+ 显式逐列权重(舵机优先),两者解耦。 + +## 2. 方案设计 + +### 2.1 固定物理范围 min-max 归一化(actpred 侧) + +位置决策:**归一化保留在 actpred 侧,pybot 继续记录原始量纲**。理由: + +- **raw at rest**:episode 是数据资产,原始角度/频率可复用于逆运动学、碰撞检测等分析,归一化不可逆 +- **反归一化链条**:若 pybot 侧归一化,`--loop` 执行前需反归一化,min/max 表要同时存在于 recorder 与 loop 两端;现状 `predict()` 返回原始量纲,pybot 无感知,边界更干净 +- **代码库先例**:IMU 即"pybot 记录原始、actpred `imu_norm.py` 按物理量程归一化",动作走同一条路保持一致 +- **pybot 零依赖承诺**:归一化逻辑入 recorder 会加深 pybot 对 numpy 的依赖面 + +固定范围常量表(新增 `actpred/action_norm.py`,类比 `imu_norm.py`): + +| 列 | 物理范围 | 依据 | +|---|---|---| +| 0-9 舵机角 | **[0, 180]** | `pybot/hd/servo.py` `MAX_ANGLE=180.0`(校准后全行程 0-180°) | +| 10 spk.F | [0, 5000] | `@Speaker max_f=5000` | +| 11 spk.delay | [0, 2000] | 实用上限(脚本用 10-500ms) | +| 12-17 保留位 | 恒 0 | 不参与归一化,损失权重 0 | + +min-max 映射:`x_norm = (x - lo) / (hi - lo)` → [0,1](或映射到 [-1,1],与 IMU 归一化输出区间一致,待实现时定)。 + +优点:免 action_stats.npy(消除 stats 路径类 bug 的整类风险)、零状态部署、数值区间有界。风险:min-max 对离群值敏感——若真实数据某列越界,需 clamp 到 [0,1] 并告警(防御口径与 `_action_bounds` 一致)。 + +可选细化(后续):按各舵机 `|max|`(s1=90°、s2=180°…)分列归一化,90° 舵机能用满 [0,1],分辨率更高;需要读取 @Servo 块的 max 列构建表。 + +### 2.2 逐列加权损失 + +```python +COLUMN_WEIGHTS = np.array([ + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, # 0-9 舵机(安全关键) + 0.2, 0.2, # 10-11 spk.F / spk.delay(声音与节奏,不影响运动) + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, # 12-17 保留位(恒 0,纯噪声) +], dtype=np.float32) +# loss = mean(w * (pred - target)^2) / mean(w) +``` + +实施点: + +- `train.py` / `pretrain_mt.py` 的 `train_one_epoch` / `validate`:MSE 处乘权重向量(权重存 DataConfig,随 checkpoint 序列化,推理端无需感知) +- 权重可经 CLI `--column-weights` 覆盖(默认用上表) +- 训练集/验证集用同一权重;`evaluate.py` 的逐列误差报告不受影响(报告仍按无权重口径,便于对照) + +### 2.3 归一化与权重的组合矩阵 + +| 配置 | 数值稳定性 | 舵机精度 | spk.F 跟踪 | 预期适用 | +|---|---|---|---|---| +| raw + 等权(模型 A) | 差(loss 数百量级) | 好 | 崩 | 仅演示/对照 | +| z-score + 等权(模型 B) | 好 | 差 | 好 | 现状 | +| **固定范围 + 加权(本方案)** | 好 | 好 | 中(w=0.2 保留梯度) | **目标** | + +## 3. 配套改进(同批实施) + +### 3.1 R4 评估器修正(指标失效修复) + +1. **oob_rate 恒值列退化**:span<阈值(如 1e-3)的列不参与越界判定;或改用固定物理界(舵机 [0,180]、spk.F [0,5000])。推荐后者——与 §2.1 常量表共用,且"越物理界"比"越经验界"更符合安全语义 +2. **error_growth 长度混叠**:按 episode 归一化后聚合(每 episode 自身首末段比值取中位数),避免短 episode 尖峰主导曲线 + +### 3.2 R1-B rollout + scheduled sampling + +闭环误差累积源于 teacher forcing 训练与自预测推理的不匹配。已实现的 `--rollout-len K --ss-prob p` 直接对标:建议 `K=4, p=0.5`,与加权损失同批训练对照。 + +### 3.3 数据扩展 + +- **实机录制**(根本瓶颈):dry-run 帧为噪声、IMU 全零,视觉/姿态通路无真实信号。实机 `--record` 后复跑本实验 +- 验证集 1 个 episode 选模噪声大:val_split 0.15→0.3 +- 补充站立/保持类长序列(当前 std 类仅 1-81 步) + +## 4. 实施步骤 + +| 步骤 | 内容 | 产出 | +|---|---|---| +| 1 | 新增 `actpred/action_norm.py`:固定范围常量表 + normalize/denormalize + clamp 防御 | 独立纯 numpy 模块 + 单测 | +| 2 | DataConfig 增 `normalize_mode`(`zscore`/`fixed_range`)与 `column_weights`;dataset/train 接入 | 训练链路支持 + 单测 | +| 3 | `evaluate.py` oob 改物理界、error_growth 按 episode 归一化 | 评估器修正 + 单测 | +| 4 | 训练对照:固定范围+加权 vs z-score+等权(同种子、同数据) | 模型 C/D checkpoint | +| 5 | 复跑 R4 闭环评估,验证权衡解耦 | 对比报告 | + +步骤 1-3 可并行;步骤 4-5 依赖前置完成。预计步骤 1-3 改动集中在 `actpred/`(`action_norm.py` 新增、`config.py`/`dataset.py`/`train.py`/`evaluate.py` 小改),不影响 pybot 与部署链(归一化在推理端内部完成,`predict()` 输出仍为原始量纲)。 + +## 5. 决策记录 + +- **归一化位置**:讨论过"移入 pybot 按列 min/max 归一化"。结论:不采纳(raw at rest、反归一化链条变长、IMU 先例不一致),但采纳其"固定物理范围"内核,落在 actpred 侧常量表实现 +- **舵机范围**:±180° 为误述,实际全行程 0-180°(`servo.py MAX_ANGLE=180.0`),归一化表按 [0,180] +- **spk.F 对数尺度**:备选未定。若加权后 spk.F 跟踪仍不足,再考虑 log 变换(频率感知是对数的,10→1000 跨两个数量级) diff --git a/docs/reports/pybot_actpred_dataset_training_eval_20260814.md b/docs/reports/pybot_actpred_dataset_training_eval_20260814.md new file mode 100644 index 0000000..e4a8fd5 --- /dev/null +++ b/docs/reports/pybot_actpred_dataset_training_eval_20260814.md @@ -0,0 +1,156 @@ +# pybot×actpred 数据集录制与训练闭环评估实验报告 + +> 文档类型:实验报告 +> 模块:pybot → actpred +> 作者:自动生成 +> 日期:2026-08-14 + +--- + +## 1. 概述 + +### 1.1 背景 + +在微动作分解与自监督录制特性(见 `pybot_micro_action_and_recording_20260813.md`)基础上,本实验走通完整链路:dry-run 批量录制全部 `.mt` 动作脚本 → actpred 训练(未归一化 vs z-score 归一化两个版本)→ R4 离线闭环评估,并基于评估结果给出下一步开发方案(见 `actpred_action_norm_weighting_plan_20260814.md`)。 + +### 1.2 实验目标 + +1. 批量录制全部 12 个动作脚本,产出 actpred 格式数据集 +2. 训练两个对照模型:`normalize_actions=False`(旧默认)与 `=True`(新默认,commit `a2c3a57`) +3. 用 `evaluate.py`(R4 闭环代理评估)对比两模型,定位误差主导列 +4. 暴露并修复训练/评估链路的缺陷 + +### 1.3 关键结论速览 + +- **闭环舵机角无发散**,误差主导列是 `spk.F`(扬声器频率,10→1000Hz 大跨度) +- 归一化使聚合 MSE 降低 2.6×(6447→2445),但存在**舵机精度 ↔ 频率跟踪的权衡** +- `oob_rate` / `error_growth` 两个指标在含恒值列的退化数据集上失效,需修正(见开发方案 §3) +- 修复 1 个潜伏 bug:`normalize_actions=True` 时 val_ds 找不到 action_stats(commit `7ec8c9d`) + +--- + +## 2. 数据集批量录制 + +### 2.1 方法 + +`scripts/*.mt` 均无 `@Camera` 块(该块为 pybot 新增,原脚本与 bot4cj 共享故未改)。采用**注入式录制**:不改动原脚本,为每个 `.mt` 在 `@Action` 前注入 `@Camera`(640×480@30fps)生成变体到 `data/pybot_record_scripts/`,再逐个 dry-run 录制。批处理脚本:`data/record_all.sh`(可重复执行)。 + +新增录制脚本 `bot4cj/scripts/record_stand.mt`(commit `af0ff56`):基于 std.mt 扩展 @Camera + MPU6050 绑定 + 5 行 @Action(delay=200),产出 81 步插值序列。 + +### 2.2 录制结果 + +dry-run 下帧为合成随机噪声、IMU 全零(Mock I2C 读零)。共 13 个 episode、865 帧、804 个训练样本(history_len=5, frame_len=3): + +| Episode | 脚本 | 步数 | Episode | 脚本 | 步数 | +|---|---|---|---|---|---| +| 0000 | record_stand | 81 | 0007 | stdd | 46 | +| 0001 | jump | 27 | 0008 | t0 | 211 | +| 0002 | left-hand | 123 | 0009 | tilt-b | 46 | +| 0003 | left-turn | 58 | 0010 | tilt-f | 46 | +| 0004 | lie-down | 11 | 0011 | tilt-l | 46 | +| 0005 | rght-hand | 123 | 0012 | tilt-r | 46 | +| 0006 | std | 1 | | | | + +注:episode_0006 仅 1 步(单行 @Action 首行不分解,属设计行为),对训练无贡献。仅 episode_0000 有 imu.npy(全零),其余无 IMU 文件,`ActionDataset` 按零向量处理。 + +### 2.3 数据验证 + +- `ActionDataset`(imu_dim=0 与 =8 两种模式)加载通过,804 样本 0 形状异常 +- 微动作插值验证:s1 舵机 45°→60° 按 20 微步线性插值(每步 0.75°),`spk.delay` 微步期 10ms,与设计一致 +- 每步帧独立(`bfa6d8d` 同步 capture 修复生效) + +--- + +## 3. 训练实验 + +### 3.1 实验设置 + +| 项 | 值 | +|---|---| +| 分辨率 | 640×480(不降分辨率) | +| 增强 | ColorJitter(--augment) | +| batch / workers | 4 / 2(CPU 训练,~4 分钟/epoch) | +| epochs / patience | 100 / 15(early stopping) | +| 训练/验证 | 12 / 1 episode(763 / 41 样本,seed 42) | + +### 3.2 模型 A:未归一化(normalize_actions=False,旧默认) + +- 50 epochs 触发 early stopping,**best val_loss = 19.02**(原始量纲 MSE,数百量级) +- loss 轨迹:4344(epoch 1)→ 296(epoch 50),val 收敛至 ~20-30 + +### 3.3 模型 B:z-score 归一化(normalize_actions=True,新默认) + +- 26 epochs 触发 early stopping,**best val_loss = 0.0288**(归一化量纲 <1.0,符合 commit `a2c3a57` 预期) +- loss 轨迹:0.188(epoch 1)→ 0.0288(epoch 11 最优) + +### 3.4 训练链路缺陷修复 + +归一化默认开启后暴露潜伏 bug:`train.py` 将 action_stats 保存到 checkpoint_dir 后,cfg 只留裸文件名 "action_stats.npy",val_ds 按 cwd 解析 → `FileNotFoundError`。修复:验证集构建用完整路径,checkpoint 配置存裸文件名(推理端按 checkpoint 目录解析)。同步修正 `test_warn_init_from_mismatch` 对旧默认值的隐式依赖(commit `7ec8c9d`,141 测试全过)。 + +### 3.5 推理冒烟 + +模型 B 加载正常,接口契约 `frame_len=3 / history_len=5 / action_dim=18 / imu_dim=None` 与 pybot 闭环鸭子类型约定一致;单次推理 ~84ms(x64 CPU)。真实帧+真实历史预测 t=5 动作:s1 误差 1.78°(模型 A 为 4.39°),数值范围合理。 + +--- + +## 4. R4 闭环评估 + +### 4.1 聚合指标对比 + +| 指标 | 模型 A(未归一化) | 模型 B(归一化) | +|---|---|---| +| overall_mse | 6446.7 | **2445.4**(2.6× 优) | +| oob_rate | 100% | **28.1%** | +| smoothness_ratio | 0.147(近常数输出,过度平滑) | **1.047**(≈真值变化,正常) | +| error_growth | 0.005 | 0.082(均 <1,无发散) | + +### 4.2 逐 episode 对比(闭环滚动 mean_mse) + +| Episode | 步数 | 模型 A | 模型 B | 比值 | +|---|---|---|---|---| +| 0000 stand | 76 | 25.5 | 134.0 | 5.26× 恶化 | +| 0001 jump | 22 | 20.5 | 221.1 | 10.78× 恶化 | +| **0002 left-hand** | 118 | 21252.2 | **6849.7** | **0.32× 改善** | +| 0003 left-turn | 53 | 276.0 | 641.0 | 2.32× 恶化 | +| 0004 lie-down | 6 | 35.2 | 46.9 | 1.33× 恶化 | +| **0005 rght-hand** | 118 | 21340.0 | **8022.9** | **0.38× 改善** | +| 0007 stdd | 41 | 49.8 | 115.9 | 2.33× 恶化 | +| 0008 t0 | 206 | 644.5 | 553.3 | 0.86× 改善 | +| 0009-0012 tilt-* | 41×4 | 29.8-34.2 | 59.6-751.7 | 1.74-25.12× 恶化 | + +### 4.3 误差主导列定位 + +对 left-hand 逐列分析(模型 A,mean_mse 21252): + +- **舵机列(0-9)预测全部在合理范围**(35-85°),无物理危险 +- **列 10(spk.F)是 MSE 主导源**:该脚本真值 10→1000Hz,模型预测 ~340Hz 折中值 → 单列平方误差 10万-44万 +- 列 8、12-17 为恒值列(s9=90°、保留位 0),预测有微小偏差但绝对值无害 + +### 4.4 指标退化发现 + +1. **oob_rate=100%(模型 A)不可信**:数据集中 7 个恒值列(span=1e-6)经验界宽度为零,任何微小偏差都计越界。模型 B 的 28.1% 改善主要来自 spk.F 落入经验界,指标本身仍需修正(见开发方案 §3) +2. **error_growth 有误导性**:不同长度 episode 混叠使曲线非单调(中期尖峰来自 123 帧 hand episode,结束后回落),末段/首段比值掩盖了中期爆涨 + +### 4.5 核心结论 + +归一化把各列方差拉平,隐式实现了"全列等权",带来 spk.F 跟踪改善(hand 类 3× 提升),代价是舵机角跟踪精度下降(简单动作 1.3-25× 恶化)。缩放方式与列重要性是**正交**的两个维度,需要显式逐列权重解耦——详见开发方案 `actpred_action_norm_weighting_plan_20260814.md`。 + +--- + +## 5. 产物与提交 + +| 产物 | 位置 | +|---|---| +| 数据集(865 帧 / 804 样本) | `data/pybot_record/`(231MB,不入 git) | +| 模型 A checkpoint | `actpred/checkpoints/recorded_run/` | +| 模型 B checkpoint | `actpred/checkpoints/recorded_run_norm/` | +| 评估报告 JSON | `data/eval_recorded.json` / `data/eval_recorded_norm.json` | +| 批录制脚本 | `data/record_all.sh` | + +相关提交:`af0ff56`(record_stand.mt)、`7ec8c9d`(stats 路径 bug 修复)。 + +## 6. 局限 + +- 帧为合成噪声、IMU 全零:视觉/姿态通路未学习到真实信号,结论仅覆盖动作动力学部分 +- 验证集仅 1 个 episode(41 样本):模型选择噪声大,最优 epoch 有偶然性 +- 两个模型各 13 万步级样本、CPU 训练 2-3 小时:结论在更大数据与实机数据上需复核