# HairDry-v0:SmolVLA 仿真吹头发任务 · 完美验收报告(2026-11-04 凌晨)

> 目标:在 MuJoCo 仿真里让 SmolVLA 学会"吹头发"全流程,并按严格判据验收。
> 结论先看 §4。所有产物在 `~/Documents/home_robot/hairdry/`。

## 0. 一句话结论

**四步全部完成。** 迁移版 v1(MetaWorld MT50 预训练 → hairdry 微调 3000 步)闭环 **20/20 全部吹干,
19/20 全程零违规(完美率 95%)**;从零版 v0 达 95%/60%。失败/违规集中在"快速吹干时贴脸过近"。
行为人眼复看通过:环绕扫掠、分区变浅、收工抬高位,与专家编排一致。

## 1. 任务设计(对应 11-04 凌晨计划)

- **干度动力学脚本化,不做发丝物理**(绕开 genesis_pilot G3 的"MPM 头发不可实时"死路):
  6 发区(top/bangs/left/right/back/nape)各带湿度 w∈[0,1],气流命中时按
  `cos²(锥角) × 距离衰减` 降湿;**气流被颅骨遮挡**(吹后脑必须绕到侧后方)。
- **干度可从像素读出**:发区色块 lerp(湿=深黑棕 0.16,0.10,0.06 → 干=浅棕 0.66,0.47,0.28),
  MuJoCo `geom_rgba` 每步更新,480×480 相机肉眼可辨。
- **风嘴 = mocap body,理发师式环绕**:水平朝向自动指向头部竖直轴(yaw 由位置决定),
  俯仰由动作控制 → 动作 = [dx,dy,dz,d_pitch] ∈ [-1,1]⁴,与 MT50 sawyer **4D 同构**(为 v1 迁移铺路)。
- **安全规则**:嘴尖-眼 <10cm = 违规;眼睛高度以下平射/上仰扫到眼睛(锥角 32°)= 违规;嘴尖-头心 <12cm = 碰撞违规。
- **success** = 全区 w<0.10 且 300 步内;**perfect** = success 且全程零违规。
- 随机化:头偏航 ±12°/位置 ±2.5cm、初始湿度(每区 75% 全湿 / 25% 部分湿)、风嘴起始位姿(抬到眼睛上方,避免开局怼脸)。

## 2. 数据与训练

| 项 | 值 |
|---|---|
| 演示 | 特权专家(上帝视角湿度+动作级安全过滤器)240 集,平均 88 步,**成功率 100%,总违规 0** |
| 指令 | 4 选 1(3 英文同义改写 + 1 中文"用吹风机把头发完全吹干") |
| 数据格式 | LeRobotDataset v3,`hairdry/hairdry_v0`,480×480 video + 4D state + 4D action,21191 帧 |
| v0(从零) | SmolVLA 450M,batch 16,MPS,**6000 步,loss 1.95→0.49,3h17m** |
| v1(迁移) | 从 `ckpt/smolvla_metaworld_paper`(MT50 49 任务)微调,冻结视觉塔只训 expert,**3000 步,loss 2.72→0.52,1h43m** |
| 评测集 | seeds 20000-20019,与训练演示(1000-1239)**完全不相交** |

专家路由踩坑记录:极半径限位穿轴瞬移、tip 比中心凸 3.5cm 导致贴眼、收工直线冲刺平射眼睛、
转移弦线斜穿脸前——最终形态 = **三段式路由(径向退出 0.30m → 弧线绕行 → 径向切入悬停点)**
+ 脸面 ±60° 航点强制抬到眼睛上方 + 动作级安全过滤器(预测违规则缩短步长)。

## 3. 训练环境的两个硬坑(16GB mini 通用教训)

1. **lerobot 0.6.1 数据集默认 libsvtav1 编码**;v3 格式把 240 集打包成 2 个大视频文件,
   训练时随机 seek 取帧。若用 ffmpeg 重转码,**必须 `-g 2`**(lerobot 默认 GOP=2);
   x264 默认 GOP≈250 时每步要解码几百帧,实测 60s/步。
2. **batch 32 + num_workers 4 在 16GB mini 上 swap 死亡**(8GB swap 打满,60s/步且持续恶化);
   安全配置 = batch 16 + workers 2 + MPS = 1.97s/步 稳定。
   另外:`--policy.path`(加载 ckpt 微调)不能与 `--policy.type` 同用;paper ckpt 的
   `freeze_vision_encoder=false` 全模型训练配置会 MPS OOM(18.6GiB/20.1GiB),微调须改
   冻结视觉塔 + 只训 expert + pad_language_to=longest(副本 `ckpt/hairdry_v1_init`)。

## 4. 闭环验收(20 集 holdout)

| 模型 | 成功率 | 完美率 | 总违规 | 吹干步数 |
|---|---|---|---|---|
| v0(从零 6000 步) | 95% (19/20) | 60% | 98 | 92-231 |
| **v1(MT50 迁移 3000 步)** | **100% (20/20)** | **95% (19/20)** | **11** | **83-232** |

- 评测脚本修复过一个真 bug:**episode 间必须 `policy.reset()`**,否则 lerobot 的动作 chunk
  队列跨集泄漏(上一集没收完的队列会驱动下一集开头)——修复前 v1 显示 95%/75% 是被污染的数字,
  同样问题存在于 11-03 的 MetaWorld 评测脚本(`smolvla_metaworld_eval.py`)。
- v1 唯一非完美集(ep16):98 步极速吹干,11 步贴眼违规——"快而毛糙"型。
- 失败模式分析(修复前):两版模型的失败集都是 **bangs 未干**(残留 0.32/0.43),其余区全 0。
- v0b(从 v0 再续 2000 步)/ v1b(从 v1 再续 1500 步)结果见 §6 追加。

## 5. 人眼复看(硬门槛)

`out/v1_montage.png`(4 集 × 6 帧抽样):策略从高位起步 → 正面/侧面/脑后环绕扫掠 →
发区色块由深黑变浅棕 → 收工风嘴抬到脑后高位。无怼脸、无穿透。 PASS。

## 6. 追加实验

| 模型 | 成功率 | 完美率 | 总违规 | 备注 |
|---|---|---|---|---|
| v0(6000 步) | 95% | 60% | 98 | 失败集 bangs 未干 |
| v0b(+2000 步续训) | 100% | 85% | 12 | 续训显著收敛 |
| **v1(迁移 3000 步)** | **100%** | **95%** | **11** | **冠军** |
| v1b(+1500 步续训) | 100% | 60% | 109 | **过训变毛糙**:更快但更莽,弃用 |
| v1 × 50 新 seed 确认 | **100%** | 84% | 123 | 与 20 集批合计:70/70 全干,完美 61/70 ≈ 87% |

**冠军 = v1**(ckpt/hairdry_smolvla_v1/checkpoints/003000):在 70 个与训练完全不相交的 seed 上
**成功率 100%,完美率 ≈87-95%,平均违规 ~1.9 步/集**。残余不完美 = 快速转移时偶发贴眼,
对应真机方案里本来就规划的硬编码安全层(距离传感/急停),策略负责语义层决策的分工是成立的。

教训:BC 续训不是单调改善——v1b 过训后"速度换安全",验收必须按集监控完美率而不是只看 loss。

## 7. 资产清单

- `hairdry/hairdry_env.py` 环境 | `expert.py` 特权专家+安全过滤器 | `gen_demos.py` 数据生成
- `hairdry_eval.py` 闭环评测(带 reset 修复) | `train_hairdry.sh` / `train_v0b.sh` 训练脚本
- `hairdry/data/` 演示数据集 | `ckpt/hairdry_smolvla_v0` / `_v1` / `_v0b` / `_v1b` 权重
- `hairdry/out/` 评测 JSON + 逐集 mp4 + 蒙太奇 | `test_render.py` 渲染验证
