Isaac Lab 强化学习入门指南

本文为读者提供了一个起点,使用Isaac Lab人形机器人示例项目在Isaac Lab中创建自己的项目。它将解释项目结构、模板创建器的使用,并演示一个自定义的示例策略。我们将修改原始的人形机器人项目,使用强化学习训练人形机器人移动到指定点,到达该点后停止并自我平衡。

1、安装Isaac Lab

首先,从Github文档安装Isaac Lab:

使用Isaac Sim pip安装

说明包括Isaac Sim的安装。为了入门,建议使用虚拟环境,因为你可以从干净的环境开始,无需担心依赖冲突。Isaac lab提供pip和conda安装选项。我们将使用的示例项目是IsaacLab-Humanoid-Direct-v0,它包含在Isaac Lab安装中。以下是所有可用示例环境的列表,可作为初学者的起点:

可用环境

Isaac Lab详解

Isaac Lab通过启动Isaac Sim实例来工作,该实例运行多个actor的并行模拟,actor由URDF文件定义。actor将学习一个策略,该策略最大化训练环境中奖励函数定义的奖励总和。当Isaac Lab代码执行时,Isaac Sim初始化并开始训练过程。

2、生成模板项目

理解Isaac Lab项目的一个障碍是示例项目都内置在源代码的文件结构中。这种设置使得很难跟踪Python导入并掌握工作流程。本节将介绍如何使用模板生成器创建外部Isaac Lab人形机器人项目,并修改它以满足你的目的。在本例中,我们将修改默认生成的项目,作为训练Isaac Lab人形机器人的环境。

方便的是,Isaac Lab文档有生成模板项目的说明。这允许你选择所需的工作流、RL库,并确保项目结构正确设置。

首先,激活创建的虚拟环境或conda环境。

导航到IsaacLab根文件夹。

然后,按照文档中的说明运行此shell命令创建模板项目:

./isaaclab.sh --new

按照以下方式配置项目:

选择External作为任务类型

指定项目构建的路径

将项目命名为humanoid_target_move

选择direct, single agent Isaac Lab工作流

选择rl_games作为RL库

这将在指定路径中创建模板项目。

Isaac Lab文件结构

项目文件结构由三个目录组成:

README.md  scripts  source

README.md将包含构建项目的自动生成说明。最重要的是,它将包含Python pip安装命令。这应该首先从顶级Isaac Lab文件夹运行。它看起来像这样:

python -m pip install -e source/humanoid_target_move

scripts包含项目的重要Python脚本:

├── list_envs.py
└── rl_games
    ├── play.py
    └── train.py

list_envs.py: 显示项目中可用的环境。模板项目设置为自动具有初始环境,执行脚本时将显示该环境。

train.py: 启动Isaac Lab开始训练。

play.py: 启动Isaac Lab回放训练好的策略。

source包含控制环境、奖励和动作的所有代码。

我们将在本文中使用的重要文件位于tasks的子目录中:

humanoid_target_move_env.py

humanoid_target_move_env_cfg.py

rl_games_ppo_cfg.yaml

Tasks位于:

humanoid_target_move/source/humanoid_target_move/humanoid_target_move/tasks

导航到项目的此目录,将相对容易找到这些文件。

模板项目生成后,这些文件将设置为运行平衡倒立摆的强化学习训练。为确保项目正确构建,导航到scripts/rl_games并运行此命令启动默认倒立摆训练:

python train.py --task=Template-Humanoid-Target-Move-Direct-v0

这将启动一个运行RL训练的Isaac Sim实例。它将平衡杆子,看起来像这样:

如果你是RL新手,观看倒立摆随着训练的继续而缓慢改进会很有启发性。然而,为了提高训练效率,在"无头"模式下运行训练非常有效。无头模式不渲染视觉效果,但仍然执行所有训练计算和环境模拟。在无头模式下运行就像添加一个headless标志一样简单:

python train.py --task=Template-Humanoid-Target-Move-Direct-v0 --headless

在无头模式下,你的主要反馈将在终端窗口本身中。运行训练时,Isaac Lab还将显示已运行的epoch数量。每次策略改进并被评估时,该改进的策略将保存为检查点。训练将在预定数量的epoch后完成。epoch的长度和epoch的数量都可以在训练器的配置文件中自定义,在本例中是rl_games_ppo_cfg.yaml

训练完成后,使用play.py运行策略时将使用最新的检查点。你可以在同一个rl_games文件夹中运行以下命令来播放训练好的策略:

python play.py --task=Template-Humanoid-Target-Move-Direct-v0

这将打开一个Isaac Sim实例,显示训练好的策略在运行。对于倒立摆,你将看到每个倒立摆实例调整其各自的位置以将杆子保持直立。我们将在此基础上创建Isaac人形机器人项目。

3、将倒立摆模板转换为Isaac人形机器人

如前所述,我们将修改这三个文件将项目转换为人形机器人:

rl_games_ppo_cfg.yaml

humanoid_target_move_env.py

humanoid_target_move_env_cfg.py

此外,我们将从Isaac源代码中提取locomotion_env.py并将其导入此项目。locomotion_env.py包含为Isaac人形机器人设计的环境。

3.1 将运动放入项目中

包含locomotion_env.py的文件夹位于Isaac Lab源代码中:

IsaacLab/source/isaaclab_tasks/isaaclab_tasks/direct/locomotion/

将locomotion文件夹的副本放在项目目录中:

humanoid_target_move/source/humanoid_target_move/humanoid_target_move

我们将在创建自定义RL训练时修改此文件,因此我们创建此副本以保留原始文件。

3.2 修改RL Games PPO YAML

RL Games中使用的默认RL方法是PPO(近端策略优化)。由于我们当前的rl_games_ppo_cfg.yaml是为倒立摆设置的,我们需要将其替换为Isaac Lab源代码中的PPO YAML。此YAML文件可用于修改RL Games PPO训练算法的许多方面,我们将使用人形机器人的PPO而不是倒立摆。此PPO文件位于:

    IsaacLab/source/isaaclab_tasks/isaaclab_tasks/direct/humanoid/agents

以下是PPO YAML:

params:
  seed: 42

  env:
    clip_actions: 1.0

  algo:
    name: a2c_continuous

  model:
    name: continuous_a2c_logstd

  network:
    name: actor_critic
    separate: False
    space:
      continuous:
        mu_activation: None
        sigma_activation: None

        mu_init:
          name: default
        sigma_init:
          name: const_initializer
          val: 0
        fixed_sigma: True
    mlp:
      units: [400, 200, 100]
      activation: elu
      d2rl: False

      initializer:
        name: default
      regularizer:
        name: None

  load_checkpoint: False
  load_path: ''

  config:
    name: humanoid_direct
    env_name: rlgpu
    device: 'cuda:0'
    device_name: 'cuda:0'
    multi_gpu: False
    ppo: True
    mixed_precision: True
    normalize_input: True
    normalize_value: True
    value_bootstrap: True
    num_actors: -1
    reward_shaper:
      scale_value: 0.01
    normalize_advantage: True
    gamma: 0.99
    tau: 0.95
    learning_rate: 5e-4
    lr_schedule: adaptive
    kl_threshold: 0.008
    score_to_win: 20000
    max_epochs: 1000
    save_best_after: 100
    save_frequency: 50
    grad_norm: 1.0
    entropy_coef: 0.0
    truncate_grads: True
    e_clip: 0.2
    horizon_length: 32
    minibatch_size: 32768
    mini_epochs: 5
    critic_coef: 4
    clip_value: True
    seq_length: 4
    bounds_loss_coef: 0.0001

agents目录中的rl_games_ppo_cfg.yaml内容替换为人形机器人PPO文件的内容。

以下是YAML文件最重要方面的一些解释,以及它们对PPO算法的含义。

mlp:
      units: [400, 200, 100]
      activation: elu
      d2rl: False

units键指定神经网络中每层的神经元数量。在本例中,第1层有400个神经元,第2层有200个神经元,第3层有100个神经元。activation键指定每层的激活函数。

device: 'cuda:0'
device_name: 'cuda:0'
learning_rate: 5e-4
lr_schedule: adaptive

devicedevice_name键允许我们指定训练使用的GPU,learning_rate和lr_scheduler是ADAM或SGD等一阶优化器的基本超参数。

本文中的示例不需要修改PPO配置,但了解选项对于一般的RL训练很有帮助。

3.3 环境配置

剩下的两个文件humanoid_target_move_env.pyhumanoid_target_move_env_cfg.py将确定奖励权重、起始位置,并加载Isaac人形机器人actor的配置。

humanoid_target_move_env.py将非常简短,只是将环境初始化为运动环境并设置配置。它应该如下所示:

from __future__ import annotations

from .humanoid_target_move_env_cfg import HumanoidTargetMoveEnvCfg
from humanoid_target_move.locomotion.locomotion_env import LocomotionEnv

class HumanoidTargetMoveEnv(LocomotionEnv):
    cfg: HumanoidTargetMoveEnvCfg
    def __init__(self, cfg: HumanoidTargetMoveEnvCfg, render_mode: str | None = None, **kwargs):
        super().__init__(cfg, render_mode, **kwargs)

这与Isaac Lab源代码版本几乎相同,但使用匹配的项目名称,并从项目文件夹中的locomotion_env.py导入LocomotionEnv的副本。

现在我们将修改humanoid_target_move_env_cfg.py以正确配置Isaac人形机器人环境。它应该如下所示:

from __future__ import annotations

from isaaclab_assets import HUMANOID_CFG
from isaaclab.assets import ArticulationCfg
from isaaclab.envs import DirectRLEnvCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationCfg
from isaaclab.utils import configclass
from isaaclab.terrains import TerrainImporterCfg
import isaaclab.sim as sim_utils

import gymnasium as gym
import numpy as np

@configclass
class HumanoidTargetMoveEnvCfg(DirectRLEnvCfg):
    # env
    episode_length_s = 15.0
    decimation = 2
    action_scale = 1.0
    action_space = 21
    observation_space = 75
    state_space = 0

    # simulation
    sim: SimulationCfg = SimulationCfg(dt=1 / 120, render_interval=decimation)
    terrain = TerrainImporterCfg(
        prim_path="/World/ground",
        terrain_type="plane",
        collision_group=-1,
        physics_material=sim_utils.RigidBodyMaterialCfg(
            friction_combine_mode="average",
            restitution_combine_mode="average",
            static_friction=1.0,
            dynamic_friction=1.0,
            restitution=0.0,
        ),
        debug_vis=False,
    )

    # scene
    scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=1024, env_spacing=4.0, replicate_physics=True)

    # robot
    robot: ArticulationCfg = HUMANOID_CFG.replace(prim_path="/World/envs/env_.*/Robot")
    joint_gears: list = [
        67.5000,  # lower_waist
        67.5000,  # lower_waist
        67.5000,  # right_upper_arm
        67.5000,  # right_upper_arm
        67.5000,  # left_upper_arm
        67.5000,  # left_upper_arm
        67.5000,  # pelvis
        45.0000,  # right_lower_arm
        45.0000,  # left_lower_arm
        45.0000,  # right_thigh: x
        135.0000,  # right_thigh: y
        45.0000,  # right_thigh: z
        45.0000,  # left_thigh: x
        135.0000,  # left_thigh: y
        45.0000,  # left_thigh: z
        90.0000,  # right_knee
        90.0000,  # left_knee
        22.5,  # right_foot
        22.5,  # right_foot
        22.5,  # left_foot
        22.5,  # left_foot
    ]

    heading_weight: float = 0.5
    up_weight: float = 0.1

    energy_cost_scale: float = 0.05
    actions_cost_scale: float = 0.01
    alive_reward_scale: float = 2.0
    dof_vel_scale: float = 0.1

    death_cost: float = -1.0
    termination_height: float = 0.8

    angular_velocity_scale: float = 0.25
    contact_force_scale: float = 0.01

    distance_reward_scale: float = 1.0 #<--- added for training move to point
    epsilon_dist: float = 0.10 #<--- added for training move to point

此配置导入所有必要的Isaac Lab配置,为关节设置HUMANOID_CFG配置文件,并设置每个奖励的权重。这些奖励在我们之前创建副本的locomotion_env.py文件中定义。

4、训练Isaac人形机器人移动到点

要训练Isaac人形机器人移动到点并停止,我们将理解并修改locomotion_env.py文件。你可能已经注意到,在上面的代码中,两个值distance_reward_scaleepsilon_dist有注释表明它们用于本文的移动到点训练。在locomotion_env.py中,我们将定义这些,使得环境配置中给出的奖励是可实现的,并考虑到Isaac人形机器人采取的动作。我们将遍历locomotion_env.py中的每个相关部分,以实现使actor移动到点并停止所需的奖励。

首先,在文件顶部附近,注意类init中的self.targets张量。

self.targets = torch.tensor([1000,0, 0], dtype=torch.float32, device=self.sim.device).repeat(
       (self.num_envs, 1)

张量可以是非常复杂的数学结构,但在本例中,我们正在查看一个表示简单3D坐标系的张量。第一个条目是X,第二个条目是Y,第三个条目是Z轴。三个一起定义空间中的一个点,更改此值允许我们为人形机器人设置目标。

将第一个条目X值设置为5,以将目标放置在仅一小段距离前方。默认设置为1000,因为库存项目不尝试使人形机器人移动到或停止在特定点。我们下面定义的奖励将允许我们按照预期使用此目标,作为空间中要到达并停止的点。这是调整后的张量版本:

self.targets = torch.tensor([5, 0, 0], dtype=torch.float32, device=self.sim.device).repeat(
       (self.num_envs, 1)

4.1 计算奖励函数

接下来,我们定义一个奖励,激励人形机器人到达并保持在指定的目标点。我们将在函数compute_rewards()中定义此奖励,权重为distance_reward_scale,距离的逆计算为inv_dist_bonus。此计算将使用到目标的距离**(dist_to_goal)创建一个奖励值,该值被纳入最终计算的奖励计算中。以下是修改compute_rewards()**以实现此奖励的方法:

@torch.jit.script
def compute_rewards(
    actions: torch.Tensor,
    reset_terminated: torch.Tensor,
    up_weight: float,
    heading_weight: float,
    heading_proj: torch.Tensor,
    up_proj: torch.Tensor,
    dof_vel: torch.Tensor,
    dof_pos_scaled: torch.Tensor,
    potentials: torch.Tensor,
    prev_potentials: torch.Tensor,
    actions_cost_scale: float,
    energy_cost_scale: float,
    dof_vel_scale: float,
    death_cost: float,
    alive_reward_scale: float,
    motor_effort_ratio: torch.Tensor,
    vel_loc: torch.Tensor,
    dist_to_goal: torch.Tensor, #<--- added reward
    distance_reward_scale: float, #<--- scale for reward
    epsilon_dist: float #<--- added epsilon
):
    heading_weight_tensor = torch.ones_like(heading_proj) * heading_weight
    heading_reward = torch.where(heading_proj > 0.8, heading_weight_tensor, heading_weight * heading_proj / 0.8)

    up_reward = torch.zeros_like(heading_reward)
    up_reward = torch.where(up_proj > 0.93, up_reward + up_weight, up_reward)

    actions_cost = torch.sum(actions**2, dim=-1)
    electricity_cost = torch.sum(
        torch.abs(actions * dof_vel * dof_vel_scale) * motor_effort_ratio.unsqueeze(0),
        dim=-1,
    )

    dof_at_limit_cost = torch.sum(dof_pos_scaled > 0.98, dim=-1)

    alive_reward = torch.ones_like(potentials) * alive_reward_scale
    progress_reward = potentials - prev_potentials

    # new added reward (inverse of distance based reward)
    inv_dist_bonus = distance_reward_scale / (epsilon_dist + dist_to_goal)

    total_reward = (
        progress_reward
        + inv_dist_bonus #<--- added
        + alive_reward
        + up_reward
        + heading_reward
        - actions_cost_scale * actions_cost
        - energy_cost_scale * electricity_cost
        - dof_at_limit_cost
    )

    total_reward = torch.where(reset_terminated, torch.ones_like(total_reward) * death_cost, total_reward)
    return total_reward

对于**compute_rewards()**方法,环境配置中输入的权重作为参数传递,然后计算并返回总奖励。提供的奖励确保机器人节能,并确保人形机器人避免摔倒。由于人形机器人已经可以保持直立,我们只需要奖励人形机器人到达并保持在给定的目标点。

为此,我们必须为人形机器人越接近目标提供越多的奖励,并继续提供该奖励以保持接近目标。我们将使用逆距离计算,使得越接近目标产生越高的奖励:

inv_dist_bonus = distance_reward_scale / (epsilon_dist + dist_to_goal)

distance_reward_scale由环境运行时设置。数字越大,随着与目标距离缩小,总奖励越大。例如,如果distance_reward_scale为2.0,(dist_to_goal + epsilon_dist)为0.2,则添加到最终奖励计算的奖励将为10。epsilon_dist也由环境设置,但它只是为了在人形机器人直接在点上时避免除以零。在我们实现的环境代码中,epsilon为0.10。这使得最大可能的奖励为distance_reward_scale除以0.10,即distance_reward_scale定义值的10倍。

如上面的代码所示,此计算中使用的值必须添加到函数的参数中。

为了允许此奖励影响RL训练,我们将把这些值添加到locomotion_env.py文件顶部附近的**get_rewards()**函数中。

    def _get_rewards(self) -> torch.Tensor:
        total_reward = compute_rewards(
            self.actions,
            self.reset_terminated,
            self.cfg.up_weight,
            self.cfg.heading_weight,
            self.heading_proj,
            self.up_proj,
            self.dof_vel,
            self.dof_pos_scaled,
            self.potentials,
            self.prev_potentials,
            self.cfg.actions_cost_scale,
            self.cfg.energy_cost_scale,
            self.cfg.dof_vel_scale,
            self.cfg.death_cost,
            self.cfg.alive_reward_scale,
            self.motor_effort_ratio,
            self.dist_to_goal, #<--- added calculated reward
            self.cfg.distance_reward_scale, #<--- added configurable weight
            self.cfg.epsilon_dist, #<--- Added configurable minimum epsilon
        )
        return total_reward

4.2 中间值函数

distance_reward_scaleepsilon_dist在环境中定义,但dist_to_goal不是由compute_rewards、环境或源代码内置定义的。因此,我们必须在**intermediate_values()**中实现此计算。此函数使用有关人形机器人的信息来跟踪奖励计算所需的值。以下是修改后的版本:

@torch.jit.script
def compute_intermediate_values(
    targets: torch.Tensor,
    torso_position: torch.Tensor,
    torso_rotation: torch.Tensor,
    velocity: torch.Tensor,
    ang_velocity: torch.Tensor,
    dof_pos: torch.Tensor,
    dof_lower_limits: torch.Tensor,
    dof_upper_limits: torch.Tensor,
    inv_start_rot: torch.Tensor,
    basis_vec0: torch.Tensor,
    basis_vec1: torch.Tensor,
    potentials: torch.Tensor,
    prev_potentials: torch.Tensor,
    dt: float,
):
    to_target = targets - torso_position
    to_target[:, 2] = 0.0

    torso_quat, up_proj, heading_proj, up_vec, heading_vec = compute_heading_and_up(
        torso_rotation, inv_start_rot, to_target, basis_vec0, basis_vec1, 2
    )

    vel_loc, angvel_loc, roll, pitch, yaw, angle_to_target = compute_rot(
        torso_quat, velocity, ang_velocity, targets, torso_position
    )

    dof_pos_scaled = torch_utils.maths.unscale(dof_pos, dof_lower_limits, dof_upper_limits)

    to_target = targets - torso_position
    to_target[:, 2] = 0.0
    prev_potentials[:] = potentials
    potentials = -torch.norm(to_target, p=2, dim=-1) / dt

    dist_to_goal = torch.norm(to_target[:, :2], dim=-1) #<--- scalar distance
    return (
        up_proj,
        heading_proj,
        up_vec,
        heading_vec,
        vel_loc,
        angvel_loc,
        roll,
        pitch,
        yaw,
        angle_to_target,
        dof_pos_scaled,
        prev_potentials,
        potentials,
        dist_to_goal, #<---- calculated distance to goal must return
    )

以下是代码的主要添加:

dist_to_goal = torch.norm(to_target[:, :2], dim=-1)

to_target在上面的函数中计算,是指定目标减去人形机器人的躯干位置。然而,to_target是多维张量,我们需要标量距离测量用于奖励计算。因此,我们可以使用PyTorch范数将此信息标准化为可用的1维标量。这将目标坐标转换为可在inv_dist_bonus计算中使用的单个值。我们还执行了PyTorch切片,删除了不必要的Z轴,因为所有导航都将在平坦表面上进行。此标准化值将在奖励计算中工作。

定义中间值后,我们需要更新文件顶部附近的**computer_intermediate_values(self)**以反映更改:

    def _compute_intermediate_values(self):
        self.torso_position, self.torso_rotation = self.robot.data.root_pos_w, self.robot.data.root_quat_w
        self.velocity, self.ang_velocity = self.robot.data.root_lin_vel_w, self.robot.data.root_ang_vel_w
        self.dof_pos, self.dof_vel = self.robot.data.joint_pos, self.robot.data.joint_vel

        (
            self.up_proj,
            self.heading_proj,
            self.up_vec,
            self.heading_vec,
            self.vel_loc,
            self.angvel_loc,
            self.roll,
            self.pitch,
            self.yaw,
            self.angle_to_target,
            self.dof_pos_scaled,
            self.prev_potentials,
            self.potentials,
            self.dist_to_goal,#<--- added here
        ) = compute_intermediate_values(
            self.targets,
            self.torso_position,
            self.torso_rotation,
            self.velocity,
            self.ang_velocity,
            self.dof_pos,
            self.robot.data.soft_joint_pos_limits[0, :, 0],
            self.robot.data.soft_joint_pos_limits[0, :, 1],
            self.inv_start_rot,
            self.basis_vec0,
            self.basis_vec1,
            self.potentials,
            self.prev_potentials,
            self.cfg.sim.dt,
        )

4.3 运行修改后的训练

完成所有这些步骤后,返回项目的顶级文件夹并导航到scripts/rl_games运行新训练:

python train.py --task=Template-Humanoid-Target-Move-Direct-v0 --headless

这需要的时间取决于你的硬件,但如果你满足Isaac Sim系统要求,可能少于5分钟。训练完成后,运行play命令查看结果:

python play.py --task=Template-Humanoid-Target-Move-Direct-v0

如果一切正常,你将看到人形机器人向前移动5个单位,然后停止并原地自我平衡。

此训练将使人形机器人直接向前移动,但返回并使用不同的X和Y值运行RL训练允许人形机器人导航到任何X和Y坐标。在较远距离,我们需要增加episode长度,可能还需要增加epoch数量,因为需要训练的时间跨度增加了。然而,10以下的较短距离在默认的epoch数量和episode长度下效果良好。


原文链接:Introduction to Isaac Lab Reinforcement Learning with the Isaac Humanoid

汇智网翻译整理,转载请标明出处