Skip to content

Commit dcb1555

Browse files
authored
Merge pull request #606 from Trusted-AI/dev_1.4.0
Update to ART 1.4.0
2 parents 16d8c97 + 6c05170 commit dcb1555

File tree

231 files changed

+19114
-5985
lines changed

Some content is hidden

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

231 files changed

+19114
-5985
lines changed

.dockerIgnore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
./venv/
2-
1+
./venv*
2+
.git
3+
./TMP/

.travis.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- python: 3.7
1818
env: TENSORFLOW_V=2.2.0 KERAS_V=2.3.1
1919
script:
20-
- (pycodestyle --max-line-length=120 art || exit 0)
20+
- (pycodestyle --ignore=C0330,C0415,E203,E231,W503 --max-line-length=120 art || exit 0)
2121
- (pylint --disable=C0330,C0415,E203,E1136 -rn art || exit 0)
2222
- (mypy art || exit 0)
2323
- py.test --pep8 -m pep8
@@ -32,6 +32,3 @@ install:
3232
- pip install -q pylint pycodestyle
3333
- pip install -q -r requirements.txt
3434
- pip list
35-
36-
script:
37-
- ./run_tests.sh

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
- International Business Machines Corporation (IBM)
88
- Two Six Labs, LLC
99
- Kyushu University
10+
- Intel Corporation

Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
FROM tensorflow/tensorflow:2.2.0-py3
1+
FROM tensorflow/tensorflow:2.2.0
22
RUN pip3 install keras==2.3.1
33
#### NOTE: comment these two lines if you wish to use the tensorflow 1 version of ART instead ####
4-
#FROM tensorflow/tensorflow:1.15.3-py3
4+
#FROM tensorflow/tensorflow:1.15.2
55
#RUN pip3 install keras==2.2.5
66

7-
RUN pip3 install numpy==1.19.1 scipy==1.4.1 matplotlib==3.3.1 scikit-learn==0.23.2 six==1.15.0 Pillow==7.2.0
7+
RUN pip3 install numpy==1.19.1 scipy==1.4.1 matplotlib==3.3.1 scikit-learn==0.22.2 six==1.15.0 Pillow==7.2.0
88
RUN pip3 install tqdm==4.48.2 statsmodels==0.11.1 pydub==0.24.1 resampy==0.2.2 ffmpeg-python==0.2.0 cma==3.0.3 mypy==0.770
9+
RUN pip3 install pandas==1.1.1
910

1011
#TODO check if jupyter notebook works
1112
RUN pip3 install jupyter==1.0.0 && pip3 install jupyterlab==2.1.0

Makefile

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

33
build:
4-
# If you have an existing python env folder make sure to first add it to the `.dockerIgnore` \
4+
# Builds a Tensorflow 2 ART docker container
5+
# IMPORTANT ! If you have an existing python env folder make sure to first add it to the `.dockerIgnore` \
56
to reduce the size of your the art docker image
6-
docker build -t project-art-tensorflow .
7+
docker build -t project-art-tf2 .
8+
9+
build1:
10+
# Builds a Tensorflow 1 ART docker container
11+
# IMPORTANT ! If you have an existing python env folder make sure to first add it to the `.dockerIgnore` \
12+
to reduce the size of your the art docker image
13+
docker build -t project-art-tf1 .
714

815
run-bash:
9-
docker run --rm -it --name project-art-run-bash -v ${PWD}:/project/ -v ~/.art/:/root/.art/ project-art-tensorflow /bin/bash
16+
docker run --rm -it --name project-art-run-bash -v ${PWD}:/project/ -v ~/.art/:/root/.art/ project-art-tf2 /bin/bash
1017

1118
run-test:
12-
docker run --rm --name project-art-run-test -v ${PWD}:/project/ -v ~/.art/:/root/.art/ project-art-tensorflow
19+
docker run --rm --name project-art-run-test -v ${PWD}:/project/ -v ~/.art/:/root/.art/ project-art-tf2
1320

1421
run-pep:
15-
docker run --rm --name project-art-run-pep -v ${PWD}:/project/ -v ~/.art/:/root/.art/ project-art-tensorflow py.test --pep8 -m pep8
22+
docker run --rm --name project-art-run-pep -v ${PWD}:/project/ -v ~/.art/:/root/.art/ project-art-tf2 py.test --pep8 -m pep8
1623

1724
run-jupyter:
18-
docker run --rm --name project-art-run-jupyter -v ${PWD}:/project/ -v ~/.art/:/root/.art/ -p 8888:8888 project-art-tensorflow jupyter notebook --ip 0.0.0.0 --no-browser --allow-root
25+
docker run --rm --name project-art-run-jupyter -v ${PWD}:/project/ -v ~/.art/:/root/.art/ -p 8888:8888 project-art-tf2 jupyter notebook --ip 0.0.0.0 --no-browser --allow-root

art/attacks/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Module providing adversarial attacks under a common interface.
33
"""
44
from art.attacks.attack import Attack, EvasionAttack, PoisoningAttack, PoisoningAttackBlackBox, PoisoningAttackWhiteBox
5-
from art.attacks.attack import ExtractionAttack, InferenceAttack, AttributeInferenceAttack
5+
from art.attacks.attack import PoisoningAttackTransformer, ExtractionAttack, InferenceAttack, AttributeInferenceAttack

art/attacks/attack.py

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@
2222

2323
import abc
2424
import logging
25-
from typing import List, Optional, Tuple, TYPE_CHECKING
25+
from typing import Any, List, Optional, Tuple, Union, TYPE_CHECKING
2626

2727
import numpy as np
2828

2929
from art.exceptions import EstimatorError
3030

3131
if TYPE_CHECKING:
32-
from art.estimators.classification.classifier import Classifier
32+
from art.utils import CLASSIFIER_TYPE
3333

3434
logger = logging.getLogger(__name__)
3535

@@ -90,11 +90,15 @@ class Attack(abc.ABC, metaclass=input_filter):
9090
"""
9191

9292
attack_params: List[str] = list()
93+
_estimator_requirements: Optional[Union[Tuple[Any, ...], Tuple[()]]] = None
9394

9495
def __init__(self, estimator):
9596
"""
9697
:param estimator: An estimator.
9798
"""
99+
if self.estimator_requirements is None:
100+
raise ValueError("Estimator requirements have not been defined in `_estimator_requirements`.")
101+
98102
if not all(t in type(estimator).__mro__ for t in self.estimator_requirements):
99103
raise EstimatorError(self.__class__, self.estimator_requirements, estimator)
100104

@@ -128,6 +132,10 @@ class EvasionAttack(Attack):
128132
Abstract base class for evasion attack classes.
129133
"""
130134

135+
def __init__(self, **kwargs) -> None:
136+
self._targeted = False
137+
super().__init__(**kwargs)
138+
131139
@abc.abstractmethod
132140
def generate( # lgtm [py/inheritance/incorrect-overridden-signature]
133141
self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs
@@ -143,34 +151,77 @@ def generate( # lgtm [py/inheritance/incorrect-overridden-signature]
143151
"""
144152
raise NotImplementedError
145153

154+
@property
155+
def targeted(self) -> bool:
156+
"""
157+
Return Boolean if attack is targeted. Return None if not applicable.
158+
"""
159+
return self._targeted
160+
161+
@targeted.setter
162+
def targeted(self, targeted) -> None:
163+
self._targeted = targeted
164+
146165

147166
class PoisoningAttack(Attack):
148167
"""
149168
Abstract base class for poisoning attack classes
150169
"""
151170

152-
def __init__(self, classifier) -> None:
171+
def __init__(self, classifier: Optional["CLASSIFIER_TYPE"]) -> None:
172+
"""
173+
:param classifier: A trained classifier (or none if no classifier is needed)
174+
"""
175+
super().__init__(classifier)
176+
177+
@abc.abstractmethod
178+
def poison(self, x: np.ndarray, y=Optional[np.ndarray], **kwargs) -> Tuple[np.ndarray, np.ndarray]:
179+
"""
180+
Generate poisoning examples and return them as an array. This method should be overridden by all concrete
181+
poisoning attack implementations.
182+
183+
:param x: An array with the original inputs to be attacked.
184+
:param y: Target labels for `x`. Untargeted attacks set this value to None.
185+
:return: An tuple holding the (poisoning examples, poisoning labels).
186+
"""
187+
raise NotImplementedError
188+
189+
190+
class PoisoningAttackTransformer(PoisoningAttack):
191+
"""
192+
Abstract base class for poisoning attack classes that return a transformed classifier.
193+
These attacks have an additional method, `poison_estimator`, that returns the poisoned classifier.
194+
"""
195+
196+
def __init__(self, classifier: Optional["CLASSIFIER_TYPE"], **kwargs) -> None:
153197
"""
154198
:param classifier: A trained classifier (or none if no classifier is needed)
155-
:type classifier: `art.estimators.classification.Classifier` or `None`
156199
"""
157200
super().__init__(classifier)
158201

159202
@abc.abstractmethod
160-
def poison(self, x, y=None, **kwargs):
203+
def poison(self, x: np.ndarray, y=Optional[np.ndarray], **kwargs) -> Tuple[np.ndarray, np.ndarray]:
161204
"""
162205
Generate poisoning examples and return them as an array. This method should be overridden by all concrete
163206
poisoning attack implementations.
164207
165208
:param x: An array with the original inputs to be attacked.
166-
:type x: `np.ndarray`
167209
:param y: Target labels for `x`. Untargeted attacks set this value to None.
168-
:type y: `np.ndarray`
169210
:return: An tuple holding the (poisoning examples, poisoning labels).
170211
:rtype: `(np.ndarray, np.ndarray)`
171212
"""
172213
raise NotImplementedError
173214

215+
@abc.abstractmethod
216+
def poison_estimator(self, x: np.ndarray, y: np.ndarray, **kwargs) -> "CLASSIFIER_TYPE":
217+
"""
218+
Returns a poisoned version of the classifier used to initialize the attack
219+
:param x: Training data
220+
:param y: Training labels
221+
:return: A poisoned classifier
222+
"""
223+
raise NotImplementedError
224+
174225

175226
class PoisoningAttackBlackBox(PoisoningAttack):
176227
"""
@@ -221,7 +272,7 @@ class ExtractionAttack(Attack):
221272
"""
222273

223274
@abc.abstractmethod
224-
def extract(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> "Classifier":
275+
def extract(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> "CLASSIFIER_TYPE":
225276
"""
226277
Extract models and return them as an ART classifier. This method should be overridden by all concrete extraction
227278
attack implementations.
@@ -292,7 +343,7 @@ def set_params(self, **kwargs) -> None:
292343
Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes.
293344
"""
294345
# Save attack-specific parameters
295-
super(AttributeInferenceAttack, self).set_params(**kwargs)
346+
super().set_params(**kwargs)
296347
self._check_params()
297348

298349
def _check_params(self) -> None:

art/attacks/evasion/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from art.attacks.evasion.saliency_map import SaliencyMapMethod
2828
from art.attacks.evasion.spatial_transformation import SpatialTransformation
2929
from art.attacks.evasion.universal_perturbation import UniversalPerturbation
30+
from art.attacks.evasion.targeted_universal_perturbation import TargetedUniversalPerturbation
3031
from art.attacks.evasion.virtual_adversarial import VirtualAdversarialMethod
3132
from art.attacks.evasion.wasserstein import Wasserstein
3233
from art.attacks.evasion.zoo import ZooAttack
@@ -39,3 +40,6 @@
3940
from art.attacks.evasion.auto_attack import AutoAttack
4041
from art.attacks.evasion.auto_projected_gradient_descent import AutoProjectedGradientDescent
4142
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

art/attacks/evasion/adversarial_patch/adversarial_patch.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@
2424
from __future__ import absolute_import, division, print_function, unicode_literals
2525

2626
import logging
27-
from typing import Optional, Tuple, Union
27+
from typing import Optional, Tuple, Union, TYPE_CHECKING
2828

2929
import numpy as np
3030

3131
from art.attacks.evasion.adversarial_patch.adversarial_patch_numpy import AdversarialPatchNumpy
3232
from art.attacks.evasion.adversarial_patch.adversarial_patch_tensorflow import AdversarialPatchTensorFlowV2
3333
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
34-
from art.estimators.classification.classifier import (
35-
ClassifierMixin,
36-
ClassifierNeuralNetwork,
37-
ClassifierGradients,
38-
)
34+
from art.estimators.classification.classifier import ClassifierMixin
35+
3936
from art.estimators.classification import TensorFlowV2Classifier
4037
from art.attacks.attack import EvasionAttack
4138

39+
if TYPE_CHECKING:
40+
from art.utils import CLASSIFIER_NEURALNETWORK_TYPE
41+
4242
logger = logging.getLogger(__name__)
4343

4444

@@ -62,7 +62,7 @@ class AdversarialPatch(EvasionAttack):
6262

6363
def __init__(
6464
self,
65-
classifier: Union[ClassifierNeuralNetwork, ClassifierGradients],
65+
classifier: "CLASSIFIER_NEURALNETWORK_TYPE",
6666
rotation_max: float = 22.5,
6767
scale_min: float = 0.1,
6868
scale_max: float = 1.0,
@@ -88,7 +88,7 @@ def __init__(
8888
Currently only supported for `TensorFlowV2Classifier`. For classifiers of other frameworks
8989
the `patch_shape` is set to the shape of the input samples.
9090
"""
91-
super(AdversarialPatch, self).__init__(estimator=classifier)
91+
super().__init__(estimator=classifier)
9292
if self.estimator.clip_values is None:
9393
raise ValueError("Adversarial Patch attack requires a classifier with clip_values.")
9494

art/attacks/evasion/adversarial_patch/adversarial_patch_numpy.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import logging
2727
import math
28-
from typing import Optional, Union, Tuple
28+
from typing import Optional, Union, Tuple, TYPE_CHECKING
2929

3030
import random
3131
import numpy as np
@@ -34,13 +34,12 @@
3434

3535
from art.attacks.attack import EvasionAttack
3636
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
37-
from art.estimators.classification.classifier import (
38-
ClassifierMixin,
39-
ClassifierNeuralNetwork,
40-
ClassifierGradients,
41-
)
37+
from art.estimators.classification.classifier import ClassifierMixin
4238
from art.utils import check_and_transform_label_format
4339

40+
if TYPE_CHECKING:
41+
from art.utils import CLASSIFIER_NEURALNETWORK_TYPE
42+
4443
logger = logging.getLogger(__name__)
4544

4645

@@ -64,7 +63,7 @@ class AdversarialPatchNumpy(EvasionAttack):
6463

6564
def __init__(
6665
self,
67-
classifier: Union[ClassifierNeuralNetwork, ClassifierGradients],
66+
classifier: "CLASSIFIER_NEURALNETWORK_TYPE",
6867
target: int = 0,
6968
rotation_max: float = 22.5,
7069
scale_min: float = 0.1,
@@ -91,7 +90,7 @@ def __init__(
9190
[(float, float), (float, float), (float, float)].
9291
:param batch_size: The size of the training batch.
9392
"""
94-
super(AdversarialPatchNumpy, self).__init__(estimator=classifier)
93+
super().__init__(estimator=classifier)
9594

9695
self.target = target
9796
self.rotation_max = rotation_max
@@ -222,17 +221,17 @@ def _check_params(self) -> None:
222221

223222
if not isinstance(self.learning_rate, float):
224223
raise ValueError("The learning rate must be of type float.")
225-
if not self.learning_rate > 0.0:
224+
if self.learning_rate <= 0.0:
226225
raise ValueError("The learning rate must be greater than 0.0.")
227226

228227
if not isinstance(self.max_iter, int):
229228
raise ValueError("The number of optimization steps must be of type int.")
230-
if not self.max_iter > 0:
229+
if self.max_iter <= 0:
231230
raise ValueError("The number of optimization steps must be greater than 0.")
232231

233232
if not isinstance(self.batch_size, int):
234233
raise ValueError("The batch size must be of type int.")
235-
if not self.batch_size > 0:
234+
if self.batch_size <= 0:
236235
raise ValueError("The batch size must be greater than 0.")
237236

238237
def _get_circular_patch_mask(self, sharpness: int = 40) -> np.ndarray:
@@ -261,14 +260,14 @@ def _get_circular_patch_mask(self, sharpness: int = 40) -> np.ndarray:
261260

262261
if self.estimator.channels_first:
263262
if self.nb_dims == 3:
264-
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after))
263+
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after)) # type: ignore
265264
elif self.nb_dims == 4:
266-
pad_width = ((0, 0), (0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after))
265+
pad_width = ((0, 0), (0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after)) # type: ignore
267266
else:
268267
if self.nb_dims == 3:
269-
pad_width = ((pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0))
268+
pad_width = ((pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0)) # type: ignore
270269
elif self.nb_dims == 4:
271-
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0))
270+
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0)) # type: ignore
272271

273272
mask = np.pad(mask, pad_width=pad_width, mode="constant", constant_values=(0, 0),)
274273

0 commit comments

Comments
 (0)