Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added SimBA (Simple Black-box Adversarial Attacks) #469

Merged
merged 92 commits into from
Jun 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
795c807
updated
kztakemoto Jan 31, 2020
ab3b2ab
updated
kztakemoto Jan 31, 2020
999e034
updated
kztakemoto Feb 1, 2020
76e19de
updated
kztakemoto Feb 2, 2020
67953f3
updated
kztakemoto Feb 2, 2020
2bd175d
updated
kztakemoto Feb 2, 2020
7deda96
updated
kztakemoto Feb 3, 2020
4e3a801
updated
kztakemoto Feb 3, 2020
b4762e4
updated
kztakemoto Feb 3, 2020
ff88be0
updated
kztakemoto Feb 4, 2020
77baeab
update && upload simba_dct
Feb 28, 2020
5434c3b
update simba_dct universal_simba
Mar 2, 2020
ca07569
updated
kztakemoto Mar 3, 2020
67feb6a
update
kztakemoto May 25, 2020
25d87b9
update
kztakemoto May 26, 2020
9b23de1
update
kztakemoto May 26, 2020
53c6fbc
update
kztakemoto May 26, 2020
e5167d7
update
kztakemoto May 27, 2020
81bbac9
update
kztakemoto May 28, 2020
dd32943
update
kztakemoto May 28, 2020
32a9e66
update
kztakemoto May 29, 2020
1916333
update
kztakemoto May 29, 2020
78e332f
update
kztakemoto May 29, 2020
8bb4407
update
kztakemoto May 29, 2020
2a2ef26
update
kztakemoto May 29, 2020
6258784
update
kztakemoto May 31, 2020
6002ca6
update
kztakemoto May 31, 2020
ae012ba
update
kztakemoto May 31, 2020
5c37a79
update
kztakemoto May 31, 2020
b517959
update
kztakemoto May 31, 2020
f0bbf6b
update
kztakemoto May 31, 2020
f4b326d
update
kztakemoto Jun 1, 2020
bb46bfe
update
kztakemoto Jun 1, 2020
b4289d1
update
kztakemoto Jun 1, 2020
304f634
update
kztakemoto Jun 2, 2020
bf96ef5
update
kztakemoto Jun 2, 2020
77d8c0f
update
kztakemoto Jun 2, 2020
a3b2fe6
update
kztakemoto Jun 2, 2020
b2c09fa
update
kztakemoto Jun 4, 2020
d1d8742
update
kztakemoto Jun 9, 2020
a1b32eb
resolve conflicts
kztakemoto Jun 21, 2020
eb170a3
deleted unrelatedfiles
kztakemoto Jun 21, 2020
925ab3d
deleted unrelatedfiles
kztakemoto Jun 21, 2020
6c3b172
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
d46023b
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
fe960ff
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
9d131cb
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
b11fa4c
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
637de2b
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
c36351f
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
c39d764
Update art/attacks/evasion/universal_perturbation.py
kztakemoto Jun 24, 2020
efde316
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
cdca49f
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
5d86968
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
f224661
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
a800b8d
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
ad92509
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
0008151
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
9da72e6
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
a86d4ee
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
5c0d648
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
ac7ef24
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
f06fc36
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 24, 2020
c0da24f
Update art/attacks/__init__.py
kztakemoto Jun 24, 2020
0bd0559
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
a449752
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
237c13d
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
9242d7f
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
83ba5cf
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
69edb46
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
afcb46f
Update art/attacks/evasion/simba.py
kztakemoto Jun 24, 2020
de5b8f7
added a docstring and typing information to the method
kztakemoto Jun 24, 2020
50f4dd2
modified code to run successfully
kztakemoto Jun 24, 2020
6b41cec
update
kztakemoto Jun 24, 2020
f6e90e1
added unittest
kztakemoto Jun 24, 2020
c840577
Update art/attacks/evasion/simba.py
kztakemoto Jun 26, 2020
97c0b78
Update art/attacks/evasion/simba.py
kztakemoto Jun 26, 2020
6de27a0
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 26, 2020
d407b4d
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 26, 2020
972b9ca
Update art/attacks/evasion/simba.py
kztakemoto Jun 26, 2020
a7dfc36
Update tests/attacks/test_targeted_universal_perturbation.py
kztakemoto Jun 26, 2020
783e202
Update tests/attacks/test_simba.py
kztakemoto Jun 26, 2020
0d6dc4d
Update tests/attacks/test_targeted_universal_perturbation.py
kztakemoto Jun 26, 2020
43c08ae
Update art/attacks/evasion/targeted_universal_perturbation.py
kztakemoto Jun 26, 2020
6a39d86
our temporary modification removed
kztakemoto Jun 26, 2020
fc96d41
new unittests added
kztakemoto Jun 26, 2020
5251b1a
pytorch ver added
kztakemoto Jun 26, 2020
d88519e
modified to run in pytorch
kztakemoto Jun 26, 2020
a512c32
modified to fix PEP8 format using black
kztakemoto Jun 26, 2020
d34b235
Update art/attacks/evasion/simba.py
kztakemoto Jun 26, 2020
8384e95
Update art/attacks/evasion/simba.py
kztakemoto Jun 26, 2020
043d79a
Update art/attacks/evasion/simba.py
kztakemoto Jun 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions art/attacks/evasion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from art.attacks.evasion.saliency_map import SaliencyMapMethod
from art.attacks.evasion.spatial_transformation import SpatialTransformation
from art.attacks.evasion.universal_perturbation import UniversalPerturbation
from art.attacks.evasion.targeted_universal_perturbation import TargetedUniversalPerturbation
from art.attacks.evasion.virtual_adversarial import VirtualAdversarialMethod
from art.attacks.evasion.wasserstein import Wasserstein
from art.attacks.evasion.zoo import ZooAttack
Expand All @@ -37,3 +38,4 @@
from art.attacks.evasion.auto_attack import AutoAttack
from art.attacks.evasion.auto_projected_gradient_descent import AutoProjectedGradientDescent
from art.attacks.evasion.square_attack import SquareAttack
from art.attacks.evasion.simba import SimBA
380 changes: 380 additions & 0 deletions art/attacks/evasion/simba.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,380 @@
# MIT License
#
# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2020
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
This module implements the black-box attack `SimBA`.

| Paper link: https://arxiv.org/abs/1905.07121
"""
from __future__ import absolute_import, division, print_function, unicode_literals

import logging
from typing import Optional

import numpy as np
from scipy.fftpack import idct

from art.attacks.attack import EvasionAttack
from art.estimators.estimator import BaseEstimator
from art.estimators.classification.classifier import (
ClassGradientsMixin,
ClassifierGradients,
)
from art.config import ART_NUMPY_DTYPE

logger = logging.getLogger(__name__)


class SimBA(EvasionAttack):
attack_params = EvasionAttack.attack_params + [
"attack",
"max_iter",
"epsilon",
"order",
"freq_dim",
"stride",
"targeted",
"batch_size",
]

kztakemoto marked this conversation as resolved.
Show resolved Hide resolved
_estimator_requirements = (BaseEstimator, ClassGradientsMixin)

def __init__(
self,
classifier: ClassifierGradients,
attack: str = "dct",
max_iter: int = 3000,
order: str = "random",
epsilon: float = 0.1,
freq_dim: int = 4,
stride: int = 1,
targeted: bool = False,
batch_size: int = 1,
):
"""
Create a SimBA (dct) attack instance.

:param classifier: A trained classifier.
:param attack: attack type: pixel (px) or DCT (dct) attacks
:param max_iter: The maximum number of iterations.
:param epsilon: Overshoot parameter.
:param order: order of pixel attacks: random or diagonal (diag)
:param freq_dim: dimensionality of 2D frequency space (DCT).
:param stride: stride for block order (DCT).
:param targeted: perform targeted attack
:param batch_size: Batch size (but, batch process unavailable in this implementation)
"""
super(SimBA, self).__init__(estimator=classifier)

self.attack = attack
self.max_iter = max_iter
self.epsilon = epsilon
self.order = order
self.freq_dim = freq_dim
self.stride = stride
self.targeted = targeted
self.batch_size = batch_size
self._check_params()

def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray:
"""
Generate adversarial samples and return them in an array.

:param x: An array with the original inputs to be attacked.
:param y: An array with the original labels to be predicted.
:return: An array holding the adversarial examples.
"""
x = x.astype(ART_NUMPY_DTYPE)
preds = self.estimator.predict(x, batch_size=self.batch_size)

if y is None:
if self.targeted:
raise ValueError("Target labels `y` need to be provided for a targeted attack.")
else:
# Use model predictions as correct outputs
logger.info("Using the model prediction as the correct label for SimBA.")
y_i = np.argmax(preds, axis=1)
else:
y_i = np.argmax(y, axis=1)

desired_label = y_i[0]
current_label = np.argmax(preds, axis=1)[0]
last_prob = preds.reshape(-1)[desired_label]

if self.estimator.channels_first:
nb_channels = x.shape[1]
else:
nb_channels = x.shape[3]

n_dims = np.prod(x.shape)

if self.attack == "px":
if self.order == "diag":
indices = self.diagonal_order(x.shape[2], nb_channels)[: self.max_iter]
elif self.order == "random":
indices = np.random.permutation(n_dims)[: self.max_iter]
indices_size = len(indices)
while indices_size < self.max_iter:
if self.order == "diag":
tmp_indices = self.diagonal_order(x.shape[2], nb_channels)
elif self.order == "random":
tmp_indices = np.random.permutation(n_dims)
indices = np.hstack((indices, tmp_indices))[: self.max_iter]
indices_size = len(indices)
elif self.attack == "dct":
indices = self._block_order(x.shape[2], nb_channels, initial_size=self.freq_dim, stride=self.stride)[
: self.max_iter
]
indices_size = len(indices)
while indices_size < self.max_iter:
tmp_indices = self._block_order(x.shape[2], nb_channels, initial_size=self.freq_dim, stride=self.stride)
indices = np.hstack((indices, tmp_indices))[: self.max_iter]
indices_size = len(indices)

def trans(z):
return self._block_idct(z, block_size=x.shape[2])

clip_min = -np.inf
clip_max = np.inf
if self.estimator.clip_values is not None:
clip_min, clip_max = self.estimator.clip_values

term_flag = 1
if self.targeted:
if desired_label != current_label:
term_flag = 0
else:
if desired_label == current_label:
term_flag = 0

nb_iter = 0
while term_flag == 0 and nb_iter < self.max_iter:
diff = np.zeros(n_dims).astype(ART_NUMPY_DTYPE)
diff[indices[nb_iter]] = self.epsilon

if self.attack == "dct":
left_preds = self.estimator.predict(
np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size
)
elif self.attack == "px":
left_preds = self.estimator.predict(
np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size
)
left_prob = left_preds.reshape(-1)[desired_label]

if self.attack == "dct":
right_preds = self.estimator.predict(
np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size
)
elif self.attack == "px":
right_preds = self.estimator.predict(
np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size
)
right_prob = right_preds.reshape(-1)[desired_label]

# Use (2 * int(self.targeted) - 1) to shorten code?
if self.targeted:
if left_prob > last_prob:
if left_prob > right_prob:
if self.attack == "dct":
x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max)
elif self.attack == "px":
x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max)
last_prob = left_prob
current_label = np.argmax(left_preds, axis=1)[0]
else:
if self.attack == "dct":
x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max)
elif self.attack == "px":
x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max)
last_prob = right_prob
current_label = np.argmax(right_preds, axis=1)[0]
else:
if right_prob > last_prob:
if self.attack == "dct":
x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max)
elif self.attack == "px":
x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max)
last_prob = right_prob
current_label = np.argmax(right_preds, axis=1)[0]
else:
if left_prob < last_prob:
if left_prob < right_prob:
if self.attack == "dct":
x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max)
elif self.attack == "px":
x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max)
last_prob = left_prob
current_label = np.argmax(left_preds, axis=1)[0]
else:
if self.attack == "dct":
x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max)
elif self.attack == "px":
x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max)
last_prob = right_prob
current_label = np.argmax(right_preds, axis=1)[0]
else:
if right_prob < last_prob:
if self.attack == "dct":
x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max)
elif self.attack == "px":
x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max)
last_prob = right_prob
current_label = np.argmax(right_preds, axis=1)[0]

if self.targeted:
if desired_label == current_label:
term_flag = 1
else:
if desired_label != current_label:
term_flag = 1

nb_iter = nb_iter + 1

if nb_iter < self.max_iter:
logger.info("SimBA (%s) %s attack succeed", self.attack, ["non-targeted", "targeted"][int(self.targeted)])
else:
logger.info("SimBA (%s) %s attack failed", self.attack, ["non-targeted", "targeted"][int(self.targeted)])

return x

def _check_params(self) -> None:

if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0:
raise ValueError("The number of iterations must be a positive integer.")

if self.epsilon < 0:
raise ValueError("The overshoot parameter must not be negative.")

if self.batch_size != 1:
raise ValueError("The batch size `batch_size` has to be 1 in this implementation.")

if not isinstance(self.stride, (int, np.int)) or self.stride <= 0:
raise ValueError("The `stride` value must be a positive integer.")

if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0:
raise ValueError("The `freq_dim` value must be a positive integer.")

if self.order != "random" and self.order != "diag":
raise ValueError("The order of pixel attacks has to be `random` or `diag`.")

if self.attack != "px" and self.attack != "dct":
raise ValueError("The attack type has to be `px` or `dct`.")

if not isinstance(self.targeted, (int)) or (self.targeted != 0 and self.targeted != 1):
raise ValueError("`targeted` has to be a logical value.")

def _block_order(self, img_size, channels, initial_size=2, stride=1):
kztakemoto marked this conversation as resolved.
Show resolved Hide resolved
"""
Defines a block order, starting with top-left (initial_size x initial_size) submatrix
expanding by stride rows and columns whenever exhausted
randomized within the block and across channels.
e.g. (initial_size=2, stride=1)
[1, 3, 6]
[2, 4, 9]
[5, 7, 8]

:param img_size: image size (i.e., width or height).
:param channels: the number of channels.
:param initial size: initial size for submatrix.
:param stride: stride size for expansion.

:return z: An array holding the block order of DCT attacks.
"""
order = np.zeros((channels, img_size, img_size)).astype(ART_NUMPY_DTYPE)
total_elems = channels * initial_size * initial_size
perm = np.random.permutation(total_elems)
order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size))
for i in range(initial_size, img_size, stride):
num_elems = channels * (2 * stride * i + stride * stride)
perm = np.random.permutation(num_elems) + total_elems
num_first = channels * stride * (stride + i)
order[:, : (i + stride), i : (i + stride)] = perm[:num_first].reshape((channels, -1, stride))
order[:, i : (i + stride), :i] = perm[num_first:].reshape((channels, stride, -1))
total_elems += num_elems
if self.estimator.channels_first:
return order.reshape(1, -1).squeeze().argsort()
else:
return order.transpose(1, 2, 0).reshape(1, -1).squeeze().argsort()

def _block_idct(self, x, block_size=8, masked=False, ratio=0.5):
kztakemoto marked this conversation as resolved.
Show resolved Hide resolved
"""
Applies IDCT to each block of size block_size.

:param x: An array with the inputs to be attacked.
:param block_size: block size for DCT attacks.
:param masked: use the mask.
:param ratio: Ratio of the lowest frequency directions in order to make the adversarial perturbation in the low
frequency space.

:return z: An array holding the order of DCT attacks.
"""
if not self.estimator.channels_first:
x = x.transpose(0, 3, 1, 2)
z = np.zeros(x.shape).astype(ART_NUMPY_DTYPE)
num_blocks = int(x.shape[2] / block_size)
mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size))
if type(ratio) != float:
for i in range(x.shape[0]):
mask[i, :, : int(block_size * ratio[i]), : int(block_size * ratio[i])] = 1
else:
mask[:, :, : int(block_size * ratio), : int(block_size * ratio)] = 1
for i in range(num_blocks):
for j in range(num_blocks):
submat = x[:, :, (i * block_size) : ((i + 1) * block_size), (j * block_size) : ((j + 1) * block_size)]
if masked:
submat = submat * mask
z[:, :, (i * block_size) : ((i + 1) * block_size), (j * block_size) : ((j + 1) * block_size)] = idct(
idct(submat, axis=3, norm="ortho"), axis=2, norm="ortho"
)

if self.estimator.channels_first:
return z
else:
return z.transpose(0, 2, 3, 1)

def diagonal_order(self, image_size, channels):
kztakemoto marked this conversation as resolved.
Show resolved Hide resolved
"""
Defines a diagonal order for pixel attacks.
order is fixed across diagonals but are randomized across channels and within the diagonal
e.g.
[1, 2, 5]
[3, 4, 8]
[6, 7, 9]

:param image_size: image size (i.e., width or height)
:param channels: the number of channels

:return z: An array holding the diagonal order of pixel attacks.
"""
x = np.arange(0, image_size).cumsum()
order = np.zeros((image_size, image_size)).astype(ART_NUMPY_DTYPE)
for i in range(image_size):
order[i, : (image_size - i)] = i + x[i:]
for i in range(1, image_size):
reverse = order[image_size - i - 1].take([i for i in range(i - 1, -1, -1)])
order[i, (image_size - i) :] = image_size * image_size - 1 - reverse
if channels > 1:
order_2d = order
order = np.zeros((channels, image_size, image_size))
for i in range(channels):
order[i, :, :] = 3 * order_2d + i

if self.estimator.channels_first:
return order.reshape(1, -1).squeeze().argsort()
else:
return order.transpose(1, 2, 0).reshape(1, -1).squeeze().argsort()