Skip to content

Commit d81dde9

Browse files
author
Beat Buesser
committed
Merge branch 'dev_1.5.0' of github.com:Trusted-AI/adversarial-robustness-toolbox into dev_1.5.0
2 parents 1db43b4 + 7c26580 commit d81dde9

File tree

133 files changed

+3256
-939
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+3256
-939
lines changed

.github/workflows/ci.yml

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,22 @@ jobs:
3737
tensorflow: 2.2.0
3838
tf_version: v2
3939
keras: 2.3.1
40+
- name: TensorFlow 2.2.0v1 (Keras 2.3.1 Python 3.7)
41+
framework: tensorflow2v1
42+
python: 3.7
43+
tensorflow: 2.2.0
44+
tf_version: v2
45+
keras: 2.3.1
4046
- name: TensorFlow 2.3.1 (Keras 2.4.3 Python 3.7)
4147
framework: tensorflow
4248
python: 3.7
4349
tensorflow: 2.3.1
4450
tf_version: v2
4551
keras: 2.4.3
46-
- name: TensorFlow 2.4.0rc2 (Keras 2.4.3 Python 3.8)
52+
- name: TensorFlow 2.4.0rc3 (Keras 2.4.3 Python 3.8)
4753
framework: tensorflow
4854
python: 3.8
49-
tensorflow: 2.4.0rc1
55+
tensorflow: 2.4.0rc3
5056
tf_version: v2
5157
keras: 2.4.3
5258
- name: Keras 2.3.1 (TensorFlow 2.2.1 Python 3.7)
@@ -94,27 +100,30 @@ jobs:
94100
uses: actions/setup-python@v2
95101
with:
96102
python-version: ${{ matrix.python }}
103+
- name: Install Dependencies
104+
run: |
105+
sudo apt-get update
106+
sudo apt-get -y -q install ffmpeg libavcodec-extra
107+
python -m pip install --upgrade pip setuptools wheel
108+
pip3 install -q -r requirements.txt
109+
pip list
97110
- name: Pre-install legacy
98111
if: ${{ matrix.framework == 'legacy' }}
99112
run: |
100113
pip install tensorflow==${{ matrix.tensorflow }}
101114
pip install keras==${{ matrix.keras }}
102115
pip install scikit-learn==${{ matrix.scikit-learn }}
116+
pip list
103117
- name: Pre-install tensorflow
104118
if: ${{ matrix.framework == 'tensorflow' || matrix.framework == 'keras' || matrix.framework == 'kerastf' }}
105119
run: |
106120
pip install tensorflow==${{ matrix.tensorflow }}
107121
pip install keras==${{ matrix.keras }}
122+
pip list
108123
- name: Pre-install scikit-learn
109124
if: ${{ matrix.framework == 'scikitlearn' }}
110125
run: |
111126
pip install scikit-learn==${{ matrix.scikit-learn }}
112-
- name: Install Dependencies
113-
run: |
114-
sudo apt-get update
115-
sudo apt-get -y -q install ffmpeg libavcodec-extra
116-
python -m pip install --upgrade pip setuptools wheel
117-
pip3 install -q -r requirements.txt
118127
pip list
119128
- name: Run ${{ matrix.name }} Tests
120129
run: ./run_tests.sh ${{ matrix.framework }}

CODE_OF_CONDUCT.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
The Adversarial Robustness Toolbox is dedicated to providing a harassment-free experience for everyone, regardless of gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, age, race, or religion. We do not tolerate harassment of participants in any form.
1+
The Adversarial Robustness Toolbox (ART) is dedicated to providing a harassment-free experience for everyone, regardless of gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, age, race, or religion. We do not tolerate harassment of participants in any form.
22

3-
This code of conduct applies to all Adversarial Robustness Toolbox spaces, both online and off. Anyone who violates this code of conduct may be sanctioned or expelled from these spaces at the discretion of the IBM Research AI team.
3+
This code of conduct applies to all Adversarial Robustness Toolbox spaces, both online and off. Anyone who violates this code of conduct may be sanctioned or expelled from these spaces at the discretion of the Trusted-AI team.
44

55
We may add additional rules over time, which will be made clearly available to participants. Participants are responsible for knowing and abiding by these rules.

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
PROJECT_HOME_DIR := ${CURDIR}
22

33
build:
4-
# Builds a Tensorflow 2 ART docker container
4+
# Builds a TensorFlow 2 ART docker container
55
# IMPORTANT ! If you have an existing python env folder make sure to first add it to the `.dockerIgnore` \
66
to reduce the size of your the art docker image
77
docker build -t project-art-tf2 .
88

99
build1:
10-
# Builds a Tensorflow 1 ART docker container
10+
# Builds a TensorFlow 1 ART docker container
1111
# IMPORTANT ! If you have an existing python env folder make sure to first add it to the `.dockerIgnore` \
1212
to reduce the size of your the art docker image
1313
docker build -t project-art-tf1 .

art/attacks/attack.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ class AttributeInferenceAttack(InferenceAttack):
317317

318318
attack_params = InferenceAttack.attack_params + ["attack_feature"]
319319

320-
def __init__(self, estimator, attack_feature: int = 0):
320+
def __init__(self, estimator, attack_feature: Union[int, slice] = 0):
321321
"""
322322
:param estimator: A trained estimator targeted for inference attack.
323323
:type estimator: :class:`.art.estimators.estimator.BaseEstimator`
@@ -346,10 +346,6 @@ def set_params(self, **kwargs) -> None:
346346
super().set_params(**kwargs)
347347
self._check_params()
348348

349-
def _check_params(self) -> None:
350-
if self.attack_feature < 0:
351-
raise ValueError("Attack feature must be positive.")
352-
353349

354350
class ReconstructionAttack(Attack):
355351
"""

art/attacks/evasion/__init__.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,26 @@
44
from art.attacks.evasion.adversarial_patch.adversarial_patch import AdversarialPatch
55
from art.attacks.evasion.adversarial_patch.adversarial_patch_numpy import AdversarialPatchNumpy
66
from art.attacks.evasion.adversarial_patch.adversarial_patch_tensorflow import AdversarialPatchTensorFlowV2
7+
from art.attacks.evasion.auto_attack import AutoAttack
8+
from art.attacks.evasion.auto_projected_gradient_descent import AutoProjectedGradientDescent
9+
from art.attacks.evasion.brendel_bethge import BrendelBethgeAttack
710
from art.attacks.evasion.boundary import BoundaryAttack
811
from art.attacks.evasion.carlini import CarliniL2Method, CarliniLInfMethod
912
from art.attacks.evasion.decision_tree_attack import DecisionTreeAttack
1013
from art.attacks.evasion.deepfool import DeepFool
14+
from art.attacks.evasion.dpatch import DPatch
15+
from art.attacks.evasion.dpatch_robust import RobustDPatch
1116
from art.attacks.evasion.elastic_net import ElasticNet
1217
from art.attacks.evasion.fast_gradient import FastGradientMethod
18+
from art.attacks.evasion.frame_saliency import FrameSaliencyAttack
19+
from art.attacks.evasion.feature_adversaries import FeatureAdversaries
1320
from art.attacks.evasion.hclu import HighConfidenceLowUncertainty
1421
from art.attacks.evasion.hop_skip_jump import HopSkipJump
22+
from art.attacks.evasion.imperceptible_asr.imperceptible_asr import ImperceptibleASR
23+
from art.attacks.evasion.imperceptible_asr.imperceptible_asr_pytorch import ImperceptibleASRPyTorch
1524
from art.attacks.evasion.iterative_method import BasicIterativeMethod
1625
from art.attacks.evasion.newtonfool import NewtonFool
26+
from art.attacks.evasion.pixel_threshold import PixelAttack
1727
from art.attacks.evasion.projected_gradient_descent.projected_gradient_descent import ProjectedGradientDescent
1828
from art.attacks.evasion.projected_gradient_descent.projected_gradient_descent_numpy import (
1929
ProjectedGradientDescentNumpy,
@@ -25,23 +35,14 @@
2535
ProjectedGradientDescentTensorFlowV2,
2636
)
2737
from art.attacks.evasion.saliency_map import SaliencyMapMethod
38+
from art.attacks.evasion.shadow_attack import ShadowAttack
39+
from art.attacks.evasion.shapeshifter import ShapeShifter
40+
from art.attacks.evasion.simba import SimBA
2841
from art.attacks.evasion.spatial_transformation import SpatialTransformation
42+
from art.attacks.evasion.square_attack import SquareAttack
43+
from art.attacks.evasion.pixel_threshold import ThresholdAttack
2944
from art.attacks.evasion.universal_perturbation import UniversalPerturbation
3045
from art.attacks.evasion.targeted_universal_perturbation import TargetedUniversalPerturbation
3146
from art.attacks.evasion.virtual_adversarial import VirtualAdversarialMethod
3247
from art.attacks.evasion.wasserstein import Wasserstein
3348
from art.attacks.evasion.zoo import ZooAttack
34-
from art.attacks.evasion.pixel_threshold import PixelAttack
35-
from art.attacks.evasion.pixel_threshold import ThresholdAttack
36-
from art.attacks.evasion.frame_saliency import FrameSaliencyAttack
37-
from art.attacks.evasion.feature_adversaries import FeatureAdversaries
38-
from art.attacks.evasion.dpatch import DPatch
39-
from art.attacks.evasion.shadow_attack import ShadowAttack
40-
from art.attacks.evasion.auto_attack import AutoAttack
41-
from art.attacks.evasion.auto_projected_gradient_descent import AutoProjectedGradientDescent
42-
from art.attacks.evasion.square_attack import SquareAttack
43-
from art.attacks.evasion.simba import SimBA
44-
from art.attacks.evasion.shapeshifter import ShapeShifter
45-
from art.attacks.evasion.imperceptible_asr.imperceptible_asr_pytorch import ImperceptibleASRPytorch
46-
from art.attacks.evasion.brendel_bethge import BrendelBethgeAttack
47-
from art.attacks.evasion.dpatch_robust import RobustDPatch

art/attacks/evasion/boundary.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from typing import Optional, Tuple, TYPE_CHECKING
2828

2929
import numpy as np
30-
from tqdm import tqdm
30+
from tqdm import tqdm, trange
3131

3232
from art.attacks.attack import EvasionAttack
3333
from art.config import ART_NUMPY_DTYPE
@@ -75,6 +75,7 @@ def __init__(
7575
num_trial: int = 25,
7676
sample_size: int = 20,
7777
init_size: int = 100,
78+
min_epsilon: Optional[float] = None,
7879
verbose: bool = True,
7980
) -> None:
8081
"""
@@ -89,6 +90,7 @@ def __init__(
8990
:param num_trial: Maximum number of trials per iteration.
9091
:param sample_size: Number of samples per trial.
9192
:param init_size: Maximum number of trials for initial generation of adversarial examples.
93+
:param min_epsilon: Stop attack if perturbation is smaller than `min_epsilon`.
9294
:param verbose: Show progress bars.
9395
"""
9496
super().__init__(estimator=estimator)
@@ -101,10 +103,13 @@ def __init__(
101103
self.num_trial = num_trial
102104
self.sample_size = sample_size
103105
self.init_size = init_size
106+
self.min_epsilon = min_epsilon
104107
self.batch_size = 1
105108
self.verbose = verbose
106109
self._check_params()
107110

111+
self.curr_adv = None
112+
108113
def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray:
109114
"""
110115
Generate adversarial samples and return them in an array.
@@ -230,8 +235,10 @@ def _attack(
230235
self.curr_delta = initial_delta
231236
self.curr_epsilon = initial_epsilon
232237

238+
self.curr_adv = x_adv
239+
233240
# Main loop to wander around the boundary
234-
for _ in range(self.max_iter):
241+
for _ in trange(self.max_iter, desc="Boundary attack - iterations", disable=not self.verbose):
235242
# Trust region method to adjust delta
236243
for _ in range(self.num_trial):
237244
potential_advs = []
@@ -273,11 +280,15 @@ def _attack(
273280

274281
if epsilon_ratio > 0:
275282
x_adv = potential_advs[np.where(satisfied)[0][0]]
283+
self.curr_adv = x_adv
276284
break
277285
else:
278286
logger.warning("Adversarial example found but not optimal.")
279287
return x_advs[0]
280288

289+
if self.min_epsilon is not None and self.curr_epsilon < self.min_epsilon:
290+
return x_adv
291+
281292
return x_adv
282293

283294
def _orthogonal_perturb(self, delta: float, current_sample: np.ndarray, original_sample: np.ndarray) -> np.ndarray:
@@ -299,20 +310,13 @@ def _orthogonal_perturb(self, delta: float, current_sample: np.ndarray, original
299310
# Project the perturbation onto sphere
300311
direction = original_sample - current_sample
301312

302-
if len(self.estimator.input_shape) == 3:
303-
channel_index = 1 if self.estimator.channels_first else 3
304-
perturb = np.swapaxes(perturb, 0, channel_index - 1)
305-
direction = np.swapaxes(direction, 0, channel_index - 1)
306-
for i in range(direction.shape[0]):
307-
direction[i] /= np.linalg.norm(direction[i])
308-
perturb[i] -= np.dot(np.dot(perturb[i], direction[i].T), direction[i])
309-
310-
perturb = np.swapaxes(perturb, 0, channel_index - 1)
311-
elif len(self.estimator.input_shape) == 1:
312-
direction /= np.linalg.norm(direction)
313-
perturb -= np.dot(perturb, direction.T) * direction
314-
else:
315-
raise ValueError("Input shape not recognised.")
313+
direction_flat = direction.flatten()
314+
perturb_flat = perturb.flatten()
315+
316+
direction_flat /= np.linalg.norm(direction_flat)
317+
perturb_flat -= np.dot(perturb_flat, direction_flat.T) * direction_flat
318+
perturb = perturb_flat.reshape(self.estimator.input_shape)
319+
316320
hypotenuse = np.sqrt(1 + delta ** 2)
317321
perturb = ((1 - hypotenuse) * (current_sample - original_sample) + perturb) / hypotenuse
318322
return perturb
@@ -403,5 +407,8 @@ def _check_params(self) -> None:
403407
if self.step_adapt <= 0 or self.step_adapt >= 1:
404408
raise ValueError("The adaptation factor must be in the range (0, 1).")
405409

410+
if self.min_epsilon is not None and (isinstance(self.min_epsilon, float) or self.min_epsilon <= 0):
411+
raise ValueError("The minimum epsilon must be a positive float.")
412+
406413
if not isinstance(self.verbose, bool):
407414
raise ValueError("The argument `verbose` has to be of type bool.")

art/attacks/evasion/brendel_bethge.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2104,17 +2104,10 @@ def generate(
21042104
"""
21052105
Applies the Brendel & Bethge attack.
21062106
2107-
Parameters
2108-
----------
2109-
inputs : Tensor that matches model type
2110-
The original clean inputs.
2111-
criterion : Callable
2112-
A callable that returns true if the given logits of perturbed
2113-
inputs should be considered adversarial w.r.t. to the given labels
2114-
and unperturbed inputs.
2115-
starting_point : Tensor of same type and shape as inputs
2116-
Adversarial inputs to use as a starting points, in particular
2117-
for targeted attacks.
2107+
:param x: The original clean inputs.
2108+
:param y: The labels for inputs `x`.
2109+
:param starting_points: Adversarial inputs to use as a starting points, in particular for targeted attacks.
2110+
:param early_stop: Early-stopping criteria.
21182111
"""
21192112
originals = x.copy()
21202113

art/attacks/evasion/dpatch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def generate(
225225
return self._patch
226226

227227
@staticmethod
228-
@deprecated_keyword_arg("channel_index", end_version="1.5.0", replaced_by="channels_first")
228+
@deprecated_keyword_arg("channel_index", end_version="1.6.0", replaced_by="channels_first")
229229
def _augment_images_with_patch(
230230
x: np.ndarray,
231231
patch: np.ndarray,
@@ -249,7 +249,7 @@ def _augment_images_with_patch(
249249
center location of the patch during sampling.
250250
:type mask: `np.ndarray`
251251
"""
252-
# Remove in 1.5.0
252+
# Remove in 1.6.0
253253
if channel_index == 3:
254254
channels_first = False
255255
elif channel_index == 1:

art/attacks/evasion/dpatch_robust.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
170170
x[i_batch_start:i_batch_end], self._patch, channels_first=self.estimator.channels_first
171171
)
172172

173-
gradients = self.estimator.loss_gradient(
174-
x=patched_images,
175-
y=patch_target,
176-
)
173+
gradients = self.estimator.loss_gradient(x=patched_images, y=patch_target,)
177174

178175
gradients = self._untransform_gradients(
179176
gradients, transforms, channels_first=self.estimator.channels_first
@@ -191,9 +188,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
191188

192189
if self.estimator.clip_values is not None:
193190
self._patch = np.clip(
194-
self._patch,
195-
a_min=self.estimator.clip_values[0],
196-
a_max=self.estimator.clip_values[1],
191+
self._patch, a_min=self.estimator.clip_values[0], a_max=self.estimator.clip_values[1],
197192
)
198193

199194
return self._patch
@@ -209,7 +204,7 @@ def _augment_images_with_patch(
209204
:param channels_first: Set channels first or last.
210205
"""
211206

212-
transformations = dict()
207+
transformations: Dict[str, Union[float, int]] = dict()
213208
x_copy = x.copy()
214209
patch_copy = patch.copy()
215210
x_patch = x.copy()
@@ -267,10 +262,7 @@ def _augment_images_with_patch(
267262
return x_patch, patch_target, transformations
268263

269264
def _untransform_gradients(
270-
self,
271-
gradients: np.ndarray,
272-
transforms: Dict[str, Union[int, float]],
273-
channels_first: bool,
265+
self, gradients: np.ndarray, transforms: Dict[str, Union[int, float]], channels_first: bool,
274266
) -> np.ndarray:
275267
"""
276268
Revert transformation on gradients.
@@ -291,8 +283,8 @@ def _untransform_gradients(
291283
gradients = np.rot90(gradients, rot90, (1, 2))
292284

293285
# Account for cropping when considering the upper left point of the patch:
294-
x_1 = self.patch_location[0] - transforms["crop_x"]
295-
y_1 = self.patch_location[1] - transforms["crop_y"]
286+
x_1 = self.patch_location[0] - int(transforms["crop_x"])
287+
y_1 = self.patch_location[1] - int(transforms["crop_y"])
296288
x_2 = x_1 + self.patch_shape[0]
297289
y_2 = y_1 + self.patch_shape[1]
298290
gradients = gradients[:, x_1:x_2, y_1:y_2, :]

art/attacks/evasion/feature_adversaries.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
8383
`scipy.optimize.show_options(solver='minimize', method='L-BFGS-B')`:
8484
Minimize a scalar function of one or more variables using the L-BFGS-B algorithm.
8585
86-
Options
87-
-------
8886
disp : None or int
8987
If `disp is None` (the default), then the supplied version of `iprint`
9088
is used. If `disp is not None`, then it overrides the supplied version
@@ -120,8 +118,6 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
120118
maxls : int, optional
121119
Maximum number of line search steps (per iteration). Default is 20.
122120
123-
Notes
124-
-----
125121
The option `ftol` is exposed via the `scipy.optimize.minimize` interface,
126122
but calling `scipy.optimize.fmin_l_bfgs_b` directly exposes `factr`. The
127123
relationship between the two is ``ftol = factr * numpy.finfo(float).eps``.

0 commit comments

Comments
 (0)