Source code for e3nn_mlx.nn_fc

"""Variance-normalized fully connected network."""

from __future__ import annotations

from typing import Any, Callable, Sequence

from .compat import mlx_module_base, require_mlx
from .nn_activation import _activation_properties


class _Layer(mlx_module_base()):
    def __init__(self, h_in: int, h_out: int, act, var_in: float, var_out: float) -> None:
        super().__init__()
        mx, _ = require_mlx()
        self.weight = mx.random.normal(shape=(h_in, h_out))
        self.h_in = h_in
        self.h_out = h_out
        self.act = act
        self.var_in = float(var_in)
        self.var_out = float(var_out)

    def __call__(self, values):
        if self.act is None:
            weight = self.weight / (self.h_in * self.var_in / self.var_out) ** 0.5
            return values @ weight
        weight = self.weight / (self.h_in * self.var_in) ** 0.5
        return self.var_out**0.5 * self.act(values @ weight)


class FullyConnectedNet(mlx_module_base()):
[docs] def __init__( self, hs: Sequence[int], act: Callable[[Any], Any] | None = None, variance_in: float = 1.0, variance_out: float = 1.0, out_act: bool = False, ) -> None: super().__init__() self.hs = tuple(int(value) for value in hs) if len(self.hs) < 2 or any(value <= 0 for value in self.hs): raise ValueError("hs must contain at least two positive dimensions") normalized_act = None if act is not None: scale, _ = _activation_properties(act) normalized_act = lambda value: scale * act(value) var_in = float(variance_in) self._layer_names = [] for index, (h_in, h_out) in enumerate(zip(self.hs, self.hs[1:])): is_last = index == len(self.hs) - 2 var_out = float(variance_out) if is_last else 1.0 layer_act = normalized_act if (not is_last or out_act) else None name = f"layer{index}" setattr(self, name, _Layer(h_in, h_out, layer_act, var_in, var_out)) self._layer_names.append(name) var_in = var_out
def __repr__(self) -> str: return f"FullyConnectedNet{list(self.hs)}" def __call__(self, values): if values.shape[-1] != self.hs[0]: raise ValueError(f"expected input dimension {self.hs[0]}") for name in self._layer_names: values = getattr(self, name)(values) return values