강화학습/OpenAI gym

OpenAI gym을 이용해 MonteCarlo prediction 구현하기

APinCan 2020. 3. 19. 22:42

이번 구현에도 역시 OpenAI의 FrozenLake-v0를 이용해서 구현해보자

몬테카를로 예측의 경우 에이전트가 환경에서 실제로 받은 reward들의 각 state에서 return을 계산해 그것들의 평균으로 참 가치함수를 예측하는 것임. 이제 이 이론을 가지고 코드를 짜보자

 

 

import gym
import numpy as np
import random

GAMMA=0.9
EPISODES=1000
POLICY = [0.25, 0.25, 0.25, 0.25]
THRESHOLD = 1e-20

얼음호수 예제의 경우 action이 4개이고 각 action을 할 확률 그러니까 policy는 모든 action이 동일하다고 가정. 그리고 이 policy에 대한 예측을 시작.

 

env = gym.make('FrozenLake-v0')

얼음호수 환경 만들기

 

# 하나의 에피소드에서 return값들을 계산
def episode_one(env):
    # 지나간 state들과 받은 reward들을 저장
    received_rewards = []
    passed_states = []
    
    # 타임스텝
    for t in range(1000):
        rand_prob = random.random()
        
        sum_policy=0
        # policy에 따른 action 계산
        for idx, action_prob in enumerate(POLICY):            
            sum_policy += action_prob
            if rand_prob <= sum_policy:
                action = idx
                break
        
        observation, reward, done, info = env.step(action)
        if observation in passed_states:
            pass
        else:
            received_rewards.append(reward)
            passed_states.append(observation)
                
        if done:
            break
            
    return received_rewards, passed_states

한번의 에피소드에서 수행해야할 작업들이 있는 코드. 

전체적으로는 에피소드의 끝을 만날때까지 타임스텝을 진행하며 거기서 받은 reward들을 received_rewards에 저장하는 것인데 중간에 policy에 따라 랜덤하게 aciton을 선택함.

그 aciton을 통해 환경에서 state(observaiton), reward, done(에피소드 종료 여부), info를 받음.

만약 에피소드 내에서 지나간 state를 다시 지나간다면 나중에 지나갔을 때의 reward를 무시하기 위해 passed_state에 지나간 state들과 received_rewards에 state에서 받은 reward들을 저장함. 그러니까 한번 지나간 state를 다음에 또 지나가면 그 때 지나간건 무시하겠다는 소리

 

def calculate_returns(rewards, states):
    rewards.reverse()
    states.reverse()
    returns = []
    return_ = 0
    
    for idx, state in enumerate(states):
        if idx==0:
            returns.append(rewards[idx])
        else:
            returns.append(rewards[idx] + GAMMA*returns[idx-1])
            
    return returns

여기서는 앞에서 받은 reward들을 이용해 각 state마다 return값을 계산함.

return값을 계산하기 위해서 나중에 받은 state와 reward들을 앞으로 오게 하기 위해서 reverse()를 시킴. 그리고 return값을 계산하기 위해 Reinforcement Learning 책 읽고 공부하기(3-2) 여기에 써놓은 return값을 계산하는 공식을 참고함. return 값을 계산하기 위해서는 다음 staate의 return값을 알아야 하기 때문에 뒤에서 부터 계산하는게 편함.

이렇게 해서 reward와 state를 reverse()시켜 시간 순서대로 바꾸고 return값 역시 뒤에서부터 계산한 것이기 때문에 시간순서대로 바꿔줌.

 

def calculate_value(value_func, states, returns, returns_number):
    after_value_func = np.copy(value_func)
    
    for idx, state in enumerate(states):
        after_value_func[state] = after_value_func[state] + 1/returns_number[state]*(returns[idx] - after_value_func[state])
            
    return after_value_func

이제 구한 return값을 이용해 value를 예측해 보는 코드.

앞에서 reward를 받은 state를 탐색해가면서 받은 return값들의 평균으로 value를 구함. 참고로 value function을 구하는 공식은 Reinforcement Learning 책 읽고 공부하기(2-2)에 나온 incremental implementation을 참고한 것임.

 

value_func = np.zeros(env.observation_space.n)
returns_number = np.zeros(env.observation_space.n)

for epi in range(EPISODES):
    observation = env.reset()

    rewards, states = episode_one(env)
    returns = calculate_returns(rewards, states)
    returns_number[states]+=1
    after_value_func = calculate_value(value_func, states, returns, returns_number)

    if np.sum(np.fabs(after_value_func - value_func)) <= THRESHOLD and np.sum(value_func) !=0:
        print("EPISODE break at {} episode".format(epi+1))
        break
    
    value_func = np.copy(after_value_func)

앞에서 본 코드들을 종합해 통합시킴.

에피소드를 진행하며 reward와 state를 얻고 return값을 얻은 state들은 return 값을 얻은 개수를 표시해주기 위해 +1을 해줌. 그리고 value function을 계산

계산 전과 계산 후의 value function을 비교해 차이가 별로 없으면 몬테카를로 예측을 종료시킴.

 

그렇게 해서 결과는 밑의 출력과 같음

근데 계속 돌리다보니 다른 값이 나옴

이렇게도 나오고
저렇게도 나오고

 

이건 왜이렇게 나오는지 문제점을 찾아보자

 

 

일단 FrozenLake 문제의 경우 오직 목표지점에 도달했을때만 reward로 1을 받고 나머지 경우에는 reward로 0을 받는데 여기서 문제가 optimal value를 찾을 경우 반복을 그만두게 하는 np.sum 부분이 문제인 듯함.

바로 이부분

if np.sum(np.fabs(after_value_func - value_func)) <= THRESHOLD and np.sum(value_func) !=0:
        print("EPISODE break at {} episode".format(epi+1))
        break

이게 왜 문제냐면 내가 만약 현재 에피소드에서 골을 찾아가 reward를 정상적으로 +1을 받았음. 근데 다음 에피소드에서 reward를 받지 못하면 결국 현재 value와 다음 value는 차이가 없게됨. 그래서 결국 optimal value를 찾지도 못했는데 break 조건에 의해 게임이 끝나게 되는 것임.

 

자 그러면 이 부분을 주석처리하고 다시 돌려보자

반복은 50만번으로 설정. 이렇게 해야 어딘가에 수렴하는게 좀 보임

이렇게 보면 FrozenLake에서 귀신같이 장애물이 있는 곳은 value가 0인 것을 볼 수 있음.

그 다음 100만번 돌렸을 때

값이 크게 바뀌지 않는 모습을 보여줌 아마 2백만번이건 5백만번이건 더 돌린다면 진정한 optimal value를 찾지 않을까?

 

끝으로 백만번을 돌리면서 다른 작업도 무리없이 하게 해주는 라이젠에게 감사. 아마 그전의 i5-6600으로 이거 돌리면서 딴거하면 버벅였을텐데 역시 r5 3600인듯