From 706e9649e7f1de610369d5bb4d77750547a21d04 Mon Sep 17 00:00:00 2001 From: Beat Buesser Date: Mon, 14 Jun 2021 23:24:45 +0100 Subject: [PATCH 1/4] Add support for multiple layers in Feature Adversaries Signed-off-by: Beat Buesser --- .../feature_adversaries_pytorch.py | 28 ++++++++------- .../feature_adversaries_tensorflow.py | 34 +++++++++++-------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py b/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py index f8fcd76188..85d8d9ff38 100644 --- a/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py +++ b/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py @@ -47,9 +47,9 @@ class FeatureAdversariesPyTorch(EvasionAttack): """ attack_params = EvasionAttack.attack_params + [ + "delta", "optimizer", "optimizer_kwargs", - "delta", "lambda_", "layer", "max_iter", @@ -64,11 +64,11 @@ class FeatureAdversariesPyTorch(EvasionAttack): def __init__( self, estimator: "PYTORCH_ESTIMATOR_TYPE", + delta: float, optimizer: Optional["Optimizer"] = None, optimizer_kwargs: Optional[dict] = None, - delta: float = 15 / 255, lambda_: float = 0.0, - layer: Optional[Union[int, str]] = -1, + layer: Union[int, str] = -1, max_iter: int = 100, batch_size: int = 32, step_size: Optional[Union[int, float]] = None, @@ -79,12 +79,12 @@ def __init__( Create a :class:`.FeatureAdversariesPyTorch` instance. :param estimator: A trained estimator. + :param delta: The maximum deviation between source and guide images. :param optimizer: Optimizer applied to problem constrained only by clip values if defined, if None the Projected Gradient Descent (PGD) optimizer is used. :param optimizer_kwargs: Additional optimizer arguments. - :param delta: The maximum deviation between source and guide images. :param lambda_: Regularization parameter of the L-inf soft constraint. - :param layer: Index of the representation layer. + :param layer: Index or tuple of indices of the representation layer(s). :param max_iter: The maximum number of iterations. :param batch_size: Batch size. :param step_size: Step size for PGD optimizer. @@ -97,7 +97,7 @@ def __init__( self._optimizer_kwargs = {} if optimizer_kwargs is None else optimizer_kwargs self.delta = delta self.lambda_ = lambda_ - self.layer = layer + self.layer = layer if isinstance(layer, tuple) else (layer,) self.batch_size = batch_size self.max_iter = max_iter self.step_size = step_size @@ -116,14 +116,16 @@ def _generate_batch(self, x: "torch.Tensor", y: "torch.Tensor") -> "torch.Tensor import torch # lgtm [py/repeated-import] def loss_fn(source_orig, source_adv, guide): - adv_representation = self.estimator.get_activations(source_adv, self.layer, self.batch_size, True) - guide_representation = self.estimator.get_activations(guide, self.layer, self.batch_size, True) + representation_loss = torch.tensor([0.0]).to(self.estimator.device) + for layer_i in self.layer: + adv_representation = self.estimator.get_activations(source_adv, layer_i, self.batch_size, True) + guide_representation = self.estimator.get_activations(guide, layer_i, self.batch_size, True) - dim = tuple(range(1, len(source_adv.shape))) - soft_constraint = torch.amax(torch.abs(source_adv - source_orig), dim=dim) + dim = tuple(range(1, len(source_adv.shape))) + soft_constraint = torch.amax(torch.abs(source_adv - source_orig), dim=dim) - dim = tuple(range(1, len(adv_representation.shape))) - representation_loss = torch.sum(torch.square(adv_representation - guide_representation), dim=dim) + dim = tuple(range(1, len(adv_representation.shape))) + representation_loss += torch.sum(torch.square(adv_representation - guide_representation), dim=dim) loss = torch.mean(representation_loss + self.lambda_ * soft_constraint) return loss @@ -221,7 +223,7 @@ def _check_params(self) -> None: if self.lambda_ < 0.0: raise ValueError("The regularization parameter `lambda_` has to be non-negative.") - if not isinstance(self.layer, int) and not isinstance(self.layer, str): + if not isinstance(self.layer, int) and not isinstance(self.layer, str) and not isinstance(self.layer, tuple): raise ValueError("The value of the representation layer must be integer or string.") if not isinstance(self.max_iter, int): diff --git a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py index 69e1573fda..6f2d722b78 100644 --- a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py +++ b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py @@ -21,7 +21,7 @@ | Paper link: https://arxiv.org/abs/1511.05122 """ import logging -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Optional, Tuple, Union import numpy as np from tqdm.auto import trange @@ -47,9 +47,9 @@ class FeatureAdversariesTensorFlowV2(EvasionAttack): """ attack_params = EvasionAttack.attack_params + [ + "delta", "optimizer", "optimizer_kwargs", - "delta", "lambda_", "layer", "max_iter", @@ -64,11 +64,11 @@ class FeatureAdversariesTensorFlowV2(EvasionAttack): def __init__( self, estimator: "TENSORFLOWV2_ESTIMATOR_TYPE", + delta: float, optimizer: Optional["Optimizer"] = None, optimizer_kwargs: Optional[dict] = None, - delta: float = 15 / 255, lambda_: float = 0.0, - layer: Optional[Union[int, str]] = -1, + layer: Union[int, str, Tuple[int, ...], Tuple[str, ...]] = -1, max_iter: int = 100, batch_size: int = 32, step_size: Optional[Union[int, float]] = None, @@ -79,12 +79,12 @@ def __init__( Create a :class:`.FeatureAdversariesTensorFlowV2` instance. :param estimator: A trained estimator. + :param delta: The maximum deviation between source and guide images. :param optimizer: Optimizer applied to problem constrained only by clip values if defined, if None the Projected Gradient Descent (PGD) optimizer is used. :param optimizer_kwargs: Additional optimizer arguments. - :param delta: The maximum deviation between source and guide images. :param lambda_: Regularization parameter of the L-inf soft constraint. - :param layer: Index of the representation layer. + :param layer: Index or tuple of indices of the representation layer(s). :param max_iter: The maximum number of iterations. :param batch_size: Batch size. :param step_size: Step size for PGD optimizer. @@ -93,11 +93,11 @@ def __init__( """ super().__init__(estimator=estimator) + self.delta = delta self.optimizer = optimizer self._optimizer_kwargs = {} if optimizer_kwargs is None else optimizer_kwargs - self.delta = delta self.lambda_ = lambda_ - self.layer = layer + self.layer = layer if isinstance(layer, tuple) else (layer,) self.batch_size = batch_size self.max_iter = max_iter self.step_size = step_size @@ -116,14 +116,18 @@ def _generate_batch(self, x: "tf.Tensor", y: "tf.Tensor") -> "tf.Tensor": import tensorflow as tf # lgtm [py/repeated-import] def loss_fn(source_orig, source_adv, guide): - adv_representation = self.estimator.get_activations(source_adv, self.layer, self.batch_size, True) - guide_representation = self.estimator.get_activations(guide, self.layer, self.batch_size, True) + representation_loss = tf.constant([0.0], shape=(1,), dtype=tf.float32) + for layer_i in self.layer: + adv_representation = self.estimator.get_activations(source_adv, layer_i, self.batch_size, True) + guide_representation = self.estimator.get_activations(guide, layer_i, self.batch_size, True) - axis = tuple(range(1, len(source_adv.shape))) - soft_constraint = tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis) + axis = tuple(range(1, len(source_adv.shape))) + soft_constraint = tf.cast( + tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis), dtype=tf.float32 + ) - axis = tuple(range(1, len(adv_representation.shape))) - representation_loss = tf.reduce_sum(tf.square(adv_representation - guide_representation), axis=axis) + axis = tuple(range(1, len(adv_representation.shape))) + representation_loss += tf.reduce_sum(tf.square(adv_representation - guide_representation), axis=axis) loss = tf.math.reduce_mean(representation_loss + self.lambda_ * soft_constraint) return loss @@ -218,7 +222,7 @@ def _check_params(self) -> None: if self.lambda_ < 0.0: raise ValueError("The regularization parameter `lambda_` has to be non-negative.") - if not isinstance(self.layer, int) and not isinstance(self.layer, str): + if not isinstance(self.layer, int) and not isinstance(self.layer, str) and not isinstance(self.layer, tuple): raise ValueError("The value of the representation layer must be integer or string.") if not isinstance(self.max_iter, int): From d6430ce628b26e0223a431aa38bb053f92e3042f Mon Sep 17 00:00:00 2001 From: Beat Buesser Date: Mon, 14 Jun 2021 23:49:34 +0100 Subject: [PATCH 2/4] Fix style checks Signed-off-by: Beat Buesser --- .../feature_adversaries/feature_adversaries_tensorflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py index 6f2d722b78..cbb7ebdeea 100644 --- a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py +++ b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py @@ -123,7 +123,7 @@ def loss_fn(source_orig, source_adv, guide): axis = tuple(range(1, len(source_adv.shape))) soft_constraint = tf.cast( - tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis), dtype=tf.float32 + tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis), tf.float32 ) axis = tuple(range(1, len(adv_representation.shape))) From 2b7184863ad0ae2e11f407876004310aa038725e Mon Sep 17 00:00:00 2001 From: Beat Buesser Date: Tue, 15 Jun 2021 00:44:09 +0100 Subject: [PATCH 3/4] Update Black format Signed-off-by: Beat Buesser --- .../feature_adversaries/feature_adversaries_tensorflow.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py index cbb7ebdeea..2288bc7374 100644 --- a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py +++ b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py @@ -122,9 +122,7 @@ def loss_fn(source_orig, source_adv, guide): guide_representation = self.estimator.get_activations(guide, layer_i, self.batch_size, True) axis = tuple(range(1, len(source_adv.shape))) - soft_constraint = tf.cast( - tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis), tf.float32 - ) + soft_constraint = tf.cast(tf.math.reduce_max(tf.abs(source_adv - source_orig), axis=axis), tf.float32) axis = tuple(range(1, len(adv_representation.shape))) representation_loss += tf.reduce_sum(tf.square(adv_representation - guide_representation), axis=axis) From 0b69e1cb54794b6d4368db3ca8236185c8cf91ed Mon Sep 17 00:00:00 2001 From: Beat Buesser Date: Tue, 15 Jun 2021 10:32:20 +0100 Subject: [PATCH 4/4] Update Feature Adversaries tests Signed-off-by: Beat Buesser --- .../feature_adversaries_pytorch.py | 2 +- .../feature_adversaries_tensorflow.py | 2 +- .../test_feature_adversaries.py | 0 .../test_feature_adversaries_pytorch.py | 13 ++++++++----- .../test_feature_adversaries_tensorflow.py | 12 +++++++----- 5 files changed, 17 insertions(+), 12 deletions(-) rename tests/attacks/evasion/{feature_adversaries.py => feature_adversaries}/test_feature_adversaries.py (100%) rename tests/attacks/evasion/{feature_adversaries.py => feature_adversaries}/test_feature_adversaries_pytorch.py (89%) rename tests/attacks/evasion/{feature_adversaries.py => feature_adversaries}/test_feature_adversaries_tensorflow.py (90%) diff --git a/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py b/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py index 85d8d9ff38..4873f5101d 100644 --- a/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py +++ b/art/attacks/evasion/feature_adversaries/feature_adversaries_pytorch.py @@ -116,7 +116,7 @@ def _generate_batch(self, x: "torch.Tensor", y: "torch.Tensor") -> "torch.Tensor import torch # lgtm [py/repeated-import] def loss_fn(source_orig, source_adv, guide): - representation_loss = torch.tensor([0.0]).to(self.estimator.device) + representation_loss = torch.zeros(size=(source_orig.shape[0],)).to(self.estimator.device) for layer_i in self.layer: adv_representation = self.estimator.get_activations(source_adv, layer_i, self.batch_size, True) guide_representation = self.estimator.get_activations(guide, layer_i, self.batch_size, True) diff --git a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py index 2288bc7374..65a1646fad 100644 --- a/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py +++ b/art/attacks/evasion/feature_adversaries/feature_adversaries_tensorflow.py @@ -116,7 +116,7 @@ def _generate_batch(self, x: "tf.Tensor", y: "tf.Tensor") -> "tf.Tensor": import tensorflow as tf # lgtm [py/repeated-import] def loss_fn(source_orig, source_adv, guide): - representation_loss = tf.constant([0.0], shape=(1,), dtype=tf.float32) + representation_loss = tf.zeros(shape=(source_orig.shape[0],), dtype=tf.float32) for layer_i in self.layer: adv_representation = self.estimator.get_activations(source_adv, layer_i, self.batch_size, True) guide_representation = self.estimator.get_activations(guide, layer_i, self.batch_size, True) diff --git a/tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries.py b/tests/attacks/evasion/feature_adversaries/test_feature_adversaries.py similarity index 100% rename from tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries.py rename to tests/attacks/evasion/feature_adversaries/test_feature_adversaries.py diff --git a/tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_pytorch.py b/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_pytorch.py similarity index 89% rename from tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_pytorch.py rename to tests/attacks/evasion/feature_adversaries/test_feature_adversaries_pytorch.py index d34e3292f7..5c2d95d30b 100644 --- a/tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_pytorch.py +++ b/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_pytorch.py @@ -42,13 +42,14 @@ def test_images_pgd(art_warning, fix_get_mnist_subset, image_dl_estimator_for_at (x_train_mnist, y_train_mnist, x_test_mnist, y_test_mnist) = fix_get_mnist_subset classifier = image_dl_estimator_for_attack(FeatureAdversariesPyTorch, functional=True) + print("classifier", classifier) attack = FeatureAdversariesPyTorch( - classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=1, random_start=False + classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=2, random_start=False ) x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3]) assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01) - assert np.mean(x_train_mnist_adv) != 0 + assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0 except ARTTestException as e: art_warning(e) @@ -67,14 +68,16 @@ def test_images_unconstrained_adam(art_warning, fix_get_mnist_subset, image_dl_e ) x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3]) assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01) - assert np.mean(x_train_mnist_adv) != 0 + assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0 except ARTTestException as e: art_warning(e) -@pytest.mark.skip_framework("tensorflow", "keras", "kerastf", "mxnet", "non_dl_frameworks") +@pytest.mark.framework_agnostic def test_classifier_type_check_fail(art_warning): try: - backend_test_classifier_type_check_fail(FeatureAdversariesPyTorch, [BaseEstimator, NeuralNetworkMixin]) + backend_test_classifier_type_check_fail( + FeatureAdversariesPyTorch, [BaseEstimator, NeuralNetworkMixin], delta=1.0 + ) except ARTTestException as e: art_warning(e) diff --git a/tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_tensorflow.py b/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py similarity index 90% rename from tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_tensorflow.py rename to tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py index f94e9fcc8f..9a1e4b880a 100644 --- a/tests/attacks/evasion/feature_adversaries.py/test_feature_adversaries_tensorflow.py +++ b/tests/attacks/evasion/feature_adversaries/test_feature_adversaries_tensorflow.py @@ -44,11 +44,11 @@ def test_images_pgd(art_warning, fix_get_mnist_subset, image_dl_estimator_for_at classifier = image_dl_estimator_for_attack(FeatureAdversariesTensorFlowV2) attack = FeatureAdversariesTensorFlowV2( - classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=1, random_start=False + classifier, delta=1.0, layer=1, batch_size=32, step_size=0.05, max_iter=2, random_start=False ) x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3]) assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01) - assert np.mean(x_train_mnist_adv) != 0 + assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0 except ARTTestException as e: art_warning(e) @@ -67,14 +67,16 @@ def test_images_unconstrained_adam(art_warning, fix_get_mnist_subset, image_dl_e ) x_train_mnist_adv = attack.generate(x=x_train_mnist[0:3], y=x_test_mnist[0:3]) assert np.mean(x_train_mnist[0:3]) == pytest.approx(0.13015705, 0.01) - assert np.mean(x_train_mnist_adv) != 0 + assert np.mean(np.abs(x_train_mnist_adv - x_train_mnist[0:3])) != 0.0 except ARTTestException as e: art_warning(e) -@pytest.mark.skip_framework("tensorflow1", "keras", "kerastf", "mxnet", "non_dl_frameworks", "pytorch") +@pytest.mark.framework_agnostic def test_classifier_type_check_fail(art_warning): try: - backend_test_classifier_type_check_fail(FeatureAdversariesTensorFlowV2, [BaseEstimator, NeuralNetworkMixin]) + backend_test_classifier_type_check_fail( + FeatureAdversariesTensorFlowV2, [BaseEstimator, NeuralNetworkMixin], delta=1.0 + ) except ARTTestException as e: art_warning(e)