# Copyright 2019 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Gradient-method Attack.
"""
from abc import abstractmethod
import numpy as np
from mindspore.nn import Cell
from mindarmour.utils.util import WithLossCell, GradWrapWithLoss, to_tensor_tuple
from mindarmour.utils.logger import LogUtil
from mindarmour.utils._check_param import check_model, check_inputs_labels, \
normalize_value, check_value_positive, check_param_multi_types, \
check_norm_level, check_param_type
from .attack import Attack
LOGGER = LogUtil.get_instance()
TAG = 'SingleGrad'
class GradientMethod(Attack):
"""
Abstract base class for all single-step gradient-based attacks.
Args:
network (Cell): Target model.
eps (float): Proportion of single-step adversarial perturbation generated
by the attack to data range. Default: 0.07.
alpha (float): Proportion of single-step random perturbation to data range.
Default: None.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: None.
loss_fn (Loss): Loss function for optimization. If None, the input network \
is already equipped with loss function. Default: None.
"""
def __init__(self, network, eps=0.07, alpha=None, bounds=None,
loss_fn=None):
super(GradientMethod, self).__init__()
self._network = check_model('network', network, Cell)
self._eps = check_value_positive('eps', eps)
self._dtype = None
if bounds is not None:
self._bounds = check_param_multi_types('bounds', bounds,
[list, tuple])
for b in self._bounds:
_ = check_param_multi_types('bound', b, [int, float])
else:
self._bounds = bounds
if alpha is not None:
self._alpha = check_value_positive('alpha', alpha)
else:
self._alpha = alpha
if loss_fn is None:
self._grad_all = self._network
else:
with_loss_cell = WithLossCell(self._network, loss_fn)
self._grad_all = GradWrapWithLoss(with_loss_cell)
def generate(self, inputs, labels):
"""
Generate adversarial examples based on input samples and original/target labels.
Args:
inputs (Union[numpy.ndarray, tuple]): Benign input samples used as references to create
adversarial examples.
labels (Union[numpy.ndarray, tuple]): Original/target labels. \
For each input if it has more than one label, it is wrapped in a tuple.
Returns:
numpy.ndarray, generated adversarial examples.
"""
inputs_image, inputs, labels = check_inputs_labels(inputs, labels)
self._dtype = inputs_image.dtype
gradient = self._gradient(inputs, labels)
# use random method or not
if self._alpha is not None:
random_part = self._alpha*np.sign(np.random.normal(
size=inputs.shape)).astype(self._dtype)
perturbation = (self._eps - self._alpha)*gradient + random_part
else:
perturbation = self._eps*gradient
if self._bounds is not None:
clip_min, clip_max = self._bounds
perturbation = perturbation*(clip_max - clip_min)
adv_x = inputs_image + perturbation
adv_x = np.clip(adv_x, clip_min, clip_max)
else:
adv_x = inputs_image + perturbation
return adv_x
@abstractmethod
def _gradient(self, inputs, labels):
"""
Calculate gradients based on input samples and original/target labels.
Args:
inputs (Union[numpy.ndarray, tuple]): Benign input samples used as references to
create adversarial examples.
labels (Union[numpy.ndarray, tuple]): Original/target labels. \
For each input if it has more than one label, it is wrapped in a tuple.
Raises:
NotImplementedError: It is an abstract method.
"""
msg = 'The function _gradient() is an abstract method in class ' \
'`GradientMethod`, and should be implemented in child class.'
LOGGER.error(TAG, msg)
raise NotImplementedError(msg)
[文档]class FastGradientMethod(GradientMethod):
"""
This attack is a one-step attack based on gradients calculation, and
the norm of perturbations includes L1, L2 and Linf.
References: `I. J. Goodfellow, J. Shlens, and C. Szegedy, "Explaining
and harnessing adversarial examples," in ICLR, 2015.
<https://arxiv.org/abs/1412.6572>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of single-step adversarial perturbation generated
by the attack to data range. Default: 0.07.
alpha (Union[float, None]): Proportion of single-step random perturbation to data range.
Default: None.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: (0.0, 1.0).
norm_level (Union[int, str, numpy.inf]): Order of the norm.
Possible values: np.inf, 1 or 2. Default: 2.
is_targeted (bool): If True, targeted attack. If False, untargeted
attack. Default: False.
loss_fn (Union[loss, None]): Loss function for optimization. If None, the input network \
is already equipped with loss function. Default: None.
Examples:
>>> from mindarmour.adv_robustness.attacks import FastGradientMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._relu = nn.ReLU()
... def construct(self, inputs):
... out = self._relu(inputs)
... return out
>>> inputs = np.asarray([[0.1, 0.2, 0.7]], np.float32)
>>> labels = np.asarray([2],np.int32)
>>> labels = np.eye(3)[labels].astype(np.float32)
>>> net = Net()
>>> attack = FastGradientMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.07, alpha=None, bounds=(0.0, 1.0),
norm_level=2, is_targeted=False, loss_fn=None):
super(FastGradientMethod, self).__init__(network,
eps=eps,
alpha=alpha,
bounds=bounds,
loss_fn=loss_fn)
self._norm_level = check_norm_level(norm_level)
self._is_targeted = check_param_type('is_targeted', is_targeted, bool)
def _gradient(self, inputs, labels):
"""
Calculate gradients based on input samples and original/target labels.
Args:
inputs (Union[numpy.ndarray, tuple]): Input sample.
labels (Union[numpy.ndarray, tuple]): Original/target labels. \
For each input if it has more than one label, it is wrapped in a tuple.
Returns:
numpy.ndarray, gradient of inputs.
"""
inputs_tensor = to_tensor_tuple(inputs)
labels_tensor = to_tensor_tuple(labels)
out_grad = self._grad_all(*inputs_tensor, *labels_tensor)
if isinstance(out_grad, tuple):
out_grad = out_grad[0]
gradient = out_grad.asnumpy()
if self._is_targeted:
gradient = -gradient
return normalize_value(gradient, self._norm_level)
[文档]class RandomFastGradientMethod(FastGradientMethod):
"""
Fast Gradient Method use Random perturbation.
An one-step attack based on gradients calculation. The adversarial noises
are generated based on the gradients of inputs, and then randomly perturbed.
References: `Florian Tramer, Alexey Kurakin, Nicolas Papernot, "Ensemble
adversarial training: Attacks and defenses" in ICLR, 2018
<https://arxiv.org/abs/1705.07204>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of single-step adversarial perturbation generated
by the attack to data range. Default: 0.07.
alpha (float): Proportion of single-step random perturbation to data range.
Default: 0.035.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: (0.0, 1.0).
norm_level (Union[int, str, numpy.inf]): Order of the norm.
Possible values: np.inf, 1 or 2. Default: 2.
is_targeted (bool): If True, targeted attack. If False, untargeted
attack. Default: False.
loss_fn (Union[loss, None]): Loss function for optimization. If None, the input network \
is already equipped with loss function. Default: None.
Raises:
ValueError: eps is smaller than alpha!
Examples:
>>> from mindarmour.adv_robustness.attacks import RandomFastGradientMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._relu = nn.ReLU()
... def construct(self, inputs):
... out = self._relu(inputs)
... return out
>>> net = Net()
>>> inputs = np.asarray([[0.1, 0.2, 0.7]], np.float32)
>>> labels = np.asarray([2],np.int32)
>>> labels = np.eye(3)[labels].astype(np.float32)
>>> attack = RandomFastGradientMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.07, alpha=0.035, bounds=(0.0, 1.0),
norm_level=2, is_targeted=False, loss_fn=None):
if eps < alpha:
raise ValueError('eps must be larger than alpha!')
super(RandomFastGradientMethod, self).__init__(network,
eps=eps,
alpha=alpha,
bounds=bounds,
norm_level=norm_level,
is_targeted=is_targeted,
loss_fn=loss_fn)
[文档]class FastGradientSignMethod(GradientMethod):
"""
The Fast Gradient Sign Method attack calculates the gradient of the input
data, and then uses the sign of the gradient to create adversarial noises.
References: `Ian J. Goodfellow, J. Shlens, and C. Szegedy, "Explaining
and harnessing adversarial examples," in ICLR, 2015
<https://arxiv.org/abs/1412.6572>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of single-step adversarial perturbation generated
by the attack to data range. Default: 0.07.
alpha (Union[float, None]): Proportion of single-step random perturbation to data range.
Default: None.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: (0.0, 1.0).
is_targeted (bool): If True, targeted attack. If False, untargeted
attack. Default: False.
loss_fn (Union[Loss, None]): Loss function for optimization. If None, the input network \
is already equipped with loss function. Default: None.
Examples:
>>> from mindarmour.adv_robustness.attacks import FastGradientSignMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._relu = nn.ReLU()
... def construct(self, inputs):
... out = self._relu(inputs)
... return out
>>> net = Net()
>>> inputs = np.asarray([[0.1, 0.2, 0.7]], np.float32)
>>> labels = np.asarray([2],np.int32)
>>> labels = np.eye(3)[labels].astype(np.float32)
>>> attack = FastGradientSignMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.07, alpha=None, bounds=(0.0, 1.0),
is_targeted=False, loss_fn=None):
super(FastGradientSignMethod, self).__init__(network,
eps=eps,
alpha=alpha,
bounds=bounds,
loss_fn=loss_fn)
self._is_targeted = check_param_type('is_targeted', is_targeted, bool)
def _gradient(self, inputs, labels):
"""
Calculate gradients based on input samples and original/target
labels.
Args:
inputs (Union[numpy.ndarray, tuple]): Input samples.
labels (Union[numpy.ndarray, tuple]): original/target labels. \
for each input if it has more than one label, it is wrapped in a tuple.
Returns:
numpy.ndarray, gradient of inputs.
"""
inputs_tensor = to_tensor_tuple(inputs)
labels_tensor = to_tensor_tuple(labels)
out_grad = self._grad_all(*inputs_tensor, *labels_tensor)
if isinstance(out_grad, tuple):
out_grad = out_grad[0]
gradient = out_grad.asnumpy()
if self._is_targeted:
gradient = -gradient
gradient = np.sign(gradient)
return gradient
[文档]class RandomFastGradientSignMethod(FastGradientSignMethod):
"""
Fast Gradient Sign Method using random perturbation.
The Random Fast Gradient Sign Method attack calculates the gradient of the input
data, and then uses the sign of the gradient with random perturbation
to create adversarial noises.
References: `F. Tramer, et al., "Ensemble adversarial training: Attacks
and defenses," in ICLR, 2018 <https://arxiv.org/abs/1705.07204>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of single-step adversarial perturbation generated
by the attack to data range. Default: 0.07.
alpha (float): Proportion of single-step random perturbation to data range.
Default: 0.035.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: (0.0, 1.0).
is_targeted (bool): True: targeted attack. False: untargeted attack.
Default: False.
loss_fn (Union[Loss, None]): Loss function for optimization. If None, the input network \
is already equipped with loss function. Default: None.
Raises:
ValueError: eps is smaller than alpha!
Examples:
>>> from mindarmour.adv_robustness.attacks import RandomFastGradientSignMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._relu = nn.ReLU()
... def construct(self, inputs):
... out = self._relu(inputs)
... return out
>>> net = Net()
>>> inputs = np.asarray([[0.1, 0.2, 0.7]], np.float32)
>>> labels = np.asarray([2],np.int32)
>>> labels = np.eye(3)[labels].astype(np.float32)
>>> attack = RandomFastGradientSignMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.07, alpha=0.035, bounds=(0.0, 1.0),
is_targeted=False, loss_fn=None):
if eps < alpha:
raise ValueError('eps must be larger than alpha!')
super(RandomFastGradientSignMethod, self).__init__(network,
eps=eps,
alpha=alpha,
bounds=bounds,
is_targeted=is_targeted,
loss_fn=loss_fn)
[文档]class LeastLikelyClassMethod(FastGradientSignMethod):
"""
The Single Step Least-Likely Class Method, a variant of FGSM, targets the
least-likely class to generate the adversarial examples.
References: `F. Tramer, et al., "Ensemble adversarial training: Attacks
and defenses," in ICLR, 2018 <https://arxiv.org/abs/1705.07204>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of single-step adversarial perturbation generated
by the attack to data range. Default: 0.07.
alpha (Union[float, None]): Proportion of single-step random perturbation to data range.
Default: None.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: (0.0, 1.0).
loss_fn (Union[Loss, None]): Loss function for optimization. If None, the input network \
is already equipped with loss function. Default: None.
Examples:
>>> from mindarmour.adv_robustness.attacks import LeastLikelyClassMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._relu = nn.ReLU()
... def construct(self, inputs):
... out = self._relu(inputs)
... return out
>>> inputs = np.asarray([[0.1, 0.2, 0.7]], np.float32)
>>> labels = np.asarray([2],np.int32)
>>> labels = np.eye(3)[labels].astype(np.float32)
>>> net = Net()
>>> attack = LeastLikelyClassMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.07, alpha=None, bounds=(0.0, 1.0),
loss_fn=None):
super(LeastLikelyClassMethod, self).__init__(network,
eps=eps,
alpha=alpha,
bounds=bounds,
is_targeted=True,
loss_fn=loss_fn)
[文档]class RandomLeastLikelyClassMethod(FastGradientSignMethod):
"""
Least-Likely Class Method use Random perturbation.
The Single Step Least-Likely Class Method with Random Perturbation, a variant of Random FGSM,
targets the least-likely class to generate the adversarial examples.
References: `F. Tramer, et al., "Ensemble adversarial training: Attacks
and defenses," in ICLR, 2018 <https://arxiv.org/abs/1705.07204>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of single-step adversarial perturbation generated
by the attack to data range. Default: 0.07.
alpha (float): Proportion of single-step random perturbation to data range.
Default: 0.035.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: (0.0, 1.0).
loss_fn (Union[Loss, None]): Loss function for optimization. If None, the input network \
is already equipped with loss function. Default: None.
Raises:
ValueError: eps is smaller than alpha!
Examples:
>>> from mindarmour.adv_robustness.attacks import RandomLeastLikelyClassMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._relu = nn.ReLU()
... def construct(self, inputs):
... out = self._relu(inputs)
... return out
>>> inputs = np.asarray([[0.1, 0.2, 0.7]], np.float32)
>>> labels = np.asarray([2],np.int32)
>>> labels = np.eye(3)[labels].astype(np.float32)
>>> net = Net()
>>> attack = RandomLeastLikelyClassMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.07, alpha=0.035, bounds=(0.0, 1.0),
loss_fn=None):
if eps < alpha:
raise ValueError('eps must be larger than alpha!')
super(RandomLeastLikelyClassMethod, self).__init__(network,
eps=eps,
alpha=alpha,
bounds=bounds,
is_targeted=True,
loss_fn=loss_fn)