유니티 머신러닝 개발 ML Agents 10편, 목표 찾기 예제 개선. 4 벽 설치, 이동 방식의 변경, 연속적인 값에서 이산적인 값으로 변경
지난 예제까지는 여러개의 훈련장을 사용하여 교육 속도를 올려보았습니다.
하지만, 교육장이 넓어져서 훈련에 많은 시간이 소요되었을 겁니다.
이번에는 이동방식을 개선해볼 생각입니다.
훈련장 조정
일단 위의 훈련장을 좀 줄이고 시작할게요
추가되었던 trainingArea를 지웁니다.
floor을 하나만 남기고 Transform 다음과 같이 합니다.
에이전트와 타겟도 다음 정보를 참고해서 수정해주세요
gRollerAgent.cs 의
public override void OnEpisodeBegin()
함수도 수정해줍니다.
public override void OnEpisodeBegin()
{
//새로운 애피소드 시작시, 다시 에이전트의 포지션의 초기화
// If the Agent fell, zero its momentum
if (this.transform.localPosition.y < 0) //에이전트가 floor 아래로 떨어진 경우 추가 초기화
{
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
float rx = 0;
float rz = 0;
rx = Random.value * 16 - 8;
rz = Random.value * 16 - 8;
Target.localPosition = new Vector3(rx,
0.5f,
rz);
}
여기까지 진행하면
7편에서 floor만 확장된 상태라 보시면 됩니다.
벽 설치, 낙하 로직 제거
이제 낙하하는 로직을 바꿀 것입니다.
훈련장 주변에 cube로 벽을 새워줍니다.
저 같은 경우는 하나를 배치하고 복사하였습니다.
이름은 wall 로 해주세요 아래를 봐주세요
gRollerAgent.cs 의
public override void OnActionReceived(ActionBuffers actionBuffers) 함수의 낙하 처리도 지워줍니다.
//판 아래로 떨어지면 학습이 종료된다.
// Fell off platform
else if (this.transform.localPosition.y < 0)
{
SetReward(-0.1f);
EndEpisode();
}
요 구문의 삭제시켜줍니다.
적당히 동작시켜 봅니다.
벽이 있는 관계로 벽에 무작정 비비는 모습을 볼 수 있습니다.
현재 상태로 여러 조건을 추가하여 추가 교육을 시킬 수도 있겠지만,
다른 걸 해보려 합니다.
이동 방식의 변경
gRollerAgent.cs 의
public override void OnActionReceived(ActionBuffers actionBuffers) 함수를 봅시다.
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);
viewModel.transform.LookAt(Target);
// Rewards
float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);
//타겟을 찻을시 리워드점수를 주고, 에피소드를 종료시킨다.
// Reached target
if (distanceToTarget < 1.42f)
{
SetReward(1.0f);
EndEpisode();
}
}
현재 상태는 actionBuffers에서 2개의 Continuous한 값(연속된 값, 이산적이지 못한 값)을 가져와 오브젝트에 바로 힘으로 줘버립니다.
일반적으로 이동을 구 한하면 사용자는 인풋을 이산적으로 입력합니다.
앞으로 가라 마라 만 입력하죠
화살표를 누르고 있으면 가는 것이고 때면 멈추는 것이죠.
회전 또한 누르고 있으면 하는 것이고, 아니면 멈추는 것이죠.
에이전트의 이동 방식을 교체해보겠습니다.
먼저 연속적 입력값을 이산적 입력값으로 교체하고자 합니다.
gRollerAgent.cs의
OnActionReceived 를 수정합니다.
/// <summary>
/// 강화학습을 위한, 강화학습을 통한 행동이 결정되는 곳
/// </summary>
public float forceMultiplier = 10;
float m_LateralSpeed = 1.0f;
float m_ForwardSpeed = 1.0f;
public override void OnActionReceived(ActionBuffers actionBuffers)
{
// Rewards
float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);
//타겟을 찻을시 리워드점수를 주고, 에피소드를 종료시킨다.
// Reached target
if (distanceToTarget < 1.42f)
{
SetReward(1.0f);
EndEpisode();
}
MoveAgent(actionBuffers.DiscreteActions);
}
public void MoveAgent(ActionSegment<int> act)
{
var dirToGo = Vector3.zero;
var rotateDir = Vector3.zero;
var forwardAxis = act[0];
var rotateAxis = act[1];
switch (forwardAxis)
{
case 1:
dirToGo = transform.forward * m_ForwardSpeed;
break;
}
switch (rotateAxis)
{
case 1:
rotateDir = transform.up * -1f;
break;
case 2:
rotateDir = transform.up * 1f;
break;
}
transform.Rotate(rotateDir, Time.deltaTime * 100f);
rBody.AddForce(dirToGo * forceMultiplier, ForceMode.VelocityChange);
}
소스 변경점이 많습니다.
마지막에 전체 소스를 다시 한번 올릴 계획입니다.
기존 Continuous 값 해석을 이산적인 값으로 해석합니다.
실제 moveAgent 함수에서 두 정수를 받습니다.
첫 번째 정수의 범위는 0,1입니다.
0은 정지입니다. 1은 전진입니다.
두 번째 정수의 범위는 0,1,2입니다.
0은 정지입니다. 1은 좌회전, 2는 우회전입니다.
직접 입력을 해가며 테스트를 위해서
아래 함수를 수정합니다.
public override void Heuristic(in ActionBuffers actionsOut)
public override void Heuristic(in ActionBuffers actionsOut)
{
var discreteActionsOut = actionsOut.DiscreteActions;
discreteActionsOut.Clear();
//forward
if (Input.GetKey(KeyCode.W))
{
discreteActionsOut[0] = 1;
}
//rotate
if (Input.GetKey(KeyCode.A))
{
discreteActionsOut[1] = 1;
}
if (Input.GetKey(KeyCode.D))
{
discreteActionsOut[1] = 2;
}
}
키보드의 w 값을 입력 시 첫 번째 값에 1을 입력합니다.
a, s 입력 시 2번째 값에 1,2가 입력됩니다.
코드 수정이 끝나면 이번에는 유니티로 돌아옵니다.
현재 Agent의 Behavior parameters는 연속적인 값 입력으로 세팅이 되어 있습니다.
다음과 같이 수정합니다.
아래 경고가 뜹니다.
현재 등록된 모델과 세팅값이 안 맞다는 겁니다.
혹시 model 파일이 세팅되어 있다면 빼버립니다. 지우세요.
일단 먼저 우리가 조작을 해볼 수 있습니다.
우리가 코딩한 값으로 조작하도록 합니다.
해보면 먼가 너무 빠른 걸 느끼실 수 있습니다.
다음 값들을 설정해 주시면 적당한 움직임이 나옵니다.
만족하시는 움직임이 되도록 적당히 수정하시고
새로운 교육을 시켜보도록 합니다.
데이터를 가져와서 돌려보면 상태가 그리 좋지 못합니다.
이전에도 말을 했었지만, 결과적으로는 우리가 제공하는 데이터, 관측치가 좀 더 좋아야 할 듯합니다.
일단 이번에는 이동 방식을 바꾼 것이고요, 다음번은 관측정보를 좀 바꿔보도록 하죠.
gRollerAgent.cs 의 변화가 많아,
일단 전체소스도 첨부합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
//mlAgent 사용시 포함해야 됨
public class gRollerAgent : Agent
{
Rigidbody rBody;
void Start()
{
rBody = GetComponent<Rigidbody>();
}
public Transform Target;
public GameObject viewModel = null;
public override void OnEpisodeBegin()
{
//새로운 애피소드 시작시, 다시 에이전트의 포지션의 초기화
// If the Agent fell, zero its momentum
if (this.transform.localPosition.y < 0) //에이전트가 floor 아래로 떨어진 경우 추가 초기화
{
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
float rx = 0;
float rz = 0;
rx = Random.value * 16 - 8;
rz = Random.value * 16 - 8;
Target.localPosition = new Vector3(rx,
0.5f,
rz);
}
/// <summary>
/// 강화학습 프로그램에게 관측정보를 전달
/// </summary>
/// <param name="sensor"></param>
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);
}
/// <summary>
/// 강화학습을 위한, 강화학습을 통한 행동이 결정되는 곳
/// </summary>
public float forceMultiplier = 10;
float m_LateralSpeed = 1.0f;
float m_ForwardSpeed = 1.0f;
public override void OnActionReceived(ActionBuffers actionBuffers)
{
// Rewards
float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);
//타겟을 찻을시 리워드점수를 주고, 에피소드를 종료시킨다.
// Reached target
if (distanceToTarget < 1.42f)
{
SetReward(1.0f);
EndEpisode();
}
MoveAgent(actionBuffers.DiscreteActions);
}
public void MoveAgent(ActionSegment<int> act)
{
var dirToGo = Vector3.zero;
var rotateDir = Vector3.zero;
var forwardAxis = act[0];
var rotateAxis = act[1];
switch (forwardAxis)
{
case 1:
dirToGo = transform.forward * m_ForwardSpeed;
break;
}
switch (rotateAxis)
{
case 1:
rotateDir = transform.up * -1f;
break;
case 2:
rotateDir = transform.up * 1f;
break;
}
transform.Rotate(rotateDir, Time.deltaTime * 100f);
rBody.AddForce(dirToGo * forceMultiplier, ForceMode.VelocityChange);
}
/// <summary>
/// 해당 함수는 직접조작 혹은 규칙성있는 코딩으로 조작시키기 위한 함수
/// </summary>
/// <param name="actionsOut"></param>
public override void Heuristic(in ActionBuffers actionsOut)
{
var discreteActionsOut = actionsOut.DiscreteActions;
discreteActionsOut.Clear();
//forward
if (Input.GetKey(KeyCode.W))
{
discreteActionsOut[0] = 1;
}
//rotate
if (Input.GetKey(KeyCode.A))
{
discreteActionsOut[1] = 1;
}
if (Input.GetKey(KeyCode.D))
{
discreteActionsOut[1] = 2;
}
}
}
봐주셔서 감사합니다.
댓글