text: import time
  import torch
  import torch.nn as nn
  import torch.optim as optim
  
  
  class Attack(object):
      r"""
      Base class for all attacks.
  
      .. note::
          It automatically set device to the device where given model is.
          It basically changes training mode to eval during attack process.
          To change this, please see `set_training_mode`.
      """
      def __init__(self, name, model, bounds=[-1, 1]):
          r"""
          Initializes internal attack state.
  
          Arguments:
              name (str): name of attack.
              model (torch.nn.Module): model to attack.
          """
          self.bounds = bounds
          self.attack = name
          self.model = model
          self.model_name = str(model).split("(")[0]
          self.device = next(model.parameters()).device
  
          self._attack_mode = 'default'
          self._targeted = False
          self._return_type = 'float'
          self._supported_mode = ['default']
  
          self._model_training = False
          self._batchnorm_training = False
          self._dropout_training = False
  
      def forward(self, *input):
          r"""
          It defines the computation performed at every call.
          Should be overridden by all subclasses.
          """
          raise NotImplementedError
  
      def get_mode(self):
          r"""
          Get attack mode.
  
          """
          return self._attack_mode
  
      def set_mode_default(self):
          r"""
          Set attack mode as default mode.
  
          """
          self._attack_mode = 'default'
          self._targeted = False
          print("Attack mode is changed to 'default.'")
  
      def set_mode_targeted_by_function(self, target_map_function=None):
          r"""
          Set attack mode as targeted.
  
          Arguments:
              target_map_function (function): Label mapping function.
                  e.g. lambda images, labels:(labels+1)%10.
                  None for using input labels as targeted labels. (Default)
  
          """
          if "targeted" not in self._supported_mode:
              raise ValueError("Targeted mode is not supported.")
  
          self._attack_mode = 'targeted'
          self._targeted = True
          self._target_map_function = target_map_function
          print("Attack mode is changed to 'targeted.'")
  
      def set_mode_targeted_least_likely(self, kth_min=1):
          r"""
          Set attack mode as targeted with least likely labels.
          Arguments:
              kth_min (str): label with the k-th smallest probability used as target labels. (Default: 1)
  
          """
          if "targeted" not in self._supported_mode:
              raise ValueError("Targeted mode is not supported.")
  
          self._attack_mode = "targeted(least-likely)"
          self._targeted = True
          assert (kth_min > 0)
          self._kth_min = kth_min
          self._target_map_function = self._get_least_likely_label
          print("Attack mode is changed to 'targeted(least-likely).'")
  
  
      def set_mode_targeted_most_likely(self, kth_min=1):
          r"""
          Set attack mode as targeted with most likely labels.
          Arguments:
              kth_min (str): label with the k-th smallest probability used as target labels. (Default: 1)
  
          """
          if "targeted" not in self._supported_mode:
              raise ValueError("Targeted mode is not supported.")
  
          self._attack_mode = "targeted(most-likely)"
          self._targeted = True
          assert (kth_min > 0)
          self._kth_min = kth_min
          self._target_map_function = self._get_most_likely_label
          print("Attack mode is changed to 'targeted(most-likely).'")
  
      def set_mode_targeted_random(self):
          r"""
          Set attack mode as targeted with random labels.
          Arguments:
              num_classses (str): number of classes.
  
          """
          if "targeted" not in self._supported_mode:
              raise ValueError("Targeted mode is not supported.")
  
          self._attack_mode = "targeted(random)"
          self._targeted = True
          self._target_map_function = self._get_random_target_label
          print("Attack mode is changed to 'targeted(random).'")
  
      def set_return_type(self, type):
          r"""
          Set the return type of adversarial images: `int` or `float`.
  
          Arguments:
              type (str): 'float' or 'int'. (Default: 'float')
  
          .. note::
              If 'int' is used for the return type, the file size of
              adversarial images can be reduced (about 1/4 for CIFAR10).
              However, if the attack originally outputs float adversarial images
              (e.g. using small step-size than 1/255), it might reduce the attack
              success rate of the attack.
  
          """
          if type == 'float':
              self._return_type = 'float'
          elif type == 'int':
              self._return_type = 'int'
          else:
              raise ValueError(type + " is not a valid type. [Options: float, int]")
  
      def set_training_mode(self, model_training=False, batchnorm_training=False, dropout_training=False):
          r"""
          Set training mode during attack process.
  
          Arguments:
              model_training (bool): True for using training mode for the entire model during attack process.
              batchnorm_training (bool): True for using training mode for batchnorms during attack process.
              dropout_training (bool): True for using training mode for dropouts during attack process.
  
          .. note::
              For RNN-based models, we cannot calculate gradients with eval mode.
              Thus, it should be changed to the training mode during the attack.
          """
          self._model_training = model_training
          self._batchnorm_training = batchnorm_training
          self._dropout_training = dropout_training
  
      def save(self, data_loader, save_path=None, verbose=True, return_verbose=False, save_pred=False):
          r"""
          Save adversarial images as torch.tensor from given torch.utils.data.DataLoader.
  
          Arguments:
              save_path (str): save_path.
              data_loader (torch.utils.data.DataLoader): data loader.
              verbose (bool): True for displaying detailed information. (Default: True)
              return_verbose (bool): True for returning detailed information. (Default: False)
              save_pred (bool): True for saving predicted labels (Default: False)
  
          """
          if save_path is not None:
              image_list = []
              label_list = []
              if save_pred:
                  pre_list = []
          correct = 0
          total = 0
          l2_distance = []
          total_batch = len(data_loader)
          given_training = self.model.training
          given_return_type = self._return_type
          self._return_type = 'float'
  
          for step, (images, labels) in enumerate(data_loader):
              start = time.time()
              adv_images = self.__call__(images, labels)
              batch_size = len(images)
              if verbose or return_verbose:
                  with torch.no_grad():
                      if given_training:
                          self.model.eval()
                      outputs = self.model(adv_images)
                      _, pred = torch.max(outputs.data, 1)
                      total += labels.size(0)
                      right_idx = (pred == labels.to(self.device))
                      correct += right_idx.sum()
                      end = time.time()
                      delta = (adv_images - images.to(self.device)).view(batch_size, -1)
                      l2_distance.append(torch.norm(delta[~right_idx], p=2, dim=1))
  
                      rob_acc = 100 * float(correct) / total
                      l2 = torch.cat(l2_distance).mean().item()
                      progress = (step+1)/total_batch*100
                      elapsed_time = end-start
                      if verbose:
                          self._save_print(progress, rob_acc, l2, elapsed_time, end='\r')
              if save_path is not None:
                  if given_return_type == 'int':
                      adv_images = self._to_uint(adv_images.detach().cpu())
                      image_list.append(adv_images)
                  else:
                      image_list.append(adv_images.detach().cpu())
                  label_list.append(labels.detach().cpu())
                  image_list_cat = torch.cat(image_list, 0)
                  label_list_cat = torch.cat(label_list, 0)
                  if save_pred:
                      pre_list.append(pred.detach().cpu())
                      pre_list_cat = torch.cat(pre_list, 0)
                      torch.save((image_list_cat, label_list_cat, pre_list_cat), save_path)
                  else:
                      torch.save((image_list_cat, label_list_cat), save_path)
          # To avoid erasing the printed information.
          if verbose:
              self._save_print(progress, rob_acc, l2, elapsed_time, end='\n')
          if given_training:
              self.model.train()
  
          if return_verbose:
              return rob_acc, l2, elapsed_time
  
      def _save_print(self, progress, rob_acc, l2, elapsed_time, end):
          print('- Save progress: %2.2f %% / Robust accuracy: %2.2f %% / L2: %1.5f (%2.3f it/s) \t' \
                % (progress, rob_acc, l2, elapsed_time), end=end)
  
      @torch.no_grad()
      def _get_target_label(self, images, labels=None):
          r"""
          Function for changing the attack mode.
          Return input labels.
          """
          if self._targeted:
              given_training = self.model.training
              if given_training:
                  self.model.eval()
              target_labels = self._target_map_function(images, labels)
              if given_training:
                  self.model.train()
              return target_labels
          else:
              raise ValueError('Please define target_map_function.')
  
      @torch.no_grad()
      def _get_least_likely_label(self, images, labels=None):
          outputs = self.model(images)
          if labels is None:
              _, labels = torch.max(outputs, dim=1)
          n_classses = outputs.shape[-1]
  
          target_labels = torch.zeros_like(labels)
          for counter in range(labels.shape[0]):
              l = list(range(n_classses))
              l.remove(labels[counter])
              _, t = torch.kthvalue(outputs[counter][l], self._kth_min)
              target_labels[counter] = l[t]
          return target_labels.long().to(self.device)
  
      @torch.no_grad()
      def _get_most_likely_label(self, images, labels=None):
          outputs = self.model(images)
          if labels is None:
              _, labels = torch.max(outputs, dim=1)
          n_classses = outputs.shape[-1]
          _kth_min = (n_classses - 1)
  
          target_labels = torch.zeros_like(labels)
          for counter in range(labels.shape[0]):
              l = list(range(n_classses))
              l.remove(labels[counter])
              _, t = torch.kthvalue(outputs[counter][l], _kth_min)
              target_labels[counter] = l[t]
          return target_labels.long().to(self.device)
  
      @torch.no_grad()
      def _get_random_target_label(self, images, labels=None):
          outputs = self.model(images)
          if labels is None:
              _, labels = torch.max(outputs, dim=1)
          n_classses = outputs.shape[-1]
  
          target_labels = torch.zeros_like(labels)
          for counter in range(labels.shape[0]):
              l = list(range(n_classses))
              l.remove(labels[counter])
              t = (len(l)*torch.rand([1])).long().to(self.device)
              target_labels[counter] = l[t]
  
          return target_labels.long().to(self.device)
  
      def _to_uint(self, images):
          r"""
          Function for changing the return type.
          Return images as int.
          """
          return (images*255).type(torch.uint8)
  
      def __str__(self):
          info = self.__dict__.copy()
          del_keys = ['model', 'attack']
          for key in info.keys():
              if key[0] == "_":
                  del_keys.append(key)
          for key in del_keys:
              del info[key]
          info['attack_mode'] = self._attack_mode
          info['return_type'] = self._return_type
          return self.attack + "(" + ', '.join('{}={}'.format(key, val) for key, val in info.items()) + ")"
  
      def __call__(self, *input, **kwargs):
          given_training = self.model.training
          if self._model_training:
              self.model.train()
              for _, m in self.model.named_modules():
                  if not self._batchnorm_training:
                      if 'BatchNorm' in m.__class__.__name__:
                          m = m.eval()
                  if not self._dropout_training:
                      if 'Dropout' in m.__class__.__name__:
                          m = m.eval()
          else:
              self.model.eval()
          images = self.forward(*input, **kwargs)
          if given_training:
              self.model.train()
          if self._return_type == 'int':
              images = self._to_uint(images)
          return images
  
  
  
  class FGSM(Attack):
      def __init__(self, model, eps=0.007, bounds=[-1, 1]):
          super().__init__("FGSM", model, bounds)
          self.eps = eps
          self._supported_mode = ['default', 'targeted']
  
      def forward(self, images, labels):
          images = images.clone().detach().to(self.device)
          labels = labels.clone().detach().to(self.device)
          if self._targeted:
              target_labels = self._get_target_label(images, labels)
          loss = torch.nn.CrossEntropyLoss()
          images.requires_grad = True
          outputs = self.model(images)
  
          # Calculate loss
          if self._targeted:
              cost = -loss(outputs, target_labels)
          else:
              cost = loss(outputs, labels)
          # Update adversarial images
          grad = torch.autograd.grad(cost, images,
                                     retain_graph=False, create_graph=False)[0]
          adv_images = images + self.eps * grad.sign()
          adv_images = torch.clamp(adv_images, min=self.bounds[0], max=self.bounds[1]).detach()
          return adv_images
  
  
  class PGD(Attack):
      def __init__(self, model, eps=0.3,
                   alpha=8./255., steps=40, random_start=True, bounds=[-1, 1]):
          super().__init__("PGD", model, bounds=bounds)
          self.eps = eps
          self.alpha = alpha
          self.steps = steps
          self.random_start = random_start
          self._supported_mode = ['default', 'targeted']
  
      def forward(self, images, labels):
          images = images.clone().detach().to(self.device)
          labels = labels.clone().detach().to(self.device)
          if self._targeted:
              target_labels = self._get_target_label(images, labels)
          loss = nn.CrossEntropyLoss()
          adv_images = images.clone().detach()
          if self.random_start:
              adv_images = adv_images + torch.empty_like(adv_images).uniform_(-self.eps, self.eps)
              adv_images = torch.clamp(adv_images, min=self.bounds[0], max=self.bounds[1]).detach()
          for _ in range(self.steps):
              adv_images.requires_grad = True
              outputs = self.model(adv_images)
              if self._targeted:
                  cost = -loss(outputs, target_labels)
              else:
                  cost = loss(outputs, labels)
              grad = torch.autograd.grad(cost, adv_images,
                                         retain_graph=False, create_graph=False)[0]
              adv_images = adv_images.detach() + self.alpha*grad.sign()
              delta = torch.clamp(adv_images - images, min=-self.eps, max=self.eps)
              adv_images = torch.clamp(images + delta, min=self.bounds[0], max=self.bounds[1]).detach()
          return adv_images
  
  
  class DeepFool(Attack):
      def __init__(self, model, steps=50, overshoot=0.02, bounds=[-1, 1]):
          super().__init__("DeepFool", model, bounds)
          self.steps = steps
          self.overshoot = overshoot
          self._supported_mode = ['default']
  
      def forward(self, images, labels, return_target_labels=False):
          images = images.clone().detach().to(self.device)
          labels = labels.clone().detach().to(self.device)
          batch_size = len(images)
          correct = torch.tensor([True]*batch_size)
          target_labels = labels.clone().detach().to(self.device)
          curr_steps = 0
          adv_images = []
          for idx in range(batch_size):
              image = images[idx:idx+1].clone().detach()
              adv_images.append(image)
          while (True in correct) and (curr_steps < self.steps):
              for idx in range(batch_size):
                  if not correct[idx]: continue
                  early_stop, pre, adv_image = self._forward_indiv(adv_images[idx], labels[idx])
                  adv_images[idx] = adv_image
                  target_labels[idx] = pre
                  if early_stop:
                      correct[idx] = False
              curr_steps += 1
          adv_images = torch.cat(adv_images).detach()
          if return_target_labels:
              return adv_images, target_labels
          return adv_images
  
      def _forward_indiv(self, image, label):
          image.requires_grad = True
          fs = self.model(image)[0]
          _, pre = torch.max(fs, dim=0)
          if pre != label:
              return (True, pre, image)
          ws = self._construct_jacobian(fs, image)
          image = image.detach()
          f_0 = fs[label]
          w_0 = ws[label]
          wrong_classes = [i for i in range(len(fs)) if i != label]
          f_k = fs[wrong_classes]
          w_k = ws[wrong_classes]
          f_prime = f_k - f_0
          w_prime = w_k - w_0
          value = torch.abs(f_prime) \
                  / torch.norm(nn.Flatten()(w_prime), p=2, dim=1)
          _, hat_L = torch.min(value, 0)
          delta = (torch.abs(f_prime[hat_L])*w_prime[hat_L] \
                   / (torch.norm(w_prime[hat_L], p=2)**2))
          target_label = hat_L if hat_L < label else hat_L+1
          adv_image = image + (1+self.overshoot)*delta
          adv_image = torch.clamp(adv_image, min=self.bounds[0], max=self.bounds[1]).detach()
          return (False, target_label, adv_image)
  
      # https://stackoverflow.com/questions/63096122/pytorch-is-it-possible-to-differentiate-a-matrix
      # torch.autograd.functional.jacobian is only for torch >= 1.5.1
      def _construct_jacobian(self, y, x):
          x_grads = []
          for idx, y_element in enumerate(y):
              if x.grad is not None:
                  x.grad.zero_()
              y_element.backward(retain_graph=(False or idx+1 < len(y)))
              x_grads.append(x.grad.clone().detach())
          return torch.stack(x_grads).reshape(*y.shape, *x.shape)
  
  
  class CW(Attack):
      def __init__(self, model, c=1e-4, kappa=0, steps=1000, lr=0.01, bounds=[-1, 1]):
          super().__init__("CW", model, bounds)
          self.c = c
          self.kappa = kappa
          self.steps = steps
          self.lr = lr
          self._supported_mode = ['default', 'targeted']
  
      def forward(self, images, labels):
          images = images.clone().detach().to(self.device)
          labels = labels.clone().detach().to(self.device)
          if self._targeted:
              target_labels = self._get_target_label(images, labels)
          # w = torch.zeros_like(images).detach() # Requires 2x times
          w = self.inverse_tanh_space(images).detach()
          w.requires_grad = True
  
          best_adv_images = images.clone().detach()
          best_L2 = 1e10*torch.ones((len(images))).to(self.device)
          prev_cost = 1e10
          dim = len(images.shape)
  
          MSELoss = nn.MSELoss(reduction='none')
          Flatten = nn.Flatten()
          optimizer = optim.Adam([w], lr=self.lr)
          for step in range(self.steps):
              # Get adversarial images
              adv_images = self.tanh_space(w)
              # Calculate loss
              current_L2 = MSELoss(Flatten(adv_images),
                                   Flatten(images)).sum(dim=1)
              L2_loss = current_L2.sum()
              outputs = self.model(adv_images)
              if self._targeted:
                  f_loss = self.f(outputs, target_labels).sum()
              else:
                  f_loss = self.f(outputs, labels).sum()
              cost = L2_loss + self.c*f_loss
              optimizer.zero_grad()
              cost.backward()
              optimizer.step()
              # Update adversarial images
              _, pre = torch.max(outputs.detach(), 1)
              correct = (pre == labels).float()
  
              # filter out images that get either correct predictions or non-decreasing loss,
              # i.e., only images that are both misclassified and loss-decreasing are left
              mask = (1-correct)*(best_L2 > current_L2.detach())
              best_L2 = mask*current_L2.detach() + (1-mask)*best_L2
              mask = mask.view([-1]+[1]*(dim-1))
              best_adv_images = mask*adv_images.detach() + (1-mask)*best_adv_images
              # Early stop when loss does not converge.
              # max(.,1) To prevent MODULO BY ZERO error in the next step.
              if step % max(self.steps//10,1) == 0:
                  if cost.item() > prev_cost:
                      return best_adv_images
                  prev_cost = cost.item()
          return best_adv_images
  
      def tanh_space(self, x):
          return 1/2*(torch.tanh(x) + 1)
  
      def inverse_tanh_space(self, x):
          # torch.atanh is only for torch >= 1.7.0
          return self.atanh(x*2-1)
  
      def atanh(self, x):
          return 0.5*torch.log((1+x)/(1-x))
  
      # f-function in the paper
      def f(self, outputs, labels):
          one_hot_labels = torch.eye(len(outputs[0]))[labels].to(self.device)
          i, _ = torch.max((1-one_hot_labels)*outputs, dim=1) # get the second largest logit
          j = torch.masked_select(outputs, one_hot_labels.bool()) # get the largest logit
          if self._targeted:
              return torch.clamp((i-j), min=-self.kappa)
          else:
              return torch.clamp((j-i), min=-self.kappa)
  
  
  class BIM(Attack):
      def __init__(self, model, eps=4/255, alpha=1/255, steps=0, bounds=[-1, 1]):
          super().__init__("BIM", model, bounds)
          self.eps = eps
          self.alpha = alpha
          if steps == 0:
              self.steps = int(min(eps*255 + 4, 1.25*eps*255))
          else:
              self.steps = steps
          self._supported_mode = ['default', 'targeted']
  
      def forward(self, images, labels):
          images = images.clone().detach().to(self.device)
          labels = labels.clone().detach().to(self.device)
          if self._targeted:
              target_labels = self._get_target_label(images, labels)
          loss = nn.CrossEntropyLoss()
          ori_images = images.clone().detach()
          for _ in range(self.steps):
              images.requires_grad = True
              outputs = self.model(images)
              if self._targeted:
                  cost = -loss(outputs, target_labels)
              else:
                  cost = loss(outputs, labels)
              grad = torch.autograd.grad(cost, images,
                                         retain_graph=False,
                                         create_graph=False)[0]
              adv_images = images + self.alpha*grad.sign()
              a = torch.clamp(ori_images - self.eps, min=0)
              b = (adv_images >= a).float()*adv_images \
                  + (adv_images < a).float()*a
              c = (b > ori_images+self.eps).float()*(ori_images+self.eps) \
                  + (b <= ori_images + self.eps).float()*b
              images = torch.clamp(c, max=self.bounds[1]).detach()
          return images
  