From Shapley values to saliency maps to mechanistic interpretability — a working primer focused on deep learning, computer vision, and LLMs.
Motivation, taxonomy, and the central tension.
A modern deep neural network can have tens of billions of parameters and produce outputs that humans accept, reject, or deploy in safety-critical settings — medical triage, credit scoring, autonomous driving, content moderation. Explainable AI (XAI) is the body of techniques that produces auxiliary information allowing a human to reason about why a model produced a given output, or how it behaves overall.
The motivation is not only philosophical. Concrete drivers include:
Classical wisdom: simpler models (linear regression, shallow decision trees) are interpretable but inaccurate; deep models are accurate but opaque. Recent work, especially Cynthia Rudin's "Stop Explaining Black Box Models" (2019), argues this tradeoff is exaggerated in many tabular domains — interpretable models can often match black-box accuracy when designed carefully. The tradeoff is real for high-dimensional perceptual tasks (vision, language), which is why XAI for deep models is an active field rather than a closed one.
Four orthogonal distinctions structure almost every paper in the field:
| Axis | Options | Example |
|---|---|---|
| Stage | Intrinsic / Post-hoc | A logistic regression is intrinsically interpretable; SHAP on a ResNet is post-hoc. |
| Specificity | Model-specific / Model-agnostic | TreeSHAP only works on trees. LIME works on any classifier. |
| Scope | Local / Global | "Why this prediction?" (local) vs. "How does the model behave overall?" (global). |
| Output type | Feature attribution / Counterfactual / Example-based / Rule extraction | SHAP gives attributions; "if income had been €5k higher, the loan would have been approved" is a counterfactual. |
Two desiderata that often conflict:
A faithful explanation may be unintelligible (e.g., a 4096-dimensional gradient vector). A plausible explanation may be a confabulation. The honest framing of any XAI method is: which of these two properties does it actually deliver, and how do we evaluate that?
Adebayo et al. (2018) showed that several popular saliency methods produce essentially the same heatmap even when the model's weights are randomized. Methods that pass this "model parameter randomization test" are faithful; those that don't are visualizing the input, not the model.
Cooperative game theory as a unified framework for feature attribution.
Lloyd Shapley (1953) asked: given a coalition of players that together earn some payout, how should the payout be split fairly? He defined four axioms (efficiency, symmetry, dummy, linearity) and proved there exists a unique attribution rule satisfying all four. That rule is the Shapley value.
The translation to machine learning, due to Štrumbelj & Kononenko (2014) and made canonical by Lundberg & Lee's SHAP (NeurIPS 2017), is:
| Cooperative game | ML interpretation |
|---|---|
| Set of players $N$ | Set of features |
| Coalition $S \subseteq N$ | Subset of features present |
| Value function $v(S)$ | Model prediction given only features in $S$ |
| Shapley value $\phi_i$ | Attribution of feature $i$ to the prediction |
The Shapley value of feature $i$ is the weighted average of its marginal contributions across all possible coalitions:
$$\phi_i(v) = \sum_{S \subseteq N \setminus \{i\}} \frac{|S|!\,(n - |S| - 1)!}{n!} \bigl[\, v(S \cup \{i\}) - v(S) \,\bigr]$$Read this as: for every subset $S$ that does not contain feature $i$, measure how much adding $i$ changes the value ($v(S \cup \{i\}) - v(S)$), and average these marginal contributions, weighted so each "coalition size" contributes equally.
These axioms uniquely determine the formula above. Any feature-attribution method satisfying all four must compute Shapley values. This is the central appeal of SHAP: it isn't one of many possible attribution methods; among methods with these properties, it is the only one.
The sum has $2^{n-1}$ coalitions. For a model with 50 features that is $\approx 5.6 \times 10^{14}$ evaluations per prediction. Worse, evaluating $v(S)$ requires marginalizing out the missing features, which typically means averaging the model's prediction over a sample of values for the absent features — adding another factor.
All practical SHAP variants are approximations or model-specific exact algorithms:
| Variant | Applies to | How |
|---|---|---|
| KernelSHAP | Any model | Weighted local linear regression on sampled coalitions; recovers Shapley values in the limit. |
| TreeSHAP | Tree ensembles (XGBoost, LightGBM, sklearn) | Exact, in polynomial time. Lundberg et al. (2020). |
| DeepSHAP | Deep neural networks | Combines DeepLIFT propagation rules with Shapley reference values. |
| GradientSHAP | Differentiable models | Stochastic estimator combining Integrated Gradients with Shapley sampling. |
| PartitionSHAP | High-dim structured inputs (text, images) | Hierarchical Shapley: cluster features, attribute clusters, recurse. |
"Marginalize out missing features" is harder than it sounds. SHAP needs a notion of $v(S)$ when only the features in $S$ are present — but neural networks don't accept partial inputs. Two common choices:
Janzing et al. (2020) argue that the choice of baseline encodes implicit causal assumptions, and different baselines yield meaningfully different attributions. This is not a solved problem.
SHAP values are attributions to the model's output, not causal effects on the underlying phenomenon. If the model is wrong, SHAP will faithfully explain its wrongness. SHAP cannot tell you what the true causal drivers are — only what the model is using.
Saliency maps, class activation, integrated gradients, occlusion.
For a vision model, the natural form of a local explanation is a heatmap over the input image highlighting which pixels (or regions) drove the prediction. The methods below differ in how they compute that heatmap, and crucially, in whether the heatmap actually reflects the model's reasoning.
The simplest idea: compute the gradient of the predicted class score with respect to the input pixels. Pixels whose small perturbations most change the output are the "salient" ones.
$$M_{ij} = \left| \frac{\partial y_c}{\partial x_{ij}} \right|$$Cheap (one backward pass). Often noisy. Tends to highlight edges in the image rather than semantically meaningful regions.
Currently the standard baseline for CNN interpretability. The insight: instead of using gradients with respect to pixels, use gradients with respect to the last convolutional layer's feature maps. Those maps still carry spatial structure (a 14×14 grid for ResNet-50 with 224×224 input) and are semantically richer than raw pixels.
Let $A^k \in \mathbb{R}^{H \times W}$ be the $k$-th feature map of the target conv layer, and $y^c$ be the score for class $c$. Compute channel weights by global-average-pooling the gradients:
$$\alpha_k^c = \frac{1}{Z} \sum_{i,j} \frac{\partial y^c}{\partial A^k_{ij}}$$Then form a weighted sum of feature maps, gated by ReLU to keep only positively-contributing regions:
$$L^c_{\text{Grad-CAM}} = \text{ReLU}\!\left( \sum_k \alpha_k^c \, A^k \right)$$The result is a low-resolution heatmap that you upsample to image size. Class-discriminative: different classes produce different maps. Survives the sanity check in Adebayo et al. (2018).
A theoretically principled feature-attribution method for any differentiable model. Given an input $x$ and a baseline $x'$ (typically a black image), integrate the gradient along the straight-line path from $x'$ to $x$:
$$\text{IG}_i(x) = (x_i - x'_i) \cdot \int_{\alpha = 0}^{1} \frac{\partial F(x' + \alpha(x - x'))}{\partial x_i} \, d\alpha$$In practice the integral is approximated by a Riemann sum over 20–50 steps. IG satisfies completeness ($\sum_i \text{IG}_i = F(x) - F(x')$, the same property as Shapley efficiency) and implementation invariance (functionally equivalent models give the same attributions). The choice of baseline matters: zero image, blur, or random noise yield different attributions.
Partition the image into ~50 superpixels (e.g., via SLIC). Generate ~1000 perturbed versions by randomly masking subsets of superpixels with a neutral color. Get the model's predictions on all perturbations, then fit a sparse linear model that predicts these outputs from the binary mask. The coefficients of the linear model are the superpixel attributions.
LIME is model-agnostic and gives intuitive region-level explanations. It's also slow (1000 forward passes per explanation) and unstable: re-running LIME on the same image with a different perturbation seed can produce visibly different heatmaps.
Slide an opaque gray patch over the image; for each position, record how much the predicted class probability drops. Drops in probability map directly to importance. Intuitive, no gradients needed, very slow (one forward pass per patch position), and conflates the model's reliance on a region with the OOD-ness of the occluded image.
| Method | Faithful? | Resolution | Cost |
|---|---|---|---|
| Vanilla saliency | Mixed — fails some sanity checks | Pixel | 1 backward pass |
| Grad-CAM | Passes sanity checks | Coarse (feature-map grid) | 1 fwd + 1 bwd |
| Integrated Gradients | Theoretically principled | Pixel | 20–50 fwd + bwd |
| LIME | Local approximation, unstable | Superpixel | ~1000 fwd |
| Occlusion | OOD-confounded | Patch grid | ~hundreds fwd |
For most CNN interpretability work today, the default is Grad-CAM for quick visual checks and Integrated Gradients when you need theoretical guarantees. SHAP (via the DeepExplainer or GradientExplainer) is also viable and ties the analysis to the Shapley framework introduced in Tab 2.
A minimal-but-real implementation in ~50 lines.
The implementation below loads a pretrained ResNet-50, registers forward and backward hooks on its last convolutional block, runs Grad-CAM on an input image, and returns a normalized heatmap. No external XAI libraries — just PyTorch and torchvision.
import torch
import torch.nn.functional as F
from torchvision import models, transforms
from PIL import Image
import numpy as np
# 1. Load a pretrained model and set it to eval mode.
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
model.eval()
# 2. Pick the target layer. For ResNet-50, the last conv block is layer4.
target_layer = model.layer4[-1]
# 3. Register hooks to capture the layer's activations and gradients.
activations = {}
gradients = {}
def fwd_hook(module, inp, out):
activations['value'] = out
def bwd_hook(module, grad_in, grad_out):
gradients['value'] = grad_out[0]
target_layer.register_forward_hook(fwd_hook)
target_layer.register_full_backward_hook(bwd_hook)
# 4. Preprocess an input image (ImageNet normalization).
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
img = Image.open('cat.jpg').convert('RGB')
x = preprocess(img).unsqueeze(0) # shape [1, 3, 224, 224]
# 5. Forward pass; pick the predicted class.
logits = model(x)
pred_class = logits.argmax(dim=1).item()
# 6. Backward pass on the predicted class score.
model.zero_grad()
logits[0, pred_class].backward()
# 7. Grad-CAM: weight feature maps by channel-averaged gradients, ReLU, upsample.
A = activations['value'] # [1, 2048, 7, 7]
grad_A = gradients['value'] # [1, 2048, 7, 7]
alpha = grad_A.mean(dim=(2, 3), keepdim=True) # [1, 2048, 1, 1]
cam = F.relu((alpha * A).sum(dim=1, keepdim=True)) # [1, 1, 7, 7]
cam = F.interpolate(cam, size=(224, 224),
mode='bilinear', align_corners=False)
cam = cam.squeeze().detach().numpy()
cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8)
# `cam` is a 224x224 array in [0, 1]. Overlay on the image with matplotlib:
# plt.imshow(img); plt.imshow(cam, alpha=0.5, cmap='jet')
forward_hook captures activations during the forward pass; full_backward_hook captures gradients during backprop.logits[0, pred_class] is a scalar — the logit for the predicted class. Calling .backward() on it populates grad_A with $\partial y^c / \partial A^k_{ij}$ for every spatial position and every channel.grad_A.mean(dim=(2,3)) implements $\alpha_k^c = \frac{1}{Z}\sum_{i,j} \partial y^c / \partial A^k_{ij}$ — global average pooling of the gradients per channel.For real projects, use a maintained library rather than rolling your own:
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
cam = GradCAM(model=model, target_layers=[model.layer4[-1]])
heatmap = cam(input_tensor=x,
targets=[ClassifierOutputTarget(pred_class)])[0]
The pytorch-grad-cam package also implements Grad-CAM++, ScoreCAM, AblationCAM, EigenCAM, and others — useful for comparing methods on your own model.
import shap
# Pick a small background set (50–100 images from the training set)
background = train_images[:100]
# DeepExplainer uses DeepLIFT-style propagation with Shapley baselines
explainer = shap.DeepExplainer(model, background)
shap_values = explainer.shap_values(test_images[:5])
# Visualize per-class pixel attributions
shap.image_plot(shap_values, test_images[:5])
DeepExplainer is fast but only works with TF/Keras and PyTorch with specific layer types. For arbitrary architectures, shap.GradientExplainer is more flexible (uses GradientSHAP). For exhaustive coverage, shap.KernelExplainer works on any model but is extremely slow on images.
Yes — with significant caveats, and not as the right tool for every question.
The short answer: SHAP is well-defined for LLMs, the shap library has dedicated explainers for text models, and it produces useful attributions for classification-style tasks. But the obstacles that make SHAP awkward on images become severe on language, and for the deepest questions about LLM behavior the field is moving toward different techniques entirely.
[MASK], with a random token from the vocabulary, or simply delete it and shorten the sequence? Each choice yields different SHAP values and encodes different counterfactual semantics.Short-text classification with a frozen model. Sentiment analysis, NLI, hate-speech detection, intent classification. The shap library's PartitionExplainer (the default for text) uses hierarchical Shapley: tokens are grouped, attributions are computed at the group level, and the tree is recursively refined where attributions are large. This brings $2^n$ down to something tractable.
import shap
import transformers
# A short-text classification pipeline
pipe = transformers.pipeline(
"sentiment-analysis",
model="distilbert-base-uncased-finetuned-sst-2-english"
)
explainer = shap.Explainer(pipe)
shap_values = explainer(["The film was a delight, but ran far too long."])
# Renders an HTML visualization with each token colored by attribution
shap.plots.text(shap_values)
This works well, runs in a few seconds, and produces interpretable per-token attributions. It is the right tool when the question is "which words in this short input drove this classification?"
Integrated Gradients still works on LLMs if applied to token embeddings rather than tokens themselves. The captum library (PyTorch) and transformers-interpret wrap this conveniently. Faster than SHAP, often comparable quality for short inputs.
Transformers expose attention weights for free. Visualizing them is tempting and common — but Jain & Wallace (2019) and Wiegreffe & Pinter (2019) demonstrated that attention weights are not reliable explanations: alternative attention patterns can produce the same output. Treat attention visualizations as suggestive heuristics, not faithful explanations.
The emerging research frontier for LLMs. Rather than asking "which input tokens mattered," ask "which internal computations does the model perform, and what do they encode?" Active sub-areas:
"Which training examples most affected this prediction?" Mathematically elegant but computationally brutal; recent approximations (TRAK, Park et al. 2023) make it feasible on large models.
Ask the model to explain itself. Plausible and cheap. Often unfaithful: stated reasons may not match actual computation. Recent work (Turpin et al., 2023) shows LLMs can produce convincing chain-of-thought explanations that have no relation to their actual decision process when an artificial bias is introduced.
There is no single XAI method that gives faithful, scalable, semantically rich explanations of arbitrary LLM behavior. For classification, use SHAP or IG. For mechanistic questions, use causal tracing and SAEs. For behavioral audits, use evaluations and red-teaming. The right method depends on the question.