文章目录
OpenAI Gym库
源码
import random
from typing import List
class Environment:
def __init__(self):
self.steps_left = 10
def get_observation(self) -> List[float]:
return [0.0, 0.0, 0.0]
def get_actions(self) -> List[int]:
return [0, 1]
def is_done(self) -> bool:
return self.steps_left == 0
def action(self, action: int) -> float:
if self.is_done():
raise Exception("Game is over")
self.steps_left -= 1
return random.random()
class Agent:
def __init__(self):
self.total_reward = 0.0
def step(self, env: Environment):
current_obs = env.get_observation()
actions = env.get_actions()
reward = env.action(random.choice(actions))
self.total_reward += reward
if __name__ == "__main__":
env = Environment()
agent = Agent()
while not env.is_done():
agent.step(env)
print("Total reward got: %.4f" % agent.total_reward)
注:以上源码可以直接运行,打印总的奖励值。
agent探深
在上面的源码中,agent是内置策略的一段代码。策略即是根据观测的结果,决定每一步的动作。
环境(Environment)则是除agent以外的部分,负责提供观测值和给出奖励值。环境根据动作值改变其状态。
首先创建环境类,初始化状态和总的步数:
class Environment:
def __init__(self):
self.steps_left = 10
定义获取环境状态函数,这里只是做一个演示,所以状态都设为0:
def get_observation(self) -> List[float]:
return [0.0, 0.0, 0.0]
定义动作函数,动作集合[0,1]:
def get_actions(self) -> List[int]:
return [0, 1]
设置标志位,查看episodes是否执行完毕:
def is_done(self) -> bool:
return self.steps_left == 0
注:有些episodes是无限的,本例子只取了10步。
下面是环境类中最重要的函数——动作函数,该函数有三个功能,一是判断episodes是否结束,若结束则返回“Game is over”;二是计算执行的步数;三是返回获得的奖励值。
def action(self, action: int) -> float:
if self.is_done():
raise Exception("Game is over")
self.steps_left -= 1
return random.random()
接下来看一看Agent类的代码:
class Agent:
def __init__(self):
self.total_reward = 0.0
def step(self, env: Environment):
current_obs = env.get_observation()
actions = env.get_actions()
reward = env.action(random.choice(actions))
self.total_reward += reward
首先初始化总的奖励为0,然后step函数接受环境的观察量,然后根据观测值选择动作,并获得奖励值。
最后是glue code,创建环境和agent,执行step函数,最后返回一个总的奖励值
if __name__ == "__main__":
env = Environment()
agent = Agent()
while not env.is_done():
agent.step(env)
print("Total reward got: %.4f" % agent.total_reward)
前面代码的简单性说明了来自RL模型的重要基本概念。环境可能是一个极其复杂的物理模型,agent可以很容易地成为实现最新RL算法的大型神经网络(NN),但基本模式将保持不变。在每一步,代理将从环境中进行一些观察,进行计算,并选择要采取的操作。这一行动的结果将是奖励和新的观察量。
硬件和软件要求
因为本书很重要的一部分内容是“DEEP”,也就是deep learning。所以硬件的要求是最好有GPU,主要是因为GPU的运行速度比同等的CPU要快10倍以上。也就是说,GPU一两个小时就可以完成的任务,在CPU上往往要运行1整天之久。
但是若没有GPU的话也没有关系,就是时间运行较长,如果你愿意等的话也可以运行出一样的结果出来。
这里推荐使用Google免费的GPU,大约有10GB的显存,大家没有条件的可以考虑登录google colab来运行自己的代码。
一些基本的软件库如:NumPy,OpenCV Python bindings,Gym,PyTorch,PyTorch Ignite,PTAN是必须的。其他的特定的库将在特定的章节指出。
软件的版本如下:(可以参考)
atari-py==0.2.6
gym==0.15.3
numpy==1.17.2
opencv-python==4.1.1.26
tensorboard==2.0.1
torch==1.3.0
torchvision==0.4.1
pytorch-ignite==0.2.1
tensorboardX==1.9
tensorflow==2.0.0
ptan==0.6
不一定非要和上面的一样,但是自己的软件的版本内部要不能冲突。特别是安装pytorch的时候,注意cudnn的版本,否则运行代码的时候会报错。具体可以参考:http://pytorch.org
OpenAI Gym API
Gym是一个python的库,由OpenAI公司维护(www.openai.com),主要目的是为了强化学习实验提供统一的界面。Gym库有非常多不同的环境,具体可以登录openai的官网查看。
一般每个Gym库中提供的环境都包含以下几个内容:
- 动作空间。这些动作可以是离散的或者连续的,或者二者的结合。
- 观测空间。即为环境的观测值提供给agent。
- step函数。该函数执行动作,并返回当前观测值,执行动作之后的奖励和判断episode是否结束。
- reset函数。将环境恢复到初始点。
下面可以具体说一下相关的部分。
动作空间
正如上面所说,动作空间的动作可以是离散的或连续的,或二者结合。
若动作是离散的,那么动作空间将是确定的。比如,网格世界中的动作只有:上,下,左,右。一共四个动作;按按钮的动作空间只有两个。
当动作空间是连续的时候,比如操控一个轮子,动作空间是连续的:[-720°,-720°];踩加速度踏板,动作空间也是连续的值:[0,1]。
然而有时候我们需要采用复合的动作,比如在同时操控两个轮子的时候,需要踩加速度踏板。Gym库定义了一个特殊的容器类,它允许将多个动作空间嵌套到一个统一的动作中。
观测空间
正如第一章中提到的,观测量是环境在每个时间戳上向代理提供的除了奖励的信息片段。观测量可以是一串数字,或者是不同摄像头的图片视频数据。观测量可以是离散的。例如,一个灯泡的观测空间就是离散的两个值:[0,1]
上面已经提到了动作空间和观测空间了,下面我们首先先说一下空间(Space)类,
上图是Gym库中Space类的层次结构。
- sample():返回空间的一个随机取样
- contains(x):检查x是否数据空间中的元素
- 子类:Discrete类中包含n个取值,序号从0到n-1;例如,Discrete(n=4),可以用作包含四个方向的动作空间:[up,down,left,right]。
- 子类:Box类中包含一个n维的张量(tensor)。例如,表示油门踏板加速度的观测量,
Box(low=0.0, high=1.0, shape=(1,), dtype=np.float32)
表示一个1维的张量,其值的取值范围是0~1.
又例如Atari游戏中的一个例子,其观测量是一个210×160像素的RGB图片:
Box(low=0, high=255, shape=(210, 160,3), dtype=np.uint8)
简单计算一下就可以知道每一个观测量都包含100800比特的数据量(大约为98KB)。每一个观测量都有这么大的数据量,所以体现出需要用GPU的必要性。
-Space的最后一个子类是一个Tuple类,它允许我们将几个Space类实例组合在一起。这使我们能够创建我们想要的任何复杂的动作和观察空间。
例如:
Tuple(spaces=(Box(low=-1.0, high=1.0, shape=(3,), dtype=np.float32), Discrete(n=3),Discrete(n=2))).
除了以上三个子类,Space类还有其他的子类,但是上面说的三个子类是最有用的。每个子类都包含contains()和sample()方法。
例如,Discrete.sample()返回离散范围内的随机元素,而Box.sample()将是一个具有适当维度和值的随机张量,位于给定范围内。
每个环境都有两个Space类的成员:action_space和observation_space。这允许我们创建可以在任何环境下工作的通用代码。当然,处理屏幕像素与处理离散观测不同(如前一种情况,我们可能希望使用卷积图层或计算机视觉工具箱中的其他方法对图像进行预处理);因此,大多数情况下,这意味着针对特定环境或环境组优化代码,但Gym并不妨碍我们编写通用代码。
环境
环境用Gym库中的Env类表示,有以下成员:
- action_space
- observation_space
- reset()
- step():此方法允许agent执行动作,并返回有关动作结果的信息——下一次观察、本地奖励和剧集结束标志。这种方法有点复杂,我们将在本节后面详细介绍它。
注:Env类中有额外的实用方法,比如Render(),它允许我们以友好的形式获得观察结果,但我们不会使用它们
与环境的通信通过step()和reset()执行
reset()方法在环境刚建立好和每集结束后时候,是状态恢复到环境的初始观测值。
Step()方法是环境功能的核心部分。它在一个调用中执行以下几项操作:
- 告诉环境我们下一步将执行哪个行动
- 在此行动之后从环境中获得新的观察结果
- 获得agent通过这一步骤获得的奖励
- 获得该集结束的指示
把step()置于一个循环中,直到一集结束。下面我们讲如何创建环境。
创建环境
EnvironmentName-vN
环境名+版本
N表示同一个环境下的不同版本,例如:
- Breakout-v0, Breakout-v4
- BreakoutDeterministic-v0, BreakoutDeterministic-v4
- BreakoutNoFrameskip-v0, BreakoutNoFrameskip-v4
- Breakout-ram-v0, Breakout-ram-v4
- Breakout-ramDeterministic-v0, Breakout-ramDeterministic-v4
- Breakout-ramNoFrameskip-v0, Breakout-ramNoFrameskip-v4
Gym 0.13.1有154个不相同的环境,分为以下几类:
- Classic control problems
- Atari 2600
- Algorithmic
- Board games
- Box2D
- MuJoCo
- Parameter tuning
- Toy text
- PyGame
- Doom
完整的环境可以参考链接:Gym.
倒立摆
举一个简单的例子——CartPole(倒立摆),大家可以自己在Console尝试一下:
>>> import gym
>>> e = gym.make('CartPole-v0')
倒立摆的问题是尽量使棍子保持平衡不掉落。初步的想法是,通过左、右的动作来控制倒立摆,每一个固定的时间步(比如1s)给予一个奖励值1,如果需要获得更多的奖励,则需要保持倒立摆一直不掉落(掉落则回合结束)。
后面的章节会介绍相关的算法,保持倒立摆在几分钟内都保持不掉落。现在我们主要通过trail and error来看看:
>>> obs=e.reset()
>>> obs
array([ 0.04736335, -0.02161967, -0.02069326, -0.0057936 ])
观测量是一个四个值的向量,从左到右分别是:倒立摆中心x坐标,速度,离水平面的角度以及角速度。
>>> e.action_space
Discrete(2)
>>> e.observation_space
Box(-3.4028234663852886e+38, 3.4028234663852886e+38, (4,), float32)
action_space的值是离散的,{0,1}——>{left,right}
observation_space的值是包含四个数值的一个向量,Box(4,)
>>> e.step(0)
(array([ 0.04693096, -0.21643883, -0.02080913, 0.28028919]), 1.0, False, {})
上面的代码让倒立摆左移,得到:
- 新的观测值:array([ 0.04693096, -0.21643883, -0.02080913, 0.28028919]);
- 奖励1.0;
- done标志位为False,说明该episode还没有结束
- 环境的额外信息
随机倒立摆agent
源码:
import gym
if __name__ == "__main__":
env = gym.make("CartPole-v0")
total_reward = 0.0
total_steps = 0
obs = env.reset()
while True:
action = env.action_space.sample() #random policy
obs, reward, done, _ = env.step(action)
total_reward += reward
total_steps += 1
if done:
break
print("Episode done in %d steps, total reward %.2f" % (
total_steps, total_reward))
首先创建环境,设置总的奖励和步数计数器为0。然后初始化环境。在循环中,采用随机策略,step函数返回值为环境观测值,奖励,done标志,其他信息。episode结束的时候,打印总的奖励值和步数。
运行结果为:
Episode done in 15 steps, total reward 15.00
一般,采用随机策略,杆子掉落步数为12~15步。一般的类似于这种情况的强化学习环境,会设置一个成功的步数N,也就是如果能保证N步之内或者更长,杆子不掉落,就说明策略成功。对于倒立摆环境,设置的是195。显然,采用随机策略不是一个很好的方法。
Gym库的wrappers和monitors
下面介绍另外两个API:wrappers和monitors,使用它们会让代码更加简洁。
Wrappers
wrapperd:n. 包装材料;[包装] 包装纸;书皮
有时候我们想让agent获得过去N(N>=2)的观测值,而不是像上面的只获得一步观测值;或者对于图像观测值只希望获得经过处理(例如:修剪)的像素。那么可以考虑使用Gym库的Wrapper类。
上图为Wrapper类的结构
Wrapper是Env类的继承,下面介绍一下它的三个子类:
- ObservationWrapper:重定义了observation函数,通过super()来调用父类的__init__,并加入新的变量:epsilon。返回特定的观测值给agent,比如上面说的过去几步总的观测值。
- RewardWrapper:这也是经过修改的奖励值
- ActionWrapper:是对原来的action函数的override,比如10%的概率随机选择。
import gym
from typing import TypeVar
import random
Action = TypeVar('Action')
class RandomActionWrapper(gym.ActionWrapper):
def __init__(self, env, epsilon=0.1):
super(RandomActionWrapper, self).__init__(env)
self.epsilon = epsilon
def action(self, action: Action) -> Action:
if random.random() < self.epsilon:
print("Random!")
return self.env.action_space.sample()
return action
if __name__ == "__main__":
env = RandomActionWrapper(gym.make("CartPole-v0"))
obs = env.reset()
total_reward = 0.0
while True:
obs, reward, done, _ = env.step(0)
total_reward += reward
if done:
break
print("Reward got: %.2f" % total_reward)
env = RandomActionWrapper(gym.make("CartPole-v0"))
上面这句代码的意思是,通过Wrapper构造器来把通常的倒立摆环境构造成我们需要的情况。比如你可以加入自己的epsilon。
剩下的代码应该都不难理解,大部分更上面的说的差不多。
总结一下就是Wrapper类把Gym库中一些固有的方法根据自己的需求做了修改,以满足自己特定的问题的情况。还是比较有用的!
下面介绍更有意思的Monitor。
Monitor
Monitor可以把agent的学习相关信息存到一个文件中,同时可以用动画显示agent的动作。有很多很漂亮的实例可以参考官方网站:gym.openai.com
直接上代码:
import gym
if __name__ == "__main__":
env = gym.make("CartPole-v0")
env = gym.wrappers.Monitor(env, "recording")
total_reward = 0.0
total_steps = 0
obs = env.reset()
while True:
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
total_reward += reward
total_steps += 1
if done:
break
print("Episode done in %d steps, total reward %.2f" % (
total_steps, total_reward))
env.close()
env.env.close()
有了monitor之后,就可以图像显示出agent的动作了,并且可以把动作的视频输出为文件。
后记
本章主要带大家看了一些强化学习的代码,感觉还是不难的。下一章介绍深度学习库PyTorch,敬请期待!