Source code for fastbnns.models.activations
"""Custom activation functions."""
import torch
from torch.distributions import Distribution
[docs]
def scaled_sigmoid(
x: torch.Tensor, alpha: torch.Tensor = torch.tensor(1.0)
) -> torch.Tensor:
"""Compute the scaled sigmoid function \frac{1.0}{1.0+\exp(-\alpha*x)}"""
return 1.0 / (1.0 + torch.exp(-alpha * x))
[docs]
class InverseTransformSampling(torch.nn.Module):
"""Activation to mimic inverse transform sampling from some distribution."""
def __init__(
self,
distribution: Distribution = torch.distributions.Normal(loc=0.0, scale=1.0),
learn_alpha: bool = False,
alpha_init: torch.tensor = torch.tensor([1.0]),
eps: float = 1.0e-6,
*args,
**kwargs
) -> None:
"""Initialize InverseTransformSampling class.
Args:
distribution: Torch distribution with a defined .icdf() method.
learn_alpha: Flag indicating we should learn the alpha scale in
the domain transform f(x) = 1.0 / (1.0 + exp(-alpha*x)),
otherwise alpha=1.0 will always be used.
eps: Clamp inputs to distribution.icdf to [eps, 1.0-eps]
"""
super().__init__(*args, **kwargs)
# Define the domain transform to convert inputs in
# (-\inf, \inf) to [0, 1]
self._alpha = torch.nn.Parameter(
torch.log(torch.exp(alpha_init) - 1.0), requires_grad=learn_alpha
) # self.alpha=softplus(self._alpha)
self.domain_tform = scaled_sigmoid
# Define the Normal distribution of interest.
self.distribution = distribution
self.eps = eps
@property
def alpha(self) -> torch.Tensor:
"""Scale self._alpha to ensure positivity and return."""
return torch.nn.functional.softplus(self._alpha)
[docs]
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Forward pass through activation."""
# Transform inputs from (-\inf, \inf) to [eps, 1.0-eps]
x_prime = self.domain_tform(x, alpha=self.alpha)
x_prime.clamp_(min=self.eps, max=1.0 - self.eps)
# Treat transformed inputs as samples from U[0, 1] and pass through
# inverse CDF of self.disribution.
return self.distribution.icdf(x_prime)