mononet#

Constrained monotonic neural networks with first-class support for PyTorch, JAX (Flax NNX), and Keras 3.

Reference implementation of:

Runje, D., Shankaranarayana, S. M. (2023). Constrained Monotonic Neural Networks. ICML 2023. arXiv:2205.11775

Install#

pip install "mononet[torch]"      # or [jax], [keras], [all]

See Installation for the full extras reference, GPU extras, and the CPU-torch (uv vs pip) caveat.

Quickstart#

Your first monotonic model — a small regressor that is non-decreasing in every input. mononet ships layers; stack them with your framework’s native Sequential.

# SPDX-License-Identifier: Apache-2.0
"""Quickstart: a monotone regressor in PyTorch.

Non-decreasing in every one of its 4 inputs. ``mononet`` ships layers, not
composed models — stack them with a native ``torch.nn.Sequential``.
"""

from __future__ import annotations

import torch
from torch import nn

from mononet.torch import MonoLinear, MonoResidual

model = nn.Sequential(
    MonoLinear(4, 32, activation="elu"),
    MonoResidual(32, 32, activation="elu"),
    MonoLinear(32, 1),
)

y = model(torch.rand(8, 4))
print(y.shape)  # torch.Size([8, 1]) — monotone in all 4 inputs
# SPDX-License-Identifier: Apache-2.0
"""Quickstart: a monotone regressor in JAX / Flax NNX.

Non-decreasing in every one of its 4 inputs. Dense layers take an explicit
``rngs`` for weight initialization.
"""

from __future__ import annotations

import jax
from flax import nnx

from mononet.jax import MonoLinear, MonoResidual

rngs = nnx.Rngs(0)
model = nnx.Sequential(
    MonoLinear(4, 32, activation="elu", rngs=rngs),
    MonoResidual(32, 32, activation="elu", rngs=rngs),
    MonoLinear(32, 1, rngs=rngs),
)

y = model(jax.random.uniform(jax.random.key(0), (8, 4)))
print(y.shape)  # (8, 1) — monotone in all 4 inputs
# SPDX-License-Identifier: Apache-2.0
"""Quickstart: a monotone regressor in Keras 3.

Non-decreasing in every one of its 4 inputs. Runs on whichever backend Keras is
configured to use (``KERAS_BACKEND``); ``MonoDense`` infers the input width.
"""

from __future__ import annotations

import keras

from mononet.keras import MonoDense, MonoResidual

model = keras.Sequential(
    [
        MonoDense(32, activation="elu"),
        MonoResidual(32, activation="elu"),
        MonoDense(1),
    ]
)

y = model(keras.ops.zeros((8, 4)))
print(tuple(y.shape))  # (8, 1) — monotone in all 4 inputs

The same layers exist in all three backends — see the guide for the full mixed-feature example.

Where to next#

  • Build something — the guides: the full mixed-feature example and per-backend specifics.

  • Understand how it stays monotoneconcepts.

  • See it work / reproduce resultsbenchmarks.

  • API details — the reference.

Citation#

If you use mononet in academic work, please cite the reference paper:

@inproceedings{runje2023constrained,
  title     = {Constrained Monotonic Neural Networks},
  author    = {Runje, Davor and Shankaranarayana, Sharath M.},
  booktitle = {Proceedings of the 40th International Conference on Machine Learning},
  series    = {Proceedings of Machine Learning Research},
  volume    = {202},
  year      = {2023},
  publisher = {PMLR},
  url       = {https://proceedings.mlr.press/v202/runje23a.html},
  eprint    = {2205.11775},
  archivePrefix = {arXiv}
}

Note: confirm the exact BibTeX entry against the PMLR proceedings page before the first PyPI release — venue, volume, and URL fields are sensitive to typos.