OpenAI gym的建模思想
一、强化学习问题需要描述那些内容
强化学习中最主要的两类对象是“个体”和“环境”,其次还有一些像“即时奖励”、“收获”、“状态”、“行为”、“价值”、“策略”、“学习”、“控制”等概念。这些概念把个体和环境联系起来。通过理论学习,我们知道:
1. 环境响应个体的行为。当个体执行一个行为时,它需要根据环境本身的动力学来更新环境,也包括更新个体状态,同时给以个体一个反馈信息:即时奖励。
2. 对于个体来说,它并不掌握整个环境信息,它只能通过观测来获得其可以获得的信息,它能观测到哪些信息取决于问题的难度;同样个体需要一定的行为与环境进行交互,哪些行为是被允许的,也要由个体和环境协商好。因此环境要确定个体的观测空间和行为空间。
3. 个体还应该有一个决策功能,该功能根据当前观测来判断下一时刻该采取什么行为,也就是决策过程。
4. 个体具有执行一个确定行为的功能。
5. 智能的个体应能从与环境的交互中学习到知识,进而在与环境交互时尽可能多的获取奖励,最终达到最大化累积奖励的目的。
6. 环境应该给个体设置一个(些)终止条件,即当个体处在这个状态或这些状态之一时,约定交互结束,即产生一个完整的Episode。随后重新开始一个Episode或者退出交互。
把上面的过程提炼成伪代码可以是下面这样:
按照上面的设计,可以写出一个不错的个体与环境的类。但是我们不打算按照这个写下去,我们看看gym库是如何描述环境,以及个体通过什么方式与环境进行交互。
二、gym库介绍
gym库的在设计环境以及个体的交互时基本上也是解决上述问题,但是它有它的规范和接口。gym库的核心在文件core.py里,这里定义了两个最基本的类Env和Space。前者是所有环境类的基类,后者是所有空间类的基类。从Space基类衍生出几个常用的空间类,其中最主要的是Discrete类和Box类。通过其__init__方法的参数以及其它方法的实现可以看出前者对应于一维离散空间,后者对应于多维连续空间。它们既可以应用在行为空间中,也可以用来描述状态空间,具体怎么用看问题本身。例如如果我要描述上篇提到的一个4*4的格子世界,其一共有16个状态,每一个状态只需要用一个数字来描述,这样我可以把这个问题的状态空间用Discrete(16)对象来描述就可以了。对于另外一个经典的小车爬山的问题(如下图),小车的状态是用两个变量来描述的,一个是小车对应目标旗杆的水平距离,另一个是小车的速度(是沿坡度切线方向的速率还是速度在水平方向的分量这个没仔细研究),因此环境要描述小车的状态需要2个连续的变量。由于描述小车的状态数据对个体完全可见,因此小车的状态空间即是小车的观测空间,此时再用Discrete来描述就不行了,要用Box类,Box空间可以定义多维空间,每一个维度可以用一个最低值和最大值来约束。同时小车作为个体可以执行的行为只有3个:左侧加速、不加速、右侧加速。因此行为空间可以用Discrete来描述。最终,该环境类的观测空间和行为空间描述如下:
从这段代码可以看出,要定义一个Discrete类的空间只需要一个参数n就可以了,而定义一个多维的Box空间需要知道每一个维度的最小最大值,当然也要知道维数。
有了描述空间的对象,再来看环境类如何声明就简单的多了。先来看看代码中关于环境基类的一段解释:
看得出,个体主要通过环境的一下几个方法进行交互:step,reset,render,close,seed,而这几个都是公用方法,具体每一个方法实际调用的都是其内部方法:_step,_reset,_render,_close,_seed。此外这段描述还指出,如果你要编写自己的环境类,也主要是重写这些私有方法,同时指定该环境的观测和行为空间。_close方法可以不用重写。这几个方法主要完成的个性化功能如下:
_step: 最核心的方法,定义环境的动力学,确定个体的下一个状态、奖励信息、是否Episode终止,以及一些额外的信息,按约定,额外的信息不被允许用来训练个体。
_reset: 开启个体与环境交互前调用该方法,确定个体的初始状态以及其他可能的一些初始化设置。
_seed: 设置一些随机数的种子。
_render: 如果需要将个体与环境的交互以动画的形式展示出来的话,需要重写该方法。简单的UI设计可以用gym包装好了的pyglet方法来实现,这些方法在rendering.py文件里定义。具体使用这些方法进行UI绘制需要了解基本的OpenGL编程思想和接口,这里暂时不做细说。
可以看出,使用gym编写自己的Agent代码,需要在你的Agent类中声明一个env变量,指向对应的环境类,个体使用自己的代码产生一个行为,将该行为送入env的step方法中,同时得到观测状态、奖励值、Episode是否终止以及调试信息等四项信息组成的元组:
state 是一个元组或numpy数组,其提供的信息维度应与观测空间的维度一样、每一个维度的具体指在制定的low与high之间,保证state信息符合这些条件是env类的_step方法负责的事情。
reward 则是根据环境的动力学给出的即时奖励,它就是一个数值。
is_done 是一个布尔变量,True或False,你可以根据具体的值来安排个体的后续动作。
info 提供的数据因环境的不同差异很大,通常它的结构是一个字典:
{"key1":data1,"key2":data2,...}
获取其中的信息应该不难。
最后一点,在自己的代码中如何建立个环境类对象呢?有两种情况,一种是在gym库里注册了的对象,你只要使用下面的语句:
其中不同的环境类有不同的注册名,只要把make方法内的字符串改成对应的环境名就可以了。
另外一种使用自己编写的未注册的环境类,这种很简单,同一般的建立对象的语句没什么区别:
相信读者已经基本清楚了如何使用gym提供的环境类了。下一步就是如何编写自己的环境类了。