Skip to main content

insurance-anam

Tests

Actuarial Neural Additive Model for insurance pricing. A production-quality Python library implementing the ANAM architecture from Laub, Pho, Wong (2025).


The problem

GLMs are interpretable and well-understood by actuaries and regulators. Neural networks fit better but are black boxes. EBMs and GAMs sit in between, but none of them natively support:

  • Poisson/Tweedie/Gamma distributional losses (not MSE)
  • Mathematically guaranteed monotonicity constraints
  • Exposure weights handled correctly at the loss level
  • Output that reads like a GLM factor table

ANAM fills this gap. It's a neural network that an actuary can present to Lloyd's, the PRA, or a reinsurer.


What ANAM is

One MLP subnetwork per feature. The model computes:

eta = bias + f_1(x_1) + f_2(x_2) + ... + f_p(x_p) [+ interactions]
mu  = exp(eta + log(exposure))
y   ~ Poisson(mu)

Because it's purely additive, every feature's contribution is visible in isolation — exactly like a GLM marginal effect plot. Because it's a neural network, the shape functions can capture non-linearity that a GLM would need polynomial or spline terms to approximate.

Actuarial-specific features:

  • Poisson, Tweedie, and Gamma deviance losses
  • Monotonicity constraints via Dykstra's projection algorithm (mathematically guaranteed, not post-hoc)
  • Smoothness regularisation (second-order difference penalty)
  • Exposure offset handled as log(exposure) in the linear predictor — same as a GLM offset
  • Shape function export as Polars DataFrames for regulatory documentation
  • sklearn-compatible API (fit, predict, score)

Install

pip install insurance-anam

Requires Python >= 3.10, PyTorch >= 2.0.


Quick start

from insurance_anam import ANAM

model = ANAM(
    loss="poisson",
    monotone_increasing=["vehicle_age"],
    monotone_decreasing=["ncd_steps"],
    categorical_features=["region", "vehicle_type"],
    hidden_sizes=[64, 32],
    n_epochs=100,
    verbose=10,
)

model.fit(X_train, y_train, sample_weight=exposure_train)

y_pred = model.predict(X_test, exposure=exposure_test)

# Shape functions (GLM-style marginal effects)
shapes = model.shape_functions()
shapes["vehicle_age"].plot()

# Export as Polars DataFrame for regulatory review
from insurance_anam import shapes_to_relativity_table
rel_table = shapes_to_relativity_table(shapes)

Monotonicity constraints

Monotonicity is enforced by the Dykstra projection algorithm: after each gradient step, the weight matrices in monotone-constrained subnetworks are clamped to the non-negative (or non-positive) orthant. For a ReLU network, this guarantees a non-decreasing (or non-increasing) output — not as a soft penalty, but as a hard constraint.

model = ANAM(
    monotone_increasing=["vehicle_age", "bonus_malus"],
    monotone_decreasing=["ncd_steps", "years_no_claims"],
    loss="poisson",
)

Constraint is verified: after project_monotone_weights(), the output is guaranteed monotone for any input in the training range.


Loss functions

Loss Distribution Use case
"poisson" Poisson Claim frequency
"tweedie" Tweedie (power p) Pure premium (frequency × severity)
"gamma" Gamma Claim severity (positive, right-skewed)
"mse" Gaussian Continuous targets

Set tweedie_p (default 1.5) for the compound Poisson-Gamma mix. Values near 1 are Poisson-like; values near 2 are Gamma-like.


Shape function export

shapes = model.shape_functions(n_points=200)

# As a relativity table (GLM-equivalent multiplicative factors)
sf = shapes["driver_age"]
rel_df = sf.to_relativities(base_level=40.0)  # base = 40-year-old driver

# As JSON for documentation systems
json_str = sf.to_json()

# Polars DataFrame
df = sf.to_polars()

Categorical features export as bar charts and category-indexed DataFrames.


Interaction terms

from insurance_anam import ANAM, InteractionConfig

model = ANAM(
    interaction_pairs=[
        ("driver_age", "vehicle_age"),
        ("region", "vehicle_type"),
    ],
    ...
)

Interaction pairs can be screened from data:

from insurance_anam import select_interactions_correlation, select_interactions_residual

# Correlation-based screening (pre-fit)
pairs = select_interactions_correlation(X_train, feature_names, threshold=0.3, top_k=5)

# Residual-based screening (post-fit)
y_resid = y_train - model.predict(X_train)
pairs = select_interactions_residual(X_train, y_resid, feature_names, top_k=5)

Comparing to a GLM

from insurance_anam import compare_shapes_to_glm

# GLM log-relativities from your existing production model
glm_coefficients = {
    "driver_age": {"25.0": 0.45, "40.0": 0.0, "65.0": 0.22},
    "region": {"0": 0.0, "1": 0.18, "2": -0.09, "3": 0.28},
}

comparison = compare_shapes_to_glm(shapes, glm_coefficients)
print(comparison)

Feature importance

fi = model.feature_importance()
# Returns Polars DataFrame sorted by importance descending

Importance is the L2 norm of the subnetwork weights. A quick heuristic for feature selection — not a replacement for permutation importance.


Architecture

insurance_anam/
├── feature_network.py    — FeatureNetwork, CategoricalFeatureNetwork
├── interaction_network.py — InteractionNetwork (pairwise)
├── model.py              — ANAMModel (orchestrator)
├── losses.py             — Poisson, Tweedie, Gamma, Bernoulli deviance + penalties
├── trainer.py            — Training loop with early stopping + monotonicity projection
├── shapes.py             — ShapeFunction, extract_shape_functions, plot_all_shapes
├── api.py                — ANAM (sklearn wrapper)
└── utils.py              — Interaction selection, GLM comparison, StandardScaler

Databricks notebook

A full worked example with synthetic data, shape function comparison to ground truth, and relativity export is in notebooks/anam_demo.py.


Citation

@article{laub2025anam,
  title   = {An Interpretable Deep Learning Model for General Insurance Pricing},
  author  = {Laub, Patrick J. and Pho, Tu and Wong, Bernard},
  journal = {arXiv preprint arXiv:2509.08467},
  year    = {2025}
}

License

MIT. Built by Burning Cost.

Metadata

Release files for insurance-anam 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for insurance-anam 0.1.1
File Size Uploaded
insurance_anam-0.1.1.tar.gz 158.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for insurance-anam 0.1.1
File Interpreter ABI Platform
insurance_anam-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 193.0 kB

Release files / insurance_anam-0.1.1.tar.gz

Download URL insurance_anam-0.1.1.tar.gz
Size 158.7 kB
Tags Source
SHA-256 checksum
How to use checksums
843fd5a07306272ef100eb57c0f9f4679d37fcce3c431bdb71344b4568698718
BLAKE2b-256 checksum
How to use checksums
54aa39240e850befdb4951fdac1b002fa39e788cbbd7b46d85a6e1bea7aa8872
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / insurance_anam-0.1.1-py3-none-any.whl

Download URL insurance_anam-0.1.1-py3-none-any.whl
Size 34.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bd9d651415b27f60e3ac75803dd193b8762c1bfbf2ce9e9187808736fa83c1fd
BLAKE2b-256 checksum
How to use checksums
ab168173d7bca0a56b33680e009e17cf03cc570506a458ae2ffa8377c3a20ca1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page