( 참고 : Fastcampus 강의 )
[ 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)