Holistic CRAFT¶
View source |
📰 CRAFT Paper |
📰 Holistic Paper
Holistic CRAFT (Concept Recursive Activation FacTorization) is a variant of the CRAFT method designed to extract concepts from full activation maps rather than image patches.
This approach preserves the global spatial context and is particularly suitable for object detection models and other tasks where spatial structure across the entire image is important.
The crop-based approach works well for classification because images of classification datasets are typically dominated by a single, well-centred object: random crops are therefore likely to contain parts of the object of interest and carry relevant signal for concept extraction. In Object Detection, the scenes generally contain multiple objects of varying sizes, often occupying only a small fraction of the image. Random crops drawn from such images are mostly background; the target objects are absent or heavily under-represented in the resulting crop dataset, making the NMF factorization blind to the very patterns it should capture.
Supported Object Detection Models¶
Holistic CRAFT works with various object detection architectures through specialized latent extractors provided by the xplique-adapters package:
PyTorch (torchvision & ultralytics):
- RetinaNet - RetinanetExtractorBuilder
- Faster R-CNN - FasterRcnnExtractorBuilder
- FCOS - FcosExtractorBuilder
- SSD - SSDExtractorBuilder
- YOLO (v11) - YoloExtractorBuilder
- DETR - DetrExtractorBuilder
TensorFlow:
- RetinaNet - RetinaNetExtractorBuilder
Each extractor handles the model-specific architecture to split it into the required g(.) and h(.) functions.
Supported Classification Models¶
For standard classification models, Holistic CRAFT does not require a custom extractor per architecture. Instead, the built-in LayeredModelExtractorBuilder can split any layered model at a chosen intermediate layer:
PyTorch:
- Any torch.nn.Module — LayeredModelExtractorBuilder (from xplique.concepts.torch.layered_model_latent_extractor)
TensorFlow:
- Any tf.keras.Model — LayeredModelExtractorBuilder (from xplique.concepts.tf.layered_model_latent_extractor)
The builder takes the model and a layer index to define the split point. Everything before that layer becomes g(.), and everything after becomes h(.).
Key Differences from Regular CRAFT¶
| Aspect | Regular CRAFT | Holistic CRAFT |
|---|---|---|
| Input | Image patches/crops | Full activation maps |
| Use Case | Classification tasks | Object detection, Classification |
| Spatial Context | Local (patch-level) | Global (full image) |
| Concepts | Visual patterns in patches | Spatial activation patterns |
| Performance | Extracts many crops per image | Processes full feature maps directly |
Workflow¶
Holistic CRAFT follows the same core principle as CRAFT but operates on full images instead of patches:
- Extract Activations: Pass input images through the model's encoder (g) to obtain spatial activation maps from an intermediate layer
- Factorize Concepts: Apply Non-negative Matrix Factorization (NMF) to these activation maps to discover recurring spatial patterns (concepts)
Warning
Activations must be non-negative to use the standard NMF. Ensure a ReLU or similar activation function is applied before the extraction layer. Third-party NMF implementations may not have this limitation (e.g., the Semi-NMF from the Overcomplete library).
- Estimate Importance: Use any attribution methods available in Xplique (gradient-based, perturbation-based) to rank concept importance
- Visualize: Generate concept heatmaps overlaid on images to show "what" and "where"
Like regular CRAFT, Holistic CRAFT requires splitting the model into two parts: \((g, h)\) such that \(f(x) = (g \cdot h)(x)\). The model \(g\) maps input to latent space (activation maps), and \(h\) maps latent space to predictions. Concepts are extracted from these activation maps in latent space.
This split is implemented through three abstractions:
-
LatentData: A container that holds the intermediate activations produced by \(g\). It abstracts away framework-specific tensor formats, providing a unified interface for reading (get_activations) and writing (set_activations) activations, with the necessary shape conversions (e.g., channel-first to channel-last). -
LatentExtractor: Wraps both \(g\) (input_to_latent_model) and \(h\) (latent_to_logit_model). It orchestrates the full forward pass, batching, device management, and output formatting. TheTorchLatentExtractorandTfLatentExtractorsubclasses provide framework-specific implementations. -
LatentExtractorBuilder: A factory that constructs aLatentExtractorfor a specific model architecture. It handles all the architecture-specific wiring (defining how to split the model, which layer to extract from, and how to format outputs) so that the rest of the CRAFT pipeline remains model-agnostic.
Example¶
Basic Usage with Object Detection¶
import xplique
from xplique.concepts import HolisticCraftTorch as Craft
from xplique_adapters.concepts.torch.latent_data_retinanet import RetinanetExtractorBuilder
# Build a latent extractor that splits the model into g(.) and h(.)
# This provides the input_to_latent (g) and latent_to_logit (h) functions
latent_extractor = RetinanetExtractorBuilder.build(
model,
device="cuda",
nb_classes=91,
extraction_location='resnet', # Choose 'resnet' or 'fpn'
extraction_layer=-1 # Extract from last ResNet feature layer
)
# Create Holistic CRAFT instance
craft = Craft(
latent_extractor=latent_extractor,
number_of_concepts=10,
device="cuda"
)
# Fit CRAFT on input images to discover concepts
craft.fit(input_images, class_id=class_id)
# Display discovered concepts as heatmaps overlaid on images
craft.display_images_per_concept(images=input_images[:5])
# Display top 3 images for each concept ranked by activation
craft.display_top_images_per_concept(images=input_images, topk=3)
# Estimate concept importance on the 20 first images using Gradient×Input method
# (GradientxInput is the default method)
importances_gi = craft.estimate_importance(
images=input_images[:20],
operator=xplique.Tasks.OBJECT_DETECTION,
class_id=class_id,
confidence=0.8
)
# Estimate concept importance on the 20 first images using Sobol method
importances_sobol = craft.estimate_importance(
images=input_images[:20],
operator=xplique.Tasks.OBJECT_DETECTION,
class_id=class_id,
confidence=0.8,
# Use Sobol method & its arguments
method="sobol",
grid_size=4,
nb_design=8,
perturbation_function="amplitude",
)
Using Different Attribution Methods to Compute the Concept Importances¶
Holistic CRAFT supports various attribution methods for concept importance estimation:
import xplique
from xplique.concepts import PartialExplainer
from xplique.attributions import VarGrad
# Use VarGrad for robust importance estimation
vargrad_explainer = PartialExplainer(
explainer_class=VarGrad,
operator=xplique.Tasks.OBJECT_DETECTION,
nb_samples=20,
noise=0.15
)
# Compute VarGrad explanation for each concept
explanation_vargrad = craft.compute_explanation_per_concept(
partial_explainer=vargrad_explainer,
images=input_images,
class_id=class_id,
confidence=0.3,
)
# Reduce the spatial dimension of the explanation
# to compute the final concepts importances
importances_vargrad = craft.reduce_to_importance(
explanation=explanation_vargrad,
)
Using a Different NMF Factorizer¶
By default, the standard Sklearn NMF is used to factorize the concepts. But other types of factorizers are supported, such as the ones provided by the Overcomplete project.
from overcomplete.optimization import SemiNMF
from xplique.concepts.torch.factorizer import OvercompleteFactorizer
nb_concepts=10
# Create a SemiNMF factorizer which allows negative activations
factorizer = OvercompleteFactorizer(
optimizer_class=SemiNMF,
nb_concepts=nb_concepts,
device=device
)
# Setup Craft to use this factorizer
craft = Craft(
latent_extractor=latent_extractor,
number_of_concepts=nb_concepts,
device=device,
factorizer=factorizer,
)
craft.fit(input_images)
Implementing Your Own Latent Extractor¶
If you're working with a model architecture that isn't supported out-of-the-box, you can implement your own latent extractor by following these steps:
1. Create a Custom LatentData Class¶
First, create a class that stores the intermediate activations from your model:
from xplique.concepts.latent_extractor import LatentData
import torch
class CustomLatentData(LatentData):
def __init__(self, fpn_outs: list, extraction_layer: int = 0):
super().__init__()
self.fpn_outs = fpn_outs
self.extraction_layer = extraction_layer
def get_activations(self, as_numpy: bool = True, keep_gradients: bool = False):
"""Extract activations from the specified layer."""
activations = self.fpn_outs[self.extraction_layer]
if not keep_gradients:
activations = activations.detach()
# Convert from (N, C, H, W) to (N, H, W, C) for Xplique
if len(activations.shape) == 4:
activations = activations.permute(0, 2, 3, 1)
if as_numpy:
activations = activations.cpu().numpy()
return activations
def set_activations(self, values: torch.Tensor) -> None:
"""Set activations back into the latent data structure."""
# Convert from (N, H, W, C) to (N, C, H, W)
if len(values.shape) == 4:
values = values.permute(0, 3, 1, 2)
self.fpn_outs[self.extraction_layer] = values
def to(self, device: torch.device) -> 'CustomLatentData':
"""Move latent data to specified device."""
self.fpn_outs = [fpn_out.to(device) for fpn_out in self.fpn_outs]
return CustomLatentData(self.fpn_outs, self.extraction_layer)
2. Create a Custom ExtractorBuilder¶
Next, implement a builder that splits your model into g(.) and h(.) functions:
import types
from xplique.concepts.latent_extractor import LatentExtractorBuilder
from xplique.concepts.torch.latent_extractor import TorchLatentExtractor
class CustomExtractorBuilder(LatentExtractorBuilder):
@classmethod
def build(
cls,
model,
device: str = 'cuda',
extraction_layer: int = -1,
batch_size: int = 1
) -> TorchLatentExtractor:
# Define g(.) function: input → latent activations
def g(self, x):
# Example: extract from backbone/feature pyramid
fpn_outs = self.backbone(x)
return CustomLatentData(
fpn_outs=list(fpn_outs),
extraction_layer=latent_extractor.extraction_layer
)
# Define h(.) function: latent activations → predictions
def h(self, latent_data: CustomLatentData):
fpn_outs = latent_data.fpn_outs
outputs = self.head(fpn_outs)
return outputs
# Bind g and h methods to the model
model.g = types.MethodType(g, model)
model.h = types.MethodType(h, model)
# Create output formatter (converts raw predictions to MultiBoxTensor)
output_formatter = CustomBoxFormatter()
# Build the latent extractor
latent_extractor = TorchLatentExtractor(
model,
model.g,
model.h,
latent_data_class=CustomLatentData,
output_formatter=output_formatter,
batch_size=batch_size,
device=device
)
# Store extraction layer for later use
latent_extractor.extraction_layer = extraction_layer
return latent_extractor
3. Use Your Custom Extractor with CRAFT¶
Once you have your custom extractor, you can use it just like the built-in ones:
from xplique.concepts import HolisticCraftTorch as Craft
# Build your custom latent extractor
latent_extractor = CustomExtractorBuilder.build(
model,
device="cuda",
extraction_layer=-1,
batch_size=16
)
# Use it with CRAFT
craft = Craft(
latent_extractor=latent_extractor,
number_of_concepts=10,
device="cuda"
)
# Fit and visualize concepts
craft.fit(input_images)
craft.display_images_per_concept(input_images[:5])
Key Points¶
- g(.) function: Maps input images to intermediate activations at a chosen layer
- h(.) function: Maps latent activations back to final predictions
- LatentData: Handles activation extraction with proper shape conversions (PyTorch uses channel-first, Xplique expects channel-last)
- Output formatter: Converts model predictions to
MultiBoxTensorformat for compatibility with Xplique
API Reference¶
HolisticCraft¶
Framework-agnostic CRAFT implementation for holistic model explanations.
__init__(self,
latent_extractor: xplique.concepts.latent_extractor.LatentExtractor,
number_of_concepts: int = 20,
device: str = None,
factorizer: Optional[Any] = None)¶
latent_extractor: xplique.concepts.latent_extractor.LatentExtractor,
number_of_concepts: int = 20,
device: str = None,
factorizer: Optional[Any] = None)
Parameters
-
latent_extractor : xplique.concepts.latent_extractor.LatentExtractor
Extractor that splits the model into encoder (input to activations) and decoder (activations to predictions) for concept extraction
-
number_of_concepts : int = 20
Number of concepts to extract via NMF decomposition
-
device : str = None
Device specification for tensor operations (framework-specific)
-
factorizer : Optional[Any] = None
Optional factorizer instance implementing the ConceptFactorizer protocol.
If None, creates a default SklearnNMFFactorizer with alpha_W=1e-2 and max_iter=200
check_if_fitted(self)¶
Checks if the factorization model has been fitted to input data.
compute_explanation_per_concept(self,
images: numpy.ndarray,
partial_explainer: xplique.concepts.holistic_craft.PartialExplainer,
class_id: Optional[int] = None,
confidence: Optional[float] = None,
verbose: bool = False) -> numpy.ndarray¶
images: numpy.ndarray,
partial_explainer: xplique.concepts.holistic_craft.PartialExplainer,
class_id: Optional[int] = None,
confidence: Optional[float] = None,
verbose: bool = False) -> numpy.ndarray
Compute explanations per concept using the provided explainer.
Parameters
-
images : numpy.ndarray
Input images as numpy arrays
-
partial_explainer : xplique.concepts.holistic_craft.PartialExplainer
PartialExplainer instance that creates an attribution explainer when called with model and batch_size arguments
-
class_id : Optional[int] = None
Target class ID for filtering detections
-
confidence : Optional[float] = None
Confidence threshold for filtering detections
-
verbose : bool = False
If True, prints progress information during processing
Return
-
explanations : numpy.ndarray
Concatenated explanations for all images, shape (N, H, W, n_concepts)
decode(self,
latent_data: xplique.concepts.latent_extractor.LatentData,
coeffs_u: Union[numpy.ndarray, Any]) -> xplique.commons.prediction_types.StructuredPrediction¶
latent_data: xplique.concepts.latent_extractor.LatentData,
coeffs_u: Union[numpy.ndarray, Any]) -> xplique.commons.prediction_types.StructuredPrediction
Decode concept coefficients back to predictions.
Parameters
-
latent_data : xplique.concepts.latent_extractor.LatentData
Single image's latent representation (not batched)
-
coeffs_u : Union[numpy.ndarray, Any]
Concept coefficients for reconstruction
Return
-
predictions : xplique.commons.prediction_types.StructuredPrediction
Predictions implementing StructuredPrediction protocol (has filter() and to_batched_tensor() methods). Concrete types are MultiBoxTensor for object detection or ClassifierTensor for classification.
display_concept_heatmap(self,
image: numpy.ndarray,
concept_heatmap: numpy.ndarray,
concept_idx: int,
ax: Any,
filter_percentile: int = 80,
clip_percentile: int = 5) -> None¶
image: numpy.ndarray,
concept_heatmap: numpy.ndarray,
concept_idx: int,
ax: Any,
filter_percentile: int = 80,
clip_percentile: int = 5) -> None
Overlay a single concept heatmap on a single image.
Parameters
-
image : numpy.ndarray
Single image as HWC numpy array, shape (H, W, C)
-
concept_heatmap : numpy.ndarray
Raw concept activation map, shape (H', W')
-
concept_idx : int
Index of the concept, used to select the colormap
-
ax : Any
Matplotlib axis on which to draw
-
filter_percentile : int = 80
Percentile used to filter the concept heatmap (only show concept if excess N-th percentile). Defaults to 80.
-
clip_percentile : int = 5
Percentile value to use if clipping is needed when drawing the concept, e.g a value of 1 will perform a clipping between percentile 1 and 99.
This parameter allows to avoid outliers in case of too extreme values.
Default to 5.
display_images_per_concept(self,
images: numpy.ndarray,
coeffs_u: Optional[numpy.ndarray] = None,
filter_percentile: int = 80,
clip_percentile: int = 5,
order: Optional[List[int]] = None) -> matplotlib.figure.Figure¶
images: numpy.ndarray,
coeffs_u: Optional[numpy.ndarray] = None,
filter_percentile: int = 80,
clip_percentile: int = 5,
order: Optional[List[int]] = None) -> matplotlib.figure.Figure
Display concept heatmaps overlaid on images.
Parameters
-
images : numpy.ndarray
Input images to visualize (array of shape (N, H, W, C) for tensorflow or (N, C, H, W) for pytorch)
-
coeffs_u : Optional[numpy.ndarray] = None
Optional pre-computed coefficients, shape (N, H, W, C) or (N, Tokens, C).
If None, coefficients will be computed via transform(images).
-
filter_percentile : int = 80
Percentile used to filter the concept heatmap (only show concept if excess N-th percentile). Defaults to 80.
-
clip_percentile : int = 5
Percentile value to use if clipping is needed when drawing the concept, e.g a value of 1 will perform a clipping between percentile 1 and 99.
This parameter allows to avoid outliers in case of too extreme values.
Default to 5.
-
order : Optional[List[int]] = None
Optional list of concept IDs to specify display order. If None, concepts are shown in sequential order
Return
-
fig : matplotlib.figure.Figure
matplotlib figure with len(images) rows and number_of_concepts columns
display_top_images_per_concept(self,
images: Union[numpy.ndarray, List[Any]],
topk: int = 3,
filter_percentile: int = 80,
clip_percentile: int = 5,
order: Optional[List[int]] = None,
coeffs_u: Optional[numpy.ndarray] = None) -> matplotlib.figure.Figure¶
images: Union[numpy.ndarray, List[Any]],
topk: int = 3,
filter_percentile: int = 80,
clip_percentile: int = 5,
order: Optional[List[int]] = None,
coeffs_u: Optional[numpy.ndarray] = None) -> matplotlib.figure.Figure
Display top N images per concept ranked by average activation.
Parameters
-
images : Union[numpy.ndarray, List[Any]]
Input images (as framework tensors or numpy arrays)
-
topk : int = 3
Number of top images to display per concept (default: 3)
-
filter_percentile : int = 80
Percentile threshold for filtering heatmaps (default: 80)
-
clip_percentile : int = 5
Percentile for clipping heatmap values (default: 5)
-
order : Optional[List[int]] = None
Optional list of concept IDs to specify display order
-
coeffs_u : Optional[numpy.ndarray] = None
Optional pre-computed concept coefficients. If None, will call self.transform(images) to compute them. Use this to pass the coefficients stored in factorization.coeffs_u after fit().
Return
-
fig : matplotlib.figure.Figure
matplotlib figure with topk rows and number_of_concepts columns
encode(self,
inputs: Union[numpy.ndarray, Any],
resize: Optional[Tuple[int, int]] = None,
differentiable: bool = False) -> List[xplique.concepts.latent_extractor.EncodedData]¶
inputs: Union[numpy.ndarray, Any],
resize: Optional[Tuple[int, int]] = None,
differentiable: bool = False) -> List[xplique.concepts.latent_extractor.EncodedData]
Encode inputs to latent data and concept coefficients.
Parameters
-
inputs : Union[numpy.ndarray, Any]
Input images to encode
-
resize : Optional[Tuple[int, int]] = None
Target size for resizing images
-
differentiable : bool = False
If True, preserves gradients for backpropagation using differentiable non-negative optimization. If False (default), uses standard NMF transform which is faster but does not preserve gradients.
Return
-
encoded_data : List[xplique.concepts.latent_extractor.EncodedData]
List of EncodedData named tuples, each containing: - latent_data: LatentData object with intermediate activations - coeffs_u: Concept coefficients (numpy array or tensor with gradients) When differentiable=False, coeffs_u are numpy arrays.
When differentiable=True, coeffs_u are framework tensors (torch.Tensor or tf.Tensor) with gradients preserved.
fit(self,
inputs,
class_id: int = 0)¶
inputs,
class_id: int = 0)
Fit NMF to extract concepts from latent activations.
Parameters
-
inputs : inputs
Input images to extract concepts from, as framework tensors or arrays
-
class_id : int = 0
Target class ID for object detection (used in factorization metadata)
get_topk_images_per_concept(self,
coeffs_u: numpy.ndarray,
topk: int = 3) -> numpy.ndarray¶
coeffs_u: numpy.ndarray,
topk: int = 3) -> numpy.ndarray
Return the indices of the top images for each concept, ranked by mean activation.
Parameters
-
coeffs_u : numpy.ndarray
Concept coefficients, shape (N, H, W, n_concepts)
-
topk : int = 3
Number of top images to return per concept (default: 3)
Return
-
top_image_ids : numpy.ndarray
Array of shape (n_concepts, topk) containing the indices of the top images for each concept, ranked by descending mean activation
latent_to_concept(self,
latent_data: xplique.concepts.latent_extractor.LatentData) -> numpy.ndarray¶
latent_data: xplique.concepts.latent_extractor.LatentData) -> numpy.ndarray
Transform latent data to concept coefficients.
Parameters
-
latent_data : xplique.concepts.latent_extractor.LatentData
Single image's latent representation containing activations
Return
-
coeffs_u : numpy.ndarray
Concept coefficients, shape (H, W, n_concepts)
latent_to_concept_differentiable(self,
latent_data: xplique.concepts.latent_extractor.LatentData) -> Any¶
latent_data: xplique.concepts.latent_extractor.LatentData) -> Any
Transform latent data to concept coefficients with gradient preservation.
Parameters
-
latent_data : xplique.concepts.latent_extractor.LatentData
Single image's latent representation containing activations
Return
-
coeffs_u : Any
Concept coefficients as framework tensor with gradients
make_concept_decoder(self,
latent_data: xplique.concepts.latent_extractor.LatentData) -> Any¶
latent_data: xplique.concepts.latent_extractor.LatentData) -> Any
Creates a concept decoder for gradient-based attribution.
Parameters
-
latent_data : xplique.concepts.latent_extractor.LatentData
Image-specific latent representation
Return
-
decoder : Any
ConceptDecoder instance with signature: (coeffs_u) -> predictions
reduce_to_importance(self,
explanation: numpy.ndarray,
spatial_reducer: Optional[str] = 'max',
abs_before_reduce: bool = True,
aggregation_reducer: Optional[str] = 'mean') -> numpy.ndarray¶
explanation: numpy.ndarray,
spatial_reducer: Optional[str] = 'max',
abs_before_reduce: bool = True,
aggregation_reducer: Optional[str] = 'mean') -> numpy.ndarray
Reduce pre-computed concept explanations to global importance scores.
Parameters
-
explanation : numpy.ndarray
Per-concept explanations, shape (N, H, W, n_concepts), as returned by :meth:
compute_explanation_per_concept.
-
spatial_reducer : Optional[str] = 'max'
Reducer applied over the spatial dimensions (H, W) to collapse each image to a per-concept score vector. Either "min", "mean", "max", "sum", "median" or
Noneto skip. Default is "max".
-
abs_before_reduce : bool = True
Whether to take the absolute value of explanations before spatial reduction.
Default is True.
-
aggregation_reducer : Optional[str] = 'mean'
Reducer applied over the image dimension after spatial reduction to produce a single score per concept. Either "min", "mean", "max", "sum", "median" or
Noneto skip (returns per-image scores). Default is "mean".
Return
-
importances : numpy.ndarray
Importance scores for each concept, shape (n_concepts,).
reduce_to_prevalence(self,
explanation: numpy.ndarray) -> numpy.ndarray¶
explanation: numpy.ndarray) -> numpy.ndarray
Compute concept prevalence from pre-computed explanations.
Parameters
-
explanation : numpy.ndarray
Per-concept explanations, shape (N, H, W, n_concepts), as returned by :meth:
compute_explanation_per_concept.
Return
-
prevalence : numpy.ndarray
Fraction of images for which each concept is dominant, shape (n_concepts,).
Values sum to 1.
reduce_to_reliability(self,
explanation: numpy.ndarray,
accuracy: numpy.ndarray) -> numpy.ndarray¶
explanation: numpy.ndarray,
accuracy: numpy.ndarray) -> numpy.ndarray
Compute concept reliability from pre-computed explanations and per-image accuracy.
Parameters
-
explanation : numpy.ndarray
Per-concept explanations, shape (N, H, W, n_concepts), as returned by :meth:
compute_explanation_per_concept.
-
accuracy : numpy.ndarray
Per-image accuracy scores, shape (N,). For classification: 0.0 or 1.0.
For object detection: per-image IoU, AP, or any scalar correctness metric computed externally by the user.
Return
-
reliability : numpy.ndarray
Mean accuracy per dominant-concept group, shape (n_concepts,).
Concepts with no dominant image get a reliability of 0.0.
transform(self,
inputs=None,
resize=None) -> numpy.ndarray¶
inputs=None,
resize=None) -> numpy.ndarray
Transform inputs to concept coefficients.
Parameters
-
inputs : inputs=None
Input images to transform. If None, returns stored coefficients from fit().
-
resize : resize=None
Target size for resizing images
Return
-
coeffs_u : numpy.ndarray
Concept coefficients for the inputs (or stored coefficients if inputs=None)
PartialExplainer¶
Wrapper for explainer classes to enable deferred instantiation.
__init__(self,
explainer_class,
**kwargs)¶
explainer_class,
**kwargs)
Parameters
-
explainer_class : explainer_class
The explainer class to instantiate (e.g., GradientInput, SobolAttributionMethod).
Must be callable and accept 'model' and 'batch_size' as keyword arguments.
-
kwargs : **kwargs
Configuration arguments for the explainer (e.g., operator, reducer, grid_size).
Should NOT include 'model' or 'batch_size' as these will be provided during instantiation.