2022. 3. 14. 11:58ㆍUnityMLAgent/수업 내용
배운 점 : 보상을 언제, 어떻게 설정하느냐에 따라 에이전트의 행동이 확연히 달라진다.
이 포스팅에서는 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에 대해 더 많은 정보를 알고 싶다면, 아래 링크를 참고하세요
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함으로써
이 작업을 간단히 수행할 수 있다.
이 방법의 대안으로, 미리 제공되어 있는 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() 메서드로 몇 줄 복사 붙여넣기 했을 뿐이다.
결과
'UnityMLAgent > 수업 내용' 카테고리의 다른 글
ML-Agent Penguin 예제로 얻은 훈련 팁 (0) | 2022.03.16 |
---|---|
22.03.10 [ML-Agent] Agent 클래스의 핵심 메서드 (0) | 2022.03.10 |