Skip to content

Latest commit

 

History

History
423 lines (261 loc) · 29.1 KB

File metadata and controls

423 lines (261 loc) · 29.1 KB

仿真与机器人学基础

本文介绍了一些仿真和机器人学的基本概念,帮助更深入地理解 ManiSkill 的技术细节,例如什么是位姿、四元数的使用方式,以及什么是 Actor 和 Articulation 等等。

常见术语 / 约定

  • 位姿(Pose):在三维空间中,物体的位置和朝向的组合。ManiSkill/SAPIEN 中的位姿由三维位置和四维四元数组成。
  • 四元数(Quaternion):一种常用于表示旋转/朝向的数学工具,由四个值组成。ManiSkill/SAPIEN 使用 wxyz 格式的四元数。欲了解更多关于仿真中旋转表示的内容,可以参考这篇博文
  • Z轴为“上”:在 ManiSkill/SAPIEN 中,Z 轴被视为标准的“向上”方向。因此,像直立的高脚杯这样的物体,其长轴通常沿 Z 轴方向。

仿真对象

对于刚体仿真,即模拟那些在物理上不易变形的物体(如木块、计算机、墙壁等),ManiSkill/SAPIEN 提供了两种主要的对象类型:ActorArticulation

在仿真过程中,通常从重新配置步骤开始,将所有对象加载到仿真环境中。加载后,我们设置所有对象的位姿并初始化它们。

Actor

Actor 通常是“单一”的物体,当受到外力作用时,整个物体会一起移动而不会发生形变。例如,棒球棒、玻璃杯、墙壁等都可以视为 Actor。Actor 具有以下属性:

  • 位姿(pose):物体在三维空间中的位置和朝向。位置以米为单位。
  • 线速度(linear velocity):物体在 x、y、z 轴方向上的平移速度,单位为米/秒。
  • 角速度(angular velocity):物体在 x、y、z 轴方向上的角速度,单位为弧度/秒。

在仿真中,Actor 由两个主要元素组成:碰撞形状和视觉形状。

碰撞形状:

碰撞形状定义了物体在仿真中的物理交互方式。一个 Actor 可以由多个凸形碰撞形状组成。

需要注意的是,Actor 并不一定需要具有碰撞形状;它们可以是“幽灵”对象,仅用于视觉指示,而不参与物理交互。

视觉形状:

视觉形状定义了物体在仿真中的外观,用于渲染显示,但不影响物理仿真。

Actor 类型:动态、运动学、静态

Actor 有三种类型:

  • 动态(Dynamic):这些 Actor 完全参与物理仿真。当施加外力时,它们会根据物理定律做出响应。
  • 运动学(Kinematic):这些 Actor 部分参与物理仿真。施加外力时,它们不会变形或移动。然而,与这些 Actor 交互的动态物体会受到反作用力的影响。与动态 Actor 相比,运动学 Actor 占用更少的 CPU/GPU 资源,仿真速度更快。
  • 静态(Static):这些 Actor 与运动学 Actor 类似,但在加载到仿真环境后,其位姿无法更改。它们占用更少的 CPU/GPU 资源,仿真速度更快。墙壁、地板、橱柜等通常被建模为运动学或静态 Actor,因为在现实中它们通常不会移动或被破坏。

根据要仿真的任务,可能需要将某些对象设置为动态。例如,在模拟将杯子从桌子上移到架子上的任务时,杯子应设置为动态 Actor,而架子可以设置为运动学或静态 Actor。

Articulation(关节机构)

Articulation 由**连杆(Links)关节(Joints)**组成。在 ManiSkill/SAPIEN 中,每两个连杆之间通过一个关节连接。Articulation 通常通过 XML 或树形结构来定义,以表示更复杂的关节机构。例如,橱柜、冰箱、汽车等都可以视为 Articulation。

连杆(Links)

连杆与 Actor 类似,具有相同的属性,可以进行物理仿真和操作。不同之处在于,连杆必须通过特定的关节与另一个连杆连接。

关节(Joints)

关节主要有三种类型:固定关节、转动关节和滑动关节。

  • 固定关节(Fixed Joint):连接两个连杆,使它们的相对位置保持固定。这在定义 Articulation 时很有用,因为通过固定关节连接的连杆实际上可以视为一个整体。
  • 转动关节(Revolute Joint):类似于铰链,连接的连杆可以绕关节轴线旋转。
  • 滑动关节(Prismatic Joint):连接的连杆可以沿关节轴线滑动。

示例

考虑一个橱柜的例子。橱柜有一个底座连杆、一个顶部抽屉连杆和一个底部门连杆。

  • 底部抽屉与底座之间通过一个滑动关节连接,允许抽屉沿特定方向滑动。
  • 底部门与底座之间通过一个转动关节连接,允许门绕轴线旋转。

通过理解这些基本概念,可以更深入地参与 ManiSkill 的开发和应用,设计出更复杂和逼真的仿真场景。

GPU 仿真

ManiSkill 利用 PhysX 在 GPU 上进行物理仿真。这种做法与传统在 CPU 上的仿真方式有所不同,具体细节如下。建议阅读本节内容,以了解 GPU 仿真的基础知识和 ManiSkill 的设计原则,这将有助于编写更高效的代码和构建优化的 GPU 并行任务。

场景和子场景

在 GPU 并行化的框架下,可以在 GPU 上同时模拟成千上万次任务。在 ManiSkill/SAPIEN 中,通过将所有 Actor 和 Articulation 放入同一个 PhysX 场景,并为每个任务在该场景中创建一个称为 子场景 的小工作空间来实现这一点。

子场景的设计使得读取 Actor 位姿等数据时,自动将其预处理为相对于子场景中心的数据,而非整个 PhysX 场景。下图展示了如何组织 64 个子场景。请注意,每个子场景之间的距离由仿真配置中的 sim_config.spacing 参数定义,在构建任务时可以设置该参数。

SAPIEN 允许子场景位于任意位置,但 ManiSkill 通常选择具有固定间距参数的方形布局。需要注意的是,如果一个子场景中的对象超出了其工作空间,可能会影响其他子场景。这是用户在模拟大型场景(如房屋或户外环境)时常遇到的问题,例如当间距参数设置过低时,子场景 0 的对象可能与子场景 1 的对象发生交互。

GPU 仿真生命周期

在 ManiSkill 中,采用 Gym API 来创建、重置和推进环境。env.reset 过程包括一次性的重新配置,随后是初始化步骤:

  1. 重新配置:将对象(包括 Actor、Articulation、光源)加载到场景中,即在初始位姿下生成它们。
  2. 调用 physx_system.gpu_init() 来初始化所有 GPU 内存缓冲区,并设置并行渲染所需的渲染组。
  3. 初始化所有 Actor 和 Articulation(设置位姿、qpos 值等)。
  4. 运行 physx_system.gpu_apply_*,将第 3 步中初始化的数据保存到 GPU 缓冲区,以准备仿真。
  5. 运行 physx_system.gpu_update_articulation_kinematics(),更新 Articulation 数据(例如,连杆位姿),以便获取。
  6. 运行 physx_system.gpu_fetch_*,更新相关 GPU 缓冲区,并生成观测数据。

在代码中,我们将 physx_system 变量保存为 env.scene.px。

env.step 过程包括反复执行以下步骤,以处理动作并生成输出:

  1. 获取用户的动作(并根据需要进行裁剪)。
  2. 处理动作,将其转换为控制信号(如关节位置/速度)来控制 Agent。
  3. 运行 physx_system.gpu_apply_articulation_target_positionphysx_system.gpu_apply_articulation_target_velocity,应用第 2 步中的目标。
  4. 运行 physx_system.step(),推进仿真。
  5. 运行 physx_system.gpu_fetch_*,更新相关 GPU 缓冲区,并生成观测数据。
  6. 返回步骤数据:观测、奖励、终止标志、截断标志和其他信息。

GPU 上的数据组织

每个子场景中每个刚体 Actor 和 Articulation 连杆的刚体数据(包括位姿(7D)、线速度(3D)和角速度(3D))都紧密地打包在 physx_system.cuda_rigid_body_data 中,形成一个大型矩阵,其组织方式如下:

[Actor 1 Data] [Actor 2 Data] ... [Articulation 1 Link 1 Data] [Articulation 1 Link 2 Data] ... [Articulation N Link M Data]

对于计划直接操作 GPU 缓冲区的用户,理解这种组织方式可能会有所帮助。否则,如果使用 ManiSkill 提供的 API,这些细节会自动处理。

值得注意的是,这种 GPU 缓冲区的组织方式可能不会遵循直观的结构(例如,每 k 行代表一个子场景的数据),这是为了获得更好的性能所做的权衡。例如,以下示例展示了当 PhysX 场景包含 3 个刚体 Actor(用红色表示)和 3 个具有不同连杆数量/自由度(DOF)的 Articulation(用绿色表示)时的数据组织方式。SAPIEN 会将每个 Articulation 分配的行数填充为整个 PhysX 场景中最高的自由度数。

ManiSkill 的设计原则

批处理一切

ManiSkill 旨在支持 CPU 和 GPU 的并行仿真方案。原因在于,对于某些任务,即使在非工业级的设置中,使用 GPU 仿真也未必比使用更多的 CPU 更快。因此,ManiSkill 中几乎所有的代码都将数据以批处理形式暴露给用户(批次维度 = 并行环境数量),并将批次大小为 1 的 CPU 仿真视为特例。

管理对象和视图

ManiSkill 可以被视为 SAPIEN 的 Python 接口。

控制器 / 动作空间

控制器是用户/策略与机器人之间的接口。每当在环境中采取一步并提供一个动作时,该动作会发送到选定的控制器,后者将动作转换为机器人的控制信号。在最低级别,所有模拟中的机器人都是通过关节位置或关节速度控制来控制的,实际上指定了每个关节应该到达的位置或速度。

例如,arm_pd_ee_delta_pose 控制器以末端执行器的相对运动作为输入,并使用 逆向运动学 将输入动作转换为机器人关节的目标位置。机器人使用 PD 控制器 来驱动电机,以实现目标关节位置。

在 ManiSkill 中,有几个关键点需要注意关于控制器的事项:

  • 控制器定义了任务的动作空间。
  • 机器人可以为不同的关节组拥有独立的控制器。动作空间是所有控制器的动作空间的串联。
  • 单个机器人可能有多组可用的控制器。

以下部分将详细介绍每个预构建的控制器及其功能。

Passive(被动)

from mani_skill.agents.controllers import PassiveControllerConfig

此控制器允许强制指定的关节不受动作控制。例如,在 CartPole 环境 中,将 CartPole 机器人的铰链关节设置为被动控制(即仅允许控制滑动盒子)。

PD Joint Position(PD关节位置)

from mani_skill.agents.controllers import PDJointPosControllerConfig

使用 PD 控制器,通过动作控制指定关节的位置。

PD EE(末端执行器)Pose(姿势)

from mani_skill.agents.controllers import PDEEPoseControllerConfig

此控制器有姿势和位置两种变体,允许更直观地控制机器人的末端执行器(或任何链节)。该控制器的默认选项设置为更直观的方式,但也提供多种选择。

要理解其工作原理,首先需要了解以下三个相关的变换坐标系:

  1. 世界坐标系
  2. 根链节坐标系
  3. 末端执行器/链节坐标系

在下图中,这些坐标系通过 RGB 轴表示,其中红色 = X 轴,绿色 = Y 轴,蓝色 = Z 轴。要控制的目标链节是表示工具控制点(TCP)的虚拟链节,它是末端执行器链节的简单偏移(因此其原点位于两个夹爪之间)。请注意,在 ManiSkill/SAPIEN 中,Z 轴通常被视为自然的“上下方向”,这与其他一些模拟器不同。

在此控制器中,实现了对末端执行器平移和旋转的解耦控制。这意味着用于平移的动作不会影响用于旋转末端执行器的动作。这提供了 6 个维度的控制,3 个用于 3D 平移,另 3 个用于旋转,具体如下。

该控制器提供增量控制和绝对控制两种模式。在每个环境时间步中,给定一个动作,ManiSkill 计算出末端执行器的新目标姿势,并利用逆向运动学计算出可以最好地实现该目标姿势的关节动作。该控制器的配置有效地改变了新目标姿势的计算方式。

增量控制

默认启用此模式,并通过 use_delta 属性进行配置。它允许用户提交定义末端执行器姿势增量的动作,以朝向目标移动。共有 4 种控制框架组合方式,源于平移和旋转的两种选择。框架由 frame 属性定义,命名规则如下:

# 命名规则是 <frame>_translation:<frame>_rotation
# 以下是 4 种可能的框架组合
"body_translation:root_aligned_body_rotation",
"root_translation:root_aligned_body_rotation",
"body_translation:body_aligned_body_rotation",
"root_translation:body_aligned_body_rotation",
# 默认的框架组合
"root_translation:root_aligned_body_rotation"

平移

在此控制器中,用户指定在 X、Y 和 Z 轴上的增量(如果未归一化,则单位为米),表示在所有这些维度上移动的距离。然后,使用逆向运动学来确定实现所需平移的关节动作。

ManiSkill 中定义了两种位置平移框架:根坐标系和平体坐标系。通过在动作中设置相应维度大于 0,其他维度设置为 0,可以实现以下两种平移方式。

旋转

对于旋转,用户指定在 X、Y 和 Z 轴上的增量旋转(如果未归一化,则单位为弧度),表示在所有这些维度上旋转的角度。它们作为 XYZ 欧拉角处理,并在内部转换为四元数。然后,使用逆向运动学来确定实现所需旋转的关节动作。

ManiSkill 实现了两种类型的旋转控制,通常被认为是最直观且常用于现实世界机器人的方式,即在一个坐标系下对齐/定位另一个坐标系的旋转。具体而言,支持两种旋转框架:根对齐身体和身体对齐身体。根对齐 A 意味着在与 A 坐标系具有相同方向但与 B 坐标系具有相同位置的框架中进行旋转。以下通过设置动作中相应维度大于 0,其他维度设置为 0,来展示这两种旋转方式。

观测模式

观测空间

观测模式定义了观测空间。 所有 ManiSkill 任务都将观测模式(obs_mode)作为输入参数传递给 gym.make(env_id, obs_mode=...)。 通常,观测是组织为一个字典(具有 gym.spaces.Dict 的观测空间)。

有三种原始观测模式:state_dict(特权状态)、sensor_data(原始传感器数据,如未经后处理的视觉数据)和 state+sensor_data(两者都有)。statestate_dict 的扁平化版本。rgb+depthrgb+depth+segmentation(或 rgbdepthsegmentation 的任意组合)以及 pointcloudsensor_data 进行后处理,以提供方便的视觉数据表示。state_dict+rgb 将返回特权的未扁平化状态和视觉数据,可以根据需要混合和匹配不同的模态。

以下详细说明了未批处理的形状。通常,返回的数据总是具有批处理维度,除非使用 CPU 仿真并将其作为 torch 张量返回。此外,我们注释了某些值的数据类型。

state_dict

观测是一个状态字典。它通常包含特权信息,如物体姿态。软体任务不支持此模式。

  • agent:机器人本体感知(任务的 _get_obs_agent 函数的返回值)
    • qpos:当前关节位置,形状为 [nq],nq 为自由度数量。
    • qvel:当前关节速度,形状为 [nq]。
    • controller:取决于所使用控制器的控制器状态,通常为空字典。
  • extra:任务特定信息的字典,例如目标位置、末端执行器位置等。这是任务的 _get_obs_extra 函数的返回值。

state

statestate_dict 的扁平化版本。观测空间为 gym.spaces.Box

sensor_data

除了 agentextrasensor_datasensor_param 也被引入。目前,只有相机类型的传感器。相机在使用不同着色器时会有所不同。默认着色器称为 minimal,是最快且最节省内存的选项。所选着色器决定了此观测模式中存储的数据。以下描述了 minimal 着色器下的原始数据格式。有关如何自定义传感器/相机的详细信息,请参阅 sensors 部分。

  • sensor_data:由环境中配置的传感器捕获的数据

    • {sensor_uid}

      如果数据来自相机传感器:

      • Color:形状为 [H, W, 4],数据类型为 torch.uint8np.uint8。RGB+Alpha 值。
      • PositionSegmentation:形状为 [H, W, 4],数据类型为 torch.int16np.int16。前三个维度表示 (x, y, z) 坐标,单位为毫米。最后一个维度表示分割 ID,更多细节请参阅 Segmentation data 部分。
  • sensor_param:每个传感器的参数,具体取决于传感器类型

    • {sensor_uid}

      如果 sensor_uid 对应于相机:

      • cam2world_gl:形状为 [4, 4],从相机坐标系到世界坐标系的转换(OpenGL/Blender 规范)。
      • extrinsic_cv:形状为 [4, 4],相机外参(OpenCV 规范)。
      • intrinsic_cv:形状为 [3, 3],相机内参(OpenCV 规范)。

rgb+depth+segmentation

通过简单地将所需的图像纹理(rgb、depth、segmentation、albedo、normal 等)用 + 连接,可以在观测模式中请求多种组合。例如,rgb+depth+segmentation 的数据格式与 sensor_data 模式 相同,但所有来自相机的传感器数据被以下结构替换:

  • sensor_data

    • {sensor_uid}

      如果数据来自相机传感器:

      • rgb:形状为 [H, W, 3],数据类型为 torch.uint8np.uint8。RGB 图像。
      • depth:形状为 [H, W, 1],数据类型为 torch.int16np.uint16。单位为毫米。值为 0 表示无效像素(超出相机的最大视距)。
      • segmentation:形状为 [H, W, 1],数据类型为 torch.int16np.uint16。更多细节请参阅 Segmentation data 部分。

请注意,这些数据未缩放/归一化到 [0, 1] 或 [-1, 1],以节省内存。因此,如果计划在 RGB、深度或分割数据上进行训练,请确保在训练前对数据进行缩放。

ManiSkill 默认灵活地支持不同组合的 RGB、深度和分割数据,即 rgbdepthsegmentationrgb+depthrgb+depth+segmentationrgb+segmentationdepth+segmentation。未选择的图像模态将不会包含在观测中,从而节省内存和 GPU 带宽。

以下是来自两个相机的 RGBD 图像示例:

--- 
alt: 来自两个相机的 RGBD 图像,显示了在 ReplicaCAD 数据集场景中 Fetch 机器人的视图。
---

在 ManiSkill 中,pointcloud 观测模式提供了一种融合多台相机数据的点云表示,适用于需要处理空间几何信息的任务。

  • pointcloud:
    • xyzw: 形状为 [N, 4],数据类型为 torch.float32np.float32。融合自所有相机的点云数据,采用齐次坐标表示。w=0 表示无限远的点(超出相机的最大视距),w=1 表示其他点。
    • rgb: 形状为 [N, 3],数据类型为 torch.uint8np.uint8。对应点云的颜色信息。
    • segmentation: 形状为 [N, 1],数据类型为 torch.int16np.int16。每个点的分割 ID,指示该点所属的对象或区域。更多细节请参阅 Segmentation data 部分。

请注意,点云数据本身并不包含更多信息,除非另有说明。

以下是使用两个相机在 ReplicaCAD 场景中获取的点云数据示例:

---
alt: 来自两个相机的点云数据,显示了在 ReplicaCAD 数据集场景中 Fetch 机器人的视图。
---

要快速演示点云的可视化,可以运行以下命令:

python -m mani_skill.examples.demo_vis_pcd -e "PushCube-v1"

这将展示任务环境 "PushCube-v1" 中的点云数据。

分割数据

在 ManiSkill 中,对象在加载时会自动分配一个分割 ID(即 sapien.Entity 对象的 per_scene_id 属性)。要获取各 ID 对应的 Actor 或 Link 信息,可以运行以下代码:

import gymnasium as gym
import mani_skill.envs
from mani_skill.utils.structs import Actor, Link

env = gym.make("PushCube-v1", obs_mode="rgbd")
for obj_id, obj in sorted(env.unwrapped.segmentation_id_map.items()):
    if isinstance(obj, Actor):
        print(f"{obj_id}: Actor, name - {obj.name}")
    elif isinstance(obj, Link):
        print(f"{obj_id}: Link, name - {obj.name}")

请注意,ID 0 通常表示遥远的背景。

要快速演示分割数据的可视化,可以运行以下命令:

python -m mani_skill.examples.demo_vis_segmentation -e "PushCube-v1"  # 绘制所有分割
python -m mani_skill.examples.demo_vis_segmentation -e "PushCube-v1" --id cube  # 仅显示名称为 "cube" 的对象

这将帮助理解和分析环境中各对象的分割信息。

传感器 / 相机

本页详细说明如何在运行时和任务/环境定义中使用/自定义 ManiSkill 的传感器和相机。在 ManiSkill 中,传感器是可以捕获某些数据模态的“设备”。目前只有相机传感器类型。

相机

ManiSkill 中的相机可以捕获多种不同模态/纹理的数据。默认情况下,ManiSkill 将这些限制为 rgbdepthposition(用于推导深度)和 segmentation。内部 ManiSkill 使用 SAPIEN 作为高度优化的渲染系统,通过着色器渲染不同的数据模态。相机的完整配置可以在 {py:class}mani_skill.sensors.camera.CameraConfig 中找到。

每个着色器都有一个预设配置,生成图像样式的数据,这些数据通常由于高度优化而呈现为难以使用的格式。ManiSkill 使用 Python 的着色器配置系统,将这些不同的着色器解析为更易于使用的纹理格式,如 rgbdepth。有关哪些纹理可供哪些着色器生成的更多详细信息,请参阅 着色器与纹理 部分。

每个 ManiSkill 环境都有 3 类相机(尽管某些类别可能为空):用于代理/策略观测的传感器、用于人类的高质量视频捕捉的 human_render_cameras,以及一个由 GUI 应用程序用于渲染环境的单一观察相机。

在运行时创建环境时,可以为这些相机传递运行时覆盖。例如,以下代码将人类渲染相机更改为使用光线追踪着色器进行照片级真实感渲染,将传感器相机的宽度和高度修改为 320 和 240,并更改观察相机的视场角(FOV)值。

gym.make("PickCube-v1",
  sensor_configs=dict(width=320, height=240),
  human_render_camera_configs=dict(shader_pack="rt"),
  viewer_camera_configs=dict(fov=1),
)

这些覆盖将影响环境中每个相机所在的类别。因此,sensor_configs=dict(width=320, height=240) 会更改每个传感器相机的宽度和高度,但不会影响人类渲染相机或观察相机。

要覆盖特定的相机,可以通过相机名称进行。例如,如果想覆盖名为 camera_0 的传感器相机以具有不同的宽度和高度,可以这样做:

gym.make("PickCube-v1",
  sensor_configs=dict(camera_0=dict(width=320, height=240)),
)

现在,所有其他传感器相机将保持默认的宽度和高度,而 camera_0 将具有指定的宽度和高度。

这些特定的自定义选项对于那些希望自定义渲染方式或生成策略观测以适应需求的用户非常有用。

着色器与纹理

以下是 ManiSkill 中可用的着色器:

着色器名称 可用纹理 描述
minimal rgb、depth、position、segmentation 最快的着色器,最小的 GPU 内存占用。请注意,背景将始终为黑色(通常是环境光的颜色)
default rgb、depth、position、segmentation、normal、albedo 速度和纹理可用性之间的平衡
rt rgb、depth、position、segmentation、normal、albedo 针对光线追踪的照片级真实感渲染优化的着色器
rt-med rgb、depth、position、segmentation、normal、albedo 与 rt 相同,但运行速度更快,质量稍低
rt-fast rgb、depth、position、segmentation、normal、albedo 与 rt-med 相同,但运行速度更快,质量稍低

以下是 ManiSkill 中可用的纹理。请注意,除非特别说明,否则所有数据都未进行缩放/归一化。

纹理 形状 数据类型 描述
rgb [H, W, 3] torch.uint8 图像的红色、绿色、蓝色。范围为 0-255
depth [H, W, 1] torch.int16 深度,单位为毫米
position [H, W, 4] torch.int16 x、y、z 坐标(单位为毫米),第 4 通道与分割数据相同
segmentation [H, W, 1] torch.int16 分割掩码,每个物体的唯一整数 ID
normal [H, W, 3] torch.float32 法向量的 x、y、z 组件
albedo [H, W, 3] torch.uint8 反照率的红色、绿色、蓝色。范围为 0-255

可复现性和随机数生成

像许多模拟器一样,ManiSkill 使用随机数生成器(RNG)来随机化环境中的一切,从物体几何形状、物体姿态、纹理等。这些随机化操作通常发生在环境重置时。

然而,随机化为可复现性带来了挑战,尤其是当在一台计算机上生成演示并希望在另一台机器上重放时,尤其是在使用 GPU 仿真/并行化环境时,通常一个种子决定了每个环境中进行的随机化。

有两种方法可以确保轨迹/演示的可复现性:批量种子 RNG 和环境状态。通常,只需要确保在环境重新配置/加载时(决定加载哪些物体/纹理到环境中)和回合初始化时(决定初始物体位置、速度等)随机数的可复现性。

通过 RNG 确保可复现性

为了解决这个问题,ManiSkill 推荐使用环境的 _batched_episode_rng 对象,它是 BatchedRNG 的一个实例。这个批量 RNG 对象与 np.random.RandomState 相同,但现在所有输出都有一个额外的批量维度,等于并行环境的数量。此外,批量 RNG 对象使用一个种子列表来初始化每个并行环境的种子。现在无论并行环境的数量如何,RNG 生成都是确定性的。如果需要,_batched_episode_rng[i] 将返回第 i 个环境的 RNG 对象,以便仅为该环境进行采样。

当用户调用 env.reset(seed=x) 时,如果 x 是一个整数,我们将使用种子 x 初始化第一个并行环境,其他环境将根据 x 随机生成种子。为了完全的可复现性,用户可以调用 env.reset(seed=[x1, x2, ..., xn]),此时种子列表将确定每个并行环境的 RNG。如果没有提供种子调用 env.reset(),则为每个并行环境生成一个随机种子。

这种方法的一个缺点是,RNG 生成比直接使用默认的 torch/numpy RNG 函数批量生成随机数要慢(目前是这样)。考虑到这一点,我们建议至少在环境重新配置期间使用批量回合 RNG(在 ML 工作流中,训练过程中环境重新配置并不频繁)。回合初始化(每回合只发生一次)可以通过下一个方法使用环境状态来处理。

通过环境状态确保可复现性

环境状态包括所有物体的关节角度、关节速度、姿态、速度等。它不包括诸如物体纹理、固定相机姿态、机器人控制器刚度等细节。

诸如物体几何形状/纹理等细节,在给定相同的种子的情况下,使用上面讨论的 _batched_episode_rng 对象进行随机化后是确定性的。

对于环境状态中包含的其他类型的状态,为确保可复现性,可以保存这些环境状态,然后在创建环境后设置它们。假设使用了相同的种子来确保所有物体相同,那么设置环境状态将确保物体处于正确的位置。