首页 > 强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例

强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例

强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例

1. 引言

在这个部分补充之前马尔科夫决策和动态规划部分的代码。在以后的内容我会把相关代码都附到相关内容的后面。本部分代码和将来的代码会参考《深度强化学习原理与python实现》与Udacity的课程《Reinforcement Learning》。

2. Gym库

Gym库(http://gym.openai.com/)是OpenAI推出的强化学习实验环境库,它用python语言实现了离散时间智能体/环境接口中的环境部分。除了依赖少量的商业库外,整个项目时开源免费的。

在这里插入图片描述

Gym库内置上百种实验环境,包括以下几类。

  • 算法环境:包括一些字符串处理等传统计算机方法的实验环境。
  • 简单文本环境:包括几个用文本表示的简单游戏。
  • 经典控制环境:包括一些简单几何体运动,常用于经典强化学习算法的研究。
  • Atari游戏环境:包括数十个Atari 2600游戏,具有像素化的图形界面,希望玩家尽可能夺得高分。
  • MuJoCo环境:利用收费的MuJoCo运动引擎进行连续性的控制任务。
  • 机械控制环境:关于机械臂的抓取和控制等。

2.1 Gym库安装

Python版本要求为 3.5+(在以后的内容中我将使用python 3.6)。然后就可以是使用pip安装Gym库

首先升级pip,

pip install --upgrade pip

以下命令为最小安装Gym环境,

pip install gym
利用源代码构建

如果愿意,还可以直接克隆gym Git存储库。若想要修改Gym本身或添加环境时,这是特别有用的。下载和安装使用:

git clone https://github.com/openai/gym
cd gym
pip install -e .

之后可以运行pip install -e .[all]来执行包含所有环境的完整安装。这需要安装多个涉及的依赖项,包括cmake和一个最近的pip版本。

2.2 Gym库使用

2.2.1 环境(enviroment)

这里有一个运行程序的最简单的例子。这将为1000个时间步骤运行CartPole-v0环境的一个实例,并在每个步骤中呈现环境。你应该看到一个窗口弹出渲染经典的车杆问题:

import gym
env = gym.make('CartPole-v0')
env.reset()
for _ in range(1000):env.render()env.step(env.action_space.sample()) # 采取随机行动
env.close()

输出如下:

在这里插入图片描述

通常,我们将结束模拟之前,车杆被允许离开屏幕。现在,忽略关于调用step()的警告,即使这个环境已经返回done = True

如果希望运行其他一些环境,可以尝试用类似MountainCar-v0MsPacman-v0(需要Atari依赖项)或Hopper-v1(需要MuJoCo依赖项)来替换上面的cartpole-v0。环境都来自于Env基类。

若丢失了任何依赖项,应该会得到错误消息来提示丢失了什么。安装缺少的依赖项通常非常简单。对于Hopper-v1需要一个的MuJoCo许可证。

代码解释

导入Gym库:

import gym

在导入Gym库后,可以通过make()函数来得到环境对象。每一个环境都有一个ID,它是形如“Xxxxx-v0”的字符串,如“CartPloe-v0”等。环境名称的最后的部分表示版本号,不同版本的环境可能有不同的行为。使用取出环境“CartPloe-v0”的代码为:

env = gym.make('CartPole-v0')

想要查看当前的Gym库已经注册了哪些环境,可以使用以下代码:

from gym import envs
env_specs = envs.registry.all()
env_ids = [env_spec.id for env_spec in env_specs]
env_ids

每个环境都定义了自己的观测空间和动作空间。环境env的观测空间用env.observation_space表示,动作空间用env.action_space表示。观测空间和动作空间既可以是离散空间(即取值是有限个离散的值),也可以使连续的空间(即取值是连续的)。在Gym库中,离散空间一般用gym.spaces.Discrete类表示,连续空间用gym.spaces.Box类表示。例如,环境‘MountainCar-v0’的动作空间是Box(2,)表示观测可以用2个float值表示;环境‘MountainCar-v0’的动作空间是Dicrete(3),表示动作取自{0,1,2}。对于离散空间gym.spaces.Discrete类实例成员n表示有几个可能的取值;对于连续空间,Box类实例的成员low和high表示每个浮点数的取值范围。

接下来使用环境对象env。首先初始化环境对象env,

env.reset()

该调用能返回智能体的初始观测,是np.array对象。

环境初始化之后就可以使用了。使用环境的核心是使用环境对象的step()方法,具体内容会在下面介绍。

env.step()的参数需要取自动作空间。可以使用以下语句从动作空间中随机选取一个动作。

action = env.action_space.sample()

每次调用env.step()只会让环境前进一步。所以,env.step()往往放在循环结构里,通过循环调用来完成整个回合。

在env.reset()或env.step()后,可以使用以下语句以图形化的方法显示当前的环境。

env.render()

使用完环境后,可以使用下列语句关闭环境。

env.close()

如果绘制和实验的图形界面窗口,那么关闭该窗口的最佳方式是调用env.close()。试图直接关闭图形界面可能会导致内存不能释放,甚至会导致死机。

测试智能体在Gym库中某个任务的性能时,学术界一般最关心100个回合的平均回合奖励。对于有些环境,还会制定一个参考的回合奖励值,当连续100个回合的奖励大于指定的值时,就认为这个任务被解决了。但是,并不是所有的任务都指定了这样的值。对于没有指定值的任务,就无所谓任务被解决了或者没有被解决。

2.2.2 观测(observation)

如果我们想得到一个更好的结果,那我们就不能在每一步都采取随机的行动。我们需要知道我们的行动对环境做了什么可能会更好。

环境的step函数返回的正是我们需要的东西。事实上,step函数会返回四个值。这四个值是

  • observation (object): 一个特定环境的对象,代表智能体对环境的观察。例如,来自摄像头的像素数据,机器人的关节角度和关节速度,或者棋盘游戏中的棋盘状态,和env.reset()返回值的意义相同。。
  • reward(float):前一个动作所获得的奖励量。不同环境下的比例不同,但目标始终是增加你的总奖励。
  • done (boolean):是否要再次重置环境。大多数(但不是全部)任务都被划分为定义明确的事件,done为True表示该事件已经终止。(例如,可能是杆子翻得太厉害,或者失去了最后的生命。)
  • info(dict):对调试有用的诊断信息。它有时会对学习有用(例如,它可能包含环境最后一次状态变化背后的原始概率)。然而,你的智能体的评估是不允许使用它来学习的。

这只是经典的 "智能体-环境循环 "的一个实现。每一个时间步,智能体选择一个动作(action),环境返回一个观察(observation)和一个奖励(reward)

在这里插入图片描述

import gym
env = gym.make('CartPole-v0')
for i_episode in range(20):observation = env.reset()for t in range(100):env.render()print(observation)action = env.action_space.sample()observation, reward, done, info = env.step(action)if done:print("Episode finished after {} timesteps".format(t+1))break
env.close()

输出如下:

在这里插入图片描述

[-0.061586   -0.75893141  0.05793238  1.15547541]
[-0.07676463 -0.95475889  0.08104189  1.46574644]
[-0.0958598  -1.15077434  0.11035682  1.78260485]
[-0.11887529 -0.95705275  0.14600892  1.5261692 ]
[-0.13801635 -0.7639636   0.1765323   1.28239155]
[-0.15329562 -0.57147373  0.20218013  1.04977545]
Episode finished after 14 timesteps
[-0.02786724  0.00361763 -0.03938967 -0.01611184]
[-0.02779488 -0.19091794 -0.03971191  0.26388759]
[-0.03161324  0.00474768 -0.03443415 -0.04105167]

2.2.3 空间(spaces)

在上面的示例中,我们从环境的操作空间中随机取样操作。但这些行为到底是什么呢?每个环境都带有一个action_space和一个observation_space。这些属性类型为Space,它们描述了有效动作和观察的格式:

import gym
env = gym.make('CartPole-v0')
print(env.action_space)
#> Discrete(2)
print(env.observation_space)
#> Box(4,)

离散(Discrete)空间允许一个固定的非负数范围,因此在本例中有效的动作(action)要么是0要么是1。Box空间表示一个n维的盒子,因此有效的观察结果将是一个4个数字的数组。我们也可以检查Box的边界:

print(env.observation_space.high)
#> array([ 2.4       ,         inf,  0.20943951,         inf])
print(env.observation_space.low)
#> array([-2.4       ,        -inf, -0.20943951,        -inf])

这种内省有助于编写适用于许多不同环境的泛型代码。Box离散空间是最常见的空间(space)。我们可以从一个空间取样或检查某物是否属于它:

from gym import spaces
space = spaces.Discrete(8) # Set with 8 elements {0, 1, 2, ..., 7}
x = space.sample()
assert space.contains(x)
assert space.n == 8

2.2.4 程序实例

这里我们通过一个完整的例子来学习如何与Gym库中的环境交互,选用经典控制任务,小车上山(MountainCar-v0)。这里主要介绍交互的代码,而不详细说明这个控制任务及其求解。

首先我们看一下这个任务的观测空间的动作空间

import gym
env = gym.make('MountainCar-v0')
print('观测空间 = {}'.format(env.observation_space))
print('动作空间 = {}'.format(env.action_space))
print('观测范围 = {} ~ {}'.format(env.observation_space.low, env.observation_space.high))
print('动作数 = {}'.format(env.action_space.n))

输出如下,

观测空间 = Box(2,)
动作空间 = Discrete(3)
观测范围 = [-1.2  -0.07] ~ [0.6  0.07]
动作数 = 3

运行结果告诉我们,观测空间是形状为(2,)的浮点型np.array,而动作空间是取{0, 1, 2}的int型数值。

接下来考虑智能体。智能体往往是我们自己实现的。下面代码给出了一个智能体类——BespokeAgent类。智能体的decide()方法实现了决策功能,而learn()方法实现了学习功能。BespokeAgent类是一个比较简单的类,它只能根据给定的数学表达式进行决策,并且不能有效学习。所以它并不是一个真正意义上的强化学习智能体类。但是,用于演示智能体和环境的交互已经足够了。

class BespokeAgent:def __init__(self, env):passdef decide(self, observation):  # 决策position, velocity = observationlb = min(-0.09 * (position + 0.25) ** 2 + 0.03, 0.3 * (position + 0.9) ** 4 - 0.008)ub = -0.07 * (position + 0.38) ** 2 + 0.06if lb < velocity < ub:action = 2else:action = 0return action  # 返回动作def learn(self, *args):  # 学习passagent = BespokeAgent(env)

接下来我们试图让智能体与环境交互。play_once()函数可以让智能体和环境交互一个回合。这个函数有4个参数。

  • 参数env是环境类。
  • 参数agent是智能体类。
  • 参数render是bool类型变量,指示在运行过程中是否要图形化显示。如果函数参数render为True,那么在交互过程中会调用env.render()以显示图形化界面,而这个界面可以通过调用env.close()关闭。
  • 参数train是bool类型的变量,指示在运行过程中是否训练智能体。在训练过程中应当设置为True,以调用agent.learn()函数;在测试过程中应当设置为False,使得智能体不变。

这个函数有一个返回值episode_reward,是float类型的数值,表示智能体与环境交互一个回合的回合总奖励。

def play_montecarlo(env, agent, render=False, train=False):episode_reward = 0.  # 记录回合总奖励,初始化为0observation = env.reset()  # 重置游戏环境,开始新回合while True:  # 不断循环,直到回合结束if render:  # 判断是否显示env.render()  # 显示图形界面,图形界面可以用env.close()语句关闭action = agent.decide(observation)next_observation, reward, done, _ = env.step(action)  # 执行动作episode_reward += reward  # 收集回合奖励if train:  # 判断是否训练智能体agent.learn(observation, action, reward, done)  # 学习if done:  # 回合结束,跳出循环breakobservation = next_observationreturn episode_reward  # 返回回合总奖励

我们可以用下列代码让智能体和环境交互一个回合,并在交互过程中图形化显示。交互完毕后,可用env.close()语句关闭图形化界面。

env.seed(0)  # 设置随机数种子,只是为了让结果可以精确复现,一般情况下可删去
episode_reward = play_montecarlo(env, agent, render=True)
print('回合奖励 ={}'.format(episode_reward))
env.close()  # 此语句可关闭图形界面

为了系统评估智能体的性能,下列代码求出了连续交互100回合的平均回合奖励。小车上山环境有一个参考的回合奖励值-110,如果当连续100个回合的平均回合奖励大于-110,则认为这个任务被解决了。BespokeAgent类对应的策略的平均回合奖励大概就在-110左右。

episode_rewards = [play_montecarlo(env, agent) for _ in range(100)]
print('平均回合奖励 ={}'.format(np.mean(episode_rewards)))

3.Markov 决策过程

3.1 实例:悬崖寻路

本节考虑Gym库中悬崖寻路问题(CliffWalking-v0)。悬崖寻路问题是这样一种回合制问题:在一个4×124 imes124×12的网格中,智能体最开始在左下角的网格,希望移动到右下角的网格。智能体每次可以在上下左右这四个方向中移动一步,每移动一步会惩罚一个单位的奖励。但是,移动有以下的限制。

  • 智能体不能移出网格。如果智能体想执行某个动作移出网格,那么就让本步不移动。但是这个操作仍然会惩罚一个单位的奖励。
  • 如果智能体将要到达最下一排网格(几开始网格和目标网格之间的10个网格),智能体会立刻回到开始网格,并惩罚100个单位的奖励。这10个网格可被视为“悬崖”。

当智能体移动到终点时,回合结束,回合奖励为各步奖励相加。

在这里插入图片描述

3.1.1 实验环境使用

以下代码演示了如何倒入这个环境并查看这个环境的基本信息。

import gym
env = gym.make('CliffWalking-v0')
print('观察空间 = {}'.format(env.observation_space))
print('动作空间 = {}'.format(env.action_space))
print('状态数量 = {}, 动作数量= {}'.format(env.nS, env.nA))
print('地图大小 = {}'.format(env.shape))

输出为,

观察空间 = Discrete(48)
动作空间 = Discrete(4)
状态数量 = 48, = 4
地图大小 = (4, 12)

这个环境是一个离散的Markov决策过程。在这个Markov决策过程中,每个状态是取自S={0,1,...,46}mathcal{S}= {0,1,...,46}S={ 0,1,...,46}的int型数值(加上终止状态则为S+=0,1,...,46,47mathcal{S}^+={0,1,...,46,47}S+=0,1,...,4

更多相关:

  • 策略梯度方法引言9.1 策略近似和其优势9.2 策略梯度定理9.2.1 梯度上升和黑箱优化9.2.2 策略梯度定理的证明9.3 REINFORCE:蒙特卡洛策略梯度9.3.1 轨迹上的REINFORCE算法9.3.2 REINFORCE算法实例9.4 带基线的REINFORCE算法...

  • 3.动态规划 3.1 介绍 术语动态规划(DP:Dynamic Programming) 指的是一个算法集合,可以用来计算最优策略,给定一个完美的环境模型,作为马尔可夫决策过程(MDP)。经典的DP算法在强化学习中的应用有限,因为它们的假设是一个完美的模型,而且计算量大,但它们仍然具有重要的理论意义。DP为理解其余部分中介绍的方法...

  • 临下班前一位做引擎的小伙伴提了个小问题, Rocksdb 实现了非常多的Env backend 这一些backend 可以让用户根据自己需求创建不同 公共接口backend,来实现自己的文件操作或者公共线程池操作。 Env* env = new rocksdb::HdfsEnv(FLAGS_hdfs) 问题是,为什么Rocksd...