Unity ML-Agent之Agents设计

Agents设计

本文翻译自https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Learning-Environment-Design-Agents.md

Agent是一个能够观察其所在的环境并使用这些观察结果来决定最佳行动方案的行动者。 在Unity中通过扩展Agent类创建agents。 对于增强学习来说,要创建可以成功学习的agents,最重要的方面是,agents收集的观察结果和你分配的reward,其中reward用于评估智能体当前状态对完成其任务的价值。

Agent将其观察结果传递给其Brain。 然后,Brain做出决定并将选定的动作传递回agent。 你的agent代码必须执行该动作,例如,将agent向一个方向或另一个方向移动。 为了使用增强学习来训练agent,必须在每次行动中计算对agent的奖励值。 该奖励用于发现最佳决策策略。(对于已经训练好的agent或模仿学习不会使用奖励。)

Brain类从agent本身抽象出决策逻辑,以便对多个agents中使用同一个Brain。 Brain如何做出决策取决于它的Brain类型。 External Brain简单地将来自agent的观察结果传递给外部进程,然后外部进程的决策结果再传回给agent。 Internal brain使用训练完成的策略参数来制定决策(并且不再调整参数以寻找更好的决策)。其他类型的Brain不直接涉及训练,但你可能会发现它们作为训练项目的一部分很有用。

决策 Decisions

你可以配置模拟步数,使得“观察-决策-动作-奖励”这个周期在一定的模拟步数后执行一次(频率默认为每步一次)(这句话根据自己的理解翻译的)。 你还可以设置agent根据需求请求决策。 以固定步长间隔进行决策通常最适合基于物理学的模拟。 根据需求做出决策通常适用于agent仅响应特定事件或采取不同持续时间的行动的情况。 例如,机器人模拟器中的agent必须提供关节力矩的精确控制,因此应该在模拟的每一步中做出决定。 另一方面,只有在特定游戏或模拟事件发生时,agent才需要使用按需决策(on-demand decision making)。

要控制基于step决策的频率,请在Unity Inspector窗口中设置Agent对象的决策频率值(Decision Frequency)。具有相同Brain实例的agents可以使用不同的频率。在没有决策请求的模拟步骤中,agent收到的是与先前决策所选择的相同动作。

按需决策 On Demand Decision Making

按需决策允许agetns只在需要时才从Brain请求决策,而不是以固定频率接收决策。 当agent承诺采取可变数量的步数或者agents无法同时作出决策时,这非常有用。 这种情况通常是基于回合的游戏,该游戏中agetns必须对事件作出反应,或者游戏中agetns做动作之间的时间间隔是变化的情况。(多看几个例子会对这里有更好的理解)

当你打开agent的按需决策时,agent代码必须调用Agent.RequestDecision()函数。 该函数调用开启了一次“观察-决策-动作-奖励”周期的迭代。Brain调用agent的CollectObservations()方法,做出决策,然后通过调用AgentAction()方法返回它。 然后,在开始下一次迭代之前,Brain等待agent来请求下一次的决策。

观察 Observations

为了做出决策,agent必须观察其环境以确定其当前状态。状态观察可以采取以下形式:

连续矢量(Continuous Vector) - 由数字数组组成的特征矢量。
离散矢量(Discrete Vector) - 一个状态表的索引(通常只对最简单的环境有用)。
视觉观察(Visual Observations) - 一个或多个相机图像。

当agent使用连续或离散矢量观察空间时,请实现Agent.CollectObservations()方法以创建特征矢量或状态索引。 当使用Visual Observations时,你只需确定哪个Unity Camera对象将提供图像,Agent基类会处理其他事情的。 当agent使用视觉观察时(除非它也使用矢量观测),则不需要实现CollectObservations()方法。

连续观察向量空间:特征向量

对于使用连续状态空间的agent,可以创建一个特征向量来表示agent在模拟的每个步骤中的观察结果。 Brain类调用其每个agent的CollectObservations()方法。 你的这个函数的实现必须调用AddVectorObs来添加矢量观察值。

观察必须包括agent完成任务所需的所有信息。 没有足够的相关信息,agent可能学习得不好,或者根本不会学习。 确定应包含哪些信息的一个合理方法是考虑你需要计算问题的解决方案。

有关各种状态观察功能的示例,可以查看ML-Agents SDK中包含的示例环境。 例如,3DBall示例使用平台的旋转,球的相对位置以及球的速度作为其状态观察。 作为一个实验,你可以从观察中移除速度分量并重新训练3DBall代理。 虽然它也会学会怎样平衡球,但没有使用速度的agent表现明显更差。

public GameObject ball;

private List<float> state = new List<float>();
public override void CollectObservations()
{
    AddVectorObs(gameObject.transform.rotation.z);
    AddVectorObs(gameObject.transform.rotation.x);
    AddVectorObs((ball.transform.position.x - gameObject.transform.position.x));
    AddVectorObs((ball.transform.position.y - gameObject.transform.position.y));
    AddVectorObs((ball.transform.position.z - gameObject.transform.position.z));
    AddVectorObs(ball.transform.GetComponent<Rigidbody>().velocity.x);
    AddVectorObs(ball.transform.GetComponent<Rigidbody>().velocity.y);
    AddVectorObs(ball.transform.GetComponent<Rigidbody>().velocity.z);
}

特征向量必须包含相同数量的元素,并且观察值必须始终位于列表中的相同位置。 如果环境中观察到的实体的数量可能会发生变化,则可以将该次观察中的任何缺失实体填充为零,或者可以将agent的观察限制为固定子集。例如,不是观察环境中的每个敌方agent,只能观察最近的五个敌人agent。

当你在Unity Editor中设置Agent的Brain时,请设置以下属性以使用连续矢量观察:

Space Size 空间大小 - 状态大小必须与特征向量的长度相匹配。
Space Type 空间类型 - 设置为连续。
Brain Type 大脑类型 - 训练期间设置为External; 使用训练好的模型时设置为Internal。

观察特征向量是浮点数列表,这意味着您必须将任何其他数据类型转换为浮点数或浮点数列表。

整数可以直接添加到观察向量中。你必须将布尔值显式转换为数字:

AddVectorObs(isTrueOrFalse ? 1 : 0);

对于位置和旋转等实体,你可以将其组件单独添加到特征列表中。 例如:

Vector3 speed = ball.transform.GetComponent<Rigidbody>().velocity;
AddVectorObs(speed.x);
AddVectorObs(speed.y);
AddVectorObs(speed.z);

类型枚举应该以one-hot风格进行编码。 也就是说,为枚举的每个元素添加一个元素到特征向量中,将表示观察到的成员的元素设置为1,并将剩余的元素设置为零。 例如,如果你的枚举包含[Sword,Shield,Bow]并且agent观察到当前项是Bow,则可以将元素:0,0,1添加到特征向量中。 以下代码示例说明了如何添加:

enum CarriedItems { Sword, Shield, Bow, LastItem }
private List<float> state = new List<float>();
public override void CollectObservations()
{
    for (int ci = 0; ci < (int)CarriedItems.LastItem; ci++)
    {
        AddVectorObs((int)currentItem == ci ? 1.0f : 0.0f);            
    }
}

归一化 Normalization

为了在训练时获得最佳效果,应将特征向量的分量归一化到[-1,+1]或[0,1]范围内。 当你将这些值归一化时,PPO神经网络通常可以更快地收敛到解。 请注意,将这些推荐范围归一化并不总是必要的,但在使用神经网络时这被认为是最佳实践。 观察部分之间的范围变化越大,训练受到的影响就越大。

旋转和角度也应该标准化。 对于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]的角度,或者减小角度,或者增加标准化公式中使用的最大值。

多个视觉观察 Multiple Visual Observations

Camera Observations使用场景中一个或多个camera渲染的纹理。Brain将纹理向量化并将它们送到神经网络中。 你可以同时使用Camera Observations和连续特征向量或离散状态观测。

使用相机图像的agent可以捕获任意复杂的状态,并且在状态难以用数字描述时很有用。可以捕获任意复杂的状态,并且在状态难以用数字描述时很有用。 但是,他们通常效率较低,训练速度较慢,有时甚至无法成功。

要向agent添加Visual Observations,请单击agent inspector中的Add Camera按钮。 然后将要添加的相机拖到相机区域。一个agent可以连接多台摄像机。

Add Camera

另外,请确保agent的Brain期望进行视觉观察。 在Brain Inspector中,在Brain Parameters -> Visual Observations下,指定agent用于视觉观察的摄像机数量。对于每个视觉观察,设置图像的宽度和高度(以像素为单位)以及观察结果是彩色还是灰度(当选中“ Black And White”时)。

离散观察向量空间:查找表 Discrete Vector Observation Space: Table Lookup

当agent只有有限数量的可能状态时,可以使用离散观察向量空间,并且这些状态可以由单个数字枚举。例如,ML-Agents中的Basic示例环境定义了一个具有离散向量观察空间的agent。该agent的状态是两个线性目标之间的整数步骤。在Basic示例中,agent学习到的是,如何移动到提供最大奖励的目标。

更一般地,离散观察向量标识符是可能状态表中的索引。 然而,随着环境变得越来越复杂,查找表格很快变得笨拙。 例如,即使像井字游戏这样的简单游戏也有765种可能的状态(如果不能通过将旋转或反射的方式来减少观察次数,则可能更多)。

要实现离散状态观察,请执行Agent子类的CollectObservations()方法并返回一个List,该list中每一个数字都标识一个状态:

public override void CollectObservations()
{
    AddVectorObs(stateIndex);  // stateIndex is the state identifier
}

动作向量 Vector Actions

动作是agent执行的Brain指令。 当Academy调用agent的AgentAction()函数时,该操作将作为参数传递给agent。 当动作向量空间是连续时,传递给agent的行动参数是一个长度等于矢量动作空间的control signals数组。 当动作向量空间是离散时,动作参数是一个仅包含单个值的数组,即列表或命令表中的索引。 在离散动作向量空间类型中,Vector Action Space Size大小是行动表中元素的数量。 在agent的Brain对象上(使用Unity Editor Inspector窗口)设置Vector Action Space大小和Vector Action Space类型属性。

Brain和训练算法都不知道动作值本身的含义。训练算法简单地尝试动作列表中不同的值,并观察在时间和训练episode上累积奖励的效果。因此,AgentAction()函数中只定义了agent唯一的place actions(这里不太明白)。 在ActionAct()中,只需根据动作向量空间的类型,取出Brain返回的动作值,然后对agent应用收到的值。

例如,如果你将agent设计为以二维方式移动,则可以使用连续或离散向量操作。 在连续的情况下,您可以将动作向量大小设置为2(每个维度一个),并且agent的Brain会创建一个具有两个浮点值的动作。在离散情况下,您可以将动作向量大小设置为4(每个方向一个),Brain将创建一个动作数组,其中包含一个值介于0到4之间的单个元素。

请注意,当你正在编写agent的动作时,使用Player 类型的Barin来测试你的动作逻辑通常很有帮助,它可以让您将键盘命令映射到操作。

3DBall和Area示例环境的设置是连续或离散动作矢量空间。

连续动作空间 Continuous Action Space

当Agent使用设置为连续动作向量空间的Brain时,传递给AgentAction()函数的动作参数是一个长度等于Brain对象的动作矢量空间大小的数组。数组中的各个值具有你赋予它们的任何意义。 例如,如果将数组中的某个元素指定为agent的速度,则训练过程通过此参数学习控制agent的速度。

Reacher示例使用四个控制值定义了一个连续的动作空间。

Reacher

这些控制值作为扭矩施加到构成手臂的身体上:

public override void AgentAction(float[] act)
{
    float torque_x = Mathf.Clamp(act[0], -1, 1) * 100f;
    float torque_z = Mathf.Clamp(act[1], -1, 1) * 100f;
    rbA.AddTorque(new Vector3(torque_x, 0f, torque_z));

    torque_x = Mathf.Clamp(act[2], -1, 1) * 100f;
    torque_z = Mathf.Clamp(act[3], -1, 1) * 100f;
    rbB.AddTorque(new Vector3(torque_x, 0f, torque_z));
}

你应该将连续动作值限制在合理值(通常为[-1,1])以避免在使用PPO算法对代理进行训练时引入不稳定性。 如上所示,您可以在clamp操作之后根据需要调整控制值。

离散动作空间 Discrete Action Space

当agent使用设置为离散动作向量空间的Brain时,传递给AgentAction()函数的动作参数是包含单个元素的数组。 该值是您的表格或操作列表中操作的索引。使用离散向量动作空间时,Vector Action Space Size表示动作表中的动作数量。

Area示例(这里应该是PushBlock示例)为离散矢量动作空间定义了五个动作:跳跃动作和每个基本方向的动作:

// Get the action index
int movement = Mathf.FloorToInt(act[0]); 

// Look up the index in the action list:
if (movement == 1) { directionX = -1; }
if (movement == 2) { directionX = 1; }
if (movement == 3) { directionZ = -1; }
if (movement == 4) { directionZ = 1; }
if (movement == 5 && GetComponent<Rigidbody>().velocity.y <= 0) { directionY = 1; }

// Apply the action results to move the agent
gameObject.GetComponent<Rigidbody>().AddForce(
    new Vector3(
        directionX * 40f, directionY * 300f, directionZ * 40f));`

请注意,上面的代码示例是AreaAgent类(现在已经找不到了,可能文档有点老,参考PushAgentBasic类)的简化提取,它为离散动作空间和连续动作空间的实现提供了参考。

回报/奖励 Rewards

在增强学习中,奖励是agent正确做事的信号。 PPO增强学习算法通过优化agent所做的选择来工作,以便agent随着时间的推移获得最高的累积奖励。你的奖励机制越好,你的agent就会学得越好。

注意:在使用已经训练好的策略进行推理时,不会使用奖励,也不会在模仿学习中使用奖励。

也许最好的建议是从简单开始,只根据需要增加复杂性。一般来说,你应该奖励结果,而不是你认为会导致期望结果的行动。为了帮助开发奖励,你可以使用Monitor类来显示agent收到的累计奖励。你甚至可以使用玩家大脑来控制agent,同时观察它如何积累奖励。

通过调用AgentAction()函数中的AddReward()方法为agent分配奖励。在任何步骤中分配的奖励应该在[-1,1]的范围内。超出此范围的值可能导致不稳定的训练。奖励值在每一步都重置为零。

示例 Examples

你可以查看示例环境中定义的AgentAction()函数,以了解这些项目如何分配奖励。

GridWorld示例中的GridAgent类使用一个非常简单的奖励系统:

Collider[] hitObjects = Physics.OverlapBox(trueAgent.transform.position, 
                                       new Vector3(0.3f, 0.3f, 0.3f));
if (hitObjects.Where(col => col.gameObject.tag == "goal").ToArray().Length == 1)
{
    AddReward(1.0f);
    Done();
}
if (hitObjects.Where(col => col.gameObject.tag == "pit").ToArray().Length == 1)
{
    AddReward(-1f);
    Done();
}

agent在达到目标时会收到正值的奖励,并在掉入坑时收到负值的奖励。否则,它不会得到回报。 这是一个稀疏奖励系统的例子。Agent必须做很多的探索才能找到不经常发生的奖励。

相比之下,Area Area中的AreaAgent每一步都会得到一个小的负回报。 为了获得最大的回报,agent必须尽快完成到达目标区域的任务:

AddReward( -0.005f);
MoveAgent(act);

if (gameObject.transform.position.y < 0.0f || 
    Mathf.Abs(gameObject.transform.position.x - area.transform.position.x) > 8f || 
    Mathf.Abs(gameObject.transform.position.z + 5 - area.transform.position.z) > 8)
{
    Done();
    AddReward(-1f);
}

如果从平台上掉落的话,Agent也会受到更大的负值惩罚。

3DBall中的Ball3DAgent采用类似的方法,但只要agent平衡着小球,就会分配一个小的积极奖励。 Agent可以通过将球保持在平台上来最大化其奖励:

if (IsDone() == false)
{
    SetReward(0.1f);
}

// When ball falls mark agent as done and give a negative penalty
if ((ball.transform.position.y - gameObject.transform.position.y) < -2f ||
    Mathf.Abs(ball.transform.position.x - gameObject.transform.position.x) > 3f ||
    Mathf.Abs(ball.transform.position.z - gameObject.transform.position.z) > 3f)
{
    Done();
    SetReward(-1f);
}

Agent Properties

Agent属性
  • 大脑 Brain - 注册该agent的大脑。使用编辑器添加。

  • 视觉观测 Visual Observations - 将用于生成观测的相机列表。

  • 最大步骤 - 每个agent的最大步数。一旦达到此数字,如果选中了Reset On Done,agent将被重置。

  • 完成时重置 Reset On Done - 当agent达到其最大步数时应否调用代理的AgentReset()函数,或者是否在代码中标记为已完成。

  • 按需决策 - agent是否以固定步长间隔请求决策或通过调用RequestDecision()明确请求决策。

    • 如果未选中,agent将按照 Decision Frequency 的步数设置来请求一个新决策并执行一次动作。在上面的例子中,CollectObservations()每5步调用一次,AgentAction()将在每一步调用。这意味着该agent将重复使用Brain给出的决定。

    • 如果选中,agent控制何时接收决策并采取行动。为此,agent可以使用以下两种方法:

      RequestDecision()表示agent正在请求做出决定。这导致agent收集其观察结果,并要求Brain在模拟的下一步中做出决策。请注意,当agent请求决定时,它也会请求执行动作。这是为了确保所有决策都会在训练期间引发动作。
      RequestAction()表示agent正在请求动作。在这种情况下向agent提供的动作与上次提出请求做出决策时提供的动作相同。

  • 决策频率 Decision Frequency - 决策请求之间的步数。如果按需决策勾选的话,则不使用。

监测Agents Monitoring Agents

我们创建了一个有用的Monitor类,可以使Unity环境中的变量可视化。 虽然这是为了在整个训练过程中监控agent的值函数而建立的,但我们认为它可能更为广泛有用。 你可以在这里了解更多(https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Feature-Monitor.md)。

运行时实例化Agent Instantiating an Agent at Runtime

要在运行时将agent添加到环境中,请使用Unity GameObject.Instantiate函数。 从Prefab实例化agent通常是最容易的(否则,你必须单独实例化组成agent的每个GameObject和Component)。 另外,你必须将Brain实例分配给新Agent,并通过调用其AgentReset()方法来初始化它。 例如,以下函数根据Prefab,Brain实例,位置和方向创建新agent:

private void CreateAgent(GameObject agentPrefab, Brain brain, Vector3 position, Quaternion orientation)
{
    GameObject agentObj = Instantiate(agentPrefab, position, orientation);
    Agent agent = agentObj.GetComponent<Agent>();
    agent.GiveBrain(brain);
    agent.AgentReset();
}

销毁Agent Destroying an Agent

在销毁Agent游戏对象之前,你必须将其标记为已完成(并等待模拟中的下一步),以便Brain知道此agent不再处于活动状态。因此,销毁Agent的最佳位置在Agent.AgentOnDone()函数中:

public override void AgentOnDone()
{
    Destroy(gameObject);
}

请注意,为了调用AgentOnDone(),agent的ResetOnDone属性必须为false。 你可以在agent的Inspector或代码中设置ResetOnDone。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容