Christopher Odoom

Time Complexity of Conformal Prediction

December 2022

Project Overview

This project explores the computational efficiency of conformal prediction methods for classification problems. Unlike traditional machine learning approaches that output single class predictions, conformal prediction generates prediction sets with guaranteed error control, making it particularly valuable for high-stakes applications.

I investigated two main approaches to conformal prediction: full conformal prediction and inductive conformal prediction, with a focus on their time complexity and practical performance trade-offs. The analysis was implemented in Python using custom code and established machine learning libraries.

Methodology

The project methodology consisted of the following steps:

  1. Implementation of full conformal prediction (FCP) algorithm
  2. Implementation of inductive conformal prediction (ICP) algorithm
  3. Selection of diverse datasets with varying sizes and complexities
  4. Design of experimental framework to measure computation time
  5. Analysis of prediction set sizes and coverage guarantees
  6. Comparison of theoretical and practical time complexity

The experiments were run with multiple underlying machine learning models (random forest, support vector machines, and neural networks) to assess whether the complexity patterns held across different base algorithms.

Python Implementation

The following code demonstrates the implementation of full and inductive conformal prediction algorithms:

import numpy as np
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

class ConformalPredictor:
    def __init__(self, base_model, alpha=0.05):
        """
        Initialize conformal predictor
        
        Parameters:
        -----------
        base_model : object
            The underlying machine learning model with fit and predict methods
        alpha : float
            Significance level (default: 0.05)
        """
        self.base_model = base_model
        self.alpha = alpha
        self.calibration_scores = None
    
    def _nonconformity_score(self, model, X, y):
        """Calculate nonconformity scores for samples"""
        # Get probability predictions for all classes
        probas = model.predict_proba(X)
        # Extract probabilities for true classes
        n_samples = X.shape[0]
        true_probas = np.array([probas[i, y[i]] for i in range(n_samples)])
        # Nonconformity score is 1 - probability of true class
        return 1 - true_probas
    
    def fit_full_conformal(self, X, y):
        """Full conformal prediction calibration"""
        n_samples = X.shape[0]
        start_time = time.time()
        
        # For each sample, fit model on rest and calculate nonconformity
        self.calibration_scores = np.zeros(n_samples)
        
        for i in range(n_samples):
            # Leave-one-out
            train_indices = [j for j in range(n_samples) if j != i]
            X_train, y_train = X[train_indices], y[train_indices]
            X_val, y_val = X[i:i+1], y[i:i+1]
            
            # Train model
            model = self.base_model.fit(X_train, y_train)
            
            # Calculate nonconformity score
            self.calibration_scores[i] = self._nonconformity_score(model, X_val, y_val)[0]
        
        self.fit_time = time.time() - start_time
        return self
    
    def fit_inductive_conformal(self, X, y, calib_size=0.3):
        """Inductive conformal prediction calibration"""
        start_time = time.time()
        
        # Split data into proper training and calibration
        X_train, X_calib, y_train, y_calib = train_test_split(
            X, y, test_size=calib_size, random_state=42
        )
        
        # Train model on proper training set
        self.model = self.base_model.fit(X_train, y_train)
        
        # Calculate nonconformity scores on calibration set
        self.calibration_scores = self._nonconformity_score(self.model, X_calib, y_calib)
        
        self.fit_time = time.time() - start_time
        return self
    
    def predict(self, X):
        """Generate prediction sets"""
        if self.calibration_scores is None:
            raise ValueError("Fit the model first")
            
        start_time = time.time()
        
        # Calculate threshold based on significance level
        threshold = np.percentile(self.calibration_scores, (1 - self.alpha) * 100)
        
        # Get probabilities for each class
        probas = self.model.predict_proba(X)
        
        # Create prediction sets
        n_samples = X.shape[0]
        n_classes = probas.shape[1]
        prediction_sets = []
        
        for i in range(n_samples):
            # Add classes to prediction set if nonconformity score <= threshold
            pred_set = [j for j in range(n_classes) if 1 - probas[i, j] <= threshold]
            prediction_sets.append(pred_set)
            
        self.predict_time = time.time() - start_time
        return prediction_sets

The code for measuring time complexity:

# Time complexity measurement
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification

def measure_time_complexity(sample_sizes, method='inductive'):
    """Measure time complexity for different sample sizes"""
    fit_times = []
    predict_times = []
    avg_set_sizes = []
    
    for n in sample_sizes:
        # Generate synthetic dataset
        X, y = make_classification(n_samples=n, n_features=20, n_classes=3, 
                                  n_informative=15, random_state=42)
        
        # Split into train and test
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
        
        # Initialize model and conformal predictor
        base_model = RandomForestClassifier(n_estimators=100, random_state=42)
        cp = ConformalPredictor(base_model)
        
        # Fit conformal predictor based on method
        if method == 'full':
            cp.fit_full_conformal(X_train, y_train)
        else:
            cp.fit_inductive_conformal(X_train, y_train)
            
        # Generate prediction sets
        pred_sets = cp.predict(X_test)
        
        # Record times and set sizes
        fit_times.append(cp.fit_time)
        predict_times.append(cp.predict_time)
        avg_set_sizes.append(np.mean([len(s) for s in pred_sets]))
    
    return fit_times, predict_times, avg_set_sizes

# Run experiment
sample_sizes = [100, 500, 1000, 2000, 5000, 10000]
fcp_fit_times, fcp_predict_times, fcp_set_sizes = measure_time_complexity(sample_sizes, 'full')
icp_fit_times, icp_predict_times, icp_set_sizes = measure_time_complexity(sample_sizes, 'inductive')

# Plot results
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
plt.plot(sample_sizes, fcp_fit_times, 'o-', label='Full CP (Fitting)')
plt.plot(sample_sizes, icp_fit_times, 's-', label='Inductive CP (Fitting)')
plt.xlabel('Sample Size')
plt.ylabel('Time (seconds)')
plt.title('Fitting Time vs. Sample Size')
plt.legend()
plt.grid(True)

plt.subplot(2, 1, 2)
plt.plot(sample_sizes, fcp_predict_times, 'o-', label='Full CP (Prediction)')
plt.plot(sample_sizes, icp_predict_times, 's-', label='Inductive CP (Prediction)')
plt.xlabel('Sample Size')
plt.ylabel('Time (seconds)')
plt.title('Prediction Time vs. Sample Size')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

Results

The analysis revealed several key findings regarding the time complexity of conformal prediction:

  • Full conformal prediction demonstrated O(n²) time complexity for the fitting phase, making it impractical for large datasets
  • Inductive conformal prediction showed O(n) time complexity for fitting, offering significant performance advantages for large-scale applications
  • Prediction time was consistently O(m) for both methods, where m is the number of test samples
  • The average prediction set size was slightly larger for inductive CP compared to full CP, representing a trade-off between efficiency and specificity

Time complexity comparison between full and inductive conformal prediction:

[Time complexity graph would appear here]

Comparison of prediction set sizes for different methods:

[Prediction set size comparison would appear here]

Conclusions

This project provided valuable insights into the computational considerations for deploying conformal prediction methods:

  • Inductive CP offers a substantial computational advantage over full CP, with minimal loss in prediction efficiency
  • The O(n²) complexity of full CP makes it unsuitable for production environments with large datasets
  • For time-critical applications, the efficiency of inductive CP makes it the preferred choice
  • Both methods maintained the theoretical guarantee of valid prediction sets (1-α coverage)

This work contributes to the practical understanding of conformal prediction methods and provides concrete performance metrics to guide implementation choices in real-world machine learning systems where prediction sets are preferred over point predictions.

Back to Projects