def _create_imperceptible(self, x: np.ndarray, x_adversarial: np.ndarray, y: np.ndarray) -> np.ndarray:
        """
        Create imperceptible, adversarial example with small perturbation.

        This method implements the part of the paper by Qin et al. (2019) that is described as the second stage of the
        attack. The resulting adversarial audio samples are able to successfully deceive the ASR estimator and are
        imperceptible to the human ear.

        :param x: An array with the original inputs to be attacked.
        :param x_adversarial: An array with the adversarial examples.
        :param y: Target values of shape (batch_size,). Each sample in `y` is a string, and it may possess different
            lengths. A possible example of `y` could be: `y = np.array(['SIXTY ONE', 'HELLO'])`.
        :return: An array with the imperceptible, adversarial outputs.
        """
        batch_size = x.shape[0]
        alpha_min = 0.0005

        # for ragged input, use object dtype
        dtype = np.float32 if x.ndim != 1 else object

        early_stop = [False] * batch_size

        alpha = np.array([self.alpha] * batch_size, dtype=np.float32)
        loss_theta_previous = [np.inf] * batch_size
        x_imperceptible = [None] * batch_size
        # if inputs are *not* ragged, we can't multiply alpha * gradients_theta
        if x.ndim != 1:
            alpha = np.expand_dims(alpha, axis=-1)

        masking_threshold, psd_maximum = self._stabilized_threshold_and_psd_maximum(x)

        x_perturbed = x_adversarial.copy()

        for i in range(1, self.max_iter_2 + 1):
            # get perturbation
            perturbation = x_perturbed - x

            # get loss gradients of both losses
            gradients_net = self.estimator.loss_gradient(x_perturbed, y, batch_mode=True)
            gradients_theta, loss_theta = self._loss_gradient_masking_threshold(
                perturbation, x, masking_threshold, psd_maximum
            )

            # check shapes match, otherwise unexpected errors can occur
            assert gradients_net.shape == gradients_theta.shape

            # perform gradient descent steps
            x_perturbed = x_perturbed - self.learning_rate_2 * (gradients_net + alpha * gradients_theta)

            if i % self.num_iter_increase_alpha == 0 or i % self.num_iter_decrease_alpha == 0:
                prediction = self.estimator.predict(x_perturbed, batch_size=batch_size)
                for j in range(batch_size):
                    # validate if adversarial target succeeds, i.e. f(x_perturbed)=y
                    if i % self.num_iter_increase_alpha == 0 and prediction[j] == y[j].upper():
                        # increase alpha
                        alpha[j] *= self.increase_factor_alpha
                        # save current best imperceptible, adversarial example
                        if loss_theta[j] < loss_theta_previous[j]:
                            x_imperceptible[j] = x_perturbed[j]
                            loss_theta_previous[j] = loss_theta[j]

                    # validate if adversarial target fails, i.e. f(x_perturbed)!=y
                    if i % self.num_iter_decrease_alpha == 0 and prediction[j] != y[j].upper():
                        # decrease alpha
                        alpha[j] = max(alpha[j] * self.decrease_factor_alpha, alpha_min)
                logger.info("Current iteration %s, alpha %s, loss theta %s", i, alpha, loss_theta)

            # note: avoids nan values in loss theta, which can occur when loss converges to zero.
            for j in range(batch_size):
                if loss_theta[j] < self.loss_theta_min and not early_stop[j]:
                    logger.warning(
                        "Batch sample %s reached minimum threshold of %s for theta loss.", j, self.loss_theta_min
                    )
                    early_stop[j] = True
            if all(early_stop):
                logger.warning(
                    "All batch samples reached minimum threshold for theta loss. Stopping early at iteration %s.", i
                )
                break

        # return perturbed x if no adversarial example found
        for j in range(batch_size):
            if x_imperceptible[j] is None:
                logger.critical("Adversarial attack stage 2 for x_%s was not successful", j)
                x_imperceptible[j] = x_perturbed[j]

        return np.array(x_imperceptible, dtype=dtype)

        Create imperceptible, adversarial example with small perturbation.

        This method implements the part of the paper by Qin et al. (2019) that is described as the second stage of the
        attack. The resulting adversarial audio samples are able to successfully deceive the ASR estimator and are
        imperceptible to the human ear.

        :param x: An array with the original inputs to be attacked.
        :param x_adversarial: An array with the adversarial examples.
        :param y: Target values of shape (batch_size,). Each sample in `y` is a string, and it may possess different
            lengths. A possible example of `y` could be: `y = np.array(['SIXTY ONE', 'HELLO'])`.
        :return: An array with the imperceptible, adversarial outputs.
        
art/attacks/evasion/imperceptible_asr/imperceptible_asr.py