最近老板突然让我编写一个自定义的强化学习环境,一头雾水(烦),没办法,硬着头皮啃官方文档咯~
第一节先学习常用的API:
在 Gym 中初始化环境非常简单,可以通过以下方式完成:
import gym
env = gym.make('CartPole-v0')
Gym 实现了经典的“代理环境循环”:
代理在环境中执行一些动作(通常通过将一些控制输入传递给环境,例如电机的扭矩输入)并观察环境状态如何变化。一种这样的动作-观察交换被称为时间步长。
RL 的目标是以某种特定方式操纵环境。例如,我们希望agent将机器人导航到空间中的特定点。如果它成功地做到了这一点(或朝着该目标取得了一些进展),它将在此时间步的观察中获得积极的奖励。如果agent尚未成功(或未取得任何进展),则奖励也可能为负数或 0。然后agent将被训练以最大化它在许多时间步长上积累的奖励。
在一些时间步之后,环境可能会进入终端状态。例如,机器人可能已经坠毁!在这种情况下,我们希望将环境重置为新的初始状态。如果agent进入这样的终端状态,环境就会向agent发出完成信号。并非所有完成信号都必须由“灾难性故障”触发:有时我们还希望在固定数量的时间步之后发出完成信号,或者如果agent已成功完成环境中的某些任务。
让我们看看 Gym 中的代理环境循环是什么样的。此示例将运行 LunarLander-v2 环境实例 1000 个时间步长。
import gym
env = gym.make("LunarLander-v2")
env.reset()
for _ in range(1000):
env.render()
action = env.action_space.sample()
observation, reward, done, info = env.step(action)
print(observation)
env.close()
输出:
每个环境都通过提供 env.action_space 属性来指定有效操作的格式。同样,有效观察的空间由 env.observation_space 指定。在上面的示例中,我们通过 env.action_space.sample() 对随机动作进行采样。请注意,我们需要将动作空间与环境分开seed,以确保可重复的样本。
gym.Env.step(self, action: gym.core.ActType) → Tuple[gym.core.ObsType, float, bool, dict]
运行环境动态的一个时间步长。
当情节结束时,我们可以调用 reset() 来重置此环境的状态。接受一个动作并返回一个元组:(observation, reward, done, info).
action:agent做出的动作
observation :环境观察空间的一个元素。例如,这可能是一个包含某些对象的位置和速度的 numpy 数组。上述例子中observation即为一个数组。
reward (float) :agent采取行动后返回的奖励值。
done (bool) :如果游戏已经结束,则为布尔值,在这种情况下,进一步的 step() 调用将返回未定义的结果。可能会因为不同的原因发出 done 信号:可能环境下的任务已成功解决,超出了某个时间限制,或者物理模拟已进入无效状态。
info (dictionary):可能包含有关附加信息的字典。 info 包含辅助诊断信息(有助于调试、学习和记录)。例如,这可能包含:描述agent性能状态的指标、对观察隐藏的变量、区分截断和终止的信息或组合起来产生总奖励的单个奖励项。
gym.Env.reset(self, *, seed: Optional[int] = None, return_info: bool = False, options: Optional[dict] = None) → Union[gym.core.ObsType, Tuple[gym.core.ObsType, dict]]
将环境重置为初始状态并返回初始观察值。
如果种子是整数或环境尚未初始化随机数生成器,此方法可以重置环境的随机数生成器。如果环境已经有一个随机数生成器,并且用 seed=None 调用了 reset(),则不应重置环境的随机数生成器。此外,reset() 应该(在典型用例中)在初始化后立即使用整数种子调用,然后再也不调用。
seed (optional int):用于初始化环境 PRNG(伪随机数生成器) 的种子。如果环境还没有 PRNG 并且通过了 seed=None(默认选项),则将从一些熵源(例如时间戳或 /dev/urandom)中选择一个种子。但是,如果环境已经有 PRNG 并且通过了 seed=None,则 PRNG 将不会被重置。如果你传递一个整数,即使 PRNG 已经存在,它也会被重置。通常,您希望在环境初始化后立即传递一个整数,然后再不传递。
return_info (bool):如果为真,则返回附加信息以及初始观察。类似于 step() 中返回的信息。
options (optional dict):指定如何重置环境的附加信息(可选,取决于具体环境)
observation (object):观察初始状态。是 observation_space 的一个元素(通常是一个 numpy 数组),类似于 step() 返回的观察值。
info (optional dictionary):仅当 return_info=True 被传递时才会返回。它包含补充观察的辅助信息。类似于 step() 返回的信息。
gym.Env.render(self, mode='human')
用于渲染环境。
一组支持的模式因环境而异。(并且一些第三方环境可能根本不支持渲染。)按照惯例,如果模式是:
human:渲染到当前显示器或终端并且不返回任何内容。
rgb_array:返回一个形状为 (x, y, 3) 的 numpy.ndarray,表示 x-by-y 像素图像的 RGB 值,适合转换为视频。
ansi:返回包含终端样式文本表示的字符串 (str) 或 StringIO.StringIO。文本可以包含换行符和 ANSI 转义序列(例如颜色)。
注:确保您的类的元数据“render_modes”键包含支持的模式列表。建议在实现中调用 super() 以使用此方法的功能。
import numpy as np
class MyEnv(Env):
metadata = {'render_modes': ['human', 'rgb_array']}
def render(self, mode='human'):
if mode == 'rgb_array':
return np.array(...) # 返回适合视频的RGB帧
elif mode == 'human':
... # 弹出一个窗口并渲染
else:
super().render(mode=mode) # 仅提出一个例外
mode:渲染模式,有效模式是 env.metadata[“render_modes”]。
Env.action_space: gym.spaces.space.Space[gym.core.ActType]
该属性给出了有效动作的格式。它是 Gym 提供的数据类型 Space。例如,如果动作空间的类型为 Discrete 并给出值 Discrete(2),这意味着有两个有效的离散动作:0 和 1。
env.action_space
Discrete(2)
env.observation_space
Box(-3.4028234663852886e+38, 3.4028234663852886e+38, (4,), float32)
Env.observation_space: gym.spaces.space.Space[gym.core.ObsType]
该属性给出了有效观察的格式。它是 Gym 提供的数据类型 Space。例如,如果观察空间是 Box 类型并且对象的形状是 (4,),这表示有效的观察将是 4 个数字的数组。我们也可以使用属性检查框边界。
env.observation_space.high
array([4.8000002e+00, 3.4028235e+38, 4.1887903e-01, 3.4028235e+38], dtype=float32)
env.observation_space.low
array([-4.8000002e+00, -3.4028235e+38, -4.1887903e-01, -3.4028235e+38], dtype=float32)
Env.reward_range = (-inf, inf)
该属性是对应于最小和最大可能奖励的元组。默认范围设置为 (-inf,+inf)。如果您想要更窄的范围,可以自定义。
gym.Env.close(self)
在您的子类中覆盖 close 以执行任何必要的清理。 当程序退出时,环境将自动关闭。
env.reset(seed=seed)
参数:
seed (Optional int) :随机数生成器的种子值
返回值:
seeds (List[int]) :返回此环境的随机数生成器中使用的种子列表。列表中的第一个值应该是“主”种子,或者是应该传递给“种子”的值。通常,主种子等于提供的“种子”,但如果seed=None,将不会生效。
如果您已经实现了自定义环境并希望执行健全性检查以确保它符合 API,您可以运行:
from gym.utils.env_checker import check_env
check_env(env)
如果您的环境不遵循 Gym API,此函数将引发异常。如果看起来您犯了错误或未遵循最佳实践(例如,如果 observation_space 看起来像图像但没有正确的 dtype),它也会产生警告。可以通过传递warn=False 来关闭警告。默认情况下,check_env 不会检查渲染方法。要更改此行为,您可以传递 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
:由一系列离散动作空间组成,每个元素中具有不同数量的动作。import numpy as np
from gym.spaces import Box, Discrete, Dict, Tuple, MultiBinary, MultiDiscrete
observation_space = Box(low=-1.0, high=2.0, shape=(3,), dtype=np.float32)
print(f"Box空间动作抽样:",observation_space.sample())
observation_space = Discrete(4)
print(f"Discrete空间动作抽样:",observation_space.sample())
observation_space = Dict({"position": Discrete(2), "velocity": Discrete(3)})
print(f"Dict空间动作抽样:",observation_space.sample())
observation_space = Tuple((Discrete(2), Discrete(3)))
print(f"Tuple空间动作抽样:",observation_space.sample())
observation_space = MultiBinary(5)
print(f"MultiBinary空间动作抽样:",observation_space.sample())
observation_space = MultiDiscrete([ 5, 2, 2 ])
print(f"MultiDiscrete空间动作抽样:",observation_space.sample())
输出:
装饰器是一种无需直接更改底层代码即可修改现有环境的便捷方式。使用装饰器将使您避免大量样板代码并使您的环境更加模块化。装饰器也可以链接起来以组合它们的效果。大多数通过 gym.make 生成的环境已经被默认包装了。
为了包装一个环境,你必须首先初始化一个基础环境。然后,您可以将此环境与(可能是可选的)参数一起传递给包装器的构造函数:
import gym
from gym.wrappers import RescaleAction
base_env = gym.make("BipedalWalker-v3")
print(base_env.action_space)
wrapped_env = RescaleAction(base_env, min_action=0, max_action=1)
print(wrapped_env.action_space)
输出:
装饰器完成了三个功能:
通过从 ActionWrapper、ObservationWrapper 或 RewardWrapper 继承并实现相应的转换,可以轻松实现此类装饰器。
但是,有时您可能需要实现一个装饰器来进行一些更复杂的修改(例如,根据信息中的数据修改奖励)。这样的装饰器可以通过从 Wrapper 继承来实现。 Gym 已经为你提供了很多常用的装饰器。一些例子:
TimeLimit
:如果已超过最大时间步数(或基础环境已发出完成信号),则发出完成信号。
ClipAction
:剪辑动作,使其位于动作空间(Box 类型)中。
RescaleAction
:重新调整动作以位于指定的时间间隔内
TimeAwareObservation
:将有关时间步长索引的信息添加到观察中。在某些情况下有助于确保转换是马尔可夫。
如果你有一个包装的环境,并且你想在所有包装层下获得未包装的环境(这样你就可以手动调用一个函数或更改环境的某些底层方面),你可以使用 .unwrapped 属性。如果环境已经是基础环境,则 .unwrapped 属性只会返回自身。
print(wrapped_env)
print(wrapped_env.unwrapped)
我们可以使用gym.utils.play 使用键盘播放游戏环境。
假设我们希望使用左右箭头键来玩 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 参数,则使用该环境的默认 key_to_action 映射。
下一讲学习怎么创建环境~