close_btn
로그인, 회원가입후 더 많은 혜택을 누리세요 로그인 회원가입 닫기

2016.07.20 Leewoongwon


Reinforcement Learning 그리고 OpenAI

 

<Contents>

 

1. Introduction to OpenAI

 

2-1. Intro to Reinforcement Learning (1) MDP & Value Function

2-2. Intro to Reinforcement Learning (2) Q Learning

 

3-1. CartPole with Deep Q Learning (1) CartPole example  

3-2. CartPole with Deep Q Learning (2) DQN(Deep Q-Networks)

3-3. CartPole with Deep Q Learning (3) TensorFlow

3-4. CartPole with Deep Q Learning (4) Code review

 

4-1. CartPole with Policy Gadient (1) Policy Gradient

4-2. CartPole with Policy Gadient (2) Code review

 

5-1. about Atari games

5-2. Atari "Pong"

5-3. Atari "Breakout"

 

6. MuJoCo

 


4-2. CartPole with Policy Gadient (2) Code review

코드분석 부분은 이영무 연구원님이 작성하시고 나머지는 제가 작성하였습니다.

 

 

1. Karpathy Blog

Policy Gradient의 Code를 접하기에는 Karpathy의 코드가 좋을 듯 합니다. Karpathy가 자신의 코드에 대해서 다음 블로그에서 설명을 해 놓았습니다. 그 코드를 바탕으로 CartPole예제에 맞게 바꿔서 강화학습하였습니다. 블로그에서 Policy gradient의 개념 또한 설명해주고 있으니 참고하시면 좋을 것 같습니다.

 

http://karpathy.github.io/2016/05/31/rl/

Screenshot from 2016-07-17 00:14:28.png

 

 

Karpathy의 코드를 살짝 변형시킨 코드는 다음과 같습니다.

https://gist.github.com/zzing0907/de3665f9f7bbe9329b283da90d72049e

 

 


2. Policy Gradient 구조

이전에 DQN 코드 분석했던 것처럼 Agent의 구조와 Code의 구조를 먼저 살펴보고 코드를 보도록 하겠습니다. DQN에서는(저희가 분석했던 코드의 예) 경험들을 하나의 Training set(o, a, r, o')으로 replay memory에 저장해놓고 batch사이즈만큼 랜덤하게 뽑아서 Q-network를 학습시켰습니다. 하지만 Policy Gradient에서는 DQN과 달리 neural network에서 Q 값이 아닌 stochastic한 policy가 나오는데 이 코드에서는 이 neural network를 step마다 학습하는 것이 아니고 Monte-Carlo 방법으로서 episode가 끝날 때마다 학습합니다. 그래서인지 더 느린 감이 있습니다. 저번 글에서 Policy Gradient Theorem을 소개했었는데 그 식은 다음과 같습니다.

Screenshot from 2016-07-15 13:38:26.png

 

중요한 것은 위 식에서 Q(s,a) 이 부분 입니다. 현재 Neural Network의 output은 Policy이기 때문에 Q값을 얻는 것이 문제가 됩니다. 하지만  Neural Network를 업데이트하려면 그 값을 알아야하기 때문에 처음은 Return을 그 값을 사용할 수 있는 Monte-Carlo방법을 택하게 됩니다. 대표적인 것이 "REINFORCE" 알고리즘입니다.

Screenshot from 2016-07-15 13:48:13.png

 

위의 알고리즘을 구조적으로 표현해보자면 밑의 그림과 같습니다.

Screenshot from 2016-07-20 03:06:02.png

Policy network를 통해서 Stochastic한 값이 나오게 되는데 그 값은 0~1이 나오게 됩니다. hidden layer의 activation함수로 sigmoid를 사용하였기 때문에 0~1사이의 확률이 output으로 나오게 되는데 cartpole에서는 action이 왼쪽, 오른쪽 두 개 밖에 없으므로 1에 가까우면 오른쪽으로 갈 확률이 더 높고 0에 가까우면 왼쪽으로 갈 확률이 더 높게 되는 것입니다. Policy의 neural network는 다음과 같습니다.

Screenshot from 2016-07-24 23:54:43.png

 

 

 

 

코드의 전체 구조는 아래와 같습니다.

Screenshot from 2016-07-20 17:21:54.png

 

 

 


 

3. 코드분석

  • import
1
2
3
import numpy as np
import pickle
import gym
cs

 

먼저 import된 패키지들을 살펴보도록 하겠습니다.

 

Pickle은 데이터를 저장하는데 이용하는 패키지입니다. 네트워크의 weight를 저장할 때 이용합니다. Policy Gradient알고리즘에 관련된 것이 아니어서 데이터를 저장하는데 이용한다는 것만 알면 될 것 같습니다.

 

 

  • Parameters
    1
    2
    3
    4
    5
    6
    render = False
    hidden = 10  
    in_size = 4
    out_size = 1
    batch_size = 10  
    resume = False
    cs
    파라미터들을 정의해 주는 부분입니다.

 

hidden : hidden layer1,2에 몇 개의 유닛이 들어갈 지 결정하는 파라미터입니다. 현재 10개의 유닛이 들어가도록 되어있습니다.

 

in_size : input으로는 4개의 입력이 들어옵니다.

 

out_size : output으로는 하나의 출력이 나갑니다.


batch_size : 한 번 에피소드가 끝날 때마다 10개의 최근 에피소드들을 모아서 학습시켜주는데 그 사이즈를 결정 해주는 겁니다.

 

resume : 저장된 데이터를 불러와서 계속 학습할지를 나타내는 파라미터입니다. 이미 저장된 데이터를 불러와서 학습을 하고 싶을 때는 True로 만들어 주면 됩니다.

 

 

  • Sigmoid
    1
    2
    3
    def sigmoid(x):    
        return 1.0 / (1.0 + np.exp(-x))    
     
    cs
    sigmoid 함수를 정의하는 부분입니다. sigmoid함수는 Neural Network를 Non-Linear하게 만들어주기 위한 Activation funtion입니다. 'W1'에는 tanh나 ReLU같은 다른 함수들을 이용해도 무방하지만 보통 많이 이용되는 sigmoid를 이용했습니다. (다만 ReLU의 경우는 hidden layer가 적으면 잘 작동하지 않을도 있습니다.)  'W2'에서는 action에 대한 확률로서 결과를 0~1로 만들어 주기 위해서 sigmoid를 이용했다고 합니다. 따라서 마지막 layer는 다른 함수로 대체할 수 없습니다.

 

 


  • Agent
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def __init__(self, in_size, out_size):
        np.random.seed(1)
        self.gamma = 0.995
        self.decay_rate = 0.99
        self.learning_rate = 0.002
     
        if resume: self.model = pickle.load(open('Cart.p''rb'))
        else:
             self.model = {}
             self.model['W1'= np.random.randn(hidden, in_size) / np.sqrt(in_size)
             self.model['W2'= np.random.randn(hidden, hidden) / np.sqrt(hidden)
             self.model['W3'= np.random.randn(hidden) / np.sqrt(hidden)

     

    cs

 

모델을 initialize하는 부분입니다. 모델이란 neural network의 형태라고 보시면 될 것 같습니다.

 

gamma : discount factor입니다. reward를 discount할 때 시간에 따라 얼마나 discount할지 정해주는 파라미터입니다.

decay_rate : RMSPropagation을 이용하는데 이때 사용되는 decay factor입니다.

learning_rate : Neural Network를 학습할 때 이용하는 learning_rate입니다.

 

Line 7~12 : Neural Network에 이용할 weight를 만들어주는 선언부입니다. resume이 True일때는 pickle을 통해 저장된 weight를 불러오게 됩니다. resume이 False일 때는 dictionary 자료형으로 'W1', 'W2'를 만들어 줍니다. 현재 설정으로는 'W1'은 3X10행렬이고 'W2'는 10X10, 'W3'는 10X1 행렬입니다. weight Initialization은 DQN에서도 이용한 Xavier Initialization을 이용했습니다.

 

# dictionary : https://wikidocs.net/16

 

 

 

1
2
3
4
5
self.grad_buffer = []        
    for i in range(batch_size): 
        self.grad_buffer.append({k: np.zeros_like(v) for k,v in self.model.iteritems()})
    self.rmsprop_cache = {k : np.zeros_like(v) for k,v in self.model.iteritems()}        
 
cs

Line 2~4 : gradient를 저장해 놓을 버퍼와 RMSPropagation을 위한 캐시의 저장공간을 만들어주는 부분입니다. 자료형과 모양은 Weight와 똑같이 만들면서 모두 0으로 초기화를 했습니다.

 

# { k : np.zeros_like(v) for k, v in model.iteritems() } : dictionary 자료형에는 key(이름)과 value(값)들이 들어 있습니다. 이 코드는 model(weight)에 있는 모든 key와 value들에 대해 key는 똑같이 하고 value는 크기를 똑같이 하면서 0으로 값들을 초기화 시키는 코드입니다. np.zeros_like(v)는 v와 크기가 같으면서 값들은 모두 0인 array를 만드는 함수입니다.

 

# RMSPropagation : RMSPropagation은 learning rate를 decay시켜주는 Adagradient의 변형입니다. RMSPropagation도 일종의 learning rate를 decay시켜주는 방법이라고 생각하시면 됩니다.

 

 

1
2
3
4
5
6
7
def discount_rewards(r):    
    discounted_r = np.zeros_like(r)    
    running_add = 0    
    for t in reversed(xrange(0, r.size)):        
        running_add = running_add * gamma + r[t]        
        discounted_r[t] = running_add     
    return discounted_r
cs

 

episode가 끝나면 정산을 하게 되는데 각 state에서의 return을 계산할 수 있게 됩니다. 이 부분이 그 return을 계산하는 부분이고 policy gradient에 곱해지는 값입니다.

 

  • Neural network
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def policy_forward(self, x):
        h1 = np.dot(self.model['W1'], x)
        h1 = sigmoid(h1)
        h2 = np.dot(self.model['W2'], h1)
        h2 = sigmoid(h2)
        logp = np.dot(self.model['W3'], h2)
        p = sigmoid(logp)
        return p, h1, h2
     
    cs

policy forward 부분입니다. Neural network에서 Feed Forward부분과 동일하며 결과로 action에 대한 probability가 나옵니다. 하나의 hidden layer위의 코드에서 p는 probability이며, h1, h2는 hidden layer의 결과값들 입니다. 실제 Karpathy코드에서는 ReLU함수를 이용하지만 CartPole 에서는 hidden unit이 적기 때문에 모든 layer에 sigmoid함수를 이용했습니다.

 

 

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def policy_backward(self, eph1, eph2, epdlogp, epx, ep_num):
        dW3 = np.dot(eph2.T, epdlogp).ravel()
        dh2 = np.outer(epdlogp, self.model['W3'])
        eph2_dot = eph2*(1-eph2)
        dW2 = dh2 * eph2_dot
        dh1 = np.dot(dW2, self.model['W2'])
        eph1_dot = eph1*(1-eph1)
        dW1 = dh1 * eph1_dot
        dW2 = np.dot(dW2.T, eph1)
        dW1 = np.dot(dW1.T, epx)
        self.grad_buffer[ep_num%batch_size] = {'W1':dW1, 'W2':dW2, 'W3':dW3}
     
     

에피소드가 끝날 때마다 작동되는 Policy backward 함수입니다. Neural network의 Back propagation과 동일합니다. 얼마나 weight를  어떻게 update 할 지를 형태만 정해놓고 후에 이 함수를 호출할 경우 weight의 gradient를 구해서 buffer에 저장하는 부분입니다.

 

 

  • Learning
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def learning(self):
        tmp = self.grad_buffer[0]
        for i in range(1,batch_size):
            for k,v in self.model.iteritems():
                tmp[k] += self.grad_buffer[i][k]
        for k,v in self.model.iteritems():
            g = tmp[k]
            self.rmsprop_cache[k] = self.decay_rate * self.rmsprop_cache[k] + 
    (1 - self.decay_rate)*g**2
            self.model[k] += self.learning_rate * g / (np.sqrt(self.rmsprop_cache[k]) + 1e-5)
     
    cs

     

앞에서 policy_backward로 gradient를 구했습니다. batch 사이즈 만큼의 weight의 gradient를 RMSProp을 이용해 weight를 업데이트 해주는 부분입니다.

 


  • Train
    1
    2
    3
    4
    5
    rl = RL(in_size, out_size)
    running_reward = None
    reward_sum, episode_num = 0,0
    xs,h1s,h2s,dlogps,drs = [],[],[],[],[]
     
    cs

Train이라는 CartPole 학습을 하는 함수의 정의 부분입니다.

 

reward_sum, episode_num : reward_sum은 episode에서 받은 reward의 합으로서 cartpole을 play한 시간을 의미합니다. episode_num 은 몇 번의 episode를 진행했는지 나타냅니다.

 

xs, h1s, h2s, dlogps, drs : 각각 state,  hidden1, hidden2, policy gradient, reward 값을 저장하기 위한 리스트입니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for i_episode in range(2000):
    done = False
    observation = env.reset()
 
    while not done:
       x = observation
       if render: env.render()
       act_prob, h1, h2 = rl.policy_forward(x)
 
       action = 1 if np.random.uniform() < act_prob else 0
       xs.append(x)
       h1s.append(h1)
       h2s.append(h2)
       y = action
       dlogps.append(y - act_prob)
 
       observation, reward, done, info = env.step(action)
       reward_sum += reward
       drs.append(reward)
 
cs

총 에피소드는 2000번까지 돌려볼 것입니다. 처음 시작할 때는 observation을 초기화시켜줍니다.

그 이후는 episode마다의 하는 일들을 정리해 놓은 것입니다.

 

Line 6~10 : action을 정하는 부분입니다. 7번째 줄의 render는 화면으로 보기 위해서인데 화면으로보면 learning이 느려지므로 개인적으로는 저는 코드의 맨 위에서 render를 false로 놓고 코드를 돌립니다.

observation을 Neural Network에 넣으면 act_prob 가 0~1사이의 숫자가 나오도록 되어있습니다. unifrom한 random value를 생성해서 act_prob 가 random value보다 크면 1을 action으로 취하고 random value보다 작으면 0을 action으로 취합니다. random value를 이용함으로서 Exploration의 문제를 해결합니다.

 

Line 11~14 : state, hidden value, action, error를 각각 저장합니다. 여기서 error는 실제 action 에서 act_prob를 뺀 값으로 action이 target value입니다. 현재 neural network를 통해서 나온 값이 act_prob입니다. gradient라는 것은 일종의 기울기입니다. random value를 통해 얻은 action을 새로운 policy라고 생각하면 그 action과 neural network의 action probability의 차이는 policy의 gradient가 될 것입니다. 그러한 policy gradient의 방향이 좋은 건지 나쁜 건지를 현재는 알 수 없는데 뒤에서 계산하는 return을 통해 그것을 알 수 있습니다. 만약 평균적으로 받는 return이 얼마인지 알고 있으면 평균적인 return에 비해서 많이 받았으면 그 행동을 하도록 더 확률을 늘리는 방향으로 학습하고, 평균적인 return에 비해서 덜 받았으면 그 행동을 하지 않도록 학습하게 됩니다. 그 평균적인 return이 Silver교수님의 강의에 나왔던 "Baseline"이 될 것 입니다.

 

Line 17 : env.step(action)은 action을 갖고 한번의 스텝을 시행합니다. 그 결과로 observation(state), reward, done, info가 주어집니다.

 

Line 19~20 : 한 스텝마다의 reward를 더해주고 그 reward를 drs에 저장해줍니다.
 

이 단계를 episode가 끝날 때 까지 돌게 됩니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if done:
   episode_num += 1
   print "episode : " + str(episode_num) + ", reward : " + 
str(reward_sum)
 
   epx = np.vstack(xs)
   eph1 = np.vstack(h1s)
   eph2 = np.vstack(h2s)
   epdlogp = np.vstack(dlogps)
   epr = np.vstack(drs)
   xs,h1s,h2s,dlogps,drs = [],[],[],[],[]
 
   discounted_epr = rl.discount_rewards(epr)
   discounted_epr -= np.mean(discounted_epr)
   discounted_epr /= np.std(discounted_epr)
   epdlogp *= discounted_epr
 
   rl.policy_backward(eph1,eph2,epdlogp,epx,episode_num)
   rl.learning()
 
   reward_sum = 0    
cs

 

이제 한 에피소드가 끝났을 때입니다. done에 episode가 종료 됬는지 정보가 들어가서 에피소드가 끝나면 done에 True가 들어갑니다.

 

Line 2~4 : 에피소드가 끝났기 때문에 episode_num 을 하나 더해주고 몇 번째 episode인지, reward는 얼마 받았는지를 프린트 해 줍니다.

 

Line 6~11 : 에피소드를 진행하면서 저장된 데이터들을 스택에 저장합니다. epx는 observation을 저장한 스택이고 eph1,2는 hidden value를 저장한 스택입니다. epdlogp에는 policy gradient들을 저장하고 epr에는 reward들을 저장하게 됩니다.

 

 

1
2
3
4
5
6
7
    # reward를 discount시키고 standardize함        
    discounted_epr = discount_rewards(epr)            
    discounted_epr -= np.mean(discounted_epr)        
    discounted_epr /= np.std(discounted_epr)                
    
    # standardize된 reward를 error에 곱함        
    epdlogp *= discounted_epr
cs

 

이 부분만 따로 살펴봅시다.

 

Line 2~4 : reward를 이용해 discounted reward를 구합니다. 이 discounted reward가 곧 return입니다. 그리고 재미있는 것은 이 discounted reward을 그냥 이용하지 않고 standardization을 한다는 점 입니다. 그래서 평균을 빼주고 표준편차로 나눠줍니다.

 

CartPole의 경우 항상 1의 reward를 받기 때문에 모든 action에 대해 return은 항상 0보다 크게 됩니다. 따라서 그대로 return을 사용할 경우 모든 action을 encourage하게 되고 그 정도에만 차이가 있을 것입니다. 하지만 이런 식으로 하면 variance가 커서 수렴을 잘 안하기 때문에 standardization을 해주게 됩니다. standardization을 하면 좋지 않은 action은 discourage하게 되고 좋은 행동은 encourage하게 되기 때문에 수학적으로는 이렇게 이용하면 variance가 줄어듭니다. 이 부분은 앞 글에서 advantage function 이라고 설명했던 부분입니다. 이렇게 정규화( (reward - mean) / std )를 하는 것이 일종의 advantage function이 될 수 있는 것 같습니다.

 

adventage_fun.png

 

Line 7 : 이렇게 만들어진 advantage funtion에 error를 곱해줍니다. 위에서 처럼 error에다가 advantage function을 곱합니다. 직관적으로 이해하면 error에다가 advantage function을 곱해서 좋은 reward를 받으면 error가 그냥 학습되도록 만들고, 안좋은 reward를 받으면 error가 반대로(거꾸로) 학습되게 만드는 것입니다.

 


  • Main
    1
    2
    3
    4
    5
    6
    if __name__ == "__main__":
        env = gym.make('CartPole-v0')
        env.monitor.start(OUT_DIR, force=True)
        train(env)
        env.monitor.close()
     
    cs

앞의 모든 것들을 포함하며 전체 코드를 돌리는 부분입니다. CartPole environment를 가져와서 정해진 directory에 저장을 시작하며 2000번의 episode동안에 학습을 하고 기록을 중지합니다.

 

 

 


4. DQN vs Policy gradient

여기까지 CartPole예제를 대상으로 DQN코드와 Policy gradient코드를 분석해보았습니다. 두 개로 학습을 시켜봤으니 그 결과를 비교해보도록 하겠습니다. 어떠한 기준이 없이(environment만 같다는 점) 알고리즘을 비교하는 것이 의미가 있을지는 모르겠습니다만 그래도 두 방법의 차이를 보여줄 순 있을 것 같습니다.

 

https://gym.openai.com/docs

Screenshot from 2016-07-24 23:58:07.png

 

OpenAI 홈페이지에 자신이 테스트해본 알고리즘을 홈페이지로 게시하는 방법이 나와있습니다. 이렇게 게시를 하면 Learning의 그래프를 볼 수 있습니다. API KEY는 github로 로그인하면 주어집니다.

 

첫 번째로 DQN의 코드의 learning graph입니다.

https://gym.openai.com/evaluations/eval_O7ttywxVSKSoJpgF10Cpnw

Screenshot from 2016-07-20 23:31:34.png

 

 

 

오른쪽의 그래프인데 x축은 episode number이고 y축은 episode의 reward의 총합입니다.

 

두번째는  Policy gradient의 learning graph입니다.

https://gym.openai.com/evaluations/eval_nXZfJbglTG2dPAU7CJ0rPg

Screenshot from 2016-07-20 23:34:22.png

 

 

두 그래프를 비교해보면 Learning을 해나가는 과정이 Policy gradient가 더 부드러운 것을 알 수 있습니다. 문제도 좀 더 빨리 풀렸습니다. 제가 아직 neural network를 design하는 법을 잘 몰라서 이 정도로만 비교해보도록 하겠습니다.

 

 


이렇게 CartPole예제에서 Policy Gradient코드를 살펴보았습니다. 다음 글부터는 atari 게임을 강화학습 시켜보겠습니다.

번호 제목 글쓴이 날짜 조회 수
공지 강화학습 (Reinforcement Learning) 연구실 OpenRL 모임 안내 모두의연구소 2016.06.12 1300
22 softmax action result에 대한 reward 김상범 2017.10.31 255
21 강화학습 논문 웹사이트 모음 xelgana 2016.10.28 1548
20 Monte-Carlo Tree Search 코드 [3] file 이영무 2016.10.06 1866
19 2016. 9. 1. Asynchronous 발표 자료 [1] file 최한철 2016.09.10 412
18 2016. 9. 1. 발표자료 [3] 최한철 2016.08.29 532
17 Fundamental of Reinforcement Learning 링크 이웅원 2016.08.24 1093
16 2016.08.18 OpenRL 발표자료 file 이웅원 2016.08.17 758
» 강화학습 그리고 OpenAI - 4: CartPole with Policy Gradient (2) Code Review [5] file 이웅원 2016.07.20 5399
14 강화학습 그리고 OpenAI - 4: CartPole with Policy Gradient (1) Policy Gradient [9] file 이웅원 2016.07.15 10661
13 강화학습 그리고 OpenAI - 3: CartPole with Deep Q Learning (4) Code Review file 이웅원 2016.07.14 8420
12 강화학습 그리고 OpenAI - 3: CartPole with Deep Q Learning (3) TensorFlow 이웅원 2016.07.13 3887
11 강화학습 그리고 OpenAI - 3: CartPole with Deep Q Learning (2) DQN file 이웅원 2016.07.13 5947
10 강화학습 그리고 OpenAI - 3: CartPole with Deep Q Learning (1) CartPole example file 이웅원 2016.07.12 6604
9 DQN 발표자료 file 플룻 2016.07.11 966
8 강화학습 그리고 OpenAI - 2: Intro to Reinforcement Learning (2) Q Learning [4] file 이웅원 2016.07.08 16143
7 강화학습 그리고 OpenAI - 2: Intro to Reinforcement Learning (1) MDP &Value Function [2] file 이웅원 2016.07.04 17414
6 강화학습 그리고 OpenAI - 1: Introduction to OpenAI [3] file 이웅원 2016.07.01 20211
5 Reinforcement Learning by Sutton Chapter 5~16 file 마르코프김 2016.07.01 579
4 Reinforcement Learning by Sutton Chapter 1~4 [1] file 마르코프김 2016.06.30 2212