域随机化#

在单一固定仿真中训练出的策略,学到的是那个确切仿真的特性。在真实硬件上,摩擦、连杆质量和执行器增益都与你的模型不同,并且会随时间漂移,这样的策略就会失效。域随机化(domain randomization) 弥合了这一差距:你为每个并行环境采样一组不同的物理与任务参数,于是策略在训练中看到的是动力学的分布,从而学到不依赖任何单一参数取值的行为。

Genesis World 正是为此而构建的。由于仿真本身已经按环境批量化,随机化逐环境参数只需传入一个首维为 n_envs 的张量。本页介绍可以随机化哪些内容、用于设置它们的批量 API,以及每类随机化应在何时施加。

强化学习中有两种反复出现的随机化模式,它们的区别在于何时施加:

  • 每次运行的物理随机化: 描述机器人及其接触的属性,例如摩擦、质量和 PD 增益。这类参数应在 scene.build() 之后立即设置一次,让每个环境在整个运行期间都仿真一个略有差异的机体。

  • 每回合的任务随机化: 随回合一起重置的量,例如速度指令、生成位姿和重置状态。在你的环境重置函数内部重新采样它们,并只作用于刚刚终止的那些环境。

逐环境随机化物理参数#

刚体实体为那些最影响 sim-to-real 迁移的属性提供了批量 setter。每个 setter 都接受首维为 n_envs 的张量,因此一次调用就能为每个环境指定不同的取值。下面的代码摘自 examples/rigid/domain_randomization.py,该示例加载一个 Go2 四足机器人,并在八个环境之间对它做随机化:

scene.build(n_envs=8)

# Scale each link's friction by a per-env, per-link factor in [0.5, 1.5).
robot.set_friction_ratio(
    friction_ratio=0.5 + torch.rand(scene.n_envs, robot.n_links),
    links_idx_local=np.arange(0, robot.n_links),
)

# Scale each link's mass by a per-env factor in [0.75, 1.25).
robot.set_links_mass(
    mass=robot.get_links_mass() * (0.75 + 0.5 * torch.rand(scene.n_envs, robot.n_links, device=gs.device)),
)

# Shift each link's center of mass by up to 5 cm on each axis.
robot.set_links_COM(
    com=robot.get_links_COM() + (-0.05 + 0.1 * torch.rand(scene.n_envs, robot.n_links, 3, device=gs.device)),
)

这三个方法修改的内容各不相同:

  • set_friction_ratio: 把每个几何体的基础摩擦系数乘以给定的因子,形状为 (n_envs, n_links)。它是缩放而不是替换,因此比例 1.0 会保持模型的摩擦不变。如果想改为给整个实体设置一个绝对摩擦系数,请使用 set_friction(friction),它接受单个浮点数,并且为保证稳定性要求该值位于 [1e-2, 5.0] 区间内。

  • set_links_mass: 以千克为单位设置每个连杆的质量,形状为 (n_envs, n_links),并保持其原始惯量不变。它接受的是质量本身,因此示例先用 get_links_mass 读回模型质量,再写入缩放后的质量。

  • set_links_COM: 以米为单位设置每个连杆的质心,形状为 (n_envs, n_links, 3),其含义是在连杆局部坐标系中的偏移。它同样接受位置本身,因此示例是在 get_links_COM 的返回值上做平移。

set_friction_ratio 写入的是逐环境状态,因此在任何场景中都可以使用。逐环境的质量或质心描述的是模型本身,只有场景以 batch_links_info=True 构建时(见下方警告),Genesis World 才会按环境分别存储这些信息。若所有环境共享同一个取值,则不需要该开关。对其中任何一个方法传入 envs_idx,即可把更新限制在部分环境上。

随机化执行器增益#

随机化 PD 控制器建模了一个事实:真实电机既不是完美整定的,彼此也不完全相同。增益 setter 接受逐 DOF 的取值,当你传入一个首维为 n_envs 的张量时,它们就会为每个环境指定一个独立的控制器:

# Per-env, per-dof position and velocity gains. Shapes: (n_envs, n_dofs).
robot.set_dofs_kp(4000 + 1000 * torch.rand(scene.n_envs, robot.n_dofs), motors_dof_idx)
robot.set_dofs_kv(400 + 100 * torch.rand(scene.n_envs, robot.n_dofs), motors_dof_idx)
robot.set_dofs_armature(0.01 + 0.02 * torch.rand(scene.n_envs, robot.n_dofs), motors_dof_idx)

set_dofs_kp 和 set_dofs_kv 设置 PD 控制器的位置增益与速度增益;set_dofs_armature 设置折算到关节侧的电机惯量,它能让刚性关节更稳定。第二个位置参数是 dofs_idx_local,即要更新的实体局部 DOF 索引,它与由关节名构建出的 motors_dof_idx 相对应。

Warning

逐环境的 DOF 增益需要批量的 DOF 信息,而逐环境的连杆质量、质心和惯量需要批量的连杆信息。两者都保存在模型的静态信息字段中,只有场景以对应选项构建时,这些字段才会按环境分别存储。请在 RigidOptions 中启用它们:

scene = gs.Scene(
    rigid_options=gs.options.RigidOptions(
        batch_dofs_info=True,   # required for per-env kp / kv / armature
        batch_links_info=True,  # required for per-env static link info
    ),
)

如果没有 batch_dofs_info=True,带 n_envs 维度的增益张量就无处安放;同理,没有 batch_links_info=True 时,逐环境的质量、质心或惯量也无处安放。set_friction_ratio 两个开关都不需要。

逐回合随机化指令与状态#

任务随机化在每个回合都会变化,因此它属于重置路径,而不是 build() 之后。运动示例(见 使用 RL 训练运动策略,源码 examples/locomotion/go2_env.py)会在环境重置或重采样间隔到期时为该环境采样新的速度指令。它的均匀采样辅助函数值得复用:

def gs_rand(lower, upper, batch_shape):
    # Uniform in [lower, upper), broadcast over the batch dimensions.
    assert lower.shape == upper.shape
    return (upper - lower) * torch.rand(size=(*batch_shape, *lower.shape), device=gs.device) + lower

随后,环境只为 envs_idx 中传入的那些环境重新采样指令,其余环境保持不动:

def _resample_commands(self, envs_idx):
    commands = gs_rand(*self.commands_limits, (self.num_envs,))  # shape (n_envs, num_commands)
    torch.where(envs_idx[:, None], commands, self.commands, out=self.commands)

同样的原则也适用于生成位姿和重置状态:先为需要重置的环境构造随机化张量,再用实体 setter 的 envs_idx 参数写入,从而只触碰这些环境。例如,entity.set_pos(random_pos, envs_idx=envs_idx) 可以随机化物体的初始位置,同时不影响其他仍在回合进行中的环境。

方法参考#

方法

形状

随机化内容

set_friction_ratio

(n_envs, n_links)

逐连杆摩擦,作为基础系数的乘数

set_links_mass

(n_envs, n_links)

逐连杆质量(kg)

set_links_COM

(n_envs, n_links, 3)

逐连杆质心(m),位于连杆坐标系

set_links_inertia

(n_envs, n_links, 3, 3)

逐连杆惯量矩阵,位于惯性坐标系

set_dofs_kp

(n_envs, n_dofs)

PD 位置增益

set_dofs_kv

(n_envs, n_dofs)

PD 速度增益

set_dofs_armature

(n_envs, n_dofs)

折算到关节侧的电机惯量

全部七个方法都接受可选的 envs_idx,用于只更新部分环境。增益与 armature 相关的 setter 需要 batch_dofs_info=True,质量、质心和惯量相关的 setter 需要 batch_links_info=True,而 set_friction_ratio 两者都不需要。

指导原则#

  • 物理随机化只在 build() 之后施加一次。 这些属性描述的是机体,而不是回合。每步都重置它们既浪费时间,也可能让求解器失稳。

  • 指令与状态随机化在重置时施加, 并用 envs_idx 限定作用范围,这样只触碰刚刚终止的环境。

  • 让首维与 n_envs 对齐。 批量大小不对的张量是最常见的错误。请用 scene.n_envs 以及实体的 n_links 或 n_dofs 来确定张量尺寸,而不要硬编码。

  • 随机化你测不准的量,而不是你已经测准的量。 对已经准确知道的参数扩大分布,只会让任务更难学习。应当让每个区间以你的最佳估计为中心,并扩展到足以覆盖你的不确定性。

另见#