Normalizer

The normalizer is the component that rescales feature and target tensors into a numerically conditioned range before they reach the model, keeping input columns on comparable scales so optimization is well behaved. It alters tensor values without changing their shape, and it is invertible, so model predictions can be mapped back into the original units. By default it normalizes input features only. Targets are normalized only when the policy requests it.

Usage

The normalizer occupies the components.normalizer slot.

components:
  normalizer:
    name: zscore
    kwargs: {}

How it Works

A normalizer derives its parameters from data rather than from fixed settings. It reads training-split statistics from the dataset computed during processing. The resolved parameters are stored as buffers on the component, so they are written into the checkpoint and reused unchanged at inference, guaranteeing that the same transform is applied during training and deployment. Whether targets are normalized is resolved once and then retained, so a model reloaded from a checkpoint behaves consistently without its original configuration.

Subclasses

Variants

The normalizer slot has no variants registered directly on the base; every selectable normalizer is provided by one of the subclasses above.

Registering a new normalizer

A normalizer is a subclass of Normalizer that declares a name and version, validates its configuration, and implements the transform. The base class drives both the forward and inverse passes through a single required method:

normalize(self, t, /, role, *, inverse) -> Tensor

Map the values of t (a segmented tensor) for the given column role and return a tensor of the same shape. When inverse is True, undo the mapping.

from typing import Any, ClassVar

from torch import Tensor
from pydantic import BaseModel

from icegraph.common.data import ColumnarRole
from icegraph.common.tensors import SegmentedTensor
from icegraph.engine.components.normalizer import Normalizer, NormalizerFactory

class Config(BaseModel):
    ...  # declare and validate any options the normalizer accepts

class MyNormalizer(Normalizer[Config]):
    name: ClassVar[str] = "my-normalizer"
    version: ClassVar[int] = 1

    @classmethod
    def validate_config(cls, config: dict[str, Any]) -> Config:
        return Config(**config)

    def build(self) -> None:
        ...  # one-time setup, e.g. registering buffers

    def normalize(self, t: SegmentedTensor, /, role: ColumnarRole, *, inverse: bool) -> Tensor:
        ...  # return the mapped values, with the same shape as t.data

NormalizerFactory.register(MyNormalizer)
components:
  normalizer:
    name: my-normalizer
    kwargs: {}