Skip to content

Commit fd5a5da

Browse files
authored
Merge pull request #1156 from Trusted-AI/development_feature_adversaries
Add support for multiple layers in Feature Adversaries
2 parents 215a68f + 76b62c2 commit fd5a5da

File tree

5 files changed

+47
-38
lines changed

5 files changed

+47
-38
lines changed

art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ class FeatureAdversariesPyTorch(EvasionAttack):
4747
"""
4848

4949
attack_params = EvasionAttack.attack_params + [
50+
"delta",
5051
"optimizer",
5152
"optimizer_kwargs",
52-
"delta",
5353
"lambda_",
5454
"layer",
5555
"max_iter",
@@ -64,11 +64,11 @@ class FeatureAdversariesPyTorch(EvasionAttack):
6464
def __init__(
6565
self,
6666
estimator: "PYTORCH_ESTIMATOR_TYPE",
67+
delta: float,
6768
optimizer: Optional["Optimizer"] = None,
6869
optimizer_kwargs: Optional[dict] = None,
69-
delta: float = 15 / 255,
7070
lambda_: float = 0.0,
71-
layer: Optional[Union[int, str]] = -1,
71+
layer: Union[int, str] = -1,
7272
max_iter: int = 100,
7373
batch_size: int = 32,
7474
step_size: Optional[Union[int, float]] = None,
@@ -79,12 +79,12 @@ def __init__(
7979
Create a :class:`.FeatureAdversariesPyTorch` instance.
8080
8181
:param estimator: A trained estimator.
82+
:param delta: The maximum deviation between source and guide images.
8283
:param optimizer: Optimizer applied to problem constrained only by clip values if defined, if None the
8384
Projected Gradient Descent (PGD) optimizer is used.
8485
:param optimizer_kwargs: Additional optimizer arguments.
85-
:param delta: The maximum deviation between source and guide images.
8686
:param lambda_: Regularization parameter of the L-inf soft constraint.
87-
:param layer: Index of the representation layer.
87+
:param layer: Index or tuple of indices of the representation layer(s).
8888
:param max_iter: The maximum number of iterations.
8989
:param batch_size: Batch size.
9090
:param step_size: Step size for PGD optimizer.
@@ -97,7 +97,7 @@ def __init__(
9797
self._optimizer_kwargs = {} if optimizer_kwargs is None else optimizer_kwargs
9898
self.delta = delta
9999
self.lambda_ = lambda_
100-
self.layer = layer
100+
self.layer = layer if isinstance(layer, tuple) else (layer,)
101101
self.batch_size = batch_size
102102
self.max_iter = max_iter
103103
self.step_size = step_size
@@ -116,14 +116,16 @@ def _generate_batch(self, x: "torch.Tensor", y: "torch.Tensor") -> "torch.Tensor
116116
import torch # lgtm [py/repeated-import]
117117

118118
def loss_fn(source_orig, source_adv, guide):
119-
adv_representation = self.estimator.get_activations(source_adv, self.layer, self.batch_size, True)
120-
guide_representation = self.estimator.get_activations(guide, self.layer, self.batch_size, True)
119+
representation_loss = torch.zeros(size=(source_orig.shape[0],)).to(self.estimator.device)
120+
for layer_i in self.layer:
121+
adv_representation = self.estimator.get_activations(source_adv, layer_i, self.batch_size, True)
122+
guide_representation = self.estimator.get_activations(guide, layer_i, self.batch_size, True)
121123

122-
dim = tuple(range(1, len(source_adv.shape)))
123-
soft_constraint = torch.amax(torch.abs(source_adv - source_orig), dim=dim)
124+
dim = tuple(range(1, len(source_adv.shape)))
125+
soft_constraint = torch.amax(torch.abs(source_adv - source_orig), dim=dim)
124126

125-
dim = tuple(range(1, len(adv_representation.shape)))
126-
representation_loss = torch.sum(torch.square(adv_representation - guide_representation), dim=dim)
127+
dim = tuple(range(1, len(adv_representation.shape)))
128+
representation_loss += torch.sum(torch.square(adv_representation - guide_representation), dim=dim)
127129

128130
loss = torch.mean(representation_loss + self.lambda_ * soft_constraint)
129131
return loss
@@ -221,7 +223,7 @@ def _check_params(self) -> None:
221223
if self.lambda_ < 0.0:
222224
raise ValueError("The regularization parameter `lambda_` has to be non-negative.")
223225

224-
if not isinstance(self.layer, int) and not isinstance(self.layer, str):
226+
if not isinstance(self.layer, int) and not isinstance(self.layer, str) and not isinstance(self.layer, tuple):
225227
raise ValueError("The value of the representation layer must be integer or string.")
226228

227229
if not isinstance(self.max_iter, int):

art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
| Paper link: https://arxiv.org/abs/1511.05122
2222
"""
2323
import logging
24-
from typing import TYPE_CHECKING, Optional, Union
24+
from typing import TYPE_CHECKING, Optional, Tuple, Union
2525

2626
import numpy as np
2727
from tqdm.auto import trange
@@ -47,9 +47,9 @@ class FeatureAdversariesTensorFlowV2(EvasionAttack):
4747
"""
4848

4949
attack_params = EvasionAttack.attack_params + [
50+
"delta",
5051
"optimizer",
5152
"optimizer_kwargs",
52-
"delta",
5353
"lambda_",
5454
"layer",
5555
"max_iter",
@@ -64,11 +64,11 @@ class FeatureAdversariesTensorFlowV2(EvasionAttack):
6464
def __init__(
6565
self,
6666
estimator: "TENSORFLOWV2_ESTIMATOR_TYPE",
67+
delta: float,
6768
optimizer: Optional["Optimizer"] = None,
6869
optimizer_kwargs: Optional[dict] = None,
69-
delta: float = 15 / 255,
7070
lambda_: float = 0.0,
71-
layer: Optional[Union[int, str]] = -1,
71+
layer: Union[int, str, Tuple[int, ...], Tuple[str, ...]] = -1,
7272
max_iter: int = 100,
7373
batch_size: int = 32,
7474
step_size: Optional[Union[int, float]] = None,
@@ -79,12 +79,12 @@ def __init__(
7979
Create a :class:`.FeatureAdversariesTensorFlowV2` instance.
8080
8181
:param estimator: A trained estimator.
82+
:param delta: The maximum deviation between source and guide images.
8283
:param optimizer: Optimizer applied to problem constrained only by clip values if defined, if None the
8384
Projected Gradient Descent (PGD) optimizer is used.
8485
:param optimizer_kwargs: Additional optimizer arguments.
85-
:param delta: The maximum deviation between source and guide images.
8686
:param lambda_: Regularization parameter of the L-inf soft constraint.
87-
:param layer: Index of the representation layer.
87+
:param layer: Index or tuple of indices of the representation layer(s).
8888
:param max_iter: The maximum number of iterations.
8989
:param batch_size: Batch size.
9090
:param step_size: Step size for PGD optimizer.
@@ -93,11 +93,11 @@ def __init__(
9393
"""
9494
super().__init__(estimator=estimator)
9595

96+
self.delta = delta
9697
self.optimizer = optimizer
9798
self._optimizer_kwargs = {} if optimizer_kwargs is None else optimizer_kwargs
98-
self.delta = delta
9999
self.lambda_ = lambda_
100-
self.layer = layer
100+
self.layer = layer if isinstance(layer, tuple) else (layer,)
101101
self.batch_size = batch_size
102102
self.max_iter = max_iter
103103
self.step_size = step_size
@@ -116,14 +116,16 @@ def _generate_batch(self, x: "tf.Tensor", y: "tf.Tensor") -> "tf.Tensor":
116116
import tensorflow as tf # lgtm [py/repeated-import]
117117

118118
def loss_fn(source_orig, source_adv, guide):
119-
adv_representation = self.estimator.get_activations(source_adv, self.layer, self.batch_size, True)
120-
guide_representation = self.estimator.get_activations(guide, self.layer, self.batch_size, True)
119+
representation_loss = tf.zeros(shape=(source_orig.shape[0],), dtype=tf.float32)
120+
for layer_i in self.layer:
121+
adv_representation = self.estimator.get_activations(source_adv, layer_i, self.batch_size, True)
122+
guide_representation = self.estimator.get_activations(guide, layer_i, self.batch_size, True)
121123

122-
axis = tuple(range(1, len(source_adv.shape)))
123-
soft_constraint = tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis)
124+
axis = tuple(range(1, len(source_adv.shape)))
125+
soft_constraint = tf.cast(tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis), tf.float32)
124126

125-
axis = tuple(range(1, len(adv_representation.shape)))
126-
representation_loss = tf.reduce_sum(tf.square(adv_representation - guide_representation), axis=axis)
127+
axis = tuple(range(1, len(adv_representation.shape)))
128+
representation_loss += tf.reduce_sum(tf.square(adv_representation - guide_representation), axis=axis)
127129

128130
loss = tf.math.reduce_mean(representation_loss + self.lambda_ * soft_constraint)
129131
return loss
@@ -218,7 +220,7 @@ def _check_params(self) -> None:
218220
if self.lambda_ < 0.0:
219221
raise ValueError("The regularization parameter `lambda_` has to be non-negative.")
220222

221-
if not isinstance(self.layer, int) and not isinstance(self.layer, str):
223+
if not isinstance(self.layer, int) and not isinstance(self.layer, str) and not isinstance(self.layer, tuple):
222224
raise ValueError("The value of the representation layer must be integer or string.")
223225

224226
if not isinstance(self.max_iter, int):

tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_pytorch.py renamed to tests/attacks/evasion/feature_adversaries/test_feature_adversaries_pytorch.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ def test_images_pgd(art_warning, fix_get_mnist_subset, image_dl_estimator_for_at
4242
(x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset
4343

4444
classifier = image_dl_estimator_for_attack(FeatureAdversariesPyTorch, functional=True)
45+
print("classifier", classifier)
4546

4647
attack = FeatureAdversariesPyTorch(
47-
classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=1, random_start=False
48+
classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=2, random_start=False
4849
)
4950
x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3])
5051
assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01)
51-
assert np.mean(x_train_mnist_adv) != 0
52+
assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0
5253
except ARTTestException as e:
5354
art_warning(e)
5455

@@ -67,14 +68,16 @@ def test_images_unconstrained_adam(art_warning, fix_get_mnist_subset, image_dl_e
6768
)
6869
x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3])
6970
assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01)
70-
assert np.mean(x_train_mnist_adv) != 0
71+
assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0
7172
except ARTTestException as e:
7273
art_warning(e)
7374

7475

75-
@pytest.mark.skip_framework("tensorflow", "keras", "kerastf", "mxnet", "non_dl_frameworks")
76+
@pytest.mark.framework_agnostic
7677
def test_classifier_type_check_fail(art_warning):
7778
try:
78-
backend_test_classifier_type_check_fail(FeatureAdversariesPyTorch, [BaseEstimator, NeuralNetworkMixin])
79+
backend_test_classifier_type_check_fail(
80+
FeatureAdversariesPyTorch, [BaseEstimator, NeuralNetworkMixin], delta=1.0
81+
)
7982
except ARTTestException as e:
8083
art_warning(e)

tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_tensorflow.py renamed to tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ def test_images_pgd(art_warning, fix_get_mnist_subset, image_dl_estimator_for_at
4444
classifier = image_dl_estimator_for_attack(FeatureAdversariesTensorFlowV2)
4545

4646
attack = FeatureAdversariesTensorFlowV2(
47-
classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=1, random_start=False
47+
classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=2, random_start=False
4848
)
4949
x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3])
5050
assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01)
51-
assert np.mean(x_train_mnist_adv) != 0
51+
assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0
5252
except ARTTestException as e:
5353
art_warning(e)
5454

@@ -67,14 +67,16 @@ def test_images_unconstrained_adam(art_warning, fix_get_mnist_subset, image_dl_e
6767
)
6868
x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3])
6969
assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01)
70-
assert np.mean(x_train_mnist_adv) != 0
70+
assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0
7171
except ARTTestException as e:
7272
art_warning(e)
7373

7474

75-
@pytest.mark.skip_framework("tensorflow1", "keras", "kerastf", "mxnet", "non_dl_frameworks", "pytorch")
75+
@pytest.mark.framework_agnostic
7676
def test_classifier_type_check_fail(art_warning):
7777
try:
78-
backend_test_classifier_type_check_fail(FeatureAdversariesTensorFlowV2, [BaseEstimator, NeuralNetworkMixin])
78+
backend_test_classifier_type_check_fail(
79+
FeatureAdversariesTensorFlowV2, [BaseEstimator, NeuralNetworkMixin], delta=1.0
80+
)
7981
except ARTTestException as e:
8082
art_warning(e)

0 commit comments

Comments
 (0)