code: def generate_zennit_explanation(
      model,
      inputs: np.ndarray,
      targets: np.ndarray,
      device: Optional[str] = None,
      **kwargs,
  ) -> np.ndarray:
      """
      Generate explanation for a torch model with zennit.
  
      Parameters
      ----------
      model: torch.nn.Module
          A model that is used for explanation.
      inputs: np.ndarray
           The inputs that ought to be explained.
      targets: np.ndarray
           The target lables that should be used in the explanation.
      device: string
          Indicated the device on which a torch.Tensor is or will be allocated: "cpu" or "gpu".
      kwargs: optional
              Keyword arguments. Pass as "explain_func_kwargs" dictionary when working with a metric class.
              Pass as regular kwargs when using the stnad-alone function.
  
              attributor: string, optional
                  XAI method.
              xai_lib_kwargs: dictionary, optional
                  Keyword arguments to be passed to the attribution function.
              softmax: boolean, optional
                  Indicated whether softmax activation in the last layer shall be removed.
              channel_first: boolean, optional
                  Indicates if the image dimensions are channel first, or channel last.
                  Inferred from the input shape if None.
              reduce_axes: tuple
                  Indicates the indices of dimensions of the output explanation array to be summed. For example, an input
                  array of shape (8, 28, 28, 3) with keepdims=True and reduce_axes = (-1,) will return an array of shape
                  (8, 28, 28, -1). Passing "()" will keep the original dimensions.
              keepdims: boolean
                  Indicated if the reduced axes shall be preserved (True) or removed (False).
      Returns
      -------
      explanation: np.ndarray
           Returns np.ndarray of same shape as inputs.
  
      """
  
      channel_first = (
          kwargs["channel_first"]
          if "channel_first" in kwargs
          else infer_channel_first(inputs)
      )
      softmax = kwargs.get("softmax", None)
  
      if softmax is not None:
          warnings.warn(
              f"Softmax argument has been passed to the explanation function. Different XAI "
              f"methods may or may not require the output to go through softmax activation. "
              f"Make sure that your softmax argument choice aligns with the method intended usage.\n",
              category=UserWarning,
          )
          wrapped_model = get_wrapped_model(
              model, softmax=softmax, channel_first=channel_first
          )
          model = wrapped_model.get_softmax_arg_model()
  
      assert 0 not in kwargs.get(
          "reduce_axes", [1]
      ), "Reduction over batch_axis is not available, please do not include axis 0 in 'reduce_axes' kwarg."
      assert len(kwargs.get("reduce_axes", [1])) <= inputs.ndim - 1, (
          "Cannot reduce attributions over more axes than each sample has dimensions, but got "
          "{} and  {}.".format(
              len(kwargs.get("reduce_axes", [1])), inputs.ndim - 1
          )
      )
  
      reduce_axes = {
          "axis": tuple(kwargs.get("reduce_axes", [1])),
          "keepdims": kwargs.get("keepdims", True),
      }
  
      # Get zennit composite, canonizer, attributor and handle canonizer kwargs.
      canonizer = kwargs.get("canonizer", None)
      if not canonizer == None and not issubclass(
          canonizer, zcanon.Canonizer
      ):
          raise ValueError(
              "The specified canonizer is not valid. "
              "Please provide None or an instance of zennit.canonizers.Canonizer"
          )
  
      # Handle attributor kwargs.
      attributor = kwargs.get("attributor", zattr.Gradient)
      if not issubclass(attributor, zattr.Attributor):
          raise ValueError(
              "The specified attributor is not valid. "
              "Please provide a subclass of zennit.attributon.Attributor"
          )
  
      # Handle attributor kwargs.
      composite = kwargs.get("composite", None)
      if not composite == None and isinstance(composite, str):
          if composite not in zcomp.COMPOSITES.keys():
              raise ValueError(
                  "Composite {} does not exist in zennit."
                  "Please provide None, a subclass of zennit.core.Composite, or one of {}".format(
                      composite, zcomp.COMPOSITES.keys()
                  )
              )
          else:
              composite = zcomp.COMPOSITES[composite]
      if not composite == None and not issubclass(
          composite, zcore.Composite
      ):
          raise ValueError(
              "The specified composite {} is not valid. "
              "Please provide None, a subclass of zennit.core.Composite, or one of {}".format(
                  composite, zcomp.COMPOSITES.keys()
              )
          )
  
      # Set model in evaluate mode.
      model.eval()
  
      if not isinstance(inputs, torch.Tensor):
          inputs = torch.Tensor(inputs).to(device)
  
      if not isinstance(targets, torch.Tensor):
          targets = torch.as_tensor(targets).to(device)
  
      canonizer_kwargs = kwargs.get("canonizer_kwargs", {})
      composite_kwargs = kwargs.get("composite_kwargs", {})
      attributor_kwargs = kwargs.get("attributor_kwargs", {})
  
      # Initialize canonizer, composite, and attributor.
      if canonizer is not None:
          canonizers = [canonizer(**canonizer_kwargs)]
      else:
          canonizers = []
      if composite is not None:
          composite = composite(
              **{
                  **composite_kwargs,
                  "canonizers": canonizers,
              }
          )
      attributor = attributor(
          **{
              **attributor_kwargs,
              "model": model,
              "composite": composite,
          }
      )
  
      n_outputs = model(inputs).shape[1]
  
      # Get the attributions.
      with attributor:
          if "attr_output" in attributor_kwargs.keys():
              _, explanation = attributor(inputs, None)
          else:
              eye = torch.eye(n_outputs, device=device)
              output_target = eye[targets]
              output_target = output_target.reshape(-1, n_outputs)
              _, explanation = attributor(inputs, output_target)
  
      if isinstance(explanation, torch.Tensor):
          if explanation.requires_grad:
              explanation = explanation.cpu().detach().numpy()
          else:
              explanation = explanation.cpu().numpy()
  
      # Sum over the axes.
      explanation = np.sum(explanation, **reduce_axes)
  
      return explanation
  
file_path: Quantus/quantus/functions/explanation_func.py
imports: import warnings
  from importlib import util
  from typing import Optional, Union
  import numpy
  import quantus
  import scipy
  from quantus.helpers import constants
  from quantus.helpers import __EXTRAS__
  from quantus.helpers.model.model_interface import ModelInterface
  from quantus.helpers.utils import get_baseline_value, infer_channel_first, make_channel_last, get_wrapped_model
label_desc: Use image node_id 5 for calling a global function with example usage: generate_zennit_explanation(model, inputs, targets, device) and returns: explanation