在这个部分补充之前马尔科夫决策和动态规划部分的代码。在以后的内容我会把相关代码都附到相关内容的后面。本部分代码和将来的代码会参考《深度强化学习原理与python实现》与Udacity的课程《Reinforcement Learning》。
Gym库(http://gym.openai.com/)是OpenAI推出的强化学习实验环境库,它用python语言实现了离散时间智能体/环境接口中的环境部分。除了依赖少量的商业库外,整个项目时开源免费的。
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版本。
这里有一个运行程序的最简单的例子。这将为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-v0
、MsPacman-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个回合的奖励大于指定的值时,就认为这个任务被解决了。但是,并不是所有的任务都指定了这样的值。对于没有指定值的任务,就无所谓任务被解决了或者没有被解决。
如果我们想得到一个更好的结果,那我们就不能在每一步都采取随机的行动。我们需要知道我们的行动对环境做了什么可能会更好。
环境的step
函数返回的正是我们需要的东西。事实上,step
函数会返回四个值。这四个值是
这只是经典的 "智能体-环境循环 "的一个实现。每一个时间步,智能体选择一个动作(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]
在上面的示例中,我们从环境的操作空间中随机取样操作。但这些行为到底是什么呢?每个环境都带有一个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
这里我们通过一个完整的例子来学习如何与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个参数。
这个函数有一个返回值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)))
本节考虑Gym库中悬崖寻路问题(CliffWalking-v0)。悬崖寻路问题是这样一种回合制问题:在一个4×124 imes124×12的网格中,智能体最开始在左下角的网格,希望移动到右下角的网格。智能体每次可以在上下左右这四个方向中移动一步,每移动一步会惩罚一个单位的奖励。但是,移动有以下的限制。
当智能体移动到终点时,回合结束,回合奖励为各步奖励相加。
以下代码演示了如何倒入这个环境并查看这个环境的基本信息。
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...