# Buggy code
def class_gradient(self, x, label=None, **kwargs):
    """
    Compute per-class derivatives w.r.t. `x`.

    :param x: Sample input with shape as expected by the model.
    :type x: `np.ndarray`
    :param label: Index of a specific per-class derivative. If `None`, then gradients for all
                  classes will be computed.
    :type label: `int`
    :param raw: Return the individual classifier raw outputs (not aggregated).
    :type raw: `bool`
    :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. If `raw=True`, an additional
             dimension is added at the beginning of the array, indexing the different classifiers.
    :rtype: `np.ndarray`
    """
    if "raw" in kwargs:
        raw = kwargs["raw"]
    else:
        raise ValueError("Missing argument `raw`.")

    grads = np.array(
        [
            self._classifier_weights[i] * self._classifiers[i].class_gradient(x, label)
            for i in range(self._nb_classifiers)
        ]
    )
    if raw:
        return grads
    return np.sum(grads, axis=0)


# Traceback (ValueError)
import torch
from torchvision import models

from art.classifiers import PyTorchClassifier, EnsembleClassifier
from art.attacks import ProjectedGradientDescent, HopSkipJump

# load and preprocess imagenet images
images = load_preprocess_images(...)

resnet18 = models.resnet18(pretrained=True)
alexnet = models.alexnet(pretrained=True)

wrapped_models = []
for model in [resnet18, alexnet]:
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), .1, momentum=0.9, weight_decay=1e-4)
wrapped_model = PyTorchClassifier(model=model, loss=loss, optimizer=optimizer, input_shape=(3,224,224), nb_classes=1000)
wrapped_models.append(wrapped_model)

ensemble = EnsembleClassifier(wrapped_models)
attack = ProjectedGradientDescent(ensemble)
adv_images = attack.generate(x = images)

"""
yields
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-117-766010f0b586> in <module>
1 attack = ProjectedGradientDescent(ensemble, eps=10000)
----> 2 adv_image = attack.generate(x = images)

~/anaconda3/envs/brainscore/lib/python3.6/site-packages/art/attacks/projected_gradient_descent.py in generate(self, x, y, **kwargs)
108
109             # Use model predictions as correct outputs
--> 110             targets = get_labels_np_array(self.classifier.predict(x, batch_size=self.batch_size))
111         else:
112             targets = y

~/anaconda3/envs/brainscore/lib/python3.6/site-packages/art/classifiers/ensemble.py in predict(self, x, batch_size, **kwargs)
114             raw = kwargs['raw']
115         else:
--> 116             raise ValueError('Missing argument `raw`.')
117
118         preds = np.array([self._classifier_weights[i] * self._classifiers[i].predict(x)

ValueError: Missing argument `raw`.

"""

# Fixed code
def class_gradient(self, x, label=None, raw=False, **kwargs):
    """
    Compute per-class derivatives w.r.t. `x`.

    :param x: Sample input with shape as expected by the model.
    :type x: `np.ndarray`
    :param label: Index of a specific per-class derivative. If `None`, then gradients for all
                  classes will be computed.
    :type label: `int`
    :param raw: Return the individual classifier raw outputs (not aggregated).
    :type raw: `bool`
    :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. If `raw=True`, an additional
             dimension is added at the beginning of the array, indexing the different classifiers.
    :rtype: `np.ndarray`
    """
    grads = np.array(
        [
            self._classifier_weights[i] * self._classifiers[i].class_gradient(x, label)
            for i in range(self._nb_classifiers)
        ]
    )
    if raw:
        return grads
    return np.sum(grads, axis=0)