def __init__(
        self,
        estimator: "CLASSIFIER_LOSS_GRADIENTS_TYPE",
        norm: int | float | str = np.inf,
        targeted: bool = False,
        overshoot: float = 1.1,
        steps: int = 1000,
        lr: float = 1e-3,
        lr_decay: float = 0.5,
        lr_num_decay: int = 20,
        momentum: float = 0.8,
        binary_search_steps: int = 10,
        init_size: int = 100,
        batch_size: int = 32,
    ):
        """
        :param estimator: A trained ART classifier providing loss gradients.
        :param norm: The norm of the adversarial perturbation. Possible values: "inf", np.inf, 1 or 2.
        :param targeted: Flag determining if attack is targeted.
        :param overshoot: If 1 the attack tries to return exactly to the adversarial boundary in each iteration. For
                          higher values the attack tries to overshoot over the boundary to ensure that the perturbed
                          sample in each iteration is adversarial.
        :param steps: Maximum number of iterations to run. Might converge and stop before that.
        :param lr: Trust region radius, behaves similar to a learning rate. Smaller values decrease the step size in
                   each iteration and ensure that the attack follows the boundary more faithfully.
        :param lr_decay: The trust region lr is multiplied with lr_decay in regular intervals (see lr_num_decay).
        :param lr_num_decay: Number of learning rate decays in regular intervals of length steps / lr_num_decay.
        :param momentum: Averaging of the boundary estimation over multiple steps. A momentum of zero would always take
                         the current estimate while values closer to one average over a larger number of iterations.
        :param binary_search_steps: Number of binary search steps used to find the adversarial boundary between the
                                    starting point and the clean image.
        :param init_size: Maximum number of random search steps to find initial adversarial example.
        :param batch_size: Batch size for evaluating the model for predictions and gradients.
        """
        from art.estimators.classification import TensorFlowV2Classifier, PyTorchClassifier

        if isinstance(estimator, TensorFlowV2Classifier):
            import tensorflow as tf

            if is_probability(estimator.predict(x=np.ones(shape=(1, *estimator.input_shape)))):
                raise ValueError(  # pragma: no cover
                    "The provided estimator seems to predict probabilities. If loss_type='difference_logits_ratio' "
                    "the estimator has to to predict logits."
                )
            else:

                def logits_difference(y_true, y_pred):
                    i_y_true = tf.cast(tf.math.argmax(tf.cast(y_true, tf.int32), axis=1), tf.int32)
                    i_y_pred_arg = tf.argsort(y_pred, axis=1)
                    i_z_i_list = []

                    for i in range(y_true.shape[0]):
                        if i_y_pred_arg[i, -1] != i_y_true[i]:
                            i_z_i_list.append(i_y_pred_arg[i, -1])
                        else:
                            i_z_i_list.append(i_y_pred_arg[i, -2])

                    i_z_i = tf.stack(i_z_i_list)

                    z_i = tf.gather(y_pred, i_z_i, axis=1, batch_dims=0)
                    z_y = tf.gather(y_pred, i_y_true, axis=1, batch_dims=0)

                    z_i = tf.linalg.diag_part(z_i)
                    z_y = tf.linalg.diag_part(z_y)

                    logits_diff = z_y - z_i

                    return tf.reduce_mean(logits_diff)

                self._loss_fn = logits_difference
                self._loss_object = logits_difference

            estimator_bb: "CLASSIFIER_LOSS_GRADIENTS_TYPE" = TensorFlowV2Classifier(
                model=estimator.model,
                nb_classes=estimator.nb_classes,
                input_shape=estimator.input_shape,
                loss_object=self._loss_object,
                optimizer=estimator.optimizer,
                train_step=estimator.train_step,
                channels_first=estimator.channels_first,
                clip_values=estimator.clip_values,
                preprocessing_defences=estimator.preprocessing_defences,
                postprocessing_defences=estimator.postprocessing_defences,
                preprocessing=estimator.preprocessing,
            )

        elif isinstance(estimator, PyTorchClassifier):
            import torch

            if is_probability(
                estimator.predict(x=np.ones(shape=(1, *estimator.input_shape), dtype=config.ART_NUMPY_DTYPE))
            ):
                raise ValueError(  # pragma: no cover
                    "The provided estimator seems to predict probabilities. If loss_type='difference_logits_ratio' "
                    "the estimator has to to predict logits."
                )
            else:

                # def difference_logits_ratio(y_true, y_pred):
                def logits_difference(y_pred, y_true):  # type: ignore
                    if isinstance(y_true, np.ndarray):  # pragma: no cover
                        y_true = torch.from_numpy(y_true)
                    if isinstance(y_pred, np.ndarray):  # pragma: no cover
                        y_pred = torch.from_numpy(y_pred)

                    y_true = y_true.float()

                    i_y_true = torch.argmax(y_true, axis=1)
                    i_y_pred_arg = torch.argsort(y_pred, axis=1)
                    i_z_i_list = []

                    for i in range(y_true.shape[0]):
                        if i_y_pred_arg[i, -1] != i_y_true[i]:
                            i_z_i_list.append(i_y_pred_arg[i, -1])
                        else:
                            i_z_i_list.append(i_y_pred_arg[i, -2])

                    i_z_i = torch.stack(i_z_i_list)

                    z_i = y_pred[:, i_z_i]
                    z_y = y_pred[:, i_y_true]

                    z_i = torch.diagonal(z_i)
                    z_y = torch.diagonal(z_y)

                    logits_diff = z_y - z_i

                    return torch.mean(logits_diff.float())

                self._loss_fn = logits_difference
                self._loss_object = logits_difference

            estimator_bb = PyTorchClassifier(
                model=estimator.model,
                loss=self._loss_object,  # type: ignore
                input_shape=estimator.input_shape,
                nb_classes=estimator.nb_classes,
                optimizer=None,
                channels_first=estimator.channels_first,
                clip_values=estimator.clip_values,
                preprocessing_defences=estimator.preprocessing_defences,
                postprocessing_defences=estimator.postprocessing_defences,
                preprocessing=estimator.preprocessing,
                device_type=str(estimator._device),
            )

        else:
            logger.warning(
                "The type of the provided estimator is not yet support for automated setting of logits "
                "difference loss. Therefore, this attack is defaulting to attacking the loss provided by "
                "the model in the provided estimator."
            )
            estimator_bb = estimator

        super().__init__(estimator=estimator_bb)

        self.norm = norm
        self._targeted = targeted
        self.overshoot = overshoot
        self.steps = steps
        self.lr = lr
        self.lr_decay = lr_decay
        self.lr_num_decay = lr_num_decay
        self.momentum = momentum
        self.binary_search_steps = binary_search_steps
        self.init_size = init_size
        self.batch_size = batch_size
        self._check_params()

        self._optimizer: Optimizer
        if norm == 0:
            self._optimizer = L0Optimizer()
        if norm == 1:
            self._optimizer = L1Optimizer()
        elif norm == 2:
            self._optimizer = L2Optimizer()
        elif norm in ["inf", np.inf]:
            self._optimizer = LinfOptimizer()

        # Set binary search threshold
        if norm == 2:
            self.theta = 0.01 / np.sqrt(np.prod(self.estimator.input_shape))
        else:
            self.theta = 0.01 / np.prod(self.estimator.input_shape)

        :param estimator: A trained ART classifier providing loss gradients.
        :param norm: The norm of the adversarial perturbation. Possible values: "inf", np.inf, 1 or 2.
        :param targeted: Flag determining if attack is targeted.
        :param overshoot: If 1 the attack tries to return exactly to the adversarial boundary in each iteration. For
                          higher values the attack tries to overshoot over the boundary to ensure that the perturbed
                          sample in each iteration is adversarial.
        :param steps: Maximum number of iterations to run. Might converge and stop before that.
        :param lr: Trust region radius, behaves similar to a learning rate. Smaller values decrease the step size in
                   each iteration and ensure that the attack follows the boundary more faithfully.
        :param lr_decay: The trust region lr is multiplied with lr_decay in regular intervals (see lr_num_decay).
        :param lr_num_decay: Number of learning rate decays in regular intervals of length steps / lr_num_decay.
        :param momentum: Averaging of the boundary estimation over multiple steps. A momentum of zero would always take
                         the current estimate while values closer to one average over a larger number of iterations.
        :param binary_search_steps: Number of binary search steps used to find the adversarial boundary between the
                                    starting point and the clean image.
        :param init_size: Maximum number of random search steps to find initial adversarial example.
        :param batch_size: Batch size for evaluating the model for predictions and gradients.
        
art/attacks/evasion/brendel_bethge.py