初始化环境 Initializing Environments
在gym中初始化环境非常简单,可以用以下代码
import gym
env = gym.make('CartPole-v0')
环境交互 Interacting with the Environment
Gym实现了经典的“模型-环境交互循环”结构
智能体agent在环境中做出一些动作,通过观察获得环境的变化状态。这样的一次交互被称之为一个时间步长timestep
强化学习模型的目的是为了用一种特殊的方式和环境交互。例如,我们希望模型引导机器人到达空间中的某个特殊点,如果他成功抵达,或者向着这个点前进,就会收到一些奖励reward作为反馈。如果模型尚未到达目标点或者没有任何进展,奖励将会是0或者负值。通过多次训练,模型最终将会使奖励值最大化
经过多个步长后,环境可能会进入终止状态terminal state,例如机器人可能会坠毁,在这种情况下,必须重置环境达到初始状态initial state。环境在终止状态下需要向模型返回终止信号done signal。然而不是所有终止信号都由这种灾难性的失败触发的,有时我们也可以在一定时长之后终止模型,或者模型已经与环境交互完成了某些特定的任务后终止
让我们通过下面这个例子,看看agent-environment loop是如何在gym中被实现的
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()
通过属性env.action_space实现了在不同环境中与之对应的特殊形式的动作空间,同理通过属性env.observation_space实现了不同环境中与之对应的特殊形式的观测值。在上面的例子中通过env.action_space.sample()方法,在动作空间中随机选取了动作。
注意,我们需要将动作空间与环境独立开来,以确保样本的可重复性(Note that we need to seed the action space separately from the environment to ensure reproducible samples.)
接口完整性检查 Checking API-Conformity
如果你已经实现了一个自定义环境,并且希望执行完整性检查以确保它与接口保持相对应,可以运行
from gym.utils.env_checker import check_env
check_env(env)
如果环境没有遵循Gym API,该函数将抛出异常。如果你犯了其他错误或者没有传入正确类型的参数,它也会发出警告。可以通过传参warn=False来关闭警告。默认情况下,check_env不会检查参数render的值,要改变这种行为,你可以传参skip_render_check= false。
注意,在环境上运行check_env之后不应该再次使用被检查的实例,因为它可能已经关闭了。
空间 Spaces
空间通常被用于指定不同动作和观测值的形式,每个环境都应该有action_space和observation_space属性,这两个属性都应该是继承自Space空间类的实例。
Gym中有多种可用的空间类型:
- Box:表示一个n维连续空间,这是一个有界的空间,我们可以定义上限和下限来描述实际观测值。
- Discrete:表示一个离散空间,其中{0,1,…,(n-1)}是观测值或动作的可能值,通过传参可以使取值范围变到{a, a+1,…,(a+n-1) }
- Dict:表示包含简单空间的字典。
- Tuple:表示由简单空间组成的元组。
- MultiBinary:表示规模为n的二进制空间。参数n可以是一个数字或一个数字列表。
- MultiDiscrete:表示一系列离散空间,每个元素中有数量不同的动作。
>>> from gym.spaces import Box, Discrete, Dict, Tuple, MultiBinary, MultiDiscrete
>>>
>>> observation_space = Box(low=-1.0, high=2.0, shape=(3,), dtype=np.float32)
>>> observation_space.sample()
[ 1.6952509 -0.4399011 -0.7981693]
>>>
>>> observation_space = Discrete(4)
>>> observation_space.sample()
1
>>>
>>> observation_space = Discrete(5, start=-2)
>>> observation_space.sample()
-2
>>>
>>> observation_space = Dict ({"position": Discrete(2), "velocity": Discrete(3)})
>>> observation_space.sample ()
OrderedDict ([('position', 0), ('velocity', 1)])
>>>
>>> observation_space = Tuple((Discrete(2), Discrete(3)))
>>> observation_space.sample()
(1, 2)
>>>
>>> observation_space = MultiBinary(5)
>>> observation_space.sample()
[1 1 1 0 1]
>>>
>>> observation_space = MultiDiscrete([ 5, 2, 2 ])
>>> observation_space.sample()
[3 0 0]
封装 Wrappers
封装是一种修改现有环境而无需直接修改底层代码的快捷方法。使用封装可以避免使用大量样板代码,并使环境更加模块化。可以组合封装来达到多种效果。大多数通过gym.make生成的环境已经默认被封装好了。
封装必须先初始化一个基本环境,然后将这个环境连同参数(也许是可选的),传递给封装器的构造函数进行封装
>>> import gym
>>> from gym.wrappers import RescaleAction
>>> base_env = gym.make("BipedalWalker-v3")
>>> base_env.action_space
Box([-1. -1. -1. -1.], [1. 1. 1. 1.], (4,), float32)
>>> wrapped_env = RescaleAction(base_env, min_action=0, max_action=1)
>>> wrapped_env.action_space
Box([0. 0. 0. 0.], [1. 1. 1. 1.], (4,), float32)
封装器三个常用功能:
- 变换动作使其与环境交互
- 变换由环境返回的观测值
- 变换由环境返回的奖励值
(Transform actions before applying them to the base environment
Transform observations that are returned by the base environment
Transform rewards that are returned by the base environment)
这样的封装器可以通过继承Actionwrapper、Observationwrapper或Rewardwrapper类和实现各自的变换函数接口轻松实现。
然而,有时你可能需要实现一个封装来实现一些更复杂的改动(例如,根据info中的数据修改奖励值)。这样的封装器可以通过继承Wrapper类来实现。
Gym已经为你提供了很多常用的封装。下面是一些例子:
- TimeLimit:如果超过了最大时间步数(或者基本环境发出了完成信号),则发出完成信号。
- ClipAction:修正动作,使其位于动作空间内(类型为Box)。
- RescaleAction:规范动作,将动作重新处于指定的取值区域内。
- TimeAwareObservation:将时间步长的索引信息添加到观察中。在某些情况下有助于确保转换是马尔可夫的。(markov:即下一个step的状态只与当前的状态有关)
马尔可夫决策过程 - 维基百科,自由的百科全书 (wikipedia.org)
如果想要对封装好的环境解封以便查看底层函数和环境的某些属性,你可以使用.unwrapped属性,如果环境已经是底层环境,调用他就会返回本身
>>> wrapped_env
<RescaleAction<TimeLimit<BipedalWalker<BipedalWalker-v3>>>>
>>> wrapped_env.unwrapped
<gym.envs.box2d.bipedal_walker.BipedalWalker object at 0x7f87d70712d0>
操纵模型 Playing within an environment
通过gym.utils.play模块中的play函数,你也可以使用键盘与环境进行交互
from gym.utils.play import play
play(gym.make('Pong-v0'))
上述指令开启了一个窗口用来交互,允许你用键盘来操纵智能体
使用键盘操作需要一组键盘的映射表,映射表的类型为dict[tuple[int], int|None],它将键映射到对应操作,举个例子,如果同时摁下w和space意味着动作2,映射表key_to_action编写如下
{
# ...
(ord('w'), ord(' ')): 2,
# ...
}
举一个更完整的例子,假如想要用left和right方向键操作模型CartPole-v0,代码如下
import gym
import pygame
from gym.utils.play import play
mapping = {(pygame.K_LEFT,): 0, (pygame.K_RIGHT,): 1}
play(gym.make("CartPole-v0"), keys_to_action=mapping)
我们从pygame中获得相应的按键id,如果未指定key_to_action的值,将会使用env中的默认映射值(若已提供)
此外,如果想要在交互的时候显示实时数据,可以使用gym.utils.play.PlayPlot模块,下面是一些示例代码,展示了最后五秒中的奖励值
def callback(obs_t, obs_tp1, action, rew, done, info):
return [rew,]
plotter = PlayPlot(callback, 30 * 5, ["reward"])
env = gym.make("Pong-v0")
play(env, callback=plotter.callback)
官方文档(科学)