处理器简介

在机器人技术中,机器人和人类产生的数据与机器学习模型期望的数据之间存在根本性的不匹配。 机器人输出原始的传感器数据,如相机图像和关节位置,这些数据在模型处理之前需要归一化、批处理和设备放置。 来自人类的语言指令必须被 token 化为数值表示,而不同的机器人使用不同的坐标系,需要标准化。

这一挑战同样延伸到模型输出。 模型可能输出 end-effector 位置,而机器人需要关节空间命令;或者 teleoperator 产生相对运动,而机器人期望绝对命令。 模型预测通常是归一化的,需要转换回真实世界的尺度。

跨域转换又增加了一层复杂性。 来自一种机器人设置的数据需要适应在不同硬件上部署,使用特定相机配置训练的模型必须能与新的布置配合,而具有不同命名约定的 dataset 需要统一。

这正是处理器发挥作用的地方。 它们充当通用翻译器,弥合这些差距,确保数据从传感器到模型再到执行器的无缝流动。 处理器处理所需的所有预处理和后处理步骤,将原始环境数据转换为模型可用的输入,反之亦然。

这意味着你最喜爱的 policy 可以像这样使用:

import torch

from lerobot.datasets import LeRobotDataset
from lerobot.policies import make_pre_post_processors
from lerobot.policies.your_policy import YourPolicy
from lerobot.processor import RobotProcessorPipeline, PolicyProcessorPipeline
dataset = LeRobotDataset("hf_user/dataset", episodes=[0])
sample = dataset[10]

model = YourPolicy.from_pretrained(
    "hf_user/model",
)
model.eval()
model.to("cuda")
preprocessor, postprocessor = make_pre_post_processors(model.config, pretrained_path="hf_user/model", dataset_stats=dataset.meta.stats)

preprocessed_sample = preprocessor(sample)
action = model.select_action(preprocessed_sample)
postprocessed_action = postprocessor(action)

什么是处理器?

在机器人技术中,数据以多种形式出现:来自相机的图像、来自传感器的关节位置、来自用户的文本指令等。每种类型的数据在模型有效使用之前都需要特定的变换。模型需要这些数据:

  • 已归一化:缩放到适合神经网络处理的范围
  • 已批处理:以正确的维度组织以进行批处理
  • 已 token 化:文本转换为数值表示
  • 已放置到设备:移动到正确的硬件(CPU/GPU)
  • 已类型转换:转换为适当的数据类型

处理器通过可组合、可复用的步骤处理这些变换,这些步骤可以链接成流水线。可以将它们视为模块化装配线,每个工位对你的数据执行特定的变换。

核心概念

EnvTransition:通用数据容器

EnvTransition 是流经所有处理器的基本数据结构。 它是一个类型化字典,表示完整的机器人-环境交互:

  • OBSERVATION:所有传感器数据(图像、state、本体感觉)
  • ACTION:要执行或已执行的 action
  • REWARD:强化学习信号
  • DONE/TRUNCATED:episode 边界指示器
  • INFO:任意元数据
  • COMPLEMENTARY_DATA:任务描述、索引、填充标志、步骤间数据

ProcessorStep:构建块

ProcessorStep 是处理转换的单个变换单元。它是一个抽象基类,有两个必需的方法:

from lerobot.processor import ProcessorStep, EnvTransition

class MyProcessorStep(ProcessorStep):
    """Example processor step - inherit and implement abstract methods."""

    def __call__(self, transition: EnvTransition) -> EnvTransition:
        """Transform the transition - REQUIRED abstract method."""
        # Your processing logic here
        return transition

    def transform_features(self, features):
        """Declare how this step transforms feature shapes/types - REQUIRED abstract method."""
        return features  # Most processors return features unchanged

__call__ 是你的处理器步骤的核心。它接收一个 EnvTransition 并返回修改后的 EnvTransition

transform_features 用于声明此步骤如何变换特征形状/类型。

DataProcessorPipeline:通用编排器

DataProcessorPipeline[TInput, TOutput] 将多个 ProcessorStep 实例链接在一起:

from lerobot.processor import RobotProcessorPipeline, PolicyProcessorPipeline

# For robot hardware (unbatched data)
robot_processor = RobotProcessorPipeline[RobotAction, RobotAction](
    steps=[step1, step2, step3],
    name="robot_pipeline"
)

# For model training/inference (batched data)
policy_processor = PolicyProcessorPipeline[dict[str, Any], dict[str, Any]](
    steps=[step1, step2, step3],
    name="policy_pipeline"
)

RobotProcessorPipeline 与 PolicyProcessorPipeline 对比

关键区别在于它们处理的数据结构:

方面RobotProcessorPipelinePolicyProcessorPipeline
输入dict[str, Any] - 单个机器人值dict[str, Any] - 批处理张量
输出dict[str, Any] - 单个机器人命令torch.Tensor - policy 预测
用例实时机器人控制模型训练/inference
数据格式非批处理、异构批处理、同构
示例{"joint_1": 0.5}{"observation.state": tensor([[0.5]])}

对机器人硬件接口使用 RobotProcessorPipeline

# Robot data structures: dict[str, Any] for observations and actions
robot_obs: dict[str, Any] = {
    "joint_1": 0.5,           # Individual joint values
    "joint_2": -0.3,
    "camera_0": image_array   # Raw camera data
}

robot_action: dict[str, Any] = {
    "joint_1": 0.2,          # Target joint positions
    "joint_2": 0.1,
    "gripper": 0.8
}

对模型训练和批处理使用 PolicyProcessorPipeline

# Policy data structures: batch dicts and tensors
policy_batch: dict[str, Any] = {
    "observation.state": torch.tensor([[0.5, -0.3]]),      # Batched states
    "observation.images.camera0": torch.tensor(...),        # Batched images
    "action": torch.tensor([[0.2, 0.1, 0.8]])              # Batched actions
}

policy_action: torch.Tensor = torch.tensor([[0.2, 0.1, 0.8]])  # Model output tensor

转换器函数

LeRobot 提供转换器函数,用于在 lerobot.processor.converters 中桥接不同的数据格式。这些函数处理机器人硬件数据结构、policy 模型格式以及流经处理器流水线的内部 EnvTransition 表示之间的关键转换。

类别函数描述
机器人硬件转换器robot_action_to_transition机器人字典 → EnvTransition
observation_to_transition机器人 observation → EnvTransition
transition_to_robot_actionEnvTransition → 机器人字典
policy/训练转换器batch_to_transition批次字典 → EnvTransition
transition_to_batchEnvTransition → 批次字典
policy_action_to_transitionpolicy 张量 → EnvTransition
transition_to_policy_actionEnvTransition → policy 张量
工具函数create_transition使用默认值构建转换
identity_transition直通转换器

关键洞察在于 机器人硬件转换器 处理单个值和字典,而 policy/训练转换器 处理批处理张量和模型输出。转换器函数自动处理结构差异,因此你的处理器步骤可以专注于核心变换,而无需担心数据格式兼容性。

处理器示例

以下示例演示了用于 policy 训练和 inference 的真实世界处理器配置。

以下是一个用于 policy 训练和 inference 的处理器示例:

# Training data preprocessing (optimized order for GPU performance)
training_preprocessor = PolicyProcessorPipeline[dict[str, Any], dict[str, Any]](
    steps=[
        RenameObservationsProcessorStep(rename_map={}),     # Standardize keys
        AddBatchDimensionProcessorStep(),                   # Add batch dims
        TokenizerProcessorStep(tokenizer_name="...", ...),  # Tokenize language
        DeviceProcessorStep(device="cuda"),                 # Move to GPU first
        NormalizerProcessorStep(features=..., stats=...),   # Normalize on GPU
    ]
)

# Model output postprocessing
training_postprocessor = PolicyProcessorPipeline[torch.Tensor, torch.Tensor](
    steps=[
        DeviceProcessorStep(device="cpu"),                  # Move to CPU
        UnnormalizerProcessorStep(features=..., stats=...), # Denormalize
    ]
    to_transition=policy_action_to_transition,
    to_output=transition_to_policy_action,
)

机器人与 policy 通过处理器的交互

最常见的真实世界场景结合了两种流水线类型:机器人硬件生成需要 policy 处理的 observation,而 policy 输出需要与机器人兼容的后处理:

# Real deployment: Robot sensors → Model → Robot commands
with torch.no_grad():
    while not done:
        raw_obs = robot.get_observation()  # dict[str, Any]

        # Add your robot observation to policy observation processor

        policy_input = policy_preprocessor(raw_obs)  # Batched dict

        policy_output = policy.select_action(policy_input)  # Policy tensor

        policy_action = policy_postprocessor(policy_output)

        # Add your robot action to policy action processor

        robot.send_action(policy_action)

特征契约:形状和类型变换

处理器不仅变换数据——它们还可以 改变数据结构本身transform_features() 方法声明这些变化,这对于 dataset 录制和 policy 创建至关重要。

为什么特征契约很重要

在构建 dataset 或 policy 时,LeRobot 需要知道:

  • 处理后将存在哪些数据字段
  • 每个字段将具有什么形状和类型
  • 如何为预期的数据结构配置模型
# Example: A processor that adds velocity to observations
class VelocityProcessor(ObservationProcessorStep):
    def observation(self, obs):
        new_obs = obs.copy()
        if "observation.state" in obs:
            # concatenate computed velocity field to the state
            new_obs["observation.state"] = self._compute_velocity(obs["observation.state"])
        return new_obs

    def transform_features(self, features):
        """Declare the new velocity field we're adding."""
        state_feature = features[PipelineFeatureType.OBSERVATION].get("observation.state")
        if state_feature:
            double_shape = (state_feature.shape[0] * 2,) if state_feature.shape else (2,)
            features[PipelineFeatureType.OBSERVATION]["observation.state"] = PolicyFeature(
                type=FeatureType.STATE, shape=double_shape
            )
        return features

特征规格函数

create_initial_features()aggregate_pipeline_dataset_features() 解决了一个关键的 dataset 创建问题:在任何数据处理之前确定确切的最终数据结构。 由于处理器流水线可以添加新特征(如速度字段)、改变张量形状(如图像裁剪)或重命名键,dataset 需要预先知道完整的输出规格,以分配适当的存储并定义模式。 这些函数协同工作:从机器人硬件规格(create_initial_features())开始,然后模拟整个流水线变换(aggregate_pipeline_dataset_features())以计算最终传递给 LeRobotDataset.create() 的特征字典,确保处理器输出与 dataset 期望存储的内容完美对齐。

from lerobot.datasets import aggregate_pipeline_dataset_features

# Start with robot's raw features
initial_features = create_initial_features(
    observation=robot.observation_features,  # {"joint_1.pos": float, "camera_0": (480,640,3)}
    action=robot.action_features            # {"joint_1.pos": float, "gripper.pos": float}
)

# Apply processor pipeline to compute final features
final_features = aggregate_pipeline_dataset_features(
    pipeline=my_processor_pipeline,
    initial_features=initial_features,
    use_videos=True
)

# Use for dataset creation
dataset = LeRobotDataset.create(
    repo_id="my_dataset",
    features=final_features,  # Knows exactly what data to expect
    ...
)

常见处理器步骤

LeRobot 提供了许多已注册的处理器步骤。以下是最常用的核心处理器:

必备处理器

  • normalizer_processor:使用 dataset 统计信息(均值/标准差或最小/最大值)归一化 observation/action
  • device_processor:将张量移动到 CPU/GPU,并可选择转换数据类型
  • to_batch_processor:为转换添加批次维度以兼容模型
  • rename_observations_processor:使用映射字典重命名 observation 键
  • tokenizer_processor:将自然语言任务描述 token 化为 token 和注意力掩码

后续步骤

总结

处理器通过提供以下内容解决了机器人技术中的数据转换问题:

  • 模块化变换:可组合、可复用的处理步骤
  • 类型安全:具有编译时检查的通用流水线
  • 性能优化:GPU 加速的操作
  • 机器人/policy 区分:针对不同数据结构的独立流水线
  • 全面生态:30+ 个针对常见任务的已注册处理器

关键洞察:RobotProcessorPipeline 处理非批处理的机器人硬件数据,而 PolicyProcessorPipeline 处理批处理的模型数据。为你的数据结构选择正确的工具!

在 GitHub 上更新