def class_gradient(self, x: np.ndarray, label: int | list[int] | np.ndarray | None = None, **kwargs) -> np.ndarray:
        """
        Compute per-class derivatives w.r.t. `x`.

        | Paper link: http://cs229.stanford.edu/proj2016/report/ItkinaWu-AdversarialAttacksonImageRecognition-report.pdf
        | Typo in https://arxiv.org/abs/1605.07277 (equation 6)

        :param x: Sample input with shape as expected by the model.
        :param label: Index of a specific per-class derivative. If an integer is provided, the gradient of that class
                      output is computed for all samples. If multiple values as provided, the first dimension should
                      match the batch size of `x`, and each value will be used as target for its corresponding sample in
                      `x`. If `None`, then gradients for all classes will be computed for each sample.
        :return: Array of gradients of input features w.r.t. each class in the form
                 `(batch_size, nb_classes, input_shape)` when computing for all classes, otherwise shape becomes
                 `(batch_size, 1, input_shape)` when `label` parameter is specified.
        :raises `ValueError`: If the model has not been fitted prior to calling this method or if the number of
            classes in the classifier is not known.
        :raises `TypeError`: If the requested label cannot be processed.
        """
        if not hasattr(self.model, "coef_"):  # pragma: no cover
            raise ValueError(
                """Model has not been fitted. Run function `fit(x, y)` of classifier first or provide a
            fitted model."""
            )
        if self.nb_classes is None:  # pragma: no cover
            raise ValueError("Unknown number of classes in classifier.")
        nb_samples = x.shape[0]

        # Apply preprocessing
        x_preprocessed, _ = self._apply_preprocessing(x, y=None, fit=False)

        y_pred = self.model.predict_proba(X=x_preprocessed)
        weights = self.model.coef_

        if self.nb_classes > 2:  # type: ignore
            w_weighted = np.matmul(y_pred, weights)

        def _f_class_gradient(i_class, i_sample):
            if self.nb_classes == 2:
                return (-1.0) ** (i_class + 1.0) * y_pred[i_sample, 0] * y_pred[i_sample, 1] * weights[0, :]

            return weights[i_class, :] - w_weighted[i_sample, :]

        if label is None:
            # Compute the gradients w.r.t. all classes
            class_gradients = []

            for i_class in range(self.nb_classes):  # type: ignore
                class_gradient = np.zeros(x.shape)
                for i_sample in range(nb_samples):
                    class_gradient[i_sample, :] += _f_class_gradient(i_class, i_sample)
                class_gradients.append(class_gradient)

            gradients = np.swapaxes(np.array(class_gradients), 0, 1)

        elif isinstance(label, int):
            # Compute the gradients only w.r.t. the provided label
            class_gradient = np.zeros(x.shape)
            for i_sample in range(nb_samples):
                class_gradient[i_sample, :] += _f_class_gradient(label, i_sample)

            gradients = np.swapaxes(np.array([class_gradient]), 0, 1)

        elif (
            (isinstance(label, list) and len(label) == nb_samples)
            or isinstance(label, np.ndarray)
            and label.shape == (nb_samples,)
        ):
            # For each sample, compute the gradients w.r.t. the indicated target class (possibly distinct)
            class_gradients = []
            unique_labels = list(np.unique(label))

            for unique_label in unique_labels:
                class_gradient = np.zeros(x.shape)
                for i_sample in range(nb_samples):
                    # class_gradient[i_sample, :] += label[i_sample, unique_label] * (weights[unique_label, :]
                    # - w_weighted[i_sample, :])
                    class_gradient[i_sample, :] += _f_class_gradient(unique_label, i_sample)

                class_gradients.append(class_gradient)

            gradients = np.swapaxes(np.array(class_gradients), 0, 1)
            lst = [unique_labels.index(i) for i in label]
            gradients = np.expand_dims(gradients[np.arange(len(gradients)), lst], axis=1)

        else:
            raise TypeError("Unrecognized type for argument `label` with type " + str(type(label)))

        gradients = self._apply_preprocessing_gradient(x, gradients)

        return gradients

        Compute per-class derivatives w.r.t. `x`.

        | Paper link: http://cs229.stanford.edu/proj2016/report/ItkinaWu-AdversarialAttacksonImageRecognition-report.pdf
        | Typo in https://arxiv.org/abs/1605.07277 (equation 6)

        :param x: Sample input with shape as expected by the model.
        :param label: Index of a specific per-class derivative. If an integer is provided, the gradient of that class
                      output is computed for all samples. If multiple values as provided, the first dimension should
                      match the batch size of `x`, and each value will be used as target for its corresponding sample in
                      `x`. If `None`, then gradients for all classes will be computed for each sample.
        :return: Array of gradients of input features w.r.t. each class in the form
                 `(batch_size, nb_classes, input_shape)` when computing for all classes, otherwise shape becomes
                 `(batch_size, 1, input_shape)` when `label` parameter is specified.
        :raises `ValueError`: If the model has not been fitted prior to calling this method or if the number of
            classes in the classifier is not known.
        :raises `TypeError`: If the requested label cannot be processed.
        
art/estimators/classification/scikitlearn.py