docstr: Find pertinent negative or pertinent positive for instance `X` using a fast iterative
  shrinkage-thresholding algorithm (FISTA).
  
  Parameters
  ----------
  X
      Instance to attack.
  Y
      Labels for `X`.
  verbose
      Print intermediate results of optimization if ``True``.
  
  Returns
  -------
  Overall best attack and gradients for that attack.
docstr_tok: ["Find","pertinent","negative","or","pertinent","positive","for","instance","`","X","`","using","a","fast","iterative","shrinkage-thresholding","algorithm","(","FISTA",")",".","Parameters","--","--","--","--","--","X","Instance","to","attack",".","Y","Labels","for","`","X","`",".","verbose","Print","intermediate","results","of","optimization","if","``","True","``",".","Returns","--","--","--","-","Overall","best","attack","and","gradients","for","that","attack","."]
code: def attack(
      self, X: np.ndarray, Y: np.ndarray, verbose: bool = False
  ) -> Tuple[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
      """
      Find pertinent negative or pertinent positive for instance `X` using a fast iterative
      shrinkage-thresholding algorithm (FISTA).
  
      Parameters
      ----------
      X
          Instance to attack.
      Y
          Labels for `X`.
      verbose
          Print intermediate results of optimization if ``True``.
  
      Returns
      -------
      Overall best attack and gradients for that attack.
      """
  
      # make sure nb of instances in X equals batch size
      assert self.batch_size == X.shape[0]
  
      # check if no info value has been set either through init or fit
      if self.no_info_val is None:
          logger.exception(
              'No value specified for "no_info_val" through init or fit method.'
          )
          raise ValueError
  
      def compare(x: Union[float, int, np.ndarray], y: int) -> bool:
          """
          Compare predictions with target labels and return whether PP or PN conditions hold.
  
          Parameters
          ----------
          x
              Predicted class probabilities or labels.
          y
              Target or predicted labels.
  
          Returns
          -------
          Bool whether PP or PN conditions hold.
          """
          if not isinstance(x, (float, int, np.int64)):
              x = np.copy(x)
              if self.mode == "PP":
                  x[y] -= self.kappa
              elif self.mode == "PN":
                  x[y] += self.kappa
              x = np.argmax(x)  # type:ignore
          if self.mode == "PP":
              return x == y
          else:
              return x != y
  
      # set the lower and upper bounds for the constant 'c' to scale the attack loss term
      # these bounds are updated for each c_step iteration
      const_lb = np.zeros(self.batch_size)
      const = np.ones(self.batch_size) * self.c_init
      const_ub = np.ones(self.batch_size) * 1e10
  
      # init values for the best attack instances for each instance in the batch
      overall_best_dist = [1e10] * self.batch_size
      overall_best_attack = [np.zeros(self.shape[1:])] * self.batch_size
      overall_best_grad = (np.zeros(self.shape), np.zeros(self.shape))
  
      # iterate over nb of updates for 'c'
      for _ in range(self.c_steps):
          # init variables
          self.sess.run(self.init)
  
          # reset current best distances and scores
          current_best_dist = [1e10] * self.batch_size
          current_best_proba = [-1] * self.batch_size
  
          # assign variables for the current iteration
          self.sess.run(
              self.setup,
              {
                  self.assign_orig: X,
                  self.assign_target: Y,
                  self.assign_const: const,
                  self.assign_adv: X,
                  self.assign_adv_s: X,
                  self.assign_no_info: self.no_info_val,
              },
          )
  
          X_der_batch: Any = []
          X_der_batch_s: Any = []
  
          for i in range(self.max_iterations):
              # numerical gradients
              grads_num = np.zeros(self.shape)
              grads_num_s = np.zeros(self.shape)
  
              if not self.model:
                  if self.mode == "PP":
                      X_der = self.delta.eval(session=self.sess)
                      X_der_s = self.delta_s.eval(session=self.sess)
                  elif self.mode == "PN":
                      X_der = self.adv.eval(session=self.sess)
                      X_der_s = self.adv_s.eval(session=self.sess)
                  X_der_batch.append(X_der)
                  X_der_batch_s.append(X_der_s)
  
                  if (
                      i % self.update_num_grad == 0 and i > 0
                  ):  # compute numerical gradients
                      c = self.const.eval(session=self.sess)
                      X_der_batch = np.concatenate(X_der_batch)
                      X_der_batch_s = np.concatenate(X_der_batch_s)
                      grads_num = self.get_gradients(X_der_batch, Y) * c
                      grads_num_s = (
                          self.get_gradients(X_der_batch_s, Y) * c
                      )
                      # clip gradients
                      grads_num = np.clip(
                          grads_num, self.clip[0], self.clip[1]
                      )
                      grads_num_s = np.clip(
                          grads_num_s, self.clip[0], self.clip[1]
                      )
                      X_der_batch, X_der_batch_s = [], []
  
              # compute and clip gradients defined in graph
              grads_vars_graph = self.sess.run(self.compute_grads)
              grads_graph = [g for g, _ in grads_vars_graph][0]
              grads_graph = np.clip(
                  grads_graph, self.clip[0], self.clip[1]
              )
  
              # apply gradients
              grads = grads_graph + grads_num_s
              self.sess.run(
                  self.apply_grads, feed_dict={self.grad_ph: grads}
              )
  
              # update adv and adv_s with perturbed instances
              self.sess.run(
                  [
                      self.adv_updater,
                      self.adv_updater_s,
                      self.delta,
                      self.delta_s,
                  ]
              )
  
              # compute overall and attack loss, L1+L2 loss, prediction probabilities
              # on perturbed instances and new adv
              # L1+L2 and prediction probabilities used to see if adv is better than the current best adv under FISTA
              if self.model:
                  (
                      loss_tot,
                      loss_attack,
                      loss_l1_l2,
                      pred_proba,
                      adv,
                  ) = self.sess.run(
                      [
                          self.loss_total,
                          self.loss_attack,
                          self.l1_l2,
                          self.pred_proba,
                          self.adv,
                      ]
                  )
              else:
                  # get updated perturbed instances
                  if self.mode == "PP":
                      X_der = self.delta.eval(session=self.sess)
                  elif self.mode == "PN":
                      X_der = self.adv.eval(session=self.sess)
                  pred_proba = self.predict(X_der)
  
                  # compute attack, total and L1+L2 losses as well as new perturbed instance
                  loss_attack = self.loss_fn(pred_proba, Y)
                  feed_dict = {self.loss_attack: loss_attack}
                  loss_tot, loss_l1_l2, adv = self.sess.run(
                      [self.loss_total, self.l1_l2, self.adv],
                      feed_dict=feed_dict,
                  )
  
              if verbose and i % (self.max_iterations // 10) == 0:
                  loss_l2, loss_l1, loss_ae = self.sess.run(
                      [self.loss_l2, self.loss_l1, self.loss_ae]
                  )
                  target_proba = np.sum(pred_proba * Y)
                  nontarget_proba_max = np.max((1 - Y) * pred_proba)
                  print(
                      "\nIteration: {}; Const: {}".format(i, const[0])
                  )
                  print(
                      "Loss total: {:.3f}, loss attack: {:.3f}".format(
                          loss_tot, loss_attack
                      )
                  )
                  print(
                      "L2: {:.3f}, L1: {:.3f}, loss AE: {:.3f}".format(
                          loss_l2, loss_l1, loss_ae
                      )
                  )
                  print(
                      "Target proba: {:.2f}, max non target proba: {:.2f}".format(
                          target_proba, nontarget_proba_max
                      )
                  )
                  print(
                      "Gradient graph min/max: {:.3f}/{:.3f}".format(
                          grads_graph.min(), grads_graph.max()
                      )
                  )
                  print(
                      "Gradient graph mean/abs mean: {:.3f}/{:.3f}".format(
                          np.mean(grads_graph),
                          np.mean(np.abs(grads_graph)),
                      )
                  )
                  if not self.model:
                      print(
                          "Gradient numerical attack min/max: {:.3f}/{:.3f}".format(
                              grads_num.min(), grads_num.max()
                          )
                      )
                      print(
                          "Gradient numerical mean/abs mean: {:.3f}/{:.3f}".format(  # type: ignore[str-format]
                              np.mean(grads_num),
                              np.mean(np.abs(grads_num)),
                          )
                      )
                  sys.stdout.flush()
  
              # update best perturbation (distance) and class probabilities
              # if beta * L1 + L2 < current best and predicted label is the same as the initial label (for PP) or
              # different from the initial label (for PN); update best current step or global perturbations
              for batch_idx, (dist, proba, adv_idx) in enumerate(
                  zip(loss_l1_l2, pred_proba, adv)
              ):
                  # current step
                  if dist < current_best_dist[batch_idx] and compare(proba, np.argmax(Y[batch_idx])):  # type: ignore
                      current_best_dist[batch_idx] = dist
                      current_best_proba[batch_idx] = np.argmax(proba)  # type: ignore
  
                  # global
                  if dist < overall_best_dist[batch_idx] and compare(proba, np.argmax(Y[batch_idx])):  # type: ignore
                      if verbose:
                          print(
                              "\nNew best {} found!".format(self.mode)
                          )
                      overall_best_dist[batch_idx] = dist
                      overall_best_attack[batch_idx] = adv_idx
                      overall_best_grad = (grads_graph, grads_num)
                      self.best_attack = True
  
          # adjust the 'c' constant for the first loss term
          for batch_idx in range(self.batch_size):
              if (
                  compare(
                      current_best_proba[batch_idx],
                      np.argmax(Y[batch_idx]),
                  )
                  and current_best_proba[batch_idx]  # type: ignore
                  != -1
              ):
                  # want to refine the current best solution by putting more emphasis on the regularization terms
                  # of the loss by reducing 'c'; aiming to find a perturbation closer to the original instance
                  const_ub[batch_idx] = min(
                      const_ub[batch_idx], const[batch_idx]
                  )
                  if const_ub[batch_idx] < 1e9:
                      const[batch_idx] = (
                          const_lb[batch_idx] + const_ub[batch_idx]
                      ) / 2
              else:
                  # no valid current solution; put more weight on the first loss term to try and meet the
                  # prediction constraint before finetuning the solution with the regularization terms
                  const_lb[batch_idx] = max(
                      const_lb[batch_idx], const[batch_idx]
                  )  # update lower bound to constant
                  if const_ub[batch_idx] < 1e9:
                      const[batch_idx] = (
                          const_lb[batch_idx] + const_ub[batch_idx]
                      ) / 2
                  else:
                      const[batch_idx] *= 10
  
      # return best overall attack
      best_attack = np.concatenate(overall_best_attack, axis=0)
      if best_attack.shape != self.shape:
          best_attack = np.expand_dims(best_attack, axis=0)
  
      # adjust for PP
      if self.mode == "PP":
          best_attack = X - best_attack
      return best_attack, overall_best_grad
  
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 6 for calling the CEM obj's underlying member method code with example usage: obj.attack(X, Y, verbose) and returns: best_attack,  overall_best_grad, unknown, unknown