實作自訂包裝器

在本教學中,我們將描述如何實作您自己的自訂包裝器。包裝器是以模組化的方式為您的環境添加功能的絕佳方法。這將為您節省大量樣板程式碼。

我們將展示如何透過以下方式建立包裝器

在繼續本教學之前,請務必查看 gymnasium.wrappers 模組的文件。

繼承自 gymnasium.ObservationWrapper

如果您想對環境返回的觀測值應用某些函數,觀測包裝器會很有用。如果您實作觀測包裝器,您只需要透過實作 gymnasium.ObservationWrapper.observation() 方法來定義此轉換。此外,如果轉換更改了觀測值的形狀(例如,透過將字典轉換為 numpy 陣列,如下例所示),您應該記得更新觀測空間。

假設您有一個 2D 導航任務,其中環境返回的觀測值是字典,其鍵為 "agent_position""target_position"。一個常見的做法可能是捨棄一些自由度,僅考慮目標相對於智能體的位置,即 observation["target_position"] - observation["agent_position"]。為此,您可以實作如下的觀測包裝器

import numpy as np
from gym import ActionWrapper, ObservationWrapper, RewardWrapper, Wrapper

import gymnasium as gym
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]


if __name__ == "__main__":
    env = gym.make("LunarLanderContinuous-v2")
    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 來實作。

如果您這樣做,您可以透過存取屬性 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