class_docstr: This class implements a classifier with the PyTorch framework.
class_docstr_tok: ["This","class","implements","a","classifier","with","the","PyTorch","framework","."]
docstr: Compute per-class derivatives w.r.t. `x`.
  
  :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.
  :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
                        Note on RNN-like models: Backpropagation through RNN modules in eval mode raises
                        RuntimeError due to cudnn issues and require training mode, i.e. RuntimeError: cudnn RNN
                        backward can only be called in training mode. Therefore, if the model is an RNN type we
                        always use training mode but freeze batch-norm and dropout layers if
                        `training_mode=False.`
  :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.
code: def class_gradient(  # pylint: disable=W0221
      self,
      x: np.ndarray,
      label: Optional[Union[int, List[int], np.ndarray]] = None,
      training_mode: bool = False,
      **kwargs,
  ) -> np.ndarray:
      """
      Compute per-class derivatives w.r.t. `x`.
  
      :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.
      :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
                            Note on RNN-like models: Backpropagation through RNN modules in eval mode raises
                            RuntimeError due to cudnn issues and require training mode, i.e. RuntimeError: cudnn RNN
                            backward can only be called in training mode. Therefore, if the model is an RNN type we
                            always use training mode but freeze batch-norm and dropout layers if
                            `training_mode=False.`
      :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.
      """
      import torch
  
      self._model.train(mode=training_mode)
  
      # Backpropagation through RNN modules in eval mode raises RuntimeError due to cudnn issues and require training
      # mode, i.e. RuntimeError: cudnn RNN backward can only be called in training mode. Therefore, if the model is
      # an RNN type we always use training mode but freeze batch-norm and dropout layers if training_mode=False.
      if self.is_rnn:
          self._model.train(mode=True)
          if not training_mode:
              logger.debug(  # pragma: no cover
                  "Freezing batch-norm and dropout layers for gradient calculation in train mode with eval parameters"
                  "of batch-norm and dropout."
              )
              self.set_batchnorm(train=False)
              self.set_dropout(train=False)
  
      if isinstance(label, list):
          label = np.array(label)
      if not (
          (label is None)
          or (
              isinstance(label, (int, np.integer))
              and label in range(self.nb_classes)
          )
          or (
              isinstance(label, np.ndarray)
              and len(label.shape) == 1
              and (label < self.nb_classes).all()
              and label.shape[0] == x.shape[0]
          )
      ):
          raise ValueError(
              f"Label {label} is out of range."
          )  # pragma: no cover
  
      # Apply preprocessing
      if self.all_framework_preprocessing:
          x_grad = torch.from_numpy(x).to(self._device)
          if self._layer_idx_gradients < 0:
              x_grad.requires_grad = True
          x_input, _ = self._apply_preprocessing(
              x_grad, y=None, fit=False, no_grad=False
          )
      else:
          x_preprocessed, _ = self._apply_preprocessing(
              x, y=None, fit=False, no_grad=True
          )
          x_grad = torch.from_numpy(x_preprocessed).to(self._device)
          if self._layer_idx_gradients < 0:
              x_grad.requires_grad = True
          x_input = x_grad
  
      # Run prediction
      model_outputs = self._model(x_input)
  
      # Set where to get gradient
      if self._layer_idx_gradients >= 0:
          input_grad = model_outputs[self._layer_idx_gradients]
      else:
          input_grad = x_grad
  
      # Set where to get gradient from
      preds = model_outputs[-1]
  
      # Compute the gradient
      grads_list = []
  
      def save_grad():
          def hook(grad):
              grads_list.append(grad.cpu().numpy().copy())
              grad.data.zero_()
  
          return hook
  
      input_grad.register_hook(save_grad())
  
      self._model.zero_grad()
      if label is None:
          if len(preds.shape) == 1 or preds.shape[1] == 1:
              num_outputs = 1
          else:
              num_outputs = self.nb_classes
  
          for i in range(num_outputs):
              torch.autograd.backward(
                  preds[:, i],
                  torch.tensor([1.0] * len(preds[:, 0])).to(
                      self._device
                  ),
                  retain_graph=True,
              )
  
          grads = np.swapaxes(np.array(grads_list), 0, 1)
  
      elif isinstance(label, (int, np.integer)):
          torch.autograd.backward(
              preds[:, label],
              torch.tensor([1.0] * len(preds[:, 0])).to(self._device),
              retain_graph=True,
          )
          grads = np.swapaxes(np.array(grads_list), 0, 1)
      else:
          unique_label = list(np.unique(label))
          for i in unique_label:
              torch.autograd.backward(
                  preds[:, i],
                  torch.tensor([1.0] * len(preds[:, 0])).to(
                      self._device
                  ),
                  retain_graph=True,
              )
  
          grads = np.swapaxes(np.array(grads_list), 0, 1)
          lst = [unique_label.index(i) for i in label]
          grads = grads[np.arange(len(grads)), lst]
  
          grads = grads[None, ...]
          grads = np.swapaxes(np.array(grads), 0, 1)
  
      if not self.all_framework_preprocessing:
          grads = self._apply_preprocessing_gradient(x, grads)
  
      return grads
  
imports: from __future__ import absolute_import, division, print_function, unicode_literals
  import copy
  import logging
  import os
  import time
  from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
  from tqdm.auto import tqdm
  import numpy
  import six
  from art import config
  from art.estimators.classification.classifier import ClassGradientsMixin, ClassifierMixin
  from art.estimators.pytorch import PyTorchEstimator
  from art.utils import check_and_transform_label_format
label_desc: Use image node_id 17 for calling the PyTorchClassifier obj's underlying member method code with example usage: obj.class_gradient(x, label, training_mode) and returns: grads, hook