Skip to content

Commit 72f78bc

Browse files
author
Beat Buesser
committed
Merge remote-tracking branch 'origin/dev_1.10.2' into development_issue_1692
2 parents 79149bb + c736e39 commit 72f78bc

File tree

13 files changed

+146
-35
lines changed

13 files changed

+146
-35
lines changed

art/attacks/evasion/carlini.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ class CarliniLInfMethod(EvasionAttack):
527527
"initial_const",
528528
"largest_const",
529529
"const_factor",
530+
"batch_size",
530531
"verbose",
531532
]
532533
_estimator_requirements = (BaseEstimator, ClassGradientsMixin)
@@ -542,6 +543,7 @@ def __init__(
542543
initial_const: float = 1e-5,
543544
largest_const: float = 20.0,
544545
const_factor: float = 2.0,
546+
batch_size: int = 1,
545547
verbose: bool = True,
546548
) -> None:
547549
"""
@@ -559,6 +561,7 @@ def __init__(
559561
:param initial_const: The initial value of constant `c`.
560562
:param largest_const: The largest value of constant `c`.
561563
:param const_factor: The rate of increasing constant `c` with `const_factor > 1`, where smaller more accurate.
564+
:param batch_size: Size of the batch on which adversarial samples are generated.
562565
:param verbose: Show progress bars.
563566
"""
564567
super().__init__(estimator=classifier)
@@ -571,6 +574,7 @@ def __init__(
571574
self.initial_const = initial_const
572575
self.largest_const = largest_const
573576
self.const_factor = const_factor
577+
self.batch_size = batch_size
574578
self.verbose = verbose
575579
self._check_params()
576580

@@ -591,7 +595,7 @@ def _loss(
591595
:param tau: Current limit `tau`.
592596
:return: A tuple of current predictions, total loss, logits loss and regularisation loss.
593597
"""
594-
z_predicted = self.estimator.predict(np.array(x_adv, dtype=ART_NUMPY_DTYPE), batch_size=1)
598+
z_predicted = self.estimator.predict(np.array(x_adv, dtype=ART_NUMPY_DTYPE), batch_size=self.batch_size)
595599
z_target = np.sum(z_predicted * target, axis=1)
596600
z_other = np.max(
597601
z_predicted * (1 - target) + (np.min(z_predicted, axis=1) - 1)[:, np.newaxis] * target,
@@ -753,7 +757,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
753757

754758
# No labels provided, use model prediction as correct class
755759
if y is None:
756-
y = get_labels_np_array(self.estimator.predict(x, batch_size=1))
760+
y = get_labels_np_array(self.estimator.predict(x, batch_size=self.batch_size))
757761

758762
if self.estimator.nb_classes == 2 and y.shape[1] == 1:
759763
raise ValueError( # pragma: no cover
@@ -830,6 +834,9 @@ def _check_params(self) -> None:
830834
if not isinstance(self.const_factor, (int, float)) or self.const_factor < 0:
831835
raise ValueError("The constant factor value must be a float and greater than 1.")
832836

837+
if not isinstance(self.batch_size, int) or self.batch_size < 1:
838+
raise ValueError("The batch size must be an integer greater than zero.")
839+
833840

834841
class CarliniL0Method(CarliniL2Method):
835842
"""

art/attacks/poisoning/clean_label_backdoor_attack.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def poison( # pylint: disable=W0221
133133
if any(no_change_detected): # pragma: no cover
134134
logger.warning("Perturbed input is the same as original data after PGD. Check params.")
135135
idx_no_change = np.arange(len(no_change_detected))[no_change_detected]
136-
logger.warning("%d indices without change: %d", len(idx_no_change), idx_no_change)
136+
logger.warning("%d indices without change: %s", len(idx_no_change), idx_no_change)
137137

138138
# Add backdoor and poison with the same label
139139
poisoned_input, _ = self.backdoor.poison(perturbed_input, self.target, broadcast=broadcast)

art/defences/postprocessor/gaussian_noise.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __call__(self, preds: np.ndarray) -> np.ndarray:
6969
# Finally normalize probability output
7070
if all_probability:
7171
post_preds[post_preds < 0.0] = 0.0
72-
sums = np.sum(post_preds, axis=1)
72+
sums = np.sum(post_preds, axis=1, keepdims=True)
7373
post_preds /= sums
7474
else:
7575
post_preds[post_preds < 0.0] = 0.0

art/estimators/certification/randomized_smoothing/numpy.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@
2525
import logging
2626
from typing import List, Union, TYPE_CHECKING, Tuple
2727

28+
import warnings
2829
import numpy as np
2930

31+
from art.config import ART_NUMPY_DTYPE
3032
from art.estimators.estimator import BaseEstimator, LossGradientsMixin, NeuralNetworkMixin
3133
from art.estimators.certification.randomized_smoothing.randomized_smoothing import RandomizedSmoothingMixin
3234
from art.estimators.classification import ClassifierMixin, ClassGradientsMixin
35+
from art.defences.preprocessor.gaussian_augmentation import GaussianAugmentation
3336

3437
if TYPE_CHECKING:
3538
from art.utils import CLASSIFIER_NEURALNETWORK_TYPE
@@ -69,6 +72,12 @@ def __init__(
6972
:param scale: Standard deviation of Gaussian noise added.
7073
:param alpha: The failure probability of smoothing
7174
"""
75+
if classifier.preprocessing_defences is not None:
76+
warnings.warn(
77+
"\n With the current backend Gaussian noise will be added by Randomized Smoothing "
78+
"BEFORE the application of preprocessing defences. Please ensure this conforms to your use case.\n"
79+
)
80+
7281
super().__init__(
7382
model=classifier.model,
7483
channels_first=classifier.channels_first,
@@ -112,7 +121,12 @@ def _fit_classifier(self, x: np.ndarray, y: np.ndarray, batch_size: int, nb_epoc
112121
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
113122
and providing it takes no effect.
114123
"""
115-
return self.classifier.fit(x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
124+
125+
g_a = GaussianAugmentation(sigma=self.scale, augmentation=False)
126+
for _ in range(nb_epochs):
127+
x_rs, _ = g_a(x)
128+
x_rs = x_rs.astype(ART_NUMPY_DTYPE)
129+
self.classifier.fit(x_rs, y, batch_size=batch_size, nb_epochs=1, **kwargs)
116130

117131
def loss_gradient( # pylint: disable=W0221
118132
self, x: np.ndarray, y: np.ndarray, training_mode: bool = False, **kwargs

art/estimators/certification/randomized_smoothing/pytorch.py

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@
2525
import logging
2626
from typing import List, Optional, Tuple, Union, TYPE_CHECKING
2727

28+
import warnings
29+
import random
30+
from tqdm import tqdm
2831
import numpy as np
2932

3033
from art.config import ART_NUMPY_DTYPE
3134
from art.estimators.classification.pytorch import PyTorchClassifier
3235
from art.estimators.certification.randomized_smoothing.randomized_smoothing import RandomizedSmoothingMixin
36+
from art.utils import check_and_transform_label_format
3337

3438
if TYPE_CHECKING:
3539
# pylint: disable=C0412
@@ -94,6 +98,12 @@ def __init__(
9498
:param scale: Standard deviation of Gaussian noise added.
9599
:param alpha: The failure probability of smoothing.
96100
"""
101+
if preprocessing_defences is not None:
102+
warnings.warn(
103+
"\n With the current backend (Pytorch) Gaussian noise will be added by Randomized Smoothing "
104+
"AFTER the application of preprocessing defences. Please ensure this conforms to your use case.\n"
105+
)
106+
97107
super().__init__(
98108
model=model,
99109
loss=loss,
@@ -126,26 +136,72 @@ def fit( # pylint: disable=W0221
126136
batch_size: int = 128,
127137
nb_epochs: int = 10,
128138
training_mode: bool = True,
129-
**kwargs
130-
):
139+
**kwargs,
140+
) -> None:
131141
"""
132142
Fit the classifier on the training set `(x, y)`.
133143
134144
:param x: Training data.
135-
:param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or indices of shape
136-
(nb_samples,).
137-
:param batch_size: Batch size.
138-
:key nb_epochs: Number of epochs to use for training
145+
:param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
146+
shape (nb_samples,).
147+
:param batch_size: Size of batches.
148+
:param nb_epochs: Number of epochs to use for training.
149+
:param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
139150
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
140151
and providing it takes no effect.
141-
:type kwargs: `dict`
142-
:return: `None`
143152
"""
153+
import torch # lgtm [py/repeated-import]
144154

145155
# Set model mode
146156
self._model.train(mode=training_mode)
147157

148-
RandomizedSmoothingMixin.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
158+
if self._optimizer is None: # pragma: no cover
159+
raise ValueError("An optimizer is needed to train the model, but none for provided.")
160+
161+
y = check_and_transform_label_format(y, self.nb_classes)
162+
163+
# Apply preprocessing
164+
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
165+
166+
# Check label shape
167+
y_preprocessed = self.reduce_labels(y_preprocessed)
168+
169+
num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size)))
170+
ind = np.arange(len(x_preprocessed))
171+
std = torch.tensor(self.scale).to(self._device)
172+
# Start training
173+
for _ in tqdm(range(nb_epochs)):
174+
# Shuffle the examples
175+
random.shuffle(ind)
176+
177+
# Train for one epoch
178+
for m in range(num_batch):
179+
i_batch = torch.from_numpy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
180+
o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
181+
182+
# Add random noise for randomized smoothing
183+
i_batch = i_batch + torch.randn_like(i_batch, device=self._device) * std
184+
185+
# Zero the parameter gradients
186+
self._optimizer.zero_grad()
187+
188+
# Perform prediction
189+
model_outputs = self._model(i_batch)
190+
191+
# Form the loss function
192+
loss = self._loss(model_outputs[-1], o_batch) # lgtm [py/call-to-non-callable]
193+
194+
# Do training
195+
if self._use_amp: # pragma: no cover
196+
from apex import amp # pylint: disable=E0611
197+
198+
with amp.scale_loss(loss, self._optimizer) as scaled_loss:
199+
scaled_loss.backward()
200+
201+
else:
202+
loss.backward()
203+
204+
self._optimizer.step()
149205

150206
def predict(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> np.ndarray: # type: ignore
151207
"""

art/estimators/certification/randomized_smoothing/randomized_smoothing.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
from tqdm.auto import tqdm
3232

3333
from art.config import ART_NUMPY_DTYPE
34-
from art.defences.preprocessor.gaussian_augmentation import GaussianAugmentation
3534

3635
logger = logging.getLogger(__name__)
3736

@@ -141,9 +140,7 @@ def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: in
141140
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
142141
and providing it takes no effect.
143142
"""
144-
g_a = GaussianAugmentation(sigma=self.scale, augmentation=False)
145-
x_rs, _ = g_a(x)
146-
self._fit_classifier(x_rs, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
143+
self._fit_classifier(x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
147144

148145
def certify(self, x: np.ndarray, n: int, batch_size: int = 32) -> Tuple[np.ndarray, np.ndarray]:
149146
"""

art/estimators/certification/randomized_smoothing/tensorflow.py

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@
2525
import logging
2626
from typing import Callable, List, Optional, Tuple, Union, TYPE_CHECKING
2727

28+
import warnings
29+
from tqdm import tqdm
2830
import numpy as np
2931

3032
from art.estimators.classification.tensorflow import TensorFlowV2Classifier
3133
from art.estimators.certification.randomized_smoothing.randomized_smoothing import RandomizedSmoothingMixin
34+
from art.utils import check_and_transform_label_format
3235

3336
if TYPE_CHECKING:
3437
# pylint: disable=C0412
@@ -91,6 +94,12 @@ def __init__(
9194
:param scale: Standard deviation of Gaussian noise added.
9295
:param alpha: The failure probability of smoothing.
9396
"""
97+
if preprocessing_defences is not None:
98+
warnings.warn(
99+
"\nWith the current backend (Tensorflow), Gaussian noise will be added by Randomized Smoothing "
100+
"AFTER the application of preprocessing defences. Please ensure this conforms to your use case.\n"
101+
)
102+
94103
super().__init__(
95104
model=model,
96105
nb_classes=nb_classes,
@@ -113,21 +122,41 @@ def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: boo
113122
def _fit_classifier(self, x: np.ndarray, y: np.ndarray, batch_size: int, nb_epochs: int, **kwargs) -> None:
114123
return TensorFlowV2Classifier.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
115124

116-
def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: int = 10, **kwargs):
125+
def fit(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128, nb_epochs: int = 10, **kwargs) -> None:
117126
"""
118127
Fit the classifier on the training set `(x, y)`.
119128
120129
:param x: Training data.
121-
:param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or indices of shape
122-
(nb_samples,).
123-
:param batch_size: Batch size.
124-
:key nb_epochs: Number of epochs to use for training
125-
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
126-
and providing it takes no effect.
127-
:type kwargs: `dict`
128-
:return: `None`
130+
:param y: Labels, one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
131+
shape (nb_samples,).
132+
:param batch_size: Size of batches.
133+
:param nb_epochs: Number of epochs to use for training.
134+
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for
135+
TensorFlow and providing it takes no effect.
129136
"""
130-
RandomizedSmoothingMixin.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
137+
import tensorflow as tf # lgtm [py/repeated-import]
138+
139+
if self._train_step is None: # pragma: no cover
140+
raise TypeError(
141+
"The training function `train_step` is required for fitting a model but it has not been " "defined."
142+
)
143+
144+
y = check_and_transform_label_format(y, self.nb_classes)
145+
146+
# Apply preprocessing
147+
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
148+
149+
# Check label shape
150+
if self._reduce_labels:
151+
y_preprocessed = np.argmax(y_preprocessed, axis=1)
152+
153+
train_ds = tf.data.Dataset.from_tensor_slices((x_preprocessed, y_preprocessed)).shuffle(10000).batch(batch_size)
154+
155+
for _ in tqdm(range(nb_epochs)):
156+
for images, labels in train_ds:
157+
# Add random noise for randomized smoothing
158+
images += tf.random.normal(shape=images.shape, mean=0.0, stddev=self.scale)
159+
self._train_step(self.model, images, labels)
131160

132161
def predict(self, x: np.ndarray, batch_size: int = 128, **kwargs) -> np.ndarray: # type: ignore
133162
"""

art/estimators/classification/pytorch.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -497,13 +497,21 @@ def fit_generator(self, generator: "DataGenerator", nb_epochs: int = 20, **kwarg
497497

498498
def clone_for_refitting(self) -> "PyTorchClassifier": # lgtm [py/inheritance/incorrect-overridden-signature]
499499
"""
500-
Create a copy of the classifier that can be refit from scratch. Will inherit same architecture, optimizer and
501-
initialization as cloned model, but without weights.
500+
Create a copy of the classifier that can be refit from scratch. Will inherit same architecture, same type of
501+
optimizer and initialization as the original classifier, but without weights.
502502
503503
:return: new estimator
504504
"""
505505
model = copy.deepcopy(self.model)
506-
clone = type(self)(model, self._loss, self.input_shape, self.nb_classes, optimizer=self._optimizer)
506+
507+
if self._optimizer is None: # pragma: no cover
508+
raise ValueError("An optimizer is needed to train the model, but none is provided.")
509+
510+
# create a new optimizer that binds to the cloned model's parameters and uses original optimizer's defaults
511+
new_optimizer = type(self._optimizer)(model.parameters(), **self._optimizer.defaults) # type: ignore
512+
513+
clone = type(self)(model, self._loss, self.input_shape, self.nb_classes, optimizer=new_optimizer)
514+
507515
# reset weights
508516
clone.reset()
509517
params = self.get_params()

art/estimators/object_detection/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"""
44
from art.estimators.object_detection.object_detector import ObjectDetectorMixin
55

6-
from art.estimators.object_detection.python_object_detector import PyTorchObjectDetector
6+
from art.estimators.object_detection.pytorch_object_detector import PyTorchObjectDetector
77
from art.estimators.object_detection.pytorch_faster_rcnn import PyTorchFasterRCNN
88
from art.estimators.object_detection.tensorflow_faster_rcnn import TensorFlowFasterRCNN

art/estimators/object_detection/pytorch_faster_rcnn.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from typing import List, Optional, Tuple, Union, TYPE_CHECKING
2323

2424

25-
from art.estimators.object_detection.python_object_detector import PyTorchObjectDetector
25+
from art.estimators.object_detection.pytorch_object_detector import PyTorchObjectDetector
2626

2727
if TYPE_CHECKING:
2828
# pylint: disable=C0412

0 commit comments

Comments
 (0)