# 动作表示

本指南介绍了在 LeRobot 中表示机器人动作的不同方式、它们之间的关系，以及何时应使用每一种方式。

## 关节空间与末端执行器空间

在讨论动作表示之前，了解动作可以存在的两个坐标空间会很有帮助。

### 联合空间

关节空间动作直接指定每个电机的目标位置。对于一个带有夹爪的 6 自由度机械臂，其关节空间动作可能如下所示：

```
action = [shoulder_pan: 45.0, shoulder_lift: -20.0, elbow: -30.0, wrist_pitch: 10.0, wrist_roll: 0.0, wrist_yaw: 5.0, gripper: 0.8]
```

Joint space是 LeRobot 的默认设置。它结构简单，无需运动学模型，并且可以直接映射到电机指令。大多数入门级配置（SO-100、Koch）都使用关节空间动作。

### 末端执行器（EE）空间

末端执行器空间动作指定了机器人工具尖端（夹爪）在笛卡尔坐标系中的期望位置和方向：

```
action = [x: 0.25, y: -0.10, z: 0.15, wx: 0.0, wy: 0.0, wz: 0.1, gripper: 0.8]
```

对于拾取和放置等任务，EE 空间更直观，因为它直接描述了夹爪应前往的位置，但它需要一个运动学模型（URDF）来在 EE 位姿和关节角度之间进行转换。

### 在空间之间进行转换

LeRobot 提供了处理器步骤，用于通过正运动学和逆运动学在关节空间和末端执行器空间之间进行转换。这些步骤基于 `RobotKinematics` 构建，该组件会加载您机器人的 URDF 模型。

```python
from lerobot.model.kinematics import RobotKinematics
from lerobot.robots.so_follower.robot_kinematic_processor import (
    ForwardKinematicsJointsToEE,
    InverseKinematicsEEToJoints,
)

kinematics = RobotKinematics(
    urdf_path="./SO101/so101_new_calib.urdf",
    target_frame_name="gripper_frame_link",
    joint_names=["shoulder", "elbow", "wrist_pitch", "wrist_roll", "wrist_yaw"],
)

# Joints → EE (for observations: "where is my gripper?")
fk_step = ForwardKinematicsJointsToEE(kinematics=kinematics, motor_names=[...])

# EE → Joints (for actions: "move my gripper here")
ik_step = InverseKinematicsEEToJoints(kinematics=kinematics, motor_names=[...])
```

请参阅 [`examples/so100_to_so100_EE/`](https://github.com/huggingface/lerobot/tree/main/examples/so100_to_so100_EE)，其中包含一个在 SO-100 机械臂上使用 EE 空间动作进行录制、回放和评估的完整可运行示例。

## 绝对、相对和增量动作

无论您在关节空间还是末端执行器空间中工作，动作值都可以用三种不同的方式表示。术语遵循[UMI (Chi et al., 2024)](https://arxiv.org/abs/2402.10329)。

### 绝对动作（LeRobot 默认）

每个操作直接指定目标位置。

**示例**（联合空间，块大小为 4）：

```
current_state = [45.0, -30.0, 10.0]

action_chunk = [
    [46.0, -29.0, 11.0],   # go to 46, -29, 11
    [47.5, -27.0, 12.0],   # go to 47.5, -27, 12
    [49.0, -25.0, 13.5],   # go to 49, -25, 13.5
    [50.0, -24.0, 15.0],   # go to 50, -24, 15
]
```

每个值都是机器人在其自身坐标系中的目标位置。这种方式简单直接，但需要一个一致的全局坐标系。这是 LeRobot 的默认设置。

### 相对动作（由 OpenPI / pi0 使用）

块中的每个动作都是相对于**预测时刻的当前状态**的偏移量。块中的所有动作共享同一个参考点：

```
current_state = [45.0, -30.0, 10.0]

relative_chunk = [
    [1.0,  1.0, 1.0],   # +1 from current → target 46, -29, 11
    [2.5,  3.0, 2.0],   # +2.5 from current → target 47.5, -27, 12
    [4.0,  5.0, 3.5],   # +4 from current → target 49, -25, 13.5
    [5.0,  6.0, 5.0],   # +5 from current → target 50, -24, 15
]
```

转换过程很简单：`relative = absolute - current_state`。要恢复绝对值：`absolute = relative + current_state`。

**为什么要使用相对动作？** 模型学习预测以零为中心的偏移量，这更容易进行归一化，并能带来更稳定的训练。由于每个块都引用相同的当前状态，因此块与块之间不会出现误差累积。

### Delta 操作（顺序差异）

每个动作都是相对于**上一个动作**的偏移量（对于第一步，则是相对于当前状态）：

```
current_state = [45.0, -30.0, 10.0]

delta_chunk = [
    [1.0,  1.0, 1.0],   # current → 46, -29, 11
    [1.5,  2.0, 1.0],   # previous action → 47.5, -27, 12
    [1.5,  2.0, 1.5],   # previous action → 49, -25, 13.5
    [1.0,  1.0, 1.5],   # previous action → 50, -24, 15
]
```

在此，每一步都相对于前一步。要恢复绝对位置，必须累加所有之前的增量，这意味着误差会随时间累积。UMI 正是基于这一原因，明确反对这种表示方法。

### 视觉对比

下图（基于 [UMI, Chi et al., 2024](https://arxiv.org/abs/2402.10329) 中的图）展示了关键区别。采用 **相对轨迹** 时，块中的每个动作都指向同一个原点（当前状态），因此新的推理步骤可以干净地重置参考系。采用 **增量** 时，每个动作都依赖于前一个动作，因此误差会累积。**绝对** 动作需要一致的全局坐标系。

<img
  src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/action_representations_umi.png"
  alt="Relative Trajectory as Action Representation (UMI, Chi et al., 2024)"
  width="85%"
/>

## 在 LeRobot 中使用相对动作

LeRobot 提供了 `RelativeActionsProcessorStep`，用于在处理器流水线内部将绝对动作转换为相对动作。pi0、pi0.5 和 pi0_fast 正是通过这种方式来支持相对动作的。

> **注意：** 所有 pi 模型（pi0、pi0.5、pi0*fast）都在归一化（`relative → normalize`）\_之前*应用相对转换，因此归一化器始终接收 delta（相对）值。这意味着在使用 `use_relative_actions=true` 进行训练时，它们都需要**相对动作统计信息**。在 pi0_fast 中，`RelativeActionsProcessorStep` 仅修改动作——状态观测保持不变——因此 `NormalizerProcessorStep` 仍在状态分词器之前运行，分词器继续按预期接收归一化后的状态。

### 工作原理

在**训练**（预处理）期间，动作在模型看到之前会从绝对值转换为相对值：

```
raw absolute action → RelativeActionsProcessorStep → normalize → model
```

在**推理**（后处理）阶段，模型的预测值会被转换回绝对值，然后再发送给机器人：

```
model output → unnormalize → AbsoluteActionsProcessorStep → robot
```

`AbsoluteActionsProcessorStep` 会从配对的 `RelativeActionsProcessorStep` 中读取缓存的当前状态，因此这两者必须连接在一起（由策略工厂自动处理）。

### 为 pi 系列（pi0、pi0.5、pi0_fast）启用相对动作

**步骤 1**：为数据集预计算相对动作统计量：

```bash
lerobot-edit-dataset \
    --repo_id your_dataset \
    --new_repo_id your_dataset \
    --operation.type recompute_stats \
    --operation.overwrite true \
    --operation.relative_action true \
    --operation.chunk_size 50 \
    --operation.relative_exclude_joints "['gripper']"
```

**步骤 2**：启用相对动作进行训练：

```bash
lerobot-train \
    --dataset.repo_id=your_dataset \
    --policy.type=pi0 \
    --policy.use_relative_actions=true \
    --policy.relative_exclude_joints='["gripper"]'
```

`relative_exclude_joints` 参数用于指定应保持在绝对空间中的关节。例如，夹爪指令通常是二值的（开/关），因此从相对编码中获益不大。

对于定义了 `action_feature_names` 的策略配置，`make_policy` 会从数据集动作特征的 `names` 元数据中填充此列表。Names 可以是扁平序列，也可以是值均为列表或元组的字典。例如，`{"arm": ["shoulder", "elbow"], "hand": ["gripper"]}` 会变成 `["shoulder", "elbow", "gripper"]`，而不是分组键。分组的插入顺序以及组内顺序均会保留，因此请按与动作向量相同的顺序声明名称。旧式的名称到索引的映射保留其键顺序行为。

### 将相对动作与 RTC 结合

[RTC](https://arxiv.org/abs/2506.07339) 以高频执行策略推理，并在预测出动作后立即将其发送给机器人，而不是等待完整的动作块。相对动作与 RTC 完全兼容：因为在相对模式下，每个动作块都引用**相同**的当前状态（在推理开始时捕获），所以即使机器人已经移动，该块中预测的每个动作仍然是有效的偏移量。无需特殊处理——`RelativeActionsProcessorStep` 在每次推理调用时缓存一次状态，而 `AbsoluteActionsProcessorStep` 会将其应用于流式输出中的每个动作。

### 将相对动作与 EE 空间结合

相对动作在关节空间和末端执行器空间中均有效。例如，如果您的数据集存储的是末端执行器动作，相对编码会将其转换为相对于当前末端执行器位姿的偏移量：

```
current_ee_state = [x: 0.25, y: -0.10, z: 0.15, gripper: 0.8]

absolute_ee_chunk = [
    [0.26, -0.09, 0.16, 0.8],
    [0.28, -0.07, 0.18, 0.8],
]

relative_ee_chunk = [
    [0.01,  0.01, 0.01, 0.0],   # offset from current EE pose
    [0.03,  0.03, 0.03, 0.0],   # offset from current EE pose
]
```

## 处理流水线摘要

以下是不同处理器的组合方式。每个箭头代表一个处理器步骤，它们可以在 `RobotProcessorPipeline` 或 `PolicyProcessorPipeline` 中串联使用：

```
                    ┌─────────────────────────────────────────┐
   Action Space     │   Joint Space  ←──IK──→  EE Space      │
                    │   ForwardKinematicsJointsToEE           │
                    │   InverseKinematicsEEToJoints           │
                    └─────────────────────────────────────────┘

                    ┌─────────────────────────────────────────┐
   Representation   │   Absolute  ←────→  Relative            │
                    │   RelativeActionsProcessorStep (pre)    │
                    │   AbsoluteActionsProcessorStep (post)   │
                    └─────────────────────────────────────────┘

                    ┌─────────────────────────────────────────┐
   Normalization    │   Raw  ←────→  Normalized               │
                    │   NormalizerProcessorStep (pre)         │
                    │   UnnormalizerProcessorStep (post)      │
                    └─────────────────────────────────────────┘
```

典型的训练预处理器可能会串联执行：`raw absolute joint actions → relative → normalize`。典型的推理后处理器：`unnormalize → absolute → (optionally IK to joints)`。

## 参考

- [通用操作接口（UMI）](https://arxiv.org/abs/2402.10329) - Chi 等人，2024 年。定义了相对轨迹动作表示，并将其与绝对动作和增量动作进行了比较。
- [处理器简介](./introduction_processors) - 处理器流水线在 LeRobot 中的工作原理。
- [`examples/so100_to_so100_EE/`](https://github.com/huggingface/lerobot/tree/main/examples/so100_to_so100_EE) - 使用 EE 空间动作进行录制和评估的完整示例。

