22.03.11 ML-Agent 롤러볼 예제 실습하기

2022. 3. 14. 11:58UnityMLAgent/수업 내용

배운 점 : 보상을 언제, 어떻게 설정하느냐에 따라 에이전트의 행동이 확연히 달라진다.

 

이 포스팅에서는 ML-Agent 가이드를 참고하여 RollerBall 예제를 실습해보겠다.

 

예제 가이드 : 

https://github.com/Unity-Technologies/ml-agents/blob/main/docs/Learning-Environment-Create-New.md

 

GitHub - Unity-Technologies/ml-agents: Unity Machine Learning Agents Toolkit

Unity Machine Learning Agents Toolkit. Contribute to Unity-Technologies/ml-agents development by creating an account on GitHub.

github.com

 

 

실습하기에 앞서, ML-Agent를 사용하려면

대상 프로젝트에 ML-Agent Unity Package가 있어야 한다.

그 패키지 안에 실습에 필요한 스크립트, API 등이 있다.

패키지 추가 : 패키지 매니저 -> + 버튼 -> Select package on disk 에서 mlagent 폴더의

package.json 선택 후 추가

패키지 설치 가이드 : https://github.com/Unity-Technologies/ml-agents/blob/main/docs/Installation.md

 

GitHub - Unity-Technologies/ml-agents: Unity Machine Learning Agents Toolkit

Unity Machine Learning Agents Toolkit. Contribute to Unity-Technologies/ml-agents development by creating an account on GitHub.

github.com

 


직접 구현해보기

 

 

일단, 빈 프로젝트에 Sphere, Cube, Plane을 만들고 각각

Agent, Target, Floor로 이름 지은 후,

Training Area라는 이름의 새 빈 오브젝트에 자식으로 넣어 그룹화한다.

그룹화 시키는 이유는 이 이후의 몇 가지 작업이 단순해지기 때문이다.

그 다음, Agent 오브젝트에 RollerAgent라는 이름의 새 스크립트를 만들어 넣자.

이 스크립트에 ML-Agent의 함수를 쓸 건데, 그러기 위해서 맨 위의 using문에

using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

를 추가해서 ML-Agent 유니티 패키지를 임포트하자.

 

그리고 밑의 코드를 입력한다.

using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;

public class RollerAgent : Agent
{
    Rigidbody rBody;
    void Start () {
        rBody = GetComponent<Rigidbody>();
    }

    public Transform Target;
    public override void OnEpisodeBegin()
    {
       // If the Agent fell, zero its momentum
        if (this.transform.localPosition.y < 0)
        {
            this.rBody.angularVelocity = Vector3.zero;
            this.rBody.velocity = Vector3.zero;
            this.transform.localPosition = new Vector3( 0, 0.5f, 0);
        }

        // Move the target to a new spot
        Target.localPosition = new Vector3(Random.value * 8 - 4,
                                           0.5f,
                                           Random.value * 8 - 4);
    }
    
    public override void CollectObservations(VectorSensor sensor)
	{
    	// Target and Agent positions
    	sensor.AddObservation(Target.localPosition);
    	sensor.AddObservation(this.transform.localPosition);

    	// Agent velocity
    	sensor.AddObservation(rBody.velocity.x);
    	sensor.AddObservation(rBody.velocity.z);
	}
    
    public float forceMultiplier = 10;
	public override void OnActionReceived(ActionBuffers actionBuffers)
	{
    	// Actions, size = 2
    	Vector3 controlSignal = Vector3.zero;
    	controlSignal.x = actionBuffers.ContinuousActions[0];
    	controlSignal.z = actionBuffers.ContinuousActions[1];
    	rBody.AddForce(controlSignal * forceMultiplier);

    	// Rewards
 	   float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);

    	// Reached target
	    if (distanceToTarget < 1.42f)
    	{
        	SetReward(1.0f);
        	EndEpisode();
    	}

  	    // Fell off platform
    	else if (this.transform.localPosition.y < 0)
    	{
        	EndEpisode();
    	}
	}
}

각 ml-agent 메서드에 대한 자세한 설명은 이전 포스팅에서 설명했다.

https://rivergembig-gameprogramming.tistory.com/76

 

22.03.10 [ML-Agent] Agent 클래스의 핵심 메서드

이 포스팅에서는 유니티 ml-agent의 가이드를 참고하여 ML-Agents 를 이해하기 위해 몇 가지 기초 지식을 알아보겠다. Agent 클래스의 핵심 메서드 https://docs.unity3d.com/Packages/com.unity.ml-agents@1.0/ap..

rivergembig-gameprogramming.tistory.com

 

이제 우리의 Agent 오브젝트와 유니티 에디터를 연결시킬 차례이다.

RollerAgent의 Target 참조칸에 우리의 Target 오브젝트를 넣고,

Agent 오브젝트에 ml-agent 패키지에 미리 지정되어 있는 Decision Requester 스크립트를 추가하고,

Decision Period 속성을 10으로 설정한다.

 

★Decision에 대해 더 많은 정보를 알고 싶다면, 아래 링크를 참고하세요

https://github.com/Unity-Technologies/ml-agents/blob/main/docs/Learning-Environment-Design-Agents.md#decisions

 

GitHub - Unity-Technologies/ml-agents: Unity Machine Learning Agents Toolkit

Unity Machine Learning Agents Toolkit. Contribute to Unity-Technologies/ml-agents development by creating an account on GitHub.

github.com

그 다음 Behavior Parameters 스크립트 또한 추가하고 아래와 같이 속성을 설정한다.

  • Behavior Name: RollerBall
  • Vector Observation > Space Size = 8
  • Actions > Continuous Actions = 2

여기까지 했으면, 진짜 훈련을 하기 전에 환경을 테스트해볼 준비가 된 것이다.

 

Agent 오브젝트의 RollerAgent 스크립트에 아래 코드를 추가한다.

public override void Heuristic(in ActionBuffers actionsOut)
{
    var continuousActionsOut = actionsOut.ContinuousActions;
    continuousActionsOut[0] = Input.GetAxis("Horizontal");
    continuousActionsOut[1] = Input.GetAxis("Vertical");
}

이는 사용자가 키보드나 기타 입력장치를 통해 Agent의 동작을 테스트해볼 수 있는 메서드이다.

유니티의 플레이 버튼을 눌러서, 에이전트가 타겟에 도착했을 때, 에이전트가 Floor 밖으로 떨어졌을 때 

정상적으로 동작하는 지 테스트해본다.

 

테스트가 완료되었다면, 진짜 훈련을 해볼 차례이다.

훈련을 위해서는 교육 환경을 설정한 정보들이 필요하다. 

이는 하이퍼 파라미터로, config 파일에 명시되어 있어야 한다.

 

ml-agents의 config 파일 -> ppo 파일로 들어가서 새 텍스트 문서를 만들고

파일명을 rollerball_config.yaml 로 지정한다.

그리고 가이드에서 제공된 아래의 하이퍼 파라미터들로 내용을 채운다.

behaviors:
  RollerBall:
    trainer_type: ppo
    hyperparameters:
      batch_size: 10
      buffer_size: 100
      learning_rate: 3.0e-4
      beta: 5.0e-4
      epsilon: 0.2
      lambd: 0.99
      num_epoch: 3
      learning_rate_schedule: linear
      beta_schedule: constant
      epsilon_schedule: linear
    network_settings:
      normalize: false
      hidden_units: 128
      num_layers: 2
    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0
    max_steps: 500000
    time_horizon: 64
    summary_freq: 10000

 

이제 진짜로 훈련을 위한 준비가 모두 끝났다.

명령 프롬프트를 열어서, 하이퍼 파라미터 yaml 파일이 있는 경로로 들어온 후, 아래의 명령을 실행한다.

mlagents-learn rollerball_config.yaml --run-id=FirstRollerBall

 

그러면 아래와 같이 유니티 로고가 뜨면서 에디터에서 플레이버튼을 누르라고 한다.

플레이 버튼을 누르면 훈련이 진행될 것이다.

진행되는 동안엔 정지 버튼을 누르면 안되고,

정지하고 싶을 때는 컨트롤 + C 를 눌러 정지할 수 있다.

 

그리고 동일한 id 이름으로 훈련을 재개하고 싶을 때는 

mlagents-learn rollerball_config.yaml --run-id=FirstRollerBall --resume 으로 이어서 훈련이 가능하다.

아니면 --force를 써서 동일한 id의 훈련 진행 상태를 강제로 새 훈련 정보로 덮어쓸 수도 있다.

 

그리고 훈련 진행 상태를 그래프로 볼 수 있는데,

명령 프롬프트에 tensorboard --logdir results --port 6006 을 치고 실행하면 

이렇게 나온다. 여기서 마지막 줄의 http://localhost:6006/ 를 복사한 후 브라우저 탭을 열어

복사한 주소에 접속하면 아래 사진과 같은 그래프를 볼 수 있다.

단, 이 그래프 사이트에 접속하기 전에 명령 프롬프트에서 컨트롤 + C를 눌러 종료하면 접속이 안되는 것 같다.

지금까지 RollerBall 예제를 실습해보았다.

 


아래부터는 Optional


한 씬 안에 다중 Training Area

 

 

많은 ml-agent 훈련 예제들을 보면 한 씬 안에 여러 개로 복제된 Training Area가 있는 것을 흔히 볼 수 있는데,

이는 동일한 교육 환경에서 동시다발적으로 더 많은 정보와 경험을 쌓음으로써 

일반적으로 교육속도가 향상 되는 효과를 볼 수 있다.

 

우리는 Agent 오브젝트를 만들때, Training Area 빈 오브젝트 안에 Agent, Target, Floor 를 그룹화시켰고,

Agent의 움직임을 처리하는 구문을 모두 localPosition에 의존했기 때문에, 

그저 Agent의 Behavior Name을 모두 같게 설정하고 Training Area를 Paste함으로써

이 작업을 간단히 수행할 수 있다. 

프리팹화 해서 간단하게 인스턴스화 할 수 있다. 그 전에 모든 Agent의 Behavior Name을 동일하게 하기.

이 방법의 대안으로, 미리 제공되어 있는 TrainingAreaReplicator 스크립트를 이용하는 방법도 있다.

씬의 하이어라키 창에서 빈 오브젝트를 새로 만들고, TrainingAreaReplicator 스크립트를 추가한다.

스크립트의 BaseArea 참조란에 우리의 Training Area를 넣는다.

복제할 개수와 Area간의 간격을 설정하고 명령프롬프트에서

mlagents-learn fileName.yaml --run-id=idName --num-area=9

위와 같이 명령을 실행하면 복제가 된다.

 


동시 유니티 인스턴스를 사용해서 훈련하기

 

 

동시 유니티 인스턴스를 사용한 훈련으로 또 다른 수준의 병렬화를 이룰 수 있다.

명령 프롬프트에 아래 명령을 입력한다.

 

mlagents-learn rollerball_config.yaml --run-id=RollerBall --num-envs=2

이는 실행될 때 2개의 교육 환경 인스턴스를 생성한 후, 에이전트 훈련을 시작시킨다.

위에서 배운 다중 Training Area와 동시 유니티 인스턴스를 결합해 훈련하면 

2레벨 수준의 병렬 처리가 이루어져 효과적으로 교육 속도를 높일 수 있다.

명령 줄의 --num-envs=<n> 에서 n은 훈련 중에 병렬로 실행되는 동시 유니티 인스턴스의 수를 설정한다.

 

 


구체 대신 캐릭터로 훈련시키기

 

 

나는 DogKnight라는 캐릭터 에셋을 사용했다.

먼저 이 캐릭터를 움직일 스크립트를 아래와 같이 작성했다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    Animator anim;

    void Start()
    {
        anim = GetComponent<Animator>();
    }

    void Update()
    {
    	//움직임에 대한 벡터 정보
        Vector3 moveVec = Vector3.zero;
        moveVec += new Vector3(0, 0, Input.GetAxisRaw("Vertical"));
        moveVec += new Vector3(Input.GetAxisRaw("Horizontal"), 0, 0);

		//이동벡터가 조금이라도 움직일 때.
        if(moveVec.magnitude > 0.01f)
        {
            this.anim.SetBool("isWalking", true);
            Quaternion q = Quaternion.LookRotation(moveVec, Vector3.up);
            this.transform.rotation = Quaternion.Lerp(this.transform.rotation, q, 0.1f);
            this.transform.position += moveVec.normalized * 5f * Time.deltaTime;
        }
        else
        {
            this.anim.SetBool("isWalking", false);
        }
    }
}

Quaternion.LookRotation()메서드는 회전을 담당하는 구문으로,

오브젝트의 진행 방향으로 자연스럽게 회전하게 해준다.

자세한 설명은 아래 링크를 참고하세요

https://rivergembig-gameprogramming.tistory.com/35

 

오브젝트가 가는 방향에 따라 자연스럽게 회전시키고 싶을 때

if(move_vector.magnitude > 0.01f) //오브젝트가 조금이라도 움직이기 시작할 때 { 1) Quaternion q = Quaternion.LookRotation(move_vector, Vector3.up); 2) this.transform.rotation = Quaternion.Lerp(this.t..

rivergembig-gameprogramming.tistory.com

 

이러면 일단 캐릭터를 움직이는 것은 완료되었다.

이제 우리의 Agent 인공지능이 이 캐릭터를 조종할 수 있게 만드는 일만 남았다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

public class RollerAgent : Agent
{
    Animator anim;

    Rigidbody rb;
    [SerializeField] Transform target;
    //[SerializeField] float forceMultiplier = 10f;

    void Start()
    {
        anim = GetComponent<Animator>();

        rb = GetComponent<Rigidbody>();
    }

    public override void OnEpisodeBegin()
    {
        if (this.transform.localPosition.y < -1)
        {
            this.rb.angularVelocity = Vector3.zero;
            this.rb.velocity = Vector3.zero;
            this.transform.localPosition = new Vector3(0, 0.5f, 0);
        }
        target.localPosition = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4);
    }

    public override void CollectObservations(VectorSensor sensor)
    {
        sensor.AddObservation(target.localPosition);
        sensor.AddObservation(this.transform.localPosition);

        sensor.AddObservation(rb.velocity.x);
        sensor.AddObservation(rb.velocity.z);
    }

    public override void OnActionReceived(ActionBuffers actionBuffers)
    {
    	//Agent가 제자리걸음을 너무 많이 해서 매순간마다 감점을 주어
        //Target을 서둘러 찾게 하려는 의도.
        SetReward(-0.001f);

        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = actionBuffers.ContinuousActions[0];
        controlSignal.z = actionBuffers.ContinuousActions[1];
        //본인은 rigidbody 대신에 포지션값을 직접 제어했음.
        //rb.AddForce(controlSignal * forceMultiplier);
        this.transform.position += controlSignal.normalized * 5f * Time.deltaTime;

        if (controlSignal.magnitude > 0.01f)
        {
            this.anim.SetBool("isWalking", true);
            Quaternion q = Quaternion.LookRotation(controlSignal, Vector3.up);
            this.transform.rotation = Quaternion.Lerp(this.transform.rotation, q, 0.1f);
        }
        else
        {
            this.anim.SetBool("isWalking", false);
        }

        float distanceToTarget = Vector3.Distance(this.transform.localPosition, target.localPosition);

        if (distanceToTarget < 1.42f)
        {
            SetReward(1.0f);
            EndEpisode();
        }else if (this.transform.localPosition.y < -1)
        {
        	//Agent가 너무 많이 떨어져서 떨어지지 않도록 감점을 좀 세게 주었음.
            SetReward(-0.5f);
            EndEpisode();
        }
    }

    public override void Heuristic(in ActionBuffers actionsOut)
    {
        var continousActionsOut = actionsOut.ContinuousActions;
        continousActionsOut[0] = Input.GetAxis("Horizontal");
        continousActionsOut[1] = Input.GetAxis("Vertical");
    }
}

Agent의 행동 정보는 OnActionReceived()의 인수인 ActionBuffers로 받아오니

거기에서 받은 정보를 이용해 우리의 캐릭터를 움직여준다.

정작 추가한 코드는 별로 없고 캐릭터를 움직이기 위한 스크립트에서

OnActionReceived() 메서드로 몇 줄 복사 붙여넣기 했을 뿐이다.

 


결과