深度强化学习实践(原书第2版)
上QQ阅读APP看书,第一时间看更新

2.5 Gym的额外功能:包装器和监控器

到目前为止,我们已经讨论了Gym核心API的三分之二,以及编写智能体所需的基础功能。剩下的API你可以不学,但是它们能让你更轻松地编写整洁的代码。所以还是简单介绍一下剩余的API!

2.5.1 包装器

很多时候,你希望以某种通用的方式扩展环境的功能。例如,想象一个环境,它给了你一些观察,但是你想将它们累积缓存起来,用以提供智能体最近N个观察。这在动态计算机游戏中是一个很常见的场景,比如单一一帧不足以了解游戏状态的完整信息。例如,你希望能裁剪或预处理一些图像像素以便智能体来消化这些信息,又或者你想以某种方式归一化奖励值。有相同结构的场景太多了,你可能想要将现有的环境“包装”起来并附加一些额外的逻辑。Gym为这些场景提供了一个方便使用的框架——Wrapper类。

类的结构如图2.4所示。

048-01

图2.4 Gym中Wrapper类的层级

Wrapper类继承自Env类。它的构造函数只有一个参数,即要被“包装”的Env类的实例。为了附加额外的功能,需要重新定义想扩展的方法,例如step()reset()。唯一的要求就是需要调用超类中的原始方法。

为了处理更多特定的要求,例如Wrapper类只想要处理环境返回的观察或只处理动作,那么用Wrapper的子类过滤特定的信息即可。它们分别是:

  • ObservationWrapper:需要重新定义父类的observation(obs)方法。obs参数是被包装的环境给出的观察,这个方法需要返回给予智能体的观察。
  • RewardWrapper:它暴露了一个reward(rew)方法,可以修改给予智能体的奖励值。
  • ActionWrapper:需要覆盖action(act)方法,它能修改智能体传给被包装环境的动作。

为了让它更实用,假设有一个场景,我们想要以10%的概率干涉智能体发出的动作流,将当前动作替换成随机动作。这看起来不是一个明智的决定,但是这个小技巧可以解决第1章提到的利用与探索问题,它是最实用、最强大的方法之一。通过发布随机动作,让智能体探索环境,时不时地偏离它原先策略的轨迹。使用ActionWrapper类很容易就能实现(完整的例子见Chapter02/03_random_action_wrapper.py)。

049-01

先通过调用父类的__init__方法初始化包装器,并保存epsilon(随机动作的概率)。

049-02

我们需要覆盖这个方法,并通过它来修改智能体的动作。每一次都先掷骰子,都会有epsilon的概率从动作空间采样一个随机动作,用来替换智能体传给我们的动作。注意,这里用了action_space和包装抽象,这样就能写抽象的代码了,这适用于Gym的任意一个环境。另外,每次替换动作的时候必须将消息打印出来,以验证包装器是否生效。当然,在生产代码中,这不是必需的。

049-03

是时候应用一下包装器了。创建一个普通的CartPole环境,并将其传入Wrapper构造函数。然后,将Wrapper类当成一个普通的Env实例,用它来取代原始的CartPole。因为Wrapper类继承自Env类,并且暴露了相同的接口,我们可以任意地嵌套包装器。这是一个强大、优雅且通用的解决方案。

049-04

除了智能体比较笨,每次都选择同样的0号动作外,代码几乎相同。通过运行代码,应该能看到包装器确实在生效了:

050-01

如果愿意,可以在包装器创建时指定epsilon参数,验证这样的随机性平均下来,会提升智能体得到的分数。

继续来看Gym中隐藏的另外一个有趣的宝藏:Monitor(监控器)。

2.5.2 监控器

另一个应该注意的类是Monitor。它的实现方式与Wrapper类似,可以将智能体的性能信息写入文件,也可以选择将智能体的动作录下来。之前,还可以将Monitor类的记录结果上传到https://gym.openai.com,查看智能体和其他智能体对比的结果排名(见图2.5),但不幸的是,在2017年8月末,OpenAI决定关闭此上传功能并销毁所有原来的结果。虽然有好几个提供相同功能的网站,但是它们都还没完全准备好。希望这个窘境能很快被解决,但是在撰写本书时,还无法将自己的智能体和他人的进行比较。

为了让你大致了解Gym的网页,图2.5给出了CartPole环境的排行榜。

050-02

图2.5 Gym网站上的CartPole提交页面

在网页上的每次提交都包含了训练的动态详情。例如,图2.6是我训练《毁灭战士》迷你游戏时得到的结果记录。

051-01

图2.6 DoomDefendLine环境提交后的动态显示

尽管如此,Monitor仍然很有用,因为你可以查看智能体在环境中的行动情况。所以,还是看一下如何将Monitor加入随机CartPole智能体中,唯一的区别就是下面这段代码(完整的代码见Chapter02/04_cartpole_random_monitor.py):

051-02

传给Monitor类的第二个参数是监控结果存放的目录名。目录不应该存在,否则程序会抛出异常(为了解决这个问题,要么手动删除目录,要么将force=True的参数传入Monitor的构造函数)。

Monitor类要求系统中有FFmpeg工具,用来将观察转换成视频文件。这个工具必须存在,否则Monitor将抛出异常。安装FFmpeg的最简单方式就是使用系统的包管理器,每个操作系统安装的方式都不同。

要执行此示例的代码,还应该满足以下三个前提中的一个:

  • 代码应该在带有OpenGL扩展(GLX)的X11会话中运行。
  • 代码应该在Xvfb虚拟显示器中运行。
  • 在SSH连接中使用X11转发。

这样做的原因是Monitor需要录制视频,也就是不停地对环境绘制的窗口进行截屏。一些环境使用OpenGL来画图,所以需要OpenGL的图形模式。云虚拟机可能会比较麻烦,因为它们没有显示器以及图形界面。为了解决这个问题,可以使用特殊的“虚拟”显示器,它被称为Xvfb(X11虚拟帧缓冲器),它会在服务器端启动一个虚拟的显示器并强制程序在它里面绘图。这足以使Monitor愉快地生成视频了。

为了在程序中使用Xvfb环境,需要安装它(通常需要安装xvfb包)并执行特定的脚本xvfb-run

052-01

从前面的日志可以看到,视频已经成功写入,因此可以通过播放来窥视智能体的某个部分。

另一个录制智能体动作的方法是使用SSH X11转发,它使用SSH的能力在X11客户端(想要显示图形信息的Python代码)和X11服务器(能访问物理显示器并知道如何显示这些图形信息的软件)之间构建了一个X11通信隧道。

在X11架构中,客户端和服务器能被分离到不同的机器上。为了使用这个方法,需要:

1)一个运行在本地机器上的X11服务器。X11服务器是Linux上的标准组件(所有的桌面环境都使用X11)。在Windows机器上,可以使用第三方X11实现,比如开源软件VcXsrv(https://sourceforge.net/projects/vcxsrv/)。

2)通过SSH登录远程机器的能力,传入-X命令行选项:ssh -X servername。该命令会建立X11隧道,并允许所有在这个会话中启动的程序访问本地的显示器输出图像。

然后,你就能启动使用Monitor类的程序,它会捕获智能体的动作,并保存成视频文件。