Skip to content

Noise estimation

Source: examples/noise_estimation.py

GaussianNoiseLikelihood wraps a forward model and observed data into a log-likelihood function, handling measurement noise in three ways.


Fixed noise

Use when σ is known from sensor specs or prior calibration:

import numpy as np
import mcmckit as mc

def forward_model(theta):
    k, c = theta
    return k * np.exp(-c * x_input)

ll = mc.GaussianNoiseLikelihood(forward_model, y_obs, noise_std=0.05)

problem = mc.Problem(prior=log_prior, likelihood=ll,
                     param_names=["k", "c"])

Estimated noise

When σ is unknown, treat log_sigma as the last element of theta:

ll = mc.GaussianNoiseLikelihood(forward_model, y_obs)   # noise_std=None

problem = mc.Problem(prior=log_prior_with_sigma, likelihood=ll,
                     param_names=["k", "c", "log_sigma"])

# theta = [k, c, log_sigma] — all sampled jointly
result = mc.DRAM(n_samples=10_000, initial_cov=np.diag([0.1, 0.02, 0.1])).run(
    problem, x0=[2.0, 0.3, np.log(0.1)])

sigma_samples = np.exp(result.samples[:, 2])
print(f"sigma posterior mean: {sigma_samples.mean():.4f}")

The prior on log_sigma must be supplied by the user. A weakly informative choice is a half-normal on σ:

def log_prior_with_sigma(theta):
    k, c, log_s = theta
    if k <= 0 or c <= 0:
        return -np.inf
    sigma = np.exp(log_s)
    return -0.5 * sigma**2    # HalfNormal(1) on sigma

Marginalised noise

Integrates σ² out analytically using an Inverse-Gamma prior. No noise parameter in theta — works for any nonlinear forward model:

ll = mc.GaussianNoiseLikelihood(
    forward_model, y_obs,
    marginalise_noise=True,
    inv_gamma_alpha=2.0,
    inv_gamma_beta=0.05**2,     # encodes prior belief σ ≈ 0.05
)

problem = mc.Problem(prior=log_prior, likelihood=ll,
                     param_names=["k", "c"])

result = mc.DRAM(n_samples=10_000, initial_cov=np.diag([0.1, 0.02])).run(
    problem, x0=[2.0, 0.3])

# posterior mean of sigma at the MAP
sigma_est = ll.posterior_sigma(result.mean())
print(f"sigma estimate: {float(sigma_est):.4f}")

Per-channel noise

For multiple measurement types with different noise levels:

# y_obs = [omega1_meas × N_repeat, omega2_meas × N_repeat]
groups = [
    np.arange(0, N_repeat),
    np.arange(N_repeat, 2 * N_repeat),
]

ll = mc.GaussianNoiseLikelihood(
    forward_model, y_obs,
    groups=groups,
    marginalise_noise=True,
    inv_gamma_alpha=[2.0, 2.0],
    inv_gamma_beta=[0.08**2, 0.03**2],   # different prior per channel
)

sigma_est = ll.posterior_sigma(result.mean())  # shape (2,)
print(f"sigma per channel: {sigma_est}")

See Structural identification for a complete engineering example.