注意
此示例與 Gymnasium 1.2.0 版相容。
實現自定義封裝器¶
在本教程中,我們將描述如何實現您自己的自定義封裝器。
封裝器是模組化地為您的環境新增功能的絕佳方式。這將為您節省大量樣板程式碼。
我們將展示如何透過以下方式建立封裝器:
在學習本教程之前,請務必檢視 gymnasium.wrappers 模組的文件。
繼承自 gymnasium.ObservationWrapper¶
當您想對環境返回的觀測值應用某些函式時,觀測封裝器非常有用。如果您實現了一個觀測封裝器,您只需要透過實現 gymnasium.ObservationWrapper.observation() 方法來定義這個轉換。此外,如果轉換改變了觀測值的形狀(例如,像下面的例子中那樣將字典轉換為 numpy 陣列),您應該記住更新觀測空間。
想象您有一個 2D 導航任務,其中環境以字典形式返回觀測值,鍵為 "agent_position" 和 "target_position"。常見的做法是捨棄一些自由度,只考慮目標相對於智慧體的位置,即 observation["target_position"] - observation["agent_position"]。為此,您可以實現一個觀測封裝器,如下所示
import numpy as np
import gymnasium as gym
from gymnasium import ActionWrapper, ObservationWrapper, RewardWrapper, Wrapper
from gymnasium.spaces import Box, Discrete
class RelativePosition(ObservationWrapper):
def __init__(self, env):
super().__init__(env)
self.observation_space = Box(shape=(2,), low=-np.inf, high=np.inf)
def observation(self, obs):
return obs["target"] - obs["agent"]
繼承自 gymnasium.ActionWrapper¶
動作封裝器可用於在將動作應用於環境之前對其進行轉換。如果您實現了一個動作封裝器,您需要透過實現 gymnasium.ActionWrapper.action() 來定義該轉換。此外,您應該透過更新封裝器的動作空間來指定該轉換的領域。
假設您有一個動作空間型別為 gymnasium.spaces.Box 的環境,但您只想使用有限的動作子集。那麼,您可能希望實現以下封裝器
class DiscreteActions(ActionWrapper):
def __init__(self, env, disc_to_cont):
super().__init__(env)
self.disc_to_cont = disc_to_cont
self.action_space = Discrete(len(disc_to_cont))
def action(self, act):
return self.disc_to_cont[act]
env = gym.make("LunarLanderContinuous-v3")
# print(env.action_space) # Box(-1.0, 1.0, (2,), float32)
wrapped_env = DiscreteActions(
env, [np.array([1, 0]), np.array([-1, 0]), np.array([0, 1]), np.array([0, -1])]
)
# print(wrapped_env.action_space) # Discrete(4)
繼承自 gymnasium.RewardWrapper¶
獎勵封裝器用於轉換環境返回的獎勵。與之前的封裝器一樣,您需要透過實現 gymnasium.RewardWrapper.reward() 方法來指定該轉換。
讓我們看一個例子:有時(特別是當我們無法控制獎勵因為它本質上是固有時),我們希望將獎勵裁剪到某個範圍以獲得數值穩定性。為此,我們可以例如實現以下封裝器
from typing import SupportsFloat
class ClipReward(RewardWrapper):
def __init__(self, env, min_reward, max_reward):
super().__init__(env)
self.min_reward = min_reward
self.max_reward = max_reward
def reward(self, r: SupportsFloat) -> SupportsFloat:
return np.clip(r, self.min_reward, self.max_reward)
繼承自 gymnasium.Wrapper¶
有時您可能需要實現一個進行更復雜修改的封裝器(例如,根據 info 中的資料修改獎勵或更改渲染行為)。此類封裝器可以透過繼承自 gymnasium.Wrapper 來實現。
您可以透過分別在
__init__中定義self.action_space或self.observation_space來設定新的動作或觀測空間您可以透過在
__init__中定義self.metadata來設定新的元資料您可以覆蓋
gymnasium.Wrapper.step()、gymnasium.Wrapper.render()、gymnasium.Wrapper.close()等方法。
如果這樣做,您可以透過訪問屬性 env 來訪問傳遞給您的封裝器的環境(該環境 *仍然* 可能被其他封裝器封裝)。
我們再來看一個此案例的示例。大多數 MuJoCo 環境會返回由不同項組成的獎勵:例如,可能有一個獎勵智慧體完成任務的項,以及一個懲罰大動作(即能量消耗)的項。通常,您可以在環境初始化期間為這些項傳遞權重引數。但是,*Reacher* 不允許您這樣做!儘管如此,獎勵的所有單獨項都返回在 info 中,因此讓我們為 Reacher 構建一個封裝器,允許我們對這些項進行加權
class ReacherRewardWrapper(Wrapper):
def __init__(self, env, reward_dist_weight, reward_ctrl_weight):
super().__init__(env)
self.reward_dist_weight = reward_dist_weight
self.reward_ctrl_weight = reward_ctrl_weight
def step(self, action):
obs, _, terminated, truncated, info = self.env.step(action)
reward = (
self.reward_dist_weight * info["reward_dist"]
+ self.reward_ctrl_weight * info["reward_ctrl"]
)
return obs, reward, terminated, truncated, info