This commit is contained in:
MishaLaskin
2020-02-08 16:36:30 -08:00
commit fe75a963ea
15 changed files with 2095 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
tmp/
notebooks
__pycache__
@@ -0,0 +1,6 @@
{
"cells": [],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 2
}
+60
View File
@@ -0,0 +1,60 @@
# SAC+CPC implementaiton in PyTorch
#
## Instructions
To train an SAC+CPC agent on the `cheetah run` task from image-based observations run:
```
CUDA_VISIBLE_DEVICES=4 python train_cpc.py \
--dmc2gym \
--domain_name reacher \
--task_name easy \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --frame_stack 1 \
--save_tb --save_video --num_train_steps 1000000 \
--work_dir ./tmp/dmc/reacher_easy_cpc \
--agent sac_cpc \
--seed 2 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 10000
CUDA_VISIBLE_DEVICES=7 python train.py \
--domain_name walker \
--task_name walk --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 \
--save_tb --pre_transform_image_size 84 --image_size 84 \
--work_dir ./tmp/icml/vanilla_sac/ML0107walker_vanilla_sac \
--agent sac_ae --frame_stack 3 \
--seed -1 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 10000 --batch_size 128 --num_train_steps 1000000
```
Try - reducing log std actor max from 2->1 or increase 2->3
This will produce 'log' folder, where all the outputs are going to be stored including train/eval logs, tensorboard blobs, and evaluation episode videos. One can attacha tensorboard to monitor training by running:
```
tensorboard --logdir log
```
and opening up tensorboad in your browser.
The console output is also available in a form:
```
| train | E: 1 | S: 1000 | D: 0.8 s | R: 0.0000 | BR: 0.0000 | ALOSS: 0.0000 | CLOSS: 0.0000 | RLOSS: 0.0000
```
a training entry decodes as:
```
train - training episode
E - total number of episodes
S - total number of environment steps
D - duration in seconds to train 1 episode
R - episode reward
BR - average reward of sampled batch
ALOSS - average loss of actor
CLOSS - average loss of critic
RLOSS - average reconstruction loss (only if is trained from pixels and decoder)
```
while an evaluation entry:
```
| eval | S: 0 | ER: 21.1676
```
which just tells the expected reward `ER` evaluating current policy after `S` steps. Note that `ER` is average evaluation performance over `num_eval_episodes` episodes (usually 10).
+17
View File
@@ -0,0 +1,17 @@
name: pytorch_sac_ae
channels:
- defaults
dependencies:
- python=3.6
- pytorch
- torchvision
- cudatoolkit=9.2
- absl-py
- pyparsing
- pip:
- termcolor
- git+git://github.com/deepmind/dm_control.git
- git+git://github.com/1nadequacy/dmc2gym.git
- tb-nightly
- imageio
- imageio-ffmpeg
+490
View File
@@ -0,0 +1,490 @@
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import copy
import math
import utils
from encoder import make_encoder
LOG_FREQ = 10000
def gaussian_logprob(noise, log_std):
"""Compute Gaussian log probability."""
residual = (-0.5 * noise.pow(2) - log_std).sum(-1, keepdim=True)
return residual - 0.5 * np.log(2 * np.pi) * noise.size(-1)
def squash(mu, pi, log_pi):
"""Apply squashing function.
See appendix C from https://arxiv.org/pdf/1812.05905.pdf.
"""
mu = torch.tanh(mu)
if pi is not None:
pi = torch.tanh(pi)
if log_pi is not None:
log_pi -= torch.log(F.relu(1 - pi.pow(2)) + 1e-6).sum(-1, keepdim=True)
return mu, pi, log_pi
def weight_init(m):
"""Custom weight init for Conv2D and Linear layers."""
if isinstance(m, nn.Linear):
nn.init.orthogonal_(m.weight.data)
m.bias.data.fill_(0.0)
elif isinstance(m, nn.Conv2d) or isinstance(m, nn.ConvTranspose2d):
# delta-orthogonal init from https://arxiv.org/pdf/1806.05393.pdf
assert m.weight.size(2) == m.weight.size(3)
m.weight.data.fill_(0.0)
m.bias.data.fill_(0.0)
mid = m.weight.size(2) // 2
gain = nn.init.calculate_gain('relu')
nn.init.orthogonal_(m.weight.data[:, :, mid, mid], gain)
class Actor(nn.Module):
"""MLP actor network."""
def __init__(
self, obs_shape, action_shape, hidden_dim, encoder_type,
encoder_feature_dim, log_std_min, log_std_max, num_layers, num_filters
):
super().__init__()
self.encoder = make_encoder(
encoder_type, obs_shape, encoder_feature_dim, num_layers,
num_filters, output_logits=True
)
self.log_std_min = log_std_min
self.log_std_max = log_std_max
self.trunk = nn.Sequential(
nn.Linear(self.encoder.feature_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, 2 * action_shape[0])
)
self.outputs = dict()
self.apply(weight_init)
def forward(
self, obs, compute_pi=True, compute_log_pi=True, detach_encoder=False
):
obs = self.encoder(obs, detach=detach_encoder)
mu, log_std = self.trunk(obs).chunk(2, dim=-1)
# constrain log_std inside [log_std_min, log_std_max]
log_std = torch.tanh(log_std)
log_std = self.log_std_min + 0.5 * (
self.log_std_max - self.log_std_min
) * (log_std + 1)
self.outputs['mu'] = mu
self.outputs['std'] = log_std.exp()
if compute_pi:
std = log_std.exp()
noise = torch.randn_like(mu)
pi = mu + noise * std
else:
pi = None
entropy = None
if compute_log_pi:
log_pi = gaussian_logprob(noise, log_std)
else:
log_pi = None
mu, pi, log_pi = squash(mu, pi, log_pi)
return mu, pi, log_pi, log_std
def log(self, L, step, log_freq=LOG_FREQ):
if step % log_freq != 0:
return
for k, v in self.outputs.items():
L.log_histogram('train_actor/%s_hist' % k, v, step)
L.log_param('train_actor/fc1', self.trunk[0], step)
L.log_param('train_actor/fc2', self.trunk[2], step)
L.log_param('train_actor/fc3', self.trunk[4], step)
class QFunction(nn.Module):
"""MLP for q-function."""
def __init__(self, obs_dim, action_dim, hidden_dim):
super().__init__()
self.trunk = nn.Sequential(
nn.Linear(obs_dim + action_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, 1)
)
def forward(self, obs, action):
assert obs.size(0) == action.size(0)
obs_action = torch.cat([obs, action], dim=1)
return self.trunk(obs_action)
class Critic(nn.Module):
"""Critic network, employes two q-functions."""
def __init__(
self, obs_shape, action_shape, hidden_dim, encoder_type,
encoder_feature_dim, num_layers, num_filters
):
super().__init__()
self.encoder = make_encoder(
encoder_type, obs_shape, encoder_feature_dim, num_layers,
num_filters, output_logits=True
)
self.Q1 = QFunction(
self.encoder.feature_dim, action_shape[0], hidden_dim
)
self.Q2 = QFunction(
self.encoder.feature_dim, action_shape[0], hidden_dim
)
self.outputs = dict()
self.apply(weight_init)
def forward(self, obs, action, detach_encoder=False):
# detach_encoder allows to stop gradient propogation to encoder
obs = self.encoder(obs, detach=detach_encoder)
q1 = self.Q1(obs, action)
q2 = self.Q2(obs, action)
self.outputs['q1'] = q1
self.outputs['q2'] = q2
return q1, q2
def log(self, L, step, log_freq=LOG_FREQ):
if step % log_freq != 0:
return
self.encoder.log(L, step, log_freq)
for k, v in self.outputs.items():
L.log_histogram('train_critic/%s_hist' % k, v, step)
for i in range(3):
L.log_param('train_critic/q1_fc%d' % i, self.Q1.trunk[i * 2], step)
L.log_param('train_critic/q2_fc%d' % i, self.Q2.trunk[i * 2], step)
class CURL(nn.Module):
"""
CURL
"""
def __init__(self, obs_shape, z_dim, batch_size, critic, critic_target, output_type="continuous"):
super(CURL, self).__init__()
self.batch_size = batch_size
self.encoder = critic.encoder
#PixelEncoder(obs_shape, z_dim, num_layers=2, num_filters=32)
self.encoder_target = critic_target.encoder
#PixelEncoder(obs_shape, z_dim, num_layers=2, num_filters=32)
#self.encoder_target.load_state_dict(self.encoder.state_dict())
self.W = nn.Parameter(torch.rand(z_dim, z_dim))
self.output_type = output_type
def encode(self, x, detach=False, ema=False):
"""
Encoder: z_t = e(x_t)
:param x: x_t, x y coordinates
:return: z_t, value in r2
"""
if ema:
with torch.no_grad():
z_out = self.encoder_target(x)
else:
z_out = self.encoder(x)
if detach:
z_out = z_out.detach()
return z_out
#def update_target(self):
# utils.soft_update_params(self.encoder, self.encoder_target, 0.05)
def compute_logits(self, z_a, z_pos):
"""
Uses logits trick for CURL:
- compute (B,B) matrix z_a (W z_pos.T)
- positives are all diagonal elements
- negatives are all other elements
- to compute loss use multiclass cross entropy with identity matrix for labels
"""
Wz = torch.matmul(self.W, z_pos.T) # (z_dim,B)
logits = torch.matmul(z_a, Wz) # (B,B)
logits = logits - torch.max(logits, 1)[0][:, None]
return logits
class CurlSacAgent(object):
"""CURL representation learning with SAC."""
def __init__(
self,
obs_shape,
action_shape,
device,
hidden_dim=256,
discount=0.99,
init_temperature=0.01,
alpha_lr=1e-3,
alpha_beta=0.9,
actor_lr=1e-3,
actor_beta=0.9,
actor_log_std_min=-10,
actor_log_std_max=2,
actor_update_freq=2,
critic_lr=1e-3,
critic_beta=0.9,
critic_tau=0.005,
critic_target_update_freq=2,
encoder_type='pixel',
encoder_feature_dim=50,
encoder_lr=1e-3,
encoder_tau=0.005,
num_layers=4,
num_filters=32,
cpc_update_freq=1,
log_interval=100,
detach_encoder=False,
curl_latent_dim=128
):
self.device = device
self.discount = discount
self.critic_tau = critic_tau
self.encoder_tau = encoder_tau
self.actor_update_freq = actor_update_freq
self.critic_target_update_freq = critic_target_update_freq
self.cpc_update_freq = cpc_update_freq
self.log_interval = log_interval
self.image_size = obs_shape[-1]
self.curl_latent_dim = curl_latent_dim
self.detach_encoder = detach_encoder
self.actor = Actor(
obs_shape, action_shape, hidden_dim, encoder_type,
encoder_feature_dim, actor_log_std_min, actor_log_std_max,
num_layers, num_filters
).to(device)
self.critic = Critic(
obs_shape, action_shape, hidden_dim, encoder_type,
encoder_feature_dim, num_layers, num_filters
).to(device)
self.critic_target = Critic(
obs_shape, action_shape, hidden_dim, encoder_type,
encoder_feature_dim, num_layers, num_filters
).to(device)
self.critic_target.load_state_dict(self.critic.state_dict())
# create CURL encoder (the 128 batch size is probably unnecessary)
self.CURL = CURL(obs_shape, encoder_feature_dim,
self.curl_latent_dim, self.critic,self.critic_target, output_type='continuous').to(self.device)
# tie encoders between actor and critic, and CURL and critic
self.actor.encoder.copy_conv_weights_from(self.critic.encoder)
self.log_alpha = torch.tensor(np.log(init_temperature)).to(device)
self.log_alpha.requires_grad = True
# set target entropy to -|A|
self.target_entropy = -np.prod(action_shape)
# optimizers
self.actor_optimizer = torch.optim.Adam(
self.actor.parameters(), lr=actor_lr, betas=(actor_beta, 0.999)
)
self.critic_optimizer = torch.optim.Adam(
self.critic.parameters(), lr=critic_lr, betas=(critic_beta, 0.999)
)
self.log_alpha_optimizer = torch.optim.Adam(
[self.log_alpha], lr=alpha_lr, betas=(alpha_beta, 0.999)
)
# optimizer for critic encoder for reconstruction loss
self.encoder_optimizer = torch.optim.Adam(
self.critic.encoder.parameters(), lr=encoder_lr
)
self.cpc_optimizer = torch.optim.Adam(
self.CURL.parameters(), lr=encoder_lr
)
self.cross_entropy_loss = nn.CrossEntropyLoss()
self.train()
self.critic_target.train()
def train(self, training=True):
self.training = training
self.actor.train(training)
self.critic.train(training)
self.CURL.train(training)
@property
def alpha(self):
return self.log_alpha.exp()
def select_action(self, obs):
with torch.no_grad():
obs = torch.FloatTensor(obs).to(self.device)
obs = obs.unsqueeze(0)
mu, _, _, _ = self.actor(
obs, compute_pi=False, compute_log_pi=False
)
return mu.cpu().data.numpy().flatten()
def sample_action(self, obs):
if obs.shape[-1] != self.image_size:
obs = utils.center_crop_image(obs, self.image_size)
with torch.no_grad():
obs = torch.FloatTensor(obs).to(self.device)
obs = obs.unsqueeze(0)
mu, pi, _, _ = self.actor(obs, compute_log_pi=False)
return pi.cpu().data.numpy().flatten()
def update_critic(self, obs, action, reward, next_obs, not_done, L, step):
with torch.no_grad():
_, policy_action, log_pi, _ = self.actor(next_obs)
target_Q1, target_Q2 = self.critic_target(next_obs, policy_action)
target_V = torch.min(target_Q1,
target_Q2) - self.alpha.detach() * log_pi
target_Q = reward + (not_done * self.discount * target_V)
# get current Q estimates
current_Q1, current_Q2 = self.critic(
obs, action, detach_encoder=self.detach_encoder)
critic_loss = F.mse_loss(current_Q1,
target_Q) + F.mse_loss(current_Q2, target_Q)
if step % self.log_interval == 0:
L.log('train_critic/loss', critic_loss, step)
# Optimize the critic
self.critic_optimizer.zero_grad()
critic_loss.backward()
self.critic_optimizer.step()
self.critic.log(L, step)
def update_actor_and_alpha(self, obs, L, step):
# detach encoder, so we don't update it with the actor loss
_, pi, log_pi, log_std = self.actor(obs, detach_encoder=True)
actor_Q1, actor_Q2 = self.critic(obs, pi, detach_encoder=True)
actor_Q = torch.min(actor_Q1, actor_Q2)
actor_loss = (self.alpha.detach() * log_pi - actor_Q).mean()
if step % self.log_interval == 0:
L.log('train_actor/loss', actor_loss, step)
L.log('train_actor/target_entropy', self.target_entropy, step)
entropy = 0.5 * log_std.shape[1] * \
(1.0 + np.log(2 * np.pi)) + log_std.sum(dim=-1)
if step % self.log_interval == 0:
L.log('train_actor/entropy', entropy.mean(), step)
# optimize the actor
self.actor_optimizer.zero_grad()
actor_loss.backward()
self.actor_optimizer.step()
self.actor.log(L, step)
self.log_alpha_optimizer.zero_grad()
alpha_loss = (self.alpha *
(-log_pi - self.target_entropy).detach()).mean()
if step % self.log_interval == 0:
L.log('train_alpha/loss', alpha_loss, step)
L.log('train_alpha/value', self.alpha, step)
alpha_loss.backward()
self.log_alpha_optimizer.step()
def update_cpc(self, obs_anchor, obs_pos, cpc_kwargs, L, step):
# time flips
"""
time_pos = cpc_kwargs["time_pos"]
time_anchor= cpc_kwargs["time_anchor"]
obs_anchor = torch.cat((obs_anchor, time_anchor), 0)
obs_pos = torch.cat((obs_anchor, time_pos), 0)
"""
z_a = self.CURL.encode(obs_anchor)
z_pos = self.CURL.encode(obs_pos, ema=True)
logits = self.CURL.compute_logits(z_a, z_pos)
labels = torch.arange(logits.shape[0]).long().to(self.device)
loss = self.cross_entropy_loss(logits, labels)
self.encoder_optimizer.zero_grad()
self.cpc_optimizer.zero_grad()
loss.backward()
self.encoder_optimizer.step()
self.cpc_optimizer.step()
if step % self.log_interval == 0:
L.log('train_cpc/cpc_loss', loss, step)
def update(self, replay_buffer, L, step):
obs, action, reward, next_obs, not_done, cpc_kwargs = replay_buffer.sample_cpc()
if step % self.log_interval == 0:
L.log('train/batch_reward', reward.mean(), step)
self.update_critic(obs, action, reward, next_obs, not_done, L, step)
if step % self.actor_update_freq == 0:
self.update_actor_and_alpha(obs, L, step)
if step % self.critic_target_update_freq == 0:
utils.soft_update_params(
self.critic.Q1, self.critic_target.Q1, self.critic_tau
)
utils.soft_update_params(
self.critic.Q2, self.critic_target.Q2, self.critic_tau
)
utils.soft_update_params(
self.critic.encoder, self.critic_target.encoder,
self.encoder_tau
)
obs_anchor, obs_pos = cpc_kwargs["obs_anchor"], cpc_kwargs["obs_pos"]
if step % self.cpc_update_freq == 0:
self.update_cpc(obs_anchor, obs_pos,cpc_kwargs, L, step)
def save(self, model_dir, step):
torch.save(
self.actor.state_dict(), '%s/actor_%s.pt' % (model_dir, step)
)
torch.save(
self.critic.state_dict(), '%s/critic_%s.pt' % (model_dir, step)
)
def load(self, model_dir, step):
self.actor.load_state_dict(
torch.load('%s/actor_%s.pt' % (model_dir, step))
)
self.critic.load_state_dict(
torch.load('%s/critic_%s.pt' % (model_dir, step))
)
+124
View File
@@ -0,0 +1,124 @@
import torch
import torch.nn as nn
def tie_weights(src, trg):
assert type(src) == type(trg)
trg.weight = src.weight
trg.bias = src.bias
OUT_DIM = {2: 39, 4: 35, 6: 31}
OUT_DIM_64 = {2: 29, 4: 25, 6: 21}
class PixelEncoder(nn.Module):
"""Convolutional encoder of pixels observations."""
def __init__(self, obs_shape, feature_dim, num_layers=2, num_filters=32,output_logits=False):
super().__init__()
assert len(obs_shape) == 3
self.obs_shape = obs_shape
self.feature_dim = feature_dim
self.num_layers = num_layers
# try 2 5x5s with strides 2x2. with samep adding, it should reduce 84 to 21, so with valid, it should be even smaller than 21.
self.convs = nn.ModuleList(
[nn.Conv2d(obs_shape[0], num_filters, 3, stride=2)]
)
for i in range(num_layers - 1):
self.convs.append(nn.Conv2d(num_filters, num_filters, 3, stride=1))
out_dim = OUT_DIM_64[num_layers] if obs_shape[-1] == 64 else OUT_DIM[num_layers]
self.fc = nn.Linear(num_filters * out_dim * out_dim, self.feature_dim)
self.ln = nn.LayerNorm(self.feature_dim)
self.outputs = dict()
self.output_logits = output_logits
def reparameterize(self, mu, logstd):
std = torch.exp(logstd)
eps = torch.randn_like(std)
return mu + eps * std
def forward_conv(self, obs):
obs = obs / 255.
self.outputs['obs'] = obs
conv = torch.relu(self.convs[0](obs))
self.outputs['conv1'] = conv
for i in range(1, self.num_layers):
conv = torch.relu(self.convs[i](conv))
self.outputs['conv%s' % (i + 1)] = conv
h = conv.view(conv.size(0), -1)
return h
def forward(self, obs, detach=False):
h = self.forward_conv(obs)
if detach:
h = h.detach()
h_fc = self.fc(h)
self.outputs['fc'] = h_fc
h_norm = self.ln(h_fc)
self.outputs['ln'] = h_norm
if self.output_logits:
out = h_norm
else:
out = torch.tanh(h_norm)
self.outputs['tanh'] = out
return out
def copy_conv_weights_from(self, source):
"""Tie convolutional layers"""
# only tie conv layers
for i in range(self.num_layers):
tie_weights(src=source.convs[i], trg=self.convs[i])
def log(self, L, step, log_freq):
if step % log_freq != 0:
return
for k, v in self.outputs.items():
L.log_histogram('train_encoder/%s_hist' % k, v, step)
if len(v.shape) > 2:
L.log_image('train_encoder/%s_img' % k, v[0], step)
for i in range(self.num_layers):
L.log_param('train_encoder/conv%s' % (i + 1), self.convs[i], step)
L.log_param('train_encoder/fc', self.fc, step)
L.log_param('train_encoder/ln', self.ln, step)
class IdentityEncoder(nn.Module):
def __init__(self, obs_shape, feature_dim, num_layers, num_filters,*args):
super().__init__()
assert len(obs_shape) == 1
self.feature_dim = obs_shape[0]
def forward(self, obs, detach=False):
return obs
def copy_conv_weights_from(self, source):
pass
def log(self, L, step, log_freq):
pass
_AVAILABLE_ENCODERS = {'pixel': PixelEncoder, 'identity': IdentityEncoder}
def make_encoder(
encoder_type, obs_shape, feature_dim, num_layers, num_filters, output_logits=False
):
assert encoder_type in _AVAILABLE_ENCODERS
return _AVAILABLE_ENCODERS[encoder_type](
obs_shape, feature_dim, num_layers, num_filters, output_logits
)
+163
View File
@@ -0,0 +1,163 @@
from torch.utils.tensorboard import SummaryWriter
from collections import defaultdict
import json
import os
import shutil
import torch
import torchvision
import numpy as np
from termcolor import colored
FORMAT_CONFIG = {
'rl': {
'train': [
('episode', 'E', 'int'), ('step', 'S', 'int'),
('duration', 'D', 'time'), ('episode_reward', 'R', 'float'),
('batch_reward', 'BR', 'float'), ('actor_loss', 'ALOSS', 'float'),
('critic_loss', 'CLOSS', 'float'), ('ae_loss', 'RLOSS', 'float')
],
'eval': [('step', 'S', 'int'), ('episode_reward', 'ER', 'float')]
}
}
class AverageMeter(object):
def __init__(self):
self._sum = 0
self._count = 0
def update(self, value, n=1):
self._sum += value
self._count += n
def value(self):
return self._sum / max(1, self._count)
class MetersGroup(object):
def __init__(self, file_name, formating):
self._file_name = file_name
if os.path.exists(file_name):
os.remove(file_name)
self._formating = formating
self._meters = defaultdict(AverageMeter)
def log(self, key, value, n=1):
self._meters[key].update(value, n)
def _prime_meters(self):
data = dict()
for key, meter in self._meters.items():
if key.startswith('train'):
key = key[len('train') + 1:]
else:
key = key[len('eval') + 1:]
key = key.replace('/', '_')
data[key] = meter.value()
return data
def _dump_to_file(self, data):
with open(self._file_name, 'a') as f:
f.write(json.dumps(data) + '\n')
def _format(self, key, value, ty):
template = '%s: '
if ty == 'int':
template += '%d'
elif ty == 'float':
template += '%.04f'
elif ty == 'time':
template += '%.01f s'
else:
raise 'invalid format type: %s' % ty
return template % (key, value)
def _dump_to_console(self, data, prefix):
prefix = colored(prefix, 'yellow' if prefix == 'train' else 'green')
pieces = ['{:5}'.format(prefix)]
for key, disp_key, ty in self._formating:
value = data.get(key, 0)
pieces.append(self._format(disp_key, value, ty))
print('| %s' % (' | '.join(pieces)))
def dump(self, step, prefix):
if len(self._meters) == 0:
return
data = self._prime_meters()
data['step'] = step
self._dump_to_file(data)
self._dump_to_console(data, prefix)
self._meters.clear()
class Logger(object):
def __init__(self, log_dir, use_tb=True, config='rl'):
self._log_dir = log_dir
if use_tb:
tb_dir = os.path.join(log_dir, 'tb')
if os.path.exists(tb_dir):
shutil.rmtree(tb_dir)
self._sw = SummaryWriter(tb_dir)
else:
self._sw = None
self._train_mg = MetersGroup(
os.path.join(log_dir, 'train.log'),
formating=FORMAT_CONFIG[config]['train']
)
self._eval_mg = MetersGroup(
os.path.join(log_dir, 'eval.log'),
formating=FORMAT_CONFIG[config]['eval']
)
def _try_sw_log(self, key, value, step):
if self._sw is not None:
self._sw.add_scalar(key, value, step)
def _try_sw_log_image(self, key, image, step):
if self._sw is not None:
assert image.dim() == 3
grid = torchvision.utils.make_grid(image.unsqueeze(1))
self._sw.add_image(key, grid, step)
def _try_sw_log_video(self, key, frames, step):
if self._sw is not None:
frames = torch.from_numpy(np.array(frames))
frames = frames.unsqueeze(0)
self._sw.add_video(key, frames, step, fps=30)
def _try_sw_log_histogram(self, key, histogram, step):
if self._sw is not None:
self._sw.add_histogram(key, histogram, step)
def log(self, key, value, step, n=1):
assert key.startswith('train') or key.startswith('eval')
if type(value) == torch.Tensor:
value = value.item()
self._try_sw_log(key, value / n, step)
mg = self._train_mg if key.startswith('train') else self._eval_mg
mg.log(key, value, n)
def log_param(self, key, param, step):
self.log_histogram(key + '_w', param.weight.data, step)
if hasattr(param.weight, 'grad') and param.weight.grad is not None:
self.log_histogram(key + '_w_g', param.weight.grad.data, step)
if hasattr(param, 'bias'):
self.log_histogram(key + '_b', param.bias.data, step)
if hasattr(param.bias, 'grad') and param.bias.grad is not None:
self.log_histogram(key + '_b_g', param.bias.grad.data, step)
def log_image(self, key, image, step):
assert key.startswith('train') or key.startswith('eval')
self._try_sw_log_image(key, image, step)
def log_video(self, key, frames, step):
assert key.startswith('train') or key.startswith('eval')
self._try_sw_log_video(key, frames, step)
def log_histogram(self, key, histogram, step):
assert key.startswith('train') or key.startswith('eval')
self._try_sw_log_histogram(key, histogram, step)
def dump(self, step):
self._train_mg.dump(step, 'train')
self._eval_mg.dump(step, 'eval')
View File
Executable
+66
View File
@@ -0,0 +1,66 @@
# curl cheetah, crop 76 > 64, grayscale + random crop, deep stack
# batch size = 256 instead of 128. maybe 256 makes 64x64 work. And, try using 512 with 64x64.
# then try adam LR (smaller) - 3e-4... You can try 2e-4 and 5e-4.
# try using the stochastic policy for eval. (you can do later.. for now the important thing is to run ablations.)
# try bigger frame stack, maybe 8.
# parser.add_argument('--critic_tau', default=0.01, type=float) # try 0.05 or 0.1
# run 1: batch 256, first try with 84
# run 2: batch 512, first try with 84, then try 64, then try their encoder
# run 3: batch 256, first try with 84
# run 4: batch 512, first try with 84, then try 64, then try their encoder
# run 5: 2e-4 lr for all
# run 6: 5e-4 for all
# run 7: critic higher tau, 0.05
# try stochastic critic eval
CUDA_VISIBLE_DEVICES=1 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb2cheetah/curl_cheetah_b256_84 \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 10000 --batch_size 128 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=2 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 \
--save_tb --work_dir ./tmp/icml/feb2cheetah/curl_cheetah_b512_84 \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 20000 --batch_size 512 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=3 python train.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb2cheetah/rad_cheetah_b256_84 \
--agent sac_ae --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 20000 --batch_size 128 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=4 python train.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 \
--save_tb --work_dir ./tmp/icml/feb2cheetah/rad_cheetah_b512_84 \
--agent sac_ae --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 20000 --batch_size 512 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=7 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 \
--save_tb --work_dir ./tmp/icml/feb2cheetah/curl_cheetah_b256_84_lr3e4 \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --encoder_lr 3e-4 --critic_lr 3e-4 --actor_lr 3e-4 \
--eval_freq 20000 --batch_size 256 --num_train_steps 3000000
+65
View File
@@ -0,0 +1,65 @@
#!/bin/bash
# 256
# 2e4 lr 2x
# 5e4 lr 2x
# 1e3 lr 2x
CUDA_VISIBLE_DEVICES=1 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb3cheetah/curl_cheetah_b256_84_lr2e4_a \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --encoder_lr 2e-4 --critic_lr 2e-4 --actor_lr 2e-4 --eval_freq 20000 --batch_size 128 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=2 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb3cheetah/curl_cheetah_b256_84_lr2e4_b \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed -1 --encoder_lr 2e-4 --critic_lr 2e-4 --actor_lr 2e-4 --eval_freq 20000 --batch_size 128 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=3 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb3cheetah/curl_cheetah_b256_84_lr5e4_a \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --encoder_lr 5e-4 --critic_lr 5e-4 --actor_lr 5e-4 --eval_freq 20000 --batch_size 128 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=4 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb3cheetah/curl_cheetah_b256_84_lr5e4_b \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed -1 --encoder_lr 5e-4 --critic_lr 5e-4 --actor_lr 5e-4 --eval_freq 20000 --batch_size 128 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=5 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb3cheetah/curl_cheetah_b256_84_lr1e3_a \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed 23 --encoder_lr 1e-3 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 20000 --batch_size 128 --num_train_steps 3000000 &
CUDA_VISIBLE_DEVICES=6 python train_cpc.py \
--domain_name cheetah \
--task_name run --dmc2gym \
--encoder_type pixel \
--decoder_type identity \
--action_repeat 4 --batch_size 256 \
--save_tb --work_dir ./tmp/icml/feb3cheetah/curl_cheetah_b256_84_lr1e3_b \
--agent sac_cpc --frame_stack 3 --pre_transform_image_size 100 --image_size 84 \
--seed -1 --encoder_lr 1e-3 --critic_lr 1e-3 --actor_lr 1e-3 --eval_freq 20000 --batch_size 128 --num_train_steps 3000000
View File
+273
View File
@@ -0,0 +1,273 @@
import numpy as np
import torch
import argparse
import os
import math
import gym
import sys
import random
import time
import json
import dmc2gym
import copy
import utils
from logger import Logger
from video import VideoRecorder
from curl_sac import CurlSacAgent
from torchvision import transforms
def parse_args():
parser = argparse.ArgumentParser()
# environment
parser.add_argument('--domain_name', default='cheetah')
parser.add_argument('--task_name', default='run')
parser.add_argument('--pre_transform_image_size', default=100, type=int)
parser.add_argument('--image_size', default=84, type=int)
parser.add_argument('--action_repeat', default=1, type=int)
parser.add_argument('--frame_stack', default=3, type=int)
# replay buffer
parser.add_argument('--replay_buffer_capacity', default=100000, type=int)
# train
parser.add_argument('--agent', default='curl_sac', type=str)
parser.add_argument('--init_steps', default=1000, type=int)
parser.add_argument('--num_train_steps', default=1000000, type=int)
parser.add_argument('--batch_size', default=32, type=int)
parser.add_argument('--hidden_dim', default=1024, type=int)
# eval
parser.add_argument('--eval_freq', default=1000, type=int)
parser.add_argument('--num_eval_episodes', default=10, type=int)
# critic
parser.add_argument('--critic_lr', default=1e-3, type=float)
parser.add_argument('--critic_beta', default=0.9, type=float)
parser.add_argument('--critic_tau', default=0.01, type=float) # try 0.05 or 0.1
parser.add_argument('--critic_target_update_freq', default=2, type=int) # try to change it to 1 and retain 0.01 above
# actor
parser.add_argument('--actor_lr', default=1e-3, type=float)
parser.add_argument('--actor_beta', default=0.9, type=float)
parser.add_argument('--actor_log_std_min', default=-10, type=float)
parser.add_argument('--actor_log_std_max', default=2, type=float)
parser.add_argument('--actor_update_freq', default=2, type=int)
# encoder
parser.add_argument('--encoder_type', default='pixel', type=str)
parser.add_argument('--encoder_feature_dim', default=50, type=int)
parser.add_argument('--encoder_lr', default=1e-3, type=float)
parser.add_argument('--encoder_tau', default=0.05, type=float)
parser.add_argument('--num_layers', default=4, type=int)
parser.add_argument('--num_filters', default=32, type=int)
parser.add_argument('--curl_latent_dim', default=128, type=int)
# sac
parser.add_argument('--discount', default=0.99, type=float)
parser.add_argument('--init_temperature', default=0.1, type=float)
parser.add_argument('--alpha_lr', default=1e-4, type=float)
parser.add_argument('--alpha_beta', default=0.5, type=float)
# misc
parser.add_argument('--seed', default=1, type=int)
parser.add_argument('--work_dir', default='.', type=str)
parser.add_argument('--save_tb', default=False, action='store_true')
parser.add_argument('--save_buffer', default=False, action='store_true')
parser.add_argument('--save_video', default=False, action='store_true')
parser.add_argument('--save_model', default=False, action='store_true')
parser.add_argument('--detach_encoder', default=False, action='store_true')
parser.add_argument('--log_interval', default=100, type=int)
args = parser.parse_args()
return args
def evaluate(env, agent, video, num_episodes, L, step, args):
all_ep_rewards = []
def run_eval_loop(sample_stochastically=True):
prefix = 'stochastic' if sample_stochastically else ''
for i in range(num_episodes):
obs = env.reset()
video.init(enabled=(i == 0))
done = False
episode_reward = 0
while not done:
# center crop image
obs = utils.center_crop_image(obs,args.image_size)
with utils.eval_mode(agent):
if sample_stochastically:
action = agent.sample_action(obs)
else:
action = agent.select_action(obs)
obs, reward, done, _ = env.step(action)
video.record(env)
episode_reward += reward
video.save('%d.mp4' % step)
L.log('eval/' + prefix + '_episode_reward', episode_reward, step)
all_ep_rewards.append(episode_reward)
mean_ep_reward = np.mean(all_ep_rewards)
best_ep_reward = np.max(all_ep_rewards)
L.log('eval/' + prefix + 'mean_episode_reward', mean_ep_reward, step)
L.log('eval/' + prefix + 'best_episode_reward', best_ep_reward, step)
run_eval_loop(sample_stochastically=True)
run_eval_loop(sample_stochastically=False)
L.dump(step)
def make_agent(obs_shape, action_shape, args, device):
if args.agent == 'curl_sac':
return CurlSacAgent(
obs_shape=obs_shape,
action_shape=action_shape,
device=device,
hidden_dim=args.hidden_dim,
discount=args.discount,
init_temperature=args.init_temperature,
alpha_lr=args.alpha_lr,
alpha_beta=args.alpha_beta,
actor_lr=args.actor_lr,
actor_beta=args.actor_beta,
actor_log_std_min=args.actor_log_std_min,
actor_log_std_max=args.actor_log_std_max,
actor_update_freq=args.actor_update_freq,
critic_lr=args.critic_lr,
critic_beta=args.critic_beta,
critic_tau=args.critic_tau,
critic_target_update_freq=args.critic_target_update_freq,
encoder_type=args.encoder_type,
encoder_feature_dim=args.encoder_feature_dim,
encoder_lr=args.encoder_lr,
encoder_tau=args.encoder_tau,
num_layers=args.num_layers,
num_filters=args.num_filters,
log_interval=args.log_interval,
detach_encoder=args.detach_encoder,
curl_latent_dim=args.curl_latent_dim
)
else:
assert 'agent is not supported: %s' % args.agent
def main():
args = parse_args()
if args.seed == -1:
args.__dict__["seed"] = np.random.randint(1,1000)
utils.set_seed_everywhere(args.seed)
env = dmc2gym.make(
domain_name=args.domain_name,
task_name=args.task_name,
seed=args.seed,
visualize_reward=False,
from_pixels=(args.encoder_type == 'pixel'),
height=args.pre_transform_image_size,
width=args.pre_transform_image_size,
frame_skip=args.action_repeat
)
env.seed(args.seed)
# stack several consecutive frames together
if args.encoder_type == 'pixel':
env = utils.FrameStack(env, k=args.frame_stack)
utils.make_dir(args.work_dir)
video_dir = utils.make_dir(os.path.join(args.work_dir, 'video'))
model_dir = utils.make_dir(os.path.join(args.work_dir, 'model'))
buffer_dir = utils.make_dir(os.path.join(args.work_dir, 'buffer'))
video = VideoRecorder(video_dir if args.save_video else None)
with open(os.path.join(args.work_dir, 'args.json'), 'w') as f:
json.dump(vars(args), f, sort_keys=True, indent=4)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
action_shape = env.action_space.shape
if args.encoder_type == 'pixel':
obs_shape = (3*args.frame_stack, args.image_size, args.image_size)
pre_aug_obs_shape = (3*args.frame_stack,args.pre_transform_image_size,args.pre_transform_image_size)
else:
obs_shape = env.observation_space.shape
pre_aug_obs_shape = obs_shape
replay_buffer = utils.ReplayBuffer(
obs_shape=pre_aug_obs_shape,
action_shape=action_shape,
capacity=args.replay_buffer_capacity,
batch_size=args.batch_size,
device=device,
image_size=args.image_size,
)
agent = make_agent(
obs_shape=obs_shape,
action_shape=action_shape,
args=args,
device=device
)
L = Logger(args.work_dir, use_tb=args.save_tb)
episode, episode_reward, done = 0, 0, True
start_time = time.time()
for step in range(args.num_train_steps):
# evaluate agent periodically
if step % args.eval_freq == 0:
L.log('eval/episode', episode, step)
evaluate(env, agent, video, args.num_eval_episodes, L, step,args)
if args.save_model:
agent.save(model_dir, step)
if args.save_buffer:
replay_buffer.save(buffer_dir)
if done:
if step > 0:
if step % args.log_interval == 0:
L.log('train/duration', time.time() - start_time, step)
L.dump(step)
start_time = time.time()
if step % args.log_interval == 0:
L.log('train/episode_reward', episode_reward, step)
obs = env.reset()
done = False
episode_reward = 0
episode_step = 0
episode += 1
if step % args.log_interval == 0:
L.log('train/episode', episode, step)
# sample action for data collection
if step < args.init_steps:
action = env.action_space.sample()
else:
with utils.eval_mode(agent):
action = agent.sample_action(obs)
# run training update
if step >= args.init_steps:
num_updates = 1 #args.init_steps if step == args.init_steps else 1
for _ in range(num_updates):
agent.update(replay_buffer, L, step)
next_obs, reward, done, _ = env.step(action)
# allow infinit bootstrap
done_bool = 0 if episode_step + 1 == env._max_episode_steps else float(
done
)
episode_reward += reward
#action = np.array([action], dtype="float32")
replay_buffer.add(obs, action, reward, next_obs, done_bool)
obs = next_obs
episode_step += 1
if __name__ == '__main__':
torch.multiprocessing.set_start_method('spawn')
main()
+497
View File
@@ -0,0 +1,497 @@
import torch
import numpy as np
import torch.nn as nn
import gym
import os
from collections import deque
import random
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import time
from imgaug import augmenters as iaa
from skimage.util.shape import view_as_windows
class eval_mode(object):
def __init__(self, *models):
self.models = models
def __enter__(self):
self.prev_states = []
for model in self.models:
self.prev_states.append(model.training)
model.train(False)
def __exit__(self, *args):
for model, state in zip(self.models, self.prev_states):
model.train(state)
return False
def soft_update_params(net, target_net, tau):
for param, target_param in zip(net.parameters(), target_net.parameters()):
target_param.data.copy_(
tau * param.data + (1 - tau) * target_param.data
)
def set_seed_everywhere(seed):
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
np.random.seed(seed)
random.seed(seed)
def module_hash(module):
result = 0
for tensor in module.state_dict().values():
result += tensor.sum().item()
return result
def make_dir(dir_path):
try:
os.mkdir(dir_path)
except OSError:
pass
return dir_path
def preprocess_obs(obs, bits=5):
"""Preprocessing image, see https://arxiv.org/abs/1807.03039."""
bins = 2**bits
assert obs.dtype == torch.float32
if bits < 8:
obs = torch.floor(obs / 2**(8 - bits))
obs = obs / bins
obs = obs + torch.rand_like(obs) / bins
obs = obs - 0.5
return obs
class ReplayBuffer(Dataset):
"""Buffer to store environment transitions."""
def __init__(self, obs_shape, action_shape, capacity, batch_size, device,image_size=84,transform=None):
self.capacity = capacity
self.batch_size = batch_size
self.device = device
self.image_size = image_size
self.transform = transform
# the proprioceptive obs is stored as float32, pixels obs as uint8
obs_dtype = np.float32 if len(obs_shape) == 1 else np.uint8
self.obses = np.empty((capacity, *obs_shape), dtype=obs_dtype)
self.next_obses = np.empty((capacity, *obs_shape), dtype=obs_dtype)
self.actions = np.empty((capacity, *action_shape), dtype=np.float32)
self.rewards = np.empty((capacity, 1), dtype=np.float32)
self.not_dones = np.empty((capacity, 1), dtype=np.float32)
self.idx = 0
self.last_save = 0
self.full = False
self.seq = iaa.Sequential([
# crop images from each side by 0 to 16px (randomly chosen)
iaa.Crop(px=(0, 20)),
#iaa.Fliplr(0.5),
# iaa.Affine(
#scale={"x": (0.8, 1.2), "y": (0.8, 1.2)},
#translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)},
#rotate=(-20, 20),
#shear=(-8, 8)
#)
], random_order=True)
def add(self, obs, action, reward, next_obs, done):
np.copyto(self.obses[self.idx], obs)
np.copyto(self.actions[self.idx], action)
np.copyto(self.rewards[self.idx], reward)
np.copyto(self.next_obses[self.idx], next_obs)
np.copyto(self.not_dones[self.idx], not done)
self.idx = (self.idx + 1) % self.capacity
self.full = self.full or self.idx == 0
def sample(self,data_aug=False):
start = time.time()
idxs = np.random.randint(
0, self.capacity if self.full else self.idx, size=self.batch_size
)
obses = self.obses[idxs]
next_obses = self.next_obses[idxs]
"""
Try to do a batch crop and see result
"""
# crop batch
obses = fast_random_crop(obses, self.image_size)
next_obses = fast_random_crop(next_obses, self.image_size)
obses = torch.as_tensor(obses, device=self.device).float()
actions = torch.as_tensor(self.actions[idxs], device=self.device)
rewards = torch.as_tensor(self.rewards[idxs], device=self.device)
next_obses = torch.as_tensor(
next_obses, device=self.device
).float()
not_dones = torch.as_tensor(self.not_dones[idxs], device=self.device)
#obses = random_grayscale_stack(obses,self.device,p=0.2)
#next_obses = random_grayscale_stack(next_obses,self.device,p=0.2)
return obses, actions, rewards, next_obses, not_dones
def sample_cpc(self):
start = time.time()
idxs = np.random.randint(
0, self.capacity if self.full else self.idx, size=self.batch_size
)
obses = self.obses[idxs]
next_obses = self.next_obses[idxs]
pos = obses.copy()
#obses = self.seq(images=np.transpose(obses, (0, 2, 3, 1)))
#pos = self.seq(images=np.transpose(pos, (0, 2, 3, 1)))
#neg = self.seq(images=np.transpose(neg, (0, 2, 3, 1)))
#next_obses = self.seq(images=np.transpose(next_obses, (0, 2, 3, 1)))
#obses = np.transpose(obses, (0, 3, 1, 2))
#next_obses = np.transpose(next_obses, (0, 3, 1, 2))
#pos = np.transpose(pos, (0, 3, 1, 2))
#neg = np.transpose(neg, (0, 3, 1, 2))
# random crop
# time flip
#time_flip_obses = obses[:, ::-1, ...].copy()
#time_pos = time_flip_obses.copy()
obses = fast_random_crop(obses, self.image_size)
next_obses = fast_random_crop(next_obses, self.image_size)
pos = fast_random_crop(pos, self.image_size)
#time_flip_obses = fast_random_crop(time_flip_obses, 84)
#time_pos = fast_random_crop(time_pos, 84)
# random flip
#obses = random_flip(obses,.2)
#next_obses = random_flip(next_obses, .2)
#pos = random_flip(pos, .2)
obses = torch.as_tensor(obses, device=self.device).float()
#time_flip_obses = torch.as_tensor(time_flip_obses, device=self.device).float()
#time_pos = torch.as_tensor(time_pos, device=self.device).float()
next_obses = torch.as_tensor(
next_obses, device=self.device
).float()
actions = torch.as_tensor(self.actions[idxs], device=self.device)
rewards = torch.as_tensor(self.rewards[idxs], device=self.device)
not_dones = torch.as_tensor(self.not_dones[idxs], device=self.device)
pos = torch.as_tensor(pos, device=self.device).float()
#obses = random_grayscale_stack(obses,self.device,p=.2)
#next_obses = random_grayscale_stack(obses,self.device,p=.2)
#pos = random_grayscale_stack(obses,self.device,p=.2)
# repeat negatives to make a batch of N
# most likely source of mistake is these negatives
# currently being stacked [[1,2,3],[1,2,3],...]
# but we may wany [[1,1,1],[2,2,2],[3,3,3]...]
#neg = neg.repeat(64,1,1,1)
#cpc_kwargs = dict(obs_anchor=obses,obs_pos=pos,time_anchor=time_flip_obses, time_pos=time_pos)
cpc_kwargs = dict(obs_anchor=obses, obs_pos=pos,
time_anchor=None, time_pos=None)
return obses, actions, rewards, next_obses, not_dones, cpc_kwargs
def save(self, save_dir):
if self.idx == self.last_save:
return
path = os.path.join(save_dir, '%d_%d.pt' % (self.last_save, self.idx))
payload = [
self.obses[self.last_save:self.idx],
self.next_obses[self.last_save:self.idx],
self.actions[self.last_save:self.idx],
self.rewards[self.last_save:self.idx],
self.not_dones[self.last_save:self.idx]
]
self.last_save = self.idx
torch.save(payload, path)
def load(self, save_dir):
chunks = os.listdir(save_dir)
chucks = sorted(chunks, key=lambda x: int(x.split('_')[0]))
for chunk in chucks:
start, end = [int(x) for x in chunk.split('.')[0].split('_')]
path = os.path.join(save_dir, chunk)
payload = torch.load(path)
assert self.idx == start
self.obses[start:end] = payload[0]
self.next_obses[start:end] = payload[1]
self.actions[start:end] = payload[2]
self.rewards[start:end] = payload[3]
self.not_dones[start:end] = payload[4]
self.idx = end
def __getitem__(self, idx):
idx = np.random.randint(
0, self.capacity if self.full else self.idx, size=1
)
idx = idx[0]
obs = self.obses[idx]
action = self.actions[idx]
reward = self.rewards[idx]
next_obs = self.next_obses[idx]
not_done = self.not_dones[idx]
if self.transform:
obs = self.transform(obs)
next_obs = self.transform(next_obs)
return obs, action, reward, next_obs, not_done
def __len__(self):
return self.capacity
class FrameStack(gym.Wrapper):
def __init__(self, env, k):
gym.Wrapper.__init__(self, env)
self._k = k
self._frames = deque([], maxlen=k)
shp = env.observation_space.shape
self.observation_space = gym.spaces.Box(
low=0,
high=1,
shape=((shp[0] * k,) + shp[1:]),
dtype=env.observation_space.dtype
)
self._max_episode_steps = env._max_episode_steps
def reset(self):
obs = self.env.reset()
for _ in range(self._k):
self._frames.append(obs)
return self._get_obs()
def step(self, action):
obs, reward, done, info = self.env.step(action)
self._frames.append(obs)
return self._get_obs(), reward, done, info
def _get_obs(self):
assert len(self._frames) == self._k
return np.concatenate(list(self._frames), axis=0)
"""
Various transforms
"""
class RandomCrop(object):
"""Crop randomly the image in a sample.
Args:
output_size (tuple or int): Desired output size. If int, square crop
is made.
"""
def __init__(self, output_size):
assert isinstance(output_size, (int, tuple))
if isinstance(output_size, int):
self.output_size = (output_size, output_size)
else:
assert len(output_size) == 2
self.output_size = output_size
def __call__(self, image):
h, w = image.shape[1:]
new_h, new_w = self.output_size
top = np.random.randint(0, h - new_h)
left = np.random.randint(0, w - new_w)
image = image[:, top: top + new_h, left: left + new_w]
return image
def random_crop(imgs,output_size):
h, w = imgs.shape[2:]
new_h, new_w = output_size, output_size
if h > new_h:
top = np.random.randint(0, h - new_h)
left = np.random.randint(0, w - new_w)
imgs = imgs[:,:, top: top + new_h, left: left + new_w]
return imgs
def fast_random_crop(imgs, output_size):
"""
Vectorized way to do random crop using sliding windows
and picking out random ones
args:
imgs, batch images with shape (B,C,H,W)
"""
# batch size
n = imgs.shape[0]
img_size = imgs.shape[-1]
crop_max = img_size - output_size
imgs = np.transpose(imgs, (0, 2, 3, 1))
w1 = np.random.randint(0, crop_max, n)
h1 = np.random.randint(0, crop_max, n)
# creates all sliding windows combinations of size (output_size)
windows = view_as_windows(
imgs, (1, output_size, output_size, 1))[..., 0,:,:, 0]
# selects a random window for each batch element
cropped_imgs = windows[np.arange(n), w1, h1]
return cropped_imgs
def random_flip(imgs, prob=0.2):
B = imgs.shape[0]
N = int(prob*B)
flipped_imgs = imgs[..., ::-1].copy()
idxs = np.random.choice(B, size=(N,), replace=False)
imgs[idxs] = flipped_imgs[idxs]
return imgs
def time_flip(imgs,device):
time_flipped_imgs = imgs[:,::-1, ...].copy()
all_imgs = np.concatenate((imgs, time_flipped_imgs), 0)
return all_imgs
def grayscale(imgs,device):
# imgs: b x c x h x w
b, c, h, w = imgs.shape
frames = c // 3
imgs = imgs.view([b,frames,3,h,w])
imgs = imgs[:, :, 0, ...] * 0.2989 + imgs[:, :, 1, ...] * 0.587 + imgs[:, :, 2, ...] * 0.114
imgs = imgs.type(torch.uint8).float()
# assert len(imgs.shape) == 3, imgs.shape
imgs = imgs[:, :, None, :, :]
imgs = imgs * torch.ones([1, 1, 3, 1, 1], dtype=imgs.dtype).float().to(device) # broadcast tiling
return imgs
def random_grayscale(images,device,p=1.):
# images: [B, C, H, W]
gray_images = grayscale(images,device)
rnd = np.random.uniform(0., 1., size=(images.shape[0],))
mask = rnd <= p
mask = torch.from_numpy(mask)
frames = images.shape[1] // 3
images = images.view(*gray_images.shape)
mask = mask[:, None] * torch.ones([1, frames]).type(mask.dtype)
mask = mask.type(images.dtype).to(device)
mask = mask[:, :, None, None, None]
return mask * gray_images + (1 - mask) * images
def random_grayscale_stack(stack,device,p=0.5):
# stack: B X C x H x W, C = num_frames * 3.
bs, channels, h, w = stack.shape
num_frames = channels // 3
#stack = stack.view([-1, 3, h, w])
stack = random_grayscale(stack, device,p=p)
stack = stack.view([bs, -1, h, w])
return stack
def random_rotate(imgs):
k = np.random.randint(4)
imgs = np.ascontiguousarray(np.rot90(imgs,k=k,axes=(-2,-1)))
return imgs
def center_crop_image(image, output_size):
h, w = image.shape[1:]
new_h, new_w = output_size, output_size
top = (h - new_h)//2
left = (w - new_w)//2
image = image[:, top:top + new_h, left:left + new_w]
return image
class CenterCrop(object):
"""Center crop the image in a sample.
Args:
output_size (tuple or int): Desired output size. If int, square crop
is made.
"""
def __init__(self, output_size):
assert isinstance(output_size, (int,))
self.output_size = (output_size, output_size)
def __call__(self, image):
h, w = image.shape[1:]
new_h, new_w = self.output_size
top = (h - new_h)//2
left = (w - new_w)//2
image = image[:, top: top + new_h, left: left + new_w]
return image
class ToTensor(object):
"""Convert ndarrays in sample to Tensors."""
def __call__(self, image,device):
# torch image: C X H X W
return torch.from_numpy(image,)
class Grayscale(object):
"""Convert ndarrays in sample to grayscale randomly."""
def __init__(self, prob):
self.prob = prob
def __call__(self, image):
if self.prob > np.random.uniform():
image = self.rgb2gray(image)
return image
def rgb2gray(self, rgb):
rgb = np.transpose(rgb, (1, 2, 0))
rgb = np.expand_dims(np.dot(rgb[..., :3], [0.2989, 0.5870, 0.1140]), 0)
rgb = np.repeat(rgb, 3, 0)
return rgb.astype(np.uint8)
class Flip(object):
"""Convert ndarrays in sample to flip randomly."""
def __init__(self, prob):
self.prob = prob
def __call__(self, image):
if self.prob > np.random.uniform():
image = self.flip(image)
return image
def flip(self, img):
return np.transpose(img, (0, 2, 1))
+38
View File
@@ -0,0 +1,38 @@
import imageio
import os
import numpy as np
class VideoRecorder(object):
def __init__(self, dir_name, height=256, width=256, camera_id=0, fps=30):
self.dir_name = dir_name
self.height = height
self.width = width
self.camera_id = camera_id
self.fps = fps
self.frames = []
def init(self, enabled=True):
self.frames = []
self.enabled = self.dir_name is not None and enabled
def record(self, env):
if self.enabled:
try:
frame = env.render(
mode='rgb_array',
height=self.height,
width=self.width,
camera_id=self.camera_id
)
except:
frame = env.render(
mode='rgb_array',
)
self.frames.append(frame)
def save(self, file_name):
if self.enabled:
path = os.path.join(self.dir_name, file_name)
imageio.mimsave(path, self.frames, fps=self.fps)
+293
View File
@@ -0,0 +1,293 @@
import gym
from gym import spaces
import numpy as np
class GoalWrapper(gym.Wrapper):
def __init__(self, env):
gym.Wrapper.__init__(self, env)
self.threshold = 0.05
self._max_episode_steps = 200
def step(self, action):
obs, _, done, info = self.env.step(action)
reward, is_success = self.compute_goal_metrics(obs)
info['is_success'] = is_success
if not done:
done = is_success
return obs, reward, done, info
def compute_goal_metrics(self, obs):
dist = np.linalg.norm(obs['achieved_goal'] - obs['desired_goal'])
is_success = dist < 0.05
reward = is_success - 1
return reward, is_success
def compute_reward(self, obs):
reward, _, _ = self.compute_goal_metrics(obs)
return reward
def reset(self):
obs = self.env.reset()
return obs
class ImageGoalDMCWrapper(gym.Wrapper):
def __init__(self, env):
gym.Wrapper.__init__(self, env)
def step(self, action):
obs, r, done, info = env.step(action)
reward = 0 if r > 0 else - 1
if reward == 0:
done = True
return dict(observation=obs,
achieved_goal=obs,
desired_goal=self.desired_goal)
def reset(self):
self.desired_goal = self.env.reset()
obs = self.env.reset()
return dict(observation=obs,
achieved_goal=obs,
desired_goal=self.desired_goal)
class LatentGoalWrapper(gym.Wrapper):
def __init__(self, env,encoder):
gym.Wrapper.__init__(self, env)
self.encoder = encoder
self.use_cuda = torch.cuda.is_available()
def step(self, action):
obs_dict, r, done, info = env.step(action)
obs_dict = self.compute_latents(obs_dict)
reward = self.compute_reward(obs_dict)
if reward == 0:
done = True
return dict(observation=obs,
achieved_goal=obs,
desired_goal=self.desired_goal)
def reset(self):
obs_dict = self.reset()
obs_dict = self.compute_latents(obs_dict)
def compute_reward(self, obs_dict):
ag = obs_dict['latent_achieved_goal']
dg = obs_dict['latent_desired_goal']
dist = np.linalg.norm(ag - dg)
r = 0 if dist < 0.05 else -1
def compute_latents(self, obs_dict):
obs, ag, dg = obs_dict['observation'],obs_dict['achieved_goal'],obs_dict['desired_goal']
obs = torch.tensor(obs)
ag = torch.tensor(ag)
dg = torch.tensor(dg)
if self.use_cuda:
obs = obs.cuda()
ag = ag.cuda()
dg = dg.cuda()
if len(ag.shape) == 3:
obs = obs.unsqueeze(0)
ag = ag.unsqueeze(0)
dg = dg.unsqueeze(0)
# encode
stack = torch.cat((obs,ag,dg),dim=0)
encoded_latents = self.encoder (stack)
obs_latent = encoded_latents[0]
ag_latent = encoded_latents[1]
dg_latent = encoded_latents[2]
obs_dict.update(dict(latent_observation=obs_latent,
latent_achieved_goal=ag_latent,
latent_desired_goal=dg_latent))
return obs_dict
class FlatImageGoalDMCWrapper(gym.Wrapper):
def __init__(self, env):
gym.Wrapper.__init__(self, env)
def step(self, action):
obs, r, done, info = env.step(action)
dist = self.dist_to_goal()
reward = 0 if dist < 0.1 else - 1
if reward == 0:
done = True
return np.concatenate((obs, self.desired_goal), axis=0)
def reset(self):
self.desired_goal = self.env.reset()
self.goal_xpos = self.env._env.physics.named.data.geom_xpos['finger']
obs = self.env.reset()
return np.concatenate((obs, self.desired_goal), axis=0)
@property
def current_xpos(self):
return self.env._env.physics.named.data.geom_xpos['finger']
def dist_to_goal(self):
return np.lingalg.norm(self.goal_xpos - self.current_xpos)
class FlattenGoalWrapper(gym.Wrapper):
def __init__(self, env):
gym.Wrapper.__init__(self, env)
self._max_episode_steps = 50
def step(self, action):
obs, _, done, info = self.env.step(action)
reward, is_success = self.compute_goal_metrics(obs)
info['is_success'] = is_success
if not done:
done = is_success
return obs, reward, done, info
def compute_goal_metrics(self, obs):
dist = np.linalg.norm(obs['achieved_goal'] - obs['desired_goal'])
is_success = dist < 0.05
reward = is_success - 1
return reward, is_success
def compute_reward(self, obs):
reward, _, _ = self.compute_goal_metrics(obs)
return reward
def reset(self):
obs = self.env.reset()
return obs
class MtnCarWrapper(gym.Wrapper):
def __init__(self, env):
gym.Wrapper.__init__(self, env)
obs = self.reset()
self._max_episode_steps = 200
self.observation_space = gym.spaces.Dict(
desired_goal=spaces.Box(-np.inf, np.inf,
shape=(1,), dtype='float32'),
achieved_goal=spaces.Box(-np.inf, np.inf,
shape=(1,), dtype='float32'),
observation=spaces.Box(-np.inf, np.inf,
shape=obs['observation'].shape, dtype='float32'),
)
def step(self, action):
obs, reward, done, info = self.env.step(action)
obs_dict = dict(
observation=obs, desired_goal=self.desired_goal, achieved_goal=obs[0])
return obs_dict, reward, done, info
def reset(self):
obs = self.env.reset()
self.desired_goal = 0.05
obs_dict = dict(observation=obs,desired_goal=self.desired_goal,achieved_goal=obs[0])
return obs_dict
class MinigridCoordGoalWrapper(gym.core.Wrapper):
def __init__(self, env):
super().__init__(env)
obs = self.reset()
obs_dim = 3
obs_min = 0
obs_max = self.width - 1
dir_max = 3
self._max_episode_steps = 50
self.observation_space = gym.spaces.Dict(
desired_goal=spaces.Box(obs_min, obs_max,
shape=(obs_dim-1,), dtype='float32'),
achieved_goal=spaces.Box(obs_min, obs_max,
shape=(obs_dim-1,), dtype='float32'),
observation=spaces.Box(obs_min, obs_max,
shape=(obs_dim,), dtype='float32'),
)
def step(self, action):
self.steps +=1
obs_max = self.width - 1
dir_max = 3
obs, reward, done, info = self.env.step(action)
pos = np.array(self.env.agent_pos) / obs_max
dir_ = np.array(self.env.agent_dir) / dir_max
obs = np.append(pos, dir_).astype(np.float32)
achieved_goal = pos
obs_dict = dict(
observation=obs, desired_goal=self.desired_goal, achieved_goal=achieved_goal)
reward = reward - 1
info['is_success'] = reward > -1.0
return obs_dict, reward, done, info
def reset(self, **kwargs):
self.steps = 0
obs_max = self.width - 1
dir_max = 3
self.desired_goal = np.array([7, 7]).astype(np.float32) / obs_max
self.env.reset()
pos = np.array(self.env.agent_pos) / obs_max
dir_ = np.array(self.env.agent_dir) / dir_max
obs = np.append(pos, dir_).astype(np.float32)
achieved_goal = pos
obs_dict = dict(
observation=obs, desired_goal=self.desired_goal, achieved_goal=achieved_goal)
return obs_dict
class MinigridImageGoalWrapper(gym.core.Wrapper):
def __init__(self, env):
super().__init__(env)
obs = self.reset()
obs_dim = 3
obs_min = 0
obs_max = self.width - 1
dir_max = 3
self._max_episode_steps = 50
self.observation_space = gym.spaces.Dict(
desired_goal=spaces.Box(obs_min, obs_max,
shape=(obs_dim-1,), dtype='float32'),
achieved_goal=spaces.Box(obs_min, obs_max,
shape=(obs_dim-1,), dtype='float32'),
observation=spaces.Box(obs_min, obs_max,
shape=(obs_dim,), dtype='float32'),
)
def step(self, action):
self.steps += 1
obs_max = self.width - 1
dir_max = 3
obs, reward, done, info = self.env.step(action)
pos = np.array(self.env.agent_pos) / obs_max
dir_ = np.array(self.env.agent_dir) / dir_max
obs = np.append(pos, dir_).astype(np.float32)
achieved_goal = pos
obs_dict = dict(
observation=obs, desired_goal=self.desired_goal, achieved_goal=achieved_goal)
reward = reward - 1
info['is_success'] = reward > -1.0
return obs_dict, reward, done, info
def reset(self,**kwargs):
self.steps = 0
obs_max = self.width - 1
dir_max = 3
self.desired_goal = np.array([14, 14]).astype(np.float32) / obs_max
self.env.reset()
pos = np.array(self.env.agent_pos) / obs_max
dir_ = np.array(self.env.agent_dir) / dir_max
obs = np.append(pos, dir_).astype(np.float32)
achieved_goal = pos
obs_dict = dict(
observation=obs, desired_goal=self.desired_goal, achieved_goal=achieved_goal)
return obs_dict