# 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.
""" Iterative gradient method attack. """
from abc import abstractmethod
import copy
import numpy as np
from PIL import Image, ImageOps
import mindspore.nn as nn
import mindspore.ops as ops
import mindspore as ms
from mindspore.nn import Cell
from mindarmour.utils.logger import LogUtil
from mindarmour.utils.util import WithLossCell, GradWrapWithLoss, to_tensor_tuple
from mindarmour.utils._check_param import check_inputs_labels, \
normalize_value, check_model, check_value_positive, check_int_positive, \
check_param_type, check_norm_level, check_param_multi_types
from .attack import Attack
from .gradient_method import FastGradientSignMethod
LOGGER = LogUtil.get_instance()
TAG = 'IterGrad'
def _reshape_l1_projection(values, eps=3):
"""
`Implementation of L1 ball projection from:`_.
.. _`Implementation of L1 ball projection from:`:
https://stanford.edu/~jduchi/projects/DuchiShSiCh08.pdf
Args:
values (numpy.ndarray): Input data reshape into 2-dims.
eps (float): L1 radius. Default: 3.
Returns:
numpy.ndarray, containing the projection.
"""
abs_x = np.abs(values)
abs_x = np.sum(abs_x, axis=1)
indexes_b = (abs_x > eps)
x_b = values[indexes_b]
batch_size_b = x_b.shape[0]
if batch_size_b == 0:
return values
# make the projection on l1 ball for elements outside the ball
b_mu = -np.sort(-np.abs(x_b), axis=1)
b_vv = np.arange(x_b.shape[1]).astype(np.float)
b_st = (np.cumsum(b_mu, axis=1)-eps)/(b_vv+1)
selected = (b_mu - b_st) > 0
rho = np.sum((np.cumsum((1-selected), axis=1) == 0), axis=1)-1
theta = np.take_along_axis(b_st, np.expand_dims(rho, axis=1), axis=1)
proj_x_b = np.maximum(0, np.abs(x_b)-theta)*np.sign(x_b)
# gather all the projected batch
proj_x = np.copy(values)
proj_x[indexes_b] = proj_x_b
return proj_x
def _projection(values, eps, clip_diff, norm_level):
"""
Implementation of values normalization within eps.
Args:
values (numpy.ndarray): Input data.
eps (float): Project radius.
clip_diff (float): Difference range of clip bounds.
norm_level (Union[int, char, numpy.inf]): Order of the norm. Possible
values: np.inf, 1 or 2.
Returns:
numpy.ndarray, normalized values.
Raises:
NotImplementedError: If the norm_level is not in [1, 2, np.inf, '1',
'2', 'inf'].
"""
if norm_level in (1, '1'):
sample_batch = values.shape[0]
x_flat = values.reshape(sample_batch, -1)
proj_flat = _reshape_l1_projection(x_flat, eps*clip_diff)
return proj_flat.reshape(values.shape)
if norm_level in (2, '2'):
return eps*normalize_value(values, norm_level)
if norm_level in (np.inf, 'inf'):
return eps*clip_diff*np.sign(values)
msg = 'Values of `norm_level` different from 1, 2 and `np.inf` are ' \
'currently not supported.'
LOGGER.error(TAG, msg)
raise NotImplementedError(msg)
[文档]class IterativeGradientMethod(Attack):
"""
Abstract base class for all iterative gradient based attacks.
Args:
network (Cell): Target model.
eps (float): Proportion of adversarial perturbation generated by the
attack to data range. Default: ``0.3``.
eps_iter (float): Proportion of single-step adversarial perturbation
generated by the attack to data range. Default: ``0.1``.
bounds (tuple): Upper and lower bounds of data, indicating the data range.
In form of (clip_min, clip_max). Default: ``(0.0, 1.0)``.
nb_iter (int): Number of iteration. Default: ``5``.
loss_fn (Union[Loss, None]): Loss function for optimization. If ``None``, the input network \
is already equipped with loss function. Default: ``None``.
"""
def __init__(self, network, eps=0.3, eps_iter=0.1, bounds=(0.0, 1.0), nb_iter=5,
loss_fn=None):
super(IterativeGradientMethod, self).__init__()
self._network = check_model('network', network, Cell)
self._eps = check_value_positive('eps', eps)
self._eps_iter = check_value_positive('eps_iter', eps_iter)
self._nb_iter = check_int_positive('nb_iter', nb_iter)
self._bounds = 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])
if loss_fn is None:
self._loss_grad = network
else:
self._loss_grad = GradWrapWithLoss(WithLossCell(self._network, loss_fn))
[文档] @abstractmethod
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.
Raises:
NotImplementedError: This function is not available in
IterativeGradientMethod.
"""
msg = 'The function generate() is an abstract method in class ' \
'`IterativeGradientMethod`, and should be implemented ' \
'in child class.'
LOGGER.error(TAG, msg)
raise NotImplementedError(msg)
[文档]class BasicIterativeMethod(IterativeGradientMethod):
"""
The Basic Iterative Method attack, an iterative FGSM method to generate
adversarial examples.
References: `A. Kurakin, I. Goodfellow, and S. Bengio, "Adversarial examples
in the physical world," in ICLR, 2017 <https://arxiv.org/abs/1607.02533>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of adversarial perturbation generated by the
attack to data range. Default: ``0.3``.
eps_iter (float): Proportion of single-step adversarial perturbation
generated by the attack to data range. Default: ``0.1``.
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``.
nb_iter (int): Number of iteration. Default: ``5``.
loss_fn (Union[Loss, None]): Loss function for optimization. If ``None``, the input network \
is already equipped with loss function. Default: ``None``.
Examples:
>>> from mindspore.ops import operations as P
>>> from mindarmour.adv_robustness.attacks import BasicIterativeMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._softmax = P.Softmax()
... def construct(self, inputs):
... out = self._softmax(inputs)
... return out
>>> net = Net()
>>> attack = BasicIterativeMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> 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()
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.3, eps_iter=0.1, bounds=(0.0, 1.0),
is_targeted=False, nb_iter=5, loss_fn=None):
super(BasicIterativeMethod, self).__init__(network,
eps=eps,
eps_iter=eps_iter,
bounds=bounds,
nb_iter=nb_iter,
loss_fn=loss_fn)
self._is_targeted = check_param_type('is_targeted', is_targeted, bool)
self._attack = FastGradientSignMethod(self._network,
eps=self._eps_iter,
bounds=self._bounds,
is_targeted=self._is_targeted,
loss_fn=loss_fn)
[文档] def generate(self, inputs, labels):
"""
Simple iterative FGSM method to generate adversarial examples.
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)
arr_x = inputs_image
if self._bounds is not None:
clip_min, clip_max = self._bounds
clip_diff = clip_max - clip_min
for _ in range(self._nb_iter):
if 'self._prob' in globals():
d_inputs = _transform_inputs(inputs_image, self._prob)
else:
d_inputs = inputs_image
if isinstance(inputs, tuple):
d_inputs = (d_inputs,) + inputs[1:]
adv_x = self._attack.generate(d_inputs, labels)
perturs = np.clip(adv_x - arr_x, (0 - self._eps)*clip_diff,
self._eps*clip_diff)
adv_x = arr_x + perturs
inputs_image = adv_x
else:
for _ in range(self._nb_iter):
if 'self._prob' in globals():
d_inputs = _transform_inputs(inputs_image, self._prob)
else:
d_inputs = inputs_image
if isinstance(inputs, tuple):
d_inputs = (inputs_image,) + inputs[1:]
adv_x = self._attack.generate(d_inputs, labels)
adv_x = np.clip(adv_x, arr_x - self._eps, arr_x + self._eps)
inputs_image = adv_x
return adv_x
[文档]class MomentumIterativeMethod(IterativeGradientMethod):
"""
The Momentum Iterative Method attack accelerates the gradient descent algorithm,
such as FGSM, FGM, and LLCM, by accumulating a velocity vector in the gradient
direction of the loss function across iterations, and thus generates the adversarial examples.
References: `Y. Dong, et al., "Boosting adversarial attacks with
momentum," arXiv:1710.06081, 2017 <https://arxiv.org/abs/1710.06081>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of adversarial perturbation generated by the
attack to data range. Default: ``0.3``.
eps_iter (float): Proportion of single-step adversarial perturbation
generated by the attack to data range. Default: ``0.1``.
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``.
nb_iter (int): Number of iteration. Default: ``5``.
decay_factor (float): Decay factor in iterations. Default: ``1.0``.
norm_level (Union[int, str, numpy.inf]): Order of the norm. Possible values:
``np.inf``, ``1`` or ``2``. Default: ``'inf'``.
loss_fn (Union[Loss, None]): Loss function for optimization. If ``None``, the input network \
is already equipped with loss function. Default: ``None``.
Examples:
>>> from mindspore.ops import operations as P
>>> from mindarmour.adv_robustness.attacks import MomentumIterativeMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._softmax = P.Softmax()
... def construct(self, inputs):
... out = self._softmax(inputs)
... return out
>>> net = Net()
>>> attack = MomentumIterativeMethod(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> 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()
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.3, eps_iter=0.1, bounds=(0.0, 1.0),
is_targeted=False, nb_iter=5, decay_factor=1.0,
norm_level='inf', loss_fn=None):
super(MomentumIterativeMethod, self).__init__(network,
eps=eps,
eps_iter=eps_iter,
bounds=bounds,
nb_iter=nb_iter,
loss_fn=loss_fn)
self._is_targeted = check_param_type('is_targeted', is_targeted, bool)
self._decay_factor = check_value_positive('decay_factor', decay_factor)
self._norm_level = check_norm_level(norm_level)
[文档] def generate(self, inputs, labels):
"""
Generate adversarial examples based on input data and origin/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)
arr_x = inputs_image
momentum = 0
if self._bounds is not None:
clip_min, clip_max = self._bounds
clip_diff = clip_max - clip_min
for _ in range(self._nb_iter):
if 'self._prob' in globals():
d_inputs = _transform_inputs(inputs_image, self._prob)
else:
d_inputs = inputs_image
if isinstance(inputs, tuple):
d_inputs = (d_inputs,) + inputs[1:]
gradient = self._gradient(d_inputs, labels)
momentum = self._decay_factor*momentum + gradient
if isinstance(d_inputs, tuple):
adv_x = d_inputs[0] + self._eps_iter*np.sign(momentum)
else:
adv_x = d_inputs + self._eps_iter*np.sign(momentum)
perturs = np.clip(adv_x - arr_x, (0 - self._eps)*clip_diff,
self._eps*clip_diff)
adv_x = arr_x + perturs
adv_x = np.clip(adv_x, clip_min, clip_max)
inputs_image = adv_x
else:
for _ in range(self._nb_iter):
if 'self._prob' in globals():
d_inputs = _transform_inputs(inputs_image, self._prob)
else:
d_inputs = inputs_image
if isinstance(inputs, tuple):
d_inputs = (d_inputs,) + inputs[1:]
gradient = self._gradient(d_inputs, labels)
momentum = self._decay_factor*momentum + gradient
if isinstance(d_inputs, tuple):
adv_x = d_inputs[0] + self._eps_iter*np.sign(momentum)
else:
adv_x = d_inputs + self._eps_iter*np.sign(momentum)
adv_x = np.clip(adv_x, arr_x - self._eps, arr_x + self._eps)
inputs_image = adv_x
return adv_x
def _gradient(self, inputs, labels):
"""
Calculate the gradient of input samples.
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 labels w.r.t inputs.
Examples:
>>> grad = self._gradient([[0.5, 0.3, 0.4]],
... [[0, 0, 0, 1, 0, 0, 0, 0, 0, 0])
"""
# get grad of loss over x
inputs_tensor = to_tensor_tuple(inputs)
labels_tensor = to_tensor_tuple(labels)
out_grad = self._loss_grad(*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 ProjectedGradientDescent(BasicIterativeMethod):
"""
The Projected Gradient Descent attack is a variant of the Basic Iterative
Method in which, after each iteration, the perturbation is projected on an
lp-ball of specified radius (in addition to clipping the values of the
adversarial sample so that it lies in the permitted data range). This is
the attack proposed by Madry et al. for adversarial training.
References: `A. Madry, et al., "Towards deep learning models resistant to
adversarial attacks," in ICLR, 2018 <https://arxiv.org/abs/1706.06083>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of adversarial perturbation generated by the
attack to data range. Default: ``0.3``.
eps_iter (float): Proportion of single-step adversarial perturbation
generated by the attack to data range. Default: ``0.1``.
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``.
nb_iter (int): Number of iteration. Default: ``5``.
norm_level (Union[int, str, numpy.inf]): Order of the norm. Possible values:
np.inf, 1 or 2. Default: ``'inf'``.
loss_fn (Union[Loss, None]): Loss function for optimization. If ``None``, the input network \
is already equipped with loss function. Default: ``None``.
Examples:
>>> from mindspore.ops import operations as P
>>> from mindarmour.adv_robustness.attacks import ProjectedGradientDescent
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._softmax = P.Softmax()
... def construct(self, inputs):
... out = self._softmax(inputs)
... return out
>>> net = Net()
>>> attack = ProjectedGradientDescent(net, loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> 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()
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.3, eps_iter=0.1, bounds=(0.0, 1.0),
is_targeted=False, nb_iter=5, norm_level='inf', loss_fn=None):
super(ProjectedGradientDescent, self).__init__(network,
eps=eps,
eps_iter=eps_iter,
bounds=bounds,
is_targeted=is_targeted,
nb_iter=nb_iter,
loss_fn=loss_fn)
self._norm_level = check_norm_level(norm_level)
[文档] def generate(self, inputs, labels):
"""
Iteratively generate adversarial examples. The
perturbation is normalized by projected method with parameter norm_level .
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)
arr_x = inputs_image
adv_x = copy.deepcopy(inputs_image)
if self._bounds is not None:
clip_min, clip_max = self._bounds
clip_diff = clip_max - clip_min
else:
clip_diff = 1
for _ in range(self._nb_iter):
inputs_tensor = to_tensor_tuple(inputs)
labels_tensor = to_tensor_tuple(labels)
out_grad = self._loss_grad(*inputs_tensor, *labels_tensor)
gradient = out_grad.asnumpy()
perturbs = _projection(gradient, self._eps_iter, clip_diff, norm_level=self._norm_level)
sum_perturbs = adv_x - arr_x + perturbs
sum_perturbs = np.clip(sum_perturbs, (0 - self._eps)*clip_diff, self._eps*clip_diff)
adv_x = arr_x + sum_perturbs
if self._bounds is not None:
adv_x = np.clip(adv_x, clip_min, clip_max)
if isinstance(inputs, tuple):
inputs = (adv_x,) + inputs[1:]
else:
inputs = adv_x
return adv_x
[文档]class AutoProjectedGradientDescent(BasicIterativeMethod):
"""
APGD (Auto Projected Gradient Descent) is an iterative method and an upgraded
version of the PGD method. In this approach, perturbations are projected onto
the Lp-ball after each iteration (in addition to clipping the values of the
adversarial samples so that they lie within the allowed data range) and adaptive
steps and momentum are introduced to accelerate convergence and improve attack
performance. This is the attack proposed for adversarial training by Croce et al.
Reference: `Croce and Hein, "Reliable evaluation of adversarial robustness with an ensemble of \
diverse parameter-free attacks" in ICML, 2020 <https://arxiv.org/abs/2003.01690>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of adversarial perturbation generated by the
attack to data range. Default: ``8 / 255``.
eps_iter (float): Proportion of single-step adversarial perturbation
generated by the attack to data range. Default: ``0.1``.
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``.
nb_iter (int) : Number of iteration. Default: ``10``.
norm_level (Union[int, str, numpy.inf]): Order of the norm. Possible values:
``inf``, ``1`` or ``2``. Default: ``inf``.
loss_fn (Union[Loss, None]): Loss function for optimization. If ``None``, the input network \
is already equipped with loss function. Default: ``None``.
eot_iter (int): Number of iteration for EOT. Default: ``1``.
thr_decr (float): Parameter for step-size update. Default: ``0.75``.
Examples:
>>> import numpy as np
>>> import mindspore.ops as ops
>>> import mindspore.nn as nn
>>> from mindarmour.adv_robustness.attacks import AutoProjectedGradientDescent
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._softmax = ops.Softmax()
... def construct(self, inputs):
... out = self._softmax(inputs)
... return out
>>> net = Net()
>>> attack = AutoProjectedGradientDescent(net, eps=0.3)
>>> 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()
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=8 / 255, eps_iter=0.1, bounds=(0.0, 1.0), is_targeted=False, nb_iter=10,
norm_level='inf', loss_fn=None, eot_iter=1, thr_decr=0.75):
super(AutoProjectedGradientDescent, self).__init__(network, eps=eps, eps_iter=eps_iter, bounds=bounds,
is_targeted=is_targeted, nb_iter=nb_iter, loss_fn=loss_fn)
self._norm = norm_level
self._steps = nb_iter
self._eot_iter = eot_iter
self._eps = eps
self._loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')
self._network = network
self._norm_level = check_norm_level(norm_level)
self._thr_decr = check_value_positive('thr_decr', thr_decr)
[文档] def generate(self, inputs, labels):
"""
Iteratively generate adversarial examples. The perturbation
is normalized by projected method with parameter `norm_level`.
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 = ms.Tensor(inputs)
labels = ms.Tensor(labels)
x = inputs.copy() if len(inputs.shape) == 4 else inputs.copy().unsqueeze(0)
y = labels.copy() if len(labels.shape) == 1 else labels.copy().unsqueeze(0)
steps_ratio, steps_min_ratio, size_decr_ratio = 0.22, 0.06, 0.03
steps, steps_min, size_decr = max(int(steps_ratio * self._steps), 1), max(
int(steps_min_ratio * self._steps), 1), max(int(size_decr_ratio * self._steps), 1)
if self._norm == 'inf':
t = 2 * ms.Tensor(np.random.rand(*x.shape).astype(np.float32)) - 1
x_adv = x + self._eps * ms.Tensor(np.ones([x.shape[0], 1, 1, 1]), dtype=ms.float32) * t / (
t.reshape([t.shape[0], -1]).abs().max(axis=1, keepdims=True)[0].reshape([-1, 1, 1, 1]))
elif self._norm == 'L2':
t = ms.Tensor(np.random.rand(*x.shape).astype(np.float32))
x_adv = x + self._eps * ms.Tensor(np.ones([x.shape[0], 1, 1, 1]), dtype=ms.float32) * t / (
(t ** 2).sum(axis=(1, 2, 3), keepdims=True).sqrt() + 1e-12)
x_adv = x_adv.clip(0.0, 1.0)
x_best = x_adv.copy()
x_best_adv = x_adv.copy()
loss_steps = np.zeros((self._steps, x.shape[0]))
loss_best_steps = np.zeros((self._steps + 1, x.shape[0]))
acc_steps = np.zeros_like(loss_best_steps)
criterion_indiv = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='none')
grad = ops.ZerosLike()(x)
for _ in range(self._eot_iter):
grad += GradWrapWithLoss(WithLossCell(self._network, self._loss_fn))(x_adv, y)
grad = ops.div(grad, self._eot_iter)
grad_best = grad.copy()
logits = self._network(x_adv)
loss_indiv = criterion_indiv(logits, y)
acc = logits.max(1)[1] == y
acc_steps[0] = acc + 0
loss_best = loss_indiv.copy()
step_size = self._eps * ms.Tensor(np.ones([x.shape[0], 1, 1, 1]), dtype=ms.float32) * ms.Tensor([2.0]).reshape(
[1, 1, 1, 1])
x_adv_old = x_adv.copy()
k = steps + 0
u = np.arange(x.shape[0])
counter = 0
momentum = 0.75
loss_best_last_check = loss_best.copy()
reduced_last_check = np.zeros(loss_best.shape) == np.zeros(loss_best.shape)
x_best = self._optimize_attack(x_adv, x_best, x_best_adv, x_adv_old, momentum, counter, k, step_size, grad,
x, y, logits, acc, acc_steps, loss_indiv, loss_steps, loss_best, grad_best,
loss_best_steps, reduced_last_check, loss_best_last_check, u,
size_decr, steps_min)
return x_best.asnumpy()
def _optimize_attack(self, x_adv, x_best, x_best_adv, x_adv_old, momentum, counter, k, step_size, grad,
x, y, logits, acc, acc_steps, loss_indiv, loss_steps, loss_best, grad_best, loss_best_steps,
reduced_last_check, loss_best_last_check, u, size_decr, steps_min):
"""
This function computes the gradient and optimizes it by updating x_adv with multiple iterations.
"""
for i in range(self._steps):
grad2 = x_adv - x_adv_old
x_adv_old = x_adv.copy()
a = momentum if i > 0 else 1.0
if self._norm == 'inf':
x_adv_1 = x_adv + step_size * ops.operations.Sign()(grad)
x_adv_1 = ops.clip_by_value(ops.clip_by_value(x_adv_1, x - self._eps, x + self._eps), 0.0, 1.0)
x_adv_1 = ops.clip_by_value(
ops.clip_by_value(x_adv + (x_adv_1 - x_adv) * a + grad2 * (1 - a), x - self._eps, x
+ self._eps), 0.0, 1.0)
elif self._norm == 'L2':
x_adv_1 = x_adv + step_size * grad / (
ms.ops.square(grad).sum(axis=(1, 2, 3), keepdims=True).sqrt() + 1e-12)
x_adv_1 = ops.clip_by_value(x + (x_adv_1 - x) / (
((x_adv_1 - x) ** 2).sum(axis=(1, 2, 3), keepdims=True).sqrt() + 1e-12) * ops.minimum(
self._eps * ops.ones(x.shape, type=ms.float32),
((x_adv_1 - x) ** 2).sum(axis=(1, 2, 3), keepdims=True).sqrt()),
0.0, 1.0)
x_adv_1 = x_adv + (x_adv_1 - x_adv) * a + grad2 * (1 - a)
x_adv_1 = ops.clip_by_value(x + (x_adv_1 - x) / (
((x_adv_1 - x) ** 2).sum(axis=(1, 2, 3), keepdims=True).sqrt() + 1e-12) * ops.minimum(
self._eps * ops.ones(x.shape, type=ms.float32),
((x_adv_1 - x) ** 2).sum(axis=(1, 2, 3), keepdims=True).sqrt() + 1e-12), 0.0, 1.0)
x_adv = x_adv_1 + 0.
grad = ops.zeros_like(x)
for _ in range(self._eot_iter):
grad += GradWrapWithLoss(WithLossCell(self._network, self._loss_fn))(x_adv, y)
grad /= float(self._eot_iter)
pred = logits.max(1)[1] == y
acc = ops.logical_and(acc, pred)
acc_steps[i + 1] = acc + 0
zero_indices = ops.nonzero(pred == 0).squeeze()
x_best_adv[zero_indices] = x_adv[zero_indices] + ops.zeros_like(x_adv[zero_indices])
y1 = loss_indiv.copy()
loss_steps[i] = y1.asnumpy() + 0
ind = (y1 >= loss_best).nonzero().squeeze()
x_best[ind] = x_adv[ind].copy()
grad_best[ind] = grad[ind].copy()
loss_best[ind] = y1[ind] + 0
loss_best_steps[i + 1] = loss_best.asnumpy() + 0
counter += 1
if counter == k:
fl_oscillation = self._check_oscillation(loss_steps, i, k,
k3=self._thr_decr)
fl_reduce_no_impr = (~reduced_last_check) * (loss_best_last_check.asnumpy() >= loss_best.asnumpy())
fl_oscillation = ~(~fl_oscillation * ~fl_reduce_no_impr)
reduced_last_check = np.copy(fl_oscillation)
loss_best_last_check = loss_best.copy()
if np.sum(fl_oscillation) > 0:
step_size_np = step_size.asnumpy()
step_size_np[u[fl_oscillation]] /= 2.0
step_size = ms.Tensor(step_size_np)
fl_oscillation = np.where(fl_oscillation)
fl_oscillation = ms.Tensor(fl_oscillation)
x_adv[fl_oscillation] = x_best[fl_oscillation].copy()
grad[fl_oscillation] = grad_best[fl_oscillation].copy()
counter = 0
k = np.maximum(k - size_decr, steps_min)
return x_best
def _check_oscillation(self, x, j, k, k3=0.75):
"""
This function checks if there is oscillation in a given set of numbers. It counts how many
times the numbers go up in a certain range around a specific number. If this count is less
than or equal to a certain threshold, there is no oscillation. Otherwise, there is oscillation.
"""
t = np.zeros(x.shape[1])
for counter in range(k):
t += x[j - counter] > x[j - counter - 1]
return t <= k * k3 * np.ones(t.shape)
def _transform_inputs(inputs, prob, low=29, high=33, full_aug=False):
"""
Inputs data augmentation.
Args:
inputs (Union[np.int8, np.float]): Inputs.
prob (float): The probability of augmentation.
low (int): Lower bound of resize image width. Default: 29.
high (int): Upper bound of resize image height. Default: 33.
full_aug (bool): type of augmentation method, use interpolation and padding
as default. Default: ``False``.
Returns:
numpy.ndarray, the augmentation data.
"""
raw_shape = inputs[0].shape
tran_mask = np.random.uniform(0, 1, size=inputs.shape[0]) < prob
tran_inputs = inputs[tran_mask]
raw_inputs = inputs[tran_mask == 0]
tran_outputs = []
for sample in tran_inputs:
width = np.random.choice(np.arange(low, high))
# resize
sample = (sample*255).astype(np.uint8)
d_image = Image.fromarray(sample, mode='L').resize((width, width), Image.NEAREST)
# pad
left_pad = (raw_shape[0] - width) // 2
right_pad = raw_shape[0] - width - left_pad
top_pad = (raw_shape[1] - width) // 2
bottom_pad = raw_shape[1] - width - top_pad
p_sample = ImageOps.expand(d_image,
border=(left_pad, top_pad, right_pad, bottom_pad))
tran_outputs.append(np.array(p_sample).astype(np.float) / 255)
if full_aug:
# gaussian noise
tran_outputs = np.random.normal(np.array(tran_outputs).shape) + tran_outputs
tran_outputs.extend(raw_inputs)
if not np.any(tran_outputs-raw_inputs):
LOGGER.error(TAG, 'the transform function does not take effect.')
return tran_outputs
[文档]class VarianceTuningMomentumIterativeMethod(MomentumIterativeMethod):
"""
VMI-FGSM is a momentum iterative method, it aggregate the gradient with variance on the input data
in each iteration, and could improve the transferability of the adversarial examples.
Reference: `X Wang, H Kun, "Enhancing the Transferability of Adversarial Attacks through Variance Tuning"
in CVPR, 2021 <https://arxiv.org/abs/2103.15571>`_.
Args:
network (Cell): Target model.
eps (float): Proportion of adversarial perturbation generated by the
attack to data range. Default: ``0.3``.
eps_iter (float): The proportion of perturbation in each step. Default: ``0.1``.
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``.
nb_iter (int) : Number of iteration. Default: ``5``.
decay_factor (float): The momentum factor. Default: ``1.0``.
nb_neighbor (int): The number of sampled examples in the neighborhood.
neighbor_beta (float): The upper bound of neighborhood. Default: ``3/2``.
norm_level (Union[int, str, numpy.inf]): Order of the norm. Possible values:
np.inf, 1 or 2. Default: ``inf``.
loss_fn (Union[Loss, None]): Loss function for optimization. If ``None``, the input network \
is already equipped with loss function. Default: ``None``.
Examples:
>>> from mindspore.ops import operations as P
>>> from mindarmour.adv_robustness.attacks import VarianceTuningMomentumIterativeMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._softmax = P.Softmax()
... def construct(self, inputs):
... out = self._softmax(inputs)
... return out
>>> net = Net()
>>> loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False)
>>> attack = VarianceTuningMomentumIterativeMethod(net, nb_neighbor=5, neighbor_beta=3/2, loss_fn=loss_fn)
>>> 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()
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=0.3, eps_iter=0.1, bounds=(0.0, 1.0),
is_targeted=False, nb_iter=5, decay_factor=1.0, nb_neighbor=5, neighbor_beta=3 / 2,
norm_level='inf', loss_fn=None):
super(VarianceTuningMomentumIterativeMethod, self).__init__(network,
eps=eps,
eps_iter=eps_iter,
bounds=bounds,
nb_iter=nb_iter,
loss_fn=loss_fn)
self._is_targeted = check_param_type('is_targeted', is_targeted, bool)
self._decay_factor = check_value_positive('decay_factor', decay_factor)
self._norm_level = check_norm_level(norm_level)
self._nb_neighbor = check_int_positive('nb_neighbor', nb_neighbor)
self._neighbor_beta = check_value_positive('neighbor_beta', neighbor_beta)
[文档] def generate(self, inputs, labels):
"""
Generate adversarial examples based on input data and origin/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)
ndim = np.ndim(inputs_image)
if ndim < 4:
for _ in range(4-ndim):
inputs_image = np.expand_dims(inputs_image, axis=0)
momentum = 0
v = 0
adv_x = copy.deepcopy(inputs_image)
clip_min, clip_max = self._bounds
clip_diff = clip_max - clip_min
for _ in range(self._nb_iter):
adv_grad = self._gradient(adv_x, labels)
grad = (adv_grad + v) / (np.mean(np.abs(adv_x + v), axis=(1, 2, 3), keepdims=True) + 1e-12)
grad = grad + momentum * self._decay_factor
momentum = grad
gv_grad = 0
for _ in range(self._nb_neighbor):
neighbor_x = adv_x + np.random.uniform(-self._neighbor_beta * self._eps,
self._eps * self._neighbor_beta,
size=inputs_image.shape).astype(np.float32)
gv_grad = self._gradient(neighbor_x, labels) + gv_grad
v = gv_grad / self._nb_neighbor - adv_grad
adv_x = adv_x + self._eps_iter * np.sign(grad)
perturs = np.clip(adv_x - inputs_image, (0 - self._eps) * clip_diff,
self._eps * clip_diff)
adv_x = inputs_image + perturs
return adv_x
[文档]class VarianceTuningNesterovIterativeMethod(MomentumIterativeMethod):
"""
VNI-FGSM algorithm is an iterative gradient-based adversarial attack method.
Compared with VMI-FGSM,VNI-FGSM uses not only the current gradient but also the average gradient of
all previous iterations in each iteration, thus increasing the stability and robustness of the attack.
Experimental results show that the attack mobility can be improved by more than 30%, and VNI-FGSM
has a higher attack success rate against the training model compared to the VMI-FGSM algorithm.
References: VNI-FGSM in the paper Enhancing the Transferability of Adversarial
Attacks through Variance Tuning [https://arxiv.org/abs/2103.15571], published as
a conference paper at CVPR 2021 Modified from "https://github.com/JHL-HUST/VT"
Args:
network (Cell): Target model.
eps (float): Proportion of adversarial perturbation generated by the
attack to data range. Default: ``8/255``.
eps_iter (float): The proportion of perturbation in each step. Default: ``2/255``.
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``.
nb_iter (int) : Number of iteration. Default: ``10``.
decay_factor (float): The momentum factor. Default: ``1.0``.
nb_neighbor (int): The number of sampled examples in the neighborhood.
neighbor_beta (float): The upper bound of neighborhood. Default: ``3/2``.
norm_level (Union[int, str, numpy.inf]): Order of the norm. Possible values:
``numpy.inf`` , ``1``, ``2``, ``'1'``, ``'2'``, ``'l1'``, ``'l2'``, ``'np.inf'``,
``'inf'`` or ``'linf'``. Default: ``'inf'``.
loss_fn (Union[Loss, None]): Loss function for optimization. If ``None``, the input network
is already equipped with loss function. Default: ``None``.
Examples:
>>> import mindspore.ops as ops
>>> from mindarmour.adv_robustness.attacks import VarianceTuningNesterovIterativeMethod
>>> class Net(nn.Cell):
... def __init__(self):
... super(Net, self).__init__()
... self._softmax = ops.Softmax()
... def construct(self, inputs):
... out = self._softmax(inputs)
... return out
>>> net = Net()
>>> attack = VarianceTuningNesterovIterativeMethod(net, nb_neighbor=5, neighbor_beta=3/2,
... loss_fn=nn.SoftmaxCrossEntropyWithLogits(sparse=False))
>>> 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()
>>> adv_x = attack.generate(inputs, labels)
"""
def __init__(self, network, eps=8/255, eps_iter=2/255, bounds=(0.0, 1.0),
is_targeted=False, nb_iter=10, decay_factor=1.0, nb_neighbor=5, neighbor_beta=3/2,
norm_level='inf', loss_fn=None):
super(VarianceTuningNesterovIterativeMethod, self).__init__(network,
eps=eps,
eps_iter=eps_iter,
bounds=bounds,
nb_iter=nb_iter,
loss_fn=loss_fn)
self._is_targeted = check_param_type('is_targeted', is_targeted, bool)
self._decay_factor = check_value_positive('decay_factor', decay_factor)
self._norm_level = check_norm_level(norm_level)
self._nb_neighbor = check_int_positive('nb_neighbor', nb_neighbor)
self._neighbor_beta = check_value_positive('neighbor_beta', neighbor_beta)
[文档] def generate(self, inputs, labels):
"""
Generate adversarial examples based on input data and origin/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)
ndim = np.ndim(inputs_image)
if ndim < 4:
for _ in range(4-ndim):
inputs_image = np.expand_dims(inputs_image, axis=0)
momentum = 0
v = 0
adv_x = copy.deepcopy(inputs_image)
clip_min, clip_max = self._bounds
clip_diff = clip_max - clip_min
for _ in range(self._nb_iter):
nes_images = adv_x + self._decay_factor * self._eps_iter * momentum
adv_grad = self._gradient(nes_images, labels)
grad = (adv_grad + v) / (np.mean(np.abs(adv_x + v), axis=(1, 2, 3), keepdims=True) + 1e-12)
grad = grad + momentum * self._decay_factor
momentum = grad
gv_grad = 0
for _ in range(self._nb_neighbor):
neighbor_x = adv_x + np.random.uniform(-self._neighbor_beta * self._eps,
self._eps * self._neighbor_beta,
size=inputs_image.shape).astype(np.float32)
gv_grad = self._gradient(neighbor_x, labels) + gv_grad
v = gv_grad / self._nb_neighbor - adv_grad
adv_x = adv_x + self._eps_iter * np.sign(grad)
perturs = np.clip(adv_x - inputs_image, (0 - self._eps) * clip_diff,
self._eps * clip_diff)
adv_x = inputs_image + perturs
return adv_x