docstr: Initialize contrastive explanation method.
  Paper: https://arxiv.org/abs/1802.07623
  
  Parameters
  ----------
  predict
      `tensorflow` model or any other model's prediction function returning class probabilities.
  mode
      Find pertinent negatives (PN) or pertinent positives (PP).
  shape
      Shape of input data starting with batch size.
  kappa
      Confidence parameter for the attack loss term.
  beta
      Regularization constant for L1 loss term.
  feature_range
      Tuple with min and max ranges to allow for perturbed instances. Min and max ranges can be `float` or
      `numpy` arrays with dimension (1x nb of features) for feature-wise ranges.
  gamma
      Regularization constant for optional auto-encoder loss term.
  ae_model
      Optional auto-encoder model used for loss regularization.
  learning_rate_init
      Initial learning rate of optimizer.
  max_iterations
      Maximum number of iterations for finding a PN or PP.
  c_init
      Initial value to scale the attack loss term.
  c_steps
      Number of iterations to adjust the constant scaling the attack loss term.
  eps
      If numerical gradients are used to compute `dL/dx = (dL/dp) * (dp/dx)`, then `eps[0]` is used to
      calculate `dL/dp` and `eps[1]` is used for `dp/dx`. `eps[0]` and `eps[1]` can be a combination of `float`
      values and `numpy` arrays. For `eps[0]`, the array dimension should be (1x nb of prediction categories)
      and for `eps[1]` it should be (1x nb of features).
  clip
      Tuple with `min` and `max` clip ranges for both the numerical gradients and the gradients
      obtained from the `tensorflow` graph.
  update_num_grad
      If numerical gradients are used, they will be updated every `update_num_grad` iterations.
  no_info_val
      Global or feature-wise value considered as containing no information.
  write_dir
      Directory to write `tensorboard` files to.
  sess
      Optional `tensorflow` session that will be used if passed instead of creating or inferring one internally.
docstr_tok: ["Initialize","contrastive","explanation","method",".","Paper",":","https",":","\/\/arxiv.org\/abs\/1802.07623","Parameters","--","--","--","--","--","predict","`","tensorflow","`","model","or","any","other","model","'s","prediction","function","returning","class","probabilities",".","mode","Find","pertinent","negatives","(","PN",")","or","pertinent","positives","(","PP",")",".","shape","Shape","of","input","data","starting","with","batch","size",".","kappa","Confidence","parameter","for","the","attack","loss","term",".","beta","Regularization","constant","for","L1","loss","term",".","feature_range","Tuple","with","min","and","max","ranges","to","allow","for","perturbed","instances",".","Min","and","max","ranges","can","be","`","float","`","or","`","numpy","`","arrays","with","dimension","(","1x","nb","of","features",")","for","feature-wise","ranges",".","gamma","Regularization","constant","for","optional","auto-encoder","loss","term",".","ae_model","Optional","auto-encoder","model","used","for","loss","regularization",".","learning_rate_init","Initial","learning","rate","of","optimizer",".","max_iterations","Maximum","number","of","iterations","for","finding","a","PN","or","PP",".","c_init","Initial","value","to","scale","the","attack","loss","term",".","c_steps","Number","of","iterations","to","adjust","the","constant","scaling","the","attack","loss","term",".","eps","If","numerical","gradients","are","used","to","compute","`","dL\/dx","=","(","dL\/dp",")","*","(","dp\/dx",")","`",",","then","`","eps","[","0","]","`","is","used","to","calculate","`","dL\/dp","`","and","`","eps","[","1","]","`","is","used","for","`","dp\/dx","`",".","`","eps","[","0","]","`","and","`","eps","[","1","]","`","can","be","a","combination","of","`","float","`","values","and","`","numpy","`","arrays",".","For","`","eps","[","0","]","`",",","the","array","dimension","should","be","(","1x","nb","of","prediction","categories",")","and","for","`","eps","[","1","]","`","it","should","be","(","1x","nb","of","features",")",".","clip","Tuple","with","`","min","`","and","`","max","`","clip","ranges","for","both","the","numerical","gradients","and","the","gradients","obtained","from","the","`","tensorflow","`","graph",".","update_num_grad","If","numerical","gradients","are","used",",","they","will","be","updated","every","`","update_num_grad","`","iterations",".","no_info_val","Global","or","feature-wise","value","considered","as","containing","no","information",".","write_dir","Directory","to","write","`","tensorboard","`","files","to",".","sess","Optional","`","tensorflow","`","session","that","will","be","used","if","passed","instead","of","creating","or","inferring","one","internally","."]
code: def __init__(
      self,
      predict: Union[
          Callable[[np.ndarray], np.ndarray], tf.keras.Model
      ],
      mode: str,
      shape: tuple,
      kappa: float = 0.0,
      beta: float = 0.1,
      feature_range: tuple = (-1e10, 1e10),
      gamma: float = 0.0,
      ae_model: Optional[tf.keras.Model] = None,
      learning_rate_init: float = 1e-2,
      max_iterations: int = 1000,
      c_init: float = 10.0,
      c_steps: int = 10,
      eps: tuple = (1e-3, 1e-3),
      clip: tuple = (-100.0, 100.0),
      update_num_grad: int = 1,
      no_info_val: Optional[Union[float, np.ndarray]] = None,
      write_dir: Optional[str] = None,
      sess: Optional[tf.Session] = None,
  ) -> None:
      """
      Initialize contrastive explanation method.
      Paper: https://arxiv.org/abs/1802.07623
  
      Parameters
      ----------
      predict
          `tensorflow` model or any other model's prediction function returning class probabilities.
      mode
          Find pertinent negatives (PN) or pertinent positives (PP).
      shape
          Shape of input data starting with batch size.
      kappa
          Confidence parameter for the attack loss term.
      beta
          Regularization constant for L1 loss term.
      feature_range
          Tuple with min and max ranges to allow for perturbed instances. Min and max ranges can be `float` or
          `numpy` arrays with dimension (1x nb of features) for feature-wise ranges.
      gamma
          Regularization constant for optional auto-encoder loss term.
      ae_model
          Optional auto-encoder model used for loss regularization.
      learning_rate_init
          Initial learning rate of optimizer.
      max_iterations
          Maximum number of iterations for finding a PN or PP.
      c_init
          Initial value to scale the attack loss term.
      c_steps
          Number of iterations to adjust the constant scaling the attack loss term.
      eps
          If numerical gradients are used to compute `dL/dx = (dL/dp) * (dp/dx)`, then `eps[0]` is used to
          calculate `dL/dp` and `eps[1]` is used for `dp/dx`. `eps[0]` and `eps[1]` can be a combination of `float`
          values and `numpy` arrays. For `eps[0]`, the array dimension should be (1x nb of prediction categories)
          and for `eps[1]` it should be (1x nb of features).
      clip
          Tuple with `min` and `max` clip ranges for both the numerical gradients and the gradients
          obtained from the `tensorflow` graph.
      update_num_grad
          If numerical gradients are used, they will be updated every `update_num_grad` iterations.
      no_info_val
          Global or feature-wise value considered as containing no information.
      write_dir
          Directory to write `tensorboard` files to.
      sess
          Optional `tensorflow` session that will be used if passed instead of creating or inferring one internally.
      """
      super().__init__(meta=copy.deepcopy(DEFAULT_META_CEM))
      # get params for storage in meta
      params = locals()
      remove = ["self", "predict", "ae_model", "sess", "__class__"]
      for key in remove:
          params.pop(key)
      self.meta["params"].update(params)
      self.predict = predict
  
      # check if the passed object is a model and get session
      is_model = isinstance(predict, tf.keras.Model)
      model_sess = tf.compat.v1.keras.backend.get_session()
      is_ae = isinstance(ae_model, tf.keras.Model)
      self.meta["params"].update(is_model=is_model, is_ae=is_ae)
  
      # if session provided, use it
      if isinstance(sess, tf.Session):
          self.sess = sess
      else:
          self.sess = model_sess
  
      if is_model:  # Keras or TF model
          self.model = True
          classes = self.sess.run(
              self.predict(
                  tf.convert_to_tensor(
                      np.zeros(shape), dtype=tf.float32
                  )
              )
          ).shape[1]
      else:
          self.model = False
          classes = self.predict(np.zeros(shape)).shape[1]
  
      self.mode = mode
      self.shape = shape
      self.kappa = kappa
      self.beta = beta
      self.gamma = gamma
      self.ae = ae_model
      self.batch_size = shape[0]
      self.max_iterations = max_iterations
      self.c_init = c_init
      self.c_steps = c_steps
      self.update_num_grad = update_num_grad
      self.eps = eps
      self.clip = clip
      self.write_dir = write_dir
      if isinstance(no_info_val, float):
          self.no_info_val = np.ones(shape) * no_info_val
      else:
          self.no_info_val = no_info_val  # type: ignore[assignment]
  
      # values regarded as containing no information
      # PNs will deviate away from these values while PPs will gravitate towards them
      self.no_info = tf.Variable(
          np.zeros(shape), dtype=tf.float32, name="no_info"
      )
  
      # define tf variables for original and perturbed instances, and target labels
      self.orig = tf.Variable(
          np.zeros(shape), dtype=tf.float32, name="orig"
      )
      self.adv = tf.Variable(
          np.zeros(shape), dtype=tf.float32, name="adv"
      )  # delta(k)
      self.adv_s = tf.Variable(
          np.zeros(shape), dtype=tf.float32, name="adv_s"
      )  # y(k)
      self.target = tf.Variable(
          np.zeros((self.batch_size, classes)),
          dtype=tf.float32,
          name="target",
      )
  
      # define tf variable for constant used in FISTA optimization
      self.const = tf.Variable(
          np.zeros(self.batch_size), dtype=tf.float32, name="const"
      )
      self.global_step = tf.Variable(
          0.0, trainable=False, name="global_step"
      )
  
      # define placeholders that will be assigned to relevant variables
      self.assign_orig = tf.placeholder(
          tf.float32, shape, name="assign_orig"
      )
      self.assign_adv = tf.placeholder(
          tf.float32, shape, name="assign_adv"
      )
      self.assign_adv_s = tf.placeholder(
          tf.float32, shape, name="assign_adv_s"
      )
      self.assign_target = tf.placeholder(
          tf.float32, (self.batch_size, classes), name="assign_target"
      )
      self.assign_const = tf.placeholder(
          tf.float32, [self.batch_size], name="assign_const"
      )
      self.assign_no_info = tf.placeholder(
          tf.float32, shape, name="assign_no_info"
      )
  
      # define conditions and values for element-wise shrinkage thresholding (eq.7)
      with tf.name_scope("shrinkage_thresholding") as scope:
          cond = [
              tf.cast(
                  tf.greater(
                      tf.subtract(self.adv_s, self.orig), self.beta
                  ),
                  tf.float32,
              ),
              tf.cast(
                  tf.less_equal(
                      tf.abs(tf.subtract(self.adv_s, self.orig)),
                      self.beta,
                  ),
                  tf.float32,
              ),
              tf.cast(
                  tf.less(
                      tf.subtract(self.adv_s, self.orig),
                      tf.negative(self.beta),
                  ),
                  tf.float32,
              ),
          ]
          upper = tf.minimum(
              tf.subtract(self.adv_s, self.beta),
              tf.cast(feature_range[1], tf.float32),
          )
          lower = tf.maximum(
              tf.add(self.adv_s, self.beta),
              tf.cast(feature_range[0], tf.float32),
          )
          self.assign_adv = (
              tf.multiply(cond[0], upper)
              + tf.multiply(cond[1], self.orig)
              + tf.multiply(cond[2], lower)
          )
  
      # perturbation update for delta and vector projection on correct set depending on PP or PN (eq.5)
      # delta(k) = adv; delta(k+1) = assign_adv
      with tf.name_scope("perturbation_delta") as scope:
          proj_d = [
              tf.cast(
                  tf.greater(
                      tf.abs(
                          tf.subtract(self.assign_adv, self.no_info)
                      ),
                      tf.abs(tf.subtract(self.orig, self.no_info)),
                  ),
                  tf.float32,
              ),
              tf.cast(
                  tf.less_equal(
                      tf.abs(
                          tf.subtract(self.assign_adv, self.no_info)
                      ),
                      tf.abs(tf.subtract(self.orig, self.no_info)),
                  ),
                  tf.float32,
              ),
          ]
          if self.mode == "PP":
              self.assign_adv = tf.multiply(
                  proj_d[1], self.assign_adv
              ) + tf.multiply(proj_d[0], self.orig)
          elif self.mode == "PN":
              self.assign_adv = tf.multiply(
                  proj_d[0], self.assign_adv
              ) + tf.multiply(proj_d[1], self.orig)
  
      # perturbation update and vector projection on correct set for y: y(k+1) = assign_adv_s (eq.6)
      with tf.name_scope("perturbation_y") as scope:
          self.zt = tf.divide(
              self.global_step,
              self.global_step + tf.cast(3, tf.float32),
          )  # k/(k+3) in (eq.6)
          self.assign_adv_s = self.assign_adv + tf.multiply(
              self.zt, self.assign_adv - self.adv
          )
          proj_d_s = [
              tf.cast(
                  tf.greater(
                      tf.abs(
                          tf.subtract(self.assign_adv_s, self.no_info)
                      ),
                      tf.abs(tf.subtract(self.orig, self.no_info)),
                  ),
                  tf.float32,
              ),
              tf.cast(
                  tf.less_equal(
                      tf.abs(
                          tf.subtract(self.assign_adv_s, self.no_info)
                      ),
                      tf.abs(tf.subtract(self.orig, self.no_info)),
                  ),
                  tf.float32,
              ),
          ]
          if self.mode == "PP":
              self.assign_adv_s = tf.multiply(
                  proj_d_s[1], self.assign_adv_s
              ) + tf.multiply(proj_d_s[0], self.orig)
          elif self.mode == "PN":
              self.assign_adv_s = tf.multiply(
                  proj_d_s[0], self.assign_adv_s
              ) + tf.multiply(proj_d_s[1], self.orig)
  
      # delta(k) <- delta(k+1);  y(k) <- y(k+1)
      with tf.name_scope("update_adv") as scope:
          self.adv_updater = tf.assign(self.adv, self.assign_adv)
          self.adv_updater_s = tf.assign(self.adv_s, self.assign_adv_s)
  
      # from perturbed instance, derive deviation delta
      with tf.name_scope("update_delta") as scope:
          self.delta = self.orig - self.adv
          self.delta_s = self.orig - self.adv_s
  
      # define L1 and L2 loss terms; L1+L2 is later used as an optimization constraint for FISTA
      ax_sum = list(np.arange(1, len(shape)))
      with tf.name_scope("loss_l1_l2") as scope:
          self.l2 = tf.reduce_sum(tf.square(self.delta), axis=ax_sum)
          self.l2_s = tf.reduce_sum(
              tf.square(self.delta_s), axis=ax_sum
          )
          self.l1 = tf.reduce_sum(tf.abs(self.delta), axis=ax_sum)
          self.l1_s = tf.reduce_sum(tf.abs(self.delta_s), axis=ax_sum)
          self.l1_l2 = self.l2 + tf.multiply(self.l1, self.beta)
          self.l1_l2_s = self.l2_s + tf.multiply(self.l1_s, self.beta)
  
          # sum losses
          self.loss_l1 = tf.reduce_sum(self.l1)
          self.loss_l1_s = tf.reduce_sum(self.l1_s)
          self.loss_l2 = tf.reduce_sum(self.l2)
          self.loss_l2_s = tf.reduce_sum(self.l2_s)
  
      with tf.name_scope("loss_ae") as scope:
          # gamma * AE loss
          if self.mode == "PP" and callable(self.ae):
              self.loss_ae = self.gamma * tf.square(
                  tf.norm(self.ae(self.delta) - self.delta)
              )
              self.loss_ae_s = self.gamma * tf.square(
                  tf.norm(self.ae(self.delta_s) - self.delta_s)
              )
          elif self.mode == "PN" and callable(self.ae):
              self.loss_ae = self.gamma * tf.square(
                  tf.norm(self.ae(self.adv) - self.adv)
              )
              self.loss_ae_s = self.gamma * tf.square(
                  tf.norm(self.ae(self.adv_s) - self.adv_s)
              )
          else:  # no auto-encoder available
              self.loss_ae = tf.constant(0.0)
              self.loss_ae_s = tf.constant(0.0)
  
      with tf.name_scope("loss_attack") as scope:
          if not self.model:
              self.loss_attack = tf.placeholder(tf.float32)
          else:
              # make predictions on perturbed instance (PN) or delta (PP)
              if self.mode == "PP":
                  self.pred_proba = self.predict(self.delta)
                  self.pred_proba_s = self.predict(self.delta_s)
              elif self.mode == "PN":
                  self.pred_proba = self.predict(self.adv)
                  self.pred_proba_s = self.predict(self.adv_s)
  
              # probability of target label prediction
              self.target_proba = tf.reduce_sum(
                  self.target * self.pred_proba, 1
              )
              target_proba_s = tf.reduce_sum(
                  self.target * self.pred_proba_s, 1
              )
  
              # max probability of non target label prediction
              self.nontarget_proba_max = tf.reduce_max(
                  (1 - self.target) * self.pred_proba
                  - (self.target * 10000),
                  1,
              )
              nontarget_proba_max_s = tf.reduce_max(
                  (1 - self.target) * self.pred_proba_s
                  - (self.target * 10000),
                  1,
              )
  
              # loss term f(x,d) for PP (eq.4) and PN (eq.2)
              if self.mode == "PP":
                  loss_attack = tf.maximum(
                      0.0,
                      self.nontarget_proba_max
                      - self.target_proba
                      + self.kappa,
                  )
                  loss_attack_s = tf.maximum(
                      0.0,
                      nontarget_proba_max_s
                      - target_proba_s
                      + self.kappa,
                  )
              elif self.mode == "PN":
                  loss_attack = tf.maximum(
                      0.0,
                      -self.nontarget_proba_max
                      + self.target_proba
                      + self.kappa,
                  )
                  loss_attack_s = tf.maximum(
                      0.0,
                      -nontarget_proba_max_s
                      + target_proba_s
                      + self.kappa,
                  )
  
              # c * f(x,d)
              self.loss_attack = tf.reduce_sum(self.const * loss_attack)
              self.loss_attack_s = tf.reduce_sum(
                  self.const * loss_attack_s
              )
  
      with tf.name_scope("loss_combined") as scope:
          # no need for L1 term in loss to optimize when using FISTA
          if self.model:
              self.loss_opt = (
                  self.loss_attack_s + self.loss_l2_s + self.loss_ae_s
              )
          else:  # separate numerical computation of loss attack gradient
              self.loss_opt = self.loss_l2_s + self.loss_ae_s
  
          # add L1 term to overall loss; this is not the loss that will be directly optimized
          self.loss_total = (
              self.loss_attack
              + self.loss_l2
              + self.loss_ae
              + tf.multiply(self.beta, self.loss_l1)
          )
  
      with tf.name_scope("training") as scope:
          self.learning_rate = tf.train.polynomial_decay(
              learning_rate_init,
              self.global_step,
              self.max_iterations,
              0,
              power=0.5,
          )
          optimizer = tf.train.GradientDescentOptimizer(
              self.learning_rate
          )
          start_vars = set(x.name for x in tf.global_variables())
  
          # first compute, then apply grads
          self.compute_grads = optimizer.compute_gradients(
              self.loss_opt, var_list=[self.adv_s]
          )
          self.grad_ph = tf.placeholder(tf.float32, name="grad_adv_s")
          var = [
              tvar
              for tvar in tf.trainable_variables()
              if tvar.name.startswith("adv_s")
          ][
              -1
          ]  # get the last in
          # case explainer is re-initialized and a new graph is created
          grad_and_var = [(self.grad_ph, var)]
          self.apply_grads = optimizer.apply_gradients(
              grad_and_var, global_step=self.global_step
          )
          end_vars = tf.global_variables()
          new_vars = [x for x in end_vars if x.name not in start_vars]
  
      # variables to initialize
      self.setup: list = []
      self.setup.append(self.orig.assign(self.assign_orig))
      self.setup.append(self.target.assign(self.assign_target))
      self.setup.append(self.const.assign(self.assign_const))
      self.setup.append(self.adv.assign(self.assign_adv))
      self.setup.append(self.adv_s.assign(self.assign_adv_s))
      self.setup.append(self.no_info.assign(self.assign_no_info))
  
      self.init = tf.variables_initializer(
          var_list=[self.global_step]
          + [self.adv_s]
          + [self.adv]
          + new_vars
      )
  
      if self.write_dir is not None:
          writer = tf.summary.FileWriter(
              write_dir, tf.get_default_graph()
          )
          writer.add_graph(tf.get_default_graph())
  
file_path: alibi/alibi/explainers/cem.py
imports: import copy
  import logging
  import sys
  from typing import Any, Callable, Optional, Tuple, Union
  import numpy
  import tensorflow.compat.v1
  from alibi.api.defaults import DEFAULT_DATA_CEM, DEFAULT_META_CEM
  from alibi.api.interfaces import Explainer, Explanation, FitMixin
label_desc: Use image node_id 1 to create a new CEM object from inherited base classes: Explainer, FitMixin with example: obj = CEM(predict, mode, shape, kappa, beta, feature_range, gamma, ae_model, learning_rate_init, max_iterations, c_init, c_steps, eps, clip, update_num_grad, no_info_val, write_dir, sess)