강화학습/RL 강의 정리

모두를 위한 RL강좌 정리하기(Lecture 4 ~ Lab 4)

APinCan 2020. 8. 12. 22:04

Lecture 3에서 프로토타입(?)같은 큐러닝을 배웠다면 여기서는 완전한 큐러닝을 배워보자

 

일단 저번시간에 본 큐테이블이다. 위의 경우는 에이전트가 우연히 발견한 길인데 딱 보기에도 에이전트가 목표지점에 가는 최적화된 길이 아님을 알 수 있다. 근데 이전에 배웠던 큐함수 식으로 큐값을 업데이트하게 된다면 랜덤하게 길을 찾고 한번 발견한 길로만 계속 가기 때문에 더 좋은 길이 있어도 그 길로 가지 않는다. 그래서 기존의 길이 아닌 새로운 길을 탐험하는 exploration이 필요하다.

강의에서는 이 exploit과 exploration을 음식점을 고르는 것에 비유했다. 내가 음식점을 가던 곳만 가면 더 맛있고 괜찮은 곳이 있어도 알 수가 없다. 그러니까 exploration을 해서 더 좋은 곳을 찾는다는 말이다. 

이런 exploration, exploit을 큐함수에 적용시킨 식이 위와 같다. e=0.1로 설정하고 if문을 수행하는데 이 말은 10%의 확률로 랜덤한 action을 선택(exploration)하고 90%의 확률로 가장 큰 큐함수를 선택(exploit)하겠다는 말이다. 근데 exploration은 결국 큐함수가 큰 방향이 아닌 랜덤으로 action을 선택하는 것이기 때문에 학습을 진행하면 할수록 원하는 action을 제대로 선택하게 만들어야 한다. 그래서 시간이 지나면 지날수록 e 값을 감소시켜 큐값이 가장 큰 것을 제대로 선택하게 한다. 이 알고리즘을 통해 큐함수를 선택하는 것을 decaying E-greedy라고 한다.

위의 E-greedy 이외에 exploration을 하는 다른 방법들이 있다. random noise라는 방법이 있는데 이는 각각의 큐함수에 말그대로 랜덤한 노이즈를 추가하는 것이다. 그림을 보면 원래의 큐값에 랜덤한 노이즈를 추가해 새로운 큐값을 만든 것을 볼 수 있다. 그리고 에이전트가 이 큐값 중 가장 큰 값을 선택하는게 이 random noise 방법이다.

그리고 이 방법 역시 큐값에 새로 더하는 random_value값들을 시간이 지날수록 감소시켜 노이즈의 영향을 줄인다. 이렇게 해서 에이전트는 점점 exploration을 줄이고 가장 큰 큐값을 가진 action을 선택한다(exploit).

이렇게 해서 에이전트는 현재 알려진 길뿐만 아니라 탐험을 하며 다른길을 탐색한다.

근데 결국 탐험을 해 새로운 길을 찾아도 위와 같이 모든 큐값이 1이라면 에이전트는 어디가 정말로 좋은 길인지 알 수가 없다. 그렇기 때문에 에이전트가 받는 reward에 discount factor를 적용한다. discount factor는 나중에 받을 reward보다 현재 받을 reward에 더 많은 가중치를 두는 것이다. 즉 나중에 받을 reward보다 현재 받을 reward를 더 중요시하는 것이다.

예를 들자면 누가 나한테 100만원을 주는데 지금 받을지 10년 뒤에 받을지 선택한다고 해보자. 현재의 100만원보다 10년 뒤에 100만원의 가치는 물가상승률때문에 더 낮을 것이다. 그러니 현재에 받는게 더 이득이다. 이 개념에서는 우리가 옛날에 들었던 마시멜로 이야기(기다리면 더 많은 마시멜로를 먹을 수 있다는 이야기)는 적용되지 않는다.

이 discount 개념을 식으로 표현한 것이 위와 같다.

나중에 받을 reward일 수록 discount factor를 더 많이 곱한 것이다. 이 discount factor(gamma)는 기본적으로 1보다 작다. 이는 곧 현재의 큐값은 이번에 받을 reward + 다음 상태에서의 큐값 * gamma가 된다.

이 discounted reward를 적용해 큐함수는 업데이트될 것이고 에이전트는 큐함수들을 보며 더 높은 큐함수를 선택해 더 좋은길을 선택할 수가 있게 된다.

여기까지 배운 큐러닝 알고리즘은 다음과 같다.

action을 선택할 때는 Exploit & Exploration을 사용하고 큐값을 업데이트 할 때는 gamma를 사용해 reward를 discount시킨다.

우리는 아직 진짜 Q값이 무엇인지 모르기 때문에 이에 대한 근사치를 \(\hat{Q} \)이라 했다. 이 \( \hat{Q} \)값을 계속 업데이트하면 결국 진짜 Q값에 수렴하게 된다. 근데 여기에는 조건이 있는데 deterministic world(이에 대한 반대로 stochastic world)가 있다. 그리고 finite state, 즉 상태가 한정되어 있어야 한다

 

이제 코드를 짜보자.

import gym
import numpy as np
import matplotlib.pyplot as plt
from gym.envs.registration import register

register(
    id='FrozenLake-v3',
    entry_point='gym.envs.toy_text:FrozenLakeEnv',
    kwargs={'map_name':'4x4',
           'is_slippery': False}
)

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

Q = np.zeros([env.observation_space.n, env.action_space.n])

# discount factor
dis=.99
num_episodes=2000
rList=[]

import와 초기화

환경을 생성하고 큐테이블을 16,4의 배열로 생성하고 0으로 초기화한다. discount factor의 초기값을 0.99로 설정한다. 이는 나중에 받을 reward에 대해서 discount를 덜하겠다는 뜻이다. 에피소드는 2000번 진행한다. 그리고 출력을 위한 rList를 초기화한다.

 

for i in range(num_episodes):
    state = env.reset()
    rAll=0
    done=False
    
    while not done:
    	# add random noise
        action = np.argmax(Q[state, :] + np.random.randn(1, env.action_space.n) /(i+1) )
        
        new_state, reward, done, _ = env.step(action)
        
	# discount factor
        Q[state, action] = reward + dis* np.max(Q[new_state, :])
        
        rAll += reward
        state = new_state
    
    rList.append(rAll)
for i in range(num_episodes):
    state = env.reset()
    rAll=0
    done=False
    
    e = 1. /((i // 100)+1)
    
    while not done:
        if np.random.rand(1) < e:
            action = env.action_space.sample()
        else:
            action = np.argmax(Q[state, :])
        
        new_state, reward, done, _ = env.step(action)
        
        Q[state, action] = reward + dis* np.max(Q[new_state, :])
        
        rAll += reward
        state = new_state
    
    rList.append(rAll)

사실 저번에 봤던 큐러닝 코드와 크게 다르진 않은데 random noise를 넣는 부분과 discount factor를 곱하는 부분이 추가되었다. 이따 E-greedy를 적용한 코드도 있는데 일단 random noise를 먼저 본다.

np.random.randn은 가우시안 표준 정규분포로 난수를 생성하는데 (1,4)의 행렬로 생성한다. 그리고 시간이 지날수록 이 random noise의 영향력을 줄인다. 큐함수를 업데이트할 때는 dis=0.99를 곱해서 큐함수를 업데이트한다.

그 밑에는 E-greedy 방식으로 action을 선택하는 방법이다. 처음에는 e=1.0이니까 무조건 exploration을 하고 시간이 지날수록 e값은 감소한다. 만약 exploration을 한다면 env.action_space.sample()로 랜덤한 action을 하나 선택한다.

 

학습시킨 후 큐값은 다음과 같다. 위가 random noise이고 아래고 E-greedy이다. E-greedy가 random noise보다 success rate가 더 낮은 것을 볼 수 있다.

random noise의 경우 랜덤이긴 하지만 기존의 큐값에 더한 것이기 때문에 기존 큐값이 충분히 크다면 exploration을 덜할 수 있다. 반면 e-greedy는 그런것 없이 초반부터 exploration을 쭈욱 했기 때문에 success rate가 더 낮아보인다. 또한 큐값들을 0에서 다른 값으로 업데이트된 곳이 많은 걸로 봐서는 에이전트가 exploration을 하며 여러 곳을 많이 다녔음을 알 수가 있다.

근데 이러나저러나 두 방법 모두 optiaml policy로 수렴할 것이다.

 

 

Reference:

[1]: http://hunkim.github.io/ml/

[2]: youtu.be/MQ-3QScrFSI

[3]: youtu.be/VYOq-He90bE