Game Development

Unity ML Agent 모방 학습으로 먹이를 먹게 하자 본문

Unity/ML Agent( AI )

Unity ML Agent 모방 학습으로 먹이를 먹게 하자

Dev Owen 2021. 6. 29. 23:38


유니티의 ML Agent를 사용하여 먹이를 먹는 방법을 학습시키자

제목부터 길어보이지만 사실 크게 다를거 없습니다. 필자는 7시간의 노가다 끝에 성공했지만 유니티 ML Agent에 배우시는 분들에게 도움이 되길 바랍니다.

 

[ 관련 자료 ]

해당 포스팅의 글은 CodeMonkey님의 ML Agent강의를 기반으로 작성되었습니다.

ML Agent가 처음이신 분들은 저의 포스팅 중 기본 강의를 보고 오시기 바랍니다 [ 바로 가기 ]

해당 ML-Agent 버전은 2.1.0-exp-1을 사용하고 있습니다.

 

프로젝트를 진행하기전 버튼에 대한 스크립트 정보입니다.

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

public class FoodButton : MonoBehaviour
{
    [SerializeField] public Transform food;

    public bool isCanPushButton = false;
    public bool isCanUseButton { get;set;}
    public bool isSpawnFood
    {
        get
        {
            return food.gameObject.activeSelf;
        }
    }
    BlueAgent _agnet;

    public void SpawnFood()
    {
        food.transform.localPosition = new Vector3(Random.Range(-0.5f, 0.5f), 1, Random.Range(-0.5f, 0.5f));
        food.gameObject.SetActive(true);
        isCanUseButton = false;
    }

    private void OnTriggerStay(Collider other)
    {
        if (other.TryGetComponent<BlueAgent>(out _agnet))
        {
            isCanPushButton = true;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.TryGetComponent<BlueAgent>(out _agnet))
        {
            isCanPushButton = false;
        }
    }
}

[ 음식을 주는 버튼을 찾게 만들자 ]

개발을 함에 있어 천천히 하나씩 해결해 나간다고 생각을 해봅시다. 이번에 할 주제가 버튼을 누르면 음식이 나오고 해당 음식을 먹는것입니다. 그렇기에 먼저 버튼을 누르게 해보도록 하겠습니다.

 

먼저 BlueAgent라는 스크립트를 하나 만들고, Agent를 상속받도록 하겠습니다.

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

public class BlueAgent : Agent
{
    
}

그 다음 현재 상태를 전달하는 CollectObservations( VectorSensor sensor )

학습된 값을 받는 OnActionReceived(ActionBuffers actions) 을 오버라이드 해주시기 바랍니다.

public class BlueAgent : Agent
{
    public override void CollectObservations(VectorSensor sensor)
    {
    }

    public override void OnActionReceived(ActionBuffers actions)
    {
    }
}

유니티 클라이언트로 돌아가 BlueAgent 스크립트를 넣고 Behavior Parameters를 수정해보도록 하겠습니다.

 

각각의 변경된 값을 수정해 주시고, 다시 스크립트로 돌아갑니다. 

이제 2가지의 생각을 할 시간이 왔습니다.


1. 어떤 값을 전달 받아야 할까?

어떤 값을 전달 받는지에 따라 오브젝트의 움직임이 달라지게 됩니다. 처리해야 하는건 다음과 같습니다.

    1. 플레이어가 움직여야할 좌표를 받아야합니다

    2. 현재 버튼을 누를지 말지에 대한 정보를 받아야합니다.

해당 부분에 대한 스크립트는 아래와 같습니다.

public override void OnActionReceived(ActionBuffers actions)
{
    float x = actions.ContinuousActions[0]; 
    float z = actions.ContinuousActions[1];

    int isPushButton = actions.DiscreteActions[0];

    float moveSpeed = 5;
    rb.velocity = new Vector3( x, rb.velocity.y, z ) * moveSpeed;

    if ( isPushButton == 1 )
    {
        if ( foodButton.isCanPushButton && foodButton.isCanUseButton )
        {
            foodButton.SpawnFood();
            SetReward(+1);
        }
    }
    SetReward(+1 / MaxStep);
}

해당 스크립트에선 버튼을 누를시, 1점의 점수를 추가해주는것을 볼 수 있습니다.

그렇기에 인공지능은 버튼을 누를 경우 좋은일이 일어난다고 알 수 있게 되었습니다.

그리고 움직일 수록 점수를 깍아 버렸습니다. 그 효과로 인공지능은 최단 경로를 찾기 시작할 껍니다.

2. 어떤 정보를 전달해 줘야 할까?

사실 제일 중요한 부분입니다. 어떤 값을 전달해 주냐에 따라서 학습이 완전달라지게 됩니다. 필자는 이부분에서 6시간이라는 시간을 버리게 되었습니다....

 

어떤 정보를 보내줘야하는지 생각을 해보도록 합시다.

   1. 현재 버튼을 누를 수 있는지에 대한 정보가 필요할껍니다.

   2. 버튼이 있는 위치의 대한 정보가 필요합니다.

   3. 음식이 스폰 됬는지에 대한 정보가 필요합니다.

   4. 음식이 스폰 되었다면 어디에 스폰 되었는지에 대한 정보가 필요합니다.

해당 부분에 대해서 스크립트를 작성하면 아래와 같습니다.

public override void CollectObservations(VectorSensor sensor)
{
    sensor.AddObservation(foodButton.isCanUseButton == true ? 1 : 0);

    Vector3 dirToFoodButton = (foodButton.transform.position - transform.position).normalized;
    sensor.AddObservation(dirToFoodButton.x);
    sensor.AddObservation(dirToFoodButton.z);

    sensor.AddObservation(foodButton.isSpawnFood == true ? 1 : 0);

    if (foodButton.isSpawnFood == true)
    {
        Vector3 dirToFood = (foodButton.food.position - transform.position).normalized;
        sensor.AddObservation(dirToFood.x);
        sensor.AddObservation(dirToFood.z);
    }
    else
    {
        sensor.AddObservation(0);  // x
        sensor.AddObservation(0);  // z
    }
}

이제 나머지 작업을 해보도록 합시다. 학습이 종료 됬을때 초기화를 해보도록 하겠습니다.

public override void OnEpisodeBegin()
{
    rb.velocity = Vector3.zero;
    foodButton.isCanUseButton = true;
    foodButton.isCanPushButton = false;
    foodButton.food.gameObject.SetActive(false);
    transform.localPosition = new Vector3(0,1,0);
}

 

먹이를 먹을 경우 점수를 얻게 해보도록 하겠습니다.

private void OnCollisionEnter(Collision other) 
{
    if ( other.transform.TryGetComponent( out Food _food ) )    
    {
        SetReward( +1 );
        _food.EatFood();
        EndEpisode();
    }
}

 

이제 새로운 방식의 학습을 시켜보도록 하겠습니다. 해당 학습으로 하기 위해 Heuristic이 필요합니다.

해당 함수는 값을 인위로 넣어주는 함수라고 생각하시면 됩니다. 아래처럼 저희가 필요한 값을 넣어주기만 하면 됩니다.

public override void Heuristic(in ActionBuffers actionsOut)
{
    ActionSegment<float> ContinuousActions = actionsOut.ContinuousActions;
    ActionSegment<int> DiscreteActions = actionsOut.DiscreteActions;

    ContinuousActions[0] = Input.GetAxis("Horizontal");
    ContinuousActions[1] = Input.GetAxis("Vertical");

    DiscreteActions[0] = Input.GetKey( KeyCode.Space ) == true ? 1: 0;
}

[ 녹화를 해보자 ]

학습을 하기전 어느정도 인공지능에게 이런 방식으로 플레이를 해줘라는 식으로 학습 데이터를 살짝 만들어 줄 수 있습니다. 그 방법이 바로 Demonstration Recorder를 이용한 방식인데요. 간단하게 설명하겠습니다.

 

Record 녹화 할지 여부
Num Steop To Record 녹화할 시간 ( 0일 경우 게임 끝날 때 까지 )
Demonstration Name 저장할때 저장할 이름
Demonstration Direction 저장할 경로

녹화를 하기전 Behavior parameters Behavior TypeHeuristic으로 변경해주세요.

그럼 직접 녹화를 해보도록 하겠습니다.

직접 조종하여 학습을 시킵니다.

녹화를 키고 녹화를 하면 폴더에 파일이 하나 생깁니다.

 

해당 파일을 이용하여 학습을 해보도록 하겠습니다.

이제 Config파일에 학습 방식을 수정해보도록 하겠습니다.

behaviors:
    AgnetBlue:
        trainer_type: ppo
        hyperparameters :
            batch_size: 1024
            buffer_size : 1024
            learning_rate: 0.0003
            beta: 0.005
            epsilon: 0.2
            lambd: 0.99
            num_epoch: 3
            learning_rate_schedule: linear
        network_settings:
            normalize: false
            hidden_units: 128
            num_layers: 2
        reward_signals:
            extrinsic:
                strength: 1.0
                gamma: 0.99
        max_steps: 10000000
        time_horizon: 64
        summary_freq : 20000

해당 파일을 복사하여 .yaml의 파일로 만든 후 프로젝트 폴더에 Config폴더를 만든 후 안에 넣어주세요.

여기서 필수적으로 맞춰줘야 하는것이 AgentBlue라고 적힌 부분과 아래의 사진에 나온 이름과 동일 해야 합니다.

 

이제 아까 레코드로 촬영했던 파일을 토대로 학습을 시켜보도록 하겠습니다.

behaviors:
    AgnetBlue:
        trainer_type: ppo
        hyperparameters :
            batch_size: 1024
            buffer_size : 1024
            learning_rate: 0.0003
            beta: 0.005
            epsilon: 0.2
            lambd: 0.99
            num_epoch: 3
            learning_rate_schedule: linear
        network_settings:
            normalize: false
            hidden_units: 128
            num_layers: 2
        reward_signals:
            extrinsic:
                strength: 1.0
                gamma: 0.99
            gail:
                strength: 0.5
                demo_path: Demos/FoodAgentDemo.demo
        behavioral_cloning:
                strength: 0.5
                demo_path: Demos/FoodAgentDemo.demo
        max_steps: 10000000
        time_horizon: 64
        summary_freq : 20000

gail 부분은 보상에 초점을 둡니다.데모 파일에서 한 행동을 정확하게 복제하는 대신에 점수를 얻기위해 필요한 친구입니다. 해당 강도를 0.5만큼 주어 인공지능이 좀 더 학습을하여 똑똑하게 만들었습니다.

 

behavioral_cloning 부분은 사전 학습한 내용의 행동을 복제하여 한다는것 입니다. 해당 부분 또한 강도를 0.5 만큼 주어

인공지능이 좀 더 학습하여 똑똑하게 만들 수 있게 하였습니다.

 

이제 해당 Config파일로 실행하는 방법을 알려드리겠습니다.

mlagents-learn 파일경로.yaml --run-id="이름"

이제 실행을하고 학습을 해보도록 하겠습니다.

어느 정도 학습 후 나름 버튼도 잘 누르고 먹이 도 잘먹네요!

최종적으로 학습한 데이터를 넣어 봅시다!

많은 학습을 하지 않았지만 먹이를 잘 찾아가는 모습이 보이네요 학습을 많이 시키면 보다 완벽해질거 같습니다!


한줄평

어렵지만 나름 재미있는걸...??

해당 파일을 깃허브를 통하여 다운로드 받으실 수 있습니다.

 

Dev-Owen-Git/Unity-FindFood

Contribute to Dev-Owen-Git/Unity-FindFood development by creating an account on GitHub.

github.com

 

Comments