英文原文:Deep Reinforcement Learning: Pong from Pixels
作者:Andrej Karpathy (Stanford University) 译者:郭江
这是一篇早就应该写的关于强化学习的文章。强化学习现在很火!你可能已经注意到计算机现在可以自动(从游戏画面的像素中)学会玩雅达利(Atari)游戏[1],它们已经击败了围棋界的世界冠军,四足机器人学会了奔跑和跳跃,机器人正在学习如何执行复杂的操作任务而无需显式对其编程。所有的这些进步都源自强化学习研究的进展。我自己在过去一年里对强化学习产生了兴趣,我读完了Richard Sutton的书、David Silver的课程,浏览了John Schulmann的讲义,在DeepMind实习的那个暑假里写了一个Javascript的强化学习库,并且最近也参与了OpenAI Gym项目的设计与开发。所以,我在强化学习这驾充满乐趣的“马车”上已经待了至少一年的时间,但是直到现在,我才抽出时间来写一写为什么强化学习那么重要,它是关于什么,如何发展起来,未来可能走向何方?
我想从强化学习近几年的进展出发,分析四个推进AI发展的关键因素:
- 计算(摩尔定律、GPU, ASIC)
- 数据(良构数据,如:ImageNet)
- 算法(研究与想法,如:反向传播、卷积网络、LSTM),以及
- 基础架构(Linux, TCP/IP, Git, ROS, PR2, AWS, AMT, TensorFlow, etc.)
与计算机视觉的情境相似,强化学习的最新进展并不是由于出现了新的天才的想法。在计算机视觉研究中,2012年的AlexNet很大程度上只是1990年ConvNets的一个扩展(更深、更宽)。类似的,2013年ATARI Deep Q-Learning的文章只是标准Q-Learning算法的一个实现,区别在于它将ConvNet作为函数逼近子(function approximator)。AlphaGo使用的是策略梯度(policy gradient)以及蒙特卡洛树搜索(MCTS)——这些都是标准且已有的技术。当然,我们需要很多技巧以及耐心来让它变得有效,并在这些“古老”的算法上进行一些巧妙的调整和变化。但是给强化学习研究带来进展的最直接原因并不是新的算法,而是计算能力、数据以及基础架构。
现在回到强化学习。每当遇到一些看似神奇而实则简单的事情时,我都会感到坐立不安并想为它写一篇文章。在这个问题(强化学习)里,我遇到过很多人,他们始终不相信我们能够通过一套算法,从像素开始从头学会玩ATARI游戏——这太惊人了,我自己也曾经这么想。但是我们所使用的方法本质上确实非常简单(我承认当事后诸葛很容易)。尽管如此,我将为大家介绍我们在解决强化学习问题中最喜欢用的方法——策略梯度(Policy Gradient, PG)。假如你是一个强化学习的门外汉,你可能会好奇为什么我不介绍一种更为人所知的强化学习算法——DQN(深度Q-网络),也就是那篇ATARI游戏的论文(来自DeepMind)中所采用的方法。实际上Q-Learning并不是一个非常棒的算法,大部分人更亲睐使用策略梯度,就连原始DQN论文的作者也证明了策略梯度能够比Q-Learning取得更好的结果。策略梯度的优势在于它是端到端(end-to-end)的:存在一个显式的策略以及方法用来直接优化期望回报(expected reward)。总之,我们将以乒乓游戏(Pong)为例,来介绍如何使用策略梯度,在深度神经网络的帮助下基于游戏画面的像素来学会玩这个游戏。完整的实现只有130行Python代码(使用Numpy)。让我们开始吧。
基于像素的乒乓游戏
乒乓游戏是研究简单强化学习的一个非常好的例子。在ATARI 2600中,我们将让你控制其中一个球拍,另一个球拍则由AI控制。游戏是这么工作的:我们获得一帧图像(210*160*3的字节数组),然后决定将球拍往上还是往下移动(2种选择)。每次选择之后,游戏模拟器会执行相应的动作并返回一个回报(得分):可能是一个+1(如果对方没有接住球),或者-1(我们没有接住球),或者为0(其他情况)。当然,我们的目标是移动球拍,使得我们的得分尽可能高。
当我们描述解决方案的时候,请记住我们会尽量减少对于乒乓游戏本身的假设。因为我们更关心复杂、高维的问题,比如机器人操作、装配以及导航。乒乓游戏只是一个非常简单的测试用例,当我们学会它的同时,也将了解如何写一个能够完成任何实际任务的通用AI系统。
策略网络
首先,我们将定义一个策略网络,用于实现我们的玩家(智能体)。该网络根据当前游戏的状态来判断应该执行的动作(上移或者下移)。简单起见,我们使用一个2层的神经网络,以当前游戏画面的像素(100,800维,210*160*3)作为输入,输出一个单独的值表示“上移”动作的概率。在每轮迭代中,我们会根据该分布进行采样,得到实际执行的动作。这么做的原因会在接下来介绍训练过程的时候更加清楚。
我们将它具体化,下面是我们如何使用Python/numpy来实现该策略网络。假设输入为x,即(预处理过的)像素信息:
在这段简短的代码中,W1与W2是两个随机初始化的权值矩阵。我们使用sigmoid函数作为非线性激活函数,以获得概率值([0,1])。直觉上,隐含层神经元会检测游戏的当前状态(比如:球与球拍的位置),而W2则根据隐含层状态来决定当前应该执行的动作。因此,W1与W2将成为决定我们玩家水平的关键因素。
补充:之所以提到预处理,是因为我们通常需要2帧以上的画面作为策略网络的输入,这样策略网络才能够检测当前球的运动状态。由于我是在自己的Macbook上进行实验的,所以我只考虑非常简单的预处理:实际上我们将当前帧与上一帧画面的差作为输入。
这听起来并不现实
我想你已经体会到这个任务的难度。每一步,我们取得100,800个值作为输入,并传递给策略网络(W1、W2的参数数目将为百万级别)。假设我们决定往上移动球拍,当前的游戏可能给我们反馈的回报是0,并呈现下一帧画面(同样是100,800个值)。我们重复这个过程上百次,最终得到一个非零的回报(得分)。假设我们最后得到的回报是+1,非常棒,但是我们如何知道赢在哪一步呢?是取得胜利之前的那一步?还是在76步之前?又或许,它和第10步与第90步有关?同时,我们又该更新哪些参数,如何更新,使得它在未来能够做得更好?我们将它称之为信用分配问题(credit assignment)。在乒乓游戏的例子里,当对方没有接住球的时候,我们会得到+1的回报。其直接原因是我们恰好将球以一个巧妙的轨迹弹出,但实际上,我们在很多步之前就已经这么做了,只不过那之后的所有动作都不直接导致我们结束游戏并获得回报。由此可见,我们正面临一个多么困难的问题。
有监督学习
在我们深入介绍策略梯度之前,我想简单回顾一下有监督学习(supervised learning),因为我们将会看到,强化学习与之很相似。参考下图。在一般的有监督学习中,我们将一幅图片作为网络的输入,并获得某种概率分布,如:上移(UP)与下移(DOWN)两个类别。在这个例子中,我给出了这两种类别的对数概率(log probabilities)(-1.2, -0.36)(我们没有列出原始的概率:0.3/0.7,因为我们始终是以正确类别的对数似然作为优化目标的)。在有监督学习中,我们已经知道每一步应该执行的动作。假设当前状态下,正确的动作是往上移(label 0)。那么在实现时,我们会先给UP的对数概率一个1.0的梯度,然后执行反向传播计算梯度向量。梯度值将指导我们如何更新相应的参数,使得网络更倾向于预测UP这个动作。当我们更新参数之后,我们的网络将在未来遇到相似输入画面时更倾向预测UP动作。
策略梯度
但是,在强化学习中我们并没有正确的标签信息,又该怎么做呢?下面介绍策略梯度方法。我们的策略网络预测上移(UP)的概率为30%,下移(DOWN)的概率为70%。我们将根据该分布采样出一个动作(如DOWN)并执行(Monte Carlo Method)。注意到一个有趣的事实,我们可以像在有监督学习中所做的,立刻给DOWN一个梯度1.0,再进行反向传播更新网络参数,使得网络在未来更倾向预测DOWN。然而问题是,在当前状态下,我们并不知道DOWN是一个好的动作。但是没关系,我们可以等着瞧!比如在乒乓游戏中,我们可以等到游戏结束,获得游戏的回报(胜:+1;负:-1),再对我们所执行过的动作赋予梯度(DOWN)(Monte Carlo Search)。在下面这个例子中,下移(DOWN)的后果是导致我们输了游戏(回报为-1),因此我们给DOWN的对数概率-1的梯度,再执行反向传播,使得网络在遇到相似输入的时候预测DOWN的概率降低。
就这么简单。我们有一个随机的策略对动作进行采样,对于那些最终能够带来胜利的动作进行激励,而抑制那些最终导致游戏失败的动作(Monte Carlo Search)。同时,回报并不限制为+1/-1,它可以是任何度量。假如确实赢得漂亮,回报可以为10.0(引入经验,先验知识_Prior Knowledge),我们将该回报值写进梯度再进行反向传播。这就是神经网络的美妙之处,使用它们的时候就像是一种骗术,你能够对百万个参数执行万亿次浮点运算,用SGD(Stochastic Gradient Descent,随机梯度下降算法)来让它做任何事情。它本不该行得通,但有趣的是,在我们生活的这个宇宙里,它确实是有效的。
训练准则
接下来,我们详细介绍训练过程。我们首先对策略网络中的W1, W2进行初始化,然后玩100局乒乓游戏(我们把最初的策略称为rollouts,这时的策略是随机初始化的)。假设每一次游戏包含200帧画面,那么我们一共作了20,000次UP/DOWN的决策,而且对于每一次决策,我们知道当前参数的梯度。剩下的工作就是为每个决策标注出其“好”或“坏”。假设我们赢了12局,输了88局,那么我们将对于那些胜局中200*12=2400个决策进行正向更新(positive update,为相应的动作赋予+1.0的梯度,并反向传播,更新参数);而对败局中200*88=17600个决策进行负向更新。是的,就这么简单。更新完成之后的策略网络将会更倾向执行那些能够带来胜利的动作。于是,我们可以使用改进之后的策略网络再玩100局,循环往复。
策略梯度:对于一个策略执行多次,观察哪些动作会带来高回报,提高执行它们的概率。
如果你仔细思考这个过程,你会发现一些有趣的性质。比如,当我们在第50帧的时候作了一个好的动作(正确地回球),但是在第150帧没有接住球,将会如何?按照我们所描述的做法,这一局中所有动作都将被标记为“坏”的动作(因为我们输了),而这样不是也否定了我们在第50帧时所作出的正确决策么?你是对的——它会这样做。然而,如果你考虑上百万次游戏过程,一个正确的动作给你带来胜利的概率仍然是更大的,也就意味着你将会观察到更多的正向更新,从而使得你的策略依然会朝着正确的方向改进(这里否定这个正确动作)。
更一般的回报函数
到目前为止,我们都是通过是否赢得游戏来判断每一步决策的好坏。在更一般的强化学习框架中,我们可能在每一步决策时都会获得一些回报rt。一个常见的选择是采用有折扣的回报(discounted reward),这样一来,“最终回报”则为:
Rt=∑∞k=0γkrt+k
,其中γ为折扣因子(0到1之间)。这意味着对于每一步所采样的动作,我们考虑未来所有回报的加权和,而且未来回报的重要性随时间以指数递减。在实际中,对回报值进行规范化也很重要。比如,当我们算完100局乒乓游戏中20,000个动作的回报Rt之后,一个好的做法是先对这些回报值进行规范化(standardize,如减去均值,除标准差)再进行反向传播。这样一来,被激励以及被抑制的动作大概各占一半(为什么这样规范化?)。从数学角度,我们可以将这种做法看成是一种控制策略梯度估计方差的一种方法。更深入的探讨请参考论文:High-Dimensional Continuous Control Using Generalized Advantage Estimation。
策略梯度的推导
接下来,我将从数学角度简要介绍策略梯度的推导过程。策略梯度是值函数梯度估计的一个特例。更一般的情况下,表达式满足这样的形式:
Ex∼p(x∣θ) f(x)
,即某个值函数f(x)在概率分布p(x;θ)下的期望。提示:f(x)将是我们的回报函数(或者advantage function),而p(x)是我们的策略网络,实际上是p(a|I),即在给定图像I的条件下,动作的概率分布。我们感兴趣的是如何调整该分布(通过它的参数θ)以提高样本的期望回报。
首先,我们有一个用以动作采样的分布p(x;θ)(比如,可以是一个高斯分布)。对于每一个采样,我们通过估值函数f对其进行打分。上面的式子告诉我们如何更新这个分布,使得我们所采样的动作能够获得更高的分数。更具体一点,我们采样出一些样本(动作)x,获得它们的得分f(x),同时对于x中的每一个样本计算式中的第二项:
这一项代表什么含义?它是一个向量,指向能够使得样本x 概率增加的参数更新方向。回到上面的式子,最终的梯度是该向量与样本得分f(x)的乘积,从而使得得分更高的样本比得分较低的样本对分布p(x;θ)的影响更大。
我希望策略网络与强化学习之间的联系已经介绍清楚了。策略网络为我们提供一些动作的样本,并且其中一些动作比另外一些得分(由估值函数给出)更高。这部分所谈及的数学部分则指导我们如何更新策略网络的参数:计算相应动作的梯度,乘上它的得分,再更新网络参数。一个更全面的推导过程以及讨论,推荐大家看看John Sculman的讲座。
学习
好,我们已经了解了策略梯度以及它的推导过程。我在OpenAI Gym项目中ATARI 2600 Pong的基础之上实现了整个方法,一共是130行Python代码。我训练了一个两层的策略网络,隐层200个神经元,使用RMSProp(这个算法需要去看一下?)算法更新参数。我没有进行太多地调参(超参:什么是超参数),实验在我的Macbook上跑的。在训练了3晚上之后(增强学习的训练时间一般都会多久,在自己点电脑上?),我得到了一个比AI玩家稍好的策略。这个算法大概模拟了200,000局游戏,执行了800次左右参数更新。我朋友告诉我,如果使用ConvNet在GPU上跑几天的话,你的胜率会更高。而如果你再优化一下超参数,你就可以完胜每一局游戏。但是我在这篇文章里只是为了展示核心的思想,并没有花太多时间去调整模型,所以最终得到的只是一个表现还不错的乒乓游戏AI。
学到的(网络)权重
我们来观察一下所学到的网络权重(weights)。经预处理之后,网络的输入是一个80*80的差值图像(当前帧减去上一帧,以反映图像变化轨迹)。我们将W1按行进行展开(每一行的维度则为80*80)并可视化。下面展示了200个隐层神经元中的40个。白色像素表示权值为正,黑色为负。可以看到,有一些神经元显示了球的运动轨迹(黑白相间的线条)。由于每个时刻球只能处在一个位置,所以这些神经元实际上在进行多任务处理(multitasking),并在球出现在相应位置时被充分激活。当球沿着相应的轨迹运动时,该神经元的响应将呈现出正弦波形的波动,而由于ReLU的影响,它只会在一些离散的位置被激活。画面中有一些噪声,我想可以通过L2正则来消解。
What isn’t happening
策略梯度算法是以一种巧妙的“guess-and-check(trial-and-error)”模式工作的,“guess”指的是我们每一次都根据当前策略来对动作进行采样,“check”指的是我们不断激励那些能够带来好运(赢或者得分更高)的动作。这种算法是目前解决强化学习问题最好的方法,但是一旦你理解了它的工作方式,你多少会感到有点失望,并心生疑问——它在什么情况下会失效呢?
与人类学习玩乒乓游戏的方式相比,我们需要注意以下区别:
- 在实际场景中,人类通常以某种方式(比如:英语)对这个任务进行交流,但是在一个标准的强化学习问题中,我们只有与环境交互而产生的回报。假如人类在对回报函数一无所知的情况下学习玩这个游戏(尤其是当回报函数是某种静态但是随机的函数),那么他也许会遇到非常多的困难,而此时策略梯度的方法则会有效得多。类似的,假如我们把每一帧画面随机打乱,那么人类很可能会输掉游戏,而对于策略梯度法却不会有什么影响。
- 人类有丰富的先验知识,比如物理相关(球的反弹,它不会瞬移,也不会忽然停止,它是匀速运动,等)与心理直觉。而且你也知道球拍是由你控制的,会对你的“上移/下移”指令作出回应。而我们的算法则是完全从一无所知的状态开始学习。
- 策略梯度是一种brute force(暴力搜索)算法。正确的动作最终会被发现并被内化至策略当中。人类则会构建一个强大的抽象模型,并根据它来作出规划。在乒乓游戏里,我能够推断出对手速度很慢,因此将球快速击出可能是一个好的策略。然而,似乎我们最终同样会把这些好的策略内化至我们的肌肉记忆,从而作出反射式操作。比如,当我们在学习开手动档车的时候,你会发现自己在一开始需要很多思考,但是最终会成为一种自动而下意识的操作。
- 采用策略梯度时需要不断地从正回报样本中进行学习,从而调整参数。而在人类进行学习的过程中,即使没有经历过相应的选择(样本),也能够判断出哪个动作会有较高的回报。比如,我并不需要上百次的撞墙经历才能够学会在开车时如何避免(撞墙)——这个例子举得感觉很好。
综上所述,一旦你理解了这些算法工作的原理,你就可以推断出它们的优势以及不足。特别地,我们离像人类一样通过构建抽象而丰富的游戏表示以进行快速地规划和学习还差得很远。未来的某天,计算机将会从一组像素中观察到一把钥匙和一扇门,并且会想要拿起钥匙去开门。目前为止,我们离这个目标还有很长的距离,而且这也是相关研究的热点领域。
神经网络中的不可微计算
这一节将讨论策略梯度的另一个有趣的应用,与游戏无关:它允许我们设计、训练含有不可微计算的神经网络。这种思想最早在论文“Recurrent Models of visual Attention”中提出。这篇论文主要研究低分辨率图像序列的处理。在每一轮迭代中,RNN会接收图片的某一部分作为输入,然后采样出下个区域的位置。例如,RNN可能先看到(5, 30)这个位置,获得该区域的图片信息,然后决定接下来看(24, 50)这个位置。这种方法的关键在于从当前图片(局部)中产生下一个观察位置的概率分布并进行采样。不幸地是,这个操作是不可微的。更一般地,我们考虑下面的神经网络:
注意到大部分箭头(蓝色)是连续可微的,但是其中某些过程可能会包含不可微的采样操作(红色)。对于蓝箭头我们可以轻易地进行反向传播,而对于红色箭头则不能。
使用策略梯度可以解决这个问题!我们将网络中的采样过程视为嵌入在网络中的一个随机策略。那么,在训练时,我们将产生不同的样本(下图中的分支),然后我们在参数更新过程中对那些最终能够得到好结果(最终的loss小)的采样进行激励。换言之,对于蓝色箭头上的参数,我们依然按照一般的反向传播来更新;而对于红色箭头上的参数,我们采用策略梯度法更新。论文:Gradient Estimation Using Stochastic Computation Graphs对这种思路作了很好地形式化描述。
可训练的记忆读写。你会在很多其他论文中看到这种想法,例如:Neural Turing Machine(NTM)有一个可读写的存储带。在执行写操作时,我们可能执行的是类似m[i]=x的操作,其中i和x是由RNN控制器预测的。但是这个操作并不可微,因为我们并不知道如果我们写的是一个不同的位置 j!=i 时loss会有什么样的变化。因此,NTM只好执行一种“软”的读/写操作:它首先预测一个注意力的分布(attention distribution)a(按可写的位置进行归一化),然后执行:for all i: m[i] = a[i]*x。是的,这样做的确可微了,但是计算代价也高了很多。想象一下,我们每次赋值都需要访问整个内存空间!
然而,我们可以使用策略梯度来避免这个问题(理论上),正如RL-NTM所做的(arxiv.org/abs/1505.00521)。我们仍然预测一个注意力的分布 a,再根据a 对将要写的地址 i 进行采样:i = sample(a); m[i] = x。在训练过程中,我们会对 i 采样多次,并通过策略梯度法来对那些最好的采样进行激励。这样一来,在预测时就只需要对一个位置进行读/写操作了。但是,正如这篇文章(RL-NTM)所指出的,策略梯度算法更适用于采样空间较小的情况,而在这种搜索空间巨大的任务中,策略梯度并不是很有效。
结论
我们已经看到策略梯度是一种强大且通用的算法。我们以ATARI 乒乓游戏为例,使用130行Python代码训练得到了一个智能体。同样的算法也适用于其他的任何游戏,甚至在未来可用于有实际价值的控制问题。在掷笔之前,我想再补充一些说明:
关于推进人工智能。我们看到策略梯度算法是通过一个brute-force搜索的过程来完成的。我们同样看到人类用以解决同样问题的方法截然不同。正是由于人类所使用的那些抽象模型非常难以(甚至不可能)进行显式地标注,所以最近越来越多的学者对于(无指导)生成模型(generative models)以及程序归纳(program induction)产生了兴趣。
在复杂机器人场景中的应用。将策略梯度算法应用到搜索空间巨大的场景中并非易事,比如机器人控制(一个或多个机器人实时与现实世界进行交互)。解决这个问题的一个相关研究思路是:确定性的策略梯度(deterministic policy gradients),这种方法采用一个确定性的策略,并从一个额外的估值网络(称为critic)中获取梯度信息。另一种思路是增加额外的指导。在很多实际的情形中,我们能够从人类玩家那儿获取一些专业的指导信息。比如AlphaGo先是根据人类玩家的棋谱,使用有监督学习得到能够较好地预测人类落子方式的策略,然后再使用策略梯度算法对此策略进行调整。
策略梯度在实际中的使用。在我之前关于RNN的博文中,我可能已经让大家看到了RNN的魔力。而事实上让这些模型有效地工作是需要很多小技巧(trick)支撑的。策略梯度法也是如此。它是自动的,你需要大量的样本,它可以一直学下去,当它效果不好的时候却很难调试。无论什么时候,当我们使用火箭炮之前必须先试试空气枪。以强化学习为例,我们在任何时候都需要首先尝试的一个(强)基线系统是:交叉熵。假如你坚持要在你的问题中使用策略梯度,请一定要注意那些论文中所提到的小技巧,从简单的开始,并使用策略梯度的的一种变型:TRPO。TRPO在实际应用中几乎总是优于基本的策略梯度方法。其核心思想是通过引入旧策略和更新之后策略所预测的概率分布之间的KL距离,来控制参数更新的过程。