From 795c8079924f7f843440d3074ac07b7308807f62 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 31 Jan 2020 18:17:24 +0900 Subject: [PATCH 01/91] updated --- art/attacks/__init__.py | 1 + art/attacks/evasion/simba_pixel.py | 138 ++++++++++++++++++ art/attacks/evasion/universal_perturbation.py | 3 +- examples/cifar10_cnn_simba.py | 98 +++++++++++++ 4 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 art/attacks/evasion/simba_pixel.py create mode 100644 examples/cifar10_cnn_simba.py diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index 66b699174e..d5c38c05ff 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -8,6 +8,7 @@ from art.attacks.evasion.carlini import CarliniL2Method, CarliniLInfMethod from art.attacks.evasion.decision_tree_attack import DecisionTreeAttack from art.attacks.evasion.deepfool import DeepFool +from art.attacks.evasion.simba_pixel import SimBA_pixel from art.attacks.evasion.elastic_net import ElasticNet from art.attacks.evasion.fast_gradient import FastGradientMethod from art.attacks.evasion.hclu import HighConfidenceLowUncertainty diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py new file mode 100644 index 0000000000..d6a703bf29 --- /dev/null +++ b/art/attacks/evasion/simba_pixel.py @@ -0,0 +1,138 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box attack `simba`. + +| Paper link: https://arxiv.org/abs/1905.07121 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np + +from art.config import ART_NUMPY_DTYPE +from art.classifiers.classifier import ClassifierGradients +from art.attacks.attack import EvasionAttack +from art.utils import compute_success,random_sphere + +logger = logging.getLogger(__name__) + + +class SimBA_pixel(EvasionAttack): + """ + Implementation of the attack from Moosavi-Dezfooli et al. (2015). + + | Paper link: https://arxiv.org/abs/1511.04599 + """ + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'batch_size'] + + def __init__(self, classifier, max_iter=3000, epsilon=0.1, batch_size=1): + """ + Create a Simba (pixel) attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + super(SimBA_pixel, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'max_iter': max_iter, 'epsilon': epsilon, 'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + preds = self.classifier.predict(x, batch_size=self.batch_size) + original_label = np.argmax(preds, axis=1)[0] + current_label = original_label + last_prob = preds.reshape(-1)[original_label] + + n_dims = np.prod(x.shape) + + clip_min, clip_max = self.classifier.clip_values + + nb_iter = 0 + while original_label == current_label and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[np.random.choice(range(n_dims))] = self.epsilon + preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + #diff = random_sphere(nb_points=1, nb_dims=n_dims, radius=epsilon, norm=2) + #preds = self.classifier.predict(x + diff, batch_size=self.batch_size) + left_prob = preds.reshape(-1)[original_label] + if left_prob < last_prob: + x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) + #x = x - diff + last_prob = left_prob + current_label = np.argmax(preds, axis=1)[0] + else: + preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + right_prob = preds.reshape(-1)[original_label] + if right_prob < last_prob: + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(preds, axis=1)[0] + + nb_iter = nb_iter + 1 + + return x, nb_iter < self.max_iter + + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + # Save attack-specific parameters + super(SimBA_pixel, self).set_params(**kwargs) + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + return True diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index abe04277e4..153643f5d9 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -45,6 +45,7 @@ class UniversalPerturbation(EvasionAttack): attacks_dict = {'carlini': 'art.attacks.evasion.carlini.CarliniL2Method', 'carlini_inf': 'art.attacks.evasion.carlini.CarliniLInfMethod', 'deepfool': 'art.attacks.evasion.deepfool.DeepFool', + 'simba_px': 'art.attacks.evasion.simba_pixel.SimBA_pixel', 'ead': 'art.attacks.evasion.elastic_net.ElasticNet', 'fgsm': 'art.attacks.evasion.fast_gradient.FastGradientMethod', 'bim': 'art.attacks.evasion.iterative_method.BasicIterativeMethod', @@ -61,7 +62,7 @@ def __init__(self, classifier, attacker='deepfool', attacker_params=None, delta= :param classifier: A trained classifier. :type classifier: :class:`.Classifier` :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'carlini', 'carlini_inf', - 'deepfool', 'fgsm', 'bim', 'pgd', 'margin', 'ead', 'newtonfool', 'jsma', 'vat'. + 'deepfool', 'simba_px', 'fgsm', 'bim', 'pgd', 'margin', 'ead', 'newtonfool', 'jsma', 'vat'. :type attacker: `str` :param attacker_params: Parameters specific to the adversarial attack. If this parameter is not specified, the default parameters of the chosen attack will be used. diff --git a/examples/cifar10_cnn_simba.py b/examples/cifar10_cnn_simba.py new file mode 100644 index 0000000000..23ca5264b9 --- /dev/null +++ b/examples/cifar10_cnn_simba.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +Trains a convolutional neural network on the CIFAR-10 dataset, then generated adversarial images using the +DeepFool attack and retrains the network on the training set augmented with the adversarial images. +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +from keras.models import Sequential +from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout +import numpy as np + +from art.attacks import DeepFool, SimBA_pixel +from art.classifiers import KerasClassifier +from art.utils import load_dataset + +import matplotlib.pyplot as plt + +# Configure a logger to capture ART outputs; these are printed in console and the level of detail is set to INFO +logger = logging.getLogger() +logger.setLevel(logging.INFO) +handler = logging.StreamHandler() +formatter = logging.Formatter('[%(levelname)s] %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +# Read CIFAR10 dataset +(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset(str('cifar10')) +x_train, y_train = x_train[:5000], y_train[:5000] +x_test, y_test = x_test[:500], y_test[:500] +im_shape = x_train[0].shape + +# Create Keras convolutional neural network - basic architecture from Keras examples +# Source here: https://github.com/keras-team/keras/blob/master/examples/cifar10_cnn.py +model = Sequential() +model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:])) +model.add(Activation('relu')) +model.add(Conv2D(32, (3, 3))) +model.add(Activation('relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Dropout(0.25)) + +model.add(Conv2D(64, (3, 3), padding='same')) +model.add(Activation('relu')) +model.add(Conv2D(64, (3, 3))) +model.add(Activation('relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Dropout(0.25)) + +model.add(Flatten()) +model.add(Dense(512)) +model.add(Activation('relu')) +model.add(Dropout(0.5)) +model.add(Dense(10)) +model.add(Activation('softmax')) + +model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + +# Create classifier wrapper +classifier = KerasClassifier(model=model, clip_values=(min_, max_)) +classifier.fit(x_train, y_train, nb_epochs=3, batch_size=128) + +# Evaluate the classifier on the train samples +preds = np.argmax(classifier.predict(x_train), axis=1) +acc = np.sum(preds == np.argmax(y_train, axis=1)) / y_train.shape[0] +logger.info('Accuracy on train samples: %.2f%%', (acc * 100)) + +idx = 1 + +# Craft adversarial samples with SimBA for single image +logger.info('Create SimBA pixel attack') +adv_crafter = SimBA_pixel(classifier, epsilon=0.01) +logger.info('Craft attack on training examples') +x_train_adv, convergent = adv_crafter.generate(x_train[idx].reshape(1,32,32,3)) +logger.info('Craft attack test examples') +preds_adv = np.argmax(classifier.predict(x_train_adv), axis=1) +print(preds[idx],preds_adv[0],convergent) + +label = [ + 'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', + 'ship', 'truck' +] + +plt.subplot(1, 3, 1) +plt.imshow(x_train[idx]) +plt.title(label[preds[idx]]) + +plt.subplot(1, 3, 2) +plt.imshow(x_train_adv[0] - x_train[idx]) +plt.title("perturbation") + +plt.subplot(1, 3, 3) +plt.imshow(x_train_adv[0]) +plt.title(label[preds_adv[0]]) + +plt.show() + From ab3b2ab4c5b64b862fb3cbfc3655b69fdc7ff9b9 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sat, 1 Feb 2020 01:14:01 +0900 Subject: [PATCH 02/91] updated --- art/attacks/evasion/simba_pixel.py | 18 ++++--- art/attacks/evasion/universal_perturbation.py | 6 ++- examples/cifar10_cnn_simba.py | 52 +++++++++++++++---- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index d6a703bf29..9dbfb69ae5 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -29,7 +29,7 @@ from art.config import ART_NUMPY_DTYPE from art.classifiers.classifier import ClassifierGradients from art.attacks.attack import EvasionAttack -from art.utils import compute_success,random_sphere +from art.utils import compute_success logger = logging.getLogger(__name__) @@ -78,7 +78,9 @@ def generate(self, x, y=None, **kwargs): """ x = x.astype(ART_NUMPY_DTYPE) preds = self.classifier.predict(x, batch_size=self.batch_size) - original_label = np.argmax(preds, axis=1)[0] + if y is None: + y = np.argmax(preds, axis=1)[0] + original_label = y current_label = original_label last_prob = preds.reshape(-1)[original_label] @@ -91,12 +93,9 @@ def generate(self, x, y=None, **kwargs): diff = np.zeros(n_dims) diff[np.random.choice(range(n_dims))] = self.epsilon preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - #diff = random_sphere(nb_points=1, nb_dims=n_dims, radius=epsilon, norm=2) - #preds = self.classifier.predict(x + diff, batch_size=self.batch_size) left_prob = preds.reshape(-1)[original_label] if left_prob < last_prob: x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) - #x = x - diff last_prob = left_prob current_label = np.argmax(preds, axis=1)[0] else: @@ -108,8 +107,13 @@ def generate(self, x, y=None, **kwargs): current_label = np.argmax(preds, axis=1)[0] nb_iter = nb_iter + 1 - - return x, nb_iter < self.max_iter + + if nb_iter < self.max_iter: + logger.info('SimBA (pixel) attack succeed') + else: + logger.info('SimBA (pixel) attack failed') + + return x def set_params(self, **kwargs): diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index 153643f5d9..5178ec267d 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -131,7 +131,11 @@ def generate(self, x, y=None, **kwargs): if current_label == original_label: # Compute adversarial perturbation - adv_xi = attacker.generate(x_i + noise) + if y is None: + adv_xi = attacker.generate(x_i + noise) + else: + adv_xi = attacker.generate(x_i + noise, y=original_label) + new_label = np.argmax(self.classifier.predict(adv_xi)[0]) # If the class has changed, update v diff --git a/examples/cifar10_cnn_simba.py b/examples/cifar10_cnn_simba.py index 23ca5264b9..652cad2f36 100644 --- a/examples/cifar10_cnn_simba.py +++ b/examples/cifar10_cnn_simba.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ Trains a convolutional neural network on the CIFAR-10 dataset, then generated adversarial images using the -DeepFool attack and retrains the network on the training set augmented with the adversarial images. +SimBA (pixel) attack and retrains the network on the training set augmented with the adversarial images. """ from __future__ import absolute_import, division, print_function, unicode_literals @@ -11,9 +11,9 @@ from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout import numpy as np -from art.attacks import DeepFool, SimBA_pixel +from art.attacks import DeepFool, SimBA_pixel, UniversalPerturbation from art.classifiers import KerasClassifier -from art.utils import load_dataset +from art.utils import load_dataset, random_sphere import matplotlib.pyplot as plt @@ -69,14 +69,48 @@ idx = 1 # Craft adversarial samples with SimBA for single image -logger.info('Create SimBA pixel attack') -adv_crafter = SimBA_pixel(classifier, epsilon=0.01) -logger.info('Craft attack on training examples') -x_train_adv, convergent = adv_crafter.generate(x_train[idx].reshape(1,32,32,3)) -logger.info('Craft attack test examples') +logger.info('Create SimBA (pixel) attack') +adv_crafter = SimBA_pixel(classifier, epsilon=0.05) +logger.info('Craft attack on a training example') +x_train_adv = adv_crafter.generate(x_train[idx].reshape(1,32,32,3)) +logger.info('Craft attack the training example') preds_adv = np.argmax(classifier.predict(x_train_adv), axis=1) -print(preds[idx],preds_adv[0],convergent) +print(preds[idx],preds_adv[0]) +x_train, y_train = x_train[:100], y_train[:100] +preds = np.argmax(classifier.predict(x_train), axis=1) + +# Craft adversarial samples with universal pertubation and SimBA +attack_params = {"attacker": "simba_px", "attacker_params": {"max_iter": 3000, "epsilon": 0.05}, "delta": 0.01, "max_iter": 1, "eps": 6, "norm": 2} +adv_crafter_simba = UniversalPerturbation(classifier) +adv_crafter_simba.set_params(**attack_params) +x_train_adv_simba = adv_crafter_simba.generate(x_train, y=1, **attack_params) +norm2_simba = np.linalg.norm(adv_crafter_simba.noise.reshape(-1), ord=2) +# compute fooling rate +preds_adv = np.argmax(classifier.predict(x_train_adv_simba), axis=1) +acc = np.sum(preds != preds_adv) / y_train.shape[0] +logger.info('Fooling rate on SimBA universal adversarial examples: %.2f%%', (acc * 100)) + +# Craft adversarial samples with random universal pertubation +x_train_adv_random = x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=6, norm=2).reshape(1,32,32,3) +preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) +acc = np.sum(preds != preds_adv) / y_train.shape[0] +logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) + +# Craft adversarial samples with universal pertubation and FGSM +attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": 6, "norm": 2} +adv_crafter_fgsm = UniversalPerturbation(classifier) +adv_crafter_fgsm.set_params(**attack_params) +x_train_adv_fgsm = adv_crafter_fgsm.generate(x_train, **attack_params) +np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2) + +preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) +acc = np.sum(preds != preds_adv) / y_train.shape[0] +logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) + + + +# plot label = [ 'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck' From 999e034a290dd81a5c184691ae9e10a895f3e327 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sat, 1 Feb 2020 22:07:37 +0900 Subject: [PATCH 03/91] updated --- art/attacks/__init__.py | 1 + art/attacks/evasion/simba_pixel.py | 7 +- art/attacks/evasion/universal_simba_pixel.py | 148 +++++++++++++++++++ examples/cacl_norm.py | 32 ++++ examples/cifar10_cnn_simba.py | 23 +-- examples/cifar10_cnn_universal_simba.py | 99 +++++++++++++ 6 files changed, 294 insertions(+), 16 deletions(-) create mode 100644 art/attacks/evasion/universal_simba_pixel.py create mode 100644 examples/cacl_norm.py create mode 100644 examples/cifar10_cnn_universal_simba.py diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index d5c38c05ff..e4172d7402 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -9,6 +9,7 @@ from art.attacks.evasion.decision_tree_attack import DecisionTreeAttack from art.attacks.evasion.deepfool import DeepFool from art.attacks.evasion.simba_pixel import SimBA_pixel +from art.attacks.evasion.universal_simba_pixel import Universal_SimBA_pixel from art.attacks.evasion.elastic_net import ElasticNet from art.attacks.evasion.fast_gradient import FastGradientMethod from art.attacks.evasion.hclu import HighConfidenceLowUncertainty diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 9dbfb69ae5..c26e36ef3f 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -35,16 +35,11 @@ class SimBA_pixel(EvasionAttack): - """ - Implementation of the attack from Moosavi-Dezfooli et al. (2015). - - | Paper link: https://arxiv.org/abs/1511.04599 - """ attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'batch_size'] def __init__(self, classifier, max_iter=3000, epsilon=0.1, batch_size=1): """ - Create a Simba (pixel) attack instance. + Create a SimBA (pixel) attack instance. :param classifier: A trained classifier. :type classifier: :class:`.Classifier` diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py new file mode 100644 index 0000000000..98b755e086 --- /dev/null +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -0,0 +1,148 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box attack `simba`. + +| Paper link: https://arxiv.org/abs/1905.07121 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np + +from art.config import ART_NUMPY_DTYPE +from art.classifiers.classifier import ClassifierGradients +from art.attacks.attack import EvasionAttack +from art.utils import compute_success + +logger = logging.getLogger(__name__) + + +class Universal_SimBA_pixel(EvasionAttack): + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta' ,'batch_size'] + + def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, batch_size=1): + """ + Create a universal SimBA (pixel) attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + super(Universal_SimBA_pixel, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta, 'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + nb_instances = x.shape[0] + preds = self.classifier.predict(x, batch_size=self.batch_size) + if y is None: + y = np.argmax(preds, axis=1) + original_labels = y + current_labels = original_labels + last_probs = preds[(range(nb_instances),original_labels)] + + n_dims = np.prod(x[0].shape) + + clip_min, clip_max = self.classifier.clip_values + + fooling_rate = 0.0 + nb_iter = 0 + while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[np.random.choice(range(n_dims))] = self.epsilon + preds = self.classifier.predict(np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + left_probs = preds[(range(nb_instances),original_labels)] + if np.sum(left_probs - last_probs) < 0.0: + x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + last_probs = left_probs + current_labels = np.argmax(preds, axis=1) + else: + preds = self.classifier.predict(np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + right_probs = preds[(range(nb_instances),original_labels)] + if np.sum(right_probs - last_probs) < 0.0: + x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + last_probs = right_probs + current_labels = np.argmax(preds, axis=1) + + # Compute the error rate + fooling_rate = np.sum(original_labels != current_labels) / nb_instances + + nb_iter = nb_iter + 1 + + if nb_iter % 50 == 0: + logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%%', nb_iter, 100 * fooling_rate) + + logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) + return x + + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + # Save attack-specific parameters + super(Universal_SimBA_pixel, self).set_params(**kwargs) + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: + raise ValueError("The desired accuracy must be in the range [0, 1].") + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + return True diff --git a/examples/cacl_norm.py b/examples/cacl_norm.py new file mode 100644 index 0000000000..4746480c2c --- /dev/null +++ b/examples/cacl_norm.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" +Trains a convolutional neural network on the CIFAR-10 dataset, then generated adversarial images using the +SimBA (pixel) attack and retrains the network on the training set augmented with the adversarial images. +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np +from art.utils import load_dataset + +#import matplotlib.pyplot as plt + +# Configure a logger to capture ART outputs; these are printed in console and the level of detail is set to INFO +logger = logging.getLogger() +logger.setLevel(logging.INFO) +handler = logging.StreamHandler() +formatter = logging.Formatter('[%(levelname)s] %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +# Read CIFAR10 dataset +(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset(str('cifar10')) +x_train, y_train = x_train[:5000], y_train[:5000] +x_test, y_test = x_test[:500], y_test[:500] +im_shape = x_train[0].shape + +norm = 0.0 +for i in range(5000): + norm = norm + np.linalg.norm(x_train[i].reshape(-1), ord=2) +print(norm / 5000) \ No newline at end of file diff --git a/examples/cifar10_cnn_simba.py b/examples/cifar10_cnn_simba.py index 652cad2f36..354db783c2 100644 --- a/examples/cifar10_cnn_simba.py +++ b/examples/cifar10_cnn_simba.py @@ -11,11 +11,11 @@ from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout import numpy as np -from art.attacks import DeepFool, SimBA_pixel, UniversalPerturbation +from art.attacks import DeepFool, SimBA_pixel, UniversalPerturbation, Universal_SimBA_pixel from art.classifiers import KerasClassifier from art.utils import load_dataset, random_sphere -import matplotlib.pyplot as plt +#import matplotlib.pyplot as plt # Configure a logger to capture ART outputs; these are printed in console and the level of detail is set to INFO logger = logging.getLogger() @@ -66,8 +66,9 @@ acc = np.sum(preds == np.argmax(y_train, axis=1)) / y_train.shape[0] logger.info('Accuracy on train samples: %.2f%%', (acc * 100)) +""" +### Single Attack ########### idx = 1 - # Craft adversarial samples with SimBA for single image logger.info('Create SimBA (pixel) attack') adv_crafter = SimBA_pixel(classifier, epsilon=0.05) @@ -76,12 +77,14 @@ logger.info('Craft attack the training example') preds_adv = np.argmax(classifier.predict(x_train_adv), axis=1) print(preds[idx],preds_adv[0]) +""" +### Universal Attack ########## x_train, y_train = x_train[:100], y_train[:100] preds = np.argmax(classifier.predict(x_train), axis=1) -# Craft adversarial samples with universal pertubation and SimBA -attack_params = {"attacker": "simba_px", "attacker_params": {"max_iter": 3000, "epsilon": 0.05}, "delta": 0.01, "max_iter": 1, "eps": 6, "norm": 2} +# Craft adversarial samples with universal pertubation based on SimBA +attack_params = {"attacker": "simba_px", "attacker_params": {"max_iter": 3000, "epsilon": 0.02}, "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} adv_crafter_simba = UniversalPerturbation(classifier) adv_crafter_simba.set_params(**attack_params) x_train_adv_simba = adv_crafter_simba.generate(x_train, y=1, **attack_params) @@ -92,24 +95,24 @@ logger.info('Fooling rate on SimBA universal adversarial examples: %.2f%%', (acc * 100)) # Craft adversarial samples with random universal pertubation -x_train_adv_random = x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=6, norm=2).reshape(1,32,32,3) +x_train_adv_random = x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2_simba, norm=2).reshape(1,32,32,3) preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) # Craft adversarial samples with universal pertubation and FGSM -attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": 6, "norm": 2} +attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} adv_crafter_fgsm = UniversalPerturbation(classifier) adv_crafter_fgsm.set_params(**attack_params) x_train_adv_fgsm = adv_crafter_fgsm.generate(x_train, **attack_params) np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2) - +# compute fooling rate preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) - +""" # plot label = [ 'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', @@ -129,4 +132,4 @@ plt.title(label[preds_adv[0]]) plt.show() - +""" diff --git a/examples/cifar10_cnn_universal_simba.py b/examples/cifar10_cnn_universal_simba.py new file mode 100644 index 0000000000..9cb7a3cb1b --- /dev/null +++ b/examples/cifar10_cnn_universal_simba.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +""" +Trains a convolutional neural network on the CIFAR-10 dataset, then generated adversarial images using the +SimBA (pixel) attack and retrains the network on the training set augmented with the adversarial images. +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +from keras.models import Sequential +from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout +import numpy as np + +from art.attacks import DeepFool, SimBA_pixel, UniversalPerturbation, Universal_SimBA_pixel +from art.classifiers import KerasClassifier +from art.utils import load_dataset, random_sphere + +#import matplotlib.pyplot as plt + +# Configure a logger to capture ART outputs; these are printed in console and the level of detail is set to INFO +logger = logging.getLogger() +logger.setLevel(logging.INFO) +handler = logging.StreamHandler() +formatter = logging.Formatter('[%(levelname)s] %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +# Read CIFAR10 dataset +(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset(str('cifar10')) +x_train, y_train = x_train[:5000], y_train[:5000] +x_test, y_test = x_test[:500], y_test[:500] +im_shape = x_train[0].shape + +# Create Keras convolutional neural network - basic architecture from Keras examples +# Source here: https://github.com/keras-team/keras/blob/master/examples/cifar10_cnn.py +model = Sequential() +model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:])) +model.add(Activation('relu')) +model.add(Conv2D(32, (3, 3))) +model.add(Activation('relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Dropout(0.25)) + +model.add(Conv2D(64, (3, 3), padding='same')) +model.add(Activation('relu')) +model.add(Conv2D(64, (3, 3))) +model.add(Activation('relu')) +model.add(MaxPooling2D(pool_size=(2, 2))) +model.add(Dropout(0.25)) + +model.add(Flatten()) +model.add(Dense(512)) +model.add(Activation('relu')) +model.add(Dropout(0.5)) +model.add(Dense(10)) +model.add(Activation('softmax')) + +model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) + +# Create classifier wrapper +classifier = KerasClassifier(model=model, clip_values=(min_, max_)) +classifier.fit(x_train, y_train, nb_epochs=3, batch_size=128) + +# Evaluate the classifier on the train samples +preds = np.argmax(classifier.predict(x_train), axis=1) +acc = np.sum(preds == np.argmax(y_train, axis=1)) / y_train.shape[0] +logger.info('Accuracy on train samples: %.2f%%', (acc * 100)) + +x_train, y_train = x_train[:100], y_train[:100] +preds = np.argmax(classifier.predict(x_train), axis=1) + +# Craft adversarial samples with SimBA for single image +logger.info('Create universal SimBA (pixel) attack') +adv_crafter = Universal_SimBA_pixel(classifier, epsilon=0.05) +logger.info('Craft attack on a training example') +x_train_adv_univ_simba = adv_crafter.generate(x_train) +logger.info('Craft attack the training example') +norm2 = np.linalg.norm((x_train_adv_univ_simba[0] - x_train[0]).reshape(-1), ord=2) +# compute fooling rate +preds_adv = np.argmax(classifier.predict(x_train_adv_univ_simba), axis=1) +acc = np.sum(preds != preds_adv) / y_train.shape[0] +logger.info('Fooling rate on universal SimBA adversarial examples: %.2f%%', (acc * 100)) + +# Craft adversarial samples with random universal pertubation +x_train_adv_random = x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3) +preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) +acc = np.sum(preds != preds_adv) / y_train.shape[0] +logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) + +# Craft adversarial samples with universal pertubation and FGSM +attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": norm2, "norm": 2} +adv_crafter_fgsm = UniversalPerturbation(classifier) +adv_crafter_fgsm.set_params(**attack_params) +x_train_adv_fgsm = adv_crafter_fgsm.generate(x_train, **attack_params) +np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2) +# compute fooling rate +preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) +acc = np.sum(preds != preds_adv) / y_train.shape[0] +logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) \ No newline at end of file From 76e19dea62e8a92fa11eedb4659e2c8210a7820f Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 2 Feb 2020 21:27:25 +0900 Subject: [PATCH 04/91] updated --- art/attacks/evasion/__init__.py | 1 + art/attacks/evasion/simba_pixel.py | 5 +- .../targeted_universal_perturbation.py | 224 ++++++++++++++++++ art/attacks/evasion/universal_simba_pixel.py | 5 +- 4 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 art/attacks/evasion/targeted_universal_perturbation.py diff --git a/art/attacks/evasion/__init__.py b/art/attacks/evasion/__init__.py index a1d62e1fb4..d8fe6ca2b9 100644 --- a/art/attacks/evasion/__init__.py +++ b/art/attacks/evasion/__init__.py @@ -16,5 +16,6 @@ from art.attacks.evasion.saliency_map import SaliencyMapMethod from art.attacks.evasion.spatial_transformation import SpatialTransformation from art.attacks.evasion.universal_perturbation import UniversalPerturbation +from art.attacks.evasion.targeted_universal_perturbation import TargetedUniversalPerturbation from art.attacks.evasion.virtual_adversarial import VirtualAdversarialMethod from art.attacks.evasion.zoo import ZooAttack diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index c26e36ef3f..3b72ee65ee 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -81,7 +81,10 @@ def generate(self, x, y=None, **kwargs): n_dims = np.prod(x.shape) - clip_min, clip_max = self.classifier.clip_values + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values nb_iter = 0 while original_label == current_label and nb_iter < self.max_iter: diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py new file mode 100644 index 0000000000..d5a539aeea --- /dev/null +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -0,0 +1,224 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the universal adversarial perturbations attack `TargetedUniversalPerturbation`. + +| Paper link: https://arxiv.org/abs/1911.06502 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import random + +import numpy as np + +from art.classifiers.classifier import ClassifierNeuralNetwork, ClassifierGradients +from art.attacks.attack import EvasionAttack +from art.utils import projection + +logger = logging.getLogger(__name__) + + +class TargetedUniversalPerturbation(EvasionAttack): + """ + Implementation of the attack from Hirano and Takemoto (2019). Computes a fixed perturbation to be applied to all + future inputs. To this end, it can use any adversarial attack method. + + | Paper link: https://arxiv.org/abs/1911.06502 + """ + attacks_dict = { + 'fgsm': 'art.attacks.evasion.fast_gradient.FastGradientMethod' + } + attack_params = EvasionAttack.attack_params + ['attacker', 'attacker_params', 'delta', 'max_iter', 'eps', 'norm'] + + def __init__(self, classifier, attacker='fgsm', attacker_params=None, delta=0.2, max_iter=20, eps=10.0, + norm=np.inf): + """ + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'fgsm'. + :type attacker: `str` + :param attacker_params: Parameters specific to the adversarial attack. If this parameter is not specified, + the default parameters of the chosen attack will be used. + :type attacker_params: `dict` + :param delta: desired accuracy + :type delta: `float` + :param max_iter: The maximum number of iterations for computing universal perturbation. + :type max_iter: `int` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 + :type norm: `int` + """ + super(TargetedUniversalPerturbation, self).__init__(classifier) + if not isinstance(classifier, ClassifierNeuralNetwork) or not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierNeuralNetwork` and ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + kwargs = {'attacker': attacker, + 'attacker_params': attacker_params, + 'delta': delta, + 'max_iter': max_iter, + 'eps': eps, + 'norm': norm + } + self.set_params(**kwargs) + + def generate(self, x, y, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs. + :type x: `np.ndarray` + :param y: An array with the targeted labels. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + logger.info('Computing targeted universal perturbation based on %s attack.', self.attacker) + + # Init universal perturbation + noise = 0 + fooling_rate = 0.0 + targeted_success_rate = 0.0 + nb_instances = len(x) + + # Instantiate the middle attacker and get the predicted labels + attacker = self._get_attack(self.attacker, self.attacker_params) + pred_y = self.classifier.predict(x, batch_size=1) + pred_y_max = np.argmax(pred_y, axis=1) + + # Start to generate the adversarial examples + nb_iter = 0 + while targeted_success_rate < 1. - self.delta and nb_iter < self.max_iter: + # Go through all the examples randomly + rnd_idx = random.sample(range(nb_instances), nb_instances) + + # Go through the data set and compute the perturbation increments sequentially + for j, (ex, ey) in enumerate(zip(x[rnd_idx], y[rnd_idx])): + x_i = ex[None, ...] + y_i = ey[None, ...] + + current_label = np.argmax(self.classifier.predict(x_i + noise)[0]) + target_label = np.argmax(ey) + + if current_label != target_label: + # Compute adversarial perturbation + adv_xi = attacker.generate(x_i + noise, y=y_i) + + new_label = np.argmax(self.classifier.predict(adv_xi)[0]) + + # If the class has changed, update v + if new_label == target_label: + noise = adv_xi - x_i + + # Project on L_p ball + noise = projection(noise, self.eps, self.norm) + nb_iter += 1 + + # Apply attack and clip + x_adv = x + noise + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values + x_adv = np.clip(x_adv, clip_min, clip_max) + + # Compute the error rate + y_adv = np.argmax(self.classifier.predict(x_adv, batch_size=1), axis=1) + fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances + targeted_success_rate = np.sum(y_adv == np.argmax(y, axis=1)) / nb_instances + + self.fooling_rate = fooling_rate + self.converged = nb_iter < self.max_iter + self.noise = noise + logger.info('Success rate of universal perturbation attack: %.2f%%', 100 * fooling_rate) + logger.info('Targeted success rate of universal perturbation attack: %.2f%%', targeted_success_rate) + + return x_adv + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'fgsm'. + :type attacker: `str` + :param attacker_params: Parameters specific to the adversarial attack. + :type attacker_params: `dict` + :param delta: desired accuracy + :type delta: `float` + :param max_iter: The maximum number of iterations for computing universal perturbation. + :type max_iter: `int` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: Order of the norm. Possible values: np.inf, 2 (default is np.inf) + :type norm: `int` + """ + super(TargetedUniversalPerturbation, self).set_params(**kwargs) + + if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: + raise ValueError("The desired accuracy must be in the range [0, 1].") + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if not isinstance(self.eps, (float, int)) or self.eps <= 0: + raise ValueError("The eps coefficient must be a positive float.") + + return True + + def _get_attack(self, a_name, params=None): + """ + Get an attack object from its name. + + :param a_name: attack name. + :type a_name: `str` + :param params: attack params. + :type params: `dict` + :return: attack object + :rtype: `object` + """ + try: + attack_class = self._get_class(self.attacks_dict[a_name]) + a_instance = attack_class(self.classifier) + + if params: + a_instance.set_params(**params) + + return a_instance + + except KeyError: + raise NotImplementedError("{} attack not supported".format(a_name)) + + @staticmethod + def _get_class(class_name): + """ + Get a class module from its name. + + :param class_name: Full name of a class. + :type class_name: `str` + :return: The class `module`. + :rtype: `module` + """ + sub_mods = class_name.split(".") + print(class_name) + module_ = __import__(".".join(sub_mods[:-1]), fromlist=sub_mods[-1]) + class_module = getattr(module_, sub_mods[-1]) + + return class_module diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index 98b755e086..20c226a031 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -84,7 +84,10 @@ def generate(self, x, y=None, **kwargs): n_dims = np.prod(x[0].shape) - clip_min, clip_max = self.classifier.clip_values + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values fooling_rate = 0.0 nb_iter = 0 From 67953f3dca0138085e1e87daca2b86ecb92ed047 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 2 Feb 2020 23:10:20 +0900 Subject: [PATCH 05/91] updated --- README.md | 5 +++++ examples/cifar10_cnn_simba.py | 6 +++--- examples/cifar10_cnn_universal_simba.py | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a4b2c9a704..1a146bfd55 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +We are modifying Adversarial Robustness 360 Toolbox (ART) v1.1. + +* Targeted universal adversarial perturbation ([Hirano and Takemoto, 2019](https://arxiv.org/abs/1911.06502)) added. +* Simple blackbox attack (SimBA based on pixel attacks; [Guo et al., 2019](https://arxiv.org/abs/1905.07121)) added. + # Adversarial Robustness 360 Toolbox (ART) v1.1

diff --git a/examples/cifar10_cnn_simba.py b/examples/cifar10_cnn_simba.py index 354db783c2..8a8930f2d0 100644 --- a/examples/cifar10_cnn_simba.py +++ b/examples/cifar10_cnn_simba.py @@ -84,18 +84,18 @@ preds = np.argmax(classifier.predict(x_train), axis=1) # Craft adversarial samples with universal pertubation based on SimBA -attack_params = {"attacker": "simba_px", "attacker_params": {"max_iter": 3000, "epsilon": 0.02}, "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} +attack_params = {"attacker": "simba_px", "attacker_params": {"max_iter": 3000, "epsilon": 0.05}, "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} adv_crafter_simba = UniversalPerturbation(classifier) adv_crafter_simba.set_params(**attack_params) x_train_adv_simba = adv_crafter_simba.generate(x_train, y=1, **attack_params) -norm2_simba = np.linalg.norm(adv_crafter_simba.noise.reshape(-1), ord=2) +norm2 = np.linalg.norm(adv_crafter_simba.noise.reshape(-1), ord=2) # compute fooling rate preds_adv = np.argmax(classifier.predict(x_train_adv_simba), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on SimBA universal adversarial examples: %.2f%%', (acc * 100)) # Craft adversarial samples with random universal pertubation -x_train_adv_random = x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2_simba, norm=2).reshape(1,32,32,3) +x_train_adv_random = np.clip(x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3), min_, max_) preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) diff --git a/examples/cifar10_cnn_universal_simba.py b/examples/cifar10_cnn_universal_simba.py index 9cb7a3cb1b..affeccfeaa 100644 --- a/examples/cifar10_cnn_universal_simba.py +++ b/examples/cifar10_cnn_universal_simba.py @@ -82,7 +82,7 @@ logger.info('Fooling rate on universal SimBA adversarial examples: %.2f%%', (acc * 100)) # Craft adversarial samples with random universal pertubation -x_train_adv_random = x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3) +x_train_adv_random = np.clip(x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3), min_, max_) preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) From 2bd175d4ff571df49b227137a486c631476e2d49 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 2 Feb 2020 23:14:33 +0900 Subject: [PATCH 06/91] updated --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a146bfd55..70aa7779cb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -We are modifying Adversarial Robustness 360 Toolbox (ART) v1.1. +# NOTE +In this repository, we are modifying Adversarial Robustness 360 Toolbox (ART) v1.1. * Targeted universal adversarial perturbation ([Hirano and Takemoto, 2019](https://arxiv.org/abs/1911.06502)) added. * Simple blackbox attack (SimBA based on pixel attacks; [Guo et al., 2019](https://arxiv.org/abs/1905.07121)) added. + # Adversarial Robustness 360 Toolbox (ART) v1.1

From 7deda967898908f9ff591a93e5a4b70470265ff4 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Mon, 3 Feb 2020 11:52:17 +0900 Subject: [PATCH 07/91] updated --- art/attacks/evasion/simba_pixel.py | 24 +++++++++++++------- art/attacks/evasion/universal_simba_pixel.py | 24 +++++++++++++------- examples/cifar10_cnn_simba.py | 9 +++++--- examples/cifar10_cnn_universal_simba.py | 9 +++++--- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 3b72ee65ee..a3d4a80362 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -90,19 +90,27 @@ def generate(self, x, y=None, **kwargs): while original_label == current_label and nb_iter < self.max_iter: diff = np.zeros(n_dims) diff[np.random.choice(range(n_dims))] = self.epsilon - preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - left_prob = preds.reshape(-1)[original_label] + + left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + left_prob = left_preds.reshape(-1)[original_label] + + right_preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + right_prob = right_preds.reshape(-1)[original_label] + if left_prob < last_prob: - x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) - last_prob = left_prob - current_label = np.argmax(preds, axis=1)[0] + if left_prob < right_prob: + x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) + last_prob = left_prob + current_label = np.argmax(left_preds, axis=1)[0] + else: + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] else: - preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - right_prob = preds.reshape(-1)[original_label] if right_prob < last_prob: x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) last_prob = right_prob - current_label = np.argmax(preds, axis=1)[0] + current_label = np.argmax(right_preds, axis=1)[0] nb_iter = nb_iter + 1 diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index 20c226a031..4acdacea6d 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -94,19 +94,27 @@ def generate(self, x, y=None, **kwargs): while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: diff = np.zeros(n_dims) diff[np.random.choice(range(n_dims))] = self.epsilon - preds = self.classifier.predict(np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) - left_probs = preds[(range(nb_instances),original_labels)] + + left_preds = self.classifier.predict(np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + left_probs = left_preds[(range(nb_instances),original_labels)] + + right_preds = self.classifier.predict(np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + right_probs = right_preds[(range(nb_instances),original_labels)] + if np.sum(left_probs - last_probs) < 0.0: - x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - last_probs = left_probs - current_labels = np.argmax(preds, axis=1) + if np.sum(left_probs - right_probs) < 0.0: + x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + last_probs = left_probs + current_labels = np.argmax(left_preds, axis=1) + else: + x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + last_probs = right_probs + current_labels = np.argmax(right_preds, axis=1) else: - preds = self.classifier.predict(np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) - right_probs = preds[(range(nb_instances),original_labels)] if np.sum(right_probs - last_probs) < 0.0: x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) last_probs = right_probs - current_labels = np.argmax(preds, axis=1) + current_labels = np.argmax(right_preds, axis=1) # Compute the error rate fooling_rate = np.sum(original_labels != current_labels) / nb_instances diff --git a/examples/cifar10_cnn_simba.py b/examples/cifar10_cnn_simba.py index 8a8930f2d0..284455a85f 100644 --- a/examples/cifar10_cnn_simba.py +++ b/examples/cifar10_cnn_simba.py @@ -11,7 +11,7 @@ from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout import numpy as np -from art.attacks import DeepFool, SimBA_pixel, UniversalPerturbation, Universal_SimBA_pixel +from art.attacks import SimBA_pixel, UniversalPerturbation from art.classifiers import KerasClassifier from art.utils import load_dataset, random_sphere @@ -59,7 +59,7 @@ # Create classifier wrapper classifier = KerasClassifier(model=model, clip_values=(min_, max_)) -classifier.fit(x_train, y_train, nb_epochs=3, batch_size=128) +classifier.fit(x_train, y_train, nb_epochs=5, batch_size=128) # Evaluate the classifier on the train samples preds = np.argmax(classifier.predict(x_train), axis=1) @@ -87,18 +87,20 @@ attack_params = {"attacker": "simba_px", "attacker_params": {"max_iter": 3000, "epsilon": 0.05}, "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} adv_crafter_simba = UniversalPerturbation(classifier) adv_crafter_simba.set_params(**attack_params) -x_train_adv_simba = adv_crafter_simba.generate(x_train, y=1, **attack_params) +x_train_adv_simba = adv_crafter_simba.generate(x_train, **attack_params) norm2 = np.linalg.norm(adv_crafter_simba.noise.reshape(-1), ord=2) # compute fooling rate preds_adv = np.argmax(classifier.predict(x_train_adv_simba), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on SimBA universal adversarial examples: %.2f%%', (acc * 100)) +logger.info('Perturbation norm: %.2f%%', norm2) # Craft adversarial samples with random universal pertubation x_train_adv_random = np.clip(x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3), min_, max_) preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) +logger.info('Perturbation norm: %.2f%%', np.linalg.norm((x_train_adv_random[0]-x_train[0]).reshape(-1), ord=2)) # Craft adversarial samples with universal pertubation and FGSM attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} @@ -110,6 +112,7 @@ preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) +logger.info('Perturbation norm: %.2f%%', np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2)) """ diff --git a/examples/cifar10_cnn_universal_simba.py b/examples/cifar10_cnn_universal_simba.py index affeccfeaa..fe44d9c441 100644 --- a/examples/cifar10_cnn_universal_simba.py +++ b/examples/cifar10_cnn_universal_simba.py @@ -11,7 +11,7 @@ from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout import numpy as np -from art.attacks import DeepFool, SimBA_pixel, UniversalPerturbation, Universal_SimBA_pixel +from art.attacks import UniversalPerturbation, Universal_SimBA_pixel from art.classifiers import KerasClassifier from art.utils import load_dataset, random_sphere @@ -59,7 +59,7 @@ # Create classifier wrapper classifier = KerasClassifier(model=model, clip_values=(min_, max_)) -classifier.fit(x_train, y_train, nb_epochs=3, batch_size=128) +classifier.fit(x_train, y_train, nb_epochs=5, batch_size=128) # Evaluate the classifier on the train samples preds = np.argmax(classifier.predict(x_train), axis=1) @@ -80,12 +80,14 @@ preds_adv = np.argmax(classifier.predict(x_train_adv_univ_simba), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on universal SimBA adversarial examples: %.2f%%', (acc * 100)) +logger.info('Perturbation norm: %.2f%%', norm2) # Craft adversarial samples with random universal pertubation x_train_adv_random = np.clip(x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3), min_, max_) preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) +logger.info('Perturbation norm: %.2f%%', np.linalg.norm((x_train_adv_random[0]-x_train[0]).reshape(-1), ord=2)) # Craft adversarial samples with universal pertubation and FGSM attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": norm2, "norm": 2} @@ -96,4 +98,5 @@ # compute fooling rate preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) acc = np.sum(preds != preds_adv) / y_train.shape[0] -logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) \ No newline at end of file +logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) +logger.info('Perturbation norm: %.2f%%', np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2)) \ No newline at end of file From 4e3a80186742f18ec609cfefcebb7dcd58eaa009 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Mon, 3 Feb 2020 14:06:50 +0900 Subject: [PATCH 08/91] updated --- art/attacks/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index e4172d7402..ae3383d9e6 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -20,6 +20,7 @@ from art.attacks.evasion.saliency_map import SaliencyMapMethod from art.attacks.evasion.spatial_transformation import SpatialTransformation from art.attacks.evasion.universal_perturbation import UniversalPerturbation +from art.attacks.evasion.targeted_universal_perturbation import TargetedUniversalPerturbation from art.attacks.evasion.virtual_adversarial import VirtualAdversarialMethod from art.attacks.evasion.zoo import ZooAttack From b4762e44dab69cce0167e17743d17a939b331bed Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Mon, 3 Feb 2020 16:42:12 +0900 Subject: [PATCH 09/91] updated --- examples/cifar10_cnn_simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cifar10_cnn_simba.py b/examples/cifar10_cnn_simba.py index 284455a85f..d35daf0e08 100644 --- a/examples/cifar10_cnn_simba.py +++ b/examples/cifar10_cnn_simba.py @@ -106,7 +106,7 @@ attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} adv_crafter_fgsm = UniversalPerturbation(classifier) adv_crafter_fgsm.set_params(**attack_params) -x_train_adv_fgsm = adv_crafter_fgsm.generate(x_train, **attack_params) +x_train_adv_fgsm = adv_crafter_fgsm.generate(x_train, y=1, **attack_params) np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2) # compute fooling rate preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) From ff88be09014f81fcca571612dc0db7226a94b295 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 4 Feb 2020 13:25:37 +0900 Subject: [PATCH 10/91] updated --- art/attacks/evasion/simba_pixel.py | 4 ++-- art/attacks/evasion/universal_perturbation.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index a3d4a80362..0d4f0af9c5 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -74,8 +74,8 @@ def generate(self, x, y=None, **kwargs): x = x.astype(ART_NUMPY_DTYPE) preds = self.classifier.predict(x, batch_size=self.batch_size) if y is None: - y = np.argmax(preds, axis=1)[0] - original_label = y + y = np.argmax(preds, axis=1) + original_label = y[0] current_label = original_label last_prob = preds.reshape(-1)[original_label] diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index 5178ec267d..8a9b6b029c 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -134,7 +134,7 @@ def generate(self, x, y=None, **kwargs): if y is None: adv_xi = attacker.generate(x_i + noise) else: - adv_xi = attacker.generate(x_i + noise, y=original_label) + adv_xi = attacker.generate(x_i + noise, y=np.array([original_label])) new_label = np.argmax(self.classifier.predict(adv_xi)[0]) From 77baeab93c2c4bf79e2831d64d1a397b2878a66b Mon Sep 17 00:00:00 2001 From: kazukikoga Date: Fri, 28 Feb 2020 13:37:35 +0900 Subject: [PATCH 11/91] update && upload simba_dct --- art/attacks/__init__.py | 1 + art/attacks/evasion/__init__.py | 2 + art/attacks/evasion/simba_dct.py | 184 ++++++++++++++++++ art/attacks/evasion/universal_perturbation.py | 28 ++- art/attacks/evasion/universal_simba_pixel.py | 23 ++- 5 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 art/attacks/evasion/simba_dct.py diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index ae3383d9e6..23919bc25f 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -9,6 +9,7 @@ from art.attacks.evasion.decision_tree_attack import DecisionTreeAttack from art.attacks.evasion.deepfool import DeepFool from art.attacks.evasion.simba_pixel import SimBA_pixel +from art.attacks.evasion.simba_dct import SimBA_dct from art.attacks.evasion.universal_simba_pixel import Universal_SimBA_pixel from art.attacks.evasion.elastic_net import ElasticNet from art.attacks.evasion.fast_gradient import FastGradientMethod diff --git a/art/attacks/evasion/__init__.py b/art/attacks/evasion/__init__.py index d8fe6ca2b9..ceb5f31da8 100644 --- a/art/attacks/evasion/__init__.py +++ b/art/attacks/evasion/__init__.py @@ -19,3 +19,5 @@ from art.attacks.evasion.targeted_universal_perturbation import TargetedUniversalPerturbation from art.attacks.evasion.virtual_adversarial import VirtualAdversarialMethod from art.attacks.evasion.zoo import ZooAttack +from art.attacks.evasion.simba_pixel import SimBA_pixel +from art.attacks.evasion.simba_dct import SimBA_dct diff --git a/art/attacks/evasion/simba_dct.py b/art/attacks/evasion/simba_dct.py new file mode 100644 index 0000000000..fdfe1d23e6 --- /dev/null +++ b/art/attacks/evasion/simba_dct.py @@ -0,0 +1,184 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box attack `simba`. + +| Paper link: https://arxiv.org/abs/1905.07121 +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import logging + +import numpy as np +from scipy.fftpack import dct, idct + +from art.attacks.attack import EvasionAttack +from art.classifiers.classifier import ClassifierGradients +from art.config import ART_NUMPY_DTYPE +from art.utils import compute_success + +logger = logging.getLogger(__name__) + + +class SimBA_dct(EvasionAttack): + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'batch_size', 'freq_dim', 'stride'] + + def __init__(self, classifier, max_iter=3000, epsilon=0.1, freq_dim=4, stride=1, batch_size=1): + """ + Create a SimBA (dct) attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + super(SimBA_dct, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'max_iter': max_iter, 'epsilon': epsilon, 'freq_dim': freq_dim, 'stride': stride, 'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + preds = self.classifier.predict(x, batch_size=self.batch_size) + if y is None: + y = np.argmax(preds, axis=1) + original_label = y[0] + current_label = original_label + last_prob = preds.reshape(-1)[original_label] + + n_dims = np.prod(x.shape) + + indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + trans = lambda z: self._block_idct(z, block_size=x.shape[2]) + + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values + + nb_iter = 0 + while original_label == current_label and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[indices[nb_iter]] = self.epsilon + + left_preds = self.classifier.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) + left_prob = left_preds.reshape(-1)[original_label] + + right_preds = self.classifier.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) + right_prob = right_preds.reshape(-1)[original_label] + + if left_prob < last_prob: + if left_prob < right_prob: + x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) + last_prob = left_prob + current_label = np.argmax(left_preds, axis=1)[0] + else: + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] + else: + if right_prob < last_prob: + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] + + nb_iter = nb_iter + 1 + + if nb_iter < self.max_iter: + logger.info('SimBA (dct) attack succeed') + else: + logger.info('SimBA (dct) attack failed') + + return x + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + # Save attack-specific parameters + super(SimBA_dct, self).set_params(**kwargs) + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + return True + + def _block_order(self, img_size, channels, initial_size=28, stride=7): + order = np.zeros((channels , img_size , img_size)) + total_elems = channels * initial_size * initial_size + perm = np.random.permutation(total_elems) + order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) + for i in range(initial_size, img_size, stride): + num_elems = channels * (2 * stride * i + stride * stride) + perm = np.random.permutation(num_elems) + total_elems + num_first = channels * stride * (stride + i) + order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) + order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) + total_elems += num_elems + return order.reshape(1, -1).squeeze().argsort() + + # applies IDCT to each block of size block_size + def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): + z = np.zeros(x.shape) + num_blocks = int(x.shape[2] / block_size) + mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) + if type(ratio) != float: + for i in range(x.shape[0]): + mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 + else: + mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 + for i in range(num_blocks): + for j in range(num_blocks): + submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] + if masked: + submat = submat * mask + z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') + return z diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index 8a9b6b029c..c03e635fc3 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -34,6 +34,19 @@ logger = logging.getLogger(__name__) +##### +import numpy as np +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import japanize_matplotlib + +import torch +import logging +import os.path + +import numpy as np + class UniversalPerturbation(EvasionAttack): """ @@ -46,6 +59,7 @@ class UniversalPerturbation(EvasionAttack): 'carlini_inf': 'art.attacks.evasion.carlini.CarliniLInfMethod', 'deepfool': 'art.attacks.evasion.deepfool.DeepFool', 'simba_px': 'art.attacks.evasion.simba_pixel.SimBA_pixel', + 'simba_dct': 'art.attacks.evasion.simba_dct.SimBA_dct', 'ead': 'art.attacks.evasion.elastic_net.ElasticNet', 'fgsm': 'art.attacks.evasion.fast_gradient.FastGradientMethod', 'bim': 'art.attacks.evasion.iterative_method.BasicIterativeMethod', @@ -54,10 +68,10 @@ class UniversalPerturbation(EvasionAttack): 'jsma': 'art.attacks.evasion.saliency_map.SaliencyMapMethod', 'vat': 'art.attacks.evasion.virtual_adversarial.VirtualAdversarialMethod' } - attack_params = EvasionAttack.attack_params + ['attacker', 'attacker_params', 'delta', 'max_iter', 'eps', 'norm'] + attack_params = EvasionAttack.attack_params + ['attacker', 'attacker_params', 'delta', 'max_iter', 'eps', 'norm', 'batch_size'] def __init__(self, classifier, attacker='deepfool', attacker_params=None, delta=0.2, max_iter=20, eps=10.0, - norm=np.inf): + norm=np.inf, batch_size=1): """ :param classifier: A trained classifier. :type classifier: :class:`.Classifier` @@ -89,7 +103,8 @@ def __init__(self, classifier, attacker='deepfool', attacker_params=None, delta= 'delta': delta, 'max_iter': max_iter, 'eps': eps, - 'norm': norm + 'norm': norm, + 'batch_size': batch_size } self.set_params(**kwargs) @@ -156,6 +171,13 @@ def generate(self, x, y=None, **kwargs): y_adv = np.argmax(self.classifier.predict(x_adv, batch_size=1), axis=1) fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances + random_noise = np.random.permutation(noise.reshape(-1)).reshape(noise.shape) + x_adv_random = x + random_noise + y_adv_random = np.argmax(self.classifier.predict(x_adv_random, batch_size=1), axis=1) + fooling_rate_random = np.sum(pred_y_max != y_adv_random) / nb_instances + + logger.info(f"iteration: {nb_iter}, norm_size: {np.linalg.norm(noise.flatten(), ord=2)}, fooling_rate: {fooling_rate}, random_fooling_rate:{fooling_rate_random}") + self.fooling_rate = fooling_rate self.converged = nb_iter < self.max_iter self.noise = noise diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index 4acdacea6d..4dc753f77b 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -115,6 +115,8 @@ def generate(self, x, y=None, **kwargs): x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) last_probs = right_probs current_labels = np.argmax(right_preds, axis=1) + + noise = projection(noise, self.eps, self.norm) # Compute the error rate fooling_rate = np.sum(original_labels != current_labels) / nb_instances @@ -127,7 +129,6 @@ def generate(self, x, y=None, **kwargs): logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) return x - def set_params(self, **kwargs): """ Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. @@ -157,3 +158,23 @@ def set_params(self, **kwargs): raise ValueError('The batch size `batch_size` has to be positive.') return True + +def projection(values, eps, norm_p): + + # Pick a small scalar to avoid division by 0 + tol = 10e-8 + values_tmp = values.reshape((values.shape[0], -1)).numpy() + + if norm_p == 2: + values_tmp = values_tmp * np.expand_dims(np.minimum(1., eps / (np.linalg.norm(values_tmp, axis=1) + tol)), + axis=1) + elif norm_p == 1: + values_tmp = values_tmp * np.expand_dims( + np.minimum(1., eps / (np.linalg.norm(values_tmp, axis=1, ord=1) + tol)), axis=1) + elif norm_p == np.inf: + values_tmp = np.sign(values_tmp) * np.minimum(abs(values_tmp), eps) + else: + raise NotImplementedError('Values of `norm_p` different from 1, 2 and `np.inf` are currently not supported.') + + values = torch.from_numpy(values_tmp.reshape(values.shape)) + return values From 5434c3bf4ad88a8d5c03104daaefda1b95d93724 Mon Sep 17 00:00:00 2001 From: kazukikoga Date: Mon, 2 Mar 2020 14:07:23 +0900 Subject: [PATCH 12/91] update simba_dct universal_simba --- art/attacks/evasion/simba_pixel.py | 34 +++++++++++++--- art/attacks/evasion/universal_simba_pixel.py | 43 +++++++------------- art/classifiers/pytorch.py | 20 ++++----- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 0d4f0af9c5..75eb0b36e5 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -35,9 +35,9 @@ class SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'batch_size'] + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'order', 'batch_size'] - def __init__(self, classifier, max_iter=3000, epsilon=0.1, batch_size=1): + def __init__(self, classifier, max_iter=3000, epsilon=0.1, order='random', batch_size=1): """ Create a SimBA (pixel) attack instance. @@ -57,7 +57,7 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, batch_size=1): + str(classifier.__class__.__bases__) + '. ' ' The classifier needs to be a Neural Network and provide gradients.')) - params = {'max_iter': max_iter, 'epsilon': epsilon, 'batch_size': batch_size} + params = {'max_iter': max_iter, 'epsilon': epsilon, 'order': order, 'batch_size': batch_size} self.set_params(**params) def generate(self, x, y=None, **kwargs): @@ -81,6 +81,11 @@ def generate(self, x, y=None, **kwargs): n_dims = np.prod(x.shape) + if self.order == "diag": + indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] + elif self.order == "perm": + indices = np.random.permutation(n_dims) + clip_min = -np.inf clip_max = np.inf if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: @@ -89,7 +94,12 @@ def generate(self, x, y=None, **kwargs): nb_iter = 0 while original_label == current_label and nb_iter < self.max_iter: diff = np.zeros(n_dims) - diff[np.random.choice(range(n_dims))] = self.epsilon + if self.order == "random": + diff[np.random.choice(range(n_dims))] = self.epsilon + elif self.order == "diag": + diff[indices[nb_iter]] = self.epsilon + elif self.order == "perm": + diff[indices[nb_iter]] = self.epsilon left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) left_prob = left_preds.reshape(-1)[original_label] @@ -121,7 +131,6 @@ def generate(self, x, y=None, **kwargs): return x - def set_params(self, **kwargs): """ Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. @@ -146,3 +155,18 @@ def set_params(self, **kwargs): raise ValueError('The batch size `batch_size` has to be positive.') return True + + def diagonal_order(self, image_size, channels): + x = np.arange(0, image_size).cumsum() + order = np.zeros((image_size, image_size)) + for i in range(image_size): + order[i, :(image_size - i)] = i + x[i:] + for i in range(1, image_size): + reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) + order[i, (image_size - i):] = image_size * image_size - 1 - reverse + if channels > 1: + order_2d = order + order = np.zeros((channels, image_size, image_size)) + for i in range(channels): + order[i, :, :] = 3 * order_2d + i + return order.reshape(1, -1).squeeze().argsort() diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index 4dc753f77b..b4a707809f 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -30,14 +30,15 @@ from art.classifiers.classifier import ClassifierGradients from art.attacks.attack import EvasionAttack from art.utils import compute_success +from art.utils import projection logger = logging.getLogger(__name__) class Universal_SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta' ,'batch_size'] + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta', 'eps', 'norm','batch_size'] - def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, batch_size=1): + def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, norm=2, batch_size=1): """ Create a universal SimBA (pixel) attack instance. @@ -59,7 +60,7 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, batch_size + str(classifier.__class__.__bases__) + '. ' ' The classifier needs to be a Neural Network and provide gradients.')) - params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta, 'batch_size': batch_size} + params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta,'eps': eps, 'norm': norm, 'batch_size': batch_size} self.set_params(**params) def generate(self, x, y=None, **kwargs): @@ -74,6 +75,7 @@ def generate(self, x, y=None, **kwargs): :rtype: `np.ndarray` """ x = x.astype(ART_NUMPY_DTYPE) + noise = 0 nb_instances = x.shape[0] preds = self.classifier.predict(x, batch_size=self.batch_size) if y is None: @@ -88,31 +90,33 @@ def generate(self, x, y=None, **kwargs): clip_max = np.inf if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: clip_min, clip_max = self.classifier.clip_values - fooling_rate = 0.0 nb_iter = 0 while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: diff = np.zeros(n_dims) diff[np.random.choice(range(n_dims))] = self.epsilon - left_preds = self.classifier.predict(np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + left_preds = self.classifier.predict(np.clip(x + noise - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) left_probs = left_preds[(range(nb_instances),original_labels)] - right_preds = self.classifier.predict(np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + right_preds = self.classifier.predict(np.clip(x + noise + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) right_probs = right_preds[(range(nb_instances),original_labels)] if np.sum(left_probs - last_probs) < 0.0: if np.sum(left_probs - right_probs) < 0.0: - x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + # x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + noise -= diff.reshape(x[0][None, ...].shape) last_probs = left_probs current_labels = np.argmax(left_preds, axis=1) else: - x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + noise += diff.reshape(x[0][None, ...].shape) last_probs = right_probs current_labels = np.argmax(right_preds, axis=1) else: if np.sum(right_probs - last_probs) < 0.0: - x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + noise += diff.reshape(x[0][None, ...].shape) last_probs = right_probs current_labels = np.argmax(right_preds, axis=1) @@ -125,6 +129,7 @@ def generate(self, x, y=None, **kwargs): if nb_iter % 50 == 0: logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%%', nb_iter, 100 * fooling_rate) + logger.info('Nise ls norm %.2f', np.linalg.norm(noise.flatten(), ord=2)) logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) return x @@ -158,23 +163,3 @@ def set_params(self, **kwargs): raise ValueError('The batch size `batch_size` has to be positive.') return True - -def projection(values, eps, norm_p): - - # Pick a small scalar to avoid division by 0 - tol = 10e-8 - values_tmp = values.reshape((values.shape[0], -1)).numpy() - - if norm_p == 2: - values_tmp = values_tmp * np.expand_dims(np.minimum(1., eps / (np.linalg.norm(values_tmp, axis=1) + tol)), - axis=1) - elif norm_p == 1: - values_tmp = values_tmp * np.expand_dims( - np.minimum(1., eps / (np.linalg.norm(values_tmp, axis=1, ord=1) + tol)), axis=1) - elif norm_p == np.inf: - values_tmp = np.sign(values_tmp) * np.minimum(abs(values_tmp), eps) - else: - raise NotImplementedError('Values of `norm_p` different from 1, 2 and `np.inf` are currently not supported.') - - values = torch.from_numpy(values_tmp.reshape(values.shape)) - return values diff --git a/art/classifiers/pytorch.py b/art/classifiers/pytorch.py index b16951c87e..1be715c784 100644 --- a/art/classifiers/pytorch.py +++ b/art/classifiers/pytorch.py @@ -112,7 +112,7 @@ def predict(self, x, batch_size=128, **kwargs): # Batch indexes begin, end = m * batch_size, min((m + 1) * batch_size, x_preprocessed.shape[0]) - model_outputs = self._model(torch.from_numpy(x_preprocessed[begin:end]).to(self._device)) + model_outputs = self._model(torch.from_numpy(x_preprocessed[begin:end]).type(torch.FloatTensor).to(self._device)) output = model_outputs[-1] results[begin:end] = output.detach().cpu().numpy() @@ -152,8 +152,8 @@ def fit(self, x, y, batch_size=128, nb_epochs=10, **kwargs): # Train for one epoch for m in range(num_batch): - i_batch = torch.from_numpy(x_preprocessed[ind[m * batch_size:(m + 1) * batch_size]]).to(self._device) - o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size:(m + 1) * batch_size]]).to(self._device) + i_batch = torch.from_numpy(x_preprocessed[ind[m * batch_size:(m + 1) * batch_size]]).type(torch.FloatTensor).to(self._device) + o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size:(m + 1) * batch_size]]).type(torch.FloatTensor).to(self._device) # Zero the parameter gradients self._optimizer.zero_grad() @@ -186,12 +186,12 @@ def fit_generator(self, generator, nb_epochs=20, **kwargs): for _ in range(nb_epochs): for i_batch, o_batch in generator.data_loader: if isinstance(i_batch, np.ndarray): - i_batch = torch.from_numpy(i_batch).to(self._device) + i_batch = torch.from_numpy(i_batch).type(torch.FloatTensor).to(self._device) else: i_batch = i_batch.to(self._device) if isinstance(o_batch, np.ndarray): - o_batch = torch.argmax(torch.from_numpy(o_batch).to(self._device), dim=1) + o_batch = torch.argmax(torch.from_numpy(o_batch).type(torch.FloatTensor).to(self._device), dim=1) else: o_batch = torch.argmax(o_batch.to(self._device), dim=1) @@ -232,7 +232,7 @@ def class_gradient(self, x, label=None, **kwargs): # Apply preprocessing x_preprocessed, _ = self._apply_preprocessing(x, y=None, fit=False) - x_preprocessed = torch.from_numpy(x_preprocessed).to(self._device) + x_preprocessed = torch.from_numpy(x_preprocessed).type(torch.FloatTensor).to(self._device) # Compute gradients if self._layer_idx_gradients < 0: @@ -306,15 +306,15 @@ def loss_gradient(self, x, y, **kwargs): x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=False) # Convert the inputs to Tensors - inputs_t = torch.from_numpy(x_preprocessed).to(self._device) + inputs_t = torch.from_numpy(x_preprocessed).type(torch.FloatTensor).to(self._device) inputs_t.requires_grad = True # Convert the labels to Tensors - labels_t = torch.from_numpy(np.argmax(y_preprocessed, axis=1)).to(self._device) + labels_t = torch.from_numpy(np.argmax(y_preprocessed, axis=1)).type(torch.FloatTensor).to(self._device) # Compute the gradient and return model_outputs = self._model(inputs_t) - loss = self._loss(model_outputs[-1], labels_t) + loss = self._loss(model_outputs[-1], labels_t.long()) # Clean gradients self._model.zero_grad() @@ -383,7 +383,7 @@ def get_activations(self, x, layer, batch_size=128): begin, end = m * batch_size, min((m + 1) * batch_size, x_preprocessed.shape[0]) # Run prediction for the current batch - layer_output = self._model(torch.from_numpy(x_preprocessed[begin:end]).to(self._device))[layer_index] + layer_output = self._model(torch.from_numpy(x_preprocessed[begin:end]).type(torch.FloatTensor).to(self._device))[layer_index] results.append(layer_output.detach().cpu().numpy()) results = np.concatenate(results) From ca07569ab4ef12918d4588b5f409df33676420de Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 3 Mar 2020 17:56:20 +0900 Subject: [PATCH 13/91] updated --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 70aa7779cb..edd4dc9c66 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # NOTE -In this repository, we are modifying Adversarial Robustness 360 Toolbox (ART) v1.1. +In this repository, kztakemoto and [nquntan](https://github.com/nquntan) are modifying Adversarial Robustness 360 Toolbox (ART) v1.1. * Targeted universal adversarial perturbation ([Hirano and Takemoto, 2019](https://arxiv.org/abs/1911.06502)) added. -* Simple blackbox attack (SimBA based on pixel attacks; [Guo et al., 2019](https://arxiv.org/abs/1905.07121)) added. +* Simple blackbox attack (SimBA) based on pixel attacks; [Guo et al., 2019](https://arxiv.org/abs/1905.07121)) added. + * Random attack (``random``) version implmented. + * 2 attack types (``diag`` and ``perm``) added by [nquntan](https://github.com/nquntan) +* SimBA based on discrete cosine transform (DCT) added by [nquntan](https://github.com/nquntan) # Adversarial Robustness 360 Toolbox (ART) v1.1 From 67feb6a18b7f92a0becd565bf980b4be7c2f2ab5 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Mon, 25 May 2020 22:54:10 +0900 Subject: [PATCH 14/91] update --- art/attacks/evasion/universal_perturbation.py | 2 +- art/attacks/evasion/universal_simba_pixel.py | 52 ++++-- .../evasion/universal_simba_pixel_Koga.py | 165 ++++++++++++++++++ .../evasion/universal_simba_pixel_v00.py | 159 +++++++++++++++++ 4 files changed, 360 insertions(+), 18 deletions(-) create mode 100644 art/attacks/evasion/universal_simba_pixel_Koga.py create mode 100644 art/attacks/evasion/universal_simba_pixel_v00.py diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index c03e635fc3..d0969f1f1e 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -171,7 +171,7 @@ def generate(self, x, y=None, **kwargs): y_adv = np.argmax(self.classifier.predict(x_adv, batch_size=1), axis=1) fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances - random_noise = np.random.permutation(noise.reshape(-1)).reshape(noise.shape) + random_noise = np.random.permutation(noise.reshape(-1)).reshape(x[0][None, ...].shape) x_adv_random = x + random_noise y_adv_random = np.argmax(self.classifier.predict(x_adv_random, batch_size=1), axis=1) fooling_rate_random = np.sum(pred_y_max != y_adv_random) / nb_instances diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index b4a707809f..b53454cd08 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -36,7 +36,7 @@ class Universal_SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta', 'eps', 'norm','batch_size'] + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta', 'eps', 'norm', 'batch_size'] def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, norm=2, batch_size=1): """ @@ -60,7 +60,7 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, + str(classifier.__class__.__bases__) + '. ' ' The classifier needs to be a Neural Network and provide gradients.')) - params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta,'eps': eps, 'norm': norm, 'batch_size': batch_size} + params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} self.set_params(**params) def generate(self, x, y=None, **kwargs): @@ -75,7 +75,6 @@ def generate(self, x, y=None, **kwargs): :rtype: `np.ndarray` """ x = x.astype(ART_NUMPY_DTYPE) - noise = 0 nb_instances = x.shape[0] preds = self.classifier.predict(x, batch_size=self.batch_size) if y is None: @@ -90,49 +89,65 @@ def generate(self, x, y=None, **kwargs): clip_max = np.inf if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: clip_min, clip_max = self.classifier.clip_values + + #nb_samples = 50 fooling_rate = 0.0 nb_iter = 0 + noise = 0 while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: + #trainIdx = np.random.choice(range(nb_instances),nb_random_samples) + #x_sample = x[trainIdx] + #original_labels = original_labels_whole[trainIdx] + #current_labels = original_labels + + #preds = self.classifier.predict(x_sample + noise, batch_size=self.batch_size) + #last_probs = preds[(range(nb_random_samples),original_labels)] + diff = np.zeros(n_dims) diff[np.random.choice(range(n_dims))] = self.epsilon - left_preds = self.classifier.predict(np.clip(x + noise - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + left_noise = noise - diff.reshape(x[0][None, ...].shape) + left_noise = projection(left_noise, self.eps, self.norm) + + left_preds = self.classifier.predict(np.clip(x + left_noise, clip_min, clip_max), batch_size=self.batch_size) left_probs = left_preds[(range(nb_instances),original_labels)] - right_preds = self.classifier.predict(np.clip(x + noise + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + right_noise = noise + diff.reshape(x[0][None, ...].shape) + right_noise = projection(right_noise, self.eps, self.norm) + + right_preds = self.classifier.predict(np.clip(x + right_noise, clip_min, clip_max), batch_size=self.batch_size) right_probs = right_preds[(range(nb_instances),original_labels)] if np.sum(left_probs - last_probs) < 0.0: if np.sum(left_probs - right_probs) < 0.0: - # x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - noise -= diff.reshape(x[0][None, ...].shape) + #x = np.clip(x + left_noise, clip_min, clip_max) last_probs = left_probs + noise = left_noise current_labels = np.argmax(left_preds, axis=1) else: - # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - noise += diff.reshape(x[0][None, ...].shape) + #x = np.clip(x + right_noise, clip_min, clip_max) last_probs = right_probs + noise = right_noise current_labels = np.argmax(right_preds, axis=1) else: if np.sum(right_probs - last_probs) < 0.0: - # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - noise += diff.reshape(x[0][None, ...].shape) + #x = np.clip(x + right_noise, clip_min, clip_max) last_probs = right_probs + noise = right_noise current_labels = np.argmax(right_preds, axis=1) - - noise = projection(noise, self.eps, self.norm) # Compute the error rate fooling_rate = np.sum(original_labels != current_labels) / nb_instances nb_iter = nb_iter + 1 - if nb_iter % 50 == 0: - logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%%', nb_iter, 100 * fooling_rate) - logger.info('Nise ls norm %.2f', np.linalg.norm(noise.flatten(), ord=2)) + if nb_iter % 10 == 0: + val_norm = np.linalg.norm(noise.flatten(), ord=self.norm) + logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%% (L%d norm of noise: %.2f)', nb_iter, 100 * fooling_rate, self.norm, val_norm) logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) - return x + return x + noise + def set_params(self, **kwargs): """ @@ -159,6 +174,9 @@ def set_params(self, **kwargs): if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: raise ValueError("The desired accuracy must be in the range [0, 1].") + if not isinstance(self.eps, (float, int)) or self.eps <= 0: + raise ValueError("The eps coefficient must be a positive float.") + if self.batch_size <= 0: raise ValueError('The batch size `batch_size` has to be positive.') diff --git a/art/attacks/evasion/universal_simba_pixel_Koga.py b/art/attacks/evasion/universal_simba_pixel_Koga.py new file mode 100644 index 0000000000..b4a707809f --- /dev/null +++ b/art/attacks/evasion/universal_simba_pixel_Koga.py @@ -0,0 +1,165 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box attack `simba`. + +| Paper link: https://arxiv.org/abs/1905.07121 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np + +from art.config import ART_NUMPY_DTYPE +from art.classifiers.classifier import ClassifierGradients +from art.attacks.attack import EvasionAttack +from art.utils import compute_success +from art.utils import projection + +logger = logging.getLogger(__name__) + + +class Universal_SimBA_pixel(EvasionAttack): + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta', 'eps', 'norm','batch_size'] + + def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, norm=2, batch_size=1): + """ + Create a universal SimBA (pixel) attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + super(Universal_SimBA_pixel, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta,'eps': eps, 'norm': norm, 'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + noise = 0 + nb_instances = x.shape[0] + preds = self.classifier.predict(x, batch_size=self.batch_size) + if y is None: + y = np.argmax(preds, axis=1) + original_labels = y + current_labels = original_labels + last_probs = preds[(range(nb_instances),original_labels)] + + n_dims = np.prod(x[0].shape) + + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values + fooling_rate = 0.0 + nb_iter = 0 + while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[np.random.choice(range(n_dims))] = self.epsilon + + left_preds = self.classifier.predict(np.clip(x + noise - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + left_probs = left_preds[(range(nb_instances),original_labels)] + + right_preds = self.classifier.predict(np.clip(x + noise + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + right_probs = right_preds[(range(nb_instances),original_labels)] + + if np.sum(left_probs - last_probs) < 0.0: + if np.sum(left_probs - right_probs) < 0.0: + # x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + noise -= diff.reshape(x[0][None, ...].shape) + last_probs = left_probs + current_labels = np.argmax(left_preds, axis=1) + else: + # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + noise += diff.reshape(x[0][None, ...].shape) + last_probs = right_probs + current_labels = np.argmax(right_preds, axis=1) + else: + if np.sum(right_probs - last_probs) < 0.0: + # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + noise += diff.reshape(x[0][None, ...].shape) + last_probs = right_probs + current_labels = np.argmax(right_preds, axis=1) + + noise = projection(noise, self.eps, self.norm) + + # Compute the error rate + fooling_rate = np.sum(original_labels != current_labels) / nb_instances + + nb_iter = nb_iter + 1 + + if nb_iter % 50 == 0: + logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%%', nb_iter, 100 * fooling_rate) + logger.info('Nise ls norm %.2f', np.linalg.norm(noise.flatten(), ord=2)) + + logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) + return x + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + # Save attack-specific parameters + super(Universal_SimBA_pixel, self).set_params(**kwargs) + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: + raise ValueError("The desired accuracy must be in the range [0, 1].") + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + return True diff --git a/art/attacks/evasion/universal_simba_pixel_v00.py b/art/attacks/evasion/universal_simba_pixel_v00.py new file mode 100644 index 0000000000..4acdacea6d --- /dev/null +++ b/art/attacks/evasion/universal_simba_pixel_v00.py @@ -0,0 +1,159 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box attack `simba`. + +| Paper link: https://arxiv.org/abs/1905.07121 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np + +from art.config import ART_NUMPY_DTYPE +from art.classifiers.classifier import ClassifierGradients +from art.attacks.attack import EvasionAttack +from art.utils import compute_success + +logger = logging.getLogger(__name__) + + +class Universal_SimBA_pixel(EvasionAttack): + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta' ,'batch_size'] + + def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, batch_size=1): + """ + Create a universal SimBA (pixel) attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + super(Universal_SimBA_pixel, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta, 'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + nb_instances = x.shape[0] + preds = self.classifier.predict(x, batch_size=self.batch_size) + if y is None: + y = np.argmax(preds, axis=1) + original_labels = y + current_labels = original_labels + last_probs = preds[(range(nb_instances),original_labels)] + + n_dims = np.prod(x[0].shape) + + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values + + fooling_rate = 0.0 + nb_iter = 0 + while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[np.random.choice(range(n_dims))] = self.epsilon + + left_preds = self.classifier.predict(np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + left_probs = left_preds[(range(nb_instances),original_labels)] + + right_preds = self.classifier.predict(np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) + right_probs = right_preds[(range(nb_instances),original_labels)] + + if np.sum(left_probs - last_probs) < 0.0: + if np.sum(left_probs - right_probs) < 0.0: + x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + last_probs = left_probs + current_labels = np.argmax(left_preds, axis=1) + else: + x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + last_probs = right_probs + current_labels = np.argmax(right_preds, axis=1) + else: + if np.sum(right_probs - last_probs) < 0.0: + x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) + last_probs = right_probs + current_labels = np.argmax(right_preds, axis=1) + + # Compute the error rate + fooling_rate = np.sum(original_labels != current_labels) / nb_instances + + nb_iter = nb_iter + 1 + + if nb_iter % 50 == 0: + logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%%', nb_iter, 100 * fooling_rate) + + logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) + return x + + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + # Save attack-specific parameters + super(Universal_SimBA_pixel, self).set_params(**kwargs) + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: + raise ValueError("The desired accuracy must be in the range [0, 1].") + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + return True From 25d87b9fa0f49ea008dc350deb82e08f74bc9ca9 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 26 May 2020 12:01:14 +0900 Subject: [PATCH 15/91] update --- art/attacks/evasion/simba_dct.py | 3 +++ art/attacks/evasion/simba_pixel.py | 6 ++++++ art/attacks/evasion/universal_perturbation.py | 10 ++++++---- art/attacks/evasion/universal_simba_pixel.py | 12 ------------ 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/art/attacks/evasion/simba_dct.py b/art/attacks/evasion/simba_dct.py index fdfe1d23e6..23c6b99f69 100644 --- a/art/attacks/evasion/simba_dct.py +++ b/art/attacks/evasion/simba_dct.py @@ -82,6 +82,9 @@ def generate(self, x, y=None, **kwargs): last_prob = preds.reshape(-1)[original_label] n_dims = np.prod(x.shape) + if self.max_iter > n_dims: + self.max_iter = n_dims + logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less', n_dims) indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] trans = lambda z: self._block_idct(z, block_size=x.shape[2]) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 75eb0b36e5..7fdb9ba0ab 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -80,6 +80,12 @@ def generate(self, x, y=None, **kwargs): last_prob = preds.reshape(-1)[original_label] n_dims = np.prod(x.shape) + if self.order == "diag" or self.order == "perm": + if self.max_iter > n_dims: + self.max_iter = n_dims + logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less for `order` of %s', n_dims, self.order) + else: + actual_max_iter = self.max_iter if self.order == "diag": indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index d0969f1f1e..1f2f9d43d5 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -128,7 +128,7 @@ def generate(self, x, y=None, **kwargs): # Instantiate the middle attacker and get the predicted labels attacker = self._get_attack(self.attacker, self.attacker_params) - pred_y = self.classifier.predict(x, batch_size=1) + pred_y = self.classifier.predict(x, batch_size=self.batch_size) pred_y_max = np.argmax(pred_y, axis=1) # Start to generate the adversarial examples @@ -168,12 +168,14 @@ def generate(self, x, y=None, **kwargs): x_adv = np.clip(x_adv, clip_min, clip_max) # Compute the error rate - y_adv = np.argmax(self.classifier.predict(x_adv, batch_size=1), axis=1) + y_adv = np.argmax(self.classifier.predict(x_adv, batch_size=self.batch_size), axis=1) fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances - random_noise = np.random.permutation(noise.reshape(-1)).reshape(x[0][None, ...].shape) + # permutated noise + tmp_noise = x_adv[0] - x[0] + random_noise = np.random.permutation(tmp_noise.reshape(-1)).reshape(x[0][None, ...].shape) x_adv_random = x + random_noise - y_adv_random = np.argmax(self.classifier.predict(x_adv_random, batch_size=1), axis=1) + y_adv_random = np.argmax(self.classifier.predict(x_adv_random, batch_size=self.batch_size), axis=1) fooling_rate_random = np.sum(pred_y_max != y_adv_random) / nb_instances logger.info(f"iteration: {nb_iter}, norm_size: {np.linalg.norm(noise.flatten(), ord=2)}, fooling_rate: {fooling_rate}, random_fooling_rate:{fooling_rate_random}") diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index b53454cd08..d785c820be 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -90,19 +90,10 @@ def generate(self, x, y=None, **kwargs): if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: clip_min, clip_max = self.classifier.clip_values - #nb_samples = 50 fooling_rate = 0.0 nb_iter = 0 noise = 0 while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: - #trainIdx = np.random.choice(range(nb_instances),nb_random_samples) - #x_sample = x[trainIdx] - #original_labels = original_labels_whole[trainIdx] - #current_labels = original_labels - - #preds = self.classifier.predict(x_sample + noise, batch_size=self.batch_size) - #last_probs = preds[(range(nb_random_samples),original_labels)] - diff = np.zeros(n_dims) diff[np.random.choice(range(n_dims))] = self.epsilon @@ -120,18 +111,15 @@ def generate(self, x, y=None, **kwargs): if np.sum(left_probs - last_probs) < 0.0: if np.sum(left_probs - right_probs) < 0.0: - #x = np.clip(x + left_noise, clip_min, clip_max) last_probs = left_probs noise = left_noise current_labels = np.argmax(left_preds, axis=1) else: - #x = np.clip(x + right_noise, clip_min, clip_max) last_probs = right_probs noise = right_noise current_labels = np.argmax(right_preds, axis=1) else: if np.sum(right_probs - last_probs) < 0.0: - #x = np.clip(x + right_noise, clip_min, clip_max) last_probs = right_probs noise = right_noise current_labels = np.argmax(right_preds, axis=1) From 9b23de1ac0b71bfedb6e60faa8710398fad2638b Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 26 May 2020 12:16:25 +0900 Subject: [PATCH 16/91] update --- art/attacks/evasion/universal_perturbation.py | 4 ++-- test_requirements.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index 1f2f9d43d5..a1cb841777 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -172,8 +172,8 @@ def generate(self, x, y=None, **kwargs): fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances # permutated noise - tmp_noise = x_adv[0] - x[0] - random_noise = np.random.permutation(tmp_noise.reshape(-1)).reshape(x[0][None, ...].shape) + noise = x_adv[0] - x[0] + random_noise = np.random.permutation(noise.reshape(-1)).reshape(x[0][None, ...].shape) x_adv_random = x + random_noise y_adv_random = np.argmax(self.classifier.predict(x_adv_random, batch_size=self.batch_size), axis=1) fooling_rate_random = np.sum(pred_y_max != y_adv_random) / nb_instances diff --git a/test_requirements.txt b/test_requirements.txt index af7daa187f..a6a88a64e9 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -17,3 +17,4 @@ GPy scipy statsmodels +japanize_matplotlib From 53c6fbccc2718fedcb6c58ca115531e383ce041e Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 26 May 2020 14:43:28 +0900 Subject: [PATCH 17/91] update --- art/attacks/__init__.py | 1 + art/attacks/evasion/simba_dct.py | 14 ++ art/attacks/evasion/universal_simba_dct.py | 224 +++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 art/attacks/evasion/universal_simba_dct.py diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index 23919bc25f..c9ef13c958 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -11,6 +11,7 @@ from art.attacks.evasion.simba_pixel import SimBA_pixel from art.attacks.evasion.simba_dct import SimBA_dct from art.attacks.evasion.universal_simba_pixel import Universal_SimBA_pixel +from art.attacks.evasion.universal_simba_dct import Universal_SimBA_dct from art.attacks.evasion.elastic_net import ElasticNet from art.attacks.evasion.fast_gradient import FastGradientMethod from art.attacks.evasion.hclu import HighConfidenceLowUncertainty diff --git a/art/attacks/evasion/simba_dct.py b/art/attacks/evasion/simba_dct.py index 23c6b99f69..8265c77439 100644 --- a/art/attacks/evasion/simba_dct.py +++ b/art/attacks/evasion/simba_dct.py @@ -49,6 +49,10 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, freq_dim=4, stride=1, :type max_iter: `int` :param epsilon: Overshoot parameter. :type epsilon: `float` + :param freq_dim: dimensionality of 2D frequency space. + :type freq_dim: `int` + :param stride: stride for block order. + :type stride: `int` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` """ @@ -137,6 +141,10 @@ def set_params(self, **kwargs): :type max_iter: `int` :param epsilon: Overshoot parameter. :type epsilon: `float` + :param freq_dim: dimensionality of 2D frequency space. + :type freq_dim: `int` + :param stride: stride for block order. + :type stride: `int` :param batch_size: Internal size of batches on which adversarial samples are generated. :type batch_size: `int` """ @@ -151,6 +159,12 @@ def set_params(self, **kwargs): if self.batch_size <= 0: raise ValueError('The batch size `batch_size` has to be positive.') + + if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: + raise ValueError("The `stride` value must be a positive integer.") + + if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: + raise ValueError("The `freq_dim` value must be a positive integer.") return True diff --git a/art/attacks/evasion/universal_simba_dct.py b/art/attacks/evasion/universal_simba_dct.py new file mode 100644 index 0000000000..66a736d7a2 --- /dev/null +++ b/art/attacks/evasion/universal_simba_dct.py @@ -0,0 +1,224 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box attack `simba`. + +| Paper link: https://arxiv.org/abs/1905.07121 +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np +from scipy.fftpack import dct, idct + +from art.config import ART_NUMPY_DTYPE +from art.classifiers.classifier import ClassifierGradients +from art.attacks.attack import EvasionAttack +from art.utils import compute_success +from art.utils import projection + +logger = logging.getLogger(__name__) + + +class Universal_SimBA_dct(EvasionAttack): + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'freq_dim', 'stride', 'delta', 'eps', 'norm', 'batch_size'] + + def __init__(self, classifier, max_iter=3000, epsilon=0.1, freq_dim=4, stride=1, delta=0.1, eps=10.0, norm=2, batch_size=1): + """ + Create a universal SimBA (dct) attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param freq_dim: dimensionality of 2D frequency space. + :type freq_dim: `int` + :param stride: stride for block order. + :type stride: `int` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + super(Universal_SimBA_dct, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'max_iter': max_iter, 'epsilon': epsilon, 'freq_dim': freq_dim, 'stride': stride, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + nb_instances = x.shape[0] + preds = self.classifier.predict(x, batch_size=self.batch_size) + if y is None: + y = np.argmax(preds, axis=1) + original_labels = y + current_labels = original_labels + last_probs = preds[(range(nb_instances),original_labels)] + + n_dims = np.prod(x[0].shape) + if self.max_iter > n_dims: + self.max_iter = n_dims + logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less', n_dims) + + indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + trans = lambda z: self._block_idct(z, block_size=x.shape[2]) + + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values + + fooling_rate = 0.0 + nb_iter = 0 + noise = 0 + while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[indices[nb_iter]] = self.epsilon + + left_noise = noise - trans(diff.reshape(x[0][None, ...].shape)) + left_noise = projection(left_noise, self.eps, self.norm) + + left_preds = self.classifier.predict(np.clip(x + left_noise, clip_min, clip_max), batch_size=self.batch_size) + left_probs = left_preds[(range(nb_instances),original_labels)] + + right_noise = noise + trans(diff.reshape(x[0][None, ...].shape)) + right_noise = projection(right_noise, self.eps, self.norm) + + right_preds = self.classifier.predict(np.clip(x + right_noise, clip_min, clip_max), batch_size=self.batch_size) + right_probs = right_preds[(range(nb_instances),original_labels)] + + if np.sum(left_probs - last_probs) < 0.0: + if np.sum(left_probs - right_probs) < 0.0: + last_probs = left_probs + noise = left_noise + current_labels = np.argmax(left_preds, axis=1) + else: + last_probs = right_probs + noise = right_noise + current_labels = np.argmax(right_preds, axis=1) + else: + if np.sum(right_probs - last_probs) < 0.0: + last_probs = right_probs + noise = right_noise + current_labels = np.argmax(right_preds, axis=1) + + # Compute the error rate + fooling_rate = np.sum(original_labels != current_labels) / nb_instances + + nb_iter = nb_iter + 1 + + if nb_iter % 10 == 0: + val_norm = np.linalg.norm(noise.flatten(), ord=self.norm) + logger.info('Fooling rate of Universal SimBA (dct) attack at %d iterations: %.2f%% (L%d norm of noise: %.2f)', nb_iter, 100 * fooling_rate, self.norm, val_norm) + + logger.info('Final fooling rate of Universal SimBA (dct) attack: %.2f%%', 100 * fooling_rate) + return x + noise + + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param freq_dim: dimensionality of 2D frequency space. + :type freq_dim: `int` + :param stride: stride for block order. + :type stride: `int` + :param delta: desired accuracy + :type delta: `float` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + # Save attack-specific parameters + super(Universal_SimBA_dct, self).set_params(**kwargs) + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: + raise ValueError("The `stride` value must be a positive integer.") + + if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: + raise ValueError("The `freq_dim` value must be a positive integer.") + + if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: + raise ValueError("The desired accuracy must be in the range [0, 1].") + + if not isinstance(self.eps, (float, int)) or self.eps <= 0: + raise ValueError("The eps coefficient must be a positive float.") + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + return True + + def _block_order(self, img_size, channels, initial_size=28, stride=7): + order = np.zeros((channels , img_size , img_size)) + total_elems = channels * initial_size * initial_size + perm = np.random.permutation(total_elems) + order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) + for i in range(initial_size, img_size, stride): + num_elems = channels * (2 * stride * i + stride * stride) + perm = np.random.permutation(num_elems) + total_elems + num_first = channels * stride * (stride + i) + order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) + order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) + total_elems += num_elems + return order.reshape(1, -1).squeeze().argsort() + + # applies IDCT to each block of size block_size + def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): + z = np.zeros(x.shape) + num_blocks = int(x.shape[2] / block_size) + mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) + if type(ratio) != float: + for i in range(x.shape[0]): + mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 + else: + mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 + for i in range(num_blocks): + for j in range(num_blocks): + submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] + if masked: + submat = submat * mask + z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') + return z From e5167d764aecc720c1d76bc9fdb975ba6d4f8dc3 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Thu, 28 May 2020 08:59:27 +0900 Subject: [PATCH 18/91] update --- art/attacks/evasion/simba_dct.py | 7 ++++--- art/attacks/evasion/simba_pixel.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/art/attacks/evasion/simba_dct.py b/art/attacks/evasion/simba_dct.py index 8265c77439..7453549ac9 100644 --- a/art/attacks/evasion/simba_dct.py +++ b/art/attacks/evasion/simba_dct.py @@ -168,7 +168,7 @@ def set_params(self, **kwargs): return True - def _block_order(self, img_size, channels, initial_size=28, stride=7): + def _block_order(self, img_size, channels, initial_size=2, stride=1): order = np.zeros((channels , img_size , img_size)) total_elems = channels * initial_size * initial_size perm = np.random.permutation(total_elems) @@ -180,10 +180,11 @@ def _block_order(self, img_size, channels, initial_size=28, stride=7): order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) total_elems += num_elems - return order.reshape(1, -1).squeeze().argsort() + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() # applies IDCT to each block of size block_size def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): + x = x.transpose(0,3,1,2) z = np.zeros(x.shape) num_blocks = int(x.shape[2] / block_size) mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) @@ -198,4 +199,4 @@ def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): if masked: submat = submat * mask z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') - return z + return z.transpose(0,2,3,1) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 7fdb9ba0ab..9cf05c22c5 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -175,4 +175,4 @@ def diagonal_order(self, image_size, channels): order = np.zeros((channels, image_size, image_size)) for i in range(channels): order[i, :, :] = 3 * order_2d + i - return order.reshape(1, -1).squeeze().argsort() + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() From 81bbac98effd1e09a0db4df21c41e47467bf2038 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Thu, 28 May 2020 09:52:18 +0900 Subject: [PATCH 19/91] update --- art/attacks/evasion/simba_pixel.py | 5 ++ art/attacks/evasion/universal_simba_dct.py | 15 ++++-- art/attacks/evasion/universal_simba_pixel.py | 54 ++++++++++++++++++-- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 9cf05c22c5..67ebee19ad 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -47,6 +47,8 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, order='random', batch :type max_iter: `int` :param epsilon: Overshoot parameter. :type epsilon: `float` + :param order: The attack order + :type order: `str` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` """ @@ -159,6 +161,9 @@ def set_params(self, **kwargs): if self.batch_size <= 0: raise ValueError('The batch size `batch_size` has to be positive.') + + if self.order != "random" and self.order != "perm" and self.order != "diag": + raise ValueError('The attack `order` has to be `random`, `perm`, or `diag`') return True diff --git a/art/attacks/evasion/universal_simba_dct.py b/art/attacks/evasion/universal_simba_dct.py index 66a736d7a2..4d8f2b4a7f 100644 --- a/art/attacks/evasion/universal_simba_dct.py +++ b/art/attacks/evasion/universal_simba_dct.py @@ -55,6 +55,10 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, freq_dim=4, stride=1, :type stride: `int` :param delta: desired accuracy :type delta: `float` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 + :type norm: `int` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` """ @@ -162,6 +166,10 @@ def set_params(self, **kwargs): :type stride: `int` :param delta: desired accuracy :type delta: `float` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 + :type norm: `int` :param batch_size: Internal size of batches on which adversarial samples are generated. :type batch_size: `int` """ @@ -191,7 +199,7 @@ def set_params(self, **kwargs): return True - def _block_order(self, img_size, channels, initial_size=28, stride=7): + def _block_order(self, img_size, channels, initial_size=2, stride=1): order = np.zeros((channels , img_size , img_size)) total_elems = channels * initial_size * initial_size perm = np.random.permutation(total_elems) @@ -203,10 +211,11 @@ def _block_order(self, img_size, channels, initial_size=28, stride=7): order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) total_elems += num_elems - return order.reshape(1, -1).squeeze().argsort() + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() # applies IDCT to each block of size block_size def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): + x = x.transpose(0,3,1,2) z = np.zeros(x.shape) num_blocks = int(x.shape[2] / block_size) mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) @@ -221,4 +230,4 @@ def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): if masked: submat = submat * mask z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') - return z + return z.transpose(0,2,3,1) diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index d785c820be..729c7eeef6 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -36,9 +36,9 @@ class Universal_SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta', 'eps', 'norm', 'batch_size'] + attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'order', 'delta', 'eps', 'norm', 'batch_size'] - def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, norm=2, batch_size=1): + def __init__(self, classifier, max_iter=3000, epsilon=0.1, order='random', delta=0.1, eps=10.0, norm=2, batch_size=1): """ Create a universal SimBA (pixel) attack instance. @@ -48,8 +48,14 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, :type max_iter: `int` :param epsilon: Overshoot parameter. :type epsilon: `float` + :param order: The attack order + :type order: `str` :param delta: desired accuracy :type delta: `float` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 + :type norm: `int` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` """ @@ -60,7 +66,7 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, + str(classifier.__class__.__bases__) + '. ' ' The classifier needs to be a Neural Network and provide gradients.')) - params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} + params = {'max_iter': max_iter, 'epsilon': epsilon, 'order': order, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} self.set_params(**params) def generate(self, x, y=None, **kwargs): @@ -84,6 +90,17 @@ def generate(self, x, y=None, **kwargs): last_probs = preds[(range(nb_instances),original_labels)] n_dims = np.prod(x[0].shape) + if self.order == "diag" or self.order == "perm": + if self.max_iter > n_dims: + self.max_iter = n_dims + logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less for `order` of %s', n_dims, self.order) + else: + actual_max_iter = self.max_iter + + if self.order == "diag": + indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] + elif self.order == "perm": + indices = np.random.permutation(n_dims) clip_min = -np.inf clip_max = np.inf @@ -95,7 +112,12 @@ def generate(self, x, y=None, **kwargs): noise = 0 while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: diff = np.zeros(n_dims) - diff[np.random.choice(range(n_dims))] = self.epsilon + if self.order == "random": + diff[np.random.choice(range(n_dims))] = self.epsilon + elif self.order == "diag": + diff[indices[nb_iter]] = self.epsilon + elif self.order == "perm": + diff[indices[nb_iter]] = self.epsilon left_noise = noise - diff.reshape(x[0][None, ...].shape) left_noise = projection(left_noise, self.eps, self.norm) @@ -145,8 +167,14 @@ def set_params(self, **kwargs): :type max_iter: `int` :param epsilon: Overshoot parameter. :type epsilon: `float` + :param order: The attack order + :type order: `str` :param delta: desired accuracy :type delta: `float` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 + :type norm: `int` :param batch_size: Internal size of batches on which adversarial samples are generated. :type batch_size: `int` """ @@ -167,5 +195,23 @@ def set_params(self, **kwargs): if self.batch_size <= 0: raise ValueError('The batch size `batch_size` has to be positive.') + + if self.order != "random" and self.order != "perm" and self.order != "diag": + raise ValueError('The attack `order` has to be `random`, `perm`, or `diag`') return True + + def diagonal_order(self, image_size, channels): + x = np.arange(0, image_size).cumsum() + order = np.zeros((image_size, image_size)) + for i in range(image_size): + order[i, :(image_size - i)] = i + x[i:] + for i in range(1, image_size): + reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) + order[i, (image_size - i):] = image_size * image_size - 1 - reverse + if channels > 1: + order_2d = order + order = np.zeros((channels, image_size, image_size)) + for i in range(channels): + order[i, :, :] = 3 * order_2d + i + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() \ No newline at end of file From dd32943e215fa09e81e4ef5ede3314da2f63b7dd Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Thu, 28 May 2020 18:06:36 +0900 Subject: [PATCH 20/91] update --- .../evasion/universal_simba_pixel_Koga.py | 165 ------------------ .../evasion/universal_simba_pixel_v00.py | 159 ----------------- 2 files changed, 324 deletions(-) delete mode 100644 art/attacks/evasion/universal_simba_pixel_Koga.py delete mode 100644 art/attacks/evasion/universal_simba_pixel_v00.py diff --git a/art/attacks/evasion/universal_simba_pixel_Koga.py b/art/attacks/evasion/universal_simba_pixel_Koga.py deleted file mode 100644 index b4a707809f..0000000000 --- a/art/attacks/evasion/universal_simba_pixel_Koga.py +++ /dev/null @@ -1,165 +0,0 @@ -# MIT License -# -# Copyright (C) IBM Corporation 2018 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -""" -This module implements the black-box attack `simba`. - -| Paper link: https://arxiv.org/abs/1905.07121 -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -import numpy as np - -from art.config import ART_NUMPY_DTYPE -from art.classifiers.classifier import ClassifierGradients -from art.attacks.attack import EvasionAttack -from art.utils import compute_success -from art.utils import projection - -logger = logging.getLogger(__name__) - - -class Universal_SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta', 'eps', 'norm','batch_size'] - - def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, eps=10.0, norm=2, batch_size=1): - """ - Create a universal SimBA (pixel) attack instance. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param delta: desired accuracy - :type delta: `float` - :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` - """ - super(Universal_SimBA_pixel, self).__init__(classifier=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) - - params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta,'eps': eps, 'norm': norm, 'batch_size': batch_size} - self.set_params(**params) - - def generate(self, x, y=None, **kwargs): - """ - Generate adversarial samples and return them in an array. - - :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` - :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` - :return: An array holding the adversarial examples. - :rtype: `np.ndarray` - """ - x = x.astype(ART_NUMPY_DTYPE) - noise = 0 - nb_instances = x.shape[0] - preds = self.classifier.predict(x, batch_size=self.batch_size) - if y is None: - y = np.argmax(preds, axis=1) - original_labels = y - current_labels = original_labels - last_probs = preds[(range(nb_instances),original_labels)] - - n_dims = np.prod(x[0].shape) - - clip_min = -np.inf - clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values - fooling_rate = 0.0 - nb_iter = 0 - while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: - diff = np.zeros(n_dims) - diff[np.random.choice(range(n_dims))] = self.epsilon - - left_preds = self.classifier.predict(np.clip(x + noise - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) - left_probs = left_preds[(range(nb_instances),original_labels)] - - right_preds = self.classifier.predict(np.clip(x + noise + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) - right_probs = right_preds[(range(nb_instances),original_labels)] - - if np.sum(left_probs - last_probs) < 0.0: - if np.sum(left_probs - right_probs) < 0.0: - # x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - noise -= diff.reshape(x[0][None, ...].shape) - last_probs = left_probs - current_labels = np.argmax(left_preds, axis=1) - else: - # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - noise += diff.reshape(x[0][None, ...].shape) - last_probs = right_probs - current_labels = np.argmax(right_preds, axis=1) - else: - if np.sum(right_probs - last_probs) < 0.0: - # x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - noise += diff.reshape(x[0][None, ...].shape) - last_probs = right_probs - current_labels = np.argmax(right_preds, axis=1) - - noise = projection(noise, self.eps, self.norm) - - # Compute the error rate - fooling_rate = np.sum(original_labels != current_labels) / nb_instances - - nb_iter = nb_iter + 1 - - if nb_iter % 50 == 0: - logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%%', nb_iter, 100 * fooling_rate) - logger.info('Nise ls norm %.2f', np.linalg.norm(noise.flatten(), ord=2)) - - logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) - return x - - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param delta: desired accuracy - :type delta: `float` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - # Save attack-specific parameters - super(Universal_SimBA_pixel, self).set_params(**kwargs) - - if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: - raise ValueError("The number of iterations must be a positive integer.") - - if self.epsilon < 0: - raise ValueError("The overshoot parameter must not be negative.") - - if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: - raise ValueError("The desired accuracy must be in the range [0, 1].") - - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') - - return True diff --git a/art/attacks/evasion/universal_simba_pixel_v00.py b/art/attacks/evasion/universal_simba_pixel_v00.py deleted file mode 100644 index 4acdacea6d..0000000000 --- a/art/attacks/evasion/universal_simba_pixel_v00.py +++ /dev/null @@ -1,159 +0,0 @@ -# MIT License -# -# Copyright (C) IBM Corporation 2018 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -""" -This module implements the black-box attack `simba`. - -| Paper link: https://arxiv.org/abs/1905.07121 -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -import numpy as np - -from art.config import ART_NUMPY_DTYPE -from art.classifiers.classifier import ClassifierGradients -from art.attacks.attack import EvasionAttack -from art.utils import compute_success - -logger = logging.getLogger(__name__) - - -class Universal_SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'delta' ,'batch_size'] - - def __init__(self, classifier, max_iter=3000, epsilon=0.1, delta=0.1, batch_size=1): - """ - Create a universal SimBA (pixel) attack instance. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param delta: desired accuracy - :type delta: `float` - :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` - """ - super(Universal_SimBA_pixel, self).__init__(classifier=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) - - params = {'max_iter': max_iter, 'epsilon': epsilon, 'delta': delta, 'batch_size': batch_size} - self.set_params(**params) - - def generate(self, x, y=None, **kwargs): - """ - Generate adversarial samples and return them in an array. - - :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` - :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` - :return: An array holding the adversarial examples. - :rtype: `np.ndarray` - """ - x = x.astype(ART_NUMPY_DTYPE) - nb_instances = x.shape[0] - preds = self.classifier.predict(x, batch_size=self.batch_size) - if y is None: - y = np.argmax(preds, axis=1) - original_labels = y - current_labels = original_labels - last_probs = preds[(range(nb_instances),original_labels)] - - n_dims = np.prod(x[0].shape) - - clip_min = -np.inf - clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values - - fooling_rate = 0.0 - nb_iter = 0 - while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: - diff = np.zeros(n_dims) - diff[np.random.choice(range(n_dims))] = self.epsilon - - left_preds = self.classifier.predict(np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) - left_probs = left_preds[(range(nb_instances),original_labels)] - - right_preds = self.classifier.predict(np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max), batch_size=self.batch_size) - right_probs = right_preds[(range(nb_instances),original_labels)] - - if np.sum(left_probs - last_probs) < 0.0: - if np.sum(left_probs - right_probs) < 0.0: - x = np.clip(x - diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - last_probs = left_probs - current_labels = np.argmax(left_preds, axis=1) - else: - x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - last_probs = right_probs - current_labels = np.argmax(right_preds, axis=1) - else: - if np.sum(right_probs - last_probs) < 0.0: - x = np.clip(x + diff.reshape(x[0][None, ...].shape), clip_min, clip_max) - last_probs = right_probs - current_labels = np.argmax(right_preds, axis=1) - - # Compute the error rate - fooling_rate = np.sum(original_labels != current_labels) / nb_instances - - nb_iter = nb_iter + 1 - - if nb_iter % 50 == 0: - logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%%', nb_iter, 100 * fooling_rate) - - logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) - return x - - - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param delta: desired accuracy - :type delta: `float` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - # Save attack-specific parameters - super(Universal_SimBA_pixel, self).set_params(**kwargs) - - if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: - raise ValueError("The number of iterations must be a positive integer.") - - if self.epsilon < 0: - raise ValueError("The overshoot parameter must not be negative.") - - if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: - raise ValueError("The desired accuracy must be in the range [0, 1].") - - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') - - return True From 32a9e66e0e45b1a6119fcb0e1ed5f74c0d5dab15 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 29 May 2020 09:30:39 +0900 Subject: [PATCH 21/91] update --- art/attacks/evasion/simba_dct.py | 9 ++++++--- art/attacks/evasion/simba_pixel.py | 16 +++++++++------- art/attacks/evasion/universal_simba_dct.py | 9 ++++++--- art/attacks/evasion/universal_simba_pixel.py | 18 ++++++++++-------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/art/attacks/evasion/simba_dct.py b/art/attacks/evasion/simba_dct.py index 7453549ac9..ed8899e876 100644 --- a/art/attacks/evasion/simba_dct.py +++ b/art/attacks/evasion/simba_dct.py @@ -86,11 +86,14 @@ def generate(self, x, y=None, **kwargs): last_prob = preds.reshape(-1)[original_label] n_dims = np.prod(x.shape) - if self.max_iter > n_dims: - self.max_iter = n_dims - logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less', n_dims) indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) + trans = lambda z: self._block_idct(z, block_size=x.shape[2]) clip_min = -np.inf diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 67ebee19ad..9aa48f52d5 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -82,17 +82,19 @@ def generate(self, x, y=None, **kwargs): last_prob = preds.reshape(-1)[original_label] n_dims = np.prod(x.shape) - if self.order == "diag" or self.order == "perm": - if self.max_iter > n_dims: - self.max_iter = n_dims - logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less for `order` of %s', n_dims, self.order) - else: - actual_max_iter = self.max_iter if self.order == "diag": indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] elif self.order == "perm": - indices = np.random.permutation(n_dims) + indices = np.random.permutation(n_dims)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + if self.order == "diag": + tmp_indices = self.diagonal_order(x.shape[2], 3) + elif self.order == "perm": + tmp_indices = np.random.permutation(n_dims) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) clip_min = -np.inf clip_max = np.inf diff --git a/art/attacks/evasion/universal_simba_dct.py b/art/attacks/evasion/universal_simba_dct.py index 4d8f2b4a7f..eab02dacc4 100644 --- a/art/attacks/evasion/universal_simba_dct.py +++ b/art/attacks/evasion/universal_simba_dct.py @@ -93,11 +93,14 @@ def generate(self, x, y=None, **kwargs): last_probs = preds[(range(nb_instances),original_labels)] n_dims = np.prod(x[0].shape) - if self.max_iter > n_dims: - self.max_iter = n_dims - logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less', n_dims) indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) + trans = lambda z: self._block_idct(z, block_size=x.shape[2]) clip_min = -np.inf diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index 729c7eeef6..f10a052827 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -90,17 +90,19 @@ def generate(self, x, y=None, **kwargs): last_probs = preds[(range(nb_instances),original_labels)] n_dims = np.prod(x[0].shape) - if self.order == "diag" or self.order == "perm": - if self.max_iter > n_dims: - self.max_iter = n_dims - logger.info('`max_iter` was reset to %d because it needs to be #pixels x #channels or less for `order` of %s', n_dims, self.order) - else: - actual_max_iter = self.max_iter - + if self.order == "diag": indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] elif self.order == "perm": - indices = np.random.permutation(n_dims) + indices = np.random.permutation(n_dims)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + if self.order == "diag": + tmp_indices = self.diagonal_order(x.shape[2], 3) + elif self.order == "perm": + tmp_indices = np.random.permutation(n_dims) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) clip_min = -np.inf clip_max = np.inf From 19163336aa64d47a8061ba5a2f72596d07663d8f Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 29 May 2020 10:00:29 +0900 Subject: [PATCH 22/91] update --- art/attacks/evasion/simba_pixel.py | 19 ++++++++++--------- art/attacks/evasion/universal_simba_pixel.py | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index 9aa48f52d5..feb7b5dcf7 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -83,18 +83,19 @@ def generate(self, x, y=None, **kwargs): n_dims = np.prod(x.shape) - if self.order == "diag": - indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] - elif self.order == "perm": - indices = np.random.permutation(n_dims)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: + if self.order != "random": if self.order == "diag": - tmp_indices = self.diagonal_order(x.shape[2], 3) + indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] elif self.order == "perm": - tmp_indices = np.random.permutation(n_dims) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices = np.random.permutation(n_dims)[:self.max_iter] indices_size = len(indices) + while indices_size < self.max_iter: + if self.order == "diag": + tmp_indices = self.diagonal_order(x.shape[2], 3) + elif self.order == "perm": + tmp_indices = np.random.permutation(n_dims) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) clip_min = -np.inf clip_max = np.inf diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py index f10a052827..f3385d9350 100644 --- a/art/attacks/evasion/universal_simba_pixel.py +++ b/art/attacks/evasion/universal_simba_pixel.py @@ -91,18 +91,19 @@ def generate(self, x, y=None, **kwargs): n_dims = np.prod(x[0].shape) - if self.order == "diag": - indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] - elif self.order == "perm": - indices = np.random.permutation(n_dims)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: + if self.order != "random": if self.order == "diag": - tmp_indices = self.diagonal_order(x.shape[2], 3) + indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] elif self.order == "perm": - tmp_indices = np.random.permutation(n_dims) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices = np.random.permutation(n_dims)[:self.max_iter] indices_size = len(indices) + while indices_size < self.max_iter: + if self.order == "diag": + tmp_indices = self.diagonal_order(x.shape[2], 3) + elif self.order == "perm": + tmp_indices = np.random.permutation(n_dims) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) clip_min = -np.inf clip_max = np.inf From 78e332f1752046a631c15ffa91c26c1fad5603f4 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sat, 30 May 2020 00:29:23 +0900 Subject: [PATCH 23/91] update --- art/attacks/__init__.py | 1 + art/attacks/evasion/simba.py | 264 +++++++++++++++++++++++++++++ art/attacks/evasion/simba_pixel.py | 2 +- 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 art/attacks/evasion/simba.py diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index c9ef13c958..bcf615c096 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -10,6 +10,7 @@ from art.attacks.evasion.deepfool import DeepFool from art.attacks.evasion.simba_pixel import SimBA_pixel from art.attacks.evasion.simba_dct import SimBA_dct +from art.attacks.evasion.simba import SimBA from art.attacks.evasion.universal_simba_pixel import Universal_SimBA_pixel from art.attacks.evasion.universal_simba_dct import Universal_SimBA_dct from art.attacks.evasion.elastic_net import ElasticNet diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py new file mode 100644 index 0000000000..0da4c5ad7d --- /dev/null +++ b/art/attacks/evasion/simba.py @@ -0,0 +1,264 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box attack `simba`. + +| Paper link: https://arxiv.org/abs/1905.07121 +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import logging + +import numpy as np +from scipy.fftpack import dct, idct + +from art.attacks.attack import EvasionAttack +from art.classifiers.classifier import ClassifierGradients +from art.config import ART_NUMPY_DTYPE +from art.utils import compute_success + +logger = logging.getLogger(__name__) + + +class SimBA(EvasionAttack): + attack_params = EvasionAttack.attack_params + ['attack', 'max_iter', 'epsilon', 'order', 'freq_dim', 'stride', 'targeted', 'batch_size',] + + def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsilon=0.1, freq_dim=4, stride=1, targeted=-999, batch_size=1): + """ + Create a SimBA (dct) attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param attack: attack type: pixel (px) or DCT (dct) attacks + :type attack: `str` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param order: order of pixel attacks: random or diagonal (diag) + :type order: `str` + :param freq_dim: dimensionality of 2D frequency space (DCT). + :type freq_dim: `int` + :param stride: stride for block order (DCT). + :type stride: `int` + :param targeted: targeted class (index) (non-targeted if negative) + :type stride: `int` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + super(SimBA, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'attack': attack, 'max_iter': max_iter, 'epsilon': epsilon, 'order': order, 'freq_dim': freq_dim, 'stride': stride, 'targeted': targeted ,'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + preds = self.classifier.predict(x, batch_size=self.batch_size) + if y is None: + y = np.argmax(preds, axis=1) + original_label = y[0] + current_label = original_label + last_prob = preds.reshape(-1)[original_label] + + n_dims = np.prod(x.shape) + + if self.attack == 'px': + if self.order == 'diag': + indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] + elif self.order == 'random': + indices = np.random.permutation(n_dims)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + if self.order == 'diag': + tmp_indices = self.diagonal_order(x.shape[2], 3) + elif self.order == 'random': + tmp_indices = np.random.permutation(n_dims) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) + elif self.attack == 'dct': + indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) + trans = lambda z: self._block_idct(z, block_size=x.shape[2]) + + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values + + nb_iter = 0 + while original_label == current_label and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[indices[nb_iter]] = self.epsilon + + if self.attack == 'dct': + left_preds = self.classifier.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) + elif self.attack == 'px': + left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + left_prob = left_preds.reshape(-1)[original_label] + + if self.attack == 'dct': + right_preds = self.classifier.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) + elif self.attack == 'px': + right_preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + right_prob = right_preds.reshape(-1)[original_label] + + if left_prob < last_prob: + if left_prob < right_prob: + if self.attack == 'dct': + x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) + last_prob = left_prob + current_label = np.argmax(left_preds, axis=1)[0] + else: + if self.attack == 'dct': + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] + else: + if right_prob < last_prob: + if self.attack == 'dct': + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] + + nb_iter = nb_iter + 1 + + if nb_iter < self.max_iter: + logger.info('SimBA (%s) attack succeed', self.attack) + else: + logger.info('SimBA (%s) attack failed', self.attack) + + return x + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param attack: attack type: pixel (px) or DCT (dct) attacks + :type attack: `str` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param order: order of pixel attacks + :type order: `str` + :param freq_dim: dimensionality of 2D frequency space (DCT). + :type freq_dim: `int` + :param stride: stride for block order (DCT). + :type stride: `int` + :param batch_size: Batch size (but, batch process unavailable in this implementation) + :type batch_size: `int` + """ + # Save attack-specific parameters + super(SimBA, self).set_params(**kwargs) + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: + raise ValueError("The `stride` value must be a positive integer.") + + if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: + raise ValueError("The `freq_dim` value must be a positive integer.") + + if self.order != 'random' and self.order != 'diag': + raise ValueError('The order of pixel attacks has to be `random` or `diag`.') + + if self.attack != 'px' and self.order != 'dct': + raise ValueError('The attack type has to be `px` or `dct`.') + + return True + + def _block_order(self, img_size, channels, initial_size=2, stride=1): + order = np.zeros((channels , img_size , img_size)) + total_elems = channels * initial_size * initial_size + perm = np.random.permutation(total_elems) + order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) + for i in range(initial_size, img_size, stride): + num_elems = channels * (2 * stride * i + stride * stride) + perm = np.random.permutation(num_elems) + total_elems + num_first = channels * stride * (stride + i) + order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) + order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) + total_elems += num_elems + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() + + # applies IDCT to each block of size block_size + def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): + x = x.transpose(0,3,1,2) + z = np.zeros(x.shape) + num_blocks = int(x.shape[2] / block_size) + mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) + if type(ratio) != float: + for i in range(x.shape[0]): + mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 + else: + mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 + for i in range(num_blocks): + for j in range(num_blocks): + submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] + if masked: + submat = submat * mask + z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') + return z.transpose(0,2,3,1) + + def diagonal_order(self, image_size, channels): + x = np.arange(0, image_size).cumsum() + order = np.zeros((image_size, image_size)) + for i in range(image_size): + order[i, :(image_size - i)] = i + x[i:] + for i in range(1, image_size): + reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) + order[i, (image_size - i):] = image_size * image_size - 1 - reverse + if channels > 1: + order_2d = order + order = np.zeros((channels, image_size, image_size)) + for i in range(channels): + order[i, :, :] = 3 * order_2d + i + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py index feb7b5dcf7..56c35654a6 100644 --- a/art/attacks/evasion/simba_pixel.py +++ b/art/attacks/evasion/simba_pixel.py @@ -47,7 +47,7 @@ def __init__(self, classifier, max_iter=3000, epsilon=0.1, order='random', batch :type max_iter: `int` :param epsilon: Overshoot parameter. :type epsilon: `float` - :param order: The attack order + :param order: order of pixel attacks :type order: `str` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` From 8bb44075e335b94a76a9cd7ebaca67d741264852 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sat, 30 May 2020 00:35:11 +0900 Subject: [PATCH 24/91] update --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 0da4c5ad7d..4dd108390c 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -210,7 +210,7 @@ def set_params(self, **kwargs): if self.order != 'random' and self.order != 'diag': raise ValueError('The order of pixel attacks has to be `random` or `diag`.') - if self.attack != 'px' and self.order != 'dct': + if self.attack != 'px' and self.attack != 'dct': raise ValueError('The attack type has to be `px` or `dct`.') return True From 2a2ef26df577c1fcea69b7d998d6c685851650c8 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sat, 30 May 2020 00:46:32 +0900 Subject: [PATCH 25/91] update --- art/attacks/evasion/simba.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 4dd108390c..a689856c87 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -85,6 +85,8 @@ def generate(self, x, y=None, **kwargs): """ x = x.astype(ART_NUMPY_DTYPE) preds = self.classifier.predict(x, batch_size=self.batch_size) + + # Todo: targeted version if y is None: y = np.argmax(preds, axis=1) original_label = y[0] From 62587847a7fc723cfa340ed53277f928dfc05e8b Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 31 May 2020 17:05:45 +0900 Subject: [PATCH 26/91] update --- art/attacks/evasion/simba.py | 25 ++++++++++++++----- art/attacks/evasion/universal_perturbation.py | 22 ++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index a689856c87..1dba581394 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -39,7 +39,7 @@ class SimBA(EvasionAttack): attack_params = EvasionAttack.attack_params + ['attack', 'max_iter', 'epsilon', 'order', 'freq_dim', 'stride', 'targeted', 'batch_size',] - def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsilon=0.1, freq_dim=4, stride=1, targeted=-999, batch_size=1): + def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsilon=0.1, freq_dim=4, stride=1, targeted=False, batch_size=1): """ Create a SimBA (dct) attack instance. @@ -57,8 +57,8 @@ def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsi :type freq_dim: `int` :param stride: stride for block order (DCT). :type stride: `int` - :param targeted: targeted class (index) (non-targeted if negative) - :type stride: `int` + :param targeted: targeted attacks + :type targeted: `bool` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` """ @@ -86,10 +86,18 @@ def generate(self, x, y=None, **kwargs): x = x.astype(ART_NUMPY_DTYPE) preds = self.classifier.predict(x, batch_size=self.batch_size) - # Todo: targeted version + #Note: need further implementation for targeted attacks if y is None: - y = np.argmax(preds, axis=1) - original_label = y[0] + if self.targeted == True: + raise ValueError('Target labels `y` need to be provided for a targeted attack.') + else: + # Use model predictions as correct outputs + logger.info('Using the model prediction as the correct label for SimBA.') + y_i = np.argmax(preds, axis=1) + else: + y_i = np.argmax(y, axis=1) + + original_label = y_i[0] current_label = original_label last_prob = preds.reshape(-1)[original_label] @@ -188,6 +196,8 @@ def set_params(self, **kwargs): :type freq_dim: `int` :param stride: stride for block order (DCT). :type stride: `int` + :param targeted: targeted attacks + :type targeted: `bool` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` """ @@ -214,6 +224,9 @@ def set_params(self, **kwargs): if self.attack != 'px' and self.attack != 'dct': raise ValueError('The attack type has to be `px` or `dct`.') + + if not isinstance(targeted, (int)) or (targeted != 0 and targeted != 1): + raise ValueError('`targeted` has to be a logical value.') return True diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index a1cb841777..a2c8086f16 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -58,6 +58,7 @@ class UniversalPerturbation(EvasionAttack): attacks_dict = {'carlini': 'art.attacks.evasion.carlini.CarliniL2Method', 'carlini_inf': 'art.attacks.evasion.carlini.CarliniLInfMethod', 'deepfool': 'art.attacks.evasion.deepfool.DeepFool', + 'simba': 'art.attacks.evasion.simba.SimBA', 'simba_px': 'art.attacks.evasion.simba_pixel.SimBA_pixel', 'simba_dct': 'art.attacks.evasion.simba_dct.SimBA_dct', 'ead': 'art.attacks.evasion.elastic_net.ElasticNet', @@ -76,7 +77,7 @@ def __init__(self, classifier, attacker='deepfool', attacker_params=None, delta= :param classifier: A trained classifier. :type classifier: :class:`.Classifier` :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'carlini', 'carlini_inf', - 'deepfool', 'simba_px', 'fgsm', 'bim', 'pgd', 'margin', 'ead', 'newtonfool', 'jsma', 'vat'. + 'deepfool', 'simba', 'fgsm', 'bim', 'pgd', 'margin', 'ead', 'newtonfool', 'jsma', 'vat'. :type attacker: `str` :param attacker_params: Parameters specific to the adversarial attack. If this parameter is not specified, the default parameters of the chosen attack will be used. @@ -128,8 +129,10 @@ def generate(self, x, y=None, **kwargs): # Instantiate the middle attacker and get the predicted labels attacker = self._get_attack(self.attacker, self.attacker_params) - pred_y = self.classifier.predict(x, batch_size=self.batch_size) - pred_y_max = np.argmax(pred_y, axis=1) + if y is None: + logger.info('Using model predictions as the correct labels for UAP.') + y = self.classifier.predict(x, batch_size=self.batch_size) + correct_y_max = np.argmax(y, axis=1) # Start to generate the adversarial examples nb_iter = 0 @@ -142,14 +145,11 @@ def generate(self, x, y=None, **kwargs): x_i = ex[None, ...] current_label = np.argmax(self.classifier.predict(x_i + noise)[0]) - original_label = np.argmax(pred_y[rnd_idx][j]) + original_label = correct_y_max[rnd_idx[j]] if current_label == original_label: # Compute adversarial perturbation - if y is None: - adv_xi = attacker.generate(x_i + noise) - else: - adv_xi = attacker.generate(x_i + noise, y=np.array([original_label])) + adv_xi = attacker.generate(x_i + noise) new_label = np.argmax(self.classifier.predict(adv_xi)[0]) @@ -169,16 +169,16 @@ def generate(self, x, y=None, **kwargs): # Compute the error rate y_adv = np.argmax(self.classifier.predict(x_adv, batch_size=self.batch_size), axis=1) - fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances + fooling_rate = np.sum(correct_y_max != y_adv) / nb_instances # permutated noise noise = x_adv[0] - x[0] random_noise = np.random.permutation(noise.reshape(-1)).reshape(x[0][None, ...].shape) x_adv_random = x + random_noise y_adv_random = np.argmax(self.classifier.predict(x_adv_random, batch_size=self.batch_size), axis=1) - fooling_rate_random = np.sum(pred_y_max != y_adv_random) / nb_instances + fooling_rate_random = np.sum(correct_y_max != y_adv_random) / nb_instances - logger.info(f"iteration: {nb_iter}, norm_size: {np.linalg.norm(noise.flatten(), ord=2)}, fooling_rate: {fooling_rate}, random_fooling_rate:{fooling_rate_random}") + logger.info(f"iteration: {nb_iter}, norm_size: {np.linalg.norm(noise.flatten(), ord=self.norm)}, fooling_rate: {fooling_rate}, random_fooling_rate:{fooling_rate_random}") self.fooling_rate = fooling_rate self.converged = nb_iter < self.max_iter From 6002ca63bcd70d10d4369a1bca9043a2fba261ba Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 31 May 2020 17:16:47 +0900 Subject: [PATCH 27/91] update --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 1dba581394..ddd68a137c 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -225,7 +225,7 @@ def set_params(self, **kwargs): if self.attack != 'px' and self.attack != 'dct': raise ValueError('The attack type has to be `px` or `dct`.') - if not isinstance(targeted, (int)) or (targeted != 0 and targeted != 1): + if not isinstance(self.targeted, (int)) or (self.targeted != 0 and self.targeted != 1): raise ValueError('`targeted` has to be a logical value.') return True From ae012ba7d6bff9990555f2ff9f4fddb0f0de2a8b Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 31 May 2020 19:19:52 +0900 Subject: [PATCH 28/91] update --- art/attacks/evasion/simba_dct.py | 205 ------------------ art/attacks/evasion/simba_pixel.py | 186 ---------------- art/attacks/evasion/universal_perturbation.py | 2 - 3 files changed, 393 deletions(-) delete mode 100644 art/attacks/evasion/simba_dct.py delete mode 100644 art/attacks/evasion/simba_pixel.py diff --git a/art/attacks/evasion/simba_dct.py b/art/attacks/evasion/simba_dct.py deleted file mode 100644 index ed8899e876..0000000000 --- a/art/attacks/evasion/simba_dct.py +++ /dev/null @@ -1,205 +0,0 @@ -# MIT License -# -# Copyright (C) IBM Corporation 2018 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -""" -This module implements the black-box attack `simba`. - -| Paper link: https://arxiv.org/abs/1905.07121 -""" -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import logging - -import numpy as np -from scipy.fftpack import dct, idct - -from art.attacks.attack import EvasionAttack -from art.classifiers.classifier import ClassifierGradients -from art.config import ART_NUMPY_DTYPE -from art.utils import compute_success - -logger = logging.getLogger(__name__) - - -class SimBA_dct(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'batch_size', 'freq_dim', 'stride'] - - def __init__(self, classifier, max_iter=3000, epsilon=0.1, freq_dim=4, stride=1, batch_size=1): - """ - Create a SimBA (dct) attack instance. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param freq_dim: dimensionality of 2D frequency space. - :type freq_dim: `int` - :param stride: stride for block order. - :type stride: `int` - :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` - """ - super(SimBA_dct, self).__init__(classifier=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) - - params = {'max_iter': max_iter, 'epsilon': epsilon, 'freq_dim': freq_dim, 'stride': stride, 'batch_size': batch_size} - self.set_params(**params) - - def generate(self, x, y=None, **kwargs): - """ - Generate adversarial samples and return them in an array. - - :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` - :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` - :return: An array holding the adversarial examples. - :rtype: `np.ndarray` - """ - x = x.astype(ART_NUMPY_DTYPE) - preds = self.classifier.predict(x, batch_size=self.batch_size) - if y is None: - y = np.argmax(preds, axis=1) - original_label = y[0] - current_label = original_label - last_prob = preds.reshape(-1)[original_label] - - n_dims = np.prod(x.shape) - - indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: - tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] - indices_size = len(indices) - - trans = lambda z: self._block_idct(z, block_size=x.shape[2]) - - clip_min = -np.inf - clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values - - nb_iter = 0 - while original_label == current_label and nb_iter < self.max_iter: - diff = np.zeros(n_dims) - diff[indices[nb_iter]] = self.epsilon - - left_preds = self.classifier.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) - left_prob = left_preds.reshape(-1)[original_label] - - right_preds = self.classifier.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) - right_prob = right_preds.reshape(-1)[original_label] - - if left_prob < last_prob: - if left_prob < right_prob: - x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) - last_prob = left_prob - current_label = np.argmax(left_preds, axis=1)[0] - else: - x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - last_prob = right_prob - current_label = np.argmax(right_preds, axis=1)[0] - else: - if right_prob < last_prob: - x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - last_prob = right_prob - current_label = np.argmax(right_preds, axis=1)[0] - - nb_iter = nb_iter + 1 - - if nb_iter < self.max_iter: - logger.info('SimBA (dct) attack succeed') - else: - logger.info('SimBA (dct) attack failed') - - return x - - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param freq_dim: dimensionality of 2D frequency space. - :type freq_dim: `int` - :param stride: stride for block order. - :type stride: `int` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - # Save attack-specific parameters - super(SimBA_dct, self).set_params(**kwargs) - - if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: - raise ValueError("The number of iterations must be a positive integer.") - - if self.epsilon < 0: - raise ValueError("The overshoot parameter must not be negative.") - - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') - - if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: - raise ValueError("The `stride` value must be a positive integer.") - - if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: - raise ValueError("The `freq_dim` value must be a positive integer.") - - return True - - def _block_order(self, img_size, channels, initial_size=2, stride=1): - order = np.zeros((channels , img_size , img_size)) - total_elems = channels * initial_size * initial_size - perm = np.random.permutation(total_elems) - order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) - for i in range(initial_size, img_size, stride): - num_elems = channels * (2 * stride * i + stride * stride) - perm = np.random.permutation(num_elems) + total_elems - num_first = channels * stride * (stride + i) - order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) - order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) - total_elems += num_elems - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() - - # applies IDCT to each block of size block_size - def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): - x = x.transpose(0,3,1,2) - z = np.zeros(x.shape) - num_blocks = int(x.shape[2] / block_size) - mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) - if type(ratio) != float: - for i in range(x.shape[0]): - mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 - else: - mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 - for i in range(num_blocks): - for j in range(num_blocks): - submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] - if masked: - submat = submat * mask - z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') - return z.transpose(0,2,3,1) diff --git a/art/attacks/evasion/simba_pixel.py b/art/attacks/evasion/simba_pixel.py deleted file mode 100644 index 56c35654a6..0000000000 --- a/art/attacks/evasion/simba_pixel.py +++ /dev/null @@ -1,186 +0,0 @@ -# MIT License -# -# Copyright (C) IBM Corporation 2018 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -""" -This module implements the black-box attack `simba`. - -| Paper link: https://arxiv.org/abs/1905.07121 -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -import numpy as np - -from art.config import ART_NUMPY_DTYPE -from art.classifiers.classifier import ClassifierGradients -from art.attacks.attack import EvasionAttack -from art.utils import compute_success - -logger = logging.getLogger(__name__) - - -class SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'order', 'batch_size'] - - def __init__(self, classifier, max_iter=3000, epsilon=0.1, order='random', batch_size=1): - """ - Create a SimBA (pixel) attack instance. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param order: order of pixel attacks - :type order: `str` - :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` - """ - super(SimBA_pixel, self).__init__(classifier=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) - - params = {'max_iter': max_iter, 'epsilon': epsilon, 'order': order, 'batch_size': batch_size} - self.set_params(**params) - - def generate(self, x, y=None, **kwargs): - """ - Generate adversarial samples and return them in an array. - - :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` - :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` - :return: An array holding the adversarial examples. - :rtype: `np.ndarray` - """ - x = x.astype(ART_NUMPY_DTYPE) - preds = self.classifier.predict(x, batch_size=self.batch_size) - if y is None: - y = np.argmax(preds, axis=1) - original_label = y[0] - current_label = original_label - last_prob = preds.reshape(-1)[original_label] - - n_dims = np.prod(x.shape) - - if self.order != "random": - if self.order == "diag": - indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] - elif self.order == "perm": - indices = np.random.permutation(n_dims)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: - if self.order == "diag": - tmp_indices = self.diagonal_order(x.shape[2], 3) - elif self.order == "perm": - tmp_indices = np.random.permutation(n_dims) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] - indices_size = len(indices) - - clip_min = -np.inf - clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values - - nb_iter = 0 - while original_label == current_label and nb_iter < self.max_iter: - diff = np.zeros(n_dims) - if self.order == "random": - diff[np.random.choice(range(n_dims))] = self.epsilon - elif self.order == "diag": - diff[indices[nb_iter]] = self.epsilon - elif self.order == "perm": - diff[indices[nb_iter]] = self.epsilon - - left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - left_prob = left_preds.reshape(-1)[original_label] - - right_preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - right_prob = right_preds.reshape(-1)[original_label] - - if left_prob < last_prob: - if left_prob < right_prob: - x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) - last_prob = left_prob - current_label = np.argmax(left_preds, axis=1)[0] - else: - x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) - last_prob = right_prob - current_label = np.argmax(right_preds, axis=1)[0] - else: - if right_prob < last_prob: - x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) - last_prob = right_prob - current_label = np.argmax(right_preds, axis=1)[0] - - nb_iter = nb_iter + 1 - - if nb_iter < self.max_iter: - logger.info('SimBA (pixel) attack succeed') - else: - logger.info('SimBA (pixel) attack failed') - - return x - - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - # Save attack-specific parameters - super(SimBA_pixel, self).set_params(**kwargs) - - if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: - raise ValueError("The number of iterations must be a positive integer.") - - if self.epsilon < 0: - raise ValueError("The overshoot parameter must not be negative.") - - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') - - if self.order != "random" and self.order != "perm" and self.order != "diag": - raise ValueError('The attack `order` has to be `random`, `perm`, or `diag`') - - return True - - def diagonal_order(self, image_size, channels): - x = np.arange(0, image_size).cumsum() - order = np.zeros((image_size, image_size)) - for i in range(image_size): - order[i, :(image_size - i)] = i + x[i:] - for i in range(1, image_size): - reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) - order[i, (image_size - i):] = image_size * image_size - 1 - reverse - if channels > 1: - order_2d = order - order = np.zeros((channels, image_size, image_size)) - for i in range(channels): - order[i, :, :] = 3 * order_2d + i - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index a2c8086f16..d8f9be3449 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -59,8 +59,6 @@ class UniversalPerturbation(EvasionAttack): 'carlini_inf': 'art.attacks.evasion.carlini.CarliniLInfMethod', 'deepfool': 'art.attacks.evasion.deepfool.DeepFool', 'simba': 'art.attacks.evasion.simba.SimBA', - 'simba_px': 'art.attacks.evasion.simba_pixel.SimBA_pixel', - 'simba_dct': 'art.attacks.evasion.simba_dct.SimBA_dct', 'ead': 'art.attacks.evasion.elastic_net.ElasticNet', 'fgsm': 'art.attacks.evasion.fast_gradient.FastGradientMethod', 'bim': 'art.attacks.evasion.iterative_method.BasicIterativeMethod', From 5c37a792e600fe96a1fbdb8d4b2e3b2f9bb4eb0a Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 31 May 2020 22:41:14 +0900 Subject: [PATCH 29/91] update --- art/attacks/evasion/simba.py | 100 ++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index ddd68a137c..0a0060c957 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -97,9 +97,9 @@ def generate(self, x, y=None, **kwargs): else: y_i = np.argmax(y, axis=1) - original_label = y_i[0] - current_label = original_label - last_prob = preds.reshape(-1)[original_label] + desired_label = y_i[0] + current_label = np.argmax(preds, axis=1)[0] + last_prob = preds.reshape(-1)[desired_label] n_dims = np.prod(x.shape) @@ -130,8 +130,16 @@ def generate(self, x, y=None, **kwargs): if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: clip_min, clip_max = self.classifier.clip_values + term_flag = 1 + if self.targeted == True: + if desired_label != current_label: + term_flag = 0 + else: + if desired_label == current_label: + term_flag = 0 + nb_iter = 0 - while original_label == current_label and nb_iter < self.max_iter: + while term_flag == 0 and nb_iter < self.max_iter: diff = np.zeros(n_dims) diff[indices[nb_iter]] = self.epsilon @@ -139,44 +147,76 @@ def generate(self, x, y=None, **kwargs): left_preds = self.classifier.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) elif self.attack == 'px': left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - left_prob = left_preds.reshape(-1)[original_label] + left_prob = left_preds.reshape(-1)[desired_label if self.attack == 'dct': right_preds = self.classifier.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) elif self.attack == 'px': right_preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - right_prob = right_preds.reshape(-1)[original_label] - - if left_prob < last_prob: - if left_prob < right_prob: - if self.attack == 'dct': - x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': - x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) - last_prob = left_prob - current_label = np.argmax(left_preds, axis=1)[0] + right_prob = right_preds.reshape(-1)[desired_label] + + if self.targeted == True: + if left_prob > last_prob: + if left_prob > right_prob: + if self.attack == 'dct': + x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) + last_prob = left_prob + current_label = np.argmax(left_preds, axis=1)[0] + else: + if self.attack == 'dct': + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] else: - if self.attack == 'dct': - x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': - x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) - last_prob = right_prob - current_label = np.argmax(right_preds, axis=1)[0] + if right_prob > last_prob: + if self.attack == 'dct': + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] else: - if right_prob < last_prob: - if self.attack == 'dct': - x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': - x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) - last_prob = right_prob - current_label = np.argmax(right_preds, axis=1)[0] + if left_prob < last_prob: + if left_prob < right_prob: + if self.attack == 'dct': + x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) + last_prob = left_prob + current_label = np.argmax(left_preds, axis=1)[0] + else: + if self.attack == 'dct': + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] + else: + if right_prob < last_prob: + if self.attack == 'dct': + x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) + elif self.attack == 'px': + x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) + last_prob = right_prob + current_label = np.argmax(right_preds, axis=1)[0] + if self.targeted == True: + if desired_label == current_label: + term_flag = 1 + else: + if desired_label != current_label: + term_flag = 1 + nb_iter = nb_iter + 1 if nb_iter < self.max_iter: - logger.info('SimBA (%s) attack succeed', self.attack) + logger.info('SimBA (%s) %s attack succeed', self.attack, ['non-targeted', 'targeted'][self.targeted]) else: - logger.info('SimBA (%s) attack failed', self.attack) + logger.info('SimBA (%s) %s attack failed', self.attack, ['non-targeted', 'targeted'][self.targeted]) return x From b517959c5f4f294cfab6786566fc673d9cfb37e2 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 31 May 2020 22:51:33 +0900 Subject: [PATCH 30/91] update --- art/attacks/evasion/simba.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 0a0060c957..46c2579d4d 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -147,8 +147,8 @@ def generate(self, x, y=None, **kwargs): left_preds = self.classifier.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) elif self.attack == 'px': left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) - left_prob = left_preds.reshape(-1)[desired_label - + left_prob = left_preds.reshape(-1)[desired_label] + if self.attack == 'dct': right_preds = self.classifier.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) elif self.attack == 'px': From f0bbf6b143e220166514b57bea90fe1e71501732 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Sun, 31 May 2020 23:19:11 +0900 Subject: [PATCH 31/91] update --- art/attacks/evasion/simba.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 46c2579d4d..fcc80ae989 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -57,7 +57,7 @@ def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsi :type freq_dim: `int` :param stride: stride for block order (DCT). :type stride: `int` - :param targeted: targeted attacks + :param targeted: perform targeted attack :type targeted: `bool` :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` @@ -148,7 +148,7 @@ def generate(self, x, y=None, **kwargs): elif self.attack == 'px': left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) left_prob = left_preds.reshape(-1)[desired_label] - + if self.attack == 'dct': right_preds = self.classifier.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) elif self.attack == 'px': @@ -250,8 +250,8 @@ def set_params(self, **kwargs): if self.epsilon < 0: raise ValueError("The overshoot parameter must not be negative.") - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') + if self.batch_size != 1: + raise ValueError('The batch size `batch_size` has to be 1 in this implementation.') if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: raise ValueError("The `stride` value must be a positive integer.") From f4b326dd7e410f99edb1e198e4f42c4baeb69e68 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Mon, 1 Jun 2020 18:23:05 +0900 Subject: [PATCH 32/91] update --- art/attacks/evasion/targeted_universal_perturbation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index d5a539aeea..67f314c6fb 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -42,7 +42,8 @@ class TargetedUniversalPerturbation(EvasionAttack): | Paper link: https://arxiv.org/abs/1911.06502 """ attacks_dict = { - 'fgsm': 'art.attacks.evasion.fast_gradient.FastGradientMethod' + 'fgsm': 'art.attacks.evasion.fast_gradient.FastGradientMethod', + 'simba': 'art.attacks.evasion.simba.SimBA' } attack_params = EvasionAttack.attack_params + ['attacker', 'attacker_params', 'delta', 'max_iter', 'eps', 'norm'] From bb46bfe47cf5ffbd3913f709e6629da55fa261fe Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 2 Jun 2020 00:07:24 +0900 Subject: [PATCH 33/91] update --- art/attacks/__init__.py | 3 +- art/attacks/evasion/simba.py | 1 - art/attacks/evasion/universal_simba.py | 323 +++++++++++++++++++++++++ 3 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 art/attacks/evasion/universal_simba.py diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index bcf615c096..6fefd42be2 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -8,9 +8,8 @@ from art.attacks.evasion.carlini import CarliniL2Method, CarliniLInfMethod from art.attacks.evasion.decision_tree_attack import DecisionTreeAttack from art.attacks.evasion.deepfool import DeepFool -from art.attacks.evasion.simba_pixel import SimBA_pixel -from art.attacks.evasion.simba_dct import SimBA_dct from art.attacks.evasion.simba import SimBA +from art.attacks.evasion.universal_simba import Universal_SimBA from art.attacks.evasion.universal_simba_pixel import Universal_SimBA_pixel from art.attacks.evasion.universal_simba_dct import Universal_SimBA_dct from art.attacks.evasion.elastic_net import ElasticNet diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index fcc80ae989..7b1ee552d3 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -86,7 +86,6 @@ def generate(self, x, y=None, **kwargs): x = x.astype(ART_NUMPY_DTYPE) preds = self.classifier.predict(x, batch_size=self.batch_size) - #Note: need further implementation for targeted attacks if y is None: if self.targeted == True: raise ValueError('Target labels `y` need to be provided for a targeted attack.') diff --git a/art/attacks/evasion/universal_simba.py b/art/attacks/evasion/universal_simba.py new file mode 100644 index 0000000000..1b5434547c --- /dev/null +++ b/art/attacks/evasion/universal_simba.py @@ -0,0 +1,323 @@ +# MIT License +# +# Copyright (C) IBM Corporation 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module implements the black-box universal attack `simba`. + +""" +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging + +import numpy as np +from scipy.fftpack import dct, idct + +from art.config import ART_NUMPY_DTYPE +from art.classifiers.classifier import ClassifierGradients +from art.attacks.attack import EvasionAttack +from art.utils import compute_success +from art.utils import projection + +logger = logging.getLogger(__name__) + + +class Universal_SimBA(EvasionAttack): + attack_params = EvasionAttack.attack_params + ['attack', 'max_iter', 'epsilon', 'order', 'freq_dim', 'stride', 'targeted', 'delta', 'eps', 'norm', 'batch_size'] + + def __init__(self, classifier, attack='dct', max_iter=3000, epsilon=0.2, order='random', freq_dim=4, stride=1, targeted=False, delta=0.01, eps=10.0, norm=2, batch_size=1): + """ + Create a universal SimBA attack instance. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param attack: attack type: pixel (px) or DCT (dct) attacks + :type attack: `str` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param order: order of pixel attacks: random or diagonal (diag) + :type order: `str` + :param freq_dim: dimensionality of 2D frequency space. + :type freq_dim: `int` + :param stride: stride for block order. + :type stride: `int` + :param targeted: perform targeted attack + :type targeted: `bool` + :param delta: desired accuracy + :type delta: `float` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 + :type norm: `int` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + super(Universal_SimBA, self).__init__(classifier=classifier) + if not isinstance(classifier, ClassifierGradients): + raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' + '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' + + str(classifier.__class__.__bases__) + '. ' + ' The classifier needs to be a Neural Network and provide gradients.')) + + params = {'max_iter': max_iter, 'epsilon': epsilon, 'freq_dim': freq_dim, 'stride': stride, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} + self.set_params(**params) + + def generate(self, x, y=None, **kwargs): + """ + Generate adversarial samples and return them in an array. + + :param x: An array with the original inputs to be attacked. + :type x: `np.ndarray` + :param y: An array with the original labels to be predicted. + :type y: `np.ndarray` + :return: An array holding the adversarial examples. + :rtype: `np.ndarray` + """ + x = x.astype(ART_NUMPY_DTYPE) + nb_instances = x.shape[0] + preds = self.classifier.predict(x, batch_size=self.batch_size) + + if y is None: + if self.targeted == True: + raise ValueError('Target labels `y` need to be provided for targeted attacks.') + else: + # Use model predictions as correct outputs + logger.info('Using the model predictions as the correct labels for SimBA.') + y_i = np.argmax(preds, axis=1) + else: + y_i = np.argmax(y, axis=1) + + desired_labels = y_i + current_labels = np.argmax(preds, axis=1) + last_probs = preds[(range(nb_instances), desired_labels)] + + n_dims = np.prod(x[0].shape) + + if self.attack == 'px': + if self.order == 'diag': + indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] + elif self.order == 'random': + indices = np.random.permutation(n_dims)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + if self.order == 'diag': + tmp_indices = self.diagonal_order(x.shape[2], 3) + elif self.order == 'random': + tmp_indices = np.random.permutation(n_dims) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) + elif self.attack == 'dct': + indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + indices_size = len(indices) + while indices_size < self.max_iter: + tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) + indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices_size = len(indices) + trans = lambda z: self._block_idct(z, block_size=x.shape[2]) + + clip_min = -np.inf + clip_max = np.inf + if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + clip_min, clip_max = self.classifier.clip_values + + success_rate = 0.0 + nb_iter = 0 + noise = 0 + while success_rate < 1. - self.delta and nb_iter < self.max_iter: + diff = np.zeros(n_dims) + diff[indices[nb_iter]] = self.epsilon + + if self.attack == 'dct': + left_noise = noise - trans(diff.reshape(x[0][None, ...].shape)) + left_noise = projection(left_noise, self.eps, self.norm) + elif self.attack == 'px': + left_noise = noise - diff.reshape(x[0][None, ...].shape) + left_noise = projection(left_noise, self.eps, self.norm) + + left_preds = self.classifier.predict(np.clip(x + left_noise, clip_min, clip_max), batch_size=self.batch_size) + left_probs = left_preds[(range(nb_instances), desired_labels)] + + if self.attack == 'dct': + right_noise = noise + trans(diff.reshape(x[0][None, ...].shape)) + right_noise = projection(right_noise, self.eps, self.norm) + elif self.attack == 'px': + right_noise = noise + diff.reshape(x[0][None, ...].shape) + right_noise = projection(right_noise, self.eps, self.norm) + + right_preds = self.classifier.predict(np.clip(x + right_noise, clip_min, clip_max), batch_size=self.batch_size) + right_probs = right_preds[(range(nb_instances), desired_labels)] + + # use (1 - 2 * int(self.targeted)) to shorten the code? + if self.targeted == True: + if np.sum(left_probs - last_probs) > 0.0: + if np.sum(left_probs - right_probs) > 0.0: + last_probs = left_probs + noise = left_noise + current_labels = np.argmax(left_preds, axis=1) + else: + last_probs = right_probs + noise = right_noise + current_labels = np.argmax(right_preds, axis=1) + else: + if np.sum(right_probs - last_probs) > 0.0: + last_probs = right_probs + noise = right_noise + current_labels = np.argmax(right_preds, axis=1) + else: + if np.sum(left_probs - last_probs) < 0.0: + if np.sum(left_probs - right_probs) < 0.0: + last_probs = left_probs + noise = left_noise + current_labels = np.argmax(left_preds, axis=1) + else: + last_probs = right_probs + noise = right_noise + current_labels = np.argmax(right_preds, axis=1) + else: + if np.sum(right_probs - last_probs) < 0.0: + last_probs = right_probs + noise = right_noise + current_labels = np.argmax(right_preds, axis=1) + + # Compute the error rate + if self.targeted == True: + success_rate = np.sum(desired_labels == current_labels) / nb_instances + else: + success_rate = np.sum(desired_labels != current_labels) / nb_instances + + nb_iter = nb_iter + 1 + + if nb_iter % 10 == 0: + val_norm = np.linalg.norm(noise.flatten(), ord=self.norm) + logger.info('Success rate of Universal SimBA (%s) %s attack at %d iterations: %.2f%% (L%d norm of noise: %.2f)', self.attack, ['non-targeted', 'targeted'][self.targeted], nb_iter, 100 * success_rate, self.norm, val_norm) + + logger.info('Final success rate of Universal SimBA (%s) %s attack: %.2f%%', self.attack, ['non-targeted', 'targeted'][self.targeted], 100 * success_rate) + return x + noise + + + def set_params(self, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. + + :param classifier: A trained classifier. + :type classifier: :class:`.Classifier` + :param attack: attack type: pixel (px) or DCT (dct) attacks + :type attack: `str` + :param max_iter: The maximum number of iterations. + :type max_iter: `int` + :param epsilon: Overshoot parameter. + :type epsilon: `float` + :param order: order of pixel attacks: random or diagonal (diag) + :type order: `str` + :param freq_dim: dimensionality of 2D frequency space. + :type freq_dim: `int` + :param stride: stride for block order. + :type stride: `int` + :param targeted: perform targeted attack + :type targeted: `bool` + :param delta: desired accuracy + :type delta: `float` + :param eps: Attack step size (input variation) + :type eps: `float` + :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 + :type norm: `int` + :param batch_size: Internal size of batches on which adversarial samples are generated. + :type batch_size: `int` + """ + # Save attack-specific parameters + super(Universal_SimBA, self).set_params(**kwargs) + + if self.attack != 'px' and self.attack != 'dct': + raise ValueError('The attack type has to be `px` or `dct`.') + + if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: + raise ValueError("The number of iterations must be a positive integer.") + + if self.epsilon < 0: + raise ValueError("The overshoot parameter must not be negative.") + + if self.order != 'random' and self.order != 'diag': + raise ValueError('The order of pixel attacks has to be `random` or `diag`.') + + if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: + raise ValueError("The `stride` value must be a positive integer.") + + if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: + raise ValueError("The `freq_dim` value must be a positive integer.") + + if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: + raise ValueError("The desired accuracy must be in the range [0, 1].") + + if not isinstance(self.eps, (float, int)) or self.eps <= 0: + raise ValueError("The eps coefficient must be a positive float.") + + if not isinstance(self.targeted, (int)) or (self.targeted != 0 and self.targeted != 1): + raise ValueError('`targeted` has to be a logical value.') + + if self.batch_size <= 0: + raise ValueError('The batch size `batch_size` has to be positive.') + + return True + + def _block_order(self, img_size, channels, initial_size=2, stride=1): + order = np.zeros((channels , img_size , img_size)) + total_elems = channels * initial_size * initial_size + perm = np.random.permutation(total_elems) + order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) + for i in range(initial_size, img_size, stride): + num_elems = channels * (2 * stride * i + stride * stride) + perm = np.random.permutation(num_elems) + total_elems + num_first = channels * stride * (stride + i) + order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) + order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) + total_elems += num_elems + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() + + # applies IDCT to each block of size block_size + def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): + x = x.transpose(0,3,1,2) + z = np.zeros(x.shape) + num_blocks = int(x.shape[2] / block_size) + mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) + if type(ratio) != float: + for i in range(x.shape[0]): + mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 + else: + mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 + for i in range(num_blocks): + for j in range(num_blocks): + submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] + if masked: + submat = submat * mask + z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') + return z.transpose(0,2,3,1) + + def diagonal_order(self, image_size, channels): + x = np.arange(0, image_size).cumsum() + order = np.zeros((image_size, image_size)) + for i in range(image_size): + order[i, :(image_size - i)] = i + x[i:] + for i in range(1, image_size): + reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) + order[i, (image_size - i):] = image_size * image_size - 1 - reverse + if channels > 1: + order_2d = order + order = np.zeros((channels, image_size, image_size)) + for i in range(channels): + order[i, :, :] = 3 * order_2d + i + return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() From b4289d10a71f4f05c3afb7097075c86f118baf7e Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 2 Jun 2020 08:17:59 +0900 Subject: [PATCH 34/91] update --- art/attacks/evasion/simba.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 7b1ee552d3..df3f637a36 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -87,7 +87,7 @@ def generate(self, x, y=None, **kwargs): preds = self.classifier.predict(x, batch_size=self.batch_size) if y is None: - if self.targeted == True: + if self.targeted: raise ValueError('Target labels `y` need to be provided for a targeted attack.') else: # Use model predictions as correct outputs @@ -154,7 +154,7 @@ def generate(self, x, y=None, **kwargs): right_preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) right_prob = right_preds.reshape(-1)[desired_label] - if self.targeted == True: + if self.targeted: if left_prob > last_prob: if left_prob > right_prob: if self.attack == 'dct': @@ -203,7 +203,7 @@ def generate(self, x, y=None, **kwargs): last_prob = right_prob current_label = np.argmax(right_preds, axis=1)[0] - if self.targeted == True: + if self.targeted: if desired_label == current_label: term_flag = 1 else: @@ -213,9 +213,9 @@ def generate(self, x, y=None, **kwargs): nb_iter = nb_iter + 1 if nb_iter < self.max_iter: - logger.info('SimBA (%s) %s attack succeed', self.attack, ['non-targeted', 'targeted'][self.targeted]) + logger.info('SimBA (%s) %s attack succeed', self.attack, ['non-targeted', 'targeted'][int(self.targeted)]) else: - logger.info('SimBA (%s) %s attack failed', self.attack, ['non-targeted', 'targeted'][self.targeted]) + logger.info('SimBA (%s) %s attack failed', self.attack, ['non-targeted', 'targeted'][int(self.targeted)]) return x From 304f634e30b58a877cfc583285454ca821a5df23 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 2 Jun 2020 10:23:22 +0900 Subject: [PATCH 35/91] update --- art/attacks/evasion/universal_simba.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/art/attacks/evasion/universal_simba.py b/art/attacks/evasion/universal_simba.py index 1b5434547c..eafa3fe960 100644 --- a/art/attacks/evasion/universal_simba.py +++ b/art/attacks/evasion/universal_simba.py @@ -74,7 +74,7 @@ def __init__(self, classifier, attack='dct', max_iter=3000, epsilon=0.2, order=' + str(classifier.__class__.__bases__) + '. ' ' The classifier needs to be a Neural Network and provide gradients.')) - params = {'max_iter': max_iter, 'epsilon': epsilon, 'freq_dim': freq_dim, 'stride': stride, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} + params = {'attack': attack, 'max_iter': max_iter, 'epsilon': epsilon, 'order':order, 'freq_dim': freq_dim, 'stride': stride, 'targeted': targeted, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} self.set_params(**params) def generate(self, x, y=None, **kwargs): @@ -93,7 +93,7 @@ def generate(self, x, y=None, **kwargs): preds = self.classifier.predict(x, batch_size=self.batch_size) if y is None: - if self.targeted == True: + if self.targeted: raise ValueError('Target labels `y` need to be provided for targeted attacks.') else: # Use model predictions as correct outputs @@ -163,7 +163,7 @@ def generate(self, x, y=None, **kwargs): right_probs = right_preds[(range(nb_instances), desired_labels)] # use (1 - 2 * int(self.targeted)) to shorten the code? - if self.targeted == True: + if self.targeted: if np.sum(left_probs - last_probs) > 0.0: if np.sum(left_probs - right_probs) > 0.0: last_probs = left_probs @@ -195,7 +195,7 @@ def generate(self, x, y=None, **kwargs): current_labels = np.argmax(right_preds, axis=1) # Compute the error rate - if self.targeted == True: + if self.targeted: success_rate = np.sum(desired_labels == current_labels) / nb_instances else: success_rate = np.sum(desired_labels != current_labels) / nb_instances From bf96ef52c3e6b34be1cc1cb20cd70787df509dc2 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 2 Jun 2020 10:26:04 +0900 Subject: [PATCH 36/91] update --- art/attacks/__init__.py | 2 - art/attacks/evasion/universal_simba_dct.py | 236 ------------------- art/attacks/evasion/universal_simba_pixel.py | 220 ----------------- 3 files changed, 458 deletions(-) delete mode 100644 art/attacks/evasion/universal_simba_dct.py delete mode 100644 art/attacks/evasion/universal_simba_pixel.py diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index 6fefd42be2..28b480c7a2 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -10,8 +10,6 @@ from art.attacks.evasion.deepfool import DeepFool from art.attacks.evasion.simba import SimBA from art.attacks.evasion.universal_simba import Universal_SimBA -from art.attacks.evasion.universal_simba_pixel import Universal_SimBA_pixel -from art.attacks.evasion.universal_simba_dct import Universal_SimBA_dct from art.attacks.evasion.elastic_net import ElasticNet from art.attacks.evasion.fast_gradient import FastGradientMethod from art.attacks.evasion.hclu import HighConfidenceLowUncertainty diff --git a/art/attacks/evasion/universal_simba_dct.py b/art/attacks/evasion/universal_simba_dct.py deleted file mode 100644 index eab02dacc4..0000000000 --- a/art/attacks/evasion/universal_simba_dct.py +++ /dev/null @@ -1,236 +0,0 @@ -# MIT License -# -# Copyright (C) IBM Corporation 2018 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -""" -This module implements the black-box attack `simba`. - -| Paper link: https://arxiv.org/abs/1905.07121 -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -import numpy as np -from scipy.fftpack import dct, idct - -from art.config import ART_NUMPY_DTYPE -from art.classifiers.classifier import ClassifierGradients -from art.attacks.attack import EvasionAttack -from art.utils import compute_success -from art.utils import projection - -logger = logging.getLogger(__name__) - - -class Universal_SimBA_dct(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'freq_dim', 'stride', 'delta', 'eps', 'norm', 'batch_size'] - - def __init__(self, classifier, max_iter=3000, epsilon=0.1, freq_dim=4, stride=1, delta=0.1, eps=10.0, norm=2, batch_size=1): - """ - Create a universal SimBA (dct) attack instance. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param freq_dim: dimensionality of 2D frequency space. - :type freq_dim: `int` - :param stride: stride for block order. - :type stride: `int` - :param delta: desired accuracy - :type delta: `float` - :param eps: Attack step size (input variation) - :type eps: `float` - :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 - :type norm: `int` - :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` - """ - super(Universal_SimBA_dct, self).__init__(classifier=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) - - params = {'max_iter': max_iter, 'epsilon': epsilon, 'freq_dim': freq_dim, 'stride': stride, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} - self.set_params(**params) - - def generate(self, x, y=None, **kwargs): - """ - Generate adversarial samples and return them in an array. - - :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` - :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` - :return: An array holding the adversarial examples. - :rtype: `np.ndarray` - """ - x = x.astype(ART_NUMPY_DTYPE) - nb_instances = x.shape[0] - preds = self.classifier.predict(x, batch_size=self.batch_size) - if y is None: - y = np.argmax(preds, axis=1) - original_labels = y - current_labels = original_labels - last_probs = preds[(range(nb_instances),original_labels)] - - n_dims = np.prod(x[0].shape) - - indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: - tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] - indices_size = len(indices) - - trans = lambda z: self._block_idct(z, block_size=x.shape[2]) - - clip_min = -np.inf - clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values - - fooling_rate = 0.0 - nb_iter = 0 - noise = 0 - while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: - diff = np.zeros(n_dims) - diff[indices[nb_iter]] = self.epsilon - - left_noise = noise - trans(diff.reshape(x[0][None, ...].shape)) - left_noise = projection(left_noise, self.eps, self.norm) - - left_preds = self.classifier.predict(np.clip(x + left_noise, clip_min, clip_max), batch_size=self.batch_size) - left_probs = left_preds[(range(nb_instances),original_labels)] - - right_noise = noise + trans(diff.reshape(x[0][None, ...].shape)) - right_noise = projection(right_noise, self.eps, self.norm) - - right_preds = self.classifier.predict(np.clip(x + right_noise, clip_min, clip_max), batch_size=self.batch_size) - right_probs = right_preds[(range(nb_instances),original_labels)] - - if np.sum(left_probs - last_probs) < 0.0: - if np.sum(left_probs - right_probs) < 0.0: - last_probs = left_probs - noise = left_noise - current_labels = np.argmax(left_preds, axis=1) - else: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - else: - if np.sum(right_probs - last_probs) < 0.0: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - - # Compute the error rate - fooling_rate = np.sum(original_labels != current_labels) / nb_instances - - nb_iter = nb_iter + 1 - - if nb_iter % 10 == 0: - val_norm = np.linalg.norm(noise.flatten(), ord=self.norm) - logger.info('Fooling rate of Universal SimBA (dct) attack at %d iterations: %.2f%% (L%d norm of noise: %.2f)', nb_iter, 100 * fooling_rate, self.norm, val_norm) - - logger.info('Final fooling rate of Universal SimBA (dct) attack: %.2f%%', 100 * fooling_rate) - return x + noise - - - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param freq_dim: dimensionality of 2D frequency space. - :type freq_dim: `int` - :param stride: stride for block order. - :type stride: `int` - :param delta: desired accuracy - :type delta: `float` - :param eps: Attack step size (input variation) - :type eps: `float` - :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 - :type norm: `int` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - # Save attack-specific parameters - super(Universal_SimBA_dct, self).set_params(**kwargs) - - if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: - raise ValueError("The number of iterations must be a positive integer.") - - if self.epsilon < 0: - raise ValueError("The overshoot parameter must not be negative.") - - if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: - raise ValueError("The `stride` value must be a positive integer.") - - if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: - raise ValueError("The `freq_dim` value must be a positive integer.") - - if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: - raise ValueError("The desired accuracy must be in the range [0, 1].") - - if not isinstance(self.eps, (float, int)) or self.eps <= 0: - raise ValueError("The eps coefficient must be a positive float.") - - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') - - return True - - def _block_order(self, img_size, channels, initial_size=2, stride=1): - order = np.zeros((channels , img_size , img_size)) - total_elems = channels * initial_size * initial_size - perm = np.random.permutation(total_elems) - order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) - for i in range(initial_size, img_size, stride): - num_elems = channels * (2 * stride * i + stride * stride) - perm = np.random.permutation(num_elems) + total_elems - num_first = channels * stride * (stride + i) - order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) - order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) - total_elems += num_elems - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() - - # applies IDCT to each block of size block_size - def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): - x = x.transpose(0,3,1,2) - z = np.zeros(x.shape) - num_blocks = int(x.shape[2] / block_size) - mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) - if type(ratio) != float: - for i in range(x.shape[0]): - mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 - else: - mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 - for i in range(num_blocks): - for j in range(num_blocks): - submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] - if masked: - submat = submat * mask - z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') - return z.transpose(0,2,3,1) diff --git a/art/attacks/evasion/universal_simba_pixel.py b/art/attacks/evasion/universal_simba_pixel.py deleted file mode 100644 index f3385d9350..0000000000 --- a/art/attacks/evasion/universal_simba_pixel.py +++ /dev/null @@ -1,220 +0,0 @@ -# MIT License -# -# Copyright (C) IBM Corporation 2018 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -""" -This module implements the black-box attack `simba`. - -| Paper link: https://arxiv.org/abs/1905.07121 -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -import numpy as np - -from art.config import ART_NUMPY_DTYPE -from art.classifiers.classifier import ClassifierGradients -from art.attacks.attack import EvasionAttack -from art.utils import compute_success -from art.utils import projection - -logger = logging.getLogger(__name__) - - -class Universal_SimBA_pixel(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['max_iter', 'epsilon', 'order', 'delta', 'eps', 'norm', 'batch_size'] - - def __init__(self, classifier, max_iter=3000, epsilon=0.1, order='random', delta=0.1, eps=10.0, norm=2, batch_size=1): - """ - Create a universal SimBA (pixel) attack instance. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param order: The attack order - :type order: `str` - :param delta: desired accuracy - :type delta: `float` - :param eps: Attack step size (input variation) - :type eps: `float` - :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 - :type norm: `int` - :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` - """ - super(Universal_SimBA_pixel, self).__init__(classifier=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) - - params = {'max_iter': max_iter, 'epsilon': epsilon, 'order': order, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} - self.set_params(**params) - - def generate(self, x, y=None, **kwargs): - """ - Generate adversarial samples and return them in an array. - - :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` - :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` - :return: An array holding the adversarial examples. - :rtype: `np.ndarray` - """ - x = x.astype(ART_NUMPY_DTYPE) - nb_instances = x.shape[0] - preds = self.classifier.predict(x, batch_size=self.batch_size) - if y is None: - y = np.argmax(preds, axis=1) - original_labels = y - current_labels = original_labels - last_probs = preds[(range(nb_instances),original_labels)] - - n_dims = np.prod(x[0].shape) - - if self.order != "random": - if self.order == "diag": - indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] - elif self.order == "perm": - indices = np.random.permutation(n_dims)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: - if self.order == "diag": - tmp_indices = self.diagonal_order(x.shape[2], 3) - elif self.order == "perm": - tmp_indices = np.random.permutation(n_dims) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] - indices_size = len(indices) - - clip_min = -np.inf - clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values - - fooling_rate = 0.0 - nb_iter = 0 - noise = 0 - while fooling_rate < 1. - self.delta and nb_iter < self.max_iter: - diff = np.zeros(n_dims) - if self.order == "random": - diff[np.random.choice(range(n_dims))] = self.epsilon - elif self.order == "diag": - diff[indices[nb_iter]] = self.epsilon - elif self.order == "perm": - diff[indices[nb_iter]] = self.epsilon - - left_noise = noise - diff.reshape(x[0][None, ...].shape) - left_noise = projection(left_noise, self.eps, self.norm) - - left_preds = self.classifier.predict(np.clip(x + left_noise, clip_min, clip_max), batch_size=self.batch_size) - left_probs = left_preds[(range(nb_instances),original_labels)] - - right_noise = noise + diff.reshape(x[0][None, ...].shape) - right_noise = projection(right_noise, self.eps, self.norm) - - right_preds = self.classifier.predict(np.clip(x + right_noise, clip_min, clip_max), batch_size=self.batch_size) - right_probs = right_preds[(range(nb_instances),original_labels)] - - if np.sum(left_probs - last_probs) < 0.0: - if np.sum(left_probs - right_probs) < 0.0: - last_probs = left_probs - noise = left_noise - current_labels = np.argmax(left_preds, axis=1) - else: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - else: - if np.sum(right_probs - last_probs) < 0.0: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - - # Compute the error rate - fooling_rate = np.sum(original_labels != current_labels) / nb_instances - - nb_iter = nb_iter + 1 - - if nb_iter % 10 == 0: - val_norm = np.linalg.norm(noise.flatten(), ord=self.norm) - logger.info('Fooling rate of Universal SimBA (pixel) attack at %d iterations: %.2f%% (L%d norm of noise: %.2f)', nb_iter, 100 * fooling_rate, self.norm, val_norm) - - logger.info('Final fooling rate of Universal SimBA (pixel) attack: %.2f%%', 100 * fooling_rate) - return x + noise - - - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param order: The attack order - :type order: `str` - :param delta: desired accuracy - :type delta: `float` - :param eps: Attack step size (input variation) - :type eps: `float` - :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 - :type norm: `int` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - # Save attack-specific parameters - super(Universal_SimBA_pixel, self).set_params(**kwargs) - - if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: - raise ValueError("The number of iterations must be a positive integer.") - - if self.epsilon < 0: - raise ValueError("The overshoot parameter must not be negative.") - - if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: - raise ValueError("The desired accuracy must be in the range [0, 1].") - - if not isinstance(self.eps, (float, int)) or self.eps <= 0: - raise ValueError("The eps coefficient must be a positive float.") - - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') - - if self.order != "random" and self.order != "perm" and self.order != "diag": - raise ValueError('The attack `order` has to be `random`, `perm`, or `diag`') - - return True - - def diagonal_order(self, image_size, channels): - x = np.arange(0, image_size).cumsum() - order = np.zeros((image_size, image_size)) - for i in range(image_size): - order[i, :(image_size - i)] = i + x[i:] - for i in range(1, image_size): - reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) - order[i, (image_size - i):] = image_size * image_size - 1 - reverse - if channels > 1: - order_2d = order - order = np.zeros((channels, image_size, image_size)) - for i in range(channels): - order[i, :, :] = 3 * order_2d + i - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() \ No newline at end of file From 77d8c0f8c254b7230788245ed36472a45652d5ce Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 2 Jun 2020 16:10:54 +0900 Subject: [PATCH 37/91] update --- art/attacks/evasion/targeted_universal_perturbation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 67f314c6fb..3c1572a311 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -119,7 +119,7 @@ def generate(self, x, y, **kwargs): y_i = ey[None, ...] current_label = np.argmax(self.classifier.predict(x_i + noise)[0]) - target_label = np.argmax(ey) + target_label = np.argmax(y_i) if current_label != target_label: # Compute adversarial perturbation @@ -146,11 +146,12 @@ def generate(self, x, y, **kwargs): fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances targeted_success_rate = np.sum(y_adv == np.argmax(y, axis=1)) / nb_instances + noise = x_adv[0] - x[0] self.fooling_rate = fooling_rate self.converged = nb_iter < self.max_iter self.noise = noise - logger.info('Success rate of universal perturbation attack: %.2f%%', 100 * fooling_rate) - logger.info('Targeted success rate of universal perturbation attack: %.2f%%', targeted_success_rate) + logger.info('Fooling rate of universal perturbation attack: %.2f%%', 100 * fooling_rate) + logger.info('Targeted success rate of universal perturbation attack: %.2f%%', 100 * targeted_success_rate) return x_adv From a3b2fe6b58effee1ff1d13924759b310d4b1a24b Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Wed, 3 Jun 2020 08:43:53 +0900 Subject: [PATCH 38/91] update --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index df3f637a36..89d9a18a7a 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -130,7 +130,7 @@ def generate(self, x, y=None, **kwargs): clip_min, clip_max = self.classifier.clip_values term_flag = 1 - if self.targeted == True: + if self.targeted: if desired_label != current_label: term_flag = 0 else: From b2c09fa6b5d15f392782442e7903ab01831eaf92 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Thu, 4 Jun 2020 09:29:03 +0900 Subject: [PATCH 39/91] update --- art/attacks/evasion/simba.py | 3 ++- art/attacks/evasion/universal_simba.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 89d9a18a7a..9b01cbcd95 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -153,7 +153,8 @@ def generate(self, x, y=None, **kwargs): elif self.attack == 'px': right_preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) right_prob = right_preds.reshape(-1)[desired_label] - + + # Use (2 * int(self.targeted) - 1) to shorten code? if self.targeted: if left_prob > last_prob: if left_prob > right_prob: diff --git a/art/attacks/evasion/universal_simba.py b/art/attacks/evasion/universal_simba.py index eafa3fe960..e4b0859cd4 100644 --- a/art/attacks/evasion/universal_simba.py +++ b/art/attacks/evasion/universal_simba.py @@ -162,7 +162,7 @@ def generate(self, x, y=None, **kwargs): right_preds = self.classifier.predict(np.clip(x + right_noise, clip_min, clip_max), batch_size=self.batch_size) right_probs = right_preds[(range(nb_instances), desired_labels)] - # use (1 - 2 * int(self.targeted)) to shorten the code? + # use Use (2 * int(self.targeted) - 1) to shorten code? if self.targeted: if np.sum(left_probs - last_probs) > 0.0: if np.sum(left_probs - right_probs) > 0.0: From d1d87427dd8643dc4b8014bd3b94009a79a35e55 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Tue, 9 Jun 2020 17:36:19 +0900 Subject: [PATCH 40/91] update --- art/attacks/evasion/universal_simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/universal_simba.py b/art/attacks/evasion/universal_simba.py index e4b0859cd4..9976a3ea39 100644 --- a/art/attacks/evasion/universal_simba.py +++ b/art/attacks/evasion/universal_simba.py @@ -204,7 +204,7 @@ def generate(self, x, y=None, **kwargs): if nb_iter % 10 == 0: val_norm = np.linalg.norm(noise.flatten(), ord=self.norm) - logger.info('Success rate of Universal SimBA (%s) %s attack at %d iterations: %.2f%% (L%d norm of noise: %.2f)', self.attack, ['non-targeted', 'targeted'][self.targeted], nb_iter, 100 * success_rate, self.norm, val_norm) + logger.info('Success rate of Universal SimBA (%s) %s attack at %d iterations: %.2f%% (L%s norm of noise: %.2f)', self.attack, ['non-targeted', 'targeted'][self.targeted], nb_iter, 100 * success_rate, str(self.norm), val_norm) logger.info('Final success rate of Universal SimBA (%s) %s attack: %.2f%%', self.attack, ['non-targeted', 'targeted'][self.targeted], 100 * success_rate) return x + noise From eb170a35d1ffa4e4b0b691e2cb88cbef527ea717 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Mon, 22 Jun 2020 07:43:49 +0900 Subject: [PATCH 41/91] deleted unrelatedfiles --- art/attacks/evasion/universal_simba.py | 323 ------------------------ examples/cacl_norm.py | 32 --- examples/cifar10_cnn_universal_simba.py | 102 -------- 3 files changed, 457 deletions(-) delete mode 100644 art/attacks/evasion/universal_simba.py delete mode 100644 examples/cacl_norm.py delete mode 100644 examples/cifar10_cnn_universal_simba.py diff --git a/art/attacks/evasion/universal_simba.py b/art/attacks/evasion/universal_simba.py deleted file mode 100644 index 9976a3ea39..0000000000 --- a/art/attacks/evasion/universal_simba.py +++ /dev/null @@ -1,323 +0,0 @@ -# MIT License -# -# Copyright (C) IBM Corporation 2018 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -""" -This module implements the black-box universal attack `simba`. - -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -import numpy as np -from scipy.fftpack import dct, idct - -from art.config import ART_NUMPY_DTYPE -from art.classifiers.classifier import ClassifierGradients -from art.attacks.attack import EvasionAttack -from art.utils import compute_success -from art.utils import projection - -logger = logging.getLogger(__name__) - - -class Universal_SimBA(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['attack', 'max_iter', 'epsilon', 'order', 'freq_dim', 'stride', 'targeted', 'delta', 'eps', 'norm', 'batch_size'] - - def __init__(self, classifier, attack='dct', max_iter=3000, epsilon=0.2, order='random', freq_dim=4, stride=1, targeted=False, delta=0.01, eps=10.0, norm=2, batch_size=1): - """ - Create a universal SimBA attack instance. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param attack: attack type: pixel (px) or DCT (dct) attacks - :type attack: `str` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param order: order of pixel attacks: random or diagonal (diag) - :type order: `str` - :param freq_dim: dimensionality of 2D frequency space. - :type freq_dim: `int` - :param stride: stride for block order. - :type stride: `int` - :param targeted: perform targeted attack - :type targeted: `bool` - :param delta: desired accuracy - :type delta: `float` - :param eps: Attack step size (input variation) - :type eps: `float` - :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 - :type norm: `int` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - super(Universal_SimBA, self).__init__(classifier=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) - - params = {'attack': attack, 'max_iter': max_iter, 'epsilon': epsilon, 'order':order, 'freq_dim': freq_dim, 'stride': stride, 'targeted': targeted, 'delta': delta, 'eps': eps, 'norm': norm, 'batch_size': batch_size} - self.set_params(**params) - - def generate(self, x, y=None, **kwargs): - """ - Generate adversarial samples and return them in an array. - - :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` - :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` - :return: An array holding the adversarial examples. - :rtype: `np.ndarray` - """ - x = x.astype(ART_NUMPY_DTYPE) - nb_instances = x.shape[0] - preds = self.classifier.predict(x, batch_size=self.batch_size) - - if y is None: - if self.targeted: - raise ValueError('Target labels `y` need to be provided for targeted attacks.') - else: - # Use model predictions as correct outputs - logger.info('Using the model predictions as the correct labels for SimBA.') - y_i = np.argmax(preds, axis=1) - else: - y_i = np.argmax(y, axis=1) - - desired_labels = y_i - current_labels = np.argmax(preds, axis=1) - last_probs = preds[(range(nb_instances), desired_labels)] - - n_dims = np.prod(x[0].shape) - - if self.attack == 'px': - if self.order == 'diag': - indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] - elif self.order == 'random': - indices = np.random.permutation(n_dims)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: - if self.order == 'diag': - tmp_indices = self.diagonal_order(x.shape[2], 3) - elif self.order == 'random': - tmp_indices = np.random.permutation(n_dims) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] - indices_size = len(indices) - elif self.attack == 'dct': - indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] - indices_size = len(indices) - while indices_size < self.max_iter: - tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] - indices_size = len(indices) - trans = lambda z: self._block_idct(z, block_size=x.shape[2]) - - clip_min = -np.inf - clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values - - success_rate = 0.0 - nb_iter = 0 - noise = 0 - while success_rate < 1. - self.delta and nb_iter < self.max_iter: - diff = np.zeros(n_dims) - diff[indices[nb_iter]] = self.epsilon - - if self.attack == 'dct': - left_noise = noise - trans(diff.reshape(x[0][None, ...].shape)) - left_noise = projection(left_noise, self.eps, self.norm) - elif self.attack == 'px': - left_noise = noise - diff.reshape(x[0][None, ...].shape) - left_noise = projection(left_noise, self.eps, self.norm) - - left_preds = self.classifier.predict(np.clip(x + left_noise, clip_min, clip_max), batch_size=self.batch_size) - left_probs = left_preds[(range(nb_instances), desired_labels)] - - if self.attack == 'dct': - right_noise = noise + trans(diff.reshape(x[0][None, ...].shape)) - right_noise = projection(right_noise, self.eps, self.norm) - elif self.attack == 'px': - right_noise = noise + diff.reshape(x[0][None, ...].shape) - right_noise = projection(right_noise, self.eps, self.norm) - - right_preds = self.classifier.predict(np.clip(x + right_noise, clip_min, clip_max), batch_size=self.batch_size) - right_probs = right_preds[(range(nb_instances), desired_labels)] - - # use Use (2 * int(self.targeted) - 1) to shorten code? - if self.targeted: - if np.sum(left_probs - last_probs) > 0.0: - if np.sum(left_probs - right_probs) > 0.0: - last_probs = left_probs - noise = left_noise - current_labels = np.argmax(left_preds, axis=1) - else: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - else: - if np.sum(right_probs - last_probs) > 0.0: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - else: - if np.sum(left_probs - last_probs) < 0.0: - if np.sum(left_probs - right_probs) < 0.0: - last_probs = left_probs - noise = left_noise - current_labels = np.argmax(left_preds, axis=1) - else: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - else: - if np.sum(right_probs - last_probs) < 0.0: - last_probs = right_probs - noise = right_noise - current_labels = np.argmax(right_preds, axis=1) - - # Compute the error rate - if self.targeted: - success_rate = np.sum(desired_labels == current_labels) / nb_instances - else: - success_rate = np.sum(desired_labels != current_labels) / nb_instances - - nb_iter = nb_iter + 1 - - if nb_iter % 10 == 0: - val_norm = np.linalg.norm(noise.flatten(), ord=self.norm) - logger.info('Success rate of Universal SimBA (%s) %s attack at %d iterations: %.2f%% (L%s norm of noise: %.2f)', self.attack, ['non-targeted', 'targeted'][self.targeted], nb_iter, 100 * success_rate, str(self.norm), val_norm) - - logger.info('Final success rate of Universal SimBA (%s) %s attack: %.2f%%', self.attack, ['non-targeted', 'targeted'][self.targeted], 100 * success_rate) - return x + noise - - - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` - :param attack: attack type: pixel (px) or DCT (dct) attacks - :type attack: `str` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param order: order of pixel attacks: random or diagonal (diag) - :type order: `str` - :param freq_dim: dimensionality of 2D frequency space. - :type freq_dim: `int` - :param stride: stride for block order. - :type stride: `int` - :param targeted: perform targeted attack - :type targeted: `bool` - :param delta: desired accuracy - :type delta: `float` - :param eps: Attack step size (input variation) - :type eps: `float` - :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 - :type norm: `int` - :param batch_size: Internal size of batches on which adversarial samples are generated. - :type batch_size: `int` - """ - # Save attack-specific parameters - super(Universal_SimBA, self).set_params(**kwargs) - - if self.attack != 'px' and self.attack != 'dct': - raise ValueError('The attack type has to be `px` or `dct`.') - - if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: - raise ValueError("The number of iterations must be a positive integer.") - - if self.epsilon < 0: - raise ValueError("The overshoot parameter must not be negative.") - - if self.order != 'random' and self.order != 'diag': - raise ValueError('The order of pixel attacks has to be `random` or `diag`.') - - if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: - raise ValueError("The `stride` value must be a positive integer.") - - if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: - raise ValueError("The `freq_dim` value must be a positive integer.") - - if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: - raise ValueError("The desired accuracy must be in the range [0, 1].") - - if not isinstance(self.eps, (float, int)) or self.eps <= 0: - raise ValueError("The eps coefficient must be a positive float.") - - if not isinstance(self.targeted, (int)) or (self.targeted != 0 and self.targeted != 1): - raise ValueError('`targeted` has to be a logical value.') - - if self.batch_size <= 0: - raise ValueError('The batch size `batch_size` has to be positive.') - - return True - - def _block_order(self, img_size, channels, initial_size=2, stride=1): - order = np.zeros((channels , img_size , img_size)) - total_elems = channels * initial_size * initial_size - perm = np.random.permutation(total_elems) - order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) - for i in range(initial_size, img_size, stride): - num_elems = channels * (2 * stride * i + stride * stride) - perm = np.random.permutation(num_elems) + total_elems - num_first = channels * stride * (stride + i) - order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) - order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) - total_elems += num_elems - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() - - # applies IDCT to each block of size block_size - def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): - x = x.transpose(0,3,1,2) - z = np.zeros(x.shape) - num_blocks = int(x.shape[2] / block_size) - mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) - if type(ratio) != float: - for i in range(x.shape[0]): - mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 - else: - mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 - for i in range(num_blocks): - for j in range(num_blocks): - submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] - if masked: - submat = submat * mask - z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') - return z.transpose(0,2,3,1) - - def diagonal_order(self, image_size, channels): - x = np.arange(0, image_size).cumsum() - order = np.zeros((image_size, image_size)) - for i in range(image_size): - order[i, :(image_size - i)] = i + x[i:] - for i in range(1, image_size): - reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) - order[i, (image_size - i):] = image_size * image_size - 1 - reverse - if channels > 1: - order_2d = order - order = np.zeros((channels, image_size, image_size)) - for i in range(channels): - order[i, :, :] = 3 * order_2d + i - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() diff --git a/examples/cacl_norm.py b/examples/cacl_norm.py deleted file mode 100644 index 4746480c2c..0000000000 --- a/examples/cacl_norm.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Trains a convolutional neural network on the CIFAR-10 dataset, then generated adversarial images using the -SimBA (pixel) attack and retrains the network on the training set augmented with the adversarial images. -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -import numpy as np -from art.utils import load_dataset - -#import matplotlib.pyplot as plt - -# Configure a logger to capture ART outputs; these are printed in console and the level of detail is set to INFO -logger = logging.getLogger() -logger.setLevel(logging.INFO) -handler = logging.StreamHandler() -formatter = logging.Formatter('[%(levelname)s] %(message)s') -handler.setFormatter(formatter) -logger.addHandler(handler) - -# Read CIFAR10 dataset -(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset(str('cifar10')) -x_train, y_train = x_train[:5000], y_train[:5000] -x_test, y_test = x_test[:500], y_test[:500] -im_shape = x_train[0].shape - -norm = 0.0 -for i in range(5000): - norm = norm + np.linalg.norm(x_train[i].reshape(-1), ord=2) -print(norm / 5000) \ No newline at end of file diff --git a/examples/cifar10_cnn_universal_simba.py b/examples/cifar10_cnn_universal_simba.py deleted file mode 100644 index fe44d9c441..0000000000 --- a/examples/cifar10_cnn_universal_simba.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Trains a convolutional neural network on the CIFAR-10 dataset, then generated adversarial images using the -SimBA (pixel) attack and retrains the network on the training set augmented with the adversarial images. -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -from keras.models import Sequential -from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout -import numpy as np - -from art.attacks import UniversalPerturbation, Universal_SimBA_pixel -from art.classifiers import KerasClassifier -from art.utils import load_dataset, random_sphere - -#import matplotlib.pyplot as plt - -# Configure a logger to capture ART outputs; these are printed in console and the level of detail is set to INFO -logger = logging.getLogger() -logger.setLevel(logging.INFO) -handler = logging.StreamHandler() -formatter = logging.Formatter('[%(levelname)s] %(message)s') -handler.setFormatter(formatter) -logger.addHandler(handler) - -# Read CIFAR10 dataset -(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset(str('cifar10')) -x_train, y_train = x_train[:5000], y_train[:5000] -x_test, y_test = x_test[:500], y_test[:500] -im_shape = x_train[0].shape - -# Create Keras convolutional neural network - basic architecture from Keras examples -# Source here: https://github.com/keras-team/keras/blob/master/examples/cifar10_cnn.py -model = Sequential() -model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:])) -model.add(Activation('relu')) -model.add(Conv2D(32, (3, 3))) -model.add(Activation('relu')) -model.add(MaxPooling2D(pool_size=(2, 2))) -model.add(Dropout(0.25)) - -model.add(Conv2D(64, (3, 3), padding='same')) -model.add(Activation('relu')) -model.add(Conv2D(64, (3, 3))) -model.add(Activation('relu')) -model.add(MaxPooling2D(pool_size=(2, 2))) -model.add(Dropout(0.25)) - -model.add(Flatten()) -model.add(Dense(512)) -model.add(Activation('relu')) -model.add(Dropout(0.5)) -model.add(Dense(10)) -model.add(Activation('softmax')) - -model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) - -# Create classifier wrapper -classifier = KerasClassifier(model=model, clip_values=(min_, max_)) -classifier.fit(x_train, y_train, nb_epochs=5, batch_size=128) - -# Evaluate the classifier on the train samples -preds = np.argmax(classifier.predict(x_train), axis=1) -acc = np.sum(preds == np.argmax(y_train, axis=1)) / y_train.shape[0] -logger.info('Accuracy on train samples: %.2f%%', (acc * 100)) - -x_train, y_train = x_train[:100], y_train[:100] -preds = np.argmax(classifier.predict(x_train), axis=1) - -# Craft adversarial samples with SimBA for single image -logger.info('Create universal SimBA (pixel) attack') -adv_crafter = Universal_SimBA_pixel(classifier, epsilon=0.05) -logger.info('Craft attack on a training example') -x_train_adv_univ_simba = adv_crafter.generate(x_train) -logger.info('Craft attack the training example') -norm2 = np.linalg.norm((x_train_adv_univ_simba[0] - x_train[0]).reshape(-1), ord=2) -# compute fooling rate -preds_adv = np.argmax(classifier.predict(x_train_adv_univ_simba), axis=1) -acc = np.sum(preds != preds_adv) / y_train.shape[0] -logger.info('Fooling rate on universal SimBA adversarial examples: %.2f%%', (acc * 100)) -logger.info('Perturbation norm: %.2f%%', norm2) - -# Craft adversarial samples with random universal pertubation -x_train_adv_random = np.clip(x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3), min_, max_) -preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) -acc = np.sum(preds != preds_adv) / y_train.shape[0] -logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) -logger.info('Perturbation norm: %.2f%%', np.linalg.norm((x_train_adv_random[0]-x_train[0]).reshape(-1), ord=2)) - -# Craft adversarial samples with universal pertubation and FGSM -attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": norm2, "norm": 2} -adv_crafter_fgsm = UniversalPerturbation(classifier) -adv_crafter_fgsm.set_params(**attack_params) -x_train_adv_fgsm = adv_crafter_fgsm.generate(x_train, **attack_params) -np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2) -# compute fooling rate -preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) -acc = np.sum(preds != preds_adv) / y_train.shape[0] -logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) -logger.info('Perturbation norm: %.2f%%', np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2)) \ No newline at end of file From 925ab3da5aa9ad668cb82caae3a41f6150edaec2 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Mon, 22 Jun 2020 07:45:30 +0900 Subject: [PATCH 42/91] deleted unrelatedfiles --- examples/cifar10_cnn_simba.py | 138 ---------------------------------- 1 file changed, 138 deletions(-) delete mode 100644 examples/cifar10_cnn_simba.py diff --git a/examples/cifar10_cnn_simba.py b/examples/cifar10_cnn_simba.py deleted file mode 100644 index d35daf0e08..0000000000 --- a/examples/cifar10_cnn_simba.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Trains a convolutional neural network on the CIFAR-10 dataset, then generated adversarial images using the -SimBA (pixel) attack and retrains the network on the training set augmented with the adversarial images. -""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import logging - -from keras.models import Sequential -from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Activation, Dropout -import numpy as np - -from art.attacks import SimBA_pixel, UniversalPerturbation -from art.classifiers import KerasClassifier -from art.utils import load_dataset, random_sphere - -#import matplotlib.pyplot as plt - -# Configure a logger to capture ART outputs; these are printed in console and the level of detail is set to INFO -logger = logging.getLogger() -logger.setLevel(logging.INFO) -handler = logging.StreamHandler() -formatter = logging.Formatter('[%(levelname)s] %(message)s') -handler.setFormatter(formatter) -logger.addHandler(handler) - -# Read CIFAR10 dataset -(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset(str('cifar10')) -x_train, y_train = x_train[:5000], y_train[:5000] -x_test, y_test = x_test[:500], y_test[:500] -im_shape = x_train[0].shape - -# Create Keras convolutional neural network - basic architecture from Keras examples -# Source here: https://github.com/keras-team/keras/blob/master/examples/cifar10_cnn.py -model = Sequential() -model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:])) -model.add(Activation('relu')) -model.add(Conv2D(32, (3, 3))) -model.add(Activation('relu')) -model.add(MaxPooling2D(pool_size=(2, 2))) -model.add(Dropout(0.25)) - -model.add(Conv2D(64, (3, 3), padding='same')) -model.add(Activation('relu')) -model.add(Conv2D(64, (3, 3))) -model.add(Activation('relu')) -model.add(MaxPooling2D(pool_size=(2, 2))) -model.add(Dropout(0.25)) - -model.add(Flatten()) -model.add(Dense(512)) -model.add(Activation('relu')) -model.add(Dropout(0.5)) -model.add(Dense(10)) -model.add(Activation('softmax')) - -model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) - -# Create classifier wrapper -classifier = KerasClassifier(model=model, clip_values=(min_, max_)) -classifier.fit(x_train, y_train, nb_epochs=5, batch_size=128) - -# Evaluate the classifier on the train samples -preds = np.argmax(classifier.predict(x_train), axis=1) -acc = np.sum(preds == np.argmax(y_train, axis=1)) / y_train.shape[0] -logger.info('Accuracy on train samples: %.2f%%', (acc * 100)) - -""" -### Single Attack ########### -idx = 1 -# Craft adversarial samples with SimBA for single image -logger.info('Create SimBA (pixel) attack') -adv_crafter = SimBA_pixel(classifier, epsilon=0.05) -logger.info('Craft attack on a training example') -x_train_adv = adv_crafter.generate(x_train[idx].reshape(1,32,32,3)) -logger.info('Craft attack the training example') -preds_adv = np.argmax(classifier.predict(x_train_adv), axis=1) -print(preds[idx],preds_adv[0]) -""" - -### Universal Attack ########## -x_train, y_train = x_train[:100], y_train[:100] -preds = np.argmax(classifier.predict(x_train), axis=1) - -# Craft adversarial samples with universal pertubation based on SimBA -attack_params = {"attacker": "simba_px", "attacker_params": {"max_iter": 3000, "epsilon": 0.05}, "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} -adv_crafter_simba = UniversalPerturbation(classifier) -adv_crafter_simba.set_params(**attack_params) -x_train_adv_simba = adv_crafter_simba.generate(x_train, **attack_params) -norm2 = np.linalg.norm(adv_crafter_simba.noise.reshape(-1), ord=2) -# compute fooling rate -preds_adv = np.argmax(classifier.predict(x_train_adv_simba), axis=1) -acc = np.sum(preds != preds_adv) / y_train.shape[0] -logger.info('Fooling rate on SimBA universal adversarial examples: %.2f%%', (acc * 100)) -logger.info('Perturbation norm: %.2f%%', norm2) - -# Craft adversarial samples with random universal pertubation -x_train_adv_random = np.clip(x_train + random_sphere(nb_points=1, nb_dims=32*32*3, radius=norm2, norm=2).reshape(1,32,32,3), min_, max_) -preds_adv = np.argmax(classifier.predict(x_train_adv_random), axis=1) -acc = np.sum(preds != preds_adv) / y_train.shape[0] -logger.info('Fooling rate on random adversarial examples: %.2f%%', (acc * 100)) -logger.info('Perturbation norm: %.2f%%', np.linalg.norm((x_train_adv_random[0]-x_train[0]).reshape(-1), ord=2)) - -# Craft adversarial samples with universal pertubation and FGSM -attack_params = {"attacker": "fgsm", "delta": 0.01, "max_iter": 1, "eps": 2, "norm": 2} -adv_crafter_fgsm = UniversalPerturbation(classifier) -adv_crafter_fgsm.set_params(**attack_params) -x_train_adv_fgsm = adv_crafter_fgsm.generate(x_train, y=1, **attack_params) -np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2) -# compute fooling rate -preds_adv = np.argmax(classifier.predict(x_train_adv_fgsm), axis=1) -acc = np.sum(preds != preds_adv) / y_train.shape[0] -logger.info('Fooling rate on fgsm universal adversarial examples: %.2f%%', (acc * 100)) -logger.info('Perturbation norm: %.2f%%', np.linalg.norm(adv_crafter_fgsm.noise.reshape(-1), ord=2)) - - -""" -# plot -label = [ - 'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', - 'ship', 'truck' -] - -plt.subplot(1, 3, 1) -plt.imshow(x_train[idx]) -plt.title(label[preds[idx]]) - -plt.subplot(1, 3, 2) -plt.imshow(x_train_adv[0] - x_train[idx]) -plt.title("perturbation") - -plt.subplot(1, 3, 3) -plt.imshow(x_train_adv[0]) -plt.title(label[preds_adv[0]]) - -plt.show() -""" From 6c3b172350f27d181994d1d2e66b738b7a0d8847 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:25:47 +0900 Subject: [PATCH 43/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 9b01cbcd95..2ec9763ae2 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -62,7 +62,7 @@ def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsi :param batch_size: Batch size (but, batch process unavailable in this implementation) :type batch_size: `int` """ - super(SimBA, self).__init__(classifier=classifier) + super(SimBA, self).__init__(estimator=classifier) if not isinstance(classifier, ClassifierGradients): raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' From d46023bddee05063938548437339d02a563f7ef9 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:26:10 +0900 Subject: [PATCH 44/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 2ec9763ae2..ea43801beb 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -63,11 +63,6 @@ def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsi :type batch_size: `int` """ super(SimBA, self).__init__(estimator=classifier) - if not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) params = {'attack': attack, 'max_iter': max_iter, 'epsilon': epsilon, 'order': order, 'freq_dim': freq_dim, 'stride': stride, 'targeted': targeted ,'batch_size': batch_size} self.set_params(**params) From fe960ff038f6169688162ae0010dd43a1fabab28 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:26:33 +0900 Subject: [PATCH 45/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index ea43801beb..ce66179c1e 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -215,29 +215,7 @@ def generate(self, x, y=None, **kwargs): return x - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param attack: attack type: pixel (px) or DCT (dct) attacks - :type attack: `str` - :param max_iter: The maximum number of iterations. - :type max_iter: `int` - :param epsilon: Overshoot parameter. - :type epsilon: `float` - :param order: order of pixel attacks - :type order: `str` - :param freq_dim: dimensionality of 2D frequency space (DCT). - :type freq_dim: `int` - :param stride: stride for block order (DCT). - :type stride: `int` - :param targeted: targeted attacks - :type targeted: `bool` - :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` - """ - # Save attack-specific parameters - super(SimBA, self).set_params(**kwargs) + def _check_params(self) -> None: if not isinstance(self.max_iter, (int, np.int)) or self.max_iter <= 0: raise ValueError("The number of iterations must be a positive integer.") From 9d131cb114e2c7b683793eed7592c3ffe9527d8a Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:26:56 +0900 Subject: [PATCH 46/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index ce66179c1e..ad852634fd 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -241,8 +241,6 @@ def _check_params(self) -> None: if not isinstance(self.targeted, (int)) or (self.targeted != 0 and self.targeted != 1): raise ValueError('`targeted` has to be a logical value.') - return True - def _block_order(self, img_size, channels, initial_size=2, stride=1): order = np.zeros((channels , img_size , img_size)) total_elems = channels * initial_size * initial_size From b11fa4c621acd06518398a59697408fdd8108f80 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:27:11 +0900 Subject: [PATCH 47/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index ad852634fd..015534d762 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -64,8 +64,15 @@ def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsi """ super(SimBA, self).__init__(estimator=classifier) - params = {'attack': attack, 'max_iter': max_iter, 'epsilon': epsilon, 'order': order, 'freq_dim': freq_dim, 'stride': stride, 'targeted': targeted ,'batch_size': batch_size} - self.set_params(**params) + self.attack = attack + self.max_iter = max_iter + self.epsilon = epsilon + self.order = order + self.freq_dim = freq_dim + self.stride = stride + self.targeted = targeted + self.batch_size = batch_size + self._check_params() def generate(self, x, y=None, **kwargs): """ From 637de2be56c19bb656d5203200f0374e738ef018 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:27:35 +0900 Subject: [PATCH 48/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 015534d762..4a4a090180 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -39,28 +39,28 @@ class SimBA(EvasionAttack): attack_params = EvasionAttack.attack_params + ['attack', 'max_iter', 'epsilon', 'order', 'freq_dim', 'stride', 'targeted', 'batch_size',] - def __init__(self, classifier, attack='dct', max_iter=3000, order='random', epsilon=0.1, freq_dim=4, stride=1, targeted=False, batch_size=1): + def __init__(self, + classifier: ClassifierGradients, + attack: str = 'dct', + max_iter: int = 3000, + order: str = 'random', + epsilon: float = 0.1, + freq_dim: int = 4, + stride: int = 1, + targeted: bool = False, + batch_size: int = 1): """ Create a SimBA (dct) attack instance. :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` :param attack: attack type: pixel (px) or DCT (dct) attacks - :type attack: `str` :param max_iter: The maximum number of iterations. - :type max_iter: `int` :param epsilon: Overshoot parameter. - :type epsilon: `float` :param order: order of pixel attacks: random or diagonal (diag) - :type order: `str` :param freq_dim: dimensionality of 2D frequency space (DCT). - :type freq_dim: `int` :param stride: stride for block order (DCT). - :type stride: `int` :param targeted: perform targeted attack - :type targeted: `bool` :param batch_size: Batch size (but, batch process unavailable in this implementation) - :type batch_size: `int` """ super(SimBA, self).__init__(estimator=classifier) From c36351f4e2a361df0e309d009080869f773b3e9e Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:27:48 +0900 Subject: [PATCH 49/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 4a4a090180..cb2f4f4a5a 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -74,16 +74,13 @@ def __init__(self, self.batch_size = batch_size self._check_params() - def generate(self, x, y=None, **kwargs): + def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray: """ Generate adversarial samples and return them in an array. :param x: An array with the original inputs to be attacked. - :type x: `np.ndarray` :param y: An array with the original labels to be predicted. - :type y: `np.ndarray` :return: An array holding the adversarial examples. - :rtype: `np.ndarray` """ x = x.astype(ART_NUMPY_DTYPE) preds = self.classifier.predict(x, batch_size=self.batch_size) From c39d76405f0852cbbe2c785757978bc4dac0e7dc Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:28:27 +0900 Subject: [PATCH 50/91] Update art/attacks/evasion/universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/universal_perturbation.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index 569db45262..a9f5925836 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -42,19 +42,6 @@ logger = logging.getLogger(__name__) -##### -import numpy as np -import matplotlib -matplotlib.use("Agg") -import matplotlib.pyplot as plt -import japanize_matplotlib - -import torch -import logging -import os.path - -import numpy as np - class UniversalPerturbation(EvasionAttack): """ From efde316a8f2d86a3050adf7062c37b24810f5ab0 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:28:39 +0900 Subject: [PATCH 51/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 3c1572a311..89734b0ae9 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -67,12 +67,6 @@ def __init__(self, classifier, attacker='fgsm', attacker_params=None, delta=0.2, :type norm: `int` """ super(TargetedUniversalPerturbation, self).__init__(classifier) - if not isinstance(classifier, ClassifierNeuralNetwork) or not isinstance(classifier, ClassifierGradients): - raise (TypeError('For `' + self.__class__.__name__ + '` classifier must be an instance of ' - '`art.classifiers.classifier.ClassifierNeuralNetwork` and ' - '`art.classifiers.classifier.ClassifierGradients`, the provided classifier is instance of ' - + str(classifier.__class__.__bases__) + '. ' - ' The classifier needs to be a Neural Network and provide gradients.')) kwargs = {'attacker': attacker, 'attacker_params': attacker_params, From cdca49f619f18f2aa210b4cb9180a32c0b17dc92 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:28:55 +0900 Subject: [PATCH 52/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- .../evasion/targeted_universal_perturbation.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 89734b0ae9..994e4ab2a2 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -47,24 +47,23 @@ class TargetedUniversalPerturbation(EvasionAttack): } attack_params = EvasionAttack.attack_params + ['attacker', 'attacker_params', 'delta', 'max_iter', 'eps', 'norm'] - def __init__(self, classifier, attacker='fgsm', attacker_params=None, delta=0.2, max_iter=20, eps=10.0, - norm=np.inf): + def __init__(self, + classifier: ClassifierGradients, + attacker: str = 'fgsm', + attacker_params: Dict[str, Any] = None, + delta: float = 0.2, + max_iter: int = 20, + eps: float = 10.0, + norm: int = np.inf): """ :param classifier: A trained classifier. - :type classifier: :class:`.Classifier` :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'fgsm'. - :type attacker: `str` :param attacker_params: Parameters specific to the adversarial attack. If this parameter is not specified, the default parameters of the chosen attack will be used. - :type attacker_params: `dict` :param delta: desired accuracy - :type delta: `float` :param max_iter: The maximum number of iterations for computing universal perturbation. - :type max_iter: `int` :param eps: Attack step size (input variation) - :type eps: `float` :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 - :type norm: `int` """ super(TargetedUniversalPerturbation, self).__init__(classifier) From 5d86968c848b067dafb3516957e7de009357a496 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:29:15 +0900 Subject: [PATCH 53/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 994e4ab2a2..9db6f2e066 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -76,16 +76,13 @@ def __init__(self, } self.set_params(**kwargs) - def generate(self, x, y, **kwargs): + def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: """ Generate adversarial samples and return them in an array. :param x: An array with the original inputs. - :type x: `np.ndarray` :param y: An array with the targeted labels. - :type y: `np.ndarray` :return: An array holding the adversarial examples. - :rtype: `np.ndarray` """ logger.info('Computing targeted universal perturbation based on %s attack.', self.attacker) From f2246614b677d4937bbefac5bbdb6e03e34b80d7 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:29:26 +0900 Subject: [PATCH 54/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 9db6f2e066..f9f01ac9f9 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -173,8 +173,6 @@ def set_params(self, **kwargs): if not isinstance(self.eps, (float, int)) or self.eps <= 0: raise ValueError("The eps coefficient must be a positive float.") - return True - def _get_attack(self, a_name, params=None): """ Get an attack object from its name. From a800b8de34cf989883a5caabea98a59e8e6c0ce8 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:29:39 +0900 Subject: [PATCH 55/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index f9f01ac9f9..e6440d03e6 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -173,16 +173,13 @@ def set_params(self, **kwargs): if not isinstance(self.eps, (float, int)) or self.eps <= 0: raise ValueError("The eps coefficient must be a positive float.") - def _get_attack(self, a_name, params=None): + def _get_attack(self, a_name: str, params: Optional[Dict[str, Any]] = None) - > EvasionAttack: """ Get an attack object from its name. :param a_name: attack name. - :type a_name: `str` :param params: attack params. - :type params: `dict` :return: attack object - :rtype: `object` """ try: attack_class = self._get_class(self.attacks_dict[a_name]) From ad925097e0f9bcc17548169cb12eadc023fa86b3 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:29:48 +0900 Subject: [PATCH 56/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index e6440d03e6..9ad9e08dcb 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -204,7 +204,6 @@ def _get_class(class_name): :rtype: `module` """ sub_mods = class_name.split(".") - print(class_name) module_ = __import__(".".join(sub_mods[:-1]), fromlist=sub_mods[-1]) class_module = getattr(module_, sub_mods[-1]) From 0008151ead8e4eefe054cdf882cb3401758f5019 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:30:03 +0900 Subject: [PATCH 57/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 9ad9e08dcb..7c9ad0ec34 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -194,14 +194,12 @@ def _get_attack(self, a_name: str, params: Optional[Dict[str, Any]] = None) - > raise NotImplementedError("{} attack not supported".format(a_name)) @staticmethod - def _get_class(class_name): + def _get_class(class_name: str) -> types.ModuleType: """ Get a class module from its name. :param class_name: Full name of a class. - :type class_name: `str` :return: The class `module`. - :rtype: `module` """ sub_mods = class_name.split(".") module_ = __import__(".".join(sub_mods[:-1]), fromlist=sub_mods[-1]) From 9da72e6eb52ca9804702089f0ea1c76c76b5143f Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:30:16 +0900 Subject: [PATCH 58/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 7c9ad0ec34..74de9c3797 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -1,6 +1,6 @@ # MIT License # -# Copyright (C) IBM Corporation 2018 +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2020 # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the From a86d4ee4a096bff8d368b1d0f2ba19dd6e3990f2 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:30:27 +0900 Subject: [PATCH 59/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 74de9c3797..91338280f4 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -47,6 +47,8 @@ class TargetedUniversalPerturbation(EvasionAttack): } attack_params = EvasionAttack.attack_params + ['attacker', 'attacker_params', 'delta', 'max_iter', 'eps', 'norm'] + _estimator_requirements = (BaseEstimator, NeuralNetworkMixin, ClassGradientsMixin, LossGradientsMixin) + def __init__(self, classifier: ClassifierGradients, attacker: str = 'fgsm', From 5c0d64849b5f3426776b7ff555a05f57e1e4fbe0 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:30:38 +0900 Subject: [PATCH 60/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 91338280f4..05bda3693f 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -67,7 +67,7 @@ def __init__(self, :param eps: Attack step size (input variation) :param norm: The norm of the adversarial perturbation. Possible values: np.inf, 2 """ - super(TargetedUniversalPerturbation, self).__init__(classifier) + super(TargetedUniversalPerturbation, self).__init__(estimator=classifier) kwargs = {'attacker': attacker, 'attacker_params': attacker_params, From ac7ef2420c692b4c093d67397d7c1795fc2d7a79 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:30:58 +0900 Subject: [PATCH 61/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- .../evasion/targeted_universal_perturbation.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 05bda3693f..ab5f4db7cf 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -69,14 +69,13 @@ def __init__(self, """ super(TargetedUniversalPerturbation, self).__init__(estimator=classifier) - kwargs = {'attacker': attacker, - 'attacker_params': attacker_params, - 'delta': delta, - 'max_iter': max_iter, - 'eps': eps, - 'norm': norm - } - self.set_params(**kwargs) + self.attacker = attacker + self.attacker_params = attacker_params + self.delta = delta + self.max_iter = max_iter + self.eps = eps + self.norm = norm + self._check_params() def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: """ From f06fc36ec10b18efeb0dd7be528cbee767154f78 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:31:11 +0900 Subject: [PATCH 62/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- .../targeted_universal_perturbation.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index ab5f4db7cf..3b7809f519 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -146,24 +146,7 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: return x_adv - def set_params(self, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks before saving them as attributes. - - :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'fgsm'. - :type attacker: `str` - :param attacker_params: Parameters specific to the adversarial attack. - :type attacker_params: `dict` - :param delta: desired accuracy - :type delta: `float` - :param max_iter: The maximum number of iterations for computing universal perturbation. - :type max_iter: `int` - :param eps: Attack step size (input variation) - :type eps: `float` - :param norm: Order of the norm. Possible values: np.inf, 2 (default is np.inf) - :type norm: `int` - """ - super(TargetedUniversalPerturbation, self).set_params(**kwargs) + def _check_params(self) -> None: if not isinstance(self.delta, (float, int)) or self.delta < 0 or self.delta > 1: raise ValueError("The desired accuracy must be in the range [0, 1].") From c0da24fc8894c77590f2118dfe4a71ac14a0d1de Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:31:39 +0900 Subject: [PATCH 63/91] Update art/attacks/__init__.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/__init__.py b/art/attacks/__init__.py index 307ce8e16a..928e1d1186 100644 --- a/art/attacks/__init__.py +++ b/art/attacks/__init__.py @@ -2,4 +2,4 @@ Module providing adversarial attacks under a common interface. """ from art.attacks.attack import Attack, EvasionAttack, PoisoningAttack, PoisoningAttackBlackBox, PoisoningAttackWhiteBox -from art.attacks.attack import ExtractionAttack, InferenceAttack, AttributeInferenceAttack \ No newline at end of file +from art.attacks.attack import ExtractionAttack, InferenceAttack, AttributeInferenceAttack From 0bd0559e42467cb627e2d4061456950a431b91c9 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:32:34 +0900 Subject: [PATCH 64/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index cb2f4f4a5a..da58097651 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -1,6 +1,6 @@ # MIT License # -# Copyright (C) IBM Corporation 2018 +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2020 # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the From a449752baffe102f849c53e300762bf01039c6f0 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:32:54 +0900 Subject: [PATCH 65/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index da58097651..6fc1c39ef9 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -26,7 +26,7 @@ import logging import numpy as np -from scipy.fftpack import dct, idct +from scipy.fftpack import idct from art.attacks.attack import EvasionAttack from art.classifiers.classifier import ClassifierGradients From 237c13d0d7a5aac69714e763bfcb38d8a971ecc9 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:33:22 +0900 Subject: [PATCH 66/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 1 - 1 file changed, 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 6fc1c39ef9..95829c5e74 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -31,7 +31,6 @@ from art.attacks.attack import EvasionAttack from art.classifiers.classifier import ClassifierGradients from art.config import ART_NUMPY_DTYPE -from art.utils import compute_success logger = logging.getLogger(__name__) From 9242d7f514ac59d8330f6cd89d2d6c8ed0050f79 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:33:45 +0900 Subject: [PATCH 67/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 95829c5e74..4ff19e0129 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -38,6 +38,8 @@ class SimBA(EvasionAttack): attack_params = EvasionAttack.attack_params + ['attack', 'max_iter', 'epsilon', 'order', 'freq_dim', 'stride', 'targeted', 'batch_size',] + _estimator_requirements = (BaseEstimator, ClassGradientsMixin) + def __init__(self, classifier: ClassifierGradients, attack: str = 'dct', From 83ba5cf8d2bd347a5ed6959d2124a7936df0cd54 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:34:06 +0900 Subject: [PATCH 68/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 4ff19e0129..6350274e37 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -20,8 +20,7 @@ | Paper link: https://arxiv.org/abs/1905.07121 """ -from __future__ import (absolute_import, division, print_function, - unicode_literals) +from __future__ import (absolute_import, division, print_function, unicode_literals) import logging From 69edb46daeffbae9f4ab35612bc16948b7b93615 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:34:25 +0900 Subject: [PATCH 69/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 6350274e37..d41a65d8c6 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -83,7 +83,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n :return: An array holding the adversarial examples. """ x = x.astype(ART_NUMPY_DTYPE) - preds = self.classifier.predict(x, batch_size=self.batch_size) + preds = self.estimator.predict(x, batch_size=self.batch_size) if y is None: if self.targeted: From afcb46f3c5082fe3704a5a9c683734abfa006719 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Wed, 24 Jun 2020 09:35:05 +0900 Subject: [PATCH 70/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index d41a65d8c6..18f5e1df71 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -125,7 +125,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n clip_min = -np.inf clip_max = np.inf - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: + if self.classifier.clip_values is not None: clip_min, clip_max = self.classifier.clip_values term_flag = 1 From de5b8f79698ab9c6f59a017ad43dde9d58ae94ff Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Wed, 24 Jun 2020 10:12:29 +0900 Subject: [PATCH 71/91] added a docstring and typing information to the method --- art/attacks/evasion/simba.py | 41 +++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 18f5e1df71..ce55ff44d8 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -246,6 +246,22 @@ def _check_params(self) -> None: raise ValueError('`targeted` has to be a logical value.') def _block_order(self, img_size, channels, initial_size=2, stride=1): + """ + Defines a block order, starting with top-left (initial_size x initial_size) submatrix + expanding by stride rows and columns whenever exhausted + randomized within the block and across channels. + e.g. (initial_size=2, stride=1) + [1, 3, 6] + [2, 4, 9] + [5, 7, 8] + + :param img_size: image size (i.e., width or height). + :param channels: the number of channels. + :param initial size: initial size for submatrix. + :param stride: stride size for expansion. + + :return z: An array holding the block order of DCT attacks. + """ order = np.zeros((channels , img_size , img_size)) total_elems = channels * initial_size * initial_size perm = np.random.permutation(total_elems) @@ -259,8 +275,18 @@ def _block_order(self, img_size, channels, initial_size=2, stride=1): total_elems += num_elems return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() - # applies IDCT to each block of size block_size + def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): + """ + Applies IDCT to each block of size block_size. + + :param x: An array with the inputs to be attacked. + :param block_size: block size for DCT attacks. + :param masked: use the mask. + :param ratio: Ratio of the lowest frequency directions in order to make the adversarial perturbation in the low frequency space. + + :return z: An array holding the order of DCT attacks. + """ x = x.transpose(0,3,1,2) z = np.zeros(x.shape) num_blocks = int(x.shape[2] / block_size) @@ -279,6 +305,19 @@ def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): return z.transpose(0,2,3,1) def diagonal_order(self, image_size, channels): + """ + Defines a diagonal order for pixel attacks. + order is fixed across diagonals but are randomized across channels and within the diagonal + e.g. + [1, 2, 5] + [3, 4, 8] + [6, 7, 9] + + :param image_size: image size (i.e., width or height) + :param channels: the number of channels + + :return z: An array holding the diagonal order of pixel attacks. + """ x = np.arange(0, image_size).cumsum() order = np.zeros((image_size, image_size)) for i in range(image_size): From 50f4dd2c8818dc442c52cc9f6a4732a2e578b697 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Wed, 24 Jun 2020 12:49:22 +0900 Subject: [PATCH 72/91] modified code to run successfully --- art/attacks/evasion/simba.py | 20 +++++++++------ .../targeted_universal_perturbation.py | 25 +++++++++++-------- art/estimators/classification/pytorch.py | 10 ++++---- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index ce55ff44d8..dbf8ccdc8c 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -23,12 +23,18 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) import logging +import types +from typing import Any, Dict, Optional, Union import numpy as np from scipy.fftpack import idct from art.attacks.attack import EvasionAttack -from art.classifiers.classifier import ClassifierGradients +from art.estimators.estimator import BaseEstimator +from art.estimators.classification.classifier import ( + ClassGradientsMixin, + ClassifierGradients, +) from art.config import ART_NUMPY_DTYPE logger = logging.getLogger(__name__) @@ -125,8 +131,8 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n clip_min = -np.inf clip_max = np.inf - if self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values + if self.estimator.clip_values is not None: + clip_min, clip_max = self.estimator.clip_values term_flag = 1 if self.targeted: @@ -142,15 +148,15 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n diff[indices[nb_iter]] = self.epsilon if self.attack == 'dct': - left_preds = self.classifier.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) + left_preds = self.estimator.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) elif self.attack == 'px': - left_preds = self.classifier.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + left_preds = self.estimator.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) left_prob = left_preds.reshape(-1)[desired_label] if self.attack == 'dct': - right_preds = self.classifier.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) + right_preds = self.estimator.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) elif self.attack == 'px': - right_preds = self.classifier.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + right_preds = self.estimator.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) right_prob = right_preds.reshape(-1)[desired_label] # Use (2 * int(self.targeted) - 1) to shorten code? diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 3b7809f519..d0b5aeee07 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -24,11 +24,17 @@ import logging import random +import types +from typing import Any, Dict, Optional, Union import numpy as np from art.classifiers.classifier import ClassifierNeuralNetwork, ClassifierGradients from art.attacks.attack import EvasionAttack +from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin, LossGradientsMixin +from art.estimators.classification.classifier import ( + ClassGradientsMixin, +) from art.utils import projection logger = logging.getLogger(__name__) @@ -52,7 +58,7 @@ class TargetedUniversalPerturbation(EvasionAttack): def __init__(self, classifier: ClassifierGradients, attacker: str = 'fgsm', - attacker_params: Dict[str, Any] = None, + attacker_params: Optional[Dict[str, Any]] = None, delta: float = 0.2, max_iter: int = 20, eps: float = 10.0, @@ -95,7 +101,7 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: # Instantiate the middle attacker and get the predicted labels attacker = self._get_attack(self.attacker, self.attacker_params) - pred_y = self.classifier.predict(x, batch_size=1) + pred_y = self.estimator.predict(x, batch_size=1) pred_y_max = np.argmax(pred_y, axis=1) # Start to generate the adversarial examples @@ -109,14 +115,14 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: x_i = ex[None, ...] y_i = ey[None, ...] - current_label = np.argmax(self.classifier.predict(x_i + noise)[0]) + current_label = np.argmax(self.estimator.predict(x_i + noise)[0]) target_label = np.argmax(y_i) if current_label != target_label: # Compute adversarial perturbation adv_xi = attacker.generate(x_i + noise, y=y_i) - new_label = np.argmax(self.classifier.predict(adv_xi)[0]) + new_label = np.argmax(self.estimator.predict(adv_xi)[0]) # If the class has changed, update v if new_label == target_label: @@ -128,16 +134,15 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: # Apply attack and clip x_adv = x + noise - if hasattr(self.classifier, 'clip_values') and self.classifier.clip_values is not None: - clip_min, clip_max = self.classifier.clip_values + if hasattr(self.estimator, 'clip_values') and self.estimator.clip_values is not None: + clip_min, clip_max = self.estimator.clip_values x_adv = np.clip(x_adv, clip_min, clip_max) # Compute the error rate - y_adv = np.argmax(self.classifier.predict(x_adv, batch_size=1), axis=1) + y_adv = np.argmax(self.estimator.predict(x_adv, batch_size=1), axis=1) fooling_rate = np.sum(pred_y_max != y_adv) / nb_instances targeted_success_rate = np.sum(y_adv == np.argmax(y, axis=1)) / nb_instances - noise = x_adv[0] - x[0] self.fooling_rate = fooling_rate self.converged = nb_iter < self.max_iter self.noise = noise @@ -157,7 +162,7 @@ def _check_params(self) -> None: if not isinstance(self.eps, (float, int)) or self.eps <= 0: raise ValueError("The eps coefficient must be a positive float.") - def _get_attack(self, a_name: str, params: Optional[Dict[str, Any]] = None) - > EvasionAttack: + def _get_attack(self, a_name: str, params: Optional[Dict[str, Any]] = None) -> EvasionAttack: """ Get an attack object from its name. @@ -167,7 +172,7 @@ def _get_attack(self, a_name: str, params: Optional[Dict[str, Any]] = None) - > """ try: attack_class = self._get_class(self.attacks_dict[a_name]) - a_instance = attack_class(self.classifier) + a_instance = attack_class(self.estimator) if params: a_instance.set_params(**params) diff --git a/art/estimators/classification/pytorch.py b/art/estimators/classification/pytorch.py index cd624d77a8..c8cb3236d9 100644 --- a/art/estimators/classification/pytorch.py +++ b/art/estimators/classification/pytorch.py @@ -261,12 +261,12 @@ def fit_generator(self, generator: "DataGenerator", nb_epochs: int = 20, **kwarg for _ in range(nb_epochs): for i_batch, o_batch in generator.iterator: if isinstance(i_batch, np.ndarray): - i_batch = torch.from_numpy(i_batch).type(torch.FloatTensor).to(self._device) + i_batch = torch.from_numpy(i_batch).to(self._device) else: i_batch = i_batch.to(self._device) if isinstance(o_batch, np.ndarray): - o_batch = torch.argmax(torch.from_numpy(o_batch).type(torch.FloatTensor).to(self._device), dim=1) + o_batch = torch.argmax(torch.from_numpy(o_batch).to(self._device), dim=1) else: o_batch = torch.argmax(o_batch.to(self._device), dim=1) @@ -315,7 +315,7 @@ def class_gradient(self, x: np.ndarray, label: Union[int, List[int], None] = Non # Apply preprocessing x_preprocessed, _ = self._apply_preprocessing(x, y=None, fit=False) - x_preprocessed = torch.from_numpy(x_preprocessed).type(torch.FloatTensor).to(self._device) + x_preprocessed = torch.from_numpy(x_preprocessed).to(self._device) # Compute gradients if self._layer_idx_gradients < 0: @@ -393,7 +393,7 @@ def loss_gradient(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: y_preprocessed = np.argmax(y_preprocessed, axis=1) # Convert the inputs to Tensors - inputs_t = torch.from_numpy(x_preprocessed).type(torch.FloatTensor).to(self._device) + inputs_t = torch.from_numpy(x_preprocessed).to(self._device) inputs_t.requires_grad = True # Convert the labels to Tensors @@ -493,7 +493,7 @@ def get_activations( ) # Run prediction for the current batch - layer_output = self._model(torch.from_numpy(x_preprocessed[begin:end]).type(torch.FloatTensor).to(self._device))[layer_index] + layer_output = self._model(torch.from_numpy(x_preprocessed[begin:end]).to(self._device))[layer_index] results.append(layer_output.detach().cpu().numpy()) results = np.concatenate(results) From 6b41cec58b0e2e5bcc0844b27946e1166cd1b567 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Wed, 24 Jun 2020 15:49:21 +0900 Subject: [PATCH 73/91] update --- art/attacks/evasion/simba.py | 8 ++++---- art/attacks/evasion/targeted_universal_perturbation.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index dbf8ccdc8c..93ba72a018 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -109,22 +109,22 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n if self.attack == 'px': if self.order == 'diag': - indices = self.diagonal_order(x.shape[2], 3)[:self.max_iter] + indices = self.diagonal_order(x.shape[2], x.shape[3])[:self.max_iter] elif self.order == 'random': indices = np.random.permutation(n_dims)[:self.max_iter] indices_size = len(indices) while indices_size < self.max_iter: if self.order == 'diag': - tmp_indices = self.diagonal_order(x.shape[2], 3) + tmp_indices = self.diagonal_order(x.shape[2], x.shape[3]) elif self.order == 'random': tmp_indices = np.random.permutation(n_dims) indices = np.hstack((indices, tmp_indices))[:self.max_iter] indices_size = len(indices) elif self.attack == 'dct': - indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + indices = self._block_order(x.shape[2], x.shape[3], initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] indices_size = len(indices) while indices_size < self.max_iter: - tmp_indices = self._block_order(x.shape[2], 3, initial_size=self.freq_dim, stride=self.stride) + tmp_indices = self._block_order(x.shape[2], x.shape[3], initial_size=self.freq_dim, stride=self.stride) indices = np.hstack((indices, tmp_indices))[:self.max_iter] indices_size = len(indices) trans = lambda z: self._block_idct(z, block_size=x.shape[2]) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index d0b5aeee07..cbd6ee604c 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -144,6 +144,7 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: targeted_success_rate = np.sum(y_adv == np.argmax(y, axis=1)) / nb_instances self.fooling_rate = fooling_rate + self.targeted_success_rate = targeted_success_rate self.converged = nb_iter < self.max_iter self.noise = noise logger.info('Fooling rate of universal perturbation attack: %.2f%%', 100 * fooling_rate) From f6e90e1c9d9039eb7f091b67ea1f0423c7b64555 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Wed, 24 Jun 2020 15:50:13 +0900 Subject: [PATCH 74/91] added unittest --- tests/attacks/test_simba.py | 152 ++++++++++++++++ .../test_targeted_universal_perturbation.py | 167 ++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 tests/attacks/test_simba.py create mode 100644 tests/attacks/test_targeted_universal_perturbation.py diff --git a/tests/attacks/test_simba.py b/tests/attacks/test_simba.py new file mode 100644 index 0000000000..113999857c --- /dev/null +++ b/tests/attacks/test_simba.py @@ -0,0 +1,152 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2020 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import unittest + +import numpy as np + +from art.attacks.evasion.simba import SimBA +from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin +from art.estimators.classification.classifier import ClassGradientsMixin +from art.utils import get_labels_np_array + +from tests.utils import TestBase +from tests.utils import get_image_classifier_tf, get_image_classifier_kr, get_image_classifier_pt +from tests.attacks.utils import backend_test_classifier_type_check_fail + +logger = logging.getLogger(__name__) + + +class TestSimBA(TestBase): + """ + A unittest class for testing the Simple Black-box Adversarial Attacks (SimBA). + + This module tests SimBA. + Note: SimBA runs only in Keras and TensorFlow (not in PyTorch) + This is due to the channel first format in PyTorch. + + | SimBA Paper link: + https://arxiv.org/abs/1905.07121 + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.n_test = 1 + cls.x_test_mnist = cls.x_test_mnist[0 : cls.n_test] + cls.y_test_mnist = cls.y_test_mnist[0 : cls.n_test] + + def test_keras_mnist(self): + """ + Test with the KerasClassifier. (Untargeted Attack) + :return: + """ + classifier = get_image_classifier_kr() + self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, False) + + def test_tensorflow_mnist(self): + """ + Test with the TensorFlowClassifier. (Untargeted Attack) + :return: + """ + classifier, sess = get_image_classifier_tf() + self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, False) + + #def test_pytorch_mnist(self): + # """ + # Test with the PyTorchClassifier. (Untargeted Attack) + # :return: + # """ + # x_test = np.reshape(self.x_test_mnist, (self.x_test_mnist.shape[0], 1, 28, 28)).astype(np.float32) + # classifier = get_image_classifier_pt() + # self._test_attack(classifier, x_test, self.y_test_mnist, False) + + def test_keras_mnist_targeted(self): + """ + Test with the KerasClassifier. (Targeted Attack) + :return: + """ + classifier = get_image_classifier_kr() + self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, True) + + def test_tensorflow_mnist_targeted(self): + """ + Test with the TensorFlowClassifier. (Targeted Attack) + :return: + """ + classifier, sess = get_image_classifier_tf() + self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, True) + + # SimBA is not avaialbe for PyTorch + #def test_pytorch_mnist_targeted(self): + # """ + # Test with the PyTorchClassifier. (Targeted Attack) + # :return: + # """ + # x_test = np.reshape(self.x_test_mnist, (self.x_test_mnist.shape[0], 1, 28, 28)).astype(np.float32) + # classifier = get_image_classifier_pt() + # self._test_attack(classifier, x_test, self.y_test_mnist, True) + + def _test_attack(self, classifier, x_test, y_test, targeted): + """ + Test with SimBA + :return: + """ + x_test_original = x_test.copy() + + # set the targeted label + if targeted: + y_target = np.zeros(10) + y_target[8] = 1.0 + + df = SimBA(classifier, attack="dct", targeted=targeted) + + if targeted: + x_test_adv = df.generate(x_test_original[0].reshape(1,28,28,1), y = y_target.reshape(1,10)) + else: + x_test_adv = df.generate(x_test_original[0].reshape(1,28,28,1)) + + for i in range(1, len(x_test_original)): + if targeted: + tmp_x_test_adv = df.generate(x_test_original[i].reshape(1,28,28,1), y = y_target.reshape(1,10)) + x_test_adv = np.concatenate([x_test_adv, tmp_x_test_adv]) + else: + tmp_x_test_adv = df.generate(x_test_original[i].reshape(1,28,28,1)) + x_test_adv = np.concatenate([x_test_adv, tmp_x_test_adv]) + + self.assertFalse((x_test == x_test_adv).all()) + self.assertFalse((0.0 == x_test_adv).all()) + + y_pred = get_labels_np_array(classifier.predict(x_test_adv)) + self.assertFalse((y_test == y_pred).all()) + + accuracy = np.sum(np.argmax(y_pred, axis=1) == np.argmax(self.y_test_mnist, axis=1)) / self.n_test + logger.info("Accuracy on adversarial examples: %.2f%%", (accuracy * 100)) + + # Check that x_test has not been modified by attack and classifier + self.assertAlmostEqual(float(np.max(np.abs(x_test_original - x_test))), 0.0, delta=0.00001) + + def test_classifier_type_check_fail(self): + backend_test_classifier_type_check_fail(SimBA, [BaseEstimator, ClassGradientsMixin]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/attacks/test_targeted_universal_perturbation.py b/tests/attacks/test_targeted_universal_perturbation.py new file mode 100644 index 0000000000..21a7c9276a --- /dev/null +++ b/tests/attacks/test_targeted_universal_perturbation.py @@ -0,0 +1,167 @@ +# MIT License +# +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2018 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +from __future__ import absolute_import, division, print_function, unicode_literals + +import logging +import unittest + +import numpy as np + +from art.attacks.evasion.targeted_universal_perturbation import TargetedUniversalPerturbation +from art.estimators.classification.classifier import ClassGradientsMixin +from art.estimators.classification.keras import KerasClassifier +from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin, LossGradientsMixin +from tests.attacks.utils import backend_test_classifier_type_check_fail +from tests.utils import ( + TestBase, + get_image_classifier_kr, + get_image_classifier_pt, + get_image_classifier_tf, +) + +logger = logging.getLogger(__name__) + + +class TestTargetedUniversalPerturbation(TestBase): + """ + A unittest class for testing the TargetedUniversalPerturbation attack. + + This module tests the Targeted Universal Perturbation. + + | Targeted Universal Perturbation Paper: + Hirano H and Takemoto K (2020) Simple iterative method for generating targeted universal adversarial perturbations. in Proceedings of 25th International Symposium on Artificial Life and Robotics (AROB 25th 2020), pp. 426-430. + (arXiv link: https://arxiv.org/abs/1911.06502) + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.n_train = 500 + cls.n_test = 10 + cls.x_train_mnist = cls.x_train_mnist[0 : cls.n_train] + cls.y_train_mnist = cls.y_train_mnist[0 : cls.n_train] + cls.x_test_mnist = cls.x_test_mnist[0 : cls.n_test] + cls.y_test_mnist = cls.y_test_mnist[0 : cls.n_test] + + def test_tensorflow_mnist(self): + """ + First test with the TensorFlowClassifier. + :return: + """ + x_test_original = self.x_test_mnist.copy() + + # Build TensorFlowClassifier + tfc, sess = get_image_classifier_tf() + + # set target label + target = 0 + y_target = np.zeros([len(self.x_train_mnist), 10]) + for i in range(len(self.x_train_mnist)): + y_target[i, target] = 1.0 + + # Attack + up = TargetedUniversalPerturbation(tfc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True}) + x_train_adv = up.generate(self.x_train_mnist, y = y_target) + self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) + + x_test_adv = self.x_test_mnist + up.noise + self.assertFalse((self.x_test_mnist == x_test_adv).all()) + + train_y_pred = np.argmax(tfc.predict(x_train_adv), axis=1) + test_y_pred = np.argmax(tfc.predict(x_test_adv), axis=1) + self.assertFalse((np.argmax(self.y_test_mnist, axis=1) == test_y_pred).all()) + self.assertFalse((np.argmax(self.y_train_mnist, axis=1) == train_y_pred).all()) + + # Check that x_test has not been modified by attack and classifier + self.assertAlmostEqual(float(np.max(np.abs(x_test_original - self.x_test_mnist))), 0.0, delta=0.00001) + + def test_keras_mnist(self): + """ + Second test with the KerasClassifier. + :return: + """ + x_test_original = self.x_test_mnist.copy() + + # Build KerasClassifier + krc = get_image_classifier_kr() + + # set target label + target = 0 + y_target = np.zeros([len(self.x_train_mnist), 10]) + for i in range(len(self.x_train_mnist)): + y_target[i, target] = 1.0 + + # Attack + up = TargetedUniversalPerturbation(krc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True}) + x_train_adv = up.generate(self.x_train_mnist, y = y_target) + self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) + + x_test_adv = self.x_test_mnist + up.noise + self.assertFalse((self.x_test_mnist == x_test_adv).all()) + + train_y_pred = np.argmax(krc.predict(x_train_adv), axis=1) + test_y_pred = np.argmax(krc.predict(x_test_adv), axis=1) + self.assertFalse((np.argmax(self.y_test_mnist, axis=1) == test_y_pred).all()) + self.assertFalse((np.argmax(self.y_train_mnist, axis=1) == train_y_pred).all()) + + # Check that x_test has not been modified by attack and classifier + self.assertAlmostEqual(float(np.max(np.abs(x_test_original - self.x_test_mnist))), 0.0, delta=0.00001) + + def test_pytorch_mnist(self): + """ + Third test with the PyTorchClassifier. + :return: + """ + x_train_mnist = np.swapaxes(self.x_train_mnist, 1, 3).astype(np.float32) + x_test_mnist = np.swapaxes(self.x_test_mnist, 1, 3).astype(np.float32) + x_test_original = x_test_mnist.copy() + + # Build PyTorchClassifier + ptc = get_image_classifier_pt() + + # set target label + target = 0 + y_target = np.zeros([len(self.x_train_mnist), 10]) + for i in range(len(self.x_train_mnist)): + y_target[i, target] = 1.0 + + # Attack + up = TargetedUniversalPerturbation(ptc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True}) + x_train_mnist_adv = up.generate(x_train_mnist, y = y_target) + self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) + + x_test_mnist_adv = x_test_mnist + up.noise + self.assertFalse((x_test_mnist == x_test_mnist_adv).all()) + + train_y_pred = np.argmax(ptc.predict(x_train_mnist_adv), axis=1) + test_y_pred = np.argmax(ptc.predict(x_test_mnist_adv), axis=1) + self.assertFalse((np.argmax(self.y_test_mnist, axis=1) == test_y_pred).all()) + self.assertFalse((np.argmax(self.y_train_mnist, axis=1) == train_y_pred).all()) + + # Check that x_test has not been modified by attack and classifier + self.assertAlmostEqual(float(np.max(np.abs(x_test_original - x_test_mnist))), 0.0, delta=0.00001) + + def test_classifier_type_check_fail(self): + backend_test_classifier_type_check_fail( + TargetedUniversalPerturbation, [BaseEstimator, NeuralNetworkMixin, ClassGradientsMixin, LossGradientsMixin] + ) + + +if __name__ == "__main__": + unittest.main() From c84057717e32db87a567f850af8cbbafe4e7bdd2 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:25:28 +0900 Subject: [PATCH 75/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 93ba72a018..c379dbc015 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -23,8 +23,7 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) import logging -import types -from typing import Any, Dict, Optional, Union +from typing import Optional import numpy as np from scipy.fftpack import idct From 97c0b788bbc981a94d077d6d82387e8acc23150e Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:25:42 +0900 Subject: [PATCH 76/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index c379dbc015..c050de9be2 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -20,7 +20,7 @@ | Paper link: https://arxiv.org/abs/1905.07121 """ -from __future__ import (absolute_import, division, print_function, unicode_literals) +from __future__ import absolute_import, division, print_function, unicode_literals import logging from typing import Optional From 6de27a089ba36c501fcfc0852793ddf74e4a1bc9 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:25:51 +0900 Subject: [PATCH 77/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index cbd6ee604c..80b362fc1b 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -25,7 +25,7 @@ import logging import random import types -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Optional import numpy as np From d407b4d0ec0821cfa160a41b681b995e9c83b251 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:26:01 +0900 Subject: [PATCH 78/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 80b362fc1b..e74b32e9b4 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -29,10 +29,10 @@ import numpy as np -from art.classifiers.classifier import ClassifierNeuralNetwork, ClassifierGradients from art.attacks.attack import EvasionAttack from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin, LossGradientsMixin from art.estimators.classification.classifier import ( + ClassifierGradients, ClassGradientsMixin, ) from art.utils import projection From 972b9cafbdcd2b341f87d35a47dfff467be3cbf3 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:26:12 +0900 Subject: [PATCH 79/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index c050de9be2..073f4cd729 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -16,7 +16,7 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """ -This module implements the black-box attack `simba`. +This module implements the black-box attack `SimBA`. | Paper link: https://arxiv.org/abs/1905.07121 """ From a7dfc36f52b851139e4f58028b5152745b543673 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:26:23 +0900 Subject: [PATCH 80/91] Update tests/attacks/test_targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- tests/attacks/test_targeted_universal_perturbation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/attacks/test_targeted_universal_perturbation.py b/tests/attacks/test_targeted_universal_perturbation.py index 21a7c9276a..14dd62c5ff 100644 --- a/tests/attacks/test_targeted_universal_perturbation.py +++ b/tests/attacks/test_targeted_universal_perturbation.py @@ -1,6 +1,6 @@ # MIT License # -# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2018 +# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2020 # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the From 783e202097cc4df78450abcb79ffbb2f93beef2a Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:26:32 +0900 Subject: [PATCH 81/91] Update tests/attacks/test_simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- tests/attacks/test_simba.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/attacks/test_simba.py b/tests/attacks/test_simba.py index 113999857c..004e221d1d 100644 --- a/tests/attacks/test_simba.py +++ b/tests/attacks/test_simba.py @@ -42,8 +42,7 @@ class TestSimBA(TestBase): Note: SimBA runs only in Keras and TensorFlow (not in PyTorch) This is due to the channel first format in PyTorch. - | SimBA Paper link: - https://arxiv.org/abs/1905.07121 + | Paper link: https://arxiv.org/abs/1905.07121 """ @classmethod From 0d6dc4d1dbcb3743d9a672f3dcc5b3427ae88f95 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:26:42 +0900 Subject: [PATCH 82/91] Update tests/attacks/test_targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- tests/attacks/test_targeted_universal_perturbation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/attacks/test_targeted_universal_perturbation.py b/tests/attacks/test_targeted_universal_perturbation.py index 14dd62c5ff..4c7cf68150 100644 --- a/tests/attacks/test_targeted_universal_perturbation.py +++ b/tests/attacks/test_targeted_universal_perturbation.py @@ -43,9 +43,7 @@ class TestTargetedUniversalPerturbation(TestBase): This module tests the Targeted Universal Perturbation. - | Targeted Universal Perturbation Paper: - Hirano H and Takemoto K (2020) Simple iterative method for generating targeted universal adversarial perturbations. in Proceedings of 25th International Symposium on Artificial Life and Robotics (AROB 25th 2020), pp. 426-430. - (arXiv link: https://arxiv.org/abs/1911.06502) + | Paper link: https://arxiv.org/abs/1911.06502) """ @classmethod From 43c08aed76b43caf0ee6f35f9b619f907063593a Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:26:53 +0900 Subject: [PATCH 83/91] Update art/attacks/evasion/targeted_universal_perturbation.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/targeted_universal_perturbation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index e74b32e9b4..4fd7326062 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -173,7 +173,7 @@ def _get_attack(self, a_name: str, params: Optional[Dict[str, Any]] = None) -> E """ try: attack_class = self._get_class(self.attacks_dict[a_name]) - a_instance = attack_class(self.estimator) + a_instance = attack_class(self.estimator) # type: ignore if params: a_instance.set_params(**params) From 6a39d86e14139cefb1afa0373113ef3d3ce4be6c Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 26 Jun 2020 09:36:06 +0900 Subject: [PATCH 84/91] our temporary modification removed --- art/estimators/classification/pytorch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/art/estimators/classification/pytorch.py b/art/estimators/classification/pytorch.py index c8cb3236d9..814ec8217e 100644 --- a/art/estimators/classification/pytorch.py +++ b/art/estimators/classification/pytorch.py @@ -401,7 +401,7 @@ def loss_gradient(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: # Compute the gradient and return model_outputs = self._model(inputs_t) - loss = self._loss(model_outputs[-1], labels_t.long()) + loss = self._loss(model_outputs[-1], labels_t) # Clean gradients self._model.zero_grad() From fc96d41eceff0defebc0489d13a01b2bb997b13a Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 26 Jun 2020 09:46:16 +0900 Subject: [PATCH 85/91] new unittests added --- run_tests.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/run_tests.sh b/run_tests.sh index 4e28129d4b..9853a4bcb3 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -54,7 +54,9 @@ declare -a attacks=("tests/attacks/test_adversarial_patch.py" \ "tests/attacks/test_zoo.py" \ "tests/attacks/test_pixel_attack.py" \ "tests/attacks/test_threshold_attack.py" \ - "tests/attacks/test_wasserstein.py" ) + "tests/attacks/test_wasserstein.py" \ + "tests/attacks/test_targeted_universal_perturbation.py" \ + "tests/attacks/test_simba.py" ) declare -a classifiers=("tests/estimators/certification/test_randomized_smoothing.py" \ "tests/estimators/classification/test_blackbox.py" \ From 5251b1adf47b902d46672631e71e8b87101af1d6 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 26 Jun 2020 11:08:05 +0900 Subject: [PATCH 86/91] pytorch ver added --- tests/attacks/test_simba.py | 44 +++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/tests/attacks/test_simba.py b/tests/attacks/test_simba.py index 004e221d1d..e9dba402cf 100644 --- a/tests/attacks/test_simba.py +++ b/tests/attacks/test_simba.py @@ -49,7 +49,7 @@ class TestSimBA(TestBase): def setUpClass(cls): super().setUpClass() - cls.n_test = 1 + cls.n_test = 2 cls.x_test_mnist = cls.x_test_mnist[0 : cls.n_test] cls.y_test_mnist = cls.y_test_mnist[0 : cls.n_test] @@ -69,14 +69,14 @@ def test_tensorflow_mnist(self): classifier, sess = get_image_classifier_tf() self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, False) - #def test_pytorch_mnist(self): - # """ - # Test with the PyTorchClassifier. (Untargeted Attack) - # :return: - # """ - # x_test = np.reshape(self.x_test_mnist, (self.x_test_mnist.shape[0], 1, 28, 28)).astype(np.float32) - # classifier = get_image_classifier_pt() - # self._test_attack(classifier, x_test, self.y_test_mnist, False) + def test_pytorch_mnist(self): + """ + Test with the PyTorchClassifier. (Untargeted Attack) + :return: + """ + x_test = np.reshape(self.x_test_mnist, (self.x_test_mnist.shape[0], 1, 28, 28)).astype(np.float32) + classifier = get_image_classifier_pt() + self._test_attack(classifier, x_test, self.y_test_mnist, False) def test_keras_mnist_targeted(self): """ @@ -95,14 +95,14 @@ def test_tensorflow_mnist_targeted(self): self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, True) # SimBA is not avaialbe for PyTorch - #def test_pytorch_mnist_targeted(self): - # """ - # Test with the PyTorchClassifier. (Targeted Attack) - # :return: - # """ - # x_test = np.reshape(self.x_test_mnist, (self.x_test_mnist.shape[0], 1, 28, 28)).astype(np.float32) - # classifier = get_image_classifier_pt() - # self._test_attack(classifier, x_test, self.y_test_mnist, True) + def test_pytorch_mnist_targeted(self): + """ + Test with the PyTorchClassifier. (Targeted Attack) + :return: + """ + x_test = np.reshape(self.x_test_mnist, (self.x_test_mnist.shape[0], 1, 28, 28)).astype(np.float32) + classifier = get_image_classifier_pt() + self._test_attack(classifier, x_test, self.y_test_mnist, True) def _test_attack(self, classifier, x_test, y_test, targeted): """ @@ -118,17 +118,19 @@ def _test_attack(self, classifier, x_test, y_test, targeted): df = SimBA(classifier, attack="dct", targeted=targeted) + x_i = x_test_original[0][None, ...] if targeted: - x_test_adv = df.generate(x_test_original[0].reshape(1,28,28,1), y = y_target.reshape(1,10)) + x_test_adv = df.generate(x_i, y = y_target.reshape(1,10)) else: - x_test_adv = df.generate(x_test_original[0].reshape(1,28,28,1)) + x_test_adv = df.generate(x_i) for i in range(1, len(x_test_original)): + x_i = x_test_original[i][None, ...] if targeted: - tmp_x_test_adv = df.generate(x_test_original[i].reshape(1,28,28,1), y = y_target.reshape(1,10)) + tmp_x_test_adv = df.generate(x_i, y = y_target.reshape(1,10)) x_test_adv = np.concatenate([x_test_adv, tmp_x_test_adv]) else: - tmp_x_test_adv = df.generate(x_test_original[i].reshape(1,28,28,1)) + tmp_x_test_adv = df.generate(x_i) x_test_adv = np.concatenate([x_test_adv, tmp_x_test_adv]) self.assertFalse((x_test == x_test_adv).all()) From d88519ec7a3ffe237c6e46e00e89ade3a5f53458 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 26 Jun 2020 11:43:45 +0900 Subject: [PATCH 87/91] modified to run in pytorch --- art/attacks/evasion/simba.py | 203 +++++++++++++++++++++-------------- 1 file changed, 121 insertions(+), 82 deletions(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 073f4cd729..e9400e59dd 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -40,20 +40,31 @@ class SimBA(EvasionAttack): - attack_params = EvasionAttack.attack_params + ['attack', 'max_iter', 'epsilon', 'order', 'freq_dim', 'stride', 'targeted', 'batch_size',] + attack_params = EvasionAttack.attack_params + [ + "attack", + "max_iter", + "epsilon", + "order", + "freq_dim", + "stride", + "targeted", + "batch_size", + ] _estimator_requirements = (BaseEstimator, ClassGradientsMixin) - def __init__(self, - classifier: ClassifierGradients, - attack: str = 'dct', - max_iter: int = 3000, - order: str = 'random', - epsilon: float = 0.1, - freq_dim: int = 4, - stride: int = 1, - targeted: bool = False, - batch_size: int = 1): + def __init__( + self, + classifier: ClassifierGradients, + attack: str = "dct", + max_iter: int = 3000, + order: str = "random", + epsilon: float = 0.1, + freq_dim: int = 4, + stride: int = 1, + targeted: bool = False, + batch_size: int = 1, + ): """ Create a SimBA (dct) attack instance. @@ -92,44 +103,51 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n if y is None: if self.targeted: - raise ValueError('Target labels `y` need to be provided for a targeted attack.') + raise ValueError("Target labels `y` need to be provided for a targeted attack.") else: # Use model predictions as correct outputs - logger.info('Using the model prediction as the correct label for SimBA.') + logger.info("Using the model prediction as the correct label for SimBA.") y_i = np.argmax(preds, axis=1) else: y_i = np.argmax(y, axis=1) - + desired_label = y_i[0] current_label = np.argmax(preds, axis=1)[0] last_prob = preds.reshape(-1)[desired_label] + if self.estimator.channels_first: + nb_channels = x.shape[1] + else: + nb_channels = x.shape[3] + n_dims = np.prod(x.shape) - if self.attack == 'px': - if self.order == 'diag': - indices = self.diagonal_order(x.shape[2], x.shape[3])[:self.max_iter] - elif self.order == 'random': - indices = np.random.permutation(n_dims)[:self.max_iter] + if self.attack == "px": + if self.order == "diag": + indices = self.diagonal_order(x.shape[2], nb_channels)[: self.max_iter] + elif self.order == "random": + indices = np.random.permutation(n_dims)[: self.max_iter] indices_size = len(indices) while indices_size < self.max_iter: - if self.order == 'diag': - tmp_indices = self.diagonal_order(x.shape[2], x.shape[3]) - elif self.order == 'random': + if self.order == "diag": + tmp_indices = self.diagonal_order(x.shape[2], nb_channels) + elif self.order == "random": tmp_indices = np.random.permutation(n_dims) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] + indices = np.hstack((indices, tmp_indices))[: self.max_iter] indices_size = len(indices) - elif self.attack == 'dct': - indices = self._block_order(x.shape[2], x.shape[3], initial_size=self.freq_dim, stride=self.stride)[:self.max_iter] + elif self.attack == "dct": + indices = self._block_order(x.shape[2], nb_channels, initial_size=self.freq_dim, stride=self.stride)[ + : self.max_iter + ] indices_size = len(indices) while indices_size < self.max_iter: - tmp_indices = self._block_order(x.shape[2], x.shape[3], initial_size=self.freq_dim, stride=self.stride) - indices = np.hstack((indices, tmp_indices))[:self.max_iter] + tmp_indices = self._block_order(x.shape[2], nb_channels, initial_size=self.freq_dim, stride=self.stride) + indices = np.hstack((indices, tmp_indices))[: self.max_iter] indices_size = len(indices) trans = lambda z: self._block_idct(z, block_size=x.shape[2]) clip_min = -np.inf - clip_max = np.inf + clip_max = np.inf if self.estimator.clip_values is not None: clip_min, clip_max = self.estimator.clip_values @@ -143,71 +161,79 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n nb_iter = 0 while term_flag == 0 and nb_iter < self.max_iter: - diff = np.zeros(n_dims) + diff = np.zeros(n_dims).astype(ART_NUMPY_DTYPE) diff[indices[nb_iter]] = self.epsilon - if self.attack == 'dct': - left_preds = self.estimator.predict(np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) - elif self.attack == 'px': - left_preds = self.estimator.predict(np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + if self.attack == "dct": + left_preds = self.estimator.predict( + np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size + ) + elif self.attack == "px": + left_preds = self.estimator.predict( + np.clip(x - diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size + ) left_prob = left_preds.reshape(-1)[desired_label] - if self.attack == 'dct': - right_preds = self.estimator.predict(np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size) - elif self.attack == 'px': - right_preds = self.estimator.predict(np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size) + if self.attack == "dct": + right_preds = self.estimator.predict( + np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max), batch_size=self.batch_size + ) + elif self.attack == "px": + right_preds = self.estimator.predict( + np.clip(x + diff.reshape(x.shape), clip_min, clip_max), batch_size=self.batch_size + ) right_prob = right_preds.reshape(-1)[desired_label] - + # Use (2 * int(self.targeted) - 1) to shorten code? if self.targeted: if left_prob > last_prob: if left_prob > right_prob: - if self.attack == 'dct': + if self.attack == "dct": x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': + elif self.attack == "px": x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) last_prob = left_prob current_label = np.argmax(left_preds, axis=1)[0] else: - if self.attack == 'dct': + if self.attack == "dct": x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': + elif self.attack == "px": x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) last_prob = right_prob current_label = np.argmax(right_preds, axis=1)[0] else: if right_prob > last_prob: - if self.attack == 'dct': + if self.attack == "dct": x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': + elif self.attack == "px": x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) last_prob = right_prob current_label = np.argmax(right_preds, axis=1)[0] else: if left_prob < last_prob: if left_prob < right_prob: - if self.attack == 'dct': + if self.attack == "dct": x = np.clip(x - trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': + elif self.attack == "px": x = np.clip(x - diff.reshape(x.shape), clip_min, clip_max) last_prob = left_prob current_label = np.argmax(left_preds, axis=1)[0] else: - if self.attack == 'dct': + if self.attack == "dct": x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': + elif self.attack == "px": x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) last_prob = right_prob current_label = np.argmax(right_preds, axis=1)[0] else: if right_prob < last_prob: - if self.attack == 'dct': + if self.attack == "dct": x = np.clip(x + trans(diff.reshape(x.shape)), clip_min, clip_max) - elif self.attack == 'px': + elif self.attack == "px": x = np.clip(x + diff.reshape(x.shape), clip_min, clip_max) last_prob = right_prob current_label = np.argmax(right_preds, axis=1)[0] - + if self.targeted: if desired_label == current_label: term_flag = 1 @@ -218,9 +244,9 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n nb_iter = nb_iter + 1 if nb_iter < self.max_iter: - logger.info('SimBA (%s) %s attack succeed', self.attack, ['non-targeted', 'targeted'][int(self.targeted)]) + logger.info("SimBA (%s) %s attack succeed", self.attack, ["non-targeted", "targeted"][int(self.targeted)]) else: - logger.info('SimBA (%s) %s attack failed', self.attack, ['non-targeted', 'targeted'][int(self.targeted)]) + logger.info("SimBA (%s) %s attack failed", self.attack, ["non-targeted", "targeted"][int(self.targeted)]) return x @@ -233,22 +259,22 @@ def _check_params(self) -> None: raise ValueError("The overshoot parameter must not be negative.") if self.batch_size != 1: - raise ValueError('The batch size `batch_size` has to be 1 in this implementation.') - + raise ValueError("The batch size `batch_size` has to be 1 in this implementation.") + if not isinstance(self.stride, (int, np.int)) or self.stride <= 0: raise ValueError("The `stride` value must be a positive integer.") - + if not isinstance(self.freq_dim, (int, np.int)) or self.freq_dim <= 0: raise ValueError("The `freq_dim` value must be a positive integer.") - - if self.order != 'random' and self.order != 'diag': - raise ValueError('The order of pixel attacks has to be `random` or `diag`.') - - if self.attack != 'px' and self.attack != 'dct': - raise ValueError('The attack type has to be `px` or `dct`.') - + + if self.order != "random" and self.order != "diag": + raise ValueError("The order of pixel attacks has to be `random` or `diag`.") + + if self.attack != "px" and self.attack != "dct": + raise ValueError("The attack type has to be `px` or `dct`.") + if not isinstance(self.targeted, (int)) or (self.targeted != 0 and self.targeted != 1): - raise ValueError('`targeted` has to be a logical value.') + raise ValueError("`targeted` has to be a logical value.") def _block_order(self, img_size, channels, initial_size=2, stride=1): """ @@ -267,7 +293,7 @@ def _block_order(self, img_size, channels, initial_size=2, stride=1): :return z: An array holding the block order of DCT attacks. """ - order = np.zeros((channels , img_size , img_size)) + order = np.zeros((channels, img_size, img_size)).astype(ART_NUMPY_DTYPE) total_elems = channels * initial_size * initial_size perm = np.random.permutation(total_elems) order[:, :initial_size, :initial_size] = perm.reshape((channels, initial_size, initial_size)) @@ -275,11 +301,13 @@ def _block_order(self, img_size, channels, initial_size=2, stride=1): num_elems = channels * (2 * stride * i + stride * stride) perm = np.random.permutation(num_elems) + total_elems num_first = channels * stride * (stride + i) - order[:, :(i+stride), i:(i+stride)] = perm[:num_first].reshape((channels, -1, stride)) - order[:, i:(i+stride), :i] = perm[num_first:].reshape((channels, stride, -1)) + order[:, : (i + stride), i : (i + stride)] = perm[:num_first].reshape((channels, -1, stride)) + order[:, i : (i + stride), :i] = perm[num_first:].reshape((channels, stride, -1)) total_elems += num_elems - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() - + if self.estimator.channels_first: + return order.reshape(1, -1).squeeze().argsort() + else: + return order.transpose(1, 2, 0).reshape(1, -1).squeeze().argsort() def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): """ @@ -292,23 +320,30 @@ def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): :return z: An array holding the order of DCT attacks. """ - x = x.transpose(0,3,1,2) - z = np.zeros(x.shape) + if not self.estimator.channels_first: + x = x.transpose(0, 3, 1, 2) + z = np.zeros(x.shape).astype(ART_NUMPY_DTYPE) num_blocks = int(x.shape[2] / block_size) mask = np.zeros((x.shape[0], x.shape[1], block_size, block_size)) if type(ratio) != float: for i in range(x.shape[0]): - mask[i, :, :int(block_size * ratio[i]), :int(block_size * ratio[i])] = 1 + mask[i, :, : int(block_size * ratio[i]), : int(block_size * ratio[i])] = 1 else: - mask[:, :, :int(block_size * ratio), :int(block_size * ratio)] = 1 + mask[:, :, : int(block_size * ratio), : int(block_size * ratio)] = 1 for i in range(num_blocks): for j in range(num_blocks): - submat = x[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] + submat = x[:, :, (i * block_size) : ((i + 1) * block_size), (j * block_size) : ((j + 1) * block_size)] if masked: submat = submat * mask - z[:, :, (i * block_size):((i + 1) * block_size), (j * block_size):((j + 1) * block_size)] = idct(idct(submat, axis=3, norm='ortho'), axis=2, norm='ortho') - return z.transpose(0,2,3,1) - + z[:, :, (i * block_size) : ((i + 1) * block_size), (j * block_size) : ((j + 1) * block_size)] = idct( + idct(submat, axis=3, norm="ortho"), axis=2, norm="ortho" + ) + + if self.estimator.channels_first: + return z + else: + return z.transpose(0, 2, 3, 1) + def diagonal_order(self, image_size, channels): """ Defines a diagonal order for pixel attacks. @@ -324,15 +359,19 @@ def diagonal_order(self, image_size, channels): :return z: An array holding the diagonal order of pixel attacks. """ x = np.arange(0, image_size).cumsum() - order = np.zeros((image_size, image_size)) + order = np.zeros((image_size, image_size)).astype(ART_NUMPY_DTYPE) for i in range(image_size): - order[i, :(image_size - i)] = i + x[i:] + order[i, : (image_size - i)] = i + x[i:] for i in range(1, image_size): - reverse = order[image_size - i - 1].take([i for i in range(i-1, -1, -1)]) - order[i, (image_size - i):] = image_size * image_size - 1 - reverse + reverse = order[image_size - i - 1].take([i for i in range(i - 1, -1, -1)]) + order[i, (image_size - i) :] = image_size * image_size - 1 - reverse if channels > 1: order_2d = order order = np.zeros((channels, image_size, image_size)) for i in range(channels): order[i, :, :] = 3 * order_2d + i - return order.transpose(1,2,0).reshape(1, -1).squeeze().argsort() + + if self.estimator.channels_first: + return order.reshape(1, -1).squeeze().argsort() + else: + return order.transpose(1, 2, 0).reshape(1, -1).squeeze().argsort() From a512c3270109d227d8780fcb9d28e369ba5bfa38 Mon Sep 17 00:00:00 2001 From: kztakemoto Date: Fri, 26 Jun 2020 11:46:35 +0900 Subject: [PATCH 88/91] modified to fix PEP8 format using black --- .../targeted_universal_perturbation.py | 37 ++++++++++--------- art/attacks/evasion/universal_perturbation.py | 4 +- tests/attacks/test_simba.py | 8 ++-- .../test_targeted_universal_perturbation.py | 18 ++++++--- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/art/attacks/evasion/targeted_universal_perturbation.py b/art/attacks/evasion/targeted_universal_perturbation.py index 4fd7326062..d57994e79a 100644 --- a/art/attacks/evasion/targeted_universal_perturbation.py +++ b/art/attacks/evasion/targeted_universal_perturbation.py @@ -47,22 +47,25 @@ class TargetedUniversalPerturbation(EvasionAttack): | Paper link: https://arxiv.org/abs/1911.06502 """ + attacks_dict = { - 'fgsm': 'art.attacks.evasion.fast_gradient.FastGradientMethod', - 'simba': 'art.attacks.evasion.simba.SimBA' - } - attack_params = EvasionAttack.attack_params + ['attacker', 'attacker_params', 'delta', 'max_iter', 'eps', 'norm'] + "fgsm": "art.attacks.evasion.fast_gradient.FastGradientMethod", + "simba": "art.attacks.evasion.simba.SimBA", + } + attack_params = EvasionAttack.attack_params + ["attacker", "attacker_params", "delta", "max_iter", "eps", "norm"] _estimator_requirements = (BaseEstimator, NeuralNetworkMixin, ClassGradientsMixin, LossGradientsMixin) - def __init__(self, - classifier: ClassifierGradients, - attacker: str = 'fgsm', - attacker_params: Optional[Dict[str, Any]] = None, - delta: float = 0.2, - max_iter: int = 20, - eps: float = 10.0, - norm: int = np.inf): + def __init__( + self, + classifier: ClassifierGradients, + attacker: str = "fgsm", + attacker_params: Optional[Dict[str, Any]] = None, + delta: float = 0.2, + max_iter: int = 20, + eps: float = 10.0, + norm: int = np.inf, + ): """ :param classifier: A trained classifier. :param attacker: Adversarial attack name. Default is 'deepfool'. Supported names: 'fgsm'. @@ -91,7 +94,7 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: :param y: An array with the targeted labels. :return: An array holding the adversarial examples. """ - logger.info('Computing targeted universal perturbation based on %s attack.', self.attacker) + logger.info("Computing targeted universal perturbation based on %s attack.", self.attacker) # Init universal perturbation noise = 0 @@ -106,7 +109,7 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: # Start to generate the adversarial examples nb_iter = 0 - while targeted_success_rate < 1. - self.delta and nb_iter < self.max_iter: + while targeted_success_rate < 1.0 - self.delta and nb_iter < self.max_iter: # Go through all the examples randomly rnd_idx = random.sample(range(nb_instances), nb_instances) @@ -134,7 +137,7 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: # Apply attack and clip x_adv = x + noise - if hasattr(self.estimator, 'clip_values') and self.estimator.clip_values is not None: + if hasattr(self.estimator, "clip_values") and self.estimator.clip_values is not None: clip_min, clip_max = self.estimator.clip_values x_adv = np.clip(x_adv, clip_min, clip_max) @@ -147,8 +150,8 @@ def generate(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray: self.targeted_success_rate = targeted_success_rate self.converged = nb_iter < self.max_iter self.noise = noise - logger.info('Fooling rate of universal perturbation attack: %.2f%%', 100 * fooling_rate) - logger.info('Targeted success rate of universal perturbation attack: %.2f%%', 100 * targeted_success_rate) + logger.info("Fooling rate of universal perturbation attack: %.2f%%", 100 * fooling_rate) + logger.info("Targeted success rate of universal perturbation attack: %.2f%%", 100 * targeted_success_rate) return x_adv diff --git a/art/attacks/evasion/universal_perturbation.py b/art/attacks/evasion/universal_perturbation.py index a9f5925836..2671e556ea 100644 --- a/art/attacks/evasion/universal_perturbation.py +++ b/art/attacks/evasion/universal_perturbation.py @@ -62,7 +62,7 @@ class UniversalPerturbation(EvasionAttack): "newtonfool": "art.attacks.evasion.newtonfool.NewtonFool", "jsma": "art.attacks.evasion.saliency_map.SaliencyMapMethod", "vat": "art.attacks.evasion.virtual_adversarial.VirtualAdversarialMethod", - 'simba': 'art.attacks.evasion.simba.SimBA', + "simba": "art.attacks.evasion.simba.SimBA", } attack_params = EvasionAttack.attack_params + [ "attacker", @@ -122,7 +122,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n # Instantiate the middle attacker and get the predicted labels attacker = self._get_attack(self.attacker, self.attacker_params) if y is None: - logger.info('Using model predictions as the correct labels for UAP.') + logger.info("Using model predictions as the correct labels for UAP.") pred_y = self.estimator.predict(x, batch_size=1) correct_y_max = np.argmax(pred_y, axis=1) diff --git a/tests/attacks/test_simba.py b/tests/attacks/test_simba.py index e9dba402cf..f2aed90f24 100644 --- a/tests/attacks/test_simba.py +++ b/tests/attacks/test_simba.py @@ -68,7 +68,7 @@ def test_tensorflow_mnist(self): """ classifier, sess = get_image_classifier_tf() self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, False) - + def test_pytorch_mnist(self): """ Test with the PyTorchClassifier. (Untargeted Attack) @@ -93,7 +93,7 @@ def test_tensorflow_mnist_targeted(self): """ classifier, sess = get_image_classifier_tf() self._test_attack(classifier, self.x_test_mnist, self.y_test_mnist, True) - + # SimBA is not avaialbe for PyTorch def test_pytorch_mnist_targeted(self): """ @@ -120,14 +120,14 @@ def _test_attack(self, classifier, x_test, y_test, targeted): x_i = x_test_original[0][None, ...] if targeted: - x_test_adv = df.generate(x_i, y = y_target.reshape(1,10)) + x_test_adv = df.generate(x_i, y=y_target.reshape(1, 10)) else: x_test_adv = df.generate(x_i) for i in range(1, len(x_test_original)): x_i = x_test_original[i][None, ...] if targeted: - tmp_x_test_adv = df.generate(x_i, y = y_target.reshape(1,10)) + tmp_x_test_adv = df.generate(x_i, y=y_target.reshape(1, 10)) x_test_adv = np.concatenate([x_test_adv, tmp_x_test_adv]) else: tmp_x_test_adv = df.generate(x_i) diff --git a/tests/attacks/test_targeted_universal_perturbation.py b/tests/attacks/test_targeted_universal_perturbation.py index 4c7cf68150..002ac2384a 100644 --- a/tests/attacks/test_targeted_universal_perturbation.py +++ b/tests/attacks/test_targeted_universal_perturbation.py @@ -74,8 +74,10 @@ def test_tensorflow_mnist(self): y_target[i, target] = 1.0 # Attack - up = TargetedUniversalPerturbation(tfc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True}) - x_train_adv = up.generate(self.x_train_mnist, y = y_target) + up = TargetedUniversalPerturbation( + tfc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True} + ) + x_train_adv = up.generate(self.x_train_mnist, y=y_target) self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) x_test_adv = self.x_test_mnist + up.noise @@ -106,8 +108,10 @@ def test_keras_mnist(self): y_target[i, target] = 1.0 # Attack - up = TargetedUniversalPerturbation(krc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True}) - x_train_adv = up.generate(self.x_train_mnist, y = y_target) + up = TargetedUniversalPerturbation( + krc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True} + ) + x_train_adv = up.generate(self.x_train_mnist, y=y_target) self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) x_test_adv = self.x_test_mnist + up.noise @@ -140,8 +144,10 @@ def test_pytorch_mnist(self): y_target[i, target] = 1.0 # Attack - up = TargetedUniversalPerturbation(ptc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True}) - x_train_mnist_adv = up.generate(x_train_mnist, y = y_target) + up = TargetedUniversalPerturbation( + ptc, max_iter=1, attacker="fgsm", attacker_params={"eps": 0.3, "targeted": True} + ) + x_train_mnist_adv = up.generate(x_train_mnist, y=y_target) self.assertTrue((up.fooling_rate >= 0.2) or not up.converged) x_test_mnist_adv = x_test_mnist + up.noise From d34b235edaeea59f1648db2c9993f92227bc703b Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 21:52:34 +0900 Subject: [PATCH 89/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index e9400e59dd..176cbff0f8 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -316,7 +316,8 @@ def _block_idct(self, x, block_size=8, masked=False, ratio=0.5): :param x: An array with the inputs to be attacked. :param block_size: block size for DCT attacks. :param masked: use the mask. - :param ratio: Ratio of the lowest frequency directions in order to make the adversarial perturbation in the low frequency space. + :param ratio: Ratio of the lowest frequency directions in order to make the adversarial perturbation in the low + frequency space. :return z: An array holding the order of DCT attacks. """ From 8384e95d73fe6c0f4440f10b3fb606f21db01e2e Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 21:52:45 +0900 Subject: [PATCH 90/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index 176cbff0f8..a59b06474d 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -144,7 +144,8 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n tmp_indices = self._block_order(x.shape[2], nb_channels, initial_size=self.freq_dim, stride=self.stride) indices = np.hstack((indices, tmp_indices))[: self.max_iter] indices_size = len(indices) - trans = lambda z: self._block_idct(z, block_size=x.shape[2]) + + def trans(z): return self._block_idct(z, block_size=x.shape[2]) clip_min = -np.inf clip_max = np.inf From 043d79a922f0dc8b92a7d6232854af61691f6628 Mon Sep 17 00:00:00 2001 From: kztakemoto <33507065+kztakemoto@users.noreply.github.com> Date: Fri, 26 Jun 2020 22:58:17 +0900 Subject: [PATCH 91/91] Update art/attacks/evasion/simba.py Co-authored-by: Beat Buesser <49047826+beat-buesser@users.noreply.github.com> --- art/attacks/evasion/simba.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/art/attacks/evasion/simba.py b/art/attacks/evasion/simba.py index a59b06474d..80229c1fd2 100644 --- a/art/attacks/evasion/simba.py +++ b/art/attacks/evasion/simba.py @@ -145,7 +145,8 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n indices = np.hstack((indices, tmp_indices))[: self.max_iter] indices_size = len(indices) - def trans(z): return self._block_idct(z, block_size=x.shape[2]) + def trans(z): + return self._block_idct(z, block_size=x.shape[2]) clip_min = -np.inf clip_max = np.inf