Gym is an open source Python library for developing and comparing reinforcement learning algorithms by providing a standard API to communicate between learning algorithms and environments, as well as a standard set of environments compliant with that API. Since its release, Gym’s API has become the field standard for doing this.
pip install gym:安装基本的 gym 库,只含有 Classic Control 环境pip install gym[atari]:安装 Atari 环境支持组件
注意现在最新版的 gym 不再自带 Atari 游戏的 ROM 本体,需要手动到 Atari 2600 VCS ROM Collection 下载,得到一个 ROMS 文件夹,再使用命令
ale-import-roms ROMS/进行导入。具体可以参考 Gym Atari: Gym no longer distributes ROMs
pip install gym[box2D]:安装 Box2D 环境支持组件pip install gym[all]:安装所有环境的支持组件env 环境,可以使用如下代码检查 env 是否满足 gym API 标准,同时这个还能检查你的实现是否遵循了最佳实践from gym.utils.env_checker import check_env
check_env(env)

import gym
env = gym.make("LunarLander-v2", render_mode="human")
env.action_space.seed(42)
observation, info = env.reset(seed=42)
for _ in range(1000):
observation, reward, terminated, truncated, info = env.step(env.action_space.sample())
# 完成任务 或 失败
if terminated or truncated:
observation, info = env.reset()
env.close()

gym.make() 方法创建环境实例时,render_moder 参数设置为 "human",这样才能在屏幕上显示游戏画面
Note:以前的 gym 版本好像没有
render_mode相关的设置,另外以前版本执行env.reset()时不会返回info
任何一个环境都应该有 action_space 和 observation_space 两个属性,它们指定 agent 可行动作和观测的格式。这两个属性都应该是 Space 类的某个子类的实例,包括
| 类名 | 描述 |
|---|---|
gym.spaces.Box | 描述一个 n 维连续空间,我们可以定义取值范围的上下界 |
gym.spaces.Discrete | 描述一个指定大小为
n
n
n 的离散空间
{
0
,
1
,
.
.
.
,
n
−
1
}
\{0,1,...,n-1\}
{0,1,...,n−1},通过设置 start 参数可以将其平移至
{
a
,
a
+
1
,
.
.
.
,
a
+
n
−
1
}
\{a,a+1,...,a+n-1\}
{a,a+1,...,a+n−1} |
gym.spaces.MultiBinary | 描述一个指定尺寸的 0-1 空间,参数可以是一个数 a 或一个列表 [a,b,c,...],分别得到维度为 (a,) 和 (a,b,c,...) 的 ndarray |
gym.spaces.MultiDiscrete | 描述一组 gym.spaces.Discrete 组成的空间,输入是一个列表,每个元素指定一个 Discrete 大小 |
gym.spaces.Dict | 描述由其他空间组成的字典 |
gym.spaces.Tuple | 描述由其他空间组成的元组 |
对 Space 类实例调用 .sample() 方法,可以从空间中采样
from gym.spaces import Box, Discrete, Dict, Tuple, MultiBinary, MultiDiscrete
import numpy as np
space1 = Box(low=-1.0, high=2.0, shape=(3,), dtype=np.float32)
space1.sample() # array([-0.9305908 , -0.33117652, -0.22603005], dtype=float32)
space2 = Discrete(4)
space2.sample() # 3
space3 = Discrete(5, start=-2)
space3.sample() # -2
space4 = MultiBinary(5)
space4.sample() # array([1, 0, 0, 1, 0], dtype=int8)
space5 = MultiBinary([2,5])
space5.sample() # array([[1, 1, 0, 1, 1],
# [1, 0, 0, 1, 1]], dtype=int8)
space6 = MultiDiscrete([ 5, 2, 2 ])
space6.sample() # array([4, 1, 1], dtype=int64)
space7 = Dict({"position": Discrete(2), "velocity": Discrete(3)})
space7.sample() # OrderedDict([('position', 1), ('velocity', 0)])
space8 = Tuple((Discrete(2), Discrete(3)))
space8.sample() # (0, 2)
space9 = Tuple((space7, space8))
space9.sample() # (OrderedDict([('position', 0), ('velocity', 2)]), (0, 2))
有时我们不满意 gym 环境原生的动作空间、观测空间或奖励函数设计,这时就可以用 Wrappers 包装类对其进行简单修改,Wrappers 实例在 “agent-environment loop” 中的地位如下

要使用 Wrappers 类,必须先构造一个 base 环境,用这个基环境实例和包装参数一起作为参数去构造包装类。大多数简单包装都可以通过继承 ActionWrapper,ObservationWrapper 或 RewardWrapper 类重写来简单地实现;对于过于复杂的需求(比如基于 env.step() 返回的 info 信息修改 reward),就需要继承 Wrapper 基类进行重写
gym 也提供了一些预定义的 Wrapper 类,比如
| 包装类名 | 描述 |
|---|---|
gym.wrappers.TimeLimit | 如果超过给定的最大 timestep (或者 base environment 已经发出done信号),则发出done信号 |
gym.wrappers.ClipAction | 自动对 agent 动作进行裁剪,使其落入可行的动作空间中(Box 类型) |
gym.wrappers.RescaleAction | 自动将动作放缩到指定的间隔内 |
gym.wrappers.TimeAwareObservation | 将有关 timestep 索引的信息添加到观察中 |
import gym
from gym.wrappers import RescaleAction, TimeAwareObservation, ClipAction
import numpy as np
base_env = gym.make('CartPole-v1')
wrapped_env = TimeAwareObservation(base_env)
print(wrapped_env.reset()[0]) # [-0.00228092 -0.04528544 -0.01722934 -0.01119073 0. ] 最后一维是 timestep 索引
print(wrapped_env.step(wrapped_env.action_space.sample())[0]) # [-0.00318663 0.15007931 -0.01745316 -0.30925953 1. ]
base_env = gym.make("BipedalWalker-v3")
print(base_env.action_space) # Box(-1.0, 1.0, (4,), float32)
wrapped_env = RescaleAction(base_env, min_action=0, max_action=np.array([0.0, 0.5, 1.0, 0.75]))
print(wrapped_env.action_space) # Box(0.0, [0. 0.5 1. 0.75], (4,), float32)
base_env = gym.make('BipedalWalker-v3')
wrapped_env = ClipAction(base_env)
print(wrapped_env.action_space) # Box(-1.0, 1.0, (4,), float32) 这和 base env 的动作空间一致,但是现在带有了裁剪功能
wrapped_env.reset()
wrapped_env.step(np.array([5.0, 2.0, -10.0, 0.0])) # Executes the action np.array([1.0, 1.0, -1.0, 0]) in the base environment
包装可以嵌套使用,对于一个经过多层包装的环境,其 .env 属性去除一层包装,.unwrapped 属性去除所有包装,去除过程直到 base env 为止。请看如下示例
import gym
from gym.wrappers import TimeAwareObservation, ClipAction
base_env = gym.make('BipedalWalker-v3')
wrapped_env = ClipAction(base_env)
double_wrapped_env = TimeAwareObservation(wrapped_env)
>>> double_wrapped_env
>>>>>>
>>> double_wrapped_env.env
>>>>>
>>> double_wrapped_env.unwrapped
从这里也可看出,gym 原生环境已经经过了一些包装
gym.utils.play 方法,可以把环境的动作输入映射到键盘上,实现环境的手动交互dict[tuple[int], int | None],比如想要按 w 或者空格时执行动作 2,这个字典形如{
# ...
(ord('w'), ord(' ')): 2,
# ...
}
pygame 包提供的 key ID 来构造上述字典,比如 pygame.K_UP 就是方向上键。如果没有向 play 方法传入这个字典,会尝试调用环境默认的映射关系import gym
import pygame
from gym.utils.play import play
mapping = {(pygame.K_UP,): 2, (pygame.K_DOWN,): 3}
env = gym.make("Pong-v0", render_mode='rgb_array') # 渲染模式必须设为 rgb_array 或 rgb_array_list 才能交互
play(env, keys_to_action=mapping)
gym.Env.step(self, action: ~ActType) -> Tuple[~ObsType, float, bool, bool, dict]
gym.Env.reset(self, *, seed: Optional[int] = None, options: Optional[dict] = None) → Tuple[ObsType, dict]
seed=None,则会利用某个熵源(如时间戳)随机设置随机数生成器gym.Env.stepgym.Env.render(self) → Optional[Union[RenderFrame, List[RenderFrame]]]
gym.make() 时指定的 render_mode 属性值计算渲染帧render_mode 不同 (有些第三方环境可能根本不支持渲染)。关于 render_mode 的约定为
None (default): 不计算渲染human:调用返回 None,会在终端不断渲染画面,供人员观察用rgb_array:调用返回代表环境当前状态的单个 frame,它是 shape(x,y,3) 的 numpy.ndarray,表示 x
×
\times
×y 像素图像的RGB值rgb_array_list:调用返回返回自上次重置以来代表环境状态的 frame 列表,frame 定义同上ansi:返回一个 str 或 StringIO。StringIO 包含每个时间步长的终端样式文本表示(可以包括换行符和ANSI转义序列)。只有少数环境需要用这种渲染方式gym.Env.close(self):
Env.action_space: Space[ActType]:给出环境指定的有效 action 格式,是 2.2 节的 Space 类实例Env.observation_space: Space[ObsType]:给出环境指定的有效 observation 格式,是 2.2 节的 Space 类实例Env.reward_range = (-inf, inf):给出代表 reward 取值范围的元组,默认 (-inf, +inf),可以设置成更窄的范围class gym.Wrapper(env: Env):
gym.Env.step() 和 gym.Env.reset() 时发挥作用class gym.ObservationWrapper(env: Env):
ObservationWrapper 并重写 observation() 方法实现对原始 observation 的包装。包装方法必须取值于 base env 的 observation space,然后映射到一个新的 observation space,如果新 observation space 和原先的不同,可以在 __init__() 时通过 self.observation_space 参数进行设置class RelativePosition(gym.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"]
TimeAwareObservation 和 TimeLimit 都是 gym.ObservationWrapper 的子类class gym.RewardWrapper(env: Env)
RewardWrapper 并重写 reward() 方法实现对原始 reward 的包装,在 __init__() 时通过 self.reward_range 参数设置包装后 reward 的取值范围class ClipReward(gym.RewardWrapper):
def __init__(self, env, min_reward, max_reward):
super().__init__(env)
self.min_reward = min_reward
self.max_reward = max_reward
self.reward_range = (min_reward, max_reward)
def reward(self, reward):
return np.clip(reward, self.min_reward, self.max_reward)
class gym.ActionWrapper(env: Env)
ActionWrapper 并重写 action() 方法实现对原始 reward 的包装,在 __init__() 时通过 self.action_space 参数设置包装后 action 的取值 spacegym.spaces.Box 类型的连续空间,但现在想将其离散化为 gym.spaces.Discrete 类型的离散空间,这个包装可以如下实现class DiscreteActions(gym.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)
ClipAction 和 RescaleAction 都是 gym.ActionWrapper 的子类