注:本文章为官方文档翻译,如有侵权行为请联系作者删除
Agent - Unity ML-Agents Toolkit–原文链接>

ML-Agents:智能体(一)
ML-Agents:智能体(二)
ML-Agents:智能体(三)
ML-Agents:智能体(四)

观测和传感器

为了让智能体能够学习,观测结果应当包含智能体完成任务所需的所有信息。如果信息不充分或不相关,智能体可能学得不好,甚至根本学不会。确定应包含哪些信息的一个合理方法是考虑计算该问题的解析解需要哪些信息,或者考虑人类解决该问题时会用到哪些信息。

生成观测结果

ML-Agents 为智能体 提供了多种观测方式:

  • 重写Agent.CollectObservations()方法并将观测结果传递给提供的VectorSensor
  • 为Agent上的字段和属性添加 [Observable] 属性。
  • 实现ISensor接口,通过附加到 智能体上的 SensorComponent来创建ISensor
Agent.CollectObservations()

Agent.CollectObservations() 适合用于观测环境中的数字和非视觉量。Policy类调用每个智能体的 CollectObservations(VectorSensor sensor)方法。您对此函数的实现必须调用 VectorSensor.AddObservation 来添加矢量观测值。

VectorSensor.AddObservation 方法提供了多种重载,用于向观察向量添加常见类型的数据。您可以直接向观察向量添加整数和布尔值,还可以添加一些常见的 Unity 数据类型,例如 Vector2Vector3Quaternion

有关各种状态观测函数的示例,您可以查看 ML-Agents SDK 中包含的示例环境。例如,3DBall 示例使用平台的旋转、球的相对位置和球的速度作为其状态观测。

public GameObject ball;

public override void CollectObservations(VectorSensor sensor)
{
    // Orientation of the cube (2 floats)
    sensor.AddObservation(gameObject.transform.rotation.z);
    sensor.AddObservation(gameObject.transform.rotation.x);
    // Relative position of the ball to the cube (3 floats)
	sensor.AddObservation(ball.transform.position -gameObject.transform.position);
    // Velocity of the ball (3 floats)
    sensor.AddObservation(m_BallRb.velocity);
    // 8 floats total
}

作为一个示例,您可以从观测中移除速度分量并重新训练 3DBall 智能体。虽然它可以学会很好地控制球,但不使用速度信息的智能体的控制表现明显更差。

传递给VectorSensor.AddObservation()的观测结果必须始终包含相同数量的元素,并且必须始终保持相同的顺序。如果环境中观测到的实体数量可能有所不同,您可以用零填充特定观测中任何缺失的实体,或者您可以将智能体的观测结果限制为固定子集。例如,您可以只观测最近的五个敌人,而不是观测环境中的每个敌人。

此外,在 Unity 编辑器中设置智能体的 Behavior Parameters 时,必须将Vector Observations -> Space Size 设置为与 CollectObservations() 写入的浮点数数量相等。

可观测字段和属性

另一种方法是将相关观测结果定义为 Agent 类上的字段或属性,并用 Observable(Attribute) 对它们进行注释(即C# [[特性(Attribute]]),用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签)。例如,在 Ball3DHardAgent 中,可以通过向 Agent 添加属性来观测位置之间的差异:

using Unity.MLAgents.Sensors.Reflection;

public class Ball3DHardAgent : Agent {

    [Observable(numStackedObservations: 9)]
    Vector3 PositionDelta
    {
        get
        {
            return ball.transform.position - gameObject.transform.position;
        }
    }
}

Observable(Attribute) 目前支持大多数基本类型(例如浮点数、整型、 布尔),以及 Vector2 , Vector3 , Vector4 , Quaternion 和枚举。

Observable(Attribute) 的行为由 智能体的Behavior Parameters脚本中的“Observable Attribute Handling”控制。其可能值为:

  • Ignore (default)- 将忽略 智能体 上的所有可观测属性。如果 智能体 上没有可观测属性,这会使初始化非常快。
  • Exclude Inherited- 仅检查声明类上的成员;忽略继承的成员。这是性能和灵活性之间的合理权衡。
  • Examine All-将检查类上的所有成员。这可能会导致启动时间变慢。

Exclude Inherited”通常就足够了,但如果您的智能体从另一个具有可观测成员的智能体实现继承,则需要使用“Examine All”。

在计算机内部,Observable(Attribute) 使用(Reflection)来确定 智能体 的哪些成员具有 Observable(Attribute)s,并且还使用(Reflection)在运行时访问字段或调用属性。这可能比使用 CollectObservations 或 ISensor 慢,不过这可能还不足以对性能产生明显的影响。

注意:在向智能体添加 [Observable] 字段或属性时,无需调整代理的 Behavior Parameters 空间大小,因为其大小可以在使用前计算得出。

ISensor 接口和 SensorComponents

ISensor接口通常面向高级用户。实际生成观测值使用 Write() 方法,但还必须实现一些其他方法,例如返回观测值的形状。

SensorComponent 抽象类用于在运行时创建实际的 ISensor 。它必须附加到与 Agent 相同的 GameObject 上,或者附加到其子 GameObject 上。

API 中提供了多个 SensorComponents,包括:

  • CameraSensorComponent- 使用来自Camera 的图像作为观测值。
  • RenderTextureSensorComponent- 使用 RenderTexture的内容作为观测值。
  • RayPerceptionSensorComponent- 使用来自一组射线投射的信息作为观测值。
  • Match3SensorComponent- 使用Match-3 游戏的棋盘 作为观测值。
  • -GridSensorComponent - 使用网格形状的一组框查询作为观测值。

注意:使用 SensorComponents 时,无需调整智能体的 Behavior Parameters 中的空间大小。

在计算机内部,Agent.CollectObservations[Observable]属性都使用 ISensors 来写入观测结果。

矢量观测

Agent.CollectObservations()Observable(Attribute)都产生矢量观测,这些观测值以 float 列表的形式表示。而 ISensor 则既能产生向量观测值,也能产生视觉观测值,视觉观测值是多维的浮点数数组。

以下是处理矢量观测时的一些其他注意事项:

One-hot编码分类信息

类型枚举应采用One-hot编码(也称独热编码)方式。也就是说,为枚举中的每个元素向特征向量中添加一个元素,将表示观察到的成员的元素设置为 1,其余的设置为 0。例如,如果您的枚举包含 [Sword, Shield, Bow],并且智能体观测到当前项目是Bow,则您需要将元素:0、0、1 添加到特征向量。以下代码示例说明了如何添加。

enum ItemType { Sword, Shield, Bow, LastItem }
public override void CollectObservations(VectorSensor sensor)
{
    for (int ci = 0; ci < (int)ItemType.LastItem; ci++)
    {
        sensor.AddObservation((int)currentItem == ci ? 1.0f : 0.0f);
    }
}

VectorSensor还提供了一个双参数函数AddOneHotObservation()作为One-hot式观测的快捷方式。以下示例与上一个示例相同。

enum ItemType { Sword, Shield, Bow, LastItem }
const int NUM_ITEM_TYPES = (int)ItemType.LastItem + 1;

public override void CollectObservations(VectorSensor sensor)
{
    // The first argument is the selection index; the second is the
    // number of possibilities
    sensor.AddOneHotObservation((int)currentItem, NUM_ITEM_TYPES);
}

Observable(Attribute)内置了对枚举的支持。请注意,在这种情况下不需要LastItem占位符:

enum ItemType { Sword, Shield, Bow }

public class HeroAgent : Agent
{
    [Observable]
    ItemType m_CurrentItem;
}
归一化

为了在训练时获得最佳结果,您应将特征向量的分量归一化为 [-1, +1] 或 [0, 1] 范围。归一化这些值后,PPO 神经网络通常可以更快地收敛到解决方案。请注意,归一化到这些建议范围并不总是必要的,但在使用神经网络时,这被认为是一种典范做法。观测结果的分量之间的范围差异越大,训练受影响的可能性就越大。

要将值标准化为 [0, 1],可以使用以下公式:

normalizedValue = (currentValue - minValue)/(maxValue - minValue)

警告: 对于向量,您应该将上述公式应用于每个分量(x、y 和 z)。请注意,这与在 Unity 中使用 Vector3.normalized 属性或 Vector3.Normalize() 方法(以及类似地使用 Vector2 )是不同的。

旋转和角度也应归一化。对于 0 到 360 度之间的角度,可以使用以下公式:

Quaternion rotation = transform.rotation;
Vector3 normalized = rotation.eulerAngles / 180.0f - Vector3.one;  // [-1,1]
Vector3 normalized = rotation.eulerAngles / 360.0f;  // [0,1]

对于超出范围 [0,360] 的角度,您可以减小角度,或者,如果转数很大,则可以增加标准化公式中使用的最大值。

堆叠

堆叠是指将先前步骤中的观测结果重复作为更大观测的一部分。例如,考虑一个智能体在四个步骤中生成这些观测结果

step 1: [0.1]
step 2: [0.2]
step 3: [0.3]
step 4: [0.4]

如果我们使用的堆栈大小为 3,则观测结果将是:

step 1: [0.1, 0.0, 0.0]
step 2: [0.2, 0.1, 0.0]
step 3: [0.3, 0.2, 0.1]
step 4: [0.4, 0.3, 0.2]

(在前 stackSize-1 步中,观测值用零填充。) 这是一种在不增加循环神经网络(RNN)复杂性的情况下,为智能体提供有限“记忆”的简单方法。

启用堆叠的步骤取决于您如何生成观测结果:

  • 对于 Agent.CollectObservations(),将 智能体 上的“Stacked Vectors”设置 Behavior Parameters为大于 1 的值。
  • 对于 Observable(Attribute),在构造函数中设置numStackedObservations参数,例如[Observable(numStackedObservations: 2)]
  • 对于ISensors,将它们包装在StackingSensor中(它也是一个ISensor)。通常,这应该在你的SensorComponentCreateSensor()方法中完成。
总结
  • 矢量观测应该包括所有允许智能体做出最佳明智决策的相关变量,理想情况下不应该包含任何无关信息。
  • 如果需要记住或随时间进行比较,则应该在模型中使用 RNN,或者应该更改智能体的Behavior Parameters脚本中的Stacked Vectors值。
  • 分类变量(例如物体类型[Sword, Shield, Bow])应以One-hot方式编码(即3-> 0, 0, 1)。这使用 VectorSensor中的AddOneHotObservation()方法 自动完成, 或者对智能体的enum字段或属性使用 [Observable]
  • 一般情况下,所有输入都应做归一化处理(在区间(0,1)或( -1 , 1)内)。例如,智能体的位置信息为x,其最大可能值为maxValue ,则应将智能体位置信息记为 , VectorSensor.AddObservation(transform.position.x / maxValue);而不是 VectorSensor.AddObservation(transform.position.x);
  • 相关游戏对象的位置信息应尽可能以相对坐标进行编码。这通常相对于智能体位置。

视觉观测

视觉观测通常通过Camera SensorRender Texture Sensor提供给智能体。它们收集图像信息并将其转换为 3D 张量,然后可以将其输入到智能体策略的卷积神经网络 (CNN) 中。有关 CNN 的更多信息,请参阅 本指南。这使得智能体可以通过观测图像中的空间规律进行学习。视觉和矢量观测可以在同一个智能体中使用。

使用视觉观测的智能体可以捕捉任意复杂度的状态,当状态难以用数字描述时非常有用。然而,与矢量观测相比,它们通常效率较低且训练速度较慢,有时甚至根本无法成功。因此,只有在无法使用矢量或射线观测正确定义问题时才应使用它们。

视觉观测可以从场景中的相机或渲染纹理中获取。要向智能体添加视觉观测,请将相机传感器(即Camera Sensor Component脚本)或渲染纹理传感器组件(即RenderTextures Sensor Component脚本)添加到智能体。然后将要添加的相机或渲染纹理拖到CameraRenderTexture字段。您可以拥有多个相机或渲染纹理,甚至可以将两者结合使用。对于每个视觉观测,设置图像的宽度和高度(以像素为单位)以及观测是彩色还是灰色。

Agent's 相机

或者

Agent渲染纹理

使用相同策略的每个智能体t必须具有相同数量的视觉观测,并且它们都必须具有相同的分辨率(包括它们是否是灰度)。此外,智能体上的每个传感器组件都必须具有唯一的名称,以便可以确定地对它们进行排序(该名称对于该智能体必须是唯一的,但多个智能体可以具有同名的传感器组件)。

视觉观测还支持堆叠,只需要指定Observation Stacks 的值大于 1 。最后 stackSize 步的视觉观察结果将在最后一个维度(通道维度)上堆叠。

使用RenderTexture视觉观测时,一个方便的调试功能是添加一个Canvas,然后添加一个Raw Image并将其纹理设置为智能体的RenderTexture。这将在游戏屏幕上呈现智能体观测结果。

注意 :目前的3.0版本所给出关于视觉的示例中使用的均是Camera Sensor 脚本。

使用原始图像渲染纹理

GridWorld环境是使用 RenderTexture 进行调试和观测的一个示例。请注意,在此示例中,Camera 被渲染到 RenderTexture,然后用于观测和调试。要更新 RenderTexture,每次在游戏代码中请求决策时都必须要求 Camera 进行渲染。当直接使用 Camera 作为观测时,此操作由智能体 自动完成。

Agent渲染纹理调试

总结
  • 为了收集视觉观测,将CameraSensorRenderTextureSensor 组件附加到Agent游戏对象 (GameObject)。
  • 一般情况下仅当矢量观测不够充分时才应使用视觉观测。
  • 图像尺寸应尽可能保持小,但不要丢失决策所需的细节。
  • 在不需要颜色信息来做出明智决策的情况下,图像应该变成灰度。

射线观测

射线观测是向智能体提供观测的另一种可行方法。这可以通过向智能体游戏对象添加Ray Perception Sensor Component3D(或Ray Perception Sensor Component2D)轻松实现。

在观测过程中,多条射线(或球体,取决于设置)被投射到物理世界中,被击中的物体决定了产生的观测矢量。

具有两个 RayPerceptionSensorComponent3D 的Agent

两个传感器组件均有多种设置:

  • Detectable Tags (可检测标签)- 对应于 智能体 应能够区分的对象类型的字符串列表。例如,在 WallJump 示例中,我们使用“墙”、“目标”和“方块”作为要检测的对象列表。
  • Rays Per Direction(每个方向的光线数)-确定投射的光线数。在 WallJump 示例中,一条光线始终向前投射,而许多条光线则向左和向右投射。
  • Max Ray Degrees(最大射线度数) -最外层射线的角度(以度为单位)。90 度对应于智能体的左侧和右侧。
  • Sphere Cast Radius(球体投射半径) -用于球体投射的球体的大小。如果设置为 0,将使用射线代替球体。射线可能更有效,尤其是在复杂场景中。
  • Ray Length(射线长度) -即射线的发射距离。
  • Ray Layer Mas(射线图层蒙版) -传递给射线投射或球体投射的_图层蒙版。 这可用于在投射时忽略某些类型的对象。
  • Observation Stacks(观测堆叠) -与之前射线检测结果进行“堆叠”的数量。请注意,这可以独立于 Behavior Parameters 中的“Stacked Vectors”设置。
  • Start Vertical Offset(起始垂直偏移)(仅限 3D)射线起点的垂直偏移。
  • End Vertical Offset(结束垂直偏移)(仅限 3D)射线终点的垂直偏移。
  • Alternating Ray Order(交替光线顺序) 交替是默认设置,它给出的顺序为 (0、-delta、delta、-2 delta、2 delta、…、-n delta、n delta)。如果禁用交替,则顺序为从左到右 (-n delta、-(n-1) delta、…、-delta、0、delta、…、(n-1) delta、n delta)。对于一般用途没有区别,但如果使用自定义模型,则可以优先选择与空间结构相匹配的从左到右布局(例如,使用卷积网络进行处理)。
  • Use Batched Raycasts(使用批量射线投射)(仅限 3D)是否使用批量射线投射。启用以使用批量射线投射和作业系统。

在上面的示例图中,智能体有两个RayPerceptionSensorComponent3D。两者都使用每个方向 3 条射线和最大射线度数 90。其中一个组件有垂直偏移,因此Agent可以判断是否可以跳过墙壁。

创建的观测值的总大小为

(Observation Stacks) * (1 + 2 * Rays Per Direction) * (Num Detectable Tags + 2)

因此,射线和标签的数量应尽可能少,以减少所使用的数据量。请注意,这与在 Behavior Parameters 中定义的状态大小是分开的,所以在设置状态大小时无需担心上述公式。

总结
  • 在游戏对象上附加RayPerceptionSensorComponent3DRayPerceptionSensorComponent2D脚本使用射线检测。
  • 当智能体具有相关的空间信息且不需要完全渲染的图像来传达时,最适合使用此观测类型。
  • 使用尽可能少的射线和标签来解决问题,以提高学习稳定性和智能体性能。
  • 如果您遇到性能问题,请尝试启用“使用批处理射线投射”设置来使用批处理射线投射。 (仅适用于 3D 射线感知传感器。)

网格观测

基于网格的观测结合了视觉观测中 2D 空间表示的优势,以及在 射线观测中定义可检测对象的灵活性。传感器使用一组网格形状的框查询,并提供围绕智能体的自上而下的 2D 视图。这可以通过 GridSensorComponent向智能体游戏对象添加来实现。

在观测过程中,传感器检测每个细胞中可检测物体的存在,并将其编码为One-hot表示。从每个单元格收集的信息形成一个 3D 张量观测值,并像视觉观测值一样被输入到智能体策略的卷积神经网络(CNN)中。

带有 GridSensorComponent 的Agent

传感器组件具有以下设置:

  • Cell Scale(单元格比例) -网格中每个单元格的比例。
  • Grid Size(网格尺寸) -网格每边的单元格数
  • Agent Game Object(智能体游戏对象) 持有网格传感器的智能体。这用于区分与智能体具有相同标签的对象,以防止智能体检测到自身。
  • Rotate With Agent(随智能体旋转) 网格是否随智能体旋转。
  • Detectable Tags(可检测标签) 智能体应能够区分的对象类型的字符串列表。
  • Collider Mask(碰撞器掩码) 传递给碰撞器检测的 LayerMask。这可用于忽略某些类型的对象。
  • Initial Collider Buffer(初始对撞机缓冲区大小) 每个单元格在非分配物理调用中使用的初始碰撞器缓冲区大小。
  • Max Collider Buffer Size(最大碰撞器缓冲区大小) 每个单元格在非分配物理调用中使用的最大碰撞器缓冲区大小。

每个网格单元的观测值是检测到的对象的One-hot编码。创建的观测值的总大小为

GridSize.x * GridSize.z * Num Detectable Tags

因此,可检测标签的数量和网格大小应尽可能小,以减少使用的数据量。这在观测的粒度和训练速度之间进行了权衡。

为了使网格传感器能够捕获更多样化的观察结果, GridSensorComponent 以及其底层的 GridSensorBase 还提供了可被重写以从检测到的对象中收集自定义观察结果的接口。有关自定义网格传感器的更多详细信息,请参阅扩展网格传感器的文档。

注意GridSensor仅在 3D 环境中有效,在 2D 环境中无法正常运行。

总结
  • 请附加上 GridSensorComponent 脚本以供使用。
  • 这种观察类型最适用于存在相关非视觉空间信息的情况,而这些信息在二维表示中能够得到最佳呈现。
  • 为提高学习稳定性和智能体性能,应使用尽可能小的网格尺寸和尽可能少的标签来解决问题。
  • 请在 2D 游戏中不要使用 GridSensor

变数量观测

智能体可以通过使用 BufferSensor 从不同数量的游戏对象收集观察结果。您可以通过向智能体的游戏对象添加 BufferSensorComponent 脚本来为其添加 BufferSensor功能 。在智能体必须关注不同数量的实体(例如不同数量的敌人或投射物)的情况下, BufferSensor 可能会很有用。在训练器端, BufferSensor 会通过注意力模块进行处理。有关注意力机制的更多信息,请点击[此处]([1706.03762] Attention Is All You Need)。使用变数量观测进行训练或推理可能比使用平面矢量观测慢。但是,注意机制能够解决需要在场景(例如我们的Sorter 环境)中的实体之间进行比较推理的问题。请注意,即使BufferSensor可以处理可变数量的实体,您仍然需要定义最大实体数。这是因为我们的网络架构需要知道观测的形状。如果观测到的实体少于最大值,则观测将用零填充,而训练师将忽略填充的观测。请注意,注意力层对填充具有不变性。这些实体的顺序无关紧要,所以在将它们输入到 BufferSensor 之前无需正确地“排序”这些实体。

BufferSensorComponent脚本有两个参数:

  • Observation Size:这是每个实体将用多少个浮点数表示。这个数字是固定的,所有实体必须具有相同的表示。例如,如果您要放入的实体BufferSensor具有相关信息位置和速度,则Observation Size应该是 6 个浮点数。
  • Maximum Number of Entities:这是BufferSensor能够收集的最大实体数量。

要将实体的观测结果添加到BufferSensorComponent中,您需要在 Agent.CollectObservations() 方法中调用 BufferSensorComponent.AppendObservation() ,并传入一个大小为 Observation Size 的浮点数数组作为参数。

注意:目前,输入的观测结果BufferSensor尚未标准化,您需要手动将观测结果标准化为 -1 和 1 之间。

总结
  • 给智能体游戏对象附加上BufferSensorComponent脚本即可使用。
  • Agent.CollectObservations() 方法中调用 BufferSensorComponent.AppendObservation() ,以将某个实体的观察结果添加到 BufferSensor 中。
  • 在将实体观测值输入到 BufferSensor 之前,先对其进行标准化处理。

目标信号

智能体有可能收集到会被当作“目标信号”处理的观察结果。目标信号用于调整智能体的策略,也就是说,如果目标发生变化,策略(即从观察结果到行动的映射)也会随之改变。请注意,对于任何观察结果而言,这都是成立的,因为所有观察结果都会在一定程度上影响智能体的策略。但通过明确指定目标信号,我们可以让这种条件对智能体来说更为重要。此特性可用于智能体必须学习解决在某些方面相似的不同任务的场景,因为智能体将学会复用来自不同任务的学习成果,从而更好地进行泛化。在 Unity 中,您可以通过将 VectorSensorComponentCameraSensorComponent 脚本附加到智能体,并选择 Goal Signal 作为 Observation Type ,来指定 VectorSensorCameraSensor 为目标。在训练器方面,有两种不同的方式来对策略进行条件设置。这个 设置由 conditioning_type)参数决定。 如果设置为 hyper (默认值),则会使用超网络根据目标观测值生成策略的部分权重。请注意,使用超网络需要大量的计算,建议在策略中使用较少的隐藏单元以减轻这一负担。 如果设置为 none ,则目标信号将被视为常规观测值。 有关如何使用目标信号的示例,请参阅 GridWorld 示例。

总结
  • VectorSensorComponent或附加CameraSensorComponent到智能体的游戏对象,并将观测类型设置为“目标”以使用该功能。
  • 在训练配置中设置conditioning_type参数。
  • 使用超网络条件类型时,减少网络中隐藏单元的数量。

鉴于作者水平有限,本文可能存在不足之处,欢迎各位读者提出指导和建议,共同探讨、共同进步。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐