( 참고 : Fastcampus 강의 )

figure2

[ 29. Policy Gradient 실습 2 ]


1. Batch REINFORCE

지난 포스트에서는 매 time step마다 update가 이루어지는 REINFORCE에 대해 알아보았다.

\(\rightarrow\) 문제점 : 계산의 효율성 BAD

\(\rightarrow\) \(\therefore\) 1개 혹은 여러개의 Episode를 단위로 update


(1) REINFORCE (1992)

  • \(\theta \leftarrow \theta + \alpha \nabla_{\theta}\ln \pi_{\theta}(A_t|S_t)G_t\).

(2) Episodic update REINFORCE

  • \(\theta \leftarrow \theta + \alpha\frac{1}{T}\biggr(\sum_{t=1}^{T}\nabla_{\theta}\ln \pi_{\theta}(A_t|S_t)G_t\biggr)\).

(3) Batch episodic update REINFORCE

  • \(\theta \leftarrow \theta + \alpha\frac{1}{\sum_{i=1}^{N} T^{i}} \biggr(\sum_{i=1}^{N}\sum_{t=1}^{T^i}\nabla_{\theta}\ln \pi_{\theta}(A_t^i|S_t^i)G_t^i\biggr)\).


2. Import Packages

import sys; sys.path.append('..') # add project root to the python path

from os.path import join
import gym
import torch

from src.part3.MLP import MultiLayerPerceptron as MLP
from src.part4.PolicyGradient import REINFORCE
from src.common.train_utils import EMAMeter, to_tensor
from src.common.memory.episodic_memory import EpisodicMemory

3. Settings

(1) Environment : Cart Pole

env = gym.make('CartPole-v1')
s_dim = env.observation_space.shape[0]
a_dim = env.action_space.n


(2) Agent & Memory & Metric

net = MLP(s_dim, a_dim, [128])
agent = REINFORCE(net)

memory = EpisodicMemory(max_size=100, gamma=1.0)
ema = EMAMeter()


4. Episode들을 저장하는 memory

Method 소개

  • push : epsiode를 memory에 추가함
  • reset : memory를 clear한다
  • get_samples : 가장 마지막으로 들어온 epsiode를 꺼낸다 (pop)


(참고) deque

  • de = Double Ended
  • front와 end에서 삭제와 삽입이 모두 가능
class EpisodicMemory:
    def __init__(self, max_size: int, gamma: float):
        self.max_size = max_size  # Trajectory의 최대 길이
        self.gamma = gamma
        self.trajectories = deque(maxlen=max_size)
        self._trajectory = Trajectory(gamma=gamma)

    def push(self, s, a, r, s_, d):
        self._trajectory.push(s, a, r, s_, d)
        if done:
            self.trajectories.append(self._trajectory)
            self._trajectory = Trajectory(gamma=self.gamma)

    def reset(self):
        self.trajectories.clear()
        self._trajectory = Trajectory(gamma=self.gamma)

    def get_samples(self):        
        S, S, R, S_, D, G = [], [], [], [], [], []
        while self.trajectories:
            traj = self.trajectories.pop()
            s, a, r, s_, d, g = traj.get_samples()
            S.append(torch.cat(s, dim=0))
            A.append(torch.cat(a, dim=0))
            R.append(torch.cat(r, dim=0))
            S_.append(torch.cat(s_, dim=0))
            D.append(torch.cat(d, dim=0))
            G.append(torch.cat(g, dim=0))

        S = torch.cat(S, dim=0)
        A = torch.cat(A, dim=0)
        R = torch.cat(R, dim=0)
        S_ = torch.cat(S_, dim=0)
        D = torch.cat(D, dim=0)
        G = torch.cat(G, dim=0)

        return S, A, R, S_, D, G

5. Trajectory

하나의 Episode의 trajectory를 기록한다

( 위의 EpisodicMemory 는 여러 Episode를 저장하는 메모리이다 )

class Trajectory:
    def __init__(self, gamma: float):
        self.gamma = gamma
        self.S = list()
        self.A = list()
        self.R = list()
        self.S_ = list()
        self.D = list()
        self.length = 0
        self.G = None
        self._discounted = False

    # 하나의 Episode 추가
    def push(self, s, a, r, s_, d):
        if d and self._discounted:
            raise RuntimeError("done is given at least two times!")
        self.S.append(state)
        self.A.append(action)
        self.R.append(reward)
        self.S_.append(next_state)
        self.D.append(done)
        self.length += 1
        if d and not self._discounted:
            self.compute_return()
            
	# Return값 계산 (주의 : 역순)
    def compute_return(self):
        R = self.R
        G = list()
        g = 0
        for r in G[::-1]: # 역순으로
            g = r + self.gamma * g
            R.insert(0, g)
        self.R = returns
        self._discounted = True
        
	# 하나의 Trajectory 내의 S/A/R/S_/D/G 반환
    def get_samples(self):
        return self.S, self.A, self.R, self.S_, self.D, self.G

6. Run Iteration

  • 총 1,000번의 epsiode를 수행 ( 50번 마다 log 출력 )
  • “2번의 epsiode” 마다 update를 진행
n_episode = 1000
update_batch_num = 2 
print_log = 50


for ep in range(n_episode):
    # (1) 환경 초기화
    s = env.reset()
    
    # (2) 하나의 epsiode의 cumulative reward
    cum_r = 0
    S = []
    A = []
    R = []
    
	# (3) Episode 시작
    while True:
        s = to_tensor(s, size=(1, 4))
        a = agent.get_action(s)
        s_, r, d, info = env.step(a.item())
        
        r = torch.ones(1,1) * r
        d = torch.ones(1,1) * d
        ## 메모리에 해당 episode를 계속 기록한다
        memory.push(s,a,r,torch.tensor(s_),d)
                
        s = s_
        cum_r += r
        if done:
            break

    ema.update(cum_r)
    if ep % print_log == 0:
        print("Episode {} || EMA: {} ".format(ep, ema.s))
    
    if ep % update_batch_num == 0:
        s,a, _, _, d, g = memory.get_samples()
        agent.update_episodes(s, a, g, use_norm=True)