Source code for extorch.adversarial.gradient_based

from typing import List, Optional
import random

import numpy as np
import torch
from torch import nn, Tensor
from torch.autograd import Variable
from torch.nn.modules.loss import _Loss
import torchvision.transforms as transforms

from extorch.nn.utils import WrapperModel
from extorch.adversarial.base import BaseAdversary


__all__ = [
    "GradientBasedAdversary", 
    "PGDAdversary", 
    "CustomizedPGDAdversary",
    "FGSMAdversary", 
    "MDIFGSMAdversary", 
    "MIFGSMAdversary", 
    "IFGSMAdversary", 
    "DiversityLayer"
]


[docs]class GradientBasedAdversary(BaseAdversary): r""" Gradient-based adversary. Args: criterion (Optional[_Loss]): Criterion to calculate the loss. Default: ``nn.CrossEntropyLoss``. use_eval_mode (bool): Whether use eval mode of the network while running attack. Default: ``False``. """ def __init__(self, criterion: Optional[_Loss] = nn.CrossEntropyLoss(), use_eval_mode: bool = False) -> None: super(GradientBasedAdversary, self).__init__(use_eval_mode) self.criterion = criterion
[docs]class PGDAdversary(GradientBasedAdversary): r""" Project Gradient Descent (PGD, `Link`_) adversarial adversary. Args: epsilon (float): Maximum distortion of adversarial example compared to origin input. n_step (int): Number of attack iterations. step_size (float): Step size for each attack iteration. rand_init (bool): Whether add random perturbation to origin input before formal attack. mean (List[float]): Sequence of means for each channel while normalizing the origin input. std (List[float]): Sequence of standard deviations for each channel while normalizing the origin input. criterion (Optional[_Loss]): Criterion to calculate the loss. Default: ``nn.CrossEntropyLoss``. use_eval_mode (bool): Whether use eval mode of the network while running attack. Default: ``False``. .. _Link: https://arxiv.org/abs/1706.06083 """ def __init__(self, epsilon: float, n_step: int, step_size: float, rand_init: bool, mean: List[float], std: List[float], criterion: Optional[_Loss] = nn.CrossEntropyLoss(), use_eval_mode: bool = False) -> None: super(PGDAdversary, self).__init__(criterion, use_eval_mode) self.epsilon = epsilon self.n_step = n_step self.step_size = step_size self.rand_init = rand_init self.mean = torch.reshape(torch.tensor(mean), (3, 1, 1)) self.std = torch.reshape(torch.tensor(std), (3, 1, 1))
[docs] def generate_adv(self, net: nn.Module, input: Tensor, target: Tensor, output: Tensor) -> Tensor: self.mean = self.mean.to(input.device) self.std = self.std.to(input.device) wrapper_net = WrapperModel(net, self.mean, self.std) input_adv = Variable((input.data.clone() * self.std + self.mean), requires_grad = True) input_clone = input_adv.data.clone() if self.rand_init: eta = input.new(input.size()).uniform_(-self.epsilon, self.epsilon) input_adv.data = torch.clamp(input_adv.data + eta, 0., 1.) for _ in range(self.n_step): output = wrapper_net(input_adv) loss = self.criterion(output, Variable(target)) loss.backward() eta = self.step_size * input_adv.grad.data.sign() input_adv = Variable(input_adv.data + eta, requires_grad = True) eta = torch.clamp( input_adv.data - input_clone, -self.epsilon, self.epsilon ) input_adv.data = torch.clamp(input_clone + eta, 0., 1.) input_adv.data = (input_adv.data - self.mean) / self.std return input_adv.data
[docs]class CustomizedPGDAdversary(PGDAdversary): r""" Customized Project Gradient Descent (PGD, `Link`_) adversarial adversary. .. _Link: https://arxiv.org/abs/2109.01983 """
[docs] def generate_adv(self, net: nn.Module, input: Tensor, target: Tensor, output: Tensor, epsilon_c: float) -> Tensor: r""" Args: epsilon_c (float): """ self.mean = self.mean.to(input.device) self.std = self.std.to(input.device) wrapper_net = WrapperModel(net, self.mean, self.std) input_adv = Variable((input.data.clone() * self.std + self.mean), requires_grad = True) input_clone = input_adv.data.clone() if self.rand_init: eta = input.new(input.size()).uniform_(-self.epsilon, self.epsilon) input_adv.data = torch.clamp(input_adv.data + eta, 0., 1.) for step in range(1, self.n_step + 1): output = wrapper_net(input_adv) loss = self.criterion(output, Variable(target)) loss.backward(retain_graph = True) gradient = input_adv.grad gradient_abs = torch.abs(gradient) g_1 = gradient / torch.sum(gradient_abs) g_t = 2 / np.pi * torch.atan(gradient / torch.mean(gradient_abs)) g_ens = g_1 + self.gamma_1 * g_t + self.gamma_2 * gradient.sign() eta = epsilon_c / step * g_ens eta = torch.clamp(input_adv + eta - input_clone, -self.epsilon, self.epsilon) input_adv = torch.clamp(input_clone + eta, 0., 1.) input_adv = (input_adv - self.mean) / self.std return input_adv.data
[docs]class FGSMAdversary(PGDAdversary): r""" Fast Gradient Sign Method (FGSM, `Link`_) adversarial adversary. Args: epsilon (float): Maximum distortion of adversarial example compared to origin input. rand_init (bool): Whether add random perturbation to origin input before formal attack. mean (List[float]): Sequence of means for each channel while normalizing the origin input. std (List[float]): Sequence of standard deviations for each channel while normalizing the origin input. criterion (Optional[_Loss]): Criterion to calculate the loss. Default: ``nn.CrossEntropyLoss``. use_eval_mode (bool): Whether use eval mode of the network while running attack. Default: ``False``. .. _Link: https://arxiv.org/abs/1412.6572 """ def __init__(self, epsilon: float, rand_init: bool, mean: List[float], std: List[float], criterion: Optional[_Loss] = nn.CrossEntropyLoss(), use_eval_mode: bool = False) -> None: super(FGSMAdversary, self).__init__( epsilon, 1, epsilon, rand_init, mean, std, criterion, use_eval_mode)
[docs]class DiversityLayer(nn.Module): r""" Diversity input layer for M-DI-FGSM ('Link'_) adversarial adversary. Args: target (int): Target reshape size. p (float): Probability to apply the transformation. Default: 0.5. .. _Link: https://arxiv.org/abs/1904.02884 """ def __init__(self, target: int, p: float = 0.5) -> None: super(DiversityLayer, self).__init__() self.p = p self.target = target
[docs] def forward(self, input: Tensor) -> Tensor: if np.random.rand() < self.p: ori_size = input.size()[-1] first_resize = random.randint(ori_size, self.target - 1) up_padding_size = random.randint(0, self.target - first_resize) left_padding_size = random.randint(0, self.target - first_resize) padding_size = [ left_padding_size, up_padding_size, self.target - first_resize - left_padding_size, self.target - first_resize - up_padding_size ] input_transforms = transforms.Compose([ transforms.Resize(first_resize), transforms.Pad(padding_size)] ) return input_transforms(input) return input
[docs]class MDIFGSMAdversary(PGDAdversary): r""" Momentum Iterative Fast Gradient Sign Method with Input Diversity Method (M-DI-FGSM, 'Link'_). Args: epsilon (float): Maximum distortion of adversarial example compared to origin input. n_step (int): Number of attack iterations. momentum (float): Gradient momentum. rand_init (bool): Whether add random perturbation to origin input before formal attack. mean (List[float]): Sequence of means for each channel while normalizing the origin input. std (List[float]): Sequence of standard deviations for each channel while normalizing the origin input. diversity_p (float): Probability to apply the input diversity transformation. target_size (int): Randomly resized shape in the diversity input layer. step_size (Optional[float]): Step size for each attack iteration. If is not specified, ``step_size = epsilon / n_step``. Default: ``None``. criterion (Optional[_Loss]): Criterion to calculate the loss. Default: ``nn.CrossEntropyLoss``. use_eval_mode (bool): Whether use eval mode of the network while running attack. Default: ``False``. .. _Link: https://arxiv.org/abs/1904.02884 """ def __init__(self, epsilon: float, n_step: int, momentum: float, rand_init: bool, mean: List[float], std: List[float], diversity_p: float, target_size: int, step_size: Optional[float] = None, criterion: Optional[_Loss] = nn.CrossEntropyLoss(), use_eval_mode: bool = False) -> None: step_size = step_size or epsilon / n_step super(MDIFGSMAdversary, self).__init__( epsilon, n_step, step_size, rand_init, mean, std, criterion, use_eval_mode) self.momentum = momentum self.diversity_layer = DiversityLayer(target_size, diversity_p)
[docs] def generate_adv(self, net: nn.Module, input: Tensor, target: Tensor, output: Tensor) -> Tensor: self.mean = self.mean.to(input.device) self.std = self.std.to(input.device) wrapper_net = WrapperModel(net, self.mean, self.std) input_adv = Variable((input.data.clone() * self.std + self.mean), requires_grad = True) input_clone = input_adv.data.clone() g = torch.zeros_like(input_adv, device = input.device) if self.rand_init: eta = input.new(input.size()).uniform_(-self.epsilon, self.epsilon) input_adv.data = torch.clamp(input_adv.data + eta, 0., 1.) for _ in range(self.n_step): output = wrapper_net(self.diversity_layer(input_adv)) loss = self.criterion(output, Variable(target)) loss.backward() gradient = input_adv.grad.data g = self.momentum * g + gradient / torch.norm(gradient, p = 1) eta = self.step_size * g.data.sign() input_adv = Variable(input_adv.data + eta, requires_grad = True) eta = torch.clamp( input_adv.data - input_clone, -self.epsilon, self.epsilon ) input_adv.data = torch.clamp(input_clone + eta, 0., 1.) input_adv.data = (input_adv.data - self.mean) / self.std return input_adv.data
[docs]class MIFGSMAdversary(MDIFGSMAdversary): r""" Momentum Iterative Fast Gradient Sign Method (MI-FGSM, `Link`_). Args: epsilon (float): Maximum distortion of adversarial example compared to origin input. n_step (int): Number of attack iterations. momentum (float): Gradient momentum. rand_init (bool): Whether add random perturbation to origin input before formal attack. mean (List[float]): Sequence of means for each channel while normalizing the origin input. std (List[float]): Sequence of standard deviations for each channel while normalizing the origin input. step_size (Optional[float]): Step size for each attack iteration. If is not specified, ``step_size = epsilon / n_step``. Default: ``None``. criterion (Optional[_Loss]): Criterion to calculate the loss. Default: ``nn.CrossEntropyLoss``. use_eval_mode (bool): Whether use eval mode of the network while running attack. Default: ``False``. .. _Link: https://arxiv.org/abs/1710.06081v3 """ def __init__(self, epsilon: float, n_step: int, momentum: float, rand_init: bool, mean: List[float], std: List[float], step_size: Optional[float] = None, criterion: Optional[_Loss] = nn.CrossEntropyLoss(), use_eval_mode: bool = False) -> None: # do not apply the diversity input transformation diversity_p = 0. target_size = 0 super(MIFGSMAdversary, self).__init__(epsilon, n_step, momentum, rand_init, mean, std, diversity_p, target_size, step_size, criterion, use_eval_mode)
[docs]class IFGSMAdversary(MIFGSMAdversary): r""" Iterative Fast Gradient Sign Method (I-FGSM, `Link`_). Args: epsilon (float): Maximum distortion of adversarial example compared to origin input. n_step (int): Number of attack iterations. rand_init (bool): Whether add random perturbation to origin input before formal attack. mean (List[float]): Sequence of means for each channel while normalizing the origin input. std (List[float]): Sequence of standard deviations for each channel while normalizing the origin input. step_size (Optional[float]): Step size for each attack iteration. If is not specified, ``step_size = epsilon / n_step``. Default: ``None``. criterion (Optional[_Loss]): Criterion to calculate the loss. Default: ``nn.CrossEntropyLoss``. use_eval_mode (bool): Whether use eval mode of the network while running attack. Default: ``False``. .. _Link: https://arxiv.org/abs/1607.02533 """ def __init__(self, epsilon: float, n_step: int, rand_init: bool, mean: List[float], std: List[float], step_size: Optional[float] = None, criterion: Optional[_Loss] = nn.CrossEntropyLoss(), use_eval_mode: bool = False) -> None: momentum = 0. super(IFGSMAdversary, self).__init__(epsilon, n_step, momentum, rand_init, mean, std, step_size, criterion, use_eval_mode)