Skip to content

Commit fdd788a

Browse files
authored
Merge pull request #592 from Trusted-AI/development_maintenance_140
General code maintenance updates
2 parents 4c8fafc + 02bfa81 commit fdd788a

File tree

152 files changed

+2175
-1550
lines changed

Some content is hidden

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

152 files changed

+2175
-1550
lines changed

.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

art/attacks/attack.py

Lines changed: 14 additions & 11 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

@@ -129,8 +133,8 @@ class EvasionAttack(Attack):
129133
"""
130134

131135
def __init__(self, **kwargs) -> None:
132-
self._targeted = None
133-
super(EvasionAttack, self).__init__(**kwargs)
136+
self._targeted = False
137+
super().__init__(**kwargs)
134138

135139
@abc.abstractmethod
136140
def generate( # lgtm [py/inheritance/incorrect-overridden-signature]
@@ -148,7 +152,7 @@ def generate( # lgtm [py/inheritance/incorrect-overridden-signature]
148152
raise NotImplementedError
149153

150154
@property
151-
def targeted(self) -> Optional[bool]:
155+
def targeted(self) -> bool:
152156
"""
153157
Return Boolean if attack is targeted. Return None if not applicable.
154158
"""
@@ -164,7 +168,7 @@ class PoisoningAttack(Attack):
164168
Abstract base class for poisoning attack classes
165169
"""
166170

167-
def __init__(self, classifier: Optional["Classifier"]) -> None:
171+
def __init__(self, classifier: Optional["CLASSIFIER_TYPE"]) -> None:
168172
"""
169173
:param classifier: A trained classifier (or none if no classifier is needed)
170174
"""
@@ -189,10 +193,9 @@ class PoisoningAttackTransformer(PoisoningAttack):
189193
These attacks have an additional method, `poison_estimator`, that returns the poisoned classifier.
190194
"""
191195

192-
def __init__(self, classifier: Optional["Classifier"], **kwargs) -> None:
196+
def __init__(self, classifier: Optional["CLASSIFIER_TYPE"], **kwargs) -> None:
193197
"""
194198
:param classifier: A trained classifier (or none if no classifier is needed)
195-
:type classifier: `art.estimators.classification.Classifier` or `None`
196199
"""
197200
super().__init__(classifier)
198201

@@ -210,7 +213,7 @@ def poison(self, x: np.ndarray, y=Optional[np.ndarray], **kwargs) -> Tuple[np.nd
210213
raise NotImplementedError
211214

212215
@abc.abstractmethod
213-
def poison_estimator(self, x: np.ndarray, y: np.ndarray, **kwargs) -> "Classifier":
216+
def poison_estimator(self, x: np.ndarray, y: np.ndarray, **kwargs) -> "CLASSIFIER_TYPE":
214217
"""
215218
Returns a poisoned version of the classifier used to initialize the attack
216219
:param x: Training data
@@ -269,7 +272,7 @@ class ExtractionAttack(Attack):
269272
"""
270273

271274
@abc.abstractmethod
272-
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":
273276
"""
274277
Extract models and return them as an ART classifier. This method should be overridden by all concrete extraction
275278
attack implementations.
@@ -340,7 +343,7 @@ def set_params(self, **kwargs) -> None:
340343
Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes.
341344
"""
342345
# Save attack-specific parameters
343-
super(AttributeInferenceAttack, self).set_params(**kwargs)
346+
super().set_params(**kwargs)
344347
self._check_params()
345348

346349
def _check_params(self) -> None:

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

art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,14 @@
3232

3333
from art.attacks.attack import EvasionAttack
3434
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
35-
from art.estimators.classification.classifier import (
36-
ClassifierMixin,
37-
ClassifierNeuralNetwork,
38-
ClassifierGradients,
39-
)
35+
from art.estimators.classification.classifier import ClassifierMixin
4036
from art.utils import check_and_transform_label_format
4137

4238
if TYPE_CHECKING:
4339
import tensorflow as tf
4440

41+
from art.utils import CLASSIFIER_NEURALNETWORK_TYPE
42+
4543
logger = logging.getLogger(__name__)
4644

4745

@@ -66,7 +64,7 @@ class AdversarialPatchTensorFlowV2(EvasionAttack):
6664

6765
def __init__(
6866
self,
69-
classifier: Union[ClassifierNeuralNetwork, ClassifierGradients],
67+
classifier: "CLASSIFIER_NEURALNETWORK_TYPE",
7068
rotation_max: float = 22.5,
7169
scale_min: float = 0.1,
7270
scale_max: float = 1.0,
@@ -92,14 +90,17 @@ def __init__(
9290
"""
9391
import tensorflow as tf # lgtm [py/repeated-import]
9492

95-
super(AdversarialPatchTensorFlowV2, self).__init__(estimator=classifier)
93+
super().__init__(estimator=classifier)
9694
self.rotation_max = rotation_max
9795
self.scale_min = scale_min
9896
self.scale_max = scale_max
9997
self.learning_rate = learning_rate
10098
self.max_iter = max_iter
10199
self.batch_size = batch_size
102-
self.patch_shape = patch_shape
100+
if patch_shape is None:
101+
self.patch_shape = self.estimator.input_shape
102+
else:
103+
self.patch_shape = patch_shape
103104
self.image_shape = classifier.input_shape
104105
self._check_params()
105106

@@ -117,9 +118,6 @@ def __init__(
117118
self.i_h = 1
118119
self.i_w = 2
119120

120-
if self.patch_shape is None:
121-
self.patch_shape = self.estimator.input_shape
122-
123121
if self.patch_shape[0] != self.patch_shape[1]:
124122
raise ValueError("Patch height and width need to be the same.")
125123

@@ -144,7 +142,7 @@ def __init__(
144142
learning_rate=self.learning_rate, momentum=0.0, nesterov=False, name="SGD"
145143
)
146144

147-
def _train_step(self, images: Optional[np.ndarray] = None, target: Optional[np.ndarray] = None) -> "tf.Tensor":
145+
def _train_step(self, images: "tf.Tensor", target: Optional["tf.Tensor"] = None) -> "tf.Tensor":
148146
import tensorflow as tf # lgtm [py/repeated-import]
149147

150148
if target is None:
@@ -211,7 +209,12 @@ def _get_circular_patch_mask(self, nb_samples: int, sharpness: int = 40) -> "tf.
211209
image_mask = tf.stack([image_mask] * nb_samples)
212210
return image_mask
213211

214-
def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional[float] = None) -> "tf.Tensor":
212+
def _random_overlay(
213+
self,
214+
images: Union[np.ndarray, "tf.Tensor"],
215+
patch: Union[np.ndarray, "tf.Variable"],
216+
scale: Optional[float] = None,
217+
) -> "tf.Tensor":
215218
import tensorflow as tf # lgtm [py/repeated-import]
216219
import tensorflow_addons as tfa
217220

@@ -271,7 +274,7 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
271274

272275
transform_vectors = list()
273276

274-
for i in range(nb_samples):
277+
for _ in range(nb_samples):
275278
if scale is None:
276279
im_scale = np.random.uniform(low=self.scale_min, high=self.scale_max)
277280
else:
@@ -292,8 +295,8 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
292295

293296
# Scale
294297
xform_matrix = rotation_matrix * (1.0 / im_scale)
295-
a0, a1 = xform_matrix[0]
296-
b0, b1 = xform_matrix[1]
298+
a_0, a_1 = xform_matrix[0]
299+
b_0, b_1 = xform_matrix[1]
297300

298301
x_origin = float(self.image_shape[self.i_w]) / 2
299302
y_origin = float(self.image_shape[self.i_h]) / 2
@@ -303,10 +306,10 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
303306
x_origin_delta = x_origin - x_origin_shifted
304307
y_origin_delta = y_origin - y_origin_shifted
305308

306-
a2 = x_origin_delta - (x_shift / (2 * im_scale))
307-
b2 = y_origin_delta - (y_shift / (2 * im_scale))
309+
a_2 = x_origin_delta - (x_shift / (2 * im_scale))
310+
b_2 = y_origin_delta - (y_shift / (2 * im_scale))
308311

309-
transform_vectors.append(np.array([a0, a1, a2, b0, b1, b2, 0, 0]).astype(np.float32))
312+
transform_vectors.append([a_0, a_1, a_2, b_0, b_1, b_2, 0, 0])
310313

311314
image_mask = tfa.image.transform(image_mask, transform_vectors, "BILINEAR",)
312315
padded_patch = tfa.image.transform(padded_patch, transform_vectors, "BILINEAR",)
@@ -318,7 +321,7 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
318321
padded_patch = tf.stack([padded_patch] * images.shape[1], axis=1)
319322
padded_patch = tf.cast(padded_patch, images.dtype)
320323

321-
inverted_mask = 1 - image_mask
324+
inverted_mask = tf.constant(1, dtype=image_mask.dtype) - image_mask
322325

323326
return images * inverted_mask + padded_patch * image_mask
324327

@@ -336,21 +339,21 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
336339

337340
shuffle = kwargs.get("shuffle", True)
338341
if shuffle:
339-
ds = (
342+
dataset = (
340343
tf.data.Dataset.from_tensor_slices((x, y))
341344
.shuffle(10000)
342345
.batch(self.batch_size)
343346
.repeat(math.ceil(x.shape[0] / self.batch_size))
344347
)
345348
else:
346-
ds = (
349+
dataset = (
347350
tf.data.Dataset.from_tensor_slices((x, y))
348351
.batch(self.batch_size)
349352
.repeat(math.ceil(x.shape[0] / self.batch_size))
350353
)
351354

352355
for _ in trange(self.max_iter, desc="Adversarial Patch TensorFlow v2"):
353-
for images, target in ds:
356+
for images, target in dataset:
354357
_ = self._train_step(images=images, target=target)
355358

356359
return (

0 commit comments

Comments
 (0)