Spaces:
Sleeping
A newer version of the Streamlit SDK is available: 1.62.0
title: XAI Vision Inspector
emoji: ποΈ
colorFrom: blue
colorTo: indigo
sdk: streamlit
sdk_version: 1.37.0
python_version: '3.10'
app_file: app.py
pinned: false
π XAI Vision Inspector
An interactive Explainable AI dashboard for visualizing how CNNs make decisions β built entirely from scratch using PyTorch. Features Eigen-IG, a novel attribution method with published benchmark results across 500 ImageNet images and three architectures.
β¨ What's Inside
Novel Method: Eigen-IG
Eigen-IG extends Integrated Gradients with per-channel trajectory SVD and eigenvalue-weighted integration. Instead of uniformly averaging all gradient steps (many of which are noisy near the zero baseline), Eigen-IG scores each step by its alignment with dominant gradient patterns and upweights the most signal-rich steps via softmax.
Benchmark results (500 ImageNet images):
| Architecture | Deletion AUC β | vs IG | Insertion AUC β | vs IG | Significant? |
|---|---|---|---|---|---|
| ResNet-50 | 0.0278 | β17.4% | 0.0560 | β36% | β p<0.0001 |
| VGG-16 | 0.0455 | β5.6% | 0.0762 | β37% | β p=0.12 |
| EfficientNet-B0 | 0.0625 | β2.9% | 0.2385 | +35.2% | β p<0.0001 |
Eigen-IG consistently produces 94β97% sparse attribution maps vs 80β93% for IG. See the companion paper for full analysis.
Raw per-image results: results/
All XAI Methods β Implemented From Scratch
| Method | Type | Resolution | Speed | Axioms |
|---|---|---|---|---|
| Eigen-IG | SVD-weighted path integral | Full pixel | π’ ~1.3Γ IG | Relaxed completeness |
| Integrated Gradients | Path integral | Full pixel | π’ Slow | β Completeness, Sensitivity |
| Grad-CAM | Gradient-based | LowβMed | β‘ Fast | None |
| Grad-CAM++ | Gradient-based | LowβMed | β‘ Fast | None |
| Occlusion Sensitivity | Perturbation | Configurable | π Very slow | Model-agnostic |
| SmoothGrad | Noise-averaged | Full pixel | π’ Slow | None |
Pretrained Models
ResNet-50, ResNet-101, VGG-16, EfficientNet-B0, DenseNet-121, MobileNet-V3
Dashboard Highlights
- π¬ Single-image analysis β side-by-side attribution map comparison with interactive Plotly heatmaps
- π Batch benchmark mode β run faithfulness metrics over a folder of images with auto CSV export
- ποΈ Configurable method parameters (steps, patch size, noise level, SVD rank, temperature)
- π¨ 8 colormaps with adjustable overlay opacity
- π Attribution statistics, distribution histograms, and faithfulness metrics table
- π In-app method documentation with paper references and axiomatic analysis
- β‘ GPU acceleration (CUDA auto-detected)
π Quick Start
1. Clone & Install
git clone <your-repo>
cd xai-vision-inspector
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
For GPU acceleration (recommended, RTX 40/50 series):
pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/cu128
Verify CUDA:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
2. Run the Dashboard
streamlit run app.py
Open http://localhost:8501 in your browser.
π Project Structure
xai-vision-inspector/
β
βββ app.py # Streamlit dashboard entry point
βββ requirements.txt
β
βββ explainers/ # XAI methods β all from scratch
β βββ eigen_integrated_gradients.py # Eigen-IG (novel method)
β βββ grad_cam.py # Grad-CAM + Grad-CAM++
β βββ integrated_gradients.py # IG with convergence delta check
β βββ occlusion_sensitivity.py # Batched sliding-window perturbation
β βββ smooth_grad.py # SmoothGrad standard/squared/variance
β
βββ evaluation/ # Faithfulness benchmark suite
β βββ faithfulness.py # Deletion AUC, Insertion AUC, Infidelity
β
βββ model_zoo/ # Model registry + layer navigation
β βββ model_loader.py
β
βββ visualization/ # Heatmap overlays, Plotly charts, stats
β βββ overlay.py
β
βββ utils/
βββ image_utils.py # Preprocessing, denormalization
π¬ Method Deep Dive
Eigen-IG
from explainers import EigenIntegratedGradients
explainer = EigenIntegratedGradients(
model,
n_steps=50, # integration steps
n_components=10, # SVD rank k
weight_temp=1.0, # softmax temperature Ο
baseline_type="zeros"
)
eigen_attrs, eigen_map = explainer(input_tensor, class_idx=None)
# Check completeness relaxation
delta = explainer.convergence_delta(input_tensor, eigen_attrs)
print(f"Convergence delta: {delta:.4f}") # mean 0.031 Β± 0.019 on ResNet-50
Algorithm:
- Collect gradient trajectory
G β R^(T Γ C Γ H Γ W)via batched backward pass - Per channel: form
G_c β R^(T Γ H*W), compute truncated SVD β top-k spatial patternsV_k - Score each step:
s_t = βg_c^(t) Β· V_k^Tββ - Softmax weights:
w_t = exp(s_t/Ο) / Ξ£ exp(s_t'/Ο) - Weighted average:
Δ_c = Ξ£ w_t Β· g_c^(t), scaled by(x - x')
Integrated Gradients
from explainers import IntegratedGradients
explainer = IntegratedGradients(model, n_steps=100, baseline_type="zeros")
ig_attrs, ig_map = explainer(input_tensor, class_idx=None)
delta = explainer.convergence_delta(input_tensor, ig_attrs, class_idx=top1_idx)
print(f"Convergence delta: {delta:.4f}") # should be near 0
Grad-CAM
from explainers import GradCAM
from model_zoo import load_model, get_layer_by_name
model, config = load_model("ResNet-50")
layer = get_layer_by_name(model, config.default_target_layer)
explainer = GradCAM(model, layer)
cam = explainer(input_tensor, class_idx=None) # (H, W) array in [0, 1]
explainer.remove_hooks()
Occlusion Sensitivity
from explainers import OcclusionSensitivity
explainer = OcclusionSensitivity(model, patch_size=32, stride=16)
sensitivity_map, resolved_class = explainer(input_tensor, batch_size=32)
SmoothGrad
from explainers import SmoothGrad
explainer = SmoothGrad(model, n_samples=50, noise_level=0.15, variant="squared")
attrs, smooth_map = explainer(input_tensor, class_idx=None)
π Faithfulness Benchmark
Run the full benchmark from the dashboard (Batch Benchmark tab) or programmatically:
from evaluation.faithfulness import run_full_faithfulness_eval
results = run_full_faithfulness_eval(
model=model,
input_tensor=tensor,
saliency_maps={
"Eigen-IG": eigen_map,
"IG": ig_map,
"Grad-CAM": cam,
},
class_idx=top1_idx,
steps=10,
)
# results[method] = {Deletion_AUC, Insertion_AUC, Infidelity, Deletion_final_drop}
Metric interpretation:
| Metric | Better | Measures |
|---|---|---|
| Deletion AUC | Lower | How causally decisive the attributed pixels are |
| Insertion AUC | Higher | How sufficient the attributed pixels are |
| Infidelity | Lower | How well attributions predict score changes under noise |
Batch Benchmark (Dashboard)
- Switch sidebar to Batch Benchmark
- Enter folder path to your ImageNet validation images
- Select methods and max image count
- Click βΆ Run Analysis
The first 5 images run without timeout (calibration phase) to measure real per-image wall time. The remaining images run with a max_observed + 0.5s per-image timeout, with live ETA and skip counter. Results auto-export as CSV.
Approximate runtimes (RTX 5050, T=50, steps=5):
| Architecture | Per image | 500 images |
|---|---|---|
| ResNet-50 | ~2β3s | ~25β35 min |
| VGG-16 | ~4β5s | ~45β60 min |
| EfficientNet-B0 | ~2β3s | ~25β35 min |
ποΈ Configuration Guide
| Parameter | Default | Effect |
|---|---|---|
| IG / Eigen-IG steps | 50 | β steps = more accurate, slower |
| Eigen-IG n_components (k) | 10 | SVD rank β stable for k β₯ 5 |
| Eigen-IG weight_temp (Ο) | 1.0 | Lower = more aggressive step weighting |
| Occlusion patch size | 32px | β patch = faster but coarser |
| Occlusion stride | 16px | β stride = higher resolution, much slower |
| SmoothGrad samples | 40 | β samples = smoother map |
| Noise level | 0.15 | Ο as fraction of input range |
π§ͺ Programmatic API Example
import torch
from PIL import Image
from model_zoo import load_model, get_layer_by_name
from explainers import GradCAM, IntegratedGradients, EigenIntegratedGradients
from utils import preprocess_image
from visualization import overlay_heatmap, make_comparison_figure
from evaluation.faithfulness import run_full_faithfulness_eval
# Load model
model, config = load_model("ResNet-50", device="cuda")
# Preprocess image
pil_img = Image.open("dog.jpg")
tensor, display = preprocess_image(pil_img, device="cuda")
# Run methods
layer = get_layer_by_name(model, config.default_target_layer)
cam_exp = GradCAM(model, layer)
cam = cam_exp(tensor); cam_exp.remove_hooks()
ig_exp = IntegratedGradients(model, n_steps=50)
_, ig = ig_exp(tensor)
eig_exp = EigenIntegratedGradients(model, n_steps=50, n_components=10)
_, eig = eig_exp(tensor)
# Faithfulness benchmark
import torch.nn.functional as F
with torch.no_grad():
probs = F.softmax(model(tensor), dim=1).squeeze()
class_idx = int(probs.argmax())
results = run_full_faithfulness_eval(
model, tensor,
{"Grad-CAM": cam, "IG": ig, "Eigen-IG": eig},
class_idx=class_idx
)
for method, metrics in results.items():
print(f"{method}: {metrics}")
# Save comparison figure
fig = make_comparison_figure(
display,
{"Grad-CAM": cam, "IG": ig, "Eigen-IG": eig},
colormap="jet", alpha=0.5,
)
fig.savefig("comparison.png", bbox_inches="tight")
π References
- Eigen-IG (this work): SVD-Weighted Path Integration for Sparser CNN Attribution Maps, 2026
- Integrated Gradients: Sundararajan et al., 2017 β arXiv:1703.01365
- Grad-CAM: Selvaraju et al., 2017 β arXiv:1610.02391
- Grad-CAM++: Chattopadhay et al., 2018 β arXiv:1710.11063
- Occlusion Sensitivity: Zeiler & Fergus, 2014 β arXiv:1311.2901
- SmoothGrad: Smilkov et al., 2017 β arXiv:1706.03825
- RISE (Deletion/Insertion): Petsiuk et al., 2018 β arXiv:1806.07421
- Infidelity: Yeh et al., 2019 β arXiv:1901.09392
π License
MIT License
