ML-Agents(九)Wall Jump

ML-Agents(九)Wall Jump

一、前言

这次我们来看一下Wall Jump示例,这个例子又和我们之前学习的示例不同,它引用了Curriculum Learning(课程学习)的学习方法,简单来讲就是使用授课学习的方式来训练神经网络,学习的样本从易到难,模拟人类学系的过程。先来看看本示例的最终效果:

walljump1

由图中可以看到本示例的效果,小蓝需要越过蓝色的墙体到达绿色的目标地点,此外还可以留意到,蓝色的墙体高度是随机变化的:当蓝色的墙体较高时,小蓝推动大白块当梯子才能越过墙体;当蓝色墙体高度较低时,小蓝则可以直接跳跃过去。

因此,在本示例中,小蓝拥有两个训练好的训练模型——SmallWallJumpBigWallJump,分别对应矮墙(无墙)和高墙情况下的行动。

下面我们先来学习一下官方对于Curriculum Learning的相关文档。

二、课程训练(Curriculum Learning)

这一节内容主要是翻译官方文档 Training with Curriculum Learning

首先文档介绍了课程学习是ML-Agents的一项功能,它允许在训练的过程中更改环境的属性来帮助学习。

一个教学示例

先想象一个任务,agent需要越过一堵墙到达目标位置(其实就是Jump Wall)。一开始训练agent来完成该任务其实是一个随机策略。因此直接训练的话,开始的策略将使agent在循环中运行,并且可能永远,或者很少正确地越过墙体到达目标以获得奖励(意思就是一开始要是训练难度过大,agent可能很难理解自己要达成的目标)。如果我们从一个更简单的任务开始,例如让agent朝着一个无障碍的目标前进,那么agent则会很容易地学会完成任务。在此基础上,我们再通过增加墙体的大小来慢慢增加任务的难度,直到agent可以完成最初几乎不可能完成的任务(就是小蓝通过大白块间接越过高墙)。下图展示了任务由易到难的过程:

image-20200503172457832

具体实现

在一个训练环境中,具有相同Behavior Name的每一组Agent具有相同的课程。这些课程被称为"metacurriculum"(元课程)。元课程允许不同组的agent在同一环境中学习不同的课程。

指定课程

为了定义课程,第一步是确定环境的哪些参数会变化。在Wall Jump示例环境中,墙的高度则是这个变量。我们将墙的高度定义为Academy.Instance.EnvironmentParameters中可以访问的Environment Parameters参数,并通过这样做使得Python API来对其调整。我们将创建一个YAML配置文件来描述课程过程,而不是通过手动来调整课程。通过该配置文件,我们可以指定墙在训练的某个阶段开始改变高度,既可以通过训练总步数的百分比来设置,也可以通过agent获得的平均奖励来设置(Wall Jump中用的是第一种)。下面来看一会下Wall Jump环境课程的示例配置。

BigWallJump:
  measure: progress
  thresholds: [0.1, 0.3, 0.5]
  min_lesson_length: 100
  signal_smoothing: true
  parameters:
    big_wall_min_height: [0.0, 4.0, 6.0, 8.0]
    big_wall_max_height: [4.0, 7.0, 8.0, 8.0]

SmallWallJump:
  measure: progress
  thresholds: [0.1, 0.3, 0.5]
  min_lesson_length: 100
  signal_smoothing: true
  parameters:
    small_wall_height: [1.5, 2.0, 2.5, 4.0]

在配置的顶层是Behavior Name,即对应于agent的行为名称(在Unity中的设置)。每种行为的课程都有以下参数:

  • measure:衡量学习进度和课程进度的方法。

    • reward:使用奖励来衡量。
    • progress:使用steps/max_steps比例来衡量。
  • thresholds(float array):配合measure使用,应当改变课程的阶段。

    简单解释一下以上两个属性,以Wall Jump为例,其measure属性为progress,对应thresholds为[0.1,0.3,0.5],其含义是:

    一开始训练时,墙的高度变化范围是0-4(参考下面parameters参数);当steps/max_steps=0.1(当前训练步数/总训练步数=0.1)时,改变一次墙的高度范围(对应下面参数为4.0-7.0);当steps/max_steps=0.3时,在改变一次墙的高度范围(6.0-8.0);当steps/max_steps=0.5时,墙的高度固定为8.0。

  • min_lesson_length(int):在课程改变之前,应该完成的episodes最小数量。如果measure设置为reward,则将使用最后min_lesson_length episodes的平均奖励来确定课程是否应该改变。必须是非负数。

    重要:与thresholds比较的平均奖励不同于控制台(Console)中记录的平均奖励。例如,如果min_lesson_length为100,那么在最近100的episodes的平均累积奖励超过当前thresholds设置的值后,课程将改变。记录到控制台的平均奖励是由配置文件中summary_freq参数决定的。

  • signal_smoothing(true/false):是否通过以前的值来衡量当前的进度。

    • 如果设置为true,则权重将由老的0.25变为新的0.75。
  • parameters(dictionary: key(string),value(float array)):对应于要控制的环境参数。每个数组的长度应该大于thresholds的数目。具体的意思在上面也有解释。

一旦我们定义好课程配置,我们就必须使用定义的环境参数,并通过agent的OnEpisodeBegin()函数来修改环境。具体我们在后面的章节介绍Wall Jump的Agent脚本时再来看。

开始训练

至此,我们指定好了我们的课程配置文件,然后通过ml-agents命令台中使用-curriculum命令字来指定我们的配置文件,PPO将使用课程学习进行训练。例如下面要通过课程学习训练Wall Jump,在控制台中可以输入:

mlagents-learn config/trainer_config.yaml --curriculum=config/curricula/wall_jump.yaml --run-id=wall-jump-curriculum

主要是留意--curriculum命令字的使用。

Note:如果要恢复使用课程的训练,在mlagents-learn时使用--lesson标志来输入最后课程的编号。

至此,我们大概了解了在ML-Agents中课程训练的配置文件,下面我们开始正式学习Wall Jump示例。

三、环境与训练参数

  • 设定:一个平台环境中,agent可以跳过一堵墙。
  • 目标:Agent必须使用方块越过墙体到达目的地。
  • Agents:环境中包含一个链接到两个不同模型的agent。Agent的策略链接改变取决于墙的高度(即墙低的时候<4时,采用SmallWallJump训练,墙高度>4时,采用BigWallJump训练)。策略的改变在WallJumpAgent脚本中实现,下面看代码会有介绍。
  • Agent奖励设定:
    • 每一步-0.0005。
    • 如果agent到达目的地,则+1.0。
    • 如果agent从平台上掉落,则-1.0。
  • 行为参数:
    • 矢量观测空间:74个变量,对应于14条射线(ray casts),每条射线检测四个物体。再加agent的世界坐标以及agent是否已接地。
    • 矢量动作空间:离散(Discrete),4个分支,分别是
      • 前后移动:前移、后移、No Action
      • 旋转:左旋转、右旋转、No Action
      • 左右移动:左平移、右平移、No Action
      • 跳跃:跳跃、NoAction
    • 视觉观察值:无
  • 可变参数:4个
  • 基准平均奖励(Big && Small Wall):0.8

四、场景基本构成

场景中包含24个训练单元,训练单元之间都相隔较远距离,如下图:

image-20200504133929364

  • PlayerCam

    PlayerCam是我们一开始游戏的相机,对应于第一个训练单元:

    image-20200504134432016

  • OverviewCam

    OverviewCam是一个鸟瞰相机,注意它是在Display 2中,具体效果如下:

    image-20200504134312573

    这个相机目前没发现什么特殊的用途,感觉应该是想把所有的训练单元都纳入,于是我自己调整了一下,就变成如下效果:

    image-20200504135106383

    这个视图还是有一点好处,就是之后我们在训练的时候,因为这个示例也和上一次讲的PushBlock示例一样,当小蓝完成任务地面就变绿闪一下;当小蓝任务失败或者掉落平台,地面就变红闪一下。用这种鸟瞰视图就很容易看出来你训练的效果怎么样,例如一开始应该是红色闪的多,越到后面就是绿色闪的多,由此来看训练效果。

  • WallJumpSettings

    WallJumpSettings物体依旧是设置了一些全局变量,主要有小蓝的速度、小蓝跳跃的高度等。

  • WallJumpArea

    WallJumpArea是一个基本的训练单元,主要有以下物体:

    image-20200504140003916

    这里面的构成和PushBlock里的很相似,没什么太难的点。主要说一下SpawnVolume,这个对象在运行的时候就令它SetActive(false)了。那为什么还需要这个对象呢?其实是在Agent的脚本里,利用了该对象的Bounds,即控制大白块的位置是随机产生在该区域里的。

    其他的物体没什么好说的,下面我们直接进入代码环节。

五、代码分析

其余代码都比较简单,我们主要来研究一下WallJumpAgent.cs脚本。

Agent初始化

using System.Collections;
using UnityEngine;
using MLAgents;
using Barracuda;
using MLAgents.Sensors;

public class WallJumpAgent : Agent
{
    //该值范围为[0,5],控制墙体高度以及设置不同的Brain
    int m_Configuration;
    //当墙体高度为0时,采用此Brain
    public NNModel noWallBrain;
    //当墙体高度为1时,采用此Brain(实际上与NoWallBrain是一样的)
    public NNModel smallWallBrain;
    //当墙体高度大于1时,采用此Brain
    public NNModel bigWallBrain;

    public GameObject ground;//地面,变换地面材质用
    public GameObject spawnArea;//大白块随机生成区域
    Bounds m_SpawnAreaBounds;//区域的Bounds

    public GameObject goal;//目标区域
    public GameObject shortBlock;//大白块
    public GameObject wall;//墙体
    Rigidbody m_ShortBlockRb;//大白块的刚体
    Rigidbody m_AgentRb;//小蓝的刚体
    Material m_GroundMaterial;
    Renderer m_GroundRenderer;
    WallJumpSettings m_WallJumpSettings;//小蓝的速度、跳跃高度等设置

    public float jumpingTime;//跳起空中时间
    public float fallingForce;//小蓝在空中时下降时所受向下的力
    //判断小蓝是否在落在地上、墙上或大白块上
    public Collider[] hitGroundColliders = new Collider[3];
    Vector3 m_JumpTargetPos;//跳跃目标位置
    Vector3 m_JumpStartingPos;//起跳位置

    /// <summary>
    /// 初始化Agent
    /// </summary>
    public override void InitializeAgent()
    {
        m_WallJumpSettings = FindObjectOfType<WallJumpSettings>();//获取全局设定
        m_Configuration = Random.Range(0, 5);//随机产生墙面高度

        m_AgentRb = GetComponent<Rigidbody>();//获得小蓝的刚体
        m_ShortBlockRb = shortBlock.GetComponent<Rigidbody>();//获得大白的刚体
        m_SpawnAreaBounds = spawnArea.GetComponent<Collider>().bounds;//获得大白随机产生的区域范围
        m_GroundRenderer = ground.GetComponent<Renderer>();
        m_GroundMaterial = m_GroundRenderer.material;//获得地面的材质,以备后面改变地面材质

        spawnArea.SetActive(false);
    }
}

初始化内容都比较简单,注意第一个变量m_Configuration,该变量只标识了墙的高度应该为多少,但并不是指定墙的高度是几,例如m_Configuration=1时,实际墙的高度会是4而不是1,该值的使用一会儿在ConfigureAgent(int config)方法中讲解。下面我们来看Agent收集的环境观测值。

环境观测值收集

在第三章环境训练参数中,我们知道了本示例小蓝除了采用了14条射线来收集射线监测数据外,还需要采集自己的世界坐标以及是否接触地面的信息,这两种信息在CollectObservations(Vector sensor)方法中进行收集。

	/// <summary>
    /// 收集环境中其他数据
    /// </summary>
    /// <param name="sensor"></param>
    public override void CollectObservations(VectorSensor sensor)
    {
        var agentPos = m_AgentRb.position - ground.transform.position;
        //小蓝相对于地面中心的位置,除以20是为了让其位置x、y、z值归一化
        sensor.AddObservation(agentPos / 20f);
        //判断小蓝是否落地
        sensor.AddObservation(DoGroundCheck(true) ? 1 : 0);
    }
    /// <summary>
    /// 检测是否落地
    /// </summary>
    /// <param name="smallCheck">墙的高度是否是低(<=4)</param>
    /// <returns>true为落地,否则为false</returns>
    public bool DoGroundCheck(bool smallCheck)
    {
        if (!smallCheck)
        {//4<墙高度<=8
            hitGroundColliders = new Collider[3];
            var o = gameObject;
            //无GC的相交盒检测,可采集与相交盒碰撞的碰撞体Collider[]
            //此处赋值给hitGroundColliders
            Physics.OverlapBoxNonAlloc(
                o.transform.position + new Vector3(0, -0.05f, 0),
                new Vector3(0.95f / 2f, 0.5f, 0.95f / 2f),
                hitGroundColliders,
                o.transform.rotation);
            var grounded = false;
            foreach (var col in hitGroundColliders)
            {//遍历与碰撞盒产生碰撞的物体
                if (col != null && col.transform != transform &&
                    (col.CompareTag("walkableSurface") ||
                     col.CompareTag("block") ||
                     col.CompareTag("wall")))
                {
                    //若碰撞的物体为地面、大白块或墙体,则判断小蓝已落地
                    grounded = true; //then we're grounded
                    break;
                }
            }
            return grounded;
        }
        else
        {//0<=墙高度<=4
            RaycastHit hit;
            //若墙的高度较低,则只需要向下发出长度为1的射线来检测小蓝是否落地
            Physics.Raycast(transform.position + new Vector3(0, -0.05f, 0), -Vector3.up, out hit,
                1f);
            if (hit.collider != null &&
                (hit.collider.CompareTag("walkableSurface") ||
                 hit.collider.CompareTag("block") ||
                 hit.collider.CompareTag("wall"))
                && hit.normal.y > 0.95f)
            {
                return true;
            }
            return false;
        }
    }
    

此处代码中,需要注意DoGroundCheck(bool smallCheck)这个方法,该方法除了在收集观测值时使用,还在其他3处地方分别使用。

首先该方法是为了检测小蓝是否落地,是则返回true,否则返回false。然后其分别处理了墙面高和低的两种情况:若墙面较高,则采用无GC的相交盒来检测小蓝的碰撞状态;若墙面较低,则直接向下发射射线来检测小蓝是否落地。

Physics.OverlapBoxNonAlloc()则是相交盒检测方法,在上一篇“ML-Agents(八)PushBlock”中介绍了Physics.CheckBox(),该方法与CheckBox()不同的是,CheckBox只会返回bool来判断是否产生碰撞,而OverlapBox()不仅可以返回bool来判断是否产生碰撞,而且可以将产生碰撞的Colliders获取到。

如下图,我将小蓝材质改透明,其中白色的方块则是代码中Physics.OverlapBoxNonAlloc()方法产生的相交盒。Physics.OverlapXXXNonAlloc()对应无GC的方式,这里XXX也可以是Sphere和Capsule。

image-20200504173503491

Agent动作反馈

	/// <summary>
    /// Agent动作
    /// </summary>
    /// <param name="vectorAction"></param>
    public override void AgentAction(float[] vectorAction)
    {
        MoveAgent(vectorAction);//小蓝移动
        if ((!Physics.Raycast(m_AgentRb.position, Vector3.down, 20))
            || (!Physics.Raycast(m_ShortBlockRb.position, Vector3.down, 20)))
        {//若小蓝落下平台或大白块落下平台
            SetReward(-1f);//惩罚1
            Done();//本次训练结束,并重置agent
            ResetBlock(m_ShortBlockRb);//重置大白块位置、速度等
            //设置地面颜色为红色
            StartCoroutine(
                GoalScoredSwapGroundMaterial(m_WallJumpSettings.failMaterial, .5f));
        }
    }
	/// <summary>
    /// Agent移动
    /// </summary>
    /// <param name="act"></param>
    public void MoveAgent(float[] act)
    {
        AddReward(-0.0005f);//每一步-0.0005
        //判断小蓝是否在地面上,若在地面上则移动速度相应要快一些,在空中的话移动速度要减半
        var smallGrounded = DoGroundCheck(true);//墙低情况
        var largeGrounded = DoGroundCheck(false);//墙高情况

        var dirToGo = Vector3.zero;
        var rotateDir = Vector3.zero;
        var dirToGoForwardAction = (int)act[0];//前后移动
        var rotateDirAction = (int)act[1];//左右旋转
        var dirToGoSideAction = (int)act[2];//左右移动
        var jumpAction = (int)act[3];//跳跃

        //前后移动
        if (dirToGoForwardAction == 1)
            dirToGo = (largeGrounded ? 1f : 0.5f) * 1f * transform.forward;
        else if (dirToGoForwardAction == 2)
            dirToGo = (largeGrounded ? 1f : 0.5f) * -1f * transform.forward;
        //左右旋转
        if (rotateDirAction == 1)
            rotateDir = transform.up * -1f;
        else if (rotateDirAction == 2)
            rotateDir = transform.up * 1f;
        //左右平移
        if (dirToGoSideAction == 1)
            dirToGo = (largeGrounded ? 1f : 0.5f) * -0.6f * transform.right;
        else if (dirToGoSideAction == 2)
            dirToGo = (largeGrounded ? 1f : 0.5f) * 0.6f * transform.right;
        //跳跃
        if (jumpAction == 1)
            if ((jumpingTime <= 0f) && smallGrounded)
            {//判断小蓝是否在地上且jumpingTime<=0,初始化起跳变量
                Jump();
            }
        transform.Rotate(rotateDir, Time.fixedDeltaTime * 300f);//旋转
        m_AgentRb.AddForce(dirToGo * m_WallJumpSettings.agentRunSpeed,
            ForceMode.VelocityChange);//前后左右移动
        
        if (jumpingTime > 0f)
        {//起跳条件满足
            m_JumpTargetPos =
                new Vector3(m_AgentRb.position.x,
                    m_JumpStartingPos.y + m_WallJumpSettings.agentJumpHeight,
                    m_AgentRb.position.z) + dirToGo;//计算跳跃后控制位置
            //使得小蓝跳到计算后的位置m_JumpTargetPos,并限制其速度
            MoveTowards(m_JumpTargetPos, m_AgentRb, m_WallJumpSettings.agentJumpVelocity,
                m_WallJumpSettings.agentJumpVelocityMaxChange);
        }

        if (!(jumpingTime > 0f) && !largeGrounded)
        {//判断小蓝处于空中,给小蓝施加向下的力使其下落
            m_AgentRb.AddForce(
                Vector3.down * fallingForce, ForceMode.Acceleration);
        }
        jumpingTime -= Time.fixedDeltaTime;
    }
	/// <summary>
    /// 重置大白块
    /// </summary>
    /// <param name="blockRb"></param>
    void ResetBlock(Rigidbody blockRb)
    {
        //重置大白块的位置
        blockRb.transform.position = GetRandomSpawnPos();
        blockRb.velocity = Vector3.zero;//速度置零
        blockRb.angularVelocity = Vector3.zero;//角速度置零
    }
 	/// <summary>
    /// 改变地面材质颜色
    /// </summary>
    /// <returns></returns>
    /// <param name="mat">要换的材质</param>
    /// <param name="time">变换材质后变回原先材质的延时时间</param>
    IEnumerator GoalScoredSwapGroundMaterial(Material mat, float time)
    {
        m_GroundRenderer.material = mat;
        yield return new WaitForSeconds(time); //等待2秒
        m_GroundRenderer.material = m_GroundMaterial;
    }

这里的代码虽长,但是都比较简单,属于一看就懂系列,但是有一个点可以注意一下,即此处小蓝起跳以及下落的代码处理过程,感觉和我见过处理跳跃的方式有一些不同。

Agent重置

	/// <summary>
    /// Agent重置
    /// </summary>
    public override void AgentReset()
    {
        ResetBlock(m_ShortBlockRb);//重置大白块
        //重置小蓝位置
        transform.localPosition = new Vector3(
            18 * (Random.value - 0.5f), 1, -12);
        m_Configuration = Random.Range(0, 5);//重置墙体高度以及选用的Brain
        m_AgentRb.velocity = default(Vector3);//小蓝速度置零
    }
	/// <summary>
    /// 检测小蓝是否到达目标区域
    /// </summary>
    /// <param name="col"></param>
    void OnTriggerStay(Collider col)
    {
        if (col.gameObject.CompareTag("goal") && DoGroundCheck(true))
        {//若小蓝到目标区域,且在地面上
            SetReward(1f);//奖励1
            Done();//结束此次训练
            //使地面置为绿色
            StartCoroutine(
                GoalScoredSwapGroundMaterial(m_WallJumpSettings.goalScoredMaterial, 2));
        }
    }

重置Agent的代码,一部分实际上是在Unity的方法OnTriggerStay(Collider col)中实现的,因为目标区域其实也是有碰撞体的,因此若小蓝Stay在目标区域,则会触发此函数。

到此为止,我们还没有看到此示例是如何使用两个Brain来回切换使用的,下面我们就来看一下这一部分代码是如何实现的。

其他

void FixedUpdate()
    {
        if (m_Configuration != -1)
        {
            //设置agent的Brain
            ConfigureAgent(m_Configuration);
            //标志位置位
            m_Configuration = -1;
        }
    }

    /// <summary>
    /// 设置Agent的Brain,墙的高低来决定不同的Brain
    /// </summary>
    /// <param name="config">
    /// 如果为0:No wall + noWallBrain
    /// 如果为1:Samll Wall + samllWallBrain
    /// 其他:Tall wall + bigWallBrain
    /// </param>
    void ConfigureAgent(int config)
    {
        var localScale = wall.transform.localScale;//墙的比例大小
        if (config == 0)
        {//如果m_Configuration==0,墙高度为0
            localScale = new Vector3(
                localScale.x,
                Academy.Instance.FloatProperties.GetPropertyWithDefault("no_wall_height", 0),
                localScale.z);
            wall.transform.localScale = localScale;
            //设置agent的Model
            GiveModel("SmallWallJump", noWallBrain);
        }
        else if (config == 1)
        {//如果m_Configuration==1
            localScale = new Vector3(
                localScale.x,
                Academy.Instance.FloatProperties.GetPropertyWithDefault("small_wall_height", 4),
                localScale.z);
            wall.transform.localScale = localScale;
            GiveModel("SmallWallJump", smallWallBrain);
        }
        else
        {//如果m_Configuration>1
            //若开始训练时,此处的min和max值取决于课程配置值
            var min = Academy.Instance.FloatProperties.GetPropertyWithDefault("big_wall_min_height", 8);
            var max = Academy.Instance.FloatProperties.GetPropertyWithDefault("big_wall_max_height", 8);
            var height = min + Random.value * (max - min);
            localScale = new Vector3(
                localScale.x,
                height,
                localScale.z);
            wall.transform.localScale = localScale;
            GiveModel("BigWallJump", bigWallBrain);
        }
    }

由以上代码可以看出,其实现是在FixedUpdate()中,实时判断m_Configuration的值来改变agent不同的Brain,m_Configuration会在训练一开始以及AgentReset的时候随机重置。

此外,之前已经讲过,Academy.Instance.FloatProperties.GetPropertyWithDefault(string key, float defaultValue)这个方法的第二个参数defaultValue是默认值,若key没读取到,则采用输入的默认值。因此在该示例运行的时候,你会发现墙的高度只有0、4、8这是三个值,但是如果开始训练后,该方法中的key就会与之前课程配置文件中的值开始对应。

image-20200504183649022

即在SmallWallJump的时候,墙的高度从1.5->2.0->2.5->4.0;在BigWallJump时,墙的高度则一开始会在0-4随机,然后在4-7随机,然后在6-8随机,最后高度固定在8。从此也可以看出课程训练从易到难的特点。

六、训练

训练配置参数

Wall Jump的课程训练配置已经在上面第二章讲解过了,下面我们来看一下Wall Jump的训练配置:

trainer_config.yaml

SmallWallJump:
    max_steps: 5e6
    batch_size: 128
    buffer_size: 2048
    beta: 5.0e-3
    hidden_units: 256
    summary_freq: 20000
    time_horizon: 128
    num_layers: 2
    normalize: false

BigWallJump:
    max_steps: 2e7
    batch_size: 128
    buffer_size: 2048
    beta: 5.0e-3
    hidden_units: 256
    summary_freq: 20000
    time_horizon: 128
    num_layers: 2
    normalize: false

首先因为要训练两个Model,分别对应SmallWallJump和BigWallJump,因此在配置文件中对应的是两个部分。可以看出Small的max_steps比Big的更小,也好理解,简单的任务训练快,难的任务需要训练步数应该较多。

其他的属性在前几篇都已经分析过,所以不再赘述,这里有一个normalize属性,其作用是是否对输入的矢量观测值(vector observation)进行规范化处理。在之前的内容我们知道,规范化对于复杂的连续控制(continuous control)问题很有用,但对于较简单的离散控制(discrete control)可能反而有害。而且我们在代码中已经对小蓝位置做过规范化处理,因此这里使其变为false。

开始训练

cd到ml-agents目录,并输入一下命令:

mlagents-learn config/trainer_config.yaml --curriculum=config/curricula/wall_jump.yaml --run-id=walljump

开始训练,一开始会发现失败的情况更多,如下图:

walljump2

上面红色闪烁则为失败的单元,同时我们观察Console会发现,SmallWallJumpBigWallJump会随机穿插进行训练,如下图:

image-20200505005313183

训练了一晚上,发现没训练成功。。。。。摔!

image-20200505095837170

由图可以看到已经训练9个小时了,但平均收益还是-1.1左右,在Unity中也是失败的多,没有什么改观。

其实这期间我又做了许多次尝试,包括修改源码之类的,但是都没有训练成功。于是我最终从git上又拉取了最新版的ml-agents(release_1),并重新新建了一个Anaconda训练环境(具体可参考Unity ML-Agents v0.15.0(一)环境部署与试运行),然后开始训练,终于得到了比较好的训练效果:

walljump3

这才是训练了40万步左右的结果,相比之前的训练效果好了不止N倍。其实我现在还没搞清楚为什么一开始的版本(0.15.0)训练失败。。。

除此之外,最新版release_1里的文档少了很多,包括课程训练的简介都没有了,大家要是想看英文原版,还是要选0.15.1之前版本的doc才能看到。

OK,这次训练结果就没有问题了,相应的Tensorboard如下:

image-20200506205703012

图中橘黄色线的是SmallWallJump的训练数据,蓝色线是BigWallJump的训练数据,在Lesson图表有明显的阶梯状,代表各个课程的开始。

我们再以蓝色线BigWallJump训练数据为例,在Cumulative Reward图标里,可以看出每次课程难度的增加,会使得改变时的累计奖励骤减,但是慢慢会上升,最终的基准平均奖励也和官方的数据一致,大概在0.8左右。

我们把训练好的模型放到Unity中试验一下:

image-20200506210914880

方便起见直接用一个来测试即可。

walljump4

OK,发现训练的模型也没有问题。

七、总结

这个示例我们主要学习了如何使用Curriculum Learning进行训练,其中关于射线传感器的数据采集内容在ML-Agents(八)PushBlock已有讲述,不熟悉的亲可以回看。本篇文章就此结束,欢迎大家留言交流~

写文不易~因此做以下申明:

1.博客中标注原创的文章,版权归原作者 煦阳(本博博主) 所有;

2.未经原作者允许不得转载本文内容,否则将视为侵权;

3.转载或者引用本文内容请注明来源及原作者;

4.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。

posted @ 2020-05-06 21:19  煦阳  阅读(1606)  评论(2编辑  收藏  举报