Notice
Recent Posts
Recent Comments
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

No Limitation

[Reinforcement Learning] Policy based RL - Policy Gradient, REINFORCE algorithm, Actor-Critic 본문

ML & DL & RL

[Reinforcement Learning] Policy based RL - Policy Gradient, REINFORCE algorithm, Actor-Critic

yesungcho 2022. 6. 10. 14:39

본 포스팅은 카이스트 산업 및 시스템 공학과 신하용 교수님의 동적계획법과 강화학습 강의 자료를 중심으로 작성되었고 노승은 님의 바닥부터 배우는 강화학습 교재를 참고해 작성되었습니다. 

 

이번 포스팅에서는 MAB, 가치 기반 RL에 이어 정책 기반 RL을 다루게 되었습니다. 사실 이들을 이해하려면 더 앞서서 정리해야할 포스팅들이 많지만, 프로젝트를 수행하는 차원에서 미리 정책 기반 RL을 공부할 필요가 있어서 미리 정리하게 되었습니다. 추후에 RL의 기본 method들부터 하나씩 정리해서 포스팅해 올리도록 하겠습니다. 

 

따라서 본 포스팅은 어느 정도 RL에 대한 개념을 아시는 분들이 참고해주시면 감사하겠습니다. (물론 저도 제대로 개념이 잡힌 지는 모릅니다 허허.. 이상한 부분은 크리틱 부탁드립니다..! )

 

지난 DQN 포스팅에도 보여드렸던 슬라이드입니다. 카이스트 신하용 교수님께서 만드신 슬라이드인데 굉장히 RL 모델들의 분류가 잘 되어있다고 생각이 드는 슬라이드입니다. 이 중에서 우리가 다루는 파트는 빨갛게 표시된 Policy based RL 중에서도 Stochastic PG 부분을 다룰 예정입니다. 이번 포스팅에서는 PG의 기본 개념부터 REINFORCE, Actor-Critic 그리고 이들의 여러 확장 버전을 다룰 예정입니다. 다음 포스팅에서는 이들을 개선한 알고리즘으로 TRPO와 PPO를 추가로 정리할 계획입니다. 

 

출처 : 카이스트 산업 및 시스템 공학과 신하용 교수님 동적계획법과 강화학습 강의자료

 

지난 DQN 포스팅에서 가치 함수와 정책 함수를 근사시키는 부분을 언급드렸습니다. 가치 기반 RL에서는 v_θ (s)나 q_θ (s, a) 같은 가치 함수의 값을 approximate해서 활용하는 것이었다면 이번 정책 기반 RL에서는 바로 정책 함수 π_θ(s)를 approximate해서 활용하는 방법입니다. 

 

정책 기반(Policy-based) RL이 가치 기반(Value-based) RL에 비해 갖는 장점은 무엇일까요? 우선, Value-based는 action을 정하는 데에 있어 deterministic합니다. 즉 argmax(q)인 a를 찾으면 되니까요. 반면 Policy-based는 stochastic policy 구축이 가능합니다.  애초에 정의 자체가 state에서 action을 선택할 확률들을 갖는 것이 π의 의미이기 때문입니다. 그래서 보다 유연한 정책을 취할 수 있습니다. 또한 high dimension이거나 continuous action space에서 훨씬 강력한 힘을 발휘합니다. 만약 action space가 [0,1] 사이의 실수를 고르는 것이라면, Value-based 에서는 여기서 가장 q를 최대화시키는 최적화 문제를 풀어야 하지만, Policy-based에서는 value function 고려 없이 바로 action을 뽑아주기 때문에 이러한 task에도 적용이 가능한 장점이 있습니다. 하지만 단점도 있습니다. 우선 policy evaluation을 수행하면서 매우 variance가 크게 되고 local optima에 빠질 가능성도 높이며, 또한 한번 사용한 샘플을 재사용할 때 efficiency가 적게 됩니다 ( 가치 기반에서는 replay buffer를 통해 history를 저장하는 기능이 잇지만 여기서는 적용이 어렵다고 합니다 ). 

 

Policy Gradient (PG)

딥러닝을 통해 근사한다면, 결국 손실 함수가 정의되어야 할텐데 policy의 경우 필연적으로 정답을 알 수 없습니다. 정답을 알면 애초에 문제를 풀 필요가 없으니까요. 손실을 정의하는 것이 아니라, 정책을 평가하는 점수를 매겨서 그 값을 증가하게 만드는 방법을 취합니다. 그 함수를 J 함수로 정의합니다. 그리고 gradient ascent를 수행해서 이 J를 최대화하려고 하는 것이 목표가 됩니다. 

 

J는 당연히 최대화하면 좋은 거니까 보상의 합으로 이루어져 있습니다. 다만 에피소드들마다 다른 state를 방문하게 되면 그 값이 상이하기 때문에 합에다 expectation을 취해준 값을 J로 정하게 됩니다. 그러면 이는 밑의 수식처럼 보상들의 합의 기대값으로 표현이 가능하고 이는 return의 기댓값과 동일하기 때문에 초기 state에서의 value function 값과 동일한 모양 형태가 됩니다. 

하지만 초기 state가 반드시 s0라고만 할 수 없죠. 고정된 상수일 수가 없기 때문에 매번 다른 상태에서 시작을 한다고 하되 그 확률 분포 d(s) (이 d(s)를 stationary distribution이라고 합니다. )를 정의하여 이를 곱해줌으로서 더 general한 정의가 가능합니다. 

즉, 모든 상태 s에 대하여 해당 상태에서 출발했을 때의 얻을 가치를 해당 상태에서 출발할 확률과 곱하여 가중 합을 해준 것을 의미하게 됩니다. 바로 이 녀석들을 J로 사용해서 이 녀석을 최대화하는 정책 함수를 찾으면 됩니다. 이는 gradient ascent를 수행하는 task가 되고 아래 수식처럼 나타낼 수 있습니다. 

저때 바로 저 gradient 값을 구하는 게 핵심이 됩니다. 

 

그렇다면 1-step 상황에서 저 J의 gradient가 어떻게 계산되는 지를 한번 살펴보겠습니다. 1-step의 경우는 s에서 a를 선택하고 얻는 보상으로 바로 J의 값이 계산이 됩니다. 즉, 아래와 같이 나타낼 수 있습니다. 

자 그럼 이제 이 수식의 양변에 gradient를 취해 gradient ascent를 수행해주어야 하는데, 우리는 저 우변 수식에서 R_s,a를 알지 못합니다. 왜냐하면 모델 프리 (model-free)한 상황을 가정하기 때문입니다. 그러면 이를 어떻게 gradient를 구할 수 있을까요? 아래 수식의 흐름을 살펴보겠습니다. 

다음 그림은 J에 대해 gradient를 구한 수식을 전개한 것입니다. 우선 세타에 대한 gradient이기 때문에 정책 함수에 gradient가 구해지게 되고, 밑에서 두 번째 줄처럼 전개가 될 수 있습니다. 저 형태는 우리가 정책 함수 π에 대한 expectation 형태임을 알 수 있습니다. 그 이유는 저 π_θ(s,a) 뒤에 나올 값에 π_θ(s,a)만큼의 가중치를 곱해서 더해주라는 뜻이고, 이는 곧 기대값의 정의이고 이 부분이 바로 policy gradient의 핵심입니다. 그리고 lnx의 미분 성질을 이용해서 맨 밑의 줄처럼 전개가 됨을 알 수 있습니다. 그리고 저 log의 gradient term과 R_s,a가 바로 score function이라고 불리게 됩니다. 그리고 이는 expectation으로 나타내면 아래와 같이 나타낼 수 있습니다. 

즉, score function에 대한 '샘플 기반 방법론'으로 문제가 바뀌게 됩니다. 저 정책 함수 π_θ(s,a)로 움직이는 에이전트를 환경에 가져다 놓고 저 score function을 여러개 모으면 됩니다. score function의 log term과 R_s,a는 쉽게 구할 수 있고 저 값을 여러 개 모아 평균을 내면 그 평균이 곧 J의 gradient가 됩니다. 지금까지 1-step의 MDP를 공부했다면 일반적인 MDP 에서의 setting은 아래와 같이 나타낼 수 있습니다.

R_s,a가 Q(s,a)로 바뀐 것을 확인할 수 있습니다. 의미상으로는 s에서 a를 할 때 받는 보상 대신 s에서 a를 할 때 얻는 return의 기댓값으로 바뀐 것을 의미합니다. 

 

그럼 이렇게 PG (Policy Gradient)에 대해 공부해보았으니 이제 이를 바탕으로 학습을 수행하는 다양한 알고리즘들에 대해 공부해보겠습니다. 우선 첫 번째로 가장 기본이 되는 REINFORCE 알고리즘을 공부해보겠습니다. 

 

REINFORCE

REINFORCE 알고리즘은 기존 Q(s,a)가 Gt로 바뀐 것을 확인할 수 있습니다.

이 Gt는 우리가 알고 있듯 unbiased return입니다. 애초에 Q(s,a)의 정의가 아래 수식과 같기 때문입니다. 

즉 Gt의 샘플을 여러 개 얻어서 평균을 내면 그 값이 실제 Q 값에 근사해지기 때문입니다. REINFORCE는 이러한 Gt를 사용하기 때문에 MC 기반의 방법으로 동작합니다. 즉, 에피소드를 끝까지 진행하고 이들을 가지고 Gt와 세타를 업데이트하게 됩니다. 그러면 세타가 업데이트를 하게 되는데 그 의미에 대해 살펴보겠습니다. 우선 gradient ascent를 수행하게 되면 아래 처럼 수식이 전개되게 됩니다. 

(1)의 경우는 실제 gradient 수식에서 Return에 대한 수식만 뺀 것입니다. (1)만 보면 저 의미는 π_θ(s,a) 를 증가시킨다는 것을 의미합니다. 그런데 그 정도를 (2)의 Gt를 곱해주어서 Gt만큼 저 π_θ(s,a) 의 확률을 증가시킨다는 것을 의미합니다. 만일 Gt가 1이라고 하면 +1만큼 π_θ(s,a) 을 증가시킬 것입니다. 반면 Gt가 -1이라고 해보죠. 그러면 -1이 곱해져 π_θ(s,a) 을 1만큼 감소시키게 됩니다. 만일 Gt가 100이면 100만큼 π_θ(s,a) 의 확률을 증가시키겠죠. gradient ascent는 바로 이러한 의미를 갖습니다. Gt를 크게 하는 더 좋은 정책을 선택할 확률을 증가시키는 것입니다. 

 

따라서 REINFORCE 알고리즘은 다음과 같이 구성됩니다.

그렇다면 실제 REINFORCE 구현 코드를 통해 구체적인 내용을 공부해보겠습니다. 예제는 이전 DQN에서 다루었던 Cartpole 예제를 다룰 것이고 코드는 노승은 님의 github(https://github.com/seungeunrho/RLfrombasics/blob/master/ch9_REINFORCE.py)를 참고하였습니다. 

 

라이브러리와 파라미터를 세팅하는 코드입니다.

import gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical

#hyperparameter
learning_rate = 0.0002
gamma = 0.98

 

다음으로 메인 함수 코드입니다.

def main() : 
    env = gym.make('CartPole-v1')
    pi = Policy() # 정책 네트워크 class
    score = 0.0
    print_interval = 20
    
    for n_epi in range(10000) : 
    	s = env.reset()
        done = False
        
        # 에피소드가 끝날 때까지
        while not done : 
            prob = pi(torch.from_numpy(s).float()) ## action 별 확률 구함
            m = Categorical(prob) # categorical distribution
            a = m.sample() # 하나의 액션 샘플링, 확률이 높은 action은 자주 뽑힘
            s_prime, r, done, info = env.step(a.item()) 
            pi.put_data((r,prob[a])) 
            s = s_prime
            score += r
        
        pi.train_net() # 에피소드가 끝나면 실제 업데이트 수행
        if n_epi%print_interval == 0 and n_epi != 0 :
            print('# of episode : {}, avg_score : {}'.format(n_epi, score/print_interval))
            score = 0.0
    env.close()

 

Policy 클래스는 정책 네트워크 클래스를 의미합니다. prob = pi 를 통해 action 별 확률을 구하게 되고 이들 중 action을 하나 선택해 한 step을 진행하고 이를 episode가 끝날 때까지 ( done=True ) 진행해서 데이터를 모읍니다. 이 과정이 완료가 되면 pi.train_net()을 통해 실제 업데이트를 수행하게 됩니다. 

 

그렇다면 policy 클래스는 어떻게 구축되어 있을까요

class Policy(nn.Module):
    def __init__(self):
        super(Policy, self).__init__()
        self.data = []
        
        self.fc1 = nn.Linear(4, 128)
        self.fc2 = nn.Linear(128, 2)
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)
        
    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.softmax(self.fc2(x), dim=0)
        return x
      
    def put_data(self, item):
        self.data.append(item)
        
    def train_net(self):
        R = 0
        self.optimizer.zero_grad()
        for r, prob in self.data[::-1]:
            R = r + gamma * R
            loss = -torch.log(prob) * R
            loss.backward()
        self.optimizer.step()
        self.data = []

우선 네트워크를 구성한 부분에서 알 수 있듯, 마지막 아웃풋은 2로, cartpole에서의 action인 left, right에 대한 확률 값을 리턴하게 됩니다. train_net 함수를 보시면, 에피소드 끝에서부터 하나씩 Gt를 계산하고 loss를 업데이트하게 되는데, 이는 우리가 앞에서 보았던 gradient 와 동일한 의미를 갖습니다. 즉, 위와 같이 loss를 gradient ascent해줍니다. 저기서 loss = -torch.log(prob)*R에서 앞에 -가 붙음을 알 수 있습니다. 이는 optimizer는 손실 함수를 자동으로 minimize하는 방향으로 업데이트를 수행하지만, 우리는 maximize를 하고 싶기 때문에 반대 방향으로 진행하게끔 하기 위해 -를 붙여줍니다. 이렇게 REINFORCE 알고리즘 구현 코드를 살펴보았습니다. 

 

다음으로는 Actor Critic에 대해 살펴보겠습니다. 

 

Actor Critic

이전 DQN에서 언급드렸듯, actor-critic은 정책 네트워크와 가치 네트워크를 같이 학습하는 형태를 의미합니다. actor는 행동하는 것으로 정책 π를 말하고 critic은 비평가로서 가치 함수 v_θ (s)나 q_θ (s, a)를 의미하게 되는 것입니다. 그러면 actor critic의 가장 기초가 되는 Q actor-critic에 대해 먼저 살펴보겠습니다. 

 

Q actor-critic

Q actor-critic에 대한 수식은 아래와 같습니다. 

사실상 앞서 설명드렸던 PG와 다르지 않습니다. 하지만 여기선 Q에 θ가 아닌 가중치 w로 되어 있음을 확인할 수 있습니다. 즉, θ로 파라미터화된 정책 네트워크 π_θ와 w로 파라미터화된 벨류 네트워크 Q_w 이렇게 2개의 neural net을 학습하게 됩니다. 저기서 π_θ는 action a를 선택하는 actor, Q_w는 a의 value를 평가하는 critic 역할을 담당하게 됩니다. critic Q에 대해서 정답으로는 MC나 TD 기반의 방법론을 전부 사용할 수 있습니다. 본 포스팅에서는 TD 기반의 target을 사용하는 것을 가정하겠습니다. 그럼 슈도 코드는 아래와 같이 구성됩니다. 

 

정책 네트워크 π_θ와 벨류 네트워크 Q_w가 함께 학습되는 것을 확인할 수 있습니다. 여기서 주목할만한 부분은 θ를 업데이트할 때에 실제 보상 값이 사용되지 않고 Q_w에 의존해서 사용된다는 점이 특징입니다. 

 

Advantage actor-critic

이제 Q actor critic을 조금 더 개선한 알고리즘에 대해 살펴보겠습니다. 다음 그림을 보시죠. 

위 그림은 cartpole 게임에서 pole이 거의 다 넘어지려고 하는 시점을 보여줍니다. 여기서 기둥을 세우기 위해서는 action은 오른쪽으로 움직이게 해서 기둥을 넘어지지 않게 해야합니다. 하지만 이 경우 state의 영향이 너무 강해 결국 action보다 현재 state의 영향에 따라 업데이트를 하게 됩니다. 이런 경우를 보완하기 위해, 즉 action의 영향을 집중해서 보기 위해 도입되는 개념이 바로 "advantage"라는 개념입니다. 아래 수식을 보시죠. 

위와 같이 모든 상태에서 업데이트할 떄, 각 상태의 밸류인 V(s)를 빼 주고자 합니다. 이렇게 함으로서 상태 s에서 액션 a를 실행하는 것이 Q(s,a)이고 V(s)는 상태 s의 밸류이기에 이를 빼주어 s에서 액션 a를 실행함으로써 "추가로" 얻는 가치를 의미하게 됩니다. 그리고 이를 Advantage라고 합니다. 

 그리고 여기서 V(s)를 기저 (baseline)이라고 부릅니다. 이는 말그대로 '기저'입니다. 무슨 말인고하니, 상태 s에 도착하는 것은 "이미 벌어진 사건"입니다. 그러니 이는 이미 주어진 것으로 받아들이고 거기서 액션 a를 했을 때 미래에 어떻게 되는 지를 보아 action a에 대한 확률을 수정하는 것이 더 좋습니다. 그리고 이런 advantage를 곱해줌으로써 gradient 추정치의 변동성이 더 작아집니다. 즉 액션으로 인해 생기는 추가 이득만 고려하게 되는 것입니다. 그래서 학습의 성능 향상에도 도움을 주게 됩니다. 하지만 실제 구현에는 Q, V 둘 다 실제로 아는 값이 아니기 때문에 근사가 필요하죠. 그래서 아래 처럼 근사하는 네트워크를 적용해야 합니다. 

따라서 Advantage actor critic은 3개의 뉴럴 넷을 필요로 합니다.

Advantage actor critic의 슈도 코드는 다음과 같습니다. 

 

TD actor-critic

하지만 advantage actor critic은 뉴럴넷을 3개나 사용하는 단점이 있습니다. 그러한 부분을 극복하기 위해, TD actor critic 아이디어가 등장하였습니다. 이는 가치 함수 V의 TD 에러의 concept을 사용하는데요. TD 에러는 아래의 수식을 의미합니다.

즉 이 δ가 TD-error가 됩니다. 그렇다면 상태 s에서 어떤 액션 a를 실행했을 때의 δ의 기대값은 어떻게 될까요? 살펴보겠습니다. 

δ의 Expectation이 Advantage가 나옴을 알 수 있었습니다. 이는 δ는 상태 s에서 액션 a를 선택할 때 상태 전이에 따라 매번 다른 값을 갖게 되는데 그 평균은 A(s,a)로 수렴하게 된다는 것을 의미합니다. 즉 이 δ를 A(s,a) 대신 사용하게 되면 V에 대한 수식으로도 전개가 되고 Q_w가 필요없게 됩니다. 즉 아래처럼 gradient가 정의됩니다. 

슈도코드는 아래와 같습니다. 

그렇다면 위에 REINFORCE에서 살펴보았던 구현코드에서 Policy를 ActorCritic 클래스로 바꾸어 살펴보겠습니다. 아래 코드는 ActorCritic 클래스 구현 코드입니다. 

 

class ActorCritic(nn.Module):
    def __init__(self):
        super(ActorCritic, self).__init__()
        self.data = []
        
        self.fc1 = nn.Linear(4,256)
        self.fc_pi = nn.Linear(256,2)
        self.fc_v = nn.Linear(256,1)
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)
    
    ## 정책 네트워크
    def pi(self, x, softmax_dim = 0):
        x = F.relu(self.fc1(x))
        x = self.fc_pi(x)
        prob = F.softmax(x, dim=softmax_dim)
        return prob
        
    ## 밸류 네트워크
    def v(self, x):
        x = F.relu(self.fc1(x))
        v = self.fc_v(x)
        return v
    
    def put_data(self, transition):
        self.data.append(transition)
    
    ## 배치 샘플링 코드
    def make_batch(self):
        s_lst, a_lst, r_lst, s_prime_lst, done_lst = [], [], [], [], []
        for transition in self.data:
            s,a,r,s_prime,done = transition
            s_lst.append(s)
            a_lst.append([a])
            r_lst.append([r/100.0])
            s_prime_lst.append(s_prime)
            done_mask = 0.0 if done else 1.0
            done_lst.append([done_mask])
        
        s_batch, a_batch, r_batch, s_prime_batch, done_batch = torch.tensor(s_lst, dtype=torch.float), torch.tensor(a_lst), \
                                                               torch.tensor(r_lst, dtype=torch.float), torch.tensor(s_prime_lst, dtype=torch.float), \
                                                               torch.tensor(done_lst, dtype=torch.float)
        self.data = []
        return s_batch, a_batch, r_batch, s_prime_batch, done_batch
  
    def train_net(self):
        s, a, r, s_prime, done = self.make_batch()
        td_target = r + gamma * self.v(s_prime) * done ## td-target 구하기
        delta = td_target - self.v(s) ## delta 구하기
        
        pi = self.pi(s, softmax_dim=1)
        pi_a = pi.gather(1,a)
        ## '+' 기준 첫 번째 항 -> policy network loss function, 두 번째 항 -> value network loss function
        loss = -torch.log(pi_a) * delta.detach() + F.smooth_l1_loss(self.v(s), td_target.detach())

        self.optimizer.zero_grad()
        loss.mean().backward()
        self.optimizer.step()

 

train_net을 보면, 실제 TD Actor critic 구현 코드를 확인할 수 있습니다. td_target을 구하고 loss function에서 policy network의 loss와 value network의 loss를 활용하였습니다. 이렇게 구현됨을 확인할 수 있습니다.

 

이상 Policy based RL의 기초 내용이었습니다! 다음 포스팅에서는 TRPO와 PPO에 대해 살펴보겠습니다! 

긴 글 읽어주셔서 감사합니다!