与 RL Agent 进行训练#
在之前的教程中,我们介绍了如何定义一个 RL 任务环境、将其注册到 gym 注册表中,并使用一个随机 agent 与其交互。现在我们继续进行下一步: 训练一个 RL agent 来解决这个任务。
尽管 envs.ManagerBasedRLEnv 符合 gymnasium.Env 接口,但它并不完全是一个 gym 环境。环境的输入和输出不是 numpy 数组,而是基于 torch tensors,其中第一个维度是环境实例的数量。
此外,大多数 RL 库都期望其自己的环境接口变体。例如, Stable-Baselines3 期望环境符合其 VecEnv API ,该 API 期望接收一个 numpy 数组列表而不是一个单一的张量。类似地, RSL-RL 、 RL-Games 和 SKRL 也预期另一个接口。由于没有一种适合所有情况的解决方案,我们不将 envs.ManagerBasedRLEnv 基于任何特定的学习库。相反,我们实现了包装器来将环境转换为所期望的接口。这些包装器在 isaaclab_rl 模块中指定。
在本教程中,我们将使用 Stable-Baselines3 来训练一个 RL agent 来解决 cartpole 平衡任务。
小心
在应用所有其他包装器后,应在最后使用相应学习框架的包装器包装环境。这是因为学习框架的包装器会修改环境 API 的解释,这可能不再与 gymnasium.Env 兼容。
代码#
本教程使用 Stable-Baselines3 工作流的训练实现,位于 isaaclab_rl.entrypoints.backends.train_sb3 模块。
train_sb3.py 代码
1# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""Stable-Baselines3 training backend of the unified reinforcement learning entrypoint."""
7
8from __future__ import annotations
9
10import argparse
11import contextlib
12import gc
13import os
14import signal
15import sys
16import time
17from datetime import datetime
18from pathlib import Path
19
20import numpy as np
21from stable_baselines3 import PPO
22from stable_baselines3.common.callbacks import CheckpointCallback, LogEveryNTimesteps
23from stable_baselines3.common.vec_env import VecNormalize
24
25from isaaclab.app import add_launcher_args, launch_simulation, report_activity
26from isaaclab.envs import DirectMARLEnvCfg
27from isaaclab.utils.seed import configure_seed
28
29import isaaclab_tasks # noqa: F401
30from isaaclab_tasks.utils import resolve_task_config, setup_preset_cli
31
32from ...sb3 import Sb3VecEnvWrapper, process_sb3_cfg
33from ..common import (
34 CHECKPOINT_SELECTORS,
35 add_common_train_args,
36 apply_env_overrides,
37 apply_video_recording,
38 close_env,
39 create_isaaclab_env,
40 dump_train_configs,
41 enable_cameras_for_video,
42 pre_launch_video_config,
43 resolve_checkpoint_selector,
44 resolve_seed,
45 set_hydra_args,
46 show_run_summary,
47 startup_screen,
48 wrap_sensor_capture,
49 write_run_manifest,
50)
51
52# PLACEHOLDER: Extension template (do not remove this comment)
53with contextlib.suppress(ImportError):
54 import isaaclab_tasks_experimental # noqa: F401
55
56
57def _cleanup_pbar(*args) -> None:
58 """Stop training and close the rich progress bars on Ctrl+C."""
59 for obj in gc.get_objects():
60 if "tqdm_rich" in type(obj).__name__:
61 obj.close()
62 raise KeyboardInterrupt
63
64
65def _parse_args(argv: list[str]) -> argparse.Namespace:
66 """Parse Stable-Baselines3 training arguments."""
67 parser = argparse.ArgumentParser(description="Train an RL agent with Stable-Baselines3.")
68 add_common_train_args(
69 parser,
70 agent_default="sb3_cfg_entry_point",
71 agent_help="Name of the RL agent configuration entry point.",
72 include_distributed=False,
73 )
74 parser.add_argument("--log_interval", type=int, default=100_000, help="Log data every n timesteps.")
75 parser.add_argument("--checkpoint", type=str, default=None, help="Checkpoint path, or latest/best.")
76 parser.add_argument(
77 "--keep_all_info",
78 action="store_true",
79 default=False,
80 help="Use a slower SB3 wrapper but keep all the extra training info.",
81 )
82 add_launcher_args(parser)
83 args_cli, hydra_args = setup_preset_cli(parser, argv)
84 enable_cameras_for_video(args_cli)
85 set_hydra_args(hydra_args)
86 return args_cli
87
88
89def _resolve_checkpoint(args_cli: argparse.Namespace, log_root_path: str) -> str | None:
90 """Resolve the checkpoint to resume from, or None when training starts from scratch."""
91 if args_cli.checkpoint in CHECKPOINT_SELECTORS:
92 return resolve_checkpoint_selector(
93 log_root_path,
94 args_cli.checkpoint,
95 library="sb3",
96 task=args_cli.task,
97 checkpoint_pattern=r"model(?:_.*)?\.zip",
98 preferred_checkpoint_pattern=r"model\.zip",
99 metadata={"agent": args_cli.agent},
100 )
101 return args_cli.checkpoint
102
103
104def run(argv: list[str]) -> None:
105 """Train a Stable-Baselines3 agent."""
106 signal.signal(signal.SIGINT, _cleanup_pbar)
107 args_cli = _parse_args(argv)
108 with startup_screen(args_cli, num_stages=3) as screen:
109 env_cfg, agent_cfg = resolve_task_config(args_cli.task, args_cli.agent)
110 pre_launch_video_config(env_cfg, args_cli)
111 show_run_summary(screen, args_cli, env_cfg, library="sb3", action="train")
112 screen.stage("Launching simulation")
113 with launch_simulation(env_cfg, args_cli), contextlib.ExitStack() as cleanup:
114 apply_env_overrides(args_cli, env_cfg)
115 args_cli.seed = resolve_seed(args_cli.seed)
116 if args_cli.seed is not None:
117 agent_cfg["seed"] = args_cli.seed
118 if args_cli.max_iterations is not None:
119 agent_cfg["n_timesteps"] = args_cli.max_iterations * agent_cfg["n_steps"] * env_cfg.scene.num_envs
120 env_cfg.seed = agent_cfg["seed"]
121
122 log_root_path = os.path.abspath(os.path.join("logs", "sb3", args_cli.task))
123 print(f"[INFO] Logging experiment in directory: {log_root_path}")
124 run_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
125 print(f"Exact experiment name requested from command line: {run_name}")
126 log_dir = os.path.join(log_root_path, run_name)
127 write_run_manifest(log_dir, library="sb3", task=args_cli.task, metadata={"agent": args_cli.agent})
128 dump_train_configs(log_dir, env_cfg, agent_cfg)
129 (Path(log_dir) / "command.txt").write_text(" ".join(sys.orig_argv))
130
131 checkpoint_path = _resolve_checkpoint(args_cli, log_root_path)
132 agent_cfg = process_sb3_cfg(agent_cfg, env_cfg.scene.num_envs)
133 policy_arch = agent_cfg.pop("policy")
134 n_timesteps = agent_cfg.pop("n_timesteps")
135 norm_args = {}
136 for key in ("normalize_input", "normalize_value", "clip_obs"):
137 if key in agent_cfg:
138 norm_args[key] = agent_cfg.pop(key)
139
140 env_cfg.log_dir = log_dir
141 apply_video_recording(env_cfg, log_dir, args_cli)
142
143 screen.stage("Creating environment")
144 env = create_isaaclab_env(
145 args_cli.task,
146 env_cfg,
147 args_cli,
148 convert_marl_to_single_agent=isinstance(env_cfg, DirectMARLEnvCfg),
149 )
150 cleanup.callback(lambda: close_env(env))
151 env = wrap_sensor_capture(env, log_dir, args_cli)
152
153 screen.stage("Preparing agent")
154 start_time = time.time()
155 report_activity("Wrapping environment")
156 env = Sb3VecEnvWrapper(env, fast_variant=not args_cli.keep_all_info)
157 report_activity(None)
158 if norm_args.get("normalize_input"):
159 print(f"Normalizing input, {norm_args=}")
160 env = VecNormalize(
161 env,
162 training=True,
163 norm_obs=norm_args["normalize_input"],
164 norm_reward=norm_args.get("normalize_value", False),
165 clip_obs=norm_args.get("clip_obs", 100.0),
166 gamma=agent_cfg["gamma"],
167 clip_reward=np.inf,
168 )
169
170 report_activity("Building policy")
171 agent = PPO(policy_arch, env, verbose=1, tensorboard_log=log_dir, **agent_cfg)
172 report_activity(None)
173 if checkpoint_path is not None:
174 agent = agent.load(checkpoint_path, env, print_system_info=True)
175
176 # configure_seed must run after PPO construction and loading so torch determinism does not disturb
177 # SB3's initialization
178 if args_cli.deterministic:
179 configure_seed(env_cfg.seed, torch_deterministic=True)
180
181 callbacks = [
182 CheckpointCallback(save_freq=1000, save_path=log_dir, name_prefix="model", verbose=2),
183 LogEveryNTimesteps(n_steps=args_cli.log_interval),
184 ]
185
186 screen.close()
187 with contextlib.suppress(KeyboardInterrupt):
188 agent.learn(total_timesteps=n_timesteps, callback=callbacks, progress_bar=True, log_interval=None)
189
190 agent.save(os.path.join(log_dir, "model"))
191 print(f"Saving to:\n{os.path.join(log_dir, 'model.zip')}")
192 if isinstance(env, VecNormalize):
193 print("Saving normalization")
194 env.save(os.path.join(log_dir, "model_vecnormalize.pkl"))
195 print(f"Training time: {round(time.time() - start_time, 2)} seconds")
代码解释#
上面的大部分代码是创建日志目录、保存解析的配置和设置不同的 Stable-Baselines3 组件的样板代码。对于本教程,重要的部分是创建环境并使用 Stable-Baselines3 包装器对其进行包装。
代码中使用了三个包装器:
gymnasium.wrappers.RecordVideo: 这个包装器记录环境的视频并将其保存到指定目录。这对于在训练过程中可视化 agent 的行为非常有用。wrappers.sb3.Sb3VecEnvWrapper: 这个包装器将环境转换为 Stable-Baselines3 兼容的环境。stable_baselines3.common.vec_env.VecNormalize: 这个包装器对环境的观测和奖励进行标准化。
这些包装器中的每一个都通过反复执行 env = wrapper(env, *args, **kwargs) 来包装前一个包装器。然后使用最终的环境来训练 agent。有关这些包装器如何工作的更多信息,请参考 包装环境 文档。
代码执行#
我们训练一个从 Stable-Baselines3 学习的 PPO agent 来解决 cartpole 平衡任务。
训练 agent#
训练 agent 有三种主要方法。每种方法都有其自己的优点和缺点。根据您的用例,您可以决定使用哪种方法。
无界面执行#
未请求可视化器时,训练期间不会打开交互式可视化窗口。这适用于远程服务器训练,或不需要实时画面反馈的情况,因为实时反馈会增加一些计算开销。工作流启用传感器或相机数据采集后,渲染仍可保持运行。
uv run --extra sb3 isaaclab train --rl_library sb3 --task Isaac-Cartpole --num_envs 64
./isaaclab.sh train --rl_library sb3 --task Isaac-Cartpole --num_envs 64
无界面执行与离屏渲染#
需要查看训练过程的行为,但不打开交互窗口时,可传入 --video。默认通过无界面 Kit 可视化器录制场景,因此需要 Isaac Sim。选择其他可视化器或相机传感器输出的方法见 录制视频 。
uv run --extra sb3,video isaaclab train --rl_library sb3 --task Isaac-Cartpole --num_envs 64 --video
./isaaclab.sh train --rl_library sb3 --task Isaac-Cartpole --num_envs 64 --video
视频保存在 logs/sb3/Isaac-Cartpole/<run-dir>/videos/train 目录,可以用任意视频播放器打开。
对于场景中带有相机的任务,还可以通过 --capture_env_sensors 在训练期间直接保存传感器图像输出。可用选项和输出格式见 在训练期间采集传感器帧 。
交互式执行#
上述两种方式可用于训练智能体,但无法与仿真交互、观察实时情况。如果需要交互,可按以下方式运行训练命令:
uv run --extra sb3 isaaclab train --rl_library sb3 --task Isaac-Cartpole --num_envs 64 --viz kit
./isaaclab.sh train --rl_library sb3 --task Isaac-Cartpole --num_envs 64 --viz kit
这会打开 Kit 可视化窗口,可以看到智能体在环境中训练。不过,启用交互式画面反馈可能降低训练速度。可以在屏幕右下角停靠的 "Isaac Lab" 窗口中切换不同渲染模式。更多信息见 sim.SimulationContext.RenderMode 。
查看日志#
在单独的终端中,您可以通过执行以下命令监视训练进度:
# execute from the root directory of the repository
uv run python -m tensorboard.main --logdir logs/sb3/Isaac-Cartpole
# execute from the root directory of the repository
./isaaclab.sh -p -m tensorboard.main --logdir logs/sb3/Isaac-Cartpole
播放经过训练的 agent#
一旦训练完成,您可以通过执行以下命令来可视化经过训练的 agent:
# execute from the root directory of the repository
uv run --extra sb3 isaaclab play --rl_library sb3 --task Isaac-Cartpole --num_envs 32 --viz kit
# execute from the root directory of the repository
./isaaclab.sh play --rl_library sb3 --task Isaac-Cartpole --num_envs 32 --viz kit
上述命令加载 logs/sb3/Isaac-Cartpole 目录中的最新检查点。也可以用 --checkpoint 指定某个检查点。