在本教程中,你将使用 LeRobot 体验完整的 Human-in-the-Loop Sample-Efficient Reinforcement Learning(HIL-SERL)工作流。你将掌握在几小时内即可在真实机器人上使用强化学习训练 policy 的技巧。
HIL-SERL 是一种样本高效的强化学习算法,它将人类 demonstration 与在线学习和人工干预相结合。该方法从少量人类 demonstration 开始,用它们训练一个奖励分类器,然后采用 actor-learner 架构,人类可以在 policy 执行过程中进行干预,以引导探索并纠正不安全的行为。在本教程中,你将使用游戏手柄来提供干预并在学习过程中控制机器人。
它结合了三个关键要素:
离线 demonstration 与奖励分类器: 少量人工 teleoperation episode 加上基于视觉的成功检测器,为 policy 提供一个塑形的起点。
真机上的 actor / learner 循环与人工干预: 分布式 Soft Actor Critic(SAC)学习器更新 policy,同时 actor 在实体机器人上探索;人类可以随时介入,纠正危险或无效的行为。
安全与效率工具: 关节/end-effector(EE)边界、裁剪感兴趣区域(ROI)预处理以及 WandB 监控,可保证数据有效且硬件安全。
这些要素共同使 HIL-SERL 能够达到近乎完美的任务成功率,并且比仅靠模仿学习的基线方法拥有更快的周期时间。

HIL-SERL 工作流,Luo 等人,2024
本指南提供了使用 LeRobot 的 HilSerl 实现在真实机器人上训练机器人 policy 的分步说明。
lerobot/model/kinematics.py)你可以使用 HIL-SERL 训练各种各样的操作任务。一些建议:
要安装带 HIL-SERL 的 LeRobot,你需要安装 hilserl 额外扩展。
pip install -e ".[hilserl]"训练过程始于为 HIL-SERL 环境进行正确的配置。主要的配置类是 lerobot/rl/gym_manipulator.py 中的 GymManipulatorConfig,它包含嵌套的 HILSerlRobotEnvConfig(定义于 lerobot/envs/configs.py)和 DatasetConfig。配置被组织为聚焦的、嵌套的子配置:
class GymManipulatorConfig:
env: HILSerlRobotEnvConfig # Environment configuration (nested)
dataset: DatasetConfig # Dataset recording/replay configuration (nested)
mode: str | None = None # "record", "replay", or None (for training)
device: str = "cpu" # Compute device
class HILSerlRobotEnvConfig(EnvConfig):
robot: RobotConfig | None = None # Main robot agent (defined in `lerobot/robots`)
teleop: TeleoperatorConfig | None = None # Teleoperator agent, e.g., gamepad or leader arm
processor: HILSerlProcessorConfig # Processing pipeline configuration (nested)
name: str = "real_robot" # Environment name
task: str | None = None # Task identifier
fps: int = 10 # Control frequency
# Nested processor configuration
class HILSerlProcessorConfig:
control_mode: str = "gamepad" # Control mode
observation: ObservationConfig | None = None # Observation processing settings
image_preprocessing: ImagePreprocessingConfig | None = None # Image crop/resize settings
gripper: GripperConfig | None = None # Gripper control and penalty settings
reset: ResetConfig | None = None # Environment reset and timing settings
inverse_kinematics: InverseKinematicsConfig | None = None # IK processing settings
reward_classifier: RewardClassifierConfig | None = None # Reward classifier settings
max_gripper_pos: float | None = 100.0 # Maximum gripper position
# Sub-configuration classes
class ObservationConfig:
add_joint_velocity_to_observation: bool = False # Add joint velocities to state
add_current_to_observation: bool = False # Add motor currents to state
add_ee_pose_to_observation: bool = False # Add end-effector pose to state
display_cameras: bool = False # Display camera feeds during execution
class ImagePreprocessingConfig:
crop_params_dict: dict[str, tuple[int, int, int, int]] | None = None # Image cropping parameters
resize_size: tuple[int, int] | None = None # Target image size
class GripperConfig:
use_gripper: bool = True # Enable gripper control
gripper_penalty: float = 0.0 # Penalty for inappropriate gripper usage
class ResetConfig:
fixed_reset_joint_positions: list[float] | None = None # Joint positions for reset
reset_time_s: float = 5.0 # Time to wait during reset
control_time_s: float = 20.0 # Maximum episode duration
terminate_on_success: bool = True # Whether to terminate episodes on success detection
class InverseKinematicsConfig:
urdf_path: str | None = None # Path to robot URDF file
target_frame_name: str | None = None # End-effector frame name
end_effector_bounds: dict[str, list[float]] | None = None # EE workspace bounds
end_effector_step_sizes: dict[str, float] | None = None # EE step sizes per axis
class RewardClassifierConfig:
pretrained_path: str | None = None # Path to pretrained reward classifier
success_threshold: float = 0.5 # Success detection threshold
success_reward: float = 1.0 # Reward value for successful episodes
# Dataset configuration
class DatasetConfig:
repo_id: str # LeRobot dataset repository ID
task: str # Task identifier
root: str | None = None # Local dataset root directory
num_episodes_to_record: int = 5 # Number of episodes for recording
replay_episode: int | None = None # Episode index for replay
push_to_hub: bool = False # Whether to push datasets to HubHIL-SERL 采用模块化的处理器流水线架构,通过一系列可组合的步骤处理机器人的 observation 和 action。流水线分为两个主要部分:
环境处理器(env_processor)负责处理传入的 observation 和环境 state:
action 处理器(action_processor)负责处理输出的 action 和人工干预:
基本 observation 处理:
{
"env": {
"processor": {
"observation": {
"add_joint_velocity_to_observation": true,
"add_current_to_observation": false,
"display_cameras": false
}
}
}
}图像处理:
{
"env": {
"processor": {
"image_preprocessing": {
"crop_params_dict": {
"observation.images.front": [180, 250, 120, 150],
"observation.images.side": [180, 207, 180, 200]
},
"resize_size": [128, 128]
}
}
}
}逆运动学设置:
{
"env": {
"processor": {
"inverse_kinematics": {
"urdf_path": "path/to/robot.urdf",
"target_frame_name": "end_effector",
"end_effector_bounds": {
"min": [0.16, -0.08, 0.03],
"max": [0.24, 0.2, 0.1]
},
"end_effector_step_sizes": {
"x": 0.02,
"y": 0.02,
"z": 0.02
}
}
}
}
}HIL-SERL 框架支持可以改进 policy learning 的附加 observation 处理功能:
启用关节速度估计,为 policy 提供运动信息:
{
"env": {
"processor": {
"observation": {
"add_joint_velocity_to_observation": true
}
}
}
}该处理器:
监测电机电流以检测接触力和负载状况:
{
"env": {
"processor": {
"observation": {
"add_current_to_observation": true
}
}
}
}该处理器:
你可以同时启用多个 observation 处理功能:
{
"env": {
"processor": {
"observation": {
"add_joint_velocity_to_observation": true,
"add_current_to_observation": true,
"display_cameras": false
}
}
}
}注意:启用额外的 observation 功能会增加 state space 的维数,这可能需要调整你的 policy network 架构,并可能需要收集更多的训练数据。
在收集 demonstration 之前,你需要确定机器人的适当操作边界。
这通过两种方式简化了在真实机器人上学习的问题:1)将机器人的操作空间限制在能够解决任务并避免不必要或不安全探索的特定区域内;2)允许在 end-effector 空间而非关节空间中进行训练。经验表明,在操作任务中,使用强化学习在关节空间中学习通常是更困难的问题——有些任务在关节空间中几乎无法学习,但当 action space 转换为 end-effector 坐标后就变得可学习了。
使用 lerobot-find-joint-limits
此脚本帮助你找到机器人 end-effector 的安全操作边界。既然你拥有 follower arm 和 leader arm,你可以使用该脚本找出训练期间将应用到 follower arm 的边界。 限制 action space 将减少智能体的冗余探索,并保证安全性。
lerobot-find-joint-limits \ --robot.type=so100_follower \ --robot.port=/dev/tty.usbmodem58760431541 \ --robot.id=black \ --teleop.type=so100_leader \ --teleop.port=/dev/tty.usbmodem58760431551 \ --teleop.id=blue
工作流
Max ee position [0.2417 0.2012 0.1027]
Min ee position [0.1663 -0.0823 0.0336]
Max joint positions [-20.0, -20.0, -20.0, -20.0, -20.0, -20.0]
Min joint positions [50.0, 50.0, 50.0, 50.0, 50.0, 50.0]env.processor.inverse_kinematics.end_effector_bounds 下(参见 lerobot/envs/configs.py 中的 InverseKinematicsConfig)示例配置
{
"env": {
"processor": {
"inverse_kinematics": {
"end_effector_bounds": {
"max": [0.24, 0.2, 0.1],
"min": [0.16, -0.08, 0.03]
}
}
}
}
}定义好边界后,你就可以安全地收集用于训练的 demonstration。使用离 policy 算法训练强化学习,使我们能够利用离线收集的 dataset 来提高学习过程的效率。
设置录制模式
创建一个用于录制 demonstration 的配置文件(或编辑已有文件,如 env_config.json):
mode 设置为 "record"dataset 部分为你的 dataset 指定一个唯一的 repo_id(例如 “username/task_name”)dataset 部分将 num_episodes_to_record 设置为你要收集的 demonstration 数量env.processor.image_preprocessing.crop_params_dict 设置为 {}(我们稍后会确定裁剪参数)env 部分配置 env.robot、env.teleop 和其他硬件设置示例配置部分:
{
"env": {
"type": "gym_manipulator",
"name": "real_robot",
"fps": 10,
"processor": {
"control_mode": "gamepad",
"observation": {
"display_cameras": false
},
"image_preprocessing": {
"crop_params_dict": {},
"resize_size": [128, 128]
},
"gripper": {
"use_gripper": true,
"gripper_penalty": 0.0
},
"reset": {
"reset_time_s": 5.0,
"control_time_s": 20.0
}
},
"robot": {
// ... robot configuration ...
},
"teleop": {
// ... teleoperator configuration ...
}
},
"dataset": {
"repo_id": "username/pick_lift_cube",
"root": null,
"task": "pick_and_lift",
"num_episodes_to_record": 15,
"replay_episode": 0,
"push_to_hub": true
},
"mode": "record",
"device": "cpu"
}除了机器人之外,你还需要一个 teleoperator 来控制它,以便为你的任务收集 dataset,并在在线训练期间进行干预。 我们支持使用游戏手柄、键盘或机器人的 leader arm。
HIL-SERL 在机器人的 end-effector 空间中学习 action。因此,teleoperation 将控制 end-effector 的 x、y、z 位移。
end-effector 变换由配置在 env.processor.inverse_kinematics(InverseKinematicsConfig)以及 env.processor.gripper / env.processor.max_gripper_pos 下的处理器流水线(InverseKinematicsRLStep、EEBoundsAndSafety、EEReferenceAndDelta、GripperVelocityToJoint)应用。与 end-effector 空间相关的默认值如下:
class InverseKinematicsConfig:
"""Configuration for inverse kinematics processing."""
urdf_path: str | None = None
target_frame_name: str | None = None
# bounds for the end-effector in x,y,z direction
end_effector_bounds: dict[str, list[float]] | None = None
# maximum step size for the end-effector in x,y,z direction
end_effector_step_sizes: dict[str, float] | None = None
class HILSerlProcessorConfig:
...
# maximum gripper position that the gripper will be open at
max_gripper_pos: float | None = 100.0Teleoperator 定义了 teleoperator。你可以在 lerobot/teleoperators 中查看可用的 teleoperator 列表。
设置游戏手柄
游戏手柄提供了一种非常方便的方式来控制机器人和 episode state。
要设置游戏手柄,你需要在配置文件中将 control_mode 设置为 "gamepad",并定义 teleop 部分。
{
"env": {
"teleop": {
"type": "gamepad",
"use_gripper": true
},
"processor": {
"control_mode": "gamepad",
"gripper": {
"use_gripper": true
}
}
}
}
用于机器人控制和 episode 管理的游戏手柄按键映射
设置 SO101 leader arm
SO101 leader arm 采用减速齿轮,使其能够在探索期间移动并跟踪 follower arm。因此,接管过程比无齿轮的 SO100 顺畅得多。
要设置 SO101 leader arm,你需要在配置文件中将 control_mode 设置为 "leader",并定义 teleop 部分。
{
"env": {
"teleop": {
"type": "so101_leader",
"port": "/dev/tty.usbmodem585A0077921",
"use_degrees": true
},
"processor": {
"control_mode": "leader",
"gripper": {
"use_gripper": true
}
}
}
}为了标注 episode 的成功/失败,你需要使用键盘:按 s 表示成功,按 esc 表示失败。
在在线训练期间,按 space 接管 policy 控制,再次按 space 将控制权交还给 policy。
SO101 leader arm teleoperation 示例:leader arm 跟踪 follower arm,按 `space` 进行干预
录制 demonstration
启动录制过程,配置文件示例可在此处找到:
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config_so100.json
录制期间:
env.processor.reset.fixed_reset_joint_positions收集 demonstration 后,对其进行处理以确定最佳的相机裁剪区域。 强化学习对背景干扰很敏感,因此将图像裁剪到相关的工作空间区域非常重要。
视觉强化学习算法直接从像素输入中学习,因此容易受到无关视觉信息的影响。诸如变化的照明、阴影、移动的人或工作空间之外的物体等背景元素可能会干扰学习过程。好的 ROI 选择应该:
注意:如果你已经知道裁剪参数,可以跳过此步骤,只需在录制期间在配置文件中设置 crop_params_dict。
确定裁剪参数
使用 crop_dataset_roi.py 脚本以交互方式选择相机图像中的感兴趣区域:
python -m lerobot.rl.crop_dataset_roi --repo-id username/pick_lift_cube
示例输出:
Selected Rectangular Regions of Interest (top, left, height, width):
observation.images.side: [180, 207, 180, 200]
observation.images.front: [180, 250, 120, 150]
用于选择感兴趣区域的交互式裁剪工具
更新配置
将以下裁剪参数添加到你的训练配置中:
{
"env": {
"processor": {
"image_preprocessing": {
"crop_params_dict": {
"observation.images.side": [180, 207, 180, 200],
"observation.images.front": [180, 250, 120, 150]
},
"resize_size": [128, 128]
}
}
}
}推荐的图像分辨率
大多数基于视觉的 policy 都已在 128×128(默认)或 64×64 像素的方形输入上得到验证。因此,我们建议将 resize_size 参数设置为 [128, 128]——或者,如果你需要节省 GPU 显存和带宽,可设置为 [64, 64]。其他分辨率也是可行的,但尚未经过广泛测试。
奖励分类器在 HIL-SERL 工作流中扮演着重要角色,它自动化奖励分配并自动检测 episode 是否成功。奖励分类器无需手动定义奖励函数或依赖每个时间步的人工反馈,而是学习根据视觉 observation 预测成功/失败。这使得强化学习算法能够基于机器人的相机输入获得一致且自动化的奖励信号,从而高效地学习。
本指南说明了如何为 LeRobot 的人在环路强化学习实现训练奖励分类器。奖励分类器学习在给定 state 下预测奖励值,该奖励值可用于强化学习设置中以训练 policy。
注意:训练奖励分类器是可选的。你可以在第一轮强化学习实验中通过游戏手柄或键盘设备手动标注成功。
lerobot/rewards/classifier/modeling_classifier.py 中的奖励分类器实现使用预训练的视觉模型来处理图像。它可以输出单个值用于二元奖励以预测成功/失败情况,也可以输出多个值用于多类设置。
为奖励分类器收集 dataset
在训练之前,你需要收集一个带有标注示例的 dataset。在配置中设置 mode: "record" 并运行 gym_manipulator.py,即可启动收集 observation、action 和奖励 dataset 的过程。
要收集 dataset,你需要修改基于 HILSerlRobotEnvConfig 的环境配置中的一些参数。
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/reward_classifier_train_config.json
数据收集的关键参数
"record" 以收集 dataset(在根级别)"hf_username/dataset_name",即 Hub 上 dataset 和仓库的名称true)env.processor.reset.terminate_on_success 参数允许你控制 episode 终止行为。将其设置为 false 时,即使检测到成功,episode 也会继续,从而允许你收集更多带有 reward=1 标签的正例。这对于训练奖励分类器至关重要,因为它会在你的 dataset 中提供更多的成功 state 示例。将其设置为 true(默认值)时,episode 在检测到成功时立即终止。
重要:对于奖励分类器训练,设置 terminate_on_success: false 以收集足够的正例。对于常规的 HIL-SERL 训练,请保持为 true 以在任务成功完成时自动终止 episode。
用于数据收集的示例配置部分:
{
"env": {
"type": "gym_manipulator",
"name": "real_robot",
"fps": 10,
"processor": {
"reset": {
"reset_time_s": 5.0,
"control_time_s": 20.0,
"terminate_on_success": false
},
"gripper": {
"use_gripper": true
}
},
"robot": {
// ... robot configuration ...
},
"teleop": {
// ... teleoperator configuration ...
}
},
"dataset": {
"repo_id": "hf_username/dataset_name",
"root": "data/your_dataset",
"task": "reward_classifier_task",
"num_episodes_to_record": 20,
"replay_episode": null,
"push_to_hub": true
},
"mode": "record",
"device": "cpu"
}奖励分类器配置
奖励分类器使用 lerobot/rewards/classifier/configuration_classifier.py 进行配置。以下是关键参数:
"helper2424/resnet10")"cnn" 或 "transformer"训练奖励分类器的示例配置:
{
"dataset": {
"repo_id": "hf_username/dataset_name",
"root": null
},
"reward_model": {
"type": "reward_classifier",
"model_name": "helper2424/resnet10",
"model_type": "cnn",
"num_cameras": 2,
"num_classes": 2,
"hidden_dim": 256,
"dropout_rate": 0.1,
"learning_rate": 1e-4,
"device": "cuda",
"input_features": {
"observation.images.front": {
"type": "VISUAL",
"shape": [3, 128, 128]
},
"observation.images.side": {
"type": "VISUAL",
"shape": [3, 128, 128]
}
},
"push_to_hub": true,
"repo_id": "hf_username/model_repo"
},
"batch_size": 16,
"num_workers": 4,
"steps": 5000,
"log_freq": 10,
"env_eval_freq": 1000,
"save_freq": 1000,
"save_checkpoint": true,
"seed": 2,
"resume": false,
"optimizer": {
"grad_clip_norm": 10.0
},
"wandb": {
"enable": true,
"project": "reward-classifier",
"disable_artifact": false
},
"job_name": "reward-classifier"
}训练分类器
要训练分类器,请使用 train.py 脚本并携带你的配置:
lerobot-train --config_path path/to/reward_classifier_train_config.json
部署和测试模型
要使用你训练好的奖励分类器,请配置 HILSerlRobotEnvConfig 以使用你的模型:
config = GymManipulatorConfig(
env=HILSerlRobotEnvConfig(
processor=HILSerlProcessorConfig(
reward_classifier=RewardClassifierConfig(
pretrained_path="path_to_your_pretrained_trained_model"
)
),
# Other environment parameters
),
dataset=DatasetConfig(...),
mode=None # For training
)或者在 json 配置文件中设置该参数。
{
"env": {
"processor": {
"reward_classifier": {
"pretrained_path": "path_to_your_pretrained_model",
"success_threshold": 0.7,
"success_reward": 1.0
},
"reset": {
"terminate_on_success": true
}
}
}
}运行 gym_manipulator.py 来测试模型。
python -m lerobot.rl.gym_manipulator --config_path path/to/env_config.json
奖励分类器将根据机器人相机的视觉输入自动提供奖励。
训练奖励分类器的示例工作流
创建配置文件: 为奖励分类器和环境创建必要的 json 配置文件。查看此处的示例。
收集 dataset:
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
训练分类器:
lerobot-train --config_path src/lerobot/configs/reward_classifier_train_config.json
测试分类器:
python -m lerobot.rl.gym_manipulator --config_path src/lerobot/configs/env_config.json
LeRobot 系统采用分布式的 actor-learner 架构进行训练。该架构将机器人的交互与学习过程解耦,使它们能够并发运行而不互相阻塞。actor 服务器处理机器人的 observation 和 action,并将交互数据发送给 learner 服务器。learner 服务器执行梯度下降并定期更新 actor 的 policy 权重。你需要启动两个进程:一个 learner 和一个 actor。
配置设置
创建一个训练配置文件(示例在此处)。训练配置基于 lerobot/rl/train_rl.py 中的主类 TrainRLServerPipelineConfig。
type="gaussian_actor"、device 等)algorithm 块下配置算法设置(type="sac"、学习率、折扣等,定义于 lerobot/rl/algorithms/sac/configuration_sac.py 中)。dataset 设置为你的裁剪后 datasetpolicy 配置在针对你的任务的 input_features 和 output_features 方面是否正确。启动 Learner
首先,启动 learner 服务器进程:
python -m lerobot.rl.learner --config_path src/lerobot/configs/train_config_hilserl_so100.json
learner 会:
gRPC 服务器与 actor 通信启动 Actor
在另一个终端中,使用相同的配置启动 actor 进程:
python -m lerobot.rl.actor --config_path src/lerobot/configs/train_config_hilserl_so100.json
actor 会:
gRPC 连接到 learner训练流程
训练会自动进行:
人在环路
space 键)。这将暂停 policy action,并允许你接管控制。wandb 仪表盘中监控干预率。
展示人工干预如何随着时间推移帮助引导 policy learning 的示例
监控与调试
如果你在配置中将 wandb.enable 设置为 true,就可以通过 Weights & Biases 仪表盘实时监控训练进度。
学习过程对干预 policy 非常敏感。需要运行几次才能了解如何有效干预。以下是一些提示和建议:
理想的行为是,你的干预率应在训练过程中逐渐下降,如下图所示。

在抓取并提起方块任务的一次训练运行中, 干预率的变化曲线
某些配置值对训练稳定性和速度有着不成比例的影响:
temperature_init(algorithm.temperature_init)——SAC 中的初始熵温度。较高的值鼓励更多的探索;较低的值使 policy 在早期更加确定。一个不错的起点是 1e-2。我们观察到,将其设置过高会使人干预失效并减慢学习速度。policy_parameters_push_frequency(policy.actor_learner_config.policy_parameters_push_frequency)——learner 向 actor 推两次权重之间的时间间隔(以秒为单位)。默认值为 4 s。将其减小到 1-2 秒 可以提供更新的权重(代价是更多的网络流量);只有在连接较慢时才增大它,因为这会降低样本效率。storage_device(policy.storage_device)——learner 保存 policy 参数所用的设备。如果你有多余的 GPU 显存,可将其设置为 "cuda"(而不是默认的 "cpu")。将权重保存在 GPU 上可以消除 CPU→GPU 的传输开销,并显著增加 learner 每秒的更新次数。恭喜 🎉,你已经完成了本教程!
如果你有任何问题或需要帮助,请在 Discord 上联系我们。
论文引用:
@article{luo2024precise,
title={Precise and Dexterous Robotic Manipulation via Human-in-the-Loop Reinforcement Learning},
author={Luo, Jianlan and Xu, Charles and Wu, Jeffrey and Levine, Sergey},
journal={arXiv preprint arXiv:2410.21845},
year={2024}
}