Source code for waxmorph.jax.mlp
"""Equinox MLP used by the JAX graph-network simulator."""
from __future__ import annotations
from functools import partial
import equinox as eqx
import jax
import jax.numpy as jnp
from waxmorph._validation import _validate_mlp_config
_ACTIVATIONS = {
"relu": jax.nn.relu,
"silu": jax.nn.silu,
"gelu": partial(jax.nn.gelu, approximate=False),
"tanh": jnp.tanh,
}
[docs]
class MLP(eqx.Module):
"""MLP with optional LayerNorm on its output.
``num_layers`` counts linear maps; one layer is affine. Hidden layers
support ``relu``, ``silu``, ``gelu``, and ``tanh``. Callers must supply a PRNG ``key``;
independent modules need independently split keys.
Raises:
TypeError: If ``num_layers`` is boolean or nonintegral.
ValueError: If ``num_layers < 1`` or the activation is unsupported.
Examples:
>>> mlp = MLP(3, 2, hidden_dim=4, num_layers=1, layer_norm=False, key=jax.random.PRNGKey(0))
>>> print(tuple(mlp(jnp.ones((5, 3))).shape))
(5, 2)
"""
net: eqx.nn.MLP
norm: eqx.nn.LayerNorm | None
activation_name: str = eqx.field(static=True)
def __init__(
self,
input_dim: int,
output_dim: int,
hidden_dim: int = 128,
num_layers: int = 2,
activation: str = "silu",
layer_norm: bool = True,
*,
key: jax.Array,
):
num_layers = _validate_mlp_config(activation, _ACTIVATIONS, num_layers)
self.activation_name = activation
# eqx depth = hidden layers; our num_layers = total linear layers = depth + 1
depth = num_layers - 1
self.net = eqx.nn.MLP(
in_size=input_dim,
out_size=output_dim,
width_size=hidden_dim,
depth=depth,
activation=_ACTIVATIONS[activation],
key=key,
)
self.norm = eqx.nn.LayerNorm(output_dim) if layer_norm else None
[docs]
def __call__(self, x: jax.Array) -> jax.Array:
"""Preserve arbitrary leading axes by vectorizing Equinox's single-vector network."""
if x.ndim == 1:
out = self.net(x)
if self.norm is not None:
out = self.norm(out)
return out
lead = x.shape[:-1]
flat = x.reshape(-1, x.shape[-1])
out = jax.vmap(self.net)(flat)
if self.norm is not None:
out = jax.vmap(self.norm)(out)
return out.reshape(*lead, out.shape[-1])