深度强化学习

1.增强学习的主要挑战是什么?

信用分配问题(credit assignment problem )与探索-开发困境(exploration-exploitation dilemma)。

2.如何用数学术语公式化强化学习?我们将会定义马尔可夫决策过程(Markov Decision Process)并利用它来推导强化学习。

3.我们如何生成长期策略?我们会定义「折扣未来奖励(discounted future reward)」,其奠定了下一章节的算法的基础。

4.我们怎样能够估测未来奖励? 简单的基于表格的 Q 学习(Q-learning)算法在这里会得到定义与解释。

5.如果我们的状态空间( state space)过大该怎么办?在这里我们会看到 Q 表格(Q-table)可以如何被(深度)神经网络所取代。

6.让它真正地运转起来还需要些什么?我们将讨论经验重放技术(Experience replay technique ),它可以稳定化神经网络的学习。

到这里就结束了吗?在最后,我们会考虑一些探索-开发的简单解决方案。

如何用数学术语公式化强化学习?我们将会定义马尔可夫决策过程(Markov Decision Process)并利用它来推导强化学习。

强化学习

强化学习位于监督与无监督式之间的某个位置。而监督式学习每一个训练样本都有着目标标签,在无监督学习中则没有,强化学习则有着稀疏(sparse)和延时(_time-delayed)的标签——奖励(reward)。仅仅基于这些奖励,人工智能代理必须学会在这个环境中的行为方式。

马尔可夫决策过程

现在的问题是,你如何公式化一个强化学习问题,然后进行推导呢?最常见的方法是通过马尔可夫决策过程。

假设你是一个代理,身处某个环境中(例如《打砖块》游戏)。这个环境处于某个特定的状态(例如,牌子的位置、球的位置与方向,每个砖块存在与否)。人工智能可以在这个环境中做出某些特定的动作(例如,向左或向右移动拍子)。

这些行为有时候会带来奖励(分数的上升)。行为改变环境,并带来新的状态,代理可以再执行另一个动作。你选择这些动作的规则叫做策略。通常来说,环境是随机的,这意味着下一状态也或多或少是随机的(例如,当你漏掉了球,发射一个新的时候,它会去往随机的方向)。

状态与动作的集合,加上改变状态的规则,组成了一个马尔可夫决策过程。这个过程(例如一个游戏)中的一个情节(episode)形成了状态、动作与奖励的有限序列。

其中 si 表示状态,ai 表示动作,ri+1 代表了执行这个动作后获得的奖励。情节以最终的状态 sn 结束(例如,「Game Over」画面)。一个马尔可夫决策过程基于马尔可夫假设(Markov assumption),即下一状态 si+1 的概率取决于现在的状态 si 和动作 ai,而不是之前的状态与动作。

3、折扣未来奖励(Discounted Future Reward)

为了长期表现良好,我们不仅需要考虑即时奖励,还有我们将得到的未来奖励。我们该如何做呢?

对于给定的马尔可夫决策过程的一次运行,我们可以容易地计算一个情节的总奖励:

鉴于此,时间点 t 的总未来回报可以表达为:

但是由于我们的环境是随机的,我们永远无法确定如果我们在下一个相同的动作之后能否得到一样的奖励。时间愈往前,分歧也愈多。因此,这时候就要利用折扣未来奖励来代替:

在这里 γ 是数值在01之间的贴现因子奖励在距离我们越远的未来,我们便考虑的越少。我们很容易看到,折扣未来奖励在时间步骤 t 的数值可以根据在时间步骤 t+1 的相同方式表示:

 

如果我们将贴现因子定义为  γ=0,那么我们的策略将会过于短浅,即完全基于即时奖励。如果我们希望平衡即时与未来奖励,那么贴现因子应该近似于  γ=0.9。如果我们的环境是确定的,相同的动作总是导致相同的奖励,那么我们可以将贴现因子定义为  γ=1

一个代理做出的好的策略应该是去选择一个能够最大化(折扣后)未来奖励的动作。

4.Q- 学习(Q-learning)

Q-学习中,我们定义了一个函数 Q(s,a) 表示当我们在状态(states 中执行动作(actiona 时所获得的最大折扣未来奖励(maximum discounted future reward),并从该点进行继续优化。

可以将 Q(s,a) 看作是「在状态 s 中执行完动作 a 后,游戏结束时最好的得分」。它被称为 Q-函数(Q-function),因为其代表了给定状态中特定动作的「质量」。

这个定义听起来可能有些费解。如果我们只知道当前状态和动作,而不知道后续的动作和奖励,那我们该怎么估计游戏最后的得分呢?我们实际上不能。但作为一种理论构想,我们可以假设确实存在这样一个函数。只要闭上你的眼睛重复五次:「Q(s,a) 存在、Q(s,a) 存在……」懂了吗?

如果你仍没被说服,那就考虑一下存在这样一个函数会有怎样的影响。假设你在一个状态中,并在琢磨应该采取动作 a 还是 b. 你想选择在游戏最后能得到最高得分的动作。一旦你有了神奇的 Q-函数,答案就变得非常简单了——采取 Q 值最高的动作!

其中 π 表示策略,即我们在每一个状态中选择动作时所遵循的规则。

好了,那么我们如何得到 Q 函数呢?让我们只关注一个转换(transition<s, a, r, s’>。 和上一节中提到折扣未来奖励一样,我们可以根据下一个状态 s’ Q 值得到状态 s 和动作 a Q 值。

这被称为贝尔曼方程(Bellman equation)。你想想看,这是很合乎逻辑的——该状态和动作的最大未来奖励等于立即得到的奖励加上下一状态的最大未来奖励。

Q 学习的主要思想是我们可以使用贝尔曼方程不断迭代近似 Q-函数。在最简单的例子中,Q -函数可通过一个表格的形式实现,其中状态作为行,动作作为列。Q-学习算法的要点可简单归结如下:

算法中的α是指学习率,其控制前一个Q值和新提出的Q值之间被考虑到的差异程度。尤其是,当 α=1时,两个Q[s,a]互相抵消,结果刚好和贝尔曼方程一样。

我们用来更新 Q[s,a]的只是一个近似,而且在早期阶段的学习中它完全可能是错误的。但是随着每一次迭代,该近似会越来越准确;而且我们还发现如果我们执行这种更新足够长时间,那么Q函数就将收敛并能代表真实的Q值。

5、深度 Q 网络(Deep Q Network)

《打砖块(Breakout)》 游戏环境的状态可由拍子的位置、球的位置和方向以及每个砖块的存在与否来定义。但这种直观的表征是这个游戏独有的。我们能想出适合所有游戏的表征吗?屏幕像素是一个明显的选择,它们毫无疑问包含了游戏状况中除了球的速度和方向以外的所有相关信息。两个连续的屏幕就能覆盖这些例外。

DeepMind 的论文描述的那样,如果我们在游戏屏幕上应用相同的预处理——以最近四张屏幕照片为例,将它们的尺寸调整到 84×84 并将它们灰度调整为 256 阶灰度——我们会得到 256^84x84x4≈10^67970 种可能的游戏状态。这意味着我们想象的Q函数表有 10^67970 ——超过已知宇宙中原子的总数!有人可能会说很多像素组合(即状态)根本不可能出现——我们也许可以用一个只包含可访问状态的稀疏的表格来表示它。即便如此,其中大部分状态都是极少能出现的,要让 Q-函数表收敛,那得需要整个宇宙寿命那么长的时间。理想情况下,我们也喜欢对我们从未遇见过的状态所对应的Q值做出很好的猜测。

这就到了深度学习的用武之地。神经网络在高度结构化数据的特征提取方面表现格外优异。我们可以使用神经网络表示我们的Q函数,并将状态(四个游戏屏幕)和动作作为输入,将对应的 Q 值作为输出。或者我们也可使用唯一的游戏屏幕作为输入,并为每一个可能的动作输出Q值。这种方法有自己的优势:如果我们想执行Q值更新或选取对应最高Q值的动作,我们只需对网络进行一次彻底的前向通过,就能立即获得所有可能的动作的Q值。

 

左图:深度 Q 网络基本形式;右图:优化过的深度 Q 网络架构

DeepMind 使用的网络架构如下:

 

这是一个经典的带有三个卷积层的卷积神经网络,后面跟随着两个全连接层。熟悉对象识别网络的人可能注意到这里并不没有池化层(pooling layer)。但如果真正仔细想想,池化层让你获得了平移不变性(translation invariance)——网络变得对图像中的物体的位置不敏感。这对于 ImageNet 这样的分类任务是有意义的,但游戏中球的位置对确定潜在的奖励是至关重要的,而且我们并不希望丢弃这个信息!

这个网络的输入是 4 个 84×84 的灰度游戏屏幕。这个网络的输出是是每一个可能动作的 Q 值(Atari 中有 18 个动作)。Q 值可以是任何真实值,这使其成为了一个回归(regression)任务,可以使用简单的平方误差损失进行优化。

 

对于给定变换< s, a, r, s’ >,前一算法中的 Q 表格更新规则必须被以下规则取代:

为当前状态 s 进行一次前向通过,获得所有动作的预测的Q.

为下一个状态 s’ 进行一次前向通过,计算整体网络输出的最大值

 

为动作设置 Q 值目标。

 

(使用第 2 步中计算出的最大值).对于所有其它动作,设置Q值目标为第1步中原本返回的值,使这些输出的误差为0。

6、经验回放(experience replay)

现在,我们有一个使用 Q 学习估计每个状态的未来奖励和使用一个卷积神经网络逼近 Q 函数的想法了。但事实证明,使用非线性函数的 Q 值逼近不是很稳定。事实上你不得不使用很多技巧才能使其收敛。而且也需要很长的处理时间——在单个 GPU 上需要几乎一周的时间。

最重要的技巧是经验回放。在游戏过程中,所有经验 < s, a, r, s’ > 都被存储在回放存储器中。当训练网络时,使用的是来自回放存储器的随机微批数据(minibatches),而不是使用最近的变换。这打破了后续训练样本 的相似性,否则其就可能使网络发展为局部最小。经验回放也会使训练任务更近似于通常的监督式学习,从而简化了算法的调式和测试。我们实际上可以从人类玩的 游戏中学习到所有这些经验,然后在这些经验之上训练网络。

7、探索-开发(Exploration-Exploitation)

Q-学习试图解决信用分配问题(Credit Assignment Problem——它能在时间中反向传播奖励,直到其到达导致了实际所获奖励的关键决策点。但我们还没触及到探索-开发困境呢……

首先观察,当 Q 函数表或 Q 网络随机初始化时,那么其预测一开始也是随机的。如果我们选择一个有最高 Q 值的动作,那么该动作就将是随机的且该代理会执行粗糙的「探索(exploration)」。当 Q 函数收敛时,它返回更稳定的 Q 值,探索的量也会减少。所以我们可以说,Q 学习将探索整合为了算法的一部分。但这种探索是「贪心的(greedy)」,它会中止于其所找到的第一个有效的策略。

针对上述问题的一个简单而有效的解决方法是 ε-贪心探索(ε-greedy exploration——其概率 ε 选择了一个随机动作,否则就将使用带有最高 Q 值的「贪心的」动作。在 DeepMind 的系统中,他们实际上随时间将 ε 1 降至了 0.1——一开始系统采取完全随机的行动以最大化地探索状态空间,然后再稳定在一个固定的探索率上。

8、深度 Q 学习算法

这带给了我们使用经验回放的最后的深度 Q 学习算法

9、最后说明

自问世以来,Q 学习已经取得了很多改进——比如:双 Q 学习(Double Q-learning)、优先经验回放(Prioritized Experience Replay)、竞争网络架构(Dueling Network Architecture)和连续动作空间的扩展。NIPS 2015 深度强化学习研讨会和 ICLR 2016 上可以看到一些最新的进展。但要清楚,深度 Q 学习已被谷歌申请了专利。

人们常常说,人工智能是一种我们仍未清楚明白的东西。一旦我们理解了它们的工作方式,它就看起来不再智能了。但深度 Q 网络仍然让我惊叹。看着它们弄懂一个新游戏就像是在观察野生动物——这本身就是一个有益的体验。

二、使用 Neon 的深度强化学习

这是探讨深度强化学习系列博文的第二部分。

当我们研究组第一次读到 DeepMind 的论文「Playing Atari with Deep Reinforcement Learning」时,便当即想要复制这一惊人结果。那是在 2014 年初, 当时 cuda-convnet2 是性能最强的卷积网络实现方式,而 RMSProp 还只是 Hinton Coursera课上的一张幻灯片。我们不断调试,学习,但是当DeepMind与他们的《自然》论文「Human-level control through deep reinforcement learning」同时公布了代码时,我们便开始在他们的代码上做研究。

从那时起深度学习系统已经演进了很多。据说 2015 年时每 22 天就会出现一个新的深度学习工具包。其中著名的有像 TheanoTorch7 Caffe 这些老前辈们,也有 NeonKeras TensorFlow 这些新生代。新的算法一经公布就会被实现。

在某种程度上我意识到一年前困扰我们的所有困难部分,如今都在大多深度学习工具包中都能被轻而易举地实现。当 Arcade Learning Environment 这一仿真 Atari 2600 游戏的系统发布一款本地 Python API 后,它可刚好用以执行新的深度强化学习。编写主代码只需花费一周左右,然后是数周的调试。

我选择在 Neon 上编写,因为它有:

最快的卷积核(convolutional kernel);

实现所需的所有算法(如 RMSProp);

一个合理的 Python API

我试着保持我的代码的简介性和易扩展性,同时兼顾性能。目前 Neon 最知名的限制是它仅能在最新的英伟达 Maxwell GPU 上运行,不过也就快改变了。

下面我将介绍:

如何安装 Simple-DQN

你能用 Simple-DQN 做什么

比较 Simple-DQN 与其它算法

如何修改 Simple-DQN

1、如何安装 Simple-DQN

这里没什么可说的,就按说明书 README 来吧。基本上你所需要的只是 NeonArcade Learning Environment simple_dqn。尝试预训练模型时你甚至不需要 GPU,因为它们也能在 CPU 上运行,虽然很慢。

2、你能用 Simple-DQN 做什么?

运行一个预训练模型:一旦你安装完所有部分,首先要做的就是尝试运行预训练模型。例如运行一个《打砖块》的预训练模型,输入:

./play.sh snapshots/breakout_77.pkl

或者如果你没有可用的 (Maxwell)  GPU,也可以换成 CPU

./play.sh snapshots/breakout_77.pkl –backend cpu

你应当会看到这样的画面,并可能伴随一些恼人的声音.

 

你可以关掉人工智能并按[m]键来掌控游戏——看看能坚持多久!再按[m]键即返回人工智能控制模式。

事实上通过重复按[a]键来降低游戏速度很管用——你可以真正看到游戏中在发生什么。按[s]重新加速。

录制视频:同样你可以录制一个游戏视频(在《打砖块》中录制,直到失去 5 条命之后):

./record.sh snapshots/breakout_77.pkl

视频将存储为 videos/breakout_77.mov 而游戏截屏存储在 videos/breakout 中。这有一个视频案例:

 

训练一个新模型:要训练新模型,首先需要一个游戏的 Atari 2600 ROM 文件。然后将它保存在 roms 文件夹中并运行以下训练代码:

./train.sh roms/pong.rom

其结果就是创建了下列文件:

results/pong.csv,包括训练过程的各个统计数据,

snapshots/pong_<epoch>.pkl,每个时期的模型简介

测试一个模型:训练过程中在每个时期后有一个测试阶段。如果你想稍后再重新测试预训练模型,则可用以下测试脚本:

./test.sh snapshots/pong_141.pkl

它将测试结果打印到控制台。保存文件并添加–csv_file <filename>参数。

绘制统计数据:训练过程中及之后,你可能想要知道模型的效果。这里有段简单的从统计文件中制作图像的绘制脚本。例如:

./plot.sh results/pong.csv

它创建了这个文件results/pong.png.

 

它会默认绘制 4 张图:

平均得分,

玩过的游戏数量,

验证集合状态的平均最大 Q值 ,

平均训练损失。

对于所有的图,你能看到随机基线(那是它们有意义的位置)和训练阶段及测试阶段的结果。实际上你可以通过列出 –fields参数中的字段名,从统计文件中绘制出任何字段。默认图由 –fieldsaverage_reward,meanq,nr_games,meancost 实现。字段名可从 CSV 文件的第一行得出。

可视化滤波器:运用这个代码可做的最令人兴奋的事莫过于窥视人工智能的心灵。我们将用到导向型反向传播(guided backpropagation),它带有即开即用的 Neon 。简单的说,对于每个卷积滤波器,它会从给定的数据集合中找到一副最能激活该滤波器的图像。然后它执行关于输入图像的反向传播,观察图像的哪一部分对滤波器的「活性(activeness)」影响最大。这可以被当做是一种形式的显著性检测(saliency detection)。

执行滤波器可视化运行以下代码:

./nvis.sh snapshots/breakout_77.pkl

它以玩游戏收集样本数据开始,然后执行导向型反向传播。结果可在 results/breakout.html 中找到,它们看起来

有三个卷积层(记为 0000、0002 和 0004),这里我只从每个(Feature Map 0-1(特征映射 0-1))中对 2 个滤波器进行了可视化。还有有 16 个滤波器可视化的《打砖块》的更细节的文件。每个滤波器选择了最能激活它的图像。右边的图像展现原始输入,左边图像展示导向型反向传播结果。你可以把每个 过滤器看成一只人工智能的「眼睛」。即左侧图展现了在右侧图像的条件下,这个特定的「眼睛」所导向的位置。

由于我们网络的输入是一系列 4 灰度级图像,所以可视化过程并不清晰。我做了一个简化:只选用某种状态的最后 3 张图像并将它们放入不同 RGB 颜色信道中。因此 3 张图像上的所有灰色部分都没有改变;蓝色是最近的变化,然后是绿色,然后又是红色。放大观察你便轻松可知——其轨道由红-绿-蓝所标记。这使得理解反向传播结果的难度增加了,不过有时你可以猜测滤波器的轨道运动(tracks movement——从一个角落到另一个角落,颜色从红到绿到蓝变化。

可视化滤波器是一款十分有用的工具,你能够立刻得到一些有趣的观察结果。

第一层滤波器主要针对抽象模式,不能与任何特定对象建立可靠的关联。它们通常最能激发比分和生命值,这可能由于其有着众多边角(供识别)。当然你也能偶尔在更高层发现一些关注比分和生命值的滤波器。

正如预期的那样,有些滤波器追踪球和拍子。还有些滤波器在球将要撞上砖头或牌子时激活最多。

同样如预期的是,更高层的滤波器有着更大感受域(receptive field)。这在《打砖块》中还不明显,但是在这个《乒乓球(Pong)》游戏的文件中可以清楚地看到。有意思的是《打砖块》中不同层的滤波器比《乒乓球》中的更相似。

模型上使用。这么做的一个好处是它将导向型反向传播及可视化整合进同一个步骤中并且无需临时文件来编写中间结果。不过那样的话我需要对保存在 nvis 文件夹中的 Neon 代码做一些修正。

3、比较 Simple-DQN 与其它算法

另外还有一些深度强化学习实现方式,将它们与在 Neon 中的实现方式进行比较会很有意思。其中最知名的是连同其《自然》杂志文章一起发表的原始版 DeepMind 代码。另一个维护版本是弗吉尼亚州詹姆斯·麦迪逊大学 Nathan Sprague 开发的 deep_q_rl

深度强化学习中最普遍的度量标准是平均每局游戏得分。为了计算它用于 simple_dqn,我使用了与那篇《自然》杂志文章中提到的相同的评估程序——运用不同初始随机条件和一个 ε=0.05 ε-贪心策略( ε-greedy policy)获得的 30 场比赛的平均分数。我并没有麻烦为每个游戏执行 5 分钟的限制,因为《打砖块》和《乒乓球》中的游戏不会持续那么久。对于 DeepMind 我是用了自然杂志那篇文章里的数据。而对于 deep_q_rl,我让 deep-q-learning 列表中的成员提供了数据。他们的分数采集没有使用完全相同的协议(下面的特殊数字是 11 场游戏的平均成绩),所以可能需要考虑一点言过其实。

另一个有趣的测试是每秒的训练步数。DeepMind simple_dqn 都报告了每个时期每秒的平均训练步数(250000步)。deep_q_rl报告了动态的步速而我只是粗略估计了下其平均步数: )。在所有案例中我都查看了第一个时期,其探测率接近 1,因此这个结果更多反映了训练速度而非预测速度。所有测试均在英伟达 Geforce Titan X 上完成。

可以看到 simple_dqn 在速度方面明显优于其他算法。尽管学习结果还不能向 DeepMind 看齐,不过运行一些有趣的实验已经足够可行了。

4、如何修改Simple-DQN?

公布 simple_dqn 代码的主要考量是展示实现过程实际上可以多么简单,每个人都能够拓展它去做一些有趣的研究。

代码中存在四个类: EnvironmentReplayMemeoryDeepQNetwork Agent。还有用于处理解析和实例化上述类的参数的 main.py,以及执行基本回调机制以保证统计数据收集与主循环分离的 Statistics 类。但是深度强化学习算法的主要部分会在上述四个类中执行。

 

Environment Environment 类只是微软 A.L.E Python API 外的轻巧包装。除了 A.L.E 应该也可轻而易举地添加其他环境。例如 Flappy Bird Torcs——你只需执行一个新的 Environment 类。试试看吧!

ReplayMemory :回放记忆(replay memory)存储状态转换或经验。基本上它只是屏幕、动作、奖励和终端状态指示器的四个大数组。

回放记忆作为一组屏幕序列存储,而非由 4 个屏幕组成的状态序列。这使得内存使用大大减少而性能方面也无重大损失。运用 NumPy 数组切片将屏幕组装进状态会更快。

屏幕像素的数据类型是 uint88位无符号整型),这意味着1M 经验需要 6.57GB ——只需 8GB 内存来运行!而默认的 float3232位浮点型)数据类型则 1M 需要大约 26GB

如果你想实现优先经验回放(prioritized experience replay),那么这是你主要需要改变的类。

DeepQNetwork :此类用于实现深度 Q 网络。它其实也是唯一一个依赖 Neon 的类。

由于深度强化学习的迷你批处理(minibatching)方式各有不同,所以没有理由运用 Neon DataIterator 类。因此转而使用更低级的 Model.fprop() Model.bprop()。下面是给想做同样事情的人的建议:

你需要在构建模型后调用 Model.initialize()。它在 GPU 内存中为层的激活及权重分配张量。

Neon 张量有几个维度(信道、高度、宽度、批大小)。尤其批大小是最后一维。这种数据布局能实现最快卷积核。

置换一个 Numpy 数组的维度来匹配 Neon 张量需求时,一定要复制数组!否则实际的内存布局并不会改变且数组不能直接复制进 GPU 中。

除了使用有数组索引的单个张量元素(比如张量 [i,j]),试着运用 tensor.set() tensor.get() 将张量作为一个整体复制到 Numpy 数组中。这能让 CPU GPU 间往返过程更少。

考虑到要在 GPU 中执行张量算法,Neon backend 提供了一个好方法。同时也要注意这些操作运算不会立即被评估,而是作为 OpTree 存储起来。只有在你使用张量或将其转换到 CPU 里时,张量才能真正得到评估。

如果想要实现双 Q 学习(double Q-learning),则需要对此类进行修改。

Agent 类:Agent 类连接所有部件并执行主要的逻辑。

5、结论

正如我向你们展示的那样,运用类似 Neon 的优良深度学习工具包实现 Atari 视频游戏的深度强化学习简直宛若一道春风。Neon 中滤波器可视化功能提供了有关模型实际所学习内容的重要参考。

尽管这不是它本身的目标,但是计算机游戏为试验新的强化学习方法提供了卓越的沙盘。希望我的 simple_dqn 实现能够成为大家进入深度强化学习研究这一迷人领域的垫脚石。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐