若需观看机器人系列相关博客,请劳驾至:【足式机器人无死角系列之-【强化学习基础-通用】、【仿真及训练环境】、【强化学习】:isaac-gym 与 isaac-lab 从零开始
郑重声明:该系列博客为本人 ( W e n h a i Z h u ) 独家私有 , 禁止转载与抄袭 , 首次举报有谢 , 若有需请私信授权! \color{red}郑重声明:该系列博客为本人(WenhaiZhu)独家私有,禁止转载与抄袭,首次举报有谢,若有需请私信授权! 郑重声明:该系列博客为本人(WenhaiZhu)独家私有,禁止转载与抄袭,首次举报有谢,若有需请私信授权!
本系列博客链接为: {\color{blue}本系列博客链接为:} 本系列博客链接为:【05.isaac-lab】最新从零无死角系列-(00) 目录最新无死角源码讲解:https://blog.youkuaiyun.com/weixin_43013761/article/details/143084425
上一篇博客,了解 Isaac-sim 设计理念与基本结构。其中包含任务设计工作流程、强化学习库比较、Hydra 配置系统。目前 isaac-lab 支持的强化学习库有 SKRL、 rsl_rl、 rl_games、 Stable-Baselines3。后续过程中,首先以 rsl_rl 库为主,进行学习与讲解,因为本人方向主要为足式机器人,且对该库比较熟悉。不过也会进行拓展,对其他强化学习库也进行讲解与剖析。 思来想去,本人最终于还是决定从主体框架入手进行讲解,只有知道整体流程是如何的,在后续分析细节时才能不迷惘,所以顺着以下思路对代码进行剖析: √ 1: 了解 Isaac-sim 设计理念与基本结构。 √ 2: 掌握训练过程的总体流程(以任务为导向)。 3: 环境与强化学习库,或者强化学习算法是如何联系到一起的 --> 该篇博客。 4: ......待定 注意:如果有提示大家,[大致过一遍即可,不用在意细节] 的地方,就不要钻牛角尖,快速阅读一下即可,后面会回过来百分百的进行详细分析。 |
本博客编写于: 20250105 ,台式机为 u b u n t u 20.04 , 3090 G e F o r c e R T X 显存 24 G { \color{purple} 本博客编写于:20250105,台式机为 ubuntu 20.04,3090 GeForce RTX 显存24G} 本博客编写于:20250105,台式机为ubuntu20.04,3090GeForceRTX显存24G:与你现在的代码,或者环境等存在一定差异也在情理之中,故切勿认为该系列博客绝对正确,且百密必有一疏,若发现错误处,恳请各位读者直接指出,本人会尽快进行整改,尽量使得后面的读者少踩坑,评论部分我会进行记录与感谢,只有这样,该系列博客才能成为精品,这里先拜谢各位朋友了。
文末正下方中心提供了本人 联系方式, 点击本人照片即可显示 W X → 官方认证,请备注 强化学习 。 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证,请备注\color{red} 强化学习}。 文末正下方中心提供了本人联系方式,点击本人照片即可显示WX→官方认证,请备注强化学习。
一、前言
正式分析代码之前,先来说一个实用的功能,那就是训练过程中可以通过 “–video”、“–video_length” 等相关参数进行可视化界面的录制(使用无头训练,即附加 “–headless” 指令,也可录制),比如说本人训练 go2 机器狗执行如下指令:
# 训练
python scripts/reinforcement_learning/rsl_rl/train.py --task Isaac-Velocity-Rough-Unitree-Go2-v0 --headless --video --video_length 1000 --video_interval 5000
"--video" : 附加该参数,则表示启用视屏录制
"--video_length": 每个视屏录制的 step 总数(与环境交互的总次数)
"--video_interval":间隔多少个 step 数进行一次视屏录制
然后等待其训练一段时间之后,IsaacLab 源码目录下可以看到如下结构
logs/
└── rsl_rl
└── unitree_go2_rough
└── 2025-01-11_14-47-31
├── events.out.tfevents.1736578071.ub20-04.5698.0
├── git
│ └── IsaacLab.diff
├── model_0.pt
├── params
│ ├── agent.pkl
│ ├── agent.yaml
│ ├── env.pkl
│ └── env.yaml
└── videos
└── train
├── rl-video-step-0.meta.json
└── rl-video-step-0.mp4
其中 logs/rsl_rl/unitree_go2_rough/2025-01-11_14-47-31/videos/train/rl-video-step-0.mp4 就是录制下来的视屏,本人截图如下所示:
二、视屏录制包装器
上一篇博客对 scripts/reinforcement_learning/rsl_rl/train.py 源码进行分析时,知道其使用了两个包装器,使用到三个包装器,相关代码如下所示:
# STEP: 5.如果任务为多类智能体任务,则将多类智能体任务转换为单类智能体任务,这是因为 RSL-RL 默认只支持单类智能体算法
env = multi_agent_to_single_agent(env)
# STEP: 6.视屏录制包装器,通过 gym.wrappers.RecordVideo 包装之后,训练过程中可根据参数自动录制视频
env = gym.wrappers.RecordVideo(env, **video_kwargs) # 将 env 包装为 gym.wrappers.RecordVideo 对象
# STEP: 7.将环境包装为 RSL-RL 的向量(并行-多智能体)环境,简单的说就是对 env 输出结果进行处理,使其符合 OnPolicyRunner 的输入
env = RslRlVecEnvWrapper(env)
所谓的 env 包装器,就是在原本 env 基础上进行功能的增加或者数据格式的转换,这里先对 gym.wrappers.RecordVideo 这个包装器讲解,使用该包装器的相关源码如下所示:
# STEP: 6.视屏录制包装器,通过 gym.wrappers.RecordVideo 包装之后,训练过程中可根据参数自动录制视频
# wrap for video recording
if args_cli.video:
video_kwargs = {
"video_folder": os.path.join(log_dir, "videos", "train"), # 视频保存目录
"step_trigger": lambda step: step % args_cli.video_interval == 0, # 每间隔 video_interval 步,该函数返回 True,则开始录制视频
"video_length": args_cli.video_length, # 视频录制的长度
"disable_logger": True, # 是否禁用日志记录
}
print("[INFO] Recording videos during training.")
print_dict(video_kwargs, nesting=4)
env = gym.wrappers.RecordVideo(env, **video_kwargs) # 将 env 包装为 gym.wrappers.RecordVideo 对象
包装器 gym.wrappers.RecordVideo 并非由 IsaacLab 提供或封装,而是由 isaac_sim 的源码提供的,其实现于源码中的 /isaac-sim/exts/omni.isaac.ml_archive/pip_prebundle/gymnasium/wrappers/record_video.py 文件。这里不对 RecordVideo 细节进行详细分析,因为个人觉得会使用这个包装器录制训练过程中的视屏即可,并不是啥核心代码,主要对如下几个核心部分进行介绍:
1.初始化函数
于 /isaac-sim/exts/omni.isaac.ml_archive/pip_prebundle/gymnasium/wrappers/record_video.py 文件可以看到 RecordVideo 初始化函数参数:
def __init__(
self,
env: gym.Env,
video_folder: str,
episode_trigger: Callable[[int], bool] = None,
step_trigger: Callable[[int], bool] = None,
video_length: int = 0,
name_prefix: str = "rl-video",
disable_logger: bool = False,
):
"""Wrapper records videos of rollouts.
Args:
env: The environment that will be wrapped, 需要被包装的 gym.Env 实例对象
video_folder (str): The folder where the recordings will be stored 视频保存目录
episode_trigger: Function that accepts an integer and returns ``True`` iff a recording should be started at this episode
接受一个函数,该函数输入一个整数(当前 episode 数),返回一个布尔值,表示是否应该在这个 episode 开始录制视频
step_trigger: Function that accepts an integer and returns ``True`` iff a recording should be started at this step
接受一个函数,该函数输入一个整数(当前 step 数),返回一个布尔值,表示是否应该在这个 step 开始录制视频
video_length (int): The length of recorded episodes. If 0, entire episodes are recorded.
Otherwise, snippets of the specified length are captured
视频录制的长度, 如果为0, 录制整个 episode, 否则录制指定长度的视频
name_prefix (str): Will be prepended to the filename of the recordings # 视频文件名前缀
disable_logger (bool): Whether to disable moviepy logger or not.
是否禁用 moviepy logger, 禁用则即不打印且不记录日志
"""
scripts/reinforcement_learning/rsl_rl/train.p 代码中默认只设置了 step_trigger 函数,如下所示:
"step_trigger": lambda step: step % args_cli.video_interval == 0, # 每间隔 video_interval 步,该函数返回 True,则开始录制视频
其接受一个 int 参数为当前 step 数,若判断已经间隔 video_interval 数则进行视屏录制,从这里可以知道的一点是,video_interval 参数最好大于 video_length 参数,否则可能出现一些意料之外的情况。
2.step(self, action)
有的朋友可能对视屏的渲染与录制视屏过程比较好奇,比如说保存为视屏的图像是如何获取的,本人并没有深入对源码进行分析,大致梳理后知道其获取仿真界面图像画面的调用过程如下所示:
def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agent_cfg: RslRlOnPolicyRunnerCfg):
runner.learn(num_learning_iterations=agent_cfg.max_iterations, init_at_random_ep_len=True)
obs, rewards, dones, infos = self.env.step(actions)
obs_dict, rew, terminated, truncated, extras = self.env.step(actions)
self.video_recorder.capture_frame()
frame = self.env.render()
self.recorded_frames.append(frame)
直白的说,其就是通过 ManagerBasedRLEnv 实例对象的 render(self, recompute: bool = False) 函数获取经过渲染后的视屏帧,添加到 VideoRecorder 对象成员 self.recorded_frames 之中,当帧数足够时则会统一保存为视屏。具体的细节这里就是不再多说了。
三、数据格式
讲解完视屏录制包装器之后,来讲解比较重要的强化学习库包装器,前面博客 【05.issa-lab】最新从零无死角系列-(03) isaac-lab之框架剖析,Omniverse、Isaac-sim 、Isaac-lab 对比,任务流程,Hydra 配置,强化学习库比较 有提到:
强化学习的库主流的有好几个,比如说:SKRL、RSL-RL、RL-Games 等,要知道他们的参数配置,数据格式,以及输入标准都是不一样的,isaac-lab 是如何对他们进行统一的呢?这个就要涉及到强化学习包装器 |
该包装器的目的其实十分简单,就是把强化学习库 SKRL、RSL-RL、RL-Games 的输出结果,转换成 train.py 代码中:
env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None)
创建的 env 对象能够接收的数据格式即可,那么具体又是实现的呢?
1.RSL-RL接收格式
本人这里以 RSL-RL 这个这个强化学习库为例进行探索,其他的强化学习库略有不同,但是大致上来说,流程与实现逻辑都是一致的,若后续本人使用到其他强化学习库,也会单独进行详细分析。首先来说,通过前面博客知道,关于强化学习库的主要代码入口如下所示,位于 source/standalone/workflows/rsl_rl/train.py:
runner = OnPolicyRunner(env, agent_cfg.to_dict(), log_dir=log_dir, device=agent_cfg.device)
runner.learn(num_learning_iterations=agent_cfg.max_iterations, init_at_random_ep_len=True)
通过对 runner.learn 函数进行追踪可以看到如下代码:
runner.learn(num_learning_iterations=agent_cfg.max_iterations, init_at_random_ep_len=True)
def learn(self, num_learning_iterations: int, init_at_random_ep_len: bool = False):
for it in range(start_iter, tot_iter): # 循环迭代,迭代次数为 num_learning_iterations
for i in range(self.num_steps_per_env): # 循环与环境进行交互,每次交互的步数为 num_steps_per_env
obs, rewards, dones, infos = self.env.step(actions) # 与环境进行一次交互,获取观测、奖励、终止状态、额外信息
先不讨论具体细节,可以知道每次训练迭代都会与环境交互 self.num_steps_per_env 个 step,每次交互都获取到 4 个结构数据,分别为:
tuple[torch.Tensor, torch.Tensor, torch.Tensor, dict]
通过上述分析,可以知道 RSL-RL 强化学习库在训练过程中,每次与环境交互需要获取 obs, rewards, dones, infos 这四个数据。那么当然需要满足 RSL-RL 的需求,毕竟 RSL-RL 不属于 isaac-lab,而是一个安装库,当然是不能去修改别人的源码的。
2.ManagerBasedRLEnv输出格式
通过上面的分析,知道了强化学习库在训练过程中,每次与环境交互需要获取 obs, rewards, dones, infos(具体含义后面章节详细分析) 这四个数据。不过可惜的是,预定义好的 isaac-lab 仿真环境 ManagerBasedRLEnv 示例对象,进行环境交互之后并不是返回这四个数据。首先来看看为什么是 ManagerBasedRLEnv 实例对象完成了与环境的交互。
首先来来说要回顾一下,执行训练的指令参数 --task Isaac-Velocity-Rough-Unitree-Go2-v0,其表明训练的任务为 Isaac-Velocity-Rough-Unitree-Go2-v0,那么该参数与那么些配置文件或者类对象有关联呢?在源码中搜索,可以在
source/extensions/omni.isaac.lab_tasks/omni/isaac/lab_tasks/manager_based/locomotion/velocity/config/go2/_init_.py
文件中看到如下代码:
gym.register(
id="Isaac-Velocity-Rough-Unitree-Go2-v0",
entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv",
disable_env_checker=True,
kwargs={
"env_cfg_entry_point": f"{__name__}.rough_env_cfg:UnitreeGo2RoughEnvCfg",
"rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:UnitreeGo2RoughPPORunnerCfg",
"skrl_cfg_entry_point": f"{agents.__name__}:skrl_rough_ppo_cfg.yaml",
},
)
这里就不对其进行细节讨论了,因为下一篇博客会进行详细的分析,这里只需要关注:
entry_point="omni.isaac.lab.envs:ManagerBasedRLEnv"
该配置表明整个仿真环境的入口类为 omni.isaac.lab.envs:ManagerBasedRLEnv,直白说 scripts/reinforcement_learning/rsl_rl/train.py 中代码
env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None)
就是构建这 omni.isaac.lab.envs:ManagerBasedRLEnv 的实例,该类实现于 source/isaaclab/isaaclab/envs/manager_based_rl_env.py 文件中,该篇博客不做细节讲解,只需知道其中包含函数 def step(self, action: torch.Tensor),其返回值如下:
def step(self, action: torch.Tensor) -> VecEnvStepReturn:
......
# return observations, rewards, resets and extras
return self.obs_buf, self.reward_buf, self.reset_terminated, self.reset_time_outs, self.extras
易知 ManagerBasedRLEnv 与环境交互之后其返回 self.obs_buf, self.reward_buf, self.reset_terminated, self.reset_time_outs, self.extras(具体含义后面章节详细分析) 这里 5 个数据,比强化学习框架训练所需要的 4 个数据 obs, rewards, dones, infos 多出一个。
四、强化学习库包装
通过上面的分析可以知道强化学习库 RSL-RL 每次所需要的数据为 obs, rewards, dones, infos,而仿真环境 ManagerBasedRLEnv 获取到的数据为 self.obs_buf, self.reward_buf, self.reset_terminated, self.reset_time_outs, self.extras。其中部分数据已经对齐,如下:
self.obs_buf=obs self.reward_buf=rewards self.extras=infos
\color{purple} \text {self.obs\_buf=obs ~~~~~~~~~self.reward\_buf=rewards~~~~~~~~self.extras=infos}
self.obs_buf=obs self.reward_buf=rewards self.extras=infos也就是说,只有 dones 与 self.reset_terminated、self.reset_time_outs 没有对应起来,那么如何对应起来呢?其实也比较简单,因为前面 dones 就是后者 self.reset_terminated 与 self.reset_time_outs 合并之后的结果。
为什么这样说呢?dones 表示智能体挂掉了,但是没有具体细分是如何挂掉的,比如说摔倒了,或者通过了这都属于 self.reset_terminated,若是生命周期达到极限了(比如说卡住,或者没有找到终点)则属于 self.reset_time_outs。总的来说,dones 包含了后面的两种情况。
源码 scripts/reinforcement_learning/rsl_rl/train.py 中的 env = RslRlVecEnvWrapper(env) 其主要作用就是把从 ManagerBasedRLEnv 实例与环境交互获取到的 self.reset_terminated、self.reset_time_outs 合并成 dones,让后传递给强化学习框架 RSL-RL 进行迭代训练,RslRlVecEnvWrapper 具体实现于 source/isaaclab_rl/isaaclab_rl/rsl_rl/vecenv_wrapper.py 文件中,值需要关注其函数 def step(self, actions: torch.Tensor) 即可,如下所示:
def step(self, actions: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, dict]:
# record step information
obs_dict, rew, terminated, truncated, extras = self.env.step(actions)
# compute dones for compatibility with RSL-RL
# 计算终止和截断的布尔值,并转换为 long 类型
dones = (terminated | truncated).to(dtype=torch.long)
# move extra observations to the extras dict
obs = obs_dict["policy"]
extras["observations"] = obs_dict
# move time out information to the extras dict
# this is only needed for infinite horizon tasks
if not self.unwrapped.cfg.is_finite_horizon:
extras["time_outs"] = truncated
# return the step information
return obs, rew, dones, extras
易知从环境 ManagerBasedRLEnv 获取到的相关数据与信息,其首先把 terminated(self.reset_terminated) 与 truncated(self.reset_time_outs) 合并成 dones,然后往字典 extras 中还额外添加了一些信息,最后返回即可。可以看到最后返回 4 个数据 obs, rew, dones, extras 与 强化学习库 RSL-RL 迭代所需数据是一致的。
五、结语
通过该篇博客,了解到 isaac-lab 中包装器这个比较重要的概念,其本质的作用主要充当转接口或装饰器。视屏录制包装器(gym.wrappers.RecordVideo),其类似于一个装饰器的作用,即为基础环境 ManagerBasedRLEnv 实例增加一个视屏录制功能。而后介绍的强化学库包装器(RslRlVecEnvWrapper)则类似于一个转接口的作用,对数据结构进行转换,但是并未添加新的功能。那么到这里为止就完成了第3点的讲解:
√ 3: 环境与强化学习库,或者强化学习算法是如何联系到一起的 --> 该篇博客。 |