跳至内容

构建迷宫求解器

本指南将引导您在 Unreal Engine 中创建一个迷宫环境并训练一个代理来通过强化学习来导航它。

在本例中,我们将创建一个静态迷宫,其中包含一个代理,该代理将通过强化学习来学习从起始位置(右侧)导航到目标位置(左侧)。代理通过传感器收集观测并执行器执行动作来与环境进行交互。在此示例中,我们的代理将使用射线投射来观察周围的墙壁,并在 x 和 y 方向上移动。

我们将通过让代理反复尝试解决迷宫来训练它。每次尝试解决迷宫都称为一个回合,当代理成功走出迷宫或时间耗尽时结束。

代理会定期回顾其在先前回合中的表现,然后更新其策略以进一步改进。为了量化代理的表现,我们定义了一个函数,在训练的每个步骤中奖励代理。在本例中,远离目标会带来小额惩罚,撞墙会带来中等惩罚,完成迷宫会带来大额奖励。通过这种方式,代理将学会一个策略,以最大化在每个回合中收到的总奖励。然后,代理可以在游戏中场时使用学习到的策略来决定采取哪些动作。

Unreal Engine 中的环境结构

要构建代理将要学习解决迷宫的游戏(以下称为环境),我们需要在 Unreal Engine 项目中进行以下设置:

  • 地图:游戏地图包括地面以及由大量墙壁构成的迷宫。所有对象,如代理和环境定义,都将放置在此地图中。

  • 代理蓝图:一个 Character 的子类,包含代理的形状、外观、传感器执行器

    • 传感器:代理有一个 传感器,即 RayCastObserver,它提供有关代理周围环境的信息。

    • 执行器:代理有一个 执行器,即 MovementInputActuator,它允许代理在不同方向上移动。

  • 训练器蓝图:一个 BlueprintTrainer 的子类,包含计算训练奖励状态的逻辑。

  • 环境定义:一个 BlueprintStaticScholaEnvironment 的子类,包含在不同训练回合之间初始化重置环境的逻辑。

  • 注册代理:将代理连接到环境定义和训练器。

初始设置

  1. 创建一个新的空白项目,并命名为您想要的名称和位置。

  2. 使用 /guides/setup_schola 指南将 Schola 插件安装到项目中。

  3. 转到 EditProject Settings,然后向下滚动找到 Schola。

  4. 对于 Gym Connector Class,选择 Python Gym Connector

创建地图

  1. 创建一个具有碰撞启用的墙蓝图类。
  2. 通过在地图场景中排列墙壁来创建迷宫。
  3. 可以选择在迷宫出口处添加终点线,以直观地标记目标。
  4. 将地图保存为 mazeMap

创建代理

  1. 创建一个新的蓝图类,父类为 Character,并将其命名为 MazeSolverAgent
  2. 添加任何所需的 静态网格 作为代理的身体,并可选地选择一个好看的材质。
  3. 保存并关闭蓝图,然后在地图的起始位置放置一个 MazeSolverAgent
  4. 检查 MazeSolverAgent 的位置是否为 x=0。如果不是,请将整个迷宫和代理一起移动,以确保起始位置的 x=0。

设置观测收集

Sensor 对象是组件,可以添加到代理或 BlueprintTrainer 中。它们可以包含一个 Observer 对象。它会告知代理周围物理对象的距离。代理有一个 Sensor,包含 Ray Cast Observer。每次射线的观测包括射线是否击中物体,以及该物体的距离。

  1. 在蓝图编辑器中打开 MazeSolverAgent 类。
  2. 添加一个 Sensor 组件。
  3. DetailsSensorObserver 中,选择 Ray Cast Observer
  4. DetailsSensorObserverSensor propertiesNumRays 中,输入 8。
  5. DetailsSensorObserverSensor propertiesRayDegrees 中,输入 360。
  6. DetailsSensorObserverSensor properties 中,勾选 DrawDebugLines 复选框。

设置执行器

ActuatorComponent 可以添加到代理或 BlueprintTrainer 中。它们可以包含一个 Actuator 对象。代理有一个 Actuator,即 Movement Input Actuator。它允许代理在不同方向上移动。在本教程中,我们将代理限制为仅在 x 和 y 方向上移动。

  1. 在蓝图编辑器中打开 MazeSolverAgent 类。
  2. 添加一个 Actuator 组件。
  3. DetailsActuator ComponentActuator 中,选择 Movement Input Actuator
  4. DetailsActuator ComponentActuatorActuator Settings 中,取消勾选 HasZDimension
  5. DetailsActuator ComponentActuatorActuator Settings 中,将 Minspeed 设置为 -10。
  6. DetailsActuator ComponentActuatorActuator Settings 中,将 MaxSpeed 设置为 10。

创建 Trainer

要在 Schola 中训练代理,代理必须由一个 AbstractTrainer 控制,该 AbstractTrainer 定义了 ComputeRewardComputeStatus 函数。在本教程中,我们将创建一个 BlueprintTrainerAbstractTrainer 的子类)。

  1. 创建一个新的蓝图类,父类为 BlueprintTrainer,并将其命名为 MazeSolverTrainer
  2. 添加一个新的布尔变量。将其命名为 hasHit。此变量将存储代理在当前步骤中是否撞墙。
  3. 将事件图设置为如下所示。这将把 On Actor Hit 事件 绑定到我们的代理,允许奖励函数检测代理何时撞墙。

定义奖励函数

在本教程中,我们使用每一步的奖励来让代理更接近目标点,并为到达目标点提供一次大的奖励。此外,如果代理撞墙,我们会给予惩罚。每一步的奖励计算方式为:如果代理未到达目标点,则为 -abs(agentPositionX - goalpostPositionX) / envSize - hasHitWall,如果代理已到达目标点,则为 10。

  1. 添加一个新的浮点变量。将其命名为 goalpostPositionX。此变量将存储目标点的 X 坐标。

    1. 返回地图,获取迷宫终点的 X 坐标。
    2. 返回 MazeSolverTrainer 类,并将 goalpostPositionX 的默认值设置为该数字。
  2. 添加一个新的浮点变量。将其命名为 envSize。此变量将存储迷宫的宽度。

    1. 返回地图,获取迷宫的宽度。
    2. 返回 MazeSolverTrainer 类,并将 envSize 的默认值设置为该数字。
  3. ComputeReward 中添加一个新的局部布尔变量。将其命名为 CachedHasHit。这将用于临时存储 hasHit 的值,以便我们可以在 ComputeReward 中重置它。

  4. ComputeReward 函数设置为如下所示。

定义状态函数

每次时间步长有三种可能的状态

  1. 运行中 (Running):回合仍在进行中,代理继续与环境交互。
  2. 已完成 (Completed):代理已成功到达终止状态,回合完成。
  3. 已截断 (Truncated):回合被强制终止,通常是由于外部限制(如时间步数或手动干预)而未能到达终止状态。

在本教程中,代理的终止状态是到达迷宫出口,我们通过确定 MazeSolverAgentX 坐标 >= goalpostPositionX 来跟踪这一点。因此,当代理越过 goalpostPositionX 时,回合即完成。我们还设置了一个最大步数,以防止回合无限期运行。

  1. 添加一个新的整数变量。将其命名为 maxStep,并将默认值设置为 5000。这意味着如果回合在 5000 步内未完成,则该回合将被截断。您可以根据环境的大小或代理的速度等因素调整此数字,以允许更长或更短的回合。
  2. 按如下方式设置 ComputeStatus

创建环境定义

要训练 Schola 中的代理,游戏必须有一个 StaticScholaEnvironment Unreal 对象,其中包含代理以及初始化或重置游戏环境的逻辑。在本教程中,我们将创建一个 Blueprint EnvironmentStaticScholaEnvironment 的子类)作为环境。InitializeEnvironment 函数在游戏开始时调用,并设置环境的初始状态。在本教程中,我们保存代理的初始位置并设置全局时间缩放 (Set Global Time Dilation),这将使地图中所有对象的时标加快 10 倍。这使得代理在训练过程中能够有意义地探索更多空间,防止模型陷入局部最小值,并减少训练时间。ResetEnvironment 函数在每个新回合开始前调用。在本教程中,我们只需将代理重置到其初始位置。

  1. 创建一个新的 Blueprint Class,父类为 BlueprintStaticScholaEnvironment,并将其命名为 MazeSolverEnvironment

  2. 添加一个名为 agentArray 的新变量,类型为 Pawn(对象引用) (Pawn (Object Reference)) 数组。此变量用于跟踪属于此环境定义的已注册代理。

    1. 将此变量设置为可公开编辑(通过单击眼睛图标切换可见性)。
  3. 添加一个名为 agentInitialLocation 的新变量,类型为 Transform。此变量用于存储代理的初始位置,以便在重置时恢复。

  4. 按如下方式设置 Event Graph 和 RegisterAgents 函数。

  5. 保存并关闭 blueprint,然后在地图中的任意位置放置一个 MazeSolverEnvironment。其位置无关紧要。

注册代理

  1. 点击地图中的 MazeSolverEnvironment

    1. 转到 Details panelDefaultAgent Array
    2. 添加一个新元素。
    3. 在下拉菜单中选择 MazeSolverAgent
  2. 在蓝图编辑器中打开 MazeSolverAgent 类。

    1. 转到 Details Panel。
    2. 搜索 AIController
    3. 在下拉菜单中,选择 MazeSolverTrainer

开始训练

我们将使用 Proximal Policy Optimization (PPO) 算法训练代理 500,000 步。以下两种方法运行相同的训练。从终端运行可能更便于进行超参数调整,而从 Unreal Editor 运行在编辑游戏时可能更方便。

  1. 在 Unreal Engine 中运行游戏(点击绿色三角形)。
  2. 打开一个终端或命令提示符,然后运行以下 Python 脚本:
终端窗口
schola-sb3 -p 8000 -t 500000 PPO

启用 TensorBoard

TensorBoard 是 TensorFlow 提供的一个可视化工具,可让您在训练期间跟踪和可视化损失和奖励等指标。

--enable-tensorboard 标志添加到命令中以启用 TensorBoard。--log-dir 标志设置日志保存的目录。

终端窗口
schola-sb3 -p 8000 -t 500000 --enable-tensorboard --log-dir experiment_maze_solver PPO

训练完成后,您可以通过在终端或命令提示符中运行以下命令来查看 TensorBoard 中的训练进度。请确保您首先 安装 TensorBoard,并将 --logdir 设置为日志保存的目录。

终端窗口
tensorboard --logdir experiment_maze_solver/PPO_1

后续步骤

恭喜!您已成功训练了您的第一个 Schola 代理!接下来,您可以尝试以下操作:

  1. 修改 reward,使其仅为稀疏奖励,然后观察代理在重新训练后的表现。
  2. 向代理添加更多传感器或修改 RayCastObserver 参数,然后观察代理在重新训练后的表现。
  3. 为每个回合更改代理的初始位置,然后观察代理在重新训练后的表现。
  4. 高级:为每个回合动态更改迷宫形状(相同大小或不同大小),并尝试训练代理来解决各种迷宫。
© . This site is unofficial and not affiliated with AMD.