Skip to main content

omnibias.keras.growable

0.0.2a1AlphaPython >=3.10Apache-2.0

Keras 3 operator layers and activation paths implemented with keras.ops for TensorFlow, JAX, or Torch.

import omnibias.keras.growable
Module contract & mathematical conventions

GrowableOperatorMultiBiasUnit (Keras backend).

An OMBU whose bias arity K can grow during training without changing its output shape. Mirrors omnibias.torch.growable.GrowableOperatorMultiBiasUnit.

Two growth strategies preserve the unit's output at the moment of growth:

  • "pair" (default; any base activation). Adds two biases at an existing location with signs (+eta, -eta) (sum zero, output unchanged).
  • "saturate" (only for activations that vanish as z -> -inf: sigmoid, softplus, gaussian, exp, relu, huber). Adds one bias at -big so the new term contributes ~0.

Biases / signs are pre-allocated at K_max; the forward only sums over the first active_K columns.

Shared formulas do not imply universal floating-point identity. Verify dtype, shape, derivative-order support, and the stated numerical assumptions for the path you use.

CONSTANT

GrowStrategy​

omnibias.keras.GrowStrategySource ↗
GrowStrategy = Literal['pair', 'saturate']

This exported constant is defined by the expression above. Its exact value and annotation come from the pinned source; use it with the module contract.

Read the implementation
GrowStrategy = Literal["pair", "saturate"]
Other public import paths
  • omnibias.keras.GrowStrategy
  • omnibias.keras.growable.GrowStrategy
CLASS

GrowableOperatorMultiBiasUnit​

omnibias.keras.GrowableOMBUSource ↗
class GrowableOperatorMultiBiasUnit(layers.Layer)

A multi-bias unit whose arity K can grow during training.

Parameters

num_channels : int init_K : int, default 1 K_max : int, default 8 base : str or ActivationSpec, default "sigmoid" init_bias : float, default 0.0 learnable_biases : bool, default True learnable_signs : bool, default True pair_eta : float, default 0.05 saturate_big : float, default 25.0

Parameters

NameType & defaultContract
num_channelspositional-or-keywordintRequiredint
init_Kpositional-or-keywordintDefault: 1int, default 1
K_maxpositional-or-keywordintDefault: 8int, default 8
basepositional-or-keywordstr | ActivationSpec[Any]Default: 'sigmoid'str or ActivationSpec, default "sigmoid"
init_biaspositional-or-keywordfloatDefault: 0.0float, default 0.0
learnable_biasespositional-or-keywordboolDefault: Truebool, default True
learnable_signspositional-or-keywordboolDefault: Truebool, default True
pair_etapositional-or-keywordfloatDefault: 0.05float, default 0.05
saturate_bigpositional-or-keywordfloatDefault: 25.0float, default 25.0
kwargsvariadic keywordAnyRequiredSee the operation contract and implementation below.
Executed regression · grow beyond kmax raises

Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.

def test_grow_beyond_kmax_raises() -> None:
    unit = GrowableOMBU(num_channels=2, init_K=1, K_max=2, base="sigmoid")
    with pytest.raises(RuntimeError):
        unit.grow("pair")
python -m pytest "packages/omnibias-keras/tests/test_growable.py::test_grow_beyond_kmax_raises" -q
Regression example · saturate growth friendly only

This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.

def test_saturate_growth_friendly_only() -> None:
    unit = GrowableOMBU(num_channels=2, init_K=1, K_max=4, base="sigmoid")
    unit.grow("saturate")
    assert unit.active_K == 2

    bad = GrowableOMBU(num_channels=2, init_K=1, K_max=4, base="tanh")
    with pytest.raises(ValueError):
        bad.grow("saturate")
python -m pytest packages/omnibias-keras/tests/test_growable.py -k test_saturate_growth_friendly_only -q

Fields & methods

__init__method
__init__(self, num_channels: int, init_K: int=1, K_max: int=8, base: str | ActivationSpec[Any]='sigmoid', init_bias: float=0.0, learnable_biases: bool=True, learnable_signs: bool=True, pair_eta: float=0.05, saturate_big: float=25.0, **kwargs: Any) -> None

See the class contract above and the exact method implementation below.

Parameters

NameType & defaultContract
num_channelspositional-or-keywordintRequiredSee the operation contract and implementation below.
init_Kpositional-or-keywordintDefault: 1See the operation contract and implementation below.
K_maxpositional-or-keywordintDefault: 8See the operation contract and implementation below.
basepositional-or-keywordstr | ActivationSpec[Any]Default: 'sigmoid'See the operation contract and implementation below.
init_biaspositional-or-keywordfloatDefault: 0.0See the operation contract and implementation below.
learnable_biasespositional-or-keywordboolDefault: TrueSee the operation contract and implementation below.
learnable_signspositional-or-keywordboolDefault: TrueSee the operation contract and implementation below.
pair_etapositional-or-keywordfloatDefault: 0.05See the operation contract and implementation below.
saturate_bigpositional-or-keywordfloatDefault: 25.0See the operation contract and implementation below.
kwargsvariadic keywordAnyRequiredSee the operation contract and implementation below.

Returns: None

Validation & failure cases
  • When num_channels < 1, raises ValueError(f'num_channels must be >= 1, got {num_channels}').
  • When K_max < 1, raises ValueError(f'K_max must be >= 1, got {K_max}').
  • When not 1 <= init_K <= K_max, raises ValueError(f'init_K must be in [1, K_max={K_max}], got {init_K}').
  • When pair_eta <= 0, raises ValueError(f'pair_eta must be > 0, got {pair_eta}').
  • When saturate_big <= 0, raises ValueError(f'saturate_big must be > 0, got {saturate_big}').
Executed regression · grow beyond kmax raises

Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.

def test_grow_beyond_kmax_raises() -> None:
    unit = GrowableOMBU(num_channels=2, init_K=1, K_max=2, base="sigmoid")
    with pytest.raises(RuntimeError):
        unit.grow("pair")
python -m pytest "packages/omnibias-keras/tests/test_growable.py::test_grow_beyond_kmax_raises" -q
Method implementation
    def __init__(
        self,
        num_channels: int,
        init_K: int = 1,
        K_max: int = 8,
        base: str | ActivationSpec[Any] = "sigmoid",
        init_bias: float = 0.0,
        learnable_biases: bool = True,
        learnable_signs: bool = True,
        pair_eta: float = 0.05,
        saturate_big: float = 25.0,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        if num_channels < 1:
            raise ValueError(f"num_channels must be >= 1, got {num_channels}")
        if K_max < 1:
            raise ValueError(f"K_max must be >= 1, got {K_max}")
        if not (1 <= init_K <= K_max):
            raise ValueError(f"init_K must be in [1, K_max={K_max}], got {init_K}")
        if pair_eta <= 0:
            raise ValueError(f"pair_eta must be > 0, got {pair_eta}")
        if saturate_big <= 0:
            raise ValueError(f"saturate_big must be > 0, got {saturate_big}")

        self.num_channels = num_channels
        self.K_max = K_max
        self._base_name = base if isinstance(base, str) else base.name
        self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)
        self.pair_eta = float(pair_eta)
        self.saturate_big = float(saturate_big)
        self.init_bias = init_bias
        self.learnable_biases = learnable_biases
        self.learnable_signs = learnable_signs

        dtype = self.variable_dtype
        biases_np = np.full((num_channels, K_max), float(init_bias), dtype=dtype)
        signs_np = np.zeros((num_channels, K_max), dtype=dtype)
        signs_np[:, :init_K] = identity_signs(init_K, dtype=dtype)[None, :]

        self.biases = self.add_weight(
            name="biases",
            shape=(num_channels, K_max),
            initializer="zeros",
            trainable=learnable_biases,
            dtype=dtype,
        )
        self.signs = self.add_weight(
            name="signs",
            shape=(num_channels, K_max),
            initializer="zeros",
            trainable=learnable_signs,
            dtype=dtype,
        )
        self.biases.assign(biases_np)
        self.signs.assign(signs_np)
        self._active_K = int(init_K)
        self.built = True
View source ↗
active_Kproperty
active_K(self) -> int

See the class contract above and the exact method implementation below.

Returns: int

Executed regression · saturate growth friendly only

Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.

def test_saturate_growth_friendly_only() -> None:
    unit = GrowableOMBU(num_channels=2, init_K=1, K_max=4, base="sigmoid")
    unit.grow("saturate")
    assert unit.active_K == 2

    bad = GrowableOMBU(num_channels=2, init_K=1, K_max=4, base="tanh")
    with pytest.raises(ValueError):
        bad.grow("saturate")
python -m pytest "packages/omnibias-keras/tests/test_growable.py::test_saturate_growth_friendly_only" -q
Method implementation
    def active_K(self) -> int:
        return self._active_K
View source ↗
can_growproperty
can_grow(self) -> bool

See the class contract above and the exact method implementation below.

Returns: bool

Method implementation
    def can_grow(self) -> bool:
        return self._active_K < self.K_max
View source ↗
callmethod
call(self, z: Any) -> Any

Literal multi-bias forward over the active K columns only.

Parameters

NameType & defaultContract
zpositional-or-keywordAnyRequiredSee the operation contract and implementation below.

Returns: Any

Executed regression · pair growth preserves output

Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.

def test_pair_growth_preserves_output() -> None:
    z = np.random.default_rng(0).normal(size=(8, 4))
    unit = GrowableOMBU(num_channels=4, init_K=1, K_max=5, base="tanh")
    before = _np(unit(_t(z)))
    added = unit.grow("pair")
    assert added == 2
    assert unit.active_K == 3
    after = _np(unit(_t(z)))
    np.testing.assert_allclose(after, before, rtol=1e-10, atol=1e-12)
python -m pytest "packages/omnibias-keras/tests/test_growable.py::test_pair_growth_preserves_output" -q
Method implementation
    def call(self, z: Any) -> Any:
        """Literal multi-bias forward over the active K columns only."""
        K = self._active_K
        biases = self.biases[:, :K]
        signs = self.signs[:, :K]
        return multibias_literal_forward(z, biases, signs, self.spec.forward)
View source ↗
growmethod
grow(self, strategy: GrowStrategy='pair', *, channel: int | None=None, anchor_value: float | np.ndarray | None=None) -> int

Grow active_K. Returns the number of newly-activated columns.

Parameters

NameType & defaultContract
strategypositional-or-keywordGrowStrategyDefault: 'pair'See the operation contract and implementation below.
channelkeyword-onlyint | NoneDefault: NoneSee the operation contract and implementation below.
anchor_valuekeyword-onlyfloat | np.ndarray | NoneDefault: NoneSee the operation contract and implementation below.

Returns: int

Validation & failure cases
  • When K + 2 > self.K_max, raises RuntimeError(f'grow(pair) would exceed K_max={self.K_max}: active_K={K}').
  • When anchor_value.shape != (self.num_channels,), raises ValueError(f'anchor_value array must have shape ({self.num_channels},), got {anchor_value.shape}').
  • When not 0 <= j < K, raises ValueError(f'channel must be in [0, {K}), got {j}').
  • When K + 1 > self.K_max, raises RuntimeError(f'grow(saturate) would exceed K_max={self.K_max}: active_K={K}').
  • When self.spec.name not in _SATURATE_FRIENDLY, raises ValueError(f"strategy='saturate' is only safe for activations that vanish at z -> -infinity: one of {sorted(_SATURATE_FRIENDLY)}. Got {self.spec.name!r}; use strategy='pair' instead.").
Executed regression · grow beyond kmax raises

Recorded coverage confirms this test executed the API body. This example belongs to the pinned upstream test suite. It may use fixtures and imports from its full file. Run it from that checkout with its test dependencies installed.

def test_grow_beyond_kmax_raises() -> None:
    unit = GrowableOMBU(num_channels=2, init_K=1, K_max=2, base="sigmoid")
    with pytest.raises(RuntimeError):
        unit.grow("pair")
python -m pytest "packages/omnibias-keras/tests/test_growable.py::test_grow_beyond_kmax_raises" -q
Method implementation
    def grow(
        self,
        strategy: GrowStrategy = "pair",
        *,
        channel: int | None = None,
        anchor_value: float | np.ndarray | None = None,
    ) -> int:
        """Grow ``active_K``. Returns the number of newly-activated columns."""
        K = self._active_K
        biases_np = np.asarray(ops.convert_to_numpy(self.biases.value))
        signs_np = np.asarray(ops.convert_to_numpy(self.signs.value))

        if strategy == "pair":
            if K + 2 > self.K_max:
                raise RuntimeError(f"grow(pair) would exceed K_max={self.K_max}: active_K={K}")
            if anchor_value is not None:
                if isinstance(anchor_value, np.ndarray):
                    if anchor_value.shape != (self.num_channels,):
                        raise ValueError(
                            f"anchor_value array must have shape ({self.num_channels},), "
                            f"got {anchor_value.shape}"
                        )
                    anchor = anchor_value.astype(biases_np.dtype)
                else:
                    anchor = np.full(self.num_channels, float(anchor_value), dtype=biases_np.dtype)
            else:
                j = 0 if channel is None else int(channel)
                if not (0 <= j < K):
                    raise ValueError(f"channel must be in [0, {K}), got {j}")
                anchor = biases_np[:, j].copy()
            biases_np[:, K] = anchor
            biases_np[:, K + 1] = anchor
            signs_np[:, K] = +self.pair_eta
            signs_np[:, K + 1] = -self.pair_eta
            self.biases.assign(biases_np)
            self.signs.assign(signs_np)
            self._active_K += 2
            return 2

        if strategy == "saturate":
            if K + 1 > self.K_max:
                raise RuntimeError(f"grow(saturate) would exceed K_max={self.K_max}: active_K={K}")
            if self.spec.name not in _SATURATE_FRIENDLY:
                raise ValueError(
                    f"strategy='saturate' is only safe for activations that vanish "
                    f"at z -> -infinity: one of {sorted(_SATURATE_FRIENDLY)}. "
                    f"Got {self.spec.name!r}; use strategy='pair' instead."
                )
            biases_np[:, K] = -self.saturate_big
            signs_np[:, K] = 0.0
            self.biases.assign(biases_np)
            self.signs.assign(signs_np)
            self._active_K += 1
            return 1

        raise ValueError(f"Unknown strategy {strategy!r}; expected 'pair' or 'saturate'.")
View source ↗
active_parametersmethod
active_parameters(self) -> dict[str, Any]

See the class contract above and the exact method implementation below.

Returns: dict[str, Any]

Method implementation
    def active_parameters(self) -> dict[str, Any]:
        K = self._active_K
        return {"biases": self.biases[:, :K], "signs": self.signs[:, :K]}
View source ↗
get_configmethod
get_config(self) -> dict[str, Any]

See the class contract above and the exact method implementation below.

Returns: dict[str, Any]

Method implementation
    def get_config(self) -> dict[str, Any]:
        config = super().get_config()
        config.update(
            {
                "num_channels": self.num_channels,
                "init_K": self._active_K,
                "K_max": self.K_max,
                "base": self._base_name,
                "init_bias": self.init_bias,
                "learnable_biases": self.learnable_biases,
                "learnable_signs": self.learnable_signs,
                "pair_eta": self.pair_eta,
                "saturate_big": self.saturate_big,
            }
        )
        return config
View source ↗
num_channelsattribute
num_channels: int = num_channels

int

View source ↗
K_maxattribute
K_max: int = K_max

int, default 8

View source ↗
specattribute
spec = base if isinstance(base, ActivationSpec) else get_activation(base)

Public instance attribute assigned by __init__. The source assignment is spec = base if isinstance(base, ActivationSpec) else get_activation(base); it describes instance state, not a literal constructor default.

View source ↗
pair_etaattribute
pair_eta = float(pair_eta)

float, default 0.05

View source ↗
saturate_bigattribute
saturate_big = float(saturate_big)

float, default 25.0

View source ↗
init_biasattribute
init_bias: float = init_bias

float, default 0.0

View source ↗
learnable_biasesattribute
learnable_biases: bool = learnable_biases

bool, default True

View source ↗
learnable_signsattribute
learnable_signs: bool = learnable_signs

bool, default True

View source ↗
biasesattribute
biases = self.add_weight(name='biases', shape=(num_channels, K_max), initializer='zeros', trainable=learnable_biases, dtype=dtype)

Public instance attribute assigned by __init__. The source assignment is biases = self.add_weight(name='biases', shape=(num_channels, K_max), initializer='zeros', trainable=learnable_biases, dtype=dtype); it describes instance state, not a literal constructor default.

View source ↗
signsattribute
signs = self.add_weight(name='signs', shape=(num_channels, K_max), initializer='zeros', trainable=learnable_signs, dtype=dtype)

Public instance attribute assigned by __init__. The source assignment is signs = self.add_weight(name='signs', shape=(num_channels, K_max), initializer='zeros', trainable=learnable_signs, dtype=dtype); it describes instance state, not a literal constructor default.

View source ↗
builtattribute
built = True

Public instance attribute assigned by __init__. The source assignment is built = True; it describes instance state, not a literal constructor default.

View source ↗
Read the implementation
class GrowableOperatorMultiBiasUnit(layers.Layer):
    """A multi-bias unit whose arity ``K`` can grow during training.

    Parameters
    ----------
    num_channels : int
    init_K : int, default 1
    K_max : int, default 8
    base : str or :class:`ActivationSpec`, default ``"sigmoid"``
    init_bias : float, default 0.0
    learnable_biases : bool, default True
    learnable_signs : bool, default True
    pair_eta : float, default 0.05
    saturate_big : float, default 25.0
    """

    def __init__(
        self,
        num_channels: int,
        init_K: int = 1,
        K_max: int = 8,
        base: str | ActivationSpec[Any] = "sigmoid",
        init_bias: float = 0.0,
        learnable_biases: bool = True,
        learnable_signs: bool = True,
        pair_eta: float = 0.05,
        saturate_big: float = 25.0,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        if num_channels < 1:
            raise ValueError(f"num_channels must be >= 1, got {num_channels}")
        if K_max < 1:
            raise ValueError(f"K_max must be >= 1, got {K_max}")
        if not (1 <= init_K <= K_max):
            raise ValueError(f"init_K must be in [1, K_max={K_max}], got {init_K}")
        if pair_eta <= 0:
            raise ValueError(f"pair_eta must be > 0, got {pair_eta}")
        if saturate_big <= 0:
            raise ValueError(f"saturate_big must be > 0, got {saturate_big}")

        self.num_channels = num_channels
        self.K_max = K_max
        self._base_name = base if isinstance(base, str) else base.name
        self.spec = base if isinstance(base, ActivationSpec) else get_activation(base)
        self.pair_eta = float(pair_eta)
        self.saturate_big = float(saturate_big)
        self.init_bias = init_bias
        self.learnable_biases = learnable_biases
        self.learnable_signs = learnable_signs

        dtype = self.variable_dtype
        biases_np = np.full((num_channels, K_max), float(init_bias), dtype=dtype)
        signs_np = np.zeros((num_channels, K_max), dtype=dtype)
        signs_np[:, :init_K] = identity_signs(init_K, dtype=dtype)[None, :]

        self.biases = self.add_weight(
            name="biases",
            shape=(num_channels, K_max),
            initializer="zeros",
            trainable=learnable_biases,
            dtype=dtype,
        )
        self.signs = self.add_weight(
            name="signs",
            shape=(num_channels, K_max),
            initializer="zeros",
            trainable=learnable_signs,
            dtype=dtype,
        )
        self.biases.assign(biases_np)
        self.signs.assign(signs_np)
        self._active_K = int(init_K)
        self.built = True

    @property
    def active_K(self) -> int:
        return self._active_K

    @property
    def can_grow(self) -> bool:
        return self._active_K < self.K_max

    def call(self, z: Any) -> Any:
        """Literal multi-bias forward over the active K columns only."""
        K = self._active_K
        biases = self.biases[:, :K]
        signs = self.signs[:, :K]
        return multibias_literal_forward(z, biases, signs, self.spec.forward)

    def grow(
        self,
        strategy: GrowStrategy = "pair",
        *,
        channel: int | None = None,
        anchor_value: float | np.ndarray | None = None,
    ) -> int:
        """Grow ``active_K``. Returns the number of newly-activated columns."""
        K = self._active_K
        biases_np = np.asarray(ops.convert_to_numpy(self.biases.value))
        signs_np = np.asarray(ops.convert_to_numpy(self.signs.value))

        if strategy == "pair":
            if K + 2 > self.K_max:
                raise RuntimeError(f"grow(pair) would exceed K_max={self.K_max}: active_K={K}")
            if anchor_value is not None:
                if isinstance(anchor_value, np.ndarray):
                    if anchor_value.shape != (self.num_channels,):
                        raise ValueError(
                            f"anchor_value array must have shape ({self.num_channels},), "
                            f"got {anchor_value.shape}"
                        )
                    anchor = anchor_value.astype(biases_np.dtype)
                else:
                    anchor = np.full(self.num_channels, float(anchor_value), dtype=biases_np.dtype)
            else:
                j = 0 if channel is None else int(channel)
                if not (0 <= j < K):
                    raise ValueError(f"channel must be in [0, {K}), got {j}")
                anchor = biases_np[:, j].copy()
            biases_np[:, K] = anchor
            biases_np[:, K + 1] = anchor
            signs_np[:, K] = +self.pair_eta
            signs_np[:, K + 1] = -self.pair_eta
            self.biases.assign(biases_np)
            self.signs.assign(signs_np)
            self._active_K += 2
            return 2

        if strategy == "saturate":
            if K + 1 > self.K_max:
                raise RuntimeError(f"grow(saturate) would exceed K_max={self.K_max}: active_K={K}")
            if self.spec.name not in _SATURATE_FRIENDLY:
                raise ValueError(
                    f"strategy='saturate' is only safe for activations that vanish "
                    f"at z -> -infinity: one of {sorted(_SATURATE_FRIENDLY)}. "
                    f"Got {self.spec.name!r}; use strategy='pair' instead."
                )
            biases_np[:, K] = -self.saturate_big
            signs_np[:, K] = 0.0
            self.biases.assign(biases_np)
            self.signs.assign(signs_np)
            self._active_K += 1
            return 1

        raise ValueError(f"Unknown strategy {strategy!r}; expected 'pair' or 'saturate'.")

    def active_parameters(self) -> dict[str, Any]:
        K = self._active_K
        return {"biases": self.biases[:, :K], "signs": self.signs[:, :K]}

    def get_config(self) -> dict[str, Any]:
        config = super().get_config()
        config.update(
            {
                "num_channels": self.num_channels,
                "init_K": self._active_K,
                "K_max": self.K_max,
                "base": self._base_name,
                "init_bias": self.init_bias,
                "learnable_biases": self.learnable_biases,
                "learnable_signs": self.learnable_signs,
                "pair_eta": self.pair_eta,
                "saturate_big": self.saturate_big,
            }
        )
        return config
Other public import paths
  • omnibias.keras.GrowableOMBU
  • omnibias.keras.GrowableOperatorMultiBiasUnit
  • omnibias.keras.growable.GrowableOperatorMultiBiasUnit