74 lines
2.6 KiB
Python
Raw Normal View History

2020-03-17 11:37:31 +08:00
import torch
import numpy as np
import torch.nn.functional as F
from tianshou.data import Batch
from tianshou.policy import BasePolicy
2020-03-18 21:45:41 +08:00
class PGPolicy(BasePolicy):
2020-03-17 11:37:31 +08:00
"""docstring for PGPolicy"""
2020-03-17 15:16:30 +08:00
def __init__(self, model, optim, dist_fn=torch.distributions.Categorical,
2020-03-17 20:22:37 +08:00
discount_factor=0.99):
2020-03-17 11:37:31 +08:00
super().__init__()
self.model = model
self.optim = optim
2020-03-17 15:16:30 +08:00
self.dist_fn = dist_fn
2020-03-17 11:37:31 +08:00
self._eps = np.finfo(np.float32).eps.item()
2020-03-18 21:45:41 +08:00
assert 0 < discount_factor <= 1, 'discount_factor should in (0, 1]'
2020-03-17 11:37:31 +08:00
self._gamma = discount_factor
def process_fn(self, batch, buffer, indice):
2020-03-17 20:22:37 +08:00
returns = self._vanilla_returns(batch)
# returns = self._vectorized_returns(batch)
2020-03-17 11:37:31 +08:00
batch.update(returns=returns)
return batch
def __call__(self, batch, state=None):
logits, h = self.model(batch.obs, state=state, info=batch.info)
logits = F.softmax(logits, dim=1)
2020-03-17 15:16:30 +08:00
dist = self.dist_fn(logits)
2020-03-18 21:45:41 +08:00
act = dist.sample()
2020-03-17 11:37:31 +08:00
return Batch(logits=logits, act=act, state=h, dist=dist)
2020-03-20 19:52:29 +08:00
def learn(self, batch, batch_size=None, repeat=1):
2020-03-17 11:37:31 +08:00
losses = []
2020-03-26 11:42:34 +08:00
r = batch.returns
batch.returns = (r - r.mean()) / (r.std() + self._eps)
2020-03-20 19:52:29 +08:00
for _ in range(repeat):
for b in batch.split(batch_size):
self.optim.zero_grad()
dist = self(b).dist
a = torch.tensor(b.act, device=dist.logits.device)
r = torch.tensor(b.returns, device=dist.logits.device)
loss = -(dist.log_prob(a) * r).sum()
loss.backward()
self.optim.step()
losses.append(loss.detach().cpu().numpy())
2020-03-19 17:23:46 +08:00
return {'loss': losses}
2020-03-17 11:37:31 +08:00
2020-03-17 20:22:37 +08:00
def _vanilla_returns(self, batch):
2020-03-17 11:37:31 +08:00
returns = batch.rew[:]
last = 0
2020-03-17 20:22:37 +08:00
for i in range(len(returns) - 1, -1, -1):
2020-03-17 11:37:31 +08:00
if not batch.done[i]:
returns[i] += self._gamma * last
last = returns[i]
return returns
2020-03-17 20:22:37 +08:00
def _vectorized_returns(self, batch):
2020-03-17 11:37:31 +08:00
# according to my tests, it is slower than vanilla
# import scipy.signal
convolve = np.convolve
# convolve = scipy.signal.convolve
rew = batch.rew[::-1]
2020-03-17 20:22:37 +08:00
batch_size = len(rew)
2020-03-17 11:37:31 +08:00
gammas = self._gamma ** np.arange(batch_size)
c = convolve(rew, gammas)[:batch_size]
T = np.where(batch.done[::-1])[0]
d = np.zeros_like(rew)
d[T] += c[T] - rew[T]
d[T[1:]] -= d[T[:-1]] * self._gamma ** np.diff(T)
return (c - convolve(d, gammas)[:batch_size])[::-1]