ms-rl-py-merge-3

70 阅读1小时+

精通 Python 强化学习(四)

原文:annas-archive.org/md5/39faf67615f2d59edcd86792ee1fcf06

译者:飞龙

协议:CC BY-NC-SA 4.0

第十一章:第十一章:泛化与领域随机化

深度强化学习RL)已经实现了早期 AI 方法无法做到的事情,例如在围棋、Dota 2 和星际争霸 II 等游戏中击败世界冠军。然而,将 RL 应用于现实问题仍然充满挑战。实现这一目标的两个关键障碍是将训练好的策略推广到广泛的环境条件,并开发能够处理部分可观测性的策略。正如我们将在本章中看到的,这两个挑战紧密相关,我们将提出解决方案。

本章将覆盖以下内容:

  • 泛化与部分可观测性的概述

  • 泛化的领域随机化

  • 利用记忆克服部分可观测性

这些主题对理解 RL 在现实世界应用中的成功实施至关重要。让我们立即深入探讨吧!

泛化与部分可观测性的概述

正如所有机器学习一样,我们希望我们的 RL 模型不仅能在训练数据上工作,还能在测试时应对广泛的条件。然而,当你开始学习 RL 时,过拟合的概念不像在监督学习中那样被优先讨论。在本节中,我们将比较监督学习和 RL 中的过拟合与泛化,描述泛化与部分可观测性之间的密切关系,并提出一个通用方法来应对这些挑战。

监督学习中的泛化与过拟合

监督学习中最重要的目标之一,例如在图像识别和预测中,就是防止过拟合,并在未见过的数据上获得高准确性——毕竟,我们已经知道训练数据中的标签。为此,我们使用各种方法:

  • 我们为模型训练、超参数选择和模型性能评估分别使用独立的训练集、开发集和测试集。模型不应根据测试集进行修改,以确保对模型性能的公正评估。

  • 我们使用各种正则化方法,例如惩罚模型方差(例如 L1 和 L2 正则化)和丢弃法,以防止过拟合。

  • 我们尽可能使用大量数据来训练模型,这本身就具有正则化作用。在数据不足的情况下,我们利用数据增强技术生成更多数据。

  • 我们的目标是拥有一个多样化的数据集,并且该数据集与我们在模型部署后期望看到的数据分布相同。

这些概念在你学习监督学习时就会出现,并且它们构成了如何训练模型的基础。然而,在 RL 中,我们似乎并没有以相同的心态来看待过拟合。

让我们更详细地了解 RL 中的不同之处,探讨原因,以及泛化是否真的不那么重要。

RL 中的泛化与过拟合

深度监督学习众所周知需要大量的数据。但深度强化学习对数据的需求远远超过了深度监督学习,因为反馈信号中存在噪声,而且强化学习任务本身的复杂性。训练强化学习模型常常需要数十亿的数据点,并且持续多个月。由于使用物理系统生成如此庞大的数据几乎不可能,深度强化学习研究已经利用了数字环境,如仿真和视频游戏。这模糊了训练与测试之间的界限。

想一想:如果你训练一个强化学习代理来玩雅达利游戏,比如《太空入侵者》(Space Invaders),并且训练得非常好(这是大多数强化学习算法的基准测试),并且使用了大量的数据,那么如果代理在训练后玩《太空入侵者》非常好,这样有问题吗?嗯,在这种情况下,没有问题。然而,正如你可能已经意识到的,这样的训练工作流程并没有像监督学习模型训练那样采取任何措施来防止过拟合。可能你的代理已经记住了游戏中的各种场景。如果你只关心雅达利游戏,那么看起来强化学习中的过拟合似乎并不是一个问题。

当我们离开雅达利(Atari)环境,例如训练一个代理去击败人类竞争者,比如围棋、Dota 2 和星际争霸 II,过拟合开始成为一个更大的问题。正如我们在第九章中看到的,多智能体强化学习,这些代理通常是通过自我对弈进行训练的。在这种环境下,一个主要的危险是代理会过拟合彼此的策略。为了防止这种情况,我们通常会训练多个代理,并让它们分阶段地相互对战,这样一个代理就能遇到多样化的对手(从代理的视角来看就是环境),从而减少过拟合的机会。

过拟合在强化学习(RL)中成为了一个巨大的问题,远远超过了我们之前提到的两种情况,即我们在仿真环境中训练模型并将其部署到物理环境中。这是因为,无论仿真有多高保真度,它(几乎)永远不会与现实世界完全相同。这就是所谓的sim2real gap(仿真到现实的差距)。仿真涉及许多假设、简化和抽象。它只是现实世界的一个模型,正如我们都知道的,所有模型都是错误的。我们无处不在地使用模型,那为什么这一下子在强化学习中变成了一个主要问题呢?嗯,原因在于,训练和强化学习代理需要大量的数据(这也是我们最初需要仿真环境的原因),而在相似数据上长期训练任何机器学习模型都会导致过拟合。因此,强化学习模型极有可能会过拟合仿真环境中的模式和怪癖。在这种情况下,我们真的需要强化学习策略能够超越仿真环境,从而使其变得有用。这对强化学习来说是一个严峻的挑战,也是将强化学习应用于实际应用中的最大障碍之一。

sim2real 差距是一个与部分可观察性密切相关的概念。接下来我们将讨论这一联系。

泛化与部分可观察性之间的联系

我们提到过,模拟永远不可能和现实世界完全相同。这种差异可以通过以下两种形式表现出来:

  • 在一些问题中,你永远无法在模拟中得到与现实世界完全相同的观察结果。训练自动驾驶汽车就是一个例子。现实场景总是不同的。

  • 在某些问题中,你可以训练代理在与现实世界中看到的相同观察下进行操作——例如一个工厂生产规划问题,其中观察到的内容包括需求预测、当前库存水平和机器状态。如果你知道观察数据的范围,你可以设计模拟以反映这一点。然而,模拟和现实生活总会有所不同。

在前一种情况中,你的训练代理可能在模拟之外无法很好地泛化,这一点更为明显。然而,后一种情况就有些微妙了。在这种情况下,尽管观察结果相同,但两种环境之间的世界动态可能并不一致(诚然,这对于前一种情况也会是一个问题)。你能回忆起这和什么相似吗?部分可观察性。你可以将模拟与现实世界之间的差距视为部分可观察性的结果:有一个环境的状态被隐藏,影响了转移动态。所以,即使代理在模拟和现实世界中做出相同的观察,它并未看到这个我们假设用来捕捉两者差异的隐藏状态。

正因为有这种联系,我们在本章中将泛化和部分可观察性一并讨论。话虽如此,即使在完全可观察的环境中,泛化仍然可能是一个问题,而即便泛化不是主要问题,我们也可能需要处理部分可观察性。我们稍后会探讨这些维度。

接下来,让我们简要讨论如何解决这些挑战,然后再深入到后续章节的细节中。

通过记忆克服部分可观察性

你还记得第一次进入高中教室时的感觉吗?很可能有很多新面孔,你希望交朋友。然而,你不会仅凭第一印象就接近别人。尽管第一印象确实能让我们了解一些关于人的信息,但它仅仅反映了他们的一部分。你真正想做的是随着时间的推移进行观察,然后再做判断。

在强化学习(RL)背景下情况类似。以下是来自 Atari 游戏《Breakout》的一个例子:

图 11.1 – Atari 游戏的单一帧画面提供了环境的部分观察

图 11.1 – Atari 游戏的单一帧画面提供了环境的部分观察

从单一游戏场景中并不十分清楚球的移动方向。如果我们有来自先前时刻的另一张快照,我们就能估算球的位置变化和速度变化。再多一张快照,能够帮助我们估算速度变化、加速度等等。因此,当环境是部分可观察的时,根据不仅是单一观察,而是一系列观察采取行动,会导致更有依据的决策。换句话说,拥有记忆使得 RL 代理能够发现单一观察中不可见的部分。

在 RL 模型中,有多种方式可以保持记忆,我们将在本章稍后详细讨论这些方法。

在此之前,让我们简要讨论如何克服过拟合。

通过随机化克服过拟合

如果你是一名司机,想想你是如何在不同条件下获得驾驶经验的。你可能开过小车、大车、加速快慢不一的车、车身高低不同的车,等等。此外,你可能还在雨天、雪天、沥青路面和碎石路面上开过车。我个人就有过这些经历。所以,当我第一次试驾特斯拉 Model S 时,刚开始确实感觉是一种完全不同的体验。但几分钟后,我就习惯了,并开始驾驶得相当舒适。

现在,作为一名司机,我们通常无法精确地定义一辆车与另一辆车之间的差异:它们在重量、扭矩、牵引力等方面的确切差异,使得我们在面对环境时会有部分不可见的感知。但是,我们在多样化驾驶条件下的过去经验帮助我们在开车几分钟后快速适应新的环境。这是如何发生的呢?我们的脑袋能够为驾驶过程建立一个通用的物理模型(并作出相应的行为),当经历足够多样时,而不是“过拟合”某种驾驶风格到特定的车和条件上。

我们处理过拟合并实现泛化的方法在我们的 RL(强化学习)代理中是类似的。我们将使代理暴露于许多不同的环境条件,包括那些它不一定能够完全观察到的条件,这被称为领域随机化DR)。这将为代理提供超越模拟及其训练条件的泛化所必需的经验。

除此之外,我们在监督学习中使用的正则化方法对于 RL 模型也非常有帮助,正如我们将要讨论的那样。

接下来,让我们在下一节中总结与泛化相关的讨论。

泛化的配方

如今通过前面的示例,应该已经很清楚,我们实现泛化需要三种成分:

  • 多样化的环境条件帮助代理丰富其经验

  • 模型记忆帮助代理发现环境中的潜在条件,尤其是当环境是部分可观察时

  • 使用类似于监督学习中的正则化方法

现在是时候让讨论更具体,谈论如何处理泛化和部分可观测性的方法了。我们从使用域随机化进行泛化开始,然后讨论使用记忆克服部分可观测性。

域随机化用于泛化

我们之前提到过,经验的多样性有助于泛化。在强化学习(RL)中,我们通过在训练过程中随机化环境参数来实现这一点,这被称为 DR。这些参数的例子,比如对于一个搬运和操作物体的机器人手来说,可能如下:

  • 物体表面的摩擦系数

  • 重力

  • 物体的形状和重量

  • 输入执行器的功率

深度强化学习(DR)在机器人应用中尤其受欢迎,因为它能够克服仿真与现实之间的差距,因为智能体通常在仿真环境中训练,并在现实世界中部署。然而,每当涉及到泛化时,DR 是训练过程中必不可少的一部分。

为了更具体地说明环境参数是如何随机化的,我们需要讨论如何表示相同类型问题的两个环境可能会有所不同。

随机化的维度

Rivilin(2019)借鉴的,下面的章节展示了相似环境之间差异的有用分类。

相同/相似状态下的不同观察结果

在这种情况下,两个环境发出的观察结果不同,尽管底层的状态和转移函数是相同的或非常相似的。一个例子是相同的 Atari 游戏场景,但背景和纹理颜色不同。游戏状态没有变化,但观察结果有所不同。一个更现实的例子是,当训练自动驾驶汽车时,仿真中的场景呈现“卡通化”外观,而在真实摄像头输入下却呈现真实的外观,即使是完全相同的场景。

解决方案——向观察结果中添加噪声

在这些情况下,帮助泛化的方法是向观察结果中添加噪声,这样强化学习模型就能专注于那些实际重要的观察模式,而不是对无关细节进行过拟合。很快,我们将讨论一种具体的方法,叫做网络随机化,它将解决这个问题。

相同/相似的观察结果对应不同的状态

POMDP(部分可观察马尔可夫决策过程)之间的另一个区别是,当观察结果相同或相似时,底层状态实际上是不同的,这也叫做同态状态(aliased states)。一个简单的例子是两个不同版本的山地车环境,外观完全相同,但重力不同。这种情况在机器人应用中非常常见。考虑用机器人手操作物体,正如 OpenAI 的著名工作所展示的(我们将在本章后面详细讨论):摩擦、重量、输入执行器的实际功率等,可能在不同的环境版本之间有所不同,而这些差异对智能体是不可观察的。

解决方案 – 在随机化的环境参数下进行训练,并使用记忆

在这里应该采取的方法是,在多个不同版本的环境中进行训练,每个版本有不同的底层参数,并在强化学习(RL)模型中加入记忆。这将帮助智能体揭示环境的潜在特征。一个著名的例子是 OpenAI 的机器人手 manipulating objects(操作物体)在模拟中训练,容易受到模拟与现实之间差距(sim2real gap)的影响。我们可以通过随机化模拟参数来克服这种差距,适用于以下情境:

当与记忆结合使用,并且经过大多数环境条件的训练后,策略有望获得适应所处环境的能力。

同一问题类别的不同复杂度层次

这是我们本质上处理相同类型问题,但在不同复杂度层次下的情况。一个来自Rivlin2019的好例子是带有不同节点数量的旅行商问题TSP)。在这个环境中,RL 智能体的任务是在每个时间步决策下一个访问的节点,使得所有节点都被访问一次且以最小成本完成,同时在最后回到初始节点。事实上,我们在 RL 中处理的许多问题自然面临这种挑战,比如训练一个象棋智能体与不同水平的对手对战等。

解决方案 – 通过课程在不同复杂度层次下进行训练

不出所料,在不同难度层次的环境中训练智能体是实现泛化的必要条件。也就是说,使用一种从简单环境配置开始,逐渐增加难度的课程,如我们之前在书中所描述的。这将可能使学习更加高效,甚至在某些没有课程时无法实现的情况下变得可行。

现在我们已经介绍了实现不同维度泛化的高级方法,接下来将讨论一些具体的算法。但首先,让我们介绍一个用于量化泛化的基准环境。

量化泛化

有多种方法可以测试某些算法/方法在未见过的环境条件下是否比其他方法更好地进行泛化,例如以下几种:

  • 创建具有不同环境参数集的验证和测试环境

  • 在现实生活中的部署中评估策略性能

执行后者并不总是可行的,因为现实生活中的部署可能不一定是一个选项。前者的挑战是确保一致性,并确保验证/测试数据在训练过程中没有被使用。此外,当基于验证性能尝试太多模型时,也可能会对验证环境进行过拟合。克服这些挑战的一种方法是使用程序化生成的环境。为此,OpenAI 创建了 CoinRun 环境,用于基准测试算法的泛化能力。我们来更详细地了解一下。

CoinRun 环境

CoinRun 环境是一个角色尝试在没有碰到任何障碍物的情况下收集硬币的游戏。角色从最左边开始,硬币在最右边。关卡是根据底层概率分布程序化生成的,难度各异,如下所示:

图 11.2:CoinRun 中的两个关卡,难度不同(来源:Cobbe 等,2018)

图 11.2:CoinRun 中的两个关卡,难度不同(来源:Cobbe 等,2018)

以下是关于环境奖励函数和终止条件的更多细节:

  • 环境中有动态和静态障碍物,当角色与其碰撞时会死亡,从而终止该回合。

  • 只有在收集到硬币时才会给予奖励,这也会终止回合。

  • 每个回合有 1,000 步的时间限制,回合会在时间到达时终止,除非角色死亡或到达硬币。

请注意,CoinRun 环境生成的所有(训练和测试)关卡都来自相同的分布,因此它不会测试策略的超出分布(外推)性能。

接下来,我们来安装这个环境并进行实验。

安装 CoinRun 环境

你可以按照以下步骤安装 CoinRun 环境:

  1. 我们从设置和激活一个虚拟 Python 环境开始,因为 CoinRun 需要特定的包。因此,在你选择的目录中运行以下命令:

    virtualenv coinenv
    source coinenv/bin/activate
    
  2. 然后,我们安装必要的 Linux 包,包括一个著名的并行计算接口 MPI:

    sudo apt-get install mpich build-essential qt5-default pkg-config
    
  3. 然后,我们安装从 GitHub 仓库获取的 Python 依赖项和 CoinRun 包:

    git clone https://github.com/openai/coinrun.git
    cd coinrun
    pip install tensorflow==1.15.3 # or tensorflow-gpu
    pip install -r requirements.txt
    pip install -e .
    

    请注意,我们需要安装一个旧版本的 TensorFlow。官方建议使用 CoinRun 的创建者推荐的 TensorFlow 1.12.0 版本。然而,使用较新的 TensorFlow 1.x 版本可能有助于避免使用 GPU 时出现的 CUDA 冲突。

  4. 你可以使用键盘上的箭头键通过以下命令来测试环境:

    python -m coinrun.interactive
    

很棒;祝你玩得开心,玩 CoinRun 愉快!我建议你先熟悉一下这个环境,以便更好地理解我们接下来要讨论的比较。

信息

你可以访问 CoinRun 的 GitHub 仓库,获取完整的命令集:github.com/openai/coinrun

引入该环境的论文(Cobbe 等,2018)还提到了各种正则化技术如何影响 RL 中的泛化能力。我们将接下来讨论这一点,然后介绍其他的泛化方法。

正则化和网络架构对 RL 策略泛化的影响

作者发现,在监督学习中用于防止过拟合的几种技术,在强化学习(RL)中也同样有效。由于在论文中复现实验结果需要非常长的时间,每个实验需要数亿步,因此我们在此不再尝试复现。相反,我们为你提供了结果摘要和运行不同版本算法的命令。但你可以观察到,即使在 500k 时间步之后,应用我们接下来提到的正则化技术也能提高测试表现。

你可以通过以下命令查看该环境的所有训练选项:

python -m coinrun.train_agent –help

让我们从运行一个没有任何正则化应用的基准开始。

基础训练

你可以使用 PPO 和 Impala 架构训练一个 RL 代理,而不对泛化能力进行任何改进,如下所示:

python -m coinrun.train_agent --run-id BaseAgent --num-levels 500 --num-envs 60

这里,BaseAgent是你为代理决定的 ID,--num-levels 500表示训练时使用 500 个游戏关卡,并使用论文中默认的种子,--num-envs 60启动 60 个并行环境进行回滚,你可以根据机器上可用的 CPU 数量调整该参数。

为了在三个并行会话中测试训练好的代理,每个会话有 20 个并行环境,每个环境有五个关卡,你可以运行以下命令:

mpiexec -np 3 python -m coinrun.enjoy --test-eval --restore-id BaseAgent -num-eval 20 -rep 5

平均测试奖励将显示在mpi_out中。在我的情况下,奖励从训练后的 300K 时间步的约 5.5 降至测试环境中的 0.8,以给你一个大概的参考。

此外,你可以通过运行以下命令来观察你的训练代理:

python -m coinrun.enjoy --restore-id BaseAgent –hres

这实际上是很有趣的。

使用更大的网络

作者发现,正如在监督学习中一样,使用更大的神经网络通过更高的容量成功解决更多的测试场景,从而提高了泛化能力。他们还指出,然而,随着网络规模的增大,泛化能力的提升是递减的,因此泛化能力并不会随着网络规模线性提升。

要使用一个具有五个残差块的架构,而不是三个,每层中的通道数是原来的两倍,你可以添加impalalarge参数:

python -m coinrun.train_agent --run-id LargeAgent --num-levels 500 --num-envs 60 --architecture impalalarge

同样,你可以使用为大代理案例提供的运行 ID 进行测试评估。

训练数据的多样性

为了测试多样化训练数据的重要性,作者比较了两种类型的训练,均包含 256M 时间步数,跨越 100 个和 10,000 个游戏关卡(通过--num-levels控制)。使用更多样化的数据后,测试性能从 30%提升到 90%以上(这也与训练性能相当)。

提示

增加数据多样性在监督学习和强化学习(RL)中起到了正则化的作用。这是因为随着多样性的增加,模型必须在相同的模型容量下解释更多的变化,从而迫使模型利用其容量专注于输入中的最重要模式,而不是过拟合噪声。

这强调了环境参数随机化在实现泛化中的重要性,稍后我们将在本章中单独讨论这一点。

Dropout 和 L2 正则化

实验结果表明,dropout 和 L2 正则化都能改善泛化能力,分别增加了约 5%和 8%的成功率,基准测试性能约为 79%。

提示

如果您需要复习 dropout 和 L2 正则化,可以查看 Chitta Ranjan 的博客:towardsdatascience.com/simplified-math-behind-dropout-in-deep-learning-6d50f3f47275

我们可以通过以下方式更详细地探讨这个问题:

  • 从经验上来看,作者发现最佳的 L2 权重为 ,最佳的 dropout 率为 0.1。

  • 从经验来看,L2 正则化对泛化的影响比 dropout 更大。

  • 如预期所示,使用 dropout 的训练收敛速度较慢,因此分配了两倍的时间步数(512M)。

要在普通代理的基础上使用 0.1 的 dropout 率,可以使用以下命令:

python -m coinrun.train_agent --run-id AgentDOut01 --num-levels 500 --num-envs 60 --dropout 0.1

同样,要使用 L2 正则化,权重为 0.0001,可以执行以下操作:

python -m coinrun.train_agent --run-id AgentL2_00001 --num-levels 500 --num-envs 60 --l2-weight 0.0001

在您的 TensorFlow RL 模型中,您可以通过以下方式使用Dropout层来添加 dropout:

from tensorflow.keras import layers 
...
x = layers.Dense(512, activation="relu")(x) 
x = layers.Dropout(0.1)(x)
...

要添加 L2 正则化,可以做类似以下操作:

from tensorflow.keras import regularizers
...
x = layers.Dense(512, activation="relu", kernel_regularizer=regularizers.l2(0.0001))(x) 

信息

TensorFlow 有一个非常好的关于过拟合和欠拟合的教程,您可以查看:www.tensorflow.org/tutorials/keras/overfit_and_underfit

接下来,让我们讨论数据增强。

使用数据增强

一种常见的防止过拟合的方法是数据增强,即对输入进行修改/扭曲,通常是随机的,以增加训练数据的多样性。当应用于图像时,这些技术包括随机裁剪、改变亮度和锐度等。以下是使用数据增强的 CoinRun 场景示例:

图 11.3 – 带数据增强的 CoinRun(来源:Cobbe 等,2018 年)

图 11.3 – 带数据增强的 CoinRun(来源:Cobbe 等,2018 年)

信息

关于数据增强的 TensorFlow 教程,请查看www.tensorflow.org/tutorials/images/data_augmentation

数据增强,事实证明,在强化学习(RL)中也很有帮助,它能够提升测试性能,虽然略逊色于 L2 正则化。

使用批量归一化

批量归一化是深度学习中的关键工具之一,它有助于稳定训练并防止过拟合。

信息

如果你需要复习批量归一化的知识,可以查看 Chris Versloot 的博客:bit.ly/3kjzjno

在 CoinRun 环境中,你可以通过如下方式启用训练中的批量归一化层:

python -m coinrun.train_agent --run-id AgentL2_00001 --num-levels 500 --num-envs 60 --use-data-augmentation 1

这将在每个卷积层后添加一个批量归一化层。

当你实现自己的 TensorFlow 模型时,批量归一化层的语法是layers.BatchNormalization(),并且可以传入一些可选的参数。

报告结果显示,使用批量归一化在所有其他正则化方法中(除了增加训练数据的多样性)能为测试性能提供第二大的提升。

添加随机性

最后,向环境中引入随机性/噪声被证明是最有用的泛化技术,能够将测试性能提高约 10%。本文在训练中使用了两种方法与 PPO 算法:

  • 使用!-贪婪策略(通常与 Q 学习方法一起使用)

  • 在 PPO 中增加熵奖励系数(),以鼓励策略提出的动作具有更多的方差

这些方法的良好超参数选择分别是!。需要注意的是,如果环境的动态已经高度随机化,那么引入更多的随机性可能没有那么显著的影响。

结合所有方法

在训练过程中同时使用所有这些正则化方法,仅稍微改善了单个方法带来的提升,表明这些方法各自都在防止过拟合方面起到了类似的作用。需要注意的是,无法确定这些方法对所有 RL 问题的影响完全相同。但我们需要记住的是,传统的监督学习正则化方法也能显著影响 RL 策略的泛化能力。

这就结束了我们关于 RL 的基本正则化技术的讨论。接下来,我们将研究另一种方法,这种方法紧随原始 CoinRun 论文之后,即网络随机化。

网络随机化和特征匹配

网络随机化,由 Lee 等人于 2020 年提出,简单地涉及使用观测的随机变换,!,如下所示:

然后,变换后的观察值作为输入被送入 RL 算法中使用的常规策略网络。在这里,是此变换的参数,每次训练迭代时都会随机初始化。通过在输入层之后添加一个不可训练并且定期重新初始化的层,可以简单实现这一点。在 TensorFlow 2 中,可以按如下方式实现一个在每次调用后转换输入的随机化层:

class RndDense(tf.keras.layers.Layer):
    def __init__(self, units=32):
        super(RndDense, self).__init__()
        self.units = units
    def build(self, input_shape):  
        self.w_init = tf.keras.initializers.GlorotNormal()
        self.w_shape = (input_shape[-1], self.units)
        self.w = tf.Variable(
            initial_value=self.w_init(shape=self.w_shape, dtype="float32"),
            trainable=True,
        )
    def call(self, inputs):  
        self.w.assign(self.w_init(shape=self.w_shape, dtype="float32"))
        return tf.nn.relu(tf.matmul(inputs, self.w))

请注意,这个自定义层具有以下特性:

  • 权重是不可训练的

  • 在每次调用时为层分配随机权重

对该架构的进一步改进是进行两次前向传递,一次带有随机输入,一次不带,并强制网络给出相似的输出。这可以通过向 RL 目标添加一个损失来实现,该损失惩罚输出差异:

在这里,是策略网络的参数,而是策略网络中倒数第二层(即在输出动作的层之前的那一层)。这被称为特征匹配,它使得网络能够区分输入中的噪声和信号。

信息

该架构在 CoinRun 环境中的 TensorFlow 1.x 实现可以在github.com/pokaxpoka/netrand找到。通过将random_ppo2.pyppo2.py进行比较,并将random_impala_cnn方法与impala_cnn方法在policies.py中的对比,您可以将其与原始的 CoinRun 环境进行比较。

回到我们之前提到的泛化维度,网络随机化有助于 RL 策略在这三个维度上进行泛化。

接下来,我们将讨论一种实现泛化的关键方法,该方法在现实生活中已被证明有效。

泛化的课程学习

我们已经讨论过,丰富的训练经验有助于 RL 策略更好地泛化。假设在您的机器人应用中,您已识别出需要在环境中随机化的两个参数,并为它们指定了最小值和最大值:

  • 摩擦:

  • 执行器扭矩:

这里的目标是使代理准备好在测试时应对具有未知摩擦-扭矩组合的环境。

事实证明,正如我们在上一章讨论课程学习时提到的,训练可能会导致一个平庸的智能体,如果你在一开始就尝试在这些参数的整个范围内进行训练。这是因为参数范围的极端值可能对尚未掌握任务基础的智能体来说过于具有挑战性(假设这些极端值围绕着某些合理的参数值)。课程学习的核心思想是从简单的场景开始,比如第一课可以是 ,然后通过扩展范围逐步增加难度。

接下来,一个关键问题是我们应该如何构建课程,课程应该是什么样子(也就是说,当智能体在当前课题中成功后,下一步的参数范围应是什么),以及何时宣布当前课题的成功。在本节中,我们将讨论两种自动生成和管理课程的课程学习方法,以有效进行领域随机化。

自动领域随机化

自动领域随机化ADR)是 OpenAI 在其研究使用机器人手操控魔方时提出的一种方法。这是 RL 在机器人技术应用中最成功的案例之一,原因有很多:

  • 灵活的机器人由于其高度的自由度,控制起来 notoriously 困难。

  • 策略完全在仿真中训练,然后成功地转移到物理机器人上,成功弥合了模拟与现实之间的差距。

  • 在测试时,机器人成功地在训练时未曾见过的条件下完成任务,例如手指被绑住、戴上橡胶手套、与各种物体发生扰动等。

    这些结果在训练策略的泛化能力方面是非常出色的。

    信息

    你应该查看这篇关于这一重要研究的博客文章,openai.com/blog/solving-rubiks-cube/。它包含了很好的可视化和对结果的深刻见解。

ADR 是该应用成功的关键方法。接下来,我们将讨论 ADR 是如何工作的。

ADR 算法

我们在训练过程中创建的每个环境都会对某些参数进行随机化,例如在前面的例子中,摩擦力和扭矩。为了正式表示这一点,我们说一个环境,,是由来参数化的,其中是参数的数量(在这个例子中为 2)。当一个环境被创建时,我们从一个分布中抽取。ADR 调整的是参数分布的,从而改变不同参数样本的可能性,使得环境变得更难或更容易,具体取决于智能体在当前难度下是否成功。

一个例子,,将包括每个参数维度的均匀分布,,其中有。与我们的例子相联系,对应摩擦系数,。然后,对于初始课程,我们将有。对于扭矩参数也类似,。然后,变为以下内容:

ADR 建议如下:

  • 随着训练的进行,分配一些环境用于评估,以决定是否更新

  • 在每个评估环境中,选择一个维度,,然后选择上限或下限之一进行关注,例如

  • 将选定维度的环境参数固定在选定的边界上。其余参数从中抽样。

  • 评估智能体在给定环境中的表现,并将该回合中获得的总奖励保存在与维度和边界相关的缓冲区中(例如,)。

  • 当缓冲区中的结果足够时,将平均奖励与您事先设定的成功和失败阈值进行比较。

  • 如果给定维度和边界的平均表现高于您的成功阈值,则扩展该维度的参数范围;如果低于失败阈值,则缩小范围。

总结来说,ADR 系统地评估每个参数维度在参数范围边界上的智能体表现,然后根据智能体的表现扩大或缩小范围。您可以参考论文中的伪代码,它应该与前面的解释配合使用时很容易理解。

接下来,我们将讨论另一种重要的自动课程生成方法。

使用高斯混合模型的绝对学习进展

另一种自动课程生成的方法是使用高斯混合模型的绝对学习进展ALP-GMM)方法。该方法的本质如下:

  • 用于识别环境参数空间中表现出最多学习进展的部分(称为 ALP 值)

  • 为了将多个 GMM 模型拟合到 ALP 数据上,使用个核,然后选择最佳的一个

  • 从最佳 GMM 模型中采样环境参数

这个想法源于认知科学,用来建模婴儿早期的语言发展。

新采样的参数向量的 ALP 分数,,计算公式如下:

这里,是通过获得的回合奖励,是先前回合获得的最接近的参数向量,是与相关的回合奖励。所有的对都保存在一个数据库中,表示为,通过它来计算 ALP 分数。然而,GMM 模型是使用最新的对来获得的。

请注意,具有较高 ALP 分数的参数空间部分更有可能被采样以生成新环境。高 ALP 分数表明该区域有学习潜力,可以通过观察新采样的下回合奖励的大幅下降或增加来获得。

信息

ALP-GMM 论文的代码库可以在github.com/flowersteam/teachDeepRL找到,其中还包含了展示算法如何工作的动画。由于篇幅限制,我们无法在这里详细讲解代码库,但我强烈建议你查看实现和结果。

最后,我们将提供一些关于泛化的额外资源,供进一步阅读。

Sunblaze 环境

本书中无法覆盖所有的泛化方法,但一个有用的资源是 Packer & Gao, 2019 年发布的博客,介绍了 Sunblaze 环境,旨在系统地评估强化学习(RL)的泛化方法。这些环境是经典的 OpenAI Gym 环境的修改版,经过参数化以测试算法的插值和外推性能。

信息

你可以在bair.berkeley.edu/blog/2019/03/18/rl-generalization/找到描述 Sunblaze 环境及其结果的博客文章。

做得非常棒!你已经了解了有关真实世界强化学习中最重要的主题之一!接下来,我们将讨论一个紧密相关的话题,即克服部分可观察性。

利用记忆克服部分可观察性

在本章开始时,我们描述了内存作为一种处理部分可观察性的有用结构。我们还提到过,泛化问题往往可以看作是部分可观察性的结果:

  • 一个区分两个环境(例如模拟环境与真实世界)的隐藏状态可以通过内存揭示出来。

  • 当我们实现领域随机化时,我们的目标是创建许多版本的训练环境,其中我们希望代理为世界动态建立一个总体模型。

  • 通过内存,我们希望代理能够识别它所在环境的特征,即使它在训练过程中没有看到过这个特定的环境,然后相应地进行行动。

现在,模型的内存不过是将一系列观察作为输入处理的方式。如果你曾经处理过其他类型的序列数据与神经网络结合的任务,比如时间序列预测或自然语言处理NLP),你可以采用类似的方法,将观察内存作为 RL 模型的输入。

让我们更详细地了解如何实现这一点。

堆叠观察

将观察序列传递给模型的一种简单方法是将它们拼接在一起,并将这个堆叠视为单一的观察。将时间点上的观察表示为,记作,我们可以形成一个新的观察,,并将其传递给模型,如下所示:

这里,是内存的长度。当然,对于,我们需要以某种方式初始化内存的早期部分,例如使用与维度相同的零向量。

事实上,简单地堆叠观察就是原始 DQN 工作处理 Atari 环境中部分可观察性的方法。更详细地说,该预处理的步骤如下:

  1. 获取一个重新缩放的 RGB 屏幕帧。

  2. 提取 Y 通道(亮度)进一步将帧压缩成图像。这样就得到了一个单一的观察

  3. 最新的帧被拼接成一个图像,形成一个具有内存的模型观察

请注意,只有最后一步涉及内存,前面的步骤并不是严格必要的。

这种方法的明显优点是非常简单,生成的模型也很容易训练。然而,缺点是,这并不是处理序列数据的最佳方法,如果你曾经处理过时间序列问题或 NLP,应该不会对这一点感到惊讶。以下是一个原因的示例。

想象一下你对虚拟语音助手(如苹果的 Siri)说的以下句子:

“帮我买一张从旧金山到波士顿的机票。”

这与以下说法是相同的:

"给我买一张从旧金山到波士顿的机票"

假设每个单词都被传递到输入神经元,神经网络很难将它们轻松地理解为相同的句子,因为通常每个输入神经元期望特定的输入。在这种结构中,你需要使用该句子的所有不同组合来训练网络。更复杂的是,输入大小是固定的,但每个句子的长度可能不同。你也可以将这个思路扩展到强化学习问题中。

现在,在大多数问题中,堆叠观察值就足够了,比如 Atari 游戏。但是如果你试图教会你的模型如何玩 Dota 2 这种策略视频游戏,那么你就会遇到困难。

幸运的是,递归 神经 网络RNN)来救场了。

使用 RNN

RNN 设计用于处理序列数据。一个著名的 RNN 变体,长短期记忆LSTM)网络,能够有效地训练来处理长序列。当涉及到处理复杂环境中的部分可观察性时,LSTM 通常是首选:它被应用于 OpenAI 的 Dota 2 和 DeepMind 的 StarCraft II 模型中,当然还有许多其他模型。

信息

详细描述 RNN 和 LSTM 如何工作的内容超出了本章的范围。如果你想了解更多关于它们的知识,Christopher Olah 的博客是一个不错的资源:colah.github.io/posts/2015-08-Understanding-LSTMs/

在使用 RLlib 时,可以按如下方式启用 LSTM 层,比如在使用 PPO 时,并在默认值的基础上进行一些可选的超参数调整:

import ray
from ray.tune.logger import pretty_print
from ray.rllib.agents.ppo.ppo import PPOTrainer
from ray.rllib.agents.ppo.ppo import DEFAULT_CONFIG
config = DEFAULT_CONFIG.copy()
config["model"]["use_lstm"] = True
# The length of the input sequence
config["model"]["max_seq_len"] = 8
# Size of the hidden state
config["model"]["lstm_cell_size"] = 64
# Whether to use
config["model"]["lstm_use_prev_action_reward"] = True

请注意,输入首先被传递到 RLlib 中的(预处理)"模型",该模型通常是由一系列全连接层组成。预处理的输出随后会传递给 LSTM 层。

全连接层的超参数也可以类似地被覆盖:

config["model"]["fcnet_hiddens"] = [32]
config["model"]["fcnet_activation"] = "linear"

在配置文件中指定环境为 Gym 环境名称或自定义环境类后,配置字典将传递给训练器类:

from ray.tune.registry import register_env
def env_creator(env_config):
    return MyEnv(env_config)    # return an env instance
register_env("my_env", env_creator)
config["env"] = "my_env"
ray.init()
trainer = PPOTrainer(config=config)
while True:
    results = trainer.train()
    print(pretty_print(results))
    if results["timesteps_total"] >= MAX_STEPS:
        break
print(trainer.save())

使用 LSTM 模型时,有几个需要注意的事项,与简单地堆叠观察值不同:

  • 由于需要对多步输入进行顺序处理,LSTM 的训练通常较慢。

  • 与前馈网络相比,训练 LSTM 可能需要更多的数据。

  • 你的 LSTM 模型可能对超参数更敏感,因此你可能需要进行一些超参数调优。

说到超参数,如果你的训练在像 PPO 这样的算法上进展不顺利,以下是一些可以尝试的值:

  • 学习率(config["lr"]):

  • LSTM 单元大小(config["model"]["lstm_cell_size"]):64,128,256。

  • 值网络和策略网络之间的层共享(config["vf_share_layers"]):如果你的回报是几百或更多,请尝试将其设为假,以防值函数损失主导策略损失。或者,你也可以减少 config["vf_loss_coeff"]

  • 熵系数(config["entropy_coeff"]):

  • 将奖励和之前的动作作为输入(config["model"]["lstm_use_prev_action_reward"]):尝试将其设为真,以便在观察之外为智能体提供更多信息。

  • 预处理模型架构(config["model"]["fcnet_hiddens"]config["model"]["fcnet_activation"]):尝试使用单一线性层。

希望这些内容有助于为你的模型构建一个良好的架构。

最后,我们来讨论一下最流行的架构之一:Transformer。

Transformer 架构

在过去几年中,Transformer 架构基本上取代了 RNNs 在自然语言处理(NLP)应用中的地位。

Transformer 架构相较于最常用的 RNN 类型 LSTM,具有几个优势:

  • LSTM 编码器将从输入序列中获得的所有信息压缩到一个单一的嵌入层,然后传递给解码器。这在编码器和解码器之间创建了一个瓶颈。而 Transformer 模型允许解码器查看输入序列的每个元素(准确来说,是查看它们的嵌入)。

  • 由于 LSTM 依赖时间反向传播,梯度很可能在更新过程中爆炸或消失。而 Transformer 模型则同时查看每个输入步骤,并不会遇到类似的问题。

  • 结果是,Transformer 模型能够有效地处理更长的输入序列。

正因为如此,Transformer 也有可能成为强化学习应用中,RNNs 的竞争替代方案。

信息

如果你想了解该主题的更多内容,Jay Alammar 提供了一篇关于 Transformer 架构的优秀教程,网址为 jalammar.github.io/illustrated-transformer/

尽管原始的 Transformer 模型有其优势,但已被证明在强化学习应用中不稳定。已有改进方案被提出(Parisotto et al., 2019),名为Gated Transformer-XLGTrXL)。

RLlib 已将 GTrXL 实现为自定义模型。它可以如下使用:

...
from ray.rllib.models.tf.attention_net import GTrXLNet
...
config["model"] = {
    "custom_model": GTrXLNet,
    "max_seq_len": 50,
    "custom_model_config": {
        "num_transformer_units": 1,
        "attn_dim": 64,
        "num_heads": 2,
        "memory_tau": 50,
        "head_dim": 32,
        "ff_hidden_dim": 32,
    },
}

这为我们提供了另一个强大的架构,可以在 RLlib 中尝试。

恭喜你!我们已经到达本章的结尾!我们已经涵盖了几个重要主题,这些内容远比我们的篇幅所能表达的要更为深刻。请继续阅读参考文献部分的来源,并尝试我们介绍的仓库,以加深你对该主题的理解。

总结

在本章中,我们涵盖了强化学习中的一个重要主题:泛化和部分可观测性,这对于现实世界的应用至关重要。请注意,这是一个活跃的研究领域:保持我们在这里的讨论作为建议,并尝试为您的问题尝试的第一种方法。新方法定期推出,所以请留意。重要的是,您应始终关注泛化和部分可观测性,以便在视频游戏之外成功实施强化学习。在下一节中,我们将通过元学习将我们的探险提升到另一个高级水平。所以,请继续关注!

参考文献

第十二章:第十二章:元强化学习

强化学习RL)智能体相比,人类从较少的数据中学习新技能。这是因为首先,我们出生时大脑中就带有先验知识;其次,我们能够高效地将一种技能的知识迁移到另一种技能上。元强化学习(Meta-RL)旨在实现类似的能力。在本章中,我们将描述什么是元强化学习,我们使用了哪些方法,以及在以下主题下面临的挑战:

  • 元强化学习简介

  • 带有递归策略的元强化学习

  • 基于梯度的元强化学习

  • 元强化学习作为部分观测强化学习

  • 元强化学习中的挑战

元强化学习简介

在本章中,我们将介绍元强化学习,这实际上是一个非常直观的概念,但一开始可能比较难以理解。为了让你更加清楚,我们还将讨论元强化学习与前几章中涉及的其他概念之间的联系。

学会学习

假设你正在说服一个朋友一起去你非常想去的旅行。你脑海中浮现出了几个论点。你可以谈论以下几点:

  • 你目的地自然风光的美丽。

  • 你已经精疲力尽,真的很需要这段时间休息。

  • 这可能是你们一起旅行的最后机会,因为你将会很忙于工作。

好吧,你已经认识你的朋友很多年了,知道他们有多喜欢大自然,所以你意识到第一个论点将是最具吸引力的,因为他们喜欢大自然!如果是你的妈妈,也许你可以用第二个论点,因为她非常关心你,并且希望支持你。在这些情况下,你知道如何达成你想要的目标,因为你和这些人有共同的过去。

你曾经在一家商店离开时,比如去车行,买了比原计划更贵的东西吗?你是怎么被说服的?也许销售员猜出了你在乎以下几点:

  • 你的家人,并说服你购买一辆让他们更舒适的 SUV

  • 你的外表,并说服你购买一辆能吸引众人目光的跑车

  • 环境,并说服你购买一辆没有排放的电动汽车

销售员不了解你,但通过多年的经验和培训,他们知道如何迅速有效地了解你。他们问你问题,了解你的背景,发现你的兴趣,弄清楚你的预算。然后,他们会给你提供一些选项,根据你的喜好和不喜欢,最终会给你一个定制的报价套餐,包括品牌、型号、升级选项和支付计划。

在这里,前面的例子对应强化学习(RL),其中智能体为特定环境和任务学习一个好的策略,以最大化其奖励。后面的例子则对应元强化学习(Meta-RL),其中智能体学习一个好的过程,以快速适应新的环境和任务,从而最大化奖励。

在这个例子之后,让我们正式定义元强化学习(meta-RL)。

定义元强化学习(meta-RL)。

在元强化学习中,每一轮,智能体面临一个任务 ,该任务来自一个分布 。任务 是一个马尔可夫 决策 过程MDP),可能具有不同的转移和奖励动态,描述为 ,其中包括以下内容:

  • 是状态空间。

  • 是动作空间。

  • 是任务 的转移分布。

  • 是任务 的奖励函数。

因此,在训练和测试期间,我们期望任务来自相同的分布,但我们并不期望它们完全相同,这正是典型机器学习问题中的设定。在元强化学习中,在测试时,我们期望智能体做以下事情:

  1. 有效地探索以理解任务。

  2. 适应任务。

元学习(meta-learning)是嵌入在动物学习中的。接下来让我们探索这种联系。

与动物学习及哈洛实验的关系

人工神经网络 notoriously 需要大量数据来训练。另一方面,我们的大脑能够从少量数据中更高效地学习。这主要有两个因素:

  • 与未经训练的人工神经网络不同,我们的大脑是预训练的,且内嵌了视觉、听觉和运动技能任务的先验。一些尤其令人印象深刻的预训练生物是初生性动物,例如鸭子,它们的小鸭子在孵化后的两小时内便能下水。

  • 当我们学习新任务时,我们在两个时间尺度上学习:在快速循环中,我们学习关于当前任务的具体内容;而如我们将看到的更多例子,在慢速循环中,我们学习抽象,这有助于我们将知识快速泛化到新的例子中。假设你学习某一特定的猫品种,例如美式卷耳猫,且你见到的所有例子都呈现白色和黄色。当你看到一只黑色的这种品种猫时,你不会觉得难以辨认。这是因为你已经发展出一种抽象的知识,帮助你通过这种猫独特的耳朵(向后卷曲)识别它,而非通过它的颜色。

机器学习中的一个大挑战是使得能够从类似先前情况的少量数据中学习。为了模仿步骤 1,我们会对训练好的模型进行微调,适应新任务。例如,一个在通用语料库(如维基百科页面、新闻文章等)上训练的语言模型,可以在一个专业语料库(如海事法)上进行微调,尽管可用的数据量有限。步骤 2 就是元学习的核心内容。

小贴士

经验上,对于新任务进行微调训练的模型,在强化学习(RL)中并不像在图像或语言任务中那样有效。事实证明,强化学习策略的神经网络表示不像图像识别中的那样层次化,例如,在图像识别中,第一层检测边缘,最后一层检测完整的物体。

为了更好地理解动物中的元学习能力,我们来看一个典型的例子:哈罗实验。

哈罗实验

哈罗实验探讨了动物中的元学习,涉及一只猴子,它一次被展示两个物体:

  • 这些物体中的一个与食物奖励相关联,猴子需要发现这一点。

  • 在每一步(共六步)中,物体被随机放置在左侧或右侧位置。

  • 猴子必须学会根据物体本身,而不是物体的位置,来判断哪个物体给它带来奖励。

  • 在六个步骤结束后,物体被替换为猴子不熟悉的新物体,并且这些物体与未知的奖励关联。

  • 猴子在第一步中学会了随机挑选一个物体,理解哪个物体给它带来奖励,并在剩下的步骤中根据物体是否带来奖励而选择这个物体,而不考虑物体的位置。

这个实验很好地表达了动物中的元学习能力,因为它涉及到以下内容:

  • 对于智能体来说,这是一个不熟悉的环境/任务

  • 智能体通过一种策略有效适应不熟悉的环境/任务,这种策略包括必要的探索,实时制定特定任务的策略(根据与奖励相关联的物体做出选择,而非其位置),然后是利用阶段。

元强化学习的目标与此相似,正如我们稍后会看到的那样。现在,让我们继续探索元强化学习与我们已经涵盖的其他概念的关系。

与部分可观测性和领域随机化的关系

元强化学习程序的主要目标之一是在测试时揭示潜在的环境/任务。根据定义,这意味着环境是部分可观测的,而元强化学习是一种专门应对这一问题的方法。

在上一章,第十一章泛化与部分可观测性中,我们讨论了处理部分可观测性时需要使用记忆和领域随机化。那么,元强化学习(meta-RL)有什么不同呢?嗯,记忆依然是元强化学习中一个关键的工具。我们还在训练元强化学习智能体时对环境/任务进行随机化,这类似于领域随机化。此时,它们可能对你来说似乎没有区别。然而,存在一个关键的区别:

  • 在领域随机化中,训练智能体的目标是为环境的所有变化开发一个健壮的策略,涵盖一系列参数范围。例如,一个机器人可以在一系列摩擦力和扭矩值下进行训练。在测试时,基于一系列携带信息和扭矩的观察,智能体使用训练好的策略采取行动。

  • 在元强化学习中,训练智能体的目标是为新的环境/任务开发一个适应程序,这可能会导致在探索阶段后测试时使用不同的策略。

在基于记忆的元强化学习方法中,差异仍然可能很微妙,并且在某些情况下,训练过程可能是相同的。为了更好地理解这种差异,请记住哈洛实验:领域随机化的想法不适用于该实验,因为每一集展示给智能体的对象在每次都是全新的。因此,智能体在元强化学习中并不是学习如何在一系列对象上采取行动。而是它学习如何发现任务,并在展示完全新对象时相应地采取行动。

现在,终于是时候讨论几种元强化学习方法了。

信息

元学习的先驱之一是斯坦福大学的切尔西·芬教授,她在博士阶段曾与加州大学伯克利分校的谢尔盖·莱文教授合作。芬教授开设了一门关于元学习的课程,课程链接在cs330.stanford.edu/。在本章中,我们主要遵循芬教授和莱文教授使用的元强化学习方法的术语和分类。

接下来,让我们从使用递归策略的元强化学习开始。

使用递归策略的元强化学习

在本节中,我们将介绍元强化学习中一种更直观的方法,该方法使用递归 神经 网络RNNs)来保持记忆,也被称为 RL-2 算法。让我们从一个示例开始,理解这种方法。

网格世界示例

假设有一个网格世界,智能体的任务是从起始状态 S 到达目标状态 G。这些状态在不同任务中是随机放置的,因此智能体必须学会探索世界,以发现目标状态在哪里,并在此后获得丰厚奖励。当同一任务被重复时,期望智能体能够快速到达目标状态,这也就是适应环境,因为每经过一步就会遭受惩罚。这个过程在图 12.1中有所示意:

图 12.1 – 元强化学习的网格世界示例。(a)任务,(b)智能体对任务的探索,(c)智能体利用其所学的知识

图 12.1 – 元强化学习的网格世界示例。(a)任务,(b)智能体对任务的探索,(c)智能体利用其所学的知识

为了在任务中表现出色,智能体必须执行以下操作:

  1. 探索环境(在测试时)。

  2. 记住并利用其先前学到的知识。

现在,由于我们希望智能体记住其先前的经验,我们需要引入一个记忆机制,意味着使用递归神经网络(RNN)来表示策略。有几个要点需要注意:

  1. 仅仅记住过去的观察值是不够的,因为目标状态在不同任务之间是变化的。智能体还需要记住过去的动作和奖励,以便能够关联在特定状态下采取的哪些动作导致了哪些奖励,从而揭示任务的规律。

  2. 仅仅记住当前回合中的历史是不够的。请注意,一旦智能体达到目标状态,回合就结束了。如果我们不将记忆传递到下一个回合,智能体将无法从前一个回合中获得的经验中受益。再者,请注意,在这里没有进行任何训练或更新策略网络的权重。这一切都发生在测试时,在一个未知的任务中。

处理前者很简单:我们只需要将动作和奖励与观察值一起喂给 RNN。为了处理后者,我们确保除非任务发生变化,否则在回合之间不会重置循环状态,以确保记忆不会中断。

现在,在训练过程中,为什么智能体会学习一个明确地以探索阶段开始新任务的策略呢?那是因为探索阶段帮助智能体发现任务,并在后续收获更高的奖励。如果我们在训练过程中仅基于单个回合奖励智能体,智能体将不会学到这种行为。因为探索是有即时成本的,这些成本只有在后续回合中才能回收,且当相同任务的记忆跨回合传递时才会得到补偿。为此,我们形成了元回合试验,即!是将多个同一任务的回合串联起来的过程。再次强调,在每个回合内,循环状态不会被重置,奖励是根据元回合来计算的。如图 12.2所示:

图 12.2 – 智能体与环境交互的过程(来源:Duan et al., 2017)

接下来,让我们看看如何在 RLlib 中实现这一点。

RLlib 实现

关于我们之前提到的,元回合可以通过修改环境来形成,因此与 RLlib 并没有直接关系。至于其他部分,我们在智能体配置中的模型字典里进行修改:

  1. 首先,我们启用长短期记忆LSTM)模型:

    "use_lstm": True
    
  2. 我们将动作和奖励与观察值一起传递给 LSTM:

    "lstm_use_prev_action_reward": True
    
  3. 我们确保 LSTM 输入序列足够长,能够覆盖一个元任务中的多个回合。默认的序列长度为 20

    max_seq_len": 20
    

就是这些!你只需要通过几行代码更改,就可以训练你的元强化学习智能体!

信息

该过程可能并不总是收敛,或者即使收敛,也可能收敛到不好的策略。可以尝试多次训练(使用不同的种子)并调整超参数设置,这可能会帮助你获得一个好的策略,但这并不能保证成功。

这样,我们可以继续使用基于梯度的方法。

基于梯度的元强化学习

基于梯度的元强化学习方法提出,通过在测试时继续训练来改进策略,使策略能够适应所应用的环境。关键在于,策略参数在适应之前的状态,,应该设置成一种能够在少数几步内完成适应的方式。

提示

基于梯度的元强化学习(meta-RL)基于一个思想:某些策略参数的初始化可以使得在适应过程中,从非常少的数据中进行学习。元训练过程旨在找到这种初始化。

这个分支中的一个特定方法叫做无模型元学习MAML),它是一种通用的元学习方法,也可以应用于强化学习。MAML 训练智能体完成各种任务,以找到一个有助于适应和从少量样本中学习的良好 值。

让我们来看一下如何使用 RLlib 来实现这一点。

RLlib 实现

MAML 是 RLlib 中实现的一个智能体,并且可以很容易地与 Ray 的 Tune 一起使用:

tune.run(
    "MAML",
    config=dict(
        DEFAULT_CONFIG,
        ...
    )
)

使用 MAML 需要在环境中实现一些额外的方法。这些方法分别是 sample_tasksset_taskget_task,它们有助于在各种任务之间进行训练。一个示例实现可以是一个摆环境,RLlib 中的实现如下 (github.com/ray-project/ray/blob/releases/1.0.0/rllib/examples/env/pendulum_mass.py):

class PendulumMassEnv(PendulumEnv, gym.utils.EzPickle, MetaEnv):
    """PendulumMassEnv varies the weight of the pendulum
    Tasks are defined to be weight uniformly sampled between [0.5,2]
    """
    def sample_tasks(self, n_tasks):
        # Mass is a random float between 0.5 and 2
        return np.random.uniform(low=0.5, high=2.0, size=(n_tasks, ))
    def set_task(self, task):
        """
        Args:
            task: task of the meta-learning environment
        """
        self.m = task
    def get_task(self):
        """
        Returns:
            task: task of the meta-learning environment
        """
        return self.m

在训练 MAML 时,RLlib 会通过 episode_reward_mean 测量智能体在任何适应之前的环境表现。经过 N 次梯度适应步骤后的表现会显示在 episode_reward_mean_adapt_N 中。这些内部适应步骤的次数是智能体的一个配置项,可以修改:

"inner_adaptation_steps": 1

在训练过程中,您可以在 TensorBoard 上看到这些指标:

图 12.3 – TensorBoard 统计数据

图 12.3 – TensorBoard 统计数据

就这样!现在,让我们介绍本章的最后一种方法。

元强化学习作为部分观测强化学习

元强化学习中的另一种方法是专注于任务的部分可观测特性,并明确地从直到此时为止的观测中估计状态:

然后,基于任务在该回合中处于活动状态的可能性,或者更准确地说,基于包含任务信息的向量,形成一个可能任务的概率分布:

然后,从这个概率分布中迭代地抽取任务向量,并将其与状态一起传递给策略:

  1. 抽样

  2. 从接收状态和任务向量作为输入的策略中采取行动,

有了这些内容,我们就结束了对三种主要元强化学习方法的讨论。在总结本章之前,让我们讨论一些元强化学习中的挑战。

元强化学习中的挑战

关于元强化学习的主要挑战,参见 Rakelly2019,如下所示:

  • 元强化学习需要在多个任务上进行元训练,这些任务通常是手工设计的。一个挑战是创建一个自动化过程来生成这些任务。

  • 在元训练过程中,应该学习的探索阶段实际上并未有效学习。

  • 元训练涉及从独立同分布的任务中进行采样,而这一假设并不现实。因此,一个目标是通过让元强化学习从任务流中学习,使其变得更加“在线”。

恭喜你走到这一步!我们刚刚讨论了元强化学习,这是一个可能很难吸收的概念。希望这次介绍能给你勇气深入阅读相关文献,并进一步探索这个话题。

总结

在这一章中,我们讨论了元强化学习(meta-RL),它是强化学习领域最重要的研究方向之一,因为它的潜力是训练能够快速适应新环境的智能体。为此,我们介绍了三种方法:递归策略、基于梯度的方法和基于部分可观察性的策略。目前,元强化学习还处于起步阶段,其表现尚不如更成熟的方法,因此我们讨论了该领域面临的挑战。

在下一章,我们将涵盖多个高级主题,并将其集中在一章中。所以,请继续关注,以进一步加深你的强化学习(RL)专业知识。

参考文献

第十三章:第十三章:其他高级主题

本章将涵盖强化学习(RL)的几个高级主题。首先,我们将深入探讨分布式强化学习,除了之前章节中涉及的内容之外。这个领域对于处理训练智能体进行复杂任务所需的过多数据至关重要。好奇驱动的强化学习处理传统探索技术无法解决的困难探索问题。离线强化学习利用离线数据来获得良好的策略。所有这些都是热门的研究领域,您将在未来几年听到更多相关内容。

所以,在本章中,您将学习以下内容:

  • 分布式强化学习

  • 好奇驱动的强化学习

  • 离线强化学习

让我们开始吧!

分布式强化学习

正如我们在之前的章节中提到的,训练复杂的强化学习智能体需要大量的数据。虽然研究的一个关键领域是提高强化学习中的样本效率;另一个互补方向则是如何最好地利用计算能力和并行化,减少训练的实际时间和成本。我们已经在前面的章节中讨论、实现并使用了分布式强化学习算法和库。因此,本节将是对之前讨论的扩展,因为这一话题非常重要。在这里,我们将介绍更多关于最先进的分布式强化学习架构、算法和库的内容。那么,让我们从 SEED RL 开始,这是一种为大规模和高效并行化设计的架构。

可扩展、高效的深度强化学习 – SEED RL

我们从重新审视 Ape-X 架构开始讨论,它是可扩展强化学习的一个里程碑。Ape-X 的关键贡献是将学习和行动解耦:演员们按自己的节奏生成经验,学习器按自己的节奏从这些经验中学习,而演员们则定期更新他们本地的神经网络策略副本。Ape-X DQN 的流程示意图见图 13.1

图 13.1 – Ape-X DQN 架构,重新审视

图 13.1 – Ape-X DQN 架构,重新审视

现在,让我们从计算和数据通信的角度来分析这个架构:

  1. 演员们,可能有数百个,会定期从中央学习器拉取 参数,即神经网络策略。根据策略网络的大小,成千上万的数字会从学习器推送到远程演员。这会在学习器和演员之间产生很大的通信负载,远远超过传输动作和观察所需的两倍数量级。

  2. 一旦一个演员接收到策略参数,它便使用这些参数推断每个环境步骤的动作。在大多数设置中,只有学习者使用 GPU,演员则在 CPU 节点上工作。因此,在这种架构中,大量的推断必须在 CPU 上进行,相较于 GPU 推断,这种方式效率要低得多。

  3. 演员在环境和推断步骤之间切换,这两者具有不同的计算需求。将这两个步骤执行在同一节点上,要么会导致计算瓶颈(当需要推断的节点是 CPU 节点时),要么会导致资源的低效利用(当节点是 GPU 节点时,GPU 的计算能力被浪费)。

为了克服这些低效问题,SEED RL 架构提出了以下关键方案:将动作推断移至学习者端。因此,演员将观察结果发送给中央学习者(那里存储着策略参数),并接收到回传的动作。通过这种方式,推断时间被缩短,因为推断在 GPU 上进行,而不是 CPU 上。

当然,故事并未结束。我们迄今所描述的情况带来了另一组挑战:

  • 由于演员需要在每个环境步骤中将观察结果发送到远程学习者以接收动作,因此出现了延迟问题,这是之前所没有的。

  • 当演员等待动作时,它保持空闲状态,导致演员节点计算资源的低效利用

  • 将单个观察结果传递给学习者 GPU 会增加总的与 GPU 的通信开销

  • GPU 资源需要调整,以同时处理推断和学习。

为了克服这些挑战,SEED RL 具有以下结构:

  • 一种非常快速的通信协议,称为gRPC,用于在演员和学习者之间传输观察结果和动作。

  • 多个环境被放置在同一个演员上,以最大化利用率。

  • 在将观察结果传递给 GPU 之前,会对其进行批处理以减少开销。

资源分配调整是第四个挑战,但这只是一个调优问题,而非根本性的架构问题。因此,SEED RL 提出了一种架构,可以做到以下几点:

  • 每秒处理数百万条观察数据。

  • 将实验成本降低高达 80%。

  • 通过将训练速度提高三倍,减少墙钟时间。

SEED RL 架构如图 13.2所示,取自 SEED RL 论文,并将其与 IMPALA 进行了比较,IMPALA 也面临与 Ape-X 类似的缺点:

图 13.2 – IMPALA 与 SEED 架构的比较(来源:Espeholt 等人,2020)

图 13.2 – IMPALA 与 SEED 架构的比较(来源:Espeholt 等人,2020)

到目前为止,一切顺利。有关实现细节,我们推荐参考Espeholt 等人,2020以及与论文相关的代码库。

信息

作者已将 SEED RL 开源,地址为 github.com/google-research/seed_rl。该仓库包含了 IMPALA、SAC 和 R2D2 代理的实现。

我们将很快介绍 R2D2 代理,并进行一些实验。但在结束本节之前,我们还会为你提供另一个资源。

信息

如果你有兴趣深入了解架构的工程方面,gRPC 是一个非常有用的工具。它是一个快速的通信协议,广泛应用于许多科技公司的微服务之间的连接。可以在 grpc.io 查看。

做得好!你现在已经掌握了分布式强化学习的最新技术。接下来,我们将介绍一种在分布式 RL 架构中使用的最先进的模型,R2D2。

分布式强化学习中的递归经验回放

最近强化学习文献中最具影响力的贡献之一,是 递归回放分布式 DQNR2D2)代理,它在当时设定了经典基准的最新技术水平。R2D2 研究的主要贡献实际上是与 递归神经网络RNNs)在强化学习代理中的有效应用有关,且这一方法也在分布式环境中实现。论文中使用了 长短期记忆LSTM)作为 RNN 的选择,我们在接下来的讨论中也会采用这种方法。那么,首先让我们从训练 RNN 时初始化递归状态的挑战谈起,再讨论 R2D2 代理如何解决这个问题。

递归神经网络中的初始递归状态不匹配问题

在前几章中,我们讨论了携带观察记忆的重要性,以便揭示部分可观察的状态。例如,单独使用一帧 Atari 游戏画面将无法传达物体速度等信息,而基于一系列过去的画面,推算出物体速度等信息来做决策,会带来更高的奖励。如我们前面提到的,处理序列数据的有效方法是使用 RNNs。

RNN 的基本思想是将序列的输入逐一传递给同一个神经网络,同时将过去步骤的信息、记忆和摘要从一个步骤传递到下一个步骤,这一点在 图 13.3 中有所说明:

图 13.3 – RNN 的示意图,其中 a) 为紧凑表示,b) 为展开表示

图 13.3 – RNN 的示意图,其中 a) 为紧凑表示,b) 为展开表示

这里的一个关键问题是,如何为初始递归状态 进行初始化。最常见和便捷的方式是将递归状态初始化为全零。对于环境中每一步的演员来说,这并不是一个大问题,因为这个初始递归状态对应于一个回合的开始。然而,在从对应于较长轨迹小段的存储样本进行训练时,这种初始化就成了一个问题。我们来看看为什么。

请考虑图 13.4所示的场景。我们正在尝试训练一个 RNN 来处理一个存储的样本 ,因此观测值是四帧组成的序列,这些帧被传递到策略网络中。所以, 是第一帧, 是采样的 序列中的最后一帧,也是最新的帧(同样的情况适用于 )。当我们输入这些数据时, 将被获取并传递到后续步骤中,而我们对 使用零值:

图 13.4 – 使用一系列帧从 RNN 获取动作

图 13.4 – 使用一系列帧从 RNN 获取动作

现在,记住递归状态 的作用是总结直到第 步所发生的事情。当我们在训练期间使用零向量作为 时,例如在生成价值函数预测和 Q 函数的目标值时,会产生一些问题,这些问题虽然相关,但有所不同:

  • 它不会传达任何关于该时间步之前发生了什么的有意义信息。

  • 我们使用相同的向量(零向量),无论采样序列之前发生了什么,这会导致过载的表示。

  • 由于零向量不是 RNN 的输出,它本身并不是 RNN 的有意义表示。

结果是,RNN 对隐状态的处理变得“混乱”,并减少了对记忆的依赖,这违背了使用递归神经网络的初衷。

一种解决方案是记录整个轨迹,并在训练时处理/重放它,以计算每一步的递归状态。这也存在问题,因为在训练时重放所有任意长度的样本轨迹会带来大量开销。

接下来,我们来看一下 R2D2 智能体是如何解决这个问题的。

R2D2 对初始递归状态不匹配的解决方案

R2D2 智能体的解决方案是双管齐下:

  • 存储回合中的递归状态。

  • 使用烧入期。

接下来我们将更详细地探讨这些解决方案。

存储回合中的递归状态

当智能体在环境中执行时,在每一集的开始,它初始化递归状态。然后,它使用递归策略网络在每个步骤采取行动,并且每个观察对应的递归状态也会被生成。R2D2 智能体将这些递归状态与采样的经验一起发送到重放缓冲区,以便稍后在训练时用它们初始化网络,而不是用零向量。

总体而言,这显著弥补了使用零初始化的负面影响。然而,这仍然不是一个完美的解决方案:存储在重放缓冲区中的递归状态在训练时使用时会变得过时。因为网络是不断更新的,而这些状态会携带由旧版本网络生成的表示,例如在回放时使用的网络。这被称为表示漂移

为了缓解表示漂移,R2D2 提出了一种额外的机制,即在序列开始时使用预热期。

使用预热期

使用预热期的工作方式如下:

  1. 存储一个比我们通常存储的序列更长的序列。

  2. 使用序列开头的额外部分,用当前参数展开 RNN。

  3. 这样,生成一个不会过时的初始状态,适用于预热部分之后。

  4. 在反向传播时不要使用预热部分。

这在图 13.5中有所描述:

图 13.5 – 表示 R2D2 使用存储的递归状态并进行两步预热的示意图

图 13.5 – 表示 R2D2 使用存储的递归状态并进行两步预热的示意图

所以,图中的例子是说,与其使用 ,它是由某些旧策略 生成的,不如做如下操作:

  1. 使用 来在训练时初始化递归状态。

  2. 使用当前参数 展开 RNN 的递归状态,通过预热部分生成

  3. 这有望从过时的表示 中恢复,并且比 更准确地初始化。

  4. 这在精确度上更好,因为它更接近我们如果从一开始存储并展开整个轨迹,直到 ,使用 所得到的结果。

所以,这就是 R2D2 智能体。在我们结束这一部分之前,先来讨论一下 R2D2 智能体的成就。

R2D2 论文的关键结果

R2D2 的工作提供了非常有趣的见解,我强烈推荐你阅读完整的论文。然而,为了我们讨论的完整性,以下是一个总结:

  • R2D2 在 Atari 基准测试中将 Ape-X DQN 创下的先前最高纪录提高了四倍,是第一个在 57 款游戏中有 52 款取得超人类水平表现的智能体,并且具有更高的样本效率。

  • 它通过在所有环境中使用一组超参数来实现这一点,显示出智能体的强大鲁棒性。

  • 有趣的是,即便在被认为是完全可观察的环境中,R2D2 也能提高性能,而在这些环境中,通常不指望使用记忆来帮助。作者通过 LSTM 的高表示能力来解释这一点。

  • 存储递归状态并使用预热期都非常有益,其中前者的影响更大。可以将这两种方法结合使用,这是最有效的,或者单独使用。

  • 使用零初始状态会降低智能体对记忆的依赖能力。

供您参考,在五个环境中,R2D2 智能体未能超过人类级别的表现,但通过修改参数,它实际上可以实现超越。剩下的两个环境,Montezuma's Revenge 和 Pitfall,是著名的难探索问题,后续章节将进一步讨论这两个环境。

这样一来,让我们在这里总结讨论,并进入一些实践工作。下一节中,我们将使用 SEED RL 架构与 R2D2 智能体。

实验 SEED RL 和 R2D2

在本节中,我们将简要演示 SEED RL 仓库及其如何用于训练智能体。让我们从设置环境开始。

设置环境

SEED RL 架构使用多个库,如 TensorFlow 和 gRPC,它们之间以相当复杂的方式进行交互。为了简化大部分配置工作,SEED RL 的维护者使用 Docker 容器来训练 RL 智能体。

信息

Docker 和容器技术是当今互联网服务背后的基础工具。如果你从事机器学习工程,或有兴趣在生产环境中提供你的模型,了解 Docker 是必不可少的。Mumshad Mannambeth 的快速 Docker 启蒙课程可在 youtu.be/fqMOX6JJhGo 上找到。

设置说明可以在 SEED RL GitHub 页面找到。简而言之,设置步骤如下:

  1. 在你的机器上安装 Docker。

  2. 启用以非 root 用户身份运行 Docker。

  3. 安装 git

  4. 克隆 SEED 仓库。

  5. 使用 run_local.sh 脚本启动仓库中定义的环境训练,如下所示:

    ./run_local.sh [Game] [Agent] [Num. actors] 
    ./run_local.sh atari r2d2 4
    

如果你的 NVIDIA GPU 没有被 SEED 容器识别,可能需要对该设置进行一些附加配置:

一旦你的设置成功,你应该看到代理开始在 tmux 终端上训练,如图 13.6所示:

图 13.6 – SEED RL 在 tmux 终端上的训练

图 13.6 – SEED RL 在 tmux 终端上的训练

信息

Tmux 是一个终端复用器,基本上是终端内的窗口管理器。要快速了解如何使用 tmux,请查看www.hamvocke.com/blog/a-quick-and-easy-guide-to-tmux/

现在,你的机器上已经运行了 SEED,这是一款最先进的强化学习框架!你可以通过按照 Atari、Football 或 DMLab 示例文件夹中的说明,插入自定义环境进行训练。

信息

R2D2 代理也可以在 DeepMind 的 ACME 库中找到,里面还有许多其他代理:github.com/deepmind/acme

接下来,我们将讨论好奇心驱动的强化学习。

好奇心驱动的强化学习

当我们讨论 R2D2 代理时,我们提到在基准测试集中只剩下少数几个 Atari 游戏,代理在这些游戏中无法超过人类表现。代理面临的剩余挑战是解决困难探索问题,这些问题有非常稀疏和/或误导性的奖励。后续的工作来自 Google DeepMind,也解决了这些挑战,使用了名为Never Give UpNGU)和Agent57的代理,在基准测试中使用的 57 个游戏中都达到了超人类水平的表现。在本节中,我们将讨论这些代理以及它们用于有效探索的方法。

让我们从描述困难探索好奇心驱动学习的概念开始。

针对困难探索问题的好奇心驱动学习

让我们来看看图 13.7所示的简单网格世界:

图 13.7 – 一个困难探索的网格世界问题

图 13.7 – 一个困难探索的网格世界问题

假设在这个网格世界中有以下设置:

  • 总共有 102 个状态,101 个用于网格世界,1 个用于环绕它的悬崖。

  • 代理从世界的最左端开始,目标是到达最右端的奖杯。

  • 到达奖杯的奖励为 1,000,掉入悬崖的奖励为-100,每个时间步的奖励为-1,以鼓励快速探索。

  • 一个回合结束的条件是:代理到达奖杯,掉进悬崖,或经过 1,000 个时间步。

  • 在每个时间步,代理有五种可用的动作:保持静止,或向上、向下、向左或向右移动。

如果你在当前设置下训练一个代理,即使使用我们已覆盖的最强算法,如 PPO、R2D2 等,最终得到的策略可能也是自杀式的:

  • 通过随机动作很难偶然找到奖杯,因此代理可能永远不会发现网格世界中有一个高奖励的奖杯。

  • 等到回合结束会导致总奖励为-1000。

  • 在这个黑暗的世界里,智能体可能决定尽早自杀,以避免长时间的痛苦。

即使是最强大的算法,这种方法中的薄弱环节也在于通过随机动作进行探索的策略。偶然碰到最优动作集的概率是!

提示

为了计算智能体通过随机动作到达奖杯所需的预期步数,我们可以使用以下方程:

其中, 是智能体在状态 时到达奖杯所需的预期步数。我们需要为所有状态生成这些方程(对于 会有所不同),并求解结果的方程组。

在我们之前讨论机器教学方法时,我们提到过,教师可以设计奖励函数,鼓励智能体在世界中走对路。这种方法的缺点是,在更复杂的环境中,手动设计奖励函数可能不可行。事实上,教师可能连最优策略都不知道,无法引导智能体。

那么问题就变成了,如何鼓励智能体高效地探索环境呢?一个好的答案是对智能体首次访问的状态给予奖励,例如,在我们的网格世界中,给它+1 的奖励。享受发现世界的乐趣可能会成为智能体避免自杀的动力,这最终也会导致赢得奖杯。

这种方法被称为好奇心驱动学习,它通过基于观察的新奇性给智能体提供内在奖励。奖励的形式如下:

其中, 是环境在时间 给予的外在奖励, 是时间 对观察的新奇性给予的内在奖励,而 是调节探索相对重要性的超参数。

在我们讨论 NGU 和 Agent57 智能体之前,让我们深入探讨一下好奇心驱动的强化学习中的一些实际挑战。

好奇心驱动的强化学习中的挑战

上面我们提供的网格世界示例是最简单的设置之一。另一方面,我们对强化学习智能体的期望是它们能够解决许多复杂的探索问题。当然,这也带来了挑战。我们来讨论其中的几个挑战。

在观察处于连续空间和/或高维空间时评估新奇性

当我们有离散的观察时,评估一个观察是否新颖很简单:我们只需要计算智能体已经看到每个观察的次数。然而,当观察处于连续空间时,例如图像,就变得复杂,因为无法简单地进行计数。类似的挑战是,当观察空间的维度过大时,就像在图像中一样。

噪声电视问题

对于好奇心驱动的探索,一个有趣的失败状态是环境中有噪声源,比如在迷宫中播放随机画面的嘈杂电视。

图 13.8 – OpenAI 实验中展示的噪声电视问题(来源:OpenAI 等,2018)

图 13.8 – OpenAI 实验中展示的噪声电视问题(来源:OpenAI 等,2018)

然后,智能体会像很多人一样,困在嘈杂的电视前,进行无意义的探索,而不是实际地发现迷宫。

终身新颖性

如前所述,内在奖励是基于一个回合内观察到的新颖性给予的。然而,我们希望智能体能够避免在不同回合中一再做出相同的发现。换句话说,我们需要一个机制来评估终身新颖性,以实现有效的探索。

解决这些挑战有多种方法。接下来,我们将回顾 NGU 和 Agent57 智能体是如何应对这些挑战的,并探讨它们如何在经典强化学习基准测试中实现最先进的性能。

永不放弃

NGU 智能体有效地将一些关键的探索策略结合在一起。接下来我们将在以下章节中详细探讨这一点。

获取观察的嵌入

NGU 智能体通过从观察中获得嵌入方式,处理了关于 a) 高维观察空间和 b) 观察中的噪声这两个挑战。具体来说:给定从环境中采样的一个三元组 ,其中 是观察, 是时间 时的动作,它通过训练神经网络从两个连续观察中预测动作。这个过程如图 13.9所示:

图 13.9 – NGU 智能体嵌入网络

图 13.9 – NGU 智能体嵌入网络

这些嵌入,即来自 嵌入网络的图像的 维度表示,记作 ,是智能体稍后用于评估观察新颖性的依据。

如果你想知道为什么有这样一个复杂的设置来获取图像观察的低维表示,这是为了应对噪声电视问题。观察中的噪声在预测导致环境从发出观察的动作时并没有提供有用的信息。换句话说,代理执行的动作不会解释观察中的噪声。因此,我们不希望一个从观察中预测动作的网络学习到包含噪声的表示,至少不会是主导的。所以,这是一个巧妙的去噪方式,处理观察表示。

接下来,让我们看看这些表示是如何使用的。

回合新颖性模块

为了评估观察结果与回合中先前观察结果的相对新颖性,并计算一个回合内的内在奖励,NGU 代理执行以下操作:

  1. 将在一个回合中遇到的观察结果的嵌入存储在一个记忆中!

  2. -最近的嵌入在中进行比较

  3. 计算一个内在奖励,该奖励与和其邻居之间相似度之和成反比

这个想法在图 13.10中得到了说明:

图 13.10 – NGU 回合新颖性模块

图 13.10 – NGU 回合新颖性模块

为了避免有些拥挤的符号表示,我们将计算的细节留给论文,但这应该能让你大致了解。

最后,让我们讨论 NGU 代理如何评估终身新颖性。

终身新颖性模块,带有随机蒸馏网络

在训练过程中,强化学习代理会在许多并行进程和回合中收集经验,这在某些应用中会导致数十亿次观察。因此,判断一个观察是否在所有观察中是新颖的并不完全直接。

解决这一问题的巧妙方法是使用随机网络蒸馏RND),这正是 NGU 代理所做的。RND 涉及两个网络:一个随机网络和一个预测器网络。它们的工作方式如下:

  1. 随机网络在训练开始时是随机初始化的。自然,它导致了从观察到输出的任意映射。

  2. 预测器网络试图学习这个映射,这是随机网络在整个训练过程中所做的。

  3. 预测器网络的误差会在先前遇到的观察上较低,而在新颖的观察上较高。

  4. 预测误差越大,内在奖励就会越大。

RND 架构在图 13.11中得到了说明:

图 13.11 – NGU 代理中的 RND 架构

图 13.11 – NGU 代理中的 RND 架构

NGU 智能体利用此误差来获得乘数,,以调整 。更具体地说,

其中, 是预测网络误差的均值和标准差。因此,要获得大于 1 的乘数,预测网络的误差,即“惊讶”,应该大于它所做的平均误差。

现在,让我们将一切整合起来。

结合内在奖励和外在奖励

在获得基于长期新奇性的观察的季节性内在奖励和乘数后,结合的内在奖励在时间 时的计算方式如下:

其中, 是一个超参数,用于限制乘数的上限。然后,回合奖励是内在奖励和外在奖励的加权和:

就是这样!我们已经涵盖了 NGU 智能体的一些关键思想。它还有更多的细节,例如如何在并行化的行为者中设置 值,然后利用它来参数化价值函数网络。

在我们结束关于好奇心驱动学习的讨论之前,让我们简要谈谈 NGU 智能体的扩展,Agent57。

Agent57 改进

Agent57 扩展了 NGU 智能体,设定了新的技术前沿。主要改进如下:

  • 它为内在奖励和外在奖励分别训练价值函数网络,然后将它们结合起来。

  • 它训练一组策略,并使用滑动窗口上置信界限 (UCB) 方法来选择 和折扣因子 ,同时优先考虑一个策略而非另一个。

有了这些,我们就结束了关于好奇心驱动的强化学习的讨论,这是解决强化学习中难以探索问题的关键。话虽如此,强化学习中的探索策略是一个广泛的话题。为了更全面地了解这一主题,我建议你阅读 Lilian Weng 关于此话题的博客文章(Weng2020),然后深入研究博客中提到的论文。

接下来,我们将讨论另一个重要领域:离线强化学习。

离线强化学习

离线强化学习 是通过使用智能体与环境进行的一些先前交互(可能是非强化学习的,如人类智能体)录制的数据来训练智能体,而不是直接与环境互动。这也被称为批量强化学习。在这一部分,我们将研究离线强化学习的一些关键组成部分。让我们从一个概述开始,了解它是如何工作的。

离线强化学习工作原理概述

在离线强化学习中,智能体不会直接与环境互动来探索和学习策略。图 13.12 将其与在线策略和离策略设置进行了对比:

图 13.12 – 在线策略、离策略与离线深度强化学习的对比(改编自 Levine, 2020)

图 13.12 – 在线策略、离策略和离线深度 RL 的比较(改编自Levine, 2020

让我们解读一下这张图所展示的内容:

  • 在在线策略 RL 中,代理会使用每个策略收集一批经验。然后,使用这批经验来更新策略。这个循环会一直重复,直到获得令人满意的策略。

  • 在离策略 RL 中,代理从重放缓冲区采样经验,以周期性地改进策略。更新后的策略会在回合中使用,生成新的经验,并逐渐替换重放缓冲区中的旧经验。这个循环会一直重复,直到获得令人满意的策略。

  • 在离线 RL 中,存在一些行为策略 与环境交互并收集经验。这个行为策略不一定属于 RL 代理。事实上,在大多数情况下,它可能是人类行为、基于规则的决策机制、经典控制器等。从这些交互中记录的经验将是 RL 代理用来学习策略的依据,希望能够改进行为策略。因此,在离线 RL 中,RL 代理并不与环境进行交互。

你可能会问的一个显而易见的问题是,为什么我们不能将离线数据放入类似重放缓冲区的东西,并使用 DQN 代理或类似的方法呢?这是一个重要的问题,我们来讨论一下。

为什么我们需要为离线学习设计特殊的算法

对于强化学习(RL)代理来说,必须与环境进行交互,以便观察其在不同状态下行为的后果。另一方面,离线 RL 不允许代理进行交互和探索,这是一个严重的限制。以下是一些示例来说明这一点:

  • 假设我们有来自一个人类在城市中开车的数据。根据日志,驾驶员达到的最大速度是 50 英里每小时。RL 代理可能会从日志中推断出,增加速度会减少旅行时间,并可能提出一个策略,建议在城市中以 150 英里每小时的速度行驶。由于代理从未观察到这种做法可能带来的后果,它没有太多机会纠正这种做法。

  • 当使用基于值的方法,如 DQN 时,Q 网络是随机初始化的。因此,某些 值可能仅凭运气就非常高,从而暗示一个策略,推动代理执行 并采取行动 。当代理能够进行探索时,它可以评估该策略并纠正这些不好的估计。但在离线 RL 中,它无法做到这一点。

所以,这里问题的核心是分布变化,即行为策略与 RL 策略之间的差异。

所以,希望你已经信服了离线 RL 需要一些特殊的算法。那么,下一个问题是,这样做值得吗?当我们可以使用我们迄今为止讨论的所有聪明方法和模型获得超人类级别的表现时,为什么我们还要为此费心呢?让我们看看原因。

为什么离线强化学习至关重要

视频游戏之所以是强化学习(RL)最常见的测试平台,是因为我们可以收集到用于训练所需的大量数据。当涉及到为实际应用(如机器人技术、自动驾驶、供应链、金融等)训练 RL 策略时,我们需要这些过程的模拟,以便能够收集必要的数据量并广泛探索各种策略。这无疑是现实世界 RL 中最重要的挑战之一

以下是一些原因:

  • 构建一个高保真度的现实世界过程模拟通常非常昂贵,可能需要数年时间。

  • 高保真度的模拟可能需要大量的计算资源来运行,这使得它们很难在 RL 训练中进行规模化。

  • 如果环境动态发生变化且未在模拟中进行参数化,模拟可能会很快变得过时。

  • 即使保真度非常高,也可能不足以满足 RL 的要求。RL 容易对它所交互的(模拟)环境中的错误、怪癖和假设过拟合。因此,这就产生了模拟到现实的差距。

  • 部署可能已对模拟过拟合的 RL 智能体可能会很昂贵或不安全。

因此,模拟在企业和组织中是一种稀有的存在。你知道我们拥有的是什么吗?数据。我们有许多生成大量数据的过程:

  • 制造环境有机器日志。

  • 零售商有关于他们过去定价策略及其结果的数据。

  • 交易公司有他们的买卖决策日志。

  • 我们有很多汽车驾驶视频,并且能够获得它们。

离线 RL 有潜力为所有这些过程推动自动化,并创造巨大的现实世界价值。

经过这段冗长但必要的动机说明后,终于到了介绍具体的离线 RL 算法的时刻。

优势加权演员评论家

离线 RL 是一个热门的研究领域,已经提出了许多算法。一个共同的主题是确保学习到的策略保持接近行为策略。评估差异的常用衡量标准是 KL 散度:

另一方面,与其他方法不同,优势加权演员评论家AWAC)表现出以下特征:

  • 它并不试图拟合一个模型来显式地学习

  • 它通过惩罚分布的偏移来隐式地进行调整。

  • 它使用动态规划来训练数据效率高的 函数。

为此,AWAC 优化以下目标函数:

这导致了以下的策略更新步骤:

其中 是超参数, 是归一化量。这里的关键思想是鼓励具有较高优势的动作。

信息

AWAC 的一个关键贡献是,基于离线数据训练的策略在有机会的情况下,可以通过与环境互动进行有效的微调。

我们将算法的详细信息推迟到论文中(由Nair 等人,2020),实现则可以在 RLkit 代码库中找到,地址为github.com/vitchyr/rlkit

让我们总结一下关于离线强化学习的讨论,包含基准数据集及相应的代码库。

离线强化学习基准

随着离线强化学习(Offline RL)逐渐兴起,来自 DeepMind 和加利福尼亚大学伯克利分校的研究人员创建了基准数据集和代码库,以便离线强化学习算法可以通过标准化的方式进行相互比较。这些将成为离线强化学习的“Gym”,可以这么理解:

  • RL Unplugged 由 DeepMind 推出,包含来自 Atari、Locomotion、DeepMind Control Suite 环境的数据集,以及真实世界的数据集。它可以在github.com/deepmind/deepmind-research/tree/master/rl_unplugged上获得。

  • D4RL 由加利福尼亚大学伯克利分校的机器人与人工智能实验室RAIL)推出,包含来自不同环境的数据集,如 Maze2D、Adroit、Flow 和 CARLA。它可以在github.com/rail-berkeley/d4rl上获得。

干得好!你现在已经跟上了这一新兴领域的步伐——离线强化学习。

总结

本章涵盖了几个非常热门的研究领域的高级主题。分布式强化学习是高效扩展强化学习实验的关键。基于好奇心驱动的强化学习通过有效的探索策略使解决困难的探索问题成为可能。最后,离线强化学习有潜力通过利用已经可用的许多过程的数据日志,彻底改变强化学习在现实世界问题中的应用。

在本章中,我们结束了关于算法和理论讨论的部分。接下来的章节将更侧重于应用,从下一章的机器人学应用开始。

参考文献

第四部分:强化学习的应用

在本节中,您将了解强化学习(RL)的各种应用,如自主系统、供应链管理、网络安全等。我们将学习如何利用这些技术,使用强化学习解决各行业中的问题。最后,我们将探讨强化学习的一些挑战及其未来发展。

本节包含以下章节:

  • 第十四章自主系统

  • 第十五章供应链管理

  • 第十六章营销、个性化与金融

  • 第十七章智能城市与网络安全

  • 第十八章强化学习的挑战与未来方向

第十四章:第十四章:自主系统

到目前为止,本书已经涵盖了许多强化学习中的最前沿算法和方法。从本章开始,我们将看到它们如何在实际应用中应对现实问题!我们将从机器人学习开始,这是强化学习的重要应用领域。为此,我们将使用 PyBullet 物理仿真训练 KUKA 机器人抓取托盘上的物体。我们将讨论几种解决这个困难探索问题的方法,并且会通过手动构建课程学习法和 ALP-GMM 算法来解决它。在本章结束时,我们还将介绍其他用于机器人学和自动驾驶的仿真库,这些库通常用于训练强化学习代理。

所以,本章内容包括以下几点:

  • 介绍 PyBullet

  • 熟悉 KUKA 环境

  • 开发解决 KUKA 环境的策略

  • 使用课程学习法训练 KUKA 机器人

  • 超越 PyBullet,进入自动驾驶领域

这是强化学习中最具挑战性且有趣的领域之一。让我们深入了解吧!

介绍 PyBullet

PyBullet 是一个流行的高保真物理仿真模块,广泛应用于机器人学、机器学习、游戏等领域。它是使用强化学习进行机器人学习时最常用的库之一,特别是在从仿真到现实的迁移研究和应用中:

图 14.1 – PyBullet 环境与可视化(来源:PyBullet GitHub 仓库)

图 14.1 – PyBullet 环境与可视化(来源:PyBullet GitHub 仓库)

PyBullet 允许开发者创建自己的物理仿真。此外,它还提供了使用 OpenAI Gym 接口的预构建环境。部分这些环境如图 14.1所示。

在接下来的部分,我们将为 PyBullet 设置一个虚拟环境。

设置 PyBullet

在 Python 项目中使用虚拟环境几乎总是一个好主意,这也是我们将在本章中的机器人学习实验中所做的。所以,让我们继续执行以下命令来安装我们将使用的库:

$ virtualenv pybenv
$ source pybenv/bin/activate
$ pip install pybullet --upgrade
$ pip install gym
$ pip install tensorflow==2.3.1
$ pip install ray[rllib]==1.0.0
$ pip install scikit-learn==0.23.2

你可以通过运行以下命令来测试你的安装是否正常:

$ python -m pybullet_envs.examples.enjoy_TF_AntBulletEnv_v0_2017may

如果一切正常,你会看到一个很酷的蚂蚁机器人四处游走,正如在图 14.2中所示:

图 14.2 – 蚂蚁机器人在 PyBullet 中行走

图 14.2 – 蚂蚁机器人在 PyBullet 中行走

太棒了!我们现在可以继续进行我们将要使用的 KUKA 环境了。

熟悉 KUKA 环境

KUKA 是一家提供工业机器人解决方案的公司,这些解决方案广泛应用于制造和组装环境。PyBullet 包含了 KUKA 机器人的仿真,用于物体抓取仿真(图 14.3):

图 14.3 – KUKA 机器人在工业中被广泛使用。(a)一台真实的 KUKA 机器人(图片来源 CNC Robotics 网站),(b)一个 PyBullet 仿真

图 14.3 – KUKA 机器人广泛应用于工业中。(a) 一台真实的 KUKA 机器人(图片来源:CNC Robotics 网站),(b) 一种 PyBullet 仿真

PyBullet 中有多个 KUKA 环境,具体如下:

  • 使用机器人和物体的位置及角度抓取矩形块

  • 使用摄像头输入抓取矩形块

  • 使用摄像头/位置输入抓取随机物体

在本章中,我们将重点关注第一个动作,接下来会更详细地介绍它。

使用 KUKA 机器人抓取矩形块

在这个环境中,机器人的目标是到达一个矩形物体,抓取它并将其抬升到一定高度。环境中的一个示例场景以及机器人坐标系如图 14.4所示:

图 14.4 – 物体抓取场景和机器人坐标系

图 14.4 – 物体抓取场景和机器人坐标系

机器人关节的动力学和初始位置在 pybullet_envs 包中的 Kuka 类中定义。我们将根据需要讨论这些细节,但你可以自由深入了解类定义,以更好地理解动力学。

信息

为了更好地理解 PyBullet 环境以及 Kuka 类的构建,你可以查看 PyBullet 快速入门指南,链接:bit.ly/323PjmO

现在让我们深入了解为控制此机器人在 PyBullet 内部创建的 Gym 环境。

KUKA Gym 环境

KukaGymEnv 封装了 Kuka 机器人类,并将其转化为一个 Gym 环境。动作、观察、奖励和终止条件定义如下。

动作

在这个环境中,代理执行的三种动作都与移动夹持器有关。这些动作如下:

  • 沿轴的速度

  • 沿轴的速度

  • 用于旋转夹持器的角速度(偏航)

环境本身将夹持器沿轴移动到托盘上,物体位于托盘中。当夹持器足够接近托盘时,它会闭合夹持器的手指以尝试抓取物体。

环境可以配置为接受离散或连续的动作。我们将在本案例中使用后者。

观察值

代理从环境中接收九个观察值:

  • 三个观察值用于夹持器的位置

  • 三个观察值用于夹持器相对于轴的欧拉角

  • 两个观察值用于物体相对于夹持器的位置,相对于夹持器。

  • 一个观察值用于物体相对于夹持器的欧拉角,沿

奖励

成功抓取对象并将其提升到一定高度的奖励是 10,000 分。 此外,还有一个轻微的成本,用于惩罚夹爪与对象之间的距离。 另外,旋转夹爪也会消耗一些能量。

终止条件

一个 episode 在 1,000 步之后或夹爪关闭之后结束,以先发生者为准。

要理解环境如何运作的最佳方式是实际进行实验,这也是接下来要做的事情。

这可以通过以下代码文件完成:Chapter14/manual_control_kuka.py.

该脚本允许您手动控制机器人。 您可以使用 "类似 gym 的" 控制模式,在此模式下,环境控制垂直速度和夹爪指角度。 或者,您可以选择非类似 gym 的模式来更多地控制。

您会注意到的一件事是,即使在类似 gym 的控制模式下将速度沿着 轴保持为零,机器人在下降时会改变其 位置。 这是因为夹爪沿 轴的默认速度太高。 您可以验证,在非类似 gym 的模式下,为 以下的值对 会使其他轴的位置发生较大改变。 在我们定制环境时,我们将减少速度以减轻这种情况。

现在您已经熟悉 KUKA 环境,让我们讨论一些解决它的替代策略。

开发解决 KUKA 环境的策略

环境中的抓取物体问题是一个 难探索 问题,这意味着在抓取对象后代理程序接收的稀疏奖励不太可能被发现。 像我们即将做的减少垂直速度将使它稍微容易一些。 不过,让我们回顾一下我们已经涵盖的解决这类问题的策略:

  • 奖励塑形 是我们之前讨论过的最常见的 机器教学 策略之一。 在某些问题中,激励代理朝向目标非常直接。 虽然在许多问题中,这样做可能会很痛苦。 因此,除非有明显的方法,否则制定奖励函数可能需要太多时间(以及对问题的专业知识)。 还请注意,原始奖励函数有一个成分来惩罚夹爪与对象之间的距离,因此奖励已经在某种程度上被塑造。 在我们的解决方案中,我们将不会超越此范围。

  • 以好奇心驱动的学习 激励代理程序发现状态空间的新部分。 对于这个问题,我们不需要代理程序过多地随机探索状态空间,因为我们已经对它应该做什么有一些想法。 因此,我们也将跳过这个技术。

  • "entropy_coeff" 配置位于 RLlib 的 PPO 训练器中,这是我们将使用的配置。然而,我们的超参数搜索(稍后会详细介绍)最终将这个值选为零。

  • 课程学习 可能是这里最合适的方法。我们可以识别出使问题变得具有挑战性的因素,从简单的水平开始训练智能体,并逐渐增加难度。

因此,课程学习是我们将用来解决这个问题的方法。但首先,让我们识别出参数化环境的维度,以便创建课程。

参数化问题的难度

当你实验环境时,你可能已经注意到一些让问题变得困难的因素:

  • 夹爪的起始位置过高,无法发现正确的抓取顺序。因此,调整高度的机器人关节将是我们需要参数化的一个维度。事实证明,它是在 Kuka 类的 jointPositions 数组的第二个元素中设置的。

  • 当夹爪不在原始高度时,它可能会与物体沿 轴的位置发生错位。我们还将对控制此位置的关节进行参数化,该关节是 Kuka 类的 jointPositions 数组的第四个元素。

  • 随机化物体位置是另一个给智能体带来困难的因素,它影响到 的位置以及物体的角度。我们将对这些组件的随机化程度进行参数化,范围从 0% 到 100%。

  • 即使物体没有被随机放置,它的中心也未必与机器人在 轴上的默认位置对齐。我们将对物体在 位置添加一些偏差,同样进行参数化。

这太棒了!我们知道该怎么做,这是一个重要的第一步。现在,我们可以开始课程学习!

使用课程学习训练 KUKA 机器人

在实际启动训练之前,第一步是定制 Kuka 类和 KukaGymEnv,使其能够与我们上面描述的课程学习参数一起工作。接下来我们就来做这个。

为课程学习定制环境

首先,我们通过创建一个继承自 PyBullet 原始 Kuka 类的 CustomKuka 类来开始。以下是我们的实现方式:

Chapter14/custom_kuka.py

  1. 我们首先需要创建一个新的类,并接受一个额外的参数,jp_override 字典,它代表 关节位置覆盖

    class CustomKuka(Kuka):
        def __init__(self, *args, jp_override=None, **kwargs):
            self.jp_override = jp_override
            super(CustomKuka, self).__init__(*args, **kwargs)
    
  2. 我们需要这个来改变在我们重写的 reset 方法中设置的 jointPositions 数组:

        def reset(self):
        ...
            if self.jp_override:
                for j, v in self.jp_override.items():
                    j_ix = int(j) - 1
                    if j_ix >= 0 and j_ix <= 13:
                        self.jointPositions[j_ix] = v
    

    现在,是时候创建 CustomKukaEnv 了。

  3. 创建一个自定义环境,接受所有这些课程学习的参数化输入:

    class CustomKukaEnv(KukaGymEnv):
        def __init__(self, env_config={}):
            renders = env_config.get("renders", False)
            isDiscrete = env_config.get("isDiscrete", False)
            maxSteps = env_config.get("maxSteps", 2000)
            self.rnd_obj_x = env_config.get("rnd_obj_x", 1)
            self.rnd_obj_y = env_config.get("rnd_obj_y", 1)
            self.rnd_obj_ang = env_config.get("rnd_obj_ang", 1)
            self.bias_obj_x = env_config.get("bias_obj_x", 0)
            self.bias_obj_y = env_config.get("bias_obj_y", 0)
            self.bias_obj_ang = env_config.get("bias_obj_ang", 0)
            self.jp_override = env_config.get("jp_override")
            super(CustomKukaEnv, self).__init__(
                renders=renders, isDiscrete=isDiscrete, maxSteps=maxSteps
            )
    

    请注意,我们还通过接受 env_config 使其兼容 RLlib。

  4. 我们使用reset方法中的随机化参数来覆盖物体位置的默认随机化程度:

        def reset(self):
            ...
            xpos = 0.55 + self.bias_obj_x + 0.12 * random.random() * self.rnd_obj_x
            ypos = 0 + self.bias_obj_y + 0.2 * random.random() * self.rnd_obj_y
            ang = (
                3.14 * 0.5
                + self.bias_obj_ang
                + 3.1415925438 * random.random() * self.rnd_obj_ang
            )
    
  5. 此外,我们现在应该用CustomKuka替换旧的Kuka类,并将关节位置重写的输入传递给它:

            ...
            self._kuka = CustomKuka(
                jp_override=self.jp_override,
                urdfRootPath=self._urdfRoot,
                timeStep=self._timeStep,
            )
    
  6. 最后,我们重写了环境的step方法,以降低在轴上的默认速度:

        def step(self, action):
            dz = -0.0005
            ...
    		...
                realAction = [dx, dy, dz, da, f]
            obs, reward, done, info = self.step2(realAction)
            return obs, reward / 1000, done, info
    

    还要注意,我们重新缩放了奖励(它将在-10 到 10 之间),以便训练更容易。

做得好!接下来,让我们讨论使用什么样的课程。

设计课程中的课程

确定参数化问题难度的维度是一回事,而决定如何将这种参数化暴露给代理则是另一回事。我们知道,代理应该从简单的课程开始,逐渐过渡到更难的课程。然而,这也引发了一些重要问题:

  • 参数化空间的哪些部分容易?

  • 课与课之间,调整参数的步长应该是多少?换句话说,我们应该如何将空间划分成不同的课程?

  • 代理转到下一课的成功标准是什么?

  • 如果代理在某一课上失败,意味着它的表现出乎意料地差,该怎么办?是否应该回到上一节课?失败的标准是什么?

  • 如果代理长时间无法进入下一课,该怎么办?这是否意味着我们设定的成功标准太高?是否应该将这一课划分为子课?

如你所见,在手动设计课程时,这些问题并非易事。但也请记得,在第十一章《泛化与部分可观察性》中,我们介绍了**使用高斯混合模型的绝对学习进度(ALP-GMM)**方法,它会为我们处理所有这些决策。在这里,我们将实现两者,首先从手动课程开始。

使用手动设计的课程对代理进行训练

我们将为这个问题设计一个相对简单的课程。当代理达到成功标准时,课程会将它推进到下一课;当表现不佳时,会回退到前一课。该课程将在CustomKukaEnv类内部实现,并包含increase_difficulty方法:

  1. 我们首先定义在课程过渡期间参数值的增量变化。对于关节值,我们将关节位置从用户输入的值(简单)降低到环境中的原始值(困难):

        def increase_difficulty(self):
            deltas = {"2": 0.1, "4": 0.1}
            original_values = {"2": 0.413184, "4": -1.589317}
            all_at_original_values = True
            for j in deltas:
                if j in self.jp_override:
                    d = deltas[j]
                    self.jp_override[j] = max(self.jp_override[j] - d, original_values[j])
                    if self.jp_override[j] != original_values[j]:
                        all_at_original_values = False
    
  2. 在每节课的过渡过程中,我们还确保增加物体位置的随机化程度:

            self.rnd_obj_x = min(self.rnd_obj_x + 0.05, 1)
            self.rnd_obj_y = min(self.rnd_obj_y + 0.05, 1)
            self.rnd_obj_ang = min(self.rnd_obj_ang + 0.05, 1)
    
  3. 最后,当物体位置完全随机化时,我们记得将偏差设置为零:

            if self.rnd_obj_x == self.rnd_obj_y == self.rnd_obj_ang == 1:
                if all_at_original_values:
                    self.bias_obj_x = 0
                    self.bias_obj_y = 0
                    self.bias_obj_ang = 0
    

到目前为止,一切顺利。我们几乎准备好训练代理了。在此之前,还有一件事:让我们讨论如何选择超参数。

超参数选择

为了在 RLlib 中调节超参数,我们可以使用 Ray Tune 库。在第十五章,《供应链管理》中,我们将提供一个示例,说明如何进行调节。现在,您可以直接使用我们在Chapter14/configs.py中选择的超参数。

小贴士

在困难的探索问题中,可能更合理的是先为问题的简化版本调节超参数。这是因为如果没有观察到一些合理的奖励,调节过程可能不会选择一个好的超参数集。我们在一个简单的环境设置中进行初步调节后,如果学习停滞,选择的值可以在后续过程中进行调整。

最后,让我们看看如何在训练过程中使用我们刚刚创建的环境,以及我们定义的课程。

使用 RLlib 在课程中训练智能体

为了继续进行训练,我们需要以下要素:

  • 课程的初始参数

  • 一些定义成功(以及失败,如果需要)的标准

  • 一个回调函数,将执行课程过渡

在以下代码片段中,我们使用 RLlib 中的 PPO 算法,设置初始参数,并在执行课程过渡的回调函数中将奖励阈值(经验值)设置为5.5

Chapter14/train_ppo_manual_curriculum.py

config["env_config"] = {
    "jp_override": {"2": 1.3, "4": -1}, "rnd_obj_x": 0, 
    "rnd_obj_y": 0, "rnd_obj_ang": 0, "bias_obj_y": 0.04}
def on_train_result(info):
    result = info["result"]
    if result["episode_reward_mean"] > 5.5:
        trainer = info["trainer"]
        trainer.workers.foreach_worker(
            lambda ev: ev.foreach_env(lambda env: env.increase_difficulty()))
ray.init()
tune.run("PPO", config=dict(config,
                            **{"env": CustomKukaEnv,
                               "callbacks": {
                          "on_train_result": on_train_result}}
                            ),
          checkpoint_freq=10)

这应该会启动训练,您将看到课程学习的实际效果!您会注意到,当智能体过渡到下一课时,随着环境变得更困难,其表现通常会下降。

我们稍后会查看此训练的结果。现在让我们继续实现 ALP-GMM 算法。

使用绝对学习进展的课程学习

ALP-GMM 方法专注于在参数空间中性能变化最大(绝对学习进展)的位置,并在该空隙周围生成参数。这个想法在图 14.5中得到了说明:

图 14.5 – ALP-GMM 在点周围生成参数(任务),在这些点之间,观察到最大的回合奖励变化

图 14.5 – ALP-GMM 在点周围生成参数(任务),在这些点之间,观察到最大的回合奖励变化

这样,学习预算就不会花费在已经学习过的状态空间部分,或者花费在当前智能体无法学习的部分上。

在这次回顾之后,让我们继续实现它。我们首先创建一个自定义环境,在这个环境中将运行 ALP-GMM 算法。

Chapter14/custom_kuka.py

我们直接从与论文(Portelas 等,2019)附带的源代码库中获取 ALP-GMM 的实现,并将其放置在Chapter14/alp目录下。然后,我们可以将其插入到我们创建的新环境ALPKukaEnv中,关键部分如下:

  1. 我们创建类并定义我们尝试教授给智能体的参数空间的所有最小值和最大值:

    class ALPKukaEnv(CustomKukaEnv):
        def __init__(self, env_config={}):
            ...
            self.mins = [...]
            self.maxs =  [...]
            self.alp = ALPGMM(mins=self.mins, 
                         maxs=self.maxs, 
                               params={"fit_rate": 20})
            self.task = None
            self.last_episode_reward = None
            self.episode_reward = 0
            super(ALPKukaEnv, self).__init__(env_config)
    

    这里的任务是由 ALP-GMM 算法生成的参数空间中的最新样本,用于配置环境。

  2. 每个回合开始时都会采样一个任务。一旦回合结束,任务(在该回合中使用的环境参数)和回合奖励将被用来更新 GMM 模型:

        def reset(self):
            if self.task is not None and self.last_episode_reward is not None:
                self.alp.update(self.task, 
                                self.last_episode_reward)
            self.task = self.alp.sample_task()
            self.rnd_obj_x = self.task[0]
            self.rnd_obj_y = self.task[1]
            self.rnd_obj_ang = self.task[2]
            self.jp_override = {"2": self.task[3], 
                                "4": self.task[4]}
            self.bias_obj_y = self.task[5]
            return super(ALPKukaEnv, self).reset()
    
  3. 最后,我们确保跟踪每一回合的奖励:

        def step(self, action):
            obs, reward, done, info = super(ALPKukaEnv,  self).step(action)
            self.episode_reward += reward
            if done:
                self.last_episode_reward = self.episode_reward
                self.episode_reward = 0
            return obs, reward, done, info
    

这里需要注意的一点是,ALP-GMM 通常以集中式方式实现:一个中心进程为所有回合工作者生成任务,并收集与这些任务相关的回合奖励进行处理。在这里,由于我们在 RLlib 中工作,所以更容易在环境实例中实现它。为了考虑单次回合中收集的数据量减少,我们使用了"fit_rate": 20,低于原始的 250 水平,这样回合工作者在拟合 GMM 到它收集的任务-奖励数据之前不会等待太久。

创建ALPKukaEnv后,其余的工作只是简单地调用 Ray 的tune.run()函数,位于Chapter14/train_ppo_alp.py中。请注意,与手动课程不同,我们没有指定参数的初始值。而是传递了 ALP-GMM 进程的边界,这些边界引导了课程的设计。

现在,我们准备进行一个课程学习的比拼!

比较实验结果

我们启动了三次训练:一次使用我们描述的手动课程,一次使用 ALP-GMM,另一次则没有实现任何课程。训练进度的 TensorBoard 视图显示在图 14.6中:

图 14.6 – TensorBoard 上的训练进度

图 14.6 – TensorBoard 上的训练进度

一开始你可能认为手动课程和 ALP-GMM 相近,而不使用课程的则排在第三。实际上情况并非如此。让我们来解析这个图:

  • 手动课程从简单到困难。这就是为什么它大部分时间都排在顶部的原因。在我们的运行中,它甚至在时间预算内无法完成最新的课程。因此,图中显示的手动课程表现被夸大了。

  • 无课程训练始终在最困难的级别进行竞争。这就是它大部分时间处于最底部的原因:其他代理并没有运行在最难的参数配置上,因此它们会慢慢到达那里。

  • ALP-GMM 大部分时间处于中间位置,因为它在同时尝试困难和极难的配置,同时关注中间的一些地方。

    由于这个图没有给出明确结论,我们在原始(最困难)配置上评估了代理的表现。每个代理经过 100 个测试回合后的结果如下:

    Agent ALP-GMM score: 3.71
    Agent Manual score: -2.79
    Agent No Curriculum score: 2.81
    
  • 手动课程表现最差,因为在训练结束时无法完成最新的课程。

  • 无课程训练取得了一些成功,但从最困难的设置开始似乎让其退步。此外,评估性能与 TensorBoard 上显示的结果一致,因为在这种情况下,评估设置与训练设置没有不同。

  • ALP-GMM 似乎从逐渐增加难度中受益,并且表现最佳。

  • 无课程训练在 TensorBoard 图表上的峰值与 ALP-GMM 的最新表现相似。因此,我们在垂直速度方面的修改减少了两者之间的差异。然而,不使用课程训练会导致代理在许多困难探索场景中完全无法学习。

您可以在Chapter14/evaluate_ppo.py中找到评估的代码。此外,您还可以使用脚本Chapter14/visualize_policy.py来观察您训练的代理如何表现,看到它们的不足之处,并提出改进性能的想法!

这部分结束了我们对 KUKA 机器人学习示例的讨论。在下一部分,我们将总结本章,并列出一些常用的仿真环境,用于训练自动化机器人和车辆。

超越 PyBullet,进入自动驾驶领域

PyBullet 是一个极好的环境,可以在高保真物理仿真中测试强化学习算法的能力。在机器人技术和强化学习交叉的领域,您会遇到以下一些库:

此外,您将看到基于 Unity 和 Unreal Engine 的环境被用于训练强化学习代理。

下一个更常见的自主性层级当然是自动驾驶车辆。RL 在现实的自动驾驶车辆仿真中也得到了越来越多的实验。这方面最流行的库有:

到此,我们结束了这一章关于机器人学习的内容。这是 RL 中的一个非常热门的应用领域,里面有很多环境,我希望您享受这部分内容并从中获得灵感,开始动手尝试机器人。

总结

自主机器人和车辆将在未来在我们的世界中发挥巨大作用,而强化学习是创建这些自主系统的主要方法之一。在本章中,我们简单了解了训练机器人完成物体抓取任务的过程,这是机器人学中的一大挑战,在制造业和仓库物料搬运中有着广泛应用。我们使用 PyBullet 物理仿真器,在一个困难的探索环境中训练了一个 KUKA 机器人,并使用了手动和基于 ALP-GMM 的课程学习方法。现在,既然你已经较为熟练地掌握了如何利用这些技术,你可以尝试解决其他类似的问题。

在下一章中,我们将深入探讨强化学习应用的另一个主要领域:供应链管理。敬请期待另一段激动人心的旅程!

参考文献

第十五章:第十五章:供应链管理

有效的供应链管理是许多企业面临的挑战,但它对于企业的盈利能力和竞争力至关重要。这一领域的困难来自于影响供需关系的复杂动态、处理这些动态的商业约束,以及其中巨大的不确定性。强化学习RL)为我们提供了一套关键能力,帮助解决这类序列决策问题。

在本章中,我们特别关注两个重要问题:库存和路由优化。对于前者,我们深入探讨了如何创建环境、理解环境中的变化,以及如何通过超参数调优有效地使用强化学习解决问题。对于后者,我们描述了一个现实的车辆路由问题——一名临时司机为在线餐饮订单提供配送服务。接着,我们展示了为什么传统的神经网络在解决不同规模的问题时存在局限性,以及指针网络如何克服这一问题。

这将是一段有趣的旅程。本章将涵盖以下内容:

  • 优化库存采购决策

  • 路由问题建模

优化库存采购决策

几乎所有制造商、分销商和零售商需要不断做出的最重要决策之一就是,如何保持足够的库存,以便可靠地满足客户需求,同时最小化成本。有效的库存管理对于大多数公司的盈利能力和生存至关重要,特别是在当今竞争激烈的环境中,考虑到薄弱的利润率和不断提升的客户期望。在本节中,我们将利用强化学习来解决这一挑战,并优化库存采购决策。

库存的需求及其管理中的权衡

当你走进超市时,你会看到物品堆积在一起。超市的仓库里可能有更多的这些物品,分销商的仓库里也有更多,制造商的工厂里更是如此。想一想,这些巨大的产品堆积物就静静地待在某个地方,等待着未来某个时刻被客户需求。如果这听起来像是资源浪费,那它在很大程度上确实是。另一方面,企业必须保持一定量的库存,因为以下原因:

  • 未来充满不确定性。客户需求、制造能力、运输计划和原材料的可用性,都可能在某些时刻以无法预见的方式变化。

  • 不可能以完美的准时制方式运作,并在客户要求时立即制造并交付物品。

由于保持库存通常是不可避免的,那么问题就在于“多少”。回答这一问题涉及到一个复杂的权衡:

  • 最小化无法满足客户需求的机会,这不仅会导致利润损失,更重要的是会损失客户忠诚度,而忠诚度一旦丧失,恢复将变得非常困难。

  • 降低库存,因为它会带来资本、劳动力、时间、材料、维护和仓储租金等成本,还可能导致商品过期或过时以及组织管理的开销。

那么,你会如何处理这种情况?是将客户满意度作为绝对优先考虑,还是更愿意保持库存的可控?实际上,这种平衡行为需要仔细的规划和高级方法的应用,而并非所有公司都有能力做到这一点。因此,大多数公司更倾向于选择“保险策略”,即保持比实际需求更多的库存,这有助于掩盖缺乏规划和相关问题。这个现象通常被形象地描述为“库存之海”,如图 15.1所示:

图 15.1 – 库存之海隐藏了许多问题

图 15.1 – 库存之海隐藏了许多问题

在这种情况下,强化学习(RL)可以发挥作用,在面对不确定性时优化库存决策。接下来,我们通过讨论库存优化问题的组成部分,开始构建我们的解决方案。

库存优化问题的组成部分。

有多个因素影响库存流动的动态,以及在给定商品的情况下,最佳补货政策将会是什么样子:

  • 商品的价格是决定其销售价格的关键因素。

  • 商品的采购成本是另一个关键因素,它与价格一起决定了每件商品所赚取的毛利润。这反过来影响了失去一个客户需求单位的成本。

  • 库存持有成本是指在单个时间步(例如一天、一周或一个月等)内,持有单个库存单位的所有成本总和。这包括存储租金、资本成本以及任何维护成本等。

  • 客户信任流失是由于丧失单个需求单位所导致的客户不满的货币成本。毕竟,这会减少客户忠诚度并影响未来的销售。虽然不满通常是定性衡量的,但企业需要估算其货币等价物,以便在决策中使用。

  • 对商品的客户需求在单个时间步中的变化是影响决策的主要因素之一。

  • 供应商交货时间VLT)是指从向供应商下订单到商品到达库存的延迟时间。毫不奇怪,VLT 是决定何时下订单以应对预期需求的关键因素。

  • 容量限制,例如单次批量可订购商品的数量,以及公司的存储能力,将限制代理可以采取的行动。

这些是我们在这里设置时需要考虑的主要因素。此外,我们还将重点关注以下内容:

  • 单一商品情景

  • 具有泊松分布的随机客户需求,在给定时期内具有确定的且固定的均值。

  • 除需求外的确定性因素

这使得我们的案例在保持足够复杂性的同时变得可解。

提示

在实际环境中,大多数动态都涉及不确定性。例如,可能到达的库存存在缺陷;价格可能会随着物品的过时程度而变化;可能由于天气原因导致现有库存损失;可能有物品需要退货。估算所有这些因素的特征并创建过程的仿真模型是一个真正的挑战,成为了将强化学习作为此类问题工具的障碍。

我们所描述的是一个复杂的优化问题,当前没有可行的最优解。然而,它的单步版本,称为新闻售货员问题,已被广泛研究和应用。这是一个极好的简化方法,可以帮助我们形成对问题的直观理解,并且还将帮助我们为多步情况获得一个接近最优的基准。让我们深入研究一下。

单步库存优化 – 新闻售货员问题

当库存优化问题涉及以下内容时:

  • 单一时间步长(因此没有 VLT;库存到货是确定性的)

  • 单一商品

  • 已知价格、购买成本和未售出库存的成本

  • 一个已知的(且方便的高斯)需求分布

然后,这个问题被称为新闻售货员问题,我们可以为其获得封闭形式的解。它描述了一个报纸销售员,旨在规划当天购买多少份报纸,单位成本为,以单位价格出售,并在当天结束时以单位价格退还未售出的份数。接着,我们定义以下量:

  • 缺货成本,,是由于错失一单位需求所造成的利润损失:

  • 过剩成本,,是未售出的单元成本:

为了找到最优的订货量,我们接下来计算一个关键比率,,如下所示:

现在,让我们来分析这个关键比率如何随着缺货和过剩成本的变化而变化:

  • 随着的增加,增加。更高的意味着错失客户需求的成本更高。这表明,在补充库存时应更加积极,以避免错失机会。

  • 随着的增加,减少,这意味着未售出库存的成本更高。这表明我们在库存量上应该保持保守。

结果表明,给出了为了优化预期利润,应该覆盖的需求场景的百分比。换句话说,假设需求有一个概率分布函数,并且有一个累积分布函数CDF。最佳订货量由给出,其中是 CDF 的逆函数。

让我们通过一个例子来进行实验。

新闻供应商示例

假设某昂贵商品的价格为,其来源为。如果该商品未售出,不能退还给供应商,且会变成废品。因此,我们有。在这种情况下,缺货成本为,过剩成本为,这使得我们得出临界比率。这表明订货量有 80%的几率能满足需求。假设需求服从正态分布,均值为 20 件,标准差为 5 件,则最佳订货量约为 24 件。

您可以使用在Newsvendor plots.ipynb文件中定义的calc_n_plot_critical_ratio函数计算并绘制最佳订货量:

Chapter15/Newsvendor plots.ipynb

calc_n_plot_critical_ratio(p=2000, c=400, r=0, mean=20, std=5)

您应该看到如下输出:

Cost of underage is 1600
Cost of overage is 400
Critical ratio is 0.8
Optimal order qty is 24.20810616786457

图 15.2展示了需求的概率分布、累积分布函数(CDF)以及该问题对应的临界比率值:

图 15.2 – 示例新闻供应商问题的最佳订货量

图 15.2 – 示例新闻供应商问题的最佳订货量

这是为了让您对单步库存优化问题的解决方案有一个直观的了解。现在,多步问题涉及到许多其他复杂性,我们在上一节中进行了描述。例如,库存到货存在滞后,剩余库存会带到下一步并产生持有成本。这就是不确定性下的序列决策问题,而这正是强化学习的强项。所以,让我们来使用它。

模拟多步库存动态

在本节中,我们创建了一个模拟环境,用于描述的多步库存优化问题。

信息

本章的其余部分紧密跟随Balaji 等人,2019中定义的问题和环境,其中的代码可在github.com/awslabs/or-rl-benchmarks找到。我们建议您阅读该论文,以获取有关经典随机优化问题的强化学习(RL)方法的更多细节。

在开始描述环境之前,让我们讨论几个注意事项:

  • 我们希望创建一个不仅适用于特定产品需求场景的策略,而是适用于广泛场景的策略。因此,对于每个周期,我们会随机生成环境参数,正如您将看到的那样。

  • 这种随机化增加了梯度估计的方差,使得学习过程比静态场景更加具有挑战性。

有了这些,我们来详细讨论一下环境的细节。

事件日历

为了正确地应用环境的阶跃函数,我们需要了解每个事件发生的时间。让我们来看一下:

  1. 每天开始时,库存补货订单会被下达。根据提前期,我们将其记录为“运输中”库存。

  2. 接下来,当前日程安排的物品到达。如果提前期为零,那么当天一开始就下的订单会立刻到达。如果提前期为一天,那么昨天的订单会到达,依此类推。

  3. 在货物收到后,需求会在一天内逐渐显现。如果库存不足以满足需求,实际销售量将低于需求,并且会失去客户的好感。

  4. 在一天结束时,我们从库存中扣除已售出的物品(不是总需求),并相应更新状态。

  5. 最后,如果提前期非零,我们会更新运输中的库存(即,我们将原本在 t + 2 到达的库存移至 t + 1)。

现在,让我们编码我们所描述的内容。

编码环境

你可以在我们的 GitHub 仓库中找到完整的环境代码,链接为:github.com/PacktPublishing/Mastering-Reinforcement-Learning-with-Python/blob/master/Chapter15/inventory_env.py

在这里,我们只描述环境的一些关键部分:

  1. 如前所述,每个回合将抽样某些环境参数,以便获得可以在广泛的价格、需求等场景中工作的策略。我们设置这些参数的最大值,然后从中生成特定回合的参数:

    class InventoryEnv(gym.Env):
        def __init__(self, config={}):
            self.l = config.get("lead time", 5)
            self.storage_capacity = 4000
            self.order_limit = 1000
            self.step_count = 0
            self.max_steps = 40
            self.max_value = 100.0
            self.max_holding_cost = 5.0
            self.max_loss_goodwill = 10.0
            self.max_mean = 200
    

    我们使用 5 天的提前期,这对确定观察空间非常重要(可以认为它等同于此问题中的状态空间,因此我们可以互换使用这两个术语)。

  2. 价格、成本、持有成本、客户好感损失和预期需求是状态空间的一部分,我们假设这些也对代理可见。此外,我们需要追踪现有库存,以及如果提前期非零,还需要追踪运输中的库存:

            self.inv_dim = max(1, self.l)
            space_low = self.inv_dim * [0]
            space_high = self.inv_dim * [self.storage_capacity]
            space_low += 5 * [0]
            space_high += [
                self.max_value,
                self.max_value,
                self.max_holding_cost,
                self.max_loss_goodwill,
                self.max_mean,
            ]
            self.observation_space = spaces.Box(
                low=np.array(space_low), 
                high=np.array(space_high), 
                dtype=np.float32
            )
    

    请注意,对于 5 天的提前期,我们有一个维度表示现有库存,四个维度(从)表示运输中的库存。正如你将看到的,通过在步骤计算结束时将运输中库存添加到状态中,我们可以避免追踪将会到达的运输中库存(更一般地说,我们不需要表示提前期)。

  3. 我们将动作规范化为在 之间,其中 1 表示以订单限额下单:

            self.action_space = spaces.Box(
                low=np.array([0]), 
                high=np.array([1]), 
                dtype=np.float32
            )
    
  4. 一个非常重要的步骤是规范化观察值。通常,智能体可能不知道观察值的边界以便进行规范化。在这里,我们假设智能体有这个信息,因此我们在环境类中方便地处理了它:

        def _normalize_obs(self):
            obs = np.array(self.state)
            obs[:self.inv_dim] = obs[:self.inv_dim] / self.order_limit
            obs[self.inv_dim] = obs[self.inv_dim] / self.max_value
            obs[self.inv_dim + 1] = obs[self.inv_dim + 1] / self.max_value
            obs[self.inv_dim + 2] = obs[self.inv_dim + 2] / self.max_holding_cost
            obs[self.inv_dim + 3] = obs[self.inv_dim + 3] / self.max_loss_goodwill
            obs[self.inv_dim + 4] = obs[self.inv_dim + 4] / self.max_mean
            return obs
    
  5. 特定回合的环境参数是在 reset 函数中生成的:

        def reset(self):
            self.step_count = 0
            price = np.random.rand() * self.max_value
            cost = np.random.rand() * price
            holding_cost = np.random.rand() * min(cost, self.max_holding_cost)
            loss_goodwill = np.random.rand() * self.max_loss_goodwill
            mean_demand = np.random.rand() * self.max_mean
            self.state = np.zeros(self.inv_dim + 5)
            self.state[self.inv_dim] = price
            self.state[self.inv_dim + 1] = cost
            self.state[self.inv_dim + 2] = holding_cost
            self.state[self.inv_dim + 3] = loss_goodwill
            self.state[self.inv_dim + 4] = mean_demand
            return self._normalize_obs()
    
  6. 我们按照前一部分描述的方式实现 step 函数。首先,我们解析初始状态和接收到的动作:

        def step(self, action):
            beginning_inv_state, p, c, h, k, mu = \
                self.break_state()
            action = np.clip(action[0], 0, 1)
            action = int(action * self.order_limit)
            done = False
    
  7. 然后,我们在观察容量的同时确定可以购买的数量,如果没有提前期,就将购买的部分添加到库存中,并采样需求:

            available_capacity = self.storage_capacity \
                                 - np.sum(beginning_inv_state)
            assert available_capacity >= 0
            buys = min(action, available_capacity)
            # If lead time is zero, immediately
            # increase the inventory
            if self.l == 0:
                self.state[0] += buys
            on_hand = self.state[0]
            demand_realization = np.random.poisson(mu)
    
  8. 奖励将是收入,我们从中扣除购买成本、库存持有成本和失去的客户信誉成本:

            # Compute Reward
            sales = min(on_hand,
                        demand_realization)
            sales_revenue = p * sales
            overage = on_hand - sales
            underage = max(0, demand_realization
                              - on_hand)
            purchase_cost = c * buys
            holding = overage * h
            penalty_lost_sale = k * underage
            reward = sales_revenue \
                     - purchase_cost \
                     - holding \
                     - penalty_lost_sale
    
  9. 最后,我们通过将在途库存移动一天来更新库存水平,如果 VLT 不为零,还需要将当日购买的物品添加到在途库存中:

            # Day is over. Update the inventory
            # levels for the beginning of the next day
            # In-transit inventory levels shift to left
            self.state[0] = 0
            if self.inv_dim > 1:
                self.state[: self.inv_dim - 1] \
                    = self.state[1: self.inv_dim]
            self.state[0] += overage
            # Add the recently bought inventory
            # if the lead time is positive
            if self.l > 0:
                self.state[self.l - 1] = buys
            self.step_count += 1
            if self.step_count >= self.max_steps:
                done = True
    
  10. 最后,我们将规范化后的观察值和缩放后的奖励返回给智能体:

            # Normalize the reward
            reward = reward / 10000
            info = {
                "demand realization": demand_realization,
                "sales": sales,
                "underage": underage,
                "overage": overage,
            }
            return self._normalize_obs(), reward, done, info
    

花时间理解库存动态是如何在步骤函数中体现的。一旦准备好,我们就可以开始开发基准策略。

开发近似最优基准策略

这个问题的精确解不可得。然而,通过类似新闻商政策的两项修改,我们获得了一个近似最优的解:

  • 考虑到的总需求是在 时间步长内的需求,而不是单一时间步。

  • 我们还将客户流失的损失加到缺货成本中,除了每单位的利润之外。

之所以仍然是近似解,是因为该公式将多个步骤视为一个单一步骤,并汇总需求和供应,意味着它假设需求在某个步骤到达,并可以在随后的步骤中积压并得到满足,这一过程持续在 步长内进行。不过,这仍然为我们提供了一个近似最优的解决方案。

以下是如何编写此基准策略的代码:

def get_action_from_benchmark_policy(env):
    inv_state, p, c, h, k, mu = env.break_state()
    cost_of_overage = h
    cost_of_underage = p - c + k
    critical_ratio = np.clip(
        0, 1, cost_of_underage
              / (cost_of_underage + cost_of_overage)
    )
    horizon_target = int(poisson.ppf(critical_ratio,
                         (len(inv_state) + 1) * mu))
    deficit = max(0, horizon_target - np.sum(inv_state))
    buy_action = min(deficit, env.order_limit)
    return [buy_action / env.order_limit]

请注意,在我们计算临界比率后,我们进行以下操作:

  • 我们为 步骤找到最优的总供应量。

  • 然后,我们减去下一步 时间步长内的在手库存和在途库存。

  • 最后,我们下单以弥补这一差额,但单次订单有上限。

接下来,让我们看看如何训练一个强化学习智能体来解决这个问题,以及强化学习解决方案与此基准的比较。

一种用于库存管理的强化学习解决方案

在解决这个问题时,有几个因素需要考虑:

  • 由于环境中的随机化,每一回合的奖励有很高的方差。

  • 这要求我们使用高于正常的批量和小批量大小,以更好地估计梯度并对神经网络权重进行更稳定的更新。

  • 在高方差的情况下,选择最佳模型也是一个问题。这是因为如果测试/评估的回合数不足,可能会仅仅因为在一些幸运的配置中评估了某个策略,就错误地认为它是最好的。

为了应对这些挑战,我们可以采取以下策略:

  1. 在有限的计算预算下进行有限的超参数调优,以识别出一组好的超参数。

  2. 使用一个或两个最佳超参数集来训练模型,并在训练过程中保存最佳模型。

  3. 当你观察到奖励曲线的趋势被噪声主导时,增加批量和小批量的大小,以便更精确地估计梯度并去噪模型性能指标。再次保存最佳模型。

  4. 根据您的计算预算,重复多次并选择最佳模型。

所以,让我们在 Ray/RLlib 中实现这些步骤以获得我们的策略。

初始超参数搜索

我们使用 Ray 的 Tune 库进行初步的超参数调优。我们将使用两个函数:

  • tune.grid_search() 在指定的值集合上进行网格搜索。

  • tune.choice() 在指定的集合中进行随机搜索。

对于每个试验,我们还指定停止条件。在我们的案例中,我们希望运行一百万个时间步骤的试验。

下面是一个示例搜索的代码:

import ray
from ray import tune
from inventory_env import InventoryEnv
ray.init()
tune.run(
    "PPO",
    stop={"timesteps_total": 1e6},
    num_samples=5,
    config={
        "env": InventoryEnv,
        "rollout_fragment_length": 40,
        "num_gpus": 1,
        "num_workers": 50,
        "lr": tune.grid_search([0.01, 0.001, 0.0001, 0.00001]),
        "use_gae": tune.choice([True, False]),
        "train_batch_size": tune.choice([5000, 10000, 20000, 40000]),
        "sgd_minibatch_size": tune.choice([128, 1024, 4096, 8192]),
        "num_sgd_iter": tune.choice([5, 10, 30]),
        "vf_loss_coeff": tune.choice([0.1, 1, 10]),
        "vf_share_layers": tune.choice([True, False]),
        "entropy_coeff": tune.choice([0, 0.1, 1]),
        "clip_param": tune.choice([0.05, 0.1, 0.3, 0.5]),
        "vf_clip_param": tune.choice([1, 5, 10]),
        "grad_clip": tune.choice([None, 0.01, 0.1, 1]),
        "kl_target": tune.choice([0.005, 0.01, 0.05]),
        "eager": False,
    },
)

为了计算总的调优预算,我们执行以下操作:

  • 获取所有网格搜索的交叉乘积,因为根据定义必须尝试每一个可能的组合。

  • 将交叉乘积与 num_samples 相乘,得到将要进行的试验总数。使用前面的代码,我们将有 20 次试验。

  • 在每次试验中,每个 choice 函数会从相应的集合中随机均匀地选择一个参数。

  • 当满足停止条件时,给定的试验将停止。

执行时,您将看到搜索过程正在进行。它看起来像图 15.3

图 15.3 – 使用 Ray 的 Tune 进行超参数调优

图 15.3 – 使用 Ray 的 Tune 进行超参数调优

一些试验会出错,除非你特别注意避免数值问题的超参数组合。然后,你可以选择表现最好的组合进行进一步的训练,如图 15.4所示:

图 15.4 – 在搜索中获得的优质超参数集的样本性能

图 15.4 – 在搜索中获得的优质超参数集的样本性能

接下来,让我们进行广泛的训练。

模型的广泛训练

现在,我们开始使用选定的超参数集进行长时间训练(或者使用多个超参数集——在我的案例中,之前的最佳集表现不好,但第二个集表现良好):

Chapter15/train_inv_policy.py

import numpy as np
import ray
from ray.tune.logger import pretty_print
from ray.rllib.agents.ppo.ppo import DEFAULT_CONFIG
from ray.rllib.agents.ppo.ppo import PPOTrainer
from inventory_env import InventoryEnv
config = DEFAULT_CONFIG.copy()
config["env"] = InventoryEnv
config["num_gpus"] = 1
config["num_workers"] = 50
config["clip_param"] = 0.3
config["entropy_coeff"] = 0
config["grad_clip"] = None
config["kl_target"] = 0.005
config["lr"] = 0.001
config["num_sgd_iter"] = 5
config["sgd_minibatch_size"] = 8192
config["train_batch_size"] = 20000
config["use_gae"] = True
config["vf_clip_param"] = 10
config["vf_loss_coeff"] = 1
config["vf_share_layers"] = False

一旦设置了超参数,就可以开始训练:

ray.init()
trainer = PPOTrainer(config=config, env=InventoryEnv)
best_mean_reward = np.NINF
while True:
    result = trainer.train()
    print(pretty_print(result))
    mean_reward = result.get("episode_reward_mean", np.NINF)
    if mean_reward > best_mean_reward:
        checkpoint = trainer.save()
        print("checkpoint saved at", checkpoint)
        best_mean_reward = mean_reward

这里有一点需要注意,那就是批量和小批量的大小:通常情况下,RLlib 中的 PPO 默认设置是"train_batch_size": 4000"sgd_minibatch_size": 128。然而,由于环境和奖励的方差,使用如此小的批量会导致学习效果不佳。因此,调优模型选择了更大的批量和小批量大小。

现在进行训练。此时,您可以根据训练进度开发逻辑来调整各种超参数。为了简化起见,我们将手动观察进度,并在学习停滞或不稳定时停止。在那之后,我们可以进一步增加批量大小进行训练,以获得最终阶段的更好梯度估计,例如 "train_batch_size": 200000"sgd_minibatch_size": 32768。这就是训练过程的样子:

图 15.5 – 训练从 20k 的批量大小开始,并继续使用 200k 的批量使用 200k 的批量大小来减少噪声

图 15.5 – 训练从 20k 的批量大小开始,并继续使用 200k 的批量大小以减少噪声

更高的批量大小微调帮助我们去噪奖励并识别真正高效的模型。然后,我们可以比较基准和强化学习(RL)解决方案。经过 2,000 个测试回合后,基准性能如下所示:

Average daily reward over 2000 test episodes: 0.15966589703658918\. 
Average total epsisode reward: 6.386635881463566

我们可以在这里看到 RL 模型的表现:

Average daily reward over 2000 test episodes: 0.14262437792900876\. 
Average total epsisode reward: 5.70497511716035

我们的 RL 模型性能在接近最优基准解的 10%以内。我们可以通过进一步的试验和训练来减少差距,但在如此噪声的环境下,这是一个具有挑战性且耗时的工作。请注意,Balaji et al., 2019报告的指标略微改善了基准,因此这是可行的。

至此,我们结束了对这个问题的讨论。做得好!我们已经将一个现实且噪声较大的供应链问题从初始形态中建模,使用 RL 进行建模,并通过 RLlib 上的 PPO 解决了它!

接下来,我们描述两个可以通过 RL 解决的额外供应链问题。由于篇幅限制,我们在这里无法解决这些问题,但我们建议您参考github.com/awslabs/or-rl-benchmarks/获取更多信息。

那么,接下来让我们讨论如何使用 RL 来建模和解决路由优化问题。

路由问题建模

路由问题是组合优化中最具挑战性和最受研究的问题之一。事实上,有相当多的研究人员将毕生精力投入到这一领域。最近,RL 方法作为一种替代传统运筹学方法的路由问题解决方案已经浮出水面。我们从一个相对复杂的路由问题开始,它是关于在线餐饮订单的取送。另一方面,这个问题的 RL 建模并不会太复杂。我们稍后将根据该领域的最新文献,扩展讨论更先进的 RL 模型。

在线餐饮订单的取送

考虑一个“零工”司机(我们的代理),他为一个在线平台工作,类似于 Uber Eats 或 Grubhub,从餐厅接订单并配送给客户。司机的目标是通过配送更多高价值订单来赚取尽可能多的小费。以下是这个环境的更多细节:

  • 城市中有多个餐厅,这些餐厅是订单的取件地点。

  • 与这些餐厅相关的订单会动态地出现在配送公司的应用程序上。

  • 司机必须接受一个订单才能去取件并进行配送。

  • 如果一个已接受的订单在创建后的一定时间内未被配送,则会遭受高额罚款。

  • 如果司机未接受一个开放订单,该订单会在一段时间后消失,意味着订单被竞争的其他司机抢走。

  • 司机可以接受任意数量的订单,但实际能携带的已取件订单数量有限。

  • 城市的不同区域以不同的速率和不同的规模生成订单。例如,一个区域可能生成频繁且高价值的订单,从而吸引司机。

  • 不必要的行程会给司机带来时间、燃料和机会成本。

在这个环境下,每个时间步,司机可以采取以下动作之一:

  • 接受其中一个开放订单。

  • 朝一个与特定订单相关的餐厅移动一步(取件)。

  • 朝客户位置移动一步(进行配送)。

  • 等待并什么都不做。

  • 朝一个餐厅移动一步(不是去取现有订单,而是希望很快会从该餐厅接到一个高价值订单)。

代理观察以下状态以做出决策:

  • 司机、餐厅和客户的坐标

  • 司机使用的和可用的运载能力

  • 订单状态(开放、已接受、已取件、非活动/已配送/未创建)

  • 订单与餐厅的关联

  • 从订单创建开始经过的时间

  • 每个订单的奖励(小费)

如需更多信息,可以访问此环境:github.com/awslabs/or-rl-benchmarks/blob/master/Vehicle%20Routing%20Problem/src/vrp_environment.py

Balaji et al., 2019 研究表明,RL 解决方案在此问题上的表现优于基于混合整数 规划MIP)的方法。这是一个相当令人惊讶的结果,因为 MIP 模型理论上可以找到最优解。MIP 解决方案在此情况下被超越的原因如下:

  • 它解决了一个眼前问题,而 RL 代理学习预测未来事件并相应地进行规划。

  • 它使用了有限的预算,因为 MIP 解决方案可能需要很长时间,而 RL 推理一旦训练好策略后,几乎是瞬时发生的。

对于如此复杂的问题,报告的强化学习(RL)性能相当鼓舞人心。另一方面,我们所采用的问题建模方式有一定局限性,因为它依赖于固定的状态和动作空间大小。换句话说,如果状态和动作空间设计为处理最多 N 个订单/餐馆,那么训练后的智能体无法用于更大的问题。另一方面,混合整数规划(MIP)模型可以接受任何大小的输入(尽管大规模问题可能需要很长时间才能解决)。

深度学习领域的最新研究为我们提供了指针网络,用于处理动态大小的组合优化问题。接下来我们将深入探讨这个话题。

用于动态组合优化的指针网络

指针网络使用基于内容的注意力机制来指向其输入中的一个节点,输入的数量可以是任意的。为了更好地解释这一点,考虑一个旅行推销员问题,其目标是在二维平面上访问所有节点,每个节点仅访问一次,并在最后回到初始节点,且以最小的总距离完成。图 15.6 展示了一个示例问题及其解决方案:

![图 15.6 – 旅行推销员问题的解决方案

(来源:en.wikipedia.org/wiki/Travelling_salesman_problem

](github.com/OpenDocCN/f…)

图 15.6 – 旅行推销员问题的解决方案(来源:en.wikipedia.org/wiki/Travelling_salesman_problem

该问题中的每个节点通过其 坐标表示。指针网络具有以下特点:

  • 使用递归神经网络从编码器中的输入节点 获得嵌入 ,并在解码的 步骤中类似地获得解码器中的

  • 在解码 步骤时,计算输入节点 的注意力,计算方式如下:

其中 是可学习的参数。

  • 拥有最高注意力 的输入节点 将成为路径中需要访问的 节点。

这种注意力方法完全灵活,可以在不对输入节点总数做任何假设的情况下指向特定的输入节点。该机制在 图 15.7 中得到了展示:

图 15.7 – 一个指针网络(来源:Vinyals 等,2017)

图 15.7 – 一个指针网络(来源:Vinyals 等,2017)

后来的研究(Nazari 等人,2018)将指针网络应用于基于策略的强化学习模型,并在相对复杂的问题中取得了非常有前景的结果,与开源路径优化器相比具有优势。指针网络的细节及其在强化学习中的应用值得进一步的探讨,我们将在本章末尾引用的论文中深入讨论。

通过上述内容,我们结束了关于强化学习在供应链问题中的应用讨论。让我们总结一下所涉及的内容,以便结束这一章节。

总结

本章中,我们讨论了供应链管理中的两个重要问题类别:库存优化和车辆路径规划。这些问题都非常复杂,强化学习(RL)最近作为一种竞争性工具出现,用于解决这些问题。在本章中,对于前者问题,我们提供了如何创建环境并解决相应强化学习问题的详细讨论。这个问题的挑战在于各个回合之间的高方差,我们通过细致的超参数调优程序来缓解这一问题。对于后者问题,我们描述了一个真实的案例,讲述了一名外卖司机如何处理来自顾客的动态订单。我们讨论了如何通过指针网络使得模型在处理不同节点大小时更加灵活。

在下一章节中,我们将讨论另一组令人兴奋的应用,涉及个性化、市场营销和金融领域。我们在那里见!

参考文献