Skip to content

laplace

laplace

Laplace distribution for ngboost-lightning.

Laplace

Laplace(params: NDArray[floating])

Bases: Distribution

Laplace distribution with log-scale parameterization.

Internal parameters are [loc, log_scale] where scale = exp(log_scale). The log-link for scale avoids constrained optimization during boosting.

The Laplace PDF is f(y) = 1/(2*b) * exp(-|y - loc| / b) where b = scale.

ATTRIBUTE DESCRIPTION
n_params

Always 2 (loc and log_scale).

loc

Location values, shape [n_samples].

TYPE: NDArray[floating]

scale

Scale (diversity) values, shape [n_samples].

TYPE: NDArray[floating]

Construct Laplace from internal parameters.

PARAMETER DESCRIPTION
params

Internal parameters, shape [n_samples, 2]. Column 0 is loc, column 1 is log(scale).

TYPE: NDArray[floating]

Source code in ngboost_lightning/distributions/laplace.py
def __init__(self, params: NDArray[np.floating]) -> None:
    """Construct Laplace from internal parameters.

    Args:
        params: Internal parameters, shape ``[n_samples, 2]``.
            Column 0 is loc, column 1 is log(scale).
    """
    self.loc: NDArray[np.floating] = params[:, 0]
    log_scale: NDArray[np.floating] = params[:, 1]
    self.scale: NDArray[np.floating] = np.exp(log_scale)
    self._dist = sp_laplace(loc=self.loc, scale=self.scale)
    self._params = params

fit staticmethod

fit(
    y: NDArray[floating],
    sample_weight: NDArray[floating] | None = None,
) -> NDArray[floating]

Estimate initial (loc, log_scale) from target data.

Uses the weighted median for loc and weighted MAD for scale, which are the MLEs for Laplace.

PARAMETER DESCRIPTION
y

Target values, shape [n_samples].

TYPE: NDArray[floating]

sample_weight

Per-sample weights, shape [n_samples].

TYPE: NDArray[floating] | None DEFAULT: None

RETURNS DESCRIPTION
NDArray[floating]

Parameter vector [loc, log(scale)], shape [2].

Source code in ngboost_lightning/distributions/laplace.py
@staticmethod
def fit(
    y: NDArray[np.floating],
    sample_weight: NDArray[np.floating] | None = None,
) -> NDArray[np.floating]:
    """Estimate initial (loc, log_scale) from target data.

    Uses the weighted median for loc and weighted MAD for scale, which
    are the MLEs for Laplace.

    Args:
        y: Target values, shape ``[n_samples]``.
        sample_weight: Per-sample weights, shape ``[n_samples]``.

    Returns:
        Parameter vector ``[loc, log(scale)]``, shape ``[2]``.
    """
    if sample_weight is None:
        loc = float(np.median(y))
    else:
        # Weighted median
        w = np.asarray(sample_weight, dtype=np.float64)
        sorted_idx = np.argsort(y)
        y_sorted = y[sorted_idx]
        w_sorted = w[sorted_idx]
        cumw = np.cumsum(w_sorted)
        half = cumw[-1] / 2.0
        loc = float(y_sorted[np.searchsorted(cumw, half)])

    mad = float(np.average(np.abs(y - loc), weights=sample_weight))
    scale = max(mad, 1e-6)
    return np.array([loc, np.log(scale)])

score

score(y: NDArray[floating]) -> NDArray[floating]

Per-sample negative log-likelihood.

NLL = log(2*scale) + |y - loc| / scale

PARAMETER DESCRIPTION
y

Observed target values, shape [n_samples].

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

NLL values, shape [n_samples].

Source code in ngboost_lightning/distributions/laplace.py
def score(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Per-sample negative log-likelihood.

    NLL = log(2*scale) + |y - loc| / scale

    Args:
        y: Observed target values, shape ``[n_samples]``.

    Returns:
        NLL values, shape ``[n_samples]``.
    """
    return -self._dist.logpdf(y)

d_score

d_score(y: NDArray[floating]) -> NDArray[floating]

Analytical gradient of NLL w.r.t. [loc, log_scale].

Derivation

NLL = log(2) + log(scale) + |y - loc| / scale

d(NLL)/d(loc) = sign(loc - y) / scale d(NLL)/d(log_scale) = 1 - |y - loc| / scale (chain rule: d(NLL)/d(log_scale) = d(NLL)/d(scale) * scale = (-|y - loc| / scale^2) * scale = 1 - |y - loc| / scale)

PARAMETER DESCRIPTION
y

Observed target values, shape [n_samples].

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

Gradient array, shape [n_samples, 2].

Source code in ngboost_lightning/distributions/laplace.py
def d_score(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Analytical gradient of NLL w.r.t. [loc, log_scale].

    Derivation:
        NLL = log(2) + log(scale) + |y - loc| / scale

        d(NLL)/d(loc) = sign(loc - y) / scale
        d(NLL)/d(log_scale) = 1 - |y - loc| / scale
            (chain rule: d(NLL)/d(log_scale)
             = d(NLL)/d(scale) * scale
             = (-|y - loc| / scale^2) * scale
             = 1 - |y - loc| / scale)

    Args:
        y: Observed target values, shape ``[n_samples]``.

    Returns:
        Gradient array, shape ``[n_samples, 2]``.
    """
    n = len(y)
    grad = np.empty((n, 2))
    grad[:, 0] = np.sign(self.loc - y) / self.scale
    grad[:, 1] = 1.0 - np.abs(y - self.loc) / self.scale
    return grad

metric

metric() -> NDArray[floating]

Fisher Information: diag(1/scale^2, 1) for each sample.

For Laplace(loc, log_scale): FI[0,0] = 1/scale^2 (from the |y-loc|/scale term) FI[1,1] = 1 (from the log(scale) + |y-loc|/scale terms) FI is diagonal because the cross-derivatives vanish in expectation (sign(y-loc) is symmetric).

RETURNS DESCRIPTION
NDArray[floating]

FI tensor, shape [n_samples, 2, 2].

Source code in ngboost_lightning/distributions/laplace.py
def metric(self) -> NDArray[np.floating]:
    """Fisher Information: diag(1/scale^2, 1) for each sample.

    For Laplace(loc, log_scale):
        FI[0,0] = 1/scale^2 (from the |y-loc|/scale term)
        FI[1,1] = 1 (from the log(scale) + |y-loc|/scale terms)
        FI is diagonal because the cross-derivatives vanish in
        expectation (sign(y-loc) is symmetric).

    Returns:
        FI tensor, shape ``[n_samples, 2, 2]``.
    """
    n = len(self.loc)
    fi = np.zeros((n, 2, 2))
    fi[:, 0, 0] = 1.0 / self.scale**2
    fi[:, 1, 1] = 1.0
    return fi

natural_gradient

natural_gradient(y: NDArray[floating]) -> NDArray[floating]

Natural gradient via diagonal Fisher (fast path).

Since FI is diagonal

nat_grad[:, 0] = d_score[:, 0] * scale^2 nat_grad[:, 1] = d_score[:, 1] / 1.0

PARAMETER DESCRIPTION
y

Observed target values, shape [n_samples].

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

Natural gradient, shape [n_samples, 2].

Source code in ngboost_lightning/distributions/laplace.py
def natural_gradient(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Natural gradient via diagonal Fisher (fast path).

    Since FI is diagonal:
        nat_grad[:, 0] = d_score[:, 0] * scale^2
        nat_grad[:, 1] = d_score[:, 1] / 1.0

    Args:
        y: Observed target values, shape ``[n_samples]``.

    Returns:
        Natural gradient, shape ``[n_samples, 2]``.
    """
    grad = self.d_score(y)
    nat_grad = np.empty_like(grad)
    nat_grad[:, 0] = grad[:, 0] * self.scale**2
    nat_grad[:, 1] = grad[:, 1]
    return nat_grad

mean

mean() -> NDArray[floating]

Conditional mean (= loc for Laplace).

RETURNS DESCRIPTION
NDArray[floating]

Mean values, shape [n_samples].

Source code in ngboost_lightning/distributions/laplace.py
def mean(self) -> NDArray[np.floating]:
    """Conditional mean (= loc for Laplace).

    Returns:
        Mean values, shape ``[n_samples]``.
    """
    return self.loc

sample

sample(n: int) -> NDArray[floating]

Draw n samples per distribution instance.

PARAMETER DESCRIPTION
n

Number of samples to draw.

TYPE: int

RETURNS DESCRIPTION
NDArray[floating]

Samples, shape [n, n_samples].

Source code in ngboost_lightning/distributions/laplace.py
def sample(self, n: int) -> NDArray[np.floating]:
    """Draw n samples per distribution instance.

    Args:
        n: Number of samples to draw.

    Returns:
        Samples, shape ``[n, n_samples]``.
    """
    return self._dist.rvs(size=(n, len(self)))

cdf

cdf(y: NDArray[floating]) -> NDArray[floating]

Cumulative distribution function.

PARAMETER DESCRIPTION
y

Values at which to evaluate the CDF.

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

CDF values, same shape as y.

Source code in ngboost_lightning/distributions/laplace.py
def cdf(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Cumulative distribution function.

    Args:
        y: Values at which to evaluate the CDF.

    Returns:
        CDF values, same shape as ``y``.
    """
    return self._dist.cdf(y)

ppf

ppf(q: NDArray[floating]) -> NDArray[floating]

Percent point function (inverse CDF / quantile function).

PARAMETER DESCRIPTION
q

Quantiles, values in [0, 1].

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

Values at the given quantiles, same shape as q.

Source code in ngboost_lightning/distributions/laplace.py
def ppf(self, q: NDArray[np.floating]) -> NDArray[np.floating]:
    """Percent point function (inverse CDF / quantile function).

    Args:
        q: Quantiles, values in [0, 1].

    Returns:
        Values at the given quantiles, same shape as ``q``.
    """
    return self._dist.ppf(q)

logpdf

logpdf(y: NDArray[floating]) -> NDArray[floating]

Log probability density function.

PARAMETER DESCRIPTION
y

Values at which to evaluate.

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

Log-density values, same shape as y.

Source code in ngboost_lightning/distributions/laplace.py
def logpdf(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Log probability density function.

    Args:
        y: Values at which to evaluate.

    Returns:
        Log-density values, same shape as ``y``.
    """
    return self._dist.logpdf(y)

crps_score

crps_score(y: NDArray[floating]) -> NDArray[floating]

Per-sample CRPS for Laplace.

Closed form

CRPS = |y - loc| + scale * exp(-|y - loc|/scale) - 0.75*scale

PARAMETER DESCRIPTION
y

Observed target values, shape [n_samples].

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

CRPS values, shape [n_samples].

Source code in ngboost_lightning/distributions/laplace.py
def crps_score(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Per-sample CRPS for Laplace.

    Closed form:
        ``CRPS = |y - loc| + scale * exp(-|y - loc|/scale) - 0.75*scale``

    Args:
        y: Observed target values, shape ``[n_samples]``.

    Returns:
        CRPS values, shape ``[n_samples]``.
    """
    abs_diff = np.abs(y - self.loc)
    result: NDArray[np.floating] = (
        abs_diff + self.scale * np.exp(-abs_diff / self.scale) - 0.75 * self.scale
    )
    return result

crps_d_score

crps_d_score(y: NDArray[floating]) -> NDArray[floating]

Gradient of CRPS w.r.t. [loc, log_scale].

Derivation (let a = |y - loc|, b = scale): CRPS = a + bexp(-a/b) - 0.75b

d(CRPS)/d(loc) = sign(loc - y) * (1 - exp(-a/b))
d(CRPS)/d(log_scale) = scale * (exp(-a/b)*(1 + a/b) - 0.75)
PARAMETER DESCRIPTION
y

Observed target values, shape [n_samples].

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

Gradient array, shape [n_samples, 2].

Source code in ngboost_lightning/distributions/laplace.py
def crps_d_score(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Gradient of CRPS w.r.t. [loc, log_scale].

    Derivation (let a = |y - loc|, b = scale):
        CRPS = a + b*exp(-a/b) - 0.75*b

        d(CRPS)/d(loc) = sign(loc - y) * (1 - exp(-a/b))
        d(CRPS)/d(log_scale) = scale * (exp(-a/b)*(1 + a/b) - 0.75)

    Args:
        y: Observed target values, shape ``[n_samples]``.

    Returns:
        Gradient array, shape ``[n_samples, 2]``.
    """
    abs_diff = np.abs(y - self.loc)
    ratio = abs_diff / self.scale
    exp_neg = np.exp(-ratio)

    n = len(y)
    grad = np.empty((n, 2))
    grad[:, 0] = np.sign(self.loc - y) * (1.0 - exp_neg)
    grad[:, 1] = self.scale * (exp_neg * (1.0 + ratio) - 0.75)
    return grad

crps_metric

crps_metric() -> NDArray[floating]

Riemannian metric for CRPS natural gradient.

For Laplace(loc, log_scale), the CRPS metric is diagonal: met[0,0] = 0.5 (E[1 - exp(-|y-loc|/b)]^2 under Laplace) met[1,1] = 0.25 * scale^2 (from E[(exp(-a/b)*(1+a/b) - 0.75)^2] * scale^2)

Derived from the expected outer product of crps_d_score.

RETURNS DESCRIPTION
NDArray[floating]

Metric tensor, shape [n_samples, 2, 2].

Source code in ngboost_lightning/distributions/laplace.py
def crps_metric(self) -> NDArray[np.floating]:
    """Riemannian metric for CRPS natural gradient.

    For Laplace(loc, log_scale), the CRPS metric is diagonal:
        met[0,0] = 0.5 (E[1 - exp(-|y-loc|/b)]^2 under Laplace)
        met[1,1] = 0.25 * scale^2
            (from E[(exp(-a/b)*(1+a/b) - 0.75)^2] * scale^2)

    Derived from the expected outer product of crps_d_score.

    Returns:
        Metric tensor, shape ``[n_samples, 2, 2]``.
    """
    n = len(self.loc)
    met = np.zeros((n, 2, 2))
    met[:, 0, 0] = 0.5
    met[:, 1, 1] = 0.25 * self.scale**2
    return met

crps_natural_gradient

crps_natural_gradient(
    y: NDArray[floating],
) -> NDArray[floating]

Natural gradient under CRPS metric (fast diagonal path).

PARAMETER DESCRIPTION
y

Observed target values, shape [n_samples].

TYPE: NDArray[floating]

RETURNS DESCRIPTION
NDArray[floating]

Natural gradient, shape [n_samples, 2].

Source code in ngboost_lightning/distributions/laplace.py
def crps_natural_gradient(self, y: NDArray[np.floating]) -> NDArray[np.floating]:
    """Natural gradient under CRPS metric (fast diagonal path).

    Args:
        y: Observed target values, shape ``[n_samples]``.

    Returns:
        Natural gradient, shape ``[n_samples, 2]``.
    """
    grad = self.crps_d_score(y)
    nat_grad = np.empty_like(grad)
    nat_grad[:, 0] = grad[:, 0] / 0.5
    nat_grad[:, 1] = grad[:, 1] / (0.25 * self.scale**2)
    return nat_grad