Nvidia Isaac Labで荒地を進むロボット犬の開発が容易ロボットは地形を進んでいく。転ばないなど成功報酬に基づいて地形を進んでいく。指定された地形を解き、境界線を越えて歩くと、次の複雑な地形に移動する。
ANYmalは、ANYbotics社で発明されました。ANYbotics社は、スイス連邦工科大学チューリッヒ校(ETH Zurich)からスピンオフした企業で、2016年に設立されました。
NvidiaではANYmalをsimulation上で訓練できます。
Issac Lab上にANYmal部品からインポートし関節を駆動させることができます。
センサーとジャイロも用意されています。
数千台のシミュレーションロボットを並列に訓練するのはゲームのプログラムから着想を得ています。荒地上を歩行する4足ロボットを訓練することができます。
Nvidia Isaac Simでは、平坦な地形では4分、凹凸のある地形では20分でポリシーを訓練することができました。シミュレーションゲームにインスパイアされた方法が採用されています。
ロボットは地形を進んでいく。転ばないなど成功報酬に基づいて地形を進んでいく。
指定された地形を解き、境界線を越えて歩くと、次の複雑な地形に移動する。
しかし、十分な距離を歩けなかった場合はレベルが下がる。
これにより、各地形タイプの難易度を個別に調整する自動カリキュラムが作成される。
例えば、500回反復した後、ポリシーは傾斜のある地形を横切り、階段を下りることができるが、階段を登ったり、障害物を横切ったりするには、より多くのトレーニング反復が必要です。1000回反復した後、ロボットはすべての地形タイプで最も難しいレベルに達しました。
そのプログラムは次のとおりです
from future import annotations
import torch
import omni.isaac.lab.sim as sim_utils
from omni.isaac.lab.assets import Articulation, ArticulationCfg
from omni.isaac.lab.envs import DirectRLEnv, DirectRLEnvCfg
from omni.isaac.lab.scene import InteractiveSceneCfg
from omni.isaac.lab.sensors import ContactSensor, ContactSensorCfg, RayCaster, RayCasterCfg, patterns
from omni.isaac.lab.sim import SimulationCfg
from omni.isaac.lab.terrains import TerrainImporterCfg
from omni.isaac.lab.utils import configclass
Pre-defined configs
from omni.isaac.lab_assets.anymal import ANYMAL_C_CFG # isort: skip
from omni.isaac.lab.terrains.config.rough import ROUGH_TERRAINS_CFG # isort: skip
@configclass
class AnymalCFlatEnvCfg(DirectRLEnvCfg):
# env
episode_length_s = 20.0
decimation = 4
action_scale = 0.5
num_actions = 12
num_observations = 48
num_states = 0
# simulation
sim: SimulationCfg = SimulationCfg(
dt=1 / 200,
render_interval=decimation,
disable_contact_processing=True,
physics_material=sim_utils.RigidBodyMaterialCfg(
friction_combine_mode="multiply",
restitution_combine_mode="multiply",
static_friction=1.0,
dynamic_friction=1.0,
restitution=0.0,
),
)
terrain = TerrainImporterCfg(
prim_path="/World/ground",
terrain_type="plane",
collision_group=-1,
physics_material=sim_utils.RigidBodyMaterialCfg(
friction_combine_mode="multiply",
restitution_combine_mode="multiply",
static_friction=1.0,
dynamic_friction=1.0,
restitution=0.0,
),
debug_vis=False,
)
# scene
scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=4096, env_spacing=4.0, replicate_physics=True)
# robot
robot: ArticulationCfg = ANYMAL_C_CFG.replace(prim_path="/World/envs/env_.*/Robot")
contact_sensor: ContactSensorCfg = ContactSensorCfg(
prim_path="/World/envs/env_.*/Robot/.*", history_length=3, update_period=0.005, track_air_time=True
)
# reward scales
lin_vel_reward_scale = 1.0
yaw_rate_reward_scale = 0.5
z_vel_reward_scale = -2.0
ang_vel_reward_scale = -0.05
joint_torque_reward_scale = -2.5e-5
joint_accel_reward_scale = -2.5e-7
action_rate_reward_scale = -0.01
feet_air_time_reward_scale = 0.5
undersired_contact_reward_scale = -1.0
flat_orientation_reward_scale = -5.0
@configclass
class AnymalCRoughEnvCfg(AnymalCFlatEnvCfg):
# env
num_observations = 235
terrain = TerrainImporterCfg(
prim_path="/World/ground",
terrain_type="generator",
terrain_generator=ROUGH_TERRAINS_CFG,
max_init_terrain_level=9,
collision_group=-1,
physics_material=sim_utils.RigidBodyMaterialCfg(
friction_combine_mode="multiply",
restitution_combine_mode="multiply",
static_friction=1.0,
dynamic_friction=1.0,
),
visual_material=sim_utils.MdlFileCfg(
mdl_path="{NVIDIA_NUCLEUS_DIR}/Materials/Base/Architecture/Shingles_01.mdl",
project_uvw=True,
),
debug_vis=False,
)
# we add a height scanner for perceptive locomotion
height_scanner = RayCasterCfg(
prim_path="/World/envs/env_.*/Robot/base",
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True,
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=False,
mesh_prim_paths=["/World/ground"],
)
# reward scales (override from flat config)
flat_orientation_reward_scale = 0.0
class AnymalCEnv(DirectRLEnv):
cfg: AnymalCFlatEnvCfg | AnymalCRoughEnvCfg
def __init__(self, cfg: AnymalCFlatEnvCfg | AnymalCRoughEnvCfg, render_mode: str | None = None, **kwargs):
super().__init__(cfg, render_mode, **kwargs)
# Joint position command (deviation from default joint positions)
self._actions = torch.zeros(self.num_envs, self.cfg.num_actions, device=self.device)
self._previous_actions = torch.zeros(self.num_envs, self.cfg.num_actions, device=self.device)
# X/Y linear velocity and yaw angular velocity commands
self._commands = torch.zeros(self.num_envs, 3, device=self.device)
# Logging
self._episode_sums = {
key: torch.zeros(self.num_envs, dtype=torch.float, device=self.device)
for key in [
"track_lin_vel_xy_exp",
"track_ang_vel_z_exp",
"lin_vel_z_l2",
"ang_vel_xy_l2",
"dof_torques_l2",
"dof_acc_l2",
"action_rate_l2",
"feet_air_time",
"undesired_contacts",
"flat_orientation_l2",
]
}
# Get specific body indices
self._base_id, _ = self._contact_sensor.find_bodies("base")
self._feet_ids, _ = self._contact_sensor.find_bodies(".*FOOT")
self._underisred_contact_body_ids, _ = self._contact_sensor.find_bodies(".*THIGH")
# Randomize robot friction
env_ids = self._robot._ALL_INDICES
mat_props = self._robot.root_physx_view.get_material_properties()
mat_props[:, :, :2].uniform_(0.6, 0.8)
self._robot.root_physx_view.set_material_properties(mat_props, env_ids.cpu())
# Randomize base mass
base_id, _ = self._robot.find_bodies("base")
masses = self._robot.root_physx_view.get_masses()
masses[:, base_id] += torch.zeros_like(masses[:, base_id]).uniform_(-5.0, 5.0)
self._robot.root_physx_view.set_masses(masses, env_ids.cpu())
def _setup_scene(self):
self._robot = Articulation(self.cfg.robot)
self.scene.articulations["robot"] = self._robot
self._contact_sensor = ContactSensor(self.cfg.contact_sensor)
self.scene.sensors["contact_sensor"] = self._contact_sensor
if isinstance(self.cfg, AnymalCRoughEnvCfg):
# we add a height scanner for perceptive locomotion
self._height_scanner = RayCaster(self.cfg.height_scanner)
self.scene.sensors["height_scanner"] = self._height_scanner
self.cfg.terrain.num_envs = self.scene.cfg.num_envs
self.cfg.terrain.env_spacing = self.scene.cfg.env_spacing
self._terrain = self.cfg.terrain.class_type(self.cfg.terrain)
# clone, filter, and replicate
self.scene.clone_environments(copy_from_source=False)
self.scene.filter_collisions(global_prim_paths=[self.cfg.terrain.prim_path])
# add lights
light_cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
light_cfg.func("/World/Light", light_cfg)
def _pre_physics_step(self, actions: torch.Tensor):
self._actions = actions.clone()
self._processed_actions = self.cfg.action_scale * self._actions + self._robot.data.default_joint_pos
def _apply_action(self):
self._robot.set_joint_position_target(self._processed_actions)
def _get_observations(self) -> dict:
self._previous_actions = self._actions.clone()
height_data = None
if isinstance(self.cfg, AnymalCRoughEnvCfg):
height_data = (
self._height_scanner.data.pos_w[:, 2].unsqueeze(1) - self._height_scanner.data.ray_hits_w[..., 2] - 0.5
).clip(-1.0, 1.0)
obs = torch.cat(
[
tensor
for tensor in (
self._robot.data.root_lin_vel_b,
self._robot.data.root_ang_vel_b,
self._robot.data.projected_gravity_b,
self._commands,
self._robot.data.joint_pos - self._robot.data.default_joint_pos,
self._robot.data.joint_vel,
height_data,
self._actions,
)
if tensor is not None
],
dim=-1,
)
observations = {"policy": obs}
return observations
def _get_rewards(self) -> torch.Tensor:
# linear velocity tracking
lin_vel_error = torch.sum(torch.square(self._commands[:, :2] - self._robot.data.root_lin_vel_b[:, :2]), dim=1)
lin_vel_error_mapped = torch.exp(-lin_vel_error / 0.25)
# yaw rate tracking
yaw_rate_error = torch.square(self._commands[:, 2] - self._robot.data.root_ang_vel_b[:, 2])
yaw_rate_error_mapped = torch.exp(-yaw_rate_error / 0.25)
# z velocity tracking
z_vel_error = torch.square(self._robot.data.root_lin_vel_b[:, 2])
# angular velocity x/y
ang_vel_error = torch.sum(torch.square(self._robot.data.root_ang_vel_b[:, :2]), dim=1)
# joint torques
joint_torques = torch.sum(torch.square(self._robot.data.applied_torque), dim=1)
# joint acceleration
joint_accel = torch.sum(torch.square(self._robot.data.joint_acc), dim=1)
# action rate
action_rate = torch.sum(torch.square(self._actions - self._previous_actions), dim=1)
# feet air time
first_contact = self._contact_sensor.compute_first_contact(self.step_dt)[:, self._feet_ids]
last_air_time = self._contact_sensor.data.last_air_time[:, self._feet_ids]
air_time = torch.sum((last_air_time - 0.5) * first_contact, dim=1) * (
torch.norm(self._commands[:, :2], dim=1) > 0.1
)
# undersired contacts
net_contact_forces = self._contact_sensor.data.net_forces_w_history
is_contact = (
torch.max(torch.norm(net_contact_forces[:, :, self._underisred_contact_body_ids], dim=-1), dim=1)[0] > 1.0
)
contacts = torch.sum(is_contact, dim=1)
# flat orientation
flat_orientation = torch.sum(torch.square(self._robot.data.projected_gravity_b[:, :2]), dim=1)
rewards = {
"track_lin_vel_xy_exp": lin_vel_error_mapped * self.cfg.lin_vel_reward_scale * self.step_dt,
"track_ang_vel_z_exp": yaw_rate_error_mapped * self.cfg.yaw_rate_reward_scale * self.step_dt,
"lin_vel_z_l2": z_vel_error * self.cfg.z_vel_reward_scale * self.step_dt,
"ang_vel_xy_l2": ang_vel_error * self.cfg.ang_vel_reward_scale * self.step_dt,
"dof_torques_l2": joint_torques * self.cfg.joint_torque_reward_scale * self.step_dt,
"dof_acc_l2": joint_accel * self.cfg.joint_accel_reward_scale * self.step_dt,
"action_rate_l2": action_rate * self.cfg.action_rate_reward_scale * self.step_dt,
"feet_air_time": air_time * self.cfg.feet_air_time_reward_scale * self.step_dt,
"undesired_contacts": contacts * self.cfg.undersired_contact_reward_scale * self.step_dt,
"flat_orientation_l2": flat_orientation * self.cfg.flat_orientation_reward_scale * self.step_dt,
}
reward = torch.sum(torch.stack(list(rewards.values())), dim=0)
# Logging
for key, value in rewards.items():
self._episode_sums[key] += value
return reward
def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:
time_out = self.episode_length_buf >= self.max_episode_length - 1
net_contact_forces = self._contact_sensor.data.net_forces_w_history
died = torch.any(torch.max(torch.norm(net_contact_forces[:, :, self._base_id], dim=-1), dim=1)[0] > 1.0, dim=1)
return died, time_out
def _reset_idx(self, env_ids: torch.Tensor | None):
if env_ids is None or len(env_ids) == self.num_envs:
env_ids = self._robot._ALL_INDICES
self._robot.reset(env_ids)
super()._reset_idx(env_ids)
if len(env_ids) == self.num_envs:
# Spread out the resets to avoid spikes in training when many environments reset at a similar time
self.episode_length_buf[:] = torch.randint_like(self.episode_length_buf, high=int(self.max_episode_length))
self._actions[env_ids] = 0.0
self._previous_actions[env_ids] = 0.0
# Sample new commands
self._commands[env_ids] = torch.zeros_like(self._commands[env_ids]).uniform_(-1.0, 1.0)
# Reset robot state
joint_pos = self._robot.data.default_joint_pos[env_ids]
joint_vel = self._robot.data.default_joint_vel[env_ids]
default_root_state = self._robot.data.default_root_state[env_ids]
default_root_state[:, :3] += self._terrain.env_origins[env_ids]
self._robot.write_root_pose_to_sim(default_root_state[:, :7], env_ids)
self._robot.write_root_velocity_to_sim(default_root_state[:, 7:], env_ids)
self._robot.write_joint_state_to_sim(joint_pos, joint_vel, None, env_ids)
# Logging
extras = dict()
for key in self._episode_sums.keys():
episodic_sum_avg = torch.mean(self._episode_sums[key][env_ids])
extras["Episode Reward/" + key] = episodic_sum_avg / self.max_episode_length_s
self._episode_sums[key][env_ids] = 0.0
self.extras["log"] = dict()
self.extras["log"].update(extras)
extras = dict()
extras["Episode Termination/base_contact"] = torch.count_nonzero(self.reset_terminated[env_ids]).item()
extras["Episode Termination/time_out"] = torch.count_nonzero(self.reset_time_outs[env_ids]).item()
self.extras["log"].update(extras)
4足ロボット(ANYmal C)の荒地道路シミュレーション訓練プログラム
ステップごと説明
環境設定
AnymalCFlatEnvCfg クラス
エピソード長: 20秒
間引き率: 4(シミュレーション更新頻度を下げる)
アクションスケール: 0.5
アクション数: 12(各関節の制御)
観測数: 48(平坦地形用)
シミュレーション設定
時間ステップ: 1/200秒
レンダリング間隔: 4ステップごと
物理マテリアル:
静止摩擦係数: 1.0
動摩擦係数: 1.0
反発係数: 0.0
AnymalCRoughEnvCfg クラス
観測数: 235(荒地用、高度スキャナーデータを含む)
地形生成器: ROUGH_TERRAINS_CFG(複雑な地形を生成)
最大初期地形レベル: 9
報酬スケール
線形速度追跡: 1.0
ヨーレート追跡: 0.5
Z方向速度抑制: -2.0
角速度抑制: -0.05
関節トルク抑制: -2.5e-5
関節加速度抑制: -2.5e-7
アクション変化率抑制: -0.01
足の空中時間: 0.5
不要な接触ペナルティ: -1.0
平坦姿勢維持(平地のみ): -5.0
主要機能
AnymalCEnv クラス
初期化処理
ロボットの摩擦係数をランダム化(0.6〜0.8の範囲)
ベース質量をランダム化(±5kg)
アクション適用
関節位置目標 = アクションスケール * アクション + デフォルト関節位置
観測取得
ロボットの状態(線形/角速度、重力方向、関節位置/速度)
コマンド(目標速度)
地形高さデータ(荒地環境のみ)
前回のアクション
報酬計算
線形速度追跡: exp(-error^2 / 0.25)
ヨーレート追跡: exp(-error^2 / 0.25)
Z方向速度抑制: 二乗誤差
XY角速度抑制: 二乗和
関節トルク/加速度抑制: 二乗和
アクション変化率抑制: 二乗和
足の空中時間: (last_air_time - 0.5) * first_contact
不要な接触: 閾値1.0Nを超える接触力の合計
リセット処理
ロボット状態の初期化
新しいコマンド(目標速度)のサンプリング
エピソード統計のリセット
このプログラムは、複雑な地形での4足ロボットの歩行制御を学習するための詳細な強化学習環境を提供しています。環境の難易度や報酬の重み付けを調整することで、様々な歩行戦略の学習が可能となります。
Q1:どのような設定でロボットの動作を評価していますか
このプログラムでは、ANYmal Cという4足ロボットの動作を以下のような設定で評価しています:
報酬関数
報酬関数は複数の要素から構成され、それぞれにスケール係数が設定されています:
線形速度追跡: 目標速度との誤差を評価(スケール: 1.0)
ヨーレート追跡: 目標回転速度との誤差を評価(スケール: 0.5)
Z方向速度抑制: 上下方向の動きを抑制(スケール: -2.0)
XY角速度抑制: 不要な傾きを抑制(スケール: -0.05)
関節トルク抑制: エネルギー効率を向上(スケール: -2.5e-5)
関節加速度抑制: 滑らかな動きを促進(スケール: -2.5e-7)
アクション変化率抑制: 急激な動きを抑制(スケール: -0.01)
足の空中時間: 適切な歩行パターンを促進(スケール: 0.5)
不要な接触ペナルティ: 脚の不適切な接触を抑制(スケール: -1.0)
平坦姿勢維持: 平地環境でのみ使用(スケール: -5.0、荒地では0.0)
観測空間
ロボットの状態や環境情報を含む観測ベクトルを使用:
ロボットの線形/角速度
重力方向
目標速度コマンド
関節位置と速度
地形高さデータ(荒地環境のみ)
前回のアクション
終了条件
以下の条件でエピソードを終了:
時間切れ(エピソード長: 20秒)
ロボットの本体が強く地面に接触した場合
その他の設定
アクションスケール: 0.5
シミュレーション時間ステップ: 1/200秒
環境の複製数: 4096
ロボットの摩擦係数: 0.6〜0.8の範囲でランダム化
ベース質量: ±5kgの範囲でランダム化
これらの設定により、ロボットの安定性、効率性、適応性を総合的に評価し、様々な地形や条件下での効果的な歩行戦略の学習を促進しています。
Q2: どのような方法でシミュレーションの結果をログに保存していますか
このプログラムでは、シミュレーションの結果を以下の方法でログに保存しています:
エピソード統計の記録:
`_episode_sums` 辞書を使用して、各報酬要素の累積値を記録しています。
リセット時のログ記録:
`_reset_idx` メソッド内で、エピソード終了時にログを記録しています。
報酬要素の平均値計算:
各報酬要素の平均値を計算し、`extras` 辞書に "Episode Reward/" プレフィックスを付けて保存しています。
終了条件の記録:
"Episode Termination/" プレフィックスを使用して、ベース接触による終了と時間切れによる終了の回数を記録しています。
ログの構造化:
`self.extras["log"]` 辞書を使用して、ログデータを構造化して保存しています。
エピソードごとのリセット:
エピソード終了時に `_episode_sums` の値をリセットし、新しいエピソードの統計を記録できるようにしています。
これらの方法により、シミュレーションの各エピソードの性能指標や終了条件などの重要な情報が構造化されたフォーマットでログに保存されます。このログデータは後で分析や可視化に使用できます。
Q3: ログに保存するデータの種類は
このプログラムでは、以下の種類のデータをログに保存しています:
エピソード報酬の内訳:
線形速度追跡(X/Y平面): "track_lin_vel_xy_exp"
角速度追跡(Z軸回転): "track_ang_vel_z_exp"
Z軸方向の線形速度: "lin_vel_z_l2"
X/Y軸の角速度: "ang_vel_xy_l2"
関節トルク: "dof_torques_l2"
関節加速度: "dof_acc_l2"
アクション変化率: "action_rate_l2"
足の空中時間: "feet_air_time"
不要な接触: "undesired_contacts"
平坦な姿勢維持: "flat_orientation_l2"
エピソード終了の理由:
ベース(本体)の接触による終了: "Episode Termination/base_contact"
時間切れによる終了: "Episode Termination/time_out"
これらのデータは、`self.extras["log"]`辞書に格納され、各エピソードの終了時に記録されます。報酬の内訳は、エピソード全体の平均値として計算され、エピソードの長さで正規化されています。
このログデータは、ロボットの性能評価、学習進捗の追跡、および問題点の診断に役立ちます。例えば、特定の報酬要素が低い場合、その側面でのロボットの性能が不十分であることを示唆し、改善の余地があることを示します。
Q4:学習過程でどのようなデータを収集していますか
このプログラムでは、ANYmal Cロボットのシミュレーション環境において、以下のようなデータを収集しています:
報酬関連データ:
線形速度追跡: "track_lin_vel_xy_exp"
角速度(ヨー)追跡: "track_ang_vel_z_exp"
Z軸方向の線形速度: "lin_vel_z_l2"
XY平面の角速度: "ang_vel_xy_l2"
関節トルク: "dof_torques_l2"
関節加速度: "dof_acc_l2"
アクション変化率: "action_rate_l2"
足の空中時間: "feet_air_time"
不要な接触: "undesired_contacts"
平坦な姿勢維持: "flat_orientation_l2"
エピソード終了理由:
ベース接触による終了: "Episode Termination/base_contact"
時間切れによる終了: "Episode Termination/time_out"
観測データ:
ロボットの線形速度と角速度
重力方向
目標速度コマンド
関節位置と速度
地形高さデータ(荒地環境の場合)
前回のアクション
エピソード統計:
各報酬要素の累積値と平均値
これらのデータは、エピソードごとに収集され、`self.extras["log"]`辞書に格納されます。このデータは、ロボットの性能評価、学習進捗の追跡、および問題点の診断に使用されます。例えば、特定の報酬要素の低い値は、その側面でのロボットの性能が不十分であることを示唆し、改善の余地があることを示します。
Q5:ロボットは報酬関数ログに基づき自ら自動的にパラメータを変え良い報酬が得られるように学習していきますか
このプログラムは、ロボットが自動的にパラメータを変更して学習するための完全な強化学習システムではありません。しかし、強化学習アルゴリズムと組み合わせて使用することを前提とした環境シミュレーションを提供しています。主な特徴は以下の通りです:
報酬関数の実装:
プログラムは詳細な報酬関数を実装しており、ロボットの様々な行動側面(速度追跡、エネルギー効率、安定性など)を評価しています。観測空間の定義:
ロボットの状態や環境情報を含む観測ベクトルが定義されています。アクション空間の定義:
ロボットの関節位置をコントロールするためのアクション空間が定義されています。エピソード管理:
エピソードの開始、終了、リセットなどの管理機能が実装されています。ログ記録:
各エピソードの報酬や終了条件などの統計情報がログに記録されます。
これらの要素は、外部の強化学習アルゴリズム(例:PPO、SAC、TD3など)と組み合わせて使用することを想定しています。強化学習アルゴリズムがこの環境とインタラクションを行い、報酬信号に基づいてポリシー(行動選択の戦略)を最適化することで、ロボットは徐々に良いパフォーマンスを学習していきます。
つまり、このプログラム自体は学習を行うものではありませんが、学習アルゴリズムのための十分な情報と制御インターフェースを提供しています。実際の学習プロセスは、このシミュレーション環境を使用する外部の強化学習アルゴリズムによって行われます。
Q6:シミュレーション後にはロボットが荒地を転ばないで歩けるプログラムを出力してくれますか
プログラムは直接的にロボットが荒地を転ばずに歩けるようなプログラムを出力するものではありません。このコードは強化学習のための環境シミュレーションを提供するものです。ロボットが荒地を安定して歩行できるようになるには、以下のステップが必要です:
シミュレーション環境の提供:
このプログラムは、ANYmal Cロボットの荒地環境でのシミュレーションを行うための環境を提供しています。強化学習アルゴリズムの適用:
このシミュレーション環境を使用して、外部の強化学習アルゴリズム(PPO、SAC、TD3など)を適用する必要があります。学習プロセス:
強化学習アルゴリズムは、このシミュレーション環境とインタラクションを繰り返し行い、報酬信号に基づいてロボットの制御ポリシーを最適化します。ポリシーの獲得:
十分な学習の後、ロボットは荒地を安定して歩行するためのポリシー(行動戦略)を獲得します。学習済みポリシーの適用:
獲得したポリシーを使用することで、ロボットは荒地を転ばずに歩行できるようになります。
このプログラムは学習プロセスの基盤となる環境を提供していますが、実際の学習と最適なポリシーの獲得には、追加の強化学習コンポーネントと計算時間が必要です。学習が完了した後、獲得したポリシーを使用することで、ロボットは荒地を安定して歩行できるようになります。
Q7:追加の強化学習開発機能をこの仕様書に追加できますか
はい、このプログラムに追加の強化学習開発機能を追加することができます。以下に、自動カリキュラム学習と進行型地形生成を実装するための変更案を示します:
地形難易度の自動調整:
@configclass
class AnymalCRoughEnvCfg(AnymalCFlatEnvCfg):
# 既存の設定に加えて
num_terrain_levels = 10
terrain_level_distance = 20.0 # メートル単位
min_terrain_difficulty = 0
max_terrain_difficulty = 9
class AnymalCEnv(DirectRLEnv):
def __init__(self, cfg: AnymalCFlatEnvCfg | AnymalCRoughEnvCfg, render_mode: str | None = None, **kwargs):
super().__init__(cfg, render_mode, **kwargs)
self.terrain_levels = torch.zeros(self.num_envs, dtype=torch.long, device=self.device)
def _get_rewards(self) -> torch.Tensor:
# 既存の報酬計算に加えて
distance_traveled = torch.norm(self._robot.data.root_pos_w[:, :2] - self._terrain.env_origins[:, :2], dim=1)
level_up_reward = (distance_traveled > self.cfg.terrain_level_distance).float() * 10.0
rewards["level_up"] = level_up_reward
return torch.sum(torch.stack(list(rewards.values())), dim=0)
def _get_dones(self) -> tuple[torch.Tensor, torch.Tensor]:
died, time_out = super()._get_dones()
level_up = torch.norm(self._robot.data.root_pos_w[:, :2] - self._terrain.env_origins[:, :2], dim=1) > self.cfg.terrain_level_distance
return died | level_up, time_out
def _reset_idx(self, env_ids: torch.Tensor | None):
super()._reset_idx(env_ids)
if env_ids is not None:
distance_traveled = torch.norm(self._robot.data.root_pos_w[env_ids, :2] - self._terrain.env_origins[env_ids, :2], dim=1)
level_up = distance_traveled > self.cfg.terrain_level_distance
level_down = self.reset_terminated[env_ids]
self.terrain_levels[env_ids] += level_up.long() - level_down.long()
self.terrain_levels[env_ids].clamp_(self.cfg.min_terrain_difficulty, self.cfg.max_terrain_difficulty)
self._terrain.update_terrain(env_ids, self.terrain_levels[env_ids])
進行型地形生成:
from omni.isaac.lab.terrains import TerrainGenerator
class ProgressiveTerrainGenerator(TerrainGenerator):
def __init__(self, cfg: TerrainImporterCfg):
super().__init__(cfg)
self.terrain_types = ["flat", "slopes", "stairs", "gaps", "obstacles"]
def generate_terrain(self, env_ids: torch.Tensor, difficulty_levels: torch.Tensor):
for env_id, difficulty in zip(env_ids, difficulty_levels):
terrain_type = self.terrain_types[difficulty // 2]
sub_difficulty = difficulty % 2
if terrain_type == "flat":
self._generate_flat_terrain(env_id)
elif terrain_type == "slopes":
self._generate_slopes(env_id, sub_difficulty)
elif terrain_type == "stairs":
self._generate_stairs(env_id, sub_difficulty)
elif terrain_type == "gaps":
self._generate_gaps(env_id, sub_difficulty)
elif terrain_type == "obstacles":
self._generate_obstacles(env_id, sub_difficulty)
# 各地形生成メソッドを実装
def _generate_flat_terrain(self, env_id):
pass
def _generate_slopes(self, env_id, difficulty):
pass
def _generate_stairs(self, env_id, difficulty):
pass
def _generate_gaps(self, env_id, difficulty):
pass
def _generate_obstacles(self, env_id, difficulty):
pass
# AnymalCRoughEnvCfgを更新
@configclass
class AnymalCRoughEnvCfg(AnymalCFlatEnvCfg):
terrain = TerrainImporterCfg(
prim_path="/World/ground",
terrain_type="generator",
terrain_generator=ProgressiveTerrainGenerator,
# その他の設定
)
これらの変更により、以下の機能が追加されます:
自動カリキュラム: ロボットが一定距離を進むと難易度が上がり、失敗すると下がります。
進行型地形生成: 難易度に応じて異なる種類の地形が生成されます。
報酬の拡張: レベルアップに対する報酬が追加されます。
これらの機能により、ロボットは徐々に複雑な地形に挑戦し、能力を向上させることができます。実際の実装では、各地形生成メソッドの詳細な実装や、難易度の調整、報酬のバランス調整などが必要になります。