import re import numpy as np import tools.utils as utils import torch.nn as nn import torch import torch.distributions as D import torch.nn.functional as F Module = nn.Module def symlog(x): return torch.sign(x) * torch.log(torch.abs(x) + 1.0) def symexp(x): return torch.sign(x) * (torch.exp(torch.abs(x)) - 1.0) def signed_hyperbolic(x: torch.Tensor, eps: float = 1e-3) -> torch.Tensor: """Signed hyperbolic transform, inverse of signed_parabolic.""" return torch.sign(x) * (torch.sqrt(torch.abs(x) + 1) - 1) + eps * x def signed_parabolic(x: torch.Tensor, eps: float = 1e-3) -> torch.Tensor: """Signed parabolic transform, inverse of signed_hyperbolic.""" z = torch.sqrt(1 + 4 * eps * (eps + 1 + torch.abs(x))) / 2 / eps - 1 / 2 / eps return torch.sign(x) * (torch.square(z) - 1) class SampleDist: def __init__(self, dist: D.Distribution, samples=100): self._dist = dist self._samples = samples @property def name(self): return 'SampleDist' def __getattr__(self, name): return getattr(self._dist, name) @property def mean(self): sample = self._dist.rsample((self._samples,)) return torch.mean(sample, 0) def mode(self): dist = self._dist.expand((self._samples, *self._dist.batch_shape)) sample = dist.rsample() logprob = dist.log_prob(sample) batch_size = sample.size(1) feature_size = sample.size(2) indices = torch.argmax(logprob, dim=0).reshape(1, batch_size, 1).expand(1, batch_size, feature_size) return torch.gather(sample, 0, indices).squeeze(0) def entropy(self): sample = self._dist.rsample((self._samples,)) logprob = self._dist.log_prob(sample) return -torch.mean(logprob, 0) def sample(self): return self._dist.rsample() class MSEDist: def __init__(self, mode, agg="sum"): self._mode = mode self._agg = agg @property def mean(self): return self._mode def mode(self): return self._mode def log_prob(self, value): assert self._mode.shape == value.shape, (self._mode.shape, value.shape) distance = (self._mode - value) ** 2 if self._agg == "mean": loss = distance.mean(list(range(len(distance.shape)))[2:]) elif self._agg == "sum": loss = distance.sum(list(range(len(distance.shape)))[2:]) else: raise NotImplementedError(self._agg) return -loss class SymlogDist: def __init__(self, mode, dims, dist='mse', agg='sum', tol=1e-8): self._mode = mode self._dims = tuple([-x for x in range(1, dims + 1)]) self._dist = dist self._agg = agg self._tol = tol self.batch_shape = mode.shape[:len(mode.shape) - dims] self.event_shape = mode.shape[len(mode.shape) - dims:] def mode(self): return symexp(self._mode) def mean(self): return symexp(self._mode) def log_prob(self, value): assert self._mode.shape == value.shape, (self._mode.shape, value.shape) if self._dist == 'mse': distance = (self._mode - symlog(value)) ** 2 distance = torch.where(distance < self._tol, torch.tensor([0.], dtype=distance.dtype, device=distance.device), distance) elif self._dist == 'abs': distance = torch.abs(self._mode - symlog(value)) distance = torch.where(distance < self._tol, torch.tensor([0.], dtype=distance.dtype, device=distance.device), distance) else: raise NotImplementedError(self._dist) if self._agg == 'mean': loss = distance.mean(self._dims) elif self._agg == 'sum': loss = distance.sum(self._dims) else: raise NotImplementedError(self._agg) return -loss class TwoHotDist: def __init__( self, logits, low=-20.0, high=20.0, transfwd=symlog, transbwd=symexp, ): assert logits.shape[-1] == 255 self.logits = logits self.probs = torch.softmax(logits, -1) self.buckets = torch.linspace(low, high, steps=255).to(logits.device) self.width = (self.buckets[-1] - self.buckets[0]) / 255 self.transfwd = transfwd self.transbwd = transbwd @property def mean(self): _mean = self.probs * self.buckets return self.transbwd(torch.sum(_mean, dim=-1, keepdim=True)) @property def mode(self): return self.mean # Inside OneHotCategorical, log_prob is calculated using only max element in targets def log_prob(self, x): x = self.transfwd(x) # x(time, batch, 1) below = torch.sum((self.buckets <= x[..., None]).to(torch.int32), dim=-1) - 1 above = len(self.buckets) - torch.sum( (self.buckets > x[..., None]).to(torch.int32), dim=-1 ) # this is implemented using clip at the original repo as the gradients are not backpropagated for the out of limits. below = torch.clip(below, 0, len(self.buckets) - 1) above = torch.clip(above, 0, len(self.buckets) - 1) equal = below == above dist_to_below = torch.where(equal, 1, torch.abs(self.buckets[below] - x)) dist_to_above = torch.where(equal, 1, torch.abs(self.buckets[above] - x)) total = dist_to_below + dist_to_above weight_below = dist_to_above / total weight_above = dist_to_below / total target = ( F.one_hot(below, num_classes=len(self.buckets)) * weight_below[..., None] + F.one_hot(above, num_classes=len(self.buckets)) * weight_above[..., None] ) log_pred = self.logits - torch.logsumexp(self.logits, -1, keepdim=True) target = target.squeeze(-2) return (target * log_pred).sum(-1) def log_prob_target(self, target): log_pred = super().logits - torch.logsumexp(super().logits, -1, keepdim=True) return (target * log_pred).sum(-1) class OneHotDist(D.OneHotCategorical): def __init__(self, logits=None, probs=None, unif_mix=0.99): super().__init__(logits=logits, probs=probs) probs = super().probs probs = unif_mix * probs + (1 - unif_mix) * torch.ones_like(probs, device=probs.device) / probs.shape[-1] super().__init__(probs=probs) def mode(self): _mode = F.one_hot(torch.argmax(super().logits, axis=-1), super().logits.shape[-1]) return _mode.detach() + super().logits - super().logits.detach() def sample(self, sample_shape=(), seed=None): if seed is not None: raise ValueError('need to check') sample = super().sample(sample_shape) probs = super().probs while len(probs.shape) < len(sample.shape): probs = probs[None] sample += probs - probs.detach() # ST-gradients return sample class BernoulliDist(D.Bernoulli): def __init__(self, logits=None, probs=None): super().__init__(logits=logits, probs=probs) def sample(self, sample_shape=(), seed=None): if seed is not None: raise ValueError('need to check') sample = super().sample(sample_shape) probs = super().probs while len(probs.shape) < len(sample.shape): probs = probs[None] sample += probs - probs.detach() # ST-gradients return sample def static_scan_for_lambda_return(fn, inputs, start): last = start indices = range(inputs[0].shape[0]) indices = reversed(indices) flag = True for index in indices: inp = lambda x: (_input[x].unsqueeze(0) for _input in inputs) last = fn(last, *inp(index)) if flag: outputs = last flag = False else: outputs = torch.cat([last, outputs], dim=0) return outputs def lambda_return( reward, value, pcont, bootstrap, lambda_, axis): # Setting lambda=1 gives a discounted Monte Carlo return. # Setting lambda=0 gives a fixed 1-step return. #assert reward.shape.ndims == value.shape.ndims, (reward.shape, value.shape) assert len(reward.shape) == len(value.shape), (reward.shape, value.shape) if isinstance(pcont, (int, float)): pcont = pcont * torch.ones_like(reward, device=reward.device) dims = list(range(len(reward.shape))) dims = [axis] + dims[1:axis] + [0] + dims[axis + 1:] if axis != 0: reward = reward.permute(dims) value = value.permute(dims) pcont = pcont.permute(dims) if bootstrap is None: bootstrap = torch.zeros_like(value[-1], device=reward.device) if len(bootstrap.shape) < len(value.shape): bootstrap = bootstrap[None] next_values = torch.cat([value[1:], bootstrap], 0) inputs = reward + pcont * next_values * (1 - lambda_) returns = static_scan_for_lambda_return( lambda agg, cur0, cur1: cur0 + cur1 * lambda_ * agg, (inputs, pcont), bootstrap) if axis != 0: returns = returns.permute(dims) return returns def static_scan(fn, inputs, start, reverse=False, unpack=False): last = start indices = range(inputs[0].shape[0]) flag = True for index in indices: inp = lambda x: (_input[x] for _input in inputs) if unpack: last = fn(last, *[inp[index] for inp in inputs]) else: last = fn(last, inp(index)) if flag: if type(last) == type({}): outputs = {key: [value] for key, value in last.items()} else: outputs = [] for _last in last: if type(_last) == type({}): outputs.append({key: [value] for key, value in _last.items()}) else: outputs.append([_last]) flag = False else: if type(last) == type({}): for key in last.keys(): outputs[key].append(last[key]) else: for j in range(len(outputs)): if type(last[j]) == type({}): for key in last[j].keys(): outputs[j][key].append(last[j][key]) else: outputs[j].append(last[j]) # Stack everything at the end if type(last) == type({}): for key in last.keys(): outputs[key] = torch.stack(outputs[key], dim=0) else: for j in range(len(outputs)): if type(last[j]) == type({}): for key in last[j].keys(): outputs[j][key] = torch.stack(outputs[j][key], dim=0) else: outputs[j] = torch.stack(outputs[j], dim=0) if type(last) == type({}): outputs = [outputs] return outputs class EnsembleRSSM(Module): def __init__( self, ensemble=5, stoch=30, deter=200, hidden=200, discrete=False, act='SiLU', norm='none', std_act='softplus', min_std=0.1, action_dim=None, embed_dim=1536, device='cuda', single_obs_posterior=False, cell_input='stoch', cell_type='gru',): super().__init__() assert action_dim is not None self.device = device self._embed_dim = embed_dim self._action_dim = action_dim self._ensemble = ensemble self._stoch = stoch self._deter = deter self._hidden = hidden self._discrete = discrete self._act = get_act(act) self._norm = norm self._std_act = std_act self._min_std = min_std self._cell_type = cell_type self.cell_input = cell_input if cell_type == 'gru': self._cell = GRUCell(self._hidden, self._deter, norm=True, device=self.device) else: raise NotImplementedError(f"{cell_type} not implemented") self.single_obs_posterior = single_obs_posterior if discrete: self._ensemble_img_dist = nn.ModuleList([ nn.Linear(hidden, stoch*discrete) for _ in range(ensemble)]) self._obs_dist = nn.Linear(hidden, stoch*discrete) else: self._ensemble_img_dist = nn.ModuleList([ nn.Linear(hidden, 2*stoch) for _ in range(ensemble)]) self._obs_dist = nn.Linear(hidden, 2*stoch) # Layer that projects (stoch, input) to cell_state space cell_state_input_size = getattr(self, f'get_{self.cell_input}_size')() self._img_in = nn.Sequential(nn.Linear(cell_state_input_size + action_dim, hidden, bias=norm != 'none'), NormLayer(norm, hidden)) # Layer that project deter -> hidden [before projecting hidden -> stoch] self._ensemble_img_out = nn.ModuleList([ nn.Sequential(nn.Linear(self.get_deter_size(), hidden, bias=norm != 'none'), NormLayer(norm, hidden)) for _ in range(ensemble)]) if self.single_obs_posterior: self._obs_out = nn.Sequential(nn.Linear(embed_dim, hidden, bias=norm != 'none'), NormLayer(norm, hidden)) else: self._obs_out = nn.Sequential(nn.Linear(deter + embed_dim, hidden, bias=norm != 'none'), NormLayer(norm, hidden)) def initial(self, batch_size): if self._discrete: state = dict( logit=torch.zeros([batch_size, self._stoch, self._discrete], device=self.device), stoch=torch.zeros([batch_size, self._stoch, self._discrete], device=self.device), deter=self._cell.get_initial_state(None, batch_size)) else: state = dict( mean=torch.zeros([batch_size, self._stoch], device=self.device), std=torch.zeros([batch_size, self._stoch], device=self.device), stoch=torch.zeros([batch_size, self._stoch], device=self.device), deter=self._cell.get_initial_state(None, batch_size)) return state def observe(self, embed, action, is_first, state=None): swap = lambda x: x.permute([1, 0] + list(range(2, len(x.shape)))) if state is None: state = self.initial(action.shape[0]) post, prior = static_scan( lambda prev, inputs: self.obs_step(prev[0], *inputs), (swap(action), swap(embed), swap(is_first)), (state, state)) post = {k: swap(v) for k, v in post.items()} prior = {k: swap(v) for k, v in prior.items()} return post, prior def imagine(self, action, state=None, sample=True): swap = lambda x: x.permute([1, 0] + list(range(2, len(x.shape)))) if state is None: state = self.initial(action.shape[0]) assert isinstance(state, dict), state action = swap(action) prior = static_scan(self.img_step, [action, float(sample) + torch.zeros(action.shape[0])], state, unpack=True)[0] prior = {k: swap(v) for k, v in prior.items()} return prior def get_stoch_size(self,): if self._discrete: return self._stoch * self._discrete else: return self._stoch def get_deter_size(self,): return self._cell.state_size def get_feat_size(self,): return self.get_deter_size() + self.get_stoch_size() def get_stoch(self, state): stoch = state['stoch'] if self._discrete: shape = list(stoch.shape[:-2]) + [self._stoch * self._discrete] stoch = stoch.reshape(shape) return stoch def get_deter(self, state): return state['deter'] def get_feat(self, state): deter = self.get_deter(state) stoch = self.get_stoch(state) return torch.cat([stoch, deter], -1) def get_dist(self, state, ensemble=False): if ensemble: state = self._suff_stats_ensemble(state['deter']) if self._discrete: logit = state['logit'] dist = D.Independent(OneHotDist(logit.float()), 1) else: mean, std = state['mean'], state['std'] dist = D.Independent(D.Normal(mean, std), 1) dist.sample = dist.rsample return dist def get_unif_dist(self, state): if self._discrete: logit = state['logit'] dist = D.Independent(OneHotDist(torch.ones_like(logit, device=logit.device)), 1) else: mean, std = state['mean'], state['std'] dist = D.Independent(D.Normal(torch.zeros_like(mean, device=mean.device), torch.ones_like(std, device=std.device)), 1) dist.sample = dist.rsample return dist def obs_step(self, prev_state, prev_action, embed, is_first, should_sample=True): if is_first.any(): prev_state = { k: torch.einsum('b,b...->b...', 1.0 - is_first.float(), x) for k, x in prev_state.items() } prev_action = torch.einsum('b,b...->b...', 1.0 - is_first.float(), prev_action) # prior = self.img_step(prev_state, prev_action, should_sample) stoch, stats = self.get_post_stoch(embed, prior, should_sample) post = {'stoch': stoch, 'deter': prior['deter'], **stats} return post, prior def get_post_stoch(self, embed, prior, should_sample=True): if self.single_obs_posterior: x = embed else: x = torch.cat([prior['deter'], embed], -1) x = self._obs_out(x) x = self._act(x) bs = list(x.shape[:-1]) x = x.reshape([-1, x.shape[-1]]) stats = self._suff_stats_layer('_obs_dist', x) stats = { k: v.reshape( bs + list(v.shape[1:])) for k, v in stats.items()} dist = self.get_dist(stats) stoch = dist.sample() if should_sample else dist.mode() return stoch, stats def img_step(self, prev_state, prev_action, sample=True,): prev_state_input = getattr(self, f'get_{self.cell_input}')(prev_state) x = torch.cat([prev_state_input, prev_action], -1) x = self._img_in(x) x = self._act(x) deter = prev_state['deter'] if self._cell_type == 'gru': x, deter = self._cell(x, [deter]) temp_state = {'deter' : deter[0] } else: raise NotImplementedError(f"no {self._cell_type} cell method") deter = deter[0] # It's wrapped in a list. stoch, stats = self.get_stoch_stats_from_deter_state(temp_state, sample) prior = {'stoch': stoch, 'deter': deter, **stats} return prior def get_stoch_stats_from_deter_state(self, temp_state, sample=True): stats = self._suff_stats_ensemble(self.get_deter(temp_state)) index = torch.randint(0, self._ensemble, ()) stats = {k: v[index] for k, v in stats.items()} dist = self.get_dist(stats) if sample: stoch = dist.sample() else: try: stoch = dist.mode() except: stoch = dist.mean return stoch, stats def _suff_stats_ensemble(self, inp): bs = list(inp.shape[:-1]) inp = inp.reshape([-1, inp.shape[-1]]) stats = [] for k in range(self._ensemble): x = self._ensemble_img_out[k](inp) x = self._act(x) stats.append(self._suff_stats_layer('_ensemble_img_dist', x, k=k)) stats = { k: torch.stack([x[k] for x in stats], 0) for k, v in stats[0].items()} stats = { k: v.reshape([v.shape[0]] + bs + list(v.shape[2:])) for k, v in stats.items()} return stats def _suff_stats_layer(self, name, x, k=None): layer = getattr(self, name) if k is not None: layer = layer[k] x = layer(x) if self._discrete: logit = x.reshape(list(x.shape[:-1]) + [self._stoch, self._discrete]) return {'logit': logit} else: mean, std = torch.chunk(x, 2, -1) std = { 'softplus': lambda: F.softplus(std), 'sigmoid': lambda: torch.sigmoid(std), 'sigmoid2': lambda: 2 * torch.sigmoid(std / 2), }[self._std_act]() std = std + self._min_std return {'mean': mean, 'std': std} def vq_loss(self, post, prior, balance): dim_repr = prior['output'].shape[-1] # Vectors and codes are the same, but vectors have gradients dyn_loss = balance * F.mse_loss(prior['output'], post['vectors'].detach()) + (1 - balance) * F.mse_loss(prior['output'].detach(), post['vectors']) dyn_loss += balance * F.mse_loss(prior['output'], post['codes'].detach()) + (1 - balance) * F.mse_loss(prior['output'].detach(), post['codes']) dyn_loss /= 2 vq_loss = 0.25 * F.mse_loss(post['output'], post['codes'].detach()) + F.mse_loss(post['output'].detach(), post['codes']) loss = vq_loss + dyn_loss return loss * dim_repr, dyn_loss * dim_repr def kl_loss(self, post, prior, forward, balance, free, free_avg,): kld = D.kl_divergence sg = lambda x: {k: v.detach() for k, v in x.items()} lhs, rhs = (prior, post) if forward else (post, prior) mix = balance if forward else (1 - balance) dtype = post['stoch'].dtype device = post['stoch'].device free_tensor = torch.tensor([free], dtype=dtype, device=device) if balance == 0.5: value = kld(self.get_dist(lhs), self.get_dist(rhs)) loss = torch.maximum(value, free_tensor).mean() else: value_lhs = value = kld(self.get_dist(lhs), self.get_dist(sg(rhs))) value_rhs = kld(self.get_dist(sg(lhs)), self.get_dist(rhs)) if free_avg: loss_lhs = torch.maximum(value_lhs.mean(), free_tensor) loss_rhs = torch.maximum(value_rhs.mean(), free_tensor) else: loss_lhs = torch.maximum(value_lhs, free_tensor).mean() loss_rhs = torch.maximum(value_rhs, free_tensor).mean() loss = mix * loss_lhs + (1 - mix) * loss_rhs return loss, value class Encoder(Module): def __init__( self, shapes, cnn_keys=r'.*', mlp_keys=r'.*', act='SiLU', norm='none', cnn_depth=48, cnn_kernels=(4, 4, 4, 4), mlp_layers=[400, 400, 400, 400], symlog_inputs=False,): super().__init__() self.shapes = shapes self.cnn_keys = [ k for k, v in shapes.items() if re.match(cnn_keys, k) and len(v) == 3] self.mlp_keys = [ k for k, v in shapes.items() if re.match(mlp_keys, k) and len(v) == 1] print('Encoder CNN inputs:', list(self.cnn_keys)) print('Encoder MLP inputs:', list(self.mlp_keys)) self._act = get_act(act) self._norm = norm self._cnn_depth = cnn_depth self._cnn_kernels = cnn_kernels self._mlp_layers = mlp_layers self._symlog_inputs = symlog_inputs if len(self.cnn_keys) > 0: self._conv_model = [] for i, kernel in enumerate(self._cnn_kernels): if i == 0: prev_depth = 3 else: prev_depth = 2 ** (i-1) * self._cnn_depth depth = 2 ** i * self._cnn_depth self._conv_model.append(nn.Conv2d(prev_depth, depth, kernel, stride=2)) self._conv_model.append(ImgChLayerNorm(depth) if norm == 'layer' else NormLayer(norm,depth)) self._conv_model.append(self._act) self._conv_model = nn.Sequential(*self._conv_model) if len(self.mlp_keys) > 0: self._mlp_model = [] for i, width in enumerate(self._mlp_layers): if i == 0: prev_width = np.sum([shapes[k] for k in self.mlp_keys]) else: prev_width = self._mlp_layers[i-1] self._mlp_model.append(nn.Linear(prev_width, width, bias=norm != 'none')) self._mlp_model.append(NormLayer(norm, width)) self._mlp_model.append(self._act) if len(self._mlp_model) == 0: self._mlp_model.append(nn.Identity()) self._mlp_model = nn.Sequential(*self._mlp_model) def forward(self, data): key, shape = list(self.shapes.items())[0] batch_dims = data[key].shape[:-len(shape)] data = { k: v.reshape((-1,) + tuple(v.shape)[len(batch_dims):]) for k, v in data.items()} outputs = [] if self.cnn_keys: outputs.append(self._cnn({k: data[k] for k in self.cnn_keys})) if self.mlp_keys: outputs.append(self._mlp({k: data[k] for k in self.mlp_keys})) output = torch.cat(outputs, -1) return output.reshape(batch_dims + output.shape[1:]) def _cnn(self, data): x = torch.cat(list(data.values()), -1) x = self._conv_model(x) return x.reshape(tuple(x.shape[:-3]) + (-1,)) def _mlp(self, data): x = torch.cat(list(data.values()), -1) if self._symlog_inputs: x = symlog(x) x = self._mlp_model(x) return x class Decoder(Module): def __init__( self, shapes, cnn_keys=r'.*', mlp_keys=r'.*', act='SiLU', norm='none', cnn_depth=48, cnn_kernels=(4, 4, 4, 4), mlp_layers=[400, 400, 400, 400], embed_dim=1024, mlp_dist='mse', image_dist='mse'): super().__init__() self._embed_dim = embed_dim self._shapes = shapes self.cnn_keys = [ k for k, v in shapes.items() if re.match(cnn_keys, k) and len(v) == 3] self.mlp_keys = [ k for k, v in shapes.items() if re.match(mlp_keys, k) and len(v) == 1] print('Decoder CNN outputs:', list(self.cnn_keys)) print('Decoder MLP outputs:', list(self.mlp_keys)) self._act = get_act(act) self._norm = norm self._cnn_depth = cnn_depth self._cnn_kernels = cnn_kernels self._mlp_layers = mlp_layers self.channels = {k: self._shapes[k][0] for k in self.cnn_keys} self._mlp_dist = mlp_dist self._image_dist = image_dist if len(self.cnn_keys) > 0: self._conv_in = nn.Sequential(nn.Linear(embed_dim, 32*self._cnn_depth)) self._conv_model = [] for i, kernel in enumerate(self._cnn_kernels): if i == 0: prev_depth = 32*self._cnn_depth else: prev_depth = 2 ** (len(self._cnn_kernels) - (i - 1) - 2) * self._cnn_depth depth = 2 ** (len(self._cnn_kernels) - i - 2) * self._cnn_depth act, norm = self._act, self._norm # Last layer is dist layer if i == len(self._cnn_kernels) - 1: depth, act, norm = sum(self.channels.values()), nn.Identity(), 'none' self._conv_model.append(nn.ConvTranspose2d(prev_depth, depth, kernel, stride=2)) self._conv_model.append(ImgChLayerNorm(depth) if norm == 'layer' else NormLayer(norm, depth)) self._conv_model.append(act) self._conv_model = nn.Sequential(*self._conv_model) if len(self.mlp_keys) > 0: self._mlp_model = [] for i, width in enumerate(self._mlp_layers): if i == 0: prev_width = embed_dim else: prev_width = self._mlp_layers[i-1] self._mlp_model.append(nn.Linear(prev_width, width, bias=self._norm != 'none')) self._mlp_model.append(NormLayer(self._norm, width)) self._mlp_model.append(self._act) self._mlp_model = nn.Sequential(*self._mlp_model) for key, shape in { k : shapes[k] for k in self.mlp_keys }.items(): self.add_module(f'dense_{key}', DistLayer(width, shape, dist=self._mlp_dist)) def forward(self, features): outputs = {} if self.cnn_keys: outputs.update(self._cnn(features)) if self.mlp_keys: outputs.update(self._mlp(features)) return outputs def _cnn(self, features): x = self._conv_in(features) x = x.reshape([-1, 32 * self._cnn_depth, 1, 1,]) x = self._conv_model(x) x = x.reshape(list(features.shape[:-1]) + list(x.shape[1:])) if len(x.shape) == 5: means = torch.split(x, list(self.channels.values()), 2) else: means = torch.split(x, list(self.channels.values()), 1) image_dist = dict(mse=lambda x : MSEDist(x), normal_unit_std=lambda x : D.Independent(D.Normal(x, 1.0), 3))[self._image_dist] dists = { key: image_dist(mean) for (key, shape), mean in zip(self.channels.items(), means)} return dists def _mlp(self, features): shapes = {k: self._shapes[k] for k in self.mlp_keys} x = features x = self._mlp_model(x) dists = {} for key, shape in shapes.items(): dists[key] = getattr(self, f'dense_{key}')(x) return dists class MLP(Module): def __init__(self, in_shape, shape, layers, units, act='SiLU', norm='none', **out): super().__init__() self._in_shape = in_shape if out['dist'] == 'twohot': shape = 255 self._shape = (shape,) if isinstance(shape, int) else shape self._layers = layers self._units = units self._norm = norm self._act = get_act(act) self._out = out last_units = in_shape for index in range(self._layers): self.add_module(f'dense{index}', nn.Linear(last_units, units, bias=norm != 'none')) self.add_module(f'norm{index}', NormLayer(norm, units)) last_units = units self._out = DistLayer(units, shape, **out) def forward(self, features): x = features x = x.reshape([-1, x.shape[-1]]) for index in range(self._layers): x = getattr(self, f'dense{index}')(x) x = getattr(self, f'norm{index}')(x) x = self._act(x) x = x.reshape(list(features.shape[:-1]) + [x.shape[-1]]) return self._out(x) class GRUCell(Module): def __init__(self, inp_size, size, norm=False, act='Tanh', update_bias=-1, device='cuda', **kwargs): super().__init__() self._inp_size = inp_size self._size = size self._act = get_act(act) self._norm = norm self._update_bias = update_bias self.device = device self._layer = nn.Linear(inp_size + size, 3 * size, bias=(not norm), **kwargs) if norm: self._norm = nn.LayerNorm(3*size) def get_initial_state(self, inputs=None, batch_size=None, dtype=None): return torch.zeros((batch_size), self._size, device=self.device) @property def state_size(self): return self._size def forward(self, inputs, deter_state): """ inputs : non-linear combination of previous stoch and action deter_state : prev hidden state of the cell """ deter_state = deter_state[0] # State is wrapped in a list. parts = self._layer(torch.cat([inputs, deter_state], -1)) if self._norm: parts = self._norm(parts) reset, cand, update = torch.chunk(parts, 3, -1) reset = torch.sigmoid(reset) cand = self._act(reset * cand) update = torch.sigmoid(update + self._update_bias) output = update * cand + (1 - update) * deter_state return output, [output] class DistLayer(Module): def __init__( self, in_dim, shape, dist='mse', min_std=0.1, max_std=1.0, init_std=0.0, bias=True): super().__init__() self._in_dim = in_dim self._shape = shape if type(shape) in [list,tuple] else [shape] self._dist = dist self._min_std = min_std self._init_std = init_std self._max_std = max_std self._out = nn.Linear(in_dim, int(np.prod(shape)) , bias=bias) if dist in ('normal', 'tanh_normal', 'trunc_normal'): self._std = nn.Linear(in_dim, int(np.prod(shape)) ) def forward(self, inputs): out = self._out(inputs) out = out.reshape(list(inputs.shape[:-1]) + list(self._shape)) if self._dist in ('normal', 'tanh_normal', 'trunc_normal'): std = self._std(inputs) std = std.reshape(list(inputs.shape[:-1]) + list(self._shape)) if self._dist == 'mse': return MSEDist(out,) if self._dist == 'normal_unit_std': dist = D.Normal(out, 1.0) dist.sample = dist.rsample return D.Independent(dist, len(self._shape)) if self._dist == 'normal': mean = torch.tanh(out) std = (self._max_std - self._min_std) * torch.sigmoid(std + 2.0) + self._min_std dist = D.Normal(mean, std) dist.sample = dist.rsample return D.Independent(dist, len(self._shape)) if self._dist == 'binary': out = torch.sigmoid(out) dist = BernoulliDist(out) return D.Independent(dist, len(self._shape)) if self._dist == 'tanh_normal': mean = 5 * torch.tanh(out / 5) std = F.softplus(std + self._init_std) + self._min_std dist = utils.SquashedNormal(mean, std) dist = D.Independent(dist, len(self._shape)) return SampleDist(dist) if self._dist == 'trunc_normal': mean = torch.tanh(out) std = 2 * torch.sigmoid((std + self._init_std) / 2) + self._min_std dist = utils.TruncatedNormal(mean, std) return D.Independent(dist, 1) if self._dist == 'onehot': return OneHotDist(out.float()) if self._dist == 'twohot': return TwoHotDist(out.float()) if self._dist == 'symlog_mse': return SymlogDist(out, len(self._shape), 'mse') raise NotImplementedError(self._dist) class NormLayer(Module): def __init__(self, name, dim=None): super().__init__() if name == 'none': self._layer = None elif name == 'layer': assert dim != None self._layer = nn.LayerNorm(dim) else: raise NotImplementedError(name) def forward(self, features): if self._layer is None: return features return self._layer(features) def get_act(name): if name == 'none': return nn.Identity() elif hasattr(nn, name): return getattr(nn, name)() else: raise NotImplementedError(name) class Optimizer: def __init__( self, name, parameters, lr, eps=1e-4, clip=None, wd=None, opt='adam', wd_pattern=r'.*', use_amp=False): assert 0 <= wd < 1 assert not clip or 1 <= clip self._name = name self._clip = clip self._wd = wd self._wd_pattern = wd_pattern self._opt = { 'adam': lambda: torch.optim.Adam(parameters, lr, eps=eps), 'nadam': lambda: torch.optim.Nadam(parameters, lr, eps=eps), 'adamax': lambda: torch.optim.Adamax(parameters, lr, eps=eps), 'sgd': lambda: torch.optim.SGD(parameters, lr), 'momentum': lambda: torch.optim.SGD(lr, momentum=0.9), }[opt]() self._scaler = torch.cuda.amp.GradScaler(enabled=use_amp) self._once = True def __call__(self, loss, params): params = list(params) assert len(loss.shape) == 0 or (len(loss.shape) == 1 and loss.shape[0] == 1), (self._name, loss.shape) metrics = {} # Count parameters. if self._once: count = sum(p.numel() for p in params if p.requires_grad) print(f'Found {count} {self._name} parameters.') self._once = False # Check loss. metrics[f'{self._name}_loss'] = loss.detach().cpu().numpy() # Compute scaled gradient. self._scaler.scale(loss).backward() self._scaler.unscale_(self._opt) # Gradient clipping. if self._clip: norm = torch.nn.utils.clip_grad_norm_(params, self._clip) metrics[f'{self._name}_grad_norm'] = norm.item() # Weight decay. if self._wd: self._apply_weight_decay(params) # # Apply gradients. self._scaler.step(self._opt) self._scaler.update() self._opt.zero_grad() return metrics def _apply_weight_decay(self, varibs): nontrivial = (self._wd_pattern != r'.*') if nontrivial: raise NotImplementedError('Non trivial weight decay') else: for var in varibs: var.data = (1 - self._wd) * var.data class StreamNorm: def __init__(self, shape=(), momentum=0.99, scale=1.0, eps=1e-8, device='cuda'): # Momentum of 0 normalizes only based on the current batch. # Momentum of 1 disables normalization. self.device = device self._shape = tuple(shape) self._momentum = momentum self._scale = scale self._eps = eps self.mag = None # torch.ones(shape).to(self.device) self.step = 0 self.mean = None # torch.zeros(shape).to(self.device) self.square_mean = None # torch.zeros(shape).to(self.device) def reset(self): self.step = 0 self.mag = None # torch.ones_like(self.mag).to(self.device) self.mean = None # torch.zeros_like(self.mean).to(self.device) self.square_mean = None # torch.zeros_like(self.square_mean).to(self.device) def __call__(self, inputs): metrics = {} self.update(inputs) metrics['mean'] = inputs.mean() metrics['std'] = inputs.std() outputs = self.transform(inputs) metrics['normed_mean'] = outputs.mean() metrics['normed_std'] = outputs.std() return outputs, metrics def update(self, inputs): self.step += 1 batch = inputs.reshape((-1,) + self._shape) mag = torch.abs(batch).mean(0) if self.mag is not None: self.mag.data = self._momentum * self.mag.data + (1 - self._momentum) * mag else: self.mag = mag.clone().detach() mean = torch.mean(batch) if self.mean is not None: self.mean.data = self._momentum * self.mean.data + (1 - self._momentum) * mean else: self.mean = mean.clone().detach() square_mean = torch.mean(batch * batch) if self.square_mean is not None: self.square_mean.data = self._momentum * self.square_mean.data + (1 - self._momentum) * square_mean else: self.square_mean = square_mean.clone().detach() def transform(self, inputs): if self._momentum == 1: return inputs values = inputs.reshape((-1,) + self._shape) values /= self.mag[None] + self._eps values *= self._scale return values.reshape(inputs.shape) def corrected_mean_var_std(self,): corr = 1 # 1 - self._momentum ** self.step # NOTE: this led to exploding values for first few iterations corr_mean = self.mean / corr corr_var = (self.square_mean / corr) - self.mean ** 2 corr_std = torch.sqrt(torch.maximum(corr_var, torch.zeros_like(corr_var, device=self.device)) + self._eps) return corr_mean, corr_var, corr_std class RequiresGrad: def __init__(self, model): self._model = model def __enter__(self): self._model.requires_grad_(requires_grad=True) def __exit__(self, *args): self._model.requires_grad_(requires_grad=False) class RewardEMA: """running mean and std""" def __init__(self, device, alpha=1e-2): self.device = device self.alpha = alpha self.range = torch.tensor([0.05, 0.95]).to(device) def __call__(self, x, ema_vals): flat_x = torch.flatten(x.detach()) x_quantile = torch.quantile(input=flat_x, q=self.range) # this should be in-place operation ema_vals[:] = self.alpha * x_quantile + (1 - self.alpha) * ema_vals scale = torch.clip(ema_vals[1] - ema_vals[0], min=1.0) offset = ema_vals[0] return offset.detach(), scale.detach() class ImgChLayerNorm(nn.Module): def __init__(self, ch, eps=1e-03): super(ImgChLayerNorm, self).__init__() self.norm = torch.nn.LayerNorm(ch, eps=eps) def forward(self, x): x = x.permute(0, 2, 3, 1) x = self.norm(x) x = x.permute(0, 3, 1, 2) return x