Transforms#

PyTorch Network Loader provides the BaseTransform class along with some common transforms for PyTorch safe weights and data preprocessing. Transforms are applied to the data before it is passed to the network for training or inference. Transforms can be saved in architectures so normalisation is consistent between forward passes & during prediction, the predictions get untransformed. Transforms are Numpy/PyTorch agnostic, so either can be passed through.

Custom Transforms#

NetLoader provides the BaseTransform class for data preprocessing, inverse transformations, and uncertainty propagation.

The BaseTransform class has the following methods:

abstractmethod BaseTransform.forward(x)[source]

Forward pass of the transformation

Parameters:
xArrayT

Input array or tensor of shape (N,…), where N is the number of elements

Returns:
ArrayT

Transformed array or tensor of shape (N,…)

abstractmethod BaseTransform.backward(x)[source]

Backwards pass to invert the transformation

Parameters:
xArrayT

Input array or tensor of shape (N,…), where N is the number of elements

Returns:
ArrayT

Untransformed array or tensor of shape (N,…)

abstractmethod BaseTransform.forward_grad(x, uncertainty)[source]

Forward pass of the transformation and uncertainty propagation

Parameters:
xArrayT

Input array or tensor of shape (N,…), where N is the number of elements

uncertaintyArrayT

Uncertainty of the input array or tensor of shape (N,…)

Returns:
tuple[ArrayT, ArrayT]

Transformed array or tensor of shape (N,…) and transformed uncertainty of shape (N,…)

abstractmethod BaseTransform.backward_grad(x, uncertainty)[source]

Backwards pass to invert the transformation and uncertainty propagation

Parameters:
xArrayT

Input array or tensor of shape (N,…), where N is the number of elements

uncertaintyArrayT

Uncertainty of the input array or tensor of shape (N,…)

Returns:
tuple[ArrayT, ArrayT]

Untransformed array or tensor of shape (N,…) and untransformed uncertainty of shape (N,…)

Along with the magic method:

BaseTransform.__call__(x, *, back=False, uncertainty=None)[source]

Calling function returns the forward, backwards or uncertainty propagation of the transformation

Overloads:

__call__(self, x: InArrayCT, *, back: bool = ...) OutArrayCT
__call__(self, x: Data[InArrayCT], *, back: bool = ...) Data[OutArrayCT]
__call__(self, x: InArrayCT, *, back: bool = ..., uncertainty: InArrayCT) tuple[OutArrayCT, OutArrayCT]
Parameters:
xArrayT | Data[ArrayCT]

Input array or tensor of shape (N,…), where N is the number of elements

backbool, Optional

If the inverse transformation should be applied, default = False

uncertaintyArrayT, Optional

Corresponding uncertainties for the input data for uncertainty propagation of shape (N,…)

Returns:
ArrayT | Data[ArrayCT] | tuple[ArrayT, ArrayT]

Transformed array or tensor of shape (N,…) and propagated uncertainties of shape (N,…) if provided

All transform operations can be performed via the __call__ method with the optional arguments back and uncertainty. BaseTransform also accepts Data objects as input to transform the data and uncertainties. To transform data, first initialise the transform object, then call the object with the data (and optional uncertainties) to be transformed.

import torch
from torch import Tensor
from netloader.data import Data
from netloader import transforms

values: Tensor
values_2: Tensor
trans_values: Tensor
uncertainties: Tensor
uncertainties_2: Tensor
trans_uncertainties: Tensor
data: Data
data_2: Data
trans_data: Data
transform: transforms.BaseTransform

 values = torch.tensor([1.0, 2.0, 3.0])
 uncertainties = torch.tensor([0.1, 0.2, 0.3])
 data = Data(values, uncertainty=uncertainties)
 transform = transforms.Log()

# Transform data
trans_values, trans_uncertainties = transform(values, uncertainty=uncertainties)
trans_data = transform(data)

# Untransform data
values_2, uncertainties_2 = transform(trans_values, back=True, uncertainty=trans_uncertainties)
data_2 = transform(trans_data, back=True)

assert torch.isclose(values, values_2).all()
assert torch.isclose(uncertainties, uncertainties_2).all()
assert torch.isclose(data.data, data_2.data).all()
assert torch.isclose(data.uncertainty, data_2.uncertainty).all()

Custom Transforms from BaseTransform#

The BaseTransform class provides the base functionality for all transforms. When creating a custom transform from BaseTransform, if the transform’s forwards or backwards operations are different from an identity transform, then the forward and/or backward methods should be defined. If the transform’s forwards or backwards uncertainty propagation is different from an identity transform, then the forward_grad and/or backward_grad methods should also be defined. All custom transforms must also implement the __getstate__ and __setstate__ methods if extra attributes are required.

Example implementing a logarithmic transform from BaseTransform:

from typing import Any
from types import ModuleType

import torch
import numpy as np
from netloader.utils.types import ArrayT
from netloader.transforms import BaseTransform

class Log(BaseTransform):
    """
    Logarithmic transform.
    """
    def __init__(self, *, base: float = 10) -> None:
        """
        Parameters
        ----------
        base : float, Optional
            Base of the logarithm, default = 10
        """
        super().__init__()
        self._base: float = base

    def __getstate__(self) -> dict[str, Any]:
        return super().__getstate__() | {'base': self._base}

    def __setstate__(self, state: dict[str, Any]) -> None:
        super().__setstate__(state)
        self._base = state['base']

    def forward(self, x: ArrayT) -> ArrayT:
        module: ModuleType = torch if isinstance(x, Tensor) else np
        return module.log(x) / np.log(self._base)

    def backward(self, x: ArrayT) -> ArrayT:
        return self._base ** x

    def forward_grad(self, x: ArrayT, uncertainty: ArrayT) -> tuple[ArrayT, ArrayT]:
        module: ModuleType = torch if isinstance(x, Tensor) else np
        return self(x), uncertainty / module.abs(x * np.log(self._base))

    def backward_grad(self, x: ArrayT, uncertainty: ArrayT) -> tuple[ArrayT, ArrayT]:
        module: ModuleType = torch if isinstance(x, Tensor) else np
        x = self(x, back=True)
        return x, uncertainty * module.abs(x * np.log(self._base))

Built-in Transforms#

All transforms currently supported by NetLoader are listed below with a brief description of their functionality and their initialisation arguments, excluding general initialisation arguments defined in BaseTransform. For more information on each transform, see API documentation.

BaseTransform : Base transformation class that other types of transforms build from.

BaseTypeTransform : Base transformation class that other types of transforms build from, with the same input and

Index : Slices the input along a given dimension assuming the input meets the required shape.

  • dim : int = -1 – Dimension to slice over

  • in_shape : tuple[int, EllipsisType] | NoneType = None – Target shape ignoring batch size so that the slice only occurs if the input has the same shape to prevent repeated slicing, if any dimension has a shape of -1, then the size of the dimension will be ignored

  • slice_ : slice = slice(None, None, None) – Slicing object

Log : Logarithm transform.

  • base : float = 10 – Base of the logarithm

  • idxs : list[int] | NoneType = None – Indices to slice the last dimension to perform the log on

MinClamp : Clamps the minimum value to be the smallest positive value.

  • dim : int | NoneType = None – Dimension to take the minimum value over

  • idxs : list[int] | NoneType = None – Indices to slice the last dimension to perform the min clamp on

MultiTransform : Applies multiple transformations.

Normalise : Normalises the data to zero mean and unit variance, or between 0 and 1.

  • mean : bool = True – If data should be normalised to zero mean and unit variance, or between 0 and 1, default = True

  • dim : int | tuple[int, EllipsisType] | NoneType = None – Dimensions to normalise over, if None, all dimensions will be normalised over

  • offset : ndarray | NoneType = None – Offset to subtract from the data if data argument is None

  • scale : ndarray | NoneType = None – Scale to divide the data if data argument is None

  • data : ndarray | Tensor | NoneType = None – Data to normalise with shape (N,…), where N is the number of elements

NumpyTensor : Converts Numpy arrays to PyTorch tensors.

  • dtype : dtype = torch.float32 – Data type of the tensor

Reshape : Reshapes the data.