- 深度强化学习实践(原书第2版)
- (俄)马克西姆·拉潘
- 2329字
- 2021-08-18 17:39:19
2.5 Gym的额外功能:包装器和监控器
到目前为止,我们已经讨论了Gym核心API的三分之二,以及编写智能体所需的基础功能。剩下的API你可以不学,但是它们能让你更轻松地编写整洁的代码。所以还是简单介绍一下剩余的API!
2.5.1 包装器
很多时候,你希望以某种通用的方式扩展环境的功能。例如,想象一个环境,它给了你一些观察,但是你想将它们累积缓存起来,用以提供智能体最近N个观察。这在动态计算机游戏中是一个很常见的场景,比如单一一帧不足以了解游戏状态的完整信息。例如,你希望能裁剪或预处理一些图像像素以便智能体来消化这些信息,又或者你想以某种方式归一化奖励值。有相同结构的场景太多了,你可能想要将现有的环境“包装”起来并附加一些额外的逻辑。Gym为这些场景提供了一个方便使用的框架——Wrapper
类。
类的结构如图2.4所示。
![048-01](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/048-01.jpg?sign=1739122311-zXuwcwfGVZu7cyDBJ5AKDe8RiBIiaelU-0-124fdc1f5a3b7629814ad4b9df88d9c9)
图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](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/049-01.jpg?sign=1739122311-OmHmIs337BpIilNp6ydRhSiWC5MGJMSE-0-036a48cd1111ce1e11fc87753e22e8ff)
先通过调用父类的__init__
方法初始化包装器,并保存epsilon(随机动作的概率)。
![049-02](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/049-02.jpg?sign=1739122311-zPusgSKKCnHDvVcgPiq9nP7VWeN3CDgs-0-5d094899283ee80e792363827b2b8cb4)
我们需要覆盖这个方法,并通过它来修改智能体的动作。每一次都先掷骰子,都会有epsilon的概率从动作空间采样一个随机动作,用来替换智能体传给我们的动作。注意,这里用了action_space
和包装抽象,这样就能写抽象的代码了,这适用于Gym的任意一个环境。另外,每次替换动作的时候必须将消息打印出来,以验证包装器是否生效。当然,在生产代码中,这不是必需的。
![049-03](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/049-03.jpg?sign=1739122311-SXYUtS0n48bKUR2Uh8jsOt4GZn0J6Nhs-0-ed2161b7f3ce9bb68f588ed1f3a71dbf)
是时候应用一下包装器了。创建一个普通的CartPole环境,并将其传入Wrapper
构造函数。然后,将Wrapper
类当成一个普通的Env
实例,用它来取代原始的CartPole。因为Wrapper
类继承自Env
类,并且暴露了相同的接口,我们可以任意地嵌套包装器。这是一个强大、优雅且通用的解决方案。
![049-04](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/049-04.jpg?sign=1739122311-XpGLVOGYPPNzRozTLRapsw6IEYcEhCG4-0-f3a32c9059a04a899971c371e44666eb)
除了智能体比较笨,每次都选择同样的0号动作外,代码几乎相同。通过运行代码,应该能看到包装器确实在生效了:
![050-01](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/050-01.jpg?sign=1739122311-py21qoopycKSlsZDkUQbNijkIV7a03K6-0-882411b0f6d6dbf1996c89f495c1cef9)
如果愿意,可以在包装器创建时指定epsilon参数,验证这样的随机性平均下来,会提升智能体得到的分数。
继续来看Gym中隐藏的另外一个有趣的宝藏:Monitor
(监控器)。
2.5.2 监控器
另一个应该注意的类是Monitor
。它的实现方式与Wrapper
类似,可以将智能体的性能信息写入文件,也可以选择将智能体的动作录下来。之前,还可以将Monitor
类的记录结果上传到https://gym.openai.com,查看智能体和其他智能体对比的结果排名(见图2.5),但不幸的是,在2017年8月末,OpenAI决定关闭此上传功能并销毁所有原来的结果。虽然有好几个提供相同功能的网站,但是它们都还没完全准备好。希望这个窘境能很快被解决,但是在撰写本书时,还无法将自己的智能体和他人的进行比较。
为了让你大致了解Gym的网页,图2.5给出了CartPole环境的排行榜。
![050-02](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/050-02.jpg?sign=1739122311-YtJDkov6Ba6KPY2SzpFBsx9Clxr5tar5-0-b1694113c7bd5356141935c37bbf195b)
图2.5 Gym网站上的CartPole提交页面
在网页上的每次提交都包含了训练的动态详情。例如,图2.6是我训练《毁灭战士》迷你游戏时得到的结果记录。
![051-01](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/051-01.jpg?sign=1739122311-AoUADbm7j9QKfZFx5wUbMBAgeZkZAyLT-0-f76334a37a2b19fe400c81bc343b7352)
图2.6 DoomDefendLine环境提交后的动态显示
尽管如此,Monitor
仍然很有用,因为你可以查看智能体在环境中的行动情况。所以,还是看一下如何将Monitor
加入随机CartPole智能体中,唯一的区别就是下面这段代码(完整的代码见Chapter02/04_cartpole_random_monitor.py
):
![051-02](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/051-02.jpg?sign=1739122311-0RRNeNQXT2DhPsSaYZYRbvhWJb93mEk4-0-e6950d6583ff3c24e9e402d6cdfd35ee)
传给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](https://epubservercos.yuewen.com/10FB3F/20903674308617306/epubprivate/OEBPS/Images/052-01.jpg?sign=1739122311-sBMoQLPER6mjVhCOcbu8keS054VR3OVW-0-8a19c45efc17c642a843e7ee3e5f5f68)
从前面的日志可以看到,视频已经成功写入,因此可以通过播放来窥视智能体的某个部分。
另一个录制智能体动作的方法是使用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
类的程序,它会捕获智能体的动作,并保存成视频文件。