소개

OpenAI에서 Reinforcement Learning을 쉽게 연구할 수 있는 환경을 제공하고 있는데 그중에 하나를 OpenAI Gym 이라고 합니다. 여러가지 게임환경과 환경에 대한 API를 제공하여 Reinforcement Learning을 위해 매번 게임을 코딩할 필요 없고 제공되는 환경에서 RL의 알고리즘만 확인을 하면 되기에 편합니다.

그런데 R에서 바로 gym을 사용하는건 불가능하고 Python에서 웹서버를 구동하여 REST API로 연결하는 방식으로만 가능합니다. 그래서 Python이 설치 되어 있는 것을 가정합니다.

설치과정

$ pip install gym
$ git clone https://github.com/openai/gym-http-api
$ cd gym-http-api
$ pip install -r requirements.txt
install.packages('gym')

설치 확인 및 gym 예제

  1. 먼저 git clone한 gym-http-api 안에 gym_http_server.py 를 실행합니다.
$ python gym_http_server.py
  1. R에서 다음과 같이 프린트 되어 나오면 성공입니다.
library(gym)
remote_base <- "http://127.0.0.1:5000"
client <- create_GymClient(remote_base)
print(client)
## <GymClient: http://127.0.0.1:5000>
  1. 가장 기본적인 CartPole 환경(env)를 실행해보겠습니다. CartPole은 막대를 세워서 중심을 잡는 게임입니다. 손바닥이 아니라 카트 위에 막대기를 올려둔 것이라 CartPole이라고 합니다.
env_id <- "CartPole-v0"
instance_id <- env_create(client, env_id)
for (iEpisode in 1:10) {
  ob <- env_reset(client, instance_id)
  for (i in 1:100) {
      action <- env_action_space_sample(client, instance_id)
      results <- env_step(client, instance_id, action, render = TRUE)
      if (results[["done"]])
        break
  }
}
env_close(client, instance_id)

그럼 아래와 같은 게임이 실행되면서 막대기의 중심을 잃으면서 게임이 끝나는 걸 볼 수 있습니다.

중심을 잃는 순간 게임 종료

중심을 잃는 순간 게임 종료

위 예제 코드로 보는 gym 라이브러리 설명

게임 인스턴스 생성

env_id <- "CartPole-v0"
instance_id <- env_create(client, env_id)
  • gym에서 CartPole-v0 게임의 인스턴스를 생성합니다. 게임 종류는 https://gym.openai.com/envs에서 확인 가능합니다.

반복 구문

for (iEpisode in 1:10) {
  ob <- env_reset(client, instance_id)
  for (i in 1:100) {
      ...
  }
}
  • 게임을 한번 시작해서 끝날 때까지를 에피소드라고 합니다. 그래서 10번의 게임을 반복하겠다는 뜻입니다.
  • env_reset(client, instance_id)는 해당 게임을 초기화 시켜주고 초기 State(Observation)값을 제공합니다.
  • 두번째 Loop은 Timestep이며 게임을 얼마나 진행할지를 나타냅니다. 학습이 잘되어서 막대기가 쓰러지지 않을 경우 무한대로 서있기 때문에 어느 정도 기간이 지나면 끝나도록 합니다.

액션 선택

action <- env_action_space_sample(client, instance_id)
  • env_action_space_sample(client, instance_id)는 해당 게임에서 가능한 Action들중 랜덤으로 선택해 돌려줍니다. CartPole-v0은 2개의 action (0 - Move Left or 1 - Move Right)을 가지고 있습니다.
  • env_action_space_info 함수로 Action에 대한 정보 화인이 가능합니다.
    • n 액션의 갯수
    • name 액션의 타입을 말해줍니다. 예제에서는 Discrete이고 좌표나 여러 타입이 있습니다.
env_action_space_info(client, instance_id)
## $n
## [1] 2
## 
## $name
## [1] "Discrete"

메인 env_step 함수 설명

results <- env_step(client, instance_id, action, render = TRUE)
results
## $observation
## $observation[[1]]
## [1] -0.03906595
## 
## $observation[[2]]
## [1] -0.2278388
## 
## $observation[[3]]
## [1] -0.02720497
## 
## $observation[[4]]
## [1] 0.2347456
## 
## 
## $reward
## [1] 1
## 
## $done
## [1] FALSE
## 
## $info
## named list()

env_step(client, instance_id, action, render=TRUE) 는 action을 실행하고 list를 돌려줍니다. list의 요소는 다음과 같습니다.

  • observations
    • 액션을 행한 후 그 다음 상태(state or obeservation)를 반환
  • reward
    • 액션을 행한 후 그에 대한 보상을 반환
    • 예시) 막대기가 쓰러지지 않고 밸런스를 유지하면 +1 실패하면 0 게임이 종료
  • done
    • 게임이 종료되었는지 여부 TRUE/FALSE
  • info
    • 게임에 대한 기타 필요 정보들이 있을 경우 여기에 포함 되어 있습니다.

기초 RL 예제 (Hill Climbing)

랜덤 액션

Baseline으로 사용하기 위해 먼저 위에서 봤던 랜덤 액션의 경우 기대값이 어떻게 되는지 대략적으로 다시 알아보겠습니다. 랜덤으로 게임을 돌릴 경우 거의 시작과 동시에 막대기가 기울면서 게임이 종료 되게 됩니다. 10번을 반복할 경우 아래와 같은 값이 나옵니다.

Random Action

Random Action

env_id <- "CartPole-v0"
instance_id <- env_create(client, env_id)
total_reward = c()
for (iEpisode in 1:10) {
  ob <- env_reset(client, instance_id)
  one_episode_reward = 0
  for (i in 1:100) {
      action <- env_action_space_sample(client, instance_id)
      results <- env_step(client, instance_id, action, render = FALSE)
      one_episode_reward = one_episode_reward + results$reward
      if (results$done) {
          total_reward = append(total_reward, one_episode_reward)
      }
  }
}
env_close(client, instance_id)

mean(total_reward) # 평균 값
## [1] 16.59453
max(total_reward) # Max
## [1] 26
min(total_reward) # Min
## [1] 9

Hill Climbing

Hill Climbing의 경우 조금씩 값을 변화하면서 좋은 값이 나올때마다 세이브하는 방식입니다. 우슨 학습이 된 결과를 보면 아래와 같이 밸런스를 잘 유지하게 됩니다. 움직이지 않는 것처럼 보이지만 잘보면 왼쪽 오른쪽으로 빠르게 움직이고 있습니다. DQN과 같은 Q-Learning이나 PG로 이렇게 학습하려면 한참 학습을 해야 하기에 이런 간단한 경우는 오히려 Hill Climbing으로 간단하게 학습 시킬 수 있습니다. Hills Climbing

Hill Climbing 전체 코드

library(gym)

setUp = function(url, name) {
    client = create_GymClient(url)
    instance_id = env_create(client, name)
    
    return(list(client=client, instance_id = instance_id))
    
}

toMatrix = function(z, ncol) {
  return(matrix(unlist(z), ncol=ncol))
}

chooseAction = function(observation, theta) {
    p = toMatrix(observation, 4) %*% theta
    
    if (p < 0) {
        return(0)
    } else {
        return(1)
    }
}

runEpisode = function(client, instance_id, theta, timesteps=10000, render=F, test=F) {
    observation = env_reset(client, instance_id)
    reward_sum = 0
    
    if (test) {
        timesteps = 10000000
    }
    
    for(i in 1:timesteps) {
        action = chooseAction(observation, theta)
        result = env_step(client, instance_id, action, render)
        reward_sum = reward_sum + result$reward
        
        observation = result$observation
        
        if (result$done) {
            break
        }
    }
    return(reward_sum)
}

trainTheta = function(client, instance_id, scaling_factor = 0.1, target_reward=200, episodes=100, log=F) {
    best_theta = NULL
    best_reward = 0
    theta = matrix(rnorm(4), 4, 1)
    
    prepare_result = function(reward, theta, success) {
        result = list()
        result$theta = theta
        result$reward = reward
        result$success = success
        return(result)
    }
    
    for(i in 1:episodes) {
        newTheta = theta + matrix(rnorm(4), 4, 1) * scaling_factor
        assertthat::are_equal(dim(theta), dim(newTheta))
        reward = runEpisode(client, instance_id, newTheta)
        
        if(log) {
            print(paste0("REWARD: ", reward, " EPISODE: ", i, " BEST REWARD: ", best_reward))
        }
        
        if(reward > best_reward) {
            best_reward = reward
            theta = newTheta
            
            if (best_reward > target_reward) {
                return(prepare_result(best_reward, theta, T))
            }
        }
        
    }
    prepare_result(best_reward, theta, F)
}


remote_base <- "http://127.0.0.1:5000"
env_id <- "CartPole-v0"
env = setUp(remote_base, env_id)
client = env$client
instance_id = env$instance_id

result = trainTheta(client, instance_id, scaling_factor = 0.3, target_reward = 1500, episodes=1000, log=T)
if (result$success) {
    runEpisode(client, instance_id, result$theta, render=T)
}

env_close(client, instance_id)