How to Train Agent Playing Pong from Andrej Karpathy


< 목차 >


머신 러닝 자료들을 구글링 하다보면 세계 각지의 석학들의 블로그들을 마주하게 됩니다. 2022년 현재 Tesla 에서 머신러닝 팀을 리딩하고 있는 Andrej Karpathy 는 박사과정 중 cs231n 의 조교일을 하면서 많은 이들에게 친숙한 연구자 일텐데요, 그의 블로그에 Pong이라는 게임을 플레이하는 Agent를 학습하는 코드가 100줄 남짓한 numpy 코드로 작성되어 있는걸 발견했습니다.

pong Fig. Pong 게임을 플레이하는 Agent

그래서 머리도 식힐겸… 이거나 돌려보자 싶었서 글을 쓰게 되었습니다.

Setup

가지고 있는 맥북이 gpu도 없고 모든 연구와 개발은 서버에서 하니 MacOS에 뭔가를 세팅할 일이 없었습니다…

그래서 먼저 아래처럼 환경설정을 좀 해줍니다.

brew install wget &&\
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh &&\
bash Miniconda3-latest-MacOSX-x86_64.sh &&\
echo "export PATH=~/miniconda3/bin:$PATH" >> ~/.zshrc &&\
. ~/.zshrc &&\
conda update -n base -c defaults conda
conda create -y -n py38 python=3.8 &&\
. activate py38
# conda remove --name py38 --all

그리고 numpygym을 깔아줍시다. (Pong이 최신버전 gym에서 이슈가 있는거 같아 구버전으로 설치해줍니다.)

pip install numpy &&\
# pip install gym\[all\] &&\
pip install gym\[atari\]==0.21 &&\
pip install gym\[accept-rom-license\]

잘 깔렸는지 체크해봅시다.

(py38) user@AL01947270 playground % python -c "import gym; print(gym.__version__); import numpy; print(numpy.__version__)"
0.21.0
1.22.3

OpenAI Gym

Openai gym 라이브러리에는 강화학습을 하는 데 필요한 굉장히 많은 환경이 있습니다.

아래처럼 쳐보면 등록된 환경들을 볼 수 있는데요,

from gym import envs
print(envs.registry.all())
├──Hopper: [ v2, v3 ]
├──Swimmer: [ v2, v3 ]
├──Walker2d: [ v2, v3 ]
├──Ant: [ v2, v3 ]
├──Humanoid: [ v2, v3 ]
└──HumanoidStandup: [ v2 ]
)

이 중에서 몇 개 실행해보도록 합시다.

import gym
env = gym.make("CartPole-v1")
observation, info = env.reset(seed=42, return_info=True)

for _ in range(1000):
    env.render()
    action = env.action_space.sample()
    observation, reward, done, info = env.step(action)

    if done:
        observation, info = env.reset(return_info=True)
env.close()

cartpole 환경을 만들고 랜덤 액션을 샘플링해서 렌더링해보니 잘 되는군요.

cartpole Fig. Cartpole 을 플레이하는 Agent

벽돌 깨기 게임 환경도 한번 만들어봅시다.

import gym
env = gym.make("ALE/Breakout-v5")
observation, info = env.reset(seed=42, return_info=True)
for _ in range(10000):
   env.render()
   # action = policy(observation)  # User-defined policy function
   action = env.action_space.sample()
   observation, reward, done, info = env.step(action)

   if done:
      observation, info = env.reset(return_info=True)
env.close()

근데 이렇게하면 2022년 기준 오류가 납니다.

  File "/Users/user/miniconda3/envs/py38/lib/python3.8/site-packages/gym/envs/atari/environment.py", line 283, in render
    raise error.Error(
gym.error.Error: render(mode='human') is deprecated. Please supply `render_mode` when constructing your environment, e.g., gym.make(ID, render_mode='human'). The new `render_mode` keyword argument supports DPI scaling, audio, and native framerates.

카트폴은 잘 되는데 아타리 게임들에서만 이러는걸 보니 아타리 환경에 대한 관리를 다른 라이브라러리로 넘긴거 같고 그래서 env.render() 가 안되는거 같습니다.

아래처럼 env.render() 대신 envrender_mode 옵션을 주면 잘 되는걸 확인할 수 있습니다.

import gym
env = gym.make("ALE/Breakout-v5", render_mode='human')
# env = gym.make("Breakout-v0", render_mode='human')
observation, info = env.reset(seed=42, return_info=True)
for _ in range(10000):
   # env.render()
   # action = policy(observation)  # User-defined policy function
   action = env.action_space.sample()
   observation, reward, done, info = env.step(action)

   if done:
      observation, info = env.reset(return_info=True)
env.close()

breakout Fig. Breakout 게임을 플레이하는 Agent

당연하게도 학습한 모델 (User-defined policy) 이 액션을 뱉는게 아니라 랜덤 샘플링을 하고 있기 때문에

# action = policy(observation)  # User-defined policy function
action = env.action_space.sample()

형편없이 게임을 하고 있는 모습을 볼 수 있습니다.

Training Agent Playing Pong with REINFORCE

with numpy

이제 Andrej Karpathy 의 코드로 Pong을 플레이하는 Agent를 학습 시켜볼겁니다.

강화학습의 알고리즘은 Policy를 explicit 하게 학습하느냐 아니냐에 따라서 크게 Value-based, Policy-based 로 나눌 수 있는데요, Karpathy의 코드는 Policy-based Algorithm 중에서도 기초 중의 기초인 REINFORCE라는 알고리즘 입니다.

Andrej Karpathy의 코드 는 python 2.x 에서 작성됐고 몇 가지 이슈가 있어서 몇줄 수정한 아래의 코드를 쓰시면 될 것 같습니다.

안드레가 작성한 코드는 완전히 numpy로만 이루어져 있는데요, 즉 torch 같은 머신러닝 프레임워크를 쓴 게 아니기 때문에 자동 미분 기능이 없어서 네트워크의 레이어는 물론 forward, backward 하는 부분을 전부 구현해 준 걸 볼 수 있습니다.

학습 초기의 애니메이션을 뽑아보면 아래처럼 보이는데요, (아 렌더링을 하면 학습 속도를 저하시킬 수 있습니다. 근데 저는 그냥 재미로 하는것이기 때문에 Agent가 생각없이 탁구를 하는 모습이 보고싶어서 렌더링했습니다…)

pong_init Fig. Pong 게임을 플레이하는 Agent

당연히 진짜 못합니다.

ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0
ep 0: game finished, reward: -1.0

에피소드 끝날 때 reward가 -1 이면 죽은건데요, 그니까 하염없이 죽기만 한거죠.

파라메터를 업데이트하다보면 좀 더 잘해지겠지만 강화학습은 랜덤 시드에 따라서 수렴을 할 수도있고 못 할수도 있는 Variance가 큰 방법론이라서 꽤 iteration을 많이 진행해야 합니다. (이런 간단한 게임은 안그러겠지만 복잡한 RL task는 한세월 돌려도 수렴 못할수도 있다고 합니다, 근데 또 랜덤시드 바꾸면 수렴 할 때도 있는…)

(REINFORCE 는 너무 간단하니 넘어가도록 하겠습니다. 제 블로그 내 다른 자료를 참고해 주세요.)

Andrej's insight

사실 2022년이나 되어서 ‘자동미분도 다 되고 알고리즘도 한줄이면 정리되는데 왜 이런짓을?’ 이라고 생각하실 수 있습니다. 뭐 REINFORCE는 알고리즘도 매우 간다하고요…

당연히 Scratch부터 직접 작성해보고 에이전트를 학습해보는게 의의일 수도 있는데요, Andrej의 Post 를 보면 그의 인사이트를 조금 엿볼 수 있습니다.

with torch

with other algorithm

Training with GPU

Refernece