docstr: Explain the model's predictions using Integrated Gradients.
  
  # Return
  
  :return: ExplanationsResult containing the explanations.
      `global_explanations`: (Explanation) Aggregate explanation for the entire input data.
  
      `row_explanations`: (List[Explanation]) A list of explanations, one for each row in the input data. Each
      explanation contains the integrated gradients for each label in the target feature's vocab with respect to
      each input feature.
  
      `expected_values`: (List[float]) of length [output feature cardinality] Average convergence delta for each
      label in the target feature's vocab.
docstr_tok: ["Explain","the","model","'s","predictions","using","Integrated","Gradients",".","#","Return",":","return",":","ExplanationsResult","containing","the","explanations",".","`","global_explanations","`",":","(","Explanation",")","Aggregate","explanation","for","the","entire","input","data",".","`","row_explanations","`",":","(","List","[","Explanation","]",")","A","list","of","explanations",",","one","for","each","row","in","the","input","data",".","Each","explanation","contains","the","integrated","gradients","for","each","label","in","the","target","feature","'s","vocab","with","respect","to","each","input","feature",".","`","expected_values","`",":","(","List","[","float","]",")","of","length","[","output","feature","cardinality","]","Average","convergence","delta","for","each","label","in","the","target","feature","'s","vocab","."]
code: def explain(self) -> ExplanationsResult:
      """Explain the model's predictions using Integrated Gradients.
  
      # Return
  
      :return: ExplanationsResult containing the explanations.
          `global_explanations`: (Explanation) Aggregate explanation for the entire input data.
  
          `row_explanations`: (List[Explanation]) A list of explanations, one for each row in the input data. Each
          explanation contains the integrated gradients for each label in the target feature's vocab with respect to
          each input feature.
  
          `expected_values`: (List[float]) of length [output feature cardinality] Average convergence delta for each
          label in the target feature's vocab.
      """
  
      # TODO(travis): add back skip encoders at the end in finally. Shouldn't be an issue in most cases as we
      # typically perform explanations on a loaded model and don't use it to predict afterwards.
      self.model.model.unskip()
      self.model.model.to(DEVICE)
  
      input_features: LudwigFeatureDict = (
          self.model.model.input_features
      )
      run_config = ExplanationRunConfig(
          batch_size=self.model.config_obj.trainer.batch_size
      )
  
      get_input_tensors_with_retry = retry_with_halved_batch_size(
          run_config
      )(get_input_tensors)
      get_total_attribution_with_retry = retry_with_halved_batch_size(
          run_config
      )(get_total_attribution)
  
      # Convert input data into embedding tensors from the output of the model encoders.
      inputs_encoded = get_input_tensors_with_retry(
          self.model, self.inputs_df, run_config
      )
      sample_encoded = get_input_tensors_with_retry(
          self.model, self.sample_df, run_config
      )
      baseline = get_baseline(self.model, sample_encoded)
  
      # Compute attribution for each possible output feature label separately.
      expected_values = []
      for target_idx in tqdm(range(self.vocab_size), desc="Explain"):
          (
              total_attribution,
              feat_to_token_attributions,
              total_attribution_global,
          ) = get_total_attribution_with_retry(
              self.model,
              self.target_feature_name,
              target_idx if self.is_category_target else None,
              inputs_encoded,
              baseline,
              len(self.inputs_df),
              run_config,
          )
  
          # Aggregate token attributions
          feat_to_token_attributions_global = {}
          for (
              feat_name,
              token_attributions,
          ) in feat_to_token_attributions.items():
              token_attributions_global = defaultdict(float)
              # sum attributions for each token
              for token, token_attribution in (
                  ta for tas in token_attributions for ta in tas
              ):
                  token_attributions_global[token] += abs(
                      token_attribution
                  )
              # divide by number of samples to get average attribution per token
              token_attributions_global = {
                  token: token_attribution
                  / max(0, len(token_attributions))
                  for token, token_attribution in token_attributions_global.items()
              }
              # convert to list of tuples and sort by attribution
              token_attributions_global = sorted(
                  token_attributions_global.items(),
                  key=lambda x: x[1],
                  reverse=True,
              )
              # keep only top 100 tokens
              token_attributions_global = token_attributions_global[
                  :100
              ]
              feat_to_token_attributions_global[
                  feat_name
              ] = token_attributions_global
  
          self.global_explanation.add(
              input_features.keys(),
              total_attribution_global,
              feat_to_token_attributions_global,
          )
  
          for i, (feature_attributions, explanation) in enumerate(
              zip(total_attribution, self.row_explanations)
          ):
              # Add the feature attributions to the explanation object for this row.
              explanation.add(
                  input_features.keys(),
                  feature_attributions,
                  {
                      k: v[i]
                      for k, v in feat_to_token_attributions.items()
                  },
              )
  
          # TODO(travis): for force plots, need something similar to SHAP E[X]
          expected_values.append(0.0)
  
          if self.is_binary_target:
              # For binary targets, we only need to compute attribution for the positive class (see below).
              break
  
      # For binary targets, add an extra attribution for the negative class (false).
      if self.is_binary_target:
          le_true = self.global_explanation.label_explanations[0]
          negated_attributions = le_true.to_array() * -1
          negated_token_attributions = {
              fa.feature_name: [
                  (t, -a) for t, a in fa.token_attributions
              ]
              for fa in le_true.feature_attributions
              if fa.token_attributions is not None
          }
          # Prepend the negative class to the list of label explanations.
          self.global_explanation.add(
              input_features.keys(),
              negated_attributions,
              negated_token_attributions,
              prepend=True,
          )
  
          for explanation in self.row_explanations:
              le_true = explanation.label_explanations[0]
              negated_attributions = le_true.to_array() * -1
              negated_token_attributions = {
                  fa.feature_name: [
                      (t, -a) for t, a in fa.token_attributions
                  ]
                  for fa in le_true.feature_attributions
                  if fa.token_attributions is not None
              }
              # Prepend the negative class to the list of label explanations.
              explanation.add(
                  input_features.keys(),
                  negated_attributions,
                  negated_token_attributions,
                  prepend=True,
              )
  
          # TODO(travis): for force plots, need something similar to SHAP E[X]
          expected_values.append(0.0)
  
      return ExplanationsResult(
          self.global_explanation,
          self.row_explanations,
          expected_values,
      )
  
imports: import copy
  import gc
  import logging
  from collections import defaultdict
  from dataclasses import dataclass
  from typing import Dict, List, Optional, Tuple
  import numpy
  import numpy.typing
  import pandas
  import torch
  from captum.attr import LayerIntegratedGradients, TokenReferenceBase
  from captum.attr._utils.input_layer_wrapper import InputIdentity
  from torch.autograd import Variable
  from tqdm import tqdm
  from ludwig.api import LudwigModel
  from ludwig.api_annotations import PublicAPI
  from ludwig.constants import BINARY, CATEGORY, DATE, IMAGE, INPUT_FEATURES, MINIMUM_BATCH_SIZE, NAME, NUMBER, PREPROCESSING, SEQUENCE, SET, TEXT, UNKNOWN_SYMBOL
  from ludwig.data.preprocessing import preprocess_for_prediction
  from ludwig.explain.explainer import Explainer
  from ludwig.explain.explanation import ExplanationsResult
  from ludwig.explain.util import get_pred_col, replace_layer_with_copy
  from ludwig.features.feature_utils import LudwigFeatureDict
  from ludwig.models.ecd import ECD
  from ludwig.utils.torch_utils import DEVICE
label_desc: Use image node_id 1 for calling the IntegratedGradientsExplainer obj's underlying member method code with example usage: obj.explain() and returns: ExplanationsResult