RL gym 环境(1)—— 安装和基础使用

  • gym 是 OpenAI 做的一套开源 RL 环境,在强化学习研究中使用非常广泛,贴一段 gym github 仓库的简介

    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.

  • 本文对 gym 库进行简单介绍,主要参考
    1. github 库:openai/gym
    2. 官方文档:Gym Documentation

1. 安装 gym

  • 目前最新版 gym(0.26.2) 支持 python 3.7,3.8,3.9,3.10,安装前确保虚拟环境版本正确
  • gym 包含 Classic Control、Atari、MuJoCo、Box2D、Toy Text 几组官方环境,可以按需求安装其中一部分
    1. pip install gym:安装基本的 gym 库,只含有 Classic Control 环境
    2. 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

    3. pip install gym[box2D]:安装 Box2D 环境支持组件
    4. 其他组件安装同理…
    5. pip install gym[all]:安装所有环境的支持组件
  • 另外,gym 也允许自定义环境,自环境定义必须符合 gym 的 API 标准。假设你定义了 env 环境,可以使用如下代码检查 env 是否满足 gym API 标准,同时这个还能检查你的实现是否遵循了最佳实践
    from gym.utils.env_checker import check_env
    check_env(env)
    

2. 基础使用

2.1 Agent-Env Loop

  • 简单说一下 RL 中经典的 “agent-environment loop”
    在这里插入图片描述
    每个 timestep,agent 向环境输入一些控制信号(action),然后观测到环境奖励和状态变化。RL 的目标是以某种特定的方式操纵环境,如果 agent 取得某些进展,就应得到正向的环境奖励。一些 timestep 之后,我们可能希望环境重置到某个初始状态,这时环境应该发送一个 done 信号来激发重置行为,重置时刻包括
    1. agent 出现 “灾难性失败”(比如撞墙了)
    2. agent 完成了任务
    3. 轨迹长度(timestep 数)超过设定上限
  • 利用 gym 库可以简便地实现 “agent-environment loop”,下面给出一个例子(需要安装 Box2D 组件)
    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()
    
    运行这段程序就会自动弹出如下窗口
    在这里插入图片描述
    两个注意事项:
    1. 为了保证可重复性,环境和动作空间都需要设置随机种子
    2. 通过 gym.make() 方法创建环境实例时,render_moder 参数设置为 "human",这样才能在屏幕上显示游戏画面

      Note:以前的 gym 版本好像没有 render_mode 相关的设置,另外以前版本执行 env.reset() 时不会返回 info

2.2 Spaces

  • 任何一个环境都应该有 action_spaceobservation_space 两个属性,它们指定 agent 可行动作和观测的格式。这两个属性都应该是 Space 类的某个子类的实例,包括

    类名描述
    gym.spaces.Box描述一个 n 维连续空间,我们可以定义取值范围的上下界
    gym.spaces.Discrete描述一个指定大小为 n n n 的离散空间 { 0 , 1 , . . . , n − 1 } \{0,1,...,n-1\} {0,1,...,n1},通过设置 start 参数可以将其平移至 { a , a + 1 , . . . , a + n − 1 } \{a,a+1,...,a+n-1\} {a,a+1,...,a+n1}
    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))
    

2.3 Wrappers

  • 有时我们不满意 gym 环境原生的动作空间观测空间奖励函数设计,这时就可以用 Wrappers 包装类对其进行简单修改,Wrappers 实例在 “agent-environment loop” 中的地位如下
    在这里插入图片描述

  • 要使用 Wrappers 类,必须先构造一个 base 环境,用这个基环境实例和包装参数一起作为参数去构造包装类。大多数简单包装都可以通过继承 ActionWrapperObservationWrapperRewardWrapper 类重写来简单地实现;对于过于复杂的需求(比如基于 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
    <TimeAwareObservation<ClipAction<TimeLimit<OrderEnforcing<PassiveEnvChecker<BipedalWalker<BipedalWalker-v3>>>>>>>
    
    >>> double_wrapped_env.env
    <ClipAction<TimeLimit<OrderEnforcing<PassiveEnvChecker<BipedalWalker<BipedalWalker-v3>>>>>>
    
    >>> double_wrapped_env.unwrapped
    <gym.envs.box2d.bipedal_walker.BipedalWalker at 0x1b54e128340>
    

    从这里也可看出,gym 原生环境已经经过了一些包装

2.4 手动交互

  • 使用 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)
    

3. Core API 简介

3.1 gym.Env 类

3.1.1 gym.Env 类方法

  • gym.Env.step(self, action: ~ActType) -> Tuple[~ObsType, float, bool, bool, dict]
    1. 环境根据 agent 动作运行一个 timestep
    2. 输入 action,返回元组 (observation, reward, terminated, truncated, info)
    3. 返回的 terminated 和 truncated 分别标识成功或失败,有一个 True 就应该 reset 环境
    4. info 字典包含有助于调试或分析的附加信息,比如描述 agent 表现状态的度量,观测中的隐藏变量或组成总 reward 的各个成分 reward,以及区分 terminated 和 truncated 的信息。在以前版本的 gym 好像不会返回这个
  • gym.Env.reset(self, *, seed: Optional[int] = None, options: Optional[dict] = None) → Tuple[ObsType, dict]
    1. 将环境重置为初始状态,并返回初始观察值
    2. 输入可选的随机种子 seed 和可选的附加操作 options,返回元组 (observation, info)
    3. 如果 seed 参数输入 int 而非 None,就会设置环境的随机数生成器。构造环境实例后应该立即用 int 随机数种子进行 reset,之后 reset 时不要再带 seed。另外如果初次调用时 seed=None,则会利用某个熵源(如时间戳)随机设置随机数生成器
    4. options 指定 reset 环境时的附加信息,根据具体环境需求设置
    5. 返回的 observation,info 同 gym.Env.step
  • gym.Env.render(self) → Optional[Union[RenderFrame, List[RenderFrame]]]
    1. 按照 gym.make() 时指定的 render_mode 属性值计算渲染帧
    2. 不同环境支持的 render_mode 不同 (有些第三方环境可能根本不支持渲染)。关于 render_mode 的约定为
      1. None (default): 不计算渲染
      2. human:调用返回 None,会在终端不断渲染画面,供人员观察用
      3. rgb_array:调用返回代表环境当前状态的单个 frame,它是 shape(x,y,3) 的 numpy.ndarray,表示 x × \times ×y 像素图像的RGB值
      4. rgb_array_list:调用返回返回自上次重置以来代表环境状态的 frame 列表,frame 定义同上
      5. ansi:返回一个 str 或 StringIO。StringIO 包含每个时间步长的终端样式文本表示(可以包括换行符和ANSI转义序列)。只有少数环境需要用这种渲染方式
  • gym.Env.close(self)
    1. 当 python 垃圾回收机制触发或程序退出时,环境会自动调用这个方法来关闭
    2. 在子类继承重写此方法来实现必要的清理工作

3.1.2 gym.Env 类属性

  • 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),可以设置成更窄的范围

3.2 gym.Wrapper 类

  • class gym.Wrapper(env: Env)
    1. 所有包装类基类,包装类实例都会根据 2.3 节逻辑在调用 gym.Env.step()gym.Env.reset() 时发挥作用
    2. 这个不能直接用,使用时需要继承并重写其中的某些方法来控制包装行为

3.2.1 gym.ObservationWrapper 子类

  • class gym.ObservationWrapper(env: Env)
    1. 所有 observation 包装类的超类
    2. 通过继承 ObservationWrapper 并重写 observation() 方法实现对原始 observation 的包装。包装方法必须取值于 base env 的 observation space,然后映射到一个新的 observation space,如果新 observation space 和原先的不同,可以在 __init__() 时通过 self.observation_space 参数进行设置
    3. 以 2D 导航任务为例,base env 的观测是一个 key 包括 “agent_position” 和 “target_position” 的字典。常见的做法可能是丢掉一些自由度,只考虑目标相对于代理的位置,这个包装可以如下实现
      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"]
      
    4. 前文 2.3 节的 TimeAwareObservationTimeLimit 都是 gym.ObservationWrapper 的子类

3.2.2 gym.RewardWrapper 子类

  • class gym.RewardWrapper(env: Env)
    1. 所有 reward 包装类的超类
    2. 通过通过继承 RewardWrapper 并重写 reward() 方法实现对原始 reward 的包装,在 __init__() 时通过 self.reward_range 参数设置包装后 reward 的取值范围
    3. 例子:有时我们希望将奖励限制在一个范围内以获得一定的数值稳定性,这个包装可以如下实现
      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)
      

3.2.3 gym.ActionWrapper 子类

  • class gym.ActionWrapper(env: Env)
    1. 所有 action 包装类的超类
    2. 通过继承 ActionWrapper 并重写 action() 方法实现对原始 reward 的包装,在 __init__() 时通过 self.action_space 参数设置包装后 action 的取值 space
    3. 例子:base env 的 action_space 是 gym.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)
      
    4. 前文 2.3 节的 ClipActionRescaleAction 都是 gym.ActionWrapper 的子类
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云端FFF

所有博文免费阅读,求打赏鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值