Skip to content

Use a custom strategy

Three things can be replaced with your own implementation: the neighborhood, the decay applied to the learning rate and radius, and the distance.

Three things can be replaced with your own implementation: the neighborhood, the decay applied to the learning rate and radius, and the distance. Each has a Protocol describing what it must accept and return, so a type checker verifies yours against a named contract rather than a bare Callable.

from python_som import DecayFunction


def linear_to_zero(value: float, step: int, total: int) -> float:
    return value * (1.0 - step / total)


som = python_som.SOM(x=10, y=10, input_len=4, learning_rate_decay=linear_to_zero)

The protocols are structural: nothing needs to inherit from them, and every callable that already worked still does. Their parameters are positional-only, so your parameter names are your own. linear_to_zero(value, step, total) and linear_to_zero(a, b, c) both satisfy DecayFunction.

One contract is worth reading before writing a neighborhood of your own: NeighborhoodFunction must be a function of the grid distance between two nodes, not of the two axis offsets separately. See Why isotropy matters for what goes wrong otherwise.

What a custom strategy costs you

One thing, and it is worth knowing before you commit: a callable cannot be written to a file without pickle, so save_npz records only its name. A map trained with your own function will not reload on its own.

python_som.SOM.load_npz("custom.npz")
# ArtifactError: ... Pass them back explicitly: load_npz(path, distance_function=...)

python_som.SOM.load_npz("custom.npz", distance_function=my_distance)  # works

That is deliberate. The alternative is guessing, and a map that silently reloads with a different distance from the one that trained it is worse than one that refuses.

If you want a strategy that round-trips without the extra argument, the ones this package ships all do: their names are the keys of NEIGHBORHOOD_FUNCTIONS, DECAY_FUNCTIONS and DISTANCE_FUNCTIONS.

Before writing a neighborhood

Read Why isotropy matters first. A neighborhood must be a function of the grid distance between two nodes, not of the two axis offsets separately, and the construction that tempts you into getting this wrong is the same one that works correctly for the gaussian.