主頁 >  其他 > ML-Agents(六)Tennis

ML-Agents(六)Tennis

2020-09-10 12:33:24 其他

目錄

  • ML-Agents(六)Tennis
    • 一、Tennis介紹
    • 二、環境與訓練引數
    • 三、場景基本結構
    • 四、代碼分析
      • 環境初始化腳本
      • Agent腳本
        • Agent初始化與重置
        • 矢量觀測空間
        • Agent動作反饋
        • Agent手動操控
    • 五、訓練
      • 普通訓練(不帶可變引數)
      • 可變引數設定
      • 一個可變引數訓練
      • 兩個可變引數訓練
    • 總結

ML-Agents(六)Tennis

喜歡的童靴希望大家多多點贊收藏哦~

這次Tennis示例研究費了我不少勁,倒不是因為示例的難度有多大,而重點是這個示例的訓練程序中遇到了許多問題值得記錄下來,其次這個訓練是一個對抗訓練,也是比較有意思的示例,

一、Tennis介紹

首先來看看效果~

teenis

OK,可以看到畫面中有18個網球場,然后藍色的球拍和紫色的球拍互相對打,這里注意一下,場景雖然都是3D的,但實際上球拍和球只在球場的中軸線上上下左右移動,也就是說其實換個相機位置的話,這里其實是個二維打球模擬,

teenis2

當然了,這樣算是簡化了訓練的程序,這個示例大部分所用到的內容和3D Ball差不多,主要有一個可以深化學習的就是對抗訓練,下面我們來先看一下官方對該示例的引數,

二、環境與訓練引數

老規矩,先來看一下官方檔案引數:

  • 設定:兩個agents控制球拍進行雙人游戲,來回擊打球過球網

  • 目標:一方agent必須打擊球,以使對手無法擊回球

  • Agent:在這個環境中,包含兩個擁有相同行為引數(Behavior Parameters)的Agent,官方還建議,當你訓練好你的球拍Agent后,可以把其中一個球拍調整為Heuristic Only手動操作,嘗試一下被電腦虐的快感(當然官方不是這么說的,但是確實有點困難= =),這里要設定成手動模式,還需要修改一下代碼,后面會提到

  • Agent獎勵設定:

    • 如果agent贏下一球,則+1,當然由于是對抗訓練,所以一方agent要通過防止另一方agent贏球來贏取獎勵
    • 如果agent輸掉一球,則-1
  • 行為引數:

    • 矢量觀測空間:一共9個變數,分別對應球和球拍的位置、速度以及方向
    • 矢量動作空間:Continuous型別,一共3個變數,對應球拍向網、遠離網的運動,跳躍和旋轉,這里通俗點講就是球拍的x、y方向運動,以及球拍繞自身z軸的旋轉
    • 視覺觀測值:None
  • 可變引數:3個

    • 重力加速度:
      • Default:9.81
      • 推薦最小值:6
      • 推薦最大值:20
    • 網球比例:球三個維度上的比例(即x、y、z比例相同)
      • Default:5
      • 推薦最小值:0.2
      • 推薦最大值:5
    • 球拍初始角度:
      • Default:55(原始碼里這么寫了)

    最后一個“球拍初始角度”官方漏寫了,我說為啥明明寫了3個引數,卻只有兩個引數寫出來,但是!!!凡事有個但是,實際上通過原始碼會發現,最后這個引數無論怎么改,并不會改變訓練時的效果,因為訓練時會讓Agent自動調整球拍的旋轉角度,其實這個引數的唯一作用就是你在手動操作和你訓練出來的agent找虐時,會使球拍固定在一個角度來打球,當然,如果你還有余力可以調整球拍角度,那你很sei li~對于agent沒啥大影響,對于手動來說,那就是天差地別了,

三、場景基本結構

場景中包含18個球場,如下圖:

image-20200407230705102

相應的Hierarchy層級:

image-20200407230725819

場景中Camera、Canvas啥的都不用太多去介紹,TennisSettings可以設定環境一些高級物理設定,例如Unity中的Physics.gravityTime.fixedDeltaTimePhysics.defaultSolverIterations等,

TennisArea是作為一個訓練的基本單位,其中:

  • TennisArea

    父物體上本來就帶有一個TennisArea.cs腳本,這個腳本主要是重置比賽環境,例如每次開始球的位置、比例等,但是我認為在這里初始化球的比例有問題,這個我們后面說,

  • Ball

    球,帶有剛體,存在HitWall.cs腳本,你會發現有意思的是,原來我們看的示例agent的獎懲基本都在AgentAction()函式中,即隨時判斷agent是否獎懲,但是這里將agent獎勵或失敗設定放到了球上,由球來決定到底是agentA贏還是agentB贏,然后重置agent,當然這也比較好理解,因為規則都在球上,球當然知道到底誰贏了(感覺有點邪惡,,,,2333),

image-20200407231415130

  • Invisible Walls

    如英文注釋,就是透明碰撞體,場地兩邊地面各一個,場后各一個,就是為了防止球掉落,當然我想吐槽一下這個碰撞體,設定的太粗曠了= =,兩邊的墻巨長無比,

image-20200407231943303

  • Scenery

    球場四周的碰撞體,網的碰撞體以及網上的碰撞體,

image-20200407233616918

  • AgentAAgentB

    兩個agent,分別代表兩對抗方,公用一套訓練引數進行訓練,

image-20200407234006882

這里兩個agent大家注意,看一下有啥不一樣,首先看Behavior Parameters組件:

image-20200408205445361

image-20200408205513237

可以看到兩個球拍的行為引數,Team Id是不同的,表示了兩個不同的陣營,猜測如果有多方陣營也可以訓練,還有這里應該是使用Behavior Name來保證訓練的Brain一致,在前幾面的文章中也有提及,

再來看一下兩個球拍的Tennis Agent

image-20200408205707097

image-20200408205717457

會發現一個Invert X勾選,另一個未勾選,這里先賣個關子,一會兒講為什么這么設定,

下面直接開始分析代碼,走起,

四、代碼分析

環境初始化腳本

這個示例中環境初始化分為兩個部分,一個部分是處于父節點的TennisArea腳本,另一個是球上的HitWall腳本,前者主要在環境重置時初始化球的位置,以及限制球的速度;后者則是包含了打球規則以及初始化球拍、呼叫TennisArea來初始化比賽,

先來看一下TennisArea.cs腳本,

using UnityEngine;

public class TennisArea : MonoBehaviour
{
    public GameObject ball;//球
    public GameObject agentA;//球拍agentA
    public GameObject agentB;//球拍agentB
    Rigidbody m_BallRb;//球的剛體

    void Start()
    {
        m_BallRb = ball.GetComponent<Rigidbody>();
        MatchReset();//一開始重置比賽,注意這里運行的順序后于Agent的InitializeAgent()
    }
    /// <summary>
    /// 重置比賽
    /// </summary>
    public void MatchReset()
    {
        var ballOut = Random.Range(6f, 8f);//球隨機x值
        var flip = Random.Range(0, 2);//隨機球出現在左邊還是右邊
        if (flip == 0)
        {//令球在場地左邊隨機出現
            ball.transform.position = new Vector3(-ballOut, 6f, 0f) + transform.position;
        }
        else
        {//令球在場地右邊隨機出現
            ball.transform.position = new Vector3(ballOut, 6f, 0f) + transform.position;
        }
        m_BallRb.velocity = new Vector3(0f, 0f, 0f);//使球的速度變為0,然后令其自由落體
        //注意:這里修改小球的比例,我認為在這里修改球的比例是不合適的
        //ball.transform.localScale = new Vector3(.5f, .5f, .5f);
        ball.GetComponent<HitWall>().lastAgentHit = -1;//重置HitWall中判斷勝利引數
    }

    void FixedUpdate()
    {
        //這里主要是控制球的速度不要太快
        var rgV = m_BallRb.velocity;
        m_BallRb.velocity = new Vector3(Mathf.Clamp(rgV.x, -9f, 9f), Mathf.Clamp(rgV.y, -9f, 9f), rgV.z);
        //Debug.Log(m_BallRb.velocity);
    }
}

這個腳本有幾個點來說一下:

  • 首先,一開始運行時,以上的Start()函式是晚于球拍Agent的初始化方法InitializeAgent()的,即MatchReset()方法是在InitializeAgent()之后的,在原始碼里,TennisArea腳本第32行設定了球的比例,而注意球的比例是可變引數(現在官方改成Parameter Randomization了,即隨機化引數,原來是Generalized Reinforcement,即可變強化訓練,叫法改了用法一樣,就是可變引數),因此,不管在Agent的InitializeAgent()里如何設定球的比例,在后執行的MatchReset()方法里都會將球的比例重新設定回(0.5f,0.5f,0.5f),

    以上還只是列舉了訓練一開始情況,蛋疼的是在訓練程序中,也會出現MatchReset()在Agent的SetBall()之后的情況,也就是如果你在訓練引入了可變引數球的size,在訓練程序中應該是要一定步驟后改變球的比例來訓練,但是如果在MatchReset()中設定球的比例,會使得球的比例一直被固定為0.5,因此應該是要去掉這一行的,

    當然,以上推測只是我初看代碼理解的,后面我們可以分別注釋這一句和加上這一句來進行可變size引數訓練,觀察tensorboard的曲線就立馬能看出來了,

  • 第二點是FixedUpdate()函式,這里將網球的x方向速度和y方向速度限制到了-9到9,當然我們可以把這里注釋掉,看看是什么效果,

    tennis3

    這里會發現球的速度過快,下面的Debug也會發現速度很容易就在十幾、二十幾,球太容易飛出場外,

OK,TennisArea腳本應該沒什么問題了,我們來分析一下網球上的HitWall腳本,

HitWall.cs

using UnityEngine;

public class HitWall : MonoBehaviour
{
    public GameObject areaObject;//父節點
    public int lastAgentHit;//最后一次哪個agent擊球,0代表agentA擊球,1代表agentB擊球
    public bool net;//判斷是否過網

    public enum FloorHit
    {
        Service,//從空中發球
        FloorHitUnset,//成功回擊球之后,使用該標志位
        FloorAHit,//在A地面彈起
        FloorBHit//在B地面彈起
    }

    public FloorHit lastFloorHit;//最后一次地板擊中狀態

    TennisArea m_Area;
    TennisAgent m_AgentA;//代理A
    TennisAgent m_AgentB;//代理B

    void Start()
    {
        m_Area = areaObject.GetComponent<TennisArea>();
        m_AgentA = m_Area.agentA.GetComponent<TennisAgent>();
        m_AgentB = m_Area.agentB.GetComponent<TennisAgent>();
    }

    /// <summary>
    /// 比賽重置,包括agentA、agentB、網球置位等
    /// </summary>
    void Reset()
    {
        m_AgentA.Done();
        m_AgentB.Done();
        m_Area.MatchReset();
        lastFloorHit = FloorHit.Service;
        net = false;
    }
    /// <summary>
    /// agentA贏
    /// </summary>
    void AgentAWins()
    {
        m_AgentA.SetReward(1);
        m_AgentB.SetReward(-1);
        m_AgentA.score += 1;
        Reset();

    }
    /// <summary>
    /// agentB贏
    /// </summary>
    void AgentBWins()
    {
        m_AgentA.SetReward(-1);
        m_AgentB.SetReward(1);
        m_AgentB.score += 1;
        Reset();

    }
    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("iWall"))
        {//如果球碰到墻(Tag=="iWall"),主要是"InvisibleWalls"和"Scenery"物體下的透明碰撞體
            if (collision.gameObject.name == "wallA")
            {//如果球碰到A這邊的墻
                if (lastAgentHit == 0 || lastFloorHit == FloorHit.FloorAHit)
                {//A自己擊球碰到A墻出界||球經過A這邊的地面彈起碰到A墻出界(A沒有接到球),則B贏
                    AgentBWins();
                }
                else
                {//B擊球的情況下:若在發球時,B第一球未碰到A地面直接出界||回球時球碰到了B自己邊的地面||回球時,球出A邊界,則A贏
                    AgentAWins();
                }
            }
            else if (collision.gameObject.name == "wallB")
            {//同上
                if (lastAgentHit == 1 || lastFloorHit == FloorHit.FloorBHit)
                {
                    AgentAWins();
                }
                else
                {
                    AgentBWins();
                }
            }
            else if (collision.gameObject.name == "floorA")
            {//如果球碰到A這邊的地面
                if (lastAgentHit == 0 || lastFloorHit == FloorHit.FloorAHit || lastFloorHit == FloorHit.Service)
                {//A擊球碰到自己的地面||最后一次也是在A地面彈起,即球在A地面彈了兩次||最后一次是B從空中發的球,A未接到球,則B贏
                    AgentBWins();
                }
                else
                {//以上情況都不是,則A接到球并成功回擊
                    lastFloorHit = FloorHit.FloorAHit;
                    if (!net)
                    {//A成功接球并回擊
                        net = true;
                    }
                }
            }
            else if (collision.gameObject.name == "floorB")
            {//同上
                if (lastAgentHit == 1 || lastFloorHit == FloorHit.FloorBHit || lastFloorHit == FloorHit.Service)
                {
                    AgentAWins();
                }
                else
                {
                    lastFloorHit = FloorHit.FloorBHit;
                    if (!net)
                    {
                        net = true;
                    }
                }
            }
            else if (collision.gameObject.name == "net" && !net)
            {//如果球碰到網,且未判定過網(即net=false)
                if (lastAgentHit == 0)
                {//如果上次擊球為A,則B贏
                    AgentBWins();
                }
                else if (lastAgentHit == 1)
                {//如果上次擊球為B,則A贏
                    AgentAWins();
                }
            }
        }
        else if (collision.gameObject.name == "AgentA")
        {//球碰到球拍A
            if (lastAgentHit == 0)
            {//如果上一次A已經擊打過,即A擊球兩次,則B贏
                AgentBWins();
            }
            else
            {//A成功回球
                if (lastFloorHit != FloorHit.Service && !net)
                {//A在空中直接回球||球在A地面彈起后A回球(即lastFloorHit=FloorAHit||FloorHitUnset)
                    //則判定過網
                    net = true;
                }

                lastAgentHit = 0;//使最后一次擊球為A
                lastFloorHit = FloorHit.FloorHitUnset;//重置擊球程序
            }
        }
        else if (collision.gameObject.name == "AgentB")
        {//同上
            if (lastAgentHit == 1)
            {
                AgentAWins();
            }
            else
            {
                if (lastFloorHit != FloorHit.Service && !net)
                {
                    net = true;
                }

                lastAgentHit = 1;
                lastFloorHit = FloorHit.FloorHitUnset;
            }
        }
    }
}

這個腳本,比較復雜的部分就是判斷誰贏的邏輯,其實就是將網球的規則編程代碼了,熟悉網球規則的童靴應該看一下再想想邏輯就明白了,當然這里也可以做了解,畢竟屬于業務部分的東西,

Agent腳本

Agent初始化與重置

首先來看一下Agent腳本中對于agent的初始化、重置部分以及變數引數,

public class TennisAgent : Agent
{
    [Header("Specific to Tennis")]
    public GameObject ball;//網球物件
    public bool invertX;//鏡像標志位
    public int score;//得分
    public GameObject myArea;//平臺
    public float angle;//球拍角度
    public float scale;//網球比例

    Text m_TextComponent;//得分板Text
    Rigidbody m_AgentRb;//球拍剛體
    Rigidbody m_BallRb;//網球剛體
    float m_InvertMult;//鏡像翻轉乘數
    IFloatProperties m_ResetParams;//可變引數(可變引數)

    const string k_CanvasName = "Canvas";
    const string k_ScoreBoardAName = "ScoreA";
    const string k_ScoreBoardBName = "ScoreB";

    public override void InitializeAgent()
    {
        m_AgentRb = GetComponent<Rigidbody>();
        m_BallRb = ball.GetComponent<Rigidbody>();
        //找球拍自己對應的得分板,不贅述
        var canvas = GameObject.Find(k_CanvasName);
        GameObject scoreBoard;
        m_ResetParams = Academy.Instance.FloatProperties;
        if (invertX)
        {
            scoreBoard = canvas.transform.Find(k_ScoreBoardBName).gameObject;
        }
        else
        {
            scoreBoard = canvas.transform.Find(k_ScoreBoardAName).gameObject;
        }
        m_TextComponent = scoreBoard.GetComponent<Text>();
        SetResetParameters();//設定可變引數
    }
    /// <summary>
    /// 設定球拍角度
    /// </summary>
    public void SetRacket()
    {
        angle = m_ResetParams.GetPropertyWithDefault("angle", 55f);
        gameObject.transform.eulerAngles = new Vector3(
            gameObject.transform.eulerAngles.x,
            gameObject.transform.eulerAngles.y,
            m_InvertMult * angle
        );
    }
    /// <summary>
    /// 設定網球比例
    /// </summary>
    public void SetBall()
    {
        scale = m_ResetParams.GetPropertyWithDefault("scale", .5f);
        ball.transform.localScale = new Vector3(scale, scale, scale);
    }
    /// <summary>
    /// 設定可變引數
    /// </summary>
    public void SetResetParameters()
    {
        SetRacket();
        SetBall();
    }
    /// <summary>
    /// Agent重置
    /// </summary>
    public override void AgentReset()
    {
        //根據inverX值來將m_InverMul置為1或-1
        m_InvertMult = invertX ? -1f : 1f;
        //球拍位置重置,X隨機(前后),Y(高度)與Z(左右)位置固定
        transform.position = new Vector3(-m_InvertMult * Random.Range(6f, 8f), -1.5f, -1.8f) + transform.parent.transform.position;
        //球拍速度置0
        m_AgentRb.velocity = new Vector3(0f, 0f, 0f);
        //設定可變引數
        SetResetParameters();
    }
}

OK,以上代碼其實大部分都很簡單,但是關于兩個變數invertXm_InvertMult,我給它們分別取名為“鏡像標志位”和“鏡像翻轉乘數”,從名字上也可以看出些許貓膩,簡單來講,invertX區分了兩個球拍,且決定了m_InvertMult的值是1還是-1,而m_InvertMult則是一個使得兩個對立的球拍方向統一化的乘數,這樣就可以使得兩個球拍雖然是對手,但是經過鏡像翻轉乘數,實際上轉換后,可以看成兩個球拍都是向同一個方向訓練,

當然在球拍位置初始化、角度初始化時,m_InvertMult也有作用,如下圖:

image-20200409234454878

image-20200409234521430

可以看到兩個球拍的TransformX與RotationZ為正負相反,這也是為什么在上述代碼中,對于球拍初始化位置和角度要乘以m_InvertMult

矢量觀測空間

我們在之前知道了該示例的觀測空間是Continous型別的,且變數有9個,下面來看一下,

/// <summary>
    /// 矢量觀測空間
    /// </summary>
    /// <param name="sensor"></param>
    public override void CollectObservations(VectorSensor sensor)
    {
        //球拍與場地的x值相對位置(前后)
        sensor.AddObservation(m_InvertMult * (transform.position.x - myArea.transform.position.x));
        //球拍與場地的y值相對位置(高低)
        sensor.AddObservation(transform.position.y - myArea.transform.position.y);
        //球拍x方向的速度
        sensor.AddObservation(m_InvertMult * m_AgentRb.velocity.x);
        //球拍y方向的速度
        sensor.AddObservation(m_AgentRb.velocity.y);

        //球與場地的x值相對位置
        sensor.AddObservation(m_InvertMult * (ball.transform.position.x - myArea.transform.position.x));
        //球與場地的y值相對位置
        sensor.AddObservation(ball.transform.position.y - myArea.transform.position.y);
        //球x方向的速度
        sensor.AddObservation(m_InvertMult * m_BallRb.velocity.x);
        //球y方向的速度
        sensor.AddObservation(m_BallRb.velocity.y);

        //球拍的旋轉角度
        sensor.AddObservation(m_InvertMult * gameObject.transform.rotation.z);
    }

總體來說,這里收集了球拍和場地、球和場地的相對位置以及它們各自的速度資訊,這里再利用圖來講一下鏡像翻轉乘數,

image-20200410000108289

這樣通過m_InverMult,則可以使得兩個球拍輸出的觀察引數變為同向,使得對抗訓練對于一個訓練單元的訓練效果變為double,對于球拍的旋轉角度也是同樣的道理,

Agent動作反饋

下面我們來看代理的AgentAction()方法,

	/// <summary>
    /// agent動作反饋
    /// </summary>
    /// <param name="vectorAction"></param>
    public override void AgentAction(float[] vectorAction)
    {
        //限制球拍x、y方向(左右、上下)乘積系數為-1到1
        var moveX = Mathf.Clamp(vectorAction[0], -1f, 1f) * m_InvertMult;
        var moveY = Mathf.Clamp(vectorAction[1], -1f, 1f);
        //限制球拍每次旋轉角度乘積系數為-1到1
        var rotate = Mathf.Clamp(vectorAction[2], -1f, 1f) * m_InvertMult;

        //當球拍在較低位置時,且moveY>0.5,則改變球拍向上的速度
        if (moveY > 0.5 && transform.position.y - transform.parent.transform.position.y < -1.5f)
        {
            m_AgentRb.velocity = new Vector3(m_AgentRb.velocity.x, 7f, 0f);
        }
        //改變球拍x(左、右)方向上的速度
        m_AgentRb.velocity = new Vector3(moveX * 30f, m_AgentRb.velocity.y, 0f);
        //改變球拍角度
        m_AgentRb.transform.rotation = Quaternion.Euler(0f, -180f, 55f * rotate + m_InvertMult * 90f);

        //限制球拍向前移動時不要越過網
        if (invertX && transform.position.x - transform.parent.transform.position.x < -m_InvertMult ||
            !invertX && transform.position.x - transform.parent.transform.position.x > -m_InvertMult)
        {
            transform.position = new Vector3(-m_InvertMult + transform.parent.transform.position.x,
                transform.position.y,
                transform.position.z);
        }

        m_TextComponent.text = score.ToString();//計分牌重繪
    }

AgentAction()代碼內容也不算太難,主要還是注意對于對抗的兩方,通過invertX和m_InvertMult來使得對抗兩方同向化,

小小提一下,在最后“限制球拍向前移動時不要越過網”部分,如果以agentA為例,此時invertX=false,m_InvertMult=1,則當滿足

(!invertX && transform.position.x - transform.parent.transform.position.x > -m_InvertMult)時,有如下情況:

image-20200410195902358

可以看到,當球拍再向前的話,就會碰到網上,因此需要限制球拍不能越過網,agentB紫色球拍也是一樣的情況,只是數值相反,

在這里深入思考一下,為什么只有限制球拍向前移動,而不用去限制球拍向后移動?可能大家也有相應的疑問,其實這里利用兩個碰撞體就可以限制球拍的移動了,即網的碰撞體以及場地后方碰撞體:

image-20200410200640970

但是你會發現網的碰撞體只有在球拍在低處時才能限制球拍向前移動,而將代碼注釋之后,球拍在空中時就會發生:

tennis4

可以看到,你可以控制球拍去對面胖揍對手= =||||,所以這里只用對球拍在自身向前的方向進行限制即可,

Agent手動操控

下面來看一下Heristic()函式:

	/// <summary>
    /// 手動操控
    /// </summary>
    /// <returns></returns>
    public override float[] Heuristic()
    {
        var action = new float[2];

        action[0] = Input.GetAxis("Horizontal");//左右移動控制
        action[1] = Input.GetKey(KeyCode.Space) ? 1f : 0f;//空格使球拍飛起
        return action;
    }

OK,以上代碼很簡單,但是有一個問題,當你想操作球拍和電腦對打時,將一個球拍的Behavior Type置為Heuristic Only后,開始游戲,你會發現以下錯誤:

image-20200410201722187

這里是說agent的回傳的矢量動作空間數量有問題,如果你比較熟悉ML-Agents之后,你會發現在AgentAction(float[] vectorAction)中,vectorAction[]陣列有三個元素,分別控制了球拍的x、y方向移動以及球拍的繞z軸的旋轉,而在以上Huristic()代碼中,action[]陣列只有兩個元素,只控制了球拍的x、y方向移動,缺少繞z軸的旋轉的行為引數,

因此,這里只需要將AgentAction()方法中的兩句代碼注釋:

var rotate = Mathf.Clamp(vectorAction[2], -1f, 1f) * m_InvertMult;

以及

m_AgentRb.transform.rotation = Quaternion.Euler(0f, -180f, 55f * rotate + m_InvertMult * 90f);

注釋后,就可以進行手動操作了,當然如果你也相同時操作球拍旋轉,也不是不可以,你可修改手動操控代碼如下:

	/// <summary>
    /// 手動操控
    /// </summary>
    /// <returns></returns>
    public override float[] Heuristic()
    {
        var action = new float[3];

        action[0] = Input.GetAxis("Horizontal");//左右移動控制
        action[1] = Input.GetKey(KeyCode.Space) ? 1f : 0f;//空格使球拍飛起
        action[2] = Input.GetAxis("Vertical");//控制球拍旋轉
        return action;
    }

五、訓練

我們這次訓練Tennis的模型,主要包含以下幾種:正常訓練(不帶可變引數)、帶兩個可變引數(scale、gravity)訓練、只帶一個可變引數(scale)不注釋代碼、只帶一個可變引數(scale)注釋代碼,

大家應該還記得我們在上述四、代碼分析中的環境初始化腳本一小節,提到在MatchReset()函式中重置小球比例是不合適的,因此我們來驗證一下這個想法是否正確,

接下來,我們先進行一組正常訓練;然后在利用兩個可變引數訓練之前,先驗證MatchReset()中設定小球比例會不會使得小球比例的可變引數設定失效;最后,我們再進行帶兩個可變引數的訓練,

普通訓練(不帶可變引數)

我們先來普通訓練一次,之前已經重復過很多次的操作~我們cd到ml-agent的目錄,然后輸入以下命令(當然這里面有一些組態檔、訓練結果的路徑,可以自行修改):

mlagents-learn config/trainer_config.yaml --run-id=Tennis_Normal --train

因為在訓練組態檔trainer_config.yaml中可以看到Tennis的max_steps為5.0e7,即五千萬步,是所有例子中訓練最大步數最大的,而之前我們的3D Ball才五十萬步,前者是后者的100倍,所以訓練時間相當長,

實際我訓練程序中,大概到100萬步的時候的效果就很不錯了,因此我將Tennis的max_steps改成了5.0e6,

此次訓練次數較多,訓練時間比較長,放一張訓練的程序截圖:

tennis7

可以大概看到兩邊打的有來有回,同時可以在螢屏下方看到雙方的得分,

同時觀察命令列輸出:

image-20200411000943322

在之前訓練中,Mean RewardStd of Reward是衡量訓練效果的很重要的兩個標準,一般來講是逐漸上升的,而這里一直是0和1,相應的有兩個其他的引數代替:Mean Opponent ELOStd Opponent ELO

經過查閱資料,首先了解一下ELO是什么:ELO等級分制度是指由匈牙利裔美國物理學家Elo創建的一個衡量各類對弈活動水平的評價方法,是當今對弈水平評估的公認的權威方法,

其實我們用簡單的話來講,例如早先英雄聯盟有排位分,你的排位分就是利用ELO計算出來的,如果你贏了比你分數更高的對手,你的排位分就會增加更多;如果輸給比你分數更少的對少,那你排位分就會減少的更多,更詳細的計算方法大家可以去查資料,這個知識點還挺有意思的,可以了解排位分大概是怎么算出來的,

通過對ELO的了解,也可以發現,這里因為用到了對抗訓練,所以采用Mean Opponent ELOStd Opponent ELO來看訓練的效果,其中我們會發現命令列中有一行是Tennis?team=1 ELO:1615.145,這里其實就代表了team為1的agent(即球拍B),現在它的ELO(可以簡單理解為排位分)是1615.145,你會發現它的ELO會隨著訓練的進行逐漸升高,相當于一直打排位,訓練自己上王者,

【Warning】等待了大約48分鐘,訓練到大概三百萬步時,突然發現如下情況:兩個球拍不對打了,罷工了!大概的情形就是兩個球拍一開始就一起移動到網前,讓球直接落地,然后立馬開始新的一局,可以之后的截圖,此時立馬Ctrl+C停止訓練,我們可以看一下tensorboard的情況:

image-20200411003841174

首先是對抗訓練中會存在ELO圖表,其實你就可以看成是排位分的變化趨勢,可以明顯觀察ELO在大概在3百萬步時突然下跌,包括Reawd也是,在3百萬步處有例外資料,將訓練到一半的Tennis_Normal.nn檔案放到Unity中:

tennis8

發現兩個球拍在一開始就往網前跑,給對手送分,看來利用Ctrl+C還是沒能將上一次的訓練模型拯救下來= =,

這里我懷疑是這樣的,因為在代碼中,沒有設計在訓練程序中,如果對抗雙方在一局里長時間來回打球,而給雙方同時獎勵的機制,當接近3百萬時,雙方的球技都挺好了,能打的有來有回的時間越來越長,導致長時間Brain沒有收到獎勵,使得Brain發現如果一直打可能一直都不能得分,自己的排位也上不去,但是如果丟球,還有機會拿獎勵分,致使Brain產生消極比賽送分的決策= =||||,這里也是很逗了,所以我將tennis的訓練最大步驟設定為2.5e6差不多到達此時情況下ELO的最大值,正所謂是實踐出真知啊,

從這也可以看到,訓練的最大步數并不是越大越好,還要基于你考慮的是否周全,而且在訓練前,應該竟可能設定好矢量觀測空間以及其他條件,運行一段時間后及時查看訓練效果,不要訓練太長時間發現沒有效果還硬著頭皮訓練,也不要一開始訓練只訓練較少步數沒有效果就換引數,

再次訓練一次最大訓練步數為2.5e6的:

tennis9

會發現訓練出來的模型還是有問題,然后我繼續調整最大訓練步數為2.0e6,對比一下tensorboard:

image-20200411081316991

我們從圖表里發現,2.5e6s的訓練模型在大概2.1M步驟的時候Brian又罷工了,訓練的隨機性還是比較大,在最大訓練步驟5.0e7時明明在3M左右才開始罷工,,,,

我們把2.0e6的訓練模型放到Unity中去,訓練效果如下:

tennis10

其實還蠻奇怪的,從訓練效果來看,2.0e6是比5.0e7及2.5e6正常一些,但是如果訓練步驟太長又會出現Brain罷工的情況,所以我也比較好奇官方Tennis給出的訓練模型是如何訓練出來的,是不是有一些什么設定我們漏了,這里要是有人知道的話也可以留言討論交流~下面我們還是繼續別的訓練,

可變引數設定

以前的文章里我們稱可變引數為泛化引數,是因為ml-agents官方改了,因此我們以后就把“泛化引數”統一稱作“可變引數”,可變引數我們這次按官方推薦的來設定,先來看一下Tennis的可變引數配置:

tennis_generalize.yaml

resampling-interval: 20000

scale:
    sampler-type: "uniform"
    min_value: 0.2
    max_value: 5
    
gravity:
    sampler-type: "uniform"
    min_value: 6
    max_value: 20

注意gravity引數的設定是在ProjectSettingOverrides.cs腳本里的:Academy.Instance.FloatProperties.RegisterCallback("gravity", f => { Physics.gravity = new Vector3(0, -f, 0); });來設定的,

此外,我們將resampling-interval設定為20000,是由于在trainer_config.yaml組態檔中,Tennis的訓練步驟我們設定的是2.0e6步,參照3D Ball,5.0e5次,可變訓練間隔為5000,依此參照,設定Tennis的這個引數為20000,當然這樣參照設定不一定適合,在訓練程序中如果想將某引數改變引入可變,那么在改變前的引數對應的訓練效果應該是基本成型的,如果訓練還么有成型就改變引數,有可能造成因為一直改引數使得訓練效果一直不佳,就和做軟體一樣,如果軟體需求一直更改,那么等到軟體成型出產品基本就等到猴年馬月了,

一個可變引數訓練

為了驗證MatchReset()中是否需要設定小球比例的問題,我們新增可變引陣列態檔:

tennis_generalize_1.yaml

resampling-interval: 20000

scale:
    sampler-type: "uniform"
    min_value: 0.2
    max_value: 5

將gravity引數去掉,用來消除gravity改變帶來的影響,我們先試驗注釋代碼后的效果,在命令列中輸入:

mlagents-learn config/trainer_config.yaml --sampler=config/tennis_generalize_1.yaml --run-id=Tennis_Gen_1_DeleteScale --train

訓練截圖如下:

tennis11

可以明顯看到一開始球的大小有改變,訓練結束后,再將代碼取消注釋,命令列中輸入如下命令進行訓練:

mlagents-learn config/trainer_config.yaml --sampler=config/tennis_generalize_1.yaml --run-id=Tennis_Gen_1_ModifyScale --train

會發現小球的大小在訓練程序中,有時候一開始會變大或者變小,但是會立馬變為正常大小進行訓練,這也符合我的想法,即MatchReset()會在SetResetParameters()之后將小球的比例復位成0.5,在相同訓練配置下,代碼注釋后的訓練相比代碼注釋前的訓練,會使得小球比例改變,從而也就證明了MatchReset()中設定小球比例在可變引數訓練中是不應該的的,

兩個可變引數訓練

我們利用兩個可變引數的組態檔,輸入以下命令(當然這里面有一些組態檔、訓練結果的路徑,可以自行修改):

mlagents-learn config/trainer_config.yaml --sampler=config/tennis_generalize.yaml --run-id=Tennis_Gen --train

等待訓練完成后,查看tensorboard圖表:

image-20200411165831643

可以看出,與不帶可變引數的訓練,帶可變引數的訓練其中還是有一些波折的,波折基本就代表了引數改變使得agent的ELO下降,這里的訓練資料僅僅作為一個參考吧,把訓練模型放到Unity中效果依然不是很好,這里就不展示了,

總結

這次的Tennis示例研究,主要時間都消耗到訓練上,最終也沒有訓練出和官方一樣的效果,這點還是有點遺憾,我認為應該是獎勵規則有漏洞的原因,具體在第五節已經寫了,當然也有可能是我哪里設定錯了,不過在這個程序中已經有挺多經驗值得學習和記錄了,程序還是比較有意思,因此這個示例的研究先到這,說不定后面研究更多的示例就會豁然開朗了,

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

1.博客中標注原創的文章,著作權歸原作者 煦陽(本博博主) 所有;

2.未經原作者允許不得轉載本文內容,否則將視為侵權;

3.轉載或者參考本文內容請注明來源及原作者;

4.對于不遵守此宣告或者其他違法使用本文內容者,本人依法保留追究權等,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/2216.html

標籤:其他

上一篇:如何將MagicaVoxel模型匯入UE4中(2)

下一篇:Unity Editor擴展編輯器中顯示腳本屬性

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more