增强学习和OpeAI Gym的介绍
基础增强学习问题的演示
编者注:想要深入学习增强学习,请查看Marcos Campos在2017年9月17 - 20日于旧金山举行的O’Reilly人工智能会议上所做的“增强学习介绍”辅导课。

你可以在Justin Francis的GitHub上找到这个博文里展示的代码。
更多人工智能内容请关注2018年4月10-13日人工智能北京大会

那些对机器学习世界感兴趣的人已经意识到了基于增强学习的人工智能的能力。在过去的几年里,使用增强学习(RL,Reinforcement Learning)在很多方面取得了突破。DeepMind公司将深度学习与增强学习结合在一起,在众多的Atari游戏中来取得超越人类的表现。并在2016年3月的围棋比赛中,以4:1击败了围棋冠军李世石。虽然RL目前在许多游戏环境中都表现很出色,但它对解决需要最优决策和效率的问题而言是种全新方法,而且肯定会在机器智能中发挥作用。

OpenAI成立于2015年底,是一个非营利组织。它的目的是“建立安全的人工通用智能(AGI),并确保AGI的福利被尽可能广泛和均匀地分布”。除了探索关于AGI的诸多问题之外,OpenAI对机器学习世界的一个主要贡献是开发了GymUniverse软件平台。

Gym是为测试和开发RL算法而设计的环境/任务的集合。它让用户不必再创建复杂的环境。Gym用Python编写,它有很多的环境,比如机器人模拟或Atari 游戏。它还提供了一个在线排行榜,供人们比较结果和代码。

简单来说,RL是一种计算方法。在这种方法中,代理通过采取行动来与环境进行交互,从而最大化累积的奖励。这里有一个简单的图,我后面会经常提到。

image3-5f8cbb1fb6fb9132fef76b13b8687bfc

图1 增强学习:入门,第二版。来自Richard S. Sutton和Andrew G. Barto,获准使用

处于当前状态(St)的代理对环境的反应和响应采取行动(At) ,然后返回一个新的状态(St+1)和对代理的行动进行的奖励(Rt+1)。考虑到更新的状态和奖励,代理再选择下一个动作,循环重复,直到环境任务被解决或终止。

OpenAI的Gym是基于这些基本原理。所以先让我们来安装Gym,看看它是如何与这个循环相关的。我们使用Python和Ubuntu终端安装Gym(你也可以根据Gym在GitHub上的介绍使用Mac电脑)。

sudo apt-get install -y python3-numpy python3-dev python3-pip cmake zlib1g-dev libjpeg-dev xvfb libav-tools xorg-dev python-opengl libboost-all-dev libsdl2-dev swig

cd ~

git clone https://github.com/openai/gym.git

cd gym

sudo pip3 install -e ‘.[all]’

接下来,我们可以在我们的终端运行Python3并且导入Gym。

python3

import gym

第一步,我们需要一个环境。作为我们的第一个例子,我们会导入非常基础的出租车环境。

env = gym.make(“Taxi-v2”)

为了初始化环境,我们必须重置它。

env.reset()

您会注意到重置环境将会返回一个整数。这个数字就是我们的初始状态。在这个出租车环境中,所有可能的状态都用一个从0到499的整数表示。我们可以使用以下命令来确定可能状态的总数:

env.observation_space.n

如果您想要可视化当前状态,键入以下内容:

env.render()

在这个环境中,黄色方块代表出租车,(“|”)表示一堵墙,蓝色字母代表接乘客的位置,紫色字母是乘客下车的位置,出租车上有乘客时就会变绿。当我们看到代表环境的颜色和形状时,算法不会像我们那样思考,它只会理解一个扁平的状态,在这个例子里就是一个整数。

现在我们已经加载了环境,并且知道了当前状态,让我们来探索代理可用的行动。

env.action_space.n

这个命令显示了总共有6种可用的行动。Gym不会总是告诉你这些动作的含义,但在这个场景下,这6种可能的行动是:下(0)、上(1)、左(2)、右(3)、接乘客(4)和放下乘客(5)。

为了学习的方便,让我们将当前状态改写为114。

env.env.s = 114

env.render()

现在我们让出租车向上移动。

env.step(1)

env.render()

你会注意到这个env.step(1)动作会返回4个变量。

(14, -1, False, {‘prob’: 1.0})

在后面我们会定义这些变量如下:

state, reward, done, info = env.step(1)

这4个变量是:新状态(St+1 = 14)、奖励(Rt+1 = -1)、一个布尔值(说明环境是否被终止或完成)以及额外的调试信息。每个Gym环境在行动后都会返回相同的4个变量,因为它们是增强学习问题的核心变量。

再看看渲染出来的环境。如果你向左走,你希望环境会回报什么?当然,你会得到和以前一样的回报。对于每个步骤,环境总是给出一个- 1的奖励,让代理尝试找出最快的解决方案。如果衡量你的累计奖励总数,持续地撞到墙会严重地惩罚你的最终奖励。当每次搭载或放下乘客不正确的时候,环境也会给你一个- 10的回报。

因此,我们如何才能解决这个环境的任务?

一种令人惊讶的可以解决这个环境任务的方法就是随机地在6种可能的行动中选择一个。当你能成功接上一个乘客并把它放到它们想去的位置,这个环境的任务被认为得到解决。一旦实现,你会得到20分的奖励,并且done变量会变成True。在进行了足够多次随机的行动后,你可能会最终幸运地完成任务。尽管这种几率很小但并非不可能。评估每个代理的表现的一个核心部分就是比较它们和随机动作的代理。在Gym的一个环境里,你可以用env.action_space.sample()来选择随机动作代理。你可以建一个循环,让代理随机动作直到任务解决。我们可以在循环里加上一个计数器来观察需要多少步才能解决环境任务。

state = env.reset()

counter = 0

reward = None

while reward != 20:

state, reward, done, info = env.step(env.action_space.sample())

counter += 1

print(counter)

可能有人会非常幸运,在很短的时间里就解决了任务。但平均下来,完全随机的策略会花2000多步才能解决任务。因此想在未来最大化我们的奖励,我们就必须让算法记住自己的动作和带来的奖励。这种情况下,算法的记忆就会成为一个Q动作价值表。

未来管理这个Q表,我们会使用一个NumPy的array列表。列表的大小是状态数(500)乘以可能的行动数(6),即500×6。

import numpy as np

Q = np.zeros([env.observation_space.n, env.action_space.n])

在经过多次解决任务的尝试回合(episode)后,我们会更新Q值,逐步提升我们算法的效率和表现。我们也希望能跟踪我们每个回合的累积奖励值,我们定义它为G。

G = 0

与大多数的机器学习问题类似,我们还是需要一个学习速率。我会使用我个人的最爱0.618,它也是数学黄金分割常量π。

alpha = 0.618

下面,我们可以实现最基本的Q值学习算法。

for episode in range(1,1001):

done = False

G, reward = 0,0

state = env.reset()

while done != True:

action = np.argmax(Q[state]) #1

state2, reward, done, info = env.step(action) #2

Q[state,action] += alpha * (reward + np.max(Q[state2]) – Q[state,action]) #3

G += reward

state = state2

if episode % 50 == 0:

print(‘Episode {} Total Reward: {}’.format(episode,G))

这些代码单独就可以解决这个出租车环境任务。里面代码比较多,我会分解开来解释。

注释(#1):这个代理使用argmax函数来为当前的状态选择一个最高的Q值的行动。argmax函数会返回某状态的最高值的索引/行动。最初时,我们的Q表里的值都是0。但是在每一步之后,每个状态—行动组的Q值都会被更新。

注释(#2):代理采用此行动,然后我们保存一下未来的状态为state2 (St+1)。这将会允许代理来比较之前的状态和新的状态。

注释(#3):使用奖励来更新状态—行动组(St , At)的Q值和state2 (St+1)的最大的Q值。这个更新是使用行动值公式(基于Bellman方程),并允许状态—行动组被用一种递归的方法来更新(基于未来的值)。图2里介绍了循环更新的公式。

image2-edf07a0abcd3899d6946ac7a58db05bb

图2 Q值的递归更新。来源:Gregz448,CC0,基于Wikimedia Commons授权

在更新完成后,我们再更新总体的奖励值G,并更新state (St)到之前state 2(St+1)。这样,循环可以再次开始,而下一个行动也能被决定了。

在经过非常多轮的回合后,这个算法会最终收敛,从而通过Q表来决定每个状态的最优行动,确保达到最大的奖励值。到这时,我们认为环境任务已经被解决。

现在,我们解决了一个非常简单的环境任务。那让我们试一试一个更复杂的Atari游戏环境——Pacman(吃豆子的小黄人)

env = gym.make(“MsPacman-v0”)

state = env.reset()

你会注意到env.reset()返回了一个非常大的数字列表。更具体点,你可以键入state.shape来显示这个状态是一个210x160x3的张量。它代表了Atari游戏里面的长、宽和3个RGB的颜色通道组合成的像素。如前,你可以可视化这个环境。

env.render()

而且和之前一样,我们可以看看所有可能的行动:

env.action_space.n

我们有9种可能的行动:整数0-8。需要注意的是代理并不知道每个行动是什么意思。它的工作只是学会哪个行动将会优化奖励。但是为了我们好理解,我们可以:

env.env.get_action_meanings()

结果展示出代理可以选择的9个行动:什么都不做,以及游戏手柄上能做的8种动作。

用我们之前的策略,首先看看一个随机的代理的表现如何。

state = env.reset()

reward, info, done = None, None, None

while done != True:

state, reward, done, info = env.step(env.action_space.sample())

env.render()

完全随机的策略最多能得几百分,而且从来没能通过第一关。

继续,我们现在是不能用基本的Q表算法了。因为这里有33,600个像素点,每个点上有3个随机的0-255之间的整数RGB值。很容易看出事情变得极度复杂了。这里可以用到深度学习来解决。使用诸如卷积神经网络或是DQN,一个机器学习库就能够把复杂的高维像素矩阵变成抽象的表达,并把这个表达转化成一个优化的行动。

总结一下,你现在已经有了一些基础知识,也知道如何使用Gym并基于别人的(甚至是你自创的)算法来做一些实验了。如果你希望你获得本博文里用的代码,并照着来做或是编辑,你可以在我的GitHub上找到这些代码。

RL领域正伴随着新的更好的解决环境任务的方法的出现而快速扩张。目前为止,A3C方法是其中最流行的方法之一。增强学习将很可能在未来的人工智能里发挥更重要的作用,并会持续产出非常有趣的结果。

Justin Francis

Justin居住在加拿大西海岸的一个小农场。这个农场专注于朴门道德和设计的农艺。在此之前,他是一个非营利性社区合作社自行车商店的创始人和教育者。在过去的两年中,他住在一艘帆船上,全职探索和体验加拿大的乔治亚海峡。但现在他的主要精力都放在了学习机器学习。

Image from page 163 of "Mazes and labyrinths; a general account of their history and developments" (1922). (source: Internet Archive on Flickr)