创建基于管理器的强化学习环境#

创建基于管理器的基础环境 中学习了如何创建基础环境后,我们现在将学习如何为强化学习创建基于管理器的任务环境。

基础环境被设计为一种感知-行为环境,代理可以向环境发送命令,并从环境中接收观测。这种最小接口对于许多应用程序,如传统运动规划和控制,是足够的。然而,许多应用程序需要任务规范,这通常用作代理的学习目标。例如,在导航任务中,代理可能需要到达目标位置。为此,我们使用 envs.ManagerBasedRLEnv 类扩展基础环境以包含任务规范。

与 Isaac Lab 中的其他组件类似,我们鼓励用户不要直接修改 envs.ManagerBasedRLEnv 基类,而是简单地为他们的任务环境实现一个配置 envs.ManagerBasedRLEnvCfg 。这种做法使我们能够将任务规范与环境实现分开,使得更容易地重用相同环境的组件用于不同的任务。

在本教程中,我们将使用 envs.ManagerBasedRLEnvCfg 配置 cartpole 环境,以创建一个平衡杆的基于管理器的任务。我们将学习如何使用奖励项、终止条件、课程和命令来指定任务。

代码#

在本教程中,我们使用 omni.isaac.lab_tasks.manager_based.classic.cartpole 模块中定义的 cartpole 环境。

cartpole_env_cfg.py 代码
  1# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
  2# All rights reserved.
  3#
  4# SPDX-License-Identifier: BSD-3-Clause
  5
  6import math
  7
  8import omni.isaac.lab.sim as sim_utils
  9from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg
 10from omni.isaac.lab.envs import ManagerBasedRLEnvCfg
 11from omni.isaac.lab.managers import EventTermCfg as EventTerm
 12from omni.isaac.lab.managers import ObservationGroupCfg as ObsGroup
 13from omni.isaac.lab.managers import ObservationTermCfg as ObsTerm
 14from omni.isaac.lab.managers import RewardTermCfg as RewTerm
 15from omni.isaac.lab.managers import SceneEntityCfg
 16from omni.isaac.lab.managers import TerminationTermCfg as DoneTerm
 17from omni.isaac.lab.scene import InteractiveSceneCfg
 18from omni.isaac.lab.utils import configclass
 19
 20import omni.isaac.lab_tasks.manager_based.classic.cartpole.mdp as mdp
 21
 22##
 23# Pre-defined configs
 24##
 25from omni.isaac.lab_assets.cartpole import CARTPOLE_CFG  # isort:skip
 26
 27
 28##
 29# Scene definition
 30##
 31
 32
 33@configclass
 34class CartpoleSceneCfg(InteractiveSceneCfg):
 35    """Configuration for a cart-pole scene."""
 36
 37    # ground plane
 38    ground = AssetBaseCfg(
 39        prim_path="/World/ground",
 40        spawn=sim_utils.GroundPlaneCfg(size=(100.0, 100.0)),
 41    )
 42
 43    # cartpole
 44    robot: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
 45
 46    # lights
 47    dome_light = AssetBaseCfg(
 48        prim_path="/World/DomeLight",
 49        spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=500.0),
 50    )
 51    distant_light = AssetBaseCfg(
 52        prim_path="/World/DistantLight",
 53        spawn=sim_utils.DistantLightCfg(color=(0.9, 0.9, 0.9), intensity=2500.0),
 54        init_state=AssetBaseCfg.InitialStateCfg(rot=(0.738, 0.477, 0.477, 0.0)),
 55    )
 56
 57
 58##
 59# MDP settings
 60##
 61
 62
 63@configclass
 64class CommandsCfg:
 65    """Command terms for the MDP."""
 66
 67    # no commands for this MDP
 68    null = mdp.NullCommandCfg()
 69
 70
 71@configclass
 72class ActionsCfg:
 73    """Action specifications for the MDP."""
 74
 75    joint_effort = mdp.JointEffortActionCfg(asset_name="robot", joint_names=["slider_to_cart"], scale=100.0)
 76
 77
 78@configclass
 79class ObservationsCfg:
 80    """Observation specifications for the MDP."""
 81
 82    @configclass
 83    class PolicyCfg(ObsGroup):
 84        """Observations for policy group."""
 85
 86        # observation terms (order preserved)
 87        joint_pos_rel = ObsTerm(func=mdp.joint_pos_rel)
 88        joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel)
 89
 90        def __post_init__(self) -> None:
 91            self.enable_corruption = False
 92            self.concatenate_terms = True
 93
 94    # observation groups
 95    policy: PolicyCfg = PolicyCfg()
 96
 97
 98@configclass
 99class EventCfg:
100    """Configuration for events."""
101
102    # reset
103    reset_cart_position = EventTerm(
104        func=mdp.reset_joints_by_offset,
105        mode="reset",
106        params={
107            "asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]),
108            "position_range": (-1.0, 1.0),
109            "velocity_range": (-0.5, 0.5),
110        },
111    )
112
113    reset_pole_position = EventTerm(
114        func=mdp.reset_joints_by_offset,
115        mode="reset",
116        params={
117            "asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]),
118            "position_range": (-0.25 * math.pi, 0.25 * math.pi),
119            "velocity_range": (-0.25 * math.pi, 0.25 * math.pi),
120        },
121    )
122
123
124@configclass
125class RewardsCfg:
126    """Reward terms for the MDP."""
127
128    # (1) Constant running reward
129    alive = RewTerm(func=mdp.is_alive, weight=1.0)
130    # (2) Failure penalty
131    terminating = RewTerm(func=mdp.is_terminated, weight=-2.0)
132    # (3) Primary task: keep pole upright
133    pole_pos = RewTerm(
134        func=mdp.joint_pos_target_l2,
135        weight=-1.0,
136        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]), "target": 0.0},
137    )
138    # (4) Shaping tasks: lower cart velocity
139    cart_vel = RewTerm(
140        func=mdp.joint_vel_l1,
141        weight=-0.01,
142        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"])},
143    )
144    # (5) Shaping tasks: lower pole angular velocity
145    pole_vel = RewTerm(
146        func=mdp.joint_vel_l1,
147        weight=-0.005,
148        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"])},
149    )
150
151
152@configclass
153class TerminationsCfg:
154    """Termination terms for the MDP."""
155
156    # (1) Time out
157    time_out = DoneTerm(func=mdp.time_out, time_out=True)
158    # (2) Cart out of bounds
159    cart_out_of_bounds = DoneTerm(
160        func=mdp.joint_pos_out_of_manual_limit,
161        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]), "bounds": (-3.0, 3.0)},
162    )
163
164
165@configclass
166class CurriculumCfg:
167    """Configuration for the curriculum."""
168
169    pass
170
171
172##
173# Environment configuration
174##
175
176
177@configclass
178class CartpoleEnvCfg(ManagerBasedRLEnvCfg):
179    """Configuration for the locomotion velocity-tracking environment."""
180
181    # Scene settings
182    scene: CartpoleSceneCfg = CartpoleSceneCfg(num_envs=4096, env_spacing=4.0)
183    # Basic settings
184    observations: ObservationsCfg = ObservationsCfg()
185    actions: ActionsCfg = ActionsCfg()
186    events: EventCfg = EventCfg()
187    # MDP settings
188    curriculum: CurriculumCfg = CurriculumCfg()
189    rewards: RewardsCfg = RewardsCfg()
190    terminations: TerminationsCfg = TerminationsCfg()
191    # No command generator
192    commands: CommandsCfg = CommandsCfg()
193
194    # Post initialization
195    def __post_init__(self) -> None:
196        """Post initialization."""
197        # general settings
198        self.decimation = 2
199        self.episode_length_s = 5
200        # viewer settings
201        self.viewer.eye = (8.0, 0.0, 5.0)
202        # simulation settings
203        self.sim.dt = 1 / 120
204        self.sim.render_interval = self.decimation

用于运行环境的脚本 run_cartpole_rl_env.py 存在于 isaaclab/source/standalone/tutorials/03_envs 目录中。该脚本与前一个教程中的 cartpole_base_env.py 脚本类似,只是它使用 envs.ManagerBasedRLEnv 而不是 envs.ManagerBasedEnv

run_cartpole_rl_env.py 代码
 1# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
 2# All rights reserved.
 3#
 4# SPDX-License-Identifier: BSD-3-Clause
 5
 6"""This script demonstrates how to run the RL environment for the cartpole balancing task."""
 7
 8"""Launch Isaac Sim Simulator first."""
 9
10import argparse
11
12from omni.isaac.lab.app import AppLauncher
13
14# add argparse arguments
15parser = argparse.ArgumentParser(description="Tutorial on running the cartpole RL environment.")
16parser.add_argument("--num_envs", type=int, default=16, help="Number of environments to spawn.")
17
18# append AppLauncher cli args
19AppLauncher.add_app_launcher_args(parser)
20# parse the arguments
21args_cli = parser.parse_args()
22
23# launch omniverse app
24app_launcher = AppLauncher(args_cli)
25simulation_app = app_launcher.app
26
27"""Rest everything follows."""
28
29import torch
30
31from omni.isaac.lab.envs import ManagerBasedRLEnv
32
33from omni.isaac.lab_tasks.manager_based.classic.cartpole.cartpole_env_cfg import CartpoleEnvCfg
34
35
36def main():
37    """Main function."""
38    # create environment configuration
39    env_cfg = CartpoleEnvCfg()
40    env_cfg.scene.num_envs = args_cli.num_envs
41    # setup RL environment
42    env = ManagerBasedRLEnv(cfg=env_cfg)
43
44    # simulate physics
45    count = 0
46    while simulation_app.is_running():
47        with torch.inference_mode():
48            # reset
49            if count % 300 == 0:
50                count = 0
51                env.reset()
52                print("-" * 80)
53                print("[INFO]: Resetting environment...")
54            # sample random actions
55            joint_efforts = torch.randn_like(env.action_manager.action)
56            # step the environment
57            obs, rew, terminated, truncated, info = env.step(joint_efforts)
58            # print current orientation of pole
59            print("[Env 0]: Pole joint: ", obs["policy"][0][1].item())
60            # update counter
61            count += 1
62
63    # close the environment
64    env.close()
65
66
67if __name__ == "__main__":
68    # run the main function
69    main()
70    # close sim app
71    simulation_app.close()

代码解释#

我们已经在 创建基于管理器的基础环境 教程中学习了上述部分,以了解如何指定场景、观测、动作和事件。因此,在本教程中,我们将只专注于环境的强化学习组件。

在 Isaac Lab 中,我们提供了 envs.mdp 模块中不同术语的各种实现。我们将在本教程中使用其中一些术语,但用户也可以自由定义自己的术语。这些通常被放置在他们任务特定的子包中(例如,在 omni.isaac.lab_tasks.manager_based.classic.cartpole.mdp 中)。

定义奖励#

managers.RewardManager 用于计算代理的奖励项。与其他管理器类似,它的术语是使用 managers.RewardTermCfg 配置的。 managers.RewardTermCfg 类指定了计算奖励的函数或可调用类,以及与之相关联的权重。它还使用 "params" 的参数字典,在奖励函数被调用时传递参数。

对于 cartpole 任务,我们将使用以下奖励项:

  • 存活奖励:鼓励代理尽可能长时间保持存活状态。

  • 终止奖励:同样惩罚代理的终止。

  • 杆角度奖励:鼓励代理保持杆在期望的直立位置。

  • 小车速度奖励:鼓励代理尽可能保持小车速度较小。

  • 杆速度奖励:鼓励代理尽可能保持杆速度较小。

@configclass
class RewardsCfg:
    """Reward terms for the MDP."""

    # (1) Constant running reward
    alive = RewTerm(func=mdp.is_alive, weight=1.0)
    # (2) Failure penalty
    terminating = RewTerm(func=mdp.is_terminated, weight=-2.0)
    # (3) Primary task: keep pole upright
    pole_pos = RewTerm(
        func=mdp.joint_pos_target_l2,
        weight=-1.0,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]), "target": 0.0},
    )
    # (4) Shaping tasks: lower cart velocity
    cart_vel = RewTerm(
        func=mdp.joint_vel_l1,
        weight=-0.01,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"])},
    )
    # (5) Shaping tasks: lower pole angular velocity
    pole_vel = RewTerm(
        func=mdp.joint_vel_l1,
        weight=-0.005,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"])},
    )

定义终止条件#

大多数学习任务在有限数量的步骤中进行,我们称之为一个回合。例如,在 cartpole 任务中,我们希望代理尽可能长时间地保持杆的平衡。然而,如果代理达到不稳定或不安全状态,我们希望终止回合。另一方面,如果代理能够长时间保持杆平衡,我们希望终止回合并开始新的回合,以便代理可以学会从不同的起始配置中平衡杆。

managers.TerminationsCfg 配置了何时终止一个回合。在本例中,我们希望当满足以下任一条件时终止任务:

  • 回合长度:回合长度大于定义的最大回合长度。

  • 小车越界:小车走出边界 [-3, 3]。

标志 managers.TerminationsCfg.time_out 指定了术语是时间限制(截断)术语还是终止术语。这些用于指示 Gymnasium’s documentation 中描述的两种终止类型。

@configclass
class TerminationsCfg:
    """Termination terms for the MDP."""

    # (1) Time out
    time_out = DoneTerm(func=mdp.time_out, time_out=True)
    # (2) Cart out of bounds
    cart_out_of_bounds = DoneTerm(
        func=mdp.joint_pos_out_of_manual_limit,
        params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]), "bounds": (-3.0, 3.0)},
    )

定义命令#

对于各种目标条件的任务,指定代理的目标或命令是有用的。这通过 managers.CommandManager 处理。命令管理器在每一步中处理重新采样和更新命令。它还可以用作向代理提供命令的观测。

对于这个简单的任务,我们不使用任何命令。这通过使用 envs.mdp.NullCommandCfg 配置的命令项来指定。但是,您可以在 locomotion 或 manipulation 任务中看到命令定义的示例。

@configclass
class CommandsCfg:
    """Command terms for the MDP."""

    # no commands for this MDP
    null = mdp.NullCommandCfg()

定义课程#

在训练学习代理时,往往从一个简单的任务开始,并随着代理的训练逐渐增加任务的难度。这就是课程学习的理念。在 Isaac Lab 中,我们提供了一个 managers.CurriculumManager 类,可以用来为您的环境定义课程。

在本教程中,为了简单起见,我们不实现课程,但是您可以在其他 locomotion 或 manipulation 任务中看到课程定义的示例。我们使用一个简单的经过课程来定义一个不修改环境的课程管理器。

@configclass
class CurriculumCfg:
    """Configuration for the curriculum."""

    pass

将所有内容联系起来#

通过定义上述所有组件,我们现在可以为 cartpole 环境创建 ManagerBasedRLEnvCfg 配置。这类似于 创建基于管理器的基础环境 中定义的 ManagerBasedEnvCfg ,只是在上述部分中添加了解释的强化学习组件。

@configclass
class CartpoleEnvCfg(ManagerBasedRLEnvCfg):
    """Configuration for the locomotion velocity-tracking environment."""

    # Scene settings
    scene: CartpoleSceneCfg = CartpoleSceneCfg(num_envs=4096, env_spacing=4.0)
    # Basic settings
    observations: ObservationsCfg = ObservationsCfg()
    actions: ActionsCfg = ActionsCfg()
    events: EventCfg = EventCfg()
    # MDP settings
    curriculum: CurriculumCfg = CurriculumCfg()
    rewards: RewardsCfg = RewardsCfg()
    terminations: TerminationsCfg = TerminationsCfg()
    # No command generator
    commands: CommandsCfg = CommandsCfg()

    # Post initialization
    def __post_init__(self) -> None:
        """Post initialization."""
        # general settings
        self.decimation = 2
        self.episode_length_s = 5
        # viewer settings
        self.viewer.eye = (8.0, 0.0, 5.0)
        # simulation settings
        self.sim.dt = 1 / 120
        self.sim.render_interval = self.decimation

运行模拟循环#

回到 run_cartpole_rl_env.py 脚本,模拟循环类似于之前的教程。唯一的区别是,我们创建了一个 envs.ManagerBasedRLEnv 的实例,而不是 envs.ManagerBasedEnv 。因此,现在 envs.ManagerBasedRLEnv.step() 方法返回额外的信号,例如奖励和终止状态。信息字典还保持记录诸如来自各个术语奖励的贡献,每个术语的终止状态,回合长度等的日志。

def main():
    """Main function."""
    # create environment configuration
    env_cfg = CartpoleEnvCfg()
    env_cfg.scene.num_envs = args_cli.num_envs
    # setup RL environment
    env = ManagerBasedRLEnv(cfg=env_cfg)

    # simulate physics
    count = 0
    while simulation_app.is_running():
        with torch.inference_mode():
            # reset
            if count % 300 == 0:
                count = 0
                env.reset()
                print("-" * 80)
                print("[INFO]: Resetting environment...")
            # sample random actions
            joint_efforts = torch.randn_like(env.action_manager.action)
            # step the environment
            obs, rew, terminated, truncated, info = env.step(joint_efforts)
            # print current orientation of pole
            print("[Env 0]: Pole joint: ", obs["policy"][0][1].item())
            # update counter
            count += 1

    # close the environment
    env.close()

代码执行#

与之前的教程类似,可以通过执行 run_cartpole_rl_env.py 脚本来运行环境。

./isaaclab.sh -p source/standalone/tutorials/03_envs/run_cartpole_rl_env.py --num_envs 32

这应该会打开与上一个教程中类似的模拟。然而,这次,环境返回了更多的信号,指定了奖励和终止状态。此外,各个环境在根据配置中指定的终止条件终止时会重新进行重置。

run_cartpole_rl_env.py 的结果

要停止模拟,您可以关闭窗口,或者在启动模拟的终端中按 Ctrl+C

在本教程中,我们学习了如何为强化学习创建任务环境。我们通过扩展基础环境来包括奖励、终止条件、命令和课程术语来实现这一点。我们还学习了如何使用 envs.ManagerBasedRLEnv 类来运行环境并从中接收各种信号。

虽然可以手动为所需的任务创建 envs.ManagerBasedRLEnv 类的实例,但这并不可伸缩,因为它需要为每个任务使用专门的脚本。因此,我们利用 gymnasium.make() 函数来创建具有 gym 接口的环境。我们将在下一个教程中学习如何做到这一点。