Search

ML Agents로 점프 플레이어 AI 만들기

date
2022/06/24
tags
아들과 2인개발
유니티3D
ML Agent
4 more properties
아들과 2인 개발 모음
다음에 만들 게임을 정하기 위해 여러 가지 프로토타입을 만들고 있습니다. 기발한 아이디어가 떠올라서 흥분한 마음으로 프로토타입을 하면 이내 여러 가지 문제가 발견되곤 합니다. 이런 패턴의 반복입니다. 그래서 몇 가지 원칙을 정했습니다.
첫째, 아들이 재밌게 플레이할 수 있는 게임을 만들자. 그러면 다른 초등학생들도 재밌게 플레이 할 것이다.
둘째, 무조건 머신 러닝이 사용되어야 한다.
셋째, 인게임의 오리지널리티보다는 완성된 아웃게임에 신경쓴다.
이런 원칙에 맞추어서 처음 프로토타이핑해본 것이 바로 벽점프 게임입니다. 일단 AI가 플레이어를 사람처럼 조작할 수 있는 것을 목표로 ML Agents를 공부해보고 있습니다.

유니티는 잘 알아도 강화학습 모르는 사람들을 위한 개요

주의! 저도 머신러닝 초보자입니다! 그러나 초보자 시절에 글을 써야 초보자가 뭘 모르는지 잘 알 수 있다는 생각에 적어봅니다.
보통의 프로그래밍은 컴퓨터에게 시킬 일을 ‘이거하고나서 저거해라’ 라는 식으로 명령을 내리는 방식이죠. 반면 머신 러닝은 이런 입력이 있을때 이런 출력이 나오면 좋겠어 라고 말하면 방대한 데이터를 기반으로 모델이 스스로 학습을 하면서 그것이 가능해지게 만듭니다.
강화학습에서는 AI에게 주변 환경에 대한 정보(=관찰)을 넘기면 AI는 특정 행동(=액션)을 하고 우리는 그 행동에 대한 보상을 줍니다. 수 많은 훈련 세션을 통해서 AI는 더 많은 보상을 얻기 위해서 어떤 행동을 하면 좋을지 학습하게됩니다.
이렇게 학습된 결과로 모델이 만들어지고, 유저에게 게임을 서비스 할 때는 이 모델을 탑재해서 AI가 상황에 맞는 행동을 하게 합니다.

설치

ML Agents 공식 매뉴얼은 여기입니다.
제가 설치한 버전을 기준으로 설명합니다.
유니티 패키지 매니저에서 ML Agents 패키지 설치 (2.0.1)
ml-agents 파이선 패키지 설치 (0.26.0)
유니티 패키지 버전과 매칭되는 파이선 패키지 버전은 여기서 찾기 ⇒ https://github.com/Unity-Technologies/ml-agents/releases
Anaconda를 사용하는 경우에는 이렇게 설치하면 됩니다. (파이선 3.7 사용)
$ conda create --name AstroJump python=3.7 $ conda activate AstroJump (AstroJump)$ pip install mlagents==0.26.0
Bash
윈도우즈에서는 pytorch를 별도로 설치해야 해요.
(AstroJump)$ pip3 install torch~=1.7.1 -f https://download.pytorch.org/whl/torch_stable.html
Bash

구현

어떤 구현을 해야되는지 리스팅만 해보겠습니다.
빨간 큐브를 움직이는 PlayerController가 Agent 상속하게 합니다.
Initialize()에서 초기화를 합니다. Awake() 대신 사용하면 됩니다.
OnEpisodeBegin()에서 모든 게임을 리셋하고 재시작. (새로운 훈련이 시작되는 콜백입니다).
OnActionReceived()에서 플레이어 조작. 저는 점프하느냐 마느냐 하나 밖에 없어요.
CollectObservation()에서 주변 내용 관찰한 것 입력합니다. 플레이어의 위치, 속도, 벽에 붙었는지 등등을 입력했어요.
Heuristic()에 수동 조작 코드 추가합니다. 여기서 채운 Action이 OnActionReceived()로 갑니다.
Agent의 인스펙터에서 AI가 제어하게 하거나, 수동 조작하게 설정할 수 있습니다. 수동 조작하게 설정하면 이 함수가 실행되어서 우리가 AI 대신에 Action을 선택할 수 있어요.
의미있는 행동을 했을 때 AddReward() 호출해서 보상을 합니다.

유니티 설정

BehaviourParameters 컴포넌트를 추가해서 Action과 Observation 스페이스에 대해 설정합니다. Action의 개수와 Observation의 개수를 코드에서 준 것과 동일하게 설정하는게 중요합니다.
DecisionRequester 컴포넌트를 추가해서 Action이 발생하게 합니다.
플레이어의 자식으로 자식에 RayPerceptionSendor3D 를 추가. 주변의 사물을 Raycast를 통해 인식할 수 있게 도와주는 컴포넌트입니다. 직접 구현해서 Observation을 제공해도 되지만, 사용하면 편리합니다.

학습

학습 설정 파일을 준비합니다. 유니티 예제를 가져와서 살짝 고쳤습니다. 저도 설정이 어떤 의미인지 잘 모르고 쓴 것이니 참고만 하세요.
behaviors: AstroJump: trainer_type: ppo hyperparameters: batch_size: 32 buffer_size: 256 learning_rate: 0.003 beta: 0.01 epsilon: 0.2 lambd: 0.95 num_epoch: 3 learning_rate_schedule: linear network_settings: normalize: false hidden_units: 20 num_layers: 1 vis_encode_type: simple reward_signals: extrinsic: gamma: 0.9 strength: 1.0 keep_checkpoints: 5 max_steps: 500000 time_horizon: 1000 summary_freq: 2000
YAML
아래와 같이 파이선 스크립트를 시작하고, 유니티를 플레이 하면 학습이 시작됩니다.
(AstroJump)$ mlagents-learn trainer_config.yaml --run-id=FirstTry01
Bash

문제 해결

TypeError: Invalid first argument to register(). typing.Dict[mlagents.trainers.settings.RewardSignalType, mlagents.trainers.settings.RewardSignalSettings] is not a class.

지금 Anaconda 설치하면 파이선 3.9.12가 설치되는데, 3.7 설치하면 이 에러가 해결됩니다.
파이선 3.7을 사용하는 Anaconda 환경은 이렇게 생성하면 됩니다.
conda create -n AstroJump python=3.7
Bash

mlagents_envs.exception.UnityTimeOutException: The Unity environment took too long to respond.

유니티와 잘 연결되었다는 로그가 나오는데도 잠시 후에 이런 에러가 나면서 종료한다면, 실제로 action이 발생하고 observation이 전달되고 있는지 로그를 찍어 보세요. 어떤 이유로든 observation이 발생하고 있지 않다면 위 에러가 나옵니다.
저는 DecisionRequester를 안붙여서 action이 발생하지 않았어요.

학습 히스토리 (처음에 이런 게 어려웠구나 기록용)

점프 잘하면 0.1, 벽이 아닌데 점프하면 -0.05. 160k 스텝을 해도 누적 0.3점을 넘기 어렵다. y위치와 차일드 센서만 썼음.
x 위치와 isOnWall, 마지막 클릭후 지난 시간을 observation에 추가했더니, 160k 스텝에 최대 1.5 까지도 나온다. 그러나 500k 에도 2.5 수준이다.
장애물 넣고 테스트. 학습 그래프가 요동치고 점수가 잘 안올라가는 걸 보면 학습이 잘 안되고 있음. 그래도 마지막에 5.65 까지 나왔다.
점프하지 않는 것에 대해 패널티를 부여했더니 조금 더 잘하는 것 같은데, 점수 체계가 바뀌니 수치적으로 판단할 방법이 없다. 텐서보드 그래프의 모습도 대조적인데 어떻게 해석해야할지 모르겠다.
통과해야 하는 장애물을 넣고, velocity x, y도 관찰에 추가. 리워드는 이전 프레임보다 얼마나 올라갔나, 내려갔는가로 정했고. 장애물 천장에 부딪히면 -5라는 큰 패널티 줬음. 잘 안됨.
⇒ AddObservation()만 호출하고 BehaviourParameters에 Vector Observation Space Size를 늘리지 않았음. 잘못된 실험.
조금 밑으로 미끄러졌다고 점프하면 좋겠는데, 그거는 안되고 무조건 뛰기만 한다. 그래서 높이에 따른 보상은 1/10 해보았다. 그래도 결과는 비슷. 무조건 점프함.
차일드 센서에 Wall과 Door 태그를 분리해보았다. Door라는게 구분되면 뭔가 더 잘하지 않을까? 아니네.
훈련 설정을 바꿔봄.
훈련이 불안정하고 보상이 늘지 않으면 hyperparameters > learning_rate를 낮춰보라고 함.
entropy가 급격이 떨어지면 beta를 늘려보라고 함.
점프와 장애물 통과 사이에 시간간격이 있으므로 액션과 보상의 시간차에 따른 디스카운트 값인 reward_signals > extrinsic > gamma를 높여봄
엔트로피는 기존보다 조금 완만한게 하강하지만 여전히 학습은 불안정함

느낀점

얕은 지식으로 흉내는 낼 수 있지만 효과적으로 하기는 어렵다
유니티 샘플 보면서 다양한 상황에서 어떻게 설정하고 구현하는지 봐야겠다
머신러닝 + 강화학습 이론 공부를 해야겠다
잘만 쓴다면 적용할 수 있는 곳이 많을 것 같다