def __init__(
        self,
        estimator: "CLASSIFIER_LOSS_GRADIENTS_TYPE",
        norm: int | float | str = np.inf,
        eps: float = 0.3,
        eps_step: float = 0.1,
        max_iter: int = 100,
        targeted: bool = False,
        nb_random_init: int = 5,
        batch_size: int = 32,
        loss_type: str | None = None,
        verbose: bool = True,
    ):
        """
        Create a :class:`.AutoProjectedGradientDescent` instance.

        :param estimator: A trained estimator.
        :param norm: The norm of the adversarial perturbation. Possible values: "inf", np.inf, 1 or 2.
        :param eps: Maximum perturbation that the attacker can introduce.
        :param eps_step: Attack step size (input variation) at each iteration.
        :param max_iter: The maximum number of iterations.
        :param targeted: Indicates whether the attack is targeted (True) or untargeted (False).
        :param nb_random_init: Number of random initialisations within the epsilon ball. For num_random_init=0
            starting at the original input.
        :param batch_size: Size of the batch on which adversarial samples are generated.
        :param loss_type: Defines the loss to attack. Available options: None (Use loss defined by estimator),
            "cross_entropy", or "difference_logits_ratio"
        :param verbose: Show progress bars.
        """
        from art.estimators.classification import TensorFlowClassifier, TensorFlowV2Classifier, PyTorchClassifier

        if isinstance(estimator, TensorFlowClassifier):
            raise ValueError("This attack does not support TensorFlow  v1.")

        if loss_type not in self._predefined_losses:
            raise ValueError(
                f"The argument loss_type has an invalid value. The following options for `loss_type` are currently "
                f"supported: {self._predefined_losses}"
            )

        if loss_type is None:
            if hasattr(estimator, "predict") and is_probability(
                estimator.predict(x=np.ones(shape=(1, *estimator.input_shape), dtype=np.float32))
            ):
                raise ValueError(  # pragma: no cover
                    "AutoProjectedGradientDescent is expecting logits as estimator output, the provided "
                    "estimator seems to predict probabilities."
                )

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

                class TensorFlowV2Loss:
                    """abstract class of loss function of tensorflow v2"""

                    @abc.abstractmethod
                    def __call__(self, y_true: tf.Tensor, y_pred: tf.Tensor, *args, **kwargs) -> tf.Tensor:
                        raise NotImplementedError()

                if loss_type == "cross_entropy":
                    # modification for image-wise stepsize update
                    class CrossEntropyLossV2(TensorFlowV2Loss):
                        """Class defining cross entropy loss with reduction options."""

                        def __init__(self, from_logits, reduction="mean"):
                            self.ce_loss = tf.keras.losses.CategoricalCrossentropy(
                                from_logits=from_logits,
                                reduction=tf.keras.losses.Reduction.NONE,
                            )
                            self.reduction = reduction

                        def __call__(self, y_true: tf.Tensor, y_pred: tf.Tensor, *args, **kwargs) -> tf.Tensor:
                            if self.reduction == "mean":
                                return tf.reduce_mean(self.ce_loss(y_true, y_pred))
                            if self.reduction == "sum":
                                return tf.reduce_sum(self.ce_loss(y_true, y_pred))
                            if self.reduction == "none":
                                return self.ce_loss(y_true, y_pred)
                            raise NotImplementedError()

                    if is_probability(estimator.predict(x=np.ones(shape=(1, *estimator.input_shape)))):
                        _loss_object_tf: TensorFlowV2Loss = CrossEntropyLossV2(from_logits=False)
                    else:
                        _loss_object_tf = CrossEntropyLossV2(from_logits=True)
                elif loss_type == "difference_logits_ratio":
                    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."
                        )

                    class DifferenceLogitsRatioTensorFlowV2(TensorFlowV2Loss):
                        """
                        Callable class for Difference Logits Ratio loss in TensorFlow v2.
                        """

                        def __init__(self):
                            self.reduction = "mean"

                        def __call__(self, y_true: tf.Tensor, y_pred: tf.Tensor, *args, **kwargs) -> tf.Tensor:
                            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_1 = tf.gather(y_pred, i_y_pred_arg[:, -1], axis=1, batch_dims=0)
                            z_3 = tf.gather(y_pred, i_y_pred_arg[:, -3], axis=1, batch_dims=0)
                            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_1 = tf.linalg.diag_part(z_1)
                            z_3 = tf.linalg.diag_part(z_3)
                            z_i = tf.linalg.diag_part(z_i)
                            z_y = tf.linalg.diag_part(z_y)

                            dlr = -(z_y - z_i) / (z_1 - z_3)

                            # modification for image-wise stepsize update
                            if self.reduction == "mean":
                                return tf.reduce_mean(dlr)
                            if self.reduction == "sum":
                                return tf.reduce_sum(dlr)
                            if self.reduction == "none":
                                return dlr
                            raise NotImplementedError()

                    _loss_object_tf = DifferenceLogitsRatioTensorFlowV2()

                estimator_apgd = TensorFlowV2Classifier(
                    model=estimator.model,
                    nb_classes=estimator.nb_classes,
                    input_shape=estimator.input_shape,
                    loss_object=_loss_object_tf,
                    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 loss_type == "cross_entropy":
                    if is_probability(
                        estimator.predict(x=np.ones(shape=(1, *estimator.input_shape), dtype=np.float32))
                    ):
                        raise ValueError(  # pragma: no cover
                            "The provided estimator seems to predict probabilities. If loss_type='cross_entropy' "
                            "the estimator has to to predict logits."
                        )

                    # modification for image-wise stepsize update
                    class CrossEntropyLossTorch(torch.nn.modules.loss._Loss):
                        """Class defining cross entropy loss with reduction options."""

                        def __init__(self, reduction="mean"):
                            super().__init__()
                            self.ce_loss = torch.nn.CrossEntropyLoss(reduction="none")
                            self.reduction = reduction

                        def __call__(self, y_pred: torch.Tensor, y_true: torch.Tensor) -> torch.Tensor:
                            if self.reduction == "mean":
                                return self.ce_loss(y_pred, y_true).mean()
                            if self.reduction == "sum":
                                return self.ce_loss(y_pred, y_true).sum()
                            if self.reduction == "none":
                                return self.ce_loss(y_pred, y_true)
                            raise NotImplementedError()

                        def forward(
                            self, input: torch.Tensor, target: torch.Tensor  # pylint: disable=redefined-builtin
                        ) -> torch.Tensor:
                            """
                            Forward method.

                            :param input: Predicted labels of shape (nb_samples, nb_classes).
                            :param target: Target labels of shape (nb_samples, nb_classes).
                            :return: Difference Logits Ratio Loss.
                            """
                            return self.__call__(y_pred=input, y_true=target)

                    _loss_object_pt: torch.nn.modules.loss._Loss = CrossEntropyLossTorch(reduction="mean")

                    reduce_labels = True
                    int_labels = True
                    probability_labels = True

                elif loss_type == "difference_logits_ratio":
                    if is_probability(
                        estimator.predict(x=np.ones(shape=(1, *estimator.input_shape), dtype=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."
                        )

                    class DifferenceLogitsRatioPyTorch(torch.nn.modules.loss._Loss):
                        """
                        Callable class for Difference Logits Ratio loss in PyTorch.
                        """

                        def __init__(self):
                            super().__init__()
                            self.reduction = "mean"

                        def __call__(self, y_pred: torch.Tensor, y_true: torch.Tensor) -> torch.Tensor:
                            if isinstance(y_true, np.ndarray):
                                y_true = torch.from_numpy(y_true)
                            if isinstance(y_pred, np.ndarray):
                                y_pred = torch.from_numpy(y_pred)

                            y_true = y_true.float()

                            i_y_true = torch.argmax(y_true, dim=1)
                            i_y_pred_arg = torch.argsort(y_pred, dim=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_1 = y_pred[:, i_y_pred_arg[:, -1]]
                            z_3 = y_pred[:, i_y_pred_arg[:, -3]]
                            z_i = y_pred[:, i_z_i]
                            z_y = y_pred[:, i_y_true]

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

                            # modification for image-wise stepsize update
                            dlr = (-(z_y - z_i) / (z_1 - z_3)).float()
                            if self.reduction == "mean":
                                return dlr.mean()
                            if self.reduction == "sum":
                                return dlr.sum()
                            if self.reduction == "none":
                                return dlr
                            raise NotImplementedError()

                        def forward(
                            self, input: torch.Tensor, target: torch.Tensor  # pylint: disable=redefined-builtin
                        ) -> torch.Tensor:
                            """
                            Forward method.

                            :param input: Predicted labels of shape (nb_samples, nb_classes).
                            :param target: Target labels of shape (nb_samples, nb_classes).
                            :return: Difference Logits Ratio Loss.
                            """
                            return self.__call__(y_true=target, y_pred=input)

                    _loss_object_pt = DifferenceLogitsRatioPyTorch()

                    reduce_labels = False
                    int_labels = False
                    probability_labels = False
                else:
                    raise NotImplementedError()

                estimator_apgd = PyTorchClassifier(
                    model=estimator.model,
                    loss=_loss_object_pt,
                    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),
                )

                estimator_apgd._reduce_labels = reduce_labels
                estimator_apgd._int_labels = int_labels
                estimator_apgd._probability_labels = probability_labels

            else:  # pragma: no cover
                raise ValueError(f"The loss type {loss_type} is not supported for the provided estimator.")

        super().__init__(estimator=estimator_apgd)
        self.norm = norm
        self.eps = eps
        self.eps_step = eps_step
        self.max_iter = max_iter
        self.targeted = targeted
        self.nb_random_init = nb_random_init
        self.batch_size = batch_size
        self.loss_type = loss_type
        self.verbose = verbose
        self._check_params()

        Create a :class:`.AutoProjectedGradientDescent` instance.

        :param estimator: A trained estimator.
        :param norm: The norm of the adversarial perturbation. Possible values: "inf", np.inf, 1 or 2.
        :param eps: Maximum perturbation that the attacker can introduce.
        :param eps_step: Attack step size (input variation) at each iteration.
        :param max_iter: The maximum number of iterations.
        :param targeted: Indicates whether the attack is targeted (True) or untargeted (False).
        :param nb_random_init: Number of random initialisations within the epsilon ball. For num_random_init=0
            starting at the original input.
        :param batch_size: Size of the batch on which adversarial samples are generated.
        :param loss_type: Defines the loss to attack. Available options: None (Use loss defined by estimator),
            "cross_entropy", or "difference_logits_ratio"
        :param verbose: Show progress bars.
        
art/attacks/evasion/auto_projected_gradient_descent.py