The MuLaConf Package#
Package Wrapper#
- class mulaconf.icp_wrapper.ICPWrapper(classification_strategy, measure: str = 'mahalanobis', weight_hamming: float = 0.0, weight_cardinality: float = 0.0, device: str | torch.device = 'cpu')[source]#
A wrapper for Inductive Conformal Prediction with Structural Penalties (Scikit-Learn compatible).
This class manages the lifecycle of the underlying multi-label classifier and the conformal predictor. It handles model training, calibration, and the efficient integration of the PyTorch mathematical engine without unnecessary retraining.
Note
Switching Strategies: You can switch the classification strategy or update its parameters. If the wrapper detects a change (via fingerprinting) during calibrate, it will automatically retrain the new model on the cached proper-training data.
Note
On-the-Fly Updates: The wrapper itself acts strictly as a bridge. If you want to perform lazy-evaluation updates on the distance measure or penalty weights without passing calibration data again, you can do so directly via wrapper (e.g.,
wrapper.measure='norm',wrapper.weight_hamming=1.0andwrapper.weight_cardinality=0.5).- Parameters:
classification_strategy (sklearn.base.BaseEstimator) – The underlying multi-label classification model (e.g., RandomForest, ClassifierChain). Must support
fitandpredict_proba.measure (str, optional, default='mahalanobis') – The distance metric used to score predictions. Supported options:
'mahalanobis'(accounts for correlations) or'norm'(standard Euclidean).weight_hamming (float, optional, default=0.0) – Initial weight for the Hamming distance penalty.
weight_cardinality (float, optional, default=0.0) – Initial weight for the Cardinality penalty.
device (str or torch.device, optional, default='cpu') – The device to use for tensor computations (
'cpu'or'cuda').
- fit(train_features: torch.Tensor | numpy.ndarray | pandas.DataFrame | pandas.Series | List | float, train_labels: torch.Tensor | numpy.ndarray | pandas.DataFrame | pandas.Series | List | float, **kwargs)[source]#
Fits the underlying multi-label classification model.
This method trains the
classification_strategyon the providedfeaturesandlabels, and caches the training data to enable auto-retraining if hyper-parameters change.- Parameters:
train_features (array-like) – The training features. Shape: (n_samples, w_features).
train_labels (array-like) – The training labels (binary multi-hot). Shape: (n_samples, c_classes).
**kwargs (dict) – Optional arguments to pass to the classifier’s parameters or
fitmethod.
- Returns:
self – The fitted wrapper instance.
- Return type:
Example
>>> import numpy as np >>> from sklearn.ensemble import RandomForestClassifier >>> from sklearn.multioutput import MultiOutputClassifier >>> >>> # 1. Generate Dummy Training Data >>> # 100 samples, 5 features, 3 target labels >>> X_train = np.random.rand(100, 5) >>> y_train = np.random.randint(0, 2, (100, 3)) >>> >>> # 2. Initialize Wrapper >>> base_model = MultiOutputClassifier(RandomForestClassifier()) >>> wrapper = ICPWrapper(base_model) >>> >>> # 3. Fit the Model (Standard) >>> wrapper.fit(X_train, y_train)
Note
Change hyperparameters : You can update the classifier’s hyperparameters during the fit call. Note the
estimator__prefix for wrapped sklearn models.>>> args = {'estimator__n_neighbors': 5} >>> wrapper.fit(X_train, y_train, **args)
- calibrate(calib_features: torch.Tensor | numpy.ndarray | pandas.DataFrame | pandas.Series | List | float = None, calib_labels: torch.Tensor | numpy.ndarray | pandas.DataFrame | pandas.Series | List | float = None)[source]#
Calibrates the conformal predictor using a dedicated calibration set.
This step calculates the nonconformity scores and determines the thresholds required to guarantee coverage.
Note
If called without arguments after an initial calibration, it will manually apply any pending updates (measure or penalty weights) and recalibrate using the cached data.
- Parameters:
calib_features (array-like) – Features of the calibration set. Shape: (q_samples, w_features).
calib_labels (array-like) – Labels of the calibration set. Shape: (q_samples, c_classes).
- Returns:
self – The calibrated wrapper instance.
- Return type:
- Raises:
RuntimeError – If calibration features and labels are not provided.
RuntimeError – If
fit()has not been called before running calibration.RuntimeError – If retraining the underlying classifier fails.
Example
>>> # 1. Initialize & Fit (See `fit()` function documentation for details) >>> >>> # 2. Generate Dummy Calibration Data >>> # 100 samples, 5 features, 3 target labels >>> X_calib = np.random.rand(100, 5) >>> y_calib = np.random.randint(0, 2, (100, 3)) >>> >>> # Calibrate >>> wrapper.calibrate(X_calib, y_calib)
Note
Strategy Switching: Change the underlying model, retrain and recalibrate automatically.
>>> from sklearn.neighbors import KNeighborsClassifier >>> from sklearn.multioutput import ClassifierChain >>> >>> wrapper.strategy = ClassifierChain(KNeighborsClassifier(n_neighbors=5)) >>> >>> # Calling `calibrate()` again detects the change and retrains automatically >>> wrapper.calibrate(X_calib, y_calib)
Note
On-the-fly Updates: You can easily update the distance measure and penalty weights after the calibration process without passing your data again. Calling calibrate() without arguments will automatically apply all pending updates simultaneously using the cached calibration data.
>>> # Optional: The `calibrate()` method recalculates the covariance matrix and scores after a measure update. >>> wrapper.measure = 'norm' >>> wrapper.calibrate()
>>> # Optional: The `calibrate()` method recalculates calibration scores after penalty weight update. >>> wrapper.weight_hamming = 1.0 >>> wrapper.weight_cardinality = 0.5 >>> wrapper.calibrate()
>>> # Optional: The `calibrate()` method applies both distance measure and penalty weight updates at once. >>> wrapper.measure = 'norm' >>> wrapper.weight_hamming = 1.0 >>> wrapper.weight_cardinality = 0.5 >>> wrapper.calibrate()
- predict(test_features: torch.Tensor | numpy.ndarray | pandas.DataFrame | pandas.Series | List | float, non_empty_prediction_regions: bool = True) PredictionRegions[source]#
Generates conformal prediction regions for the input features.
This method calculates p-values for all test samples based on the calibrated scores.
- Parameters:
test_features (array-like) – The test features. Shape: (t_samples, w_features).
non_empty_prediction_regions (bool, optional) – If True (default), the combination with the highest p-value is returned to ensure non-empty predictions.
- Returns:
A callable object that returns prediction regions. You must call this object with a specific
significance_levelto retrieve the final prediction sets.- Return type:
- Raises:
RuntimeError – If
calibrate()has not been called, and the ICP engine does not exist.RuntimeError – If the classifier has not been fitted yet (
fit()must be called first).RuntimeError – If the classifier model changed. Run the fit and calibration procedure.
Example
>>> # ... Assume wrapper is already fitted and calibrated (see fit() for details) ... >>> X_test = np.random.rand(10, 5) >>> >>> # 1. Get prediction regions object with non empty prediction regions >>> prediction_region_obj = wrapper.predict(X_test) >>> >>> # 2. Extract Prediction Sets (e.g., at 10% significance / 90% confidence) >>> # Returns a list of Tensors, where each Tensor contains the indices of predicted labels. >>> prediction_sets = prediction_region_obj(significance_level=0.1)
Note
Equivalent Syntax: Because the
predictmethod returns a callablePredictionRegionsobject, you can chain the operations to evaluate the test features and extract prediction sets in a single line of code:>>> prediction_sets = wrapper.predict(test_features)(significance_level=0.1)
Note
On-the-fly Update: You can update the distance measure and penalty weights on the fly and predict again immediately.
>>> wrapper.measure = 'norm' >>> wrapper.weight_hamming = 2.0 >>> wrapper.weight_cardinality = 1.5 >>> updated_obj = wrapper.predict(X_test) >>> updated_sets = updated_obj(significance_level=0.1)
Inductive Conformal Predictor#
- class mulaconf.icp_predictor.InductiveConformalPredictor(predicted_probabilities: torch.Tensor | numpy.ndarray | list | pandas.DataFrame | pandas.Series, true_labels: torch.Tensor | numpy.ndarray | list | pandas.DataFrame | pandas.Series, measure: str = 'mahalanobis', weight_hamming: float = 0.0, weight_cardinality: float = 0.0, device: str | torch.device = 'cpu')[source]#
Inductive Conformal Predictor with Structural Penalties.
This class implements Inductive Conformal Prediction (ICP) for multi-label classification, extended with structural penalties (Hamming and Cardinality). It uses a generalized distance metric (e.g., Mahalanobis or Euclidean Norm) in the error vector space to score predictions. Additionally, it allows for on-the-fly updating of the distance measure and penalty weights without retraining the underlying model or requiring the calibration data to be passed again.
Note
The predictor calculates and caches the structural penalty vectors for all possible label combinations during initialization.
Note
On-the-Fly Updates: You can update the distance measure (
'norm'or'mahalanobis'),weight_hamming, andweight_cardinalityat any time after the calibration process.The predictor utilizes lazy evaluation for automatic recalibration. This means you do not need to manually pass your calibration data again or explicitly call
calibrate(). Simply assign new values to the properties (e.g.,icp.measure = 'norm',icp.weight_hamming = 1.0,icp.weight_cardinality = 0.5) and immediately callpredict(). It will automatically reform the underlying covariance matrix and recalibrate the scores on the fly before generating predictions.Note
Memory Management: This class uses optimized batching, tensor expansion (compressed loading) to prevent GPU/CPU memory overflow when processing exponential powerset combinations. Even with these optimizations, Powerset Scoring prediction scales at O(2^C), where C is the number of labels. For standard systems with 16GB of RAM/VRAM, we recommend limiting tasks to a maximum of ~20 labels.
The default batching limits are approximated based on PyTorch’s
float32data type (which consumes 4 bytes per element) and the overhead required to hold multiple massive intermediate tensors simultaneously in memory during calculation.Users can manually tune the hardware limits by modifying the module-level configuration variables (located in
constants.py) to optimize for their specific CPU/GPU memory constraints:_CPU_MAX_COMBINATIONS: Caps the maximum number of combinations processed at once during heavy matrix math on the CPU to protect system RAM (default: 2,000,000)._GPU_MAX_COMBINATIONS: Caps the maximum number of combinations processed at once during heavy matrix math on the GPU to protect VRAM (default: 5,000,000)._REGION_BATCH_SIZE: Caps memory usage (System RAM) when extracting the final prediction sets. Because this phase relies on lightweight boolean filtering rather than heavy matrix operations, this threshold is safely set much higher (default: 100,000,000)._EMPTY_CUDA_CACHE: Controls whether the engine aggressively clears the CUDA memory cache after heavy tensor operations. Keep this set toTrue(default) to prevent VRAM fragmentation and Out-Of-Memory crashes. Set toFalseonly for strict performance benchmarking to bypass the ~300ms OS-level synchronization delay.Increasing these values speeds up the time required to generate predictions, but risks memory overflow and system instability. Decreasing these values results in slower predictions but guarantees safety from system crashes. In the documentation of
constants.py, you can find a hardware cheat sheet for memory requirements.- Parameters:
measure (str, optional, default='mahalanobis') – The distance metric used to score predictions. Supported options: ‘mahalanobis’ (accounts for correlations) or ‘norm’ (standard Euclidean).
predicted_probabilities (Union[torch.Tensor, np.ndarray, list, pd.DataFrame, pd.Series]) – The predicted probabilities for the proper training set. Shape: (n_samples, c_classes).
true_labels (Union[torch.Tensor, np.ndarray, list, pd.DataFrame, pd.Series]) – The ground truth binary labels for the proper training set. Shape: (n_samples, c_classes).
weight_hamming (float, optional, default=0.0) – The weight for the Hamming distance penalty. Higher values penalize predictions that are structurally different from observed training labels.
weight_cardinality (float, optional, default=0.0) – The weight for the Cardinality penalty. Higher values penalize prediction set sizes that are infrequent in the training data.
device (str or torch.device, optional, default='cpu') – The device to use for computations (‘cpu’ or ‘cuda’).
Example
>>> import torch >>> >>> # 1. Generate dummy training data (500 samples, 5 classes) >>> train_probs = torch.rand(500, 5) >>> train_labels = torch.randint(0, 2, (500, 5)).float() >>> >>> # 2. Initialize the predictor >>> icp = InductiveConformalPredictor( ... predicted_probabilities=train_probs, ... true_labels=train_labels, ... measure='mahalanobis', ... weight_hamming=2.0, ... weight_cardinality=1.5, ... )
- calibrate(probabilities: torch.Tensor | numpy.ndarray | list | pandas.DataFrame | pandas.Series = None, labels: torch.Tensor | numpy.ndarray | list | pandas.DataFrame | pandas.Series = None)#
Calibrates the predictor using a dedicated calibration set.
This method computes nonconformity scores for the calibration data and sorts them to determine thresholds for future predictions.
Note
If called without arguments, it recalculates the calibration scores using the current distance measure and set penalty weights on the existing calibration data.
- Parameters:
probabilities (Union[torch.Tensor, np.ndarray, list, pd.DataFrame, pd.Series]) – Predicted probabilities for the calibration set. Shape: (q_samples, c_classes).
labels (Union[torch.Tensor, np.ndarray, list, pd.DataFrame, pd.Series]) – Ground truth labels for the calibration set. Shape: (q_samples, c_classes).
- Returns:
self – The initialized and calibrated predictor object.
- Return type:
- Raises:
RuntimeError – If one of
probabilitiesorlabelsis provided but not the other.RuntimeError – If the provided calibration set is empty.
RuntimeError – If
labelsshape does not match the number of classes.RuntimeError – If
probabilitiesshape does not match the number of classes.
Example
>>> # 1. Generate dummy calibration data (100 samples, 5 classes) >>> calib_probs = torch.rand(100, 5) >>> calib_labels = torch.randint(0, 2, (100, 5)).float() >>> >>> # 2. Calibrate >>> icp.calibrate(calib_probs, calib_labels)
Note
Update the distance measure and penalty weights before calling
calibrate().>>> # Optional: The `calibrate()` method recalculates the distance matrix and scores after a measure update. >>> icp.measure = 'norm' >>> icp.calibrate()
>>> # Optional: The `calibrate()` method recalculates calibration scores after penalty weight update. >>> icp.weight_hamming = 1.0 >>> icp.weight_cardinality = 0.5 >>> icp.calibrate()
>>> # Optional: The `calibrate()` method applies both distance measure and penalty weight updates at once. >>> icp.measure = 'norm' >>> icp.weight_hamming = 1.0 >>> icp.weight_cardinality = 0.5 >>> icp.calibrate()
- predict(probabilities: torch.Tensor | numpy.ndarray | list | pandas.DataFrame | pandas.Series, non_empty_prediction_regions: bool = True) PredictionRegions#
Computes p-values for the test samples.
This method calculates the p-value for every possible label combination based on the calibrated scores.
- Parameters:
probabilities (Union[torch.Tensor, np.ndarray, list, pd.DataFrame, pd.Series]) – Predicted probabilities for the test set. Shape: (t_samples, c_classes).
non_empty_prediction_regions (bool, optional) – If True (default), the combination with the highest p-value is returned to ensure non-empty predictions.
- Returns:
A callable object that wraps the p-values and combinations. You must call this object with a significance level to get the actual prediction sets.
- Return type:
- Raises:
RuntimeError – If a distance measure was changed, but no calibration data is cached to perform the auto-recalculation.
RuntimeError – If
calibrate()has not been called beforepredict().RuntimeError – If
probabilitiesshape does not match the number of classes.
Example
>>> # Generate dummy test probabilities >>> test_probs = torch.rand(30, 5) >>> >>> # Get prediction regions object with non empty prediction regions >>> prediction_obj = icp.predict(test_probs) >>> >>> # Extract prediction sets for significance level 0.1 (90% confidence) >>> prediction_sets = prediction_obj(significance_level=0.1)
Note
Equivalent Syntax: Because the predictor itself is callable and it returns a callable
PredictionRegionsobject, you can chain the operations to extract prediction sets in a single line of code:>>> prediction_sets = icp.predict(test_probs)(significance_level=0.1)
Note
Update distance measure and penalty weights on-the-fly and predict again. The predictor will automatically apply the pending updates and recalibrate the scores before generating the new predictions.
>>> icp.measure = 'norm' >>> icp.weight_hamming = 1.0 >>> icp.weight_cardinality = 0.5 >>> new_prediction_obj = icp.predict(test_probs) >>> new_prediction_sets = new_prediction_obj(significance_level=0.1)
Prediction Regions#
- class mulaconf.prediction_regions.PredictionRegions(p_values: torch.Tensor, combinations: torch.Tensor, non_empty_prediction_regions: bool = True)[source]#
A container class for conformal prediction results.
This class wraps the p-values generated by the Inductive Conformal Predictor and provides methods to extract prediction sets at specific significance levels and evaluate performance metrics.
- Parameters:
p_values (torch.Tensor) – The p-values associated with every possible label combination for each test sample. Shape: (t_samples, 2^(c_classes)).
combinations (torch.Tensor) – The matrix of all possible label combinations corresponding to the columns of p_values. Shape: (2^(c_classes), c_classes).
non_empty_prediction_regions (bool, optional) – If True (default), the combination with the highest p-value is returned to ensure non-empty predictions.
Example
>>> import torch >>> from mulaconf.prediction_regions import PredictionRegions >>> >>> # 1. Generate dummy data (100 samples, 5 classes -> 32 combinations) >>> combinations = torch.cartesian_prod(*[torch.tensor([0, 1])] * 5) >>> p_values = torch.rand(100, 2**5) >>> >>> # 2. Initialize container >>> prediction_regions_obj = PredictionRegions(p_values, combinations)
- __call__(significance_level: float | List[float] | numpy.ndarray | pandas.Series | torch.Tensor | None = None) List[Tuple[torch.Tensor, float] | torch.Tensor] | Dict[float, List[Tuple[torch.Tensor, float] | torch.Tensor]]#
Extracts prediction sets for one or multiple significance levels.
- Parameters:
significance_level (float or List[float]) – The significance level(s) (alpha) for which to return prediction regions. Must be strictly between 0 and 1.
- Returns:
If
significance_levelis a scalar: Returns a list of valid tuples.If
significance_levelis a list: Returns a dictionary mapping alpha values to lists of valid tuples.
- Return type:
Union[List[torch.Tensor], Dict[float, List[torch.Tensor]]]
- Raises:
ValueError – If
significance_levelis None or out of range [0, 1].
Example
>>> # Scalar alpha >>> prediction_sets_lst = prediction_regions_obj(0.1) >>> >>> # Multiple alphas >>> prediction_sets_dict = prediction_regions_obj([0.05, 0.1])
- evaluate(return_true_label_p_value: bool = True, return_coverage: bool = True, return_n_criterion: bool = True, return_s_criterion: bool = True, return_observed_fuzziness: bool = True, return_observed_excess: bool = True, *, true_labelsets: numpy.ndarray | torch.Tensor | pandas.DataFrame | pandas.Series | List | None = None, significance_level: float | List[float] | None = None) Dict | None#
Evaluates the conformal predictor using standard metrics.
Calculates validity (coverage) and efficiency (N-criterion, S-criterion, Observed fuzziness, Observed excess) metrics.
- Parameters:
return_true_label_p_value (bool, default=True) – Whether to return the p-values of the true class (requires true labelsets).
return_coverage (bool, default=True) – Whether to calculate empirical coverage (requires true labelsets and significance_level).
return_n_criterion (bool, default=True) – Whether to calculate average set size (N-criterion) (requires true labelsets and significance_level).
return_s_criterion (bool, default=True) – Whether to calculate the S-criterion (sum of p-values), a threshold-independent efficiency metric.
return_observed_fuzziness (bool, default=True) – Whether to calculate the observed fuzziness (requires true labelsets).
return_observed_excess (bool, default=True) – Whether to calculate the observed excess (requires true labelsets and significance_level).
true_labelsets (torch.Tensor or array-like, optional) – The ground truth labels. Required for coverage and p-value metrics.
significance_level (float or List[float], optional) – The alpha level(s) to evaluate coverage and N-criterion.
Note
The
true_labelsetsand thesignificance_levelmust be passed as a keyword argument.- Returns:
A dictionary containing the requested evaluation metrics.
Depending on the boolean flags provided, the returned dictionary will contain the following specific string keys: -
'true_labels_p_values': A 1D tensor of the exact p-values for the true labels. -'coverage': The empirical coverage (validity). -'n_criterion': The average prediction set size. -'s_criterion': The average sum of all p-values. -'observed_fuzziness': The average sum of the p-values of the false labels. -'observed_excess': The average sum of false labels included in the prediction region.If
significance_levelis a list of multiple alphas, it returns a nested dictionary where the top-level keys are the alpha values (floats), and the values are metric dictionaries containing the keys listed above.- Return type:
Dict
- Raises:
ValueError – If true_labelsets` and the
significance_levelis not provided.ValueError – If
true_labelsetsshape does not match the number of classes.
Example
>>> # Generate dummy test labels >>> y_test = torch.rand(10, 5) >>> >>> # Evaluate specific alpha >>> metrics = prediction_regions_obj.evaluate(true_labelsets=y_test, significance_level=0.1) >>> >>> # Evaluate multiple alphas >>> metrics_multi = prediction_regions_obj.evaluate(true_labelsets=y_test, significance_level=[0.05, 0.1])