IO 描述符 101#
在本教程中,我们将学习关于IO描述符的内容,它们是什么,如何导出它们,以及如何将它们添加到您的环境中。我们将以Anymal-D机器人为例进行演示,演示如何从环境中导出IO描述符,并使用我们自己的项演示如何将IO描述符附加到自定义动作和观测项。
什么是IO Descriptors?#
在深入研究IO描述符之前,让我们先了解它们是什么,以及它们如何有用。
IO 描述符是一种描述在 Isaac Lab 中使用 ManagerBasedRLEnv 训练的策略的输入和输出的方法。换句话说,它们描述了策略的动作和观测项。该描述用于生成一个 YAML 文件,可以在外部工具中加载,以便运行这些策略,而无需手动输入动作和观测项的配置。
除此之外,IO描述符提供以下信息: - articulation中所有关节的参数。 - 一些仿真参数,包括仿真时间步长和策略时间步长。 - 对于一些动作和观察项,它提供与动作/观察项中出现的关节名称或身体名称相同的顺序。 - 对于观察和动作项,它提供的项的顺序与管理器中出现的顺序完全一致。 使得可以轻松从YAML文件中重建它们。
以下是Anymal-D机器人根据IO描述符生成的YAML文件中动作部分的示例:
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
actions:
- action_type: JointAction
clip: null
dtype: torch.float32
extras:
description: Joint action term that applies the processed actions to the articulation's
joints as position commands.
full_path: isaaclab.envs.mdp.actions.joint_actions.JointPositionAction
joint_names:
- LF_HAA
- LH_HAA
- RF_HAA
- RH_HAA
- LF_HFE
- LH_HFE
- RF_HFE
- RH_HFE
- LF_KFE
- LH_KFE
- RF_KFE
- RH_KFE
mdp_type: Action
name: joint_position_action
offset:
- 0.0
- 0.0
- 0.0
- 0.0
- 0.4000000059604645
- -0.4000000059604645
- 0.4000000059604645
- -0.4000000059604645
- -0.800000011920929
- 0.800000011920929
以下是Anymal-D机器人根据IO描述符生成的YAML文件中观测部分的示例片段:
- RH_HFE
- LF_KFE
- LH_KFE
- RF_KFE
- RH_KFE
observations:
policy:
- dtype: torch.float32
extras:
axes:
- X
- Y
- Z
description: Root linear velocity in the asset's root frame.
modifiers: null
units: m/s
full_path: isaaclab.envs.mdp.observations.base_lin_vel
mdp_type: Observation
name: base_lin_vel
observation_type: RootState
overloads:
clip: null
flatten_history_dim: true
history_length: 0
scale: null
shape:
- 3
- dtype: torch.float32
extras:
axes:
- X
- Y
- Z
description: Root angular velocity in the asset's root frame.
modifiers: null
units: rad/s
full_path: isaaclab.envs.mdp.observations.base_ang_vel
mdp_type: Observation
name: base_ang_vel
observation_type: RootState
overloads:
clip: null
flatten_history_dim: true
history_length: 0
scale: null
shape:
- 3
- dtype: torch.float32
extras:
description: 'The joint positions of the asset w.r.t. the default joint positions.
Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have
their positions returned.'
modifiers: null
units: rad
full_path: isaaclab.envs.mdp.observations.joint_pos_rel
joint_names:
- LF_HAA
- LH_HAA
- RF_HAA
- RH_HAA
- LF_HFE
- LH_HFE
- RF_HFE
- RH_HFE
- LF_KFE
- LH_KFE
- RF_KFE
- RH_KFE
joint_pos_offsets:
- 0.0
- 0.0
- 0.0
- 0.0
- 0.4000000059604645
- -0.4000000059604645
- 0.4000000059604645
- -0.4000000059604645
- -0.800000011920929
- 0.800000011920929
- -0.800000011920929
- 0.800000011920929
mdp_type: Observation
name: joint_pos_rel
observation_type: JointState
overloads:
clip: null
需要特别说明的是:动作和观测术语均以字典列表的形式返回,而非字典的字典。这种设计可确保术语顺序的严格保留。因此,用户需要通过字典中的 name
键来检索特定动作或观测术语。
例如,在以下代码段中,我们正在查看 projected_gravity
观测项。 name
键用于标识该项。 full_path
键用于提供到Isaac Lab源代码中用于计算该项的函数的明确路径。还提供了一些标志,如 mdp_type
和 observation_type
,它们不具有任何功能影响。它们在这里是为了向用户通知此项属于哪个类别。
flatten_history_dim: true
history_length: 0
scale: null
shape:
- 3
- dtype: torch.float32
extras:
axes:
- X
- Y
- Z
description: Gravity projection on the asset's root frame.
modifiers: null
units: m/s^2
full_path: isaaclab.envs.mdp.observations.projected_gravity
mdp_type: Observation
name: projected_gravity
observation_type: RootState
overloads:
clip: null
从环境中导出IO描述符#
在这一节中,我们将讨论如何从环境中导出IO描述符。请记住,此功能仅适用于基于管理器的RL环境。
如果已经使用给定配置对策略进行了训练,那么可以使用以下方法导出IO描述符:
./isaaclab.sh -p scripts/environments/export_io_descriptors.py --task <task_name> --output_dir <output_dir>
例如,如果我们想要导出 Anymal-D 机器人的 IO 描述符,我们可以运行:
./isaaclab.sh -p scripts/environments/export_io_descriptors.py --task Isaac-Velocity-Flat-Anymal-D-v0 --output_dir ./io_descriptors
在训练策略时,也可以在训练开始时请求导出IO描述符。这可以通过在命令行中设置 export_io_descriptors
标志来实现。
./isaaclab.sh -p scripts/reinforcement_learning/rsl_rl/train.py --task Isaac-Velocity-Flat-Anymal-D-v0 --export_io_descriptors
./isaaclab.sh -p scripts/reinforcement_learning/sb3/train.py --task Isaac-Velocity-Flat-Anymal-D-v0 --export_io_descriptors
./isaaclab.sh -p scripts/reinforcement_learning/rl_games/train.py --task Isaac-Velocity-Flat-Anymal-D-v0 --export_io_descriptors
./isaaclab.sh -p scripts/reinforcement_learning/skrl/train.py --task Isaac-Velocity-Flat-Anymal-D-v0 --export_io_descriptors
将IO描述符附加到自定义观察项#
在这个部分,我们将讨论如何将IO描述符附加到自定义观察项。
让我们看看如何将IO描述符附加到一个简单的观察项中:
@generic_io_descriptor(
units="m/s", axes=["X", "Y", "Z"], observation_type="RootState", on_inspect=[record_shape, record_dtype]
)
def base_lin_vel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""Root linear velocity in the asset's root frame."""
# extract the used quantities (to enable type-hinting)
asset: RigidObject = env.scene[asset_cfg.name]
return asset.data.root_lin_vel_b
在这里,我们正在定义一个自定义观测项,称为 base_lin_vel
,用于计算机器人的root线速度。我们还将IO描述符附加到此项上。使用 @generic_io_descriptor
装饰器定义IO描述符。
@generic_io_descriptor
装饰器是一个特殊的装饰器,用于将 IO 描述符附加到自定义观测项。它接受任意参数,用于描述观测项,在这种情况下,我们提供了可能对最终用户有用的额外信息。:
单位
: 观测项的单位。axes
: 观测项的坐标轴。observation_type
: 观测项的类型。
您还会注意到提供了 on_inspect
参数。 这是用于检查观测项的函数列表。 在本例中,我们使用 record_shape
和 record_dtype
函数来记录观测项输出的形状和数据类型。
这些函数的定义如下:
def record_shape(output: torch.Tensor, descriptor: GenericObservationIODescriptor, **kwargs) -> None:
"""Record the shape of the output tensor.
Args:
output: The output tensor.
descriptor: The descriptor to record the shape to.
**kwargs: Additional keyword arguments.
"""
descriptor.shape = (output.shape[-1],)
def record_dtype(output: torch.Tensor, descriptor: GenericObservationIODescriptor, **kwargs) -> None:
"""Record the dtype of the output tensor.
Args:
output: The output tensor.
descriptor: The descriptor to record the dtype to.
**kwargs: Additional keyword arguments.
"""
descriptor.dtype = str(output.dtype)
这些函数始终将观测项的输出张量作为第一个参数,描述符作为第二个参数。在 kwargs
中提供了观测项所有的输入。除了 on_inspect
函数外,装饰器还会在后台调用一些函数来收集观测项的 name
、 description
和 full_path
。请注意,添加这个装饰器不会改变观测项的签名,因此可以安全地与观测管理器一起使用!
让我们现在来看一个更复杂的例子: 获取机器人的相对关节位置。
@generic_io_descriptor(
observation_type="JointState",
on_inspect=[record_joint_names, record_dtype, record_shape, record_joint_pos_offsets],
units="rad",
)
def joint_pos_rel(env: ManagerBasedEnv, asset_cfg: SceneEntityCfg = SceneEntityCfg("robot")) -> torch.Tensor:
"""The joint positions of the asset w.r.t. the default joint positions.
Note: Only the joints configured in :attr:`asset_cfg.joint_ids` will have their positions returned.
"""
# extract the used quantities (to enable type-hinting)
asset: Articulation = env.scene[asset_cfg.name]
return asset.data.joint_pos[:, asset_cfg.joint_ids] - asset.data.default_joint_pos[:, asset_cfg.joint_ids]
与上一个示例类似,我们正在向自定义观测项添加一个IO描述符,其中包含一组函数,用于检查观测项。
要获取关节的名称,我们可以编写以下函数:
def record_joint_names(output: torch.Tensor, descriptor: GenericObservationIODescriptor, **kwargs) -> None:
"""Record the joint names of the output tensor.
Expects the `asset_cfg` keyword argument to be set.
Args:
output: The output tensor.
descriptor: The descriptor to record the joint names to.
**kwargs: Additional keyword arguments.
"""
asset: Articulation = kwargs["env"].scene[kwargs["asset_cfg"].name]
joint_ids = kwargs["asset_cfg"].joint_ids
if joint_ids == slice(None, None, None):
joint_ids = list(range(len(asset.joint_names)))
descriptor.joint_names = [asset.joint_names[i] for i in joint_ids]
请注意,我们可以通过 kwargs
字典访问观测项的所有输入。因此,我们可以访问包含计算观测项的关节配置的 asset_cfg
。
要获得偏移量,我们可以编写以下函数:
def record_joint_pos_offsets(output: torch.Tensor, descriptor: GenericObservationIODescriptor, **kwargs):
"""Record the joint position offsets of the output tensor.
Expects the `asset_cfg` keyword argument to be set.
Args:
output: The output tensor.
descriptor: The descriptor to record the joint position offsets to.
**kwargs: Additional keyword arguments.
"""
asset: Articulation = kwargs["env"].scene[kwargs["asset_cfg"].name]
ids = kwargs["asset_cfg"].joint_ids
# Get the offsets of the joints for the first robot in the scene.
# This assumes that all robots have the same joint offsets.
descriptor.joint_pos_offsets = asset.data.default_joint_pos[:, ids][0]
有了这个想法,您现在应该能够将一个IO描述符附加到您自定义的观测项中! 但是,在我们结束本教程之前,让我们看一下如何将IO描述符附加到自定义动作项中。
将IO描述符附加到自定义操作项。#
在这个部分中,我们将介绍如何将IO描述符附加到自定义操作项中。操作项是从 managers.ActionTerm
类继承的类。要将IO描述符添加到操作项中,我们需要扩展其 ActionTerm.IO_descriptor()
属性。
默认情况下, ActionTerm.IO_descriptor()
属性返回基本描述符,并填充以下字段: - name
: 动作项的名称。 - full_path
: 动作项的完整路径。 - description
: 动作项的描述。 - export
: 是否导出动作项。
@property
def IO_descriptor(self) -> GenericActionIODescriptor:
"""The IO descriptor for the action term."""
self._IO_descriptor.name = re.sub(r"([a-z])([A-Z])", r"\1_\2", self.__class__.__name__).lower()
self._IO_descriptor.full_path = f"{self.__class__.__module__}.{self.__class__.__name__}"
self._IO_descriptor.description = " ".join(self.__class__.__doc__.split())
self._IO_descriptor.export = self.export_IO_descriptor
return self._IO_descriptor
为了向描述符添加更多信息,我们需要重写 ActionTerm.IO_descriptor()
属性。让我们看一个示例,了解如何向描述符添加关节名称、scale、offset和clip。
@property
def IO_descriptor(self) -> GenericActionIODescriptor:
"""The IO descriptor of the action term.
This descriptor is used to describe the action term of the joint action.
It adds the following information to the base descriptor:
- joint_names: The names of the joints.
- scale: The scale of the action term.
- offset: The offset of the action term.
- clip: The clip of the action term.
Returns:
The IO descriptor of the action term.
"""
super().IO_descriptor
self._IO_descriptor.shape = (self.action_dim,)
self._IO_descriptor.dtype = str(self.raw_actions.dtype)
self._IO_descriptor.action_type = "JointAction"
self._IO_descriptor.joint_names = self._joint_names
self._IO_descriptor.scale = self._scale
# This seems to be always [4xNum_joints] IDK why. Need to check.
if isinstance(self._offset, torch.Tensor):
self._IO_descriptor.offset = self._offset[0].detach().cpu().numpy().tolist()
else:
self._IO_descriptor.offset = self._offset
# FIXME: This is not correct. Add list support.
if self.cfg.clip is not None:
if isinstance(self._clip, torch.Tensor):
self._IO_descriptor.clip = self._clip[0].detach().cpu().numpy().tolist()
else:
self._IO_descriptor.clip = self._clip
else:
self._IO_descriptor.clip = None
return self._IO_descriptor
这就是了!您现在应该能够将IO描述符附加到自定义动作项上,这样就完成了本教程。