實作自訂包裝器

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

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

在學習本教學之前,請務必查看 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