Python-深度强化学习教程-全-

Python 深度强化学习教程(全)

原文:Deep Reinforcement Learning with Python

协议:CC BY-NC-SA 4.0

一、强化学习简介

强化学习是发展最快的学科之一,正在帮助人工智能成为现实。将深度学习与强化学习相结合已经导致了许多重大进步,这些进步越来越多地让机器接近人类的行为方式。在本书中,我们将从基础开始,以掌握该领域的一些最新发展结束。使用 PyTorch 和 TensorFlow 将会很好地混合理论(用最少的数学)和代码实现。

在这一章中,我们将设置背景,并为你在本书的其余部分做好一切准备。

强化学习

所有有智慧的生物都是从一些知识开始的。然而,随着他们与世界的互动和经验的积累,他们学会了适应环境,变得更擅长做事。引用 1994 年《华尔街日报》、 1 、专栏中的一句话,智力可以定义如下:

一种非常普遍的心智能力,包括推理、计划、解决问题、抽象思考、理解复杂想法、快速学习和从经验中学习的能力。这不仅仅是书本知识、狭隘的学术技能或应试技巧。相反,它反映了理解我们周围环境的更广泛和更深入的能力——对事物的“理解”、“理解”或“弄清楚”该做什么。

机器背景下的智能叫做人工智能*。牛津语言对人工智能(AI)的定义如下:

能够执行通常需要人类智能的任务的计算机系统的理论和发展,例如视觉感知、语音识别、决策和语言之间的翻译。

这就是我们将在本书中研究的内容:通过与环境互动并不断从成功、失败和回报中学习,帮助机器(代理)获得执行任务能力的算法的理论和设计。最初,人工智能围绕着设计解决方案,作为一系列可以用逻辑和数学符号表达的正式规则。这些规则由一系列编入知识库的信息组成。这些人工智能系统的设计还包括一个推理引擎,使用户能够查询知识库,并结合规则/知识的各个部分进行推理。这些系统也被称为专家系统决策支持系统等。然而,很快人们意识到这些系统太脆弱了。随着问题越来越复杂,编纂知识或建立有效的推理系统变得越来越困难。

强化学习的现代概念是两种不同的思路通过各自的发展结合起来的。首先是最优控制的概念。在许多解决最优控制问题的方法中,理查德·贝尔曼在 1950 年提出了动态规划的原则,我们将在本书中广泛使用。然而,动态编程不涉及学习。这是所有关于规划通过空间的各种选择使用贝尔曼递归方程。在第二章和第三章中,我们会有很多关于这些方程的内容。

第二条线索是通过尝试和错误来学习,它起源于动物训练心理学。爱德华·桑戴克是第一个明确表达试错概念的人。用他的话说:

在对同一情况做出的几种反应中,那些伴随着或紧随其后的对动物的满足的反应,在其他条件相同的情况下,将与该情况更紧密地联系在一起,因此,当它重现时,它们将更有可能重现;那些伴随着或紧随其后的动物的不适,在其他条件相同的情况下,它们与这种情况的联系被削弱,这样,当它复发时,它们就不太可能发生。满足感或不适感越大,这种纽带的加强或削弱就越大。

我们将在关于政策梯度的第七章中看到增加好结果发生率和减少坏结果发生率的概念。

在 20 世纪 80 年代,这两个领域合并起来,形成了现代强化学习领域。在过去的十年里,随着强大的深度学习方法的出现,强化学习与深度学习相结合,正在产生非常强大的算法,这些算法可以在未来的时代实现人工智能。今天的强化系统与世界互动,以获取经验,并通过概括世界的经验,学习根据与世界互动的结果优化自己的行动。专家知识没有明确的编码。

机器学习分支

机器学习涉及从呈现给系统的数据中学习,以便系统可以执行指定的任务。系统没有被明确告知如何完成任务。更确切地说,它与数据一起呈现,并且系统学习基于定义的目标执行一些任务。我们不会多说,因为我们假设你熟悉机器学习的概念。

机器学习方法传统上分为三大类,如图 1-1 所示。

img/502835_1_En_1_Fig1_HTML.jpg

图 1-1

机器学习的分支

机器学习的三个分支在学习系统可用的“反馈”的意义上有所不同。下面几节将对它们进行讨论。

监督学习

在监督学习中,向系统呈现已标记的数据,目标是概括知识,以便可以标记新的未标记数据。考虑将猫和狗的图像连同哪个图像显示猫或狗的标签一起呈现给系统。输入数据表示为一组数据 D = ( x 1y 1 ), x 2y 2 ),…(xny x n 是各个图像的像素值, y 1y 2 ,…, y n 是各个图像的标签,比如说,猫的图像的值为 0,狗的图像的值为 1。 系统/模型接受该输入并学习从图像 x i 到标签 y i 的映射。一旦训练完毕,系统将呈现一幅新图像 x ' ,以根据图像是猫还是狗来预测标签 y ' = 0 或 1。

这是一个分类问题,系统学习将输入分类到正确的类别。另一个问题类型是回归问题,我们希望根据房子的特征来预测房子的价格。训练数据再次表示为 D = ( x 1y 1 ),( x 2y 2 ),…(xny n 输入是 x 1x 2 ,…, x n 其中每个输入 x i 是一个具有某些属性的向量,例如,一所房子的房间数量、其面积和前草坪的大小等。系统给出的标签为 y i ,房子的市场价值。系统使用来自许多房屋的输入数据来学习输入特征 x i 到房屋价值 y i 的映射。然后给训练好的模型呈现一个向量 x ' ,这个向量由新房子的特征组成,模型预测这个新房子的市场价值 y '

无监督学习

无监督学习没有标签。它只有输入 D = x 1x 2 ,…, x n ,没有标签。系统使用这些数据来学习数据的隐藏结构,从而可以将数据聚类/分类到一些大的类别中。后学习,当系统呈现新的数据点 x ' 时,它可以将新的数据点匹配到已学习的聚类之一。与监督学习不同,每个类别没有明确定义的含义。一旦数据被聚类到一个类别中,基于一个聚类中最常见的属性,我们可以赋予它一些意义。无监督学习的另一个用途是利用基础输入数据来学习数据分布,以便随后可以查询系统来产生新的合成数据点。

很多时候,无监督学习被用于特征提取,然后被馈送到监督学习系统。通过聚类数据,我们可以首先识别隐藏的结构,并将数据重新映射到一个更低的维度形式。有了这个更低维度的数据,监督学习就能够学习得更快。这里我们使用无监督学习作为特征提取器。

还有另一种方法来利用无监督学习方法。考虑这种情况,当你有少量的标记数据和大量的未标记数据。标记数据和未标记数据首先被聚集在一起。接下来,在每个这样的聚类中,基于该聚类中已标记数据的强度,给未标记数据分配标签。我们基本上是利用有标签的数据给无标签的数据分配标签。完全标记的数据接下来被馈送到监督学习算法中,以训练分类系统。这种将监督和非监督学习结合在一起的方法被称为半监督学习

强化学习

我们先来看一个例子。我们正试图设计一种可以自己驾驶的自动驾驶汽车。我们有一辆汽车,我们将称之为代理,即一个正在学习自己驾驶的系统或算法。它正在学习一种行为来驾驶。当它的当前坐标、速度和运动方向组合成一个数字向量时,就被称为它的当前状态。代理使用其当前状态来决定是应用刹车还是踩油门。它还利用这些信息来转动方向盘,以改变汽车的运动方向。“刹车/加速”和“驾驶汽车”的组合决定被称为动作。特定当前状态到特定动作的映射被称为策略。代理人的行为在好的时候会产生一个快乐的结果,而在坏的时候,会导致一个不快乐的结果。代理使用结果的反馈来评估其动作的有效性。作为反馈的结果被称为奖励,代理人在特定的状态下以特定的方式行动得到奖励。基于当前状态及其动作,汽车达到一组新的坐标、速度和方向。这是新状态,代理根据其在上一步中的行为发现自己所处的状态。谁提供这个结果并决定新的状态?是车的周围环境,是车/代理无法控制的东西。代理不控制的其他一切都被称为环境。在整本书中,我们会对这些术语进行更多的讨论。

在这种设置中,系统以状态向量、采取的行动和获得的奖励的形式提供的数据是连续的和相关的。基于代理人采取的行动,从环境中获得的下一个状态和奖励可能会发生剧烈变化。在前面的自动驾驶汽车的例子中,想象有一个行人正在汽车前面过马路。在这种情况下,加速和刹车会有非常不同的结果。使汽车加速可能会导致行人受伤以及汽车和乘员受损。制动汽车可以避免任何损坏,并且在道路畅通后汽车可以继续前进。

在强化学习中,主体没有系统的先验知识。它收集反馈,并使用反馈来计划/学习行动,以最大限度地实现特定目标。由于它最初没有足够的关于环境的信息,它必须探索以收集洞察力。一旦它收集了“足够”的知识,它需要利用这些知识来开始调整它的行为,以最大化它所追求的目标。难的是没有办法知道什么时候探索是“足够的”。如果代理即使在获得了完美的知识后还继续探索,那么它试图收集没有剩余的新信息就是在浪费资源。另一方面,如果代理过早地认为它已经收集了足够的知识,它可能会基于不完整的信息进行优化,并且可能表现不佳。这种何时探索、何时利用的困境是强化学习算法反复出现的核心主题。当我们在本书中讨论不同的行为优化算法时,我们会看到这个问题一次又一次地发生。

核心要素

强化学习系统可以分为四个关键部分:政策、奖励、价值函数和环境模型。

策略是形成代理智能的东西。代理开始与环境交互以感知环境的当前状态,例如,机器人从系统获得视觉和其他感官输入,也称为环境的当前状态或机器人感知的当前观察数据。像智能实体一样,机器人使用当前信息和可能的过去历史来决定下一步做什么,即执行什么动作。该策略将状态映射到代理采取的操作。策略可以是确定性的。换句话说,对于给定的环境状态,代理采取固定的动作。有时政策可以是随机的。换句话说,对于给定的状态,代理可以采取多种可能的动作。

奖励是指代理人试图达到的目标/目的。假设一个机器人试图从 A 点到 b 点,它感应到当前位置并采取行动。如果这个行动使它更接近目标 B,我们期望回报是正的。如果它把机器人从 B 点带走,这是一个不利的结果,我们会期望回报是负的。换句话说,奖励是一个数字值,表示代理人基于其试图实现的目的/目标而采取的行动的好坏。奖励是代理评估行为是好是坏的主要方式,并使用此信息来调整其行为,即优化其正在学习的策略。

奖励是环境的内在属性。所获得的回报是代理人当前所处状态以及在该状态下所采取的行动的函数。代理遵循的奖励和政策定义了价值函数

  • 状态中的值是基于代理所处的状态及其遵循的政策,代理期望获得的总累积奖励。

  • 奖励是基于状态和在该状态下采取的行动的来自环境的即时反馈。与价值不同,奖励不会因代理人的行为而改变。在特定的状态下采取特定的行动总是会产生相同的回报。

价值函数就像长期回报一样,不仅受环境的影响,还受代理人遵循的政策的影响。价值因为回报而存在。代理在遵循策略时累积奖励,并使用这些累积的奖励来评估状态中的值。然后,它改变其政策,以增加国家的价值。

我们可以将这个想法与我们之前谈到的勘探开发困境联系起来。在某些情况下,最佳行动可能会带来直接的负面回报。然而,这样的动作可能仍然是最佳的,因为它可以将代理置于一个新的状态,从该状态它可以更快地到达它的目标。一个例子是穿过一个驼峰或绕道走一条更短的路到达目标。

除非代理进行了足够的探索,否则它可能无法发现这些较短的路径,并可能最终满足于一个次优路径。然而,在发现了更短的路径之后,它无法知道它是否仍然需要更多的探索来找到另一条更快的路径,或者它是否更好地利用其先前的知识来奔向目标。

这本书的前五章着重于使用前面描述的价值函数来寻找最优行为/政策的算法。

最后一个部分是环境模型。在一些寻找最佳行为的方法中,代理使用与环境的交互来形成环境的内部模型。这种内部模型有助于代理人进行规划,即考虑一个或多个动作链来评估最佳动作序列。这种方法叫做基于模型的学习。同时,还有其他完全基于试错的方法。这种方法不形成任何环境模型。因此,这些被称为无模型方法。大多数代理使用基于模型和无模型方法的组合来寻找最优策略。

具有强化学习的深度学习

近年来,涉及基于神经网络的模型的机器学习的子分支出现了爆炸式增长。随着功能强大的计算机、大量数据和新算法的出现,现在可以训练模型根据图像、文本和语音等原始输入进行归纳,类似于人类的操作方式。在深度学习的子分支下,对特定领域手工制作的特征来训练模型的需求正在被强大的基于神经网络的模型所取代。

2014 年,DeepMind 成功地将深度学习技术与强化学习相结合,从一个没有对原始输入进行任何特定领域处理的环境中收集的原始数据中进行学习。它的第一个成功是将强化学习下的传统 Q 学习算法转换为深度 Q 学习方法,该方法被命名为深度 Q 网络(DQN)。Q-learning 涉及一个智能体,它遵循某种策略,以当前状态、它采取的行动、它得到的回报以及它发现自己所处的下一个状态的元组的形式来收集它的行动经验。然后,代理在迭代循环中使用这些经验和贝尔曼方程来寻找最优策略,使得每个状态的价值函数(如前所述)增加。

早期将深度学习与强化学习相结合的尝试并不成功,因为这种结合方法的性能不稳定。DeepMind 做了一些有趣而聪明的改变来克服不稳定问题。它首先将传统的强化学习和深度学习相结合的方法应用于开发 Atari 游戏的游戏代理代理将获得游戏的快照,并且事先不知道游戏的规则。代理将使用这些原始视觉数据来学习玩游戏,例如 Atari 视频游戏。在许多情况下,它达到了人类水平的性能。该公司随后扩展了这一方法,以开发能够在围棋等游戏中击败冠军人类选手的代理人。将深度学习与强化学习结合使用,已经使机器人变得更加智能,而不需要手工制作特定领域的知识。这是一个令人兴奋的快速发展的领域。我们将在第五章中访问这个。我们从第六章开始学习的大多数算法都将涉及深度学习和强化学习的结合。

示例和案例研究

为了激励你,我们将看看强化学习的各种用途,以及它如何帮助解决一些现实世界的问题。

自动驾驶汽车

首先,我们看看自动驾驶汽车(AVs)领域。AVs 有像激光雷达、雷达、摄像机等传感器。感知附近的环境。这些传感器然后被用于执行对象检测、车道检测等。原始感觉数据和对象检测被组合以获得用于规划到目的地的路径的统一场景表示。接下来,计划的路径用于将输入馈送到控制,以使系统/代理遵循该路径。运动规划是规划轨迹的部分。

像逆向强化学习这样的概念可以用来优化成本函数,以得出平滑的轨迹,在逆向强化学习中,我们观察专家,并根据专家的交互来学习隐含的目标/回报。超车、变道和自动泊车等行为也利用强化学习的各个部分来将智能构建到行为中。另一种选择是手工制定各种各样的规则,这永远不可能是详尽的或灵活的。

机器人

使用计算机视觉和自然语言处理或使用深度学习技术的语音识别,为自主机器人增加了类似人类的感知能力。此外,结合深度学习和强化学习的方法已经导致教会机器人学习像人类一样走路、拿起和操纵物体,或者通过相机观察人类行为并学习像人类一样表演。

推荐系统

今天,我们随处可见推荐系统。视频共享/托管应用 YouTube、抖音和脸书根据我们的观看历史向我们推荐我们想要观看的视频。当我们访问任何电子商务网站时,根据我们当前查看的产品和我们过去的购买模式,或者根据其他用户的行为方式,我们会看到其他类似的产品推荐。

所有这些推荐引擎越来越多地由基于强化学习的系统驱动。这些系统不断地从用户对引擎给出的建议的反应中学习。根据建议采取行动的用户在给定的上下文中将这些行动强化为好的行动。

金融和贸易

由于其顺序动作优化的焦点,其中过去的状态和动作影响未来的结果,强化学习在时间序列分析中发现了重要的用途,特别是在金融和股票交易领域。许多自动化交易策略使用强化学习方法,根据过去行动的反馈不断改进和微调交易算法。银行和金融机构使用聊天机器人与用户互动,以提供有效、低成本的用户支持和参与。这些机器人再次使用强化学习来微调其行为。投资组合风险优化和信用评分系统也受益于基于 RL 的方法。

卫生保健

强化学习在医疗保健中有着重要的用途,无论是生成预测信号和实现早期医疗干预,还是机器人辅助手术或管理医疗和患者数据。它还用于改进成像数据的解释,成像数据本质上是动态的。基于 RL 的系统提供从其经验中学习到的建议,并不断发展。

博弈

最后,我们不能过分强调基于 RL 的代理能够在许多棋盘游戏中击败人类玩家。虽然设计能玩游戏的代理看起来有些浪费,但这是有原因的。游戏提供了一个更简单的理想化世界,使设计、训练和比较方法变得更容易。在这种理想化的环境/设置下学习的方法可以随后被增强,以使代理在真实世界的情况下表现良好。游戏提供了一个控制良好的环境来更深入地研究这个领域。

正如我们之前所说,深度强化学习是一个令人着迷且快速发展的领域,我们希望为您提供一个坚实的基础,以便在您掌握该领域的旅程中入门。

库和环境设置

本书中的所有代码示例都是用 Python 和 PyTorch、TensorFlow、OpenAI Gym 库编写的。虽然有许多方法可以设置环境,但我们将使用conda环境。以下是获得完整环境的步骤:

  1. 访问 https://docs.conda.io/en/latest/miniconda.html 并为您的平台选择 Miniconda install 来安装 Miniconda。请选择最新的 Python3.x 版本。如果您已经安装了 Anaconda 或 Miniconda,可以跳过这一步。

  2. 我们将创建一个新的环境来运行本书附带的代码。打开命令终端,键入以下内容:

    conda create -n apress python=3.8
    
    

    其中apress是环境的名称,我们使用的是 Python 3.8.x。对所有提示回答“是”。

  3. 使用以下命令切换到您创建的新环境:

    conda activate apress
    
    

    确保我们在接下来的步骤中要求您运行的所有命令都在您激活新的conda环境的同一终端中执行。

  4. 安装 TensorFlow 2.x,可以参考 https://www.tensorflow.org/install 了解更多细节,也可以在conda shell 中运行以下命令:

    pip install tensorflow
    
    

    按照提示,适当回答(大部分是)。

  5. 我们现在将安装 PyTorch。访问 https://pytorch.org/get-started/locally/ 并选择您拥有的环境设置。你不需要一台支持 GPU 的机器。本书中的大多数例子都可以在 CPU 上运行良好,除了第六章中的一个例子,在那里我们训练一个代理玩 Atari 游戏。即使有了 GPU,训练一个雅达利游戏代理也可能需要很长时间。

    在写这本书的时候,我们在前一页选择了以下组合:

    PyTorch build: Stable (1.7.0)

    Your OS: Windows or Mac

    Package: Conda

    Language: Python

    CUDA: None

    有了这些选择,生成的命令如下:

    conda install pytorch torchvision torchaudio cpuonly -c pytorch
    
    

    将生成的命令从网页复制并粘贴到您的conda终端中,在这里您将apress作为当前活动的conda环境(步骤 3)。

    确保使用链接 https://pytorch.org/get-started/locally/ 为本地机器生成命令,并做出适当的选择。

  6. 接下来,我们将安装一个 Jupyter 笔记本。在之前的终端中,在终端中运行以下命令:

    conda install -c conda-forge jupyter notebook
    
    

    请注意,我们使用的是经典的 Jupyter 笔记本。然而,如果你喜欢你可以安装一个 JupyterLab 界面,你可以在 https://jupyter.org/install 找到更多的细节。

  7. 我们现在将安装 OpenAI Gym 库,其中包含各种强化学习环境,我们将使用这些环境来培训代理。请在命令行中键入以下内容:

    pip install gym
    
    

    详情可以参考 https://gym.openai.com/docs/#installation

  8. 下一行是matplotlib,,它有绘制图形的例程。请在前一个终端中运行以下命令。请注意,所有命令都必须在命令 shell 中运行,其中apress是当前活动的conda环境。

    conda install -c conda-forge matplotlib
    
    

    你可以在 https://matplotlib.org/ 了解更多matplotlib

  9. 让我们也安装另一个名为seaborn.的绘图库,它建立在matplotlib之上,帮助安装格式良好的图形例程。在终端中运行以下命令:

    conda install -c anaconda seaborn
    
    

你可以在 https://seaborn.pydata.org/index.html 了解更多关于 Seaborn 的信息。

  1. 我们将安装一个小图书馆,为培训提供进度条。在终端运行以下命令:

  2. 让我们为 OpenAI Gym 安装一些额外的依赖项。我们将安装 Atari 相关的依赖项,以便 Atari 游戏可以通过 OpenAI Gym 接口进行训练。我们还将安装 Box2D,以允许我们将在第八章中使用的连续控制依赖。最后,我们将安装pygame,它允许我们玩 Atari 游戏,并使用键盘与之交互。请在终端中使用以下命令:

conda install -c conda-forge tqdm

pip install gym[atari]

在 Mac 上,您可能需要运行pip install 'gym[atari]'。请注意单引号。

接下来,在终端运行以下两个命令:

conda install -c conda-forge pybox2d

pip install pygame

如果您使用的是 Windows,您可能需要重新安装 Atari 模拟器。从pip install gym[atari]直接安装似乎不能与 Windows 一起工作,并给出一个dll not found错误。但是,请不要跳过跑pip install gym[atari]。运行 Atari 模拟器需要安装一些其他的依赖项。运行以下两个命令来解决这个问题,并在您执行了pip install gym[atari]之后仅运行:

*1. 最后,我们安装stable-baselines3,它已经交付了许多流行的 RL 算法的实现。要安装它,请在终端中运行以下命令:

pip unistall atari-py

pip install -f https://github.com/Kojoley/atari-py/releases atari_py

pip install stable-baselines3

你可以在 https://github.com/DLR-RM/stable-baselines3 了解更多关于这个图书馆的信息。

  1. 现在您下载并解压缩本书附带的代码文件。打开终端,在解压后文件夹中导航。使用以下命令切换到先前安装的conda环境:
conda activate apress

接下来,在终端使用以下命令启动 Jupyter 笔记本:

jypyter notebook

此时,您将看到您的默认浏览器打开,并且您已经准备好导航到相应的章节(图 1-2 )。

img/502835_1_En_1_Fig2_HTML.jpg

图 1-2

您可以打开笔记本来运行和研究代码。

图 1-3 显示了打开的第二章中的一个笔记本。

img/502835_1_En_1_Fig3_HTML.jpg

图 1-3

打开示例笔记本

安装本地环境的替代方法

解压代码文件夹后,您会看到一个名为environments的文件夹。它包含两个 YML 环境文件。一个是 Windows 版,名为environment_win.yml,另一个是 macOS 版,名为environment_mac.yml。您可以使用这些文件在本地机器上复制环境。导航到该文件夹并运行以下程序。

在 Windows 上:

conda env create -f environment_win.yml

在 macOS 上:

conda env create -f environment_mac.yml

使用这种方法将处理前面的步骤 2 到 12。

摘要

在这一章中,我们首先介绍了强化学习领域,以及它是如何从一个严格的基于规则的决策系统演变为一个灵活的最佳行为学习系统的历史,它可以根据以前的经验自行学习。

我们讨论了机器学习的三个子分支,即监督学习、非监督学习和强化学习。我们比较了这三种方法,以阐述每种方法适用的问题背景。我们还讨论了组成强化设置的子组件和术语。它们是主体、行为、状态、行动、政策、奖励和环境。我们用一辆汽车和一个机器人的例子来说明这些子组件是如何相互作用的,以及这些术语的含义。

我们讨论了奖励和价值函数的概念。我们讨论了奖励是短期反馈,价值函数是代理行为的长期反馈。最后,我们介绍了基于模型和无模型的学习方法。接下来,我们谈到了深度学习在强化学习领域的影响,以及 DQN 是如何开启深度学习与强化学习相结合的趋势的。我们还讨论了组合方法如何产生可扩展的学习,包括来自图像、文本和语音等非结构化输入的学习。

我们接着讨论了强化学习的各种用例,引用了自动驾驶汽车、智能机器人、推荐系统、金融和贸易、医疗保健以及视频/棋盘游戏等领域的例子。最后,我们介绍了设置 Python 环境并能够运行本书附带的代码示例所需的步骤。

**

二、马尔可夫决策过程

正如在第一章中所讨论的,强化学习包括顺序决策。在这一章中,我们将在模拟连续决策行为的概率分支下形式化使用随机过程的概念。虽然我们在强化学习中研究的大多数问题都被建模为马尔可夫决策过程 (MDP),但我们首先介绍马尔可夫链(MC),然后介绍马尔可夫奖励过程(MRP)。我们最后深入讨论了 MDP,同时涵盖了 MDP 背后的模型设置和假设。

然后我们讨论了相关的概念,如状态的价值函数和状态-动作对的动作价值函数。最后,我们在本章结尾详细讨论了各种形式的贝尔曼方程,如贝尔曼期望方程和贝尔曼最优性方程,并简要介绍了各种类型的学习算法。

虽然本章的重点将是强化学习的理论基础,我们将有例子和练习来帮助巩固概念。没有比自己编码更好的学习方法了,这是本书反复出现的主题。

强化学习的定义

在前一章中,我们讨论了一个主体通过基于其当前状态采取行动、获得数字奖励以及发现自己处于新状态而与环境互动的循环。

图 2-1 说明了这个概念。

img/502835_1_En_2_Fig1_HTML.jpg

图 2-1

代理环境交互的循环

代理在时间 t 处于状态 S t 。从代理在这种状态下可以采取的一组动作中,它采取了一个特定的动作 A t 。此时,系统过渡到下一个时间段(t+1)。环境用数字奖励 R t+1 来响应代理,并且将代理置于 S t+1 的新状态。“状态到行动到奖励和下一个状态”的循环一直持续到时间代理达到某个最终目标状态,如游戏结束、任务完成或特定数量的时间步结束。

代理行为环境反应

St->At->Rt+1,S t+1

当前状态采取的行动奖励和新状态

S 0 ,A 0 ,R 1 ,S 1 ,A 1 ,R 2 ,S 2 ,A 2 ,R 3 ,S 3 ,A 3 ,R 4 ,S..

有一个状态、行动、回报和状态(S,A,R,S)的循环。

代理根据它发现自己所处的状态采取行动;即,代理通过采取动作来“行动”。环境对代理的行为作出“反应”,给代理一些数字奖励,并把代理转换到一个新的状态。代理人的目标是采取一系列行动,使其从环境中获得的总回报最大化。

强化学习的目的是让代理学习它可能发现自己所处的每个状态的最佳可能动作,牢记累积报酬最大化的目标。

作为一个例子,考虑国际象棋比赛。棋子在棋盘上的位置可以形成当前状态(St)。代理人(玩家)通过移动棋子采取动作(At)。代理人获得奖励(Rt+1),假设 0 代表安全移动,1 代表导致将死的移动。游戏也移动到新的状态,(St+1)

有时,在文献中,状态也被称为观察值,以区分在某些情况下代理可能只能看到实际状态的部分细节。这种部分可观测的状态被称为观测。代理必须使用全部或部分状态信息来做出关于它应该采取的动作的决定。在现实生活的实现中,理解代理将观察到什么以及它将有多详细是一个重要的方面。学习算法的选择和理论保证会受到部分可观测性水平的显著影响。我们将首先关注状态和观察值相同的情况;换句话说,代理知道当前状态的每一个可能的细节。但是从第五章开始,我们将开始关注状态不是完全已知,或者即使完全已知也需要使用某种近似来总结的情况。

现在让我们通过几个例子来深入理解状态/观察到行动再到下一个状态的循环。在整本书中,我们将使用 OpenAI 的 Python 库 Gym,它实现了一些常见的简单环境。我们来看第一个环境:MountainCar。启动 Jupyter 笔记本并导航至listing_2_1.ipynb

MountainCar环境中,有一个汽车试图攀爬的小山,最终目标是到达小山右上角的旗帜。车子动力不够,需要向左摆动,然后向右加速才能到达目标。这种来回摆动需要发生多次,这样汽车才能获得足够的动力,到达右谷顶部的旗帜。见图 2-2 。

img/502835_1_En_2_Fig2_HTML.jpg

图 2-2

登山车-v0 环境。这个环境有一个二维状态和一组三个离散的动作

清单 2-1 显示了测试MountainCar环境的代码。

import gym
env = gym.make('MountainCar-v0')

# reset environment and get initial observation/state
# Observation/state is a tuple of (position, velocity)
obs = env.reset()
print("initial observation:", obs)

# possible 3 actions
# {0: "accelerate to left", "1": "do nothing", "2": "accelerate to right"}
print("possible actions:", env.action_space.n)

# reinforcement learning is all
# about learing to take good actions
# from a given state/observation
# right now taking a random action
def policy(observation):
    return env.action_space.sample()

# take 5 random actions/steps
for _ in range(5):

    # to render environment for visual inspection
    # when you train, you can skip rendering to speed up
    env.render()

    # based on curret policy, use the current observation
    # to find the best action to take.
    action = policy(obs)
    print("\ntaking action:", action)

    # pass the action to env which will return back
    # with new state/"observation" and "reward"
    # there is a "done" flag which is true when game ends
    # "info" provides some diagnostic information
    obs, reward, done, info = env.step(action)
    print("got reward: {0}. New state/observation is: {1}".format(reward, obs))

# close the enviroment
env.close()

Listing 2-1MountainCar Environment

让我们一行一行地浏览代码。我们先用import gym导入 OpenAI 健身房库。OpenAI Gym 为强化学习实现了多种环境。在我们阅读本书的章节时,我们将会用到其中的一些环境。

继续,我们用env = gym.make('MountainCar-v0').实例化MountainCar环境,然后通过obs = env.reset()初始化环境,返回初始观察值。在MountainCar,的情况下,观察值是两个值的元组:(x-position, x-velocity)。代理通过观察找到最佳行动:action = policy(obs)

在清单 2-1 中,代理采取随机行动。然而,随着我们在本书中的进展,我们将学习不同的算法,这些算法将被用来寻找报酬最大化的政策。对于MountainCar环境,有三种可能的动作:向左加速,什么都不做,向右加速。代理将动作传递给环境。在这一点上,系统在概念上采用从时间 t 移动到时间 t+1 的时间步长。

环境执行这个动作并返回一个由四个值组成的元组:时间(t+1)的新观察值、奖励(r t+1 )、完成标志和一些调试信息。这些值用obsrewarddoneinfo = env.step(action)存储在本地。

接下来,代理使用新的观察再次采取下一步骤,取回新的四个值的元组,即下一状态、奖励、完成标志和调试信息。这种“状态到行动再到奖励到新状态”的循环一直持续到游戏结束或者在代码中终止。在这个设置中,代理在每个时间步长获得-1 的奖励,当游戏结束时奖励为 0。因此,代理的目标是在尽可能短的步数内到达标志。

让我们看看另一个环境。用env = gym.make('CartPole-v1')替换env = gym.make('MountainCar-v0')行,再次运行清单 2-1 中的代码。见图 2-3 。

img/502835_1_En_2_Fig3_HTML.jpg

图 2-3

CartPole-v1 环境。这样做的目的是尽可能长时间保持立杆的平衡

CartPole环境有一个由四个值组成的观察空间。购物车在 x 轴上的位置和沿 x 轴的速度是前两个值。杆的倾斜角是观察元组中的第三个值,必须在-24 o 到 24 o 之间。观测元组中的第四个值是极点的角速度。

可能的动作是 0 和 1,分别向左或向右推购物车。代理人在每个时间步长不断获得 1 的奖励,从而激励代理人在尽可能长的时间内平衡极点并获得尽可能多的点。如果杆子在任一侧倾斜超过 12 度 o ,或者推车在任一侧移动超过 2.4 度,即推车到达任一端,或者已经走了 200 步,游戏结束。

您可能已经注意到,这两种情况下的代码设置是相同的。我们只是更改了一行代码来实例化不同的环境。遍历环境并接收反馈的其余代码保持不变。OpenAI Gym 库提供的抽象使我们更容易在许多环境中测试特定的算法。此外,我们还可以根据手头的问题创建自己的定制环境。

本节涵盖了强化学习的简要定义和我们将使用的 OpenAI 健身房环境的基础知识。在接下来的章节中,我们将详细介绍强化学习(RL)设置的不同组件。

代理和环境

代理和环境的设置非常灵活。代理是一个从系统外部获取状态/观察细节的封闭系统。它使用一个给定的策略或学习一个策略来最大化提供给它的一些目标。它还根据当前的观察/状态和代理采取的行动从环境中获得奖励。代理无法控制这些奖励将会是什么。代理也不控制从一种状态到另一种状态的转换。这种转变取决于环境,主体只能通过决定在特定状态下采取何种行动来间接影响结果。

另一方面,环境是代理之外的一切。换句话说,它可能是整个世界的其余部分。然而,环境的定义通常非常狭窄:包括可能影响奖励的信息。环境接收代理想要采取的动作。它以奖励的形式向代理提供反馈,并根据代理采取的行动转换到新的状态。接下来,环境向代理提供部分修改后的状态信息,该信息成为代理的新观察/状态。

代理和环境之间的边界是抽象的。它是根据手头的任务和我们在特定情况下试图实现的目标来定义的。让我们看一些例子。

考虑一下自动驾驶汽车的情况。代理状态可以是来自多个相机、光探测和测距(LiDAR)的视觉图像、其他传感器读数以及地理坐标。虽然“环境”是代理之外的一切,即整个世界,但是代理的相关状态/观察仅是与代理采取行动相关的世界的那些部分。两个街区外的行人的位置和动作可能与自动驾驶汽车做出决策无关,因此不需要成为代理的观察/状态的一部分。自动驾驶汽车的行动空间可以根据油门踏板值、刹车和转向控制来定义。车辆采取的行动导致汽车转换到新的观察状态。如此循环下去。期望代理(即,自动驾驶汽车)基于特定目标(例如,从 A 点到 b 点)采取最大化回报的行动

让我们考虑另一个机器人试图解魔方的例子。这种情况下的观察/状态是魔方六个面的配置,而动作是可以在魔方上执行的操作。对于每个时间步长,奖励可以是-1,而在成功求解结束时,即在终止时,奖励可以是 0。这样的奖励设置将激励代理找到最少数量的操作来解决难题。

在我们的设置中,我们将使用 OpenAI 健身房环境,观察将始终是一个各种值的元组,其确切组成取决于特定的健身房环境。行动将取决于具体的环境。奖励将由环境提供,具体的实数数值取决于具体的健身房环境。在这种情况下,代理将是我们编写的软件程序。代理(软件程序)的主要工作将是接收来自 OpenAI 健身房环境的观察,采取行动,并等待环境在奖励和下一状态方面提供反馈。

虽然我们谈论的是离散的步骤,即处于状态(S t )的代理采取行动(A t )以及在下一个时间步骤中接收奖励(R t+1 )和下一个状态(St+1),但实际问题的本质往往是连续的。在这种情况下,我们可以在概念上将时间划分为小的离散时间步长,从而将问题建模回离散时间步长环境,并使用前面所示的熟悉设置进行求解。

在最一般的层面上,当代理发现自己处于状态 S 时,它可以采取许多可能的动作中的一个,这些动作在动作空间上具有某种概率分布。这种政策被称为随机政策。此外,在某些情况下,每当代理发现自己处于给定状态时,代理可能只对该状态采取一个特定动作。这种策略被称为确定性策略。策略的定义如下:

$$ \pi =p\left(a|s\right) $$

(2.1)

即当代理处于状态 s 时采取动作 a 的概率

类似地,在最一般的水平上,所接收的奖励和代理的下一个状态将是奖励和下一个状态的可能值的概率分布。这被称为跃迁动力学

$$ p\left({s}^{\prime },r\right)=\mathit{\Pr}\left{{S}_t={s}^{\prime },{R}_t=\left.r\ \right|\ {S}_{t-1}=s,{A}_{t-1}=a\right} $$

(2.2)

其中 S t 和 S t-1 属于所有可能的状态。A t 属于所有可能的行动,奖励 r 是一个数值。前面的等式定义了下一个状态是 s '的概率,当上一个状态是 s,代理采取行动 a 时,奖励是 r。

奖励

在强化学习中,奖励是环境给代理人的一个信号,让代理人知道行动的好坏。代理使用这个反馈来微调它的知识,并学习采取好的行动来最大化回报。这引发了一些重要的问题,比如你最大化了什么,对最后一次行动的直接回报还是对整个生命历程的回报?当代理对环境不够了解时会发生什么?它应该在多大程度上通过在开始之前采取一些随机步骤来探索环境?这种困境被称为勘探与开发的困境。在我们讨论各种算法时,我们会不断回到这一点。代理人最大化累计总报酬的目标被称为报酬假设

重申一下,奖励是一个信号,或者是一个单一的数值,由环境发回给代理,告知代理行动的质量。请注意,观察可以是多维的,比如对MountainCar是二维的,对CartPole是四维的。类似地,动作可以是多维的,例如,自动驾驶汽车场景的加速度值和转向角。然而,在每种情况下,奖励总是一个标量实数。只有一个值似乎有局限性,但事实并非如此。最后,为了达到目标,代理人接受训练,而奖励则记录了这一过程。

让我们来看一个迷宫的例子,在这个例子中,代理试图找到它的出路。我们可以把奖励表述为代理在每个时间点获得-1 的奖励,在一集结束时获得 0 的奖励。这样的奖励设置激励代理人以最少的可能步数走出迷宫,并最小化负一的总和(-1)。另一个奖励设置可以是代理在所有时间步骤中获得奖励 0,在每集结束时代理走出迷宫时获得奖励 1。你认为在后期设定中,代理的行为会发生什么变化?代理人有理由走出迷宫去收集+1 的奖励,但它并不着急。不管是 5 步后出来还是 500 步后出来都会得到相同的+1。我们如何改变这种情况,促使代理不仅仅关注奖励的收取,而是在尽可能短的时间内这样做?

这个问题自然引出了贴现的概念。什么更有用?五个时间步后奖励“x”还是 500 个时间步后奖励“x”?当然,越早越好,因此 5 步后+1 的奖励比 500 步后+1 的奖励更有价值。我们通过贴现未来到现在的回报来诱导这种行为。来自下一个时间步的奖励“R”通过折扣因子“γ”(γ)折扣到当前时间。折扣系数是一个介于 0 和 1 之间的值。在迷宫示例中,在五个步骤中完成迷宫的奖励意味着奖励γ 5 。(+1)对奖励γ 500 。(+1)为 500 步完成。时间“t”处的“返回”定义如下:

g= r+r+2+2【r】++

贴现因子类似于我们在金融界看到的。这和现在的钱比将来的钱更有价值是一个概念。

贴现因子还有一个重要的数学目的,即确保总收益 G t 对于连续任务是有界的。考虑一个持续任务的场景,其中每个状态给出一些积极的奖励。由于这是一项没有逻辑终点的持续任务,总回报将不断增加,并爆炸到无穷大。然而,有了折扣因素,总的累积奖励将得到上限。因此,折扣总是在连续任务中引入,在间断任务中是可选的。

有趣的是要注意贴现因子对代理人试图最大化累积报酬的影响。考虑折扣系数为 0。如果你在( 2.3 中使用这个折扣值,你会看到累积奖励正好等于下一个即时奖励的奖励。这反过来会导致代理人变得短视和贪婪,只考虑下一个时间步骤的奖励。接下来,考虑贴现因子接近 1 的另一个极端。在这种情况下,代理人将变得越来越有远见,因为使用贴现因子 1,我们可以看到在( 2.3 )中定义的累积报酬将给予所有未来报酬同等的重要性。从当前时间步到结束的整个行动序列变得重要,而不仅仅是紧接的下一个时间步奖励。

前面的讨论应该强调根据代理需要优化的行为来设计适当的奖励的重要性。奖励是代理用来决定好的或坏的状态和/或行动的信号。例如,在一场国际象棋比赛中,如果你将奖励设计为捕获的对手棋子的数量,那么代理人可能会学习进行危险的移动,以最大化立即行动的奖励,而不是牺牲自己的一个棋子来获得优势地位和未来移动的可能胜利。奖励设计领域是一个开放的研究领域。话虽如此,书中的例子还是会使用相当简单直观的奖励定义。

奖励设计不是一件容易的事情,尤其是在连续控制和机器人领域。考虑一个人形机器人的任务,比如说,训练的目标是让代理尽可能长时间地学习奔跑。代理将如何知道手臂和腿需要协调移动的方式来学习跑步的任务?代理人的重心离地面的距离、做出动作所花费的能量、躯干与地面的角度等具体措施,结合试错法,使代理人学会一个好的策略。如果没有一个好的奖励信号来塑造我们希望代理人学习的行为,代理人将需要很长时间来学习。更糟糕的是,代理有时可能会学习到违反直觉的行为。一个很好的例子是 OpenAI 的代理玩视频游戏 CoastRunners,其目标是快速完成划船比赛,并领先于其他玩家。游戏提供了击中目标的分数,没有明确的完成游戏的分数。这位代理人学会了一种滑稽和破坏性的行为,即重复击中一组目标,但在比赛中没有进步,但得分比人类选手高 20%。你可以在 OpenAI 博客 1 中了解更多信息,并观看一段视频。奖励需要精心设计,以确保自主 RL 代理不会学习到潜在的危险行为。

在其他情况下,根本不清楚如何对奖励函数进行建模。假设一个机器人试图学习类似人类的行为,或者说,从水壶向玻璃杯倒水,而不会因为过度的抓握力而溢出水或打碎玻璃杯。在这种情况下,被称为逆强化学习的强化学习的扩展被用于基于观看人类专家执行任务的观察来学习隐式奖励函数。我们将在第十章中简要谈论它。然而,对奖励形成和发现的详细研究可以单独成书。

因此,简而言之,就像数据质量对监督学习很重要一样,一个合适的奖励函数对于让算法训练代理人达到预期行为也很重要。

马尔可夫过程

强化学习领域是基于马尔可夫过程的形式主义。在我们深入学习(行为优化)算法之前,我们需要很好地掌握这个基本的理论结构。在这一节中,我们将讨论马尔可夫链,然后是马尔可夫回报过程,最后是马尔可夫决策过程。

马尔可夫链

先说什么是马尔可夫性质。考虑图 2-4 中的图表。我们正试图模拟一个城市每天的降雨状况。它有两种状态;也就是说,在任何一天,要么下雨,要么不下雨。从一个状态到另一个状态的箭头表示基于当前状态第二天处于两个状态之一的概率。比如今天下雨,第二天下雨的几率是 0.3,第二天不下雨的几率是 0.7。同样,如果今天没有下雨,明天继续干燥的可能性是 0.8,而明天下雨的可能性是 0.2。

img/502835_1_En_2_Fig4_HTML.jpg

图 2-4

简单的双态马尔可夫链。一种状态是“下雨”,第二种状态是“不下雨”

在这个模型中,我们假设某一天的降雨取决于前一天的状态;即,如果今天也下雨,明天下雨的可能性是 0.3,如果今天不下雨,明天下雨的可能性是 0.2。昨天下雨还是之前下雨对明天下雨的概率没有影响。这是一个重要的概念。被称为独立;即知道现在(t 时刻的现在状态)使得未来(t+1 时刻的未来状态)独立于过去(0,1,…,t-1 时刻的所有过去状态)。数学上,我们可以这样表达:

$$ P\left({S}_{t+1}={s}^{\prime}\right|\ {S}_0,{S}_1,\dots \dots {S}_{t-1},{S}_t=\mathrm{s}\left)=P\left({S}_{t+}={s}^{\prime }\ \right|\ {S}_t=\mathrm{s}\right) $$

(2.4)

它仅仅意味着处于时间 t+1 的状态的概率仅取决于时间 t 的状态。时间(t+1)的状态不依赖于时间(t)之前的状态,即不依赖于状态 S 0 至 S t-1

如果环境向代理提供足够详细的观察,那么代理可以从它的当前状态知道什么是已知的,并且不需要记住它在过去到达现在必须经历的状态/事件链。这个马尔可夫独立性是一个重要的假设,需要它来证明强化学习算法的理论合理性。在实践中,很多时候我们对于非马尔可夫系统仍然得到相当好的结果。然而,重要的是要记住,在没有马尔可夫属性的情况下,结果不能被评估为理论上的最坏情况界限。

回到图 2-3 中的马尔可夫链图,我们可以将转移概率定义为在前一时间步中从状态 S t 移动到状态 S t+1 的概率。如果一个系统有 m 个状态,转移概率将是一个 m 行 m 列的方阵。图 2-3 的转移概率矩阵如下所示:

$$ P=\left[\begin{array}{cc}0.3& 0.7\ {}0.2& 0.8\end{array}\right] $$

每一行中的值之和将为 1。行值表示从一个给定状态到系统中所有状态的概率。例如,第 1 行表示从 s 1 到 s 1 的概率是 0.3,从 s 1 到 s 2 的概率是 0.7。

前面的马尔可夫链将有一个稳定状态,其中在给定的一天有一个确定的概率处于两个状态之一。假设处于状态 s 1 和 s 2 的概率由一个向量 S =【S1S2T给出。从图 2-4 中我们可以看出

s1= 0.3 x s1+0.2 x s2(a)

s2= 0.7 x s1+0.8 x s2(b)

我们也知道

s 1 + s 2 = 1 (c)

因为系统在任何时间点都必须处于两种状态之一。

(a)、(b)和(c)中的方程构成了一个一致的、可解的方程组。

从(a)中,0.7 x s 1 = 0.2 x s 2 。或者,s 1 = (0.2/0.7) s 2

将之前(c)中的 s 1 的值代入,我们得到:

0.2/0.7 秒 2 +秒 2 = 1

由此得出 s 2 = 0.7/0.9 = 0.78,进而得出 s 1 = 0.2/0.9 = 0.22。

在向量代数符号中,我们可以将稳定状态下的关系指定如下:

S T = S T 。P ……(c)

该关系可用于迭代求解稳态概率。清单 2-2 给出了一个代码片段。

# import numpy library to do vector algebra
import numpy as np

# define transition matrix
P = np.array([[0.3, 0.7], [0.2, 0.8]])
print("Transition Matrix:\n", P)

# define any starting solution to state probabilities
# Here we assume equal probabilities for all the states
S = np.array([0.5, 0.5])

# run through 10 iterations to calculate steady state
# transition probabilities
for i in range(10):
    S = np.dot(S, P)
    print("\nIter {0}. Probability vector S = {1}".format(i, S))

print("\nFinal Vector S={0}".format(S))

Listing 2-2Markov Chain Example and Its Solution by Iterative Method

当我们运行清单 _2_2.ipynb 中的程序时,产生的输出如下所示,它与我们一起求解等式(a)、(b)和(c)得到的值相匹配:

Final Vector S=[0.22222222 0.77777778]

图 2-3 中的公式没有开始和结束状态。这是一个继续任务的例子。还有另一类公式有一个或多个最终状态。让我们看看图 2-4 。这就是所谓的阶段性任务,其中代理从某个状态开始,经过许多转换,最终到达结束状态。可能有一个或多个具有不同结果的结束状态。在图 2-5 中,结束状态是成功完成考试并获得证书。一盘棋可能有三种结局:赢、输或平。

img/502835_1_En_2_Fig5_HTML.jpg

图 2-5

具有一个通过方形框描述的结束状态的情节马尔可夫链的例子

像连续公式一样,有一个转移概率矩阵的概念。对于图 2-4 ,它看起来像这样:

s 1 =“学习”;s 2 =“走神”;s 3 =“参加考试”;s 4 = "证书"

$$ P=\left[\begin{array}{cccc}0.3& 0.5& 0.2& 0\ {}0.1& 0.9& 0& 0\ {}0.4& 0& 0& 0.6\ {}0& 0& 0& 1\end{array}\right] $$

在前面显示的情节任务的情况下,我们可以查看多次运行,每次运行称为一个情节。让开始状态总是 s 1 。多集的例子可能是这样的。在前面的例子中,我们只有一个结束状态,因此剧集将总是在 s 4 结束,如下所示:

s 1 ,s 2 ,s 2 ,s 1 ,s 3 ,s 4

s 1 ,s 2 ,s 2 ,s 1 ,s 3 ,s 1 ,s 2 ,s 3, s 4

s 1 ,s 2 ,s 2 ,s 2 ,s 2 ,s 2 ,s 2 ,s 2 ,s 2 ,s 2 ,s 2 ,s 2 ,s

在阶段性任务中,我们没有稳定状态的概念。最终,系统将转换到最终状态之一,而不管转换的顺序如何。

马尔可夫奖励过程

转到马尔可夫奖励过程,我们现在引入奖励的概念。查看图 2-6 和图 2-7 中修改后的状态图。它们与上一节中的问题相同(分别为图 2-4 和图 2-5 ),只是在每个过渡阶段增加了奖励。

img/502835_1_En_2_Fig7_HTML.jpg

图 2-7

类似于图 2-5 的阶段性马尔可夫奖励过程,每次转换都有额外的奖励 R

img/502835_1_En_2_Fig6_HTML.jpg

图 2-6

持续马尔可夫奖励过程。这类似于图 2-4 中的马尔可夫链,只是为每个转移箭头增加了奖励 R

在前面的两个 MRP 设置中,我们可以计算类似于前面 MC 中的转移概率。此外,我们可以计算一个状态 v(s)的值,这是代理人在 t 时刻处于状态 S=s 时得到的累积报酬,它遵循系统的动力学。

$$ v(s)=E\left[{G}_t\ \right|\ {S}_t=s\Big] $$

(2.5)

其中 G t 在 2.3 中定义。

$$ {G}_t={R}_{t+1}+\gamma {R}_{t+2}+\kern0.5em {\gamma}²{R}_{t+3}+\dots $$

符号E[Gt|St=S读作期望返回 G t 当 t 时刻的起始状态为St=**期望值一词是指进行大量模拟时 G t 的平均值。期望算子(E[])用于推导公式和证明理论结果。然而,在实践中,它被许多样本模拟的平均值所代替,也被称为蒙特卡罗模拟

我们还注意到使用了 γ 作为折扣因子。如前所述, γ 抓住了今天的回报比明天的回报更好的概念。在数学上避免连续任务的无限回报也很重要。除了提到这个事实之外,我们将不在这里深入研究数学细节。

γ = 1 意味着代理人是有远见的,它对未来的回报和对眼前的回报一样关心。值 γ = 0 意味着代理人目光短浅,只关心下一时间步的即时回报。你可以通过在方程Gt=Rt+1+γR+2+γ2R中放入 γ 的不同值来检验这个概念

综上所述,到目前为止我们已经介绍了转移概率 P 的概念,返回Gt,以及状态值v(S)=E**Gt|St=S

马尔可夫决策过程

马尔可夫决策过程通过引入“行动”的额外概念来扩展奖励过程在 MRP 中,代理人无法控制结果。一切都受环境支配。然而,在 MDP 体制下,代理可以基于当前状态/观察来选择动作。代理人可以学习采取使累积报酬最大化的行动,即总回报 G t

让我们看看图 2-7 中的阶段性 MRP 的扩展。查看图 2-8 ,可以看到在状态学习中,代理可以采取两种行动中的一种,要么继续学习,要么参加考试。代理以 0.5 的相等概率选择这两个动作。该动作影响下一个状态的概率和奖励值。如果代理人决定学习更多,则有 0.7%的可能性代理人会分心并忙于社交媒体,有 0.3%的可能性代理人会继续专注于学习。但是,“考”的决定导致两种结果。要么代理以 0.4 的概率失败回去学习奖励-2,要么代理以 0.6 的概率成功完成考试,“领证”,奖励 10。

img/502835_1_En_2_Fig8_HTML.jpg

图 2-8

图 2-7 中给出的作为马尔可夫回报过程的扩展的情景马尔可夫决策过程。黑色实心圆圈是代理可以做出的决定

转换函数现在是从当前状态和动作到下一个状态和奖励的映射。它定义了每当代理人在状态 s 采取行动 a 时,获得奖励 r 并转移到下一个状态s’的概率。

$$ p\left({s}^{\prime },r\ \right|\ s,a\left)=\Pr \left{\ {S}_t={s}^{\prime },{R}_t=r\ \right|\ {S}_{t-1}=s,{A}_{t-1}=a\right} $$

(2.6)

我们可以用( 2.6 )推导出很多有用的关系。转移概率可以从先前的转移函数中导出。转移概率定义了当代理在状态 s 中采取动作 a 时,它发现自己处于状态s’的概率。其定义如下:

$$ p\left({s}^{\prime}\right|\ s,a\left)=\Pr \left{\ {S}_t={s}^{\prime }\ \right|\ {S}_{t-1}=s,{A}_{t-1}=a\right}=\sum \limits_{r\in R}p\left({s}^{\prime },r\right|s,a\Big) $$

(2.7)

( 2.7 )已经通过平均代理人在从 (s,a) 过渡到s’时可能获得的所有奖励而获得。

让我们看看 MDP 的另一个例子,这是一项持续的任务。想象一下机场里的电动货车将乘客从一个航站楼运送到另一个航站楼。货车有两种状态,“高电荷”和“低电荷”在每个州,面包车可以“保持闲置”,“通过连接到充电站充电”,或“运送乘客”如果面包车运送乘客,它得到奖励“b”,但在“低电量”状态下,运送乘客有可能会完全耗尽电池,面包车需要救援,导致奖励-10。图 2-9 显示了 MDP。

img/502835_1_En_2_Fig9_HTML.jpg

图 2-9

电动货车:具有两种状态的连续马尔可夫决策过程。每个状态有三个动作:空闲、摆渡或充电

在“低”状态下,货车可以从一组可能的动作{再充电、空闲、摆渡}中采取一个动作。在“High”状态下,面包车可以从{idle,ferry}采取一个动作。所有其他跃迁为零。有了前面给出的 p(s ',r | s,a ),就可以计算 p(s' | s,a)。在这种情况下,它与 p(s ',r | s,a)相同。这是因为从(s,a)到(s’)的每一次转变只有一个固定的回报。换句话说,奖励没有不确定性或概率分布。每当处于状态 s 的代理采取行动 a 并发现自己处于下一个状态s’时,奖励是相同的固定值。前面的设置是实际问题中最常见的设置之一。然而,从理论上讲,在大多数情况下,奖励可能是一种概率分布,如“摆渡”行动奖励与货车运送的乘客数量相关联。

我们将在本章的以下部分继续使用这个例子。

政策和价值函数

如前所述,MDP 有状态,代理可以采取措施将代理从当前状态转换到下一个状态。此外,代理人以“奖励”的形式从环境中获得反馈 MDP 的动力学定义为p(St=S'Rt=R|St—1=SA 我们也看到了“累计回报” G t ,是从时间 t 开始收到的所有奖励的总和,代理人对过渡动态没有控制。这超出了代理的控制范围。然而,代理可以控制决策,即在哪个状态下采取什么动作。

这正是代理试图基于系统的转换动态来学习的内容。代理这样做的目的是最大化每个状态 S tG t ,这可以在多次运行中平均预期。状态到动作的映射被称为策略。其正式定义如下:

$$ \uppi \left({A}_t=a\ |\ {S}_t=s\right) $$

(2.8)

策略被定义为当代理在时间 t 处于状态 s 时,在时间 t 采取行动 a 的概率。代理试图学习从状态到行动的映射函数,以最大化总回报。

策略有两种类型。参见图 2-10 。第一类策略是随机策略,其中π( a | s )是概率函数。对于一个给定的状态,代理可以采取多种行动,采取每种行动的概率由 π ( a | s 定义。第二种类型是确定性策略,其中对于给定的状态只有一个唯一的动作。换句话说,概率函数π(At=A|St=S)就变成了一个简单的映射函数,对于某个动作At=【1】

img/502835_1_En_2_Fig10_HTML.jpg

图 2-10

政策类型。(a)随机政策,其中代理可以根据概率分布采取多种行动中的一种。(b)确定性策略,其中代理学习只采取一个最优的行动

代理人在状态 S t 时在时间 t 所能获得的累积奖励Gt(即回报)依赖于状态 S t ,策略代理人如下。它被称为状态值函数G t 的值取决于代理在时间 t 之后将看到的状态的轨迹,这又取决于代理将遵循的策略。因此,值函数总是在代理遵循的策略的上下文中定义。它也被称为代理人的行为。其正式定义如下:

$$ {v}_{\pi }(s)={E}_{\pi}\left[\ {G}_t\ \right|\ {S}_t=s\ \Big] $$

(2.9)

让我们试着稍微分解一下。vπ(s)指定代理在遵循策略π时状态 s 的“状态值”。 E π 【】表示方括号内的值是多个样本的平均值。虽然它在数学上被称为策略π下方括号内表达式的期望值,但实际上我们通常使用模拟来计算这些值。我们通过多次迭代进行计算,然后取平均值。根据统计学的一个基本定律,在一般情况下,平均值收敛于期望值。这个概念在计算中被广泛使用,并被命名为蒙特卡罗模拟。到了最后一部分,方括号内的表达式是gtst=s,即代理人在 t 时刻处于状态 s 的行为下,在 t 时刻可以得到的多次运行的平均收益 G t

至此,我们来介绍一下术语备份图。它示出了从代理处于状态 S t 的时间 t 到代理在时间 t+1 可以发现自己处于其后继状态的路径。这取决于代理人在 t 时刻采取的动作,即π(At=A|St=S)。再进一步,还要看环境/车型过渡函数 Pr {St+1=SRt+1=R|St=S 它将代理带到状态 S t+1 ,基于状态 S t 和它采取的动作 A t 奖励 R t+1 。 形象地说,从当前状态到可能的后续状态的一步转换称为备份图,看起来如图 2-11 所示。

img/502835_1_En_2_Fig11_HTML.jpg

图 2-11

备份图表从状态开始,采取行动。空心圆圈表示状态,黑圆圈表示动作

我们将广泛使用备用图,尤其是在下一节讨论贝尔曼方程时。备份图有助于对等式进行概念化和推理,以及为各种学习算法提供证明。它们也有助于推理培训代理所需的数据收集。

另一个与价值函数相关的概念是行动价值函数的概念。价值函数是代理人根据策略 π 采取行动时获得的预期累积报酬。然而,假设代理可以在这个第一时间步 t 自由地采取任何行动,条件是它必须在所有随后的时间步 t+1 上遵循策略 π 。代理人在 t 时刻得到的期望收益现在称为行动价值函数qπ(sa )。正式定义如下:

$$ {q}_{\pi}\left(s,a\right)={E}_{\pi }\ \left[\ {G}_t\ \right|\ {S}_t=s,{A}_t=a\ \Big] $$

(2.10)

v(状态值)和 q 值(状态动作值)之间有一个简单而微妙的关系,这将在下一节中详细探讨。

这基本上完成了 MDP 各种组件的定义。在下一节中,我们将探讨 t 时刻状态/状态动作的 v 和 q 值与 t+1 时刻后续值之间的递归关系。几乎所有的学习算法都利用了这种递归关系。

贝尔曼方程

我们再来看看( 2.9 ),它定义了价值函数。再来看( 2.3 )中定义的Gt的定义;两者均转载于此:

$$ {G}_t={R}_{t+1}+\upgamma {R}_{t+2}+{\upgamma}²{R}_{t+3}+\dots .+{\upgamma}^{T-t+1}{R}_T $$

(2.11)

$$ {v}_{\pi }(s)={E}_{\pi}\left[\ {G}_t\ \right|\ {S}_t=s\ \Big] $$

(2.12)

换句话说,一个状态的值是在该状态下的代理人 s 遵循一个策略 π 时累积回报的期望值/平均值。主体发现自己所处的状态和它从环境中获得的回报取决于它遵循的策略,即它在给定状态下采取的行动。有一个递归关系,其中用于 G t 的表达式可以用Gt+1来表示。

$$ {G}_t={R}_{t+1}+\upgamma \left[{R}_{t+2}+\upgamma {R}_{t+3}+{\upgamma}²{R}_{t+4}+\dots .+{\upgamma}^{T-t+1-1}{R}_T\right] $$

(2.13)

让我们专注于方括号内的表达式。

$$ {R}_{t+2}+\upgamma {R}_{t+3}+{\upgamma}²{R}_{t+4}+\dots .+{\upgamma}^{T-t+1-1}{R}_T $$

接下来,我们将变量从 t 更改为 t ' ,其中t'=t+1。前面的表达式可以重写如下:

$$ {R}_{t\prime +1}+\upgamma {R}_{t\prime +2}+{\upgamma}²{R}_{t\prime +3}+\dots .+{\upgamma}^{T-t\prime +1}{R}_T $$

(2.14)

比较( 2.14 )与( 2.11 )中给出的Gt的表达式,我们看到

$$ {R}_{t\prime +1}+\upgamma {R}_{t\prime +2}+{\upgamma}²{R}_{t\prime +3}+\dots .+{\upgamma}^{T-t\prime +1}{R}_T={G}_{t\prime }={G}_{t+1} $$

(2.15)

接下来,我们将( 2.15 )代入( 2.13 ),得到这个:

$$ {G}_t={R}_{t+1}+\upgamma {G}_{t+1} $$

(2.16)

我们现在可以将表达式中的 G t 的递归定义替换为( 2.12 )中的vπ(s)以得到:

$$ {v}_{\pi }(s)={E}_{\pi}\left[\ {R}_{t+1}+\gamma\ {G}_{t+1}\ \right|\ {S}_t=s\ \Big] $$

(2.17)

期望 E π 是在状态St=S以及环境将代理转换到由转换函数p(Sr 定义的所有新状态下,代理可以采取的所有可能动作 a 期望的扩展形式导致vπ(s)的修正表达式如下:

$$ {v}_{\pi }(s)=\sum \limits_a\pi \left(a|s\right)\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_{\pi}\left({s}^{\prime}\right)\right] $$

(2.18)

解释这个等式的方式是, s 的状态值是后续状态s’的所有回报和状态值的平均值。平均是基于在状态 s 中采取动作 a 的策略 π ( a | s ),接着是代理转移的环境转移概率p(sr | sa )( 2.18 )中的等式显示了将当前状态的状态值与后续状态的状态值相链接的递归性质。

动作值函数也存在类似的关系。让我们从( 2.10 )开始,走一遍 q 值之间递归关系的推导。

$$ {q}_{\pi}\left(s,a\right)={E}_{\pi }\ \left[\ {G}_t\ \right|\ {S}_t=s,{A}_t=a\ \Big] $$

$$ =\kern0.5em {E}_{\pi }\ \left[\ {R}_t+\gamma\ {G}_{t+1}\ \right|\ {S}_t=s,{A}_t=a\ \Big] $$

通过对所有可能性求和来扩展期望值,我们得到这个:

$$ {q}_{\pi}\left(s,a\right)=\sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_{\pi}\left({s}^{\prime}\right)\right] $$

(2.19)

我们现在来看看vπ(s)和qπ(sa )的关系。它们通过代理所遵循的策略π( s | a )相关联。q 值是元组(s,a)的值,状态值是状态(s)的值。该策略通过概率分布将状态与一组可能的动作联系起来。结合关系中的这些结果,我们得到这样的结果:

$$ {v}_{\pi }(s)=\sum \limits_a\pi \left(a\ \right|s\Big).{q}_{\pi}\left(s,a\right) $$

(2.20)

前面的关系也可以从方程( 2.18 )和( 2.19 )合起来推导出来,其中我们根据方程( 2.19 )用qπ(sa )替换( 2.18 中的部分右边表达式。

就像等式( 2.18 )给出了一个关于下一个状态的状态值的递归关系vπ(s)vπ(s),我们也可以表示为 q π ain(2.19)就对子而言qπ(sa)。 这是通过将( 2.1 )中的vπ(s)替换为( 2.20 )中的表达式来实现的。这种操作为我们提供了 q 值之间的递归关系。

$$ {q}_{\pi}\left(s,a\right)=\sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ \sum \limits_{a^{\prime }}\pi \left({a}^{\prime }|{s}{\prime}\right) {q}_{\pi}\left({s}{\prime },{a}^{\prime}\right)\right] $$

(2.21)

将当前状态值或 q 值与连续值联系起来的方程( 2.18 )和( 2.21 )可以通过备份图来表示。我们现在扩展图 2-12 中的后图,以涵盖前两种情况。该图遵循标准惯例:状态 s 显示为空心圆圈,表示 a 的动作节点显示为实心黑色圆圈。

img/502835_1_En_2_Fig12_HTML.jpg

图 2-12

状态值和动作值的备份图。空心圆表示状态,实心圆表示动作

方程( 2.18 为vπT7(s)的贝尔曼方程,方程( 2.21 为qπ(sa )的贝尔曼方程。这些方程在类似强化学习的顺序决策设置中形式化了递归关系。强化学习中的所有算法都基于这两个方程的变体,目的是在不同的假设和近似下最大化价值函数。当我们讨论这些算法时,我们将继续强调方程中在某些假设下被近似的部分,以及各种方法的优缺点。

作为强化学习的实践者,你的大部分专业知识将首先围绕将现实生活中的问题公式化为 RL 设置,其次基于约束和假设选择正确的算法集。因此,本书的重点将放在使一种技术发挥最佳作用所需的条件和假设上,选择使用一种特定的技术是有意义的,以及对于一个给定的问题可用的竞争选择的利弊。我们将给出数学方程来形式化这种关系,但核心焦点将是帮助你对正在发生的事情以及给定的方法/算法何时有意义有一个直观的感觉。

最优性贝尔曼方程

解决强化学习问题意味着找到(学习)最大化状态值函数的策略。假设你有一套政策。目标是选择最大化状态值 v π (s)的策略。状态的最佳值函数被定义为 v ӿ (s)。最佳状态值的关系可以表述如下:

$$ {v}_{\ast }(s)={\mathit{\max}}_{\pi }\ {v}_{\pi }(s) $$

(2.22)

前面的等式表明,最佳状态值是在所有可能的策略π上可以获得的最大状态值。假设这个最优策略用策略的上标(*)表示,即π。如果一个代理遵循最优策略,那么处于(s)状态的代理将采取行动 a,这使得在最优策略下获得的 q(s,a)最大化。换句话说,等式( 2.20 )从期望值修改为最大值,如下所示:

$$ {v}_{\ast }(s)={\mathit{\max}}_a\ {q}_{\pi^{\ast }}\left(s,a\right) $$

(2.23)

此外,类似于方程( 2.18 )和( 2.22 ),最佳状态和动作值函数的递归形式给出如下:

$$ {v}_{\ast }(s)={\mathit{\max}}_a\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_{\ast}\left({s}^{\prime}\right)\right] $$

(2.24)

$$ {q}_{\ast}\left(s,a\right)=\sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {\mathit{\max}}_{a\prime }\ {q}_{\ast}\left({s}^{\prime },{a}^{\prime}\right)\right] $$

(2.25)

这些最佳等式可以用图 2-13 所示的备份图来表示,突出显示当前值和后续值之间的递归关系。

img/502835_1_En_2_Fig13_HTML.jpg

图 2-13

最佳状态值和动作值的备份图。策略π被“最大”操作所取代

如果你有所有的 v* 值,那么就很容易找到最优策略。我们使用如图 2-12 所示的一步备份图来寻找产生最优值 v* 的 a* 。可以看作是一步到位的搜索。如果给我们一个最优 q*(s,a),找到最优策略就更容易了。在状态 s 下,我们只要选择动作 a ,它的 q 值最高。这从等式( 2.23 )可以明显看出。让我们将这些概念应用于图 2-9 中介绍的电动厢式车的问题,该问题在图 2-14 中重现。

img/502835_1_En_2_Fig14_HTML.jpg

图 2-14

电动货车:具有两种状态的连续马尔可夫决策过程。每个状态有三个动作:空闲、摆渡或充电。图 2-9 的再现

让我们画出描述各种值的表格,如表 2-1 所示。在这个表中我们列出了 ( sas'r )的所有可能的组合以及概率p(s'r | sa )。

表 2-1

图 2-14 中 MDP 的系统动力学

|

国家

|

行动(a)

|

新状态

|

奖励(r)

|

P(s ',r | s,a)

|
| --- | --- | --- | --- | --- |
| 低电量 | 闲置的 | 低电量 | a | One |
| 低电量 | 费用 | 高电量电池 | Zero | One |
| 低电量 | 渡船 | 低电量 | b | 一个 |
| 低电量 | 渡船 | 高电量电池 | -10 | 1- a |
| 高电量电池 | 闲置的 | 高电量电池 | a | One |
| 高电量电池 | 渡船 | 高电量电池 | b | B |
| 高电量电池 | 渡船 | 低电量 | b | 1-b |

让我们使用表 2-1 使用等式( 2.24 )计算最佳状态值。

$$ {v}_{\ast}\left(\mathrm{low}\right)=\mathit{\max}\ \left{\begin{array}{c}a+\gamma\ {v}_{\ast }(low)\ {}\gamma\ {v}_{\ast }(high)\ {}\alpha \left[b+\gamma {v}_{\ast }(low)\right]+\left(1-\alpha \right).\left[-10+\gamma\ {v}_{\ast }(high)\right]\end{array}\right. $$

$$ {v}_{\ast }(high)=\max \left{\begin{array}{c}a+\gamma\ {v}_{\ast }(high)\ {}\beta\ \left[b+\gamma\ {v}_{\ast }(high)\right]+\left(1-\beta \right).\left[b+\gamma\ {v}_{\ast }(low)\right]\end{array}\right. $$

对于给定的 a、b、α、β、γ值,将有一组唯一的值v】∫()和v()满足前面的等式。然而,方程的显式求解只适用于简单的玩具问题。现实生活中更大的问题需要使用其他可扩展的方法。这正是我们在本书的其余部分将要研究的内容:求解贝尔曼方程的各种方法和途径,以得到一个最优策略,在给定的环境中,代理人应该遵循这个策略。

使用思维导图的解决方法的类型

在强化学习设置和贝尔曼方程的背景下,是时候看看强化学习世界中算法的前景了。来自 OpenAI 的图 2-15 显示了 RL 空间中各种类型的学习算法的高级景观。

img/502835_1_En_2_Fig15_HTML.jpg

图 2-15

强化学习算法的思维导图。这是一个仅显示宽泛分类的高级图(参见 spinningup)。openai。com/en/latest/spinning up/rl _ intro 2。html

如在贝尔曼方程中所见,系统跃迁动力学p(sr | sa )形成中心部分。转变动力学描述了环境的模型。然而,跃迁动力学并不总是已知的。因此,学习算法的第一个广泛分类可以基于模型的知识(或缺乏模型的知识)来完成,即基于模型和无模型算法的分类。

基于模型的算法可以进一步分为两类:一类是给我们模型,例如围棋或象棋,第二类是代理需要探索和学习模型。“学习模型”下的一些流行方法是世界模型、想象力增强代理(I2A)、基于模型的 RL 和无模型微调(MBMF)以及基于模型的价值探索(MBVE)。

回到无模型设置,我们注意到贝尔曼方程为我们提供了一种找到状态/动作值并使用它们找到最优策略的方法。这些算法大多采用迭代改进的方法。最终,我们希望代理有一个最优的策略,算法使用贝尔曼方程来评估策略的好坏,并引导向正确的方向改进。然而,还有另一种方法。为什么不直接改善政策,而不是通过价值观间接影响政策?这种直接策略改进的方法被称为策略优化

回到无模型世界的范畴,Q 学习形成了无模型贝尔曼驱动的状态/动作值优化的主要部分。这种方法下的流行变体是深度 Q 网络(DQN)以及 DQN 的各种次要变体,如分类 51 原子 DQN (C51)、分位数回归 DQN (QR-DQN)和后见之明经验重放(HER)。

直接遵循策略优化路径的算法是策略梯度、行动者批评及其变体(A2C/A3C)、邻近策略优化(PPO)和信赖域策略优化(TRPO)。

最后,有一组算法介于 Q 学习和策略优化之间。这一类别中最受欢迎的是深度确定性政策梯度(DDPG)、孪生延迟 DDPG (TD3)和软行动者-批评家(SAC)。

这些分类只是为了帮助你理解不同的方法和流行的算法。然而,清单和分类并不详尽。强化学习领域正在迅速发展,新的方法也在不断增加。请仅将之前的思维导图用作高级指导。

摘要

本章介绍了什么是强化学习、设置和各种定义。然后讨论了贝尔曼方程和最优化方程,以及状态/动作值函数和备份图。本章最后对算法的前景进行了展望。

接下来的章节将从下一章的动态编程开始,更深入地研究这些算法。

三、基于模型的算法

在第二章中,我们讨论了构成代理的设置部分和构成环境的部分。代理获得状态St=S,并学习将状态映射到动作的策略π( s | a )。当处于状态St=S时,代理使用该策略采取动作At=A。系统转换到下一个时刻 t + 1。环境通过将代理置于新的状态St+1=S'并以奖励的形式向代理提供反馈 R t +来响应动作(At)代理人无法控制新状态St+1和奖励Rt+1会是什么样。这个过渡从(St=SAt=A)→(Rt+1=R这被称为跃迁动力学。对于给定的一对( sa ),可能有一对或多对( rs ' )。在一个确定性的世界里,我们会有一对( rs' )的固定组合( sa )。然而,在随机环境中,即具有不确定结果的环境中,对于给定的( sa ),我们可能有许多对( rs' )。

在这一章中,我们将重点介绍跃迁动力学 Pr {St+1=SRt+1=R|St=S,代理将使用这些知识来“规划”一个策略,使状态值 v π (s)的累积回报最大化。所有这些算法都将基于动态规划,它允许我们将问题分解成更小的子问题,并使用第二章中解释的贝尔曼方程的递归关系。在此过程中,您将了解如何在一般意义上改进政策的其他概念。

然而,在我们深入研究算法之前,我们将绕一小段路来研究本章将要用到的 RL 环境。一旦我们解决了这个问题,我们将把重点放在基于模型的算法上。

开放 AI 健身房

编码练习基于上一章简单介绍的 OpenAI Gym 环境。Gym 是 OpenAI 开发的一个库,用于比较强化学习(RL)算法。它提供了一套标准化的环境,可用于开发和比较各种 RL 算法。所有这些环境都有一个共享的接口,允许我们编写通用算法。

Gym 的安装很简单,在第一章的设置部分已经解释过了。在第二章中,我们介绍了学习强化学习时使用的两种流行环境:MountainCar-v0CartPole-v1。在这一章中,我们将使用一个更简单的环境来讨论动态编程。它是一个 4x4 的网格,如图 3-1 所示。左上和右下位置是终端状态,如图中阴影单元所示。在给定的单元中,代理可以向四个方向移动:UPRIGHTDOWNLEFT。除非有一堵墙,否则这些动作确定性地在动作的方向上移动代理。在碰壁的情况下,代理停留在当前位置。代理在每个时间步长获得-1 的奖励,直到它到达终止状态。

img/502835_1_En_3_Fig1_HTML.png

图 3-1

网格世界环境。它是一个 4×4 的网格,终端状态位于左上角和右下角。网格中的数字代表状态 S

健身房图书馆不提供这种环境。我们将在 OpenAI 健身房创建一个自定义环境。虽然如果我们想要向外界发布环境,需要遵循一个文档化的( https://github.com/openai/gym/blob/master/docs/creating-environments.md )文件结构,但是我们将遵循一个更简单的单文件结构来定义网格世界环境,因为它仅供我们个人使用。一个环境必须实现以下功能:step(action)reset()render()

我们将采用扩展 Gym 中提供的模板环境之一的方法:DiscreteEnv。它已经实现了stepreset功能。我们只需要提供nA(每个状态中的动作数)nS(状态总数),以及一个字典P,其中P[s][a]给出了一个带有值(probability, next_state, reward, done)的元组列表。换句话说,它提供了过渡动力。换句话说,对于给定的状态 s 和动作 a,它给出了由可能的下一个状态 s '、奖励 r 和概率p(s'r | sa )组成的元组列表。元组中的第四个值是布尔标志done,指示下一个状态 s’是终止状态还是非终止状态。

在基于模型的学习的当前设置下,过渡动态P是已知的,这是本章的重点。然而,P不应该直接用于无模型算法,即在没有模型知识的情况下学习的算法(过渡动态)。我们将在后续章节中研究无模型算法。清单 3-1 显示了脚本文件gridworld.py

import numpy as np
import sys
from gym.envs.toy_text import discrete
from contextlib import closing
from io import StringIO

# define the actions
UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3

class GridworldEnv(discrete.DiscreteEnv):
    """
    A 4x4 Grid World environment from Sutton's Reinforcement
    Learning book chapter 4\. Termial states are top left and
    the bottom right corner.

    Actions are (UP=0, RIGHT=1, DOWN=2, LEFT=3).
    Actions going off the edge leave agent in current state.
    Reward of -1 at each step until agent reachs a terminal state.
    """

    metadata = {'render.modes': ['human', 'ansi']}

    def __init__(self):
        self.shape = (4, 4)
        self.nS = np.prod(self.shape)
        self.nA = 4

        P = {}
        for s in range(self.nS):
            position = np.unravel_index(s, self.shape)
            P[s] = {a: [] for a in range(self.nA)}
            P[s][UP] = self._transition_prob(position, [-1, 0])
            P[s][RIGHT] = self._transition_prob(position, [0, 1])
            P[s][DOWN] = self._transition_prob(position, [1, 0])
            P[s][LEFT] = self._transition_prob(position, [0, -1])

        # Initial state distribution is uniform

        isd = np.ones(self.nS) / self.nS

        # We expose the model of the environment for dynamic programming
        # This should not be used in any model-free learning algorithm
        self.P = P

        super(GridworldEnv, self).__init__(self.nS, self.nA, P, isd)

    def _limit_coordinates(self, coord):
        """
        Prevent the agent from falling out of the grid world
        :param coord:
        :return:
        """
        coord[0] = min(coord[0], self.shape[0] - 1)
        coord[0] = max(coord[0], 0)
        coord[1] = min(coord[1], self.shape[1] - 1)
        coord[1] = max(coord[1], 0)
        return coord

    def _transition_prob(self, current, delta):
        """
        Model Transitions. Prob is always 1.0.
        :param current: Current position on the grid as (row, col)
        :param delta: Change in position for transition
        :return: [(1.0, new_state, reward, done)]
        """

        # if stuck in terminal state
        current_state = np.ravel_multi_index(tuple(current), self.shape)
        if current_state == 0 or current_state == self.nS - 1:
            return [(1.0, current_state, 0, True)]

        new_position = np.array(current) + np.array(delta)
        new_position = self._limit_coordinates(new_position).astype(int)
        new_state = np.ravel_multi_index(tuple(new_position), self.shape)

        is_done = new_state == 0 or new_state == self.nS - 1

        return [(1.0, new_state, -1, is_done)]

    def render(self, mode="human"):
        outfile = StringIO() if mode == 'ansi' else sys.stdout

        for s in range(self.nS):
            position = np.unravel_index(s, self.shape)
            if self.s == s:
                output = " x "
            # Print terminal state
            elif s == 0 or s == self.nS - 1:
                output = " T "
            else:
                output = " o "

            if position[1] == 0:
                output = output.lstrip()
            if position[1] == self.shape[1] - 1:
                output = output.rstrip()
                output += '\n'

            outfile.write(output)
        outfile.write('\n')

        # No need to return anything for human
        if mode != 'human':
            with closing(outfile):
                return outfile.getvalue()

Listing 3-1Grid World Environment

GridworldEnv是通过扩展健身房库中提供的模板环境discrete.DiscreteEnv创建的。在__init__(self)中,我们根据图 3-1 中描述的动态定义nA, nS和过渡函数P。清单 3-1 完整地描述了将在本章剩余部分使用的定制健身房环境。

现在让我们把重点放在无模型算法上,这也是我们打算在本章“动态编程”一节中学习的内容。

动态规划

动态规划是 20 世纪 50 年代由理查德·贝尔曼提出的一种优化技术。它是指将一个复杂的问题分解成更简单的子问题,寻找子问题的最优解,然后将子问题最优解组合起来,得到原问题的最优解。我们来看看贝尔曼方程( 2。18 )表示一个状态的值vπ(s)就政策而言( a | s ),系统动力学p(sr | sa

??

我们是用其他状态值vπ(s)来表示值vπ(s),这些都是未知的。如果我们能够以某种方式获得当前状态的所有后续状态值,我们将能够计算出vπ(s)。这显示了方程的递归性质。

我们还注意到,当 s’是某个状态 s 的后继状态时,将会多次需要特定的值vπ(s)。由于这一性质,我们可以缓存(即存储)值vπ(s)并多次使用它来避免

动态规划是一种广泛用于各类问题的优化技术,它允许将复杂的问题分解成较小的问题。一些常见的应用是调度算法、最短路径之类的图形算法、维特比算法之类的图形模型以及生物信息学中的网格模型。因为这本书是关于强化学习的,我们将限制动态规划在求解贝尔曼期望和贝尔曼最优方程中的应用,包括价值函数和行动-价值函数。这些方程在( 2 中给出。18 ),( 2。21 ),( 2。24 ),以及( 2。25 ),并将其复制以备参考。

下面是价值函数的贝尔曼期望方程:

$$ {v}_{\pi }(s)=\sum \limits_a\pi \left(a|s\right)\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_{\pi}\left({s}^{\prime}\right)\right] $$

(3.1)

下面是行为-价值函数的贝尔曼期望方程:

$$ {q}_{\pi}\left(s,a\right)=\sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ \sum \limits_{a^{\prime }}\pi \left({a}^{\prime }|{s}{\prime}\right) q\left({s}{\prime },{a}^{\prime}\right)\right] $$

(3.2)

下面是价值函数的贝尔曼最优性方程:

$$ {v}_{\ast }(s)={\mathit{\max}}_a\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_{\ast}\left({s}^{\prime}\right)\right] $$

(3.3)

下面是作用值函数的贝尔曼最优方程:

$$ {q}_{\ast}\left(s,a\right)=\sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {\mathit{\max}}_{a^{\prime }}\ {q}_{\ast}\left({s}^{\prime },{a}^{\prime}\right)\right] $$

(3.4)

这四个方程中的每一个都根据满足动态编程的递归性质的后继状态或状态动作的值来表示状态或状态动作对的 v 或 q 值。在下面的章节中,我们将首先使用期望方程来评估一项政策,这被称为评估预测。然后,我们将利用最优性方程来寻找最大化状态值和状态动作值的最优策略。接下来是一个关于通用设置的部分,这是一个广泛用于政策改进的通用框架。我们将通过谈论大规模问题设置中的实际挑战和在这种情况下优化动态规划的各种方法来结束这一章。

这一章将主要集中在这样一类问题上,在这类问题中,我们有一个有限的状态集,代理可以发现自己所处的状态,以及在每个状态中的有限的动作集。具有连续状态和连续动作的问题在技术上可以通过首先离散化状态和动作使用动态编程来解决。你可以在第四章的末尾看到这种方法的例子。它也将构成第五章的主要部分。

政策评估/预测

我们现在将利用方程( 3.1 )利用其迭代性质和动态编程的概念来导出状态值。等式( 3.1 )表示根据其后继状态的状态?? sT7 的状态值。状态的值还取决于代理遵循的策略,该策略被定义为策略π(a|s)。由于值对策略的这种依赖性,所有状态值都标有π,以表示( 3.1 )中的状态值是通过遵循特定策略π(a|s获得的值。请注意,改变策略π会产生一组不同的值vπ(s)和qπ(sa )。

( 3.1 中的关系可以用备份图图形化表示,如图 3-2 。

img/502835_1_En_3_Fig2_HTML.jpg

图 3-2

状态值函数的贝尔曼期望方程的备份图。空心圆圈表示状态,黑圆圈表示动作

代理开始于状态 s 。它根据其当前策略π(a|s)将 a 作为动作。环境根据系统动力学p(s'r | sa )将智能体转换到一个新的状态 s ' 连同奖励 r 。如你所见,方程( 3.1 )是一个方程组,每个状态一个方程。如果有∣ S ∣国家,我们就会有∣ S ∣这样的方程。等式的数量等于∣ S ∣,并且与未知 v(s)的数量相同,每个 vs=s一个。因此,( 3.1 )代表具有|S|个未知数的∣ S ∣方程组。我们可以用任何线性规划技术来解这个方程组。然而,它将涉及矩阵的求逆,因此对于现实生活中的大多数 RL 问题来说不是很实用。

相反,我们将求助于迭代解的使用。这是通过在第一次迭代 k = 0 时从一些随机状态值 v 0 ( s )开始,并在( 3.1 )的右侧使用它们来获得下一次迭代步骤的状态值来实现的。

$$ {v}_{k+1}(s)\leftarrow \sum \limits_a\pi \left(a|s\right)\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_k\left({s}^{\prime}\right)\right] $$

(3.6)

注意下标从π到( k )和( k + 1)的变化。还要注意等号(=)到赋值(←)的变化。我们现在根据先前迭代 k 中的状态值来表示迭代( k + 1)中的状态值,并且在每次迭代中将有∣ S ∣(状态总数)这样的更新。可以看出,随着 k 增大并趋于无穷大(∞), V k 会收敛到 V π 。先前为给定策略寻找所有状态值的方法被称为策略评估。我们从在 k = 0 时任意选择的值 V 0 开始,并使用等式( 3.6 )迭代状态值,直到状态值 V k 停止变化。策略评估的另一个名称是预测,即预测给定策略的状态值。

通常,在每次迭代中,我们创建一个现有状态值 v 的新副本,并根据前一个数组中所有状态的值更新新数组中的所有值。我们为状态值维护两组数组, V kV k + 1 。这被称为同步更新,即基于来自先前迭代的状态值更新所有状态值。但是,还有一种替代方法。人们可以只维护一个状态值数组,并在适当的位置进行更新,其中每个新值立即覆盖旧值。如果每个状态都更新了足够的次数,就地更新有助于加快收敛。这种就地更新被称为异步更新。在本章的后面,我们有一节专门介绍各种类型的就地更新。

图 3-3 给出了迭代策略评估的伪代码。

ITERATIVE POLICY EVALUATION

img/502835_1_En_3_Fig3_HTML.png

图 3-3

策略评估算法

现在让我们将前面的算法应用到图 3-1 中给出的网格世界。我们将假设一个随机策略π( a | s ),其中四个动作(UPRIGHTDOWNLEFT)的每一个都有 0.25 的相等概率。清单 3-2 显示了应用于网格世界的策略评估代码。这是来自listing3_2.ipynb的文件。

Note

本书中的代码清单将只显示讨论上下文中的相关代码。请查看 Python 脚本文件和/或 Python 笔记本以了解完整的实现。

def policy_eval(policy, env, discount_factor=1.0, theta=0.00001):
    """
    Evaluate a policy given an environment and
    a full description of the environment's dynamics.

    Args:
        policy: [S, A] shaped matrix representing the policy. Random in our case
        env: OpenAI env. env.P -> transition dynamics of the environment.
            env.P[s][a] [(prob, next_state, reward, done)].
            env.nS is number of states in the environment.
            env.nA is number of actions in the environment.
        theta: Stop evaluation once value function change is
            less than theta for all states.
        discount_factor: Gamma discount factor.

    Returns:
        Vector of length env.nS representing the value function.
    """
    # Start with a (all 0) value function
    V = np.zeros(env.nS)
    V_new = np.copy(V)
    while True:
        delta = 0
        # For each state, perform a "backup"
        for s in range(env.nS):
            v = 0
            # Look at the possible next actions
            for a, pi_a in enumerate(policy[s]):
                # For each action, look at the possible next states...
                for prob, next_state, reward, done in env.P[s][a]:
                    # Calculate the expected value as per backup diagram

                    v += pi_a * prob * \
                        (reward + discount_factor * V[next_state])
            # How much our value function changed (across any states)
            V_new[s] = v
            delta = max(delta, np.abs(V_new[s] - V[s]))
        V = np.copy(V_new)
        # Stop if change is below a threshold
        if delta < theta:
            break
    return np.array(V)

Listing 3-2Policy Evaluation/Policy Planning: listing3_2.ipynb

当我们用遵循随机策略的代理运行网格世界的代码时,我们看到图 3-4 中给出的每个网格单元的状态值v【π(s)。

img/502835_1_En_3_Fig4_HTML.jpg

图 3-4

策略评估vπ(s)对于图 3-1 中的网格世界,代理遵循随机策略。四个动作UPDOWNLEFTRIGHT中的每一个都有 0.25 的相等概率

我们可以看到价值观已经趋同。我们来看看最后一列的第三行,状态值为vπ(s)= 14。在这种状态下,动作UP将代理带到状态值为-20 的单元,动作LEFT将代理带到状态值为-18 的单元,动作DOWN将代理带到值为 0 的终止状态,动作RIGHT碰壁,使代理处于相同的状态。让我们应用方程式( 3.1 )。我们将展开等式的右侧( 3.1 ),按顺序应用动作— TOPRIGHTDOWNLEFT:

-14 = 0.25(-1+(-20)) + 0.25(-1+(-14)) + 0.25(-1+0) + 0.25(-1+(-18))

-14 = -14

两边的值是匹配的,这证实了收敛。因此,图 3-4 所示的值是代理遵循随机策略时的状态值。请注意,我们已经考虑了γ = 1.0 的贴现因子。

了解了策略评估之后,在下一节中,我们将讨论如何针对给定的环境改进策略。

政策改进和迭代

上一节展示了如何迭代获取给定策略的状态值 v π ( s )。我们可以利用这些信息来改进政策。在我们的网格世界中,我们可以从任何状态采取四种行动。现在,我们不再遵循随机的策略π( a | s ),而是着眼于分别采取所有四个行动,然后在这一步之后遵循策略π的价值。这样就会给我们四个值 q ( sa )动作值,分别是在网格世界中采取四种可能动作中的每一种的动作值。

$$ q\left(s,a\right)=\sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_{\pi}\left({s}^{\prime}\right)\right] $$

注意 q ( sa )没有π的下标。我们正在评估 q ( sa )在状态 S = s 时所有可能的动作。如果任一个 q ( sa )大于当前状态值vπ(S),则意味着当前策略π( a | s )没有采取最优行动,我们可以在当前状态下对策略S=采取 q 值最大化动作 A = a 并将其定义为状态 S = s 中的策略,将给出比当前策略π( a | s )更高的状态值。换句话说,我们定义如下:

$$ {\pi}^{\prime}\left(a|s\right)=\underset{a}{argmax}\ q\left(s,a\right) $$

(3.7)

由于一个叫做策略改进定理的一般结果(我们在这里不详细讨论),在新策略π ' 下所有状态的值将等于或大于策略π下的状态值。换句话说,在特定状态下选择一个最大化动作 S = s 虽然提高了那个状态的状态值,但不能降低其他状态的值。它既可以保持它们不变,也可以改善那些依赖于 S = s 的其他状态。数学上,我们可以这样表达:

$$ {v}_{\pi}^{\prime }(s)\ge {v}_{\pi }(s)\kern0.75em s\ \mathit{\text{\EUR}}\ S $$

最大化的先前最大化步骤(贪婪步骤)可以基于它们当前的 q 值应用于所有状态。这种跨所有状态的最大化动作的扩展被称为贪婪策略,递归状态值关系由贝尔曼最优方程( 3.4 给出)。

我们现在有了一个改进政策的框架。对于给定的 MDP,我们首先迭代地进行策略评估,以获得状态值 v ( s ),然后我们根据( 3.7 )应用最大化 q 值的动作的贪婪选择。这导致状态值与贝尔曼方程不同步,因为最大化步骤被应用于每个单独的状态,而没有流过所有的后续状态。因此,我们再次在新策略π ' 下进行策略迭代,以找到改进策略下的状态/动作值。一旦获得状态值,可以再次应用最大化动作来进一步将策略改进为π”。如此循环下去,直到观察不到进一步的改善。这个动作顺序可以描述如下:

$$ {\pi}_0\overset{evaluate}{\to }\ {v}_{\pi_0}\overset{improve}{\to }\ {\pi}_1\overset{evaluate}{\to }{v}_{\pi_1}\overset{improve}{\to }{\pi}_2\dots ..\overset{improve}{\to }\ {\pi}_{\ast}\overset{evaluate}{\to }{v}_{\ast } $$

从政策改进定理我们知道,贪婪改进和政策评估的每一次刷卡,$$ {v}_{\pi}\overset{improve}{\to }\ {\pi}^{\prime}\overset{evaluate}{\to }{v}_{\pi_{\prime }} $$都给了我们一个优于上一个带$$ {v}_{\pi}^{\prime }(s)\ge {v}_{\pi }(s)\kern0.75em s\ \text{\EUR}\ S $$的政策。对于一个具有有限数量的离散状态和每个状态中有限数量的动作的 MDP,每次滑动都会导致一个改进,一旦我们停止观察状态值的任何进一步改进,就会找到一个最优策略。这在有限数量的改善周期内必然会发生。

这种寻找最优策略的方法被称为策略迭代。图 3-5 显示了策略迭代的伪代码。

POLICY ITERATION

img/502835_1_En_3_Fig5_HTML.png

图 3-5

有限 MDP 的策略迭代算法

让我们将前面的算法应用到图 3-1 中的网格世界。清单 3-3 显示了应用于网格世界的策略迭代的代码。完整代码见listing3_3.ipynb。功能policy_evaluation保持与清单 3-2 相同。有一个新函数policy_improvement,它应用贪婪最大化来返回一个对现有策略进行改进的策略。policy_iteration是一个循环运行policy_evaluationpolicy_improvement的函数,直到状态值停止增加并收敛到一个固定点。

# Policy Improvement

def policy_improvement(policy, V, env, discount_factor=1.0):
    """
    Improve a policy given an environment and a full description
    of the environment's dynamics and the state-values V.

    Args:
        policy: [S, A] shaped matrix representing the policy.
        V: current state-value for the given policy
        env: OpenAI env. env.P -> transition dynamics of the environment.
            env.P[s][a] [(prob, next_state, reward, done)].
            env.nS is number of states in the environment.
            env.nA is number of actions in the environment.
        discount_factor: Gamma discount factor.

    Returns:
        policy: [S, A] shaped matrix representing improved policy.
        policy_changed: boolean which has value of `True` if there
                        was a change in policy
    """

    def argmax_a(arr):
        """
        Return idxs of all max values in an array.
        """
        max_idx = []
        max_val = float('-inf')
        for idx, elem in enumerate(arr):
            if elem == max_val:
                max_idx.append(idx)
            elif elem > max_val:
                max_idx = [idx]
                max_val = elem
        return max_idx

    policy_changed = False

    Q = np.zeros([env.nS, env.nA])
    new_policy = np.zeros([env.nS, env.nA])

    # For each state, perform a "greedy improvement"
    for s in range(env.nS):
        old_action = np.array(policy[s])
        for a in range(env.nA):
            for prob, next_state, reward, done in env.P[s][a]:
                # Calculate the expected value as per backup diagram
                Q[s,a] += prob * (reward + discount_factor * V[next_state])

        # get maximizing actions and set new policy for state s
        best_actions = argmax_a(Q[s])
        new_policy[s, best_actions] = 1.0 / len(best_actions)

    if not np.allclose(new_policy[s], policy[s]):
        policy_changed = True

    return new_policy, policy_changed

# Policy Iteration
def policy_iteration(env, discount_factor=1.0, theta=0.00001):

    # initialize a random policy
    policy = np.ones([env.nS, env.nA]) / env.nA
    while True:
        V = policy_evaluation(policy, env, discount_factor, theta)
        policy, changed = policy_improvement(policy, V, env, discount_factor)
        if not changed: #terminate iteration once no improvement is observed
            V_optimal = policy_evaluation(policy, env, discount_factor, theta)
            print("Optimal Policy\n", policy)
            return np.array(V_optimal)

Listing 3-3Policy Iteration: listing3_3.ipynb

图 3-6 显示了在网格世界中运行policy_iteration后每个网格单元的状态值。

img/502835_1_En_3_Fig6_HTML.jpg

图 3-6

图 3-1 中网格世界的策略迭代v(s)。代理遵循通过应用清单 3-3 中的 policy_iteration 找到的最优策略

我们看到,最佳状态值是达到最接近的终端状态所需的步数的负数。由于在代理到达终端状态之前,每个时间步长的回报都是-1,所以最优策略会以最少的可能步长将代理带到终端状态。对于某些状态,一个以上的动作可能导致相同数量的步骤到达最终状态。例如,查看状态值= -3 的右上角状态,需要三个步骤才能到达左上角的终止状态或右下角的终止状态。换句话说,状态值是状态和最近的终端状态之间的曼哈顿距离的负值。

我们还可以提取最优策略,如图 3-7 所示。图的左边显示了从清单 3-3 的代码中提取的策略,图的右边显示了以图形方式叠加在网格上的相同策略。

img/502835_1_En_3_Fig7_HTML.jpg

图 3-7

从图 3-1 到图 3-6 所示网格世界的策略迭代v(s)。左图:网格中每个单元都有行动概率的最优策略。右图:叠加了最优策略的网格

策略评估也被称为预测,因为我们试图找到与代理正在遵循的当前策略一致的状态值。同样,利用策略迭代寻找最优策略也被称为ccontrol:控制 agent,寻找最优策略。

价值迭代

让我们看看策略迭代,并尝试评估我们需要多少遍才能找到最优策略。策略迭代有两个循环步骤。第一个是策略评估,它针对当前策略运行,并需要多次通过状态空间,以便状态值收敛并变得与当前策略一致。循环的第二部分是策略改进,这需要在状态空间上一遍,以找到每个状态的最佳动作,即,相对于当前状态动作值的贪婪改进。由此可见,很大一部分时间花在了政策评估和让价值观趋同上。

另一种方法是截断策略评估中的循环。当我们将策略评估中的循环截短为只有一个循环时,我们有一种称为值迭代的方法。类似于方程( 3.6 )的方法,我们采用贝尔曼最优方程( 3.3 )作为状态值,并通过迭代将其转化为分配。修正后的方程式如下:

$$ {v}_{k+1}(s)\leftarrow {\mathit{\max}}_a\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_k\left({s}^{\prime}\right)\right] $$

(3.8)

随着我们的迭代,状态值将不断提高,并将收敛到 v*,这是最优值。

$$ {v}_0\to {v}_1\to \dots {v}_k\to {v}_{k+1}\to \dots \to {v}_{\ast } $$

一旦值收敛到最佳状态值,我们可以使用一步备份图来找到最佳策略。

$$ {\pi}_{\ast}\left(a\ |\ s\right)\underset{a}{= argmax}\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_{\ast}\left({s}^{\prime}\right)\right] $$

(3.9)

之前迭代每一步取最大值的过程称为值迭代。图 3-8 显示了伪代码。

VALUE ITERATION

img/502835_1_En_3_Fig8_HTML.png

图 3-8

有限 MDP 的值迭代算法

让我们将前值迭代算法应用于图 3-1 中给出的网格世界。清单 3-4 包含应用于网格世界的值迭代代码。您可以查看文件listing3_4.ipynb了解详细的实现。函数value_iteration是图 3-8 中伪代码的直接实现。

# Value Iteration
def value_iteration(env, discount_factor=1.0, theta=0.00001):
    """
    Varry out Value iteration given an environment and a full description
    of the environment's dynamics

.

    Args:
        env: OpenAI env. env.P -> transition dynamics of the environment.
            env.P[s][a] [(prob, next_state, reward, done)].
            env.nS is number of states in the environment.
            env.nA is number of actions in the environment.
        discount_factor: Gamma discount factor.
        theta: tolernace level to stop the iterations

    Returns:
        policy: [S, A] shaped matrix representing optimal policy.
        value : [S] length vector representing optimal value
    """

    def argmax_a(arr):
        """
        Return idx of max element in an array.
        """
        max_idx = []
        max_val = float('-inf')
        for idx, elem in enumerate(arr):
            if elem == max_val:
                max_idx.append(idx)
            elif elem > max_val:
                max_idx = [idx]
                max_val = elem
        return max_idx

    optimal_policy = np.zeros([env.nS, env.nA])
    V = np.zeros(env.nS)
    V_new = np.copy(V)

    while True:
        delta = 0
        # For each state, perform a "greedy backup"
        for s in range(env.nS):
            q = np.zeros(env.nA)
            # Look at the possible next actions
            for a in range(env.nA):
                # For each action, look at the possible next states
                # to calculate q[s,a]
                for prob, next_state, reward, done in env.P[s][a]:

                    # Calculate the value for each action as per backup diagram
                    if not done:
                        q[a] += prob * (reward + discount_factor * V[next_state])
                    else:
                        q[a] += prob * reward

            # find the maximum value over all possible actions
            # and store updated state value
            V_new[s] = q.max()
            # How much our value function changed (across any states)
            delta = max(delta, np.abs(V_new[s] - V[s]))

        V = np.copy(V_new)

        # Stop if change is below a threshold
        if delta < theta

:
            break

    # V(s) has optimal values. Use these values and one step backup
    # to calculate optimal policy
    for s in range(env.nS):
        q = np.zeros(env.nA)
        # Look at the possible next actions
        for a in range(env.nA):
            # For each action, look at the possible next states
            # and calculate q[s,a]
            for prob, next_state, reward, done in env.P[s][a]:

                # Calculate the value for each action as per backup diagram
                if not done:
                    q[a] += prob * (reward + discount_factor * V[next_state])
                else:
                    q[a] += prob * reward

        # find the optimal actions
        # We are returning stochastic policy which will assign equal
        # probability to all those actions which are equal to maximum value
        best_actions = argmax_a(q)
        optimal_policy[s, best_actions] = 1.0 / len(best_actions)

    return optimal_policy, V

Listing 3-4Value Iteration: listing3_4.ipynb

对网格世界运行价值算法的输出将产生价值和策略完全相似的最优状态值v∫(s)和最优策略$$ {v}_{\pi^{\ast }}\left(a|s\right) $$,如图 3-7 和 3-8 所示。

在继续之前,我们先总结一下。我们到目前为止所看到的被归类为同步动态编程算法,如表 3-1 中所总结的。

表 3-1

同步动态规划算法

|

算法

|

贝尔曼方程

|

问题类型

|
| --- | --- | --- |
| 迭代策略评估 | 期望方程 | 预报 |
| 策略迭代 | 期望方程和贪婪改进 | 控制 |
| 价值迭代 | 最优性方程 | 控制 |

广义策略迭代

前面描述的策略迭代有两个步骤:策略评估,它使状态值与代理正在遵循的策略保持同步,需要多次遍历所有状态以使值收敛到 v π ,以及贪婪动作选择以改进策略。正如所解释的,改进的第二步导致值的当前状态与新策略不同步。因此,我们需要执行另一轮策略评估,以使状态值重新与新策略同步。当状态值没有进一步变化时,评估和改进的循环停止。当代理达到最佳策略且状态值也是最佳的并且与最佳策略同步时,就会发生这种情况。收敛到最优策略(不动点)v π 可以直观的描绘出来,如图 3-9 所示。

img/502835_1_En_3_Fig9_HTML.jpg

图 3-9

两步之间的迭代。第一步是评估,使状态值与遵循的策略同步。第二步是政策改进,对行动进行贪婪的最大化

我们已经看到在策略评估步骤中迭代的循环数有两种极端情况。此外,每次迭代,无论是策略评估还是策略改进,都覆盖了模型中的所有状态。然而,即使在一次迭代中,我们也只能通过贪婪选择访问部分状态集来评估和/或改进状态操作。策略改进定理保证了即使所有状态的部分覆盖也会导致改进,除非代理已经在遵循最优策略。换句话说,状态值同步不需要完成。可能会中途终止,导致图 3-9 中的箭头未触及v=vπ的底线而停止。同样,策略改进步骤可能不会对所有状态进行改进,这再次导致图 3-9 中的箭头在π = 贪婪 ( v )的上线处停止。

总之,只要每个状态在评估和改进中被访问足够的次数,策略评估和策略改进的这两个步骤以及它们在评估步骤中扫描多少个状态或在多少次迭代之后停止的所有变化都会导致收敛。这被称为广义策略迭代 (GPI)。我们将要研究的大多数算法都可以归类为某种形式的 GPI。当我们通过各种算法时,请记住图 3-10 中给出的图片。

img/502835_1_En_3_Fig10_HTML.jpg

图 3-10

两步之间的迭代。第一步是评估,使状态值与遵循的策略同步。第二步是政策改进,对行动进行贪婪的最大化

异步备份

基于动态编程的算法存在可扩展性问题。动态规划方法比直接求解方法(如线性规划,涉及求解矩阵方程)好得多,并且可扩展。然而,动态编程仍然不能很好地适应现实生活中的问题。考虑策略迭代下的单次扫描。它需要访问每个州,在每个州下,你需要考虑所有可能的行动。此外,每个动作涉及一个计算,该计算理论上可能再次涉及所有状态,这取决于状态转移函数p(sr | sa )。换句话说,每次迭代的复杂度为O(|A|∫|s|2)。我们从策略迭代开始,在策略迭代下,我们执行多次迭代,作为状态值收敛的评估步骤的一部分。我们研究的第二种控制方法是值迭代。通过利用贝尔曼最优方程,我们将评估迭代减少到只有一步。所有这些都是同步动态规划算法,在这些算法下,使用贝尔曼备份方程( 3.1 )到( 3.4 )来更新所有状态。

然而,不需要在每次迭代中更新每个状态。我们可以以任何顺序更新和/或优化,仅覆盖系统中全部状态的一部分。只要每个状态被足够频繁地访问,所有这些扫描状态的方法都会产生最优的状态值和最优的策略。清扫有多种方法。

第一种是原地动态编程。到目前为止,我们一直维护着状态的两个副本。第一个副本保存现有的状态值,第二个副本保存正在更新的新状态值。一个就地策略只使用状态值数组的一个副本。同一数组用于读取旧的状态值和更新新的状态值。作为一个例子,让我们看看来自( 3.8 )的值迭代方程。请注意原始值迭代等式左右两侧状态值的子指标与就地版本相比的细微差异。原版本使用数组 V k 更新一个新数组Vk+1,而原地编辑更新的是同一个数组。

以下是原文:

$$ {v}_{k+1}(s)\leftarrow {\mathit{\max}}_a\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_k\left({s}^{\prime}\right)\right] $$

这是就地更新:箭头两边都是相同的 v(s)数组。

$$ v(s)\leftarrow {\mathit{\max}}_a\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ v\left({s}^{\prime}\right)\right] $$

实验表明,即使在迭代中途,当值向上移动时,就地编辑也能提供更快的收敛。

第二个想法围绕着状态更新的顺序。在同步编程中,我们在一次迭代中更新所有的状态。然而,如果我们使用优先扫描,值可能收敛得更快。优先扫描需要了解状态的前任。假设我们刚刚更新了状态 S = s ,并且值改变了δ。状态 S = s 的所有前趋状态被添加到优先级为δ的优先级队列中。如果一个前趋状态已经在优先级队列中,其优先级大于δ,则它保持不变。在下一次迭代中,一个具有最高优先级的新状态被从队列中取出并更新,将我们带回循环的开始。优先级扫描的策略需要反向动态的知识,即给定状态的前身。

第三个思路是实时动态编程。使用这种方法,我们只更新代理当前看到的状态的值,即与代理相关的状态,并使用其当前的探索路径来区分更新的优先级。这种方法避免了对不在代理当前路径范围内的状态进行无用的更新,因此这些状态大多是不相关的。

动态编程,无论是同步还是异步,都使用全宽度备份,如图 3-2 所示。对于一个给定的状态,我们需要知道每个可能的动作和每个后继状态 S = s ' 。我们还需要了解环境动态 p ( s 'r | sa )。然而,使用异步方法并不能完全解决可伸缩性问题。它只是稍微扩展了可伸缩性。换句话说,即使是异步更新,动态编程也只适用于中型问题。

从下一章开始,我们将考虑使用基于样本的方法来解决强化学习问题的更具可扩展性的方法。在基于样本的方法中,我们不了解环境动态,也不进行全宽度扫描。

摘要

在这一章中,我们介绍了动态规划的概念以及它是如何应用于强化学习领域的。我们着眼于预测的策略评估,然后是控制的策略迭代。然后我们看了价值迭代。这些讨论导致了广义的政策迭代。本章最后快速回顾了异步变体,以获得更有效的状态更新方法。

四、无模型方法

在前一章中,我们看了动态编程,其中我们知道模型动态p(sr | sa ),并且这些知识被用于“计划”最优动作。这也被称为计划问题。在这一章中,我们将转移我们的焦点,看看学习问题,即模型动态未知的设置。我们将通过抽样来学习价值和行动-价值函数,即通过遵循现实世界中的一些策略或通过模拟中的策略运行代理来收集经验。在另一类问题中,我们发现无模型方法更适用。在一些问题中,采样比计算转移动态更容易,例如,考虑解决寻找玩像 21 点这样的游戏的最佳策略的问题。有许多组合来达到一个分数,这取决于到目前为止看到的卡和仍然在甲板上的卡。几乎不可能计算从一个状态到另一个状态的精确转移概率,但是很容易从环境中采样状态。总之,当我们不知道模型动态或我们知道模型时,我们使用无模型方法,但是采样比计算转变动态更实际。

在这一章中,我们将研究两大类无模型学习:蒙特卡罗(MC)方法和时间差分(TD)方法。我们将首先理解策略评估在无模型设置中如何工作,然后扩展我们的理解来看控制,即找到最优策略。我们也将触及自举的重要概念和探索与开发的困境,以及政策外与政策内的学习。最初,重点将是分别研究 MC 和 TD 方法,之后我们将研究其他概念,如 n 步回报、重要性抽样和合格跟踪,以将 MC 和 TD 方法结合成一个通用的、更通用的方法,称为 TD(λ)。

蒙特卡罗估计/预测

当我们不知道模型动力学时,我们做什么?回想一下你对某个问题一无所知的情况。在那种情况下你做了什么?你尝试,采取一些步骤,并找出情况如何反应。例如,假设你想找出一个骰子或硬币是否有偏差。你多次投掷硬币或骰子,观察结果,并以此来形成你的观点。换句话说,你取样。统计学中的大数定律告诉我们,样本的平均值可以很好地替代平均值。此外,随着样本数量的增加,这些平均值会变得更好。如果你回顾上一章的贝尔曼方程,你会注意到在那些方程中我们有期望算子 E[];例如一个状态的值为v(S)=E**Gt|St=S。此外,为了计算 v ( s ),我们使用了需要转换动态p(s'r | sa )的动态编程。在缺乏模型动力学知识的情况下,我们该怎么办?我们只是从模型中取样,观察从状态 S = s 开始直到剧集结束的返回。然后,我们对所有剧集运行的回报进行平均,并使用该平均值作为代理遵循的策略π的估计值vπ(s)。简而言之,这就是蒙特卡罗方法的方法:用样本回报的平均值代替预期回报。

有几点需要注意。MC 方法不需要模型知识。唯一需要的是,我们应该能够从中取样。我们需要知道从一个状态开始直到终止的回报,因此我们只能在每一次运行最终终止的间断 MDP 上使用 MC 方法。它不能在非终止环境中工作。第二点是,对于一个大的 MDP,我们可以把重点放在只对 MDP 的相关部分进行采样,而避免探索 MDP 的不相关部分。这种方法使得 MC 方法对于非常大的问题具有高度的可扩展性。一种叫做蒙特卡罗树搜索 (MCTS)的 MC 方法的变体被 OpenAI 用于训练一个围棋游戏代理。

第三点是关于马尔可夫假设;即过去完全编码在当前状态中。换句话说,了解现在使未来独立于过去。我们在第 [2 章讨论了这个属性。马尔可夫独立性的这一特性形成了贝尔曼方程的递归性质的基础,其中一个状态仅仅依赖于后继状态值。然而,在 MC 方法下,我们观察从状态 S 开始直到终止的全部回报。我们不依赖于后续状态的值来计算当前状态值。这里没有马尔可夫性质的假设。MC 方法中缺少马尔可夫假设,这使得它们对于被称为 POMDPs(对于“部分可观测 MDP”)的 MDPs 类更加可行。在 POMDP 环境中,我们只获得部分状态信息,这被称为观察

接下来,让我们看看一种估计给定策略的状态值的正式方法。我们让代理开始新的一集,并观察从时间代理的返回在该集中第一次处于状态 S = s ,直到该集结束。进行许多集运行,并取跨集返回的平均值作为对vπ(S=S)的估计。这就是所谓的首诊 MC 法。请注意,根据动态,代理可能会在终止前的同一情节中的某个后续步骤中访问同一状态 S = s 。然而,在首次访问 MC 方法中,我们仅从一集的首次访问到该集的结束获取总回报。还有另一个变体,我们取每次访问该州的平均回报,直到该集结束。这就是所谓的每次拜访司仪法

图 4-1 显示了 MC 方法的备份图。在 DP 中,我们进行了一次完整的滑动,以涵盖从一个状态到一个新状态的所有可能的动作,以及从状态-动作对( S = sA = a )到一个新状态的所有可能的转换。我们只从状态 S = sA = a 再到下一个状态S=S'再深入一层。与此相比,在 MC 方法中,备份覆盖了从当前状态 S = s 到终止状态的完整样本轨迹。它没有涵盖所有的分支可能性;相反,它只覆盖了从起始状态 S = s 到终止状态的一条采样路径。

img/502835_1_En_4_Fig1_HTML.jpg

图 4-1

与基于贝尔曼方程的 DP 备份相比,MC 方法的备份图

现在让我们看看初诊版本的伪代码,如图 4-2 所示。我们输入代理当前遵循的策略 π 。我们初始化两个数组:一个保存当前对 V ( s )的估计,第二个保存对状态 S = s 的访问次数 N ( s )。执行多集,每集更新 V ( s )和 N ( s )的国家特工访问,在“首次访问版本”中只更新首次访问,在“每次访问版本”中更新每次访问伪代码仅涵盖了“首次访问变体”“每次访问”变体很容易实现,只需删除条件“除非 St出现在 S 0 ,S 1 ,… ”根据大数定律,蒙特卡洛模拟所基于的统计定律,当对每个状态的访问次数趋于无穷大时, V ( s )将收敛为真Vπ(s)。

First Visit MC Prediction

img/502835_1_En_4_Fig2_HTML.png

图 4-2

初访 MC 预测估算vπ(s)。伪代码使用在线版本在收到样本时更新值

我们存储累计总数和第一次访问一个州的次数。平均值的计算方法是将总数除以计数。每次访问一个状态,进行如下更新:

$$ N(s)=N(s)+1;\kern1em S(s)=S(s)+G;\kern1.25em V(s)=S(s)/N(s) $$

稍作改动,我们可以从另一个角度来看这个更新,而不需要累计总数 S ( s )。在这个替代公式中,我们将有一个数组 V ( s ),它直接为每次访问更新,而不需要将总数除以计数。这个更新规则的推导如下:

$$ N{(s)}_{n+1}=N{(S)}_n+1 $$

$$ V{(s)}_{n+1}=\left[S{(s)}_n+G\right]/N{(s)}_{n+1} $$

$$ =\left[V{(s)}_n\ast N{(s)}_n+G\right]/N{(s)}_{n+1} $$

$$ =V{(s)}_n+1/N{(s)}_{n+1}\ast \left[G\hbox{--} V{(s)}_n\right] $$

(4.1)

差值[GV(s)n]可视为最新采样值 G 与当前估计值V(s)n之间的误差。然后,通过将 1/N误差加到当前估计值,该差值/误差被用于更新当前估计值。随着访问次数的增加,新样本对修正 V ( s )估计值的影响越来越小。这是因为随着 N 变得非常大,乘法因子 1/ N 减小到零。

有时候,我们可以不用一个递减因子 1/ N ,而是在差值前面用一个常数α作为倍增因子[GV(s)N]。

$$ {V}_{n+1}={V}_n+\upalpha \left(G\hbox{--} {V}_n\right) $$

(4.2)

常数乘数法更适合于非平稳的问题,或者当我们希望给所有误差一个常数权重时。当旧的估计值 V n 可能不太准确时,可能会发生这种情况。

现在让我们看看 MC 值预测的实现。清单 4-1 显示了代码,完整的代码在文件listing4_1.ipynb中。

def mc_policy_eval(policy, env, discount_factor=1.0, episode_count=100):

    # Start with (all 0) state value array and a visit count of zero
    V = np.zeros(env.nS)
    N = np.zeros(env.nS)
    i = 0

    # run multiple episodes
    while i < episode_count:

        #collect samples for one episode
        episode_states = []
        episode_returns = []
        state = env.reset()
        episode_states.append(state)
        while True:
            action = np.random.choice(env.nA, p=policy[state])
            (state, reward, done, _) = env.step(action)
            episode_returns.append(reward)
            if not done:
                episode_states.append(state)
            else:
                break

        #update state values
        G = 0
        count = len(episode_states)
        for t in range(count-1, -1, -1):
            s, r = episode_states[t], episode_returns[t]
            G = discount_factor * G + r
            if s not in episode_states[:t]:
                N[s] += 1
                V[s] = V[s] + 1/N[s] * (G-V[s])

        i = i+1

    return np.array(V)

Listing 4-1MC Value Prediction Algorithm for Estimation

清单 4-1 中的代码是图 4-2 中伪代码的直接实现。代码实现在线版本的更新,如等式( 4.1 所解释的,即 N[s]+= 1;V[s]=V[s]+1/N[s]*(G-V[s])。该代码还实现了“第一次访问”版本,但可以通过一个非常小的调整转换为“每次访问”。我们只需要去掉“如果”检查,即“如果不在集状态[:t]”来执行每一步的更新。

为了确保收敛到真实的状态值,我们需要确保每个状态被访问足够的次数,在极限中是无限的。如 Python 笔记本末尾的结果所示,状态值在 100 集内没有很好地收敛。然而,对于 10,000 集,这些值已经很好地收敛,并且与列表 3-2 中给出的 DP 方法产生的值相匹配。

mc 预测方法的偏差和方差

现在让我们来看看“首次就诊”与“每次就诊”的利弊两者是否都收敛到真正的底层 V ( s )?他们收敛的时候波动大吗?一个人是否更快地收敛到真正的价值?在我们回答这个问题之前,让我们先回顾一下在所有统计模型估计中看到的偏差-方差权衡的基本概念,例如,在监督学习中。

偏差是指模型收敛到我们试图估计的真实潜在价值的性质,在我们的例子中是vπ(s)。一些估计值是有偏差的,这意味着由于其固有的缺乏灵活性,即对于给定的真实模型来说过于简单或受限制,它们不能收敛到真实值。同时,在其他一些情况下,随着样本数量的增加,模型的偏差会下降到零。

方差指的是对所使用的特定样本数据敏感的模型估计。这意味着估计值可能波动很大,因此可能需要大量的数据集或试验来使估计平均值收敛到一个稳定的值。

这些模型非常灵活,具有较低的偏差,因为它们能够使模型适应数据集的任何配置。同时,由于灵活性,他们可以过度适应数据,使得估计随着训练数据的变化而变化很大。另一方面,模型越简单,偏差越大。由于固有的简单性和局限性,这样的模型可能无法代表真正的底层模型。但是它们也将具有低方差,因为它们不会过拟合。这就是所谓的偏差-方差权衡,可以用图 4-3 来表示。

img/502835_1_En_4_Fig3_HTML.jpg

图 4-3

偏差方差权衡。模型复杂度在 x 轴上向右增加。当模型受到限制时,偏差从高开始,随着模型变得灵活,偏差下降。方差显示了与模型复杂性相反的趋势

将“首次就诊”与“每次就诊”进行比较,首次就诊是无偏的,但具有较高的方差。每次访问都有偏差,随着试验次数的增加,偏差下降到零。此外,每次访问都具有低方差,并且通常比第一次访问更快地收敛到真实值估计。

蒙特卡罗控制

现在让我们来谈谈无模型设置中的控制。我们需要在不知道模型动态的情况下找到这个设置中的最优策略。作为复习,让我们看看在第三章中介绍的通用策略迭代(GPI)。在 GPI 中,我们在两个步骤之间迭代。第一步是找到给定策略的状态值,第二步是使用贪婪优化来改进策略。对于 MC 下的控制,我们将遵循相同的 GPI 方法。不过,我们会做一些调整,以考虑到我们处于无模型世界中,无法访问/了解转换动态的事实。

在第三章中,我们看了状态值, v(s) 。然而,在没有过渡动态的情况下,仅有状态值是不够的。对于贪婪改进步骤,我们需要访问动作值, q ( sa )。我们需要知道所有可能动作的 q 值,即在状态 S = s 下所有可能动作 a 的所有q(S=Sa )。只有有了这些信息,我们才能应用贪婪最大化来选择最佳行动,即 arg maxaq(s, a )

当与 DP 相比时,我们还有另一个复杂因素。代理在生成样本的时间遵循一个策略。然而,这样的策略可能导致许多状态-动作对从不被访问,如果该策略是确定性的,则更是如此。如果代理不访问状态-动作对,它不知道给定状态的所有 q ( sa ),因此它不能找到产生动作的最大 q 值。解决该问题的一种方法是通过探索开始来确保足够的探索,从而确保代理从随机的状态-动作对开始一集,并且在许多集的过程中覆盖每个状态-动作对足够的时间 s,事实上,无限长的时间。**

4-4 显示了 GPI 图随着v-值到q-值的变化。现在的评估步骤是 MC p 预测步骤,该步骤在前面的部分中介绍。一旦q-值稳定下来,就可以应用贪婪最大化来获得新的策略。政策与 ?? 改进定理确保新政策会更好,或者至少和旧政策一样好。GPI 之前的方法将成为一个反复出现的主题。根据设置,评估步骤将会改变,而改进步骤将始终保持贪婪最大化。****

*img/502835_1_En_4_Fig4_HTML.jpg

图 4-4

两步之间的迭代。第一步是评估,以使状态-动作值与所遵循的策略同步。第二步是政策改进,对行动进行贪婪的最大化

“探索开始”的假设不太实际,效率也不高。在许多场景中,这是不实际的,因为代理不能选择开始条件,例如,在训练自动驾驶汽车时。这是没有效率的,因为它可能是不可行的,并且对于代理来说,无限(有限)次访问每个状态-动作对也是浪费。我们仍然需要继续探索,让代理访问当前策略所访问的州中的所有动作。这是通过使用ε-贪婪策略实现的。**

**在ε-贪婪策略中,代理以最大 q 值概率 1-ε采取行动,并以概率ε/|A|随机采取任何行动。换句话说,ε的剩余概率在所有动作中平均分配,以确保智能体继续探索非最大化动作。换句话说,智能体以概率 1-ε利用的知识,它以概率ε探索

$$ \pi \left(a|s\right)=\left{\begin{array}{c}1-\varepsilon +\frac{\varepsilon }{\left|A\right|}\kern1.25em for\ a={argmax}_a\ Q\left(s,a\right)\ {}\frac{\varepsilon }{\mid A\mid}\kern3em otherwise\kern5.5em \end{array}\right. $$

(4.3)

作为随机探索的一部分,从 greedy max 和ε/|A|中以概率 1-ε挑选具有最大 q 值的动作。所有其他动作以概率ε/|A|被拾取。当代理在多个情节中学习时,ε的可以慢慢减小到零,以达到最优贪婪策略。

让我们对迭代的估计/预测步骤再做一次改进。在上一节中,我们看到,即使对于一个简单的 4×4 网格 MDP,我们也需要 10,000 集的数量级才能使值收敛。参见图 3-9 ,该图显示了 GPI 的收敛:MC 预测步骤将v-值或 q- 值与当前策略同步。然而,在前一章中,我们也看到了代理人没有必要一路走到价值观的趋同。类似地,我们可以运行 MC 预测,然后逐集进行政策改进。这种方法将消除在估计/预测步骤中大量迭代的需要,从而使该方法可扩展用于大的 MDP。与图 3-9 中的收敛图相比,这种方法将产生如图 4-5 所示的收敛。

img/502835_1_En_4_Fig5_HTML.jpg

图 4-5

两步之间的迭代。第一步是针对单个步骤的 MC 预测/评估,以在当前策略的方向上移动 q 值。第二步是政策改进,对行动进行ε-贪婪最大化

结合所有先前的调整,我们有了一个基于蒙特卡罗的最优策略学习控制的实用算法。这在《极限与无限探索》(GLIE)中被称为贪婪。我们将在 q 值预测步骤中使用每次访问版本。但是它需要一个小的调整,就像图 4-6 中 MC 预测中的那个一样,使它成为第一次访问的变体。

GLIE For Policy Optimization

img/502835_1_En_4_Fig6_HTML.png

图 4-6

用于策略优化的每次访问(GLIE) MC 控制

现在让我们看看listing4_2.ipynb中给出的实际实现。清单 4-2 转载了代码的相关部分。实现遵循伪代码。我们有一些像argmax_a()这样的额外函数,帮助我们找到给定状态下max Q(s,a)的动作。另一个函数get_action(state)返回当前策略的ε-贪婪操作。我们鼓励您进行调整,并将其转换为“首次访问”版本。您可能还想比较“首次就诊”与“每次就诊”MC 对照的结果。

def GLIE(env, discount_factor=1.0, episode_count=100):
    """
    Find optimal policy given an environment.
    Returns:
        Vector of length env.nS representing the value function.
        policy: [S, A] shaped matrix representing the policy. Random in our case

    """
    # Start with (all 0) state value array and state-action matrix.
    # also initialize visit count to zero for the state-action visit count.
    V = np.zeros(env.nS)
    N = np.zeros((env.nS, env.nA))
    Q = np.zeros((env.nS, env.nA))
    #random policy
    policy = [np.random.randint(env.nA) for _ in range(env.nS)]
    k = 1
    eps = 1

    def argmax_a(arr):
        """
        Return idx of max element in an array.
        Break ties uniformly.
        """
        max_idx = []
        max_val = float('-inf')
        for idx, elem in enumerate(arr):
            if elem == max_val:
                max_idx.append(idx)
            elif elem > max_val:
                max_idx = [idx]
                max_val = elem
        return np.random.choice(max_idx)

    def get_action(state):
        if np.random.random() < eps:
            return np.random.choice(env.nA)
        else:
            return argmax_a(Q[state])

    # run multiple episodes
    while k <= episode_count:

        #collect samples for one episode
        episode_states = []
        episode_actions = []
        episode_returns = []
        state = env.reset()
        episode_states.append(state)
        while True:
            action = get_action(state)
            episode_actions.append(action)
            (state, reward, done, _) = env.step(action)
            episode_returns.append(reward)
            if not done:
                episode_states.append(state)
            else:
                break

        #update state-action values
        G = 0
        count = len(episode_states)
        for t in range(count-1, -1, -1):
            s, a, r = episode_states[t], episode_actions[t], episode_returns[t]
            G = discount_factor * G + r
            N[s, a] += 1
            Q[s, a] = Q[s, a] + 1/N[s, a] * (G-Q[s, a])

        #Update policy and optimal value
        k = k+1
        eps = 1/k
        #uncomment this to have higher exploration initially
        #and then let epsilon decay after 5000 episodes
        #if k <=100:
        #    eps = 0.02

        for s in range(env.nS):
            best_action = argmax_a(Q[s])
            policy[s] = best_action
            V[s] = Q[s,best_action]

    return np.array(V), np.array(policy)

Listing 4-2GLIE MC Control Algorithm

到目前为止,本章我们已经研究了预测和控制的策略算法。换句话说,相同的策略用于生成样本以及策略改进。策略改进是针对相同ε-贪婪策略的 q 值进行的。我们需要探索为所有的状态-行为对找到 Q ( sa ),这是通过使用ε-贪婪策略来实现的。然而,随着我们的进展,带有限制的ε-贪婪策略是次优的。随着我们对环境的了解越来越多,我们需要开发更多。我们还看到,虽然理论上策略会收敛到最优策略,但有时需要仔细控制ε值。MC 方法的另一个大缺点是,我们需要在更新 q 值和执行策略优化之前完成剧集。因此,MC 方法只能应用于情节性的和情节结束的环境。从下一节开始,我们将开始研究一种不同的算法,称为时间差分方法,它结合了 DP(单时间步长更新)和 MC(即不需要了解系统动态)的优点。然而,在我们深入研究 TD 方法之前,让我们先来看一小段介绍,然后比较策略上学习和策略外学习。

不符合政策的 MC 控制

在 GLIE 中,我们看到,为了进行足够的探索,我们需要使用ε-贪婪策略,以便在有限的范围内足够频繁地访问所有状态动作。在循环结束时学习的策略被用于为循环的下一次迭代生成情节。我们正在使用与被最大化的策略相同的策略进行探索。这种方法被称为 on-policy ,从正在优化的同一策略中生成样本。

还有另一种方法,其中使用具有较高ε的更具探索性的策略来生成样本,而被优化的策略可能具有较低的ε,甚至可能是完全确定性的策略。这种使用与优化策略不同的策略进行学习的方法被称为偏离策略学习。用于生成样本的策略被称为行为策略,被学习(最大化)的策略被称为目标策略。让我们看看图 4-7 中的非策略 MC 控制算法的伪代码。

Off Policy MC Control Optimization

img/502835_1_En_4_Fig7_HTML.png

图 4-7

用于策略优化的非策略 MC 控制

时间差异学习方法

参考图 4-1 研究 DP 和 MC 方法的备份图。在 DP 中,我们使用来自后继状态的值来估计当前状态值,仅备份一步的值。我们还根据所遵循的政策,从( sa )对到所有可能的奖励和后继状态,对行动概率进行了预期。

$$ {v}_{\pi }(s)={\sum}_a\pi \left(a|s\right){\sum}_{s^{\prime },r}p\left({s}^{\prime },r\right|s,a\left[r+\gamma {v}_{\pi}\left({s}^{\prime}\right)\right] $$

一个状态的值vπ(s)是根据后继状态的当前估计值vπ(s)来估计的。这被称为自举。该估计基于另一组估计。这两个总和就是图 4-1 中 DP 备份图中表示为分支节点的总和。与 DP 相比,MC 基于从一个状态开始,并基于代理遵循的当前策略对结果进行采样。估计值是多次运行的平均值。换句话说,模型转移概率的总和被平均值代替,因此 MC 的备份图是从一个状态到最终状态的单一长路径。MC 方法允许我们建立一个可扩展的学习方法,同时不需要知道确切的模型动态。然而,它产生了两个问题:MC 方法仅适用于情节环境,并且更新仅在情节结束时发生。DP 的优势在于使用对后续状态的估计来更新当前状态值,而无需等待情节结束。

*时间差异学习是一种结合了动态规划和蒙特卡罗的优点的方法,使用了动态规划的自举和蒙特卡罗的基于样本的方法。TD 的更新方程式如下:

$$ V(s)=V(s)+\alpha \left[R+\gamma \ast V\left({s}^{\prime}\right)-V(s)\right] $$

(4.4)

状态 S = s ,即 G t 的总回报的当前估计,现在是通过从样本运行中所示的后继状态的当前估计(S)中引导给出的。换句话说,方程( 4.2 )中的 G t 换成R+γV(s),一个估计。与此相比,在 MC 方法中, G t 是样本运行的贴现总回报。

所描述的 TD 方法被称为 TD(0),并且它是一步估计。当我们在本章末尾讨论 TD(λ)时,称之为 TD(0)的原因将变得更加清楚。为了清楚起见,让我们看看图 4-8 中给出的使用 TD(0)进行值估计的伪代码。

TD(0) For Estimation

img/502835_1_En_4_Fig8_HTML.png

图 4-8

TD(0)策略估计

已经看到了所有三种方法,DP、MC 和 TD,让我们把所有三种方法的备份图放在一起,如图 4-9 所示。TD(0)类似于 DP,因为它使用自举,自举是对下一个状态值的估计,以估计当前状态值。TD(0)类似于 MC,因为它对情节进行采样,并使用观察到的回报和下一个状态来更新估计。

img/502835_1_En_4_Fig9_HTML.jpg

图 4-9

比较 DP、MC 和 TD(0)的备用图

现在我们来介绍另一个你很可能在强化学习文献中反复看到的量。在等式( 4.4 )中,量[R+γV(S)]是从状态 S = s 的总回报的修正估计,并且它基于从后继状态的一步备份。此外,在等式( 4.4 )中,赋值右侧的 V ( s )是当前估计值。更新是将估计值从 V ( s )移动到V(s)+α(),其中差 δ t 由以下表达式给出:

$$ {\delta}_t={R}_{t+1}+\gamma \ast V\left({S}_{t+1}\right)-V\left({S}_t\right) $$

(4.5)

差值δt称为TD误差。它代表基于奖励Rt+1加上贴现的下一时间步状态值V(St+1)的估计值 V ( s )和当前估计值V(S的误差(差)该状态的值被移动该误差 δ t 的一个比例(学习率、步进率 α )。

TD 比 DP 有优势,因为它不需要模型知识(转移函数)。TD 方法优于 MC,因为 TD 方法可以在每一步更新状态值;也就是说,它们可以用于在线设置,通过在线设置,我们可以随着情况的发展而学习,而不必等到一集结束。

时间差异控制

本节将开始带你进入 RL 世界中使用的真实算法的领域。在这一章的剩余部分,我们将看看在 TD 学习中使用的各种方法。我们将从一个简单的一步到位的策略学习方法开始,称为 SARSA 。接下来是一种叫做 Q-learning 的强大的策略外技术。在本章中,我们将研究 Q 学习的一些基础方面,在下一章中,我们将把深度学习与 Q 学习相结合,给我们提供一种称为深度 Q 网络(DQN)的强大方法。使用 DQN,你将能够在雅达利模拟器上训练游戏代理。在这一章中,我们还将讨论 Q-learning 的一个变种,叫做预期 SARSA ,另一种非策略学习算法。然后,我们将讨论 Q 学习中最大化偏差的问题,带我们到双 Q 学习。当与深度学习结合来表示状态空间时,Q 学习的所有变体都变得非常强大,这将构成下一章的主要内容。在本章的末尾,我们将介绍其他概念,如经验回放,它使非学习算法在学习最优策略所需的样本数量方面更有效。然后我们将讨论一个强大的、有点复杂的方法,叫做 TD( λ ),它试图在一个连续体上结合 MC 和 TD 方法。最后,我们将看看一个具有连续状态空间的环境,以及如何将状态值二进制化并应用前面提到的 TD 方法。这个练习将展示我们将在下一章讨论的方法的必要性,包括状态表示的函数近似和深度学习。在关于深度学习和 DQN 的第 5 和 6 章之后,我们将展示另一种称为策略优化的方法,该方法围绕直接学习策略,而不需要找到最佳状态/动作值。

到目前为止,我们一直使用 4×4 网格世界。现在,我们将在本章的剩余部分看看更多的环境。我们将以封装的方式编写代理,这样相同的代理/算法无需任何更改就可以应用于各种环境。

我们将使用的第一个环境是网格世界的变体;这是健身房图书馆的一部分,叫做悬崖漫步环境。在这个环境中,我们有一个 4×12 的网格世界,左下角的单元格是起始状态 S ,右下角的状态是目标状态 G 。底排的其余部分形成悬崖;踩上去会获得-100 的奖励,代理人再次回到开始状态。每走一步获得-1 的奖励,直到代理达到目标状态。类似于 4×4 网格世界,代理可以向任何方向(上、右、下、左)走一步。当代理达到目标状态时,情节终止。图 4-10 描述了该设置。

img/502835_1_En_4_Fig10_HTML.jpg

图 4-10

4×10 悬崖世界。踩悬崖有-100 的奖励,其他转场有-1 的奖励。目标是从 S 开始,到达 G 并获得尽可能多的奖励

我们要看的下一个环境是“出租车问题”有四个标记为 R、G、Y 和 b 的位置。当一集开始时,一名乘客随机站在这四个位置中的一个。随机选择四个方块中的一个作为目的地。出租车有 25 个可能的位置。乘客有五个位置:起始位置或内部出租车。有四个目的地。所有这些组合将状态空间的可能值作为 500 种不同的组合。状态值被表示为一个元组(taxi_row, taxi_col, passenger_location, destination)。有六种行动可以果断地采取。出租车可以移动北、南、西、东,然后有客两个动作。奖励为每个时间步长-1,成功取货/卸货+20,从错误地点取货/卸货-10。当乘客被成功接送时,这一集就结束了。图 4-11 所示为环境示意图。

img/502835_1_En_4_Fig11_HTML.png

图 4-11

5×5 出租车问题。所有转换都有-1 的奖励。当乘客在目的地下车时,一集结束

我们现在来看看被称为CartPole的第三种环境。我们在第二章中讨论了这个环境。在这种状态下,空间是一个连续的空间,包括四个观察:[Cart position, Car velocity, Pole angle, pole angular velocity]。代理有两个动作:向左推车向右推车,即两个离散动作。每个时间步长的回报都是+1,代理希望通过在最长的时间间隔内保持极点平衡来最大化回报。一旦杆角度在任一方向上超过 12 度,或者推车位置偏离中心超过 2.4 度,即< -2.4 度或> 2.4 度,或者如果代理能够平衡杆 200 个时间步,该情节终止。图 4-12 显示了实际环境的快照。

img/502835_1_En_4_Fig12_HTML.jpg

图 4-12

推车杆子问题。目标是在最大数量的时间步长内垂直平衡极点,每个时间步长奖励+1

解释了各种环境之后,现在让我们开始用 TD 算法解决这些问题,首先从 SARSA 开始,这是一种基于策略的方法。

保单 SARSA

像 MC 控制方法一样,我们将再次利用 GPI。我们将使用 TD 驱动的方法进行政策价值评估/预测步骤,并将继续使用贪婪最大化方法进行政策改进。就像 MC 一样,我们需要探索足够多的地方,并访问所有的州无数次,以找到一个最佳策略。类似于 MC 方法,我们可以使用ε-贪婪策略,并慢慢地将ε值降低到零,即,对于将探索降低到零的限制。

TD 设置是无模型的;也就是说,我们事先没有关于转变的全面知识。同时,为了能够通过选择正确的行动来最大化回报,我们需要知道状态行动值 Q ( SA )。我们可以将等式( 4.4 )中的 TD 估计重新表述为( 4.6 )中的 TD 估计,本质上是用 Q ( sa )替换 V ( s )。这两种设置都是马尔可夫过程,方程( 4.4 )关注状态到状态的转换,现在关注的焦点是状态-动作到状态-动作。

$$ Q\left({S}_t,{A}_t\right)=Q\left({S}_t,{A}_t\right)+\alpha \ast \left[{R}_{t+1}+\gamma \ast Q\left({S}_{t+1},{A}_{t+1}\right)-Q\left({S}_t,{A}_t\right)\right] $$

(4.6)

与等式( 4.5 )类似,TD 误差现在以 q 值的形式给出。

$$ {\delta}_t={R}_{t+1}+\gamma \ast Q\left({S}_{t+1},{A}_{t+1}\right)-Q\left({S}_t,{A}_t\right) $$

(4.7)

为了根据等式( 4.6 )进行更新,我们需要所有五个值StA tRt+1St+1A 这就是这种方法被称为 SARSA(状态、行动、奖励、状态、行动)的原因。我们将遵循ε-greedy 策略生成样本,使用( 4.6 )更新 q 值,然后基于更新的 q 值创建新的ε-greedy。政策改进定理保证新政策将优于旧政策,除非旧政策已经是最优的。当然,为了保证成立,我们需要将极限中的探索概率ε降低到零。

还请注意,对于所有的阶段性策略,终端状态有 Q ( SA )等于零;即,一旦处于终止状态,此人不能转移到任何地方,并将继续获得零奖励。这是情节结束的另一种说法,并且对于所有终端状态, Q ( SA )为零。因此,当St+1为终态时,方程( 4.6 )会有Q(St+1At+1)= 0,更新方程会是这样的:【1

现在让我们看看 SARSA 算法的伪代码;参见图 4-13 。

SARSA On-Policy TD Control

img/502835_1_En_4_Fig13_HTML.png

图 4-13

SARSA,保单 TD 控制

现在让我们浏览一下来自listing4_3.ipynb的代码,它实现了 SARSA。代码中有一个名为SARSAAgent的类,它实现了 SARSA 学习代理。它有两个关键函数:update(),取值为stateactionrewardnext_statenext_action以及done标志。它使用 TD 更新公式( 4.6 和( 4.8 )更新 q 值。另一个函数是get_action(state),以概率ε返回一个随机动作,以概率 1-ε返回argmax Q(S,A)。在类之外有一个通用函数train_agent(),它在给定的环境中训练代理。有两个辅助函数:plot_rewards()随着训练的进行绘制每集的奖励,以及print_policy()打印学习到的最佳策略。该清单仅显示了SARSAAgent.的代码,其余代码请参考listing4_3.ipynb(清单 4-3 )。

# SARSA Learning agent class
from collections import defaultdict

class SARSAAgent:
    def __init__(self, alpha, epsilon, gamma, get_possible_actions):
        self.get_possible_actions = get_possible_actions
        self.alpha = alpha
        self.epsilon = epsilon
        self.gamma = gamma
        self._Q = defaultdict(lambda: defaultdict(lambda: 0))

    def get_Q(self, state, action):
        return self._Q[state][action]

    def set_Q(self, state, action, value):
        self._Q[state][action] = value

    # carryout SARSA updated based on the sample (S, A, R, S', A')
    def update(self, state, action, reward, next_state, next_action, done):
        if not done:
            td_error = reward + self.gamma * self.get_Q(next_state, next_action) /
- self.get_Q(state,action)
        else:
            td_error = reward - self.get_Q(state,action)

        new_value = self.get_Q(state,action) + self.alpha * td_error
        self.set_Q(state, action, new_value)

    # get argmax for q(s,a)
    def max_action(self, state):
        actions = self.get_possible_actions(state)
        best_action = []
        best_q_value = float("-inf")

        for action in actions:
            q_s_a = self.get_Q(state, action)
            if q_s_a > best_q_value:
                best_action = [action]
                best_q_value = q_s_a
            elif  q_s_a == best_q_value:
                best_action.append(action)
        return np.random.choice(np.array(best_action))

    # choose action as per ε-greedy policy
    def get_action(self, state):
        actions = self.get_possible_actions(state)

        if len(actions) == 0:
            return None

        if np.random.random() < self.epsilon:
            a = np.random.choice(actions)
            return a
        else:
            a = self.max_action(state)
            return a

Listing 4-3SARA On-Policy TD Control

图 4-14 显示了随着学习的进展和学习到的最佳策略,每集奖励的图表。我们可以看到回报很快接近最优值。我们学到的策略是通过首先一路向上,然后右转走向目标来避免悬崖。这是令人惊讶的,因为我们本来期望代理学习策略以跳过悬崖并到达目标,与代理学习的策略相比,这将是最短的四步路径。

img/502835_1_En_4_Fig14_HTML.jpg

图 4-14

在 SARSA 和代理学习的策略下学习期间的报酬图

但是,随着我们的策略继续使用ε-greedy 进行探索,总有很小的几率,在某个状态下,当智能体在悬崖细胞旁边时,它随机采取一个动作,掉下悬崖。它表明了即使对环境已经有了足够的了解,也要继续探索的问题,也就是说,当同样的ε-贪婪策略被用于采样和改进时。我们将看到如何在 Q-learning 中避免这个问题,在 Q-learning 中,使用探索性行为策略来执行非策略学习以生成训练样本,并且将确定性策略学习为最佳目标策略。

接下来,我们将研究我们的第一个非策略 TD 算法,称为 Q-learning

Q-Learning:一种非策略 TD 控制

在 SARSA 中,我们使用值为 SARS'A' 的样本,这些样本由以下策略生成。动作来自状态S的一个是使用ε-greedy 策略产生的,该策略随后在 GPI 的“改进”步骤中得到改进。然而,与其从策略中生成 A ' ,不如我们查看所有的Q(S'A ' )并选择动作 A ' ,这使得跨动作 A 的 Q(S ',A ')的值最大化我们可以继续生成样本( SARS ' )(注意编号 A ' 为该元组中的第五个值),使用类似ε-greedy 的探索性策略。但是我们通过选择 A 'arg maxA'Q(S'A ' )来改进策略。这种方法上的小变化创造了一种学习最优策略的新方法,称为 Q-learning 。这不再是一种策略上的学习,而是一种策略外的控制方法,其中样本( SARS ' )是由探索性策略生成的,同时我们最大化Q(S', *A *

我们使用带有ε-贪婪策略的探索来生成样本( SARS ' )。同时,我们通过寻找状态 S ' 中的 Q 最大化动作arg maxA'Q(S'A ' )来利用已有的知识。我们将在第九章讲述更多关于勘探和开采之间的权衡。

q 值的更新规则现在定义如下:

$$ Q\left({S}_t,{A}_t\right)\leftarrow Q\left({S}_t,{A}_t\right)+\alpha \ast \left[{R}_{t+1}+\gamma \ast {\mathit{\max}}_{A_{t+1}}Q\left({S}_{t+1},{A}_{t+1}\right)-Q\left({S}_t,{A}_t\right)\right] $$

(4.10)

将前面的等式与等式( 4.8 )进行比较,你会注意到这两种方法之间的微妙差异,以及这是如何使 Q-learning 成为一种非策略方法的。Q-learning 的非策略行为很方便,它使样本很有效。我们将在稍后讨论体验回放回放缓冲时触及这一点。图 4-15 给出了 Q 学习的伪代码。

Q-learning Off-Policy TD Control

img/502835_1_En_4_Fig15_HTML.png

图 4-15

q-学习,非策略 TD 控制

listing4_4.ipynb中的代码实现了 Q-learning。像 SARSA 一样,它有一个 Q-learning 代理,类似于清单 4-3 中的 SARSA 代理,除了更新规则的变化,它遵循( 4.10 )用于 Q-learning。学习功能与 SARSA 相比也有微小的变化。在 SARSA 中我们不再需要第五个值,即来自'next_action A ' 。我们现在选择最佳行动A同时处于状态S来改进类似于贝尔曼最优方程的策略。来自 SARSA 的其余实现被带到 Q-learning 中。清单 4-4 展示了QLearningAgent的实现。请仔细注意与 SARSA 代理相比,更新规则中的更改。

# Q- Learning agent class
from collections import defaultdict

class QLearningAgent:
    def __init__(self, alpha, epsilon, gamma, get_possible_actions):
        self.get_possible_actions = get_possible_actions
        self.alpha = alpha
        self.epsilon = epsilon
        self.gamma = gamma
        self._Q = defaultdict(lambda: defaultdict(lambda: 0))

    def get_Q(self, state, action):
        return self._Q[state][action]

    def set_Q(self, state, action, value):
        self._Q[state][action] = value

    # Q learning update step
    def update(self, state, action, reward, next_state, done):
        if not done:
            best_next_action = self.max_action(next_state)
            td_error = reward + self.gamma * self.get_Q(next_state, best_next_action) - self.get_Q(state,action)
        else:
            td_error = reward - self.get_Q(state,action)

        new_value = self.get_Q(state,action) + self.alpha * td_error
        self.set_Q(state, action, new_value)

    # get best A for Q(S,A) which maximizes the Q(S,a) for actions in state S
    def max_action(self, state):
        actions = self.get_possible_actions(state)
        best_action = []
        best_q_value = float("-inf")

        for action in actions:
            q_s_a = self.get_Q(state, action)
            if q_s_a > best_q_value:
                best_action = [action]
                best_q_value = q_s_a
            elif  q_s_a == best_q_value:
                best_action.append(action)
        return np.random.choice(np.array(best_action))

    # choose action as per ε-greedy policy for exploration
    def get_action(self, state):
        actions = self.get_possible_actions(state)

        if len(actions) == 0:
            return None

        if np.random.random() < self.epsilon:
            a = np.random.choice(actions)
            return a
        else:
            a = self.max_action(state)
            return a

Listing 4-4Q Learning Off-Policy TD Control

让我们看看 Q-learning 在悬崖世界环境中的应用。每集的奖励随着训练而提高,并达到最佳值-13,而在 SARSA 下为-17。如图 4-16 所示,Q-learning 下更好的政策是显而易见的。在 Q-learning 下,代理学习通过悬崖上方第二行的单元格导航到目标。在 Q-learning 下,agent 在学习一个确定性的策略,我们的环境也是确定性的。换句话说,如果代理人采取向右移动的行动,它肯定会向右移动,并且没有机会在任何其他方向上采取任何随机步骤。因此,代理人学习通过越过悬崖向目标前进的最优策略。

img/502835_1_En_4_Fig16_HTML.jpg

图 4-16

Q 学习和 agent 学习策略下学习过程中的报酬图

文件listing4_4.ipynb还显示了 Q-agent 应用于出租车世界环境时的学习曲线。我们将在下一章的状态值近似的深度学习方法中重新讨论 Q-learning。在这种情况下,Q-learning 被称为 DQN。DQN 及其变体将被用于在一些雅达利游戏上训练游戏代理。

为了结束对 Q-learning 的讨论,让我们看看 Q-learning 可能引入的一个特殊问题,即最大化偏差。

最大化偏差和双重学习

如果你回头看看方程( 4.10 ,你会注意到我们正在对A进行最大化,以获得最大值Q(SA)。类似地,在 SARSA 中,我们发现了一个新的ε-贪婪策略,它也是在 Q 上最大化,以获得具有最高 Q 值的动作。此外,这些 q 值是真实状态-动作值的估计值。总之,我们使用 q 估计的最大值作为最大值的“估计”。这种将“最大估计值”作为“最大估计值”的方法引入了+ve 偏差。

为了看到这一点,考虑一个场景,其中在某些转换中奖励取三个值:5、0、+5,每个值的概率为 1/3。预期回报是零,但当我们看到+5 的时候,我们把它作为最大化的一部分,然后它永远不会下降。因此,+5 成为真实回报的估计值,否则预期值为 0。这是由于最大化步骤而引入的正偏差。

消除+ve 偏差的方法之一是使用一组两个 q 值。一个q-值用于寻找使 q 值最大化的动作,然后另一组 q 值用于寻找该最大动作的 q 值。在数学上,它可以表示如下:

替换??【最大】【a】【q12】(

*我们使用 Q 2 来寻找最大化动作 A ,然后使用 Q 1 来寻找最大 Q 值。可以看出,这种方法消除了+ve 或最大化偏差。当我们谈到 DQN 时,我们将再次讨论这个概念。

预期的 SARSA 控制

让我们看看另一种方法,它是 Q-learning 和 SARSA 的混合体;叫做预期萨萨。它与 Q-learning 类似,只是( 4.10 )中的“max”被替换为一个期望,如下所示:

$$ Q\left({S}_t,{A}_t\right)\leftarrow Q\left({S}_t,{A}_t\right)+\alpha \ast \left[{R}_{t+1}+\gamma \ast {\sum}_a\pi \left(a|{S}_{t+1}\right).Q\left({S}_{t+1},a\right)-Q\left({S}_t,{A}_t\right)\right] $$

(4.11)

由于随机选择了 A t + 1 ,预期的 SARSA 与在 SARSA 中看到的方差相比具有较低的方差。在预期 SARSA 中,我们对所有可能的行为都采用预期,而不是抽样。

在悬崖世界问题中,我们有确定性动作,因此我们可以设置学习速率 α = 1,而不会对学习质量产生任何重大影响。我们给出了算法的伪代码。除了采用期望而不是最大化的更新逻辑之外,它反映了 Q-learning。我们可以将 expected SARSA 作为 on-policy 运行,这是我们在 cliff 和 taxi 环境中测试它时要做的事情。它也可以脱离策略运行,其中行为策略更具探索性,而目标策略 π 遵循确定性贪婪策略。参见图 4-17 。

Expected SARSA TD Control

img/502835_1_En_4_Fig17_HTML.png

图 4-17

预期 SARA TD 控制

文件listing4_5.ipynb显示了预期的 SARSA 代理的代码。它类似于 Q-agent,只是用期望代替了最大化。这种变化增加了算法的计算复杂度,但比 SARSA 和 Q-learning 收敛得更快。清单 4-5 重现了预期的 SARSA 代理类的代码。

# Expected SARSA Learning agent class

class ExpectedSARSAAgent:
    def __init__(self, alpha, epsilon, gamma, get_possible_actions):
        self.get_possible_actions = get_possible_actions
        self.alpha = alpha
        self.epsilon = epsilon
        self.gamma = gamma
        self._Q = defaultdict(lambda: defaultdict(lambda: 0))

    def get_Q(self, state, action):
        return self._Q[state][action]

    def set_Q(self, state, action, value):
        self._Q[state][action] = value

    # Expected SARSA Update
    def update(self, state, action, reward, next_state, done):
        if not done:
            best_next_action = self.max_action(next_state)
            actions = self.get_possible_actions(next_state)
            next_q = 0
            for next_action in actions:
                if next_action == best_next_action:
                    next_q += (1-self.epsilon+self.epsilon/len(actions))* self.get_Q(next_state, next_action)
                else:
                    next_q += (self.epsilon/len(actions))* self.get_Q(next_state, next_action)

            td_error = reward + self.gamma * next_q - self.get_Q(state,action)
        else:
            td_error = reward - self.get_Q(state,action)

        new_value = self.get_Q(state,action) + self.alpha * td_error
        self.set_Q(state, action, new_value)

    # get best A for Q(S,A) which maximizes the Q(S,a) for actions in state S
    def max_action(self, state):
        actions = self.get_possible_actions(state)
        best_action = []
        best_q_value = float("-inf")

        for action in actions:
            q_s_a = self.get_Q(state, action)
            if q_s_a > best_q_value:
                best_action = [action]
                best_q_value = q_s_a
            elif  q_s_a == best_q_value:
                best_action.append(action)
        return np.random.choice(np.array(best_action))

    # choose action as per ε-greedy policy for exploration
    def get_action(self, state):
        actions = self.get_possible_actions(state)

        if len(actions) == 0:
            return None

        if np.random.random() < self.epsilon:
            a = np.random.choice(actions)
            return a
        else:
            a = self.max_action(state)
            return a

Listing 4-5Expected SARSA TD Control

图 4-18 显示了为悬崖世界训练预期的 SARSA 代理的结果。与 SARSA 和 Q-learning 相比,它能最快地收敛到最优值。你可以实验,看看变化的学习率 α 在极限内对收敛没有大的影响。有趣的是,预期 SARA 学习的策略介于 Q-learning 和 SARSA 之间。在这种策略下的代理人通过迷宫的中间一排到达目标。在我们的例子中,我们使用预期的 SARSA 作为 on-policy,即使用相同的ε-greedy 策略进行探索和改进。但可能由于这种期望,它从常规的 SARSA 中学习改进,并发现通过中间一排足够安全。在 Python 笔记本中,你还可以看到对出租车世界运行这个算法的结果。

img/502835_1_En_4_Fig18_HTML.jpg

图 4-18

带有悬崖世界中预期 SARSA 和代理学习的策略的报酬图

重放缓冲和偏离策略学习

离策学习涉及两个独立的策略:行为策略 b ( a | s )探索并生成范例;以及 π ( a | s ),代理正在努力学习的目标策略为最优策略。因此,我们可以反复使用行为策略产生的样本来训练代理。该方法使得过程样本高效,因为由代理观察到的单个转换可以被多次使用。

这叫做体验回放。作为学习过程的一部分,代理从环境中收集经验,并多次重复这些经验。在体验回放中,我们将样本(s, a, r, s', done)存储在缓冲区中。样本是使用探索性行为策略生成的,而我们使用 q 值来改进确定性目标策略。因此,我们总是可以使用行为策略中的旧样本,并一次又一次地应用它们。我们将缓冲区大小固定为某个预先确定的大小,并在收集新样本时不断删除旧样本。该过程通过多次重用样本来提高学习样本的效率。该方法的其余部分与非策略代理相同。

让我们将这种方法应用于 Q-learning agent。这次我们将跳过给出伪代码,因为除了在每次转换中多次使用重放缓冲区中的样本之外,几乎没有任何变化。我们在缓冲器中存储一个新的过渡,然后从缓冲器中采样batch_size个样本。这些样本用于以通常的方式训练 Q-agent。然后,代理在环境中采取另一个步骤,循环再次开始。Listing4_6.ipynb给出了重放缓冲区的实现以及如何在学习算法中使用它。参见清单 4-6 。

class ReplayBuffer:
    def __init__(self, size):
        self.size = size #max number of items in buffer
        self.buffer =[] #array to hold buffer

    def __len__(self):
        return len(self.buffer)

    def add(self, state, action, reward, next_state, done):
        item = (state, action, reward, next_state, done)
        self.buffer = self.buffer[-self.size:] + [item]

    def sample(self, batch_size):
        idxs = np.random.choice(len(self.buffer), batch_size)
        samples = [self.buffer[i] for i in idxs]
        states, actions, rewards, next_states, done_flags = list(zip(*samples))
        return states, actions, rewards, next_states, done_flags

# training algorithm with reply buffer
def train_agent(env, agent, episode_cnt=10000, tmax=10000,
anneal_eps=True, replay_buffer = None, batch_size=16):

    episode_rewards = []
    for i in range(episode_cnt):
        G = 0
        state = env.reset()
        for t in range(tmax):
            action = agent.get_action(state)
            next_state, reward, done, _ = env.step(action)
            if replay_buffer:
                replay_buffer.add(state, action, reward, next_state, done)
                states, actions, rewards, next_states, done_flags = replay_buffer(batch_size)
                for i in range(batch_size):
                    agent.update(states[i], actions[i], rewards[i], next_states[i], done_flags[i])
            else:
                agent.update(state, action, reward, next_state, done)

            G += reward
            if done:
                episode_rewards.append(G)
                # to reduce the exploration probability epsilon over the
                # training period.
                if anneal_eps:
                    agent.epsilon = agent.epsilon * 0.99
                break
            state = next_state
    return np.array(episode_rewards)

Listing 4-6Q-Learning with Replay Buffer

具有重放缓冲器的 Q-agent 应该通过从缓冲器中重复采样来改善初始收敛。当我们看 DQN 时,抽样效率将变得更加明显。从长远来看,在有或没有重放缓冲区的情况下,学习到的最佳值之间不会有任何显著差异。它的另一个优点是打破了样本之间的相关性。当我们用 Q-learning(即 DQN)来看深度学习时,这一点也会变得很明显。

连续状态空间的 q 学习

到目前为止,我们看到的所有例子都有离散的状态空间。迄今为止所研究的所有方法都可以归类为列表法。状态动作空间被表示为一个矩阵,其中状态沿一维,动作沿横轴。

我们将很快过渡到连续状态空间,并大量使用深度学习来通过神经网络表示状态。然而,我们仍然可以用一些简单的方法解决许多连续状态问题。在为下一章做准备时,让我们看看将连续值转换为离散值的最简单的方法。我们将采用的方法是以一定的精度对连续浮点数进行舍入,例如,将-1 到 1 之间的连续状态空间值转换为-1,-0.9,-0.8,… 0,0.1,0.2,… 1.0。

listing4_7.ipynb展示了这种方法的实际应用。我们将继续使用来自listing4_6的 Q 学习代理、经验回复和学习算法。然而,这一次我们将在一个连续的环境中应用这些知识,这个环境就是在本章开始时详细描述的CartPole。我们需要的关键变化是从环境中接收状态值,离散化这些值,然后将其作为观察结果传递给代理。代理只能看到离散值,并使用这些离散值通过 QAgent 学习最佳策略。我们在清单 4-7 中重现了用于将连续状态值转换成离散值的方法。见图 4-19 。

# We will use ObservationWrapper class from gym to wrap our environment.
# We need to implement observation() method which will receive the
# original state values from underlying environment
# In observation() we will discretize the state values
# which then will be passed to outside world by env
# the agent will use these discrete state values
# to learn an effective policy using q-learning
from gym.core import ObservationWrapper

class Discretizer(ObservationWrapper):
    def observation(self, state):
        discrete_x_pos = round(state[0], 1)
        discrete_x_vel = round(state[1], 1)
        discrete_pole_angle = round(state[2], 1)
        discrete_pole_ang_vel = round(state[3], 1)

        return (discrete_x_pos, discrete_x_vel,
                discrete_pole_angle, discrete_pole_ang_vel)

Listing 4-7Q-Learning (Off-Policy) on Continuous State Environment

img/502835_1_En_4_Fig19_HTML.jpg

图 4-19

CartPole 连续状态空间环境下 Q 学习的报酬图

与 200 的最大奖励相比,状态离散化的 Q-agent 能够获得大约 50 的奖励。在后续章节中,我们将研究其他更强大的方法来获得更多奖励。

n 步返回

在本节中,我们将统一 MC 和 TD 方法。MC 方法从一个状态采样返回,直到情节结束,并且它们不引导。因此,MC 方法不能用于连续的任务。另一方面,TD 使用一步回报来估计剩余奖励的价值。TD 方法在一个步骤之后立即对轨迹和自举进行短暂观察。

这两种方法都是两个极端,在很多情况下,中庸之道会产生更好的结果。 n 步的思路是用后面 n 步的奖励,然后从 n+1 步 bootstrap 来估算剩余奖励的价值。图 4-20 显示了 n 的不同值的备份图。一个极端是一步法,这是我们刚刚在 SARSA、Q-learning 和其他相关方法的上下文中看到的 TD(0)方法。另一个极端是∞-步 TD,它只不过是一种 MC 方法。广义的观点是,TD 和 MC 方法是同一连续体的两个极端。

img/502835_1_En_4_Fig20_HTML.jpg

图 4-20

TD(0)和 MC 处于两个极端的 n 步方法的备份图,n=1 和 n=∞(本集结束)

n-step 方法可用于 on-policy 设置。我们可以有 n 步 SARSA 和 n 步预期 SARSA,这些都是我们目前所学内容的自然延伸。然而,在非策略学习中使用 n 步方法需要我们再考虑一个概念,即在行为策略和目标策略下观察特定 n 步状态转换的相对差异。为了使用来自行为策略 b ( a | s )的数据来优化目标策略 π ( a | s ),我们需要将在行为策略下观察到的 n 步回报乘以一个比率,称为重要性抽样比率

考虑一个起始状态 S t 以及动作的轨迹和状态序列直到剧集结束, A tSt+1At+1,…。, S T 。在策略 π 下观察序列的概率由下面给出:

$$ \mathit{\Pr}\left{ trajectory\right}=\pi \left({A}_t\right|{S}_t.p\left({S}_{t+1}\right|{S}_t,{A}_t.\pi \left({A}_{t+1}\right|{S}_{t+1}\dots \dots ..p\left({S}_T\right|{S}_{T-1},{A}_{T-1} $$

重要抽样比是目标策略下轨迹的概率 π 与行为策略下轨迹的概率 b 之比。

$$ {\rho}_{t:T-1}=\frac{{\mathit{\Pr}}_{\pi}\left{ trajectory\right}}{{\mathit{\Pr}}_b\left{ trajectory\right}}={\prod}_{k=t}^{T-1}\frac{\pi \left({A}_k\vee {S}_k\right)}{b\left({A}_k\vee {S}_k\right)} $$

(4.12)

重要性采样比率确保基于在目标策略下观察轨迹的相对机会与在行为策略下观察相同轨迹的机会,向上或向下调整在行为策略下观察到的轨迹的返回。

没有免费的东西,重要性抽样就是这种情况。重要抽样比率会导致很大的差异。另外,这些计算效率不高。有许多先进的技术,如折扣感知重要性采样每决策重要性采样,它们以各种不同的方式查看重要性采样和奖励,以减少方差,并使这些算法有效。

在本书中,我们不会深入讨论实现这些算法的细节。我们的重点是在概念层面上介绍这些,并让您了解先进的技术。

资格跟踪和 TD(λ)

资格追踪以算法有效的方式统一了 MC 和 TD 方法。当 TD 方法与合格跟踪结合时,产生 TD(λ),其中 λ = 0,使其相当于我们到目前为止所研究的一步 TD。这就是一步 TD 也被称为 TD(0)的原因。 λ = 1 的值使其类似于常规的∞-步长 TD,或者换句话说,类似于 MC 方法。资格追踪使得在非周期性任务中应用 MC 方法成为可能。我们将只涵盖资格追踪和 TD( λ )的高级概念。

在上一节中,我们讨论了 n 步回报,其中 n=1 表示常规 TD 方法,n=∞表示 MC 方法。我们也提到了两个极端都不好的事实。一种算法在 n 的某个中间值时表现最佳,n-step 提供了如何统一 TD 和 MC 的观点。资格所做的是提供一种有效的方法来组合它们,而不需要跟踪每一步的 n 步转换。到目前为止,我们已经研究了一种基于未来的下一个 n 转换来更新状态值的方法。这叫做前视。然而,你也可以向后看,即,在每个时间步 t ,并且看到在时间步 t 的奖励在过去对前面的 n 状态的影响。这就是所谓的后视,构成了 TD 的核心( λ )。该方法允许在 TD 学习中集成 n 步返回的有效实现。

回头看图 4-20 。如果不是选择不同的 n 值,而是将所有的 n 步收益与某个权重相结合,会怎么样?这就是所谓的λ-返回,等式如下:

$$ {G}_t^{\lambda }=\left(1-\lambda \right){\sum}_{n=1}{T-t-1}{\lambda}{n-1}{G}_{t:t+n}+{\lambda}^{T-t-1}{G}_t $$

(4.13)

这里,Gt:t+n是在第 n 步结束时使用剩余步骤的引导值的 n 步返回。其定义如下:

$$ {G}_{t:t+n}={R}_{t+1}+\gamma {R}_{t+2}+\dots +{\gamma}{n-1}{R}_{t+n}+{\gamma}nV\left({S}_{t+n}\right) $$

(4.14)

如果我们把 λ = 0 放入( 4.13 ,我们得到如下:

$$ {G}_t⁰={G}_{t:t+1}={R}_{t+1}+\gamma V\left({S}_{t+1}\right) $$

前面的表达式类似于( 4.7 )中 TD(0)状态动作更新的目标值。

另一方面,将 λ = 1 放入( 4.13 )使$$ {G}_t¹ $$模仿 MC 并返回 G * t * 如下:

$$ {G}_t¹={G}_t={R}_{t+1}+\gamma {R}_{t+2}+\dots +{\gamma}^{T-t-1}{R}_T $$

TD(λ)算法使用先前的λ-return 和被称为合格跟踪的跟踪向量,以使用“后向”视图获得有效的在线 TD 方法。资格跟踪记录了一个州在过去多长时间内被观察到,以及该州的估计值将在多大程度上受到当前观察到的回报的影响。

我们将在这里停止对λ回报、合格跟踪和 TD(λ)的基本介绍。关于数学推导以及基于它们的各种算法的详细回顾,请参考《强化学习:巴尔托和萨顿的介绍》一书,第二版。

DP、MC 和 TD 之间的关系

在本章的开始,我们讨论了 DP、MC 和 TD 方法的比较。我们先介绍 MC 方法,然后介绍 TD 方法,然后我们使用 n 步和 TD(λ)将 MC 和 TD 结合起来,作为基于样本的无模型学习的两个极端。

作为本章的总结,表 4-1 总结了 DP 和 TD 方法的比较。

表 4-1

贝尔曼方程背景下 DP 和 TD 方法的比较

|   |

完整备份(DP)

|

示例备份(TD)

|
| --- | --- | --- |
| 贝尔曼期望方程为vπ(s | 迭代策略评估 | TD 预测 |
| 贝尔曼期望方程为 q π ( sa | q-策略迭代 | 撒尔沙 |
| 贝尔曼最优方程为qπ(sa ) | q 值迭代 | q 学习 |

摘要

在这一章中,我们看了强化学习的无模型方法。我们从使用蒙特卡罗方法估计状态值开始。我们研究了“首次就诊”和“每次就诊”的方法。然后,我们从总体上,特别是在 MC 方法的背景下,研究了偏差和方差的权衡。在 MC 估算的基础上,我们将 MC 控制方法与第三章中介绍的政策改进 GPI 框架联系起来。我们看到了如何通过将基于 DP 的方法的估计步骤换成基于 MC 的方法来应用 GPI。我们详细研究了需要平衡的勘探开发困境,尤其是在转换概率未知的无模型世界中。然后,我们简要地讨论了 MC 方法上下文中的非策略方法。

TD 是我们研究的关于无模型学习的下一个方法。我们从建立 TD 学习的基础开始,从基于 TD 的价值评估开始。接下来是对 SARSA 的深入研究,这是一种政策性 TD 控制方法。然后我们研究了 Q-learning,一种强大的非策略 TD 学习方法,以及它的一些变体,如 expected SARSA。

在 TD 学习的背景下,我们还引入了状态近似的概念,将连续状态空间转换为近似的离散状态值。状态近似的概念将构成下一章的主要内容,并将允许我们将深度学习与强化学习结合起来。

在结束这一章之前,我们最后看了 n 步回报、合格轨迹和 TD(λ)作为将 TD 和 MC 结合到一个单一框架中的方法。******

五、函数近似

在前三章中,我们研究了规划和控制的各种方法,首先使用动态规划(DP),然后使用蒙特卡罗方法(MC),最后使用时间差分(TD)方法。在所有这些方法中,我们总是着眼于状态空间和动作都是离散的问题。只有在前一章快结束时,我们才谈到连续状态空间中的 Q 学习。我们使用任意方法离散化状态值,并训练一个学习模型。在这一章中,我们将通过讨论近似的理论基础以及它如何影响强化学习的设置来扩展这种方法。然后,我们将研究近似数值的各种方法,首先是具有良好理论基础的线性方法,然后是专门针对神经网络的非线性方法。深度学习与强化学习相结合的这一方面是最令人兴奋的发展,它将强化学习算法推向了规模。

通常,该方法将在预测/估计设置的上下文中查看一切,其中代理试图遵循给定的策略来学习状态值和/或动作值。接下来将讨论控制,即寻找最优策略。我们将继续处于一个没有模型的世界,在那里我们不知道转变的动力。然后我们将讨论函数近似世界中的收敛性和稳定性问题。到目前为止,在精确和离散的状态空间中,收敛性还不是一个大问题。然而,函数近似带来了新的问题,需要考虑的理论保证和实际的最佳做法。我们还将触及批处理方法,并将它们与本章第一部分讨论的增量学习方法进行比较。

我们将以深度学习、基础理论和使用 PyTorch 和 TensorFlow 构建/训练模型的基础的快速概述来结束这一章。

介绍

强化学习可用于解决具有许多离散状态配置的非常大的问题或具有连续状态空间的问题。考虑双陆棋游戏,它有接近 10 个 20 个离散状态,或者考虑围棋游戏,它有接近 10 个 170 个离散状态。也考虑像自动驾驶汽车、无人机或机器人这样的环境:这些都有一个连续的状态空间。

到目前为止,我们已经看到了状态空间离散且规模较小的问题,例如大约有 100 个状态的网格世界或有 500 个状态的出租车世界。我们如何将目前所学的算法扩展到更大的环境或具有连续状态空间的环境?一直以来,我们都是用一个表格来表示状态值 V ( s )或动作值 Q ( sa ),每个状态值 s 或状态值 s 和动作值 a 的组合都有一个条目。随着数量的增加,表的大小将变得巨大,使得在表中存储状态或动作值变得不可行。此外,会有太多的组合,这会减慢策略的学习。该算法可能花费太多时间处于在环境的实际运行中概率非常低的状态。

我们现在将采取不同的方法。让我们用下面的函数来表示状态值(或状态-动作值):

$$ \hat{v}\left(s;w\right)\approx {v}_{\pi }(s) $$

$$ \hat{q}\left(s,a;w\right)\approx {q}_{\pi}\left(s,a\right) $$

(5.1)

它们现在由函数$$ \hat{v}\left(s;w\right)\ \mathrm{or}\ \hat{q}\left(s,a;w\right) $$表示,而不是在表中表示值,其中参数 w 取决于代理遵循的策略,其中 s 或( sa )是状态或状态值函数的输入。我们选择参数∣ * w * ∣的数量比状态∣ * s * ∣的数量或者状态-动作对的数量(∣sxa∣).)小得多这种方法的结果是存在状态-动作值的状态表示的一般化。当我们基于给定状态 s 的某个更新等式来更新权重向量 w 时,它不仅更新该特定状态 s 或( sa )的值,而且还更新与更新所针对的原始状态或( sa )接近的许多其他状态或状态动作的值这取决于函数的几何形状。如前所示, s 附近的其他状态值也将受到这种更新的影响。我们用一个比状态数更受限制的函数来近似这些值。具体来说,我们现在不是直接更新 v ( s )或 q ( sa ),而是更新函数的参数集 w ,这反过来会影响估计值$$ \hat{v}\left(s;w\right)\ \mathrm{or}\ \hat{q}\left(s,a;w\right) $$。当然,像以前一样,我们使用 MC 或 TD 方法执行 w 更新。函数近似有多种方法。我们可以提供状态向量(表示状态的所有变量的值,例如位置、速度、地点等。)并得到$$ \hat{v}\left(s;w\right) $$,或者我们可以输入状态和动作向量并得到$$ \hat{q}\left(s,a;w\right) $$作为输出。在动作是离散的并且来自一个小集合的情况下,另一种非常占优势的方法是馈送状态向量 s 并且得到∣ * A * ∣数$$ \hat{q}\left(s,a;w\right) $$,一个对应一个可能的动作(∣ * A * ∣表示可能动作的数量)。图 5-1 为示意图。

img/502835_1_En_5_Fig1_HTML.jpg

图 5-1

使用函数近似法表示$$ \hat{v}\left(s;w\right) $$$$ \hat{q}\left(s,a;w\right) $$的方法。第一个和最后一个是我们在本章中最常用的

有各种方法来构建这样的函数近似器,但我们将探索两种常见的方法:使用平铺的线性近似器和使用神经网络的非线性近似器。

然而,在我们这样做之前,我们需要重温理论基础,看看需要什么操作来使 w 移动,以便我们连续减少目标值和状态或状态动作值的当前估计值之间的误差, v ( sq ( sa )。

近似理论

函数近似是监督学习领域中广泛研究的主题,其中基于训练数据,我们构建底层模型的一般化。监督学习的大部分理论可以应用于函数近似的强化学习。然而,函数近似的 RL 也带来了新的问题,如如何引导以及它对非平稳性的影响。在监督学习中,当算法正在学习时,生成训练数据的问题/模型不会改变。然而,当涉及到函数近似的 RL 时,目标(在监督学习中标记为输出)的形成方式,会诱发非平稳性,我们需要想出新的方法来处理它。我们所说的非平稳是指我们不知道 v ( s )或 q ( sa )的实际目标值。我们使用 MC 或 TD 方法来形成评估,然后使用这些评估作为“目标”当我们改进我们对目标值的估计时,我们使用修改后的估计作为新的目标。在监督学习中,情况就不同了;目标在培训期间给出并固定。学习算法对目标没有影响。在强化学习中,我们没有实际的目标,我们使用目标值的估计值。随着这些估计值的改变,学习算法中使用的目标也会改变;即,它们在学习期间不是固定的或静止的。

让我们重温一下 MC(方程 4.2)和 TD(方程 4.4)的更新方程,复制在这里。我们修改了等式,使 MC 和 TD 对当前时间使用相同的下标 t ,对下一时刻使用相同的下标 t + 1。两个等式执行相同的更新以将Vt(s)移动到更接近其目标,在 MC 更新的情况下是Gt(s),而Rt+1+γ

$$ {V}_{t+1}(s)={V}_t(s)+\alpha\ \left[{G}_t(s)-{V}_t(s)\right] $$

(5.2)

$$ {V}_{t+1}(s)={V}_t(s)+\alpha\ \left[{R}_{t+1}+\gamma \ast {V}_t\left({s}^{\prime}\right)-{V}_t(s)\right] $$

(5.3)

这和我们在监督学习,尤其是线性最小二乘回归中所做的事情类似。我们有输出值/目标值 y ( t ),我们有输入特征 x ( t ),统称为训练数据。我们可以选择一个模型模型w[x(t)】像多项式线性模型,决策树,或者支持向量,甚至像神经网络这样的其他非线性模型。训练数据用于最小化模型预测值和训练集中实际输出值之间的误差。称为最小化损失函数,表示如下:

$$ J(w)={\left[y(t)-\hat{y}\left(t;w\right)\right]}²; where\ \hat{y}\left(t;w\right)={Model}_w\left[x(t)\right] $$

(5.4)

J ( w )是一个可微函数时,这将是本书中的情况,我们可以使用梯度下降来调整模型的权重/参数 w ,以最小化误差/损失函数 J ( w )。通常,使用相同的训练数据分批多次进行更新,直到损失停止进一步减少。权重为 w 的模型现在已经学习了从输入 x ( t )到输出 y ( t )的底层映射。执行增量更新的方式在以下等式中给出:

j(w)wrtw∴=∇wj(w)

对于给定的损失函数:$$ {\Delta }_w\ J(w)=-2\ast \left[y(t)-\hat{y}\left(t;w\right)\right]\ast {\nabla}_w\hat{y}\left(t;w\right) $$

调整 w ,我们在∈wJ(w)的负方向迈一小步,这样会减少误差。

$$ {w}_{t+1}={w}_t-\alpha .{\nabla}_w\ J(w) $$

(5.5)

权重沿着使损失最小化的方向移动,即,实际输出值和预测输出值之间的差异。接下来,让我们花点时间讨论函数近似的各种方法。最常见的方法如下:

  • 特征的线性组合。我们结合特征(如速度、速率、位置等。)通过向量 w 加权,并将计算值用作状态值。常见的方法如下:

    • 多项式

    • 傅立叶基函数

    • 径向基函数

    • 粗略编码

    • 瓦片编码

  • 非线性但可区分的方法,其中神经网络是最受欢迎和当前趋势的方法。

  • 非参数的,基于记忆的方法。

在本书中,我们将主要讨论基于深度学习的神经网络方法,这些方法适合非结构化输入,如代理的视觉系统捕获的图像或使用自然语言处理(NLP)的自由文本。本章的后面部分和下一章将致力于使用基于深度学习的函数近似,我们将看到许多使用 PyTorch 和 TensorFlow 的完整代码示例的变体。但是我们太超前了。让我们首先检查几种常见的线性近似方法,如粗编码和瓦片编码。由于本书的重点是深度学习在强化学习中的应用,我们不会花太多时间讨论其他各种线性近似方法。同样,仅仅因为我们没有把时间花在所有的线性方法上,并不意味着它们缺乏有效性。根据手头的问题,线性近似方法可能是正确的方法;它有效、快速,并且有收敛保证。

粗略编码

我们来看看图 2-2 中讨论过的山地车问题。汽车有一个二维状态,一个位置,和一个速度。假设我们将二维状态空间分成重叠的圆,每个圆代表一个特征。如果状态 S 位于圆内,则该特定特征存在且值为 1;否则,该特征不存在,其值为 0。特征的数量是圆的数量。假设我们有 p 圈;然后,我们将二维连续状态空间转换为 p 维状态空间,其中每个维度可以是 0 或 1。换句话说,每个维度可以属于{0,1}。

Note

{0,1}表示一组可能的值,即 0 或 1。带有常规括号的(0,1)表示值的范围,即从 0 到 1 的任何值,不包括 0 和 1。[0,1]表示 0 到 1 之间的值的范围以及左边的值;即 0 包括在该范围内。

状态 S 所在的圆圈所代表的所有特征将为“开”或等于 1。图 5-2 给出了一个例子。图中显示了两种状态,根据这些点所在的圆圈,相应的功能将被打开,而其他功能将被关闭。概化将取决于圆圈的大小以及圆圈的密集程度。如果用一个椭圆来代替圆,则推广将更多地在伸长的方向上进行。我们也可以选择圆形以外的形状来控制泛化的数量。

img/502835_1_En_5_Fig2_HTML.jpg

图 5-2

使用圆的二维粗编码。概括取决于圆的大小以及圆放在一起的密度

现在考虑大而密集的圆圈的情况。在两个遥远的州连接在一起的地方,一个大圆使最初的概化变宽,因为它们至少落入一个公共圆内。然而,密度(即圆的数量)允许我们控制细粒度的概括。通过使用多个圆,我们可以确保相邻的州之间至少有一个不同的特征。即使每个单独的圆圈都很大,也是如此。借助于具有不同圆形尺寸和圆形数量的实验,可以微调圆形的尺寸和数量,以控制适合于所讨论的问题/领域的一般化。

平铺编码

图块编码是一种可以通过编程进行规划的粗略编码形式。它适用于多维空间,比一般的粗编码有用得多。

让我们考虑一个二维空间,就像我们刚刚谈到的山地车。我们把空间分成覆盖整个空间的不重叠的网格。每一个这样的划分都称为平铺,如图 5-3 左图所示。这里的瓦片是正方形的,并且根据状态 S 在这个 2D 空间上的位置,只有一个瓦片是 1,而所有其他瓦片都是 0。

然后我们有许多这样的平铺相互偏移。假设我们使用 n 个 tilings 那么对于一个状态,每个瓷砖中只有一个瓷砖是开的。换句话说,如果有 n 个镶嵌,那么正好 n 个特征将是 1,n 个镶嵌中的每一个的单个特征。图 5-3 给出了一个例子。

img/502835_1_En_5_Fig3_HTML.jpg

图 5-3

瓷砖编码。如左图所示,我们在一个单幅图块中有 4×4=16 个图块。我们有四个相互重叠的镶嵌,如右图中四种不同的颜色所示。一个状态(绿色圆点)照亮每个瓷砖中的一个瓷砖。泛化由单个分块中的分块数量以及分块总数控制

注意,如果步长的学习率是等式( 5.1 和( 5.2 )中的α(α),我们现在将它替换为$$ \frac{\alpha }{n} $$,其中 n 是分块的数量。这是为了使算法不受镶嵌数量的限制。由于粗编码和平铺编码都使用二进制特征,数字计算机可以加快计算速度。

概括的性质现在取决于以下因素:

  • 单个瓷砖中的瓷砖数量(图 5-3 中的左图)

  • 镶嵌的数量(图 5-4 中的右图显示了四种不同颜色的四个镶嵌)

  • 偏移的性质,无论是均匀的、对称的还是不对称的

有一些通用的策略来决定前面的数字。考虑这样一种情况,单个瓷砖中的每个瓷砖都是宽度为 w 的正方形。对于一个 k 维的连续空间,它将是一个 k 维的正方形,每边宽 w 。假设我们有 n 个镶嵌,因此这些镶嵌需要在所有维度上相互偏移$$ \frac{w}{n} $$的距离。这被称为位移矢量。第一个启发式是选择 n 使得n= 2I≥4k。每个方向的位移都是(1,3,5,7,…)的奇数倍。,2k1)的位移矢量$$ \frac{w}{n} $$。在我们即将到来的例子中,我们将使用一个库来帮助我们将二维山地汽车状态空间划分成适当的分块。我们将提供 2D 状态向量,库将返回给我们活动的瓦片向量。

近似值的挑战

虽然我们利用了基于监督学习的方法的知识,如前面解释的梯度下降,但我们必须记住两件事,与监督学习相比,基于梯度的方法在强化学习中更难工作。

首先,在监督学习中,训练数据保持不变。数据是从模型中生成的,当我们这样做时,模型不会改变。这是一个给我们的基本事实,我们试图通过使用数据来了解输入到输出的映射方式。提供给训练算法的数据是算法外部的,并且它不以任何方式依赖于算法。它被给定为常数,与学习算法无关。不幸的是,在 RL 中,尤其是在无模型设置中,情况并非如此。用于生成训练样本的数据基于代理所遵循的策略,它并不是底层模型的完整图像。随着我们对环境的探索,我们了解了更多,并生成了一组新的训练数据。我们或者使用基于 MC 的方法观察实际轨迹,或者使用 TD 下的 bootstrap 来形成目标值的估计值 y(t) 。随着我们探索和学习得越来越多,目标 y(t) 会发生变化,这与监督学习的情况不同。这就是所谓的非稳定目标问题。

第二,监督学习基于样本彼此不相关的理论前提,数学上称为 i.i.d .(独立同分布)数据。然而,在 RL 中,我们看到的数据取决于代理生成数据所遵循的策略。在给定的事件中,我们看到的状态取决于代理在该时刻遵循的策略。稍后时间步骤中出现的状态取决于代理先前采取的操作(决策)。换句话说,数据是相关的。我们看到的下一个状态st+1取决于当前状态 s t 和动作atagent 在该状态下采取的动作。

这两个问题使得 RL 设置中的函数近似更加困难。在此过程中,我们将看到为应对这些挑战而采取的各种方法。

对该方法有了大致的了解后,现在是时候开始我们通常的课程了,首先查看值预测/估计以学习可以表示值函数的函数。然后我们将看看控制方面,即代理试图优化策略的过程。它将遵循使用通用策略迭代(GPI)的通常模式,就像前一章中的方法一样。

增量预测:MC,TD,TD(λ)

在本节中,我们将研究预测问题,即如何使用函数近似来估计状态值。

接下来,让我们尝试使用( 5.4 中的损失函数和( 5.5 中的权重更新)将使用由输入和目标组成的训练数据找到模型的监督训练过程扩展到 RL 下的函数近似。如果您比较( 5.4 中的损失函数和( 5.2 和( 5.3 )中的 MC/TD 更新,您可以通过将 MC 和 TD 更新视为操作来进行比较,这些操作试图最小化实际目标vπ(s)和当前估计值v( 我们可以将损失函数表示如下:

$$ J(w)={E}_{\pi }{\left[{V}_{\pi }(s)-{V}_t(s)\right]}² $$

(5.6)

遵循与( 5.5 )中相同的推导,并使用随机梯度下降(即,在每个样本用更新替换期望值),我们可以将权重向量的更新方程写成如下:

$$ {w}_{t+1}={w}_t-\alpha .{\nabla}_w\ J(w) $$

**$$ {w}_{t+1}={w}_t+\alpha .\left[{V}_{\pi }(s)-{V}_t\left(s;w\right)\right].{\nabla}_w\ {V}_t\left(s;w\right) $$

(5.7)

但是,与监督学习不同,我们没有实际/目标输出值Vπ(s);相反,我们使用这些目标的估计值。对于 MC,Vπ(s)的估计/目标是Gt(s), w ,而 TD(0)下的估计/目标是Rt+1因此,在 MC 和 TD(0)下用函数近似的更新可以写成如下。

下面是 MC 更新:

$$ {w}_{t+1}={w}_t+\alpha .\left[{G}_t(s)-{V}_t\left(s;w\right)\right].{\nabla}_w\ {V}_t\left(s;w\right) $$

(5.8)

下面是 TD(0)更新:

$$ {w}_{t+1}={w}_t+\alpha .\left[{R}_{t+1}+\gamma \ast {V}_t\left({s}^{\prime };w\right)-{V}_t\left(s;w\right)\right].{\nabla}_w\ {V}_t\left(s;w\right) $$

(5.9)

对于 q 值,可以写出一组类似的等式。我们将在下一节看到这一点。这与我们在前一章中对 MC 和 TD 控制部分所做的是一样的。

让我们首先考虑线性近似的设置,其中状态值$$ \hat{v}\left(s;w\right) $$可以表示为状态向量 x(s) 和权重向量 w :

$$ \hat{v}\left(s;w\right)=x{(s)}^T.w=\sum \limits_i{x}_i(s)\ast {w}_i $$

(5.10)的点积

$$ \hat{v}\left(s;w\right) $$相对于 w 的导数现在将只是状态向量 x ( s )。

$$ {\Delta }_w\ {V}_t\left(s;w\right)=x(s) $$

(5.11)

将( 5.11 )与方程式( 5.7 )结合起来,我们得到如下:

$$ {w}_{t+1}={w}_t+\alpha .\left[{V}_{\pi }(s)-{V}_t\left(s;w\right)\right].x(s) $$

(5.12)

如前所述,我们不知道真实的状态值Vπ(s),因此我们在 MC 方法中使用估计值Gt(s)和估计值Rt+1+γ这给出了线性近似情况下 MC 和 TD 的权重更新规则如下。

下面是 MC 更新:

$$ {w}_{t+1}={w}_t+\alpha .\left[{G}_t(s)-{V}_t\left(s;w\right)\right].x(s) $$

(5.13)

下面是 TD(0)更新:

$$ {w}_{t+1}={w}_t+\alpha .\left[{R}_{t+1}+\gamma \ast {V}_t\left({s}^{\prime };w\right)-{V}_t\left(s;w\right)\right].x(s) $$

(5.14)

简单来说,权重的更新,即( 5.14 )右侧的第二项,可以表示如下:

更新=学习率 x 预测误差 x 特征值

让我们把它与上一章看到的离散状态的基于表格的方法联系起来。我们将说明查表是线性方法的一个特例。考虑 x(s)的每个分量或者是 1 或者是 0,并且它们中只有一个可以具有值 1,而所有其余的特征都是 0。 x ( s )是一个大小为 p 的列向量,其中在任何一点只有一个元素的值为 1,其余的元素都等于 0。根据代理所处的状态,相应的元素将是 1。

$$ {x}^{table}(s)=\left(\genfrac{}{}{0pt}{}{1\left(s={s}_1\right)}{\begin{array}{c}..\ {}..\ {}1\left(s={s}_p\right)\end{array}}\right) $$

权重向量包括每个 s = s 1 ,s 2 ,… s p 的状态 v(s)的值。

$$ \left(\genfrac{}{}{0pt}{}{w_1}{\begin{array}{c}..\ {}..\ {}{w}_p\end{array}}\right)=\left(\genfrac{}{}{0pt}{}{v\left({s}_1\right)}{\begin{array}{c}..\ {}..\ {}v\left({s}_1\right)\end{array}}\right) $$

使用( 5.10 )中的这些表达式,我们得到如下:

$$ \hat{v}\left(s={s}_k;w\right)=x{(s)}^T.w=v\left({s}_k\right) $$

我们在线性更新方程( 5.13 )和( 5.14 )中使用该表达式,以从第四章中获得熟悉的更新规则:

下面是 MC 更新:

$$ {v}_{t+1}(s)={v}_t(s)+\alpha .\left[{G}_t(s)-{V}_t(s)\right],\kern0.5em s\ \epsilon\ {s}_1,{s}_1,\dots, {s}_p $$

(5.15)

下面是 TD(0)更新:

$$ {v}_{t+1}(s)={v}_t(s)+\alpha .\left[{R}_{t+1}+\gamma \ast {V}_t\left({s}^{\prime}\right)-{V}_t(s)\right],\kern0.5em s\ \epsilon\ {s}_1,{s}_1,\dots, {s}_p $$

(5.16)

前面的推导是将表查找作为更一般的线性函数近似的特例。

还有一点需要注意,我们在推导更新方程时忽略了,MC 中的目标估计点Gt(s)和Rt+1+γVt(s’举个例子,我们再重温一下( 5.6 )用 TD 目标替换Vπ(s)然后取梯度。

$$ loss:\kern1.5em J(w)={\left[{V}_{\pi }(s)-{V}_t(s)\right]}² $$

$$ or,\kern0.5em J(w)={\left[{R}_{t+1}+\gamma \ast {V}_t\left({s}^{\prime };w\right)-{V}_t\left(s;w\right)\right]}² $$

如果我们对 J ( w )对 w 求导,我们实际上会得到两项,一项是由于Vt(s)的求导; w ),下一个状态,又一个因Vt(s)而衍生的项; w )。这样一种取两者梯度贡献的方法∇vt(sw )和∇vt(sw )恶化学习速度。首先,原因是我们希望目标保持不变,因此我们需要忽略∇vt(s)的贡献; w )。第二,概念上用梯度下降我们试图拉当前状态的值Vt(sw )向着它的目标前进。取第二贡献项∇vt(sw )意味着我们正试图将下一个状态S = S的值移向当前状态 S=s 的值。

综上,我们只取当前状态值的导数Vt(sw )并忽略下一个状态值的导数Vt(sw 。该方法使得值估计看起来类似于利用固定目标的监督学习所使用的方法。这也是为什么有时( 5.8 )和( 5.9 )中使用的梯度下降法也被称为半梯度法的原因。

正如我们前面提到的,算法的收敛性不再得到保证,不像我们在表格设置中由于收缩定理而得到的保证。然而,大多数经过仔细考虑的算法在实践中确实是收敛的。表 5-1 显示了各种预测/估计算法的收敛性。我们将不详细解释这些收敛特性。这样的讨论更适合一本侧重于学习的理论方面的书。我们的是一个实用的,有足够的理论来理解背景和欣赏算法的细微差别,核心焦点是在 PyTorch 或 TensorFlow 中编码这些算法。

表 5-1

预测/估计算法的收敛性

|

保单类型

|

算法

|

表格查找

|

线性的

|

非线性的

|
| --- | --- | --- | --- | --- |
| 政策上的 | 主持人 TD(0)TD(λ) | YYY | YYY | Y 普通普通 |
| 不符合政策 | 主持人 TD(0)TD(λ) | YYY | Y 普通普通 | Y 普通普通 |

在后面的章节中,我们将看到自举(例如 TD)、函数近似和偏离策略的组合在一起会对稳定性产生不利影响,除非仔细考虑学习过程。

现在让我们看看控制问题,即如何用函数近似来优化策略。

增量控制

就像上一章一样,我们将遵循类似的方法。我们从函数近似开始来估计 q 值。

$$ \hat{q}\left(s,a;w\right)\approx {q}_{\pi}\left(s,a\right) $$

(5.17)

像以前一样,我们在目标值和当前值之间形成一个损失函数。

$$ J(w)={E}_{\pi}\left[{\left({q}_{\pi}\left(s,a\right)-\hat{q}\left(s,a;w\right)\right)}²\right] $$

(5.18)

相对于 w 损失最小,进行随机梯度下降:

$$ {w}_{t+1}={w}_t-\kern0.5em \alpha .{\nabla}_wJ(w) $$

在哪里,

$$ {\nabla}_wJ(w)=\Big({q}_{\pi}\left(s,a\right)-\hat{q}\left(s,a;w\right) $$$$ {\nabla}_w\hat{q}\left(s,a;w\right) $$ (5.19)

像之前一样,我们可以在$$ \hat{q}\left(s,a;w\right) $$$$ \hat{q}\left(s,a;w\right)=x{\left(s,a\right)}^T.w $$使用线性近似时简化方程。在前面所示的线性情况下,导数$$ {\nabla}_w\hat{q}\left(s,a;w\right) $$将变成$$ {\nabla}_w\hat{q}\left(s,a;w\right)=x\left(s,a\right) $$

接下来,由于我们不知道真实的 q 值q【π】(sa ),我们用使用 MC 或 TD 的估计来替换它,给我们一组等式。

下面是 MC 更新:

$$ {w}_{t+1}={w}_t+\alpha .\left[{G}_t(s)-{q}_t\left(s,a;w\right)\right].{\nabla}_w\ {q}_t\left(s,a\right) $$

(5.20)

下面是 TD(0)更新:

$$ {w}_{t+1}={w}_t+\alpha .\left[{R}_{t+1}+\gamma \ast {q}_t\left({s}^{\prime },{a}^{\prime };w\right)-{q}_t\left(s,a;w\right)\right].{\nabla}_w\ {q}_t\left(s,a;w\right) $$

(5.21)

这些等式允许我们进行 q 值估计/预测。这是广义策略迭代的评估步骤,我们执行多轮梯度下降来改进给定策略的 q 值估计,并使其接近实际目标值。

评估之后是贪婪的政策最大化来改进政策。图 5-4 显示了函数近似 GPI 下的迭代过程。

img/502835_1_En_5_Fig4_HTML.jpg

图 5-4

具有函数近似的广义策略迭代

半梯度 N 步 SARSA 控制

让我们将等式( 5.9 )用于 SARSA 政策控制制度中的 TD 目标。代理使用当前策略对环境进行采样,并观察状态、动作、奖励、下一个状态、下一个动作( s ta trt+1st+1a图 5-5 显示了完整的算法。**

Semi-Gradient N-Step SARSA Control (Episodic)

img/502835_1_En_5_Fig5_HTML.png

图 5-5

用于事件控制的 n 步半梯度 SARSA

w 更新是等式( 5.20 ,目标是 G ,n 步返回。在我们对山地汽车的 n 步 SARSA 的例子中,我们将使用 tile 编码,一种二进制特征近似器。在我们的设置中:

$$ \hat{q}\left(S,A;w\right)=x{\left(S,A\right)}^T.w $$;其中 x ( SA )是瓦片编码的特征向量

据此,$$ \nabla \hat{q}\left({S}_{\tau },{A}_{\tau },w\right)=x\left(S,A\right) $$

在清单 5-1 中,我们有一个类QEstimator来保存权重并执行平铺。函数get_active_featuresS 的连续二维值和离散输入动作 A 作为输入,返回平铺编码的active_feature x ( SA ),即对于给定( SA )有效的二进制平铺特征。函数q_predict也将 SA 作为输入,并返回估计值$$ \hat{q}\left(S,A;w\right) $$ = * x * ( SA)Tw 。它在内部调用get_active_features来首先获得特征,并与权重向量进行点积。图 5-5 中算法末尾所示的权重更新方程是函数q_update所执行的。函数get_eps_greedy_action使用ε贪婪$$ \hat{q}\left({S}_0,\bullet; w\right) $$进行动作选择。

另一个函数sarsa_n实现图 5-5 给出的算法,根据需要调用QEstimator中的函数。类似于前一章中的许多例子,我们也有一个助手函数plot_rewards来随着训练的进行绘制每集的奖励。清单 5-1 给出了代码(listing5_1.ipynb)。

class QEstimator:

    def __init__(self, step_size, num_of_tilings=8, tiles_per_dim=8, max_size=2048, epsilon=0.0):
        self.max_size = max_size
        self.num_of_tilings = num_of_tilings
        self.tiles_per_dim = tiles_per_dim
        self.epsilon = epsilon
        self.step_size = step_size / num_of_tilings

        self.table = IHT(max_size)

        self.w = np.zeros(max_size)

        self.pos_scale = self.tiles_per_dim / (env.observation_space.high[0] \
                                               - env.observation_space.low[0])
        self.vel_scale = self.tiles_per_dim / (env.observation_space.high[1] \
                                               - env.observation_space.low[1])

    def get_active_features(self, state, action):
        pos, vel = state
        active_features = tiles(self.table, self.num_of_tilings,
                     [self.pos_scale * (pos - env.observation_space.low[0]),
                     self.vel_scale * (vel- env.observation_space.low[1])],
                     [action])
        return active_features

    def q_predict(self, state, action):
        pos, vel = state
        if pos == env.observation_space.high[0]:  # reached goal
            return 0.0
        else:
            active_features = self.get_active_features(state, action)
            return np.sum(self.w[active_features])

    # learn with given state, action and target
    def q_update(self, state, action, target):
        active_features = self.get_active_features(state, action)
        q_s_a = np.sum(self.w[active_features])
        delta = (target - q_s_a)
        self.w[active_features] += self.step_size * delta

    def get_eps_greedy_action(self, state):
        pos, vel = state
        if np.random.rand() < self.epsilon:
            return np.random.choice(env.action_space.n)
        else:
            qvals = np.array([self.q_predict(state, action) for action in range(env.action_space.n)])
            return np.argmax(qvals)

########################
def sarsa_n(qhat, step_size=0.5, epsilon=0.0, n=1, gamma=1.0, episode_cnt = 10000):
    episode_rewards = []
    for _ in range(episode_cnt):
        state = env.reset()
        action = qhat.get_eps_greedy_action(state)
        T = float('inf')
        t = 0
        states = [state]
        actions = [action]
        rewards = [0.0]
        while True:
            if t < T:
                next_state, reward, done, _ = env.step(action)
                states.append(next_state)
                rewards.append(reward)

                if done:
                    T = t+1
                else:
                    next_action = qhat.get_eps_greedy_action(next_state)
                    actions.append(next_action)

            tau = t - n + 1

            if tau >= 0:
                G = 0

                for i in range(tau+1, min(tau+n, T)+1):
                    G += gamma ** (i-tau-1) * rewards[i]
                if tau+n < T:
                    G += gamma**n * qhat.q_predict(states[tau+n], actions[tau+n])
                qhat.q_update(states[tau], actions[tau], G)

            if tau == T - 1:
                episode_rewards.append(np.sum(rewards))
                break
            else:
                t += 1
                state = next_state
                action = next_action

    return np.array(episode_rewards)

Listing 5-1N-Step SARA Control: Mountain Car

图 5-6 显示了运行此算法训练山地车的结果。我们可以看到,在 50 集内,代理达到一个稳定状态,它能够在大约 110 个时间步长内达到击中山谷右侧旗帜的目标。

img/502835_1_En_5_Fig6_HTML.jpg

图 5-6

带登山车的 n 步半梯度 SARSA

半梯度 SARSA(λ)控制

接下来,我们将研究具有合格轨迹的半梯度 SARSA(λ)算法。SARSA(λ)进一步推广了 n 步 SARSA。当状态或状态-动作值由线性函数近似的二进制特征表示时,就像 tile 编码的山地车一样,我们得到如图 5-7 所示的算法。该算法引入了合格轨迹的概念,该合格轨迹具有与权重向量相同数量的分量。权重向量是对许多情节的长期记忆,从所示的所有例子中进行归纳。合格痕迹是持续时间小于情节长度的短时记忆。它通过影响体重来帮助学习过程。我们将不深入讨论更新规则的详细推导。概念和数学推导的详细解释可以参考 http://incompleteideas.net/book/the-book.html

Semi-Gradient SARSA(Λ) Control (Episodic)

img/502835_1_En_5_Fig7_HTML.png

图 5-7

半梯度 SARSA(λ)用于当特征是二元的并且值函数是特征向量和权重向量的线性组合时的情节控制

让我们看看在山地车上运行前面的算法。Listing5_2.ipynb有完整的代码。在清单 5-2 中,我们强调了代码的重要部分。像listing5_1.ipynb一样,我们有一个名为QEstimator的类,做了一些小的修改来存储跟踪值,并在权重更新函数q_update中使用跟踪。我们还有两个辅助函数:accumulating_tracereplacing trace来实现对两个轨迹变量的跟踪。函数sarsa_lambda实现图 5-7 中给出的整体学习算法。我们也有一个功能来运行训练有素的代理通过一些插曲,并记录行为。一旦您训练了代理并生成了动画,您就可以运行 MP4 文件并查看代理为达到目标所遵循的策略。

def accumulating_trace(trace, active_features, gamma, lambd):
    trace *= gamma * lambd
    trace[active_features] += 1
    return trace

def replacing_trace(trace, active_features, gamma, lambd):
    trace *= gamma * lambd
    trace[active_features] = 1
    return trace

# code omitted as it largely similar to listing 5-1
# except for adding trace vector to init fn and to q_update fn
class QEstimator:

def sarsa_lambda(qhat, episode_cnt = 10000, max_size=2048, gamma=1.0):
    episode_rewards = []
    for i in range(episode_cnt):
        state = env.reset()
        action = qhat.get_eps_greedy_action(state)
        qhat.trace = np.zeros(max_size)
        episode_reward = 0

        while True:
            next_state, reward, done, _ = env.step(action)
            next_action = qhat.get_eps_greedy_action(next_state)
            episode_reward += reward
            qhat.q_update(state, action, reward, next_state, next_action)
            if done:
                episode_rewards.append(episode_reward)
                break
            state = next_state
            action = next_action
    return np.array(episode_rewards)

Listing 5-2SARSA (λ) Control: Mountain Car

图 5-8 显示了运行 SARSA(λ)算法训练山地车的结果。我们可以看到结果类似于图 5-6 中的结果。这是一个太小的问题,但对于更大的问题,资格追踪驱动的算法将显示更好和更快的收敛。

img/502835_1_En_5_Fig8_HTML.jpg

图 5-8

半坡 SARSA(λ)带山地车

函数近似的收敛性

让我们通过看一个例子来开始探索收敛。如图 5-9 所示,让我们考虑一个双态转换作为一些 MDP 的一部分。假设我们将使用函数近似,第一个状态的值为 w ,第二个状态的值为 2 w 。这里, w 是一个单一的数字,而不是一个向量。

img/502835_1_En_5_Fig9_HTML.jpg

图 5-9

泛函近似下的两步跃迁

假设 w = 10,代理从第一状态转换到第二状态,即从值为 10 的状态转换到值为 20 的状态。我们还假设从第一个状态到第二个状态的转变是第一个状态中唯一可能的转变,而这个转变的回报每次都是零。设学习率为 α = 0.1。

现在让我们将方程式( 5.14 )应用于前一个。

??

??$$ {w}_{t+1}={w}_t+0.1\ast \left[0+\gamma \ast 2\ {w}_t-{w}_t\right].1 $$

i . ew+1=**+0.1】w****

我们假设 λ 接近 1,当前权重为 10。更新后的权重会是这样的:w*t+1= 10+0.1∫10∫(2 1)= 11。

只要(2γ—1)>为 0,每次更新都会导致权重发散。这表明函数近似会导致发散。这是因为值的泛化,即更新给定州的值也会更新附近或相关州的值。列出了不稳定问题的三个角度。

  • 函数近似:一种使用权重对非常大的状态空间进行概括的方法,与状态总数相比,权重是一个较小的向量

  • 自举:使用状态值的估计值形成目标值,例如在 TD(0)中,目标是估计值Rt+1+γVt(s); w

  • 非策略学习:使用行为策略训练代理,但学习不同的最优策略

即使在简单的预测/估计场景中,这三个部分的共同存在也显著增加了发散的机会。控制和优化问题分析起来更加复杂。也已经表明,只要这三者不同时存在,就可以避免不稳定性。这就给我们带来了一个问题,我们能放弃这三者中的任何一个,并评估这种放弃的影响吗?

函数近似,尤其是使用神经网络的函数近似,使得 RL 对于大型现实世界的问题是可行的。其他替代方案不实用。自举使流程样本变得高效。通过观看完整集来形成目标的替代方案虽然可行,但不太实用。策略外学习可以用策略内学习来代替,但是为了让 RL 更接近人类的学习方式,我们需要策略外学习,通过探索另一个类似的问题来了解一些问题/情况。因此,对此没有简单的答案。我们不能放弃这三项要求中影响较小的任何一项。那是理论上的一面。在实践中,大多数时候,算法通过一些仔细的监控和调整而收敛。

梯度时间差异学习

用方程( 5.9 )所示的更新方程进行的半梯度 TD 学习不遵循真实梯度。在取损失函数的梯度时,我们保留了对目标的估计,即Rt+1+γVt(sw ),常数。它没有出现在关于重量 w 的导数中。真正的更夫误差是Rt+1+γVt(swVt(sw ),它的导数理想情况下应该对两个Vt(s)都有梯度项; w )和Vt(sw )。

有一种称为梯度时间差异学习的变体,它遵循真实梯度,并在所有查表、线性和非线性函数近似以及符合策略和不符合策略方法的情况下提供收敛。将此添加到算法组合中,可以修改表 5-1 ,如表 5-2 所示。我们不会在这本书里深入讨论它的数学证明,因为这本书的重点是算法的实际实现。

表 5-2

预测/估计算法的收敛性

|

保单类型

|

算法

|

表格查找

|

线性的

|

非线性的

|
| --- | --- | --- | --- | --- |
| 政策上的 | 主持人 TD(0)TD(λ)梯度 TD | YYYY | YYYY | Y 普通普通 Y |
| 不符合政策 | 主持人 TD(0)TD(λ)梯度 TD | YYYY | Y 普通普通 Y | Y 普通普通 Y |

接着,我们在表 5-3 中给出了控制算法的收敛性。

表 5-3

控制算法的收敛性

|

算法

|

表格查找

|

线性的

|

非线性的

|
| --- | --- | --- | --- |
| MC 控制 | Y | (Y) | 普通 |
| 保单 TD (SARSA) | Y | (Y) | 普通 |
| 政策外 Q-learning | Y | 普通 | 普通 |
| 梯度 Q 学习 | Y | Y | 普通 |

(Y):在接近最优值函数附近波动。在所有非线性情况下,收敛保证都是无效的。

批量方法(DQN)

到目前为止,我们一直专注于增量算法;也就是说,我们对转换进行采样,然后使用这些值,在随机梯度下降的帮助下更新权重向量 w 。但是这种方法不具有样本效率。我们只使用一次样本就丢弃它。然而,对于非线性函数近似,特别是对于神经网络,我们需要多次通过网络才能使网络权重收敛到真实值。此外,在许多现实生活场景中,如机器人,我们需要两个方面的样本效率:神经网络收敛缓慢,因此需要多次通过,以及在现实生活中生成样本非常慢。在关于批量强化方法的这一节中,我们将带您了解批量方法在深度 Q 网络中的具体使用,深度 Q 网络是非策略 Q 学习的深度网络版本。

像之前一样,我们使用函数近似来估计状态值,如等式( 5.1 ): $$ \hat{v}\left(s,w\right)\approx {v}_{\pi }(s) $$所示。

考虑我们以某种方式知道实际状态值v【π】(s),并且我们正试图学习权重向量 w 以达到良好的估计值$$ \hat{v}\left(s,w\right)\approx {v}_{\pi }(s) $$。我们收集了一批经验。

$$ D=\left{<{s}_1,{v}_1^{\pi }>,<{s}_2,{v}_2^{\pi }>,\dots .,<{s}_T,{v}_T^{\pi }>\right} $$

我们将最小平方损失作为真实值和估计值之间的差的平均值,然后执行梯度下降以最小化误差。我们使用小批量梯度下降来获取过去经验的样本,并使用学习率 α 来移动权重向量。$$ LS(w)={E}_D\left[{\left({v}_{\pi }(s)-\hat{v}\left(s;w\right)\right)}²\right]. $$

我们用样本来近似它。

$$ LS(w)=\frac{1}{N}{\sum}_{i=1}^N{\left({v}_{\pi}\left({s}_i\right)-\hat{v}\left({s}_i;w\right)\right)}² $$

(5.22)

LS ( w )相对于 w 的梯度,用负梯度调整 w ,得到方程( 5.23 ,类似于方程( 5.7 )。

$$ {w}_{t+1}={w}_t+\alpha .\kern0.5em \frac{1}{N}{\sum}_{i=1}^N\left[{v}_{\pi}\left({s}_i\right)-\hat{v}\left({s}_i;w\right)\right].{\nabla}_w\ \hat{v}\left({s}_i;w\right) $$

(5.23)

像以前一样,我们可以用 q 值进行类似的更新。

$$ {w}_{t+1}={w}_t+\alpha .\kern0.5em \frac{1}{N}{\sum}_{i=1}^N\left[{q}_{\pi}\left({s}_i,{a}_i\right)-\hat{q}\left({s}_i,{a}_i;w\right)\right].{\nabla}_w\ \hat{q}\left({s}_i,{a}_i;w\right) $$

(5.24)

但是,我们并不知道真正的值函数,vπ(sIqπ(sIa i )。像以前一样,我们使用 MC 或 TD 方法将真实值替换为估计值。现在让我们来看一个叫做 DQN 的版本,Q-learning 的深度学习版本,如第四章所示。在 DQN,一种非策略算法,我们对当前状态进行采样,根据当前行为策略采取步骤,一种使用当前 q 值的ε贪婪策略。我们观察奖励 r 和下一个状态s。我们用$$ \mathit{\max}\ \hat{q}\left({s}^{\prime },.;w\right) $$来表示状态中可能出现的所有动作来形成目标。********

$$ {q}_{\pi}\left({s}_i,{a}_i\right)={r}_i+\gamma\ {\mathit{\max}}_{a^{\prime }}q\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right) $$

这里我们使用了不同的权重向量wt来计算目标的估计值。本质上,我们有两个网络,一个称为在线网络,其权重为 w ,根据等式 5.24 进行更新,第二个类似的网络称为目标网络,但其权重副本称为w。权重向量w?? 更新频率较低,比如在线网络权重 w 每更新 100 次。这种方法保持目标网络不变,并允许我们使用监督学习的机制。还请注意,我们使用下标 i 表示小批量中的样品,使用 t 表示权重更新的指数。将所有这些放在一起,最终的更新方程可以写成如下形式:

$$ {w}_{t+1}={w}_t+\alpha .\kern0.5em \frac{1}{N}{\sum}_{i=1}N\left[{r}_i+\gamma {\mathit{\max}}_{a_i{\prime }}\overset{\sim }{q}\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right)-\hat{q}\left({s}_i,{a}_i;w\right)\right].{\nabla}_w\ \hat{q}\left({s}_i,{a}_i;w\right) $$

**(5.25)

简而言之,我们使用ε-greedy 策略在环境中运行代理,并在名为 replay buffer D 的缓冲区中收集经验。我们使用( 5.25 )对在线网络进行权重更新。我们也偶尔更新目标网络权重(比如在每 100 次批量更新 w 之后)。我们使用带有ε-探索的更新的 q 值来将更多的体验添加到重放缓冲器中,并再次执行整个循环。这实质上是 DQN 的方法。在下一章,我们还有很多要说的,这一章完全是关于 DQN 及其变体的。现在,我们把话题留在这里,继续前进。

线性最小二乘法

在批处理方法中使用的经验重放找到最小平方解,最小化使用 TD 或 MC 估计的目标和当前值函数估计之间的误差。但是,需要多次迭代才能收敛。但如果我们对预测用的值函数$$ \hat{v}\left(s;w\right)=x{(s)}^Tw $$和控制用的$$ \hat{q}\left(s,a;w\right)=x{\left(s,a\right)}^Tw $$用线性函数近似,就可以直接求出最小二乘解。先来看预测。

我们从方程( 5.22 ,代入$$ \hat{v}\left(s;w\right)=x{(s)}^Tw $$得到这个:

$$ LS(w)=\frac{1}{N}{\sum}_{i=1}N{\left({v}_{\pi}\left({s}_i\right)-x{\left({s}_i\right)}T.w\right)}² $$

LS ( w )相对于 w 的梯度并设为零,我们得到如下:

$$ {\sum}_{i=1}Nx\left({s}_i\right) {v}_{\pi}\left({s}_i\right)={\sum}_{i=1}Nx\left({s}_i\right).x{\left({s}_i\right)}^T\ w $$

求解 w 给出如下:

$$ w={\left({\sum}_{i=1}Nx\left({s}_i\right).x{\left({s}_i\right)}T\ \right)}{-1}{\sum}_{i=1}Nx\left({s}_i\right)\ {v}_{\pi}\left({s}_i\right) $$

(5.26)

前面的解决方案涉及一个 N N 矩阵的求逆,这需要 O ( N 3 )的计算。但是,使用 Shermann-Morrison,我们可以在O(N2)时间内解决这个问题。和以前一样,我们不知道真实值vπ(sI)。我们使用 MC、TD(0)或 TD(λ)估计值将真实值替换为其估计值,从而为我们提供线性最小二乘 MC (LSMC)、LSTD 或 LSTD(λ)预测算法。

$$ {v}_{\pi}\left({s}_i\right)\approx {G}_i $$

$$ {v}_{\pi}\left({s}_i\right)\approx R+\gamma\ \hat{v}\left({s}_i^{\prime };w\right) $$

LSD(λ):v(【我】)**

***所有这些预测算法对于非策略或策略都具有良好的收敛性。

接下来,我们将分析扩展到使用 q 值线性函数近似和 GPI 的控制,其中前面的方法用于 q 值预测,随后是策略改进步骤中的贪婪 q 值最大化。这被称为线性最小二乘策略迭代 (LSPI)。我们通过这些循环迭代预测,然后改进,直到策略收敛,即,直到权重收敛。我们在这里给出线性最小二乘 Q 学习(LSPI)的最终结果,而不经过推导。

下面是预测步骤:

$$ w={\left({\sum}_{i=1}Nx\left({s}_i,{a}_i\right).\left(x\left({s}_i,{a}_i\right)+\gamma x\right({s}_i{\prime },\pi \left({s}_i{\prime}\right)\left)\right){}T\ \right)}{-1}{\sum}_{i=1}Nx\left({s}_i,{a}_i\right)\ {r}_i $$

其中( i )是第 i th 样( s ia ir isI)来自经验回放

这是控制步骤:

对于每个状态 s ,我们改变在先前预测步骤中执行的权重更新 w 之后最大化 q 值的策略。

$$ {\pi}{\prime}\left({s}_i\right)={argmax}_a \hat{q}\left({s}_i,a;{w}{updated}\right) $$

前面,我们讨论了使用函数近似的策略迭代的大多数变体:增量、批处理和线性方法。让我们绕一小段路来介绍 PyTorch 和 TensorFlow 库。

深度学习图书馆

本章的前几节向我们展示了通过使用函数近似方法,我们需要一种有效的方法来计算状态值函数$$ {\nabla}_w\ \hat{v}\left({s}_i;w\right) $$或动作值函数$$ {\nabla}_w\ \hat{q}\left({s}_i,{a}_i;w\right) $$的导数。如果我们使用神经网络,我们需要使用反向传播来计算网络每一层的导数。这就是 PyTorch 和 TensorFlow 这样的库出现的原因。类似于 NumPy 库,它们也以有效的方式执行向量/矩阵计算。此外,它们在处理张量(二维以上的数组)方面进行了高度优化。

在神经网络中,我们需要能够反向传播误差,以计算相对于各级层权重的误差梯度。这两个库都是高度抽象和优化的,可以在幕后为我们处理这些问题。我们只需要构建正向计算,通过所有的计算获取输入,从而得到最终的输出。这些库跟踪计算图形,并允许我们只通过一次函数调用来对权重进行梯度更新。

为了更新您对这两个库的了解,我们提供了两个 Python 笔记本,带您使用 MNIST 数据集训练一个简单的数字分类模型。listing5_3_pytorch_intro.ipynb是使用 PyTorch 的代码,listing5_4_tensorflow.ipynb使用 TensorFlow 遍历同样的东西。我们在这里不复制文本中的代码,因为这些笔记本只是为了让你更新关于 PyTorch 和 TensorFlow 的知识。

摘要

在这一章中,我们主要关注的是函数近似在非常大或连续的状态空间中的应用,这些空间不能用我们在前面章节中看到的基于表格的学习方法来处理。

我们讨论过用函数近似进行优化意味着什么。我们还展示了监督学习中的训练概念,即训练模型以产生接近目标的值,如何应用于强化学习,对移动目标和 RL 中所示的样本相互依赖进行适当处理。

然后,我们研究了函数近似的各种策略,包括线性和非线性策略。我们还看到了基于表格的方法只是线性近似的特例。随后详细讨论了预测和控制的增量方法。我们看到这些被应用在山地车上,使用 n 步 SARSA 和 SARSA(λ)构建训练代理。

接下来,我们讨论了批处理方法,并探索了 DQN 更新规则的完整推导过程,这是批处理方法家族中的一个流行算法。然后我们研究了预测和控制的线性最小二乘法。在此过程中,我们不断强调一般的融合问题以及正在讨论的特定方法中的融合。

我们最后以 PyTorch 和 TensorFlow 等深度学习框架的简要介绍结束了这一章。**************

六、深度 Q 学习

在这一章中,我们将深入探讨 Q 学习与使用神经网络的函数近似的结合。使用神经网络的深度学习环境中的 Q-learning 也被称为深度 Q 网络 (DQN)。我们将首先总结到目前为止我们所谈论的关于 Q-learning 的内容。然后我们将看看 DQN 在简单问题上的代码实现,然后训练一个代理玩 Atari 游戏。接下来,我们将通过查看可以对 DQN 进行的各种修改来扩展我们的知识,以改进学习,包括一些非常新的和最先进的方法。其中一些方法可能涉及一些数学知识,以理解这些方法的基本原理。然而,我们将努力保持数学最少,只包括所需的细节,以了解背景和推理。本章中的所有例子都将使用 PyTorch 或 TensorFlow 库进行编码。一些代码演练将同时包含 PyTorch 和 TensorFlow 的代码,而其他代码将仅使用 PyTorch 进行讨论。

深度 Q 网络

在第四章中,我们讨论了 Q-learning 作为一种无模型的非策略 TD 控制方法。我们首先看一下在线版本,其中我们使用了一个探索性行为策略(ε-贪婪)来在状态 S 时采取一个步骤(动作 A )。奖励 R 和下一个状态S然后被用来更新 Q 值 Q ( SA )。图 4-14 和清单 4-4 详细说明了伪代码和实际实现。这里给出了在这种情况下使用的更新公式。在你继续前进之前,你可能想重温这一点。

$$ Q\left({S}_t,{A}_t\right)\leftarrow Q\left({S}_t,{A}_t\right)+\alpha \ast \left[{R}_{t+1}+\gamma \ast {}_{a\kern0.5em }{}^{\mathit{\max}}Q\left({S}_{t+1},{A}_{t+1}\right)-Q\left({S}_t,{A}_t\right)\right] $$

(6.1)

我们简要地讨论了最大化偏差和双 Q 学习的方法,其中我们使用了两个 Q 值表。在这一章中,当我们研究双 DQN 时,我们会有更多的内容要说。

接下来,我们研究了多次使用一个样本来将在线 TD 更新转换为批量 TD 更新的方法,从而提高样本效率。它向我们介绍了重放缓冲区的概念。虽然它只是关于离散状态和状态-动作空间上下文中的样本效率,但通过神经网络的函数近似,它几乎成为了使深度学习神经网络收敛的必备条件。我们将再次讨论这一点,当我们谈到优先重放时,我们将看看从缓冲区中采样过渡/体验的其他选项。

接下来,在第五章中,我们看了函数近似的各种方法。我们将瓦片编码视为实现线性函数近似的一种方式。然后,我们讨论了 DQN,即使用神经网络作为函数近似器的批量 Q 学习。我们经历了长时间推导,以得出如等式( 5 中给出的权重(具有神经网络参数)更新等式。25 。此处转载如下:

$$ {w}_{t+1}={w}_t+\alpha .\kern0.5em \frac{1}{N}\sum \limits_{i=1}N\left[{r}_i+\gamma {\mathit{\max}}_{a_i{\prime }}\overset{\sim }{q}\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right)-\hat{q}\left({s}_i,{a}_i;w\right)\right].{\nabla}_w\ \hat{q}\left({s}_i,{a}_i;w\right) $$

(6.2)

还请注意,我们使用下标 i 表示小批量中的样品,使用 i 表示权重更新的指数。方程式( 6.2 )是我们将在本章中广泛使用的一个。当我们谈论不同的修改和研究它们的影响时,我们将对这个等式进行各种调整。

我们还讨论了在梯度更新的非线性函数近似下没有收敛的理论保证。在这一章里,我们将对此有更多的论述。Q-learning 方法用于离散状态和动作,其中使用( 6.1 )更新 Q 值,而不是调整 DQN 基于深度学习的方法的权重参数。Q-learning 的案例有收敛的保证,而 DQN 的案例则没有这样的保证。DQN 也是计算密集型的。然而,尽管 DQN 有这些缺点,DQN 使得使用原始图像训练代理成为可能,这在普通的 Q-learning 中是完全不可想象的。现在让我们将等式( 6.2 )付诸实践,以在各种环境中训练 DQN 代理。

让我们再来看一下CartPole问题,它有一个四维连续状态,包含当前购物车位置、速度、杆的角度和杆的角速度的值。动作有两种:向左推车或向右推车,目的是尽可能长时间保持杆子平衡。以下是环境的详细信息:

Observation:
    Type: Box(4)
    Num  Observation            Min                    Max
    0    Cart Position           -4.8                   4.8
    1    Cart Velocity           -Inf                   Inf
    2    Pole Angle             0.418 rad (-24 deg)    0.418 rad (24 deg)
    3    Pole Angular Velocity  -Inf                   Inf
Actions:
    Type: Discrete(2)
    Num   Action
    0     Push cart to the left
    1     Push cart to the right

我们将建立一个小的神经网络,以 4 为输入维度,三个隐藏层,然后是一个输出层,维度 2 是可能的动作数。图 6-1 为网络图。

img/502835_1_En_6_Fig1_HTML.jpg

图 6-1

简单神经网络

我们将使用 PyTorch 的nn.Module类来构建网络。我们还将实现一些额外的功能。函数get_qvalues取一批状态作为输入,即一个(N×4)维的张量,其中 N 是样本数。它通过网络传递状态值以产生 q 值。输出向量的大小为(n×2);即每个输入一行。每行有两个 q 值,一个用于左推动作,另一个用于右推动作。同一个类中的函数sample_actions接收一批 q 值(N×2)。它使用ε-贪婪策略(等式 4。3 选择一个动作。输出是(N×1)向量。清单 6-2 显示了 PyTorch 中的代码。清单 6-3 显示了 TensorFlow 2.0 的急切执行模式中的相同代码。您可以在文件listing6_1_dqn_pytorch.ipynb中找到 PyTorch 的完整实现,在文件listing6_1_dqn_tensorflow.ipynb中找到 TensorFlow 的完整实现。

注意虽然不是必需的,但是您将从代码讨论中获得更多关于 PyTorch 或 TensorFlow 的知识。你应该能够创建基本网络,定义损失函数,并执行优化的基本训练步骤。TensorFlow 新的急切执行模型类似于 PyTorch。出于这个原因,我们将在两个库中提供有限的示例代码,以帮助您入门。否则,书中的大部分代码都是 PyTorch。

class DQNAgent(nn.Module):
    def __init__(self, state_shape, n_actions, epsilon=0):
        super().__init__()
        self.epsilon = epsilon
        self.n_actions = n_actions
        self.state_shape = state_shape

        state_dim = state_shape[0]
        # a simple NN with state_dim as input vector (inout is state s)
        # and self.n_actions as output vector of logits of q(s, a)
        self.network = nn.Sequential()
        self.network.add_module('layer1', nn.Linear(state_dim, 192))
        self.network.add_module('relu1', nn.ReLU())
        self.network.add_module('layer2', nn.Linear(192, 256))
        self.network.add_module('relu2', nn.ReLU())
        self.network.add_module('layer3', nn.Linear(256, 64))
        self.network.add_module('relu3', nn.ReLU())
        self.network.add_module('layer4', nn.Linear(64, n_actions))
        #
        self.parameters = self.network.parameters

    def forward(self, state_t):
        # pass the state at time t through the newrok to get Q(s,a)
        qvalues = self.network(state_t)
        return qvalues

    def get_qvalues(self, states):
        # input is an array of states in numpy and outout is Qvals as numpy array
        states = torch.tensor(states, device=device, dtype=torch.float32)
        qvalues = self.forward(states)
        return qvalues.data.cpu().numpy()

    def sample_actions(self, qvalues):
        # sample actions from a batch of q_values using epsilon greedy policy
        epsilon = self.epsilon
        batch_size, n_actions = qvalues.shape
        random_actions = np.random.choice(n_actions, size=batch_size)
        best_actions = qvalues.argmax(axis=-1)
        should_explore = np.random.choice(
            [0, 1], batch_size, p=[1-epsilon, epsilon])
        return np.where(should_explore, random_actions, best_actions)

Listing 6-1A Simple DQN Agent in PyTorch

清单 6-2 显示了 TensorFlow 2.x 中使用 Keras 接口的相同代码。我们使用新的急切执行模型,它类似于 PyTorch 采用的方法。TensorFlow 在早期版本中使用了不同的模型,这有点难以概念化,分为两个独立的阶段:一个是构建所有网络操作的符号图,然后是通过将数据作为张量传递到第一阶段的模型构建中来训练模型的第二阶段。

class DQNAgent:
    def __init__(self, state_shape, n_actions, epsilon=0):
        self.epsilon = epsilon
        self.n_actions = n_actions
        self.state_shape = state_shape

        state_dim = state_shape[0]
        self.model = tf.keras.models.Sequential()
        self.model.add(tf.keras.Input(shape=(state_dim,)))
        self.model.add(tf.keras.layers.Dense(192, activation="relu"))
        self.model.add(tf.keras.layers.Dense(256, activation="relu"))
        self.model.add(tf.keras.layers.Dense(64, activation="relu"))
        self.model.add(tf.keras.layers.Dense(n_actions))

    def __call__(self, state_t):
        # pass the state at time t through the newrok to get Q(s,a)
        qvalues = self.model(state_t)
        return qvalues

    def get_qvalues(self, states):
        # input is an array of states in numpy and outout is Qvals as numpy array
        qvalues = self.model(states)
        return qvalues.numpy()

    def sample_actions(self, qvalues):
        # sample actions from a batch of q_values using epsilon greedy policy
        epsilon = self.epsilon
        batch_size, n_actions = qvalues.shape
        random_actions = np.random.choice(n_actions, size=batch_size)
        best_actions = qvalues.argmax(axis=-1)
        should_explore = np.random.choice(
            [0, 1], batch_size, p=[1-epsilon, epsilon])
        return np.where(should_explore, random_actions, best_actions)

Listing 6-2A Simple DQN Agent in TensorFlow

重放缓冲区的代码很简单。我们有一个名为self.buffer的缓冲区来保存前面的例子。函数add接收(state, action, reward, next_state, done),即来自代理单步/转换的值,并将其添加到缓冲区。如果缓冲区已经达到最大长度,它会丢弃最早的过渡,为新添加的内容腾出空间。函数sample获取整数batch_size并从缓冲器返回batch_size样本/转换。在这种普通实现中,存储在缓冲器中的每个转换被采样的概率相等。清单 6-3 显示了重放缓冲区的代码。

class ReplayBuffer:
    def __init__(self, size):
        self.size = size #max number of items in buffer
        self.buffer =[] #array to hold samples
        self.next_id = 0

    def __len__(self):
        return len(self.buffer)

    def add(self, state, action, reward, next_state, done):
        item = (state, action, reward, next_state, done)
        if len(self.buffer) < self.size:
           self.buffer.append(item)
        else:
            self.buffer[self.next_id] = item
        self.next_id = (self.next_id + 1) % self.size

    def sample(self, batch_size):
        idxs = np.random.choice(len(self.buffer), batch_size)
        samples = [self.buffer[i] for i in idxs]
        states, actions, rewards, next_states, done_flags = list(zip(*samples))
        return np.array(states),
                    np.array(actions),
      np.array(rewards),
      np.array(next_states),
      np.array(done_flags)

Listing 6-3Replay Buffer (Same in PyTorch or TensorFlow)

接下来,我们有一个效用函数play_and_store,它接受一个env(例如CartPole)、一个agent(例如DQNAgent)、一个exp_replay ( ReplayBuffer)、代理的start_staten_steps(即在环境中要采取的步骤/动作的数量)。该功能使agent从初始状态start_state开始走n_steps步数。基于代理使用agent.sample_actions遵循的当前ε-贪婪策略采取这些步骤,并将这些n_steps转换记录在缓冲器中。清单 6-4 显示了代码。

def play_and_record(start_state, agent, env, exp_replay, n_steps=1):

    s = start_state
    sum_rewards = 0

    # Play the game for n_steps and record transitions in buffer
    for _ in range(n_steps):
        qvalues = agent.get_qvalues([s])
        a = agent.sample_actions(qvalues)[0]
        next_s, r, done, _ = env.step(a)
        sum_rewards += r
        exp_replay.add(s, a, r, next_s, done)
        if done:
            s = env.reset()
        else:
            s = next_s

    return sum_rewards, s

Listing 6-4Implementation of Function play_and_record

接下来,我们看看学习过程。我们首先建立我们想要最小化的损失 L 。它是使用一步 TD 值的当前状态动作的目标值和当前状态值之间的平均平方误差。正如在第五章中所讨论的,我们使用原始神经网络的副本,它具有权重w(w带上标)。我们使用损失来计算代理(在线/原始)网络的权重 w 的梯度,并在梯度的负方向上采取一个步骤来减少损失。请注意,如第五章所述,我们保持目标网络的权重w不变,并以较低的频率更新这些权重。引用第五章 DQN 批量方法部分:

这里我们使用了不同的权重向量 wt来计算目标的估计值。本质上,我们有两个网络,一个称为在线网络,权重为“w,根据等式(5.24)进行更新,第二个类似的网络称为目标网络,但具有权重“w”的副本,称为“**【w。权重向量**w更新频率较低,比如说在线网络每更新 100 次。这种方法保持目标网络不变,并允许我们使用监督学习的机制。

损失函数如下:

$$ L=\frac{1}{N}{\sum}_{i=1}^N{\left[{r}_i+\left(\left(1- don{e}_i\right).\upgamma .\underset{a_i^{\prime }}{\max}\hat{q}\left({s}_i^{\prime },{a}_i^{\prime };{w}_t^{-}\right)\right)\hbox{--} \hat{q}\left({s}_i,{a}_i;{w}_t\right)\right]}² $$

(6.3)

我们采用 L 相对于 w 的梯度(导数),然后使用该梯度来更新在线网络的权重 w 。这些等式如下:

$$ {\nabla}_{\mathrm{w}}\mathrm{L}=-\frac{1}{\mathrm{N}}{\sum}_{\mathrm{i}=1}^{\mathrm{N}}\left[{\mathrm{r}}_{\mathrm{i}}+\left(\left(1-\mathrm{don}{\mathrm{e}}_{\mathrm{i}}\right).\upgamma .\underset{{\mathrm{a}}_{\mathrm{i}}^{\prime }}{\max}\hat{\mathrm{q}}\left({\mathrm{s}}_{\mathrm{i}}^{\prime },{\mathrm{a}}_{\mathrm{i}}^{\prime };{\mathrm{w}}_{\mathrm{t}}^{-}\right)\right)\hbox{--} \hat{\mathrm{q}}\left({\mathrm{s}}_{\mathrm{i}},{\mathrm{a}}_{\mathrm{i}};{\mathrm{w}}_{\mathrm{t}}\right)\right]\nabla \hat{\mathrm{q}}\left({\mathrm{s}}_{\mathrm{i}},{\mathrm{a}}_{\mathrm{i}};{\mathrm{w}}_{\mathrm{t}}\right) $$

(6.4)

$$ {w}_{t+1}\leftarrow {w}_t-\upalpha {\nabla}_wL $$

(6.5)

结合这两者,我们得到我们熟悉的方程更新( 6.2 )。然而,在 PyTorch 和 TensorFlow 中,我们不通过直接编码来进行更新,因为计算梯度$$ \nabla \hat{q}\left({s}_i,{a}_i;{w}_t\right) $$并不容易。这是使用 PyTorch 和 TensorFlow 等软件包的主要原因之一,这些软件包可以根据计算损耗度量 L 的操作自动计算梯度。我们只需要一个函数来计算这个度量。这是通过函数compute_td_loss.完成的,它接收一批(states, actions, rewards, next_states, done_flags)。它还接受折扣参数 γ 以及代理/在线和目标网络。该函数然后根据等式( 6.3 )计算损耗 L 。清单 6-5 给出 PyTorch 中的实现,清单 6-6 给出 TensorFlow 中的实现。

def compute_td_loss(agent, target_network, states, actions, rewards, next_states,
                     done_flags, gamma=0.99):

    # get q-values for all actions in current states
    # use agent network
    predicted_qvalues = agent(states)

    # compute q-values for all actions in next states
    # use target network
    predicted_next_qvalues = target_network(next_states)

    # select q-values for chosen actions
    row_indices= tf.range(len(actions))
    indices = tf.transpose([row_indices, actions])
    predicted_qvalues_for_actions = tf.gather_nd(predicted_qvalues, indices)

    # compute Qmax(next_states, actions) using predicted next q-values
    next_state_values = tf.reduce_max(predicted_next_qvalues, axis=1)

    # compute "target q-values"
    target_qvalues_for_actions = rewards + gamma * next_state_values * (1-done_flags)

    # mean squared error loss to minimize
    loss = tf.keras.losses.MSE(target_qvalues_for_actions, predicted_qvalues_for_actions)

    return loss

Listing 6-6Compute TD Loss in TensorFlow

def compute_td_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma=0.99, device=device):

    # convert numpy array to torch tensors
    states = torch.tensor(states, device=device, dtype=torch.float)
    actions = torch.tensor(actions, device=device, dtype=torch.long)
    rewards = torch.tensor(rewards, device=device, dtype=torch.float)
    next_states = torch.tensor(next_states, device=device, dtype=torch.float)
    done_flags = torch.tensor(done_flags.astype('float32'),device=device,dtype=torch.float)

    # get q-values for all actions in current states
    # use agent network
    predicted_qvalues = agent(states)

    # compute q-values for all actions in next states
    # use target network
    predicted_next_qvalues = target_network(next_states)

    # select q-values for chosen actions
    predicted_qvalues_for_actions = predicted_qvalues[range(
        len(actions)), actions]

    # compute Qmax(next_states, actions) using predicted next q-values
    next_state_values,_ = torch.max(predicted_next_qvalues, dim=1)

    # compute "target q-values"
    target_qvalues_for_actions = rewards + gamma * next_state_values * (1-done_flags)

    # mean squared error loss to minimize
    loss = torch.mean((predicted_qvalues_for_actions -
                       target_qvalues_for_actions.detach()) ** 2)

    return loss

Listing 6-5Compute TD Loss in PyTorch

在这一点上,我们有所有的机器来训练代理平衡杆。首先,我们定义一些超参数,如batch_size,总训练步数total_steps,以及探索ε衰减的速率。它从 1.0 开始,随着代理学习最优策略,慢慢地将探索减少到 0.05。我们还定义了一个optimizer,它可以接受前面列表中创建的损失 L ,并帮助我们采取梯度步骤来调整权重,本质上实现了等式( 6.4 )和( 6.5 )。清单 6-7 给出了 PyTorch 中的训练代码。清单 6-8 给出了 TensorFlow 中相同的代码。

for step in trange(total_steps + 1):

    # reduce exploration as we progress
    agent.epsilon = epsilon_schedule(start_epsilon, end_epsilon, step, eps_decay_final_step)

    # take timesteps_per_epoch and update experience replay buffer
    _, state = play_and_record(state, agent, env, exp_replay, timesteps_per_epoch)

    # train by sampling batch_size of data from experience replay
    states, actions, rewards, next_states, done_flags = exp_replay.sample(batch_size)

    with tf.GradientTape() as tape:
        # loss = <compute TD loss>
        loss = compute_td_loss(agent, target_network,
                               states, actions, rewards, next_states, done_flags,
                               gamma=0.99)

    gradients = tape.gradient(loss, agent.model.trainable_variables)
    clipped_grads = [tf.clip_by_norm(g, max_grad_norm) for g in gradients]
    optimizer.apply_gradients(zip(clipped_grads, agent.model.trainable_variables))

Listing 6-8Train the Agent in TensorFlow

for step in trange(total_steps + 1):

    # reduce exploration as we progress
    agent.epsilon = epsilon_schedule(start_epsilon, end_epsilon, step, eps_decay_final_step)

    # take timesteps_per_epoch and update experience replay buffer
    _, state = play_and_record(state, agent, env, exp_replay, timesteps_per_epoch)

    # train by sampling batch_size of data from experience replay
    states, actions, rewards, next_states, done_flags = exp_replay.sample(batch_size)

    # loss = <compute TD loss>
    loss = compute_td_loss(agent, target_network,
                           states, actions, rewards, next_states, done_flags,
                           gamma=0.99,
                           device=device)

    loss.backward()
    grad_norm = nn.utils.clip_grad_norm_(agent.parameters(), max_grad_norm)
    opt.step()
    opt.zero_grad()

###Omitted code here###
### code to periodically evaluate the performance and plot some graphs

Listing 6-7Train the Agent in PyTorch

我们现在有一个训练有素的特工。我们训练代理人,并在我们训练代理人 50,000 步时定期绘制每集的平均奖励。在图 6-2 的左图中,x 轴值 10 对应第 10000。我们还绘制了每 20 步的 TD 损耗,这就是为什么右侧图中的 x 轴从 0 到 2500,即 0 到 2500x20=50,000 步。与监督学习不同,目标不是固定的。我们在短时间内保持目标网络固定,并通过用在线网络刷新目标网络权重来定期更新它。此外,如所讨论的,具有偏离策略学习(Q 学习)和引导目标(目标网络只是实际值的估计和使用其他 Q 值的当前估计形成的估计)的非线性函数近似(神经网络)没有收敛保证。培训可能会看到损失上升,爆炸或波动。与通常的监督学习中的损失图相比,这个损失图是违反直觉的。图 6-2 显示了来自训练 DQN 的图表。

img/502835_1_En_6_Fig2_HTML.jpg

图 6-2

DQN 的训练曲线

代码笔记本listing6_1_dqn_pytorch.ipynblisting6_1_dqn_tensorflow.ipynb有更多的代码,以视频文件的形式记录受训代理的行为,然后播放视频来展示行为。

这完成了使用深度学习来训练代理的完整 DQN 的实现。对于这样一个简单的网络来说,使用一个复杂的神经网络可能看起来有些矫枉过正。核心思想是专注于算法,教你如何写一个 DQN 学习代理。我们现在将使用相同的实现,但稍作调整,以便代理可以使用游戏图像像素值作为状态来玩 Atari 游戏。

使用 DQN 的雅达利游戏代理

在 2013 年题为“用深度强化学习玩雅达利”的开创性论文中, 1 作者使用深度学习模型创建了一种基于神经网络的 Q 学习算法。他们将其命名为深度 Q 网络。这正是我们在上一节中实现的。我们现在将简要讨论作者采取的训练代理玩 Atari 游戏的附加步骤。主要要点与上一节相同,但有两个关键区别:使用游戏图像像素值作为需要一些预处理的状态输入,以及在代理内部使用卷积网络,而不是我们在上一节中看到的线性层。计算损耗 L 和进行训练的其余方法与上一节相同。请注意,使用卷积网络进行训练需要大量时间,尤其是在普通 PC/笔记本电脑上。准备好观看训练代码运行几个小时,即使是在中等强大的基于 GPU 的机器上。

您可以在文件listing6_2_dqn_atari_pytorch.ipynb中找到在 PyTorch 中训练代理的完整代码。你可以在listing6_2_dqn_atari_tensorflow.ipynb的 TensorFlow 中找到相同的代码。体育馆图书馆已经在 Atari 图像上实现了许多所需的转换,只要有可能,我们将使用相同的东西。

现在让我们来讨论一下为了将图像像素值输入到深度学习网络而进行的图像预处理。我们将在一个名为 Breakout 的游戏中讨论这个问题,在这个游戏中,底部有一个桨,这个想法是移动桨以确保球不会掉到它下面。我们需要用桨击打并取出尽可能多的砖块。每当球错过球拍,玩家就失去一条生命。玩家有五次生命开始。图 6-3 显示了游戏的三帧画面。

img/502835_1_En_6_Fig3_HTML.jpg

图 6-3

雅达利突破游戏图片

Atari 游戏图像是具有 128 色调色板的 210×160 像素图像。我们将做预处理来修剪图像,使卷积网络运行得更快。我们缩小图像。我们还删除了侧面的一些信息,只保留图像的相关部分用于训练。我们可以再次将图像转换为灰度,以减少输入向量的大小,用一个灰度通道代替 RGB 的三个颜色通道(红、绿、蓝通道)。尺寸为(PyTorch 中的 1×84×84 或 TensorFlow 中的 84×84×1)的预处理单帧图像仅给出静态。球或桨的位置不能告诉我们两者运动的方向。相应地,我们将把几帧连续的游戏图像堆叠在一起,来训练代理。我们将叠加四幅缩小尺寸的灰度图像,这些图像将状态 s 输入神经网络。输入(即状态 s )在 PyTorch 中将是 4×84×84 的大小,在 TensorFlow 中将是 84×84×4 的大小,其中 4 是指游戏图像的四帧,84×84 是每帧的灰度图像大小。将四帧堆叠在一起将允许代理网络推断球和桨的运动方向。我们使用 Gym 的AtariPreprocessing来执行从 210×160×3 大小的彩色图像阵列到 84×84 的灰度图像阵列的图像缩减。该函数还通过设置scale_obs=True.将单个像素值从范围(0,255)缩小到(0.0,1.0)。接下来,我们使用FrameStack将前面讨论的四幅图像叠加在一起。最后,根据最初的方法,我们还将奖励值裁剪为-1 或 1。清单 6-9 给出了执行所有这些转换的代码。

from gym.wrappers import AtariPreprocessing
from gym.wrappers import FrameStack
from gym.wrappers import TransformReward

def make_env(env_name, clip_rewards=True, seed=None):
    env = gym.make(env_name)
    if seed is not None:
        env.seed(seed)
    env = AtariPreprocessing(env, screen_size=84, scale_obs=True)
    env = FrameStack(env, num_stack=4)
    if clip_rewards:
        env = TransformReward(env, lambda r: np.sign(r))
    return env

Listing 6-9Train the Agent in PyTorch

前面的预处理步骤产生了我们将输入网络的最终状态。这在 PyTorch 中将是 4×84×84 的大小,在 TensorFlow 中将是 84×84×4 的大小,其中 4 是指游戏图像的四帧,84×84 是每帧的灰度图像大小。图 6-4 显示了网络的输入。

img/502835_1_En_6_Fig4_HTML.jpg

图 6-4

经处理的图像将用作神经网络的状态输入

接下来,我们构建神经网络,该网络将接收之前的图像,即状态/观察值 s ,并为这种情况下的所有四个动作产生 q 值。这个游戏的动作是['NOOP', 'FIRE', 'RIGHT', 'LEFT'],用空格键开始,也就是开火,按键盘上的 A 向左移动拨片,按 D 向右移动拨片,最后按 Esc 退出游戏。以下是我们将要构建的网络的规格:

    input: tensorflow: [batch_size, 84, 84, 4]
        pytorch:  [batch_size, 4, 84, 84]

    1st hidden layer: 16 nos of 8x8 filters with stride 4 and ReLU activation
    2nd hidden layer: 32 nos of 4x4 filters with stride of 2 and ReLU activation
    3nd hidden layer: Linear layer with 256 outputs and ReLU activation
    output layer: Linear with “n_actions” units with no activation

其余的代码与我们之前的代码相似。清单 6-10 和清单 6-11 分别显示了 PyTorch 和 TensorFlow 中修改后的 DQN 代理的代码。

class DQNAgent:
    def __init__(self, state_shape, n_actions, epsilon=0):

        super().__init__()
        self.epsilon = epsilon
        self.n_actions = n_actions
        self.state_shape = state_shape

        # a simple NN with state_dim as input vector (inout is state s)
        # and self.n_actions as output vector of logits of q(s, a)
        self.model = tf.keras.models.Sequential()
        self.model.add(tf.keras.Input(shape=state_shape))
        self.model.add(tf.keras.layers.Conv2D(16, kernel_size=8, strides=4, activation="relu"))
        self.model.add(tf.keras.layers.Conv2D(32, kernel_size=4, strides=2, activation="relu"))
        self.model.add(tf.keras.layers.Flatten())
        self.model.add(tf.keras.layers.Dense(256, activation="relu"))
        self.model.add(tf.keras.layers.Dense(n_actions))

    def __call__(self, state_t):
        # pass the state at time t through the newrok to get Q(s,a)
        qvalues = self.model(state_t)
        return qvalues

    def get_qvalues(self, states):
        # input is an array of states in numpy and outout is Qvals as numpy array
        qvalues = self.model(states)
        return qvalues.numpy()

    def sample_actions(self, qvalues):
        # sample actions from a batch of q_values using epsilon greedy policy
        epsilon = self.epsilon
        batch_size, n_actions = qvalues.shape
        random_actions = np.random.choice(n_actions, size=batch_size)
        best_actions = qvalues.argmax(axis=-1)
        should_explore = np.random.choice(
            [0, 1], batch_size, p=[1-epsilon, epsilon])
        return np.where(should_explore, random_actions, best_actions)

Listing 6-11DQN Agent in TensorFlow

class DQNAgent(nn.Module):
    def __init__(self, state_shape, n_actions, epsilon=0):

        super().__init__()
        self.epsilon = epsilon
        self.n_actions = n_actions
        self.state_shape = state_shape

        state_dim = state_shape[0]
        # a simple NN with state_dim as input vector (inout is state s)
        # and self.n_actions as output vector of logits of q(s, a)
        self.network = nn.Sequential()
        self.network.add_module('conv1', nn.Conv2d(4,16,kernel_size=8, stride=4))
        self.network.add_module('relu1', nn.ReLU())
        self.network.add_module('conv2', nn.Conv2d(16,32,kernel_size=4, stride=2))
        self.network.add_module('relu2', nn.ReLU())
        self.network.add_module('flatten', nn.Flatten())
        self.network.add_module('linear3', nn.Linear(2592, 256)) #2592 calculated above
        self.network.add_module('relu3', nn.ReLU())
        self.network.add_module('linear4', nn.Linear(256, n_actions))

        self.parameters = self.network.parameters

    def forward(self, state_t):
        # pass the state at time t through the newrok to get Q(s,a)
        qvalues = self.network(state_t)
        return qvalues

    def get_qvalues(self, states):
        # input is an array of states in numpy and outout is Qvals as numpy array
        states = torch.tensor(states, device=device, dtype=torch.float32)
        qvalues = self.forward(states)
        return qvalues.data.cpu().numpy()

    def sample_actions(self, qvalues):
        # sample actions from a batch of q_values using epsilon greedy policy
        epsilon = self.epsilon
        batch_size, n_actions = qvalues.shape
        random_actions = np.random.choice(n_actions, size=batch_size)
        best_actions = qvalues.argmax(axis=-1)
        should_explore = np.random.choice(
            [0, 1], batch_size, p=[1-epsilon, epsilon])
        return np.where(should_explore, random_actions, best_actions)

Listing 6-10DQN Agent in PyTorch

您会注意到 PyTorch 和 TensorFlow 在急切执行模式下的代码相似。建议你专注于一个框架并掌握概念。一旦你掌握了一个,将代码移植到另一个框架就很容易了。我们将在本书的大部分例子中使用 PyTorch,并在一些地方使用 TensorFlow 版本。

除了这两个变化,即一些特定问题的预处理和一个适合问题的神经网络,其余代码在CartPole和 Atari 之间保持不变。您还可以使用雅达利版本在雅达利游戏的任何版本上培训代理。此外,除了这两个变化之外,相同的代码可以用于为任何环境训练 DQN 代理。您可以从健身房库文档中查找可用的健身房环境,并尝试修改来自listing6_1_dqn_pytorch.ipynblisting6_1_dqn_atari_pytorch.ipynb的代码,以便为不同的环境培训代理。

这就完成了 DQN 的实施和培训。现在,我们知道了如何训练 DQN 代理,我们将研究一些问题和各种方法,我们可以采取修改 DQN。正如我们在本章开始时谈到的,我们将看看一些最新的和最先进的变化。

优先重放

在前一章中,我们了解了如何在 DQN 中使用批量版本的更新来解决在线版本中存在的一些关键问题,更新是在每次转换时完成的,而转换会在学习一步后立即被丢弃。以下是在线版本中的关键问题:

  • 训练样本(转换)是相关的,打破了独立同分布假设。在网上学习中,我们有一系列相互关联的转变。每个过渡都与前一个过渡相链接。这打破了应用梯度下降所需的内径假设。

  • 随着代理学习和丢弃,它可能永远不会访问初始的探索性转换。如果代理走上了一条错误的道路,它将继续从状态空间的那一部分看到例子。它可能会选择一个非常次优的解决方案。

  • 对于神经网络,基于单个转换的学习是困难且低效的。对于神经网络来说,将会有太多的变化来学习任何有效的东西。神经网络在成批学习训练样本时效果最佳。

这些问题在 DQN 通过使用存储所有过渡的体验重放得到了解决。每个跃迁都是一个(state, action, reward, next_state, done)元组。随着缓冲区变满,我们丢弃旧的样本来添加新的样本。然后,我们从当前缓冲区中抽样一批,缓冲区中的每个转换在一批中被选中的概率相等。它允许从缓冲区中多次选取罕见的和更具探索性的转换。然而,一个普通的体验回放没有任何方法来选择一些优先的重要过渡。以某种方式为存储在重放缓冲器中的每个过渡分配一个重要性分数,并使用这些重要性分数作为选择的概率从缓冲器中对批次进行采样,为重要的过渡分配较高的选择概率,如它们各自的重要性分数所表示的,这是否会有所帮助?

这就是来自 DeepMind 的论文《优先化体验回放》 2 的作者在 2016 年探索的。我们将遵循本文的主要概念来创建我们自己的体验重放实现,并将其应用于CartPole环境的 DQN 代理。让我们先谈一谈这些重要性分数是如何分配的,以及损失 L 是如何修改的。

本文的主要方法是利用训练样本的时延误差为缓冲区中的训练样本分配重要性分数。当从缓冲区中选取一批样本时,我们计算 TD 误差,作为损耗 L 计算的一部分。TD 误差由下式给出:

$$ {\updelta}_i={r}_i+\left(\left(1- don{e}_i\right).\upgamma .\underset{a_i^{\prime }}{\max}\hat{q}\left({s}_i^{\prime },{a}_i^{\prime };{w}_t^{-}\right)\right)\hbox{--} \hat{q}\left({s}_i,{a}_i;{w}_t\right) $$

(6.6)

它出现在我们计算损失的方程式( 6.3 )中。对所有样本的误差进行平方和平均,以计算权重向量的更新幅度,如等式( 6.4 和( 6.5 )所示。TD 误差的幅度 δ i 表示采样跳变 (i) 对更新的贡献。作者使用这种推理来为每个样本分配重要性分数 p i ,其中 p i 由以下等式给出:

$$ {\mathrm{p}}_{\mathrm{i}}=\left|{\updelta}_{\mathrm{i}}\right|+\varepsilon $$

(6.7)

增加一个小常数ε,避免 TD 误差 δ i 为零时 p i 为零的边缘情况。当一个新的转换被添加到缓冲器中时,我们将缓冲器中所有当前转换的最大值 p i 分配给它。当选择一个批次进行训练时,我们计算每个样本的 TD 误差 δ i 作为损失/梯度计算的一部分。然后,这个 TD 误差被用于更新缓冲器中这些样本的重要性分数。

本文还谈到了另一种基于等级的优先级排序方法。使用该方法,$$ {p}_i=\frac{1}{\mathit{\operatorname{rank}}(i)} $$,其中等级 ( i )是当基于| * δ * * i * |对重放缓冲器转换进行排序时转换(I)的等级。在我们的代码示例中,我们将使用第一种方法,称为比例优先

接下来,在采样时,我们通过使用以下等式将 p i 转换成概率:

$$ P(i)=\frac{p_i{\upalpha}}{\sum_i{p}_i{\upalpha}} $$

(6.8)

这里, P ( i )表示缓冲器中的转换(I)被采样并作为训练批次的一部分的概率。这为具有较高 TD 误差的转换分配了较高的采样概率。这里,α是一个超参数,使用网格搜索进行了调整,作者发现 α = 0.6 是我们将要实现的比例变量的最佳值。

先前用某种基于重要性的采样来打破均匀采样的方法引入了偏差。我们需要在计算损耗 L 时修正偏差。在本文中,通过用权重 w i 对每个样本进行加权,然后求和得到修正的损失函数 L ,使用重要性抽样对其进行了修正。计算重量的公式如下:

$$ {w}_i={\left(\frac{1}{N}.\frac{1}{P(i)}\right)}^{\upbeta} $$

(6.9)

这里, N 是训练批次中的样本数, P ( i )是前面的表达式中计算出的选择样本的概率。β是另一个超参数,我们将使用论文中的值 0.4。权重由$$ \frac{1}{ma{x}_i{w}_i} $$进一步标准化,以确保权重保持在界限内。

$$ {w}_i=\frac{1}{ma{x}_i{w}_i}{w}_i $$

(6.10)

有了这些适当的改变,损耗 L 等式也被更新,以用wI对批次中的每个转变进行加权,如下所示:

$$ L=\frac{1}{N}{\sum}_{i=1}^N{\left[\left({r}_i+\left(\left(1- don{e}_i\right).\upgamma .\underset{a_i^{\prime }}{\max}\hat{q}\left({s}_i^{\prime },{a}_i^{\prime };{w}_t^{-}\right)\right)\hbox{--} \hat{q}\left({s}_i,{a}_i;{w}_t\right)\right).{w}_i\right]}² $$

(6.11)

注意等式中的 w i 。在计算出 L 之后,我们使用损失梯度相对于在线神经网络权重 w 的反向传播来遵循通常的梯度步骤。

请记住,上一个等式中的 TD 误差用于为当前训练批次中的这些转换更新重放缓冲区中的重要性分数。这就完成了对优先重放的理论讨论。我们现在来看看实现。在listing6_3_dqn_prioritized_replay.ipynb中给出了用优先重放训练 DQN 代理的完整代码,它有两种风格,一种在 PyTorch 中,另一种在 TensorFlow 中。然而,从现在开始,我们将只列出 PyTorch 版本。建议您在阅读完下面给出的解释后,详细研究代码和参考文件。跟踪学术论文并将论文中的细节与工作代码相匹配的能力是成为一名优秀实践者的重要组成部分。解释只是让你开始。为了牢固地掌握材料,你应该详细地遵循伴随的代码。如果你在吸收了代码的工作原理后,尝试自己编码,那就更好了。

回到解释上来,我们首先看一下优先重放实现,这是与以前的 DQN 培训笔记相比,代码中的主要变化。清单 6-12 给出了优先重放的代码。大部分代码与我们之前看到的普通代码ReplayBuffer相似。我们现在有一个名为self.priorities的附加数组来保存每个样本的重要性/优先级分数 p i 。修改add函数,将 p i 赋值给正在添加的新样本。它只是数组self.priorities中的最大值。功能sample是变化最大的一个。使用等式( 6.8 )计算第一个概率,然后使用( 6.9 )和( 6.10 )计算权重。该函数现在返回另外两个数组:权重数组np.array(weights)和索引数组np.array(idxs)。索引数组包含批次中采样的缓冲区中样本的索引。这是必需的,以便在丢失步骤中计算 TD 误差之后,我们可以更新缓冲器中的优先级/重要性。功能update_priorities(idxs, new_priorities)正是为了这个目的。

class PrioritizedReplayBuffer:
    def __init__(self, size, alpha=0.6, beta=0.4):
        self.size = size #max number of items in buffer
        self.buffer =[] #array to holde buffer
        self.next_id = 0
        self.alpha = alpha
        self.beta = beta
        self.priorities = np.ones(size)
        self.epsilon = 1e-5

    def __len__(self):
        return len(self.buffer)

    def add(self, state, action, reward, next_state, done):
        item = (state, action, reward, next_state, done)
        max_priority = self.priorities.max()
        if len(self.buffer) < self.size:
           self.buffer.append(item)
        else:
            self.buffer[self.next_id] = item
        self.priorities[self.next_id] = max_priority
        self.next_id = (self.next_id + 1) % self.size

    def sample(self, batch_size):
        priorities = self.priorities[:len(self.buffer)]
        probabilities = priorities ** self.alpha
        probabilities /= probabilities.sum()
        N = len(self.buffer)
        weights = (N * probabilities) ** (-self.beta)
        weights /= weights.max()

        idxs = np.random.choice(len(self.buffer), batch_size, p=probabilities)

        samples = [self.buffer[i] for i in idxs]
        states, actions, rewards, next_states, done_flags = list(zip(*samples))
        weights = weights[idxs]

        return  (np.array(states), np.array(actions), np.array(rewards),
                np.array(next_states), np.array(done_flags), np.array(weights), np.array(idxs))

    def update_priorities(self, idxs, new_priorities):
        self.priorities[idxs] = new_priorities+self.epsilon

Listing 6-12Prioritized Replay

接下来,我们来看看损失计算。代码几乎类似于我们在清单 6-5 中看到的 TD 损耗计算。有两个变化。第一个是将 TD 误差乘以权重,符合等式( 6.11 )。第二个变化是从函数内部调用update_priorities来更新缓冲区中的优先级。清单 6-13 显示了修改后的TD_loss compute_td_loss_priority_replay计算的代码。

def compute_td_loss_priority_replay(agent, target_network, replay_buffer,
                                    states, actions, rewards, next_states, done_flags, weights, buffer_idxs,
                                    gamma=0.99, device=device):

    # convert numpy array to torch tensors
    states = torch.tensor(states, device=device, dtype=torch.float)
    actions = torch.tensor(actions, device=device, dtype=torch.long)
    rewards = torch.tensor(rewards, device=device, dtype=torch.float)
    next_states = torch.tensor(next_states, device=device, dtype=torch.float)
    done_flags = torch.tensor(done_flags.astype('float32'),device=device,dtype=torch.float)
    weights = torch.tensor(weights, device=device, dtype=torch.float)

    # get q-values for all actions in current states
    # use agent network
    predicted_qvalues = agent(states)

    # compute q-values for all actions in next states
    # use target network
    predicted_next_qvalues = target_network(next_states)

    # select q-values for chosen actions
    predicted_qvalues_for_actions = predicted_qvalues[range(
        len(actions)), actions]

    # compute Qmax(next_states, actions) using predicted next q-values
    next_state_values,_ = torch.max(predicted_next_qvalues, dim=1)

    # compute "target q-values"
    target_qvalues_for_actions = rewards + gamma * next_state_values * (1-done_flags)

    #compute each sample TD error
    loss = ((predicted_qvalues_for_actions - target_qvalues_for_actions.detach()) ** 2) * weights

    # mean squared error loss to minimize
    loss = loss.mean()

    # calculate new priorities and update buffer
    with torch.no_grad():
        new_priorities = predicted_qvalues_for_actions.detach() - target_qvalues_for_actions.detach()
        new_priorities = np.absolute(new_priorities.detach().numpy())
        replay_buffer.update_priorities(buffer_idxs, new_priorities)

    return loss

Listing 6-13TD Loss with Prioritized Replay

训练代码和以前一样。可以看看listing6_3_dqn_prioritized_replay_pytorch.ipynb笔记本看详情。像以前一样,我们训练代理人,我们可以看到代理人学会了用这种方法很好地平衡杆子。图 6-5 显示了训练曲线。

img/502835_1_En_6_Fig5_HTML.jpg

图 6-5

DQN 代理人的训练曲线,具有在钢管上优先重放的经验

这就完成了关于优先重放的部分。建议您参考原始论文和代码笔记本以了解更多详细信息。

双 Q 学习

您在第五章中看到,使用相同的网络来选择最大化行动以及该最大化行动的 q 值会导致高估偏差,进而可能导致次优政策。论文《深度强化学习与双 Q 学习》的作者首先从数学上探讨了这种偏差,然后在 DQN 关于雅达利游戏的背景下进行了探讨。

让我们看看常规 DQN 中的 max 运算。我们计算 TD 目标如下:

$$ {Y}^{DQN}=r+\upgamma .\underset{a^{\prime }}{\max}\hat{q}\left({s}^{\prime },{a}^{\prime };{w}_t^{-}\right) $$

我们通过去掉下标( i )以及去掉(1-done)乘数(去掉了终态的第二项)来稍微简化这个等式。我们这样做是为了保持解释的整洁。现在,让我们通过将“ max ”移入来解开这个等式。先前的更新可以等价地写成如下:

$$ r+\upgamma .\hat{q}\left({s}^{\prime }, argma{x}_{a^{\prime }}\hat{q}\left({s}^{\prime },{a}^{\prime };{w}_t{-}\right);{w}_t{-}\right) $$

我们通过首先采取最大动作,然后采取该最大动作的 q 值,将最大值移到了内部。这类似于直接取最大 q 值。在之前的展开方程中,我们可以清楚地看到,我们使用了相同的网络权重$$ {w}_t^{-} $$,首先用于选择最佳动作,然后用于获得该动作的 q 值。这就是导致最大化偏差的原因。该论文的作者提出了一种他们称为双 DQN (DDQN)的方法,其中用于选择最佳行动的权重$$ argma{x}_{a^{\prime }}\hat{q}\left({s}^{\prime },{a}^{\prime}\right) $$来自具有权重t的在线网络,然后具有权重$$ {w}_t^{-} $$的目标网络用于选择最佳行动的 q 值。这一变化导致更新的 TD 目标如下:

$$ r+\gamma .\hat{q}\left({s}^{\prime }, argma{x}_{a^{\prime }}\hat{q}\left({s}^{\prime },{a}^{\prime };{w}_t\right);{w}_t^{-}\right) $$

注意,现在用于选择最佳行动的内部网络使用在线权重 w t* 。其他一切保持不变。我们像以前一样计算损失,然后使用梯度步长来更新在线网络的权重。我们还定期用来自在线网络的权重更新目标网络权重。我们使用的更新损失函数如下:

$$ L=\frac{1}{N}{\sum}_{i=1}^N{\left[{r}_i+\left(\left(1- don{e}_i\right).\upgamma .\hat{q}\left({s}_i^{\prime }, argma{x}_{a^{\prime }}\hat{q}\left({s}_i^{\prime },{a}^{\prime };{w}_t\right);{w}_t^{-}\right)\right)\hbox{--} \hat{q}\left({s}_i,{a}_i;{w}_t\right)\right]}² $$

(6.12)

作者表明,前面的方法导致高估偏差的显著减少,这反过来导致更好的政策。现在让我们来看看实现细节。与 DQN 实施相比,唯一会改变的是损失的计算方式。我们现在将使用方程 6.12 来计算损失。其他的一切,包括 DQN 代理代码,重放缓冲区,以及通过梯度反向传播进行训练的方式,都将保持不变。清单 6-14 给出了修正的损失函数计算。我们用q_s = agent(states)计算当前的 q 值,然后,对于每一行,选择对应于动作 a i 的 q 值。然后我们使用代理网络来计算下一个状态的 q 值:q_s1 = agent(next_states)。这用于查找每行的最佳动作,然后我们使用具有最佳动作的目标网络来查找目标 q 值。

def td_loss_ddqn(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma=0.99, device=device):

    # convert numpy array to torch tensors
    states = torch.tensor(states, device=device, dtype=torch.float)
    actions = torch.tensor(actions, device=device, dtype=torch.long)
    rewards = torch.tensor(rewards, device=device, dtype=torch.float)
    next_states = torch.tensor(next_states, device=device, dtype=torch.float)
    done_flags = torch.tensor(done_flags.astype('float32'),device=device,dtype=torch.float)

    # get q-values for all actions in current states
    # use agent network
    q_s = agent(states)

    # select q-values for chosen actions
    q_s_a = q_s[range(
        len(actions)), actions]

    # compute q-values for all actions in next states
    # use agent network (online network)
    q_s1 = agent(next_states).detach()

    # compute Q argmax(next_states, actions) using predicted next q-values
    _,a1max = torch.max(q_s1, dim=1)

    #use target network to calclaute the q value for best action chosen above
    q_s1_target = target_network(next_states)

    q_s1_a1max = q_s1_target[range(len(a1max)), a1max]

    # compute "target q-values"
    target_q = rewards + gamma * q_s1_a1max * (1-done_flags)

    # mean squared error loss to minimize
    loss = torch.mean((q_s_a - target_q).pow(2))

    return loss

Listing 6-14TD Loss with Double Q-Learning

    q_s1 = agent(next_states).detach()
    _,a1max = torch.max(q_s1, dim=1)
    q_s1_target = target_network(next_states)
    q_s1_a1max = q_s1_target[range(len(a1max)), a1max]

CartPole上运行 DDQN 产生图 6-6 中给出的训练图。您可能没有注意到很大的区别,因为CartPole是一个太简单的问题,无法显示其优势。此外,我们已经运行了少量剧集的训练算法来演示这些算法。要了解该方法的量化优势,您应该查看参考文献。

img/502835_1_En_6_Fig6_HTML.jpg

图 6-6

扁担 DDQN 训练曲线

关于 DDQN 的讨论到此结束。接下来,我们看看决斗 DQN。

决斗 DQN

到目前为止,我们所有的网络都接收状态 S,并为状态 S 中的所有动作 A 产生 Q 值 Q ( SA )。图 6-1 显示了这种网络的一个例子。然而,很多时候在特定的状态下,采取任何特定的动作都没有影响。考虑这样一种情况,一辆汽车行驶在路中间,而你的汽车周围没有汽车。在这样的场景中,稍微向左或向右,或稍微加速或稍微刹车的动作都没有影响;这些动作都产生相似的 q 值。有没有办法把一个状态的平均值和采取特定行动的优势比那个平均值分开?这就是题为“深度强化学习的决斗网络架构” 4 的论文作者在 2016 年采取的方法。他们表明,这导致了显著的改善,并且随着一个状态中可能的动作数量的增加,改善越大。

让我们来推导决斗 DQN 网络执行的计算。我们在第二章中的方程( 2)中看到了状态值和动作值函数的定义。9 )和( 2。10 ),现转载如下:

$$ {v}_{\pi }(s)={E}_{\pi}\left[\ {G}_t\ \right|\ {S}_t=s\ \Big] $$

$$ {q}_{\pi}\left(s,a\right)={E}_{\pi }\ \left[\ {G}_t\ \right|\ {S}_t=s,{A}_t=a\ \Big] $$

然后,在关于函数近似的第五章中,我们看到当我们切换到将状态/动作值表示为参数化函数时,这些方程随着参数 w 的引入而发生了一些变化。

$$ \hat{v}\left(s,w\right)\approx {v}_{\pi }(s) $$

$$ \hat{q}\left(s,a,w\right)\approx {q}_{\pi}\left(s,a\right) $$

两组方程都向我们展示了 v π 度量的是处于一般状态的值, q π 向我们展示了从状态 S 采取特定动作的值。如果我们从 V 中减去 Q ,我们会得到一个叫做优势 A 的东西。请注意,有一点符号超载。 AQ ( SA )代表动作,等式左边的A【π代表优势,而不是动作。

$$ {A}_{\uppi}\left(s,a\right)={Q}_{\uppi}\left(s,a\right)-{V}_{\uppi}(s) $$

(6.13)

作者创建了一个网络,像以前一样接受状态 S 作为输入,在几层网络之后产生两个流,一个给出状态值 V ,另一个给出优势 A ,网络的一部分是单独的层集合,一个用于 V ,一个用于 A 。最后,最后一层结合优势 A 和状态值 V 来恢复 Q 。然而,为了具有更好的稳定性,他们做了一个额外的改变,从 Q ( SA )的每个输出节点中减去优势值的平均值。神经网络实现的等式如下:

$$ \hat{Q}\left(s,a;{w}_1,{w}_2,{w}_3\right)=\hat{V}\left(s;{w}_1,{w}_2\right)+\left(\hat{A}\left(s,a;{w}_1,{w}_3\right)-\frac{1}{\left|A\right|}{\sum}_{a^{\prime }}\hat{A}\left(s,{a}^{\prime };{w}_1,{w}_3\right)\right) $$

(6.14)

在上式中,权重 w 1 对应网络的初始公共部分, w 2 对应网络预测状态值$$ \hat{V} $$的部分,最后 w 3 对应网络预测优势$$ \hat{A} $$的部分。图 6-7 显示了一个典型的网络架构。

img/502835_1_En_6_Fig7_HTML.jpg

图 6-7

决斗网。网络在初始层有一组公共的权重,然后它分支,一组权重产生值 V ,另一组产生优势 A

作者将这种架构命名为决斗网络,因为它有两个融合在一起的网络和一个初始的公共部分。由于决斗网络处于代理网络级别,因此它独立于其他组件,如重放缓冲器的类型或学习权重的方式(即,简单 DQN 或双 DQN)。因此,我们可以独立于重放缓冲器的类型或学习的类型来使用决斗网络。在我们的演练中,我们将使用一个简单的重放缓冲区,该缓冲区中的每个过渡都有统一的选择概率。此外,我们将使用 DQN 代理。与 DQN 相比,唯一的变化将是网络的构建方式。清单 6-15 显示了决斗代理网络的代码。

class DuelingDQNAgent(nn.Module):
    def __init__(self, state_shape, n_actions, epsilon=0):

        super().__init__()
        self.epsilon = epsilon
        self.n_actions = n_actions
        self.state_shape = state_shape

        state_dim = state_shape[0]
        # a simple NN with state_dim as input vector (inout is state s)
        # and self.n_actions as output vector of logits of q(s, a)
        self.fc1 = nn.Linear(state_dim, 64)
        self.fc2 = nn.Linear(64, 128)
        self.fc_value = nn.Linear(128, 32)
        self.fc_adv = nn.Linear(128, 32)
        self.value = nn.Linear(32, 1)
        self.adv = nn.Linear(32, n_actions)

    def forward(self, state_t):
        # pass the state at time t through the newrok to get Q(s,a)
        x = F.relu(self.fc1(state_t))
        x = F.relu(self.fc2(x))
        v = F.relu(self.fc_value(x))
        v = self.value(v)
        adv = F.relu(self.fc_adv(x))
        adv = self.adv(adv)
        adv_avg = torch.mean(adv, dim=1, keepdim=True)
        qvalues = v + adv - adv_avg
        return qvalues

    def get_qvalues(self, states):
        # input is an array of states in numpy and outout is Qvals as numpy array
        states = torch.tensor(states, device=device, dtype=torch.float32)
        qvalues = self.forward(states)
        return qvalues.data.cpu().numpy()

    def sample_actions(self, qvalues):
        # sample actions from a batch of q_values using epsilon greedy policy
        epsilon = self.epsilon
        batch_size, n_actions = qvalues.shape
        random_actions = np.random.choice(n_actions, size=batch_size)
        best_actions = qvalues.argmax(axis=-1)
        should_explore = np.random.choice(
            [0, 1], batch_size, p=[1-epsilon, epsilon])
        return np.where(should_explore, random_actions, best_actions)

Listing 6-15Dueling Network

我们有两层公共网络(self.fc1self.fc2)。对于 V 预测,我们在fc1fc2之上还有另外两层(self.fc_valueself.value)。类似地,为了进行优势评估,我们再次在fc1fc2之上设置了单独的两层(self.fc_advself.adv)。然后将这些输出组合起来,按照公式( 6.14 )给出修正的 q 值。代码的其余部分,如 TD 损失的计算和权重更新的梯度下降,与 DQN 保持相同。图 6-8 显示了在CartPole上训练前一个网络的结果。

img/502835_1_En_6_Fig8_HTML.jpg

图 6-8

决斗网络的训练曲线

就像我们说的,你可以试着用PrioritizedReplayBuffer.代替ReplayBuffer,他们也可以用双 DQN 代替 DQN 作为学习代理。关于决斗 DQN 的讨论到此结束。我们现在在下一节看一个非常不同的变体。

大声公 DQN

我们需要探索部分状态空间。我们一直在使用ε贪婪策略来这样做。在这个探索下,我们采取概率为(1- ε)的最大 q 值动作,我们采取概率为ε的随机动作。最近一篇题为“探索的嘈杂网络”的 2018 年论文的作者, 5 使用了一种不同的方法,将随机扰动添加到线性图层作为参数,像网络权重一样,这些也是学习的。

通常的线性层是仿射变换,如下式所示:

$$ y= wx+b $$

在噪声线性版本中,我们在权重中引入随机扰动,如下所示:

$$ y=\left({\upmu}w+{\upsigma}w\odot {\upepsilon}w\right)x+\left({\upmu}b+{\upsigma}^b\odot {\upepsilon}^b\right) $$

在前面的等式中,μw, σ wμ bσ b 是学习到的网络的权值。 ϵ wϵ b 是引入随机性导致探索的随机噪声。图 6-9 给出了线性层的噪点版本示意图,解释了我们上一段刚刚讲的方程。

img/502835_1_En_6_Fig9_HTML.jpg

图 6-9

噪声线性层。权重和偏差是均值和标准差的线性组合,就像常规线性图层中的权重和偏差一样

我们将实现论文中讨论的分解版本,其中矩阵的每个元素$$ {\upepsilon}_{i,j}^w $$都被分解。假设我们有个 p 单位的投入和个 q 单位的产出。相应地,我们生成一个p-大小的高斯噪声向量ϵ * i 和一个q-大小的高斯噪声向量ϵ j * 。每个$$ {\epsilon}_{i,j}^w $$$$ {\upepsilon}_j^b $$现在可以写成如下形式:

$$ {\upepsilon}_{i,j}^w=f\left({\upepsilon}_i\right)f\left({\upepsilon}_j\right) $$

$$ {\upepsilon}_j^b=f\left({\upepsilon}_j\right) $$

$$ f(x)=\mathit{\operatorname{sgn}}(x)\sqrt{\left|x\right|} $$

对于我们正在使用的因式分解网络,我们建议您按如下方式初始化权重:

  • μ wμ b 的每一个元素μIj,其中 p 为输入单元的个数。

  • 同样,每个元素σ iσ wσ b 被初始化为常数$ \frac{\upsigma_0}{\sqrt{p}} $,超参数 σ 0 设置为 0.5。

我们沿着 PyTorch 提供的线性图层创建一个噪波图层。我们通过从 PyTorch 扩展nn.Module来实现。这是一个简单的标准实现,您可以在init函数中创建您的权重向量。然后你写一个forward函数来获取一个输入,并通过一组噪声线性和规则线性图层进行转换。你还需要一些额外的功能。在我们的例子中,我们编写了一个名为reset_noise的函数来生成噪声 ϵ wϵ b 。这个函数在内部使用了一个叫做_noise的辅助函数。我们还有一个功能reset_parameters来按照前面概述的策略重置参数。我们可以使用一个嘈杂的网络与 DQN,DDQN,决斗 DQN,并优先重播在各种组合。但是,出于演练的目的,我们将重点关注对 DQN 使用常规重放缓冲区。我们也使用常规的 DQN 方法训练,而不是 DDQN。清单 6-16 给出了噪声线性的代码。

class NoisyLinear(nn.Module):
    def __init__(self, in_features, out_features, sigma_0 = 0.4):
        super(NoisyLinear, self).__init__()
        self.in_features  = in_features
        self.out_features = out_features
        self.sigma_0= sigma_0

        self.mu_w = nn.Parameter(torch.FloatTensor(out_features, in_features))
        self.sigma_w = nn.Parameter(torch.FloatTensor(out_features, in_features))
        self.mu_b = nn.Parameter(torch.FloatTensor(out_features))
        self.sigma_b = nn.Parameter(torch.FloatTensor(out_features))

        self.register_buffer('epsilon_w', torch.FloatTensor(out_features, in_features))
        self.register_buffer('epsilon_b', torch.FloatTensor(out_features))

        self.reset_noise()
        self.reset_params()

    def forward(self, x):
        if self.training:
            w = self.mu_w + self.sigma_w * self.epsilon_w
            b = self.mu_b + self.sigma_b * self.epsilon_b
        else:
            w = self.mu_w
            b = self.mu_b
        return F.linear(x, w, b)

    def reset_params(self):
        k = 1/self.in_features
        k_sqrt = math.sqrt(k)
        self.mu_w.data.uniform_(-k_sqrt, k_sqrt)
        self.sigma_w.data.fill_(k_sqrt*self.sigma_0)
        self.mu_b.data.uniform_(-k_sqrt, k_sqrt)
        self.sigma_b.data.fill_(k_sqrt*self.sigma_0)

    def reset_noise(self):
        eps_in = self._noise(self.in_features)
        eps_out = self._noise(self.out_features)
        self.epsilon_w.copy_(eps_out.ger(eps_in))
        self.epsilon_b.copy_(self._noise(self.out_features))

    def _noise(self, size):
        x = torch.randn(size)
        x = torch.sign(x)*torch.sqrt(torch.abs(x))
        return x

Listing 6-16Noisy Linear Layer in PyTorch

实现的其余部分保持不变。现在唯一的不同是,我们在 DQN 代理的函数sample_actions中没有ε-贪婪选择。我们还有一个reset_noise功能,用于在每批之后重置噪声。这符合论文中去相关的建议。清单 6-17 包含了之前修改过的NoisyDQN版本。其余的实现类似于普通的 DQN 代理。

class NoisyDQN(nn.Module):
    def __init__(self, state_shape, n_actions):
        super(NoisyDQN, self).__init__()
        self.n_actions = n_actions
        self.state_shape = state_shape
        state_dim = state_shape[0]
        # a simple NN with state_dim as input vector (inout is state s)
        # and self.n_actions as output vector of logits of q(s, a)
        self.fc1 = NoisyLinear(state_dim, 64)
        self.fc2 = NoisyLinear(64, 128)
        self.fc3 = NoisyLinear(128, 32)
        self.q = NoisyLinear(32, n_actions)

    def forward(self, state_t):
        # pass the state at time t through the newrok to get Q(s,a)
        x = F.relu(self.fc1(state_t))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        qvalues = self.q(x)
        return qvalues

    def get_qvalues(self, states):
        # input is an array of states in numpy and outout is Qvals as numpy array
        states = torch.tensor(states, device=device, dtype=torch.float32)
        qvalues = self.forward(states)
        return qvalues.data.cpu().numpy()

    def sample_actions(self, qvalues):
        # sample actions from a batch of q_values using greedy policy
        batch_size, n_actions = qvalues.shape
        best_actions = qvalues.argmax(axis=-1)
        return best_actions

    def reset_noise(self):
        self.fc1.reset_noise()
        self.fc2.reset_noise()
        self.fc3.reset_noise()
        self.q.reset_noise()

Listing 6-17NoisyDQN Agent in PyTorch

CartPole环境中训练一个 NoisyDQN 产生训练曲线,如图 6-10 所示。我们可能看不出这个变体和 DQN(或者所有的变体)之间有什么显著的区别。原因是我们在用一个简单的问题,对它进行短集的训练。书中的想法是教你一个特定变体的内部细节。对于改进和其他观察的彻底研究,建议你参考原始论文。此外,我们再次强调,您应该详细阅读附带的 Python 笔记本,在掌握了细节之后,您应该尝试重新编写示例代码。

img/502835_1_En_6_Fig10_HTML.jpg

图 6-10

DQN 训练图

你也可以尝试编写一个吵闹版的决斗 DQN。此外,你也可以尝试学习的 DDQN 变体。换句话说,根据我们目前所学,我们可以尝试以下组合:

  • 网络

  • DDQN(影响我们的学习方式)

  • 决斗 DQN(影响训练架构)

  • 决斗 DDQN

  • 用优先重放缓冲区替换普通重放缓冲区

  • 在任何以前的方法中,用 NoisyNets 代替ε-exploration

  • 对 TensorFlow 上的所有组合进行编码

  • 尝试许多其他健身房环境,对网络进行适当的更改(如果有的话)

  • 在 Atari 上运行其中的一些,尤其是如果你有 GPU 机器的话

分类 51 原子 DQN (C51)

在 2017 年题为“强化学习的分布式视角”的论文中, 6 作者支持 RL 的分布式本质。他们没有看 Q 值这样的期望值,而是看了 Z ,一个期望值为 Q 的随机分布。

到目前为止,我们一直在输出输入状态 sQ ( sa )值。输出中的单元数量大小为n_action。在某种程度上,输出值是预期的 Q ( sa ),使用蒙特卡洛技术对多个样本进行平均,以形成对实际预期值E**Q(sa )的估计![$$ \hat{Q}\left(s,a\right) $$。在分类的 51 原子 DQN 中,对于每个 Q ( sa ) ( n_action其中),我们现在产生一个 Q ( sa )值的分布估计:每个 Q ( san_atom(准确地说是 51)值网络现在预测建模为分类概率分布的整个分布,而不是仅仅估计 Q ( sa )的平均值。$$ Q\left(s,a\right)={\sum}_i{z}_i{p}_i\left(s,a\right) $$

pI(sa )是动作值在( sa )将为 z i 的概率。

我们现在有n_action * n_atom个输出,即n_action的每个值有n_atom个输出。此外,这些输出是概率。对于一个动作,我们有n_atom个概率,这些是 q 值在V_minV_max范围内的任意一个n_atom离散值中的概率。你应该参考前面提到的论文了解更多细节。

在分布式 RL 的 C51 版本中,作者在值-10 到 10 上取𝑖为 51 个原子(支持点)。我们将在代码中使用相同的设置。因为这些值在代码中是参数化的,所以欢迎您更改它们并探究其影响。

应用贝尔曼更新后,值会发生变化,可能不会落在 51 个支持点上。有一个投影的步骤,将概率分布带回 51 个原子的支撑点。

损失也从均方误差替换为交叉熵损失。代理使用ε-贪婪策略进行训练,类似于 DQN。整个数学是相当复杂的,这将是一个很好的练习,让你把文章和代码一起过一遍,把每一行代码和文章中的具体细节联系起来。这是你作为 RL 实践者需要具备的一项重要技能。

类似于 DQN 方法,我们有一个类CategoricalDQN,它是神经网络,通过它将状态 s 作为输入来产生 Q 的分布Z(sa )。有一个计算 TD 损耗的函数:td_loss_categorical_dqn。如前所述,我们需要一个投影步骤将值带回n_atom支持点,这在函数compute_projection中执行。计算损耗计算时,函数compute_projectiontd_loss_categorical_dqn内使用。其余的训练和以前一样。

图 6-11 给出了在CartPole环境下运行的训练曲线。

img/502835_1_En_6_Fig11_HTML.jpg

图 6-11

分类 51 原子 DQN (C51)训练图

分位数回归 DQN

在关于 C51 算法的论文于 2017 年年中发表后不久,一些原始作者和其他几位作者(都来自 DeepMind)提出了一种变体,他们称之为分位数回归 DQN (QR-DQN)。在一篇题为“使用分位数回归的分布式强化学习” 7 的论文中,作者使用了一种与最初的 C51 略有不同的方法,但仍然在相同的分布焦点区域 RL 内。

类似于分布 RL 的 C51 方法,QR-DQN 方法也依赖于使用分位数来预测 Q ( sa )的分布,而不是预测 Q ( sa )的平均值的估计。C51 和 QR DQN 都是分布式 RL 的变体,由 DeepMind 的科学家制作。

C51 方法将名为 Z π (s,a)的 Q π (s,a)的分布建模为 V min 到 V max 范围内的概率过定点的分类分布。这些点上的概率就是网络所学习到的。这种方法导致在贝尔曼更新之后使用投影步骤,以将新的概率带回到固定支持点n_atoms,该固定支持点在VminVmax上均匀分布。虽然结果是可行的,但这与推导该算法的理论基础有点脱节。

QR-DQN 的方法略有不同。支撑点仍然是 N,但是现在概率固定为 1/N,这些点的位置由网络学习。引用作者的话:

我们“转置”来自 C51 的参数化:前者使用 N 个固定位置用于其近似分布并调整它们的概率,而我们将固定的、均匀的概率分配给 N 个可调整的位置。

DQ DQN 使用的损失是分位数回归损失与胡伯损失的混合。这叫做分位数胡伯损失;参考文献中的等式 9 和 10 给出了细节。我们没有在这里显示代码清单,因为我们希望您阅读论文,并将论文中的等式与笔记本listing6_8_qr_dqn_pytorch.ipynb中的代码进行匹配。这篇论文充满了数学,除非你对高等数学很熟悉,否则你应该试着把重点放在方法的更高层次的细节上。

图 6-12 为训练曲线。

img/502835_1_En_6_Fig12_HTML.jpg

图 6-12

分位数回归 DQN

马后炮经验回放

在 open ai 2018 年发表的题为“后知之明体验回放”的论文中, 8 作者提出了一种在回报稀少的环境中学习的有效方法。常见的方法是以某种方式塑造奖励函数,以引导代理朝向最优化。这是不能一概而论的。

与从成功的结果中学习的 RL 代理相比,人类似乎不仅从成功的结果中学习,也从不成功的结果中学习。这是被称为后见之明经验回放(她)的后见之明回放方法中提出的想法的基础。虽然她可以结合各种 RL 方法,在我们的代码走查,我们将使用她与决斗 DQN,给我们她-DQN。

在 HER 方法中,在一个情节结束后,比如说一个不成功的情节,我们形成一个次要目标,其中原始目标被终止前的最后一个状态代替,作为该轨迹的目标。

说一集已经打完: s 0s 1 ,…。s??T。通常我们在重放缓冲区中存储一个元组( s ta trst+1done )。假设这一集的目标是 g ,这一次无法实现。在 HER 方法中,我们将在重放缓冲区中存储以下内容:

  • (s**| |【a】**

**** (st|ga tr(sta tg 对奖励进行了修改,以显示状态转换stst+1对于g的子目标是好是坏。***

**原始文件讨论了形成这些子目标的各种策略。我们将使用一个名为 future 的,它是一个带有 k 个随机状态的重播,这些状态来自与正在重播的过渡相同的一集,并在它之后被观察到。

我们还使用了不同于以往笔记本电脑的环境。我们将使用一个比特翻转实验环境。假设您有一个 n 位向量,每一位都是{0,1}范围内的二进制数。因此,有 2 个 n 个 的组合可能。在重置时,环境以随机选择的 n 位配置开始,目标也随机选择为一些不同的 n 位配置。每个动作都是翻转一点。要翻转的位是代理试图学习的策略π( a | s )。如果代理能够找到与目标匹配的正确配置,或者当代理在一集中用尽了 n 个动作时,一集结束。清单 6-18 显示了环境的代码。完整的代码在笔记本listing6_9_her_dqn_pytorch.ipynb里。

class BitFlipEnvironment:

    def __init__(self, bits):
        self.bits = bits
        self.state = np.zeros((self.bits, ))
        self.goal = np.zeros((self.bits, ))
        self.reset()

    def reset(self):
        self.state = np.random.randint(2, size=self.bits).astype(np.float32)
        self.goal = np.random.randint(2, size=self.bits).astype(np.float32)
        if np.allclose(self.state, self.goal):
            self.reset()
        return self.state.copy(), self.goal.copy()

    def step(self, action):
        self.state[action] = 1 - self.state[action]  # Flip the bit on position of the action
        reward, done = self.compute_reward(self.state, self.goal)
        return self.state.copy(), reward, done

    def render(self):
        print("State: {}".format(self.state.tolist()))
        print("Goal : {}\n".format(self.goal.tolist()))

    @staticmethod
    def compute_reward(state, goal):
        done = np.allclose(state, goal)
        return 0.0 if done else -1.0, done

Listing 6-18Bit-Flipping Environment

我们已经实现了我们自己的renderstep功能,因此我们的环境界面仍然与 Gym 中的界面相似,这样我们就可以使用我们以前开发的机器。我们还有一个自定义函数compute_reward,当输入一个状态和一个目标时,返回rewarddone标志。

作者表明,对于常规的 DQN,其中状态(n 位的配置)被表示为深度网络,常规的 DQN 代理几乎不可能学习超过 15 位的组合。然而,结合赫尔-DQN 方法,代理人能够很容易地学习甚至像 50 左右的大数字组合。在图 6-13 中,我们给出了论文中的完整伪代码,并做了一些修改,使其与我们的符号相匹配。

Hindsight Experience Replay (HER)

img/502835_1_En_6_Fig13_HTML.png

图 6-13

她使用了未来策略

我们使用决斗 DQN。代码中最有趣的部分是按照图 6-13 中给出的伪代码实现 HER 算法。清单 6-19 是该伪代码的逐行实现。

def train_her(env, agent, target_network, optimizer, td_loss_fn):

    success_rate = 0.0
    success_rates = []

    exp_replay = ReplayBuffer(10**6)

    for epoch in range(num_epochs):

        # Decay epsilon linearly from eps_max to eps_min
        eps = max(eps_max - epoch * (eps_max - eps_min) / int(num_epochs * exploration_fraction), eps_min)
        print("Epoch: {}, exploration: {:.0f}%, success rate: {:.2f}".format(epoch + 1, 100 * eps, success_rate))
        agent.epsilon = eps
        target_network.epsilon = eps

        successes = 0
        for cycle in range(num_cycles):

            for episode in range(num_episodes):

                # Run episode and cache trajectory
                episode_trajectory = []
                state, goal = env.reset()

                for step in range(num_bits):

                    state_ = np.concatenate((state, goal))
                    qvalues = agent.get_qvalues([state_])
                    action = agent.sample_actions(qvalues)[0]
                    next_state, reward, done = env.step(action)

                    episode_trajectory.append((state, action, reward, next_state, done))
                    state = next_state
                    if done:
                        successes += 1
                        break

                # Fill up replay memory
                steps_taken = step
                for t in range(steps_taken):

                    # Usual experience replay
                    state, action, reward, next_state, done = episode_trajectory[t]
                    state_, next_state_ = np.concatenate((state, goal)), np.concatenate((next_state, goal))
                    exp_replay.add(state_, action, reward, next_state_, done)

                    # Hindsight experience replay
                    for _ in range(future_k):
                        future = random.randint(t, steps_taken)  # index of future time step
                        new_goal = episode_trajectory[future][3]  # take future next_state from (s,a,r,s',d) and set as goal
                        new_reward, new_done = env.compute_reward(next_state, new_goal)
                        state_, next_state_ = np.concatenate((state, new_goal)), np.concatenate((next_state, new_goal))
                        exp_replay.add(state_, action, new_reward, next_state_, new_done)

            # Optimize DQN
            for opt_step in range(num_opt_steps):
                # train by sampling batch_size of data from experience replay
                states, actions, rewards, next_states, done_flags = exp_replay.sample(batch_size)
                # loss = <compute TD loss>
                optimizer.zero_grad()
                loss = td_loss_fn(agent, target_network,
                                  states, actions, rewards, next_states, done_flags,
                                  gamma=0.99,
                                  device=device)
                loss.backward()
                optimizer.step()

            target_network.load_state_dict(agent.state_dict())

        success_rate = successes / (num_episodes * num_cycles)
        success_rates.append(success_rate)

    # print graph
    plt.plot(success_rates, label="HER-DQN")

    plt.legend()
    plt.xlabel("Epoch")
    plt.ylabel("Success rate")
    plt.title("Number of bits: {}".format(num_bits))
    plt.show()

Listing 6-19Hindsight Experience Replay Implementation

在代码中,我们使用之前编码的td_loss_dqn函数来计算 TD 损耗,并采取梯度步骤。我们还从ε=0.2 的非常探索性的行为策略开始,并在训练进行到一半时慢慢将其降低到零。代码的其余部分与图 6-13 中的伪代码逐行匹配。

图 6-14 为训练曲线。对于 50 位的BitFlipping环境,代理和她一起能够 100%成功地解决环境问题。请记住,环境以 50 位的随机组合作为起点,以另一个随机组合作为目标。代理最多有 50 个翻转动作来达到目标组合。彻底的搜索需要代理尝试 2 个 50 个组合中的每一个,除了最初开始的那个。

img/502835_1_En_6_Fig14_HTML.jpg

图 6-14

成功率图:她使用未来策略的比特翻转环境

这让我们结束了对她的讨论,也结束了这一章。

摘要

这是一个相当长的章节,我们看了 DQN 和它的大多数流行和最近的变种。

我们从快速回顾 Q-learning 和 DQN 更新方程的推导开始。然后,我们在 PyTorch 和 TensorFlow 中查看了一个简单的CartPole环境中 DQN 的实现。在这之后,我们研究了雅达利游戏,这是 2013 年的原始灵感,能够在强化学习的背景下使用深度学习。我们研究了额外的预处理步骤和网络从线性到基于卷积层的变化。

接下来,我们讨论了优先重放,其中根据分配给样本的某个重要性分数从缓冲区中选取样本,该分数与 TD 误差的大小成比例。

接下来,我们在 DQN 的背景下重新审视了双 Q 学习,即所谓的双 DQN。这是一种影响学习方式并试图减少最大化偏差的方法。

然后我们看了决斗 DQN,其中使用了两个网络和一个初始共享网络。随后是噪声层,ε贪婪的探索被噪声层所取代。

接下来,我们研究了两种类型的分布 RL,在这两种分布 RL 下,网络产生了 q 值的分布 Z 。它不是产生预期的动作值 Q ( SA ),而是输出整个分布,特别是分类分布。我们还看到了投影步骤和损失的使用,如交叉熵分位数 huber 损失

最后一部分是关于后知之明的经验回放,它解决了在奖励稀少的环境中学习的问题。以前的学习方法集中在只从成功的结果中学习,但是事后诸葛亮也允许我们从不成功的结果中学习。

我们在本章看到的许多算法和方法都是最先进的研究。通过查看原始论文以及逐行浏览代码,您将会收获良多。我们还建议了各种组合,你可以尝试编码,以进一步巩固你脑海中的概念。

本章总结了我们对基于价值的方法的探索,在这种方法中,我们首先通过使用 VQ 函数来学习策略,然后使用这些函数来找到最优策略。在下一章中,我们将切换到基于策略的方法,在这种方法中,我们找到最佳策略,而不需要学习 V /Q 函数这一中间步骤。

***

七、策略梯度算法

到目前为止,我们一直专注于基于模型和无模型的方法。使用这些方法的所有算法都估计了给定当前策略的动作值。在第二步中,这些估计值被用于通过选择给定状态中的最佳动作来找到更好的策略。这两个步骤反复循环进行,直到观察不到数值的进一步提高。在这一章中,我们将通过直接在策略空间中操作来研究学习最优策略的不同方法。我们将在不明确学习或使用状态或状态行为值的情况下改进策略。

我们还将看到,基于策略的方法和基于价值的方法并不是两种不相交的方法。有些方法将基于价值的方法和基于政策的方法结合起来,如行动者-批评家方法。

本章的核心将是建立定义,并从数学上推导出基于策略的优化的关键部分。基于策略的方法是目前强化学习中解决大规模连续空间问题最流行的方法之一。

介绍

我们首先从简单的基于模型的方法开始我们的旅程,其中我们通过迭代贝尔曼方程来解决小的、离散的状态空间问题。接下来,我们讨论了使用蒙特卡罗和时间差分方法的无模型设置。然后,我们使用函数近似将分析扩展到大的或连续的状态空间。特别是,我们将 DQN 及其许多变体视为政策学习的途径。

所有这些方法的核心思想是首先了解当前政策的价值,然后对政策进行迭代改进以获得更好的回报。这是使用广义政策迭代 (GPI) 的一般框架完成的。如果你想一想,你会意识到我们的真正目标是学习一个好的政策,我们使用价值函数作为中间步骤来指导我们找到一个好的政策。

这种学习价值函数以改进策略的方法是间接的。与直接学习好的政策相比,学习价值观并不总是那么容易。考虑一下你在慢跑道上遇到一只熊的情况。你首先想到的是什么?你的大脑是否试图评估可能行动的状态(你面前的熊)和行动值(“冻结”、“抚摸熊”、“逃命”或“攻击熊”)?还是几乎确定地“运行”,即遵循概率为 1.0 的action="run"策略?我确信答案是后者。让我们举另一个玩 Atari Breakout 游戏的例子,我们在前一章的 DQN 例子中使用的那个。考虑球几乎接近你的球拍的右边缘并远离球拍的情况(“状态”)。作为一个人类玩家,你会怎么做?你是否试图评估两个动作的状态动作值 Q ( sa ),然后决定桨需要向右还是向左移动?还是只看状态,学会右移球拍避免球掉下来?同样,我肯定答案是第二个。在这两个例子中,后一种更容易的选择是学习直接行动,而不是先学习值,然后使用状态值在可能的选择中找到最佳行动。

基于政策的方法的利弊

前面的例子表明,在许多情况下,与学习值函数然后使用它们来学习策略相比,学习策略(在给定状态下采取什么行动)更容易。那么,为什么我们要经历像萨莎、Q-learning、DQN 等价值方法的途径呢?,一点都没有?好吧,政策学习虽然更容易,但也不是一帆风顺的。它有自己的一系列挑战,特别是基于我们目前的知识和可用的算法,这些挑战如下:

  • 优势

    • 更好的融合

    • 在高维连续动作空间有效

    • 学习随机政策

  • 不足之处

    • 通常收敛于局部最大值而不是全局最大值

    • 政策评估效率低且差异大

详细阐述这几点,还记得 DQN 学习曲线吗?我们看到政策的价值在培训中变化很大。在“车杆子”问题中,我们看到分数(如上一章所有训练进度图左图所示)波动很大。对于更好的政策没有稳定的一致意见。基于策略的方法,特别是我们将在本章末尾讨论的一些附加控制,确保我们在学习过程中朝着更好的策略平稳地前进。

我们的行动空间一直是一个可能行动的小集合。即使在函数近似与 DQN 这样的深度学习相结合的情况下,我们的动作空间也仅限于个位数。这些动作是一维的。想象一下,试图一起控制行走机器人的各个关节。我们需要对机器人的每个关节做出决定,在机器人的给定状态下,这些单独的选择合在一起会做出一个完整的动作。此外,每个关节的单独动作不会是离散的。最有可能的是,这些动作,如马达的速度或手臂或腿需要移动的角度,将会在一个连续的范围内。基于策略的方法更适合处理这些操作。

在我们迄今为止看到的所有基于价值的方法中,我们总是学到一种最优策略——一种确定性策略,在这种策略中,我们确切地知道在给定状态下应该采取的最佳行动。实际上,我们不得不引入探索的概念,使用ε-贪婪策略来尝试不同的行动,随着代理人学会采取更好的行动,探索概率会降低。最终的结果总是一个确定的政策。然而,确定性策略并不总是最优的。在有些情况下,最优策略是以某种概率分布采取多个行动,尤其是在多代理环境中。如果你有一些博弈论的经验,你会立即从囚徒困境和相应的纳什均衡中意识到这一点。不管怎样,我们来看一个简单的情况。

你玩过石头剪子布的游戏吗?这是一个双人游戏。在一个回合中,每个玩家必须从剪刀、石头或布三个选项中选择一个。两个玩家同时这样做,同时展示他们的选择。规则规定剪刀打败布是因为剪刀能剪出布,石头打败剪刀是因为石头能砸破剪刀,而布打败石头是因为纸能盖住石头。

什么是最好的政策?没有明显的赢家。如果你总是选择,比如说,摇滚,那么我作为你的对手会利用这些知识,总是选择纸。你能想到任何其他确定性的政策吗(例如,总是从三者中选择一个)?为了避免对手利用你的策略,你必须完全随机地做出选择。你必须以相等的概率随机选择剪刀或石头或布,即随机策略。确定性策略是随机策略的一种特殊形式,其中一个选择的概率为 1.0,所有其他行为的概率为零。随机策略更通用,这就是基于策略的方法所学习的。

以下是确定性策略:

$$ a={\uppi}_{\uptheta}(s) $$

换句话说,这是在状态 s 时要采取的具体动作 a

这是随机策略:

$$ a\sim {\uppi}_{\uptheta}\left(a|s\right) $$

换句话说,这就是给定状态下 s 的动作概率分布。

也有不利之处。基于策略的方法虽然具有良好的收敛性,但可能收敛到局部最大值。第二大缺点是基于策略的方法不学习任何价值函数的直接表示,这使得评估给定策略的价值效率低下。评估策略通常需要使用策略播放代理的多个片段,然后使用这些结果来计算策略值,本质上是 MC 方法,这带来了估计策略值的高方差问题。我们将通过结合基于价值的方法和基于政策的方法这两个领域的优点,找到解决这一问题的方法。这就是所谓的演员兼评论家算法家族。

策略表示

在前一章中,我们讨论了函数近似的无模型设置,我们在方程( 5)中表示了值函数。1 )如下:

$$ \hat{v}\left(s;w\right)\approx {v}_{\pi }(s) $$

$$ \hat{q}\left(s,a;w\right)\approx {q}_{\pi}\left(s,a\right) $$

我们有一个权重为 w 的模型(线性模型或神经网络)。我们用由权重 w 参数化的函数来表示状态值 v 和状态动作值 q 。相反,我们现在将直接参数化策略,如下所示:

$$ \pi \left(a|s;\uptheta \right)\approx {\uppi}_{\uptheta}\left(a|s\right) $$

离散案例

对于不太大的离散动作空间,我们实际上会参数化另一个函数 h ( sa;θ)用于状态-动作对。概率分布将使用 h 的软最大值形成。

$$ \uppi \left(a|s;\uptheta \right)=\frac{e^{h\left(s,a;\uptheta \right)}}{\sum_b{e}^{h\left(s,b;\uptheta \right)}} $$

h ( sa;θ)被称为逻辑动作偏好。这类似于我们在监督分类情况下采用的方法。在监督学习中,我们输入观察值 X ,在 RL 中,我们将状态 S 输入到模型中。在监督情况下,模型的输出是属于不同类别的输入 X 的逻辑值。而在 RL 中,模型的输出是采取该特定动作的动作偏好 ha。

连续案例

在连续动作空间中,策略的高斯表示是自然的选择。假设我们的行动空间是连续和多维的,比如说,维度为 d 。我们的模型将以状态 S 作为输入,并产生多维均值向量∈ R d 。方差σ2Id也可以参数化或者可以保持不变。代理将遵循的策略是具有均值 μ 和方差σ2Id的高斯策略。

$$ \pi \left(a|s;\theta \right)\sim N\left(\mu, {\sigma}²{I}_d\right) $$

政策梯度推导

推导基于策略的算法的方法类似于我们在监督学习中所做的。下面是我们提出算法的步骤概要:

  1. 我们形成一个我们想要最大化的目标,就像监督学习一样。这将是遵循一项政策所获得的全部回报。这将是我们希望最大化的目标。

  2. 我们将导出梯度更新规则来执行梯度上升。我们正在做梯度上升,而不是梯度下降,因为我们的目标是最大化总平均奖励。

  3. 我们需要将梯度更新公式重新转换为期望值,以便梯度更新可以使用样本来近似。

  4. 我们将正式地将更新规则转换成一个算法,该算法可以与 PyTorch 和 TensorFlow 等自动微分库一起使用。

目标函数

让我们从我们想要最大化的目标开始。正如前面列表中第一个项目符号所强调的,它将是策略的价值,即代理通过遵循策略可以获得的奖励。预期回报的表现形式有很多变化。我们将看看其中的一些,并简要讨论何时使用哪种表示法的背景。然而,算法的详细推导将使用其中一个变体来完成,因为其他奖励公式的推导非常相似。奖励函数及其变体如下:

  • 未打折的插曲 : $$ J\left(\theta \right)={\sum}_{t=0}^{T-1}{r}_r $$

  • 偶发性打折 : $$ J\left(\uptheta \right)={\sum}_{t=0}{T-1}{\upgamma}t{r}_t $$

  • 无限地平线打折 : $$ J\left(\uptheta \right)={\sum}_{t=0}^{\infty }{\upgamma}^t{r}_t $$

  • 平均奖励 : $$ J\left(\uptheta \right)=\underset{T\to \infty }{\mathit{\lim}}\frac{1}{T}\sum {r}_t $$

我们的大部分推导将遵循一个不确定的奖励结构,只是为了保持数学简单,并专注于推导的关键方面。

我也想让你感受一下折现因子 γ 。折扣用于无限公式中,以保持总和有界。通常,我们使用 0.99 或类似的折扣值来获得理论上有界的总和。在某些公式中,贴现因子还扮演着利息的角色——例如,今天的奖励比明天同样的奖励更有价值。使用一个折扣因子带来了今天有利于奖励的概念。贴现因子也用于通过提供时间范围的软截止来减少估计中的方差。

假设你在每一个时间步都得到 1 的回报,你使用的贴现因子是γ。这个无穷级数的和是$$ \frac{1}{1-\upgamma} $$。假设我们有 γ = 0.99。无穷级数和等于 100。因此,你可以认为 0.99 的折扣将你的视野限制在 100 步,在这 100 步中,你每一步都获得 1 英镑的奖励,这样你总共获得 100 英镑。

总的来说,折现率γ意味着时间跨度为$$ \frac{1}{1-\gamma } $$步。

使用γ还可以确保在轨迹的初始阶段改变政策行动的影响比在轨迹的后期阶段决策的影响对政策的整体质量有更大的影响。

回到推导,现在让我们计算用于改进策略的梯度更新。代理遵循由 θ 参数化的策略。

策略由 θ 参数化。

$$ {\uppi}_{\uptheta}\left(a|s\right) $$

(7.1)

代理遵循策略并生成轨迹τ,如下所示:

$$ {s}_1\to {a}_1\to {s}_2\to {a}_2\to \dots .\to {s}_{T-1}\to {a}_{T-1}\to {s}_T\to {a}_T $$

这里, s T 不一定是终点状态,而是某个时间范围 T 直到我们考虑的轨迹。

轨迹的概率τ取决于转移概率p(st+1|sta t )和政策πθ(at|)它由以下表达式给出:

$$ {p}_{\uptheta}\left(\uptau \right)={p}_{\uptheta}\left({s}_1,{a}_1,{s}_2,{a}_2,\dots, {s}_T,{a}_T\right)=p\left({s}_1\right){\prod}_{t=1}^T{\uppi}_{\uptheta}\left({a}_t|{s}_t\right)p\left({s}_{t+1}|{s}_t,{a}_t\right) $$

(7.2)

遵循策略π的预期回报由下式给出:

$$ J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[{\sum}_tr\left({s}_t,{a}_t\right)\right] $$

(7.3)

我们要找到使期望报酬/回报 J (θ)最大化的θ。换句话说,最优θ=θ由以下表达式给出:

$$ {\uptheta}^{\ast }=\underset{\uptheta}{\arg\ \max}\kern0.75em {E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[{\sum}_tr\left({s}_t,{a}_t\right)\right] $$

(7.4)

在我们继续之前,让我们看看我们将如何评估目标 J (θ)。我们将( 7.3 中的期望值转换为样本的平均值;也就是说,我们通过策略多次运行代理,收集 N 条轨迹。我们计算每条轨迹中的总奖励,并对跨越 N 条轨迹的总奖励取平均值。这是期望值的蒙特卡罗(MC)估计。这就是我们谈论评估政策时的意思。我们得到的表达式如下:

$$ J\left(\uptheta \right)\approx \frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}Tr\left({s}_ti,{a}_ti\right) $$

(7.5)

导数更新规则

继续,让我们试着找到最优θ。为了让记法更容易理解,我们将∑tr(sta t )替换为 r (τ)。重写( 7.3 ),我们得到如下:

$$ J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[r\left(\uptau \right)\right]=\int {p}_{\uptheta}\left(\uptau \right)r\left(\uptau \right)\ d\tau $$

(7.6)

我们取上一个表达式相对于θ的梯度/导数。

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={\nabla}_{\uptheta}\int {p}_{\uptheta}\left(\uptau \right)r\left(\uptau \right)\ d\uptau $$

(7.7)

利用线性,我们可以移动积分内的梯度。

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)=\int {\nabla}_{\uptheta}{p}_{\uptheta}\left(\uptau \right)\kern0.5em r\left(\uptau \right)\ d\uptau $$

(7.8)

用对数求导的小技巧,我们知道∇xf(x)=f(x)∇x对数 f ( x )。利用这一点,我们可以把前面的表达式( 7.8 )写成如下:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)=\int {p}_{\uptheta}\left(\uptau \right)\ \left[{\nabla}_{\uptheta}\log {p}_{\uptheta}\left(\uptau \right)r\left(\uptau \right)\right]\kern0.5em d\uptau $$

(7.9)

我们现在可以将积分写回期望值,这给出了下面的表达式:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\kern0.5em \left[{\nabla}_{\theta}\log {p}_{\theta}\left(\tau \right)\ r\left(\tau \right)\right] $$

(7.10)

让我们从方程( 7.2 )写出 p θ (τ)的完整表达式,从而展开术语∇θlogpθ(τ)。

$$ {\nabla}_{\uptheta}\log {p}_{\uptheta}\left(\uptau \right)={\nabla}_{\uptheta}\log \left[p\left({s}_1\right){\prod}_{t=1}^T{\uppi}_{\uptheta}\left({a}_t|{s}_t\right)p\left({s}_{t+1}|{s}_t,{a}_t\right)\right] $$

(7.11)

我们知道,项数乘积的对数可以写成项数对数的和。换句话说:

$$ \log {\prod}_i{f}_i(x)={\sum}_i\mathit{\log}{f}_i(x) $$

(7.12)

将( 7.12 )代入方程( 7.11 ,得到如下结果:

$$ {\nabla}_{\uptheta}\log {p}_{\uptheta}\left(\uptau \right)={\nabla}_{\uptheta}\left[\mathit{\log}\ p\left({s}_1\right)+{\sum}_{t=1}^T\left{\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)+\log p\left({s}_{t+1}|{s}_t,{a}_t\right)\right}\right] $$

(7.13)

( 7.13 )中唯一依赖于θ的项是πθ(at|st)。另外两个术语log p(s1)和 logp(st+1|sta t )不依赖于θ。相应地,我们可以将前面的表达式( 7.13 )简化如下:

$$ {\nabla}_{\uptheta}\log {p}_{\uptheta}\left(\uptau \right)={\sum}_{t=1}^T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right) $$

(7.14)

将方程( 7.14 )代入方程( 7.10 )中∇ θ J (θ)的表达式,并将 r (τ)展开为∑tr(sta

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[\left({\sum}_{t=1}^T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\right)\left({\sum}_{t=1}^Tr\left({s}_t,{a}_t\right)\right)\right] $$

*(7.15)

现在,我们可以用多个轨迹的估计值/平均值替换外部预期,以获得政策目标的梯度的以下表达式:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)\approx \frac{1}{N}{\sum}_{i=1}N\left[\left({\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\right)\left({\sum}_{t=1}Tr\left({s}_ti,{a}_t^i\right)\right)\right] $$

(7.16)

其中上标索引 i 表示Ith轨迹。

为了改进政策,我们朝着∇θj(θ)的方向迈出了+ve 的一步。

$$ \uptheta =\uptheta +\upalpha {\nabla}_{\uptheta}J\left(\uptheta \right) $$

(7.17)

综上所述,我们设计一个以状态 s 为输入的模型,并产生策略分布π θ ( a | s )作为模型的输出。我们使用由当前模型参数θ确定的策略来生成轨迹,计算每个轨迹的总回报。我们用( 7.16 )计算∇θj(θ),然后用( 7.17 )中的表达式θ = θ + α∇ θ J (θ)改变模型参数θ。

更新规则背后的直觉

让我们开发一些方程式背后的直觉( 7.16 )。让我们用文字来解释这个等式。我们对 N 条轨迹进行平均,这是最外面的和。轨迹的平均值是多少?对于每一条轨迹,我们查看我们在该轨迹中获得的总回报,并将其乘以该轨迹上所有行为的对数概率之和。

现在假设一条轨迹的总回报rI)为+ve。第一个内部和中的每个梯度——即$$ {\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right) $$,该行为对数概率的梯度——乘以总回报 r* i )。它导致单个梯度对数项被轨迹的总回报放大,在方程( 7.17 )中,它的贡献是将模型参数θ向$$ {\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right) $$的+ve 方向移动,即增加系统处于状态$$ {s}_t^i $$时采取行动$$ {a}_t^i $$的概率。但是,如果rI*)是一个-ve 量,则方程( 7.16 )和( 7.17 )导致θ向-ve 方向移动,导致系统处于状态$$ {s}_t^i $$时采取动作$$ {a}_t^i $$的概率降低。

我们可以总结整个解释说,政策优化是所有关于试错。我们推出多种轨迹。对于那些好的轨迹,沿着轨迹的所有动作的概率增加。对于不良轨迹,沿着这些不良轨迹的所有动作的概率都降低了,如图 7-1 所示。

img/502835_1_En_7_Fig1_HTML.jpg

图 7-1

轨迹展开。轨迹 1 是好的,我们希望模型产生更多的轨迹。轨迹 2 不好也不坏,模型不要太担心。轨迹 3 是不好的,我们希望模型能降低它的概率

让我们通过比较( 7.17 )中的表达式和最大似然的表达式来看看同样的解释。如果我们只想对看到我们看到的轨迹的概率进行建模,我们会得到最大似然估计——我们观察到一些数据(轨迹),我们希望建立一个产生观察到的数据/轨迹的概率最高的模型。这是最大似然模型的建立。在这种情况下,我们将得到如下等式:

$$ {\nabla}_{\uptheta}{J}_{ML}\left(\uptheta \right)\approx \frac{1}{N}{\sum}_{i=1}N\left[\left({\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\right)\right] $$

(7.18)

在方程( 7.18 )中,我们只是增加动作的概率来增加轨迹的整体概率。我们在( 7.16 )的政策梯度中做了同样的事情,只是我们用回报来衡量对数概率梯度,以便增加好的轨迹和减少坏的轨迹概率——而不是增加所有轨迹的概率。

在我们结束这一节之前,我们想做的一个观察是关于马尔可夫性质和部分可观测性。在推导过程中,我们并没有真正使用马尔可夫假设。最后方程( 7.16 )只是说增加好东西的概率,减少坏东西的概率。到目前为止,我们还没有使用过贝尔曼方程。政策梯度也适用于非马尔可夫结构。

强化算法

我们现在将方程( 7.16 )转换成策略优化的算法。我们给出了图 7-2 中的基本算法。它被称为强化。

REINFORCE

img/502835_1_En_7_Fig2_HTML.png

图 7-2

强化算法

让我们看看一些实现级别的细节。假设您使用神经网络作为模型,该模型将状态值作为输入,并生成在该状态下采取所有可能行动的 logit(对数概率)。图 7-3 显示了这种模型的示意图。

img/502835_1_En_7_Fig3_HTML.jpg

图 7-3

预测政策的神经网络模型

我们使用 PyTorch 或 TensorFlow 等自动分化库。我们不明确地计算微分。等式( 7.16 )给出了∇θj(θ)的表达式。用 PyTorch 或者 TensorFlow,我们需要一个表达式 J (θ)。神经网络模型会以状态 S 为输入,产生πθ(at|St)。我们需要使用这个输出,并执行进一步的计算,以得出 J (θ)的表达式。PyTorch 或 TensorFlow 等自动微分软件包将根据 J (θ)的表达式自动计算梯度∇ θ J (θ)。 J (θ)的正确表达式如下:

$$ \overset{\sim }{J}\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N\left[\left({\sum}_{t=1}T\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\right)\left({\sum}_{t=1}Tr\left({s}_ti,{a}_t^i\right)\right)\right] $$

(7.19)

您可以检查并确认该表达式的梯度将为我们提供∇θj(θ)的正确值,如( 7.16 )所示。

( 7.19 中的表达式被称为伪目标。这是我们需要在 PyTorch 和 TensorFlow 等自动差异库中实现的表达式。我们计算对数概率$$ \log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right) $$,用轨迹的总回报$$ \left({\sum}_{t=1}Tr\left({s}_ti,{a}_t^i\right)\right) $$对概率进行加权,然后计算加权量的负对数似然(NLL,或交叉熵损失),给出我们在( 7.20 中的表达式。这类似于我们在监督学习设置中用于训练多类分类模型的方法。唯一的区别是用轨迹回报来衡量对数概率。这是我们在行动离散时将采取的方法。我们在 PyTorch/TensorFlow 中实现的损失如下:

$$ {L}_{cross- entropy}\left(\uptheta \right)=-1\ast \kern0.5em \frac{1}{N}{\sum}_{i=1}N\left[\left({\sum}_{t=1}T\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\right)\left({\sum}_{t=1}Tr\left({s}_ti,{a}_t^i\right)\right)\right] $$

(7.20)

请注意 PyTorch 和 TensorFlow 通过向损失的负方向迈出一步来最小化损失。还要注意( 7.20 )的-ve 梯度是( 7.19 )的+ve 梯度,因为在( 7.20 )中存在-1 的因子。

接下来,我们看看动作连续的情况。如所讨论的,由θ参数化的模型将状态 S 作为输入,并产生多元正态分布的平均值μ。我们考虑的是正态分布的方差已知并固定为某个小值的情况,比如说σ2Id

$$ \pi \left(a|s;\theta \right)\sim N\left(\mu, {\sigma}²{I}_d\right) $$

假设状态为$$ {s}_t^i $$,模型产生的平均值为$$ {\upmu}_{\theta}\left({s}_t^i\right) $$$$ \log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right) $$的值由下式给出:

$$ \log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)=\log \frac{1}{\sqrt{2\pi}\sigma }{e}{-\frac{1}{2{\sigma}²}{\left({a}_ti-{\mu}_{\theta}\right)}²} $$

$$ =-\frac{1}{2}\ \log 2\pi -\log \sigma -\frac{1}{2{\sigma}²}{\left({a}_t^i-{\mu}_{\theta}\right)}² $$

(7.20)

上式中唯一依赖于模型参数θ的值是$$ {\mu}_{\theta}\left({s}_t^i\right) $$。让我们取一个关于θ的梯度( 7.20 )。我们得到以下结果:

$$ {\nabla}_{\uptheta}\log {\pi}_{\theta}\left({a}_ti|{s}_ti\right)= const\ x\ \left({a}_t^i-{\mu}_{\theta}\right)\ {\nabla}_{\theta }\ {\mu}_{\theta}\left({s}_t^i\right) $$

为了在 PyTorch 或 TensorFlow 中实现这一点,我们将形成一个修正的均方误差,就像我们对之前的离散操作采取的方法一样。我们用弹道返回来衡量均方误差。我们在 PyTorch 或 TensorFlow 中实现的损耗方程在( 7.21 )中给出。

$$ {L}_{MSE}\left(\uptheta \right)=\kern0.75em \frac{1}{N}{\sum}_{i=1}N\left[\left({\sum}_{t=1}T{\left({a}_ti-{\mu}_{\theta}\right)}²\right)\left({\sum}_{t=1}Tr\left({s}_ti,{a}_ti\right)\right)\right] $$

(7.21)

再次注意,使用梯度 L MSE (θ)然后在梯度的-ve 方向上迈出一步将产生以下结果:

$$ -{\nabla}_{\theta }{L}_{MSE}\left(\uptheta \right)=\kern0.75em \frac{1}{N}{\sum}_{i=1}N\left[\left({\sum}_{t=1}T\left({a}_t^i-{\mu}_{\theta}\right)\ {\nabla}_{\theta }\ {\mu}_{\theta}\left({s}_ti\right)\right)\left({\sum}_{t=1}Tr\left({s}_ti,{a}_ti\right)\right)\right] $$

(7.22)

向−∇θlMSE(θ)方向的一步是向$$ \nabla \overset{\sim }{J}\left(\uptheta \right) $$方向的一步,如( 7.19 )中给出。这是一个尝试增加$$ \overset{\sim }{J}\left(\uptheta \right), $$价值的步骤,即最大化保单回报。

总之,PyTorch 或 TensorFlow 中的实现要求我们在离散动作空间中形成交叉熵损失,或者在连续动作空间的情况下形成均方损失,其中每个损失项由对$$ \left({s}_ti,{a}_ti\right) $$来自的轨迹的总回报加权。这类似于我们在监督学习中采取的方法,除了额外的通过轨迹返回rI)=$$ \left({\sum}_{t=1}Tr\left({s}_ti,{a}_t^i\right)\right) $$进行加权的步骤。

还请注意,加权交叉熵损失加权均方损失没有任何意义或重要性。它只是一个方便的表达式,允许我们使用 PyTorch 和 TensorFlow 的 auto-diff 功能,通过反向传播计算梯度,然后采取措施改进策略。相比之下,在监督学习中,损失确实表明了预测的质量。在政策梯度的情况下,没有这样的推论或含义。这就是为什么我们称它们为伪亏损/目标。

带奖励的方差缩减

我们在方程式( 7.16 )中推导出的表达式,如果以目前的形式使用,就有问题。它有很高的方差。我们现在将利用问题的时间性质来做一些方差减少。

当我们推出政策(即根据政策采取行动)产生一条轨迹时,我们计算这条轨迹的总回报rI)。接下来,轨迹中动作的每个动作概率项由该轨迹回报加权。

然而,在一个时间步中采取的行动,比如说,只能影响我们在那个行动之后看到的回报。我们在时间步 t ' 之前看到的奖励不受我们在时间步 t ' 采取的动作或任何后续动作的影响。原因是世界是因果的。未来的行动不会影响过去的回报。我们将使用该属性删除( 7.16 )中的某些术语,并减少差异。给出了推导修正公式的步骤。请注意,它不是严格的数学证明。

**我们从方程开始( 7.15 )。

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[\left({\sum}_{t=1}^T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\right)\right] $$

我们将奖励项的求和索引从 t 更改为t,并将第一次求和中的和移到πθ上。这给出了下面的表达式:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[\left({\sum}_{t=1}^T\left({\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right){\sum}_{t\prime =1}^Tr\left({s}_{t\prime },{a}_{t\prime}\right)\right)\right)\right] $$

在指标 t 总和的求和项中,我们去掉了时间 t 之前的奖励项。在时间 t 时,我们采取的行动只能影响在时间 t 及以后到来的奖励。这导致第二个内和从T'=T变为 T ,而不是从 t ' = 1 变为 T 。换句话说,开始索引现在是t'= t而不是t =1。修改后的表达式如下:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[\left({\sum}_{t=1}^T\left({\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right){\sum}_{t\prime =t}^Tr\left({s}_{t\prime },{a}_{t\prime}\right)\right)\right)\right] $$

内部总和$$ {\sum}_{t\prime =t}^Tr\left({s}_{t\prime },{a}_{t\prime}\right) $$不再是轨迹的总回报。而是我们从时间 = tT 看到的剩余轨迹的回报。如您所知,这就是 q 值。q 值是我们在状态stttt时,在时间 t 采取一个步骤/动作 a * t 之后,从时间 t 开始直到结束,我们得到的预期奖励。我们也可以称之为赏去*。因为表达式$$ {\sum}_{t\prime =t}^Tr\left({s}_{t\prime },{a}_{t\prime}\right) $$只针对一条轨迹,我们表示它是对预期收益的估计。更新的梯度方程如下:

$$ {\hat{Q}}_t^i={\sum}_{t\prime =t}Tr\left({s}_{t\prime}i,{a}_{t\prime}^i\right) $$

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\ {\hat{Q}}_t^i $$

(7.23)

要在 PyTorch 或 TensorFlow 中使用这个方程,我们只需要做一个小小的修改。不是用总的轨迹回报来衡量每个对数概率项,我们现在用该时间步的剩余回报来衡量它;换句话说,我们用奖励去价值来衡量它。图 7-4 显示了一个使用 reward to go 的修正增强算法。

Reinforce With Reward to go

img/502835_1_En_7_Fig4_HTML.png

图 7-4

用奖励去强化算法

本章到目前为止我们已经做了很多理论,数学公式有点超载。我们试图保持它的最小化,如果到目前为止从这一章有什么收获的话,那就是图 7-4 中的加强算法。现在让我们把这个等式付诸实践。我们将从图 7-4 对我们通常的CartPole问题用连续的状态空间和离散的动作来实现加强。

在此之前,我们先介绍最后一个数学术语。策略梯度算法中对状态-动作空间的探索来自于这样一个事实,即我们学习一个随机策略,该策略为给定状态的所有动作分配一个概率,而不是使用 DQN 选择最佳可能动作。为了确保探索得以维持,并确保π θ ( a | s )不会以高概率崩溃为单个动作,我们引入了一个正则化项,称为。分布的熵定义如下:

$$ H(X)={\sum}_x-p(x).\mathit{\log}\ p(x) $$

为了保持足够的探索,我们将希望概率具有分散的分布,并且不要让概率分布过早地在单个值或小区域附近达到峰值。分布的扩散越大,分布的熵 H(x)越高。因此,输入 PyTorch/TensorFlow 最小化器的项如下:

$$ Loss\left(\uptheta \right)=-J\left(\uptheta \right)-H\left({\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\right) $$

$$ =-\frac{1}{N}{\sum}_{i=1}N\left[{\sum}_{t=1}T\left(\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right){\sum}_{t^{\prime }=t}T{\upgamma}{t^{\prime }-t}r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)\right)-\upbeta {\sum}_{a_i}{\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right).\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\right] $$

在我们的代码示例中,我们只采用一条轨迹,即 N = 1。但是,我们将对其进行平均,得出平均损失。我们将实际实现的功能如下:

$$ Loss\left(\uptheta \right)=-J\left(\uptheta \right)-H\left({\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\right) $$

$$ =-\frac{1}{T}{\sum}_{t=1}^T\left(\log {\pi}_{\theta}\left({a}_t|{s}_t\right)G\left({s}_t\right)-\beta {\sum}_{a_i}{\pi}_{\theta}\left({a}_t|{s}_t\right).\log {\pi}_{\theta}\left({a}_t|{s}_t\right)\right) $$

在哪里,

$$ G\left({s}_t\right)={\sum}_{t^{\prime }=t}T{\upgamma}{t-{t}^{\prime }}r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right) $$

请注意,我们在前面的表达式中重新引入了贴现因子γ。

现在让我们浏览一下实现。您可以在listing7_1_reinforce_pytorch.ipynb中找到完整的代码清单。我们在listing7_1_reinforce_tensorflow.ipynb中也有一个 TensorFlow 版本的代码。然而,我们将只浏览 PyTorch 版本。TensorFlow 版本遵循几乎相同的步骤,除了我们定义网络或计算损耗以及逐步通过梯度的方式略有不同。在我们的代码中,我们在急切执行模式下使用了 TensorFlow 2.0。

我们之前解释过环境。它有一个四维连续的状态空间和两个动作的离散动作空间:“左移”和“右移”。让我们首先定义一个简单的策略网络,它有一个 192 个单元的隐藏层和 ReLU 激活。最终输出没有激活。清单 7-1 显示了代码。

model = nn.Sequential(
            nn.Linear(state_dim,192),
            nn.ReLU(),
            nn.Linear(192,n_actions),
)

Listing 7-1Policy Network in PyTorch

接下来,我们定义一个generate_trajectory函数,该函数采用当前策略来生成一集的(states, actions, rewards)轨迹。它使用一个助手函数predict_probs来完成这项工作。清单 7-2 给出了代码。它从初始化环境开始,然后按照当前策略连续采取步骤,返回它展开的轨迹的(states, actions, rewards)

def generate_trajectory(env, n_steps=1000):
    """
    Play a session and genrate a trajectory
    returns: arrays of states, actions, rewards
    """
    states, actions, rewards = [], [], []

    # initialize the environment
    s = env.reset()

    #generate n_steps of trajectory:
    for t in range(n_steps):
        action_probs = predict_probs(np.array([s]))[0]
        #sample action based on action_probs
        a = np.random.choice(n_actions, p=action_probs)
        next_state, r, done, _ = env.step(a)

        #update arrays

        states.append(s)
        actions.append(a)
        rewards.append(r)

        s = next_state
        if done:
            break

    return states, actions, rewards

Listing 7-2generate_trajectory in PyTorch

我们还有另一个助手函数,根据表达式$$ G\left({s}_t\right)={\sum}_{t^{\prime }=t}T{\gamma}{t-{t}^{\prime }}r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right) $$将单个步骤的回报$$ r\left({s}_{t^{\prime }},{a}_{t^{\prime }}\right) $$转换为奖励。清单 7-3 包含了这个函数的实现。

def get_rewards_to_go(rewards, gamma=0.99):

    T = len(rewards) # total number of individual rewards
    # empty array to return the rewards to go
    rewards_to_go = [0]*T
    rewards_to_go[T-1] = rewards[T-1]

    for i in range(T-2, -1, -1): #go from T-2 to 0
        rewards_to_go[i] = gamma * rewards_to_go[i+1] + rewards[i]

    return rewards_to_go

Listing 7-3get_rewards_to_go in PyTorch

我们现在准备实施培训。我们构建了损失函数,我们将把它输入 PyTorch 优化器。如前所述,我们将实现以下表达式:

$$ Loss\left(\theta \right)=-\frac{1}{T}{\sum}_{t=1}^T\left(\log {\pi}_{\theta}\left({a}_t|{s}_t\right)G\left({s}_t\right)-\beta {\sum}_{a_i}{\pi}_{\theta}\left({a}_t|{s}_t\right).\log {\pi}_{\theta}\left({a}_t|{s}_t\right)\right) $$

清单 7-4 包含损失计算的代码。

#init Optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

def train_one_episode(states, actions, rewards, gamma=0.99, entropy_coef=1e-2):

    # get rewards to go
    rewards_to_go = get_rewards_to_go(rewards, gamma)

    # convert numpy array to torch tensors

    states = torch.tensor(states, device=device, dtype=torch.float)
    actions = torch.tensor(actions, device=device, dtype=torch.long)
    rewards_to_go = torch.tensor(rewards_to_go, device=device, dtype=torch.float)

    # get action probabilities from states
    logits = model(states)
    probs = nn.functional.softmax(logits, -1)
    log_probs = nn.functional.log_softmax(logits, -1)

    log_probs_for_actions = log_probs[range(len(actions)), actions]

    #Compute loss to be minized
    J = torch.mean(log_probs_for_actions*rewards_to_go)
    H = -(probs*log_probs).sum(-1).mean()

    loss = -(J+entropy_coef*H)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    return np.sum(rewards) #to show progress on training

Listing 7-4Training for One Trajectory in PyTorch

我们现在准备进行训练。清单 7-5 展示了我们如何训练代理 10000 步,打印 100 步轨迹训练后的平均剧集奖励。一旦我们达到 300 的平均奖励,我们也停止训练。

total_rewards = []
for i in range(10000):
    states, actions, rewards = generate_trajectory(env)
    reward = train_one_episode(states, actions, rewards)
    total_rewards.append(reward)
    if i != 0 and i % 100 == 0:
        mean_reward = np.mean(total_rewards[-100:-1])
        print("mean reward:%.3f" % (mean_reward))
        if mean_reward > 300:
            break
env.close()

Listing 7-5Training the Agent in PyTorch

训练结束时,代理已经学会了很好地平衡杆子。您还会注意到,与基于 DQN 的方法相比,该程序实现这一结果所需的迭代次数和时间要少得多。

请注意,加强是一个基于策略的算法。

使用基线进一步减少差异

我们从( 7.15 中的原始政策梯度更新表达式开始,并使用( 7.16 中的平均值将期望值转换为估计值。接下来,我们展示了如何通过考虑奖励而不是全轨迹奖励来减少方差。等式( 7.23 )给出了这个奖励的表达式。

在这一节中,我们来看另一个使政策梯度更加稳定的变化。我们来考虑一下动机。假设您已经按照一个策略完成了三次轨迹的展开。假设奖励是 300,200,100。为了使解释简单,请考虑总报酬和总轨迹概率版本的梯度更新方程的情况,如方程( 7.10 )所示,复制如下:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\kern0.5em \left[{\nabla}_{\theta}\log {p}_{\theta}\left(\tau \right)\ r\left(\tau \right)\right] $$

那么,渐变更新会有什么作用呢?它会用 300 衡量第一个轨迹的对数概率的梯度,用 200 衡量第二个轨迹,用 100 衡量第三个轨迹。这意味着三个轨迹中的每一个的概率都增加了不同的量。让我们来看看它的图示,如图 7-5 所示。

img/502835_1_En_7_Fig5_HTML.jpg

图 7-5

具有实际轨迹回报的策略的梯度更新

从图中可以看出,我们用不同的权重因子增加了所有三个轨迹的概率,都是+ve 权重,使得所有轨迹的概率都上升了。理想情况下,我们会喜欢增加奖励为 300 的轨迹的概率,减少奖励为 100 的轨迹的概率,因为它不是一个很好的轨迹。我们希望政策能够改变,这样就不会经常产生奖励 100 的轨迹。然而,使用当前的方法,修正的概率曲线变得更平坦,因为它试图增加所有三个轨迹的概率,并且概率曲线下的总面积必须为 1。

让我们考虑一个场景,从三个回报中减去三个轨迹的平均回报。我们得到 100,0,和-100(300-200;200-200;100-200).让我们使用修正后的轨迹奖励作为权重来进行梯度更新。图 7-6 显示了这种更新的结果。我们可以看到,概率曲线变得越来越窄,越来越陡,因为它沿 x 轴的分布越来越小。

img/502835_1_En_7_Fig6_HTML.jpg

图 7-6

具有减少基线的轨迹奖励的策略的梯度更新

用基线减少奖励会减少更新的方差。在极限中,无论我们使用还是不使用基线,结果都是一样的。基线的引入不会改变最优解。它只是减少了差异,从而加快了学习。我们将从数学上证明基线的引入不会改变梯度更新的期望值。基线可以是跨越所有轨迹和轨迹中所有步骤的固定基线,或者可以是取决于状态的变化量。然而,它不能依赖于行动。让我们先来看看基线是状态函数的推导$$ {s}_t^i $$

让我们更新( 7.15 )中的等式,引入一个基线。

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[\left({\sum}_{t=1}^T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\right)\left(r\left(\tau \right)-\mathrm{b}\Big({\mathrm{s}}_t\right)\Big)\right] $$

让我们把b(st)的项分离出来,评估期望会是什么。

$$ {E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[\left({\sum}_{t=1}^T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\right)b\left({s}_t\right)\right] $$

由于期望的线性性质,我们将第一个内和移出,得到表达式。

$$ {\sum}_{t=1}^T{E}_{{\mathrm{a}}_t\sim {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)}\left[{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)b\left({s}_t\right)\right] $$

我们把期望从τ∽pθ(τ)切换到了 at∼πθ(at|st)。这是因为我们将第一个内和移到了期望值之外,之后唯一依赖于概率分布的项就是概率为π的动作atθ(at|st)。

我们只关注内心的期待:$$ {E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)b\left({s}_t\right)\right] $$。我们可以把它写成积分,如下所示:

$$ {E}_{{\mathrm{a}}_t\sim {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)}\left[{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)b\left({s}_t\right)\right] $$

$$ =\int {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\kern0.5em \left({\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\right)\ b\left({s}_t\right)\ d{a}_t $$

$$ =\int {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\kern0.5em \frac{\nabla_{\uptheta}{\uppi}_{\uptheta}\left({a}_t|{s}_t\right)}{\uppi_{\uptheta}\left({a}_t|{s}_t\right)}\kern0.5em b\left({s}_t\right)\ d{a}_t $$

$$ =\int {\nabla}_{\uptheta}{\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\kern0.5em b\left({s}_t\right)\ d{a}_t $$

$$ =b\left({s}_t\right)\ {\nabla}_{\uptheta}\int {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\kern0.75em d{a}_t $$

作为b(st)不依赖于 a t ,我们可以拿出来。同样,由于积分的线性,我们可以交换梯度和积分。现在积分将计算为 1,因为这是使用π θ 曲线的总概率。相应地,我们得到以下结果:

$$ {E}_{{\mathrm{a}}_t\sim {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)}\left[{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)b\left({s}_{t\prime}\right)\right] $$

$$ =b\left({s}_t\right){\nabla}_{\uptheta}(1)=b\left({s}_t\right).0 $$

$$ =0 $$

前面的推导告诉我们,减去一个依赖于状态或者可能是常数的基线不会改变期望值。条件是不应该依赖于动作 a t

因此,强化基线将经历如下更新:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={E}_{\uptau \sim {p}_{\uptheta}\left(\uptau \right)}\left[\left({\sum}_{t=1}^T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\right)\left(r\left(\tau \right)-\mathrm{b}\Big({\mathrm{s}}_t\right)\Big)\right] $$

(7.24)

我们可以用基线修改( 7.23 )中给出的奖励,得到以下结果:

$$ \hat{Q}\left({s}_ti,{a}_ti\right)={\sum}_{t^{\prime }=t}T{\upgamma}{t^{\prime }-t}r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right) $$

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\ \left[{\hat{Q}}i\left({s}_t,{a}_t\right)-{\mathrm{b}}i\left({s}_t\right)\right] $$

(7.25)

方程 7.25 用基线和奖励来加强。我们使用了两个技巧来减少普通钢筋的差异。我们使用一个时间结构来移除过去的奖励,而不受现在的行为的影响。然后,我们使用基线让坏政策获得-ve 奖励,让好政策获得+ve 奖励,以使政策梯度在我们学习过程中显示较低的变化。

请注意,REINFORCE 及其所有变体都是基于策略的算法。政策权重更新后,我们需要推出新的轨迹。旧的轨迹不再代表旧的政策。这也是为什么像基于价值的政策方法一样,加强也是样本低效的原因之一。我们不能使用早期政策的过渡。我们必须在每次权重更新后丢弃它们并生成新的转换。

演员-评论家方法

在本节中,我们将通过将策略梯度与价值函数相结合来进一步完善算法,以获得所谓的行动者-批评家算法家族(A2C/A3C)。我们先来定义一个术语叫做优势 A ( sa )。

定义优势

先说方程中的表达式$$ \hat{Q}\left({s}_ti,{a}_ti\right) $$(7.25)。就是在给定的轨迹( i )和给定的状态 s * t * 中走下去的奖励。

$$ \hat{Q}\left({s}_ti,{a}_ti\right)={\sum}_{t^{\prime }=t}Tr\left({s}_{t{\prime}}i,{a}_{t{\prime}}^i\right) $$

为了使用前面的表达式评估$$ \hat{Q} $$值,我们使用蒙特卡罗模拟。换句话说,我们正在累加从那个时间步 t 直到结束的所有奖励,即,直到 T 。它将再次具有高方差,因为它只是期望值的一个轨迹估计。在前一章无模型策略学习中,我们看到 MC 方法零偏差,但方差很大。相比之下,TD 方法有一些偏差,但方差较低,并且由于方差较低,可以导致更快的收敛。我们能在这里做类似的事情吗?这是什么奖励?表情$$ \hat{Q}\left({s}_ti,{a}_ti\right) $$有什么期待?无非是状态-动作对的 q 值( s * t a t * )。如果我们可以得到 q 值,我们可以用 q 估计值代替个人奖励的总和。

$$ {\hat{Q}}^i\left({s}_t,{a}_t\right)=q\left({s}_t,{a}_t;\phi \right) $$

(7.26)

让我们将q(sta t )的值滚动一个时间步长。这类似于我们在第五章中看到的 TD(0)方法。我们可以这样写$$ {\hat{Q}}^i\left({s}_t,{a}_t\right) $$:

$$ \hat{Q}\left({s}_ti,{a}_ti\right)=r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)+V\left({s}_{t+1}\right) $$

(7.27)

这是未打折的展示。正如本章开始时所讨论的,我们将在有限视界未贴现设置的背景下进行所有推导。该分析可以容易地扩展到其他设置。我们将在算法的最终伪代码中切换到更一般的情况,同时将我们的分析限制在未折扣的情况。

再看方程( 7.25 ),你能想到一个可以用的好基线 bIT5(st)吗?用状态值V(st)怎么样?如上所述,我们可以使用任何值作为基线,只要它不依赖于动作 a tV(st)就是这样一个依赖于状态 s t 而不依赖于动作t的量。

$$ {b}^i\left({s}_t\right)=V\left({s}_t\right) $$

*(7.28)

使用前面的表达式:

$$ \hat{Q}\left({s}_ti,{a}_ti\right)-{\mathrm{b}}i\left({s}_t\right)={\hat{Q}}i\left({s}_t,{a}_t\right)-V\left({s}_t\right) $$

(7.29)

右侧称为优势 A(sta t )。它是我们在状态 s t 采取步骤 a t 所获得的额外收益/奖励,它给出的奖励是$$ \hat{Q}\left({s}_ti,{a}_ti\right) $$相对于我们在状态 s * t * 所获得的平均奖励,用 V ( s 表示我们现在可以将方程式( 7.27 )代入( 7.29 )得到如下:

$$ \hat{A}\left({s}_ti,{a}_ti\right) $$

$$ =\hat{Q}\left({s}_ti,{a}_ti\right)-{\mathrm{b}}^i\left({s}_t\right) $$

$$ =\hat{Q}\left({s}_ti,{a}_ti\right)-V\left({s}_t\right) $$

$$ =\kern0.5em r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)+V\left({s}_{t+1}\right)-V\left({s}_t\right) $$

(7.30)

优势演员评论家

继续上一节,让我们根据前面的表达式重写等式( 7.25 )中给出的梯度更新。

这是来自方程( 7.25 )的原始梯度更新:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\ \left[{\hat{Q}}i\left({s}_t,{a}_t\right)-{\mathrm{b}}i\left({s}_t\right)\right] $$

代入,bI(st)= V(st)由( 7.28 ),我们得到如下:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\ \left[\hat{Q}\left({s}_ti,{a}_ti\right)-V\left({s}_t\right)\right] $$

使用 MC 方法,我们得到:

$$ \hat{Q}\left({s}_ti,{a}_ti\right)={\sum}_{t^{\prime }=t}Tr\left({s}_{t{\prime}}i,{a}_{t{\prime}}^i\right) $$

或者,使用 TD(0)方法,我们得到这个:

$$ \hat{Q}\left({s}_ti,{a}_ti\right)=\kern0.5em r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)+V\left({s}_{t+1}\right)-V\left({s}_t\right) $$

(7.31)

看前面表达式中的内心表达式$$ \hat{Q}\left({s}_ti,{a}_ti\right)-V\left({s}_t\right) $$Q 是使用当前策略遵循特定步骤 a * t 的值。换句话说,“actor”和 V 是下面当前政策的平均值,即“critical”演员试图最大化回报,评论家告诉算法,与平均水平相比,特定步骤是好是坏。行动者-批评者方法是一系列算法,其中行动者改变政策梯度以改进行动,而批评者*告知算法使用当前政策的行动的良好性。

我们可以用优势的形式重写( 7.30 )得到如下:

$$ {\nabla}_{\uptheta}J\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\ \left[{\hat{A}}^i\left({s}_t,{a}_t\right)\right] $$

(7.32)

这种表达也是我们称之为优势演员评论家 (A2C)的原因。请注意,演员批评家是一个方法家族,A2C 和 A3C 是其中的两个具体例子。有时在文学中演员评论家也可互换地称为 A2C。与此同时,一些论文将 A2C 称为 A3C 的同步版本,我们将在下一节简要讨论。

我们可以进一步将( 7.32 )与( 7.30 )组合起来表达如下:

$$ MC\  approach:\hat{A}\left({s}_ti,{a}_ti\right)={\sum}_{t^{\prime }=t}Tr\left({s}_{t{\prime}}i,{a}_{t{\prime}}^i\right)-V\left({s}_t\right) $$

$$ TD(0)\  approach:\hat{A}\left({s}_ti,{a}_ti\right)=r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)+V\left({s}_{t+1}\right)-V\left({s}_t\right) $$

使用 actor critic 的修订更新规则如下:

$$ MC:{\nabla}_{\uptheta}J\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\ \left[{\sum}_{t^{\prime }=t}Tr\left({s}_{t{\prime}}i,{a}_{t{\prime}}^i\right)-V\left({s}_t\right)\right] $$

$$ TD:{\nabla}_{\uptheta}J\left(\uptheta \right)=\frac{1}{N}{\sum}_{i=1}N{\sum}_{t=1}T{\nabla}_{\uptheta}\log {\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\ \left[r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)+V\left({s}_{t+1}\right)-V\left({s}_t\right)\right] $$

(7.33)

我们需要两个网络,一个网络估计由参数 ϕ 参数化的状态值函数 V ( s t )另一个网络输出由 θ参数化的策略πθ(at|st图 7-7 显示了演员评论家的完整伪代码(也称为优势演员评论家 A2C)。

Advantage Actor-Critic Algorithm

img/502835_1_En_7_Fig7_HTML.png

图 7-7

优势行动者-批评家算法

请注意,在前面的伪代码中,我们使用了一步不打折返回来获得优势$$ \hat{A}\left({s}_ti,{a}_ti\right) $$

$$ r\left({s}_ti,{a}_ti\right)+{V}_{\varnothing}\left({s}_{t+1}\right)-{V}_{\varnothing}\left({s}_t\right) $$

折扣后的一步到位版本如下:

$$ r\left({s}_ti,{a}_ti\right)+\gamma {V}_{\varnothing}\left({s}_{t+1}\right)-{V}_{\varnothing}\left({s}_t\right) $$

同样,n 步返回的折扣版本如下:

$$ \left(\sum \limits_{t^{\prime }=t}{t+n-1}{\gamma}{t\prime -t}r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)\right)+{\gamma}^n{V}_{\varnothing}\left({s}_{t+n}\right)-{V}_{\varnothing}\left({s}_t\right) $$

使用直接使用 rewards to go 的 MC 方法,优势如下:

$$ \hat{A}\left({s}_ti,{a}_ti\right)={\sum}_{t^{\prime }=t}T{\gamma}{t\prime -t}r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)-V\left({s}_t\right) $$

这是我们将在代码中实现的版本。

A2C 算法的实现

我们来看看图 7-7 中伪代码的实现细节。我们需要两个网络/模型——一个是带有参数向量θ的政策网络(参与者),另一个是带有参数向量ϕ.的价值评估网络(批评家)在实际设计中,策略网络和价值评估网络可以共享一些初始权重。这类似于我们在前一章看到的决斗网络架构。这实际上是加快收敛的理想设计选择。图 7-8 给出了组合模型的示意图。

img/502835_1_En_7_Fig8_HTML.jpg

图 7-8

初始层具有公共权重的演员评论网络

在我们的代码遍历中,我们将对图 7-7 中给出的 actor-critic 算法进行以下更改:

  • We will use the MC discounted version of the advantage.

    $$ \hat{\mathrm{A}}\left({s}_ti,{\mathrm{a}}_ti\right)={\sum}_{t^{\prime }=t}T{\gamma}{t\prime -t}r\left({s}_{t{\prime}}i,{a}_{t{\prime}}i\right)-V\left({s}_t\right) $$

  • 像加强,我们将介绍熵正则化。

  • 代替训练第一拟合 V(s)的两个单独的损失训练步骤,然后进行策略梯度,我们将形成单个损失目标,其将与熵正则化器一起执行 V(s)拟合以及策略梯度步骤。

使用具有先前修改的演员评论家的损失如下:

$$ Loss\left(\uptheta, \upphi \right)=-J\left(\uptheta, \upphi \right)-H\left({\uppi}_{\uptheta}\left({a}_ti|{s}_ti\right)\right) $$

$$ =-\frac{1}{N}{\sum}_{i=1}N\left[{\sum}_{t=1}T\left(\log {\pi}_{\theta}\left({a}_ti|{s}_ti\right)\left[\hat{Q}\left({s}_ti,{a}_ti\right)-{V}_{\phi}\left({s}_t^i\right)\right]\right)-\beta {\sum}_a{\pi}_{\theta}\left(a|{s}_t^i\right).\log {\pi}_{\theta}\left(a|{s}_t^i\right)\right] $$

像加强,我们将进行重量更新后,每个轨迹。因此, N = 1。但是,我们将对其进行平均,得出平均损失。因此,我们将实际实现的函数如下:

$$ Loss\left(\uptheta, \upphi \right)=-\frac{1}{T}\left[{\sum}_{t=1}^T\left(\log {\uppi}_{\uptheta}\left({a}_t|{s}_t\right)\left[\hat{Q}\left({s}_t,{a}_t\right)-{V}_{\upphi}\left({s}_t\right)\right]\right)-\upbeta {\sum}_a{\uppi}_{\uptheta}\left(a|{s}_t\right).\log {\uppi}_{\uptheta}\left(a|{s}_t\right)\right] $$

这是我们将实施的损失。你可以在listing7_2_actor_critic_pytorch.ipynb中找到 PyTorch 中实现 actor critic 的完整代码。代码库也有一个 TensorFlow 版本,在listing7_2_actor_critic_tensorflow.ipynb中给出。实施将遵循我们在加强中的相同步骤。只有一些小的变化:网络结构和损耗计算与前面的表达式一样。

先说网络。我们将有一个共享权重的联合网络,一个生成政策行动概率,另一个生成状态值。对于CartPole,这是一个相当简单的网络,如图 7-9 所示。

img/502835_1_En_7_Fig9_HTML.jpg

图 7-9

南极环境演员-评论家网络

清单 7-6 显示了 PyTorch 中的实现。它是网络的直接实现,如图 7-9 所示。

class ActorCritic(nn.Module):
    def __init__(self):
        super(ActorCritic, self).__init__()
        self.fc1 = nn.Linear(state_dim, 128)
        self.actor = nn.Linear(128,n_actions)
        self.critic = nn.Linear(128,1)

    def forward(self, s):
        x = F.relu(self.fc1(s))
        logits = self.actor(x)
        state_value = self.critic(x)
        return logits, state_value

model = ActorCritic()

Listing 7-6Actor-Critic Network in PyTorch

另一个变化是我们为一集实现训练代码的方式。它类似于清单 7-4 中的代码,只是引入了V(st)作为基线值。清单 7-7 给出了train_one_episode的完整代码。

#init Optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

def train_one_episode(states, actions, rewards, gamma=0.99, entropy_coef=1e-2):

    # get rewards to go
    rewards_to_go = get_rewards_to_go(rewards, gamma)

    # convert numpy array to torch tensors

    states = torch.tensor(states, device=device, dtype=torch.float)
    actions = torch.tensor(actions, device=device, dtype=torch.long)
    rewards_to_go = torch.tensor(rewards_to_go, device=device, dtype=torch.float)

    # get action probabilities from states
    logits, state_values = model(states)
    probs = nn.functional.softmax(logits, -1)
    log_probs = nn.functional.log_softmax(logits, -1)

    log_probs_for_actions = log_probs[range(len(actions)), actions]

    advantage = rewards_to_go - state_values.squeeze(-1)

    #Compute loss to be minized
    J = torch.mean(log_probs_for_actions*(advantage))
    H = -(probs*log_probs).sum(-1).mean()

    loss = -(J+entropy_coef*H)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    return np.sum(rewards) #to show progress on training

Listing 7-7train_one_episode for Actor Critic Using MC Rewards to Go in PyTorch

请注意,在多个轨迹上训练的代码与之前相同。当我们运行代码时,我们看到,与强化相比,使用 A2C 的训练进行得更快,朝着更好的策略稳步前进。

请注意,演员评论也是一种政策上的方法,就像加强一样。

异步优势演员评论家

2016 年,论文《深度强化学习的异步方法》作者 1 介绍了 A2C 的异步版本。基本想法很简单。我们有一个全局服务器,它是提供网络参数的“参数”服务器:θ,ϕ.有多个演员-评论家代理并行运行。每个演员-评论家代理从服务器获得参数,进行轨迹滚动,并在 θϕ 上进行梯度下降。代理将参数更新回服务器。它允许更快的学习,特别是在我们使用模拟器的环境中,例如机器人环境。我们可以首先在模拟器的多个实例上使用 A3C 训练一个算法。随后的学习将是在真实环境中的物理机器人上进一步微调/训练算法。

图 7-10 显示了 A3C 的高级示意图。请注意,这是对该方法的简单解释。对于实际的实现细节,建议您详细参考参考文献。

img/502835_1_En_7_Fig10_HTML.jpg

图 7-10

异步优势演员评论家

正如所解释的,一些论文将多个代理一起训练的同步版本称为 A3C 的 A2C 版本,即没有异步部分的 A3C。然而,有时有一个经纪人的演员评论家也被称为优势演员评论家 (A2C)。最后,演员评论家是一个算法家族,其中我们一起使用两个网络:一个价值网络来估计 V ( s )和一个政策网络来估计政策πθ(a|st)。我们正在利用两个世界的优势:基于价值的方法和政策梯度方法。

信赖域策略优化算法

到目前为止,我们在本章中详细介绍的方法也被称为标准政策梯度 (VPG)。我们使用 VPG 训练的策略是一个随机策略,它提供了自己的探索,而没有显式地使用ε-贪婪探索。随着培训的进行,策略分布变得更加清晰,以最佳行动为中心。这减少了探索,使算法越来越多地利用它所学到的东西。这会导致策略卡在局部最大值。我们试图通过引入正则化来解决这个问题,但这不是唯一的方法。

正如我们在前面章节的策略梯度方法中看到的,我们通过以下等式给出的小量来更新策略参数:

$$ {\uptheta}_{new}={\uptheta}_{old}+\upalpha {\left.{\nabla}_{\uptheta}J\left(\uptheta \right)\right|}_{\theta ={\theta}_{old}} $$

换句话说,在旧的策略参数θ=θ旧的 处评估梯度,然后通过采取由步长 α 确定的小步长来更新梯度。VPG 通过使用学习率α限制策略参数从θ 到θ 的变化,试图在参数空间中保持新旧策略彼此接近。但是,仅仅因为策略参数在附近,并不能保证新旧策略(即动作概率分布)实际上是相互接近的。参数 θ 的微小变化可能导致政策概率的巨大差异。理想情况下,我们希望在概率空间而不是参数空间中保持新旧策略彼此接近。这是 2015 年题为“信任区域策略优化”的论文的作者详述的关键见解 2 在深入细节之前,我们先花几分钟时间来讲一个叫做 Kullback-Liebler 散度(KL-divergence)的度量。这是衡量两个概率有多大不同的尺度。它来自信息论领域,深入研究它需要一本自己的书。我们将只试图给出公式及其背后的一些直觉,而不进入数学证明。

假设我们有两个离散的概率分布 P 和 Q 定义在某个值的范围内(称为支持)。假设支持度为“x ”,从 1 到 6。PX(X=X)使用概率分布 P 定义了 X=x 的概率,类似地,我们在相同的支持度上定义了另一个概率分布 Q。作为例子,考虑具有概率分布的六个面的模具。

|

x

|

one

|

Two

|

three

|

four

|

five

|

six

|
| --- | --- | --- | --- | --- | --- | --- |
| P(x) | 1/6 | 1/6 | 1/6 | 1/6 | 1/6 | 1/6 |
| Q(x) | 2/9 | 1/6 | 1/6 | 1/6 | 1/6 | 1/9 |

骰子 Q 被加载以显示少于 6 而多于 1,而 P 是公平骰子,显示骰子的任何面的概率相等。

P 和 Q 之间的 KL-散度表示如下:

$$ {D}_{KL}\left(P\Big\Vert Q\right)=\sum \limits_xP(x)\log \frac{P(x)}{Q(x)} $$

(7.34)

我们来计算一下上表的DKL(PQ)。

$$ {D}_{KL}\left(P\Big\Vert Q\right)=\frac{1}{6}\log \frac{1/6}{2/9}+\frac{1}{6}\log \frac{1/6}{1/6}+\frac{1}{6}\log \frac{1/6}{1/6}\kern0.5em +\frac{1}{6}\log \frac{1/6}{1/6}+\frac{1}{6}\log \frac{1/6}{1/6}+\frac{1}{6}\log \frac{1/6}{1/9} $$

$$ =\frac{1}{6}\log \frac{3}{4}+\frac{1}{6}\log 1+\frac{1}{6}\log 1\kern0.5em +\frac{1}{6}\log 1+\frac{1}{6}\log 1+\frac{1}{6}\log \frac{3}{2} $$

$$ =\frac{1}{6}\log \frac{3}{4}+\frac{1}{6}x\ 0+\frac{1}{6}x\ 0+\frac{1}{6}x\ 0+\frac{1}{6}x0+\frac{1}{6}\log \frac{3}{2} $$

$$ =\frac{1}{6}\left(\log \frac{3}{4}+\log \frac{3}{2}\ \right) $$

$$ =\frac{1}{6}\log \left(\frac{3}{4}x\ \frac{3}{2}\right) $$

$$ =\frac{1}{6}\log \left(\frac{9}{8}\right)=0.0196 $$

你可以通过放 P = Q 得到DKL(PQ)= 0 来满足自己。当两个概率相等时,KL 散度为 0。对于其他任意两个不相等的概率分布,你会得到一个+ve KL 散度。分布越远,KL 散度值越高。有严格的数学证明表明,只有当两个分布相等时,KL 散度才总是+ve 和 0。

还要注意 KL 散度是不对称的。

$$ {D}_{KL}\left(P\Big\Vert Q\right)\ne {D}_{KL}\left(Q\Big\Vert P\right) $$

KL 散度是概率空间中两个概率分布之间距离的一种伪测度。连续概率分布的 KL 散度公式如下所示:

$$ {D}_{KL}\left(P\Big\Vert Q\right)=\int P(x)\log \frac{P(x)}{Q(x)} dx $$

(7.35)

回到 TRPO,我们希望保持新旧政策在概率空间而不是参数空间上相互接近。这就等于说我们希望 KL-divergence 在每个更新步骤中都是有界的,以确保新旧策略不会偏离太远。

$$ {D}_{KL}\left(\theta \Big\Vert {\theta}_k\right)\le \delta $$

这里, θ k 为当前策略参数, θ 为更新后的策略参数。

现在让我们把注意力转向我们试图最大化的目标。我们之前的度量 J (θ)对新旧政策参数没有任何显式的依赖,分别说θk+1θ k 。有一个使用重要性抽样的政策目标的替代公式。我们将在没有数学推导的情况下陈述这一点,如下所示:

$$ J\left(\uptheta, {\uptheta}_k\right)={E}_{a\sim {\uppi}_{\uptheta_k}\left(a|s\right)}\left[\frac{\uppi_{\uptheta}\left(a|s\right)}{\uppi_{\uptheta_k}\left(a|s\right)}{A}^{\uppi_{\uptheta_k}}\left(s,a\right)\right] $$

(7.36)

这里,θ是修改/更新策略的参数, θ k 是旧策略的参数。我们正试图采取最大可能的步骤,从旧政策参数 θ k 到具有参数 θ 的修订政策,使得新老政策之间的 KL 差异不会相差太多。换句话说,找到一个最大限度地增加目标而不走出旧政策周围的信任区域的新政策,定义为DKL(θ**θk)≤δ。用数学术语来说,我们可以将最大化问题总结如下:

$$ {\uptheta}_{k+1}=\underset{\uptheta}{\arg\ \max }\ J\left(\uptheta, {\uptheta}_k\right) $$

$$ s.t.{D}_{KL}\left(\uptheta \Big\Vert {\theta}_k\right)\le \updelta $$

$$ where,J\left(\uptheta, {\uptheta}_k\right)={E}_{a\sim {\uppi}_{\uptheta_k}\left(a|s\right)}\left[\frac{\uppi_{\uptheta}\left(a|s\right)}{\uppi_{\uptheta_k}\left(a|s\right)}{A}^{\uppi_{\uptheta_k}}\left(s,a\right)\right] $$

(7.37)

优势$$ {A}^{\uppi_{\uptheta_k}}\left(s,a\right) $$定义如前:

$$ {A}{\uppi_{\uptheta_k}}\left(s,a\right)={Q}{\uppi_{\uptheta_k}}\left(s,a\right)-{V}^{\uppi_{\uptheta_k}}(s) $$

或者,当我们推出一个步骤,并且 v 由另一个具有参数 ϕ 的网络参数化时,下面是等式:

$$ {A}{\uppi_{\uptheta_k}}\left({s}_t,{a}_t\right)=r\left({s}_t,{a}_t\right)+{V}{\uppi_{\uptheta_k}}\left({s}_{t+1};\upphi \right)-{V}^{\uppi_{\uptheta_k}}\left({s}_t;\upphi \right) $$

这是 TRPO 目标最大化的理论表述。但是利用目标$$ {\theta}_{k+1}=\underset{\uptheta}{\arg\ \max }\ J\left(\theta, {\theta}_k\right) $$的泰勒级数展开和 KL 约束DKL(θ**θk)≤δ,再加上拉格朗日对偶利用凸优化,就可以得到一个近似的更新表达式。这种近似可以打破 KL-散度有界的保证,为此在更新规则中增加了回溯线搜索。最后,它涉及一个 *** nxn *** 矩阵的求逆,这不容易计算。在这种情况下,使用共轭梯度算法。此时,我们有了一个使用 TRPO 计算更新的实用算法。

我们不会深入这些推导的细节,也不会给出完整的算法。我们希望您了解基本设置。大多数时候,我们不会自己动手实现这些算法。

近似策略优化算法

近似策略优化(PPO)也是由与 TRPO 相同的问题驱动的。“我们如何在策略参数中采用最大可能的步长,而不会走得太远,导致比更新前的原始策略更差的策略?”

我们将要详述的 PPO-clip 变体没有 KL-divergence。它依赖于裁剪目标函数中的梯度,使得更新没有动机将策略移动得离原始步骤太远。PPO 实现起来更简单,并且经验表明其性能与 TRPO 一样好。详细信息见 2017 年题为“近似策略优化算法”的论文。 3

使用 PPO-clip 变体的目的如下:

$$ J\left(\uptheta, {\uptheta}_k\right)=\mathit{\min}\left(\frac{\uppi_{\uptheta}\left(a|s\right)}{\uppi_{\uptheta_k}\left(a|s\right)}{A}^{\uppi_{\uptheta_k}}\left(s,a\right),g\left(\upepsilon, {A}^{\uppi_{\uptheta_k}}\left(s,a\right)\right)\right) $$

其中:

$$ g\left(\upepsilon, A\right)=\kern0.5em \left{\begin{array}{c}\left(1+\epsilon \right)A,A\ge 0\ {}\left(1-\epsilon \right)A,A<0\end{array}\right. $$

(7.38)

我们改写一下 J ( θθ k ),优势 A 为+ve 时,如下图:

$$ J\left(\uptheta, {\uptheta}_k\right)=\mathit{\min}\left(\frac{\uppi_{\uptheta}\left(a|s\right)}{\uppi_{\uptheta_k}\left(a|s\right)},\left(1+\upepsilon \right)\right){A}^{\uppi_{\uptheta_k}}\left(s,a\right) $$

当优势为+ve 时,我们要更新参数,使新策略πθ(a|s)高于旧策略$ {\pi}_{\theta_k}\left(a|s\right) $。但是,我们没有将其增加太多,而是剪切梯度以确保新策略的增加在旧策略的 1+ε倍以内。

类似地,当优势为-ve 时,我们得到以下结果:

$$ J\left(\theta, {\theta}_k\right)=\mathit{\min}\left(\frac{\pi_{\theta}\left(a|s\right)}{\pi_{\theta_k}\left(a|s\right)},\left(1-\epsilon \right)\right){A}^{\pi_{\theta_k}}\left(s,a\right) $$

换句话说,当优势为-ve 时,我们希望更新参数,从而降低( sa )对的策略概率。然而,我们不是一直减小,而是剪切梯度,使得新策略概率不会下降到旧策略概率的(1-ε)倍以下。

换句话说,我们剪切梯度以确保策略更新使策略概率分布在旧概率分布的(1-ε)到(1+ε)倍之内。ϵ作为一个正则化。与 TRPO 相比,PPO 很容易实现。对于 A2C,我们可以遵循图 7-7 中给出的相同伪代码,仅做一处修改,将图 7-7 中的目标 J(θ)与( 7.38 中给出的目标互换。

这一次,我们将使用一个库,而不是自己编码。OpenAI 有一个库叫做 Baselines ( https://github.com/openai/baselines )。它实现了许多流行和最新的算法。还有另一个基于基线的库,叫做稳定基线 3。你可以在 https://stable-baselines3.readthedocs.io/en/master/ 了解更多。

我们的代码将遵循与以前相同的模式,只是我们不会显式地定义策略网络。我们也不会自己写计算损失和通过梯度的训练步骤。您可以在listing7_3_ppo_baselines3.ipynb中找到使用 PPO 训练并记录CartPole训练表现的完整代码。我们现在将浏览创建代理、在CartPole上训练它并评估性能的代码片段,如清单 7-8 所示。

from stable_baselines3 import PPO
from stable_baselines3.ppo.policies import MlpPolicy
from stable_baselines3.common.evaluation import evaluate_policy

#create enviroment
env_name = 'CartPole-v1'
env = gym.make(env_name)

# build model
model = PPO(MlpPolicy, env, verbose=0)

# Train the agent for 30000 steps
model.learn(total_timesteps=30000)

# Evaluate the trained agent
mean_reward, std_reward = evaluate_policy(model, env, n_eval_episodes=100)
print(f"mean_reward:{mean_reward:.2f} +/- {std_reward:.2f}")

Listing 7-8PPO Agent for CartPole Using a Stable Baselines 3 Implementation

就是这样。用 PPO 训练代理只需要几行代码。Python 笔记本包含额外的代码,用于记录受训代理的表现并播放视频。我们不打算讨论它的代码细节。建议感兴趣的读者使用之前的链接深入研究 OpenAI 基线和稳定基线。

我们也想利用这个机会再次强调了解流行的 RL 库的重要性。在浏览本书中各种算法的实现时,您应该注意到,熟悉流行的 RL 实现并学会使用它们来满足您的特定需求同样重要。本书附带的代码有助于您更好地理解这些概念。它绝不是生产代码。像 Baselines 这样的库拥有高度优化的代码,能够利用 GPU 和多核并行运行许多代理。

摘要

本章向您介绍了另一种方法,即直接学习策略,而不是先学习状态/动作值,然后使用它们来找到最佳策略。

我们研究了 REINFROCE 的推导,这是最基本的政策梯度方法。在最初的推导之后,我们研究了一些减少方差的技术,比如奖励和基线的使用。

这让我们看到了演员-评论家家族,在这个家族中,我们结合了基于价值的方法来学习使用强化作为基线的状态值。政策网络(actor)与国家价值网络(critical)使我们能够结合基于价值的方法和政策梯度方法的优点。我们简要介绍了异步版本 A3C。

最后,我们看了一些高级策略优化技术,比如信任区域策略优化(TRPO)和邻近策略优化(PPO)。我们讨论了使用这两种技术的主要动机和方法。我们还看到了使用库来训练使用 PPO 的代理。

****

八、结合策略梯度和 Q 学习

到目前为止,在本书中,在深度学习与强化学习相结合的背景下,我们已经在第六章中查看了深度 Q 学习及其变体,并在第七章中查看了策略梯度。神经网络训练需要多次迭代,Q-learning 是一种非策略方法,它使我们能够多次使用转换,从而提高样本效率。然而,Q-learning 有时会不稳定。此外,这是一种间接的学习方式。我们不是直接学习一个最优策略,而是先学习 q 值,然后用这些动作值来学习最优行为。在第七章中,我们看到了直接学习策略的方法,这给了我们更好的改进保证。然而,我们在第七章看到的所有政策都是政策上的。我们使用策略与环境进行交互,并对策略权重进行更新,以增加好的轨迹/动作的概率,同时减少坏的轨迹/动作的概率。然而,我们在策略上进行学习,因为在更新策略权重之后,先前的转换变得无效。

在这一章中,我们将着眼于结合这两种方法的优点,即非策略学习和直接学习策略。我们将首先讨论 Q 学习与政策梯度方法的权衡。在此之后,我们将研究三种将 Q 学习与策略梯度相结合的流行方法:深度确定性策略梯度(DDPG)、双延迟 DDPG (TD3)和软行动者批评(SAC)。我们将主要遵循 OpenAI Spinning Up 库中记录的符号、方法和示例代码。 1

政策梯度和 Q-Learning 的权衡

在第七章中,我们看了 DQN,深度学习版的 Q-learning。在 Q-learning(一种非策略方法)中,我们从探索性行为策略中收集转换,然后在批量随机梯度更新中使用这些转换来学习 Q 值。当我们学习 q 值时,我们通过取一个状态中所有可能行动的 q 值的最大值来选择最佳行动,从而改进策略。这是我们遵循的等式:

$$ {w}_{t+1}={w}_t+\alpha .\kern0.5em \frac{1}{N}\sum \limits_{i=1}N\left[{r}_i+\gamma {\mathit{\max}}_{a_i{\prime }}\overset{\sim }{q}\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right)-\hat{q}\left({s}_i,{a}_i;w\right)\right].{\nabla}_w\ \hat{q}\left({s}_i,{a}_i;w\right) $$

(8.1)

请注意最大值。通过取最大值,即$$ {\mathit{\max}}_{a_i^{\prime }}\overset{\sim }{q}\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right) $$,我们正在提高目标值$$ {r}_i+\gamma\ {\mathit{\max}}_{a_i^{\prime }}\overset{\sim }{q}\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right) $$,这迫使当前状态动作 q 值$$ \hat{q}\left({s}_i,{a}_i;w\right) $$更新权重以达到更高的目标。这就是我们强迫网络权重满足的贝尔曼最优方程。整个学习是偏离策略的,因为无论遵循什么策略,贝尔曼最优方程都适用于最优策略。所有的( sars' )转换都需要满足这一点,不管这些转换是使用哪个策略生成的。我们能够使用重放缓冲区重用转换,这使得学习非常简单高效。然而,Q-learning 也有一些问题。

第一个是关于使用 Q-learning 的当前形式进行持续的行动。请看$$ {\mathit{\max}}_{a_i^{\prime }}\overset{\sim }{q}\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right) $$。我们在第六章中看到的所有例子都是离散动作的例子。你知道为什么吗?当动作空间是连续和多维的,例如一起移动机器人的多个关节时,你认为你将如何执行 max

当动作离散时,很容易取 max 。我们将状态 s 输入到模型中,对于所有可能的动作,我们得到 Q ( sa )。由于离散动作的数量有限,选择最大值很容易。图 8-1 显示了一个样品模型。

img/502835_1_En_8_Fig1_HTML.jpg

图 8-1

具有离散动作的 DQN 学习的一般模型

现在想象这些动作是连续的!你会如何取最大值?为了找到每个$$ {s}_i^{\prime } $$$$ {\mathit{\max}}_{a_i^{\prime }}\overset{\sim }{q}\left({s}_i^{\prime },{a}_i^{\prime };{w_t}^{-}\right) $$,我们必须运行另一个优化算法来找到最大值。这将是一个昂贵的过程,因为作为政策改进的一部分,需要对批次中的每个过渡执行该过程。

第二个问题是学习错误的目标。我们实际上想要一个最优的政策,但我们不会在 DQN 的直接领导下这样做。我们学习行动值函数,然后使用 max 找到最佳 q 值/最佳行动。

第三个问题是,DQN 有时也不稳定。没有理论上的保证,我们正在尝试使用我们在第五章中谈到的半梯度更新来更新权重。我们基本上是在尝试遵循目标不断变化的监督学习过程。我们所学的会影响新轨迹的生成,这反过来会影响我们的学习质量。我们看到的所有 DQN 平均回报的进度图都没有持续改善。它们非常不稳定,需要仔细调整超参数,以确保算法朝着好的策略发展。

最后,第四个问题是 DQN 学会了确定性政策。我们使用探索性行为策略来生成和探索代理在确定性策略中学习的内容。特别是在机器人领域的实验已经表明,一定量的随机策略更好,因为我们对世界的建模和关节的操作并不总是完美的。我们需要一些随机性来调整不完美的建模或动作值到实际机器人关节运动的转换。此外,确定性策略是随机策略的极限情况。

让我们把注意力转向政策梯度方法。在策略梯度方法中,我们输入状态,得到的输出是离散动作的动作概率或连续动作的概率分布的参数。我们可以看到,策略梯度允许我们学习离散和连续动作的策略。然而,学习连续动作在 DQN 是不可行的。图 8-2 显示了政策梯度中使用的模型。

img/502835_1_En_8_Fig2_HTML.jpg

图 8-2

政策梯度方法的政策网络。在第七章中,我们看到了不连续的动作,但是这个过程对于连续的动作也很有效,就像在那一章中解释的那样

此外,使用策略梯度方法,我们直接学习改进策略,而不是先学习值函数,然后使用它们来寻找最优策略的迂回方式。普通政策梯度确实遭遇了向坏区域的崩溃,我们看到了 TRPO 和 PPO 等方法控制步长以改善政策梯度的保证,从而产生更好的政策。

与 DQN 不同,政策梯度学习随机政策,因此探索是我们试图学习的政策的一部分。然而,政策梯度法的最大缺陷是,它是一种政策性方法。一旦我们使用转换来计算梯度更新,模型就移动到新的策略。在这个更新的政策世界中,早期的转变不再适用。我们需要在更新后丢弃以前的转换,并生成新的轨迹/转换来训练模型。这使得策略学习非常样本低效。

我们确实使用行动者-批评家方法将价值学习作为政策梯度的一部分,其中政策网络是试图学习最佳行动的行动者,而价值网络是告知政策网络行动好坏的批评家。然而,即使使用演员-评论家的方法,学习是在政策上。我们使用批评家来指导参与者,但是在更新策略(和/或价值)网络之后,我们仍然需要丢弃所有的转换。

有没有一种方法可以让我们直接学习策略,但同时利用 Q-learning 来学习非策略?对于连续行动空间,我们能这样做吗?这就是我们将在本章中讨论的内容。我们将把 Q-learning 和策略梯度结合起来,提出不依赖于策略的算法,并很好地用于连续动作。

结合策略梯度和 Q-Learning 的通用框架

我们将着眼于持续行动政策。我们将有两个网络。一个是学习给定状态下的最优行动,即行动者网络。假设策略网络用θ参数化,网络学习到一个策略,这个策略产生动作 a = μ θ ( s ),这个动作最大化 Q ( sa )。在数学符号中:

$$ \underset{a^{\prime }}{\mathit{\max}}{Q}{\ast}\left({s}{\prime },{a}^{\prime}\right)\approx {Q}{\ast}\left({s}{\prime },{\upmu}_{\uptheta}(s)\right) $$

第二个网络,评论家网络,将再次把状态( s )作为一个输入,并把来自第一个网络的最优动作,μθ(s),作为另一个输入,以产生 q 值q【ϕ(sμ θ 图 8-3 从概念上展示了网络的相互作用。

img/502835_1_En_8_Fig3_HTML.jpg

图 8-3

结合策略和 Q-learning。我们使用两个网络直接学习策略和 Q,在这两个网络中,来自第一个网络(行动者)的行动输出被馈送到第二个网络(批评家),第二个网络学习 Q ( sa )

为了确保探索,我们要采取行动 a ,这是探索性的。这类似于我们在 Q-learning 中采用的方法,在 Q-learning 中,我们学习了确定性策略,但从探索性ε-贪婪策略生成了转换。同样,在这里,我们在学习 a 的同时,加入一点随机性 ϵ ~ N (0, σ 2 )并使用 a + ε 动作来探索环境,生成轨迹。

像 Q-learning 一样,我们将使用重放缓冲区来存储转换,并重用以前的转换来学习。这是我们将在本章中看到的所有方法的最大好处之一。它们将使策略学习偏离策略,从而提高样本效率。

在 Q-learning 中,我们必须使用一个目标网络,它是 Q-网络的副本。原因是在学习 q 值时提供某种文具目标。你可以在第五章和第六章重温目标网络的讨论。在这些方法中,目标网络权重定期用在线/代理网络权重更新。这里我们也将使用一个目标网络。然而,用于更新本章中算法的目标网络权重的方法将是 polyak 平均 ( 指数平均)的方法,如下式所示:

$$ {\upphi}_{target}\leftarrow \uprho {\upphi}_{target}+\left(1-\uprho \right)\upphi $$

(8.2)

我们还将使用策略网络的目标网络。这与提供稳定的目标 Q 值的原因相同,这允许我们执行梯度下降的监督学习风格,并将权重调整到近似的 Q ( sa )。

有了这个背景,我们就可以开始研究我们的第一个算法:深度确定性策略梯度。

深度确定性政策梯度

2016 年,在一篇题为“深度强化学习的连续控制”的论文中,来自 DeepMind 的作者 2 介绍了 DDPG 算法。作者对他们的方法提出了以下几点:

  • 而 DQN 求解高维状态空间,只能处理离散的低维动作空间。DQN 不能应用于连续和高维的动作领域,例如,像机器人这样的物理控制任务。

  • 由于维数灾难,离散化行动空间不是一个选项。假设你有一个有七个关节的机器人,每个关节可以在范围内移动( kk )。让我们对每个关节进行粗略的离散化,每个关节有三个可能的值{k,0, k }。即使采用这种粗略的离散化,所有七个维度中的离散动作的总组合也达到 3 7 = 2187。相反,如果我们决定将每个关节的离散范围划分为范围内的 10 个可能值( kk ),我们会得到 10 7 = 10 百万个选项。这是维度的诅咒,其中可能的动作组合集合随着每个新维度/关节呈指数增长。

  • DDPG 是一种如下的算法:

    • 型号自由:我们不知道型号。我们从主体与环境的互动中学习。

    • 偏离政策 : DDPG 和 DQN 一样,使用探索性政策来产生转变,并学习确定性政策。

    • 连续高维动作空间 : DDPG 只对连续动作域有效,对高维动作空间也很有效。

    • 行动者-批评家:这意味着我们有一个行动者(政策网络)和批评家,行动-价值(q 值)网络。

    • 重放缓冲器:像 DQN 一样,DDPG 使用重放缓冲器来存储过渡,并利用它们来学习。这打破了训练示例的时间依赖性/相关性,否则会搞乱学习。

    • 目标网络:和 DQN 一样,它使用目标网络为 q 值学习提供相当稳定的目标。然而,与 DQN 不同,它不通过定期复制在线/代理/主网络的权重来更新目标网络。相反,它使用 polyak/指数平均值在每次更新主网络后将目标网络移动一点点。

现在让我们将注意力转向网络架构和我们计算的损耗。首先让我们看看 Q-learning 部分,然后我们将看看政策学习网络。

DDPG 的 Q-Learning(评论家)

在 DQN,我们计算了通过梯度下降最小化的损失。损失由方程式( 6)给出。3 ),转载于此:

$$ L=\frac{1}{N}{\sum}_{i=1}^N{\left[{r}_i+\left(\left(1- don{e}_i\right).\upgamma .\underset{a_i^{\prime }}{\max}\hat{q}\left({s}_i^{\prime },{a}_i^{\prime };{w}_t^{-}\right)\right)\hbox{--} \hat{q}\left({s}_i,{a}_i;{w}_t\right)\right]}² $$

(8.3)

让我们重写等式。我们将删除 subindex it 以减少符号的混乱。我们将求和改为期望,以强调我们通常想要的是一个期望,但它是在蒙特卡罗下通过样本的平均值来估计的。最终在代码中,我们得到了和,但是它们是一些期望值的蒙特卡罗估计。我们还将用ϕ 目标 替换目标网络权重$$ {w}_t^{-} $$。同样,我们将主要权重 w * t 替换为ϕ.进一步,我们将把权重从函数参数内部移到函数上的子索引,即qϕ(…)←q*(…)。;ϕ).由于所有这些符号变化,等式( 8.3 )看起来像这样:

$$ L\left(\upphi, D\right)=\underset{\left(s,a,r,{s}^{'},d\right)\sim D}{E}\left[{\left({Q}_{\upphi}\left(s,a\right)-\left(r+\upgamma \left(1-d\right)\underset{a^{\prime }}{\mathit{\max}}{Q}_{\upphi_{targ}}\left({s}^{\prime },{a}^{\prime}\right)\right)\right)}²\right] $$

(8.4)

这仍然是 DQN 公式,我们在状态(s)中取最大离散动作来得到$$ \underset{a^{\prime }}{\mathit{\max}}{Q}_{\phi_{targ}}\left({s}^{\prime },{a}^{\prime}\right) $$。在连续空间中,我们不能取 max ,因此我们有另一个网络(actor)来取输入状态 s 并产生动作,这使$$ {Q}_{\phi_{targ}}\left({s}^{\prime },{a}^{\prime}\right) $$最大化;即,我们用$$ {Q}_{\upphi_{targ et}}\left({s}{'},{\upmu}_{\uptheta_{targ}}\left({s}{'}\right)\right) $$替换$$ \underset{a^{\prime }}{\mathit{\max}}{Q}_{\phi_{targ}}\left({s}^{\prime },{a}^{\prime}\right) $$,其中$$ {a}^{\prime }={\mu}_{\theta_{targ}}\left({s}^{'}\right) $$是目标策略。更新后的损失表达式如下:

$$ L\left(\phi, D\right)=\underset{\left(s,a,r,{s}^{'},d\right)\sim D}{E}\left[{\left({Q}_{\phi}\left(s,a\right)-\left(r+\gamma \left(1-d\right){Q}_{\phi_{targ et}}\left({s}{'},{\mu}_{\theta_{targ}}\left({s}{'}\right)\right)\right)\right)}²\right] $$

(8.5)

这是更新的均方贝尔曼误差(MSBE),我们将在代码中实现,然后进行反向传播,以最小化损失函数。请注意,这只是ϕ的函数,因此 L ( ϕD )的梯度是相对于ϕ.的如前所述,在代码中,我们将用样本平均值代替期望值,样本平均值是期望值的 MC 估计值。

接下来我们来看看政策学习部分。

DDPG 的政策学习(演员)

在策略学习部分,我们试图学习 a = μ θ ( s ),一个确定性策略,给出最大化 Q ϕ ( sa )的动作。由于动作空间是连续的,并且我们假设 Q 函数相对于动作是可微的,我们可以相对于要求解的策略参数执行梯度上升。

$$ \underset{\theta }{\mathit{\max}}\ J\left(\theta, D\right)=\underset{\theta }{\mathit{\max}}\underset{s\sim D}{E}\left[{Q}_{\upphi}\left(s,{\upmu}_{\uptheta}(s)\right)\right] $$

(8.6)

由于策略是确定性的,( 8.6 )中的期望不依赖于策略,这与我们在前一章的随机梯度中看到的不同。那里的期望算子依赖于策略参数,因为策略是随机的,这反过来影响期望的 q 值。

我们可以取 J 相对于 θ 的梯度,得到如下结果:

$$ {\nabla}_{\uptheta}J\left(\uptheta, D\right)=\underset{s\sim D}{E}\left[{\nabla}_a{Q}_{\upphi}\left(s,a\right){\left.\kern0em \right|}_{a={\upmu}_{\upphi}(s)}{\nabla}_{\upphi}{\upmu}_{\upphi}(s)\right] $$

(8.7)

这是链式法则的直接应用。还请注意,我们在期望中没有得到任何∇ 对数 (…)项,因为对其进行期望的状态 s 来自重放缓冲器,并且它与关于哪个梯度进行期望的参数θ无关。

此外,在 2014 年题为“确定性政策梯度算法”的论文中, 3 作者表明,方程( 8.7 )是政策梯度,即政策绩效的梯度。建议你通读这两篇论文,以便对 DDPG 背后的数学有更深入的理论理解。

如前所述,为了帮助探索,当我们学习确定性策略时,我们将使用所学策略的嘈杂探索版本来探索和生成转换。我们通过向学习策略中添加平均零高斯噪声来做到这一点。

伪代码和实现

至此,我们已经准备好给出完整的伪代码了。请参见图 8-4 进行说明。

Deep Deterministic Policy Gradient

img/502835_1_En_8_Fig4_HTML.png

图 8-4

深度确定性策略梯度算法

代码中使用的健身房环境

转向实现,我们将在本章中使用两个环境来运行代码。第一个是称为Pendulum-v0 .的钟摆摆动环境,这里的状态是给出钟摆角度的三维向量(即,其 cossin 分量),第三维是角速度(θ点):$$ \left[\mathit{\cos}\left(\uptheta \right),\mathit{\sin}\left(\uptheta \right),\dot{\theta}\right] $$。这个动作是一个单一的值,力矩施加在钟摆上。这个想法是尽可能长时间地保持钟摆直立。参见图 8-5 。

img/502835_1_En_8_Fig5_HTML.jpg

图 8-5

来自开放体育馆图书馆的钟摆环境

在我们在这个具有一维动作空间的简单连续动作环境上训练网络之后,我们将研究另一个称为月球着陆器连续环境的环境:LunarLanderContinuous-v2。在这种环境下,我们试图将登月舱降落在月球的两面旗帜之间。状态向量是八维的:[x_pos, y_pos, x_vel, y_vel, lander_angle, lander_angular_vel, left_leg_ground_contact_flag, right_leg_ground_contact_flag]

动作是二维浮动:[main engine, left-right engines]

  • 主机 : -1..0 表示发动机关闭,范围(0,1)是从 50%到 100%功率的发动机油门。发动机不能在低于 50%的功率下工作。

  • 左右 : range(-1.0, -0.5)点火左发动机,range(+0.5, +1.0)点火右发动机,range(-0.5, 0.5)两个发动机都关。

图 8-6 显示了环境的快照。

img/502835_1_En_8_Fig6_HTML.jpg

图 8-6

月球着陆器连续从开放体育馆图书馆

代码列表

现在让我们把注意力转向实现图 8-4 中给出的 DDPG 伪代码的实际代码。代码来自文件listing8_1_ddpg_pytorch.ipynb.我们在 TensorFlow 2.0 的文件listing8_1_ddpg_tensorflow.ipynb.中也有完整的实现,所有的代码遍历都将借用这两个文件中的代码片段。我们将首先讨论 Q 和策略网络,然后是损失计算,然后是训练循环。最后,我们将讨论运行和测试经过培训的代理的性能的代码。

政策网络行动者

首先让我们看看演员/政策网络。清单 8-1 显示了 PyTorch 中的策略网络代码。我们定义了一个简单的神经网络,它有两个大小为 256 的隐藏层,每个层都有 ReLU 激活。如果你查看函数forward,,你会注意到最后一层(self.actor)通过了tanh激活。Tanh是挤压功能;它将(∞,∞)中的值重新映射到一个压缩范围(-1,1)。然后,我们将该压缩值乘以动作限值(self.act_limit),以便MLPActor的连续输出在环境可接受的动作值的有效范围内。我们通过扩展 PyTorch nn.Module类来创建我们的网络类,这需要我们定义一个forward函数,将输入状态 S 作为产生动作值作为网络输出的参数。

class MLPActor(nn.Module):
    def __init__(self, state_dim, act_dim, act_limit):
        super().__init__()
        self.act_limit = act_limit
        self.fc1 = nn.Linear(state_dim, 256)
        self.fc2 = nn.Linear(256, 256)
        self.actor = nn.Linear(256, act_dim)

    def forward(self, s):
        x = self.fc1(s)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        x = self.actor(x)
        x = torch.tanh(x)  # to output in range(-1,1)
        x = self.act_limit * x
        return x

Listing 8-1Policy Network in PyTorch

政策网络参与者(TensorFlow)

清单 8-2 包含了 TensorFlow 2.0 中相同的函数。它非常类似于 PyTorch 实现,除了我们子类化了tf.keras.Model而不是nn.Module。我们在一个名为call的函数中实现网络转发逻辑,而不是在函数forward中。此外,层的命名方式也有细微的差别,例如denselinear以及层的维度传递方式。

class MLPActor(tf.keras.Model):
    def __init__(self, state_dim, act_dim, act_limit):
        super().__init__()
        self.act_limit = act_limit
        self.fc1 = layers.Dense(256, activation="relu")
        self.fc2 = layers.Dense(256, activation="relu")
        self.actor = layers.Dense(act_dim)

    def call(self, s):
        x = self.fc1(s)
        x = self.fc2(x)
        x = self.actor(x)
        x = tf.keras.activations.tanh(x)  # to output in range(-1,1)
        x = self.act_limit * x
        return x

Listing 8-2Policy Network in TensorFlow

q-网络评论家实现

接下来我们来看看 Q-network ( 评论家)。这也是一个简单的两层隐藏网络,具有 ReLU 激活,然后是最后一层,输出数量等于 1。最后一层没有任何激活,使网络能够产生任何值作为网络的输出。该网络输出 q 值,因此我们需要一个可能的范围(∞,∞)。

PyTorch

清单 8-3 显示了 PyTorch 中批评家网络的代码,清单 8-4 显示了 TensorFlow 中的代码。它们非常类似于参与者/策略网络的实现,除了前面讨论的一些小的区别。

class MLPQFunction(nn.Module):
    def __init__(self, state_dim, act_dim):
        super().__init__()
        self.fc1 = nn.Linear(state_dim+act_dim, 256)
        self.fc2 = nn.Linear(256, 256)
        self.Q = nn.Linear(256, 1)

    def forward(self, s, a):
        x = torch.cat([s,a], dim=-1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        q = self.Q(x)
        return torch.squeeze(q, -1)

Listing 8-3Q/Critic Network in PyTorch

TensorFlow

清单 8-4 显示了 TensorFlow 中 critic 网络的代码。

class MLPQFunction(tf.keras.Model):
    def __init__(self, state_dim, act_dim):
        super().__init__()
        self.fc1 = layers.Dense(256, activation="relu")
        self.fc2 = layers.Dense(256, activation="relu")
        self.Q = layers.Dense(1)

    def call(self, s, a):
        x = tf.concat([s,a], axis=-1)
        x = self.fc1(x)
        x = self.fc2(x)
        q = self.Q(x)
        return tf.squeeze(q, -1)

Listing 8-4Q/Critic Network in TensorFlow

组合的模型-参与者评论实现

一旦这两个网络都定义好了,我们就把它们组合成一个类,这样我们就可以以一种更加模块化的方式来管理在线网络和目标网络。这只是为了更好地组织代码,仅此而已。结合两个网络的类实现为MLPActorCritic。在这个类中,我们还定义了一个函数get_action,它接受状态和噪声比例。它通过策略网络传递状态得到μθ(s),然后它加上一个噪声(零均值高斯噪声)给出一个有噪声的动作进行探索。这是实现算法步骤 4 的函数:

$$ a= clip\left({\mu}_{\theta }(s)+\epsilon, {a}_{Low},{a}_{High}\right),\mathrm{where}\kern0.5em \epsilon \sim N $$

清单 8-5 展示了 PyTorch 中MLPActorCritic的实现,清单 8-6 展示了 TensorFlow 版本。实现非常相似,除了个别的细微差别,比如子类化哪个类。另一个区别是,在 PyTorch 中,您需要将 NumPy 数组转换为 Torch 张量,然后才能通过网络传递它们,而在 TensorFlow 中,我们可以直接将 NumPy 数组传递给模型。

class MLPActorCritic(tf.keras.Model):
    def __init__(self, observation_space, action_space):
        super().__init__()
        self.state_dim = observation_space.shape[0]
        self.act_dim = action_space.shape[0]
        self.act_limit = action_space.high[0]

        #build Q and policy functions
        self.q = MLPQFunction(self.state_dim, self.act_dim)
        self.policy = MLPActor(self.state_dim, self.act_dim, self.act_limit)

    def act(self, state):
        return self.policy(state).numpy()

    def get_action(self, s, noise_scale):
        a = self.act(s.reshape(1,-1).astype("float32")).reshape(-1)
        a += noise_scale * np.random.randn(self.act_dim)
        return np.clip(a, -self.act_limit, self.act_limit)

Listing 8-6MLPActorCritic in TensorFlow

class MLPActorCritic(nn.Module):
    def __init__(self, observation_space, action_space):
        super().__init__()
        self.state_dim = observation_space.shape[0]
        self.act_dim = action_space.shape[0]
        self.act_limit = action_space.high[0]

        #build Q and policy functions
        self.q = MLPQFunction(self.state_dim, self.act_dim)
        self.policy = MLPActor(self.state_dim, self.act_dim, self.act_limit)

    def act(self, state):
        with torch.no_grad():
            return self.policy(state).numpy()

    def get_action(self, s, noise_scale):
        a = self.act(torch.as_tensor(s, dtype=torch.float32))
        a += noise_scale * np.random.randn(self.act_dim)
        return np.clip(a, -self.act_limit, self.act_limit)

Listing 8-5MLPActorCritic in PyTorch

体验回放

像 DQN 一样,我们使用经验回放。这和我们在 DDPG 使用的 PyTorch 和 TensorFlow 来自 DQN 的版本是一样的。它是使用 NumPy 数组实现的,同样的代码适用于 PyTorch 和 TensorFlow。因为它是从 DQN 借来的相同的实现,我们不给出它的代码。要查看ReplayBuffer的代码,请查看 Jupyter 笔记本中的实现。

q 损耗实现

接下来,我们来看看 Q 损耗的计算。我们实际上是在实现伪代码的步骤 11 和 12 中的等式。

$$ y\left(r,{s}^{\prime },d\right)=r+\upgamma \left(1-d\right){Q}_{targ}\left({s}^{\prime },{\upmu}_{\uptheta_{targ}}\left({s}^{\prime}\right)\right) $$

$$ {\nabla}_{\upphi}\frac{1}{\mid B\mid }{\sum}_{\left(s,a,r,{s}^{\prime },d\right)\in B}{\left({Q}_{\upphi}\left(s,a\right)-y\left(r,{s}^{\prime },d\right)\right)}² $$

PyTorch

清单 8-7 给出了 PyTorch 的实现。我们先把这批( sars'd )转换成 PyTorch 张量。接下来我们用批( sa )计算 Qϕ ( sa ),并通过策略网络传递。接下来,我们根据上式计算目标值 y ( rsd )。在计算目标时,我们使用with torch.no_grad()来停止梯度计算,因为我们不想使用 PyTorch 中的 auto-diff 来调整目标网络权重。我们将使用 polyak 平均手动调整目标网络权重。停止计算不需要的梯度可以加快训练速度,还可以确保梯度步长不会对您想要保持冻结或手动调整的权重产生任何意外的副作用。最后,我们计算损失。

$$ {Q}_{Loss}=\frac{1}{\mid B\mid }{\sum}_{\left(s,a,r,{s}^{\prime },d\right)\in B}{\left({Q}_{\upphi}\left(s,a\right)-y\left(r,{s}^{\prime },d\right)\right)}² $$

PyTorch 执行反向传播来计算梯度。我们不需要在代码中明确计算梯度。

def compute_q_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma=0.99):

    # convert numpy array to torch tensors
    states = torch.tensor(states, dtype=torch.float)
    actions = torch.tensor(actions, dtype=torch.float)
    rewards = torch.tensor(rewards, dtype=torch.float)
    next_states = torch.tensor(next_states, dtype=torch.float)
    done_flags = torch.tensor(done_flags.astype('float32'),dtype=torch.float)

    # get q-values for all actions in current states
    # use agent network
    predicted_qvalues = agent.q(states, actions)

    # Bellman backup for Q function
    with torch.no_grad():
        q__next_state_values = target_network.q(next_states, target_network.policy(next_states))
        target = rewards + gamma * (1 - done_flags) * q__next_state_values

    # MSE loss against Bellman backup
    loss_q = ((predicted_qvalues - target)**2).mean()

    return loss_q

Listing 8-7Q-Loss Computation in PyTorch

TensorFlow

TensorFlow 版本类似,清单 8-8 列出了完整的实现。与 PyTorch 的主要区别在于,我们没有将 NumPy 数组转换为张量。但是,我们将数据类型转换为float32,使其与网络权重的默认数据类型兼容。请记住,对于所有 TensorFlow 实现,我们都使用热切执行模式,与 PyTorch 类似,我们使用with tape.stop_recording()来停止目标网络中的梯度计算。

def compute_q_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma, tape):

    # convert numpy array to proper data types
    states = states.astype('float32')
    actions = actions.astype('float32')
    rewards = rewards.astype('float32')
    next_states = next_states.astype('float32')
    done_flags = done_flags.astype('float32')

    # get q-values for all actions in current states
    # use agent network
    predicted_qvalues = agent.q(states, actions)

    # Bellman backup for Q function
    with tape.stop_recording():
        q__next_state_values = target_network.q(next_states, target_network.policy(next_states))
        target = rewards + gamma * (1 - done_flags) * q__next_state_values

    # MSE loss against Bellman backup
    loss_q = tf.reduce_mean((predicted_qvalues - target)**2)

    return loss_q

Listing 8-8Q-Loss Computation in TensorFlow

保单损失执行

接下来,我们按照伪代码的第 13 步计算保单损失。

$$ {Policy}_{Loss}=-\frac{1}{\left|B\right|}{\sum}_{s\in B}{Q}_{\phi}\left(s,{\mu}_{\theta }(s)\right) $$

这是一个简单的计算。它只是 PyTorch 和 TensorFlow 中的一个三行代码实现。清单 8-9 包含 PyTorch 版本,清单 8-10 包含 TensorFlow 版本。请注意损失中的-ve符号。我们的算法需要在策略目标上做梯度上升,但是像 PyTorch 和 TensorFlow 这样的自动微分库实现梯度下降。将政策目标乘以-1.0 会导致亏损,亏损的梯度下降与政策目标的梯度上升相同。

def compute_policy_loss(agent, states, tape):

    # convert numpy array to proper data type
    states = states.astype('float32')

    predicted_qvalues = agent.q(states, agent.policy(states))

    loss_policy = - tf.reduce_mean(predicted_qvalues)

    return loss_policy

Listing 8-10Policy-Loss Computation in TensorFlow

def compute_policy_loss(agent, states):

    # convert numpy array to torch tensors
    states = torch.tensor(states, dtype=torch.float)

    predicted_qvalues = agent.q(states, agent.policy(states))

    loss_policy = - predicted_qvalues.mean()

    return loss_policy

Listing 8-9Policy-Loss Computation in PyTorch

一步更新实施

接下来,我们定义一个名为one_step_update的函数,它获取一批( sars 'd )并计算 Q 损失,随后是反向传播,然后是类似的策略损失计算步骤,随后是梯度步骤。最后,它使用 polyak 平均对目标网络权重进行更新。本质上,这个步骤和前面的两个函数compute_q_losscompute_policy_loss一起实现了伪代码的步骤 11 到 14。

清单 8-11 显示了one_step_update的 PyTorch 版本。第一步是计算 Q 损失,并对 critic/Q 网络权重执行梯度下降。然后,我们冻结 Q-网络权重,使得策略网络上的梯度下降不会影响 Q-网络的权重。随后计算行动者/策略网络权重的策略损失和梯度下降。我们再次解冻 Q-网络权重。最后,我们使用 polyak 平均来更新目标网络权重。

def one_step_update(agent, target_network, q_optimizer, policy_optimizer,
                    states, actions, rewards, next_states, done_flags,
                    gamma=0.99, polyak=0.995):

    #one step gradient for q-values
    q_optimizer.zero_grad()
    loss_q = compute_q_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma)
    loss_q.backward()
    q_optimizer.step()

    #Freeze Q-network
    for params in agent.q.parameters():
        params.requires_grad = False

    #one setep gradient for policy network
    policy_optimizer.zero_grad()
    loss_policy = compute_policy_loss(agent, states)
    loss_policy.backward()
    policy_optimizer.step()

    #UnFreeze Q-network
    for params in agent.q.parameters():
        params.requires_grad = True

    # update target networks with polyak averaging
    with torch.no_grad():
        for params, params_target in zip(agent.parameters(), target_network.parameters()):
            params_target.data.mul_(polyak)
            params_target.data.add_((1-polyak)*params.data)

Listing 8-11One-Step Update in PyTorch

清单 8-12 给出了one_step_update的 TensorFlow 版本,类似于 PyTorch 实现流程。不同之处在于计算梯度的方式、在每个库中采用梯度步长的方式、冻结和解冻权重的方式以及更新目标网络权重的方式。逻辑是一样的;这只是调用哪个库函数和传递什么参数的区别——基本上是两个库的语法区别。

def one_step_update(agent, target_network, q_optimizer, policy_optimizer,
                    states, actions, rewards, next_states, done_flags,
                    gamma=0.99, polyak=0.995):

    #one step gradient for q-values
    with tf.GradientTape() as tape:
        loss_q = compute_q_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma, tape)

        gradients = tape.gradient(loss_q, agent.q.trainable_variables)
        q_optimizer.apply_gradients(zip(gradients, agent.q.trainable_variables))

    #Freeze Q-network
    agent.q.trainable=False

    #one setep gradient for policy network
    with tf.GradientTape() as tape:
        loss_policy = compute_policy_loss(agent, states, tape)
        gradients = tape.gradient(loss_policy, agent.policy.trainable_variables)
        policy_optimizer.apply_gradients(zip(gradients, agent.policy.trainable_variables))

    #UnFreeze Q-network
    agent.q.trainable=True

    # update target networks with polyak averaging
    updated_model_weights = []
    for weights, weights_target in zip(agent.get_weights(), target_network.get_weights()):
        new_weights = polyak*weights_target+(1-polyak)*weights
        updated_model_weights.append(new_weights)
    target_network.set_weights(updated_model_weights)

Listing 8-12One-Step Update in TensorFlow

DDPG:主循环

最后一步是 DDPG 算法的实现,它使用了前面的one_step_update函数。它创建优化器并初始化环境。它使用当前的在线策略在环境中不断地步进。最初,对于第一个start_steps=10000,它采取一个随机动作来探索环境,一旦收集到足够多的转换,它就使用当前的带有噪声的策略来选择动作。转换被添加到ReplayBuffer,如果缓冲区已满,则从缓冲区中删除最早的一个。update_after告诉算法仅在update_after=1000转换被收集到缓冲器后才开始进行梯度更新。代码按照参数epoch=5的定义多次运行循环。我们使用了epoch=5进行演示。你可能想要运行更长的时间,比如说 100 个纪元左右。这绝对是推荐给月球着陆器环境的。我们在清单 8-13 中仅给出 PyTorch 版本的清单。

def ddpg(env_fn, seed=0,
         steps_per_epoch=4000, epochs=5, replay_size=int(1e6), gamma=0.99,
         polyak=0.995, policy_lr=1e-3, q_lr=1e-3, batch_size=100, start_steps=10000,
         update_after=1000, update_every=50, act_noise=0.1, num_test_episodes=10,
         max_ep_len=1000):

    torch.manual_seed(seed)
    np.random.seed(seed)

    env, test_env = env_fn(), env_fn()

    ep_rets, ep_lens = [], []

    state_dim = env.observation_space.shape
    act_dim = env.action_space.shape[0]

    act_limit = env.action_space.high[0]

    agent = MLPActorCritic(env.observation_space, env.action_space)
    target_network = deepcopy(agent)

    # Freeze target networks with respect to optimizers (only update via polyak averaging)
    for params in target_network.parameters():
        params.requires_grad = False

    # Experience buffer

    replay_buffer = ReplayBuffer(replay_size)

    #optimizers
    q_optimizer = Adam(agent.q.parameters(), lr=q_lr)
    policy_optimizer = Adam(agent.policy.parameters(), lr=policy_lr)

    total_steps = steps_per_epoch*epochs
    state, ep_ret, ep_len = env.reset(), 0, 0

    for t in range(total_steps):
        if t > start_steps:
            action = agent.get_action(state, act_noise)
        else:
            action = env.action_space.sample()

        next_state, reward, done, _ = env.step(action)
        ep_ret += reward
        ep_len += 1

        # Ignore the "done" signal if it comes from hitting the time
        # horizon (that is, when it's an artificial terminal signal
        # that isn't based on the agent's state)
        done = False if ep_len==max_ep_len else done

        # Store experience to replay buffer
        replay_buffer.add(state, action, reward, next_state, done)

        state = next_state

        # End of trajectory handling
        if done or (ep_len == max_ep_len):
            ep_rets.append(ep_ret)
            ep_lens.append(ep_len)
            state, ep_ret, ep_len = env.reset(), 0, 0

        # Update handling
        if t >= update_after and t % update_every == 0:
            for _ in range(update_every):
                states, actions, rewards, next_states, done_flags = replay_buffer.sample(batch_size)

                one_step_update(
                        agent, target_network, q_optimizer, policy_optimizer,
                        states, actions, rewards, next_states, done_flags,
                        gamma, polyak
                )

        # End of epoch handling
        if (t+1) % steps_per_epoch == 0:
            epoch = (t+1) // steps_per_epoch

            avg_ret, avg_len = test_agent(test_env, agent, num_test_episodes, max_ep_len)
            print("End of epoch: {:.0f}, Training Average Reward: {:.0f}, Training Average Length: {:.0f}".format(epoch, np.mean(ep_rets), np.mean(ep_lens)))
            print("End of epoch: {:.0f}, Test Average Reward: {:.0f}, Test Average Length: {:.0f}".format(epoch, avg_ret, avg_len))
            ep_rets, ep_lens = [], []

    return agent

Listing 8-13DDPG Outer Training Loop in PyTorch

TensorFlow 版本非常相似,除了在调用哪个库函数和如何传递参数上有微小的区别。在 TensorFlow 版本中,我们有一段额外的代码来初始化网络权重,这样我们就可以在开始训练之前冻结目标网络权重。按照我们构建模型的方式,直到训练的第一步才构建模型,因此我们需要在 TensorFlow 中添加额外的代码来强制构建模型。我们不会给出 TensorFlow 版本的代码清单。

剩下的代码是训练代理,然后记录被训练的代理的表现。我们首先为钟摆环境运行该算法,然后为月球着陆器健身房环境运行该算法。这些代码版本读起来很有趣,但是因为它们是我们学习 DDPG 的目标的附带内容,所以我们不会深入这些代码实现的细节。然而,感兴趣的读者可能希望查阅相关的库文档并逐步阅读代码。

这就完成了代码实现演练。我们可以看到,即使经过五个时期的训练,代理人能够在简单的摆环境中表现得非常好,并且能够在更复杂的月球着陆环境中表现得相当好。

接下来,我们将看看孪生延迟 DDPG,也称为 TD3。它有一些其他的增强和技巧来解决一些在 DDPG 看到的稳定性和收敛速度问题。

孪生延迟 DDPG

双延迟 DDPG 于 2018 年在一篇题为“解决演员-评论家方法中的函数近似误差”的论文中提出 4 DDPG 患有我们在第四章的 Q-learning 中看到的高估偏差(在“最大化偏差和双 Q 学习”一节)。我们在第六章中看到了双 DQN 方法,通过解耦最大化动作和最大 q 值来解决偏差。在前面提到的论文中,作者表明 DDPG 也遭受同样的高估偏差。他们提出了一种双 Q 学习的变体,解决了 DDPG 的这种高估偏差。该方法使用以下修改:

  • 削波双 Q 学习 : TD3 使用两个独立的 Q 函数,在贝尔曼方程下形成目标时取两者中的最小值,即图 8-4 中 DDPG 伪代码第 11 步中的目标。这种修改就是该算法被称为孪生的原因。

  • 延迟策略更新:与 Q 函数更新相比,TD3 更新策略和目标网络的频率较低。该论文建议对于 Q 函数的每两次更新,对策略和目标网络进行一次更新。这意味着在图 8-4 中的 DDPG 伪代码的步骤 13 和 14 中,对于步骤 11 和 12 中的 Q 函数的每两次更新,执行一次更新。这个修改就是把这个算法叫做延迟的原因。

  • 目标策略平滑 : TD3 给目标动作增加了噪声,使得策略更难利用 Q 函数估计误差和控制高估偏差。

目标政策平滑

用于计算目标 y ( rs 'd )的动作基于目标网络。在 DDPG,我们在图 8-4 的步骤 11 中计算$$ {a}{\prime}\left({s}{\prime}\right)={\upmu}_{\uptheta_{targ}}\left({s}^{\prime}\right) $$。然而,在 TD3 中,我们通过向动作添加噪声来执行目标策略平滑。对于确定性动作$$ {\upmu}_{\uptheta_{targ}}\left({s}^{\prime}\right) $$,我们添加了一个带有一些剪辑范围的平均零高斯噪声。然后使用 tanh 进一步剪切动作,并乘以max_action_range以确保动作值在可接受的动作值范围内。

$$ {a}{\prime}\left({s}{\prime}\right)=\mathrm{clip}\left({\upmu}_{\uptheta_{\mathrm{targ}}}\left({s}^{\prime}\right)+\mathrm{clip}\left(\upepsilon, -c,c\right),{a}_{Low},{a}_{High}\right),\kern1.25em \upepsilon \sim \mathcal{N}\left(0,\upsigma \right) $$

(8.8)

Q-Loss(评论家)

我们使用两个独立的 Q 函数,并从使用两个独立的 Q 函数中的最小值的公共目标学习它们。以数学方式表达目标看起来像这样:

$$ y\left(r,{s}^{\prime },d\right)=r+\upgamma \left(1-d\right)\underset{i=1,2}{\min }{Q}_{\upphi_{\mathrm{targ},i}}\left({s}^{\prime },{a}{\prime}\left({s}{\prime}\right)\right) $$

(8.9)

我们首先利用方程( 8.8 )来寻找有噪声的目标动作a(s)。这又用于计算目标 Q 值:第一和第二 Q 目标网络的 Q 值:$$ {Q}_{\phi_{\mathrm{targ},1}}\left({s}^{\prime },{a}{\prime}\left({s}{\prime}\right)\right) $$$$ {Q}_{\phi_{\mathrm{targ},2}}\left({s}^{\prime },{a}{\prime}\left({s}{\prime}\right)\right) $$

( 8.9 )中的共同目标用于查找两个 Q 网络的损耗,如下所示:

$$ {Q}_{Loss,1}=\frac{1}{B}{\sum}_{\left(s,a,r,{s}^{\prime },d\right)\in B}{\left({Q}_{\upphi_1}\left(s,a\right)-y\left(r,{s}^{\prime },d\right)\right)}² $$

还有这里:

$$ {Q}_{Loss,2}=\frac{1}{B}{\sum}_{\left(s,a,r,{s}^{\prime },d\right)\in B}{\left({Q}_{\upphi_2}\left(s,a\right)-y\left(r,{s}^{\prime },d\right)\right)}² $$

(8.10)

损失加在一起,然后独立地最小化,以训练$$ {Q}_{\phi_1} $$$$ {Q}_{\phi_2} $$网络(即两个在线评论家网络)。

$$ {Q}_{Loss}={\sum}_{i=1,2}{Q}_{Loss,i} $$

(8.11)

保单损失(参与者)

保单损失计算方法保持不变,与 DDPG 使用的方法相同。

$$ \mathrm{Polic}{\mathrm{y}}_{\mathrm{Loss}}=-\frac{1}{\mathrm{B}}{\sum}_{\mathrm{s}\in B}{\mathrm{Q}}_{\upphi_1}\left(\mathrm{s},{\upmu}_{\upphi}\left(\mathrm{s},{\upmu}_{\uptheta}\left(\mathrm{s}\right)\right)\right) $$

(8.12)

请注意,我们在等式中只使用了$$ {Q}_{\phi_1} $$。和 DDPG 一样,也请注意-ve标志。我们需要做梯度上升,但是 PyTorch 和 TensorFlow 做梯度下降。我们使用一个-ve符号将上升转换为下降。

延迟更新

我们以延迟的方式更新在线策略和代理网络权重,即在线 Q 网络$$ {Q}_{\phi_1} $$$$ {Q}_{\phi_2} $$的每两次更新更新一次。

伪代码和实现

至此,我们已经准备好给出完整的伪代码。请参见图 8-7 。

Twin Delayed DDPG

img/502835_1_En_8_Fig7_HTML.png

图 8-7

双延迟 DDPG 算法

代码实现

现在让我们浏览一下代码实现。像 DDPG 一样,我们将在摆锤和月球着陆器上运行该算法。除了我们前面谈到的三处修改,大部分代码与 DDPG 的代码相似。因此,我们将只介绍 PyTorch 和 TensorFlow 版本中这些变化的亮点。您可以在文件listing8_2_td3_pytorch.ipynb中找到 PyTorch 版本的完整代码,在listing8_2_td3_tensorflow.ipynb中找到 TensorFlow 版本的完整代码。

组合的模型-参与者评论实现

我们首先来看看代理网络。个人 Q-网络(评论家)MLPQFunction和政策-网络(演员)MLPActor与之前相同。然而,我们将演员和评论家结合在一起的代理人,即MLPActorCritic,看到了一个微小的变化。我们现在有两个 Q 网络与 TD3 的“孪生”部分一致。清单 8-14 包含了MLPActorCritic的代码,在 PyTorch 和 TensorFlow 中都有。

#################PyTorch#################
class MLPActorCritic(nn.Module):
    def __init__(self, observation_space, action_space):
        super().__init__()
        self.state_dim = observation_space.shape[0]
        self.act_dim = action_space.shape[0]
        self.act_limit = action_space.high[0]

        #build Q and policy functions
        self.q = MLPQFunction(self.state_dim, self.act_dim)
        self.policy = MLPActor(self.state_dim, self.act_dim, self.act_limit)

    def act(self, state):
        with torch.no_grad():
            return self.policy(state).numpy()

    def get_action(self, s, noise_scale):
        a = self.act(torch.as_tensor(s, dtype=torch.float32))
        a += noise_scale * np.random.randn(self.act_dim)
        return np.clip(a, -self.act_limit, self.act_limit)

################# TensorFlow #################
class MLPActorCritic(tf.keras.Model):
    def __init__(self, observation_space, action_space):
        super().__init__()
        self.state_dim = observation_space.shape[0]
        self.act_dim = action_space.shape[0]
        self.act_limit = action_space.high[0]

        #build Q and policy functions

        self.q1 = MLPQFunction(self.state_dim, self.act_dim)
        self.q2 = MLPQFunction(self.state_dim, self.act_dim)
        self.policy = MLPActor(self.state_dim, self.act_dim, self.act_limit)

    def act(self, state):
        return self.policy(state).numpy()

    def get_action(self, s, noise_scale):
        a = self.act(s.reshape(1,-1).astype("float32")).reshape(-1)
        a += noise_scale * np.random.randn(self.act_dim)
        return np.clip(a, -self.act_limit, self.act_limit)

Listing 8-14MPLActorCritic in PyTorch and TensorFlow

q 损耗实现

重放缓冲区保持不变。下一个变化是 Q 损耗的计算方式。我们按照等式( 8.8 )到( 8.11 )实现目标策略平滑和限幅双 Q 学习。清单 8-15 包含 PyTorch 中compute_q_loss的代码。这次我们没有明确列出 TensorFlow 的代码,可以在文件listing8_2_td3_tensorflow.ipynb中进一步探究。

def compute_q_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma, target_noise, noise_clip, act_limit, tape):

    # convert numpy array to proper data types
    states = states.astype('float32')
    actions = actions.astype('float32')
    rewards = rewards.astype('float32')
    next_states = next_states.astype('float32')
    done_flags = done_flags.astype('float32')

    # get q-values for all actions in current states
    # use agent network

    q1 = agent.q1(states, actions)
    q2 = agent.q2(states, actions)

    # Bellman backup for Q function
    with tape.stop_recording():

        action_target = target_network.policy(next_states)

        # Target policy smoothing
        epsilon = tf.random.normal(action_target.shape) * target_noise
        epsilon = tf.clip_by_value(epsilon, -noise_clip, noise_clip)
        action_target = action_target + epsilon
        action_target = tf.clip_by_value(action_target, -act_limit, act_limit)

        q1_target = target_network.q1(next_states, action_target)
        q2_target = target_network.q2(next_states, action_target)
        q_target = tf.minimum(q1_target, q2_target)
        target = rewards + gamma * (1 - done_flags) * q_target

    # MSE loss against Bellman backup
    loss_q1 = tf.reduce_mean((q1 - target)**2)
    loss_q2 = tf.reduce_mean((q2 - target)**2)
    loss_q = loss_q1 + loss_q2

    return loss_q

Listing 8-15Q-Loss in PyTorch

保单损失执行

策略损失计算保持不变,除了我们仅使用 q 网络之一,这实际上是第一个具有权重ϕ 1 的网络。

一步更新实施

one_step_update函数的实现也非常相似,除了我们需要针对作为q_params传递到函数中的 Q1 和 Q2 网络的组合网络权重来实现 q 损耗的梯度。此外,我们需要冻结和解冻q1q2的网络权重。

清单 8-16 包含 PyTorch 中one_step_update的实现,清单 8-17 包含 TensorFlow 中的代码。请特别注意权重是如何冻结和解冻的,梯度更新是如何计算的,以及权重是如何更新到目标网络的。这些操作在 PyTorch 代码和 TensorFlow 代码之间有一些细微差别。

def one_step_update(agent, target_network, q_params, q_optimizer, policy_optimizer,
                    states, actions, rewards, next_states, done_flags,
                    gamma, polyak, target_noise, noise_clip, act_limit,
                    policy_delay, timer):

    #one step gradient for q-values
    q_optimizer.zero_grad()
    loss_q = compute_q_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                    gamma, target_noise, noise_clip, act_limit)
    loss_q.backward()
    q_optimizer.step()

    # Update policy and target networks after policy_delay updates of Q-networks
    if timer % policy_delay == 0:
        #Freeze Q-network
        for params in q_params:
            params.requires_grad = False

        #one setep gradient for policy network
        policy_optimizer.zero_grad()
        loss_policy = compute_policy_loss(agent, states)
        loss_policy.backward()
        policy_optimizer.step()

        #UnFreeze Q-network

        for params in q_params:
            params.requires_grad = True

        # update target networks with polyak averaging
        with torch.no_grad():
            for params, params_target in zip(agent.parameters(), target_network.parameters()):
                params_target.data.mul_(polyak)
                params_target.data.add_((1-polyak)*params.data)

Listing 8-16One-Step Update in PyTorch

清单 8-17 显示了一步更新中的 TensorFlow 版本。

def one_step_update(agent, target_network, q_params, q_optimizer, policy_optimizer,
                    states, actions, rewards, next_states, done_flags,
                    gamma, polyak, target_noise, noise_clip, act_limit,
                    policy_delay, timer):

    #one step gradient for q-values
    with tf.GradientTape() as tape:
        loss_q = compute_q_loss(agent, target_network, states, actions, rewards, next_states, done_flags,
                        gamma, target_noise, noise_clip, act_limit, tape)

        gradients = tape.gradient(loss_q, q_params)
        q_optimizer.apply_gradients(zip(gradients, q_params))

    # Update policy and target networks after policy_delay updates of Q-networks
    if timer % policy_delay == 0:
        #Freeze Q-network
        agent.q1.trainable=False
        agent.q2.trainable=False

        #one setep gradient for policy network
        with tf.GradientTape() as tape:
            loss_policy = compute_policy_loss(agent, states, tape)
            gradients = tape.gradient(loss_policy, agent.policy.trainable_variables)
            policy_optimizer.apply_gradients(zip(gradients, agent.policy.trainable_variables))

        #UnFreeze Q-network

        agent.q1.trainable=True
        agent.q2.trainable=True

        # update target networks with polyak averaging
        updated_model_weights = []
        for weights, weights_target in zip(agent.get_weights(), target_network.get_weights()):
            new_weights = polyak*weights_target+(1-polyak)*weights
            updated_model_weights.append(new_weights)
        target_network.set_weights(updated_model_weights)

Listing 8-17One-Step Update in TensorFlow

TD3 主回路

下一个变化是更新的频率。与 DDPG 不同,在 TD3 中,我们每更新两次 Q 网络就更新一次在线策略和目标权重。这是对 DDPG 规范的一个小改动,因此我们不在这里列出。

我们现在首先为钟摆环境运行 TD3,然后为月球着陆器健身房环境运行 TD3。我们可以看到,在五集之后,钟摆在直立状态下变得很平衡。月球着陆器的训练质量,就像 DDPG 一样,有点平庸。月球着陆器的环境是复杂的,因此我们需要运行更多次数的训练,比如 50 或 100 次。

我们可能也看不出 DDPG 和 TD3 在学习质量上有什么明显的区别。然而,如果我们可以在更复杂的环境中运行它,我们将会看到 TD3 相对于 DDPG 更高的性能。感兴趣的读者可以参考 TD3 的原始论文,查看 TD3 的作者对其他算法所做的基准研究。

很快我们就会看到本章的最后一个算法,一个叫做软演员评论家的算法。在此之前,我们将绕一小段路来理解 SAC 使用的一种叫做的重新参数化技巧

重新参数化技巧

重新参数化技巧是变分自编码器(VAEs)中使用的变量方法的一个变化。在那里,需要通过随机的节点传播梯度。重新参数化也被用来降低梯度估计的方差。第二个原因是我们将在这里探讨的。这篇深度文章是在戈克尔·埃尔多安 5 的一篇博客文章之后进行的,其中有额外的分析推导和解释。

假设我们有一个随机变量 x ,它遵循正态分布。让分布由 θ 参数化如下:

$$ x\sim {p}_{\uptheta}(x)=N\left(\uptheta, 1\right)=\frac{1}{\sqrt{2\uppi}}{e}^{-\frac{1}{2}{\left(x-\uptheta \right)}²} $$

(8.13)

我们从中抽取样本,然后使用这些样本找出下列各项中的最小值:

$$ J\left(\uptheta \right)={E}_{x\sim {p}_{\uptheta}(x)}\left[{x}²\right] $$

我们使用梯度下降法。我们的重点是找到两种不同的方法来确定导数/梯度∇θj(θ)的估计值。

得分/强化方式

首先,我们将遵循 log 技巧,这是我们在讨论使用策略梯度进行强化时所做的。我们看到它有很高的方差,这就是我们希望为前面显示的简单示例分布所展示的。

我们对 J ( θ )相对于 θ 求导。

$$ {\nabla}_{\uptheta}J\left(\uptheta \right) $$

$$ ={\nabla}_{\uptheta}{E}_{x\sim {p}_{\uptheta}(x)}\left[{x}²\right] $$

$$ ={\nabla}_{\uptheta}\int {p}_{\uptheta}(x){x}² dx $$

$$ =\int {\nabla}_{\uptheta}{p}_{\uptheta}(x){x}² dx $$

$$ =\int \frac{p_{\uptheta}(x)}{p_{\uptheta}(x)}{\nabla}_{\uptheta}{p}_{\uptheta}(x){x}² dx $$

$$ =\int {p}_{\uptheta}(x)\frac{\nabla_{\uptheta}{p}_{\uptheta}(x)}{p_{\uptheta}(x)}{x}² dx $$

$$ =\int {p}_{\uptheta}(x){\nabla}_{\uptheta}\mathit{\log}\ {p}_{\uptheta}(x){x}² dx $$

$$ ={E}_{x\sim {p}_{\uptheta}(x)}\left[{\nabla}_{\uptheta}\mathit{\log}\ {p}_{\uptheta}(x){x}²\right] $$

接下来,我们使用蒙特卡罗来使用样本形成∇θj(θ)的估计。

$$ \hat{\nabla_{\uptheta}J\left(\uptheta \right)}=\frac{1}{N}{\sum}_{i=1}^N{\nabla}_{\uptheta}\mathit{\log}\ {p}_{\uptheta}\left({x}_i\right){x}_i² $$

将前面的表达式代入pθ(x)并取一个后跟梯度 wrt θ的对数,我们得到如下结果:

$$ \hat{\nabla_{\uptheta}J\left(\uptheta \right)}=\frac{1}{N}{\sum}_{i=1}^N\left({x}_i-\uptheta \right){x}_i² $$

(8.14)

重新参数化技巧和路径导数

第二种方法是重新参数化技巧。我们将把 x 重新定义为一个常数和一个没有参数 θ 的正态分布的组合。让 x 定义如下:

$$ x=\uptheta +\upepsilon \kern1em \mathrm{where}\kern0.75em \upepsilon \sim N\left(0,1\right) $$

我们可以看到,之前的重新参数化使 x 的分布保持不变。

$$ {p}_{\uptheta}(x)=N\left(\uptheta, 1\right) $$

我们来计算一下$$ {\nabla}_{\uptheta}J\left(\uptheta \right)={\nabla}_{\uptheta}{E}_{x\sim {p}_{\uptheta}(x)}\left[{x}²\right] $$

$$ {\nabla}_{\uptheta}J\left(\uptheta \right) $$

$$ ={\nabla}_{\uptheta}{E}_{x\sim {p}_{\uptheta}(x)}\left[{x}²\right] $$

$$ ={\nabla}_{\uptheta}{E}_{\upepsilon \sim N\left(0,1\right)}\left[{\left(\uptheta +\upepsilon \right)}²\right] $$

由于期望值不依赖于θ,我们可以将梯度移入,而不会遇到前面方法中所示的“ log ”问题(即在导数内找到 log)。

$$ ={\nabla}_{\uptheta}\int p\left(\upepsilon \right){\left(\uptheta +\upepsilon \right)}²d\upepsilon $$

$$ =\int p\left(\upepsilon \right){\nabla}_{\uptheta}{\left(\uptheta +\upepsilon \right)}²d\upepsilon $$

$$ =\int p\left(\upepsilon \right)2\left(\uptheta +\upepsilon \right)d\upepsilon $$

$$ ={E}_{\upepsilon \sim N\left(0,1\right)}\left[2\left(\uptheta +\upepsilon \right)\right] $$

接下来,我们将期望值转换为 MC 估计值,得到以下结果:

$$ \hat{\nabla_{\uptheta}J\left(\uptheta \right)}=\frac{1}{N}{\sum}_{i=1}^N2\left(\uptheta +{\upepsilon}_i\right) $$

(8.15)

实验

我们使用方程( 8.14 )和( 8.15 )通过两种方法计算估计值的均值和方差。我们使用不同的 N 值来计算( 8.14 )和( 8.15 ),并且我们对 N 的每个值重复实验 10,000 次,以计算在( 8.14 )和( 8.15 )中给出的梯度估计的平均值和方差。我们的实验将表明这两个方程的平均值是相同的。换句话说,他们估计的是同一个值,但是( 8.14 )中估计的方差比( 8.15 )中估计的方差高了几乎一个数量级。在我们的例子中,它高出了 21.75 倍。

让我们冻结这个:

$$ \uptheta =2 $$

  • 我们为xn(θ,1)生成样本,并在等式( 8.14 )中使用这些样本来计算∇θj(θ)的加固估计。

  • 我们为ϵ∽n(0,1)生成样本,并在等式( 8.15 )中使用这些样本来计算∇θj(θ)的重新参数化估计。

实验的细节和代码请看listing8_3_reparameterization.ipynb。在笔记本中,我们还计算了解析解以得出结果,如下所示。

对于使用( 8.14 )的加固梯度,梯度如下:

$$ mean=4 $$

$$ \mathrm{variance}=\frac{87}{N} $$

对于使用( 8.15 )重新参数化的梯度,梯度如下:

$$ mean=4 $$

$$ \mathrm{variance}=\frac{4}{N} $$

我们可以看到,在两种方法下,梯度估计具有相同的均值。然而,重新参数化方法的方差要小得多,小了一个数量级。这正是我们的代码运行所确认的。

综上所述,假设我们有一个以状态 s 为输入的策略网络,网络由 θ 参数化。策略网络产生策略的均值和方差,即一个正态分布的随机策略,其均值和方差是网络的输出,如图 8-8 所示。

img/502835_1_En_8_Fig8_HTML.jpg

图 8-8

随机政策网络

我们将动作 a 定义如下:

$$ a\sim N\left({\upmu}_{\uptheta}(s),{\upsigma}_{\uptheta}²(s)\right) $$

(8.16)

我们重新参数化动作 a 以分解出确定性和随机部分,使得随机部分不依赖于网络参数 θ

$$ a={\upmu}_{\uptheta}(s)+{\upsigma}_{\uptheta}²(s).\varepsilon $$

$$ \mathrm{where}:\kern0.75em \upepsilon \sim N\left(0,1\right) $$

(8.17)

与使用非参数化方法相比,重新参数化允许我们计算具有较低方差的策略梯度。此外,重新参数化允许我们通过将随机部分分离为非参数化部分,以另一种方式将梯度流回网络。我们将在软演员-评论家算法中使用这种方法。

熵解释

在我们开始深入研究 SAC 的细节之前,还有一件事:让我们重温一下熵。在前一章中,我们讨论了熵作为正则项作为增强代码遍历的一部分。我们会做一些类似的事情。我们来理解一下熵是什么。

假设我们有一个随机变量 x 遵循某种分布 P ( x )。 x 的熵定义如下:

$$ H(P)=\underset{x\sim P}{E}\left[- logP(x)\right] $$

(8.18)

假设我们有一个硬币,硬币的 P ( H ) = ρP(T)= 1ρ。我们针对 ρ ε (0,1)的不同值计算熵 H

$$ H(x)=-\left[\rho\ log\rho +\left(1-\rho \right)\ \mathit{\log}\left(1-\rho \right)\right] $$

我们可以绘制出 H ( x )对 ρ 的曲线,如图 8-9 所示。

img/502835_1_En_8_Fig9_HTML.jpg

图 8-9

作为 p 的函数的伯努利分布的熵,它是在试验中得到 1 的概率

我们可以看到,熵 Hρ = 0.5 的最大值,也就是说,当我们在得到 1 或 0 之间具有最大不确定性时。换句话说,通过最大化熵,我们可以确保随机行动策略具有广泛的分布,并且不会过早地崩溃到一个尖峰。尖锐的峰会减少探索。

软演员评论家

软演员评论家大约与 TD3 同时出现。像 DDPG 和 TD3 一样,SAC 也使用了一个行动者-批评家结构,通过政策外学习进行连续控制。然而,与 DDPG 和 TD3 不同,SAC 学习随机策略。因此,SAC 在确定性策略算法(如 DDPG 和 TD3)与随机策略优化之间架起了一座桥梁。该算法是在 2018 年发表的一篇题为“软行动者-批评家:随机行动者的非策略最大熵深度强化学习”的论文中引入的。 6

它使用像 TD3 一样的限幅双 Q 技巧,并且由于它的学习随机策略,它间接地受益于目标策略平滑,而不明显需要向目标策略添加噪声。

SAC 的核心特性是使用熵作为最大化的一部分。引用论文作者的话:

“在这个框架中,行动者的目标是同时最大化预期收益和熵;也就是说,在尽可能随机行动的同时成功完成任务。”

SAC 对 TD3

这是两者的相似之处:

  • 两者都使用均方贝尔曼误差(MSBE)最小化来达到共同目标。

  • 使用目标 Q 网络来计算公共目标,该目标 Q 网络是使用聚丙烯平均获得的。

  • 两者都使用限幅双 Q,它至少由两个 Q 值组成,以避免高估。

这就是不同之处:

  • SAC 使用熵正则化,这是 TD3 中没有的。

  • TD3 目标策略用于计算下一个状态的动作,而在 SAC 中,我们使用当前策略来获得下一个状态的动作。

  • 在 TD3 中,目标策略通过向动作添加随机噪声来使用平滑。然而,在 SAC 中,学习到的策略是随机的,它提供了平滑效果,而没有任何明显的噪声添加。

熵正则化 q 损失

熵测量分布的随机性。熵越高,分布越平坦。一个尖峰政策的所有概率都集中在那个尖峰附近,因此它将具有低熵。通过熵正则化,策略被训练为最大化期望回报和熵之间的权衡,其中 α 控制该权衡。该策略被训练为最大化预期回报和熵之间的权衡,熵是策略中随机性的度量。

$$ {\uppi}^{\ast }=\arg \underset{\uppi}{\max}\underset{\uptau \sim \uppi}{E}\left[{\sum}_{t=0}^{\infty }{\upgamma}^t\left(R\left({s}_t,{a}_t,{s}_{t+1}\right)+\upalpha H\left(\uppi \left(\cdotp |{s}_t\right)\right)\right)\right] $$

(8.19)

在此设置中, V π 被更改为包含每个时间步长的熵。

$$ {V}^{\uppi}(s)=\underset{\uptau \sim \uppi}{E}\left[\left.{\sum}_{t=0}^{\infty }{\upgamma}^t\left(R\left({s}_t,{a}_t,{s}_{t+1}\right)+\upalpha H\left(\uppi \left(\cdotp |{s}_t\right)\right)\right)\right|{s}_0=s\right] $$

(8.20)

此外, Q π 被更改为包括除第一个时间步之外的每个时间步的熵奖励。

$$ {Q}^{\uppi}\left(s,a\right)=\underset{\uptau \sim \uppi}{E}\left[\left.{\sum}_{t=0}^{\infty }{\upgamma}^tR\left({s}_t,{a}_t,{s}_{t+1}\right)+\upalpha {\sum}_{t=1}^{\infty }{\upgamma}^tH\left(\uppi \left(\cdotp |{s}_t\right)\right)\right|{s}_0=s,{a}_0=a\right] $$

(8.21)

有了这些定义, V πQ π 通过以下方式连接:

$$ {V}^{\uppi}(s)=\underset{a\sim \uppi}{E}\left[{Q}^{\uppi}\left(s,a\right)+\upalpha H\left(\uppi \left(\cdotp |s\right)\right)\right] $$

(8.22)

关于Qπ的贝尔曼方程如下:

$$ {Q}{\uppi}\left(s,a\right)=\underset{s{\prime}\sim P,{a}^{\prime}\sim \uppi}{E}\left[R\left(s,a,{s}^{\prime}\right)+\upgamma \left({Q}{\uppi}\left({s}{\prime },{a}^{\prime}\right)+\upalpha H\left(\uppi \left(\cdotp |{s}^{\prime}\right)\right)\right)\right] $$

$$ =\underset{s^{\prime}\sim P}{E}\left[R\left(s,a,{s}^{\prime}\right)+\upgamma {V}{\uppi}\left({s}{\prime}\right)\right] $$

(8.23)

右边是我们转换成样本估计的期望值。

$$ {Q}^{\uppi}\left(s,a\right)\approx r+\upgamma \left({Q}{\uppi}\left({s}{\prime },\overset{\sim }{a^{\prime }}\right)-\upalpha \mathrm{log}\uppi \left(\overset{\sim }{a^{\prime }}|{s}^{\prime}\right)\right),\kern1.25em \overset{\sim }{a^{\prime }}\sim \uppi \left(\cdotp |{s}^{\prime}\right) $$

(8.24)

以上,( sars)来自重放缓冲区,$$ \overset{\sim }{a^{\prime }} $$来自在线/代理策略采样。在 SAC 中,我们根本不使用目标网络策略。

像 TD3 一样,SAC 使用限幅双 Q 并最小化均方贝尔曼误差(MSBE)。综上所述,SAC 中 Q 网络的损耗函数如下:

$$ L\left({\upphi}_i,D\right)=\underset{\left(s,a,r,{s}^{\prime },d\right)\sim D}{E}\left[{\left({Q}_{\upphi_i}\left(s,a\right)-y\left(r,{s}^{\prime },d\right)\right)}²\right],\kern1.25em i=1,2 $$

(8.25)

其中目标由下式给出:

$$ y\left(r,{s}^{\prime },d\right)=r+\upgamma \left(1-d\right)\left(\underset{i=1,2}{\min }{Q}_{\upphi_{\mathrm{targ},i}}\left({s}^{\prime },\overset{\sim }{a^{\prime }}\right)-\upalpha \mathrm{log}{\uppi}_{\uptheta}\left(\overset{\sim }{a^{\prime }}|{s}^{\prime}\right)\right),\kern1.25em \overset{\sim }{a^{\prime }}\sim {\uppi}_{\uptheta}\left(\cdotp |{s}^{\prime}\right) $$

(8.26)

我们将期望值转化为样本平均值。

$$ L\left({\upphi}_i,\mathcal{D}\right)=\frac{1}{\left|B\right|}{\sum}_{\left(s,a,r,{s}^{\prime },d\right)\in B}{\left({Q}_{\upphi_i}\left(s,a\right)-y\left(r,{s}^{\prime },d\right)\right)}²,\kern1.25em \mathrm{for}\ i=1,2 $$

(8.27)

我们将最小化的最终 Q 损耗如下:

$$ {Q}_{Loss}=L\left({\upphi}_1,\mathcal{D}\right)+L\left({\upphi}_1,\mathcal{D}\right) $$

(8.28)

重新参数化策略的策略损失

政策要选择最大化预期未来收益和未来熵的行动,即V【π】(s)。

$$ {V}^{\uppi}(s)=\underset{a\sim \uppi}{E}\left[{Q}^{\uppi}\left(s,a\right)+\upalpha H\left(\uppi \left(\cdotp |s\right)\right)\right] $$

我们将其改写如下:

$$ {V}^{\uppi}(s)=\underset{a\sim \uppi}{E}\left[{Q}^{\uppi}\left(s,a\right)-\upalpha \mathrm{log}\uppi \left(a|s\right)\right] $$

(8.29)

该论文的作者使用了重新参数化和压缩高斯策略。

$$ \overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)=\tanh \left({\upmu}_{\uptheta}(s)+{\upsigma}_{\uptheta}(s)\odot \upxi \right),\kern1.25em \upxi \sim \mathcal{N}\left(0,I\right) $$

(8.30)

结合前面的两个等式( 8.29 )和( 8.30 ),并且还注意到我们的策略网络由策略网络权重 θ 参数化,我们得到以下等式:

$$ \underset{a\sim {\uppi}_{\uptheta}}{E}\left[{Q}^{\uppi_{\uptheta}}\left(s,a\right)-\upalpha \mathrm{log}{\uppi}_{\uptheta}\left(a|s\right)\right]=\underset{\upxi \sim \mathcal{N}}{E}\left[{Q}^{\uppi_{\uptheta}}\left(s,\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)\right)-\upalpha \mathrm{log}{\uppi}_{\uptheta}\left(\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)|s\right)\right] $$

(8.31)

接下来,我们用函数近似器代替 Q,取两个 Q 函数中的最小值。

$$ {Q}^{\uppi_{\uptheta}}\left(s,\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)\right)=\underset{i=1,2}{\min }{Q}_{\upphi_i}\left(s,\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)\right) $$

(8.32)

政策目标相应地转变为:

$$ \underset{\uptheta}{\max}\underset{\underset{\upxi \sim \mathcal{N}}{s\sim \mathcal{D}}}{E}\left[\underset{i=1,2}{\min }{Q}_{\upphi_i}\left(s,\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)\right)-\upalpha \mathrm{log}{\uppi}_{\uptheta}\left(\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)|s\right)\right] $$

(8.33)

像以前一样,我们在 PyTorch/TensorFlow 中使用最小化器。因此,我们引入了一个-ve符号来将最大化转换为损失最小化。

$$ Polic{y}_{Loss}=-\underset{\underset{\upxi \sim \mathcal{N}}{s\sim \mathcal{D}}}{E}\left[\underset{i=1,2}{\min }{Q}_{\upphi_i}\left(s,\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)\right)-\upalpha \mathrm{log}{\uppi}_{\uptheta}\left(\overset{\sim }{a_{\uptheta}}\left(s,\upxi \right)|s\right)\right] $$

(8.34)

我们还使用样本将期望值转换为估计值,得到以下结果:

$$ Polic{y}_{Loss}=-\frac{1}{\left|B\right|}{\sum}_{s\in B}\left(\underset{i=1,2}{\min }{Q}_{\upphi_i}\left(s,\overset{\sim }{a_{\uptheta}}(s)\right)-\upalpha \mathrm{log}{\uppi}_{\uptheta}\left(\left.\overset{\sim }{a_{\uptheta}}(s)\right|s\right)\right) $$

(8.35)

伪代码和实现

至此,我们已经准备好给出完整的伪代码。请参见图 8-10 。

Soft Actor Critic

img/502835_1_En_8_Fig10_HTML.png

图 8-10

软演员评论家算法

代码实现

有了所有的数学推导和伪代码,是时候深入 PyTorch 和 TensorFlow 的实现了。使用 PyTorch 的实现在文件listing_8_4_sac_pytorch.ipynb中。像以前一样,我们使用 SAC 在钟摆和月球着陆器环境中训练代理。TensorFlow 2.0 实现在文件listing_8_4_sac_tensorflow.ipynb中。

策略网络-参与者实施

我们先看演员网。这一次,actor 实现将状态作为输入,和以前一样。然而,输出有两个部分。

  • 要么是压扁(即通过 tanh 传递动作值)的确定性动作 a ,即μ θ ( s ),要么是来自分布$$ N\left({\upmu}_{\uptheta}(s),{\upsigma}_{\uptheta}²(s)\right) $$的样本动作 a 。采样使用了重新参数化技巧,PyTorch 为您实现了这个技巧,如distribution.rsample()。你可以在 https://pytorch.org/docs/stable/distributions.html 的“路径导数”主题下阅读

  • The second output is the log probability that we will need for calculating entropy inside the Q-loss as per equation (8.26). Since we are using squashed/tanh transformation, the log probability needs to apply a change of variables for random distribution using the following:

    $$ {f}_Y(y)={f}_X\left({g}{-1}(y)\right)\left|\frac{d}{dy}\left({g}{-1}(y)\right)\right| $$

代码使用一些技巧来计算数值稳定的版本。你可以在原文中找到更多的细节。

清单 8-18 列出了前面讨论过的SquashedGaussianMLPActor的代码。神经网络仍然和以前一样:两个大小为 256 单位的隐藏层,具有 ReLU 激活。

LOG_STD_MAX = 2
LOG_STD_MIN = -20

class SquashedGaussianMLPActor(nn.Module):
    def __init__(self, state_dim, act_dim, act_limit):
        super().__init__()
        self.act_limit = act_limit
        self.fc1 = nn.Linear(state_dim, 256)
        self.fc2 = nn.Linear(256, 256)
        self.mu_layer = nn.Linear(256, act_dim)
        self.log_std_layer = nn.Linear(256, act_dim)
        self.act_limit = act_limit

    def forward(self, s, deterministic=False, with_logprob=True):
        x = self.fc1(s)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        mu = self.mu_layer(x)
        log_std = self.log_std_layer(x)
        log_std = torch.clamp(log_std, LOG_STD_MIN, LOG_STD_MAX)
        std = torch.exp(log_std)

        # Pre-squash distribution and sample
        pi_distribution = Normal(mu, std)
        if deterministic:
            # Only used for evaluating policy at test time.
            pi_action = mu
        else:
            pi_action = pi_distribution.rsample()

        if with_logprob

:
            # Compute logprob from Gaussian, and then apply correction for Tanh squashing.
            # NOTE: The correction formula is a little bit magic. To get an understanding
            # of where it comes from, check out the original SAC paper (arXiv 1801.01290)
            # and look in appendix C. This is a more numerically-stable equivalent to Eq 21.
            # Try deriving it yourself as a (very difficult) exercise. :)
            logp_pi = pi_distribution.log_prob(pi_action).sum(axis=-1)
            logp_pi -= (2*(np.log(2) - pi_action - F.softplus(-2*pi_action))).sum(axis=1)
        else:
            logp_pi = None

        pi_action = torch.tanh(pi_action)
        pi_action = self.act_limit * pi_action

        return pi_action, logp_pi

Listing 8-18SquashedGaussianMLPActor in PyTorch

清单 8-19 显示了 TensorFlow 版本。对于重新参数化,我们使用下面的代码行对其进行示例说明:

pi = mu + tf.random.normal(tf.shape(mu)) * std

核心 TensorFlow 包没有计算熵的对数似然的函数。我们实现了自己的小函数gaussian_likelihood来做这件事。所有这些的一个替代方案是使用 TensorFlow 分布包:tfp.distributions.Distribution。actor 的其余实现类似于我们在 PyTorch 中看到的。

LOG_STD_MAX = 2
LOG_STD_MIN = -20

EPS = 1e-8

def gaussian_likelihood(x, mu, log_std):
    pre_sum = -0.5 * (((x-mu)/(tf.exp(log_std)+EPS))**2 + 2*log_std + np.log(2*np.pi))
    return tf.reduce_sum(pre_sum, axis=1)

def apply_squashing_func(mu, pi, logp_pi):
    # Adjustment to log prob
    # NOTE: This formula is a little bit magic. To get an understanding of where it
    # comes from, check out the original SAC paper (arXiv 1801.01290) and look in
    # appendix C. This is a more numerically-stable equivalent to Eq 21.
    # Try deriving it yourself as a (very difficult) exercise. :)
    logp_pi -= tf.reduce_sum(2*(np.log(2) - pi - tf.nn.softplus(-2*pi)), axis=1)

    # Squash those unbounded actions!
    mu = tf.tanh(mu)
    pi = tf.tanh(pi)
    return mu, pi, logp_pi

class SquashedGaussianMLPActor(tf.keras.Model):
    def __init__(self, state_dim, act_dim, act_limit):
        super().__init__()
        self.act_limit = act_limit
        self.fc1 = layers.Dense(256, activation="relu")
        self.fc2 = layers.Dense(256, activation="relu")
        self.mu_layer = layers.Dense(act_dim)
        self.log_std_layer = layers.Dense(act_dim)
        self.act_limit = act_limit

    def call(self, s):
        x = self.fc1(s)
        x = self.fc2(x)
        mu = self.mu_layer(x)
        log_std = self.log_std_layer(x)
        log_std = tf.clip_by_value(log_std, LOG_STD_MIN, LOG_STD_MAX)
        std = tf.exp(log_std)

        pi = mu + tf.random.normal(tf.shape(mu)) * std
        logp_pi = gaussian_likelihood(pi, mu, log_std)
        mu, pi, logp_pi = apply_squashing_func(mu, pi, logp_pi)

        mu *= self.act_limit
        pi *= self.act_limit

        return mu, pi, logp_pi

Listing 8-19SquashedGaussianMLPActor in TensorFlow

q-网络、组合模型和经验重放

Q-function 网络MLPQFunction,它将演员和评论家组合成类MLPActorCritic中的一个代理,和ReplayBuffer的实现是相同的,或者至少非常相似。因此,我们不在这里列出代码的这些部分。

q 损失和保单损失执行

接下来我们看看compute_q_losscompute_policy_loss。这是图 8-10 中伪代码的步骤 11 到 13 的直接实现。如果你将这些步骤与图 8-7 中 TD3 的步骤 11 到 14 进行比较,你会发现很多相似之处,除了动作是从在线网络中采样的,SAC 在两个损失中都有一个额外的熵项。这些变化很小,因此我们没有在这里的文本中明确列出代码。

单步更新和 SAC 主循环

同样,one_step_update的代码和整个训练算法遵循与之前类似的模式。

一旦我们运行并训练了代理,我们会看到类似于 DDPG 和 TD3 的结果。如前所述,我们使用的是简单的环境,因此本章中的三种连续控制算法(DDPG、TD3 和 SAC)都表现良好。请参考本章中引用的各种论文,深入研究这些不同方法的官方性能比较。

这就把我们带到了在演员-评论家环境中持续控制这一章的结尾。到目前为止,我们已经看到了基于模型的策略迭代方法、基于深度学习的 Q 学习(DPN)方法、针对离散动作的策略梯度以及针对连续控制的策略梯度。这涵盖了大多数强化学习的流行方法。在结束我们的旅程之前,我们还有一个主要的主题要考虑:在无模型的世界中使用模型学习,以及在我们知道模型但它太复杂或太庞大而无法彻底探索的环境中进行有效的模型探索。

摘要

在这一章中,我们研究了持续控制的行动者-批评者方法,其中我们将非策略 Q 学习类型与策略梯度相结合,以导出非策略持续控制行动者-批评者方法。

我们首先看了 2016 年推出的深度确定性政策梯度。这是我们第一个连续控制算法。DDPG 是一种具有确定性连续控制策略的行动者-批评家方法。

接下来,我们看了双延迟 DDPG,它于 2018 年问世,解决了 DDPG 存在的一些稳定性和低效率挑战。像 DDPG 一样,它也通过演员-评论家架构学习了非政策环境中的确定性政策。

最后,我们看了软行动者批评家,它把 DDPG 式的学习和使用熵的随机政策优化联系起来。SAC 是一种使用行动者-批评家设置的非策略随机策略优化。我们还看到了重新参数化技巧,以获得梯度的较低方差估计。

九、综合规划和学习

本书中反复出现的一个主题是,分别研究不同的主题,然后一起学习。我们首先在第三章看了基于模型的算法。使用这种设置,我们知道代理在其中操作的世界的模型动态。代理使用模型动力学知识和贝尔曼方程首先执行评估/预测任务,以学习状态或状态-动作值。然后,它通过改进策略来获得最佳行为,这被称为策略改进/策略迭代。一旦我们知道了模型,我们就可以提前计划执行评估/改进步骤。这被称为计划阶段

在第四章中,我们从探索无模型机制开始。无模型意味着我们不知道模型。我们通过与模型互动来学习模型。我们称之为学习。在无模型设置中,我们分别研究了蒙特卡罗(MC)和时间差分(TD)方法。我们比较了使用这两种方法的优缺点。接下来,我们使用 n 步资格追踪将 MC 和 TD 合并成一个统一的方法。

在第六章中,我们使用函数近似和深度学习将第四章的方法扩展到非表格连续空间大规模问题。第 3 至 6 章中涵盖的所有方法被称为基于值的方法,其中状态或状态动作值被学习,然后使用这些学习的状态/状态动作值进行策略改进。

第七章关注的是另一种方法,即基于政策的方法。这种方法意味着直接学习最优策略,而不经过学习状态/动作值的中间步骤。最初,它给人一种基于价值和基于政策的方法截然不同的印象。然而,就像在资格追踪下将 MC 和 TD 结合成一个一样,在第八章中,我们结合了基于价值的 Q 学习和政策学习,以在行动者-批评家(AC)方法下获得两个世界的最佳效果,如 DDPG、TD3 和 SAC。

在这一章中,我们将结合基于模型的方法和无模型的方法,通过利用两者的优点,使我们的算法更强大,样本效率更高。这是本章的重点。

我们还将更详细地研究探索-开发的困境,而不仅仅是盲目地遵循贪婪的政策。我们将着眼于更简单的设置,以更好地理解探索-开发的困境。接下来将深入探讨一种“引导式前瞻树搜索方法”,称为蒙特卡罗树搜索 (MCTS)。

基于模型的强化学习

什么是无模型 RL?在无模型 RL 中,我们不知道模型。相反,我们通过让代理与环境交互来学习价值函数和/或策略,即通过经验学习。这就是我们在本书第 4 到 8 章中看到的。相比之下,我们现在谈论的基于模型的 RL 是通过让代理与环境交互来学习模型的 RL,即,再次通过经验学习。学习到的模型被用来计划价值函数和政策——类似于我们在第三章中看到的内容。在第三章中,我们假设了模型的先验知识,我们也假设它是完美的。然而,在基于模型的 RL 中,我们学习模型,然后使用该知识。但是,知识可能是不完整的;换句话说,我们可能不知道确切的转移概率或奖励的完全分布。当代理人与环境互动时,我们学习我们所经历的系统动力学的一部分。我们的知识是基于有限的相互作用,因此没有涵盖所有可能性的详尽的相互作用,我们对模型的知识是不完整的。

我们这里的模型是什么意思?这意味着要有转移概率的估计P(sta t )和报酬R(sta t )。主体与世界/环境相互作用,形成对世界的印象。形象地说,可如图 9-1 所示。

img/502835_1_En_9_Fig1_HTML.jpg

图 9-1

基于模型的强化学习。代理根据其与环境的交互来学习模型,然后将该模型用于规划

优点是学习变得高效。与无模型方法相比,我们可以使用学习到的模型来有效地进行计划。此外,模型交互就是在状态 s t 时采取一个动作 a t ,并观察下一个状态st+1的结果和奖励rt+1。我们可以使用监督学习机器从与现实世界的交互中学习,其中给定( s ta t )是样本输入,st+1和/或rt+1

$$ {s}_1,{a}_1\to {r}_2,{s}_2 $$

$$ {s}_2,{a}_2\to {r}_3,{s}_3 $$
: :

$$ {s}_{T-1},{a}_{T-1}\to {r}_T,{s}_T $$

像任何其他常规的监督机器学习设置一样,我们可以从代理从其与环境的交互中收集的先前样本中学习。

  • 学习 s tatrt+1是一个损失类似均方损失的回归问题。

  • 学习跃迁动力学st,atst+1是一个密度估计问题。我们可以学习离散的分类分布、高斯分布或高斯模型参数的混合。该损失可能是 KL-divergence 损失。

如果我们将学习扩展到贝叶斯学习,那么我们也可以对模型不确定性进行推理,即,我们对所学习的模型转换和奖励函数有多确定或不确定。贝叶斯学习方法产生的不仅仅是点估计,而是估计的整体分布,这使我们有能力对估计的强度进行推理。估计值的窄概率分布意味着大部分概率以峰值为中心;也就是说,我们对估计有很强的信心。相反,估计值的广泛分布反映了估计值的较高不确定性。

然而,生活中没有免费的东西。首先学习模型的不完美表示,然后使用该不完美表示来计划或寻找最优策略的这种两步方法引入了两个误差源。首先,我们对模型动力学的了解可能是不准确的。第二,从不完善的模型中学习一个价值函数可能有其自身的不准确性。

学习到的模型可以表示为“查表”(类似于我们在第 2 和 3 章中看到的)、线性期望模型或线性高斯模型。甚至可以有更复杂的模型表示,如高斯过程模型或深度信任网络模型。这取决于问题的性质、数据收集的难易程度等。决定正确的表示需要领域专业知识。让我们看一个学习表查找模型的简单例子。

首先,我们看一下我们将用来学习奖励和转换动态的表达式。这是一个简单的平均方法,我们将在这里使用。为了估计转移概率,我们取转移次数的平均值( s tat)→st+1)并除以代理人在( s t中看到自己的总次数

img/502835_1_En_9_Figa_HTML.gif 9.1

这里,img/502835_1_En_9_Figb_HTML.gif是一个指标函数。当括号内的条件为真时,指示符函数取值 1,否则取值 0。总之,该指示器功能是计数( sa )导致转换到下一个状态的次数,St+1=S

同样,我们也可以将奖励学习定义为平均值。

img/502835_1_En_9_Figc_HTML.gif (9.2)

让我们看一个简单的环境,其中只有两种状态,我们观察一组(八个)代理与环境的交互。我们假设不贴现,即 γ = 1。

假设我们看到( A ,0, B ,0)。这意味着代理从状态 A 开始,观察到奖励为零,并看到自己处于状态 B,然后奖励再次为 0,最后转换到终止状态。

假设代理收集的八个转换/交互如下:

a,0,B,0

b,1

b,1

b,1

b,1

b,1

b,1

b,0

我们应用方程( 9.1 )和( 9.2 )构建模型如下:

  • 我们只看到一个从 A 到 B 的转变,我们得到的回报是 0。于是,我们得出结论: P ( B | A )为 1, R ( 状态 = A ) = 0。请注意,在前面的例子中,为了简单起见,我们没有明确显示动作。你可以认为这是在每个州采取随机行动,也可以认为这是马尔可夫奖励过程(MRP ),而不是全面的 MDP。

  • 我们看到了从 B 开始的八个转变,它们都通向终态。在两种情况下,奖励是零,而在其余六种情况下,奖励是 1。这可以建模为说 P ( 终端 | B ) = 1。奖励 R ( B ) = 1 有 0.75 (6/8)的概率 R ( B ) = 0 有 0.25 的概率。

图 9-2 显示了我们从这八个交互中学习到的模型。

img/502835_1_En_9_Fig2_HTML.jpg

图 9-2

从环境交互中学习的表查找模型

这是许多参数化模型表示中的一个,在这里我们明确地学习了模型动态,然后丢弃了与真实世界的示例交互。然而,在 DQN 还有另外一种方法:非参数化模型。在非参数化模型中,我们将交互存储在缓冲区中,然后从缓冲区中进行采样。前面八个交互的一个例子是在一个列表中存储(state, reward, next_state)的元组(记住在这个例子中我们没有动作,因为它是一个 MRP)。

D = [(A,0,B),(B,0,T),(B,1,T),(B,1,T),(B,1,T),(B,1,T),(B,1,T),(B,1,T),(B,1,T),(B,1,T),(B,0,T)]

缓冲器 D 中的前两个值来自一个转换,即 A ,0, B ,0。缓冲区中剩余的七个条目是前一个示例中剩余的七个转换。

模型学习的大致分类如下:

  • 参数化:

    • 表格查找模型

    • 线性期望模型

    • 线性高斯模型

    • 高斯过程模型

    • 深度信念网络模型

  • 非参数化:将所有交互( sars ' )存储在一个缓冲区中,然后从这个缓冲区中进行采样,以生成示例转换。

使用已学习的模型进行规划

一旦我们知道了模型,我们就可以用它来执行计划,使用我们在第三章中看到的价值或策略迭代。在这些方法中,我们使用贝尔曼方程进行一步部署。

然而,有另一种方法可以使用学习的模型。我们可以从中抽取样本,并在 MC 或 TD 学习方法下使用这些样本。在这种情况下,当执行 MC 或 TD 学习风格时,代理不与真实环境交互。更确切地说,它与环境的模型相互作用,而环境是它从过去的经验中近似得到的。在某种程度上,它仍然被称为计划。我们使用从与现实世界的直接互动中学到的模型来计划,而不是计划。

请记住,我们学习的模型是一个近似值。它是基于我们的代理人与环境的部分相互作用。它通常不完整或详尽。我们在前一节中讨论了这一点。学习模型中的不准确性会限制学习的质量。算法优化了关于我们已经形成的模型而不是真实世界模型的学习。这可能会限制学习质量。如果我们对学习模型的信心不是太高,我们可以回到之前看到的无模型 RL 方法。或者我们可以使用贝叶斯方法来推理模型的不确定性。贝叶斯方法不是我们将在本书中进一步探讨的东西。感兴趣的读者可以查看各种高级 RL 文本和论文来进一步探索这一点。

到目前为止,我们已经看到,基于模型的 RL 提供了学习模型的优势,从而使学习更加有效。然而,这是以模型估计不准确为代价的,这反过来限制了学习的质量。有没有一种方法可以将基于模型的学习和无模型学习结合到一个统一的框架中,并利用这两种方法的优点这就是我们现在要探讨的。

整合学习和计划(Dyna)

我们看到了两种类型的体验:一种是代理人与真实环境交互以获得下一个状态和奖励的真实体验, s t + 1rt+1;以及模拟体验,其中代理使用所学习的模型来生成额外的模拟体验。

模拟体验更便宜,也更容易产生,尤其是在机器人领域。我们有快速机器人模拟器来生成样本,这些样本在现实世界中可能不是 100%准确,但与机器人的现实世界交互相比,它们可以帮助我们以更快的速度生成和模拟代理行为。然而,模拟可能不准确,这正是从真实世界的经验中学习会有所帮助的地方。我们可以修改图 9-1 的图来引入这一步,如图 9-3 所示。

img/502835_1_En_9_Fig3_HTML.jpg

图 9-3

Dyna 架构

在 Dyna 中,主体与环境交互(表演)以产生真实的体验。这些真实的经验被用来学习世界的模型,并像无模型 RL 设置一样直接改进价值/策略。这和我们在第 4 到 8 章看到的是一样的。从真实世界交互中学习到的模型用于生成模拟转换。这些模拟转换用于进一步改进价值/策略。我们称这一步为计划,因为我们使用世界模型来产生体验。我们可以通过对“表演”的每一步多次使用模拟体验来执行规划步骤,以从现实世界中生成新的样本。

让我们来看看 Dyna 的具体实现,即表格式 Dyna Q。在这种情况下,我们假设状态和动作是离散的,并形成一个小集合,以便我们可以使用表格式表示 Q ( sa ),即 Q 值。我们使用 TD 下的 q-learning 方法来学习和改进政策。这类似于我们在 Q-learning 的图 4-14 中看到的方法,即偏离策略的 TD 控制。我们还将从模拟体验中学习一些额外的步骤。在图 4-14 中,我们仅使用真实体验来学习 q 值,但我们现在将学习模型,并使用该模型来生成额外的模拟体验以供学习。图 9-4 给出了完整的算法。

TABULAR DYNA Q

img/502835_1_En_9_Fig4_HTML.png

图 9-4

表格动态 Q

从图 9-4 中的伪代码可以看出,表格式 Dyna Q 类似于图 4-14 中的表格式 Q-learning,除了在 Dyna Q 中,对于真实环境中的每一步,我们还使用模拟体验执行额外的 n 步骤,也就是说,在这种情况下,从真实世界中看到的先前过渡中采样。随着我们增加 n ,收敛和学习一个最佳值所需的片段数将会减少。

考虑这样一种情况,除了奖励值为 1 的最后一个终端/目标状态转换之外,迷宫对每个转换的奖励为零。这种迷宫的一个例子是图 9-5 左侧的迷宫。我们还假设每个( sa )对的初始 q 值为零。在常规 Q-learning 中,我们会学习第一集终止时的目标值 1。则值 1 将缓慢传播——每集一个单元/级别,最终从开始状态到达。在此之后,q 值和最优策略将在其他事件中开始收敛。然而,在 Dyna Q 的情况下,我们将为环境中的每个真实步骤生成 n 个附加示例。这将加速收敛。在普通 q-learning 方法之前,我们将会看到政策趋同。有关这种更快收敛的更多理论和详细解释,请参考《强化学习:简介》一书的第八章。

img/502835_1_En_9_Fig5_HTML.jpg

图 9-5

在代理人学会通过右侧的开口后,带网格的 Dyna Q 在中途变得更加困难。代理学习通过砖墙左侧的新开口来导航网格

让我们将前面的表格 Dyna Q 伪代码应用到我们在第四章中看到的相同环境中。我们将修改清单 4-4 来合并模型学习和 n 个计划步骤(从模拟经验中学习),用于从实际经验中直接学习基于 RL 的每一步。完整代码见listing9_1_dynaQ.ipynb。在演练中,我们将强调第四章中 Dyna Q 代码和 Q-learning 代码之间的主要区别。

首先,我们将代理从QLearningAgent重命名为DynaQAgent。这只是一个名称的变化,代码保持不变,只是增加了一个字典来存储所看到的真实世界的转换。我们将下面两行代码添加到__init__函数中:

self.buffer = {}
self.n = n # the number of planning steps to be taken from simulated exp

其次,我们添加逻辑以遵循图 9-4 中伪代码的规划部分,如下所示:

循环重复 n 次:

S ←一个随机的先前看到的状态

一个 ←先前在 S 采取的随机行动

RS ,,型号 ( SA )

$$ Q\left(S,A\right)\leftarrow Q\left(S,A\right)+\alpha \ast \left[R+\gamma \ast ma{x}_{a{'}}Q\left({S}{'},{a}^{'}\right)\hbox{--} Q\left(S,A\right)\right] $$

这被添加到我们训练代理的函数train_agent中。清单 9-1 显示了修改后的函数实现。

#training algorithm
def train_agent(env, agent, episode_cnt = 10000, tmax=10000, anneal_eps=True):
    episode_rewards = []
    for i in range(episode_cnt):
        G = 0
        state = env.reset()
        for t in range(tmax):
            action = agent.get_action(state)
            next_state, reward, done, _ = env.step(action)
            agent.update(state, action, reward, next_state, done)
            G += reward
            if done:
                episode_rewards.append(G)
                # to reduce the exploration probability epsilon over the
                # training period.
                if anneal_eps:
                    agent.epsilon = agent.epsilon * 0.99
                break

           # add the experience to agent's buffer (i.e. agent's model estimate)
            agent.buffer[(state,action)] = (next_state, reward, done)
            state = next_state
           # plan n steps through simulated experience
            for j in range(agent.n):
                state_v, action_v = random.choice(list(agent.buffer))
                next_state_v, reward_v, done_v = agent.buffer[(state_v,action_v)]
                agent.update(state_v, action_v, reward_v, next_state_v, done_v)

    return np.array(episode_rewards)

Listing 9-1Train_agent Using Dyna Q

接下来,我们在迷宫和 OpenAI Gym 的出租车世界环境中运行 Dyna Q。查看 Python 笔记本中的训练曲线,我们注意到,与普通 Q-learning 相比,Dyna Q 下收敛花费的时间更少。这证明了 Dyna Q(或任何 Dyna 架构)是样本高效的。

Dyna Q 和不断变化的环境

现在让我们考虑这样一种情况,我们首先使用 Dyna Q 学习迷宫的最优策略。经过一些步骤后,我们改变环境并使其变得更难,如图 9-5 所示。左图是原始网格,代理学习通过灰色砖墙右侧的开口从起点 S 导航到目标 G。在代理学习了最佳行为之后,网格被改变。右侧的开口被关闭,左侧开了一个新的开口。当代理试图通过先前学习的路径导航到目标时,现在看到原始路径被阻塞,即在原始迷宫的右侧打开。这是因为我们刚刚做的改变。

萨顿和巴尔托在他们的书强化学习:介绍中指出,Dyna Q 需要一段时间来学习变化的环境。一旦环境中途改变,在许多场景中,代理继续向右走,但发现路径被阻塞,因此在ε-贪婪策略下,它需要许多额外的步骤来学习从左侧的开口到达目标的替代路径。

让我们考虑他们在书中展示的第二种情况。第二种情况,中途改变环境,变得更简单;即,新的开口被引入到右侧,而不关闭左侧的原始开口。图 9-6 显示了变化前后的迷宫。

img/502835_1_En_9_Fig6_HTML.jpg

图 9-6

代理学习了左侧网格的最佳策略后,Dyna Q 的网格变得更加简单。Dyna Q 代理未能发现通过新右侧缺口的较短路径

通过在该设置上运行 Dyna Q 学习算法,我们可以显示 Dyna Q 没有学习砖墙右侧的新开口。如果遵循通过砖墙右侧的新路径,将提供到达目标状态的更短路径。Dyna Q 已经学会了最优策略,它没有动力去探索变化的环境。新开放的随机/偶然探索取决于探索政策,而探索政策又取决于 ε 探索。

有一个名为 Dyna Q+的修正算法可以解决这个问题,如下一节所述。

Dyna Q+

Dyna Q+是探索/利用困境的典型例子,需要在所有强化学习设置中仔细考虑。我们之前谈过了。如果我们过早地开始利用知识(即模型或策略),我们就有可能不知道更好的途径。代理贪婪地变得对它设法学到的任何东西感到高兴。另一方面,如果代理探索得太多,即使它已经得到了最优解,也是在浪费时间寻找次优路径/选项。不幸的是,代理没有直接的方法知道它已经达到了最优策略,因此它需要使用其他试探法/方法来平衡探索-开发困境。

在 Dyna Q+中,我们通过在我们观察到的状态之外增加奖励来鼓励探索未知的状态。这个额外的奖励条款鼓励探索现实世界中有一段时间没有被访问过的州。

在模拟规划部分,我们在奖励中加入$$ \kappa \sqrt{\tau } $$;即,奖励 r 变成$$ r+\kappa \sqrt{\tau } $$,其中 κ 是一个小常数,而 τ 是自从在现实世界探索中看到所讨论的转变以来的时间。它鼓励代理尝试这些转换,从而有效地捕捉环境中的变化,并有一些由 κ 控制的滞后。在图 9-6 中,代理最初学习通过左侧开口。它按照修改后的术语进行探索,但发现到达目标的唯一途径是通过左侧开口。然而,在通过在右侧制造一个新的开口来改变环境之后,代理人在随后对迷宫的这一部分的探索中将发现新的开口,并最终发现这是一条通向目标的更短路径的事实。代理将修改其最佳行为,以遵循右侧的开口。随着最后一次访问网格部分的时间增加,奖励中的$$ \kappa \sqrt{\tau } $$项会增加,在某个阶段可能会增加太多,以至于盖过了当前最佳行为的奖励,迫使代理再次探索未访问的部分。

预期更新与示例更新

我们已经看到了将学习和计划结合起来的各种方法。我们可以看到,学习和规划都是关于价值函数更新的方式。第一维是关于更新什么,是状态值( v )还是动作值( q )。另一个维度是更新的宽度。换句话说,更新是基于看到的单个样本发生的(样本更新),还是基于使用给定当前状态和动作的下一个状态的转移概率的所有可能的转移(预期更新),即,p(sta t )?第三个也是最后一个维度是是否对任意策略 v πq π 或最优策略vq进行更新。让我们看看各种组合,并把它们映射回我们在书中迄今为止所看到的内容。

  • vπ(s)使用预期更新进行更新。更新是针对值函数的。它是使用转移概率分布 p ( s 'r | sa )覆盖来自给定状态的所有可能转移的预期更新。该操作是使用当前策略代理执行的。

    This is policy evaluation using dynamic programming, as shown in equation (3.​6).

    $$ {v}_{k+1}(s)\leftarrow \sum \limits_a\pi \left(a|s\right)\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_k\left({s}^{\prime}\right)\right] $$

  • vπ(s)使用样本更新进行更新。更新是针对值函数的,并且基于代理看到的遵循策略 π 的样本。

    This is policy evaluation under TD(0), as shown in equation (4.​4). The value of state (s) is updated based on the action taken by the agent as per policy π, the subsequent reward (r), and the next state (s) seen by the agent.

    $$ {v}_{k+1}(s)\leftarrow {v}_k(s)+\alpha \left[r+\gamma {v}_k\left({s}^{'}\right)\hbox{--} {v}_k(s)\right] $$

  • v(s)在所有可能的动作上使用 max 更新。state ( s )的值基于所有可能的下一个状态和奖励的期望来更新。通过在所有可能的动作中取最大值来完成更新,即在给定时间点的最优动作,而不是基于代理正在遵循的当前策略。

这是根据方程式( 3)使用动态编程的值迭代。8 。

$$ {v}_{k+1}(s)\leftarrow {\mathit{\max}}_a\ \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ {v}_k\left({s}^{\prime}\right)\right] $$

  • qπ(sa )使用预期更新进行更新。被更新的值是 q 值,并且在基于来自给定状态-动作对的所有可能的下一个状态和回报的期望中被更新。该更新基于代理遵循的当前策略。

    This is q-policy evaluation using dynamic programming, i.e., equation (3.​2) expressed in iterative form.

    $$ {q}_{k+1}\left(s,a\right)\leftarrow \sum \limits_{s^{\prime },r}p\left({s}^{\prime },r\ \right|s,a\Big)\ \left[r+\gamma\ \sum \limits_{a^{\prime }}\pi \left({a}^{\prime }|{s}{\prime}\right){q}_k\left({s}{\prime },{a}^{\prime}\right)\right] $$

  • qπ(sa )使用样本更新。q 值的更新基于样本和代理遵循的当前策略,即基于策略的更新。

    This is q-value iteration using dynamic programming. Here is the iterative version of equation (3.​4):

    $$ {q}_{k+1}\left(s,a\right)\leftarrow {\sum}_{s',r}p\left({s}{'},r|s,a\right)\left[r+\gamma ma{x}_{a'}{q}_k\left({s}{'},{a}^{'}\right)\right] $$

  • q(sa )使用所有可能的状态进行更新,并在下一个状态下对所有可能的动作进行 max。这是对 q 值的更新,并且基于所有可能的后续状态和动作对的期望。它通过采取最大限度的动作进行更新,从而更新最佳策略,而不是代理当前遵循的策略。

    This is SARSA using a model-free setup, as shown in equation (4.​6). It has been rewritten to match the notations in this section.

    $$ {q}_{k+1}\left(s,a\right)\leftarrow {q}_k\left(s,a\right)+\alpha \left[r+\gamma {q}_k\left({s}{'},{a}{'}\right)-{q}_k\left(s,a\right)\right] $$

  • q(sa )使用样本动作更新,然后从下一个状态取所有可能动作的最大值。这是使用样本更新 q 值。该更新基于所有可能操作的最大值,即不符合策略的更新,而不是代理遵循的当前策略的更新。

    *This is the Q-learning that we studied in Chapter 4 and then extended to deep networks under DQN. This is equation (4.​10) rewritten to match the notations in this chapter.

    $$ {q}_{k+1}\left(s,a\right)\leftarrow {q}_k\left(s,a\right)+\alpha \left[r+\gamma ma{x}_{a'}\ {q}_k\left({s}{'},{a}{'}\right)-{q}_k\left(s,a\right)\right] $$*

前面的解释说明了动态规划和无模型世界是如何联系在一起的。在 DP 中,既然我们知道了模型动力学,我们就对所有可能的跃迁p(sta t* )进行大范围的扫描。在无模型的世界中,我们不知道模型,因此我们进行基于样本的 MC 或 TD 更新。动态规划和无模型之间的区别在于更新是基于期望值还是基于样本。

它还表明,一旦我们开始学习模型动力学,我们可以在 Dyna 设置下混合和匹配任何以前的方法。就像前面一样,我们将无模型和基于模型的方法结合在一个统一的设置中。这类似于我们通过将基于价值的 DQN 和政策梯度结合到 actor critic 下的统一方法中所做的,以及我们使用资格跟踪将一步样本(TD)和多步样本(MC)结合到单个框架中所做的。

组织 RL 算法的另一种方式是查看二维世界,其中纵轴是更新的长度/深度,TD(0)在一端,MC 在另一端。第二个/水平维度可以看作是更新的宽度,在一端是动态编程预期更新,在另一端是基于样本的 MC/TD 更新。图 9-7 显示了这种区别。

img/502835_1_En_9_Fig7_HTML.jpg

图 9-7

简而言之,强化学习方法

我们可以通过添加更多的维度来进一步完善这种统一。与更新的宽度和深度正交的第三个可能的维度是不符合策略的更新与符合策略的更新。我们还可以增加第四个维度,即纯粹基于价值的方法或纯粹的政策学习方法或两者的混合,即行为者批评。

到目前为止,我们已经在所谓的后向视角中看到了学习和计划的结合。我们使用规划(例如 Dyna Q)来更新值函数,但是在现实世界中选择动作时不涉及规划。这是一种被动的计划方法,其中计划用于生成额外的步骤/合成示例来训练和改进模型/策略/价值功能。在接下来的部分中,我们将看一下提前计划,其中我们在决策时间执行计划。换句话说,我们使用模型知识(学习到的或给定的)来预测未来,然后根据我们认为的最佳行动采取行动。我们将在蒙特卡罗树搜索算法(MCTS)的背景下研究这种方法。然而,在此之前,让我们在下一节回顾一下探索与利用的问题。

探索与开发

在强化学习中,我们总是需要在使用现有知识和探索更多知识以获得新知识之间进行平衡。最初,我们对世界(环境)一无所知或知之甚少。我们可以探索更多,并开始改善我们对世界的信念。随着我们的信念得到改善和加强,代理人开始利用这个信念,同时逐渐减少对它的探索。

到目前为止,我们已经研究了不同形式的探索。

  • ε-贪婪策略是代理基于其当前信念以概率(1-ε)采取最佳行动,并以概率ε随机探索。随着步骤的进行,ε逐渐减小。我们在所有的 DQN 方法中都看到了这一点。如果您查看第六章的笔记本,您会看到以下功能:

  • 我们总是从ε= 1.0 开始,经过一段时间后降至 0.05。前一个函数实现了 epsilon 缩减计划。

  • 我们还看到了在策略梯度方法中以学习随机策略的形式进行的探索,其中我们总是学习策略分布π θ ( a | s )。我们没有采取任何超过最大值的行动来学习单一的最优行动,这是在 DQN 的情况。这确保了我们不是在学习确定性动作。所有行为的不同非零概率确保了探索。

  • 我们还看到了熵正则化在策略梯度和行动者批评方法中的使用,这些方法强制进行足够的探索,以确保代理不会在没有对策略/行动空间的未知部分进行足够探索的情况下过早地承诺利用。请参考第七章的列表 7-1 获取该方法的快速示例。

def epsilon_schedule(start_eps, end_eps, step, final_step):
    return start_eps + (end_eps-start_eps)*min(step, final_step)/ final_step

在本节中,我们将通过一个简单的设置来更正式地研究勘探与开发之间的权衡,以及有效勘探的各种策略。

多臂强盗

我们将基于多个赌场老丨虎丨机堆叠在一起的概念,考虑一个被称为多臂土匪的环境。代理不知道每个老丨虎丨机的成功率的单独奖励金额。代理人的工作是选择一台机器,拉动吃角子老丨虎丨机控制杆,以获得奖励。只要我们愿意,这个循环就会一直重复下去。代理需要探索尝试所有机器,并形成对每台机器的个人奖励分配的信念。随着它的信念越来越强,它必须开始更多地利用它的知识/信念来拉动最佳机器的杠杆,减少探索。图 9-8 显示了设置。

img/502835_1_En_9_Fig8_HTML.jpg

图 9-8

非文本多臂强盗

我们研究多臂 bandit 的原因是它是一个简单的设置。此外,它可以扩展到上下文多臂强盗,其中每个老丨虎丨机的奖励分配是不固定的。相反,它取决于给定时间点的上下文,例如代理的“当前状态”。上下文多臂 bandit 有许多实际的用例。我们将在这里考虑一个这样的例子。

假设您有 10 个广告,并且您想要基于页面的上下文(例如,内容)在浏览器窗口中向用户显示一个广告。当前页面是上下文,显示 10 个广告中的一个是动作。当用户点击广告时,用户获得 1 的奖励;否则,奖励为 0。代理具有用户在相同的上下文中点击显示的广告的次数的历史,即,点击具有相似内容的网页上的显示的广告。代理想要采取一系列行动,目的是增加/最大化用户点击所显示广告的机会。这是一个上下文多臂土匪的例子。

另一个例子是一个在线商店,根据用户当前查看的内容显示其他产品推荐。在线商店希望用户会发现一些有趣的推荐并点击它们。另一个例子是从 K 个可能的选项中选择一种药物,给病人服用该药物以选择最好的一个,并在最多 T 次尝试后得出结论。

我们可以进一步扩展 bandit 框架来考虑全面的 MDP。但是每一次扩展都使得形式分析变得更加费力和麻烦。因此,我们将在这里研究非文本 bandit 的最简单的设置,以帮助您理解基础知识。感兴趣的读者可以利用网上的许多好资源进一步研究这个问题。

遗憾:探索质量的衡量标准

接下来,我们定义探索的质量。考虑系统处于状态 S 。在非上下文 bandit 中,它是一个冻结的初始状态,不变,每次都保持不变。对于上下文相关的 bandit,状态 S 可能会随着时间而改变。进一步,假设我们遵循一个策略πt(s)在时间 t 选择一个动作。同样,考虑你知道最优策略π∫(s)。

在时间 t 的后悔,即从非最优策略中选择一个动作的后悔,定义如下:

$$ regret={E}_{a\sim {\pi}^{\ast }(s)}\left[r\left(s,a\right)\right]-{E}_{a\sim {\pi}_t(s)}\left[r\left(s,a\right)\right] $$

(9.3)

它是遵循最优政策的预期回报减去遵循特定政策的回报。

我们可以对所有时间步进行求和,以获得 T 步的总遗憾,如下所示:

$$ \eta ={\sum}_{t=1}^T\left({E}_{a\sim {\pi}^{\ast }(s)}\left[r\left(s,a\right)\right]-{E}_{a\sim {\pi}_t(s)}\left[r\left(s,a\right)\right]\right) $$ (9,4)

因为我们在看非上下文 bandit,其中状态( s )在每个时间步保持不变,即保持固定为初始状态。我们可以简化( 9.4 )如下所示:

$$ \eta =T.\kern0.5em {E}_{a\sim {\pi}^{\ast }(s)}\left[r\left(s,a\right)\right]-{\sum}_{t=1}^T\left({E}_{a\sim {\pi}_t(s)}\left[r\left(s,a\right)\right]\right) $$

(9.5)

我们将使用( 9.5 )来比较各种采样策略。在接下来的几节中,我们将研究三种常用的探索策略。我们将在代码示例的帮助下这样做。你可以在listing9_2_exploration_exploitation.ipynb中找到完整的代码。

让我们首先描述一下我们将使用的设置。考虑多臂土匪有K动作。每个行动都有一个固定的概率θk产生奖励 1,以及一个(1-??)θk产生奖励 0 的概率。最优动作是k(小写 k )的值,成功概率最大。

$$ {\theta}^{\ast }={\theta}_k $$

清单 9-2 显示了强盗的代码。我们将k存储为self.n_actions,将所有 θ k 存储为self._probs.函数pull将动作k作为输入,根据动作的概率分布返回奖励 1 或 0,即θ k 。函数optimal_reward返回self._probs.max

class Bandit:
    def __init__(self, n_actions=5):
        self._probs = np.random.random(n_actions)
        self.n_actions = n_actions

    def pull(self, action):
        if np.random.random() > self._probs[action]:
            return 0.0
        return 1.0

    def optimal_reward(self):
        return np.max(self._probs)

Listing 9-2Bandit Environment

接下来,我们将从ε-贪婪探索策略开始来看三种探索策略。

ε贪婪探索

这类似于我们在前几章看到的ϵ-贪婪策略。代理人尝试不同的行动,并针对土匪环境的不同行动形成未知实际成功概率 θ k 的估计$$ {\hat{\theta}}_k $$。它以(1-??)ϵ的概率采取动作 k$$ {\hat{\theta}}^{\ast }=\mathit{\max}\ {\hat{\theta}}_k $$。换句话说,它利用到目前为止获得的知识,根据$$ {\hat{\theta}}_k $$估计采取它认为最好的行动。此外,它以概率 ϵ 采取随机行动进行探索。

我们将保持 ϵ = 0.01 作为探索概率,并在整个实验中保持不变。清单 9-3 显示了实现这个策略的代码。首先,我们将RandomAgent定义为基类。我们有两个数组来存储每个操作的成功和失败结果的累积计数。我们将这些计数存储在数组中:self.success_cntself.failure_cnt

函数update(self, action, reward)根据采取的行动和结果更新这些计数。功能reset(self)重置这些计数。我们这样做是为了多次运行相同的实验,并绘制平均值以消除随机性。职能get_action(self)是实施勘探战略的地方。它将根据策略类型使用不同的方法来平衡探索和利用。对于RandomAgent,动作总是从一组可能的动作中随机选择。

接下来,我们在类EGreedyAgent中实现ε-探索代理。这是通过扩展RandomAgent和覆盖函数get_action(self)来实现的。如上所述,以概率(1ϵ)选择概率最高的动作,以概率 ϵ 选择随机动作。

class RandomAgent:
    def __init__(self, n_actions=5):
        self.success_cnt = np.zeros(n_actions)
        self.failure_cnt = np.zeros(n_actions)
        self.total_pulls = 0
        self.n_actions = n_actions

    def reset(self):
        self.success_cnt = np.zeros(n_actions)
        self.failure_cnt = np.zeros(n_actions)
        self.total_pulls = 0

    def get_action(self):
        return np.random.randint(0, self.n_actions)

    def update(self, action, reward):
        self.total_pulls += 1
        self.success_cnt[action] += reward
        self.failure_cnt[action] += 1 – reward

class EGreedyAgent(RandomAgent):
    def __init__(self, n_actions=5, epsilon=0.01):
        super().__init__(n_actions)
        self.epsilon = epsilon

    def get_action(self):
        estimates = self.success_cnt / (self.success_cnt+self.failure_cnt+1e-12)
        if np.random.random() < self.epsilon:
            return np.random.randint(0, self.n_actions)
        else:
            return np.argmax(estimates)

Listing 9-3Epsilon Greedy Exploration Agent

在我们的设置中,我们没有改变ε值,因此代理永远不会学习执行一个完美的最优策略。它永远以ε概率继续探索。因此,遗憾永远不会降到零,我们期望由( 9.5 )给出的累积遗憾随着由ε的值定义的增长斜率线性增长。ε值越大,增长的斜率越陡。

置信上限探索

我们现在来看看称为置信上限 (UCB)的策略。在这种方法中,代理尝试不同的动作并记录成功的次数( α k )或失败的次数( β k )。

它计算成功概率的估计值,这些值将用于查看哪个操作具有最高的成功估计值。

$$ exploit={\hat{\theta}}_k=\frac{\alpha_k}{\alpha_k+{\beta}_k} $$

它还会计算偏好最少访问操作的浏览需求,如下所示:

$$ explore=\sqrt{\frac{2.\log t}{\upalpha_k+{\upbeta}_k}} $$

其中 t 是到目前为止采取的步骤总数。

然后,代理选择得分最高(浏览+利用)的操作,如下所示:

$$ score=\frac{\upalpha_k}{\upalpha_k+{\upbeta}_k}+\uplambda \sqrt{\frac{2.\log t}{\upalpha_k+{\upbeta}_k}} $$

(9.6)

其中 λ 控制探索与利用的相对重要性。我们将在代码中使用 λ = 1。

UCB 的方法是计算置信上限(置信区间上限),以确保我们有 0.95(或任何其他置信水平)的概率,即实际估计值在 UCB 值之内。随着试验次数的增加,随着我们一次又一次地采取特定的行动,我们估计中的不确定性会减少。( 9.6 中的分数将接近预估值$$ {\hat{\theta}}_k $$。那些根本没有被访问过的动作,会因为方程(αk+βk)= 0 而得分=∞(9.6)。UCB 显示出接近最佳增长界限。我们不会深入研究这一点的数学证明。可以参考土匪问题的进阶课文。

让我们看一下 UCB 代理的实现。清单 9-4 给出了代码。如前所述,我们再次扩展了清单 9-3 中所示的RandomAgent类,并覆盖了get_action函数,以根据 UCB 方程( 9.6 )选择动作。请注意,为了避免被零除,我们在出现self.success_cnt+self.failure_cnt的分母中添加了一个小常数1e-12

class UCBAgent(RandomAgent):
    def get_action(self):
        exploit = self.success_cnt / (self.success_cnt+self.failure_cnt+1e-12)
        explore = np.sqrt(2*np.log(np.maximum(self.total_pulls,1))/(self.success_cnt+self.failure_cnt+1e-12))
        estimates =  exploit + explore

        return np.argmax(estimates)

Listing 9-4UCB Exploration Agent

汤普森取样勘探

接下来,我们看看我们的最终策略,即汤普森抽样。这是基于这样一种想法,即选择一个行动的基础是它是最大化预期回报的行动的概率。虽然 UCB 是基于概率的频率概念,汤普森抽样是基于贝叶斯思想。

最初,我们不知道哪一个动作更好,即哪一个动作具有最高的成功概率 θ k 。相应地,我们形成一个初始信念,所有的 θ k 值都均匀分布在范围(0,1)内。这在贝叶斯术语中称为先验。我们用$$ {\hat{\theta}}_k\sim Beta\left(\alpha =1,\beta =1\right) $$的一个贝塔分布 2 来表示。代理为每个动作采样一个值 k 并选择采样值最大的动作$$ {\hat{\theta}}_k. $$

接下来,它播放该动作(即调用函数pull来执行所选择的动作)并观察结果。基于结果,它更新$$ {\hat{\theta}}_k. $$的后验分布。随着每一步中成功和失败计数的更新,代理更新其对$$ {\hat{\theta}}_k $$的信念。

$$ {\hat{\theta}}_k\sim Beta\left(\alpha ={\alpha}_k+1,\beta ={\beta}_k+1\right) $$

(9.7)

循环往复。随着我们经历多个步骤, α kβ k 增加。β分布变窄,峰值在$$ \frac{\alpha_k}{\alpha_k+{\beta}_k} $$的平均值附近。

让我们看看代码中 Thompson 采样的实现,如清单 9-5 所示。像以前一样,我们扩展了RandomAgent类并覆盖了get_action方法来实现等式( 9.7 )。

class ThompsonAgent(RandomAgent):
    def get_action(self):
        estimates = np.random.beta(self.success_cnt+1, self.failure_cnt+1)
        return np.argmax(estimates)

Listing 9-5Thompson Sampling Exploration

比较不同的勘探策略

我们实现了一个函数get_regret来使用 bandit 环境和一个选择的探索策略采取n_steps=10000步骤。我们进行了n_trials=10次实验,并将平均累积遗憾存储为步数的函数。清单 9-6 给出了实现get_regret的代码。

清单 9-6。 获取 _ 后悔功能

def get_regret(env, agent, n_steps=10000, n_trials=10):
    score = np.zeros(n_steps)
    optimal_r = env.optimal_reward()

    for trial in range(n_trials):
        agent.reset()
        for t in range(n_steps):
            action = agent.get_action()
            reward = env.pull(action)
            agent.update(action, reward)
            score[t] += optimal_r - reward
    score = score / n_trials
    score = np.cumsum(score)
    return score

我们对三个代理人进行了实验:ε-贪婪、UCB 和汤姆森探索。我们绘制了累积奖励,如图 9-9 所示。

img/502835_1_En_9_Fig9_HTML.jpg

图 9-9

不同探索策略的后悔曲线

我们可以看到,UCB 和汤普森有次线性增长,而ε-贪婪后悔继续以更快的速度增长(实际上是线性增长,增长率由ε的值决定)。

现在让我们回到蒙特卡罗树搜索的讨论,我们将使用 UCB 作为一种方式来执行探索与利用决策。选择 MCTS 的 UCB 是基于流行的 MCTS 实现中显示的当前趋势。

决策时的计划和蒙特卡罗树搜索

Dyna 使用规划的方式叫做后台规划。学习到的模型用于生成模拟样本,然后将模拟样本馈送给算法,就像真实样本一样。这些额外的模拟样本有助于在使用真实世界交互所做的估计之上进一步改进价值估计。当一个动作必须在比如说州St中被选择时,在这个决策时间没有计划。

计划的另一种方式是在状态 S t 时开始和结束计划,以展望未来,有点像使用迄今为止学习的模型在你的头脑中播放各种场景,并使用该计划来决定在时间 t 要采取的动作 A t 。一旦采取了动作 A t ,代理发现自己处于新的状态代理人再次向前看,计划,并采取步骤。如此循环下去。这被称为决策时间规划。根据可用的时间,代理从当前状态计划的越远/越深入,该计划在决定在时间 t 采取什么行动时就越有力和有帮助。因此,在不需要快速响应的情况下,决策时间规划最有帮助。在像国际象棋和围棋这样的棋盘游戏中,决策时间计划可以在决定采取什么行动之前预测十几步棋。但是,如果需要快速响应,那么后台规划是最合适的。

这些向前搜索算法通过向前看来选择最佳的可能动作。他们从代理所处的当前状态 S t 开始构建可能选项的搜索树。给定状态-动作对中的可能选项可能取决于代理从先前的交互中学习到的 MDP 模型,或者它可能基于开始时给代理的 MDP 模型。例如,在像国际象棋和围棋这样的棋盘游戏中,我们已经清楚地定义了游戏规则,因此我们对环境了如指掌。图 9-10 显示了一个搜索树的例子。

img/502835_1_En_9_Fig10_HTML.jpg

图 9-10

从当前状态 S t 开始搜索树

然而,如果分支因子很高,我们就不能构建一个穷举搜索树。在围棋比赛中,平均分支因子在 250 左右。即使构建三级深度穷举树,第三级也会有 250 个3= 1562.5 万个节点,即 1500 万个节点。为了尽可能利用向前看的优势,我们想比两三个层次更深入。这就是形成基于模拟的搜索树而不是穷举搜索树的原因。我们使用一种叫做展示策略的策略来模拟多个轨迹,并形成行动值的 MC 估计。这些估计用于选择根状态 S t 的最佳动作。这有助于改进政策。在采取最佳行动后,代理发现自己处于一个新的状态,并从头开始从新的状态展开模拟树。如此循环下去。尝试的卷展轨迹的数量取决于代理需要决定动作的时间限制。

MCTS 是决策时间规划的一个例子。MCTS 与前面描述的完全一样,只是增加了一些额外的增强,以累积价值评估,并将搜索导向树中最有回报的部分。MCTS 的基本版本由四个步骤组成。

  1. 选择/树遍历:从根节点开始,用一个树策略遍历树,到达一个叶节点。

  2. 展开:给叶子节点添加子节点(动作),选择其中一个。

  3. 模拟/展示:从先前选择的子节点开始,玩游戏直到终止。我们使用固定的展示策略来完成这一过程,也可以使用随机的展示策略。

  4. 反向传播:使用播放的结果,通过跟随(回溯)所遍历的路径,更新从终点到根节点的所有节点。

这个循环重复多次,总是从当前根节点开始,直到我们用完分配给搜索的时间,或者我们运行它一个固定的迭代次数。此时,基于累积的统计数据,选择具有最高值的动作。图 9-11 显示了 MCTS 的示意图。

img/502835_1_En_9_Fig11_HTML.jpg

图 9-11

由四个步骤组成的一次 MCTS 迭代

接下来,代理在真实环境中执行选择的动作。这将代理移动到新的状态,并且先前的 MCTS 循环再次开始,这一次新的状态是根状态,从该根状态构建模拟的基于树。

让我们看一个简单的虚构的例子。我们将使用以下 UCB 衡量标准:

$$ UCB1\left({S}_i\right)=\underset{_}{V_i}+C\sqrt{\frac{lnN}{n_i}}, where\ C=2 $$

UCB 1 将用于决定在扩展阶段选择哪个节点。我们从根节点 S 0 开始,对于每个节点,我们保留两个统计数据,总分和对该节点的访问次数。初始设置如下:

img/502835_1_En_9_Figd_HTML.jpg

我们通过添加从 S 0 开始的所有可用动作,比如说 a 1a 2 来扩展根节点处的树。这些动作将我们带到两个新的状态,分别是S1 和S2。我们初始化这两个节点的分数和计数统计。此时的树看起来像这样:

img/502835_1_En_9_Fige_HTML.jpg

这是初始树。我们现在开始执行 MCTS 的步骤。第一步是使用 UCB 1 值遍历树。我们在 S 0,并且我们需要选择具有最高 UCB 1 值的子节点。让我们为S1 计算 UCB 1。当 n = 0 时, UCB 1,即$$ \sqrt{\frac{lnN}{n}} $$中的探索项是无限的。S?? 2 就是这种情况。作为我们的树策略的一部分,让我们在 ties 的情况下选择具有较小子索引的节点,这导致选择S1。由于 S 1 之前从未被访问过,即对于 S 1 来说 n = 0,我们不展开,而是转到展示的步骤 3。我们使用展示策略来模拟从SS1直到终止的随机动作。假设我们观察到终止时的值为 20,如下所示:

img/502835_1_En_9_Figf_HTML.jpg

下一步是第四步,即将值从终端状态一直反向传播到根节点。我们更新从终端状态到根节点的路径中所有节点的访问计数和总分。然后我们从 S 1 中擦除了卷展栏部分。此时,树看起来就像图中所示的那样。请注意节点S1 和S0 的更新统计。

img/502835_1_En_9_Figg_HTML.jpg

这使我们结束了 MCTS 步骤的第一次迭代。我们现在将再运行一次 MCTS 步骤的迭代,再次从 S 0 开始。第一步是从S0 开始的树遍历。我们需要选择两个节点中的一个,即 S 1S 2 ,基于哪个具有更高的 UCB 1 值。对于S1, UCB 1 的值由下式给出:

$$ UCB\left({S}_1\right)=\frac{20}{1}+2.\sqrt{\frac{\mathit{\ln}1}{1}}=20 $$

ln 1 中的 1 来自根节点的 n 的值,它告诉我们到目前为止的总尝试次数。而平方根里面的分母中的 1 来自于节点S1 的 n 的值。UCB(SS2)继续为∞,因为S2 的 n 仍然为 0。所以,我们选择节点S2。这结束了树遍历步骤,因为S2 是叶节点。我们从S2 开始进行首次展示。让我们假设从S2 的卷展产生的终端值为 10。这就完成了推广阶段。此时的树看起来像这样:

img/502835_1_En_9_Figh_HTML.jpg

下一步是将终端值 10 一路反向传播回根状态,并且在该过程中更新路径中所有节点的统计,即,在这种情况下的 S 2S 0 。然后我们删除卷展栏部分。在反向传播步骤结束时,更新后的树如下所示:

img/502835_1_En_9_Figi_HTML.jpg

让我们对 MCTS 再进行一次迭代,这将是最后一次迭代。我们再次从 S 0 开始,重新计算子节点的 UCB 1 值。

$$ UCB\left({S}_1\right)=\frac{20}{1}+2.\sqrt{\frac{\mathit{\ln}2}{1}}=21.67 $$

$$ UCB\left({S}_2\right)=\frac{10}{1}+2.\sqrt{\frac{\mathit{\ln}2}{1}}=11.67 $$

由于 S 1 的 UCB 值较高,我们选择该值。我们已经到达了一个叶节点,这结束了 MCTS 的树遍历部分步骤。我们在 S 1 处,它之前已经被访问过,所以我们现在通过模拟 S 1 的所有可能动作以及我们最终所处的结果状态来扩展 S 1 。此时的树看起来像这样:

img/502835_1_En_9_Figj_HTML.jpg

我们需要在 S 3S 4 之间选择一个叶节点,然后从选择的节点开始展示。和前面一样,对于两个S3 和S4 的 UCB 1 为∞。按照我们的树策略,在出现平局的情况下,我们选择子索引较低的节点。这意味着我们选择S3 并从S3 中进行一次展示。假设卷展栏结束时的终端状态产生的终端值为 0。这就完成了 MCTS 的推广步骤。这棵树看起来像这样:

img/502835_1_En_9_Figk_HTML.jpg

最后一步是将零值一路反向传播回根节点。我们更新路径中节点的统计,即节点 S 3S 1S 0 。这棵树看起来像这样:

img/502835_1_En_9_Figl_HTML.jpg

此时,假设我们已经没有时间了。该算法现在需要选择在 S 0 的最佳行动,并在现实世界中采取这一步。此时,我们将只比较平均值,对于S1 和S2 这两个平均值都是 10。这是一条领带。我们可以有额外的规则来打破平局。然而,假设我们总是选择一个随机的行动来打破平局,同时做出最佳行动的最终选择。假设我们随机选择了S1。代理采取行动 a 1 并在S1 着陆,假设确定性设置。我们现在使 S 1 成为新的根节点,并从 S 1 开始执行 MCTS 的多次迭代,最终选择在 S 1 采取最佳行动。通过这种方式,我们步入了真实的世界。

这是 MCTS 的精髓。MCTS 有许多变体,我们就不一一赘述了。主要目的是帮助您熟悉简单和基本的 MCTS 设置,并引导您完成计算以巩固您的理解。

在结束本章之前,我们将简要谈谈 MCTS 最近的一个著名用例,即 AlphaGo 和 AlphaGo Zero,它们帮助设计了一种算法,让计算机在围棋比赛中击败人类专家。

AlphaGo 演练

AlphaGo 由 Deep Mind 在 2016 年设计的 3 在围棋比赛中击败人类专家。一个标准的搜索树将从一个给定的位置测试所有可能的东西。但是分支因子高的游戏不能用这种方式。如果一个游戏的分支因子为 b (即任意位置的合法移动次数为 b ),游戏总长度(即深度或直到结束的连续移动次数)为 d ,那么就有 b d 种可能的移动顺序。评估所有选项对于任何一款中等的棋盘游戏来说都是不可行的。考虑到和( b ≈ 35, d ≈ 50)下棋或者和( b ≈ 250, d ≈ 150)下围棋,穷举搜索是不可能的。但是有效的搜索空间可以用两种通用的方法来减少。

  • 您可以通过位置评估来减少深度。在将搜索树扩展到一定深度后,用一个近似的评价函数v(s)≈v(s)替换一个叶节点处的状态。这种方法适用于棋盘游戏,如国际象棋、跳棋和奥赛罗,但不适用于围棋,因为围棋很复杂。

  • 第二种方法是通过来自策略 p ( s )的采样动作,从节点处的完全扩展减小树的宽度。蒙特卡洛展开根本不分支;他们从一个叶节点播放一集,然后使用平均回报作为对 v ( s )的估计。我们在 MCTS 看到了这种方法,其中部署值用于估计叶节点的 v(s),并且通过使用修订的估计值 v ( s )来进一步改进策略。

在之前的学术论文中,作者将 MCTS 与深度神经网络相结合,实现了超人的表现。他们将棋盘图像作为 19×19 像素的图像传递给卷积神经网络(CNN),并使用它来构建棋盘位置的表示,即状态

他们使用专家人类移动进行监督学习,学习两个策略,即 p ( s )。

  • SL 策略网络(pσ):作者使用了监督策略学习网络 p σ 。这是使用 CNN 风格的网络进行训练的,该网络以 19×19 个棋盘位置作为输入状态( s ),棋盘位置基于专业人员的移动。

  • Rollout policy(pπT7):同样的数据也被用于训练另一个名为fast policy pπ的网络,该网络在 MCTS 的“Rollout”阶段充当了 Rollout policy。

接下来,作者训练了一个强化学习(RL)网络 p ρ ,其从 SL 策略网络初始化,试图改进 SL 策略网络以具有更多获胜,即,使用策略优化来改进以具有更多获胜的目标。它类似于我们在第七章中看到的。

最后,另一个网络 v θ 被训练来预测 RL 策略网络 p ρ 用于自演的状态 v ( s )的值。图 9-12 显示了神经网络训练的高级图。

img/502835_1_En_9_Fig12_HTML.jpg

图 9-12

神经网络训练流水线。转载自 AlphaGo 论文图 14

最后,将政策网络和价值网络结合起来实现 MCTS,并进行向前搜索。作者使用了一种类似 UCB 的方法来遍历树的扩展部分。一旦到达叶节点( S L ),他们使用来自 SL 策略网络 p σ 的一个样本来扩展叶节点(MCTS 的步骤 2)。以两种方式评估叶节点:一种是通过使用值网络vθ(SL)另一种是通过快速卷展直到终止使用卷展网络 p π 获得结果 z L 。使用混合参数 λ 组合这两个值。

$$ V\left({S}_L\right)=\left(1-\lambda \right){v}_{\theta}\left({s}_L\right)+\lambda {z}_L $$

MCTS 的第四步是更新每个节点的得分和访问次数,正如我们在上一节的示例中看到的那样。

一旦搜索完成,算法从根节点选择最佳动作,然后 MCTS 循环从新的状态继续。

随后,一年后,在 2017 年,作者们又推出了一项名为“在没有人类知识的情况下掌握围棋游戏”的进一步改进, 5 训练在没有任何人类专家棋盘位置的情况下进行游戏,从随机游戏开始,并在此基础上进行改进。更多细节请参考论文。

这就把我们带到了本章的结尾。这是本书的最后一章,我们深入探讨了强化学习的某些特定方面。Alpha Go 的解释是在概念层面上的。感兴趣的读者不妨参考原文。

摘要

在这一章中,我们进行了最后的统一,即将基于模型的规划与无模型学习相结合。首先,我们研究了基于模型的 RL,我们学习模型并使用它来进行规划。接下来,我们研究了在 Dyna 下将规划和学习集成到一个架构中。然后,我们查看了 Dyna Q 中带 Q-learning 的 Dyna 的特定版本。我们还简要介绍了 Dyna Q+对长期未访问的州的工程探索。这有助于我们在不断变化的环境中管理学习。接下来,我们详细探讨了状态或动作值在期望中或通过基于样本的更新进行更新的各种方式。

然后,我们讨论了在一个简化的多臂强盗环境中探索与开发的两难境地。我们研究了各种探索策略,如ϵ-贪婪探索、置信上限探索和汤姆森抽样探索。在实验的帮助下,我们看到了他们在累积后悔上的相对表现。

在本章的最后一部分,我们学习了前瞻性和基于树的搜索。我们深入研究了蒙特卡罗树搜索,并演示了它在解决围棋游戏中的应用。

下一章,也是最后一章,将对本书中没有涉及的话题进行广泛的探讨。

*

十、进一步探索和后续步骤

这是这本书的最后一章。在整本书中,我们深入探讨了强化学习(RL)的许多基础方面。我们用动态规划法研究了 MDP 和 MDP 的规划。我们研究了无模型价值方法。我们讨论了使用函数近似的放大解决方案技术,特别是通过使用基于深度学习的方法,如 DQN。我们研究了基于政策的方法,如强化、TRPO、PPO 等。我们在行动者-批评家(AC)方法中统一了价值和政策优化方法。最后,我们在前一章看了如何结合基于模型和无模型的方法。

这些方法中的大多数都是掌握强化学习的基础。然而,强化学习是一个快速扩展的领域,在自动驾驶汽车、机器人和类似的其他领域有许多专门的用例。这些超越了我们在本书中用来解释概念的问题设置。作为核心主题的一部分,我们不可能涵盖所有新出现的方面。那会使这本书变得非常笨重;此外,许多地区几乎每天都有新的方法出现。

在最后一章中,我们将介绍一些我们认为您应该高度关注的话题。我们将把讨论保持在一个概念性的水平,并在适当的地方提供一些流行的研究/学术论文的链接。你可以根据你在 RL 领域的个人兴趣,使用这些参考资料来扩展你的知识范围。与前几章不同,你不会总能找到详细的伪代码或实际的代码实现。这样做是为了提供一些新兴领域和新进展的旋风之旅。你应该利用这一章对不同的主题有一个 30,000 英尺的了解。基于您的特定兴趣,您应该使用这里给出的参考资料作为起点,深入研究特定的主题。

我们还会谈到一些流行的图书馆和继续学习的方法。

基于模型的 RL:其他方法

在前一章中,我们看了基于模型的 RL,其中我们通过让代理与环境交互来学习模型。然后,所学习的模型被用于生成额外的转换,即,通过与真实世界的实际交互来增加由代理收集的数据。这是 Dyna 算法采用的方法。

虽然 Dyna 有助于加快学习过程,并解决无模型 RL 中出现的一些样本低效问题,但它主要用于简单函数近似器的问题。在需要大量样本来训练的深度学习函数近似器中,它还没有成功;由于模拟器精确模拟世界的能力不完善,来自模拟器的太多训练样本会降低学习质量。在这一节中,我们将看看最近在深度学习的背景下将学习模型与无模型方法相结合的一些方法。

世界模特

在 2018 年题为“世界模型”的论文中, 1 作者提出了一种建立生成神经网络模型的方法,这是环境的空间和时间方面的压缩表示,并使用它来训练代理。

我们人类发展了一种对周围世界的心理模型。我们不会存储环境中所有最小的细节。相反,我们存储世界的抽象高级表示,它压缩了世界的空间方面和时间方面以及世界的不同实体之间的关系。我们只存储我们接触过的或与我们相关的世界的一部分。

我们着眼于当前的状态或手头任务的背景,思考我们想要采取的行动,并预测行动将把我们带入的状态。基于这种预测思维,我们选择最佳行动。想想棒球击球手。他有几毫秒的时间来行动,在正确的时间和正确的方向挥动球棒来接球。你认为他是怎么做到的?经过多年的练习,击球手已经开发了一个强大的内部模型,在这个模型中,他可以预测球的未来轨迹,并可以从现在开始挥动球棒,几毫秒后到达轨迹中的准确点。一个好的玩家和一个坏的玩家之间的区别很大程度上归结于玩家基于其内部模型的预测能力。多年的实践使整个事情变得直观,而无需在头脑中进行大量有意识的规划。

在论文中,作者展示了他们如何使用一个大型/强大的循环神经网络(RNN)模型(他们称之为世界模型)和一个小型控制器模型来实现这种预测内部模型。小控制器模型的原因是将信用分配问题保持在界限内,并且在训练期间使策略迭代得更快。同时,大的“世界模型”允许它保留拥有环境的良好模型所需的所有空间和时间表达性,并拥有小的控制器模型以在训练期间将策略搜索集中在快速迭代上。

作者探索了在生成的世界模型上训练代理的能力,完全取代了与真实世界的交互。一旦代理在内部模型表示上训练有素,学习到的策略就被转移到现实世界。他们还展示了在内部世界模型中添加一个小的随机噪声的好处,以确保代理不会过度利用学习模型的缺陷。

接下来让我们来看一下所用模型的细分。图 10-1 给出了流水线的高级概述。在每一个瞬间,代理接收到来自环境的观察。

img/502835_1_En_10_Fig1_HTML.jpg

图 10-1

“世界观”中使用的代理模型概述转载自 Ha 和 Schmidhuber《循环世界模型促进政策演进》2018 2

世界模型由两个模块组成。

  • 视觉模型(V)使用来自深度学习的变分自编码器(VAE)将高维图像(观察向量)编码成低维潜在向量。潜向量 z ,模块 V 的输出,将观测的空间信息压缩成更小的向量。

  • 潜在向量 z 被馈送到记忆 RNN (M)模块中,以捕捉环境的时间方面。作为一个 RNN 网络,M 模型压缩了一段时间内发生的事情,并作为一个预测模型。因为许多复杂的环境是随机的,也因为我们学习中的不完善,M 模型被训练来预测下一个状态P(Zt+1)的分布,而不是预测确定性的值Zt+1。它通过基于当前和过去(RNN 部分)预测下一个潜在向量Zt+1的分布,作为高斯分布P(Zt+1|atz 的混合这就是为什么它被称为基于循环神经网络 (MDM-RNN)的混合密度模型。

控制器模型负责获取空间信息( z t )和时间信息( h t ),将它们连接在一起,并馈入单层线性模型。信息的总体流程如下:

  1. 代理在时间 t 得到一个观察值。观察结果被输入到 V 模型中,V 模型将其编码成一个更小的潜在向量zt

  2. 接下来,潜在向量被馈入预测 z t + 1 ,即 h t 的 M 模型。

  3. V 和 M 模型的输出被连接并馈入 C 控制器,C 控制器产生动作 a t 。换句话说,用 z tzt+1,预测 a t

  4. 该动作被反馈到现实世界中,并且被 M 模型用来更新其隐藏状态。

  5. 现实世界中的动作产生下一个状态/观察,下一个循环开始。

关于伪代码、所用网络的实现细节以及训练中使用的损耗等其他细节,请参考前面引用的论文。在论文中,作者还谈到了他们如何使用预测能力,通过反馈预测 z t + 1 作为下一个真实世界的观察,来提出假设场景。他们进一步展示了他们如何能够在“梦幻世界”中训练一个智能体,然后将所学知识转移到现实世界中。

想象力增强代理(I2A)

如前所述,Dyna 提出了一种结合无模型基于模型方法的方法。无模型方法在复杂环境中具有更高的可扩展性,并且已经被认为在深度学习中工作良好。然而,这些不是样本有效的,因为深度学习需要大量的训练样本才能有效。即使是一个简单的雅达利游戏策略培训也可能需要数百万个例子才能得到培训。另一方面,基于模型的是样本高效的。Dyna 提供了一种结合两种优势的方法。除了使用真实世界的过渡来训练代理之外,真实世界的过渡还用于学习用于生成/模拟附加训练示例的模型。然而,问题是模型学习可能并不完美,除非考虑到这一事实,否则在复杂的深度学习结合强化学习中直接使用 Dyna 并不能给出好的结果。缺乏模型知识会导致过度乐观和代理人表现不佳。

与之前使用世界模型的方法一样,想象力增强代理 (I2A)方法结合了基于模型和无模型的方法,使这种结合的方法能够很好地适用于复杂的环境。I2A 形成一个近似的环境模型,并通过“学习解释”学习到的模型缺陷来利用它。它提供了一种端到端的学习方法来提取从模型模拟中收集的有用信息,而不仅仅依赖于模拟的回报。代理人使用内部模型,也称为想象,寻求积极的结果,同时避免不利的结果。来自 DeepMind 的作者在其 2018 年题为想象力增强的深度强化学习代理3的论文中表明,这种方法可以在更少的数据和不完善的模型下更好地学习。**

**图 10-2 详细描述了参考文件中解释的 I2A 架构。

  • 图 10-2(a) 中的环境模型,在给定当前信息的情况下,对未来做出预测。想象核心(IC)有一个策略网络$$ \hat{\uppi} $$,它将当前观察$$ {o}_t\ \mathrm{or}\ {\hat{o}}_t $$(真实的或想象的)作为输入,并产生一个展示动作$$ {\hat{a}}_t $$。观察$$ {o}_t\ \mathrm{or}\ {\hat{o}}_t $$和首次展示行动$$ {\hat{a}}_t $$被输入到环境模型,一个基于 RNN 的网络,以预测下一次观察$$ {\hat{o}}_{t+1} $$和奖励$$ {\hat{r}}_{t+1} $$

  • 许多这样的集成电路串在一起,将前一个集成电路的输出馈送到下一个集成电路,并产生一个长度为 τ图像展示轨迹,如图 10-2(b) 所示。 n 产生这样的轨迹$$ {\hat{T}}_{1\dots }{\hat{T}}_{i\dots }{\hat{T}}_n $$。由于学习模型不能被认为是完美的,仅仅依赖于预测的回报可能不是一个好主意。此外,轨迹可以包含奖励序列之外的信息。因此,每个卷展栏通过顺序处理输出进行编码,以在每个轨迹上获得嵌入,如图 10-2(b) 右侧所示。

  • 最后,图 10-2(c) 中的聚合器将这些单独的 n 个展开组合起来,并作为一个额外的上下文提供给策略网络,以及直接提供观察的无模型路径。

img/502835_1_En_10_Fig2_HTML.jpg

图 10-2

I2A 架构。它描述了(a)中的 IC、(b)中的单个想象力展示以及(c)中的完整 I2A 架构。转载自论文《深度强化学习的想象力-增强代理》,2018 4

该论文的作者表明,在步骤(b)中学习卷展编码器在能够很好地处理不完美模型学习中起着重要作用。

具有无模型微调的基于模型的 RL(MBMF)

在 2017 年题为基于模型的深度强化学习的神经网络动力学与无模型微调的论文中,作者展示了另一种结合无模型和基于模型的 RL 的方法。他们研究了运动任务的领域。训练机器人运动的无模型方法具有高样本复杂性,这是我们在所有基于深度学习的模型中看到的。作者将无模型和基于模型的方法结合起来,提出了样本高效模型,以使用中等复杂性的神经网络来学习具有不同任务目标的运动动力学。**

**问题假设奖励函数rt=r(sta t )给定。本文给出了前进轨迹跟随的奖励函数的例子。轨迹由稀疏的路点显示,这些路点定义了机器人需要遵循的路径。航路点是给定轨迹上的点,当用线连接时,接近给定轨迹,如图 10-3 所示。有关如何将航路点用于轨迹规划的更多详细信息,请参考任何关于自动驾驶汽车和机器人的文本。

img/502835_1_En_10_Fig3_HTML.jpg

图 10-3

轨迹和航路点

在任何一点上,机器人计划的行动顺序都要考虑。对于每个动作,连接当前状态$$ {\hat{s}}_t $$和估计的下一状态$$ {\hat{s}}_{t+1}={\hat{f}}_{\uptheta}\left({\hat{s}}_t,{a}_t\right) $$的线段被投影到连接轨迹的两个连续航路点的最近线段上。如果机器人沿着航路点线段移动,奖励为正,垂直于航路点线段移动,奖励为负。类似地,他们对前进的目标有另一套奖励函数。

神经网络正在学习动力学$$ {\hat{s}}_{t+1}={\hat{f}}_{\uptheta}\left({\hat{s}}_t,{a}_t\right) $$。但是,网络不是预测 s * t * + 1 ,而是取( s * t a t )并预测差值st+1-st*。预测差异而不是整个新状态会放大变化,并允许捕捉微小的变化。

奖励函数和动态模型被馈送到模型预测控制器 (MPC)。MPC 接受奖励和下一个状态,使用预测的 s t + 1 作为预测st+2的新输入,提前计划 H 步骤,以此类推。 K 个随机生成的动作序列,每个长度为 H 步,进行评估,根据 K 个序列中最高的累积奖励,找到初始时间步 t 的最佳动作。然后机器人采取动作at。此时,样本序列被丢弃,并且在时间 t + 1 完成下一个 K 序列的完全重新计划,每个序列的长度为 H 。使用该模型的预测控制器(而不是开环方法)确保误差不会向前传播。每一步都有重新计划。图 10-4 给出了完整算法。

MODEL-BASED REINFORCEMENT LEARNING

img/502835_1_En_10_Fig4_HTML.png

图 10-4

MBMF 算法

一旦模型被训练,为了进一步改进模型,作者通过初始化一个无模型代理来微调它,该代理具有如前面所示训练的基于模型的学习者。无模型训练可以用我们以前学过的任何一种方法来完成。这就是将其命名为基于模型,无模型微调 (MBMF)的原因。

基于模型的价值扩展(MBVE)

在 2018 年题为“基于模型的价值扩展,用于高效的无模型强化学习”的论文中, 5 作者采用了将基于模型和无模型的方法与已知的奖励函数相结合的方法,以获得更有纪律的价值估计方法。

该算法通过学习系统动力学$$ {\hat{s}}_{t+1}={\hat{f}}_{\uptheta}\left({\hat{s}}_t,{a}_t\right) $$来模拟轨迹的短期视界,通过 Q 学习来估计长期值。这通过为训练提供更高质量的目标值来改进 Q 学习。

作者显示了将 MBVE 与 DDPG 组合与普通 DDPG 相比的性能,并证明了通过将 MBVE 与 DDPG 组合的显著改善。

模仿学习和逆向强化学习

还有一个学习分支叫做模仿学习。通过它,我们可以记录专家的互动,然后使用监督设置来学习可以模仿专家的行为。

如图 10-5 所示,我们有一个专家查看状态st并产生动作 a t 。我们在监督设置中使用该数据,其中状态 s t 作为模型的输入,动作 a t 作为学习策略πθ(at|st的目标这是学习一种行为最简单的方法,叫做行为克隆。它甚至比强化学习的整个学科还要简单。系统/学习者不分析或推理任何事情;它只是盲目地学习模仿专家的行为。

img/502835_1_En_10_Fig5_HTML.jpg

图 10-5

专家论证

然而,学习并不完美。假设你学习了一个近乎完美的政策,但有一些小错误,假设你从状态 s 1 开始。你遵循学到的策略采取行动a1,这与专家会采取的行动有一点小偏差。你不断地按顺序采取这些行动,有些与专家的行动一致,有些则与专家的行动稍有偏差。这些多次动作的偏差会累积起来,将您的车辆(图 10-5 )带到道路边缘。现在,最有可能的是,这位专家不会把车开得那么糟糕,开到路的边缘。专家训练数据从未看到专家在这种情况下会做什么。这项政策没有受过这种情况的训练。学习到的策略将最有可能采取随机的行动,并且绝对不是为了纠正错误以将车辆带回到道路中心而设计的。

这是一个开环问题。每一个动作的误差都在增加,使得实际轨迹偏离专家轨迹,如图 10-6 所示。

img/502835_1_En_10_Fig6_HTML.jpg

图 10-6

轨迹随时间漂移

这也被称为分布转移。换句话说,策略训练中的状态分布不同于代理在没有纠正反馈的开放循环中执行策略时看到的状态分布。

在这种情况下,有附加误差,有一种替代算法称为 DAgger (用于“数据集聚合”),其中通过首先根据专家演示的数据训练代理来迭代训练代理。经过训练的策略用于生成附加状态。然后,专家为这些生成的状态提供正确的操作。增加的数据与原始数据一起再次用于微调策略。如此循环下去,代理人在跟随行为方面越来越接近专家。这可以归类为直接政策学习。图 10-7 给出了匕首的伪代码。

DAGGER

img/502835_1_En_10_Fig7_HTML.png

图 10-7

行为克隆匕首

DAgger 有一个人类专家标记看不见的状态,因为 Figure 10-7 策略被执行,它训练代理从错误/漂移中恢复。匕首简单高效。然而,它只是行为克隆而不是强化学习。除了试图学习一种行为来跟随专家的动作之外,它不考虑任何事情。如果专家覆盖了代理可能看到的状态空间的很大一部分,这个算法可以帮助代理学习一个好的行为。然而,任何需要长期规划的事情都不适合 DAgger。

如果不是强化学习,为什么在这里说?事实证明,在许多情况下,让专家进行演示是理解代理试图实现的目标的好方法。当与其他增强相结合时,模仿学习是一种有用的方法。我们将讨论这样一个问题。

到目前为止,在本书中我们已经研究了各种算法来训练代理。对于一些算法,我们知道动态和转换,如基于模型的设置,在其他算法中,我们在无模型设置中学习,而没有显式学习模型,最后在其他算法中,我们从与世界的交互中学习模型,以增强无模型学习。然而,在所有这些情况下,我们认为奖励是简单的,直观的,众所周知的。在其他一些情况下,我们可以手工制作一个简单的奖励函数,例如,学习使用 MBMF 跟踪一个轨迹。但所有现实世界的案例都没有这么简单。奖励有时是不明确的和/或稀疏的。在没有明确奖励的情况下,以前的算法都不起作用。

考虑这样一种情况,你正试图训练一个机器人拿起一壶水,把水倒在一个玻璃杯里。整个序列中每个动作的回报是什么?当机器人能够将水倒入玻璃杯,而不会将水洒在桌子上或打碎/掉落水壶/玻璃杯时,它会是 1 吗?还是会根据洒了多少水来定义一个奖励范围?你将如何诱导一种行为让机器人像人类一样学习流畅的动作?你能想出一种奖励,能够给机器人提供正确的反馈,告诉它什么是好的一步,什么是坏的一步吗?

现在看看另一种情况。机器人可以看到人类完成倒水的任务。它不是学习一种行为,而是首先学习一种奖励功能,将所有与人类行为匹配的行为记录为好,其他行为为坏,好的程度取决于与它看到的人类行为的偏离程度。然后,作为下一步,它可以使用所学习的奖励函数来学习执行类似动作的策略/动作序列。这是逆强化学习 再加上模仿学习的领域。

表 10-1 比较了行为克隆、直接策略学习和反向 RL。

表 10-1

模仿学习的类型

|   |

直接政策学习

|

奖励学习

|

接触环境

|

交互式演示者/专家

|

预先收集的演示

|
| --- | --- | --- | --- | --- | --- |
| 行为克隆 | 是 | 不 | 不 | 不 | 是 |
| 直接政策学习 | 是 | 不 | 是 | 是 | 可选择的 |
| 逆向强化学习 | 不 | 是 | 是 | 不 | 是 |

反向强化学习是 MDP 设置,其中我们知道模型动态,但我们不知道奖励函数。数学上,我们可以表达如下:

$$ Given:D=\left{{\uptau}_1,{\uptau}_2,\dots .,{\uptau}_m\right}=\left{\left({s}_0i,{a}_0i,\dots .{s}_TI,{a}_Ti\right)\right}\sim {\uppi}^{\ast } $$

$$ \mathrm{The}\ \mathrm{goal}\ \mathrm{is}\ \mathrm{to}\ \mathrm{learn}\ \mathrm{a}\ \mathrm{reward}\ \mathrm{function}\kern0.5em {r}^{\ast}\mathrm{so}\ \mathrm{that} $$

$$ {\uppi}^{\ast }= argma{x}_{\uppi}{E}_{\uppi}\left[{r}^{\ast}\left(s,a\right)\right] $$

图 10-8 显示了逆 RL 的高级伪代码。我们从专家那里收集样本轨迹,并用它们来学习奖励函数。接下来,使用学习到的奖励函数,我们学习一个使奖励最大化的策略。将学习到的策略与专家进行比较,并使用差异来调整学习到的奖励。如此循环下去。

INVERSE RL

img/502835_1_En_10_Fig8_HTML.png

图 10-8

逆向强化学习

请注意,内部 do 循环有一个迭代学习策略的步骤(步骤 2,“学习策略给定奖励函数”)。它实际上是一个抽象成单行伪代码的循环。当状态空间是连续的和高维的,以及当系统动力学未知时,我们需要调整以使之前的方法工作。在 2016 年题为“引导成本学习:通过政策优化进行深度逆最优控制”的论文中, 6 作者使用了最大熵逆 RL 7 的基于样本的近似。您可以查看之前引用的论文以了解更多详细信息。图 10-9 显示了该方法的高级示意图。

img/502835_1_En_10_Fig9_HTML.jpg

图 10-9

引导成本学习:通过政策优化的深度逆最优控制

该架构可以比作来自深度学习的生成对抗网络 (GANs)。在 GAN 中,发生器网络试图产生合成样本,鉴别器网络试图给实际样本打高分,而给合成样本打低分。生成器试图更好地生成越来越难以从“真实世界的例子”中区分的例子,而鉴别器在区分合成和真实世界的例子方面变得越来越好。

同样,图 10-9 中给出的引导成本学习可以被认为是一个 GAN 设置,其中鉴别器在给予人类观察高回报的同时给予政策网络生成的行动/轨迹低回报。政策网络在产生类似人类专家的行动方面变得越来越好。

结合模仿学习的逆向学习有很多用途。

  • 制作动画电影中的角色:可以随着角色说的话同步移动面部和嘴唇。人类专家的面部/嘴唇运动首先被记录下来(除了演示),一个策略被训练成使角色的面部/嘴唇像人类一样运动。

  • 词性标注:这是基于一些专家/人工标注。

  • 平滑模仿学习:让一个自主摄像机跟随篮球这样的比赛,类似于人类操作会做的事情,跟随球穿过球场,根据某些事件缩放和平移。

  • 协调的多智能体模仿学习:观看足球比赛(人类专家示范)的录像,然后学习一个策略,根据序列预测球员的下一个位置。

模仿学习是一个迅速扩展的领域。我们只是简单地介绍了一下这个话题。有很多好地方可以开始探索这些主题。关于模仿学习的 ICML2018 教程是一个很好的起点。这是一个两小时的视频教程,由加州理工学院的两位专家提供幻灯片。

无导数方法

让我们回到我们在本书主要部分看到的常规无模型 RL。在这一节中,我们将简要地谈一谈允许我们改进政策的方法,而不用对政策参数θ求导π θ ( a | s )。

我们将看看进化的方法。它们为什么会进化?因为它们像自然进化一样运作。更好/更适合的东西生存下来,更弱的东西逐渐消失。

我们看到的第一种方法叫做交叉熵方法。这简单得令人尴尬。

  1. 选择一个随机政策。

  2. 展开几个会话。

  3. 选择一定比例的奖励较高的会议。

  4. 改进策略以增加选择这些操作的概率。

图 10-10 给出了训练一个连续动作策略的交叉熵方法的伪代码,假设该策略是一个具有 d 维动作空间的正态分布。也可以使用任何其他分布,但是对于许多领域,已经表明正态分布在平衡分布的可表达性和分布的参数数量方面是最好的。

CROSS-ENTROPY METHOD

img/502835_1_En_10_Fig10_HTML.png

图 10-10

交叉熵方法

一种叫做协方差矩阵自适应进化策略 (CMA-ES)的类似方法在图形世界中很流行,用于优化角色的步态。在交叉熵方法中,我们将对角线高斯拟合到前 k%的展开。然而,在 CMA-ES 中,我们优化了协方差矩阵,与通常的导数方法中的一阶模型学习相比,它相当于二阶模型学习。

交叉熵方法的一个主要缺点是,它们对于相对低维的动作空间,如CartPoleLunarLander等,工作得很好。进化策略可以为具有高维行动空间的深度网络策略服务吗?在 2017 年题为“进化策略作为强化学习的可扩展替代方案”的论文中, 9 作者表明,es 可以可靠地训练神经网络策略,这种方式非常适合于扩大到现代分布式计算机系统,用于在 MuJoCo 物理模拟器中控制机器人。

让我们从概念上介绍一下本文中采用的方法。考虑一个政策参数的概率分布:θ∽Pμ(θ)。这里θ表示策略的参数,这些参数遵循μ参数化的某种概率分布Pμ(θ)。

我们的目标是找到政策参数 θ ,使其生成最大化累积回报的轨迹。这类似于我们对政策梯度方法的目标。

$$ Goal:\mathit{\operatorname{maximize}}{E}_{\uptheta \sim {P}_{\upmu}\left(\uptheta \right),\uptau \sim {\uppi}_{\uptheta}}\left[R\left(\uptau \right)\right] $$

与政策梯度一样,我们做随机梯度上升,但与政策梯度不同,我们不在θ中这样做,而是在μ空间中这样做。

$$ {\nabla}_{\upmu}{E}_{\uptheta \sim {P}_{\upmu}\left(\uptheta \right),\uptau \sim {\uppi}_{\uptheta}}\left[R\left(\uptau \right)\right]={E}_{\uptheta \sim {P}_{\upmu}\left(\uptheta \right),\uptau \sim {\uppi}_{\uptheta}}\left[{\nabla}_{\upmu}\mathit{\log}{P}_{\upmu}\left(\uptheta \right)R\left(\uptau \right)\right] $$

(10.1)

前面的表达式类似于我们在政策梯度中看到的。然而,有一个微妙的区别。我们不是在θ上做梯度步长。因此,我们不用担心π θ ( a | s )。我们忽略了关于轨迹的大部分信息,即状态、动作和奖励。我们只担心政策参数θ和总轨迹报酬 R (τ)。这反过来支持非常可扩展的分布式培训,类似于运行多个工作人员的 A3C 方法,如图 7-10 所示。

我们来举一个具体的例子。假设θPμ(θ)是均值为μ的高斯分布,协方差矩阵为σ 2。那么( 10.1 )中“期望”内部给出的logPμ(θ)可以表示如下:

$$ \mathit{\log}{P}_{\upmu}\left(\uptheta \right)=-\frac{{\left|\left|\uptheta -\upmu \right|\right|}²}{2{\upsigma}²}+ const $$

取上一表达式相对于μ的梯度,我们得到:

$$ {\nabla}_{\upmu}\mathit{\log}{P}_{\upmu}\left(\uptheta \right)=\frac{\uptheta -\upmu}{\upsigma²} $$

假设我们绘制两个参数样本θ 1 和θ 2 ,得到两个轨迹:τ 1 和τ 2

$$ {E}_{\uptheta \sim {P}_{\upmu}\left(\uptheta \right),\uptau \sim {\uppi}_{\uptheta}}\left[{\nabla}_{\upmu}\mathit{\log}{P}_{\upmu}\left(\uptheta \right)R\left(\uptau \right)\right]\approx \frac{1}{2}\left[R\left({\uptau}_1\right)\frac{\uptheta_1-\mu }{\upsigma¹}+R\left({\uptau}_2\right)\frac{\uptheta_2-\mu }{\upsigma²}\right] $$

(10.2)

这仅仅是将( 10.1 中的期望值转换为基于两个样本的估计值。你能解释方程式( 10.2 )吗?这个分析类似于我们在第七章中所做的。如果轨迹的回报是+ve,我们调整均值μ以接近θ。如果轨迹回报是-ve,我们将μ从采样的θ移开。换句话说,就像政策梯度一样,我们调整μ来增加好轨迹的概率,减少坏轨迹的概率。然而,我们这样做是通过直接调整参数来源的分布,而不是调整政策所依赖的θ。这允许我们忽略状态和动作等的细节。

之前引用的论文使用了对立采样。换句话说,它采样一对带有镜像噪声的策略(θ+=μ+σϵθ-=μ-σϵ),然后采样两个轨迹τ + 和τ-以评估(10.2 将这些代入( 10.2 ,表达式可简化如下:

$$ {\nabla}_{\upmu}E\left[R\left(\uptau \right)\right]\approx \frac{\upepsilon}{2\upsigma}\left[R\left({\uptau}_{+}\right)+R\left({\uptau}_{-}\right)\right] $$

前面的操作允许在工人和参数服务器之间有效地传递参数。开始的时候,μ是已知的,只需要沟通ϵ,减少了需要来回传递的参数数量。它在使方法并行化方面带来了显著的可伸缩性。

图 10-11 显示了并行化的进化策略伪代码。

PARALLELIZED EVOLUTION STRATEGIES

img/502835_1_En_10_Fig11_HTML.png

图 10-11

并行化进化策略算法 2 10

论文作者报告了以下内容:

  • 他们发现,使用虚拟批量标准化和神经网络策略的其他重新参数化大大提高了进化策略的可靠性。

  • 他们发现进化策略方法是高度并行的(如前所述)。特别是,使用 1,440 名工人,它能够在不到 10 分钟的时间内解决 MuJoCo 3D 人形任务。

  • 进化策略的数据效率出奇的好。一个小时的 ES 结果需要的计算量与公布的异步优势因素评论家(A3C)一天的结果大致相同。在 MuJoCo 任务上,我们能够匹配 TRPO 学习到的政策表现。

  • ES 表现出比 TRPO 等策略梯度方法更好的探索行为。在 MuJoCo 人形任务上,ES 已经能够学习各种各样的步态(比如侧着走或者倒着走)。TRPO 从未观察到这些不寻常的步态,这表明了一种性质不同的探索行为。

  • 他们发现进化策略方法是稳健的。

如果你有兴趣,你应该通读参考文件,深入了解细节,看看它与其他方法相比如何。

迁移学习和多任务学习

在前几章中,我们研究了如何使用 DQN 和策略梯度算法来训练代理人玩 Atari 游戏。如果你查阅展示这些实验的论文,你会注意到一些 Atari 游戏更容易训练,而一些则更难训练。如果你看看 Atari 游戏《突围》与《蒙特祖马的复仇》,如图 10-12 所示,你会注意到,与训练蒙特祖马相比,《突围》更容易训练。这是为什么呢?

img/502835_1_En_10_Fig12_HTML.jpg

图 10-12

雅达利游戏简单易学

突围有简单的规则。但是,图 10-12 右侧蒙特祖马的复仇有一些复杂的规则。它们不容易学。作为一个人,即使我们是第一次玩这个游戏,并且事先不知道确切的规则,我们知道“钥匙”是我们通常用来打开新东西和/或获得大奖励的东西。我们知道“梯子”可以用来爬上爬下,而“头骨”是要避免的。换句话说,我们过去玩过其他游戏或读过寻宝游戏或看过一些电影的经验给了我们背景,或以前的学习,让我们快速执行我们可能从未见过的新任务。

事先了解问题结构可以帮助我们快速解决新的复杂任务。当代理解决先前的任务时,它获得有用的知识,可以帮助代理解决新的任务。但是这些知识储存在哪里呢?以下是一些可能的选项:

  • Q 函数:它们告诉我们什么是好的状态和行为。

  • 政策:它们告诉我们哪些行为是有用的,哪些是无用的。

  • 模型:它们编纂了关于世界如何运转的知识,比如牛顿定律、摩擦、重力、动量等物理定律。

  • 特征/隐藏状态:神经网络的隐藏层抽象出更高层次的结构和知识,这些结构和知识可以在不同的领域/任务中推广。我们在监督学习的计算机视觉中看到了这一点。

利用从一组任务中获得的经验来更快、更有效地完成新任务的能力称为迁移学习,即将从过去经验中获得的知识转移到新任务中。我们看到这在监督学习中有着重要的应用,特别是在计算机视觉领域,在 ImageNet 数据集上训练的 ResNet 等流行的卷积网络架构被用作预训练网络来训练新的视觉任务。我们将简要地看一下这项技术如何应用于强化学习领域。让我们定义一些迁移学习文献中常见的术语。

  • 任务:在 RL 中,我们试图训练代理解决的 MDP 问题。

  • 源域:首先训练代理的问题。

  • 目标领域:我们希望通过利用来自“源领域”的知识来更快解决的 MDP

  • Shot :目标域的尝试次数。

  • 0-shot :直接在目标域上运行在源域上训练的策略。

  • 1-shot :在目标域上只重新培训一次经过源域培训的代理。

  • 少量:在目标域上对源域的训练有素的代理进行几次再培训。

接下来,让我们看看如何在强化学习的背景下将从源领域获得的知识转移到目标领域。在这一点上,已经尝试了三大类方法。

  • 前转:训练一个任务,转到新任务。

  • 多任务转移:多任务训练,转移到新任务。

  • 传送型号和值 功能

我们将简要讨论每一种方法。正向迁移是监督学习中,尤其是计算机视觉中最常见的知识迁移方式之一。一个模型,如流行的架构 ResNet,被训练来对 ImageNet 数据集上的图像进行分类。这叫做预训练。然后通过替换最后一层或最后几层来改变训练的模型。该网络接受了一项名为微调的新任务的再培训。然而,强化学习中的正向迁移会面临一些领域转换的问题。换句话说,在源域中学习的表征在目标域中可能不太适用。此外,MDP 也有所不同。换句话说,某些事情在源域中是可能的,而在目标域中是不可能的。还有微调的问题。例如,在源域上训练的策略可能在概率分布上具有尖锐的峰值,几乎接近确定性。这种 pekad 分布可能会妨碍对目标域进行微调时的探索。

监督学习中的迁移学习似乎工作得很好,这可能是因为 ImageNet 数据集中的大量不同图像有助于网络学习非常好的通用表示,然后可以针对特定任务进行微调。然而,在强化学习中,任务通常很少多样化,这使得代理更难学习高级概括。此外,还存在策略过于确定的问题,这阻碍了在微调期间探索更好的收敛。这个问题可以通过使策略在具有熵正则项的目标的源域上学习来处理,这是我们在前面章节的几个示例中看到的,例如第八章中的软行为者批评(SAC)。熵正则化确保在源系统上学习的策略保留足够的随机性,以允许在微调期间进行探索。

还有另一种方法可以使源领域的学习更加通用。我们可以给源域添加一些随机化。比方说,我们正在训练一个机器人去完成某项任务;我们可以用机器人每个手臂的不同质量或摩擦系数来实例化源域的许多版本。这将诱导代理学习底层物理,而不是记忆在特定配置中做得好。在另一个涉及图像的真实世界设置中,我们可以再次借鉴计算机视觉的“图像增强”实践,其中我们用一些随机旋转、缩放等来增强训练图像。

接下来,我们来看看多任务迁移,这是迁移学习的第二种方法。这里有两个关键的想法:加速一起执行的所有任务的学习,并解决多个任务,以便为目标领域提供更好的预训练。

一种在多任务上训练代理的简单方法是用表示特定任务的代码/指示符来扩充状态,例如,将状态 S 扩展为( S + 指示符)。当一集开始时,系统随机选择一个 MDP(任务),然后根据初始状态分布选择初始状态。然后像其他 MDP 一样进行训练。图 10-13 显示了这种方法的示意图。这种方法有时会很困难。想象一下,一项政策在解决特定的 MDP 方面变得越来越好;优化将开始以牺牲他人为代价优先学习那个 MDP。

img/502835_1_En_10_Fig13_HTML.jpg

图 10-13

一起解决多项任务

我们也可以训练代理分别解决不同的任务,然后结合这些学习来解决一个新的任务。我们需要以某种方式将从不同任务中学到的策略组合/提炼成一个单一的策略。有多种方法可以实现这一点,我们在这里就不赘述了。

让我们看看多任务转移的另一种变化:单个代理在同一环境中学习两个不同的任务。一个例子是一个机器人学习洗衣服以及清洗餐具。在这种方法中,我们增加状态来添加任务的上下文并训练代理。这就是所谓的背景政策。使用这种方法,状态表示如下:

$$ \overset{\sim }{s}=\left[\begin{array}{c}s\ {}\omega \end{array}\right] $$

我们学习的政策如下:

$$ {\uppi}_{\uptheta}\left(a|s,\upomega \right) $$

这里,ω是上下文,即任务。

最后,还有第三种迁移学习方法,即迁移模型或价值函数。在这个设置中,我们假设源域和目标域中的动态p(st+1|sta t )是相同的。但是,奖励函数是不同的;例如,自动驾驶汽车学习驾驶到一小组地方(源域)。然后,它必须导航到新的目的地(目标域)。我们可以转移三者中的任何一个:模型、价值函数或策略。模型是一个合乎逻辑的选择并且简单的转移,因为模型p(st+1|s|ta t )原则上是独立于奖励函数的。转移策略通常是通过上下文策略来完成的,但是由于策略πθ(a|s)包含的关于动态函数的信息最少,因此转移策略并不容易。价值函数传递也不是那么简单。价值函数耦合了动力、回报和政策。

img/502835_1_En_10_Figa_HTML.jpg

我们可以使用后继特征和表征进行价值转移,其细节可以在详述迁移学习的文本中找到。

元学习

我们已经研究了让代理从经验中学习的各种方法。但这是人类学习的方式吗?当前的人工智能系统擅长掌握一项技能。我们有像 AlphaGo 这样的代理人,他们打败了最好的人类围棋选手。IBM Watson 击败了最佳人类危险游戏选手。但是 AlphaGo 会玩桥牌这种卡牌游戏吗?危险代理可以进行智能聊天吗?特技飞行的专家直升机操控员可以用来执行救援任务吗?相比之下,人类学会在许多新的情况下聪明地行动,利用他们过去的经验或当前的专业知识。

如果我们希望代理能够获得跨不同领域的许多技能,我们不能以数据低效的方式为每个特定任务训练代理——当前 RL 代理的方式。为了获得真正的人工智能,我们需要代理能够通过利用他们过去的经验来快速学习新任务。这种学习的方法被称为元学习。这是使 RL 智能体像人一样,根据经验不断学习和提高,有效地完成各种任务的关键一步。

元学习系统在许多不同的任务(元训练集)上被训练,然后在新的任务上被测试。术语元学习迁移学习可能会混淆。这些是不断发展的学科,术语的使用并不一致。然而,区分它们的一个简单方法是将元学习视为学习优化模型的超参数(例如,节点数量、架构),将迁移学习视为微调已经调好的网络。

在元学习期间,有两种优化在起作用:学习者,它学习(优化)任务;以及元学习者,它训练(优化)代理。元学习方法有三大类。

  • 循环模型(Recurrent models):这种方法通过元训练集中的任务片段来训练一种循环模型,如长短期记忆(LSTM)。

  • 度量学习:在这种方法中,代理学习一个新的度量空间,在这个空间中学习是有效的。

  • 学习优化者:在这种方法中,有两个网络:元学习和学习者。元学习者学习更新学习者网络。元学习者可以被认为是学习优化模型的超参数,而学习者网络可以被认为是用于预测动作的规则网络。

这是一个很有潜力的有趣的研究领域。感兴趣的读者可以参考机器学习国际会议(ICML-2019)的一篇精彩教程。 11

流行的 RL 图书馆

我们将简要地看一下用于强化学习的流行库。有典型的三类:实现一些流行环境的库,负责深度学习的库,以及实现我们在本书中看到的许多流行 RL 算法的库。

就增强现实环境而言,开放体育馆 12 是迄今为止最受欢迎的图书馆。有不同类型的环境,从简单的经典控制到雅达利游戏到机器人。如果你想使用雅达利游戏,你将需要额外的安装。有一个流行的库,使用一个叫做 MuJoCo 的物理模拟器来执行连续控制任务。MuJoCo 13 需要付费许可。然而,它提供了一个免费的试用许可证以及一个免费的学生许可证。雅达利的游戏也需要额外安装,但都是免费的。

我们已经尝试了健身房的一些环境。作为下一步的一部分,你应该看看许多其他环境,尝试各种学习算法,无论是手工制作的还是流行的 RL 库中的实现。

接下来是深度学习库。有很多,但最受欢迎的是谷歌的 TensorFlow 和脸书的 PyTorch。另一个库,来自深层思想的十四行诗, 14 建立在 TensorFlow 之上,抽象常见模式。Apache MXNet 是另一个流行的库。还有一些,但提到的是目前最受欢迎的。我们建议您掌握其中一个流行的库,然后分支学习其他流行的深度学习库。目前,PyTorch 正在获得超过 TensorFlow 的巨大牵引力。你可以选择这两个中的任何一个作为你的第一个深度学习库,并在扩展到其他库之前先掌握它。

最后一类是实现通用 RL 算法的流行库。我们在这里列出了受欢迎的几个:

  • open ai Spinning Up15:这是进一步扩展您的知识并深入了解实现基础的理想选择。本书中的一些代码演练是 Spinning Up 实现的修改版本。

  • OpenAI 基线 16 : OpenAI 基线是一套强化学习算法的高质量实现。这些算法使研究界更容易复制、提炼和识别新想法,然后创建良好的基线来进行研究。

  • 稳定基线 17 :稳定基线是基于 OpenAI 基线的 RL 算法的一组改进实现。

  • Garage18:Garage 是一个开发和评估强化学习算法的工具包。它附带了一个使用该工具包构建的最新实现库。

  • Keras RL19:Keras RL 用 Python 实现了一些最先进的深度强化学习算法,并与深度学习库 Keras 无缝集成。此外,Keras RL 与 OpenAI 健身房合作。这意味着评估和试验不同的算法很容易。

  • tensor force20:tensor force 是一个开源的深度强化学习框架,强调模块化的灵活库设计和在研究和实践中应用的简单易用性。TensorForce 建立在 Google 的 TensorFlow 框架之上,需要 Python 3。

可能还有很多,新的总是会出现。然而,前面的列表已经很大了。正如我们所说,您应该选择一个深度学习库、一个环境框架和一个 RL 实现。首先,我们推荐 TensorFlow 的 PyTorch 和 Gym and Spinning Up。

如何继续学习

深度强化学习有了很大的发展,这是今天让代理从经验中学习智能行为的最受欢迎的方法。我们希望这本书只是你的一个开始。我们希望你能继续了解这个令人兴奋的领域。您可以使用以下资源继续学习:

  • 寻找课程和在线视频来扩展知识。麻省理工学院、斯坦福大学和加州大学伯克利分校有许多在线课程,这些课程是深入研究这一学科的合理选择。有一些来自 Deep Mind 和该领域其他专家的 YouTube 视频。

  • 养成定期访问 OpenAI 和 Deep Mind 网站的习惯。他们有大量补充研究论文的材料,并且是我们在本书中看到的许多算法的基础。

  • 为强化学习领域的新论文设置一个谷歌提醒,并尝试跟踪这些论文。阅读研究论文是一门艺术,斯坦福大学的吴恩达教授就如何掌握一门学科给出了一些有用的建议;参见 https://www.youtube.com/watch?v=733m6qBH-jI .

最后,不言而喻:请跟随我们在本书中谈到的算法。深入研究构成这些算法基础的论文。尝试自己重新实现代码,或者按照本书附带的笔记本中给出的代码,或者查看实现,特别是在开放的人工智能旋转库中。

摘要

这一章是围绕强化学习的各种新兴主题的旋风之旅,我们无法在本书的核心章节中深入探讨。

我们在前面的章节里讲了很多。然而,还有同样多的主题是我们在前面的章节中无法涵盖的。这一章的目的是通过谈论这些被忽略的话题,让读者对强化学习有一个快速的 360 度的了解。因为我们有很多内容要介绍,所以重点是提供一个高层次的概述,并提供进一步深入研究的指针。

****

posted @ 2024-10-01 21:02  绝不原创的飞龙  阅读(7)  评论(0编辑  收藏  举报