code: def shap_values(
      self,
      X,
      nsamples=200,
      ranked_outputs=None,
      output_rank_order="max",
      rseed=None,
      return_variances=False,
  ):
      global tf, keras
  
      import tensorflow as tf
      import tensorflow.keras as keras
  
      # check if we have multiple inputs
      if not self.multi_input:
          assert not isinstance(
              X, list
          ), "Expected a single tensor model input!"
          X = [X]
      else:
          assert isinstance(X, list), "Expected a list of model inputs!"
      assert len(self.model_inputs) == len(
          X
      ), "Number of model inputs does not match the number given!"
  
      # rank and determine the model outputs that we will explain
      if not tf.executing_eagerly():
          model_output_values = self.run(
              self.model_output, self.model_inputs, X
          )
      else:
          model_output_values = self.run(
              self.model, self.model_inputs, X
          )
      if ranked_outputs is not None and self.multi_output:
          if output_rank_order == "max":
              model_output_ranks = np.argsort(-model_output_values)
          elif output_rank_order == "min":
              model_output_ranks = np.argsort(model_output_values)
          elif output_rank_order == "max_abs":
              model_output_ranks = np.argsort(
                  np.abs(model_output_values)
              )
          elif output_rank_order == "custom":
              model_output_ranks = ranked_outputs
          else:
              emsg = "output_rank_order must be max, min, max_abs or custom!"
              raise ValueError(emsg)
  
          if output_rank_order in ["max", "min", "max_abs"]:
              model_output_ranks = model_output_ranks[
                  :, :ranked_outputs
              ]
      else:
          model_output_ranks = np.tile(
              np.arange(len(self.gradients)), (X[0].shape[0], 1)
          )
  
      # compute the attributions
      output_phis = []
      output_phi_vars = []
      samples_input = [
          np.zeros((nsamples,) + X[t].shape[1:], dtype=np.float32)
          for t in range(len(X))
      ]
      samples_delta = [
          np.zeros((nsamples,) + X[t].shape[1:], dtype=np.float32)
          for t in range(len(X))
      ]
      # use random seed if no argument given
      if rseed is None:
          rseed = np.random.randint(0, 1e6)
  
      for i in range(model_output_ranks.shape[1]):
          np.random.seed(
              rseed
          )  # so we get the same noise patterns for each output class
          phis = []
          phi_vars = []
          for k in range(len(X)):
              phis.append(np.zeros(X[k].shape))
              phi_vars.append(np.zeros(X[k].shape))
          for j in range(X[0].shape[0]):
              # fill in the samples arrays
              for k in range(nsamples):
                  rind = np.random.choice(self.data[0].shape[0])
                  t = np.random.uniform()
                  for u in range(len(X)):
                      if self.local_smoothing > 0:
                          x = (
                              X[u][j]
                              + np.random.randn(*X[u][j].shape)
                              * self.local_smoothing
                          )
                      else:
                          x = X[u][j]
                      samples_input[u][k] = (
                          t * x + (1 - t) * self.data[u][rind]
                      )
                      samples_delta[u][k] = x - self.data[u][rind]
  
              # compute the gradients at all the sample points
              find = model_output_ranks[j, i]
              grads = []
              for b in range(0, nsamples, self.batch_size):
                  batch = [
                      samples_input[a][
                          b : min(b + self.batch_size, nsamples)
                      ]
                      for a in range(len(X))
                  ]
                  grads.append(
                      self.run(
                          self.gradient(find), self.model_inputs, batch
                      )
                  )
              grad = [
                  np.concatenate([g[a] for g in grads], 0)
                  for a in range(len(X))
              ]
  
              # assign the attributions to the right part of the output arrays
              for a in range(len(X)):
                  samples = grad[a] * samples_delta[a]
                  phis[a][j] = samples.mean(0)
                  phi_vars[a][j] = samples.var(0) / np.sqrt(
                      samples.shape[0]
                  )  # estimate variance of means
  
              # TODO: this could be avoided by integrating between endpoints if no local smoothing is used
              # correct the sum of the values to equal the output of the model using a linear
              # regression model with priors of the coefficients equal to the estimated variances for each
              # value (note that 1e-6 is designed to increase the weight of the sample and so closely
              # match the correct sum)
              # if False and self.local_smoothing == 0: # disabled right now to make sure it doesn't mask problems
              #     phis_sum = np.sum([phis[l][j].sum() for l in range(len(X))])
              #     phi_vars_s = np.stack([phi_vars[l][j] for l in range(len(X))], 0).flatten()
              #     if self.multi_output:
              #         sum_error = model_output_values[j,find] - phis_sum - self.expected_value[find]
              #     else:
              #         sum_error = model_output_values[j] - phis_sum - self.expected_value
  
              #     # this is a ridge regression with one sample of all ones with sum_error as the label
              #     # and 1/v as the ridge penalties. This simplified (and stable) form comes from the
              #     # Sherman-Morrison formula
              #     v = (phi_vars_s / phi_vars_s.max()) * 1e6
              #     adj = sum_error * (v - (v * v.sum()) / (1 + v.sum()))
  
              #     # add the adjustment to the output so the sum matches
              #     offset = 0
              #     for l in range(len(X)):
              #         s = np.prod(phis[l][j].shape)
              #         phis[l][j] += adj[offset:offset+s].reshape(phis[l][j].shape)
              #         offset += s
  
          output_phis.append(phis[0] if not self.multi_input else phis)
          output_phi_vars.append(
              phi_vars[0] if not self.multi_input else phi_vars
          )
      if not self.multi_output:
          if return_variances:
              return output_phis[0], output_phi_vars[0]
          else:
              return output_phis[0]
      elif ranked_outputs is not None:
          if return_variances:
              return output_phis, output_phi_vars, model_output_ranks
          else:
              return output_phis, model_output_ranks
      else:
          if return_variances:
              return output_phis, output_phi_vars
          else:
              return output_phis
  
imports: import warnings
  import numpy
  import pandas
  from packaging import version
  from .._explanation import Explanation
  from ..explainers._explainer import Explainer
  from ..explainers.tf_utils import _get_graph, _get_model_inputs, _get_model_output, _get_session
label_desc: Use image node_id 3 for calling the _TFGradient obj's underlying member method code with example usage: obj.shap_values(X, nsamples, ranked_outputs, output_rank_order, rseed, return_variances) and returns: output_phis,  output_phi_vars, output_phis, output_phis,  output_phi_vars,  model_output_ranks, output_phis,  model_output_ranks, output_phis,  output_phi_vars, output_phis