From 8b8f61b53d242db97d780ebfcf45a9887dfdb98c Mon Sep 17 00:00:00 2001 From: mehulrastogi Date: Fri, 27 Aug 2021 22:49:22 +0530 Subject: [PATCH 1/8] Adding One Pixel Attack Core --- code_soup/ch5/models/one_pixel_attack.py | 286 ++++++++++++++++++ requirements.txt | 1 + .../test_models/test_one_pixel_attack.py | 24 ++ 3 files changed, 311 insertions(+) create mode 100644 code_soup/ch5/models/one_pixel_attack.py create mode 100644 tests/test_ch5/test_models/test_one_pixel_attack.py diff --git a/code_soup/ch5/models/one_pixel_attack.py b/code_soup/ch5/models/one_pixel_attack.py new file mode 100644 index 0000000..6db6fe7 --- /dev/null +++ b/code_soup/ch5/models/one_pixel_attack.py @@ -0,0 +1,286 @@ +import numpy as np +import torch +from scipy.optimize import differential_evolution + +""" +The code is a slight modification and refactoring from the following repository +https://github.com/Hyperparticle/one-pixel-attack-keras + +""" + + +class OnePixelAttack: + + """ + Attack using One Pixel + + """ + + def __init__(self, model, device=None): + + """ + + Initialize the Attack obj + Parameters + ---------- + model: torch.nn + model to be attacked + device : string + 'cpu' / 'cuda' + + """ + + self.model = model + self.device = device + if self.device is None: + self.device = "cuda" if torch.cuda.is_available() else "cpu" + if self.labels is None: + self.labels = range(10) + + def perturb_image(self, perturbation, orig_img): + """ + orig_image: image to be changed + perturbation: tuple of (x,y,r,g,b) on basis of which the image is changed + [(),(),()]. Image can have multiple perturbations + """ + x_pos, y_pos, *rgb = perturbation + new_img = orig_img + new_img[:, x_pos, y_pos] = rgb + + return new_img + + def perturbation_image(self, perturbation_array, image): + """ + Applies multiple perturbation to a single image + + image: the image to be perturbated + perturbation_array: array like [x1, y1, r1, g1, b1, x2, y2, r2, g2, b2, ...] + the number of pixels to be changed + """ + perturbation_array = np.array(perturbation_array) + + perturbation_array = perturbation_array.astype(int) + + perturbation_array = np.split(perturbation_array, len(perturbation_array) // 5) + + new_image = image + + for perturb in perturbation_array: + new_image = self.perturb_image(perturb, new_image) + + return new_image + + def predict_class(self, xs, img, target_class, minimize=True): + """ + xs: 1d array to be evolved + img: image to be perturbed + traget_class: class to be targeted or avoided + """ + # Perturb the image with the given pixel(s) x and get the prediction of the model + img_perturbed = self.perturbation_image(xs, img) + prediction = self.model_predict(img_perturbed)[target_class] + + # This function should always be minimized, so return its complement + # if needed basically is targetted increase the prob + return prediction if minimize else 1 - prediction + + def model_predict(self, image): + """ + Helper function to predict probs from the model of just 1 image + """ + prediction = None + with torch.no_grad(): + image = torch.FloatTensor(image).reshape(1, *image.shape) + image = image.to(self.device) + prediction = self.model(image)[0].detach().numpy() + return prediction + + def attack_success( + self, x, img, target_class, targeted_attack=False, verbose=False + ): + """ + check if the attack is a success. the callback helper function for differential_evolution + Parameters + ---------- + x: 1d array that is evolved + img: image to be perturbed + target_class: class to be targeted or avoided + Returns: + ------- + true if the evolution needs to be stopped + """ + # Perturb the image with the given pixel(s) and get the prediction of the model + attack_image = self.perturbation_image(x, img) + + confidence = self.model_predict(attack_image) + predicted_class = np.argmax(confidence) + + # If the prediction is what we want (misclassification or + # targeted classification), return True + if verbose: + print("Confidence:", confidence[target_class]) + if (targeted_attack and predicted_class == target_class) or ( + not targeted_attack and predicted_class != target_class + ): + return True + + return False + + def attack( + self, + image, + original_label, + target=None, + pixel_count=1, + maxiter=75, + popsize=400, + verbose=False, + ): + + """ + Runs the attack on a single image, searches the image space + Parameters + ---------- + image: Numpy.Array + image of shape(channel, height, width) + original_label: int + class the image belongs too + target: int + class to be targetted + pixels_count: int + Number of Pixels to be perturbed (changed) + maxiter:int, optional + The maximum number of generations over which the entire population is evolved. + The maximum number of function evaluations (with no polishing) is: (maxiter + 1) * popsize * len(x) + popsize:int, optional + A multiplier for setting the total population size. The population has popsize * len(x) individuals. + verbose: boolean + set to true to print the confidence + Returns + ------- + perturbation_array: + List of all the best perturbations to the images in the batch + + + """ + + # Change the target class based on whether this is a targeted attack or not + targeted_attack = target is not None + target_class = target if targeted_attack else original_label + + # Define bounds for a flat vector of x,y,r,g,b values + # For more pixels, repeat this layout + print("Image shape:", image.shape) + dim_x, dim_y = image.shape[1], image.shape[2] + bounds = [(0, dim_x), (0, dim_y), (0, 256), (0, 256), (0, 256)] * pixel_count + + # Population multiplier, in terms of the size of the perturbation vector x + popmul = max(1, popsize // len(bounds)) + + # Format the predict/callback functions for the differential evolution algorithm + def predict_fn(xs): + print("Predicting!") + + return self.predict_class(xs, image, target_class, target is None) + + def callback_fn(x, convergence): + print("check success!") + return self.attack_success(x, image, target_class, targeted_attack, verbose) + + # Call Scipy's Implementation of Differential Evolution + attack_result = differential_evolution( + predict_fn, + bounds, + maxiter=maxiter, + popsize=popmul, + recombination=1, + atol=-1, + callback=callback_fn, + polish=False, + ) + + # ----------------- Calculate some useful statistics to return from this function + # attack_image = self.perturbation_image(attack_result.x, image) + # prior_probs = self.model_predict(image) + # predicted_probs = self.model_predict(attack_image) + # predicted_class = np.argmax(predicted_probs) + # actual_class = original_label + # success = predicted_class != actual_class + # cdiff = prior_probs[actual_class] - predicted_probs[actual_class] + + # Show the best attempt at a solution (successful or not) + # if plot: + # helper.plot_image(attack_image, actual_class, self.class_names, predicted_class) + + # return [pixel_count, attack_image, actual_class, predicted_class, success, cdiff, prior_probs, + # predicted_probs, attack_result.x] + + # return the best perturbation array + return attack_result.x + + def step( + self, + data, + labels=None, + pixels_perturbed=1, + targeted=False, + maxiter=75, + popsize=400, + verbose=False, + ): + """ + Runs the attack on a batch of images, searches the image space on a single batch of images + Parameters + ---------- + data : torch.Tensor + Batch of data + labels: List + list of all the unique classes from the dataset + pixels_perturbed: int + Number of Pixels to be perturbed (changed) + targeted: boolean + To decide if this is a targetted attack or not (in casee of targetted attack run all labels) + maxiter:int, optional + The maximum number of generations over which the entire population is evolved. + The maximum number of function evaluations (with no polishing) is: (maxiter + 1) * popsize * len(x) + popsize:int, optional + A multiplier for setting the total population size. The population has popsize * len(x) individuals. + verbose: boolean + set to true to print the confidence + + Returns + ------- + perturbation_array: + List of all the best perturbations to the images in the batch + + """ + batch_size = len(data) + + images, image_orig_label = data + + # store the best perturbation for all the images in the batch + perturbation_array = [] + + for i in range(batch_size): + image = images[i].detach().numpy() + orig_label = image_orig_label[i].detach().numpy() + self.samples_counter = self.samples_counter + 1 + targets = [None] if not targeted else range(len(labels)) + + for target in targets: + if targeted: + print("Attacking with target", labels[target]) + if target == orig_label: + continue + best_perturbation = self.attack( + image, + orig_label, + target, + pixels_perturbed, + maxiter=maxiter, + popsize=popsize, + verbose=verbose, + ) + perturbation_array.append(best_perturbation) + + return perturbation_array diff --git a/requirements.txt b/requirements.txt index 4ba746c..390ea82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ Pillow==8.3.1 torch==1.9.0 torchvision==0.10.0 parameterized==0.8.1 +scipy diff --git a/tests/test_ch5/test_models/test_one_pixel_attack.py b/tests/test_ch5/test_models/test_one_pixel_attack.py new file mode 100644 index 0000000..8d00b14 --- /dev/null +++ b/tests/test_ch5/test_models/test_one_pixel_attack.py @@ -0,0 +1,24 @@ +import unittest + +import torch +import torch.nn as nn + +from code_soup.ch5.models.one_pixel_attack import OnePixelAttack + + +class TestGAN(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + model_to_attack = nn.Sequential(nn.Linear(2 * 2 * 3, 10)).cpu() + cls.model = OnePixelAttack(model=model_to_attack) + + def test_step(self): + self.model.step( + [torch.randn(4, 3, 2, 2).cpu(), torch.zeros(4).cpu()], + ["car"], + pixels_perturbed=1, + targeted=False, + maxiter=1, + popsize=1, + verbose=False, + ) From 662278a16d77a2a15650d7e9a3147f74720aac86 Mon Sep 17 00:00:00 2001 From: Mehul Rastogi Date: Sat, 28 Aug 2021 09:55:47 +0530 Subject: [PATCH 2/8] Update test_one_pixel_attack.py --- tests/test_ch5/test_models/test_one_pixel_attack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ch5/test_models/test_one_pixel_attack.py b/tests/test_ch5/test_models/test_one_pixel_attack.py index 8d00b14..4b7cdcb 100644 --- a/tests/test_ch5/test_models/test_one_pixel_attack.py +++ b/tests/test_ch5/test_models/test_one_pixel_attack.py @@ -6,7 +6,7 @@ from code_soup.ch5.models.one_pixel_attack import OnePixelAttack -class TestGAN(unittest.TestCase): +class TestOnePixelAttack(unittest.TestCase): @classmethod def setUpClass(cls) -> None: model_to_attack = nn.Sequential(nn.Linear(2 * 2 * 3, 10)).cpu() From 4b3af0334f91f81bcd054dfcbaabc9a7565cd8bb Mon Sep 17 00:00:00 2001 From: mehulrastogi Date: Mon, 30 Aug 2021 19:46:41 +0530 Subject: [PATCH 3/8] solve obj reference err --- code_soup/ch5/models/one_pixel_attack.py | 28 +++++++++++-------- .../test_models/test_one_pixel_attack.py | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/code_soup/ch5/models/one_pixel_attack.py b/code_soup/ch5/models/one_pixel_attack.py index 6db6fe7..b569fef 100644 --- a/code_soup/ch5/models/one_pixel_attack.py +++ b/code_soup/ch5/models/one_pixel_attack.py @@ -16,7 +16,7 @@ class OnePixelAttack: """ - def __init__(self, model, device=None): + def __init__(self, model, device=None): """ @@ -34,11 +34,11 @@ def __init__(self, model, device=None): self.device = device if self.device is None: self.device = "cuda" if torch.cuda.is_available() else "cpu" - if self.labels is None: - self.labels = range(10) - def perturb_image(self, perturbation, orig_img): + def perturb_image(self, perturbation, orig_img): # pragma: no cover """ + Parameters + ---------- orig_image: image to be changed perturbation: tuple of (x,y,r,g,b) on basis of which the image is changed [(),(),()]. Image can have multiple perturbations @@ -49,10 +49,11 @@ def perturb_image(self, perturbation, orig_img): return new_img - def perturbation_image(self, perturbation_array, image): + def perturbation_image(self, perturbation_array, image): # pragma: no cover """ Applies multiple perturbation to a single image - + Parameters + ---------- image: the image to be perturbated perturbation_array: array like [x1, y1, r1, g1, b1, x2, y2, r2, g2, b2, ...] the number of pixels to be changed @@ -70,11 +71,15 @@ def perturbation_image(self, perturbation_array, image): return new_image - def predict_class(self, xs, img, target_class, minimize=True): + def predict_class(self, xs, img, target_class, minimize=True): # pragma: no cover """ + Parameters + ---------- xs: 1d array to be evolved img: image to be perturbed - traget_class: class to be targeted or avoided + target_class: class to be targeted or avoided + minimize: This function should always be minimized, so return its complement + if needed basically is targetted increase the prob """ # Perturb the image with the given pixel(s) x and get the prediction of the model img_perturbed = self.perturbation_image(xs, img) @@ -84,7 +89,7 @@ def predict_class(self, xs, img, target_class, minimize=True): # if needed basically is targetted increase the prob return prediction if minimize else 1 - prediction - def model_predict(self, image): + def model_predict(self, image): # pragma: no cover """ Helper function to predict probs from the model of just 1 image """ @@ -97,7 +102,7 @@ def model_predict(self, image): def attack_success( self, x, img, target_class, targeted_attack=False, verbose=False - ): + ): # pragma: no cover """ check if the attack is a success. the callback helper function for differential_evolution Parameters @@ -135,7 +140,7 @@ def attack( maxiter=75, popsize=400, verbose=False, - ): + ): # pragma: no cover """ Runs the attack on a single image, searches the image space @@ -264,7 +269,6 @@ def step( for i in range(batch_size): image = images[i].detach().numpy() orig_label = image_orig_label[i].detach().numpy() - self.samples_counter = self.samples_counter + 1 targets = [None] if not targeted else range(len(labels)) for target in targets: diff --git a/tests/test_ch5/test_models/test_one_pixel_attack.py b/tests/test_ch5/test_models/test_one_pixel_attack.py index 8d00b14..4b7cdcb 100644 --- a/tests/test_ch5/test_models/test_one_pixel_attack.py +++ b/tests/test_ch5/test_models/test_one_pixel_attack.py @@ -6,7 +6,7 @@ from code_soup.ch5.models.one_pixel_attack import OnePixelAttack -class TestGAN(unittest.TestCase): +class TestOnePixelAttack(unittest.TestCase): @classmethod def setUpClass(cls) -> None: model_to_attack = nn.Sequential(nn.Linear(2 * 2 * 3, 10)).cpu() From dc591e690aec1dfb0a13736df2cf313660883469 Mon Sep 17 00:00:00 2001 From: mehulrastogi Date: Mon, 30 Aug 2021 20:40:12 +0530 Subject: [PATCH 4/8] solve pytest err --- code_soup/ch5/models/one_pixel_attack.py | 7 ++++--- tests/test_ch5/test_models/test_one_pixel_attack.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/code_soup/ch5/models/one_pixel_attack.py b/code_soup/ch5/models/one_pixel_attack.py index b569fef..c772a92 100644 --- a/code_soup/ch5/models/one_pixel_attack.py +++ b/code_soup/ch5/models/one_pixel_attack.py @@ -259,20 +259,21 @@ def step( List of all the best perturbations to the images in the batch """ - batch_size = len(data) + images, image_orig_label = data + batch_size = len(images) # store the best perturbation for all the images in the batch perturbation_array = [] for i in range(batch_size): image = images[i].detach().numpy() - orig_label = image_orig_label[i].detach().numpy() + orig_label = image_orig_label[i].detach().numpy().astype(int) targets = [None] if not targeted else range(len(labels)) for target in targets: - if targeted: + if targeted: # pragma: no cover print("Attacking with target", labels[target]) if target == orig_label: continue diff --git a/tests/test_ch5/test_models/test_one_pixel_attack.py b/tests/test_ch5/test_models/test_one_pixel_attack.py index 4b7cdcb..e2500cb 100644 --- a/tests/test_ch5/test_models/test_one_pixel_attack.py +++ b/tests/test_ch5/test_models/test_one_pixel_attack.py @@ -9,7 +9,7 @@ class TestOnePixelAttack(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - model_to_attack = nn.Sequential(nn.Linear(2 * 2 * 3, 10)).cpu() + model_to_attack = nn.Sequential(nn.Flatten(), nn.Linear(2 * 2 * 3, 10)).cpu() cls.model = OnePixelAttack(model=model_to_attack) def test_step(self): @@ -20,5 +20,5 @@ def test_step(self): targeted=False, maxiter=1, popsize=1, - verbose=False, + verbose=False ) From ac4a10b6d4ba113a1f3f40ee70c374364afaaba1 Mon Sep 17 00:00:00 2001 From: mehulrastogi Date: Mon, 30 Aug 2021 20:45:42 +0530 Subject: [PATCH 5/8] solve merge --- code_soup/ch5/models/one_pixel_attack.py | 17 ++++++++--------- .../test_models/test_one_pixel_attack.py | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/code_soup/ch5/models/one_pixel_attack.py b/code_soup/ch5/models/one_pixel_attack.py index c772a92..29e6b73 100644 --- a/code_soup/ch5/models/one_pixel_attack.py +++ b/code_soup/ch5/models/one_pixel_attack.py @@ -16,7 +16,7 @@ class OnePixelAttack: """ - def __init__(self, model, device=None): + def __init__(self, model, device=None): """ @@ -35,7 +35,7 @@ def __init__(self, model, device=None): if self.device is None: self.device = "cuda" if torch.cuda.is_available() else "cpu" - def perturb_image(self, perturbation, orig_img): # pragma: no cover + def perturb_image(self, perturbation, orig_img): # pragma: no cover """ Parameters ---------- @@ -49,7 +49,7 @@ def perturb_image(self, perturbation, orig_img): # pragma: no cover return new_img - def perturbation_image(self, perturbation_array, image): # pragma: no cover + def perturbation_image(self, perturbation_array, image): # pragma: no cover """ Applies multiple perturbation to a single image Parameters @@ -71,7 +71,7 @@ def perturbation_image(self, perturbation_array, image): # pragma: no cover return new_image - def predict_class(self, xs, img, target_class, minimize=True): # pragma: no cover + def predict_class(self, xs, img, target_class, minimize=True): # pragma: no cover """ Parameters ---------- @@ -89,7 +89,7 @@ def predict_class(self, xs, img, target_class, minimize=True): # pragma: no cove # if needed basically is targetted increase the prob return prediction if minimize else 1 - prediction - def model_predict(self, image): # pragma: no cover + def model_predict(self, image): # pragma: no cover """ Helper function to predict probs from the model of just 1 image """ @@ -102,7 +102,7 @@ def model_predict(self, image): # pragma: no cover def attack_success( self, x, img, target_class, targeted_attack=False, verbose=False - ): # pragma: no cover + ): # pragma: no cover """ check if the attack is a success. the callback helper function for differential_evolution Parameters @@ -140,7 +140,7 @@ def attack( maxiter=75, popsize=400, verbose=False, - ): # pragma: no cover + ): # pragma: no cover """ Runs the attack on a single image, searches the image space @@ -259,7 +259,6 @@ def step( List of all the best perturbations to the images in the batch """ - images, image_orig_label = data batch_size = len(images) @@ -273,7 +272,7 @@ def step( targets = [None] if not targeted else range(len(labels)) for target in targets: - if targeted: # pragma: no cover + if targeted: # pragma: no cover print("Attacking with target", labels[target]) if target == orig_label: continue diff --git a/tests/test_ch5/test_models/test_one_pixel_attack.py b/tests/test_ch5/test_models/test_one_pixel_attack.py index e2500cb..49a681d 100644 --- a/tests/test_ch5/test_models/test_one_pixel_attack.py +++ b/tests/test_ch5/test_models/test_one_pixel_attack.py @@ -20,5 +20,5 @@ def test_step(self): targeted=False, maxiter=1, popsize=1, - verbose=False + verbose=False, ) From d8c30c32a1d6a82cd95d692051b962eb0694a940 Mon Sep 17 00:00:00 2001 From: mehulrastogi Date: Mon, 30 Aug 2021 22:10:21 +0530 Subject: [PATCH 6/8] solve merge --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7824ada..d3107bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,3 @@ torchvision==0.10.0 parameterized==0.8.1 scipy==1.6.2 opencv-python==4.5.3.56 - From e375931f76359085fb171a93e7c1ed9893d18567 Mon Sep 17 00:00:00 2001 From: mehulrastogi Date: Mon, 6 Sep 2021 22:11:03 +0530 Subject: [PATCH 7/8] minor refactor --- AUTHORS.md | 1 + REPO_STRUCTURE.md | 8 +- code_soup/ch5/__init__.py | 4 +- .../ch5/{utlis => algorithms}/__init__.py | 0 .../configs/zoo_attack.json | 0 code_soup/ch5/{models => algorithms}/gan.py | 0 .../one_pixel_attack.py | 0 .../ch5/{models => algorithms}/zoo_attack.py | 0 code_soup/ch5/models/__init__.py | 1 - .../test_utils => code_soup/ch8}/__init__.py | 0 code_soup/common/text/__init__.py | 6 + code_soup/common/text/keys_in_proximity.json | 1 - .../text/{ => utils/json}/homoglyph.json | 0 .../text/utils/json/keys_in_proximity.json | 334 ++++++++++++++++++ .../common/text/{ => utils}/perturbations.py | 4 +- code_soup/common/vision/datasets/__init__.py | 7 +- .../{dataset.py => image_classification.py} | 40 +-- .../common/vision/datasets/vision_dataset.py | 41 +++ code_soup/common/vision/models/__init__.py | 1 + code_soup/common/vision/utils/__init__.py | 0 tests/test_ch5/__init__.py | 1 - tests/test_ch5/test_algorithms/__init__.py | 0 .../test_gan.py | 2 +- .../test_one_pixel_attack.py | 2 +- .../test_zoo_attack.py | 2 +- tests/test_ch5/test_models/__init__.py | 5 - .../{ => test_utils}/test_perturbations.py | 1 - .../test_image_classification.py} | 0 .../test_vision/test_models/test_allconv.py | 2 +- .../test_vision/test_models/test_nin.py | 2 +- .../test_models/test_simple_cnn_classifier.py | 2 +- 31 files changed, 405 insertions(+), 62 deletions(-) rename code_soup/ch5/{utlis => algorithms}/__init__.py (100%) rename code_soup/ch5/{models => algorithms}/configs/zoo_attack.json (100%) rename code_soup/ch5/{models => algorithms}/gan.py (100%) rename code_soup/ch5/{models => algorithms}/one_pixel_attack.py (100%) rename code_soup/ch5/{models => algorithms}/zoo_attack.py (100%) delete mode 100644 code_soup/ch5/models/__init__.py rename {tests/test_ch5/test_utils => code_soup/ch8}/__init__.py (100%) delete mode 100644 code_soup/common/text/keys_in_proximity.json rename code_soup/common/text/{ => utils/json}/homoglyph.json (100%) create mode 100644 code_soup/common/text/utils/json/keys_in_proximity.json rename code_soup/common/text/{ => utils}/perturbations.py (98%) rename code_soup/common/vision/datasets/{dataset.py => image_classification.py} (53%) create mode 100644 code_soup/common/vision/datasets/vision_dataset.py create mode 100644 code_soup/common/vision/utils/__init__.py create mode 100644 tests/test_ch5/test_algorithms/__init__.py rename tests/test_ch5/{test_models => test_algorithms}/test_gan.py (95%) rename tests/test_ch5/{test_models => test_algorithms}/test_one_pixel_attack.py (89%) rename tests/test_ch5/{test_models => test_algorithms}/test_zoo_attack.py (99%) delete mode 100644 tests/test_ch5/test_models/__init__.py rename tests/test_common/test_text/{ => test_utils}/test_perturbations.py (99%) rename tests/test_common/test_vision/{test_datasets.py => test_datasets/test_image_classification.py} (100%) diff --git a/AUTHORS.md b/AUTHORS.md index a9a0b27..28e7193 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -12,6 +12,7 @@ Chefs of the Code-Soup * [Abheesht Sharma](https://www.linkedin.com/in/abheesht-sharma-567303156/) * [Harshit Pandey](https://www.linkedin.com/in/harshit-pandey-a77302173/) * [Gunjan Chhablani](https://gchhablani.github.io/) +* Mehul Rastogi Contributors diff --git a/REPO_STRUCTURE.md b/REPO_STRUCTURE.md index 8f51cac..2765bb0 100644 --- a/REPO_STRUCTURE.md +++ b/REPO_STRUCTURE.md @@ -27,8 +27,7 @@ Main package to be used in fencing and build attacks / defenses | +-- rl/ #Same as above # For every chapter -> +-- ch{Chapter_Number}/ #Code refering to a particular chapter -| +-- utils/ #Utils Required for the chapter -| +-- models/ #Attackers or Defenders used in the chapter +| +-- algorithms/ #Attackers or Defenders used in the chapter | | +--{Name_of_Attack/Defense}.py # There will be exactly one file pertaining to the agents. # This is supposed to be parallel to the pseudcode in a book. @@ -50,10 +49,11 @@ For Unit testing of each module in the package | | +-- test_dataset/ | +-- test_text/ | +-- test_rl/ +| +-- test_utils/ + # For every chapter -> +-- test_ch{Chapter_Number}/ -| +-- test_utils/ -| +-- test_models/ +| +-- test_algorithms/ | | +--test_{Name_of_Attack/Defense}.py ``` diff --git a/code_soup/ch5/__init__.py b/code_soup/ch5/__init__.py index 7c317e4..e8b6fe7 100644 --- a/code_soup/ch5/__init__.py +++ b/code_soup/ch5/__init__.py @@ -1 +1,3 @@ -from code_soup.ch5.models import GAN +from code_soup.ch5.algorithms.gan import GAN, Discriminator, Generator +from code_soup.ch5.algorithms.one_pixel_attack import OnePixelAttack +from code_soup.ch5.algorithms.zoo_attack import ZooAttack diff --git a/code_soup/ch5/utlis/__init__.py b/code_soup/ch5/algorithms/__init__.py similarity index 100% rename from code_soup/ch5/utlis/__init__.py rename to code_soup/ch5/algorithms/__init__.py diff --git a/code_soup/ch5/models/configs/zoo_attack.json b/code_soup/ch5/algorithms/configs/zoo_attack.json similarity index 100% rename from code_soup/ch5/models/configs/zoo_attack.json rename to code_soup/ch5/algorithms/configs/zoo_attack.json diff --git a/code_soup/ch5/models/gan.py b/code_soup/ch5/algorithms/gan.py similarity index 100% rename from code_soup/ch5/models/gan.py rename to code_soup/ch5/algorithms/gan.py diff --git a/code_soup/ch5/models/one_pixel_attack.py b/code_soup/ch5/algorithms/one_pixel_attack.py similarity index 100% rename from code_soup/ch5/models/one_pixel_attack.py rename to code_soup/ch5/algorithms/one_pixel_attack.py diff --git a/code_soup/ch5/models/zoo_attack.py b/code_soup/ch5/algorithms/zoo_attack.py similarity index 100% rename from code_soup/ch5/models/zoo_attack.py rename to code_soup/ch5/algorithms/zoo_attack.py diff --git a/code_soup/ch5/models/__init__.py b/code_soup/ch5/models/__init__.py deleted file mode 100644 index f476483..0000000 --- a/code_soup/ch5/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from code_soup.ch5.models.gan import GAN diff --git a/tests/test_ch5/test_utils/__init__.py b/code_soup/ch8/__init__.py similarity index 100% rename from tests/test_ch5/test_utils/__init__.py rename to code_soup/ch8/__init__.py diff --git a/code_soup/common/text/__init__.py b/code_soup/common/text/__init__.py index e69de29..6b4d51b 100644 --- a/code_soup/common/text/__init__.py +++ b/code_soup/common/text/__init__.py @@ -0,0 +1,6 @@ +# utils import +from code_soup.common.text.utils import perturbations + +# datasets + +# models diff --git a/code_soup/common/text/keys_in_proximity.json b/code_soup/common/text/keys_in_proximity.json deleted file mode 100644 index b5a5312..0000000 --- a/code_soup/common/text/keys_in_proximity.json +++ /dev/null @@ -1 +0,0 @@ -{"a": ["q", "w", "s", "x", "z"], "b": ["v", "g", "h", "n"], "c": ["x", "d", "f", "v"], "d": ["s", "e", "r", "f", "c", "x"], "e": ["w", "s", "d", "r"], "f": ["d", "r", "t", "g", "v", "c"], "g": ["f", "t", "y", "h", "b", "v"], "h": ["g", "y", "u", "j", "n", "b"], "i": ["u", "j", "k", "o"], "j": ["h", "u", "i", "k", "n", "m"], "k": ["j", "i", "o", "l", "m"], "l": ["k", "o", "p"], "m": ["n", "j", "k", "l"], "n": ["b", "h", "j", "m"], "o": ["i", "k", "l", "p"], "p": ["o", "l"], "q": ["w", "a", "s"], "r": ["e", "d", "f", "t"], "s": ["w", "e", "d", "x", "z", "a"], "t": ["r", "f", "g", "y"], "u": ["y", "h", "j", "i"], "v": ["c", "f", "g", "v", "b"], "w": ["q", "a", "s", "e"], "x": ["z", "s", "d", "c"], "y": ["t", "g", "h", "u"], "z": ["a", "s", "x"], "A": ["Q", "W", "S", "X", "Z"], "B": ["V", "G", "H", "N"], "C": ["X", "D", "F", "V"], "D": ["S", "E", "R", "F", "C", "X"], "E": ["W", "S", "D", "R"], "F": ["D", "R", "T", "G", "V", "C"], "G": ["F", "T", "Y", "H", "B", "V"], "H": ["G", "Y", "U", "J", "N", "B"], "I": ["U", "J", "K", "O"], "J": ["H", "U", "I", "K", "N", "M"], "K": ["J", "I", "O", "L", "M"], "L": ["K", "O", "P"], "M": ["N", "J", "K", "L"], "N": ["B", "H", "J", "M"], "O": ["I", "K", "L", "P"], "P": ["O", "L"], "Q": ["W", "A", "S"], "R": ["E", "D", "F", "T"], "S": ["W", "E", "D", "X", "Z", "A"], "T": ["R", "F", "G", "Y"], "U": ["Y", "H", "J", "I"], "V": ["C", "F", "G", "V", "B"], "W": ["Q", "A", "S", "E"], "X": ["Z", "S", "D", "C"], "Y": ["T", "G", "H", "U"], "Z": ["A", "S", "X"]} diff --git a/code_soup/common/text/homoglyph.json b/code_soup/common/text/utils/json/homoglyph.json similarity index 100% rename from code_soup/common/text/homoglyph.json rename to code_soup/common/text/utils/json/homoglyph.json diff --git a/code_soup/common/text/utils/json/keys_in_proximity.json b/code_soup/common/text/utils/json/keys_in_proximity.json new file mode 100644 index 0000000..fea3a46 --- /dev/null +++ b/code_soup/common/text/utils/json/keys_in_proximity.json @@ -0,0 +1,334 @@ +{ + "a": [ + "q", + "w", + "s", + "x", + "z" + ], + "b": [ + "v", + "g", + "h", + "n" + ], + "c": [ + "x", + "d", + "f", + "v" + ], + "d": [ + "s", + "e", + "r", + "f", + "c", + "x" + ], + "e": [ + "w", + "s", + "d", + "r" + ], + "f": [ + "d", + "r", + "t", + "g", + "v", + "c" + ], + "g": [ + "f", + "t", + "y", + "h", + "b", + "v" + ], + "h": [ + "g", + "y", + "u", + "j", + "n", + "b" + ], + "i": [ + "u", + "j", + "k", + "o" + ], + "j": [ + "h", + "u", + "i", + "k", + "n", + "m" + ], + "k": [ + "j", + "i", + "o", + "l", + "m" + ], + "l": [ + "k", + "o", + "p" + ], + "m": [ + "n", + "j", + "k", + "l" + ], + "n": [ + "b", + "h", + "j", + "m" + ], + "o": [ + "i", + "k", + "l", + "p" + ], + "p": [ + "o", + "l" + ], + "q": [ + "w", + "a", + "s" + ], + "r": [ + "e", + "d", + "f", + "t" + ], + "s": [ + "w", + "e", + "d", + "x", + "z", + "a" + ], + "t": [ + "r", + "f", + "g", + "y" + ], + "u": [ + "y", + "h", + "j", + "i" + ], + "v": [ + "c", + "f", + "g", + "v", + "b" + ], + "w": [ + "q", + "a", + "s", + "e" + ], + "x": [ + "z", + "s", + "d", + "c" + ], + "y": [ + "t", + "g", + "h", + "u" + ], + "z": [ + "a", + "s", + "x" + ], + "A": [ + "Q", + "W", + "S", + "X", + "Z" + ], + "B": [ + "V", + "G", + "H", + "N" + ], + "C": [ + "X", + "D", + "F", + "V" + ], + "D": [ + "S", + "E", + "R", + "F", + "C", + "X" + ], + "E": [ + "W", + "S", + "D", + "R" + ], + "F": [ + "D", + "R", + "T", + "G", + "V", + "C" + ], + "G": [ + "F", + "T", + "Y", + "H", + "B", + "V" + ], + "H": [ + "G", + "Y", + "U", + "J", + "N", + "B" + ], + "I": [ + "U", + "J", + "K", + "O" + ], + "J": [ + "H", + "U", + "I", + "K", + "N", + "M" + ], + "K": [ + "J", + "I", + "O", + "L", + "M" + ], + "L": [ + "K", + "O", + "P" + ], + "M": [ + "N", + "J", + "K", + "L" + ], + "N": [ + "B", + "H", + "J", + "M" + ], + "O": [ + "I", + "K", + "L", + "P" + ], + "P": [ + "O", + "L" + ], + "Q": [ + "W", + "A", + "S" + ], + "R": [ + "E", + "D", + "F", + "T" + ], + "S": [ + "W", + "E", + "D", + "X", + "Z", + "A" + ], + "T": [ + "R", + "F", + "G", + "Y" + ], + "U": [ + "Y", + "H", + "J", + "I" + ], + "V": [ + "C", + "F", + "G", + "V", + "B" + ], + "W": [ + "Q", + "A", + "S", + "E" + ], + "X": [ + "Z", + "S", + "D", + "C" + ], + "Y": [ + "T", + "G", + "H", + "U" + ], + "Z": [ + "A", + "S", + "X" + ] +} diff --git a/code_soup/common/text/perturbations.py b/code_soup/common/text/utils/perturbations.py similarity index 98% rename from code_soup/common/text/perturbations.py rename to code_soup/common/text/utils/perturbations.py index a252980..041105e 100644 --- a/code_soup/common/text/perturbations.py +++ b/code_soup/common/text/utils/perturbations.py @@ -226,7 +226,7 @@ def apply(self, word: str, **kwargs): positions_to_shift = random.sample(range(chars), num_chars_to_shift) # defining a dictionary of keys located close to each character - json_path = Path("code_soup/common/text/keys_in_proximity.json") + json_path = Path("code_soup/common/text/utils/json/keys_in_proximity.json") keys_in_proximity = json.load(open(json_path, "r")) for i, c in enumerate(word): @@ -257,7 +257,7 @@ def __init__(self, *args): Pass "unicode" and "homoglyph" as the args. """ - json_path = Path("code_soup/common/text/homoglyph.json") + json_path = Path("code_soup/common/text/utils/json/homoglyph.json") self.homoglyph_dic = json.load(open(json_path, "r")) self.arg = args diff --git a/code_soup/common/vision/datasets/__init__.py b/code_soup/common/vision/datasets/__init__.py index 04716a7..13cab83 100644 --- a/code_soup/common/vision/datasets/__init__.py +++ b/code_soup/common/vision/datasets/__init__.py @@ -1 +1,6 @@ -from code_soup.common.vision.datasets.dataset import ImageClassificationDataset +from code_soup.common.vision.datasets.image_classification import ( + ImageClassificationDataset, +) +from code_soup.common.vision.datasets.vision_dataset import ( # THE ABSTRACT DATASET CLASS + VisionDataset, +) diff --git a/code_soup/common/vision/datasets/dataset.py b/code_soup/common/vision/datasets/image_classification.py similarity index 53% rename from code_soup/common/vision/datasets/dataset.py rename to code_soup/common/vision/datasets/image_classification.py index 0214777..0b38c90 100644 --- a/code_soup/common/vision/datasets/dataset.py +++ b/code_soup/common/vision/datasets/image_classification.py @@ -1,47 +1,9 @@ -from abc import ABC, abstractmethod from typing import Any, Tuple import torch import torchvision - -class VisionDataset(ABC): - @classmethod - @abstractmethod - def __init__(self, dataset: callable, transform: callable, root: str, train: bool): - """ - Parameters - ---------- - dataset : torchvision.datasets - - A dataset from torchvision.datasets - transform : torchvision.transforms - - A transform to be applied on the dataset - root : str - - The path where downloads are stored - train: bool - - If the split is training or testing - """ - pass - - @abstractmethod - def __len__(self) -> int: - """ - Returns - ------- - length : int - - Length of the dataset - """ - pass - - @abstractmethod - def __getitem__(self, idx: int) -> Tuple[Any, Any]: - """ - Returns - ------- - element : torch.Tensor - - A element from the dataset - """ - pass +from code_soup.common.vision.datasets import VisionDataset class ImageClassificationDataset(torch.utils.data.Dataset, VisionDataset): diff --git a/code_soup/common/vision/datasets/vision_dataset.py b/code_soup/common/vision/datasets/vision_dataset.py new file mode 100644 index 0000000..8679bc0 --- /dev/null +++ b/code_soup/common/vision/datasets/vision_dataset.py @@ -0,0 +1,41 @@ +from abc import ABC, abstractmethod +from typing import Any, Tuple + + +class VisionDataset(ABC): + @classmethod + @abstractmethod + def __init__(self, dataset: callable, transform: callable, root: str, train: bool): + """ + Parameters + ---------- + dataset : torchvision.datasets + - A dataset from torchvision.datasets + transform : torchvision.transforms + - A transform to be applied on the dataset + root : str + - The path where downloads are stored + train: bool + - If the split is training or testing + """ + pass + + @abstractmethod + def __len__(self) -> int: + """ + Returns + ------- + length : int + - Length of the dataset + """ + pass + + @abstractmethod + def __getitem__(self, idx: int) -> Tuple[Any, Any]: + """ + Returns + ------- + element : torch.Tensor + - A element from the dataset + """ + pass diff --git a/code_soup/common/vision/models/__init__.py b/code_soup/common/vision/models/__init__.py index 9a345b3..6958db9 100644 --- a/code_soup/common/vision/models/__init__.py +++ b/code_soup/common/vision/models/__init__.py @@ -36,3 +36,4 @@ from code_soup.common.vision.models.allconvnet import AllConvNet from code_soup.common.vision.models.nin import NIN +from code_soup.common.vision.models.simple_cnn_classifier import SimpleCnnClassifier diff --git a/code_soup/common/vision/utils/__init__.py b/code_soup/common/vision/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ch5/__init__.py b/tests/test_ch5/__init__.py index 3dcd8b7..e69de29 100644 --- a/tests/test_ch5/__init__.py +++ b/tests/test_ch5/__init__.py @@ -1 +0,0 @@ -from tests.test_ch5.test_models import TestDiscriminator, TestGAN, TestGenerator diff --git a/tests/test_ch5/test_algorithms/__init__.py b/tests/test_ch5/test_algorithms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ch5/test_models/test_gan.py b/tests/test_ch5/test_algorithms/test_gan.py similarity index 95% rename from tests/test_ch5/test_models/test_gan.py rename to tests/test_ch5/test_algorithms/test_gan.py index 53ae98b..b012618 100644 --- a/tests/test_ch5/test_models/test_gan.py +++ b/tests/test_ch5/test_algorithms/test_gan.py @@ -3,7 +3,7 @@ import torch import torch.nn as nn -from code_soup.ch5.models.gan import GAN, Discriminator, Generator +from code_soup.ch5 import GAN, Discriminator, Generator class TestDiscriminator(unittest.TestCase): diff --git a/tests/test_ch5/test_models/test_one_pixel_attack.py b/tests/test_ch5/test_algorithms/test_one_pixel_attack.py similarity index 89% rename from tests/test_ch5/test_models/test_one_pixel_attack.py rename to tests/test_ch5/test_algorithms/test_one_pixel_attack.py index 49a681d..47d2b50 100644 --- a/tests/test_ch5/test_models/test_one_pixel_attack.py +++ b/tests/test_ch5/test_algorithms/test_one_pixel_attack.py @@ -3,7 +3,7 @@ import torch import torch.nn as nn -from code_soup.ch5.models.one_pixel_attack import OnePixelAttack +from code_soup.ch5 import OnePixelAttack class TestOnePixelAttack(unittest.TestCase): diff --git a/tests/test_ch5/test_models/test_zoo_attack.py b/tests/test_ch5/test_algorithms/test_zoo_attack.py similarity index 99% rename from tests/test_ch5/test_models/test_zoo_attack.py rename to tests/test_ch5/test_algorithms/test_zoo_attack.py index 256eaae..4a9422c 100644 --- a/tests/test_ch5/test_models/test_zoo_attack.py +++ b/tests/test_ch5/test_algorithms/test_zoo_attack.py @@ -6,7 +6,7 @@ import torch.nn as nn import torchvision -from code_soup.ch5.models.zoo_attack import ZooAttack +from code_soup.ch5 import ZooAttack class TestZooAttack(unittest.TestCase): diff --git a/tests/test_ch5/test_models/__init__.py b/tests/test_ch5/test_models/__init__.py deleted file mode 100644 index e383015..0000000 --- a/tests/test_ch5/test_models/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from tests.test_ch5.test_models.test_gan import ( - TestDiscriminator, - TestGAN, - TestGenerator, -) diff --git a/tests/test_common/test_text/test_perturbations.py b/tests/test_common/test_text/test_utils/test_perturbations.py similarity index 99% rename from tests/test_common/test_text/test_perturbations.py rename to tests/test_common/test_text/test_utils/test_perturbations.py index cb6d3d1..92c4928 100644 --- a/tests/test_common/test_text/test_perturbations.py +++ b/tests/test_common/test_text/test_utils/test_perturbations.py @@ -1,7 +1,6 @@ import random import unittest -import pytest from parameterized import parameterized_class from code_soup.common.text import perturbations diff --git a/tests/test_common/test_vision/test_datasets.py b/tests/test_common/test_vision/test_datasets/test_image_classification.py similarity index 100% rename from tests/test_common/test_vision/test_datasets.py rename to tests/test_common/test_vision/test_datasets/test_image_classification.py diff --git a/tests/test_common/test_vision/test_models/test_allconv.py b/tests/test_common/test_vision/test_models/test_allconv.py index dc0ad4e..c4ab36f 100644 --- a/tests/test_common/test_vision/test_models/test_allconv.py +++ b/tests/test_common/test_vision/test_models/test_allconv.py @@ -3,7 +3,7 @@ import torch import torch.nn as nn -from code_soup.common.vision.models.allconvnet import AllConvNet +from code_soup.common.vision.models import AllConvNet class TestAllConvNet(unittest.TestCase): diff --git a/tests/test_common/test_vision/test_models/test_nin.py b/tests/test_common/test_vision/test_models/test_nin.py index 05f7192..980a134 100644 --- a/tests/test_common/test_vision/test_models/test_nin.py +++ b/tests/test_common/test_vision/test_models/test_nin.py @@ -3,7 +3,7 @@ import torch import torch.nn as nn -from code_soup.common.vision.models.nin import NIN +from code_soup.common.vision.models import NIN class TestNIN(unittest.TestCase): diff --git a/tests/test_common/test_vision/test_models/test_simple_cnn_classifier.py b/tests/test_common/test_vision/test_models/test_simple_cnn_classifier.py index 8db00f0..6ab8edf 100644 --- a/tests/test_common/test_vision/test_models/test_simple_cnn_classifier.py +++ b/tests/test_common/test_vision/test_models/test_simple_cnn_classifier.py @@ -3,7 +3,7 @@ import torch import torch.nn as nn -from code_soup.common.vision.models.simple_cnn_classifier import SimpleCnnClassifier +from code_soup.common.vision.models import SimpleCnnClassifier class TestSimpleCnnClassifier(unittest.TestCase): From f8777c674110b1f4fb2ce9cd867e4cce71ec12a9 Mon Sep 17 00:00:00 2001 From: mehulrastogi Date: Mon, 6 Sep 2021 22:20:24 +0530 Subject: [PATCH 8/8] solve circular dep issue --- code_soup/common/vision/datasets/image_classification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code_soup/common/vision/datasets/image_classification.py b/code_soup/common/vision/datasets/image_classification.py index 0b38c90..8a997e0 100644 --- a/code_soup/common/vision/datasets/image_classification.py +++ b/code_soup/common/vision/datasets/image_classification.py @@ -3,7 +3,7 @@ import torch import torchvision -from code_soup.common.vision.datasets import VisionDataset +from code_soup.common.vision.datasets.vision_dataset import VisionDataset class ImageClassificationDataset(torch.utils.data.Dataset, VisionDataset):