原文:
annas-archive.org/md5/0ce7869756af78df5fbcd0d4ae8d5259
译者:飞龙
第十章:模仿与迁移学习
在本文撰写时,一种名为 AlphaStar 的新 AI,深度强化学习(DRL)代理,使用模仿学习(IL)在实时战略游戏《星际争霸 II》中以五比零击败了人类对手。AlphaStar 是 David Silver 和 Google DeepMind 为建立更智能、更强大 AI 的工作的延续。AlphaStar 使用的具体技术可以写成一本书,而 IL 和模仿人类游戏的学习方法如今受到高度关注。幸运的是,Unity 已经在离线和在线训练场景的形式中实现了 IL。尽管我们在这一章中不会达到 AlphaStar 的水平,但我们仍将了解 IL 和其他形式的迁移学习的基础技术。
在这一章中,我们将研究 ML-Agents 中 IL 的实现,然后探讨其他迁移学习的应用。我们将在本章中涵盖以下内容:
-
IL 或行为克隆
-
在线训练
-
离线训练
-
迁移学习
-
模仿迁移学习
尽管 AlphaStar 在一场实时战略游戏中对人类职业玩家取得了惊人的战术胜利,但它仍然因所使用的游戏方式和动作类型而受到审视。许多玩家表示,AI 的战术能力显然优于人类,但整体的战略规划则非常糟糕。看看 Google DeepMind 如何应对这一批评应该会很有趣。
这将是一个令人兴奋的章节,并将为你的未来开发提供大量的训练可能性,一切从下一节开始。
IL 或行为克隆
IL 或行为克隆是通过从人类或可能是另一种 AI 捕捉观察和动作,并将其作为输入用于训练代理的过程。代理本质上由人类引导,并通过他们的动作和观察进行学习。一组学习观察可以通过实时游戏(在线)接收,或者从已保存的游戏中提取(离线)。这提供了捕捉多个代理的游戏并同时或单独训练它们的能力。IL 提供了训练代理的能力,或者实际上是为你可能无法通过常规 RL 训练的任务编程代理,正因如此,它很可能成为我们在不久的将来用于大多数任务的关键 RL 技术。
很难评估某个东西给你带来的价值,直到你看到没有它的情况。考虑到这一点,我们首先将通过一个没有使用 IL,但显然可以受益于 IL 的例子开始。打开 Unity 编辑器并按照以下步骤进行练习:
-
打开位于 Assets | ML-Agents | Examples | Tennis | Scenes 文件夹中的 Tennis 场景。
-
选择并禁用额外的代理训练区域,TennisArea(1) 到 TennisArea(17)。
-
选择 AgentA,并确保 Tennis Agent | Brain 设置为 TennisLearning。我们希望每个代理在这个例子中互相对抗。
-
选择 AgentB 并确保 Tennis Agent | Brain 设置为 TennisLearning。
在这个示例中,短时间内我们正在同一环境中训练多个代理。我们将在第十一章《构建多代理环境》中讨论更多代理与其他代理进行学习的场景。
-
选择 Academy 并确保 Tennis Academy | Brains 设置为 TennisLearning,并且控制选项已启用,如下图所示:
在 Academy 上将控制设置为启用
- 打开 Python/Anaconda 窗口并为训练做准备。我们将通过以下命令启动训练:
mlagents-learn config/trainer_config.yaml --run-id=tennis --train
- 观看训练过程几千次,足以让你确信代理不会轻易学会这个任务。当你确信之后,停止训练并继续进行。
仅通过查看这个第一个示例,你就可以发现普通训练以及我们所讨论的其他高级方法,如课程学习和好奇心学习,都会很难实现,而且在这种情况下可能会适得其反。在接下来的部分中,我们将展示如何在在线训练模式下使用 IL 运行这个示例。
在线训练
在线模仿学习是指你教代理实时学习一个玩家或另一个代理的观察。它也是训练代理或机器人最有趣且吸引人的方式之一。接下来,我们将跳入并为在线模仿学习设置网球环境:
-
选择 TennisArea | AgentA 对象,并将 Tennis Agent | Brain 设置为 TennisPlayer。在这个 IL 场景中,我们有一个大脑作为教师,即玩家,另一个大脑作为学生,即学习者。
-
选择 AgentB 对象并确保 Tennis Agent | Brain 设置为 TennisLearning。这将是学生大脑。
-
打开
ML-Agents/ml-agents/config
文件夹中的online_bc_config.yaml
文件。IL 使用的配置与 PPO 不同,因此这些参数的名称可能相似,但可能不会像你所习惯的那样响应。 -
在文件中向下滚动,找到**
TennisLearning
**大脑配置,如以下代码片段所示:
TennisLearning:
trainer: online_bc
max_steps: 10000
summary_freq: 1000
brain_to_imitate: TennisPlayer
batch_size: 16
batches_per_epoch: 5
num_layers: 4
hidden_units: 64
use_recurrent: false
sequence_length: 16
-
仔细查看超参数,我们可以看到有两个新的参数需要关注。以下是这些参数的总结:
-
trainer
:online_
或offline_bc
—使用在线或离线行为克隆。在这种情况下,我们正在进行在线训练。 -
brain_to_imitate
:TennisPlayer
—这设置了学习大脑应尝试模仿的目标大脑。此时我们不会对文件进行任何更改。
-
-
打开你准备好的 Python/Anaconda 窗口,并通过以下命令启动训练:
mlagents-learn config/online_bc_config.yaml --run-id=tennis_il --train --slow
- 在编辑器中按下播放按钮后,你将能够用W、A、S、D键控制左边的挡板。玩游戏时,你可能会惊讶于代理学习的速度,它可能变得相当优秀。以下是游戏进行中的一个示例:
使用 IL 进行代理的播放和教学
- 如果愿意,继续玩完示例。也可以有趣的是在游戏过程中切换玩家,甚至训练大脑并使用训练好的模型进行后续对战。你记得怎么运行训练好的模型吗?
在完成上一个练习的过程中,您可能会想,为什么我们不以这种方式训练所有的 RL 代理。这是一个很好的问题,但正如您可以想象的那样,这取决于情况。虽然 IL 非常强大,且是一个相当能干的学习者,但它并不总是会按预期工作。而且,IL 代理仅会学习它所看到的搜索空间(观察),并且只会停留在这些限制内。以 AlphaStar 为例,IL 是训练的主要输入,但团队也提到,AI 确实有很多时间进行自我对战,这可能是它许多获胜策略的来源。所以,虽然 IL 很酷且强大,但它并不是解决所有 RL 问题的“金蛋”。然而,在完成本练习后,您很可能会对 RL,尤其是 IL,产生新的更深的理解。在下一部分,我们将探索使用离线 IL。
离线训练
离线训练是通过玩家或代理在游戏中进行游戏或执行任务时生成的录制游戏文件,然后将其作为训练观察反馈给代理,帮助代理进行后续学习。虽然在线学习当然更有趣,在某些方面更适用于网球场景或其他多人游戏,但它不太实际。毕竟,通常您需要让代理实时玩好几个小时,代理才会变得优秀。同样,在在线训练场景中,您通常只能进行单代理训练,而在离线训练中,可以将演示播放提供给多个代理,以实现更好的整体学习。这还允许我们执行有趣的训练场景,类似于 AlphaStar 的训练,我们可以教一个代理,让它去教其他代理。
我们将在第十一章中深入学习多代理游戏玩法,构建多代理环境。
在接下来的练习中,我们将重新访问我们老朋友“Hallway/VisualHallway”示例。再次这么做是为了将我们的结果与之前使用该环境运行的示例练习进行比较。请按照本练习的步骤设置一个新的离线训练会话:
-
克隆并下载 ML-Agents 代码到一个新的文件夹,可能选择
ml-agents_b
、ml-agents_c
或其他名称。这样做的原因是为了确保我们在干净的环境中运行这些新练习。此外,有时返回到旧环境并回忆可能忘记更新的设置或配置也能有所帮助。 -
启动 Unity 并打开UnitySDK项目以及 Hallway 或 VisualHallway 场景,您可以选择其中之一。
-
场景应设置为以播放器模式运行。只需确认这一点。如果需要更改,请进行更改。
-
如果场景中有其他活动的代理训练环境,请禁用它们。
-
在层级视图中选择 HallwayArea | Agent。
-
点击 Inspector 窗口底部的 Add Component 按钮,输入
demo
,并选择演示录制组件,如下图所示:
添加演示录制器
-
如前面的截图所示,点击新建的演示录制组件上的“Record”按钮,确保检查所有选项。同时,填写录制的“演示名称”属性,如图所示。
-
保存场景和项目。
-
按下 Play 按钮并播放场景一段时间,至少几分钟,可能不到几个小时。当然,你的游戏表现也会决定代理的学习效果。如果你玩的不好,代理也会学得差。
-
当你认为足够的时间已经过去,并且你已经尽力完成了游戏后,停止游戏。
在游戏播放结束后,你应该会看到一个名为 Demonstrations 的新文件夹,在项目窗口的 Assets 根文件夹中创建。文件夹内将包含你的演示录制。这就是我们在下一部分将喂入代理的数据。
设置训练环境
现在我们已经有了演示录制,可以继续进行训练部分。然而,这一次,我们将把观察文件回放给多个代理,在多个环境中进行训练。打开 Hallway/VisualHallway 示例场景,并按照以下练习设置训练:
-
选择并启用所有 HallwayArea 训练环境,HallwayArea(1)到 HallwayArea(15)。
-
在层级视图中选择 HallwayArea | Agent,然后将 Hallway Agent | Brain 切换为 HallwayLearning,如下图所示:
设置代理组件
-
同时,选择并禁用演示录制组件,如前面的屏幕截图所示。
-
确保场景中的所有代理都使用 HallwayLearning 大脑。
-
在层级视图中选择 Academy,然后启用 Hallway Academy | Brains | Control 选项,如下图所示:
启用 Academy 控制大脑
- 保存场景和项目
现在,我们已经为代理学习配置了场景,可以进入下一部分,开始喂入代理数据。
喂入代理数据
在执行在线 IL 时,我们每次只给一个代理喂入数据,场景是网球场。然而,这次我们将从同一个演示录制中训练多个代理,以提高训练效果。
我们已经为训练做好了准备,现在开始在接下来的练习中喂入代理数据:
-
打开一个 Python/Anaconda 窗口,并从新的
ML-Agents
文件夹中设置训练环境。你已经重新克隆了源代码,对吧? -
从
ML-Agents/ml-agents_b/config
文件夹中打开offline_bc_config.yaml
文件。文件内容如下,供参考:
default:
trainer: offline_bc
batch_size: 64
summary_freq: 1000
max_steps: 5.0e4
batches_per_epoch: 10
use_recurrent: false
hidden_units: 128
learning_rate: 3.0e-4
num_layers: 2
sequence_length: 32
memory_size: 256
demo_path: ./UnitySDK/Assets/Demonstrations/<Your_Demo_File>.demo
HallwayLearning:
trainer: offline_bc
max_steps: 5.0e5
num_epoch: 5
batch_size: 64
batches_per_epoch: 5
num_layers: 2
hidden_units: 128
sequence_length: 16
use_recurrent: true
memory_size: 256
sequence_length: 32
demo_path: ./UnitySDK/Assets/Demonstrations/demo.demo
- 将
HallwayLearning
或VisualHallwayLearning
大脑的最后一行更改为以下内容:
HallwayLearning:
trainer: offline_bc
max_steps: 5.0e5
num_epoch: 5
batch_size: 64
batches_per_epoch: 5
num_layers: 2
hidden_units: 128
sequence_length: 16
use_recurrent: true
memory_size: 256
sequence_length: 32
demo_path: ./UnitySDK/Assets/Demonstrations/AgentRecording.demo
-
请注意,如果你使用的是
VisualHallwayLearning
大脑,你还需要在前面的配置脚本中更改相应的名称。 -
完成编辑后,保存你的更改。
-
返回你的 Python/Anaconda 窗口,使用以下命令启动训练:
mlagents-learn config/offline_bc_config.yaml --run-id=hallway_il --train
- 当提示时,在编辑器中按下 Play 并观看训练过程。你会看到代理使用与自己非常相似的动作进行游戏,如果你玩的不错,代理将很快开始学习,你应该会看到一些令人印象深刻的训练成果,这一切都得益于模仿学习。
强化学习可以被看作是一种蛮力学习方法,而模仿学习和通过观察训练的改进无疑将主导未来的代理训练。当然,难道这真的是令人惊讶的事情吗?毕竟,我们这些简单的人类就是这样学习的。
在下一部分,我们将探讨深度学习的另一个令人兴奋的领域——迁移学习,以及它如何应用于游戏和深度强化学习(DRL)。
迁移学习
模仿学习,按定义属于迁移学习(TL)的一种类型。我们可以将迁移学习定义为一个代理或深度学习网络通过将经验从一个任务转移到另一个任务来进行训练的过程。这可以像我们刚才进行的观察训练那样简单,或者像在代理的大脑中交换层/层权重,或者仅仅在一个相似的任务上训练代理那样复杂。
在迁移学习中,我们需要确保我们使用的经验或先前的权重是可以泛化的。通过本书的基础章节(第 1-3 章),我们学习了使用诸如 Dropout 和批量归一化等技术进行泛化的价值。我们了解到,这些技术对于更通用的训练非常重要;这种训练方式使得代理/网络能够更好地推理测试数据。这与我们使用一个在某个任务上训练的代理去学习另一个任务是一样的。一个更通用的代理,实际上比一个专门化的代理更容易转移知识,甚至可能完全不同。
我们可以通过一个快速的示例来演示这一点,开始训练以下简单的练习:
-
在 Unity 编辑器中打开 VisualHallway 场景。
-
禁用任何额外的训练区域。
-
确认 Academy 控制大脑。
-
从 Hallway/Brains 文件夹中选择 VisualHallwayLearning 大脑,并将 Vector Action | Branches Size | Branch 0 Size 设置为
7
,如下面的截图所示:
增加代理的向量动作空间
-
我们增加了大脑的动作空间,使其与我们的迁移学习环境所需的动作空间兼容,稍后我们会详细介绍。
-
保存场景和项目。
-
打开一个准备好的 Python/Anaconda 窗口以进行训练。
-
使用以下代码启动训练会话:
mlagents-learn config/trainer_config.yaml --run-id=vishall --train --save-freq=10000
-
在这里,我们引入了一个新的参数,用于控制模型检查点创建的频率。目前,默认值设置为 50,000,但我们不想等这么久。
-
在编辑器中运行代理进行训练,至少保存一个模型检查点,如下图所示:
ML-Agents 训练器正在创建一个检查点。
-
检查点是一种获取大脑快照并将其保存以供后用的方法。这允许你返回并继续从上次停止的地方进行训练。
-
让代理训练到一个检查点,然后通过按Ctrl + C(在 Python/Anaconda 窗口中)或在 Mac 上按command + C来终止训练。
当你终止训练后,是时候在下一个部分尝试将这个已保存的大脑应用到另一个学习环境中了。
转移大脑。
我们现在想将刚刚训练过的大脑带入一个新的、但相似的环境中重新使用。由于我们的代理使用视觉观察,这使得任务变得更简单,但你也可以尝试用其他代理执行这个示例。
让我们打开 Unity,进入 VisualPushBlock 示例场景并按照这个练习操作:
-
选择 Academy 并启用它来控制大脑。
-
选择代理并设置它使用 VisualPushBlockLearning 大脑。你还应该确认这个大脑的配置与我们刚才运行的 VisualHallwayLearning 大脑相同,即视觉观察和向量动作空间相匹配。
-
在文件资源管理器或其他文件浏览器中打开
ML-Agents/ml-agents_b/models/vishall-0
文件夹。 -
将文件和文件夹的名称从
VisualHallwayLearning
更改为VisualPushBlockLearning
,如以下截图所示:
手动更改模型路径。
-
通过更改文件夹的名称,我们实际上是在告诉模型加载系统将我们的 VisualHallway 大脑恢复为 VisualPushBlockBrain。这里的技巧是确保两个大脑具有相同的超参数和配置设置。
-
说到超参数,打开
trainer_config.yaml
文件,确保 VisualHallwayLearning 和 VisualPushBlockLearning 参数相同。以下代码片段显示了这两个配置的参考示例:
VisualHallwayLearning:
use_recurrent: true
sequence_length: 64
num_layers: 1
hidden_units: 128
memory_size: 256
beta: 1.0e-2
gamma: 0.99
num_epoch: 3
buffer_size: 1024
batch_size: 64
max_steps: 5.0e5
summary_freq: 1000
time_horizon: 64
VisualPushBlockLearning:
use_recurrent: true
sequence_length: 64
num_layers: 1
hidden_units: 128
memory_size: 256
beta: 1.0e-2
gamma: 0.99
num_epoch: 3
buffer_size: 1024
batch_size: 64
max_steps: 5.0e5
summary_freq: 1000
time_horizon: 64
-
编辑完成后,保存配置文件。
-
打开你的 Python/Anaconda 窗口,使用以下代码启动训练:
mlagents-learn config/trainer_config.yaml --run-id=vishall --train --save-freq=10000 --load
-
之前的代码不是打印错误;它是我们用来运行 VisualHallway 示例的完全相同的命令,只是在末尾加上了
--load
。这应该会启动训练并提示你运行编辑器。 -
随时可以运行训练,只要你喜欢,但请记住,我们几乎没有训练原始代理。
现在,在这个示例中,即使我们已经训练了代理完成 VisualHallway,这可能也不太有效地将知识转移到 VisualPushBlock。为了这个示例,我们选择了这两个,因为它们非常相似,将一个训练好的大脑转移到另一个上要简单一些。对于你自己,能够转移训练过的大脑可能更多的是关于在新的或修改过的关卡上重新训练代理,甚至允许代理在逐渐更难的关卡上进行训练。
根据你使用的 ML-Agents 版本,这个示例可能效果不一。具体问题在于模型的复杂性、超参数的数量、输入空间以及我们正在运行的奖励系统。保持这些因素一致也需要非常注意细节。在接下来的章节中,我们将稍作偏离,探讨这些模型的复杂性。
探索 TensorFlow 检查点
TensorFlow 正迅速成为支撑大多数深度学习基础设施的底层图计算引擎。尽管我们没有详细介绍这些图引擎是如何构建的,但从视觉上查看这些 TensorFlow 模型是很有帮助的。我们不仅能更好地理解这些系统的复杂性,而且一个好的图像往往胜过千言万语。让我们打开浏览器,进行下一个练习:
-
使用你最喜欢的搜索引擎在浏览器中搜索短语
netron tensorflow
。Netron 是一个开源的 TensorFlow 模型查看器,完美符合我们的需求。 -
找到指向 GitHub 页面的链接,在页面中找到下载二进制安装程序的链接。选择适合你平台的安装程序并点击下载。这将带你到另一个下载页面,你可以选择下载的文件。
-
使用适合你平台的安装程序安装 Netron 应用程序。在 Windows 上,下载安装 exe 安装程序并运行即可。
-
运行 Netron 应用程序,启动后,你将看到以下内容:
Netron 应用程序
-
点击窗口中间的“打开模型…”按钮
-
使用文件资源管理器定位到
ML-Agents/ml-agents/models/vishall-0\VisualHallwayLearning
文件夹,并找到raw_graph.def
文件,如下图所示:
选择要加载的模型图定义
- 加载图形后,使用右上角的 - 按钮将视图缩放到最大,类似于以下截图:
我们代理大脑的 TensorFlow 图模型
-
如插图所示,这个图形极其复杂,我们不太可能轻易理解它。然而,浏览并观察这个模型/图形是如何构建的还是很有趣的。
-
向图表顶部滚动,找到一个名为 advantages 的节点,然后选择该节点,并查看图表和输入的模型属性,如下图所示:
优势图模型的属性
- 在这个模型的属性视图中,你应该能够看到一些非常熟悉的术语和设置,例如 visual_observation_0,它显示该模型输入是形状为 [84,84,3] 的张量。
完成后,可以随意查看其他模型,甚至探索 Unity 之外的其他模型。虽然这个工具还不完全能够总结像我们这样的复杂模型,但它展示了这些工具变得越来越强大的潜力。更重要的是,如果你能找到合适的方式,你甚至可以导出变量以供以后检查或使用。
模仿迁移学习
模仿学习的一个问题是,它常常将代理引导到一个限制其未来可能行动的路径上。这和你被教导错误的方式执行任务,然后按照那个方式做,可能没有多想,最后才发现其实有更好的方法并无太大区别。事实上,人类在历史上一直容易犯这种问题。也许你小时候学到吃完饭后游泳很危险,但后来通过自己的实验,或只是凭常识,你才知道那只是个神话,这个神话曾经被认为是事实很长时间。通过观察训练代理也没有什么不同,它会限制代理的视野,将其狭隘化,只能局限于它所学的内容。然而,有一种方法可以让代理回到部分的蛮力或试错探索,从而扩展它的训练。
使用 ML-Agents,我们可以将 IL 与某种形式的迁移学习结合起来,让代理先通过观察学习,然后通过从曾经的学生身上继续学习进一步训练。这种 IL 链式学习,若你愿意,可以让你训练一个代理来自动训练多个代理。让我们打开 Unity,进入 TennisIL 场景,并按照下一个练习操作:
- 选择 TennisArea | Agent 对象,在检查器中禁用 BC Teacher Helper 组件,然后添加一个新的演示记录器,如下图所示:
检查 BC Teacher 是否附加到代理上
-
BC Teacher Helper 是一个记录器,功能与演示记录器类似。BC 记录器允许你在代理运行时打开和关闭录制,非常适合在线训练,但在编写本文时,该组件无法使用。
-
确保 Academy 设置为控制 TennisLearning 大脑。
-
保存场景和项目。
-
打开一个 Python/Anaconda 窗口,并使用以下命令启动训练:
mlagents-learn config/online_bc_config.yaml --run-id=tennis_il --train --slow
-
当提示时点击播放,在编辑器中运行游戏。使用 W, A, S, D 键控制蓝色球拍,并玩几秒钟来热身。
-
热身后,按 R 键开始录制演示观察。玩几分钟游戏,让智能体变得更有能力。在智能体能够回球后,停止训练。
这不仅会训练智能体,效果很好,而且还会创建一个演示录制回放,我们可以用它进一步训练智能体,让它们学习如何像 AlphaStar 训练一样互相对战。接下来,我们将在下一个部分设置我们的网球场景,以便在离线训练模式下运行多个智能体。
使用一个演示训练多个智能体
现在,通过录制我们打网球的过程,我们可以将此录制用于训练多个智能体,所有智能体的反馈都汇入一个策略。打开 Unity 到网球场景,即那个拥有多个环境的场景,并继续进行下一个练习:
- 在层级窗口的过滤栏中输入
agent
,如以下截图所示:
搜索场景中的所有智能体
-
选择场景中所有智能体对象,并批量更改它们的大脑,使用 TennisLearning 而不是 TennisPlayer。
-
选择 Academy 并确保启用它以控制智能体大脑。
-
打开
config/offline_bc_config.yaml
文件。 -
在底部为
TennisLearning
大脑添加以下新部分:
TennisLearning:
trainer: offline_bc
max_steps: 5.0e5
num_epoch: 5
batch_size: 64
batches_per_epoch: 5
num_layers: 2
hidden_units: 128
sequence_length: 16
use_recurrent: true
memory_size: 256
sequence_length: 32
demo_path: ./UnitySDK/Assets/Demonstrations/TennisAgent.demo
-
保存场景和项目。
-
打开 Python/Anaconda 窗口并使用以下代码进行训练:
mlagents-learn config/offline_bc_config.yaml --run-id=tennis_ma --train
-
你可能希望添加
--slow
参数来观看训练过程,但这不是必需的。 -
让智能体训练一段时间,并注意其进步。即使只有短暂的观察录制输入,智能体也能很快成为一个有能力的玩家。
有多种方法可以执行这种类型的 IL 和迁移学习链,使智能体在训练时具有一定的灵活性。你甚至可以不使用 IL,而是直接使用已训练的模型的检查点,并像我们之前那样通过迁移学习来运行智能体。可能性是无限的,最终哪些做法会成为最佳实践仍然有待观察。
在下一个部分,我们将提供一些练习,你可以用它们来进行个人学习。
练习
本章结尾的练习可能会提供数小时的乐趣。尽量只完成一到两个练习,因为我们还需要完成本书:
-
设置并运行 PyramidsIL 场景以进行在线 IL 训练。
-
设置并运行 PushBlockIL 场景以进行在线 IL 训练。
-
设置并运行 WallJump 场景以进行在线 IL 训练。这需要你修改场景。
-
设置并运行 VisualPyramids 场景以使用离线录制。录制训练过程,然后训练智能体。
-
设置并运行 VisualPushBlock 场景以使用离线录制。使用离线 IL 训练智能体。
-
设置 PushBlockIL 场景以录制观察演示。然后,使用此离线训练来训练多个代理在常规的 PushBlock 场景中。
-
设置 PyramidsIL 场景以录制演示。然后,使用该数据进行离线训练,以训练多个代理在常规的 Pyramids 场景中。
-
在 VisualHallway 场景中训练一个代理,使用任何你喜欢的学习方式。训练后,修改 VisualHallway 场景,改变墙壁和地板的材质。在 Unity 中更改物体材质非常容易。然后,使用交换模型检查点的技术,将之前训练好的大脑迁移到新环境中。
-
完成第八个练习,但使用 VisualPyramids 场景。你也可以在此场景中添加其他物体或方块。
-
完成第八个练习,但使用 VisualPushBlock 场景。尝试添加其他方块或代理可能需要避开的其他物体。
只需记住,如果你正在尝试任何迁移学习练习,匹配复杂图表时要特别注意细节。在下一节中,我们将总结本章所涵盖的内容。
摘要
在本章中,我们介绍了一种新兴的强化学习技术,叫做模仿学习(Imitation Learning)或行为克隆(Behavioral Cloning)。正如我们所学,这种技术通过捕捉玩家玩游戏时的观察数据,然后在在线或离线环境中使用这些观察数据进一步训练代理。我们还了解到,IL 只是迁移学习的一种形式。接着,我们介绍了使用 ML-Agents 的一种技术,它可以让你在不同的环境中迁移大脑。最后,我们探讨了如何将 IL 和迁移学习结合起来,作为激励代理自主开发新策略的训练方法。
在下一章中,我们将通过研究多个代理训练场景,进一步加深对游戏中深度强化学习(DRL)的理解。
第十一章:构建多代理环境
在完成单代理的经验后,我们可以进入更加复杂但同样有趣的多代理环境中,在该环境中训练多个代理以合作或竞争的方式工作。这也为训练具有对抗性自我对抗、合作性自我对抗、竞争性自我对抗等的新机会打开了大门。在这里,可能性变得无穷无尽,这也许就是 AI 的真正“圣杯”。
在本章中,我们将介绍多代理训练环境的多个方面,主要的章节主题如下所示:
-
对抗性和合作性自我对抗
-
竞争性自我对抗
-
多大脑游戏
-
通过内在奖励增加个体性
-
个体化的外部奖励
本章假设你已经完成了前三章并做了一些练习。在下一节中,我们将开始介绍各种自我对抗场景。
最好从 ML-Agents 仓库的一个新克隆开始本章内容。我们这样做是为了清理我们的环境,确保没有不小心保存的错误配置。如果你需要帮助,可以查阅前面的章节。
对抗性和合作性自我对抗
术语自我对抗当然对不同的人来说有不同的含义,但在此案例中,我们的意思是大脑通过操控多个代理来与自己进行竞争(对抗性)或合作。在 ML-Agents 中,这可能意味着一个大脑在同一环境中操控多个代理。在 ML-Agents 中有一个很好的示例,所以打开 Unity 并按照下一个练习准备好这个场景以进行多代理训练:
-
从 Assets | ML-Agents | Examples | Soccer | Scenes 文件夹中打开 SoccerTwos 场景。该场景默认设置为玩家模式运行,但我们需要将其转换回学习模式。
-
选择并禁用所有 SoccerFieldTwos(1)到 SoccerFieldTwos(7)区域。我们暂时不使用这些区域。
-
选择并展开剩余的活动 SoccerFieldTwos 对象。这将显示一个包含四个代理的游戏区,其中两个标记为 RedStriker 和 BlueStriker,另外两个标记为 RedGoalie 和 BlueGoalie。
-
检查代理并将每个代理的大脑设置为 StrikerLearning 或 GoalieLearning,具体设置请参见下图:
在代理上设置学习大脑
- 在这个环境中,我们有四个代理,由大脑控制,这些大脑既在合作又在竞争。说实话,这个示例非常出色,极好地展示了合作性和竞争性自我对抗的概念。如果你还在努力理解一些概念,可以参考这个图示,它展示了如何将这些内容结合起来:
SoccerTwos 的大脑架构
-
如我们所见,我们有两个大脑控制四个代理:两个前锋和两个守门员。前锋的任务是进攻守门员,当然,守门员的任务是防守进球。
-
选择“Academy”并启用“Soccer Academy | Brains | Control”以控制两个大脑,如下所示:
在 Academy 中设置大脑控制
- 同时,注意一下“Striker”、“Goalie Reward”和“Punish”设置,这些都位于“Soccer Academy”组件的底部。还需要注意的是,每个大脑的
reward
函数是如何运作的。以下是该示例中reward
函数的数学描述:
-
这意味着,当进球时,每个四个代理会根据其位置和队伍获得奖励。因此,如果红队进球,红队的前锋将获得
+1
奖励,蓝队前锋将获得-0.1
奖励,红队守门员将获得+0.1
奖励,而可怜的蓝队守门员将获得-1
奖励。现在,你可能会认为这可能会导致重叠,但请记住,每个代理对一个状态或观察的看法是不同的。因此,奖励将根据该状态或观察应用于该代理的策略。实质上,代理正在基于其当前对环境的看法进行学习,这种看法会根据哪个代理发送该观察而变化。 -
编辑完毕后保存场景和项目。
这为我们的场景设置了多代理训练,使用两个大脑和四个代理,既包括竞争性又包括合作性的自我对战。在接下来的部分中,我们完成外部配置并开始训练场景。
训练自我对战环境
训练这种类型的自我对战环境不仅为增强训练提供了更多可能性,还为有趣的游戏环境开辟了新的可能性。在某些方面,这种类型的训练环境看起来和观看游戏一样有趣,正如我们将在本章结束时所看到的。
但现在,我们将回到前面,继续设置我们需要的配置,以便在下一步的练习中训练我们的 SoccerTwos 多代理环境:
- 打开
ML-Agents/ml-agents/config/trainer_config.yaml
文件,查看StrikerLearning
和GoalieLearning
配置部分,如下所示:
StrikerLearning:
max_steps: 5.0e5
learning_rate: 1e-3
batch_size: 128
num_epoch: 3
buffer_size: 2000
beta: 1.0e-2
hidden_units: 256
summary_freq: 2000
time_horizon: 128
num_layers: 2
normalize: false
GoalieLearning:
max_steps: 5.0e5
learning_rate: 1e-3
batch_size: 320
num_epoch: 3
buffer_size: 2000
beta: 1.0e-2
hidden_units: 256
summary_freq: 2000
time_horizon: 128
num_layers: 2
normalize: false
-
显而易见的想法是大脑应该有类似的配置,你可能会从这种方式开始,没错。然而,请注意,即使在这个示例中,
batch_size
参数对于每个大脑也设置得不同。 -
打开 Python/Anaconda 窗口,切换到 ML-Agents 虚拟环境,然后从
ML-Agents/ml-agents
文件夹中启动以下命令:
mlagents-learn config/trainer_config.yaml --run-id=soccer --train
- 当提示时按下播放,你应该能看到以下训练会话正在运行:
在训练模式下运行的 SoccerTwos 场景
-
如前所述,这可以是一个非常有趣的示例,观看时很有娱乐性,并且训练速度惊人地快。
-
在进行了一些训练后,打开 Python/Anaconda 控制台,并注意到现在你得到了两个大脑的统计信息,分别是 StrikerLearning 和 GoalieLearning,如下图所示:
控制台输出显示来自两个大脑的统计信息
-
注意到 StrikerLearning 和 GoalieLearning 互相返回相反的奖励。这意味着,为了训练这些代理,它们必须使两者的平均奖励都平衡到 0。当代理们进行训练时,你会注意到它们的奖励开始收敛到 0,这是这个示例的最佳奖励。
-
让示例运行至完成。观看这些环境时很容易迷失其中,因此你甚至可能没有注意到时间流逝。
这个示例展示了我们如何通过自我游戏利用多代理训练的力量,同时教两个大脑如何同时进行竞争和合作。在接下来的部分,我们将看看多个代理如何在自我游戏中相互竞争。
对抗性自我游戏
在前面的示例中,我们看到了一个既有合作又有竞争的自我游戏示例,其中多个代理几乎是共生地运作的。虽然这是一个很好的示例,但它仍然将一个大脑的功能与另一个大脑通过奖励函数联系起来,因此我们观察到代理们几乎处于奖励对立的情境中。相反,我们现在想要查看一个能够仅通过对抗性自我游戏来训练大脑与多个代理的环境。当然,ML-Agents 就有这样一个环境,称为 Banana,它包括几个随机游走在场景中并收集香蕉的代理。这些代理还有一个激光指示器,如果击中对手,可以使其禁用几秒钟。接下来的练习中,我们将查看这个场景:
-
打开位于 Assets | ML-Agents | Examples | BananaCollectors | Scenes 文件夹中的 Banana 场景。
-
选择并禁用额外的训练区域 RLArea(1) 到 RLArea(3)。
-
选择 RLArea 中的五个代理(Agent、Agent(1)、Agent(2)、Agent(3)、Agent(4))。
-
将 Banana Agent | Brain 从 BananaPlayer 切换到 BananaLearning。
-
选择学院并将 Banana Academy | Brains | Control 属性设置为启用。
-
在编辑器中选择 Banana Agent 组件(脚本),并在你选择的代码编辑器中打开。如果你向下滚动到页面底部,你会看到
OnCollisionEnter
方法,如下所示:
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("banana"))
{
Satiate();
collision.gameObject.GetComponent<BananaLogic>().OnEaten();
AddReward(1f);
bananas += 1;
if (contribute)
{
myAcademy.totalScore += 1;
}
}
if (collision.gameObject.CompareTag("badBanana"))
{
Poison();
collision.gameObject.GetComponent<BananaLogic>().OnEaten();
AddReward(-1f);
if (contribute)
{
myAcademy.totalScore -= 1;
}
}
}
- 阅读上述代码后,我们可以将
reward
函数总结为以下内容:
这仅仅意味着代理们只会因吃香蕉而获得奖励。有趣的是,禁用对手(使用激光或被禁用)并没有奖励。
-
保存场景和项目。
-
打开准备好的 Python/Anaconda 控制台,并使用以下命令开始训练:
mlagents-learn config/trainer_config.yaml --run-id=banana --train
- 当提示时,按下编辑器中的 Play 按钮,并观察接下来截图中展示的动作:
香蕉收集器代理正在执行任务
- 让场景运行尽可能长的时间。
这个场景是一个很好的例子,展示了代理如何学习使用一个不返回奖励的次要游戏机制,但像激光一样,它仍然被用来使对抗性的收集者无法动弹,从而获得更多的香蕉,同时仅仅因吃香蕉才获得奖励。这个例子展示了强化学习(RL)的真正力量,以及如何利用它来发现次要策略以解决问题。虽然这是一个非常有趣的方面,观看起来也很有趣,但请考虑其更深远的影响。研究表明,RL 已经被用来优化从网络到推荐系统的所有内容,通过对抗性自我游戏,因此未来看强化学习这种学习方法能够达成什么目标将会非常有趣。
多脑模式游戏
ML-Agents 工具包的一个真正伟大的特点是能够快速添加由多个大脑驱动的多个代理。这使我们能够构建更复杂的游戏环境或场景,拥有有趣的代理/人工智能,既可以与其互动也可以对抗。让我们看看将我们的足球示例转换为让所有代理使用独立大脑是多么容易:
-
打开我们之前查看的 SoccerTwos 场景的编辑器。
-
定位到示例中的
Brains
文件夹,路径为 Assets | ML-Agents | Examples | Soccer | Brains。 -
点击窗口右上角的 Create 菜单,在上下文菜单中选择 ML-Agents | Learning Brain:
创建一个新的学习大脑
-
将新大脑命名为
RedStrikerLearning
。在同一文件夹中创建三个新的大脑,分别命名为RedGoalieLearning
、BlueGoalieLearning
和BlueStrikerLearning
。 -
选择 RedStrikerLearning。然后选择并拖动 StrikerLearning 大脑,将其放入“从槽复制大脑参数”位置:
从另一个大脑复制大脑参数
-
对于 BlueStrikerLearning,复制 StrikerLearning 的参数。然后对 RedGoalieLearning 和 BlueGoalieLearning 执行相同操作,复制 GoalieLearning 的参数。
-
在 Hierarchy 窗口中选择 RedAgent,并将 Agent Soccer | Brain 设置为 RedStrikerLearning。对其他每个代理执行相同操作,将颜色与位置匹配。BlueGoalie -> BlueGoalieLearning。
-
选择 Academy,并从 Soccer Academy | Brains 列表中移除当前所有的大脑。然后使用添加新按钮将所有我们刚创建的新大脑添加回列表,并设置为控制:
将新的大脑添加到 Academy
-
保存场景和项目。现在,我们只是将示例从使用两个并行大脑的自我游戏模式切换为让代理们分别在不同的队伍中。
-
打开一个设置好用于训练的 Python/Anaconda 窗口,并用以下内容启动:
mlagents-learn config/trainer_config.yaml --run-id=soccer_mb --train
- 让训练运行并注意观察代理的表现,看看他们是否像之前一样发挥得那么好。同时也查看控制台输出。你会看到现在它为四个代理提供报告,但代理之间仍然有些共生关系,因为红色前锋与蓝色守门员相对。然而,现在他们的训练速度要慢得多,部分原因是每个大脑现在只能看到一半的观察数据。记得之前我们有两个前锋代理将数据输入到一个大脑,而正如我们所学,这种额外的状态输入可以显著加速训练。
此时,我们有四个代理,四个独立的大脑正在进行一场足球比赛。当然,由于代理仍通过共享奖励函数进行共生式训练,我们不能真正把它们称为独立个体。除了,如我们所知,队伍中的个体往往会受到其内在奖励系统的影响。我们将在下一部分中查看内在奖励的应用如何使最后的这个练习更加有趣。
通过内在奖励增加个体性
正如我们在第九章《奖励与强化学习》中学到的,内在奖励系统和代理动机的概念目前在 ML-Agents 中只是作为好奇心学习实现的。应用内在奖励或动机与强化学习结合的这一领域,在游戏和人际应用中有广泛的应用,例如仆人代理。
在下一个练习中,我们将为一些代理添加内在奖励,并观察这对游戏产生什么影响。打开上一个练习的场景并按以下步骤操作:
-
打开
ML-Agents/ml-agents/config/trainer_config.yaml
文件,用文本编辑器进行编辑。我们之前没有为我们的代理添加任何专门的配置,但现在我们将纠正这一点并添加一些额外的配置。 -
将以下四个新的大脑配置添加到文件中:
BlueStrikerLearning:
max_steps: 5.0e5
learning_rate: 1e-3
batch_size: 128
num_epoch: 3
buffer_size: 2000
beta: 1.0e-2
hidden_units: 256
summary_freq: 2000
time_horizon: 128
num_layers: 2
normalize: false
BlueGoalieLearning:
use_curiosity: true
summary_freq: 1000
curiosity_strength: 0.01
curiosity_enc_size: 256
max_steps: 5.0e5
learning_rate: 1e-3
batch_size: 320
num_epoch: 3
buffer_size: 2000
beta: 1.0e-2
hidden_units: 256
time_horizon: 128
num_layers: 2
normalize: false
RedStrikerLearning:
use_curiosity: true
summary_freq: 1000
curiosity_strength: 0.01
curiosity_enc_size: 256
max_steps: 5.0e5
learning_rate: 1e-3
batch_size: 128
num_epoch: 3
buffer_size: 2000
beta: 1.0e-2
hidden_units: 256
time_horizon: 128
num_layers: 2
normalize: false
RedGoalieLearning:
max_steps: 5.0e5
learning_rate: 1e-3
batch_size: 320
num_epoch: 3
buffer_size: 2000
beta: 1.0e-2
hidden_units: 256
summary_freq: 2000
time_horizon: 128
num_layers: 2
normalize: false
-
注意我们已经在
BlueGoalieLearning
和RedStrikerLearning
大脑上启用了use_curiosity: true
。你可以从文件中原有的GoalieLearning
和StrikerLearning
大脑配置中复制并粘贴大部分内容;只需注意细节即可。 -
编辑完成后保存文件。
-
打开你的 Python/Anaconda 控制台并使用以下命令开始训练:
mlagents-learn config/trainer_config.yaml --run-id=soccer_icl --train
- 让代理训练一段时间,你会注意到,尽管它们确实表现得像更独立的个体,但它们的训练能力仍然较差,任何在训练中看到的进步很可能是因为给了几个代理好奇心奖励。
通过内在奖励或动机为代理添加个性化的能力,随着深度强化学习(DRL)在游戏和其他潜在应用中的发展,肯定会逐渐成熟,并希望能够提供其他不完全专注于学习的内在奖励模块。然而,内在奖励确实能鼓励个性化,因此,在下一节中,我们将为修改后的示例引入外在奖励。
迁移学习的另一个优秀应用是,在代理已经完成一般任务的训练后,能够添加内在奖励模块。
个性化的外在奖励
我们已经在多个章节中广泛讨论了外部或外在奖励,以及如何使用技术来优化和鼓励它们对代理的作用。现在,看似通过修改代理的行为来调整其外在奖励或本质上的奖励函数,似乎是一种简便的方法。然而,这可能会带来困难,并且通常会导致训练表现变差,这就是我们在前一节中为几个代理添加课程学习(CL)时所观察到的情况。当然,即使训练变差,我们现在手头上有许多技巧,比如迁移学习(TL),也叫做模仿学习(IL);好奇心;和 CL,来帮助我们纠正问题。
在接下来的练习中,我们将通过添加额外的外在奖励来为我们的代理增添更多个性。打开我们刚才正在操作的前一个练习示例并跟着做:
-
从菜单中选择窗口 | 资产商店。这将带你进入 Unity 资产商店,这是一个非常优秀的辅助资源库。虽然大多数这些资源是付费的,但老实说,与同类开发者工具相比,价格非常低廉,而且有几个免费的、非常优秀的资源,你可以开始使用它们来增强你的训练环境。资产商店是 Unity 最好与最糟糕的地方之一,所以如果你购买了资源,记得查看评论和论坛帖子。任何好的资源通常都会有自己的开发者论坛,而艺术资源则较少。
-
在搜索栏中输入
toony tiny people
并按 Enter 键或点击搜索按钮。这将显示搜索结果。
我们要感谢Polygon Blacksmith,感谢他们的支持,使我们能够将他们的 Toony Tiny People Demo 资源与本书的源文件一起分发。此外,他们的角色资源包做得非常好,且易于使用。如果你决定构建一个完整的游戏或增强版演示,他们的一些较大资源包的价格也是一个很好的起点。
- 选择名为 Toony Tiny People Demo 的结果,由 Polygon Blacksmith 提供,并点击选择它。它将显示在此截图中:
Polygon Blacksmith 的 Toony Tiny People Demo 资源
- 点击红色的下载按钮,下载完成后,按钮会变为导入,如前面的截图所示。点击导入按钮导入资产。当导入对话框弹出时,确保选中所有内容,然后点击导入。
这些类型的低多边形或卡通资产非常适合让简单的游戏或模拟更具娱乐性和观看乐趣。虽然看起来不多,但你可以花费大量时间观看这些训练模拟的运行,若它们看起来更吸引人,那将大有帮助。
-
选择并展开层级中的所有代理对象。这包括 RedStriker、BlueStriker、RedGoalie 和 BlueGoalie。
-
打开项目窗口中的 Assets | TooyTinyPeople | TT_demo | prefabs 文件夹。
-
从前面的文件夹中选择并拖动 TT_demo_Female 预设体,并将其拖放到层级窗口中的 RedStriker 代理对象上。选择位于代理对象下方的立方体对象,并在检查器中禁用它。继续按以下列表对其他代理执行相同操作:
-
TT_demo_female -> RedStriker
-
TT_demo_male_A -> BlueStriker
-
TT_demo_police -> BlueGoalie
-
TT_demo_zombie -> RedGoalie
-
这一点在下图中得到了进一步展示:
设置新的代理模型
- 确保也将新代理模型的 Transform 位置和朝向重置为
[0,0,0]
,如以下截图所示:
重置拖动的预设体的朝向和位置
- 保存场景和项目。
此时,你可以在训练中运行场景,观看新的代理模型移动,但这并没有多大意义。代理的行为仍然是一样的,所以接下来我们需要做的是基于某些任意的个性设置额外的外在奖励,我们将在下一节定义这些个性。
通过自定义奖励函数创建独特性
尽管结果可能没有我们预期的那样独特,我们通过加入内在奖励成功地使我们的代理变得更具个性。这意味着我们现在希望通过修改代理的外在奖励来使其行为更具个性,最终使游戏更具娱乐性。
我们开始实现这一点的最佳方式是查看我们之前描述的 SoccerTwos
奖励函数;这些奖励函数在这里列出,供参考:
我们现在想做的是基于当前角色对奖励函数进行一些个性化修改。我们将通过简单地将函数链与基于角色类型的修改进行组合来实现,如下所示:
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/03fb39c9-5222-466c-a90a-b30d4e4c340c.png 或 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/7be32220-52f5-4067-a9a1-d9731f2d59dc.png
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/a2547f6b-3e83-4ce3-abb7-a9405f5ef26b.png 或 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/33de502b-245d-4659-9715-41af693970b9.png
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/fd164fb9-7793-492c-a371-3bfac8a77c08.png 或 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/b540589a-8861-490a-8476-253b634e98b7.png
https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/dd76d52b-c033-4888-856b-f5e6384f4440.png 或 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/hsn-dl-gm/img/ef696a36-6623-4c61-bb89-57805927aac0.png
我们在这些奖励函数中所做的只是通过某些个性修改来调整奖励值。对于女孩,我们给她 1.25 倍的奖励,反映出她可能很兴奋。男孩则不那么兴奋,因此我们将他的奖励调整为 0.95 倍,稍微减少奖励。警察则始终冷静且掌控自如,奖励保持不变。最后,我们引入了一个变数——半死的僵尸。为了表现它是半死不活,我们还将它的奖励减少一半。
当然,你可以根据游戏机制修改这些函数,但需要注意的是,你所应用的个性修改可能会妨碍训练。在我们开始训练这个示例时,务必记住这一点。
一个女孩,一个男孩,一个僵尸和一个警察走进了足球场。
现在我们理解了新的奖励函数,我们想要在示例中添加一些内容,说明是时候打开 Unity 并编写代码了。这个示例将需要对 C# 文件做一些轻微的修改,但代码非常简单,任何有 C 语言经验的程序员都应该能轻松理解。
打开 Unity,进入我们在上一个示例中修改的场景,并跟随下一个练习:
-
在层级窗口中找到 RedStriker 代理并选择它。
-
从 Inspector 面板中,点击 Agent Soccer 组件旁边的齿轮图标,然后在上下文菜单中选择“编辑脚本”。这将会在你的编辑器中打开脚本和解决方案。
-
在文件顶部的当前
enum AgentRole
后添加一个新的enum
,名为PersonRole
,如代码所示:
public enum AgentRole
{
striker,goalie
} *//after this line*
public enum PersonRole
{
girl, boy, police, zombie
}
-
这创建了一个新的角色,实际上是我们希望应用到每个大脑的个性。
-
向类中添加另一个新变量,如下所示:
public AgentRole agentRole; *//after this line*
public PersonRole playerRole;
- 这将
PersonRole
新角色添加到代理中。现在,我们还想通过向InitializeAgent
方法添加一行代码来将新类型添加到设置中,如下所示:
public override void InitializeAgent()
{
base.InitializeAgent();
agentRenderer = GetComponent<Renderer>();
rayPer = GetComponent<RayPerception>();
academy = FindObjectOfType<SoccerAcademy>();
PlayerState playerState = new PlayerState();
playerState.agentRB = GetComponent<Rigidbody>();
agentRB = GetComponent<Rigidbody>();
agentRB.maxAngularVelocity = 500;
playerState.startingPos = transform.position;
playerState.agentScript = this;
area.playerStates.Add(playerState);
playerIndex = area.playerStates.IndexOf(playerState);
playerState.playerIndex = playerIndex;
playerState.personRole = personRole; *//add this line*
}
- 你现在可能会看到一行错误。这是因为我们还需要将新的
personRole
属性添加到PlayerState
中。打开PlayerState
类并按如下所示添加属性:
[System.Serializable]
public class PlayerState
{
public int playerIndex;
public Rigidbody agentRB;
public Vector3 startingPos;
public AgentSoccer agentScript;
public float ballPosReward;
public string position;
public AgentSoccer.PersonRole personRole { get; set; } *//add me*
}
- 你现在应该已经进入了
SoccerFieldArea.cs
文件。滚动到RewardOrPunishPlayer
方法,并按如下所示修改:
public void RewardOrPunishPlayer(PlayerState ps, float striker, float goalie)
{
if (ps.agentScript.agentRole == AgentSoccer.AgentRole.striker)
{
RewardOrPunishPerson(ps, striker); *//new line*
}
if (ps.agentScript.agentRole == AgentSoccer.AgentRole.goalie)
{
RewardOrPunishPerson(ps, striker); *//new line*
}
ps.agentScript.Done(); //all agents need to be reset
}
- 我们在这里做的是注入另一个奖励函数,
RewardOrPunishPerson
,以便添加我们外部的个性奖励。接下来,添加一个新的RewardOrPunishPerson
方法,如下所示:
private void RewardOrPunishPerson(PlayerState ps, float reward)
{
switch (ps.personRole)
{
case AgentSoccer.PersonRole.boy:
ps.agentScript.AddReward(reward * .95f);
break;
case AgentSoccer.PersonRole.girl:
ps.agentScript.AddReward(reward*1.25f);
break;
case AgentSoccer.PersonRole.police:
ps.agentScript.AddReward(reward);
break;
case AgentSoccer.PersonRole.zombie:
ps.agentScript.AddReward(reward * .5f);
break;
}
}
- 这段代码的功能与我们之前定制的奖励函数完全相同。编辑完成后,保存所有文件并返回到 Unity 编辑器。如果有任何错误或编译警告,它们将在控制台中显示。如果需要返回并修复任何(红色)错误,进行修正即可。
如你所见,凭借很少的代码,我们就能够添加外在的个性奖励。当然,你可以以任何方式增强这个系统,甚至让它更通用、参数化。在接下来的部分,我们将把所有这些内容整合起来,开始训练我们的代理。
配置代理的个性
所有代码设置好后,我们现在可以继续回到编辑器,设置代理以匹配我们想要应用的个性。再次打开编辑器,按照接下来的练习将个性应用到代理上并开始训练:
- 在层级视图中选择 RedStriker,并将我们刚刚创建的 Agent Soccer | Person Role 参数设置为 Girl,如下所示:
为每个代理设置个性
-
更新所有代理,使其具备与我们之前分配的模型匹配的相关个性:BlueStriker -> 男孩,BlueGoalie -> 警察,RedGoalie -> 僵尸,如前面的截图所示。
-
保存场景和项目。
-
现在,在这一点上,如果你希望更详细些,你可能想回去更新每个代理的大脑名称以反映它们的个性,比如 GirlStrikerLearning 或 PoliceGoalieLearning,并且可以省略团队颜色。务必将新的大脑配置设置添加到你的
trainer_config.yaml
文件中。 -
打开你的 Python/Anaconda 训练控制台,并使用以下命令开始训练:
mlagents-learn config/trainer_config.yaml --run-id=soccer_peeps --train
- 现在,这会非常有趣,正如你在下面的截图中看到的:
观看不同个性在踢足球
-
请注意,我们保留了团队颜色的立方体,以显示每个代理所属的团队。
-
让代理训练几千次迭代后,再打开控制台;注意代理们现在看起来不再那么共生了。在我们的示例中,它们仍然是成对出现的,因为我们仅对奖励应用了简单的线性变换。当然,你也可以应用更复杂的非线性函数,这些函数不是反相关的,可以描述代理的其他动机或个性。
-
最后,让我们打开 TensorBoard,查看我们多代理训练的更好比较。在你当前工作的
ML-Agents/ml-agents
文件夹中,再次打开一个 Python/Anaconda 控制台,并运行以下命令:
tensorboard --logdir=summaries
- 使用浏览器打开 TensorBoard 界面并检查结果。确保禁用所有额外的结果,只专注于我们当前训练中四个大脑的表现。我们要关注的三个主要图表已合并在这个图示中:
TensorBoard 绘图,显示四个大脑训练的结果
从 TensorBoard 的结果中可以看出,代理的训练效果不佳。我们当然可以通过增加额外的训练区域并提供更多的观测值来改善这一点,从而训练策略。然而,如果你查看策略损失图,结果表明,代理之间的竞争导致了最小的策略变化,这在训练初期是一个不好的现象。如果有的话,僵尸代理似乎是从这些结果中学到最多的代理。
当然,你可以通过很多其他方式修改外部奖励函数,以鼓励在多智能体训练场景中某些行为方面的表现。这些技术中有些效果很好,有些则效果不佳。我们仍处于开发这项技术的初期阶段,最佳实践仍在逐步形成。
在下一部分中,我们将探讨你可以做的进一步练习,以巩固我们在本章中所涵盖的所有内容。
练习
和往常一样,尝试至少一个或两个以下练习,来获得个人的乐趣和学习:
-
打开 BananaCollectors 示例中的 Banana 场景,并在训练模式下运行。
-
修改 BananaCollectors | Banana 场景,使其使用五个独立的学习大脑,然后在训练模式下运行。
-
修改最后一个 SoccerTwos 练习中的奖励函数,使用指数或对数函数。
-
修改最后一个 SoccerTwos 练习中的奖励函数,使用非逆相关和非线性函数。这样,正负奖励的调整方式对于每个个性来说都是不同的。
-
修改 SoccerTwos 场景,使用不同的角色和个性。也要建立新的奖励函数,然后训练代理。
-
修改 BananaCollectors 示例中的 Banana 场景,使其使用与 SoccerTwos 示例相同的个性和自定义奖励函数。
-
用 BananaCollectors 示例做练习 3。
-
用 BananaCollectors 示例做练习 4。
-
用 BananaCollectors 示例做练习 5。
-
使用当前示例中的一个作为模板,或创建自己的,构建一个新的多智能体环境。这个最后的练习很有可能变成你自己的游戏。
你可能已经注意到,随着我们在书中的进展,练习变得更加耗时和困难。为了你自己的个人利益,请尽量完成至少几个练习。
总结
在本章中,我们探讨了多智能体训练环境中的无限可能性。我们首先研究了如何通过自我对弈设置环境,在这种环境中,一个大脑可以控制多个大脑,它们既相互竞争又相互合作。接着,我们探讨了如何通过使用 ML-Agents 好奇心学习系统,结合内在奖励的方式,增加个性化元素,激发智能体的好奇心。然后,我们研究了如何使用外在奖励来塑造智能体的个性并影响训练。我们通过添加免费的风格资产,并通过奖励函数链应用自定义的外部奖励来实现这一点。最后,我们训练了环境,并被男孩智能体彻底击败僵尸的结果逗乐;如果你观看完整的训练过程,你将看到这一点。
在下一章,我们将探讨深度强化学习(DRL)在调试和测试已构建游戏中的另一种新颖应用。
第三部分:构建游戏
在最后这一部分,我们将探讨深度学习目前如何在游戏中应用,并展望深度学习在游戏中的未来。
在本节中,我们将包括以下章节:
-
第十二章,使用 DRL 调试/测试游戏
-
第十三章,障碍塔挑战及其扩展
第十二章:使用 DRL 调试/测试游戏
虽然 ML-Agents 框架为构建游戏中的 AI 代理提供了强大的功能,但它也为调试和测试提供了自动化工具。任何复杂软件的开发都需要与广泛的产品测试和优秀的质量保证团队的审查相结合。测试每个方面、每种可能的组合和每个级别可能非常耗时且昂贵。因此,在本章中,我们将探讨使用 ML-Agents 作为自动化方式来测试一个简单的游戏。当我们修改或更改游戏时,我们的自动化测试系统可以通知我们是否存在问题或可能已经破坏了测试的变更。我们还可以进一步利用 ML-Agents,例如,评估训练性能。
以下是本章将涵盖内容的简要总结:
-
介绍游戏
-
设置 ML-Agents
-
重写 Unity 输入系统
-
通过模仿进行测试
-
分析测试过程
本章假设你对 ML-Agents 工具包有一定的了解,并且对 Unity 游戏引擎有一定的熟悉程度。你还应该对奖励函数以及如何使用 ML-Agents 进行模仿学习有较好的掌握。
在接下来的部分中,我们将从下载并导入游戏开始;我们将在接下来的部分中教你如何让 ML-Agents 玩游戏。即使是对于有经验的 Unity 用户来说,这一章也应视为进阶内容。因此,如果你对 Unity 和/或 C#相对较新,只需慢慢来,逐步完成练习。本章结束时,如果你完成了所有练习,你应该已经朝着成为 Unity 高手的方向迈进。
介绍游戏
我们将要介绍的游戏是一个免费的示范样本资产,它是典型游戏的优秀示例。我们测试的游戏将采用离散控制机制和第一人称视角,类似于我们过去看过的游戏。我们将在这里展示的技术是如何将游戏的控制器映射/破解到 ML-Agents 中,以便它可以由 ML-Agents 驱动。使用这种技术应该能让你将 ML-Agents 附加到任何现有的游戏中,尽管不同的控制器,比如第三人称或俯视视角,可能需要稍微调整方法。
如果你认为自己是有经验的 Unity 用户,并且有自己的项目使用 FPS 系统,那么你可以尝试将这个示例适应到自己的游戏或示例中。
由于某些被称为资源翻转的可疑技术,你通常很难找到好的 Unity 示范游戏项目。实际上,一些开发者会拿到一个示范项目,并快速为其换皮,作为他们自己的游戏进行转售。由于这一行为通常会给 Unity 这个优秀的游戏引擎带来负面影响,Unity 社区普遍对这种做法表示反对。这些快速制作的游戏通常质量很差,且没有任何支持,更不用说这些开发者通常仅使用免费许可证,这意味着这些设计不佳的游戏会标注上Made with Unity字样。
我们希望展示如何将 ML-Agents 集成到一个正在运行的游戏中,用于测试、调试和/或作为 AI 增强功能。让我们从导入基础项目并设置游戏在编辑器中运行开始。在此过程中,我们可能需要对一些内容进行调整,以确保一切正常运行,但这是我们的目标。打开 Unity 编辑器并按照下一部分中的练习设置基础游戏项目:
-
创建一个名为
HoDLG
的新项目(或使用你自己喜欢的名字)。等待空项目加载完成。如果你觉得自己有足够资格,可以使用你自己的项目。 -
从菜单中选择Window | Asset Store。
-
在搜索面板中,输入
ms vehicle system
并按Enter或点击Search按钮。我们将查看一个免费的资源包,名为 MS Vehicle System,它有一个有趣的小环境可以玩耍。通常,像这样的免费环境比较难找到(如前所述),但一般来说,制作精良的商业(非免费)资源包会提供良好的演示环境,比如这个。Unity 也有一些教程环境,但它们通常会迅速过时,而且更新也不一定容易。 -
点击MS Vehicle System卡片,等待资源页面加载,如下图所示:
选择要下载的资源包
-
点击下载按钮下载资源,然后点击导入将资源导入项目中。按照导入对话框的提示将所有资源导入项目中。
-
在Assets | MSVehicleSystem (FreeVersion) 文件夹中找到MainScene场景并打开它。
-
按下Play按钮在编辑器中运行场景,使用控制来驾驶车辆。注意如何切换车辆和相机控制。当测试(游戏)完成后,通过按下 Play 停止场景。
-
在Hierarchy筛选框中输入
canvas
,然后选择场景中的所有Canvas对象,如下图所示:
禁用场景中的 Canvas UI
-
这将禁用场景中的 UI,我们在测试时不需要它,而且在这种情况下它并不重要。如果这是一个真正的游戏,可能会有更多颜色鲜艳的视觉效果来表示分数,当然,你也可以随时添加这些。
-
点击过滤器输入框旁的X,清除它并将场景恢复正常。
-
再次播放场景并探索多个区域。寻找一个你认为可能适合作为目标的地方;记住,最初不要设置太难的目标。以下是一个可能成为有趣目标的位置示例;看看你能否找到这个位置:
寻找适合放置目标的位置
即使你找不到具体的地方,也要找到一个不容易到达的区域。这样,代理必须广泛地探索该关卡才能找到目标(或目标点)。在我们的例子中,我们将随机放置目标方块在关卡中,并鼓励代理去寻找这些目标。这样,我们也可以通过探索的频率来绘制出探索的区域,然后决定如何覆盖其他区域进行测试。在进入下一部分之前,我们将添加 ML-Agents。
设置 ML-Agents
在写这本书时,ML-Agents 是作为一个 GitHub 项目进行开发和发布的。随着产品的成熟,可能会将其作为独立的资源包发布,但目前并不是这样。
因此,我们首先需要将 ML-Agents 导出为资源包。打开一个新的 Unity 编辑器会话,进入 ML-Agents 或 Unity SDK 项目,并按照以下步骤操作:
-
定位到项目窗口中的ML-Agents文件夹,并选择它。
-
从菜单中选择资源 | 导出包。
-
确保所有文件夹内容都已高亮显示,如下所示的导出包对话框摘录:
将 ML-Agents 导出为资源包
-
确保取消选中包括依赖项复选框,如上文摘录所示。只要选择了正确的根文件夹,所有我们需要的依赖项应该都会被打包。
-
在对话框中点击**导出…**按钮,然后选择并保存资产文件到一个你稍后容易找到的位置。
-
打开 Unity 编辑器,进入我们在上一个练习中开始的项目。
-
从菜单中选择资源 | 导入包 | 自定义包。定位我们刚刚导出的包,并将其导入到新的测试项目中。
-
定位到项目窗口,在资源根目录下创建一个名为
HoDLG
的新文件夹,然后在该新文件夹内创建名为Brains
、Prefabs
**、**和Scripts
的新文件夹,如下图所示:
创建新的项目文件夹
- 创建这些文件夹是为新的资源、示例或项目打基础的标准方式。现在你可以关闭旧的 ML-Agents Unity SDK 项目,因为我们不再需要它。
现在我们已经导入了 ML-Agents 并为测试游戏奠定了基础,接下来我们可以开始添加 ML-Agents 的学习部分进行测试。
向游戏中引入奖励
目前场景没有明确的目标。许多开放世界和探索类型的游戏目标定义较为松散。然而,对于我们的目的,我们只希望代理能够测试整个游戏关卡,并尽可能识别出任何游戏缺陷,或许还能发现一些我们从未预见的策略。当然,这并不意味着如果汽车驾驶代理变得足够强大,我们也能把它们作为游戏对手使用。底线是,我们的代理需要学习,而它通过奖励来实现这一点;因此,我们需要制定一些奖励函数。
让我们首先为目标定义一个奖励函数,如下所示:
这个过程非常简单;每当代理遇到目标时,它们将获得一个奖励值 1。为了避免代理花费过长时间,我们还会引入一个标准的步骤奖励,具体如下:
这意味着我们为每个代理的行动应用一个奖励,奖励值为-1 除以最大步骤数。这是相当标准的做法(例如我们的 Hallway 代理就使用了它),所以这里没有什么新东西。因此,我们的奖励函数将非常简单,这很好。
在许多情况下,您的游戏可能有明确的目标,您可以基于这些目标给予奖励。例如,一款驾驶游戏会有一个明确的目标,我们可以为代理设定目标。在这种情况下,在我们的开放世界游戏中,为代理设定目标是有意义的。当然,如何实现奖励结构非常重要,但请根据您的实际情况选择合适的方式。
在定义了奖励函数后,接下来是将目标的概念引入游戏中。我们希望保持这个系统的通用性,因此我们将在一个名为TestingAcademy
的新对象中构建一个目标部署系统。这样,您可以将这个学院对象拖放到任何类似的 FPS 或第三人称控制的世界中,它就能正常工作。
第一人称射击游戏(FPS)指的是一种游戏类型,也是一种控制/摄像头系统。我们感兴趣的是后者,因为它是我们控制汽车的方式。
打开新的合并项目的编辑器,接着按照下一个练习来构建TestingAcademy
对象:
-
在层级窗口中点击,然后从菜单中选择游戏对象 | 创建空物体。将新对象命名为
TestingAcademy
。 -
找到并点击HoDLG | Scripts文件夹,然后在项目窗口中打开创建子菜单。
-
在创建菜单中,选择C# 脚本。将脚本重命名为
TestingAcademy
。 -
打开新的TestingAcademy脚本并输入以下代码:
using MLAgents;
using UnityEngine;
namespace Packt.HoDLG
{
public class TestingAcademy : Academy
{
public GameObject goal;
public int numGoals;
public Vector3 goalSize;
public Vector3 goalCenter;
public TestingAgent[] agents;
public GameObject[] goals;
}
}
本章练习的所有代码都包含在Chapter_12_Code.assetpackage
中,该包随书籍的源代码一同提供。
- 这段代码通过使用所需的命名空间来定义我们的类和导入内容。然后,我们定义了自己的命名空间
Packt.HoDLG
,并且类继承自 ML-Agents 的基类Academy
。接下来声明了几个变量来定义目标部署的立方体。可以把它想象成一个虚拟的空间立方体,用来生成目标。其基本思路是让物理引擎处理剩余部分,让目标直接掉到地面上。
命名空间在 Unity 中是可选的,但强烈建议将代码放入命名空间中,以避免大多数命名问题。如果你使用了很多资源或修改了现有资源,就像我们在这里所做的那样,这些问题会很常见。
- 接下来,我们将定义标准的
Academy
类设置方法InitializeAcademy
。该方法会自动调用,具体如下所示:
public override void InitializeAcademy()
{
agents = FindObjectsOfType<TestingAgent>();
goals = new GameObject[numGoals];
}
- 这个方法是作为 ML-Agents 设置的一部分被调用的,它实际上启动了整个 SDK。通过添加
Academy
(TestingAcademy
),我们实际上启用了 ML-Agents。接下来,我们将添加最后一个方法,该方法会在所有代理回合结束时重置学院,如下所示:
public override void AcademyReset()
{
if (goalSize.magnitude > 0)
{
for(int i = 0; i < numGoals; i++)
{
if(goals[i] != null && goals[i].activeSelf)
Destroy(goals[i]);
}
for(int i = 0; i < numGoals; i++)
{
var x = Random.Range(-goalSize.x / 2 + goalCenter.x, goalSize.x / 2 + goalCenter.x);
var y = Random.Range(-goalSize.y / 2 + goalCenter.y, goalSize.y / 2 + goalCenter.y);
var z = Random.Range(-goalSize.z / 2 + goalCenter.z, goalSize.z / 2 + goalCenter.z);
goals[i] = Instantiate(goal, new Vector3(x, y, z), Quaternion.identity, transform);
}
}
}
-
这段代码会随机生成目标,并将其放置在虚拟立方体的范围内。然而,在此之前,它首先使用
Destroy
方法清除旧的目标。Destroy
会将对象从游戏中移除。然后,代码再次循环并在虚拟立方体内的随机位置创建新的目标。实际创建目标的代码行被高亮显示,并使用了Instantiate
方法。Instantiate
会在指定的位置和旋转角度创建游戏中的对象。 -
保存文件并返回编辑器。此时不必担心任何编译错误。如果你是从头开始编写代码,可能会缺少一些类型,稍后我们会定义这些类型。
创建好新的TestingAcademy
脚本后,我们可以继续在下一节中将该组件添加到游戏对象并设置学院。
设置 TestingAcademy
创建好TestingAcademy
脚本后,接下来通过以下步骤将其添加到游戏对象中:
-
从脚本文件夹中拖动新的TestingAcademy脚本文件,并将其放到层次结构窗口中的TestingAcademy对象上。这会将该组件添加到对象中。在完成学院设置之前,我们还需要创建其他一些组件。
-
在层次结构窗口中点击,菜单中选择Game Object | 3D Object | Cube。将新对象重命名为
goal
。 -
选择该对象并将Tag更改为
goal
。然后,通过点击Target图标并选择v46或其他闪亮材质,来更换其材质,如下图所示:
更换目标对象的材质
-
在菜单中选择goal对象,接着选择Component | Physics | Rigidbody。这将添加一个名为 Rigidbody 的物理系统组件。通过将Rigidbody添加到对象上,我们允许它受物理系统的控制。
-
将goal对象拖放到Project窗口中的HoDLG | Prefabs文件夹中。这将使目标对象成为一个Prefab。预制件是自包含的对象,包含自己的层级结构。预制件可以包含一个完整的场景,或者只是一个对象,就像我们这里所做的一样。
-
在Hierarchy窗口中选择并删除goal对象。未来,我们将通过使用它的 Prefab 从 Academy 中以编程方式实例化goal。
-
点击HoDLG | Brains文件夹,点击打开Create菜单。在菜单中选择ML-Agents | LearningBrain。将新建的大脑命名为**
TestingLearningBrain
**,然后创建一个名为TestingPlayerBrain
的新玩家大脑。暂时无需配置这些大脑。 -
在Hierarchy窗口中选择TestingAcademy对象,然后更新Testing Academy组件的值,如下图所示:
设置 TestingAcademy
-
请注意,我们正在TestingAcademy脚本中设置以下属性:
-
Brains: TestingLearningBrain
-
Max Steps: 3000
-
Goal: 通过从文件夹中拖动预制件来设置目标
-
Num Goals: 3(从盒子中丢出的目标数)
-
Goal Size: (50, 50, 50)(确定目标框的最大边界)
-
Goal Center: (85, 110, -37)(目标框的中心点)
-
此时你可能会想要运行项目;如果你刚刚下载了代码,倒是可以先运行,但等我们在下一节中定义TestingAgent
时再开始吧。
编写 TestingAgent 脚本
当然,如果没有代理来与环境交互并进行学习,我们的测试(或者说我们想要推进的模拟程度)是没有意义的。在接下来的练习中,我们将定义描述TestingAgent
组件的脚本:
-
点击HoDLG | Scripts文件夹,点击Create按钮以打开菜单。
-
在菜单中选择C# Script并将脚本命名为
TestingAgent
。 -
在编辑器中打开脚本,并开始用以下代码进行编写:
using MLAgents;
using UnityEngine;
namespace Packt.HoDLG
{
public class TestingAgent : Agent
{
public string[] axisAction;
protected Vector3 resetPos;
protected Quaternion resetRot;
}
}
-
这开始了我们的类;这次它是从
Agent
这个基类扩展而来。接着,我们定义了一些基本字段,用于设置变量和记录代理的起始位置和旋转。 -
接下来,我们定义
InitializeAgent
方法。该方法会被调用一次,用于设置代理并确保动作长度一致;我们很快就会讲到。我们记住代理开始时的位置/旋转,以便稍后恢复。代码如下:
public override void InitializeAgent()
{
base.InitializeAgent();
if (axisAction.Length != brain.brainParameters.vectorActionSize[0])
throw new MLAgents.UnityAgentsException("Axis actions must match agent actions");
resetPos = transform.position;
resetRot = transform.rotation;
}
- 接下来,我们定义一个空的方法,名为
CollectObservations
。通常,这是代理观察环境的地方;由于我们计划使用视觉观察,因此可以将其留空。代码如下:
public override void CollectObservations(){ }
- 接下来,我们定义另一个必需的方法:
AgentAction
。这是我们添加负面步骤奖励并移动代理的地方,如下方代码片段所示:
public override void AgentAction(float[] vectorAction, string textAction)
{
AddReward(-1f / agentParameters.maxStep);
MoveAgent(vectorAction);
}
public void MoveAgent(float[] act)
{
for(int i=0;i<act.Length;i++)
{
var val = Mathf.Clamp(act[i], -1f, 1f);
TestingInput.Instance.setAxis(val,axisAction[i]);
}
}
-
这里的代码解读来自大脑的动作,并将它们注入到一个新的类中(我们稍后将构建它),叫做
TestingInput
。TestingInput
是一个辅助类,我们将用它来覆盖游戏的输入系统。 -
保存脚本,并且再次忽略任何编译器错误。我们有一个新的依赖项,
TestingInput
,我们将在稍后定义它。
有了新的脚本,我们可以在下一部分开始设置TestingAgent
组件。
设置 TestingAgent
现在,我们正在构建的系统是相当通用的,旨在用于多个环境。在设置过程中请记住这一点,特别是当某些概念看起来有点抽象时。打开编辑器,让我们将TestingAgent
脚本添加到一个对象中:
-
选择场景中的Vehicle1、Vehicle3、Vehicle4和Vehicle5,并禁用它们。我们当前只希望给代理提供驾驶能力,而不是切换车辆;因此,我们只需要默认的Vehicle2。
-
从HoDLG | Scripts文件夹中选择TestingAgent脚本并将其拖动到Vehicle2对象上。这将把TestingAgent组件添加到我们的Vehicle2,使其成为一个代理(嗯,差不多)。
-
打开Vehicle2 | Cameras在Hierarchy窗口中,并选择你希望代理使用的视图。我们将选择Camera2进行此练习,但每个五个摄像头的选项如以下截图所示:
选择作为输入的视觉观察摄像头
-
最佳选择是Camera1或Camera5,如上面截图所示。请注意,摄像头的顺序是反向的,从右到左,编号从 1 开始。当然,这也留给我们很多机会在未来尝试其他视觉输入。
-
选择Vehicle2并将选中的TestingPlayerBrain和Camera1拖到需要的插槽中,如下方截图所示:
设置 TestingAgent 组件
-
你还需要定义其他属性,具体如下:
-
大脑:TestingPlayerBrain。
-
摄像头 1:点击添加摄像头以添加一个新摄像头,然后从Vehicle2的摄像头中选择Camera1。
-
决策频率:
10
(这决定了代理做决策的频率;10
是这个游戏的一个不错的起始值。它会有所变化,可能需要根据需要进行调整) -
轴向动作:2:
-
元素 0:垂直(表示我们将要覆盖的轴,允许代理控制游戏。稍后我们会更详细地介绍轴的描述)
-
元素 1:水平(与之前相同)
-
-
-
保存项目和场景,并再次忽略任何编译错误。
这完成了 TestingAgent
的设置;正如你所看到的,启动这个功能所需的配置或代码并不多。未来,你可能会看到更多高级的测试/调试或构建代理的方式。不过,目前我们需要通过注入 Unity 输入系统来完成我们的示例,下一部分我们将会这样做。
覆盖 Unity 输入系统
Unity 最具吸引力的特点之一是其跨平台能力,能够在任何系统上运行,而这也带来了多个有助于我们将代码注入其中的抽象层。然而,所讨论的游戏需要遵循 Unity 的最佳实践,以便轻松实现这种注入。并不是说我们不能通过覆盖游戏的输入系统来实现,只是那样做起来不那么简单。
在我们开始描述注入如何工作之前,先回顾一下使用 Unity 输入系统的最佳实践。多年来,Unity 输入系统已经从最初的简单查询设备输入的方式,演变成现在更具跨平台特性的系统。然而,包括 Unity 本身在内的许多开发者,仍然使用查询特定按键代码的输入方法。最佳实践是定义一组轴(输入通道),来定义游戏的输入。
我们可以通过以下练习轻松查看它在游戏中的当前定义:
-
从编辑器菜单中选择 编辑 | 项目设置。
-
选择“输入”标签,然后展开 轴 | 水平 和 轴 | 垂直,如下面的截图所示:
检查输入轴设置
- 垂直 和 水平 轴定义了将用于控制游戏的输入。通过在这个标签中定义它们,我们可以通过查询轴来跨平台控制输入。请注意,轴输入允许我们定义按钮和摇杆(触摸)输入。对输入系统的查询(使用
getAxis
)返回一个从-1
到+1
的值,或者是连续的输出。这意味着我们可以将任何离散形式的输入(比如按键)立即转换为连续值。例如,当用户按下 W 键时,输入系统将其转换为 垂直轴 上的正 1 值,相反,按下 S 键则会生成负 1 值,同样是在 垂直轴 上。类似地,A 和 D 键控制 水平轴。
正如你在本书的几章中所看到的,使用.6 版本的 ML-Agents 时,当前的离散动作解决方案远不如连续动作。因此,未来我们更倾向于使用连续动作。
到这个阶段,你可能会想,为什么我们使用了离散动作?这是一个很好的问题。如何在未来处理这个二分法,Unity 还未明确。在下一节中,我们将探讨如何将其注入到输入系统中。
构建 TestingInput
我们将使用一种名为单例模式的设计模式,以实现一个可以在代码的任何地方访问的类,类似于当前使用的 Unity 输入类。Unity 的优点是能够让输入完全静态,但对于我们的目的,我们将使用定义良好的脚本版本。打开编辑器并按照接下来的练习构建TestingInput
脚本和对象:
-
选择HoDLG | Scripts文件夹并打开创建菜单。
-
从创建菜单中,选择C#脚本。将新脚本命名为
Singleton
。这个脚本是来自wiki.unity3d.com/index.php/Singleton
的标准模式脚本;脚本如下所示:
using UnityEngine;
namespace Packt.HoDLG
{
/// <summary>
/// Inherit from this base class to create a singleton.
/// e.g. public class MyClassName : Singleton<MyClassName> {}
/// </summary>
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
// Check to see if we're about to be destroyed.
private static bool m_ShuttingDown = false;
private static object m_Lock = new object();
private static T m_Instance;
/// <summary>
/// Access singleton instance through this propriety.
/// </summary>
public static T Instance
{
get
{
if (m_ShuttingDown)
{
Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
"' already destroyed. Returning null.");
return null;
}
lock (m_Lock)
{
if (m_Instance == null)
{
// Search for existing instance.
m_Instance = (T)FindObjectOfType(typeof(T));
// Create new instance if one doesn't already exist.
if (m_Instance == null)
{
// Need to create a new GameObject to attach the singleton to.
var singletonObject = new GameObject();
m_Instance = singletonObject.AddComponent<T>();
singletonObject.name = typeof(T).ToString() + " (Singleton)";
// Make instance persistent.
DontDestroyOnLoad(singletonObject);
}
}
return m_Instance;
}
}
}
private void OnApplicationQuit()
{
m_ShuttingDown = true;
}
private void OnDestroy()
{
m_ShuttingDown = true;
}
}
}
-
输入上述代码,或者直接使用从书籍源代码下载的代码。单例模式允许我们定义一个线程安全的特定类实例,所有对象都可以引用。典型的静态类不是线程安全的,可能会导致数据损坏或内存问题。
-
在HoDLG | Scripts文件夹中创建一个名为
TestingInput
的新脚本,并打开它进行编辑。 -
我们将从以下代码开始该类:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Packt.HoDLG
{
public class TestingInput : Singleton<TestingInput>
{
public string[] axes;
public bool isPlayer;
}
}
- 请注意高亮显示的那一行,以及我们如何声明该类继承自
Singleton
类型,该类型包装了TestingInput
类型。这个使用泛型的递归类型非常适合单例模式。如果这一点有点不清楚,别担心;你需要记住的唯一一点是,我们现在可以在代码的任何地方访问该类的实例。请注意我们提到的是实例而不是类,这意味着我们还可以在TestingInput
类中保持状态。我们在这里声明的变量,axes
和isPlayer
,要么在编辑器中设置,要么在Start
方法中定义,如下所示:
void Start()
{
axisValues = new Dictionary<string, float>();
//reset the axes to zero
foreach(var axis in axes)
{
axisValues.Add(axis, 0);
}
}
-
在
Start
方法中,我们定义了一个Dictionary
来保存我们希望该组件覆盖的轴和值。这使我们能够控制希望覆盖哪些输入。然后,我们构建名称/值对集合。 -
接下来,我们将定义几个方法,使我们能够模拟并设置输入系统的轴值。Unity 没有直接设置轴值的方式。目前,
Input
系统直接查询硬件以读取输入状态,并且没有提供覆盖此状态进行测试的方式。虽然这是社区长期以来的一个需求,但是否最终会实现仍然有待观察。 -
然后我们输入
setAxis
和getAxis
方法,如下所示:
public void setAxis(float value, string axisName)
{
if (isPlayer == false && axes.Contains(axisName)) //don't if player mode
{
axisValues[axisName] = value;
}
}
public float getAxis(string axisName)
{
if (isPlayer)
{
return Input.GetAxis(axisName);
}
else if(axes.Contains(axisName))
{
return axisValues[axisName];
}
else
{ return 0; }
}
- 这完成了脚本的编写;如果你在过程中已经添加了代码,保存文件并返回 Unity。此时,你应该看不到编译错误,因为所有必需的类型应该已经存在并且是完整的。
这将设置TestingInput
脚本;现在,我们需要继续下一部分,将它添加到场景中。
将 TestingInput 添加到场景
单例可以在任何地方被调用,实际上,它们并不需要场景中的游戏对象。然而,通过将对象添加到场景中,我们更清晰地意识到所需的依赖关系,因为它使我们能够为特定的场景设置所需的参数。打开 Unity 编辑器,按照接下来的练习将TestingInput
组件添加到场景中:
-
点击层级窗口,然后从菜单中选择游戏对象 | 创建空对象。重命名该对象为
TestingInput
。 -
将TestingInput脚本从HoDLG | 脚本文件夹拖到层级窗口中的新TestingInput对象上。
-
选择TestingInput对象,然后设置所需的轴,如下图所示:
设置要重写的轴
-
我们需要定义两个要重写的轴。在这个例子中,我们只重写垂直(S和W)和水平(A和D)键。当然,你也可以重写任何你想要的轴,但在这个例子中,我们只重写了两个。
-
保存项目和场景。
到此为止,你无法运行项目,因为实际的输入系统还没有重写任何内容。我们将在下一部分完成最终的注入。
重写游戏输入
到此为止,我们已经建立了一个完整的测试系统;接下来只需要完成注入的最后部分。这部分的操作可能需要敏锐的眼光和一些代码的挖掘。幸运的是,有一些清晰的指示器,可以帮助你找到注入的位置。打开编辑器,按照以下步骤完成注入:
-
在层级窗口中选择Control对象。
-
在检查器窗口中找到MS Scene Controller Free组件,然后使用上下文菜单打开代码编辑器中的脚本。
-
找到以下代码块,大约在286行(大约中间位置),如下所示:
case ControlTypeFree.windows:
verticalInput = Input.GetAxis (_verticalInput);
horizontalInput = Input.GetAxis (_horizontalInput);
mouseXInput = Input.GetAxis (_mouseXInput);
mouseYInput = Input.GetAxis (_mouseYInput);
mouseScrollWheelInput = Input.GetAxis (_mouseScrollWheelInput);
break;
}
-
这里是游戏查询
GetAxis
方法的位置,用来返回相应输入轴的值。正如我们所讨论的,我们这里只关心垂直和水平轴。你当然可以根据需要重写其他轴。 -
修改设置
verticalInput
和horizontalInput
的代码行,如下所示:
verticalInput = TestingInput.Instance.getAxis(_verticalInput);
horizontalInput = TestingInput.Instance.getAxis(_horizontalInput);
-
请注意,我们调用
TestingInput.Instance
,以便访问我们类的单例实例。这使我们能够查询该类当前的输入值。现在,TestingInput
对象可以作为该类在输入方面的真实来源。 -
之前,我们快速浏览了设置输入的代理代码,但这里再次提供供参考:
public void MoveAgent(float[] act)
{
for(int i=0;i<act.Length;i++)
{
var val = Mathf.Clamp(act[i], -1f, 1f);
TestingInput.Instance.setAxis(val,axisAction[i]);
}
}
-
注意
TestingAgent
MoveAgent
方法中的高亮行。这里是我们通过代理重写输入并将值注入到游戏中的地方。 -
保存代码并返回编辑器。确保现在修复任何编译问题。
不幸的是,我们仍然无法运行场景,因为我们还有最后一个配置步骤需要处理。在下一部分,我们将通过设置脑模型来完成配置。
配置所需的脑模型
最后一块拼图是配置我们之前快速构建的脑模型。ML-Agents 要求脑模型配置所需的输入和观察空间,以便正常工作。我们将在下一个练习中设置 TestingPlayerBrain
和 TestingLearningBrain
:
-
打开 Unity 编辑器并从 HoDLG | Brains 文件夹中选择 TestingLearningBrain,以在 Inspector 中打开它。
-
设置 Brain 参数,如下图所示:
设置 TestingPlayerBrain 的参数
-
需要设置几个参数,它们总结如下:
-
视觉观察:
84
x84
,无灰度 -
向量动作:
-
空间类型:连续
-
空间大小:
2
-
动作描述:
-
大小:
2
-
元素 0:垂直
-
元素 1:水平
-
-
-
轴向连续玩家动作:
-
大小:
2
-
垂直:
-
轴向:垂直
-
索引:
0
-
缩放:
1
-
-
水平:
-
轴向:水平
-
索引:
1
-
缩放:
1
-
-
-
-
选择 TestingLearningBrain 并以相同方式进行配置,但这是用于学习,如下图所示:
配置 TestingLearningBrain
-
学习脑的配置要简单得多,但即使在以玩家模式运行示例时(如你记得的,它已被设置为这样做),这也是必须的。
-
保存场景和项目。最终,我们已经完成了所需的配置。
-
按下播放按钮运行场景并以玩家模式玩游戏。我们通过 ML-Agents 系统控制游戏。几秒钟后,你应该能看到附近掉落一些目标。
-
控制车辆并驶入目标,如下图所示:
驾驶进入目标
- 当你完成游戏后,停止游戏。
现在我们能够通过配置好的玩家脑模型通过 ML-Agents 玩游戏,我们将在下一部分切换到学习脑,让代理控制游戏。
训练时间
无论我们决定如何使用这个平台,无论是用于训练还是测试,我们现在需要做最后的脑配置步骤,以便设置任何我们可能决定用于训练的自定义超参数。打开 Python/Anaconda 控制台并准备进行训练,然后按照以下步骤进行:
-
打开位于
ML-Agents/ml-agents/config
文件夹中的trainer_config.yaml
文件。 -
我们将向配置文件中添加一个新的配置部分,模仿其他视觉环境中的配置。按照以下方式添加新的配置:
TestingLearningBrain:
use_recurrent: true
sequence_length: 64
num_layers: 1
hidden_units: 128
memory_size: 256
beta: 1.0e-2
gamma: 0.99
num_epoch: 3
buffer_size: 1024
batch_size: 64
max_steps: 5.0e5
summary_freq: 1000
time_horizon: 64
-
注意,我们添加了
brain
这个词,以便与其他脑区分开来。这个脑是基于我们之前花时间探索的VisualHallwayBrain
模型制作的。然而,请记住,我们现在运行的是一个连续动作问题,这可能会影响某些参数。 -
保存文件并返回到 Unity 编辑器。
-
定位到
TestingAcademy
对象,将其Brains
替换为TestingLearningBrain
,并将其设置为Control
,就像你之前做过的那样。 -
保存场景和项目,并返回到 Python/Anaconda 控制台。
-
通过运行以下命令开始训练/学习/测试会话:
mlagents-learn config/trainer_config.yaml --run-id=testing --train
- 观看训练过程和代理玩游戏的表现。代理将开始运行,取决于你训练的时间长度,它可能会变得擅长于寻找目标。
到此为止,你可以让代理自行运行并探索你的关卡。然而,我们要做的是通过使用模仿学习来控制或引导测试代理走向正确的路径,我们将在下一节中讨论这一方法。
通过模仿进行测试
在学习的这一阶段,你已经掌握了几种策略,我们可以应用它们来帮助测试代理学习并找到目标。我们可以轻松地使用好奇心或课程学习,我们将把这作为读者的练习。我们想要的是一种控制某些测试过程的方法,而且我们不希望代理随机地测试所有内容(至少在这个阶段不希望)。当然,有些地方完全随机测试效果很好。(顺便提一下,这种随机测试被称为猴子测试,因为它类似于猴子乱按键盘或输入。)然而,在像我们的游戏这样的空间中,探索每一个可能的组合可能需要很长时间。因此,最好的替代方法是捕捉玩家的录制并将其用作我们测试代理的模仿学习来源。
一切设置好后,并且我们现在能够通过 ML-Agents 将输入事件传递过去,我们可以以代理需要学习的形式捕捉玩家输入。接下来,让我们打开一个备份的 Unity 并设置场景来捕捉玩家的录制,步骤如下:
-
在Hierarchy窗口中选择Vehicle2对象。回忆一下,这里是TestingAgent脚本附加的地方。
-
使用Inspector窗口底部的Add Component按钮将Demonstration Recorder组件添加到代理上。
-
将Demonstration Recorder设置为Record,并将Demonstration Name设置为Testing,然后将大脑切换到TestingPlayerBrain,如下面的截图所示:
向智能体添加演示记录器
-
选择TestingAcademy对象,确保禁用Brain上的Control选项。我们希望玩家在录制时控制智能体。
-
按下 Play 键并运行游戏。使用键盘上的WASD控制键驾驶车辆穿越目标。玩一会儿,以生成一个不错的录制。
-
完成后,检查
Assets
文件夹中是否有一个名为Demonstrations
的新文件夹,其中包含你的Testing.demo
录制文件。
现在,随着玩家录制功能启用,我们可以设置并运行智能体,使用模仿学习来测试关卡。
配置智能体使用 IL
我们已经完成了设置和运行离线模仿学习 (IL) 会话的过程,但让我们在接下来的练习中回顾一下这个过程:
-
打开 Unity 编辑器,进入相同项目,找到包含智能体的Vehicle2对象。
-
将智能体的大脑从TestingPlayerBrain切换到TestingLearningBrain。
-
选择TestingAcademy并启用Testing Academy | Brains组件上的Control属性。
-
保存场景和项目。
-
打开
config/offline_bc_config.yaml
文件,在文本或代码编辑器中编辑。 -
添加以下部分(
HallwayLearning
的修改版):
TestingLearningBrain:
trainer: offline_bc
max_steps: 5.0e5
num_epoch: 5
batch_size: 64
batches_per_epoch: 5
num_layers: 2
hidden_units: 128
sequence_length: 16
use_recurrent: true
memory_size: 256
sequence_length: 32
demo_path: ./UnitySDK/Assets/Demonstrations/Testing.demo
-
编辑完成后保存文件。
-
打开一个准备好进行训练的 Python/Anaconda 控制台,并输入以下命令:
mlagents-learn config/offline_bc_config.yaml --run-id=testing_il --train
-
注意几个修改,已用粗体标出。训练开始后,观察智能体以你训练它的方式驾驶汽车(或者至少,它会尽力去做)。
-
让智能体玩游戏,观察它的表现以及是否遇到问题。
这个演示/游戏相当稳定,不容易出现明显问题,这使得测试明显问题变得困难。然而,希望你能理解,如果在游戏早期实现这种类型的系统,即使只是为了测试,它也能提供快速发现 bug 和其他问题的能力。当然,目前我们唯一的识别问题的方法是观察智能体的游戏表现,这并没有节省时间。我们需要的是一种追踪智能体活动的方法,并确定智能体是否(以及何时)遇到问题。幸运的是,我们可以通过添加分析功能轻松地增加这种追踪方式,接下来我们将介绍这一部分。
分析测试过程
ML-Agents 当前缺少的一个关键功能是额外的训练分析(超出控制台和 TensorBoard 提供的内容)。一个可能至关重要的功能(而且不难添加)是训练分析。可以通过 Unity Analytics 服务来实现这个功能,所有游戏都可以免费试用。由于这不是 ML-Agents 的现有功能,因此我们将在下一个练习中通过添加我们自己的训练分析系统来实现:
-
打开 Unity 编辑器,从菜单中选择Window | General | Services。这将打开一个名为Services的新窗口,通常会出现在Inspector窗口上方。
-
点击新打开的服务窗口中的Analytics服务。你需要通过几屏设置,询问你的偏好和确认,如下图所示:
为你的项目设置分析
-
点击按钮启用Google Analytics。然后,选择Discover玩家洞察开关,你将被提示按下编辑器中的Play。
-
在编辑器中按下Play,让游戏运行几秒钟。
-
返回到服务窗口和 Analytics 页面,在顶部,你应该看到一个叫做Go to Dashboard的按钮。点击该按钮,如下图所示:
使用仪表板探索你的数据
- 这将打开你的默认网页浏览器,带你到你的项目分析页面,你应该能看到一些事件,如appStart和appStop。
这就完成了分析服务的设置,正如你所看到的,它其实非常简单。然而,像所有事情一样,我们需要自定义一些我们将要发送到分析服务的数据。你将在下一节学习如何发送你自己的自定义分析。
发送自定义分析
如果你之前使用过分析服务,你可能已经有了自己追踪游戏使用情况的最佳实践;如果是这样,随时可以使用这些方法。我们在这里展示的方法是作为起点,帮助你设置并发送自定义分析数据用于训练,或者用于跟踪玩家使用情况。
让我们开始,打开 Unity 编辑器并进行下一个练习:
-
在
HoDLG
的Scripts
文件夹中创建一个名为TestingAnalytics
的新 C#脚本。 -
打开并编辑
TestingAnalytics
脚本,在编辑器中输入以下代码:
using UnityEngine;
namespace Packt.HoDLG
{
public class TestingAnalytics : Singleton<TestingAnalytics>
{
private TestingAcademy academy;
private TestingAgent[] agents;
private void Start()
{
academy = FindObjectOfType<TestingAcademy>();
agents = FindObjectsOfType<TestingAgent>();
}
public string CurrentGameState
{
get
{
var state = string.Empty;
foreach (var agent in agents)
{
foreach (var goal in academy.goals)
{
var distance = Vector3.Distance(goal.transform.position, agent.transform.position);
state += agent.name + " distance to goal " + distance + "/n";
}
}
return state;
}
}
}
}
-
这段代码所做的就是收集目标的当前位置以及它们与代理的接近程度。那是我们目前关心的内容。此外,注意我们将其设为public property,以便像方法一样调用,而不仅仅是一个字段。这在后面会很重要。
-
保存文件并返回编辑器。确认没有编译错误。
-
在场景中创建一个新的空游戏对象,并命名为
TestingAnalytics
。将新的TestingAnalytics
脚本拖到该对象上,将其设置为场景组件。虽然这个类是单例,但我们仍然希望将其作为场景的依赖项添加(本质上是作为一个提醒)。然而,我们还可以使用另一个技巧来编程预制体。 -
将 TestingAnalytics 对象拖入 HoDLG | Prefabs 文件夹。这将使该对象成为预制体,其他所有预制体现在都可以访问它。
-
双击 HoDLG | Prefabs 文件夹中的 goal 预制体,以在其自己的迷你编辑器中打开该对象。
-
使用 添加组件 按钮为对象添加 Analytics Event Tracker 组件并进行配置,如下图所示:
设置分析事件跟踪器
-
配置组件如下:
-
何时:生命周期
-
生命周期事件:销毁时
-
发送事件:
-
名称:目标摧毁事件
-
参数:1/10:
-
名称:状态
-
值:动态
-
对象:TestingAnalytics(预制体)
-
方法:CurrentGameState
-
-
-
-
通过更改学院和代理配置,将场景切换回玩家模式。
-
保存场景和项目。
-
通过按 播放 运行场景,驾车经过一个目标。当你碰到目标时,查看 Analytics 仪表板并注意事件是如何被追踪的。
在这个阶段,分析数据只有在目标被摧毁时才会报告,并报告每个代理与目标的距离。因此,对于一个代理和三个目标,当目标被撞毁或物体重置时,它们会报告三个距离。通过这些统计数据,你可以大致了解每个代理测试会话的整体情况,不管是好是坏。当然,你可以添加任何你想要的分析数据;这很容易让人过度投入。谁知道呢;未来,Unity 可能会提供一个由 ML-Agents 驱动的自测平台,提供测试分析数据。
本章即将结束,当然,我们也接近你最喜欢的部分——练习。
练习
本章的练习是结合了使用 ML-Agents 和构建你自己的测试分析平台。因此,从下面的列表中选择一到两个你可以自己完成的练习:
-
配置 TestingAgent 使用不同的相机进行视觉观察输入。
-
启用代理大脑上的 好奇心学习。
-
设置 TestingAgent 控制另一辆车。
-
设置 TestingAgent 在另一辆车上运行,让 ML-Agents 同时控制两个代理。
-
为代理添加额外的追踪分析自定义事件。也许可以跟踪代理的移动距离与其生命周期的关系。这将提供一个速度因子,也能表示代理的效率。一个更快撞到目标的代理会有更好的速度因子。
-
通过添加带有学习代理的第二辆车,启用在线模仿学习。如果需要,回顾一下网球场景的设置。
-
设置学院使用课程学习。也许允许虚拟目标部署框在训练迭代中增长(按 10%或其他因素)。这将使目标分散更远,使得代理更难找到。
-
修改大脑使用的视觉观察输入为
184
x184
,这是新的标准,看看这对代理训练有何影响。 -
修改视觉观察卷积编码网络,就像我们在第七章中所做的那样,Agents and the Environment,使用更多层和/或不同的过滤器。
-
将这个测试框架应用到你自己的游戏中。确保也添加分析功能,以便跟踪培训和玩家使用情况。
这些练习比前几章更复杂,因为这是一个重要的大章节。在下一节中,我们将回顾你在本章学到和涵盖的内容。
摘要
在本书的所有章节中,如果你正在开发自己的游戏,这可能是最有用的章节之一。游戏测试是需要大量时间和注意力的事情,必须部分自动化。虽然深度强化学习在几乎任何游戏中都表现良好是有意义的,但尚不清楚这是否是这种新学习现象的一个利基。然而,有一点可以肯定的是,ML-Agents 完全可以作为一个测试工具,并且我们确信随着时间的推移它会变得更加出色。
在本章中,我们看了建立一个通用测试平台,由 ML-Agents 提供支持,可以自动测试任何游戏。我们首先看了我们需要调整的每个组件,学院和代理,以及它们如何被泛化用于测试。然后,我们看了如何注入到 Unity 输入系统中,并使用我们的TestingAgent
来覆盖游戏的输入并学习如何独立控制它。之后,我们看了如何通过使用离线 IL 来更好地设置我们的测试,并记录一个演示文件,以便稍后用来训练代理。最后,为了查看我们的测试效果如何,我们添加了分析功能,并根据我们的需求进行了定制。
下一章将是我们的最后一章,也是我们对游戏深度学习的最后讨论;适当地,我们将展望 ML-Agents 和 DRL 的未来。
第十三章:障碍塔挑战及其后续
在本章中,我们的最后一章,我们将审视游戏中**深度学习(DL)和深度强化学习(DRL)**的当前和未来状态。我们诚实而坦率地看待这些技术是否已经准备好投入商业游戏,或者它们只是新奇玩意。几年后,我们是否会看到 DRL 代理在每一款游戏中击败人类玩家?尽管这还有待观察,而且事情变化迅速,但真正的问题是:DL 是否准备好为您的游戏服务?这可能是您此刻正在问自己的问题,希望我们在本章中能够回答。
本章将是一些实际练习和一般讨论的结合,不幸的是没有练习。好吧,有一个大练习,但我们很快就会谈到。以下是本章将涵盖的内容:
-
Unity 障碍塔挑战
-
您的游戏的深度学习?
-
制作你的游戏
-
更多学习的基础知识
本章假定您已经完成了本书中的众多练习,以便理解上下文。我们将提到这些部分以提醒读者,请不要跳到本章。
Unity 障碍塔挑战
Unity 障碍塔挑战于 2019 年 2 月引入,作为一个离散的视觉学习问题。正如我们之前所见,这是游戏、机器人和其他模拟学习的圣杯。更有趣的是,这一挑战是在 ML-Agents 之外引入的,并且要求挑战者从头开始编写他们自己的 Python 代码来控制游戏——这是我们在本书中接近学习如何做到的,但我们省略了技术细节。相反,我们专注于调整超参数、理解奖励和代理状态的基础知识。如果您决定挑战这个塔,所有这些基础知识都将派上用场。
在撰写本书时,用于开发的 ML-Agents 版本是0.6
。如果您已经完成了所有的练习,您会注意到,所有使用离散动作空间的视觉学习环境都存在梯度消失或梯度爆炸问题。您将看到的情况是代理基本上学不到东西,并执行随机动作;通常需要数十万次迭代才能看到结果。但在使用矢量观察的状态空间较小的环境中,我们并不会看到这个问题。然而,在具有大输入状态的视觉环境中,这个问题经常会出现。这意味着,基本上在撰写本书时,您不会希望使用 Unity 代码;它目前是离散动作的可视学习者。
在写这篇文章时,Unity Obstacle Tower Challenge 刚刚启动,早期的度量指标已经开始报告。目前,谷歌 DeepMind 提出的领先算法毫不奇怪,就是一个名为Rainbow的算法。简而言之,Rainbow 是许多不同的深度强化学习(DRL)算法和技术的结合,旨在更好地学习障碍塔所定义的离散动作视觉学习空间。
既然我们已经确认你可能想要编写自己的代码,那么接下来我们将理解你的代理需要解决的高层关键问题。解释如何编写代码以及其他技术细节可能需要另一本书,因此我们将讨论整体挑战和你需要解决的关键要素。此外,获胜者更可能需要使用更多的概率方法来解决问题,而这一点目前在任何地方的讨论都不充分。
让我们在接下来的练习中设置挑战并启动它:
-
从
github.com/Unity-Technologies/obstacle-tower-env
下载 Obstacle Tower 环境的二进制文件。 -
按照指示操作,下载适合你环境的压缩文件。在大多数系统上,这只需要下载并解压到一个文件夹,稍后你将在该文件夹中执行文件。
-
将文件解压到一个常用的文件夹中。
-
通过双击程序(Windows)或在控制台中输入名称来启动程序。启动挑战后,你实际上可以像人类一样参与其中。玩这个游戏,看看你能爬到多少楼层。以下截图展示了正在运行的挑战示例:
玩家模式下的 Obstacle Tower 挑战
你在游戏过程中会学到的第一件事之一是,游戏开始时相对简单,但在后面的楼层,难度会增加,甚至对人类来说也很困难。
如前所述,解决这个挑战超出了本书的范围,但希望你现在能理解一些目前制约深度强化学习领域的复杂性。我们已经在下表中回顾了你在进行此方法时将面临的主要挑战:
问题 | 章节 | 当前 状态 | 未来 |
---|---|---|---|
视觉观测状态——你需要构建一个足够复杂的卷积神经网络(CNN),并可能需要递归神经网络(RNN)来编码视觉状态中的足够细节。 | 第七章,代理与环境 | 当前的 Unity 视觉编码器远未达标。 | 幸运的是,CNN 和递归网络在视频分析中已有大量研究。记住,你不仅仅是想捕捉静态图像;你还需要编码图像的序列。 |
DQN, DDQN 或 Rainbow | 第五章, 介绍深度强化学习 | Rainbow 目前是最好的,并且可以在 GCP 上使用。 | 正如我们在本书中看到的,PPO 仅在连续动作空间上表现良好。为了应对离散动作空间的问题,我们回顾了更基础的方法,如 DQN 或新兴的 Rainbow,它是所有基本方法的汇总。我们还将讨论未来可能通过进一步使用深度概率方法来解决当前问题的途径。 |
内在奖励 | 第九章, 奖励与强化学习 | 使用内在奖励系统在探索方面表现出色。 | 引入像好奇心学习这样的内在奖励系统,可以让智能体根据某种对状态的期望来探索新环境。这种方法将对任何计划达到塔楼更高层次的算法至关重要。 |
理解 | 第六章, Unity ML-Agents | Unity 提供了一个出色的示范环境,用于构建和测试模型。 | 你可以很容易地在 Unity 中快速构建并独立测试一个类似的环境。难怪 Unity 从未发布过原始的 Unity 环境作为项目。这很可能是因为这会吸引许多初学者,他们以为仅凭训练就能解决问题。但有时候,训练并不是答案。 |
稀疏奖励 | 第九章, 奖励与强化学习 第十章, 模仿与迁移学习 | 可以实施课程学习或模仿学习。 | 我们已经讨论了许多管理稀疏奖励问题的示例。看看获胜者是否依赖这些方法中的一种,如模仿学习(IL)来取得胜利,将会非常有趣。 |
离散动作 | 第八章, 理解 PPO | 我们学会了如何利用 PPO 通过随机方法解决连续动作问题。 | 正如我们之前提到的,可能需要通过深度概率方法和技术来解决当前的一些问题。这可能需要新算法的开发,而开发所需的时间仍然需要观察。 |
前表中突出的每个问题可能需要部分或全部解决,才能让智能体从 1 层到 100 层,完成整个挑战。如何在 Unity、DRL 以及整个深度强化学习领域中发挥作用,还需要进一步观察。在接下来的部分,我们将讨论深度学习和深度强化学习的实际应用,以及它们如何用于你的游戏。
深度学习在你的游戏中的应用?
你可能是因为希望通过学习深度学习(DL)和深度强化学习(DRL)在游戏中的应用,进而获得理想的工作或完成理想的游戏,才开始阅读这本书。无论如何,你会面临一个问题:决定这项技术是否值得加入自己的游戏,以及在什么程度上加入。以下是十个问题,可以帮助你判断深度学习(DL)是否适合你的游戏:
-
你是否已经决定并需要使用深度学习(DL)或深度强化学习(DRL)来构建游戏?
-
是的 – 10 分
-
不是 – 0 分
-
-
你的游戏是否能从某种形式的自动化中受益,无论是通过测试还是管理重复性的玩家任务?
-
是的 – 10 分
-
不是 – 0 分
-
-
你是否希望将训练、人工智能或其他类似活动作为游戏的一部分?
-
是的 – (-5)分。你可能更适合使用一种更强大的人工智能来模拟训练。训练 DRL 需要太多的迭代和样本,至少目前,它作为游戏内训练工具并不高效。
-
不是 – 0 分。
-
-
你是否希望在你的游戏中加入前沿的人工智能技术?
-
是的 – 10 分。确实有很多方法可以将人工智能技术叠加,并让深度强化学习(DRL)解决方案奏效。谈到当前的人工智能技术,真的没有比这更前沿的技术了。
-
不是 – 0 分。
-
-
你是否有足够的时间来训练人工智能?
-
是的 – 10 分
-
不是 – (-10)分
-
-
你是否阅读了这本书的很大一部分,并完成了至少一些练习?
-
是的 – 10 分,若完成超过 50%则加 5 分
-
不是 – (-10)分;感谢你的诚实
-
-
你是否有数学背景或对数学感兴趣?
-
是的 – 10 分
-
不是 – (-10)分
-
-
你在学术层面阅读了多少关于强化学习的论文?
-
10+ – 25 分
-
5–10 – 10 分
-
1–5 – 5 分
-
0 – 0 分
-
-
你的完成时间表是什么?
-
1–3 个月 – (-10)分
-
3–6 个月 – 0 分
-
6–12 个月 – 10 分
-
1–2 年 – 25 分
-
-
你的团队规模是多少?
-
单打独斗 – (-10)分
-
2–5 – 0 分
-
6–10 – 10 分
-
11+ – 25 分
-
回答所有问题并评分,以确定你是否完全准备好。请参阅以下内容,了解你和/或你的团队的准备情况:
-
<0 分 - 你是怎么走到这本书的这一部分的?你还没有准备好,最好放下这本书。
-
0-50 - 你显然有潜力,但你还需要更多帮助;查看下一步和进一步学习领域的部分。
-
50-100 - 你显然在构建知识基础和实现有趣的深度强化学习(DRL)方面有了进展,但你可能仍然需要一些帮助。查看下一步和进一步学习领域的部分。
-
100+ - 你已经完全准备好了,非常感谢你抽出时间阅读这本书。也许可以利用一些个人时间,将你的知识传递给你认识的人或团队成员。
当然,前述测试结果没有绝对的规则,您可能会发现自己的得分很低,但随后可能会做出下一个伟大的人工智能游戏。您如何看待结果由您决定,下一步如何进行也完全由您决定。
在下一部分,我们将探讨您可以采取的下一步措施,以便深入了解 DRL,并如何在游戏中构建更好的自动化和人工智能。
构建您的游戏
现在您已经决定为您的游戏使用深度学习和/或深度强化学习,是时候确定如何在游戏中实现各种功能了。为了做到这一点,我们将通过一个表格,概述您需要经过的步骤来构建游戏的人工智能代理:
步骤 | 行动 | 总结 |
---|---|---|
启动 | 确定您希望游戏中的人工智能在什么层次上操作,从基础层次(也许只是用于测试和简单的自动化)到高级层次(人工智能与玩家对抗)。 | 确定人工智能的层次。 |
资源配置 | 确定资源的数量。基本的人工智能或自动化可以由团队内部处理,而更复杂的人工智能可能需要一个或多个经验丰富的团队成员。 | 团队需求。 |
知识 | 确定团队所拥有的知识水平以及所需的知识。显然,任何实施新人工智能的团队都需要学习新技能。 | 知识差距分析。 |
演示 | 始终从构建一个简单但可行的概念验证开始,展示系统的所有关键方面。 | 演示团队能够完成基本前提。 |
实施 | 以简洁且可维护的方式构建实际系统。保持所有已知的内容简单清晰。 | 构建系统。 |
测试 | 一遍又一遍地测试系统。系统必须彻底测试,当然,最好的测试方法就是使用 DRL 自动化测试系统。 | 测试系统。 |
修复 | 正如任何开发过软件超过几周的人所告诉你的那样,过程是构建、测试、修复并重复。这本质上就是软件开发的过程,因此尽量不要增加太多其他无关的功能,以免分散注意力。 | 修复系统。 |
发布 | 向用户/玩家发布软件对成功的游戏或任何类型的软件产品至关重要。您始终希望尽早发布并频繁发布,这意味着必须鼓励玩家进行测试并提供反馈。 | 发布错误。 |
重复 | 这一过程是无止境的,只要您的产品/游戏能带来利润,它就会持续进行。 | 支持系统。 |
前述过程是基本前提,适用于大多数开发需求。在大多数情况下,您可能希望在工作或任务板上跟踪单个工作项,如功能或错误。您可能希望使用更明确的流程,例如 Scrum,但通常保持简洁是最好的行动方式。
Scrum 及其他软件开发流程是很好的学习范例,但除非你有经过正式培训的员工,否则最好避免自己去实施这些流程。这些流程中往往有一些微妙的规则,需要执行才能像它们所声称的那样有效。即使是经过培训的 Scrum Master,也可能需要在许多组织中每天进行斗争,才能落实这些规则,最终它们的价值变得更多是由管理驱动,而非开发者主导。可以参考前面的表格作为你构建下一个游戏时的步骤指南,始终记住“构建、发布、修复、重复”是做好软件的关键。
在下一部分,我们将介绍你可以用来扩展学习的其他内容。
更多的学习基础
目前有着日益增长的学习机器学习、深度学习和深度学习回归(DLR)的资源。这个资源库正在不断扩大,选择材料的余地也越来越多。因此,我们现在总结出我们认为对游戏 AI 和深度学习最具前景的领域:
-
基础数据科学课程:如果你从未学习过数据科学的基础课程,那么你肯定应该学习一门。这些课程可以帮助你理解数据的特性、统计学、概率和变异性,这些基础知识多得无法一一列举。务必先打好这个基础。
-
概率编程:这是通过变分推断方法的结合,来回答给定事件的概率及某事件可能发生的概率。这些类型的模型和语言已经用于多年来分析金融信息和风险,但它们现在在机器学习技术中逐渐崭露头角。
-
深度概率编程:这是变分推断与深度学习模型的结合。变分推断是一个过程,你通过给定多个可能概率的输入来回答一个带有概率的问题。因此,我们不是用一系列权重来训练网络,而是使用一系列概率分布。这种方法已经证明非常有效,最近它已经用修改后的概率 CNN 模型执行了视觉图像分类任务。
-
视觉状态分类与编码:深度学习系统的一个关键方面是开发卷积神经网络(CNN)模型来分类图像。为了为你的游戏环境构建网络,你需要深入理解这个领域。请记住,不同的环境可能需要使用 CNN 模型。
-
记忆:记忆当然可以有多种形式,但最值得关注的主要形式是递归神经网络(RNN)。在本书的早期,我们介绍了目前使用的标准递归网络模型——长短时记忆(LSTM)块。即使在写作时,关于门控递归单元(GRU)的兴趣也在重新升温,这是一种更复杂的递归网络,已被证明能更好地解决梯度消失问题。人们始终对云技术或其他支持的技术以及它们如何与新的深度学习技术互动充满兴趣。
-
深度学习即服务:像 Google、Amazon、Microsoft、OpenAI 等公司,虽然声称注重开放性,但通常远非如此。在大多数情况下,如果你想将这些技术融入你的游戏,你需要订阅他们的服务——当然,这也有其优缺点。主要问题在于,如果你的游戏变得非常流行,并且你过度依赖深度学习服务,你的利润就会与其挂钩。幸运的是,Unity 至今还没有采取这种方式,但这一切还得看社区如何顺利解决障碍塔挑战。
-
数学:一般来说,无论你是否打算深入构建自己的模型,你都需要不断提升自己的数学技能。最终,你对数学的直觉理解将为你提供解决这些复杂技术所需的洞察力。
-
毅力:学会失败,然后继续前行。这是至关重要的,也是许多新开发者常常感到沮丧的地方,他们会选择转向更简单、更容易、更少回报的事情。当你失败时要高兴,因为失败是学习和理解的过程。如果你从未失败过,那你其实从未真正学习过,所以学会去失败。
硬编码的学习资源列表很可能在这本书还没有印刷或发布之前就已经过时。请利用前面的列表来概括你的学习,拓宽你在基础机器学习和数据科学方面的知识。最重要的是,深度学习是一项数据科学追求,必须尊重数据;永远不要忘记这一点。
在下一节的最终章中,我们将总结本章内容和整本书。
总结
在本章中,我们简要介绍了与深度学习(DL)和深度强化学习(DRL)相关的许多基本概念;也许你会决定参与 Unity 障碍塔挑战并完成它,或者仅在自己的项目中使用 DRL。我们通过简单的测验来评估你是否有潜力深入学习并在游戏中应用 DRL。之后,我们探讨了开发的下一步,并最终看到了可能想要专注的其他学习领域。
本书是一次了解深度学习(DL)在未来应用于游戏项目时如何有效的练习。我们一开始探讨了许多基础的 DL 原理,并研究了更具体的网络类型,如 CNN 和 LSTM。接着,我们考察了这些基础网络形式如何应用于自动驾驶和构建聊天机器人等应用。之后,我们研究了当前机器学习算法的“王者”——强化学习和深度强化学习。然后,我们研究了当前的领导者之一——Unity ML-Agents,并通过多个章节讲解如何实现这一技术,逐步从简单的环境构建到更复杂的多智能体环境。这也使我们有机会探索不同形式的内在/外在奖励和学习系统,包括课程学习、好奇心、模仿学习和迁移学习。
最后,在完成本章之前,我们进行了一个关于使用深度强化学习(DRL)进行自动化测试和调试的长期练习,并额外提供了使用内在学习(IL)增强测试的选项。