ML & DL

[강화 학습 PyTorch] Reinforcement Learning Tutorial (Part 1)

ssun-g 2022. 4. 29. 12:35
gym library의 CartPole을 사용하여 강화 학습에 입문해보자.

CartPole 게임이란?

  1. 마찰이 없는 트랙에 카트(cart)하나와 카트에 연결된 막대(pole)가 하나 있다.
  2. 막대가 세워진 상태로 게임이 시작되며, 목표는 막대가 넘어지지 않도록 하는 것이다.
  3. 막대는 조작할 수 없으며 오직 카트만 조작 가능하고, 막대가 서 있는 매 시간 스텝마다 +1의 보상이 주어진다.

에피소드가 종료되는 조건은 다음과 같다.

  • 막대가 수직으로부터 12도 이상 기울어짐. (-12 ~ 12)
  • 카트가 중심으로부터 2.4이상 벗어남 (-2.4 ~ 2.4)
  • 시간 스텝이 200보다 커짐 (CartPole-v1의 경우 500보다 커졌을 때)

100번의 시도에서 평균 195.0 이상의 보상을 얻으면 게임을 해결했다고 정의한다.

 

gym

gym library를 사용하여 진행해보자.

pip install gym

또는

git clone https://github.com/openai/gym
cd gym
pip install -e .

관찰 (Observations)

일반적인 (에이전트-환경) 사이의 반복 과정은 다음과 같다.

각 스텝마다 에이전트는 행동을 선택하고 환경은 관찰 값, 보상을 반환한다.

 

이 때, 환경이 반환하는 값들은 총 4가지로 다음과 같다.

  1. observation
    환경에 대한 관찰을 나타내는 객체이며 환경에 따라 달라진다. 예를 들어, 카메라에서 얻어지는 pixel, 로봇 관절의 각도나 속도, 보드게임의 상태 등이 될 수 있다.
  2. reward
    이전 행동으로 인해 얻을 수 있는 보상의 양. 그 크기는 환경에 따라 달라지지만 최종 목표는 보상을 최대로 받는 것이다.

  3. done
    환경을 다시 시작해야 할지에 대한 여부. True일 경우 에피소드가 종료 되었음을 의미한다.

  4. info
    디버깅에 유용한 진단 정보. 학습에 유용할 경우가 종종있다. (환경의 마지막 상태 변화를 위한 확률 같은 정보가 될 수 있음) 하지만, 에이전트가 공식적인 평가에 이를 사용할 수는 없다.

위 과정은 reset() 함수로 시작된다.

 

observation

[카트의 위치, 카트의 속도, 막대의 각도(radian), 막대의 회전율] 을 반환한다.

import gym

env = gym.make("CartPole-v1")
observation = env.reset()

print(observation)
출력
[-0.04578666  0.03549419  0.04340602  -0.00676553]

 

action

게임 환경에서 선택할 수 있는 행동. 항상 0 또는 1을 반환한다.

  • 0: 카트가 좌측으로 이동.
  • 1: 카트가 우측으로 이동.
import gym

env = gym.make("CartPole-v1")
observation = env.reset()
action = env.action_space.sample()

print(action)
출력
1

 

step

(observation, reward, done, info)를 반환한다.

import gym

env = gym.make("CartPole-v1")
observation = env.reset()
action = env.action_space.sample()
step = env.step(action)

print("First observation:", observation)
print("Action:", action)
print("Step:", step)
출력
First observation: [ 0.00492155 -0.00359811 -0.005484 -0.01001996]
Action: 1
Step: (array([ 0.00484959, 0.19160207, -0.00568439, -0.3044281 ], dtype=float32), 1.0, False, {})

예시를 살펴보면 다음과 같다.

  • 행동 1을 수행 했을 때의 observation: [ 0.00484959, 0.19160207, -0.00568439, -0.3044281 ]
  • 스텝 당 보상 1.0이 주어진다.
  • 에피소드가 아직 종료되지 않았다. (False)

 

done

import gym

env = gym.make("CartPole-v1")

for i_episode in range(20):
    observation = env.reset()

    for t in range(100):
        env.render()
        print(observation)
        action = env.action_space.sample()
        observation, reward, done, info = env.step(action)

        if done:
            print("Episode finished after {} timesteps".format(t + 1))
            break

env.close()
출력
[ 0.04181011  0.00650144 -0.01220737 -0.00726616]
[ 0.04194014  0.20179631 -0.01235269 -0.30377552]
[ 0.04597607  0.00685256 -0.01842821 -0.01501381]
[ 0.04611312  0.20223388 -0.01872848 -0.3134536  ]
[ 0.05015779  0.39761755 -0.02499755 -0.6119836  ]
[ 0.05811014  0.5930798   -0.03723723 -0.91243386]
[ 0.06997174  0.7886852   -0.0554859   -1.2165838  ]
[ 0.08574545  0.594321     -0.07981758 -0.9417907  ]
[ 0.09763186  0.40036017 -0.09865339 -0.6752171  ]
[ 0.10563907  0.2067375   -0.11215773 -0.4151524  ]
[ 0.10977382  0.40325555 -0.12046078 -0.74098265]
[ 0.11783893  0.5998164   -0.13528043 -1.0690172  ]
[ 0.12983526  0.7964428   -0.15666078 -1.4009134  ]
[ 0.14576411  0.99312586 -0.18467905 -1.7381951  ]
Episode finished after 14 timesteps

공간 (Spaces)

위 예제에서는 CartPole 게임 환경에서 임의의 행동을 선택했다. 모든 환경은 action_spaceobservation_space를 가진다.

이러한 속성들은 공간의 한 유형이며, 유효한 행동과 관찰의 형식을 보여준다.

 

action / observation space

이 공간에서 샘플을 얻거나 무엇이 이 공간에 포함되는지 확인할 수 있다.

import gym

env = gym.make("CartPole-v1")

print(env.action_space)
print(env.observation_space)
출력
Discrete(2)
Box([-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], [4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38], (4,), float32)
  • Discrete : 고정된 범위의 음이 아닌 숫자를 허용하는 공간. 유효한 행동은 0 또는 1이 된다.
  • Box : n-차원의 박스를 나타내며, 유효한 관찰값은 4개의 숫자 array가 된다.

 

샘플 얻기 / 확인하기

from gym import spaces

space = spaces.Discrete(8)
x = space.sample()

print(space.contains(x))
print(space.n == 8)
출력
True
True

행동

 

행동 0

import gym

env = gym.make("CartPole-v1")
observation = env.reset()
print(observation)

observation, reward, done, info = env.step(0)
print(observation)

env.close()
출력
[ 0.00848651 -0.01456189 0.04437874 -0.02457321]
[ 0.00819528 -0.21029125 0.04388728   0.28177482]

에이전트가 행동 0을 수행했을 때, 카트의 속도는 감소(왼쪽 방향 속도 증가)하고 막대는 오른쪽으로 기울게 된다.

따라서, 행동 0카트가 왼쪽으로 이동하는 것이다.

 

행동 0, 반복

import gym
from gym.wrappers import RecordVideo

env = gym.make('CartPole-v1')
env.reset()

for i in range(10):
    env.render()
    observation, reward, done, info = env.step(0)
    print(observation, done)
    
    if done:
        break

env.close()
출력
[-0.0216308    -0.15845136 0.04800196  0.32576144] False
[-0.02479983  -0.3542227   0.05451719  0.6331874  ] False
[-0.03188429  -0.5500611   0.06718094  0.94252867] False
[-0.04288551  -0.74602085 0.08603151  1.2555416  ] False
[-0.05780593  -0.9421325   0.11114234  1.573883    ] False
[-0.07664858  -1.1383905   0.14262         1.8990625 ] False
[-0.09941639  -1.3347399   0.18060125   2.2323844 ] False
[-0.12611118  -1.5310591   0.22524893   2.5748825 ] True

막대의 각도가 12도(0.20944 radian) 이상 기울어 졌을 때 에피소드가 종료(True)된 것을 확인할 수 있다.

 

행동 1, 반복

import gym
from gym.wrappers import RecordVideo

env = gym.make("CartPole-v1")
env.reset()

for i in range(10):
    env.render()
    observation, reward, done, info = env.step(1)
    print(observation, done)
    
    if done:
        break

env.close()
출력
[-0.02317673 0.2221822    -0.04557218  -0.2751537  ] False
[-0.01873309 0.41792372  -0.05107526  -0.58185476] False
[-0.01037462 0.6137227    -0.06271235  -0.8901798  ] False
[ 0.00189984 0.80963695  -0.08051595  -1.2018988  ] False
[ 0.01809258 1.0057025    -0.10455392  -1.5186905  ] False
[ 0.03820663 1.2019217    -0.13492773  -1.842095    ] False
[ 0.06224506 1.3982501    -0.17176963  -2.1734593  ] False
[ 0.09021007 1.5945798    -0.21523882  -2.5138724  ] True

행동 1도 마찬가지로 막대의 각도가 12도 이상 기울어 졌을 때 에피소드가 종료 되는 것을 알 수 있다.

또한, 속도가 증가하는 것으로 보아 행동 1카트가 오른쪽으로 이동하는 것임을 알 수 있다.

 

algorithm 1

막대가 오른쪽으로 기울어져 있다면 카트를 오른쪽으로 움직이고, 그렇지 않으면 카트를 왼쪽으로 움직인다.

import gym
from gym.wrappers import RecordVideo

env = gym.make("CartPole-v1")
observation = env.reset()

for i in range(100):
    env.render()
    
    # algorithm 1
    if observation[2] > 0:
        action = 1
    else:
        action = 0

    observation, reward, done, info = env.step(action)
    print(observation, done)

    if done:
        print(i+1)
        break

env.close()
출력
[ 0.02924276 -0.17707692 -0.03054931 0.26880786] False
[ 0.02570122 -0.37174988 -0.02517315 0.55170095] False
[ 0.01826623 -0.5665094 -0.01413913 0.8363476 ] False
[ 0.00693604 -0.7614354 0.00258782 1.1245506 ] False
[-0.00829267 -0.5663475 0.02507883 0.83268046] False
[-0.01961962 -0.37157705 0.04173244 0.54798913] False
[-0.02705116 -0.17706546 0.05269222 0.26874152] False
[-0.03059247 0.01726648 0.05806705 -0.00686745] False
[-0.03024714 0.21150966 0.05792971 -0.28067905] False
[-0.02601695 0.40575948 0.05231613 -0.5545432 ] False
[-0.01790176 0.60010934 0.04122526 -0.83029515] False
[-0.00589957 0.79464424 0.02461936 -1.1097329 ] False
[0.00999331 0.9894342 0.0024247 -1.3945918 ] False
[ 0.029782 1.1845258 -0.02546714 -1.6865157 ] False
[ 0.05347252 0.9897076 -0.05919745 -1.4018695 ] False
[ 0.07326667 0.79536897 -0.08723484 -1.1282661 ] False
[ 0.08917405 0.6014913 -0.10980016 -0.8641699 ] False
[ 0.10120387 0.4080215 -0.12708355 -0.6079293 ] False
[ 0.1093643 0.21488388 -0.13924214 -0.3578212 ] False
[ 0.11366198 0.02198771 -0.14639857 -0.11208241] False
[ 0.11410174 -0.1707662 -0.14864022 0.13106799] False
[ 0.11068641 -0.36348093 -0.14601886 0.3734131 ] False
[ 0.10341679 -0.5562596 -0.1385506 0.61672664] False
[ 0.0922916 -0.7492018 -0.12621607 0.86276287] False
[ 0.07730757 -0.94240016 -0.10896081 1.1132462 ] False
[ 0.05845957 -1.1359358 -0.08669589 1.369857 ] False
[ 0.03574085 -1.3298728 -0.05929875 1.6342131 ] False
[ 0.00914339 -1.5242507 -0.02661448 1.907844 ] False
[-0.02134162 -1.7190756 0.0115424 2.192154 ] False
[-0.05572313 -1.5240669 0.05538548 1.9030539 ] False
[-0.08620448 -1.3295857 0.09344655 1.6280544 ] False
[-0.11279619 -1.1356784 0.12600765 1.3658957 ] False
[-0.13550976 -0.9423392 0.15332556 1.1151345 ] False
[-0.15435654 -0.7495256 0.17562824 0.87420845] False
[-0.16934705 -0.5571704 0.19311242 0.6414838 ] False
[-0.18049046 -0.36518994 0.2059421 0.4152888 ] False
[-0.18779425 -0.17349084 0.21424787 0.1939363 ] True
37

출력 결과를 보면 37번의 스텝 동안 에피소드가 종료되지 않았다는 것을 알 수 있다.

하나의 행동을 반복해서 선택했을 때와 비교하면 성능이 크게 향상 되었다.