Source code for neural_lam.utils.tensor
"""Numerically stable inverses of common activation functions."""
# Third-party
import torch
[docs]
def inverse_softplus(
x: torch.Tensor, beta: float = 1.0, threshold: float = 20.0
) -> torch.Tensor:
"""
Inverse of :func:`torch.nn.functional.softplus`.
For most inputs this function is exact up to numerical precision. The
input is clamped to ensure numerical stability: values above
``threshold / beta`` are treated as linear (which is exact in that
regime), and values near zero are clamped to avoid ``log`` of
non-positive numbers. Only near the lower clamping bound does the
result deviate from the true inverse.
Parameters
----------
x : torch.Tensor
Input tensor whose softplus inverse should be computed.
beta : float, optional
Softplus ``beta`` parameter that controls the sharpness. Default ``1``.
threshold : float, optional
Threshold above which the function is treated as linear for numerical
stability. Default ``20``.
Returns
-------
torch.Tensor
Tensor containing the inverse-softplus values.
Notes
-----
``torch.clamp`` will zero the gradients near the bounds, but values this
close to zero or ``threshold / beta`` already have negligible gradients.
"""
x_clamped = torch.clamp( # ty: ignore[no-matching-overload]
x, min=torch.log(torch.tensor(1e-6 + 1)) / beta, max=threshold / beta
)
non_linear_part = torch.log(torch.expm1(x_clamped * beta)) / beta
below_threshold = x * beta <= threshold
x = torch.where(condition=below_threshold, input=non_linear_part, other=x)
return x
[docs]
def inverse_sigmoid(x: torch.Tensor) -> torch.Tensor:
"""
Inverse of ``torch.sigmoid`` with clamping for numerical stability.
Sigmoid output takes values in ``[0, 1]``; we clamp the input slightly
within that open interval before applying ``log(x / (1 - x))``.
Note that ``torch.clamp`` will make gradients 0 near the bounds, but
this is not a problem as values of x that are this close to 0 or 1
have gradients of 0 anyhow.
Parameters
----------
x : torch.Tensor
Input tensor assumed to contain logits after a sigmoid.
Returns
-------
torch.Tensor
Tensor containing ``log(x / (1 - x))`` after clamping away from the
saturation limits.
Notes
-----
``torch.clamp`` zeroes gradients for values at the bounds, but values this
close to 0 or 1 already have negligible gradients.
"""
x_clamped = torch.clamp(x, min=1e-6, max=1 - 1e-6)
return torch.log(x_clamped / (1 - x_clamped))