import torch from torch import nn class SoftNormBottleneck(nn.Module): def __init__(self, dim = 32, noise_augment_dim=0, noise_regularize = False, auto_scale = False, freeze = False): super().__init__() self.noise_augment_dim = noise_augment_dim self.scaling_factor = nn.Parameter(torch.ones(1,dim,1)) self.bias = nn.Parameter(torch.zeros(1,dim,1)) self.noise_scaling_factor = nn.Parameter(torch.ones(1,noise_augment_dim,1)) self.noise_regularize = noise_regularize self.freeze = freeze if self.freeze: self.scaling_factor.requires_grad = False self.bias.requires_grad = False self.noise_scaling_factor.requires_grad = False if auto_scale: running_std = torch.ones(1) self.register_parameter("running_std", nn.Parameter(running_std, requires_grad=False)) def encode(self, x, return_info=False, **kwargs): info = {} x = x * self.scaling_factor + self.bias if self.training and hasattr(self,"running_std") and not self.freeze: # Update running std self.running_std.data = (self.running_std.data * 0.999 + x.std().detach() * 0.001).clamp(min = 1e-4) if hasattr(self, "running_std"): x = x / self.running_std if self.training and return_info: var = (x.std(dim=-1) ** 2).clip(min = 1e-4) logvar = torch.log(var) mean = x.mean(dim=-1) loss = (mean * mean + var - logvar - 1).mean() var = (x.std(dim=-2) ** 2).clip(min = 1e-4) logvar = torch.log(var) mean = x.mean(dim=-2) loss = loss + 0.4 * (mean * mean + var - logvar - 1).mean() info["softnorm_loss"] = loss if return_info: return x, info return x def decode(self, x, **kwargs): if hasattr(self, "running_std"): x = x * self.running_std if self.noise_regularize: if hasattr(self, "running_std"): scaling = self.running_std else: scaling = x.std(dim = -1).unsqueeze(-1) if self.training: scale = 5e-2 else: scale = 1e-3 noise = torch.randn_like(x) * scaling * scale x = x + noise if self.noise_augment_dim > 0: noise = self.noise_scaling_factor * torch.randn(x.shape[0], self.noise_augment_dim, x.shape[-1]).type_as(x) x = torch.cat([x, noise], dim=1) return x