Lee Henriques
add HF space config
db162d7
|
Raw
History Blame Contribute Delete
11.1 kB

A newer version of the Streamlit SDK is available: 1.62.0

Upgrade
metadata
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.

Dashboard Preview


✨ 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:

  1. Collect gradient trajectory G ∈ R^(T Γ— C Γ— H Γ— W) via batched backward pass
  2. Per channel: form G_c ∈ R^(T Γ— H*W), compute truncated SVD β†’ top-k spatial patterns V_k
  3. Score each step: s_t = β€–g_c^(t) Β· V_k^Tβ€–β‚‚
  4. Softmax weights: w_t = exp(s_t/Ο„) / Ξ£ exp(s_t'/Ο„)
  5. 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)

  1. Switch sidebar to Batch Benchmark
  2. Enter folder path to your ImageNet validation images
  3. Select methods and max image count
  4. 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


πŸ“ License

MIT License