A post-processor version of attack bundling, that chooses the strongest
  example from the output of multiple earlier bundling strategies.

  :param sess: tf.session.Session
  :param model: cleverhans.model.Model
  :param adv_x_list: list of numpy arrays
    Each entry in the list is the output of a previous bundler; it is an
      adversarial version of the whole dataset.
  :param y: numpy array containing true labels
  :param goal: AttackGoal to use to choose the best version of each adversarial
    example
  :param report_path: str, the path the report will be saved to
  :param batch_size: int, batch size

def bundle_examples_with_goal(sess, model, adv_x_list, y, goal,
                              report_path, batch_size=BATCH_SIZE):
  """
  A post-processor version of attack bundling, that chooses the strongest
  example from the output of multiple earlier bundling strategies.

  :param sess: tf.session.Session
  :param model: cleverhans.model.Model
  :param adv_x_list: list of numpy arrays
    Each entry in the list is the output of a previous bundler; it is an
      adversarial version of the whole dataset.
  :param y: numpy array containing true labels
  :param goal: AttackGoal to use to choose the best version of each adversarial
    example
  :param report_path: str, the path the report will be saved to
  :param batch_size: int, batch size
  """

  # Check the input
  num_attacks = len(adv_x_list)
  assert num_attacks > 0
  adv_x_0 = adv_x_list[0]
  assert isinstance(adv_x_0, np.ndarray)
  assert all(adv_x.shape == adv_x_0.shape for adv_x in adv_x_list)

  # Allocate the output
  out = np.zeros_like(adv_x_0)
  m = adv_x_0.shape[0]
  # Initialize with negative sentinel values to make sure everything is
  # written to
  correctness = -np.ones(m, dtype='int32')
  confidence = -np.ones(m, dtype='float32')

  # Gather criteria
  criteria = [goal.get_criteria(sess, model, adv_x, y, batch_size=batch_size) for adv_x in adv_x_list]
  assert all('correctness' in c for c in criteria)
  assert all('confidence' in c for c in criteria)
  _logger.info("Accuracy on each advx dataset: ")
  for c in criteria:
    _logger.info("\t" + str(c['correctness'].mean()))

  for example_idx in range(m):
    # Index of the best attack for this example
    attack_idx = 0
    # Find the winner
    for candidate_idx in range(1, num_attacks):
      if goal.new_wins(criteria[attack_idx], example_idx,
                       criteria[candidate_idx], example_idx):
        attack_idx = candidate_idx
    # Copy the winner into the output
    out[example_idx] = adv_x_list[attack_idx][example_idx]
    correctness[example_idx] = criteria[attack_idx]['correctness'][example_idx]
    confidence[example_idx] = criteria[attack_idx]['confidence'][example_idx]

  assert correctness.min() >= 0
  assert correctness.max() <= 1
  assert confidence.min() >= 0.
  assert confidence.max() <= 1.
  correctness = correctness.astype('bool')
  _logger.info("Accuracy on bundled examples: " + str(correctness.mean()))

  report = ConfidenceReport()
  report['bundled'] = ConfidenceReportEntry(correctness, confidence)
  serial.save(report_path, report)
  assert report_path.endswith('.joblib')
  adv_x_path = report_path[:-len('.joblib')] + "_adv_x.npy"
  np.save(adv_x_path, out)