GPClarity is a library that transforms black-box Gaussian Process models into interpretable, debuggable, and trustworthy tools. Built on GPy, it provides human-readable insights into kernel behavior, uncertainty patterns, and model complexity.
- Kernel Interpretation: Translate raw kernel math into human-readable summaries with configurable thresholds
- Uncertainty Profiling: Visualize, diagnose, and calibrate uncertainty across input space
- Hyperparameter Tracking: Monitor optimization trajectories with early stopping and convergence detection
- Complexity Quantification: Score model complexity and detect over/underfitting risk
- Data Influence Analysis: Identify impactful training points using leverage scores and leave-one-out analysis
- Model Health Checks: Validate model state before analysis
pip install gpclaritygit clone https://github.com/AngadKumar16/gpclarity.git
cd gpclarity
pip install -e ".[dev]"| Package | Required for |
|---|---|
scipy |
calibrate_uncertainty() with scaling method |
sklearn |
Faster nearest-neighbor search for large datasets |
joblib |
Parallel LOO computation in DataInfluenceMap |
tqdm |
Progress bar in compute_loo_variance_increase |
pandas |
HyperparameterTracker.to_dataframe() |
import gpclarity
import GPy
import numpy as np
# Train a Gaussian Process
X = np.linspace(0, 10, 50).reshape(-1, 1)
y = np.sin(X).flatten() + 0.1 * np.random.randn(50)
kernel = GPy.kern.RBF(1) + GPy.kern.White(1)
model = GPy.models.GPRegression(X, y[:, None], kernel)
model.optimize()
# Check model health
health = gpclarity.check_model_health(model)
print(health["healthy"], health["log_likelihood"])
# Summarize kernel structure
summary = gpclarity.summarize_kernel(model)
# Prints human-readable description of each kernel component
# Profile uncertainty
profiler = gpclarity.UncertaintyProfiler(model, X_train=X)
X_test = np.linspace(-2, 12, 200).reshape(-1, 1)
profiler.plot(X_test, y_train=y)
# Track hyperparameter optimization
tracker = gpclarity.HyperparameterTracker(model)
history = tracker.wrapped_optimize(max_iters=100)
tracker.plot_evolution()
# Compute complexity score
result = gpclarity.compute_complexity_score(model, X)
print(f"Complexity: {result['score']:.2f} — {result['interpretation']}")from gpclarity import (
summarize_kernel,
format_kernel_tree,
interpret_lengthscale,
interpret_variance,
extract_kernel_params_flat,
get_lengthscale,
get_noise_variance,
count_kernel_components,
)Generate a human-readable interpretation of the model's kernel.
summary = gpclarity.summarize_kernel(model)
# Returns dict with kernel_structure, components, overall assessment
# Access component details
for comp in summary["components"]:
print(comp["type"], comp["params"], comp["interpretation"])Use config to customize interpretation thresholds:
from gpclarity.kernel_summary import InterpretationConfig, LengthscaleThresholds, VarianceThresholds
config = InterpretationConfig(
lengthscale=LengthscaleThresholds(rapid_variation=0.3, smooth_trend=3.0),
variance=VarianceThresholds(very_low=0.05, high=5.0),
)
summary = gpclarity.summarize_kernel(model, config=config)Pretty-print the kernel hierarchy:
print(gpclarity.format_kernel_tree(model))
# Output:
# └─ rbf
# └─ whiteGet all kernel parameters as a flat dict with dotted paths:
params = gpclarity.extract_kernel_params_flat(model)
# {"add.rbf.lengthscale": 1.23, "add.white.variance": 0.01}ls = gpclarity.get_lengthscale(model) # scalar
ls_map = gpclarity.get_lengthscale(model, as_dict=True) # per-component dictnoise = gpclarity.get_noise_variance(model)from gpclarity import UncertaintyProfilerprofiler = UncertaintyProfiler(model, X_train=X)predict(X_test, cache_key=None) → PredictionResult
Safe prediction with variance clipping and caching:
result = profiler.predict(X_test, cache_key="grid")
print(result.mean, result.variance, result.std)
# Get confidence intervals
lower, upper = result.get_interval(level=2.0) # 2-sigmacompute_diagnostics(X_test) → dict
Spatial uncertainty statistics:
diag = profiler.compute_diagnostics(X_test)
# Keys: mean_uncertainty, median_uncertainty, max_uncertainty,
# min_uncertainty, uncertainty_std, coefficient_of_variation,
# high_uncertainty_ratio, n_extrapolation_points, ...identify_uncertainty_regions(X_test, threshold_percentile=None, return_regions=False) → dict
regions = profiler.identify_uncertainty_regions(X_test, threshold_percentile=90)
high_pts = regions["high_uncertainty_points"]["points"]classify_regions(X_test) → np.ndarray of UncertaintyRegion
Classify each test point as INTERPOLATION, EXTRAPOLATION, BOUNDARY, or STRUCTURAL:
from gpclarity.uncertainty_analysis import UncertaintyRegion
labels = profiler.classify_regions(X_test)
n_extrap = np.sum(labels == UncertaintyRegion.EXTRAPOLATION)calibrate_uncertainty(X_val, y_val, method="scaling") → dict
Calibrate uncertainty against validation data:
cal = profiler.calibrate_uncertainty(X_val, y_val)
print(f"Optimal scale: {cal['optimal_scale']:.3f}")
print(f"Original coverage: {cal['original_coverage']:.2%}")get_summary(X_test) → dict
Full summary with diagnostics, region breakdown, and actionable recommendations:
summary = profiler.get_summary(X_test)
for rec in summary["recommendations"]:
print(rec)plot(X_test, X_train=None, y_train=None, confidence_levels=(1.0, 2.0), show_regions=False, ...) → plt.Axes
ax = profiler.plot(X_test, X_train=X, y_train=y, show_regions=True)Standalone helpers:
from gpclarity.uncertainty_analysis import quick_uncertainty_check, compare_uncertainty_profiles
# One-liner
print(quick_uncertainty_check(model, X_test, X_train=X))
# Compare multiple models
results = compare_uncertainty_profiles({"m1": model1, "m2": model2}, X_test)from gpclarity import HyperparameterTrackerwrapped_optimize(max_iters=100, capture_every=1, convergence_tolerance=1e-6, patience=10, callback=None) → dict
Optimizes with automatic early stopping and full trajectory recording:
tracker = HyperparameterTracker(model)
history = tracker.wrapped_optimize(max_iters=200, patience=15)
# history = {"lengthscale": [1.0, 1.1, ...], "variance": [...]}Use a callback to run custom logic each iteration:
def my_callback(model, iteration, history):
if iteration % 10 == 0:
print(f"Iter {iteration}: LL={model.log_likelihood():.3f}")
tracker.wrapped_optimize(max_iters=100, callback=my_callback)get_parameter_trajectory(param_name) → (iterations, values)
iters, vals = tracker.get_parameter_trajectory("lengthscale")get_convergence_report(window=10, convergence_threshold=0.01) → dict
Statistical convergence analysis per parameter:
report = tracker.get_convergence_report()
for param, metrics in report.items():
print(f"{param}: converged={metrics['converged']}, trend={metrics['trend_direction']}")detect_optimization_issues() → dict
Detect NaN values, parameter oscillation, and decreasing log-likelihood:
issues = tracker.detect_optimization_issues()
for w in issues["warnings"]:
print(w)
for r in issues["recommendations"]:
print(r)plot_evolution(params=None, show_convergence=True, show_ll=True) → plt.Figure
fig = tracker.plot_evolution()to_dataframe() → pd.DataFrame
Export full history (requires pandas):
df = tracker.to_dataframe()
# Columns: iteration, log_likelihood, gradient_norm, timestamp, <param names>from gpclarity import compute_complexity_score, compute_roughness_score, compute_noise_ratioThree scoring strategies are available:
| Strategy | Description |
|---|---|
"default" |
Composite score from components, roughness, and SNR |
"geometric" |
Effective rank of kernel matrix eigenvalue spectrum |
"bayesian" |
Log marginal likelihood curvature (gradient-based) |
result = gpclarity.compute_complexity_score(model, X)
print(result["score"]) # raw score
print(result["category"]) # "MODERATE", "COMPLEX", etc.
print(result["interpretation"]) # human-readable description
print(result["risk_level"]) # "LOW", "MEDIUM", "HIGH"
print(result["risk_factors"]) # list of specific risks
print(result["recommendations"]) # actionable suggestions
# Geometric strategy
result = gpclarity.compute_complexity_score(model, X, strategy="geometric")
# Full diagnostics object
from gpclarity.model_complexity import ComplexityMetrics
metrics: ComplexityMetrics = gpclarity.compute_complexity_score(model, X, return_diagnostics=True)
print(metrics.effective_degrees_of_freedom)
print(metrics.capacity_ratio)
print(metrics.is_well_specified)Geometric mean of inverse lengthscales across all kernel components. Higher = more wiggly.
roughness = gpclarity.compute_roughness_score(model.kern)Signal-to-noise ratio (signal_variance / noise_variance):
snr = gpclarity.compute_noise_ratio(model)
# >1 means signal dominates, <1 means noise dominatesfrom gpclarity import DataInfluenceMapcompute_influence_scores(X_train) → InfluenceResult
Leverage scores via hat matrix diagonal (O(n³)):
influence = DataInfluenceMap(model)
result = influence.compute_influence_scores(X)
scores = result.scores # np.ndarray of influence per point
print(result.computation_time) # seconds
print(result.metadata) # kernel type, noise variance, etc.
# Find most influential point
top_idx = np.argmax(scores)
print(f"Most influential: point {top_idx} at {X[top_idx]}")compute_loo_variance_increase(X_train, y_train, n_jobs=1, verbose=False) → (variance_increase, prediction_errors)
Exact leave-one-out analysis (slow for large n, supports parallelism via joblib):
var_increase, pred_errors = influence.compute_loo_variance_increase(
X, y, n_jobs=-1, verbose=True # use all cores, show tqdm progress
)
# Most impactful point is where var_increase is largestget_influence_report(X_train, y_train, compute_loo=True, n_jobs=1) → dict
Full analysis combining leverage scores and LOO:
report = influence.get_influence_report(X, y)
print(report["most_influential_point"]) # index, location, score
print(report["least_influential_point"])
print(report["diagnostics"]["high_leverage_count"])
print(report["loo_analysis"]["mean_error"])plot_influence(X_train, influence_scores, ax=None) → plt.Axes
result = influence.compute_influence_scores(X)
ax = influence.plot_influence(X, result)from gpclarity import check_model_healthValidate model attributes, parameters, and log-likelihood before analysis:
health = gpclarity.check_model_health(model)
if not health["healthy"]:
print("Issues:", health["issues"])
print("Warnings:", health["warnings"])
print("Log-likelihood:", health["log_likelihood"])
print("Parameters:", health["n_parameters"]) KERNEL SUMMARY
==================================================
Structure: ['rbf', 'white']
Thresholds:
Lengthscale: rapid<0.5, smooth>2.0
Variance: very_low<0.01, high>10.0
rbf (parts[0])
└─ lengthscale: 1.23
Moderate flexibility (1.23)
white (parts[1])
└─ variance: 0.01
Very low noise (≈0.010)
{
"score": 2.34,
"category": "MODERATE",
"interpretation": "Well-balanced complexity",
"risk_level": "LOW",
"risk_factors": [],
"recommendations": [],
"components": {
"n_parameters": 3,
"n_kernel_components": 2,
"roughness_score": 0.81,
"signal_noise_ratio": 4.5,
"effective_dof": 12.3,
"capacity_ratio": 0.25,
}
}gpclarity/
├── kernel_summary.py # Kernel parsing, interpretation, parameter extraction
├── uncertainty_analysis.py # Uncertainty profiling, region classification, calibration
├── hyperparam_tracker.py # Optimization tracking, convergence detection
├── model_complexity.py # Complexity scoring (3 strategies), roughness, SNR
├── data_influence.py # Leverage scores, LOO analysis, influence visualization
├── plotting.py # Rendering backend for all plot methods
├── utils.py # Model health checks, numerical utilities
└── exceptions.py # Custom exception hierarchy
@software{gpclarity2026,
title={gpclarity: Gaussian Process Interpretability Toolkit},
author={Angad Kumar},
year={2026},
url={https://github.com/AngadKumar16/gpclarity},
version={0.1.3}
}GPClarity is licensed under the MIT License. See LICENSE for details.
Contributions are welcome!
- Report bugs or request features via GitHub Issues
- Submit pull requests for fixes or enhancements
- Run
pip install -e ".[dev]"and ensure tests pass before submitting
Author: Angad Kumar (GitHub, Email)
- Emukit integration:
ClarityBayesianOptimizationLoopfor instrumented Bayesian optimization - Conda package support
- Automated example notebooks
- Support for GPflow and other GP backends