def loss_gradient(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray:
        """
        Compute the gradient of the loss function w.r.t. `x`.

        :param x: Sample input with shape as expected by the model.
        :param y: Correct labels, one-vs-rest encoding.
        :return: Array of gradients of the same shape as `x`.
        """
        epsilon_map = self.sigma * np.random.normal(size=([self.num_basis] + list(self.input_shape)))
        grads = []
        for i in range(len(x)):
            minus, plus = self._generate_samples(x[i : i + 1], epsilon_map)

            # Vectorized; small tests weren't faster
            # ent_vec = np.vectorize(lambda p: entropy(y[i], p), signature='(n)->()')
            # new_y_minus = ent_vec(self.predict(minus))
            # new_y_plus = ent_vec(self.predict(plus))
            # Vanilla
            new_y_minus = np.array([entropy(y[i], p) for p in self.predict(minus)])
            new_y_plus = np.array([entropy(y[i], p) for p in self.predict(plus)])
            query_efficient_grad = 2 * np.mean(
                np.multiply(
                    epsilon_map.reshape(self.num_basis, -1),
                    (new_y_plus - new_y_minus).reshape(self.num_basis, -1) / (2 * self.sigma),
                ).reshape([-1] + list(self.input_shape)),
                axis=0,
            )
            grads.append(query_efficient_grad)
        grads_array = self._apply_preprocessing_gradient(x, np.array(grads))
        return grads_array

        Compute the gradient of the loss function w.r.t. `x`.

        :param x: Sample input with shape as expected by the model.
        :param y: Correct labels, one-vs-rest encoding.
        :return: Array of gradients of the same shape as `x`.
        
art/estimators/classification/query_efficient_bb.py