code: def projected_optimization(
      loss_fn,
      input_image,
      label,
      epsilon,
      num_steps,
      optimizer,
      clip_min=None,
      clip_max=None,
      early_stop_loss_threshold=None,
      project_perturbation=_project_perturbation,
      is_debug=False,
  ):
      """
      Generic projected optimization, generalized to work with approximate gradients. Used for e.g.
      the SPSA attack.
  
      Args:
        :param loss_fn: A callable which takes `input_image` and `label` as
                        arguments, and returns a batch of loss values.
        :param input_image: Tensor, a batch of images
        :param label: Tensor, a batch of labels
        :param epsilon: float, the L-infinity norm of the maximum allowable
                        perturbation
        :param num_steps: int, the number of steps of gradient descent
        :param optimizer: A `SPSAAdam` object
        :param clip_min: float, minimum pixel value
        :param clip_max: float, maximum pixel value
        :param project_perturbation: A function, which will be used to enforce
                                     some constraint. It should have the same
                                     signature as `_project_perturbation`.
        :param early_stop_loss_threshold: A float or None. If specified, the attack will end if the loss is below
           `early_stop_loss_threshold`.
            Enabling this option can have several different effects:
              - Setting the threshold to 0. guarantees that if a successful attack is found, it is returned.
                This increases the attack success rate, because without early stopping the optimizer can accidentally
                bounce back to a point where the attack fails.
              - Early stopping can make the attack run faster because it may run for fewer steps.
              - Early stopping can make the attack run slower because the loss must be calculated at each step.
                The loss is not calculated as part of the normal SPSA optimization procedure.
                For most reasonable choices of hyperparameters, early stopping makes the attack much faster because
                it decreases the number of steps dramatically.
        :param is_debug: A bool. If True, print debug info for attack progress.
  
      Returns:
        adversarial version of `input_image`, with L-infinity difference less than epsilon, which tries
        to minimize loss_fn.
  
      Note that this function is not intended as an Attack by itself. Rather, it is designed as a helper
      function which you can use to write your own attack methods. The method uses a tf.while_loop to
      optimize a loss function in a single sess.run() call.
      """
      assert num_steps is not None
      if is_debug:
          with tf.device("/cpu:0"):
              tf.print("Starting PGD attack with epsilon: %s" % epsilon)
  
      init_perturbation = tf.random.uniform(
          tf.shape(input_image),
          minval=tf.cast(-epsilon, input_image.dtype),
          maxval=tf.cast(epsilon, input_image.dtype),
          dtype=input_image.dtype,
      )
      init_perturbation = project_perturbation(
          init_perturbation,
          epsilon,
          input_image,
          clip_min=clip_min,
          clip_max=clip_max,
      )
      init_optim_state = optimizer.init_state([init_perturbation])
  
      def loop_body(i, perturbation, flat_optim_state):
          """Update perturbation to input image."""
          optim_state = tf.nest.pack_sequence_as(
              structure=init_optim_state, flat_sequence=flat_optim_state
          )
  
          def wrapped_loss_fn(x):
              return loss_fn(input_image + x, label)
  
          new_perturbation_list, new_optim_state = optimizer.minimize(
              wrapped_loss_fn, [perturbation], optim_state
          )
          projected_perturbation = project_perturbation(
              new_perturbation_list[0],
              epsilon,
              input_image,
              clip_min=clip_min,
              clip_max=clip_max,
          )
  
          # Be careful with this bool. A value of 0. is a valid threshold but evaluates to False, so we
          # must explicitly check whether the value is None.
          early_stop = early_stop_loss_threshold is not None
          compute_loss = is_debug or early_stop
          # Don't waste time building the loss graph if we're not going to use it
          if compute_loss:
              # NOTE: this step is not actually redundant with the optimizer step.
              # SPSA calculates the loss at randomly perturbed points but doesn't calculate the loss at the current point.
              loss = tf.reduce_mean(
                  wrapped_loss_fn(projected_perturbation), axis=0
              )
  
              if is_debug:
                  with tf.device("/cpu:0"):
                      tf.print(loss, "Total batch loss")
  
              if early_stop:
                  i = tf.cond(
                      tf.less(loss, early_stop_loss_threshold),
                      lambda: float(num_steps),
                      lambda: i,
                  )
  
          return (
              i + 1,
              projected_perturbation,
              tf.nest.flatten(new_optim_state),
          )
  
      def cond(i, *_):
          return tf.less(i, num_steps)
  
      flat_init_optim_state = tf.nest.flatten(init_optim_state)
      _, final_perturbation, _ = tf.while_loop(
          cond,
          loop_body,
          loop_vars=(
              tf.constant(0.0),
              init_perturbation,
              flat_init_optim_state,
          ),
          parallel_iterations=1,
          back_prop=False,
          maximum_iterations=num_steps,
      )
  
      if project_perturbation is _project_perturbation:
          # TODO: this assert looks totally wrong.
          # Not bothering to fix it now because it's only an assert.
          # 1) Multiplying by 1.1 gives a huge margin of error. This should probably take the difference
          #    and allow a tolerance of 1e-6 or something like that.
          # 2) I think it should probably check the *absolute value* of final_perturbation
          perturbation_max = epsilon * 1.1
          check_diff = tf.debugging.assert_less_equal(
              final_perturbation,
              tf.cast(perturbation_max, final_perturbation.dtype),
              message="final_perturbation must change no pixel by more than %s"
              % perturbation_max,
          )
      else:
          # TODO: let caller pass in a check_diff function as well as
          # project_perturbation
          check_diff = tf.no_op()
  
      if clip_min is None or clip_max is None:
          raise NotImplementedError(
              "This function only supports clipping for now"
          )
      check_range = [
          tf.debugging.assert_less_equal(
              input_image, tf.cast(clip_max, input_image.dtype)
          ),
          tf.debugging.assert_greater_equal(
              input_image, tf.cast(clip_min, input_image.dtype)
          ),
      ]
  
      with tf.control_dependencies([check_diff] + check_range):
          adversarial_image = input_image + final_perturbation
      return tf.stop_gradient(adversarial_image)
  
file_path: cleverhans/cleverhans/tf2/attacks/spsa.py
imports: import tensorflow
label_desc: Use image node_id 4 for calling a global function with example usage: projected_optimization(loss_fn, input_image, label, epsilon, num_steps, optimizer, clip_min, clip_max, early_stop_loss_threshold, project_perturbation, is_debug) and returns: tf, unknown,  projected_perturbation,  tf, tf, loss_fn