Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions docs-self/小车跟踪抓取任务实现设计.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# 小车跟踪抓取任务实现设计

## 1. 任务描述

当前 ACT 尚未实现可用的推理,需要基于现有数据集结构实现正确的 ACT 训练和推理。

任务分为三个阶段:

| 阶段 | 目标 | 状态 |
|------|------|------|
| 基础 | 小车跟踪小球 | 已实现(管线已修通,待充分训练验证) |
| 进阶 | 小车跟踪并停下再抓取小球 | 已实现(规则衔接,待端到端验证) |
| 进阶 | 小车跟踪并抓取小球后跟踪红桶并投放 | 未实现 |

## 2. 现状分析:为什么 ACT 推理不可用

排查发现三个阻塞问题,任意一个都会导致推理输出无意义:

### 2.1 动作格式不匹配(致命)

录制、训练、推理三个环节对"动作"的定义互相矛盾:

```
录制: action = [1, 0, 0, 0, 0] ← 5维 one-hot,按键编码
训练: label = (action[0], action[1]) ← 取前两维作为 (v, w) 标签
推理: output → (v, w) 连续值 ← 期望连续速度
```

实际效果:

```
按 W 前进 → action = [1, 0, 0, 0, 0] → 训练标签 = (1, 0) ← 速度恒为 1,不真实
按 A 左转 → action = [0, 0, 1, 0, 0] → 训练标签 = (0, 0) ← 角速度丢失
停止不动 → action = [0, 0, 0, 0, 1] → 训练标签 = (0, 0)
```

`action[1]` 在 one-hot 中是"后退标志位",不是角速度。模型学到的标签是错的。

### 2.2 特征版本不兼容

早期模型(V1)使用 14 维状态向量,当前版本(V2)使用 16 维。两者不是简单的多/少几个维度,特征语义不同:

```
V1 (14维): index 2 = rotation(原始弧度值,范围 [-π, π])
V2 (16维): index 2 = sin(rotation),index 3 = cos(rotation)(范围 [-1, 1])
```

加载旧模型时如果用新版特征喂入,index 2 的值从弧度变成了 sin 值,语义完全错乱。

### 2.3 反应控制器("老师")缺陷

反应控制器是采集训练数据的"老师",模型通过模仿它来学习。老师本身有三个问题:

| 问题 | 原因 |
|------|------|
| 球在右边时仍向左转一整圈 | `targetTurn = turnSpeed` 写死为正值,不看球的方位 |
| 跟踪速度慢、响应迟钝 | 速度上限和转向增益都偏保守 |
| 到达目标后不停车 | 原逻辑是到达后把球传送走,没有停车动作 |

但注意:推理效果差并非仅仅是"老师"的问题。其他因素同样在影响:
- 低通滤波器(alpha = 0.5)衰减模型输出的峰值
- 时间集成(Temporal Ensembling)对重叠 chunk 取加权平均,进一步平滑
- 训练数据量不足(< 200 帧无法覆盖状态空间)
- 模型容量(浅层 CNN + 2 层 Dense 的表达能力有限)

## 3. 设计方案

### 3.1 修复动作格式:discrete 5-dim → continuous 2-dim

录制时直接记录小车当前的运动学量:

```typescript
const action = [
sim.current.robotState.velocity, // 线速度
sim.current.robotState.angularVelocity // 角速度
];
```

`[velocity, angularVelocity]` 两个分量足以完整描述平面差速驱动的运动。这样录制、训练、推理三个环节对动作的理解完全一致。

### 3.2 多版本特征适配器

不能靠截断或补零来兼容不同版本,必须维护独立的特征映射表。

**设计选择**:版本路由表——根据模型输入维度自动选择对应的特征映射。

```
FeatureFactory // 纯几何计算
├── calculateRelativeTarget() // 距离、角度差、可见性
└── checkOcclusion() // 射线步进遮挡检测

V1_FEATURE_MAP (14维) // [x, z, rotation, speed, targetDist, collision, ...]
V2_FEATURE_MAP (16维) // [x, z, sin(θ), cos(θ), speed, targetX, targetZ, ...]
VERSION_MAP = { 14: V1, 16: V2 } // 路由表

FeatureAdapter.getFeatures(dim, simData) // 入口:按维度路由 → 选映射表 → 组装向量
```

这样做的好处:
- 新增版本只需追加一张映射表 + 一行路由,不碰已有逻辑
- 映射表注释本身就是特征文档
- 数据采集和推理都通过同一入口 `getFeatures()`,消除不一致风险

模型加载时自动检测维度:
```typescript
const stateDim = model.inputs[1].shape[1]; // inputs[0]=图像, inputs[1]=状态
```

### 3.3 修复反应控制器

| 修改 | 改动 |
|------|------|
| 转向方向 | `angleDiff > 0 ? turnSpeed : -turnSpeed`,根据球的实际方位决定 |
| 跟踪速度 | 速度上限 × 2.0,转向增益 1.0 → 1.5,角度大时减速比 0.5 → 0.3 |
| 减速曲线 | `(distance - 1.0) * 0.8`,更陡的线性减速 |

### 3.4 停车 + 机械臂衔接

导航和抓取是两个性质不同的子任务:
- **导航**:连续控制,状态空间大,适合模型学习
- **抓取**:确定性操作,只需在固定距离触发预设动作序列

因此采用混合架构——模型负责导航,规则负责停车和抓取触发:

```
距离 > 1.5 → 模型推理正常控制
1.0 < 距离 < 1.5 → 停车,清空动作缓冲,继续轮询
距离 < 1.0 → 终止推理,若机械臂空闲则自动触发抓取
```

不用训练来学习"何时停车"和"何时抓取":
- 停车是简单的距离阈值判断
- 抓取是确定性状态机
- 规则衔接可以保证可靠性,不受模型精度影响

## 4. 实现说明

### 代码位置

| 文件 | 职责 |
|------|------|
| `src/services/featureService.ts` | 特征工厂 + 版本适配器(新增) |
| `src/services/actService.ts` | 模型定义、训练、推理(未改动) |
| `src/App.tsx` | 数据采集、推理管线、反应控制器 |

### 提交记录

```
5d5e095 feat: add multi-version feature adapter for model dimension compatibility
43d7ee1 fix: switch action recording from discrete 5-dim to continuous 2-dim
8f70fce feat: add model dimension auto-detection and FeatureAdapter in inference
a5ea294 feat: improve reactive controller and add stop-at-target with auto arm grab
```

## 5. 已实现的效果

- 加载 14 维旧模型时自动使用 V1 特征映射,加载 16 维新模型时使用 V2 映射
- 采集的训练数据中动作标签为真实的 `(v, w)` 连续值,与推理一致
- 反应控制器能正确跟踪任意方向的目标,可作为采集高质量训练数据的"老师"
- 小车到达目标后停车,自动衔接机械臂抓取

## 6. 待实现 / 遗留问题

### 6.1 阶段三:跟踪红桶并投放(未实现)

完整管线应为:跟踪小球 → 停车 → 抓取 → 跟踪红桶 → 停车 → 投放。当前只实现了前三步。后续需要:
- 抓取完成后切换跟踪目标为红桶
- 红桶到达后触发投放动作
- 状态机需扩展为多阶段:`navigate_ball → grab → navigate_barrel → drop`

### 6.2 训练数据质量与数量

当前验证用的数据量(约 16 个 episode,每个约 12 帧)远不足以让模型学到有效策略。建议:
- 每个 episode 至少 50-100 帧(5-10 秒连续操作)
- 至少 50 个 episode 以覆盖不同起始位置和目标方位
- 使用修复后的反应控制器自动采集,而非手动操作

### 6.3 低通滤波器过度平滑

推理时的低通滤波 `alpha = 0.5` 会将模型输出衰减约一半:
```typescript
robotState.velocity = robotState.velocity * 0.5 + targetV * 0.5;
```
模型预测 `v = 0.3` 时,实际施加的速度从 0 开始需要多步才能接近 0.3,导致响应滞后。可考虑提高 alpha 到 0.7-0.8。

### 6.4 时间集成权重衰减

当前 `exp(-0.5 * i)` 使第 5 步的权重衰减到 `e^(-2.5) ≈ 0.08`。数据不足时模型输出本身就弱,加权平均会进一步削弱有效信号。

### 6.5 模型容量

当前前端模型(2 层 CNN + 2 层 Dense,约 20 万参数)对端到端视觉控制任务可能偏小。充足数据下仍欠拟合时可考虑增加 CNN 深度或 Dense 宽度,但需权衡浏览器端推理延迟。

### 6.6 状态向量归一化

状态向量各维度量纲差异较大(位置 `[-6, 6]`、角度 `[-1, 1]`、速度 `[0, 0.5]`、布尔 `{0, 1}`),未做归一化可能影响训练稳定性。优先级低于上述问题。

### 6.7 文档同步

`FRONTEND_TRAINING_DOCS.md` 中的数据定义(14 维状态、5 维动作)已过时,需更新为 16 维状态、2 维动作。

## 附录:前期改动记录

以下为本次重构之前的迭代改动,按时间顺序排列。

### A.1 添加小球运动类型和配置

- 新增 `BallMotionMode` 类型(fixed / random / mixed)
- 新增 `BallMotionConfig` 接口,支持配置运动半径、速度、随机强度

### A.2 改进神经网络架构

- 状态输入维度从 14 调整为 12
- 状态处理层从 32 单元增加到 64 单元
- 新增一层 Dense(64) 用于更好的特征提取

### A.3 实现小球运动控制系统

- 新增 `updateBallMotion()` 函数,支持三种运动模式:
- 固定轨迹(圆周运动)
- 随机运动(随机游走)
- 混合运动(圆周 + 随机扰动)
- 新增小球速度计算用于状态表示
- 状态向量扩展为 12 维:机器人位姿(sin/cos 编码)、速度、小球位置和速度、相对角度和距离、碰撞标志
- 录制和推理路径同步更新
- 新增小球运动控制 UI 面板

### A.4 增强球跟踪与反应控制基础

- 状态向量从 12 维扩展到 16 维,新增小球加速度、可见性、遮挡特征
- 新增小球手动控制模式
- 改进 UI 布局,建立动态场景边界系统
- 编写反应控制逻辑技术文档
Loading