# 3D Balance Ball 环境入门

本教程介绍在 Unity 中打开 ML-Agents 示例环境、
构建 Unity 可执行文件、在其中训练 agent 以及
最终将经过训练的模型嵌入到 Unity 环境中的端到端过程。

ML-Agents 包含大量[示例环境](Learning-Environment-Examples.md)，
您可以通过这些环境来了解 ML-Agents 的
不同使用方式。这些环境也可以用作新环境的模板
或用作测试新 ML 算法的方法。阅读本教程后，
您应该能够了解并构建示例环境。

![3D Balance Ball](images/balance.png)

本演练过程将使用 **3D Balance Ball** 环境。3D Balance Ball 包含
大量平台和球（它们都是彼此的副本）。
每个平台都会试图通过水平或垂直旋转的方式
防止球掉落。在这种环境下，平台是一个 **agent**，
可以在对球进行平衡的每一步中获得奖励。agent 也会
因为掉球而得到负奖励（惩罚）。训练过程的
目标是让平台学会绝不掉球。

让我们开始吧！

## 安装

为了安装和设置 ML-Agents、Python 所依赖的库和 Unity 软件，
请参阅[安装说明](Installation.md)。

## 了解 Unity 环境 (3D Balance Ball)

agent 是一种观测并与_环境_交互的
自主参与者 (actor)。在 ML-Agent的语境下，环境是一个包含一个 Academy， 一个或多个 Brain， 一个或多个Agent， Agent 与其他实体交互的场景。

![Unity Editor](images/mlagents-3DBallHierarchy.png)

**注意：**在 Unity 中，场景内所有元素的基础对象均为
_游戏对象_(GameObject)。游戏对象本质上是其他任何元素
（包括行为、图形、物理等）的容器。要查看组成游戏对象的组件，
请在 Scene 窗口中选择 GameObject，然后打开
Inspector 窗口。Inspector 会显示游戏对象上的每个组件。

在打开 3D Balance Ball 场景后，您可能会首先注意到它包含的
不是一个平台，而是多个平台。场景中的每个平台都是
独立的 agent，但它们全部共享同一个 Brain。3D Balance Ball 通过
这种方式可以加快训练速度，因为所有 12 个 agent 可以并行参与训练任务。

### Academy

在这个示例场景中的 Academy 对象是 Ball3DAcademy 游戏对象。
当您在 Inspector 中查看该 Academy 组件时，可以看到若干
用于控制环境工作方式的属性。例如，Inspector中可以看到
**Training** 和 **Inference Configuration** 属性， 在其中我们可以设置之后生成的 Unity 可执行文件的
图形和 Time Scale 属性。Academy 在训练期间使用
**Training Configuration**，而在不训练时使用
**Inference Configuration**。（*Inference* 等同于**不**进行训练的任何时候，此时 agent 可以使用经过训练的模型控制，或用写定的代码控制，或让玩家直接控制。）
通常情况下，您需要为 **Training configuration** 设置低图形质量
和高Time Scale，而为 **Inference Configuration** 设置高图形质量和
`1.0` 的Time Scale。

**注意：**如果您想在训练期间观测环境，则可以调整
**Inference Configuration** 设置来使用更大的窗口和更接近
1:1 的时间刻度。当你要正式训练时一定要重新设置这些参数；
否则，训练可能需要很长时间。

对于环境，另一个需要关注的方面是 Academy 的实现。
由于 Academy 基类是抽象的，您必须始终定义一个子类。
您可以实现以下三个函数，但这些函数都是可选的：

* Academy.InitializeAcademy() — 启动环境时调用一次。
* Academy.AcademyStep() — 在
Agent.AgentAction() 之前（以及 agent 收集其观测结果之后）的每个模拟步骤调用。
* Academy.AcademyReset() — 在 Academy 开始或重新开始模拟
（包括第一次）时调用。

3D Balance Ball 环境不使用这些函数（每个 agent 在需要时
会自行重置），但许多环境都会使用这些函数来
控制 agent 周围的环境。

### Brain

场景中的 Ball3DBrain 游戏对象包含 Brain 组件，
是 Academy 对象的子级。（场景中的所有 Brain 对象都必须是
Academy 的子级。）3D Balance Ball 环境中的所有 agent 使用
同一个 Brain 实例。
Brain 不存储关于 agent 的任何信息，
只是将 agent 收集的观测结果发送到决策过程，
然后将所选的动作返回给 agent。因此，所有 agent 可共享
同一个 Brain，但会独立行动。Brain 设置可以提供很多
关于 agent 工作方式的信息。

**Brain Type** 决定了 agent 如何决策。
**External** 和 **Internal** 类型需要协同使用：训练 agent 时使用 **External**，
而在采用经过训练的模型时使用 **Internal**。
**Heuristic** Brain 允许您通过扩展 Decision 类来对 agent 的逻辑进行
手动编码。最后，**Player** Brain 可让您将键盘命令
映射到动作，这样在测试 agent 和环境时
会非常有用。如果这些类型的 Brain 都不能满足您的需求，您可以
实现自己的 CoreBrain 来创建自有的类型。

在本教程中，进行训练时，需要将 **Brain Type** 设置为 **External**；
当您将经过训练的模型嵌入到 Unity 应用程序中时，需要将
**Brain Type** 更改为 **Internal**。

**向量观测空间**

在决策之前，agent 会收集有关自己在环境中所处的状态的
观测结果。ML-Agents 将观测分为两类：
**Continuous** 和 **Discrete**。**Continuous** 向量观测空间
会收集浮点数向量中的观测结果。**Discrete**
向量观测空间是一个状态表的索引。大多数示例环境
都使用连续的向量观测空间。

3D Balance Ball 示例中所用的 Brain 实例使用 **State Size** 为 8 的
**Continuous** 向量观测空间。这意味着
包含 agent 观测结果的特征向量包含八个元素：
平台旋转的 `x` 和 `z` 分量以及球相对位置和
速度的 `x`、`y` 和 `z` 分量。（观测结果值
在 agent 的 `CollectObservations()` 函数中进行定义。）

**向量运动空间**

Brain 以*动作*的形式向 agent 提供指令。与状态
一样，ML-Agents 将动作分为两种类型：**Continuous**
向量运动空间是一个可以连续变化的数字向量。向量
每个元素的含义都是由 agent 逻辑定义的（PPO 训练过程是一个了解agent的哪种状态更好的过程，这个过程是通过学习不同agent的不同状态会对应多少奖励来实现的）。
例如，一个元素可能表示施加到 agent 某个
`Rigidbody` 上的力或扭矩。**Discrete** 向量运动空间将其动作
定义为一个表。提供给 agent 的具体动作是这个表的
索引。

根据设计，3D Balance Ball 示例会使用这两种类型的向量运动
空间。
您可以尝试使用两种设置进行训练，观测是否有
差异。（使用离散运动空间时将 `Vector Action Space Size` 设置为 4，
而使用连续运动空间时将其设置为 2。）

### Agent

Agent 是在环境中进行观测并采取动作的参与者。
在 3D Balance Ball 环境中，Agent 组件被放置在 12 个
平台游戏对象上。基础 Agent 对象有一些影响其行为的
属性：

* **Brain** — 每个 Agent 必须有一个 Brain。Brain 决定了 agent 如何
决策。3D Balance Ball 场景中的所有 agent 共享同一个
Brain。
* **Visual Observations** — 定义 agent 用来观测其环境的
任何 Camera 对象。3D Balance Ball 不使用摄像机观测。
* **Max Step** — 定义在 agent 决定自己完成之前可以发生多少个
模拟步骤。在 3D Balance Ball 中，agent 在 5000 步之后重新开始。
* **Reset On Done** — 定义 agent 是否在完成时重新开始。
3D Balance Ball 将此项设置为 true，因此 agent 在达到
**Max Step** 计数后或在掉球后重新开始。

也许 agent 更有趣的方面在于 Agent 子类的
实现。在创建 agent 时，您必须扩展 Agent 基类。
Ball3DAgent 子类定义了以下方法：

* Agent.AgentReset() — Agent 重置时（包括会话开始时）
调用。Ball3DAgent 类使用重置函数来重置
平台和球。该函数会将重置值随机化，从而使
训练不局限于特定的开始位置和平台
姿态。
* Agent.CollectObservations() — 在每个模拟步骤调用。负责
收集 agent 对环境的观测结果。由于分配给
agent 的 Brain 实例设置为状态大小为 8 的连续向量观测空间，
因此 `CollectObservations()` 必须调用 8 次
`AddVectorObs`。
* Agent.AgentAction() — 在每个模拟步骤调用。接收 Brain 选择的
动作。Ball3DAgent 示例可以处理连续和离散
运动空间类型。在此环境中，两种状态类型之间实际上
没有太大的差别：这两种向量运动空间在每一步都会
导致平台旋转发生小变化。`AgentAction()` 函数
为 agent 分配奖励；在此示例中，agent 在每一步
将球保持在平台上时收到较小的正奖励，
而在掉球时收到更大的负奖励。agent 在掉球时还会被标记为
完成状态，因此会重置一个用于下一模拟步骤的
新球。

## 构建环境

第一步是打开包含 3D Balance Ball 环境的
Unity 场景：

1. 启动 Unity。
2. 在 Projects 对话框上，选择窗口顶部的 **Open** 选项。
3. 使用随后打开的文件对话框，找到 ML-Agents 项目内的
`unity-environment` 文件夹，然后单击 **Open**。
4. 在 `Project` 窗口中，找到文件夹
`Assets/ML-Agents/Examples/3DBall/`。
5. 双击 `Scene` 文件以加载包含 Balance Ball 环境的
场景。

![3DBall 场景](images/mlagents-Open3DBall.png)

由于我们要建立此环境来进行训练，因此我们需要
将 agent 使用的 Brain 设置为 **External**。这样 agent 在
进行决策时能够与外部训练过程进行通信。

1. 在 **Scene** 窗口中，单击 Ball3DAcademy 对象旁边的三角形
图标。
2. 选择其子对象 `Ball3DBrain`。
3. 在 Inspector 窗口中，将 **Brain Type** 设置为 `External`。

![将 Brain 设置为 External](images/mlagents-SetExternalBrain.png)

接下来，我们希望设置场景以便在训练过程启动我们的环境可执行文件时
正确播放场景。这意味着：
* 环境应用程序在后台运行
* 没有对话需要互动
* 正确的场景会自动加载

1. 打开 Player Settings（菜单：**Edit** > **Project Settings** > **Player**）。
2. 在 **Resolution and Presentation** 下方：
    - 确保选中 **Run in Background**。
    - 确保 **Display Resolution Dialog** 设置为 Disabled。
3. 打开 Build Settings 窗口（菜单：**File** > **Build Settings**）。
4. 选择目标平台。
    -（可选）选择“Development Build”以便
    [记录调试消息](https://docs.unity3d.com/Manual/LogFiles.html)。
5. 如果 **Scenes in Build** 列表中显示了任何场景，请确保
唯一选中的是 3DBall Scene。（如果该列表为空，则表示
仅当前场景包含在编译中）。
6. 单击 *Build*：
    a. 在 File 对话框中，导航至 ML-Agents 目录中的 `python`
    文件夹。
    b. 指定文件名，然后单击 **Save**。

![编译窗口](images/mlagents-BuildWindow.png)

## 使用 Reinforcement Learning（强化学习）来训练 Brain

有了一个包含模拟环境的 Unity 可执行文件后，现在我们
可以执行训练。为了首先确保您的环境和 Python
API 能正常工作，您可以使用 `python/Basics`
[Jupyter 笔记本](/docs/Background-Jupyter.md)。
此笔记本包含了 API 功能的简单演练。
在 `Basics` 中，务必将 `env_name` 设置为您先前构建的
环境文件的名称。

### 使用 PPO 进行训练

为了训练 agent 对球进行正确平衡，我们将使用一种称为 Proximal Policy Optimization (PPO) 的
Reinforcement Learning（强化学习）算法。
与其他许多 RL 算法相比，这种算法经证明是一种安全、
有效且更通用的方法，因此我们选择它作为与 ML-Agents
一起使用的示例算法。有关 PPO 的更多信息，
请参阅 OpenAI 近期发布的[博客文章](https://blog.openai.com/openai-baselines-ppo/)，
其中对 PPO 进行了说明。


为了训练 Balance Ball 环境中的 agent，我们将使用 Python
包。我们提供了一个名为 `learn.py` 的方便的 Python 包装脚本，此脚本会接受用于配置训练和预测阶段的参数。


我们将向这个脚本传递我们刚才构建的环境可执行文件的路径。（可选）我们可以
使用 `run_id` 来识别实验并创建用于存储模型和摘要统计信息的文件夹。当使用
TensorBoard 来观测训练统计信息时，将每次训练的此项设置为顺序值
将会很有用。也就是说，第一次训练时为“BalanceBall1”，
第二次训练时为“BalanceBall2”，依此类推。如果不这样做，每次训练的
摘要信息都会保存在同一个目录中，并且全部将包含在
同一个图中。

总之，转到命令行，进入 `ml-agents` 目录并输入：

```
python3 python/learn.py <env_name> --run-id=<run-identifier> --train
```

`--train` 标志告诉 ML-Agents 以训练模式运行。`env_name` 应该是刚才创建的 Unity 可执行文件的名字。


### 观测训练进度

开始使用 `learn.py` 按照前面部分所述的方式进行训练后，`ml-agents` 文件夹将
包含一个 `results` 目录。为了更详细地观测训练过程，
您可以使用 TensorBoard。从命令行中运行：

`tensorboard --logdir results`

然后导航至 `localhost:6006`。

从 TensorBoard 中，您将看到摘要统计信息：

* Lesson - 只有在进行
[课程训练](/docs/Training-Curriculum-Learning.md)时才有意义。
3D Balance Ball 环境中不使用此项。
* Cumulative Reward - 所有 agent 的平均累积场景奖励。
在成功训练期间应该增大。
* Entropy - 模型决策的随机程度。在成功训练过程中
应该缓慢减小。如果减小得太快，应增大 `beta`
超参数。
* Episode Length - 所有 agent 在环境中每个场景的
平均长度。
* Learning Rate - 训练算法搜索最优 policy 时需要多大的
步骤。随着时间推移应该减小。
* Policy Loss - policy 功能更新的平均损失。与 policy
（决定动作的过程）的变化程度相关。此项的幅度
在成功训练期间应该减小。
* Value Estimate - agent 访问的所有状态的平均价值估算。
在成功训练期间应该增大。
* Value Loss - 价值功能更新的平均损失。与模型
对每个状态的价值进行预测的能力相关。此项
在成功训练期间应该减小。

![TensorBoard 运行示例](images/mlagents-TensorBoard.png)

## 将经过训练的 Brain 嵌入到 Unity 环境中（测试功能）

一旦训练过程完成，并且训练过程保存了模型
（通过 `Saved Model` 消息可看出），您便可以将该模型添加到 Unity 项目中，
然后将其用于 Brain 类型为 **Internal** 的 agent。

### 设置 TensorFlowSharp 支持

由于 TensorFlowSharp 支持仍处于实验阶段，因此默认情况下会
将其禁用。为了启用这项支持，必须遵循以下步骤。请注意，
只有完成这些步骤后才能使用 `Internal` Brain 模式。

1. 确保 TensorFlowSharp 插件位于 `Assets` 文件夹中。
可在
[此处](https://s3.amazonaws.com/unity-ml-agents/0.5/TFSharpPlugin.unitypackage)下载一个包含 TF# 的 Plugins 文件夹。
下载后，双击并将其导入。您可以在 Project 选项卡中
（位于 `Assets` > `ML-Agents` > `Plugins` > `Computer` 下）
检查 TensorFlow 的相关文件来查看是否安装成功
2. 转到 `Edit` > `Project Settings` > `Player`
3. 对于每个目标平台
（**`PC, Mac and Linux Standalone`**、**`iOS`** 或 **`Android`**）：
    1.转到 `Other Settings`。
    2.选择 `Scripting Runtime Version` 为
    `Experimental (.NET 4.6 Equivalent)`
    3.在 `Scripting Defined Symbols` 中，添加标志 `ENABLE_TENSORFLOW`。
    输入后，按 Enter。
4. 转到 `File` > `Save Project`
5. 重新启动 Unity Editor。

### 将经过训练的模型嵌入到 Unity 中

1. 经过训练的模型存储在 `ml-agents` 文件夹中的 `models/<run-identifier>` 内。训练
完成后，该位置会有一个 `<env_name>.bytes` 文件，其中的 `<env_name>` 是训练期间使用的可执行文件的
名称。
2. 将 `<env_name>.bytes` 从 `python/models/ppo/` 移入
`unity-environment/Assets/ML-Agents/Examples/3DBall/TFModels/`。
3. 打开 Unity Editor，然后选择 `3DBall` 场景（如上所述）。
4. 从 Scene 层级视图中选择 `Ball3DBrain` 对象。
5. 将 `Type of Brain` 更改为 `Internal`。
6. 将 `<env_name>.bytes` 文件从 Editor 的 Project 窗口拖入
`3DBallBrain` Inspector 窗口中的 `Graph Model` 占位区域。
7. 按 Editor 顶部的 Play 按钮。

如果您正确执行了这些步骤，您现在应该能够
看到 Unity 编辑器中有这个用于控制平衡球行为的
训练模型。从这里开始，您便可以重新构建 Unity 的可执行文件，
并单独运行该可执行文件，在其中内置 agent 新学到的行为。
