Source code for maldideepkit.uncertainty._result
"""Container dataclass and shared helpers for uncertainty estimators.
The :class:`UncertaintyResult` dataclass is the common return value of
every estimator in :mod:`maldideepkit.uncertainty`. The two helpers
:func:`_softmax` and :func:`_entropy` are reused across the submodules
to keep the numerical conventions identical between methods.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
import numpy as np
def _softmax(logits: np.ndarray) -> np.ndarray:
"""Return numerically-stable row-wise softmax of ``logits``.
Parameters
----------
logits : ndarray of shape (..., n_classes)
Unnormalised log-probabilities. The last axis is the class axis.
Returns
-------
ndarray
Same shape as ``logits``; rows sum to 1 along the class axis.
"""
logits = np.asarray(logits, dtype=np.float64)
shifted = logits - logits.max(axis=-1, keepdims=True)
exp = np.exp(shifted)
return exp / exp.sum(axis=-1, keepdims=True)
def _entropy(proba: np.ndarray) -> np.ndarray:
"""Return per-row Shannon entropy of ``proba`` normalised to ``[0, 1]``.
Divides by ``log(n_classes)`` so the value is independent of the
cardinality of the class set; ``0`` corresponds to a one-hot vector
and ``1`` to a uniform distribution over classes.
Parameters
----------
proba : ndarray of shape (..., n_classes)
Probabilities that sum to 1 along the last axis.
Returns
-------
ndarray of shape (...,)
Entropy of each row in ``[0, 1]``.
"""
proba = np.asarray(proba, dtype=np.float64)
n_classes = int(proba.shape[-1])
if n_classes < 2:
return np.zeros(proba.shape[:-1], dtype=np.float64)
safe = np.clip(proba, 1e-12, 1.0)
h = -np.sum(safe * np.log(safe), axis=-1)
return (h / np.log(n_classes)).astype(np.float64, copy=False)
[docs]
@dataclass(frozen=True)
class UncertaintyResult:
"""Container for the output of a :class:`BaseUncertaintyEstimator`.
Attributes
----------
predictions : ndarray of shape (n_samples,)
Hard labels drawn from the classifier's :attr:`classes_`.
proba_mean : ndarray of shape (n_samples, n_classes)
Mean softmax probabilities. For non-Bayesian methods this is
simply the classifier's :meth:`predict_proba` output.
uncertainty : ndarray of shape (n_samples,)
Scalar per-sample uncertainty in ``[0, 1]`` where possible. The
precise semantics are method-specific and documented on the
producing class.
epistemic : ndarray of shape (n_samples,) or None
Model-uncertainty component, when the method decomposes total
uncertainty into epistemic and aleatoric parts. ``None`` for
methods that do not decompose.
aleatoric : ndarray of shape (n_samples,) or None
Data-uncertainty component, paired with :attr:`epistemic`.
``None`` for methods that do not decompose.
method : str
Name of the method that produced this result (e.g.
``"mc_dropout"``, ``"laplace"``, ``"conformal"``).
metadata : dict
Method-specific extras (per-pass samples for MC Dropout,
prediction sets for conformal, predictive variance for
Laplace, ...).
"""
predictions: np.ndarray
proba_mean: np.ndarray
uncertainty: np.ndarray
epistemic: np.ndarray | None
aleatoric: np.ndarray | None
method: str
metadata: dict[str, Any] = field(default_factory=dict)
__all__ = ["UncertaintyResult", "_entropy", "_softmax"]