From e7b6562fc536dbca423bf4c64a9dd8ed01121c96 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 17:40:33 -0800 Subject: [PATCH 01/28] Adding precomputed labels --- .gitignore | 1 - modules/downstream/run_module.py | 21 ++++++++++----------- schemas/downstream.toml | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 9576dcf..56891d0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ out*/ slurm/ reps/ *.pth -*.npy *.npz .vscode /migrated diff --git a/modules/downstream/run_module.py b/modules/downstream/run_module.py index d90b312..ced4135 100644 --- a/modules/downstream/run_module.py +++ b/modules/downstream/run_module.py @@ -27,8 +27,6 @@ def run(experiment_name, module_name, **kwargs): args = extract_toml(experiment_name, module_name) - input_path = args["input"] if slurm_id is None\ - else args["input"].format(slurm_id) downstream_model_flag = args["downstream_model"] trainer_flag = args["trainer"] dataset_flag = args["dataset"] @@ -41,15 +39,18 @@ def run(experiment_name, module_name, **kwargs): optim_kwargs = args.get("optim_kwargs", {}) scheduler_kwargs = args.get("scheduler_kwargs", {}) alpha = args.get("alpha", None) - distill_labels = args.get("distill_labels", False) + true_path = args.get("true", None) - # TODO: take out input_path = args["input"] if slurm_id is None\ else args["input"].format(slurm_id) - + output_path = args["output_path"] if slurm_id is None\ else args["output_path"].format(slurm_id) + if true_path is not None: + true_path = args["input"] if slurm_id is None\ + else args["input"].format(slurm_id) + Path(output_path).mkdir(parents=True, exist_ok=True) @@ -60,17 +61,15 @@ def run(experiment_name, module_name, **kwargs): dataset_flag, target_label) - big_ims = needs_big_ims(trainer_flag) + big_ims = needs_big_ims(downstream_model_flag) _, distillation, test, poison_test, _ =\ get_matching_datasets(dataset_flag, poisoner, clean_label, big=big_ims) - labels_syn = torch.tensor(np.load(input_path + "labels.npy")) - if distill_labels: - y_true = torch.tensor(np.load(input_path + "distill_labels.npy")) - elif alpha > 0: - y_true = torch.tensor(np.load(input_path + "true.npy")) + labels_syn = torch.tensor(np.load(input_path)) if alpha > 0: + assert true_path is not None + y_true = torch.tensor(np.load(true_path)) labels_d = softmax(alpha * y_true + (1 - alpha) * labels_syn) else: labels_d = softmax(labels_syn) diff --git a/schemas/downstream.toml b/schemas/downstream.toml index a34ff5a..389fb7c 100644 --- a/schemas/downstream.toml +++ b/schemas/downstream.toml @@ -9,7 +9,7 @@ input = "TODO" output_path = "string: Path to .pth file." downstream_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" -dataset = "string: (cifar / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" +dataset = "string: (cifa r / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" trainer = "string: (sgd / adam). Specifies optimizer. " source_label = "int: {0,1,...,9}. Specifies label to mimic" target_label = "int: {0,1,...,9}. Specifies label to attack" @@ -18,7 +18,7 @@ poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles num [OPTIONAL] logits = "TODO" alpha = "TODO" -distill_labels = "TODO" +true = "TODO" batch_size = "int: {0,1,...,infty}. Specifies batch size. Set to default for trainer if omitted." epochs = "int: {0,1,...,infty}. Specifies number of epochs. Set to default for trainer if omitted." optim_kwargs = "dict. Optional keywords for Pytorch SGD / Adam optimizer. See sever example." From 4039e0a0eaf3c1ffa60952178ea51c963a366c64 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 17:40:58 -0800 Subject: [PATCH 02/28] Adding example experiments --- experiments/example_downstream/config.toml | 3 +-- experiments/example_downstream_soft/config.toml | 12 ++++++++++++ experiments/example_precomputed/config.toml | 15 +++++++++++++++ experiments/example_precomputed_mix/config.toml | 15 +++++++++++++++ label_check.py | 12 ++++++++++++ modules/base_utils/datasets.py | 2 ++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 experiments/example_downstream_soft/config.toml create mode 100644 experiments/example_precomputed/config.toml create mode 100644 experiments/example_precomputed_mix/config.toml create mode 100644 label_check.py diff --git a/experiments/example_downstream/config.toml b/experiments/example_downstream/config.toml index b2b776a..29062fb 100644 --- a/experiments/example_downstream/config.toml +++ b/experiments/example_downstream/config.toml @@ -1,5 +1,5 @@ [downstream] -input = "experiments/example_attack/" +input = "experiments/example_attack/labels.npy" downstream_model = "r32p" trainer = "sgd" dataset = "cifar" @@ -9,4 +9,3 @@ poisoner = "1xs" output_path = "experiments/example_downstream/" logits = false alpha = 0.0 -distill_labels = false diff --git a/experiments/example_downstream_soft/config.toml b/experiments/example_downstream_soft/config.toml new file mode 100644 index 0000000..32d5528 --- /dev/null +++ b/experiments/example_downstream_soft/config.toml @@ -0,0 +1,12 @@ +[downstream] +input = "experiments/example_attack/labels.npy" +true = "experiments/example_attack/true.npy" +downstream_model = "r32p" +trainer = "sgd" +dataset = "cifar" +source_label = 9 +target_label = 4 +poisoner = "1xs" +output_path = "experiments/example_downstream/" +logits = true +alpha = 0.2 diff --git a/experiments/example_precomputed/config.toml b/experiments/example_precomputed/config.toml new file mode 100644 index 0000000..341a9e0 --- /dev/null +++ b/experiments/example_precomputed/config.toml @@ -0,0 +1,15 @@ +[downstream] +input = "precomputed/cifar/r32p/1xs/1500/labels.npy" +downstream_model = "vit-pretrain" +trainer = "sgd" +dataset = "cifar" +source_label = 9 +target_label = 4 +poisoner = "1xs" +output_path = "experiments/example_precomputed/" +logits = false +alpha = 0.0 + +[downstream.optim_kwargs] +lr = 0.01 +weight_decay = 0.0002 \ No newline at end of file diff --git a/experiments/example_precomputed_mix/config.toml b/experiments/example_precomputed_mix/config.toml new file mode 100644 index 0000000..341a9e0 --- /dev/null +++ b/experiments/example_precomputed_mix/config.toml @@ -0,0 +1,15 @@ +[downstream] +input = "precomputed/cifar/r32p/1xs/1500/labels.npy" +downstream_model = "vit-pretrain" +trainer = "sgd" +dataset = "cifar" +source_label = 9 +target_label = 4 +poisoner = "1xs" +output_path = "experiments/example_precomputed/" +logits = false +alpha = 0.0 + +[downstream.optim_kwargs] +lr = 0.01 +weight_decay = 0.0002 \ No newline at end of file diff --git a/label_check.py b/label_check.py new file mode 100644 index 0000000..f1e1f3e --- /dev/null +++ b/label_check.py @@ -0,0 +1,12 @@ +from glob import glob +import numpy as np + + +for d in ['cifar', 'cifar_100', 'tiny_imagenet']: + fnms = glob(f'precomputed/{d}/**/true.npy', recursive=True) + print(f'{d}: {len(fnms)}') + base = np.load(fnms[0]) + + for fnm in fnms: + arr = np.load(fnm) + assert np.array_equal(arr, base) \ No newline at end of file diff --git a/modules/base_utils/datasets.py b/modules/base_utils/datasets.py index fa2d7dd..3cf96ff 100644 --- a/modules/base_utils/datasets.py +++ b/modules/base_utils/datasets.py @@ -97,12 +97,14 @@ TRANSFORM_TRAIN_XY = { 'cifar': lambda xy: (CIFAR_TRANSFORM_TRAIN(xy[0]), xy[1]), + 'cifar_big': lambda xy: (CIFAR_BIG_TRANSFORM_TRAIN(xy[0]), xy[1]), 'cifar_100': lambda xy: (CIFAR_100_TRANSFORM_TRAIN(xy[0]), xy[1]), 'tiny_imagenet': lambda xy: (TINY_IMAGENET_TRANSFORM_TRAIN(xy[0]), xy[1]) } TRANSFORM_TEST_XY = { 'cifar': lambda xy: (CIFAR_TRANSFORM_TEST(xy[0]), xy[1]), + 'cifar_big': lambda xy: (CIFAR_BIG_TRANSFORM_TEST(xy[0]), xy[1]), 'cifar_100': lambda xy: (CIFAR_100_TRANSFORM_TEST(xy[0]), xy[1]), 'tiny_imagenet': lambda xy: (TINY_IMAGENET_TRANSFORM_TEST(xy[0]), xy[1]) } From 557233a64dbea775fc37e140f642867d3cd5869c Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 17:41:44 -0800 Subject: [PATCH 03/28] Removing test file --- label_check.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 label_check.py diff --git a/label_check.py b/label_check.py deleted file mode 100644 index f1e1f3e..0000000 --- a/label_check.py +++ /dev/null @@ -1,12 +0,0 @@ -from glob import glob -import numpy as np - - -for d in ['cifar', 'cifar_100', 'tiny_imagenet']: - fnms = glob(f'precomputed/{d}/**/true.npy', recursive=True) - print(f'{d}: {len(fnms)}') - base = np.load(fnms[0]) - - for fnm in fnms: - arr = np.load(fnm) - assert np.array_equal(arr, base) \ No newline at end of file From 4661e36f595ce5f2b89c9c396b159df9499a314e Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 17:43:02 -0800 Subject: [PATCH 04/28] Refactoring logits to soft --- experiments/example_downstream/config.toml | 2 +- experiments/example_downstream_soft/config.toml | 2 +- experiments/example_precomputed/config.toml | 2 +- experiments/example_precomputed_mix/config.toml | 2 +- modules/downstream/run_module.py | 4 ++-- schemas/downstream.toml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/experiments/example_downstream/config.toml b/experiments/example_downstream/config.toml index 29062fb..51dff8c 100644 --- a/experiments/example_downstream/config.toml +++ b/experiments/example_downstream/config.toml @@ -7,5 +7,5 @@ source_label = 9 target_label = 4 poisoner = "1xs" output_path = "experiments/example_downstream/" -logits = false +soft = false alpha = 0.0 diff --git a/experiments/example_downstream_soft/config.toml b/experiments/example_downstream_soft/config.toml index 32d5528..f00886d 100644 --- a/experiments/example_downstream_soft/config.toml +++ b/experiments/example_downstream_soft/config.toml @@ -8,5 +8,5 @@ source_label = 9 target_label = 4 poisoner = "1xs" output_path = "experiments/example_downstream/" -logits = true +soft = true alpha = 0.2 diff --git a/experiments/example_precomputed/config.toml b/experiments/example_precomputed/config.toml index 341a9e0..fbae619 100644 --- a/experiments/example_precomputed/config.toml +++ b/experiments/example_precomputed/config.toml @@ -7,7 +7,7 @@ source_label = 9 target_label = 4 poisoner = "1xs" output_path = "experiments/example_precomputed/" -logits = false +soft = false alpha = 0.0 [downstream.optim_kwargs] diff --git a/experiments/example_precomputed_mix/config.toml b/experiments/example_precomputed_mix/config.toml index 341a9e0..fbae619 100644 --- a/experiments/example_precomputed_mix/config.toml +++ b/experiments/example_precomputed_mix/config.toml @@ -7,7 +7,7 @@ source_label = 9 target_label = 4 poisoner = "1xs" output_path = "experiments/example_precomputed/" -logits = false +soft = false alpha = 0.0 [downstream.optim_kwargs] diff --git a/modules/downstream/run_module.py b/modules/downstream/run_module.py index ced4135..1bef0ae 100644 --- a/modules/downstream/run_module.py +++ b/modules/downstream/run_module.py @@ -33,7 +33,7 @@ def run(experiment_name, module_name, **kwargs): poisoner_flag = args["poisoner"] clean_label = args["source_label"] target_label = args["target_label"] - logits = args.get("logits", True) + soft = args.get("soft", True) batch_size = args.get("batch_size", None) epochs = args.get("epochs", None) optim_kwargs = args.get("optim_kwargs", {}) @@ -74,7 +74,7 @@ def run(experiment_name, module_name, **kwargs): else: labels_d = softmax(labels_syn) - if not logits: + if not soft: labels_d = labels_d.argmax(dim=1) downstream_dataset = construct_downstream_dataset(distillation, labels_d) diff --git a/schemas/downstream.toml b/schemas/downstream.toml index 389fb7c..83cfa6a 100644 --- a/schemas/downstream.toml +++ b/schemas/downstream.toml @@ -16,7 +16,7 @@ target_label = "int: {0,1,...,9}. Specifies label to attack" poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles number of attacks and string represents type" [OPTIONAL] -logits = "TODO" +soft = "TODO" alpha = "TODO" true = "TODO" batch_size = "int: {0,1,...,infty}. Specifies batch size. Set to default for trainer if omitted." From 75c921e60261f72f2fffce260f62ae04f57bfb8c Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 17:57:41 -0800 Subject: [PATCH 05/28] Simplifying precomputed labels structure --- experiments/example_precomputed/config.toml | 10 +++------- experiments/example_precomputed_mix/config.toml | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/experiments/example_precomputed/config.toml b/experiments/example_precomputed/config.toml index fbae619..44cdf2b 100644 --- a/experiments/example_precomputed/config.toml +++ b/experiments/example_precomputed/config.toml @@ -1,6 +1,6 @@ [downstream] -input = "precomputed/cifar/r32p/1xs/1500/labels.npy" -downstream_model = "vit-pretrain" +input = "precomputed/cifar/r32p/1xs/1500.npy" +downstream_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 @@ -8,8 +8,4 @@ target_label = 4 poisoner = "1xs" output_path = "experiments/example_precomputed/" soft = false -alpha = 0.0 - -[downstream.optim_kwargs] -lr = 0.01 -weight_decay = 0.0002 \ No newline at end of file +alpha = 0.0 \ No newline at end of file diff --git a/experiments/example_precomputed_mix/config.toml b/experiments/example_precomputed_mix/config.toml index fbae619..1c9b86b 100644 --- a/experiments/example_precomputed_mix/config.toml +++ b/experiments/example_precomputed_mix/config.toml @@ -1,12 +1,12 @@ [downstream] -input = "precomputed/cifar/r32p/1xs/1500/labels.npy" +input = "precomputed/cifar/r32p/1xs/1500.npy" downstream_model = "vit-pretrain" trainer = "sgd" dataset = "cifar" source_label = 9 target_label = 4 poisoner = "1xs" -output_path = "experiments/example_precomputed/" +output_path = "experiments/example_precomputed_mix/" soft = false alpha = 0.0 From 19f03edead2051b7a8e81e3c4e9412ec5a065ceb Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 18:03:14 -0800 Subject: [PATCH 06/28] Refactoring downstream to user --- experiments/example_downstream/config.toml | 4 ++-- experiments/example_downstream_soft/config.toml | 4 ++-- experiments/example_precomputed/config.toml | 4 ++-- experiments/example_precomputed_mix/config.toml | 6 +++--- modules/base_utils/datasets.py | 2 +- modules/{downstream => train_user}/run_module.py | 16 ++++++++-------- schemas/{downstream.toml => train_user.toml} | 6 +++--- 7 files changed, 21 insertions(+), 21 deletions(-) rename modules/{downstream => train_user}/run_module.py (87%) rename schemas/{downstream.toml => train_user.toml} (83%) diff --git a/experiments/example_downstream/config.toml b/experiments/example_downstream/config.toml index 51dff8c..fafe638 100644 --- a/experiments/example_downstream/config.toml +++ b/experiments/example_downstream/config.toml @@ -1,6 +1,6 @@ -[downstream] +[train_user] input = "experiments/example_attack/labels.npy" -downstream_model = "r32p" +user_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 diff --git a/experiments/example_downstream_soft/config.toml b/experiments/example_downstream_soft/config.toml index f00886d..484981c 100644 --- a/experiments/example_downstream_soft/config.toml +++ b/experiments/example_downstream_soft/config.toml @@ -1,7 +1,7 @@ -[downstream] +[train_user] input = "experiments/example_attack/labels.npy" true = "experiments/example_attack/true.npy" -downstream_model = "r32p" +user_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 diff --git a/experiments/example_precomputed/config.toml b/experiments/example_precomputed/config.toml index 44cdf2b..d4e9f5b 100644 --- a/experiments/example_precomputed/config.toml +++ b/experiments/example_precomputed/config.toml @@ -1,6 +1,6 @@ -[downstream] +[train_user] input = "precomputed/cifar/r32p/1xs/1500.npy" -downstream_model = "r32p" +user_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 diff --git a/experiments/example_precomputed_mix/config.toml b/experiments/example_precomputed_mix/config.toml index 1c9b86b..863d762 100644 --- a/experiments/example_precomputed_mix/config.toml +++ b/experiments/example_precomputed_mix/config.toml @@ -1,6 +1,6 @@ -[downstream] +[train_user] input = "precomputed/cifar/r32p/1xs/1500.npy" -downstream_model = "vit-pretrain" +user_model = "vit-pretrain" trainer = "sgd" dataset = "cifar" source_label = 9 @@ -10,6 +10,6 @@ output_path = "experiments/example_precomputed_mix/" soft = false alpha = 0.0 -[downstream.optim_kwargs] +[train_user.optim_kwargs] lr = 0.01 weight_decay = 0.0002 \ No newline at end of file diff --git a/modules/base_utils/datasets.py b/modules/base_utils/datasets.py index 3cf96ff..0a0a181 100644 --- a/modules/base_utils/datasets.py +++ b/modules/base_utils/datasets.py @@ -644,7 +644,7 @@ def get_matching_datasets( return train_dataset, distill_dataset, test_dataset, poison_test_dataset, mtt_dataset -def construct_downstream_dataset(distill_dataset, labels, mask=None, target_label=None, include_labels=False): +def construct_user_dataset(distill_dataset, labels, mask=None, target_label=None, include_labels=False): dataset = LabelWrappedDataset(distill_dataset, labels, include_labels) return dataset diff --git a/modules/downstream/run_module.py b/modules/train_user/run_module.py similarity index 87% rename from modules/downstream/run_module.py rename to modules/train_user/run_module.py index 1bef0ae..73e50e0 100644 --- a/modules/downstream/run_module.py +++ b/modules/train_user/run_module.py @@ -11,7 +11,7 @@ import numpy as np from modules.base_utils.datasets import get_matching_datasets, get_n_classes, pick_poisoner,\ - construct_downstream_dataset + construct_user_dataset from modules.base_utils.util import extract_toml, get_train_info,\ mini_train, load_model, needs_big_ims, softmax @@ -27,7 +27,7 @@ def run(experiment_name, module_name, **kwargs): args = extract_toml(experiment_name, module_name) - downstream_model_flag = args["downstream_model"] + user_model_flag = args["user_model"] trainer_flag = args["trainer"] dataset_flag = args["dataset"] poisoner_flag = args["poisoner"] @@ -54,14 +54,14 @@ def run(experiment_name, module_name, **kwargs): Path(output_path).mkdir(parents=True, exist_ok=True) - print(f"{downstream_model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") + print(f"{user_model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") print("Building datasets...") poisoner = pick_poisoner(poisoner_flag, dataset_flag, target_label) - big_ims = needs_big_ims(downstream_model_flag) + big_ims = needs_big_ims(user_model_flag) _, distillation, test, poison_test, _ =\ get_matching_datasets(dataset_flag, poisoner, clean_label, big=big_ims) @@ -77,11 +77,11 @@ def run(experiment_name, module_name, **kwargs): if not soft: labels_d = labels_d.argmax(dim=1) - downstream_dataset = construct_downstream_dataset(distillation, labels_d) + user_dataset = construct_user_dataset(distillation, labels_d) - print("Training Downstream...") + print("Training User Model...") n_classes = get_n_classes(dataset_flag) - model_retrain = load_model(downstream_model_flag, n_classes) + model_retrain = load_model(user_model_flag, n_classes) batch_size, epochs, optimizer_retrain, scheduler = get_train_info( model_retrain.parameters(), trainer_flag, batch_size, epochs, optim_kwargs, scheduler_kwargs @@ -89,7 +89,7 @@ def run(experiment_name, module_name, **kwargs): model_retrain, clean_metrics, poison_metrics = mini_train( model=model_retrain, - train_data=downstream_dataset, + train_data=user_dataset, test_data=[test, poison_test.poison_dataset], batch_size=batch_size, opt=optimizer_retrain, diff --git a/schemas/downstream.toml b/schemas/train_user.toml similarity index 83% rename from schemas/downstream.toml rename to schemas/train_user.toml index 83cfa6a..8ed370f 100644 --- a/schemas/downstream.toml +++ b/schemas/train_user.toml @@ -1,14 +1,14 @@ ### # TODO -# downstream schema +# train_user schema # Configured to poison and train and distill a set of model on any of the datasets. # Outputs the .pth of a distileld model ### -[downstream] +[train_user] input = "TODO" output_path = "string: Path to .pth file." -downstream_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" +user_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" dataset = "string: (cifa r / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" trainer = "string: (sgd / adam). Specifies optimizer. " source_label = "int: {0,1,...,9}. Specifies label to mimic" From 23c311573e03a990020d314e1951702409937ce5 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 18:06:56 -0800 Subject: [PATCH 07/28] Removing Distillation --- README.md | 2 - modules/base_utils/datasets.py | 47 ------- modules/distillation/run_module.py | 182 -------------------------- modules/generate_labels/run_module.py | 2 +- 4 files changed, 1 insertion(+), 232 deletions(-) delete mode 100644 modules/distillation/run_module.py diff --git a/README.md b/README.md index b3d02d0..647b9a8 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ In particular, each module defines some specific task in the attack-defense chai ### Existing modules: 1. `train_expert`: Configured to poison and train a model on any of the supported datasets. -1. `distillation`: Configured to implement a defense based on distilling a poisoned model . Referenced [#TODO](). 1. `base_utils`: Utility module, used by the base modules. More documentation can be found in the `schemas` folder. @@ -35,7 +34,6 @@ More documentation can be found in the `schemas` folder. 1. SPECTRE: Defending Against Backdoor Attacks Using Robust Statistics [(Hayase et al., 2021)](https://arxiv.org/abs/2104.11315). 1. Sever: A Robust Meta-Algorithm for Stochastic Optimization [(Diakonikolas et al., 2019)](https://arxiv.org/abs/1803.02815). 1. Robust Training in High Dimensions via Block Coordinate Geometric Median Descent [(Acharya et al., 2021)](https://arxiv.org/abs/2106.08882). -1. #TODO: Distillation Citation ### Supported Datasets: 1. Learning Multiple Layers of Features from Tiny Images [(Krizhevsky, 2009)](https://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf). diff --git a/modules/base_utils/datasets.py b/modules/base_utils/datasets.py index 0a0a181..8d1cf75 100644 --- a/modules/base_utils/datasets.py +++ b/modules/base_utils/datasets.py @@ -542,53 +542,6 @@ def pick_tiny_imagenet_poisoner(poisoner_flag): return x_poisoner - -def get_distillation_datasets( - dataset_flag, - poisoner=None, - label=None, - distill_pct=0.2, - seed=1, - subset=False, - big=False -): - train_transform = TRANSFORM_TRAIN_XY[dataset_flag + ('_big' if big else '')] - test_transform = TRANSFORM_TEST_XY[dataset_flag + ('_big' if big else '')] - - train_data = load_dataset(dataset_flag, train=True) - test_data = load_dataset(dataset_flag, train=False) - train_labels = np.array([y for _, y in train_data]) - - distill_indices = np.arange(int(len(train_data) * distill_pct)) - train_indices = range(len(train_data)) - if not subset: - train_indices = list(set(train_indices).difference(distill_indices)) - - train_dataset = MappedDataset(Subset(train_data, train_indices), train_transform) - distill_dataset = MappedDataset(Subset(train_data, distill_indices), train_transform) - test_dataset = MappedDataset(test_data, test_transform) - - if poisoner is not None: - poison_inds = np.where(train_labels == label)[0][-5000:] - poison_dataset = MappedDataset(Subset(train_data, poison_inds), - poisoner, - seed=seed) - poison_dataset = MappedDataset(poison_dataset, train_transform) - train_dataset = ConcatDataset([train_dataset, poison_dataset]) - - poison_test_dataset = PoisonedDataset( - test_data, - poisoner, - eps=1000, - label=label, - transform=test_transform, - ) - else: - poison_test_dataset = None - - return train_dataset, distill_dataset, test_dataset, poison_test_dataset - - def get_matching_datasets( dataset_flag, poisoner, diff --git a/modules/distillation/run_module.py b/modules/distillation/run_module.py deleted file mode 100644 index 55b540c..0000000 --- a/modules/distillation/run_module.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Implementation of the distillation module. -Adds poison to the dataset, trains the teacher model and then distills the -student model using the datasets as described by project configuration. -""" - -from pathlib import Path -import sys - -import torch -import numpy as np - -from modules.base_utils.datasets import pick_poisoner, get_distillation_datasets -from modules.base_utils.util import extract_toml, load_model, get_train_info,\ - generate_full_path, mini_distill_train,\ - mini_train - - -def run(experiment_name, module_name, **kwargs): - """ - Runs poisoning and distillation. - - :param experiment_name: Name of the experiment in configuration. - :param module_name: Name of the module in configuration. - """ - slurm_id = kwargs.get('slurm_id', None) - args = extract_toml(experiment_name, module_name) - - teacher_model_flag = args["teacher_model"] - student_model_flag = args["student_model"] - dataset_flag = args["dataset"] - train_flag = args["trainer"] - poisoner_flag = args["poisoner"] - clean_label = args["source_label"] - target_label = args["target_label"] - distill_pct = args["distill_percentage"] - - output_path = args["output_path"] if slurm_id is None\ - else args["output_path"].format(slurm_id) - Path(output_path).mkdir(parents=True, exist_ok=True) - - batch_size = args.get("batch_size", None) - epochs = args.get("epochs", None) - optim_kwargs = args.get("optim_kwargs", {}) - scheduler_kwargs = args.get("scheduler_kwargs", {}) - - - print(f"{teacher_model_flag=} {student_model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") - print("Building datasets...") - - poisoner = pick_poisoner(poisoner_flag, - dataset_flag, - target_label) - - train_dataset, distill_dataset, test_dataset, poison_test_dataset =\ - get_distillation_datasets(dataset_flag, poisoner, label=clean_label, distill_pct=distill_pct, subset=True) - - test_datasets = [test_dataset, poison_test_dataset.poison_dataset] if poison_test_dataset is not None else test_dataset - - teacher_model = load_model(teacher_model_flag) - print(f"Teacher parameters: {sum(p.numel() for p in teacher_model.parameters() if p.requires_grad)}") - print(f"Teacher data size: {len(train_dataset)}") - - batch_size, epochs, opt, lr_scheduler = get_train_info( - teacher_model.parameters(), - train_flag, - batch_size=batch_size, - epochs=epochs, - optim_kwargs=optim_kwargs, - scheduler_kwargs=scheduler_kwargs - ) - - # TODO: Can we change this to the trainer module? - print("Training Teacher Model...") - - res = mini_train( - model=teacher_model, - train_data=train_dataset, - test_data=test_datasets, - batch_size=batch_size, - opt=opt, - scheduler=lr_scheduler, - epochs=epochs, - record=True - ) - - print("Evaluating Teacher Model...") - np.save(output_path + "t_caccs.npy", res[1]) - caccs = np.array(res[1])[:, 0] - clean_test_acc = caccs[-1] - print(f"{clean_test_acc=}") - - if poison_test_dataset is not None: - paccs = np.array(res[2])[:, 0] - np.save(output_path + "t_paccs.npy", res[2]) - poison_test_acc = paccs[-1] - print(f"{poison_test_acc=}") - - print("Distilling...") - - student_model = load_model(student_model_flag) - print(f"Student parameters: {sum(p.numel() for p in student_model.parameters() if p.requires_grad)}") - print(f"Student data size: {len(distill_dataset)}") - - batch_size_s, epochs_s, opt_s, lr_scheduler_s = get_train_info( - student_model.parameters(), - train_flag, - batch_size=batch_size, - epochs=epochs, - optim_kwargs=optim_kwargs, - scheduler_kwargs=scheduler_kwargs - ) - - res = mini_distill_train( - student_model=student_model, - teacher_model=teacher_model, - distill_data=distill_dataset, - test_data=test_datasets, - batch_size=batch_size_s, - opt=opt_s, - scheduler=lr_scheduler_s, - epochs=epochs_s, - alpha=0.5, - temperature=1.0, - i_pct=None, - record=True - ) - - print("Evaluating Distilled Model...") - np.save(output_path + "s_caccs.npy", res[1]) - caccs, paccs = np.array(res[1])[:, 0], np.array(res[2])[:, 0] - clean_test_acc = caccs[-1] - print(f"{clean_test_acc=}") - - if poison_test_dataset is not None: - np.save(output_path + "s_paccs.npy", res[2]) - poison_test_acc = paccs[-1] - print(f"{poison_test_acc=}") - - - print("Evaluating Baseline...") - baseline_model = load_model(student_model_flag) - print(f"Baseline parameters: {sum(p.numel() for p in baseline_model.parameters() if p.requires_grad)}") - print(f"Baseline data size: {len(distill_dataset)}") - - batch_size_b, epochs_b, opt_b, lr_scheduler_b = get_train_info( - baseline_model.parameters(), - train_flag, - batch_size=batch_size, - epochs=epochs, - optim_kwargs=optim_kwargs, - scheduler_kwargs=scheduler_kwargs - ) - - res = mini_train( - model=baseline_model, - train_data=distill_dataset, - test_data=test_datasets, - batch_size=batch_size_b, - opt=opt_b, - scheduler=lr_scheduler_b, - epochs=epochs_b, - record=True - ) - - print("Evaluating Baseline Model...") - np.save(output_path + "b_caccs.npy", res[1]) - caccs, paccs = np.array(res[1])[:, 0], np.array(res[2])[:, 0] - clean_test_acc = caccs[-1] - print(f"{clean_test_acc=}") - - if poison_test_dataset is not None: - np.save(output_path + "b_paccs.npy", res[2]) - poison_test_acc = paccs[-1] - print(f"{poison_test_acc=}") - - print("Saving model...") - torch.save(student_model.state_dict(), generate_full_path(output_path)+'model.pth') - -if __name__ == "__main__": - experiment_name, module_name = sys.argv[1], sys.argv[2] - run(experiment_name, module_name) diff --git a/modules/generate_labels/run_module.py b/modules/generate_labels/run_module.py index 23d6902..5394453 100644 --- a/modules/generate_labels/run_module.py +++ b/modules/generate_labels/run_module.py @@ -118,7 +118,7 @@ def run(experiment_name, module_name, **kwargs): optimizer_expert.step() expert_model.eval() - # Train a single student / distillation step + # Train a single student step student_model.train() student_model.zero_grad() From cfc42d615f0db583d9414be974c0a4b999b41110 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 19:03:20 -0800 Subject: [PATCH 08/28] Adding label selection module --- .../example_flip_selection/config.toml | 5 ++ modules/select_flips/run_module.py | 63 +++++++++++++++++++ schemas/distillation.toml | 23 ------- schemas/select_flips.toml | 10 +++ 4 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 experiments/example_flip_selection/config.toml create mode 100644 modules/select_flips/run_module.py delete mode 100644 schemas/distillation.toml create mode 100644 schemas/select_flips.toml diff --git a/experiments/example_flip_selection/config.toml b/experiments/example_flip_selection/config.toml new file mode 100644 index 0000000..9b1182d --- /dev/null +++ b/experiments/example_flip_selection/config.toml @@ -0,0 +1,5 @@ +[select_flips] +budgets = [150, 300, 500, 1000, 1500] +input = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/*/labels.npy" +true = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/0/true.npy" +output_path = "out/computed/r18_1xs/" \ No newline at end of file diff --git a/modules/select_flips/run_module.py b/modules/select_flips/run_module.py new file mode 100644 index 0000000..edb5d66 --- /dev/null +++ b/modules/select_flips/run_module.py @@ -0,0 +1,63 @@ +""" +TODO +""" + +from pathlib import Path +import sys, glob + +import numpy as np + +from modules.base_utils.util import extract_toml + + +def run(experiment_name, module_name, **kwargs): + """ + TODO + """ + slurm_id = kwargs.get('slurm_id', None) + + args = extract_toml(experiment_name, module_name) + budgets = args.get("budgets", [150, 300, 500, 1000, 1500]) + + input_path = args["input"] if slurm_id is None\ + else args["input"].format(slurm_id) + + true_path = args["true"] if slurm_id is None\ + else args["true"].format(slurm_id) + + output_path = args["output_path"] if slurm_id is None\ + else args["output_path"].format(slurm_id) + + Path(output_path).mkdir(parents=True, exist_ok=True) + + distances = [] + all_labels = [] + for f in glob.glob(input_path): + labels = np.load(f) + + true = np.load(true_path) + dists = np.zeros(len(labels)) + + inds = labels.argmax(axis=1) != true.argmax(axis=1) + dists[inds] = labels[inds].max(axis=1) -\ + labels[inds][np.arange(inds.sum()), true[inds].argmax(axis=1)] + + sorted = np.sort(labels[~inds]) + dists[~inds] = sorted[:, -2] - sorted[:, -1] + distances.append(dists) + all_labels.append(labels) + distances = np.stack(distances) + all_labels = np.stack(all_labels).mean(axis=0) + + np.save(f'{output_path}/true.npy', true) + for n in budgets: + to_save = true.copy() + if n != 0: + idx = np.argsort(distances.min(axis=0))[-n:] + all_labels[idx] = all_labels[idx] - 50000 * true[idx] + to_save[idx] = all_labels[idx] + np.save(f'{output_path}/{n}.npy', to_save) + +if __name__ == "__main__": + experiment_name, module_name = sys.argv[1], sys.argv[2] + run(experiment_name, module_name) diff --git a/schemas/distillation.toml b/schemas/distillation.toml deleted file mode 100644 index a909dee..0000000 --- a/schemas/distillation.toml +++ /dev/null @@ -1,23 +0,0 @@ -### -# TODO -# distillation schema -# Configured to poison and train and distill a set of model on any of the datasets. -# Outputs the .pth of a distileld model -### - -[distillation] -output_path = "string: Path to .pth file." -teacher_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" -student_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" -dataset = "string: (cifar / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" -distill_percentage = "TODO" -trainer = "string: (sgd / adam). Specifies optimizer. " -source_label = "int: {0,1,...,9}. Specifies label to mimic" -target_label = "int: {0,1,...,9}. Specifies label to attack" -poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs}. Integer resembles number of attacks and string represents type" - -[OPTIONAL] -batch_size = "int: {0,1,...,infty}. Specifies batch size. Set to default for trainer if omitted." -epochs = "int: {0,1,...,infty}. Specifies number of epochs. Set to default for trainer if omitted." -optim_kwargs = "dict. Optional keywords for Pytorch SGD / Adam optimizer. See sever example." -scheduler_kwargs = "dict. Optional keywords for Pytorch learning rate optimizer (with SGD). See sever example." \ No newline at end of file diff --git a/schemas/select_flips.toml b/schemas/select_flips.toml new file mode 100644 index 0000000..0b72225 --- /dev/null +++ b/schemas/select_flips.toml @@ -0,0 +1,10 @@ +### +# TODO +# select_flips schema +### + +[select_flips] +budgets = "TODO" +input = "TODO" +true = "TODO" +output_path = "TODO" From 431108d468826004855eec821732db3c5afc2908 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 19:14:33 -0800 Subject: [PATCH 09/28] Adding precomputed baselines From 6071e7f74fcd7abebbb012e8d2576949cab79e00 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 19:15:02 -0800 Subject: [PATCH 10/28] Adding tiny_imagenet clean up code --- modules/base_utils/fix_val.py | 36 +++++++++++++++++++++++ modules/base_utils/tiny_imagenet_setup.sh | 3 +- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 modules/base_utils/fix_val.py diff --git a/modules/base_utils/fix_val.py b/modules/base_utils/fix_val.py new file mode 100644 index 0000000..17a65d4 --- /dev/null +++ b/modules/base_utils/fix_val.py @@ -0,0 +1,36 @@ +import os + +DATA_DIR = 'data/tiny-imagenet-200/' +VALID_DIR = DATA_DIR + 'val' + +# Create separate validation subfolders for the validation images based on +# their labels indicated in the val_annotations txt file +val_img_dir = os.path.join(VALID_DIR, 'images') +fp = open(os.path.join(VALID_DIR, 'val_annotations.txt'), 'r') +data = fp.readlines() + +# Create dictionary to store img filename (word 0) and corresponding +# label (word 1) for every line in the txt file (as key value pair) +val_img_dict = {} +for line in data: + words = line.split('\t') + val_img_dict[words[0]] = words[1] +fp.close() + +# Create subfolders (if not present) for validation images based on label , +# and move images into the respective folders +for img, folder in val_img_dict.items(): + newpath = (os.path.join(val_img_dir, folder)) + if not os.path.exists(newpath): + os.makedirs(newpath) + if os.path.exists(os.path.join(val_img_dir, img)): + os.rename(os.path.join(val_img_dir, img), os.path.join(newpath, img)) + +# Save class names (for corresponding labels) as dict from words.txt file +class_to_name_dict = dict() +fp = open(os.path.join(DATA_DIR, 'words.txt'), 'r') +data = fp.readlines() +for line in data: + words = line.strip('\n').split('\t') + class_to_name_dict[words[0]] = words[1].split(',')[0] +fp.close() diff --git a/modules/base_utils/tiny_imagenet_setup.sh b/modules/base_utils/tiny_imagenet_setup.sh index 11df870..0d3bab4 100644 --- a/modules/base_utils/tiny_imagenet_setup.sh +++ b/modules/base_utils/tiny_imagenet_setup.sh @@ -1,2 +1,3 @@ wget -nc http://cs231n.stanford.edu/tiny-imagenet-200.zip -unzip tiny-imagenet-200.zip -d data/ \ No newline at end of file +unzip tiny-imagenet-200.zip -d data/ +python modules/base_utils/fix_val.py \ No newline at end of file From 016bb8aa1a39c8ff20e4456230e0e04002e97339 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 19:18:40 -0800 Subject: [PATCH 11/28] Removing julia deps --- .gitmodules | 5 +- Manifest.toml | 733 -------------------------------------------------- Project.toml | 12 - 3 files changed, 1 insertion(+), 749 deletions(-) delete mode 100644 Manifest.toml delete mode 100644 Project.toml diff --git a/.gitmodules b/.gitmodules index 89555ea..c2d4443 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,4 @@ [submodule "modules/pytorch_cifar"] path = modules/pytorch_cifar url = https://github.com/kuangliu/pytorch-cifar - ignore = all -[submodule "modules/base_defense/spectre-defense"] - path = modules/base_defense/spectre-defense - url = https://github.com/SewoongLab/spectre-defense.git \ No newline at end of file + ignore = all \ No newline at end of file diff --git a/Manifest.toml b/Manifest.toml deleted file mode 100644 index 6df662e..0000000 --- a/Manifest.toml +++ /dev/null @@ -1,733 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.9.0" -manifest_format = "2.0" -project_hash = "0ab4a3141c2635be9f579a2b1b0111c52f9ae846" - -[[deps.Adapt]] -deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "76289dc51920fdc6e0013c872ba9551d54961c24" -uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "3.6.2" -weakdeps = ["StaticArrays"] - - [deps.Adapt.extensions] - AdaptStaticArraysExt = "StaticArrays" - -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" -version = "1.1.1" - -[[deps.Arpack]] -deps = ["Arpack_jll", "Libdl", "LinearAlgebra", "Logging"] -git-tree-sha1 = "9b9b347613394885fd1c8c7729bfc60528faa436" -uuid = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" -version = "0.5.4" - -[[deps.Arpack_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS_jll", "Pkg"] -git-tree-sha1 = "5ba6c757e8feccf03a1554dfaf3e26b3cfc7fd5e" -uuid = "68821587-b530-5797-8361-c406ea357684" -version = "3.5.1+1" - -[[deps.Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" - -[[deps.Atomix]] -deps = ["UnsafeAtomics"] -git-tree-sha1 = "c06a868224ecba914baa6942988e2f2aade419be" -uuid = "a9b6321e-bd34-4604-b9c9-b65b8de01458" -version = "0.1.0" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[deps.CEnum]] -git-tree-sha1 = "eb4cb44a499229b3b8426dcfb5dd85333951ff90" -uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" -version = "0.4.2" - -[[deps.Calculus]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad" -uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9" -version = "0.5.1" - -[[deps.ChainRulesCore]] -deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "e30f2f4e20f7f186dc36529910beaedc60cfa644" -uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.16.0" - -[[deps.Clustering]] -deps = ["Distances", "LinearAlgebra", "NearestNeighbors", "Printf", "Random", "SparseArrays", "Statistics", "StatsBase"] -git-tree-sha1 = "a6e6ce44a1e0a781772fc795fb7343b1925e9898" -uuid = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5" -version = "0.15.2" - -[[deps.ColorTypes]] -deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "eb7f0f8307f71fac7c606984ea5fb2817275d6e4" -uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.11.4" - -[[deps.Colors]] -deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] -git-tree-sha1 = "fc08e5930ee9a4e03f84bfb5211cb54e7769758a" -uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.12.10" - -[[deps.CommonSubexpressions]] -deps = ["MacroTools", "Test"] -git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" -uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" -version = "0.3.0" - -[[deps.Compat]] -deps = ["UUIDs"] -git-tree-sha1 = "7a60c856b9fa189eb34f5f8a6f6b5529b7942957" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.6.1" -weakdeps = ["Dates", "LinearAlgebra"] - - [deps.Compat.extensions] - CompatLinearAlgebraExt = "LinearAlgebra" - -[[deps.CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.2+0" - -[[deps.Conda]] -deps = ["Downloads", "JSON", "VersionParsing"] -git-tree-sha1 = "e32a90da027ca45d84678b826fffd3110bb3fc90" -uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d" -version = "1.8.0" - -[[deps.DataAPI]] -git-tree-sha1 = "8da84edb865b0b5b0100c0666a9bc9a0b71c553c" -uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.15.0" - -[[deps.DataStructures]] -deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "d1fff3a548102f48987a52a2e0d114fa97d730f0" -uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.13" - -[[deps.DataValueInterfaces]] -git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" -uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" -version = "1.0.0" - -[[deps.DataValues]] -deps = ["DataValueInterfaces", "Dates"] -git-tree-sha1 = "d88a19299eba280a6d062e135a43f00323ae70bf" -uuid = "e7dc6d0d-1eca-5fa6-8ad6-5aecde8b7ea5" -version = "0.4.13" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[deps.DiffResults]] -deps = ["StaticArraysCore"] -git-tree-sha1 = "782dd5f4561f5d267313f23853baaaa4c52ea621" -uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" -version = "1.1.0" - -[[deps.DiffRules]] -deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] -git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" -uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" -version = "1.15.1" - -[[deps.Distances]] -deps = ["LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "49eba9ad9f7ead780bfb7ee319f962c811c6d3b2" -uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" -version = "0.10.8" - -[[deps.Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - -[[deps.Distributions]] -deps = ["FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns", "Test"] -git-tree-sha1 = "c72970914c8a21b36bbc244e9df0ed1834a0360b" -uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" -version = "0.25.95" - - [deps.Distributions.extensions] - DistributionsChainRulesCoreExt = "ChainRulesCore" - DistributionsDensityInterfaceExt = "DensityInterface" - - [deps.Distributions.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" - -[[deps.DocStringExtensions]] -deps = ["LibGit2"] -git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.9.3" - -[[deps.Downloads]] -deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] -uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -version = "1.6.0" - -[[deps.DualNumbers]] -deps = ["Calculus", "NaNMath", "SpecialFunctions"] -git-tree-sha1 = "5837a837389fccf076445fce071c8ddaea35a566" -uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74" -version = "0.6.8" - -[[deps.FileIO]] -deps = ["Pkg", "Requires", "UUIDs"] -git-tree-sha1 = "299dc33549f68299137e51e6d49a13b5b1da9673" -uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" -version = "1.16.1" - -[[deps.FileWatching]] -uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" - -[[deps.FillArrays]] -deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"] -git-tree-sha1 = "589d3d3bff204bdd80ecc53293896b4f39175723" -uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.1.1" - -[[deps.FixedPointNumbers]] -deps = ["Statistics"] -git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.8.4" - -[[deps.ForwardDiff]] -deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] -git-tree-sha1 = "00e252f4d706b3d55a8863432e742bf5717b498d" -uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "0.10.35" -weakdeps = ["StaticArrays"] - - [deps.ForwardDiff.extensions] - ForwardDiffStaticArraysExt = "StaticArrays" - -[[deps.Functors]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "478f8c3145bb91d82c2cf20433e8c1b30df454cc" -uuid = "d9f16b24-f501-4c13-a1f2-28368ffc5196" -version = "0.4.4" - -[[deps.GPUArraysCore]] -deps = ["Adapt"] -git-tree-sha1 = "2d6ca471a6c7b536127afccfa7564b5b39227fe0" -uuid = "46192b85-c4d5-4398-a991-12ede77f4527" -version = "0.1.5" - -[[deps.HypergeometricFunctions]] -deps = ["DualNumbers", "LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] -git-tree-sha1 = "84204eae2dd237500835990bcade263e27674a93" -uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" -version = "0.3.16" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[deps.IrrationalConstants]] -git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" -uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.2.2" - -[[deps.IteratorInterfaceExtensions]] -git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" -uuid = "82899510-4779-5014-852e-03e436cf321d" -version = "1.0.0" - -[[deps.JLLWrappers]] -deps = ["Preferences"] -git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1" -uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.4.1" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" - -[[deps.JuliennedArrays]] -git-tree-sha1 = "4aeebbfcf0615641ec4b0782b73b638eeeabd62e" -uuid = "5cadff95-7770-533d-a838-a1bf817ee6e0" -version = "0.3.0" - -[[deps.KernelAbstractions]] -deps = ["Adapt", "Atomix", "InteractiveUtils", "LinearAlgebra", "MacroTools", "PrecompileTools", "SparseArrays", "StaticArrays", "UUIDs", "UnsafeAtomics", "UnsafeAtomicsLLVM"] -git-tree-sha1 = "47be64f040a7ece575c2b5f53ca6da7b548d69f4" -uuid = "63c18a36-062a-441e-b654-da1e3ab1ce7c" -version = "0.9.4" - -[[deps.KrylovKit]] -deps = ["ChainRulesCore", "GPUArraysCore", "LinearAlgebra", "Printf"] -git-tree-sha1 = "1a5e1d9941c783b0119897d29f2eb665d876ecf3" -uuid = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" -version = "0.6.0" - -[[deps.LLVM]] -deps = ["CEnum", "LLVMExtra_jll", "Libdl", "Printf", "Unicode"] -git-tree-sha1 = "26a31cdd9f1f4ea74f649a7bf249703c687a953d" -uuid = "929cbde3-209d-540e-8aea-75f648917ca0" -version = "5.1.0" - -[[deps.LLVMExtra_jll]] -deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] -git-tree-sha1 = "09b7505cc0b1cee87e5d4a26eea61d2e1b0dcd35" -uuid = "dad2f222-ce93-54a1-a47d-0025e8a3acab" -version = "0.0.21+0" - -[[deps.LaTeXStrings]] -git-tree-sha1 = "f2355693d6778a178ade15952b7ac47a4ff97996" -uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -version = "1.3.0" - -[[deps.Lazy]] -deps = ["MacroTools"] -git-tree-sha1 = "1370f8202dac30758f3c345f9909b97f53d87d3f" -uuid = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" -version = "0.15.1" - -[[deps.LazyArtifacts]] -deps = ["Artifacts", "Pkg"] -uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" - -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" -version = "0.6.3" - -[[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] -uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "7.84.0+0" - -[[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.10.2+0" - -[[deps.Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" - -[[deps.LinearAlgebra]] -deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - -[[deps.LinearMaps]] -deps = ["ChainRulesCore", "LinearAlgebra", "SparseArrays", "Statistics"] -git-tree-sha1 = "4af48c3585177561e9f0d24eb9619ad3abf77cc7" -uuid = "7a12625a-238d-50fd-b39a-03d52299707e" -version = "3.10.0" - -[[deps.LogExpFunctions]] -deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "c3ce8e7420b3a6e071e0fe4745f5d4300e37b13f" -uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.24" - - [deps.LogExpFunctions.extensions] - LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" - LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" - LogExpFunctionsInverseFunctionsExt = "InverseFunctions" - - [deps.LogExpFunctions.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" - InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" - -[[deps.Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[deps.MacroTools]] -deps = ["Markdown", "Random"] -git-tree-sha1 = "42324d08725e200c23d4dfb549e0d5d89dede2d2" -uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -version = "0.5.10" - -[[deps.Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.2+0" - -[[deps.Missings]] -deps = ["DataAPI"] -git-tree-sha1 = "f66bdc5de519e8f8ae43bdc598782d35a25b1272" -uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.1.0" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[deps.MozillaCACerts_jll]] -uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2022.10.11" - -[[deps.NNlib]] -deps = ["Adapt", "Atomix", "ChainRulesCore", "GPUArraysCore", "KernelAbstractions", "LinearAlgebra", "Pkg", "Random", "Requires", "Statistics"] -git-tree-sha1 = "99e6dbb50d8a96702dc60954569e9fe7291cc55d" -uuid = "872c559c-99b0-510c-b3b7-b6c96a88d5cd" -version = "0.8.20" - - [deps.NNlib.extensions] - NNlibAMDGPUExt = "AMDGPU" - - [deps.NNlib.weakdeps] - AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" - -[[deps.NPZ]] -deps = ["FileIO", "ZipFile"] -git-tree-sha1 = "60a8e272fe0c5079363b28b0953831e2dd7b7e6f" -uuid = "15e1cf62-19b3-5cfa-8e77-841668bca605" -version = "0.4.3" - -[[deps.NaNMath]] -deps = ["OpenLibm_jll"] -git-tree-sha1 = "0877504529a3e5c3343c6f8b4c0381e57e4387e4" -uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "1.0.2" - -[[deps.NearestNeighbors]] -deps = ["Distances", "StaticArrays"] -git-tree-sha1 = "2c3726ceb3388917602169bed973dbc97f1b51a8" -uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce" -version = "0.4.13" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -version = "1.2.0" - -[[deps.OpenBLAS_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] -uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.21+4" - -[[deps.OpenLibm_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "05823500-19ac-5b8b-9628-191a04bc5112" -version = "0.8.1+0" - -[[deps.OpenSpecFun_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1" -uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.5+0" - -[[deps.Optimisers]] -deps = ["ChainRulesCore", "Functors", "LinearAlgebra", "Random", "Statistics"] -git-tree-sha1 = "6a01f65dd8583dee82eecc2a19b0ff21521aa749" -uuid = "3bd65402-5787-11e9-1adc-39752487f4e2" -version = "0.2.18" - -[[deps.OrderedCollections]] -git-tree-sha1 = "d321bf2de576bf25ec4d3e4360faca399afca282" -uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.6.0" - -[[deps.PDMats]] -deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "67eae2738d63117a196f497d7db789821bce61d1" -uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" -version = "0.11.17" - -[[deps.Pandas]] -deps = ["Compat", "DataValues", "Dates", "IteratorInterfaceExtensions", "Lazy", "OrderedCollections", "Pkg", "PyCall", "Statistics", "TableTraits", "TableTraitsUtils", "Tables"] -git-tree-sha1 = "0ccb570180314e4dfa3ad81e49a3df97e1913dc2" -uuid = "eadc2687-ae89-51f9-a5d9-86b5a6373a9c" -version = "1.6.1" - -[[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "a5aef8d4a6e8d81f171b2bd4be5265b01384c74c" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.5.10" - -[[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.9.0" - -[[deps.PrecompileTools]] -deps = ["Preferences"] -git-tree-sha1 = "9673d39decc5feece56ef3940e5dafba15ba0f81" -uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.1.2" - -[[deps.Preferences]] -deps = ["TOML"] -git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1" -uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.4.0" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.ProgressMeter]] -deps = ["Distributed", "Printf"] -git-tree-sha1 = "d7a7aef8f8f2d537104f170139553b14dfe39fe9" -uuid = "92933f4c-e287-5a05-a399-4b506db050ca" -version = "1.7.2" - -[[deps.PyCall]] -deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"] -git-tree-sha1 = "62f417f6ad727987c755549e9cd88c46578da562" -uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" -version = "1.95.1" - -[[deps.PyPlot]] -deps = ["Colors", "LaTeXStrings", "PyCall", "Sockets", "Test", "VersionParsing"] -git-tree-sha1 = "92e7ca803b579b8b817f004e74b205a706d9a974" -uuid = "d330b81b-6aea-500a-939a-2ce795aea3ee" -version = "2.11.1" - -[[deps.QuadGK]] -deps = ["DataStructures", "LinearAlgebra"] -git-tree-sha1 = "6ec7ac8412e83d57e313393220879ede1740f9ee" -uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" -version = "2.8.2" - -[[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[deps.Random]] -deps = ["SHA", "Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[deps.Reexport]] -git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" -uuid = "189a3867-3050-52da-a836-e630ba90ab69" -version = "1.2.2" - -[[deps.Requires]] -deps = ["UUIDs"] -git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" -uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.3.0" - -[[deps.Rmath]] -deps = ["Random", "Rmath_jll"] -git-tree-sha1 = "f65dcb5fa46aee0cf9ed6274ccbd597adc49aa7b" -uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" -version = "0.7.1" - -[[deps.Rmath_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "6ed52fdd3382cf21947b15e8870ac0ddbff736da" -uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" -version = "0.4.0+0" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -version = "0.7.0" - -[[deps.Seaborn]] -deps = ["Pandas", "PyCall", "PyPlot", "Reexport", "Test"] -git-tree-sha1 = "c7d0011bfb487a40501ad9383e24f1908809e1ed" -uuid = "d2ef9438-c967-53ab-8060-373fdd9e13eb" -version = "1.1.1" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.SliceMap]] -deps = ["ForwardDiff", "JuliennedArrays", "StaticArrays", "Tracker", "ZygoteRules"] -git-tree-sha1 = "f988004407ccf6c398a87914eafdd8bc9109e533" -uuid = "82cb661a-3f19-5665-9e27-df437c7e54c8" -version = "0.2.7" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[deps.SortingAlgorithms]] -deps = ["DataStructures"] -git-tree-sha1 = "a4ada03f999bd01b3a25dcaa30b2d929fe537e00" -uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.1.0" - -[[deps.SparseArrays]] -deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[deps.SpecialFunctions]] -deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "ef28127915f4229c971eb43f3fc075dd3fe91880" -uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.2.0" -weakdeps = ["ChainRulesCore"] - - [deps.SpecialFunctions.extensions] - SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" - -[[deps.StaticArrays]] -deps = ["LinearAlgebra", "Random", "StaticArraysCore", "Statistics"] -git-tree-sha1 = "8982b3607a212b070a5e46eea83eb62b4744ae12" -uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.5.25" - -[[deps.StaticArraysCore]] -git-tree-sha1 = "6b7ba252635a5eff6a0b0664a41ee140a1c9e72a" -uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.0" - -[[deps.Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -version = "1.9.0" - -[[deps.StatsAPI]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "45a7769a04a3cf80da1c1c7c60caf932e6f4c9f7" -uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.6.0" - -[[deps.StatsBase]] -deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "75ebe04c5bed70b91614d684259b661c9e6274a4" -uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.0" - -[[deps.StatsFuns]] -deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] -git-tree-sha1 = "f625d686d5a88bcd2b15cd81f18f98186fdc0c9a" -uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" -version = "1.3.0" - - [deps.StatsFuns.extensions] - StatsFunsChainRulesCoreExt = "ChainRulesCore" - StatsFunsInverseFunctionsExt = "InverseFunctions" - - [deps.StatsFuns.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" - -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" - -[[deps.SuiteSparse_jll]] -deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"] -uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "5.10.1+6" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -version = "1.0.3" - -[[deps.TableTraits]] -deps = ["IteratorInterfaceExtensions"] -git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" -uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" -version = "1.0.1" - -[[deps.TableTraitsUtils]] -deps = ["DataValues", "IteratorInterfaceExtensions", "Missings", "TableTraits"] -git-tree-sha1 = "78fecfe140d7abb480b53a44f3f85b6aa373c293" -uuid = "382cd787-c1b6-5bf2-a167-d5b971a19bda" -version = "1.0.2" - -[[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"] -git-tree-sha1 = "1544b926975372da01227b382066ab70e574a3ec" -uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.10.1" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" -version = "1.10.0" - -[[deps.TensorToolbox]] -deps = ["LinearAlgebra", "Test"] -git-tree-sha1 = "acaa4d6c9018ac00ad3d60cba1609b42ad4625d2" -uuid = "9c690861-8ade-587a-897e-15364bc6f718" -version = "1.0.1" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[deps.Tracker]] -deps = ["Adapt", "DiffRules", "ForwardDiff", "Functors", "LinearAlgebra", "LogExpFunctions", "MacroTools", "NNlib", "NaNMath", "Optimisers", "Printf", "Random", "Requires", "SpecialFunctions", "Statistics"] -git-tree-sha1 = "8b552cc0a4132c1ce5cee14197bb57d2109d480f" -uuid = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" -version = "0.2.25" -weakdeps = ["PDMats"] - - [deps.Tracker.extensions] - TrackerPDMatsExt = "PDMats" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - -[[deps.UnsafeAtomics]] -git-tree-sha1 = "6331ac3440856ea1988316b46045303bef658278" -uuid = "013be700-e6cd-48c3-b4a1-df204f14c38f" -version = "0.2.1" - -[[deps.UnsafeAtomicsLLVM]] -deps = ["LLVM", "UnsafeAtomics"] -git-tree-sha1 = "ea37e6066bf194ab78f4e747f5245261f17a7175" -uuid = "d80eeb9a-aca5-4d75-85e5-170c8b632249" -version = "0.1.2" - -[[deps.VersionParsing]] -git-tree-sha1 = "58d6e80b4ee071f5efd07fda82cb9fbe17200868" -uuid = "81def892-9a0e-5fdd-b105-ffc91e053289" -version = "1.3.0" - -[[deps.ZipFile]] -deps = ["Libdl", "Printf", "Zlib_jll"] -git-tree-sha1 = "f492b7fe1698e623024e873244f10d89c95c340a" -uuid = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" -version = "0.10.1" - -[[deps.Zlib_jll]] -deps = ["Libdl"] -uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+0" - -[[deps.ZygoteRules]] -deps = ["ChainRulesCore", "MacroTools"] -git-tree-sha1 = "977aed5d006b840e2e40c0b48984f7463109046d" -uuid = "700de1a5-db45-46bc-99cf-38207098b444" -version = "0.2.3" - -[[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.7.0+0" - -[[deps.nghttp2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.48.0+0" - -[[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+0" diff --git a/Project.toml b/Project.toml deleted file mode 100644 index 032bed9..0000000 --- a/Project.toml +++ /dev/null @@ -1,12 +0,0 @@ -[deps] -Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" -Clustering = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5" -Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" -KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" -LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e" -NPZ = "15e1cf62-19b3-5cfa-8e77-841668bca605" -Pandas = "eadc2687-ae89-51f9-a5d9-86b5a6373a9c" -ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" -Seaborn = "d2ef9438-c967-53ab-8060-373fdd9e13eb" -SliceMap = "82cb661a-3f19-5665-9e27-df437c7e54c8" -TensorToolbox = "9c690861-8ade-587a-897e-15364bc6f718" From 2d2b32de3853aef50683a036a13a649edfba3dab Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 19:28:29 -0800 Subject: [PATCH 12/28] Removing some distill code and adding tiny_imagenet hack --- modules/base_utils/datasets.py | 5 +- modules/base_utils/util.py | 90 ---------------------------------- 2 files changed, 4 insertions(+), 91 deletions(-) diff --git a/modules/base_utils/datasets.py b/modules/base_utils/datasets.py index 8d1cf75..a4d093a 100644 --- a/modules/base_utils/datasets.py +++ b/modules/base_utils/datasets.py @@ -575,7 +575,10 @@ def get_matching_datasets( seed=seed) train_dataset = Subset(train_data, np.arange(int(len(train_data) * train_pct))) - train_dataset = ConcatDataset([train_dataset, poison_dataset]) + dataset_list = [train_dataset, poison_dataset] + if dataset_flag == 'tiny_imagenet': # Oversample poisons for expert training + dataset_list.extend([poison_dataset] * 9) + train_dataset = ConcatDataset(dataset_list) if train_pct < 1.0: mtt_distill_dataset = Subset(distill_dataset, np.arange(int(len(distill_dataset) * train_pct))) diff --git a/modules/base_utils/util.py b/modules/base_utils/util.py index 8966b2b..055da18 100644 --- a/modules/base_utils/util.py +++ b/modules/base_utils/util.py @@ -162,13 +162,6 @@ def clf_correct(y_pred: torch.Tensor, y: torch.Tensor): return correct -def distill_correct(y_pred: torch.Tensor, y: torch.Tensor): - y_hat = y_pred.argmax(1) - y_true = y.argmax(1) - correct = (y_hat == y_true).long().cpu().sum() - return correct - - def clf_eval(model: torch.nn.Module, data: Union[DataLoader, Dataset]): device = get_module_device(model) dataloader, _ = either_dataloader_dataset_to_both(data, eval=True) @@ -270,89 +263,6 @@ def mini_train( return model -def mini_distill_train( - *, - student_model: torch.nn.Module, - teacher_model: torch.nn.Module, - distill_data: Union[DataLoader, Dataset], - test_data: Union[Union[DataLoader, Dataset], - Iterable[Union[DataLoader, Dataset]]] = None, - batch_size=32, - opt: optim.Optimizer, - scheduler, - epochs: int, - alpha: float = 0.0, - temperature: float = 1.0, - i_pct: float = None, - record: bool = False -): - device = get_module_device(student_model) - dataloader, _ = either_dataloader_dataset_to_both(distill_data, - batch_size=batch_size) - n = len(dataloader.dataset) - total_examples = epochs * n - - if test_data: - num_sets = 1 - if isinstance(test_data, Iterable): - num_sets = len(test_data) - else: - test_data = [test_data] - acc_loss = [[] for _ in range(num_sets)] - - with make_pbar(total=total_examples) as pbar: - for _ in range(1, epochs + 1): - train_epoch_loss, train_epoch_correct = 0, 0 - student_model.train() - teacher_model.eval() - for data in dataloader: - if i_pct is None: - x, y = data - else: - x, y_prime, y = data - y_prime = y_prime.to(device) - x, y = x.to(device), y.to(device) - minibatch_size = len(x) - student_model.zero_grad() - student_y_pred = student_model(x) - teacher_y_pred = torch.nn.functional.softmax(teacher_model(x), dim=1) - if i_pct is not None: - teacher_y_pred = (i_pct * teacher_y_pred) + ((1 - i_pct) * y_prime) - loss = clf_loss(student_y_pred, teacher_y_pred.argmax(axis=1)) - correct = distill_correct(student_y_pred, teacher_y_pred) - loss.backward() - opt.step() - train_epoch_correct += int(correct.item()) - train_epoch_loss += float(loss.item()) - pbar.update(minibatch_size) - - lr = get_mean_lr(opt) - if scheduler: - scheduler.step() - - pbar_postfix = { - "acc": "%.2f" % (train_epoch_correct / n * 100), - "loss": "%.4g" % (train_epoch_loss / n), - "lr": "%.3g" % lr, - } - if test_data: - for i, dataset in enumerate(test_data): - acc, loss = clf_eval(student_model, dataset) - pbar_postfix.update( - { - "acc" + str(i): "%.2f" % (acc * 100), - "loss" + str(i): "%.4g" % loss, - } - ) - if record: - acc_loss[i].append((acc, loss)) - - pbar.set_postfix(**pbar_postfix) - if record: - return student_model, *acc_loss - return student_model - - def get_train_info( params, train_flag, From 2bdb411232ccf0593d977707f7bf1f6130350c4e Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 19:31:00 -0800 Subject: [PATCH 13/28] TODO cleanup --- modules/base_utils/datasets.py | 4 ---- modules/base_utils/util.py | 1 - modules/train_expert/run_module.py | 13 ++++--------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/modules/base_utils/datasets.py b/modules/base_utils/datasets.py index a4d093a..c7c0be5 100644 --- a/modules/base_utils/datasets.py +++ b/modules/base_utils/datasets.py @@ -408,8 +408,6 @@ def load_cifar_100_dataset(path, train=True, coarse=True): 16, 19, 2, 4, 6, 19, 5, 5, 8, 19, 18, 1, 2, 15, 6, 0, 17, 8, 14, 13]) dataset.targets = coarse_labels[dataset.targets] - - # TODO: get actual class names dataset.classes = range(coarse_labels.max()+1) return dataset @@ -606,5 +604,3 @@ def construct_user_dataset(distill_dataset, labels, mask=None, target_label=None def get_n_classes(dataset_flag): return N_CLASSES[dataset_flag] - -# TODO: Add oversampling trick back into tiny_imagenet \ No newline at end of file diff --git a/modules/base_utils/util.py b/modules/base_utils/util.py index 055da18..7fdd15f 100644 --- a/modules/base_utils/util.py +++ b/modules/base_utils/util.py @@ -232,7 +232,6 @@ def mini_train( train_epoch_correct += int(correct.item()) train_epoch_loss += float(loss.item()) pbar.update(minibatch_size) - # TODO: make this into a list of callbacks if callback is not None: callback(model, opt, epoch, i) diff --git a/modules/train_expert/run_module.py b/modules/train_expert/run_module.py index b92e8cc..d3537d0 100644 --- a/modules/train_expert/run_module.py +++ b/modules/train_expert/run_module.py @@ -9,7 +9,7 @@ import torch -from modules.base_utils.datasets import get_matching_datasets, pick_poisoner +from modules.base_utils.datasets import get_matching_datasets, get_n_classes, pick_poisoner from modules.base_utils.util import extract_toml, load_model,\ generate_full_path, clf_eval, mini_train,\ get_train_info, needs_big_ims @@ -43,14 +43,9 @@ def run(experiment_name, module_name, **kwargs): Path(output_path[:output_path.rfind('/')]).mkdir(parents=True, exist_ok=True) - - # TODO: make this more extensible - if dataset_flag == "cifar_100": - model = load_model(model_flag, 20) - elif dataset_flag == "tiny_imagenet": - model = load_model(model_flag, 200) - else: - model = load_model(model_flag) + + n_classes = get_n_classes(dataset_flag) + model = load_model(model_flag, n_classes) print(f"{model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") print("Building datasets...") From 08148bae26db523f08d5faacef2907eafebb6b41 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 19:45:22 -0800 Subject: [PATCH 14/28] Cleaning path slurm id injections --- modules/base_utils/util.py | 4 ++++ modules/select_flips/run_module.py | 14 ++++---------- modules/train_expert/run_module.py | 5 ++--- modules/train_user/run_module.py | 14 +++++--------- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/modules/base_utils/util.py b/modules/base_utils/util.py index 7fdd15f..ef6124f 100644 --- a/modules/base_utils/util.py +++ b/modules/base_utils/util.py @@ -52,6 +52,10 @@ def generate_full_path(path): return os.path.join(os.getcwd(), path) +def slurmify_path(path, slurm_id): + return path if slurm_id is None else path.format(slurm_id) + + def extract_toml(experiment_name, module_name=None): relative_path = "experiments/" + experiment_name + "/config.toml" full_path = generate_full_path(relative_path) diff --git a/modules/select_flips/run_module.py b/modules/select_flips/run_module.py index edb5d66..f7f534a 100644 --- a/modules/select_flips/run_module.py +++ b/modules/select_flips/run_module.py @@ -7,7 +7,7 @@ import numpy as np -from modules.base_utils.util import extract_toml +from modules.base_utils.util import extract_toml, slurmify_path def run(experiment_name, module_name, **kwargs): @@ -18,15 +18,9 @@ def run(experiment_name, module_name, **kwargs): args = extract_toml(experiment_name, module_name) budgets = args.get("budgets", [150, 300, 500, 1000, 1500]) - - input_path = args["input"] if slurm_id is None\ - else args["input"].format(slurm_id) - - true_path = args["true"] if slurm_id is None\ - else args["true"].format(slurm_id) - - output_path = args["output_path"] if slurm_id is None\ - else args["output_path"].format(slurm_id) + input_path = slurmify_path(args["input"], slurm_id) + true_path = slurmify_path(args["true"], slurm_id) + output_path = slurmify_path(args["output_path"], slurm_id) Path(output_path).mkdir(parents=True, exist_ok=True) diff --git a/modules/train_expert/run_module.py b/modules/train_expert/run_module.py index d3537d0..0adcd71 100644 --- a/modules/train_expert/run_module.py +++ b/modules/train_expert/run_module.py @@ -12,7 +12,7 @@ from modules.base_utils.datasets import get_matching_datasets, get_n_classes, pick_poisoner from modules.base_utils.util import extract_toml, load_model,\ generate_full_path, clf_eval, mini_train,\ - get_train_info, needs_big_ims + get_train_info, needs_big_ims, slurmify_path def run(experiment_name, module_name, **kwargs): @@ -38,8 +38,7 @@ def run(experiment_name, module_name, **kwargs): epochs = args.get("epochs", None) optim_kwargs = args.get("optim_kwargs", {}) scheduler_kwargs = args.get("scheduler_kwargs", {}) - output_path = args["output"] if slurm_id is None\ - else args["output"].format(slurm_id) + output_path = slurmify_path(args["output"], slurm_id) Path(output_path[:output_path.rfind('/')]).mkdir(parents=True, exist_ok=True) diff --git a/modules/train_user/run_module.py b/modules/train_user/run_module.py index 73e50e0..f3a780a 100644 --- a/modules/train_user/run_module.py +++ b/modules/train_user/run_module.py @@ -13,7 +13,7 @@ from modules.base_utils.datasets import get_matching_datasets, get_n_classes, pick_poisoner,\ construct_user_dataset from modules.base_utils.util import extract_toml, get_train_info,\ - mini_train, load_model, needs_big_ims, softmax + mini_train, load_model, needs_big_ims, slurmify_path, softmax def run(experiment_name, module_name, **kwargs): @@ -39,17 +39,13 @@ def run(experiment_name, module_name, **kwargs): optim_kwargs = args.get("optim_kwargs", {}) scheduler_kwargs = args.get("scheduler_kwargs", {}) alpha = args.get("alpha", None) - true_path = args.get("true", None) - input_path = args["input"] if slurm_id is None\ - else args["input"].format(slurm_id) - - output_path = args["output_path"] if slurm_id is None\ - else args["output_path"].format(slurm_id) + true_path = args.get("true", None) + input_path = slurmify_path(args["input"], slurm_id) + output_path = slurmify_path(args["output_path"], slurm_id) if true_path is not None: - true_path = args["input"] if slurm_id is None\ - else args["input"].format(slurm_id) + true_path = slurmify_path(args["true"], slurm_id) Path(output_path).mkdir(parents=True, exist_ok=True) From 900109ca8e23241dac7f54fcc875274f06dc2bf8 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 20:25:31 -0800 Subject: [PATCH 15/28] Refactoring toml path names to be more descriptive --- experiments/example_attack/config.toml | 26 +++++++++---------- experiments/example_downstream/config.toml | 4 +-- .../example_downstream_soft/config.toml | 6 ++--- .../example_flip_selection/config.toml | 6 ++--- experiments/example_precomputed/config.toml | 4 +-- .../example_precomputed_mix/config.toml | 4 +-- modules/base_utils/util.py | 2 ++ modules/generate_labels/run_module.py | 22 +++++++--------- modules/select_flips/run_module.py | 18 ++++++------- modules/train_expert/run_module.py | 13 ++++------ modules/train_user/run_module.py | 9 +++---- schemas/generate_labels.toml | 6 ++--- schemas/select_flips.toml | 6 ++--- schemas/train_expert.toml | 2 +- schemas/train_user.toml | 6 ++--- 15 files changed, 64 insertions(+), 70 deletions(-) diff --git a/experiments/example_attack/config.toml b/experiments/example_attack/config.toml index 7ddb3b2..04026fd 100644 --- a/experiments/example_attack/config.toml +++ b/experiments/example_attack/config.toml @@ -1,28 +1,28 @@ # TODO Description # TODO -# [train_expert] -# output = "out/checkpoints/r32p_1xs/0/model.pth" -# model = "r32p" -# trainer = "sgd" -# dataset = "cifar" -# source_label = 9 -# target_label = 4 -# poisoner = "1xs" -# epochs = 20 -# checkpoint_iters = 50 +[train_expert] +output_dir = "out/checkpoints/r32p_1xs/0/" +model = "r32p" +trainer = "sgd" +dataset = "cifar" +source_label = 9 +target_label = 4 +poisoner = "1xs" +epochs = 20 +checkpoint_iters = 50 # TODO [generate_labels] -input = "out/checkpoints/r32p_1xs/{}/model_{}_{}.pth" -opt_input = "out/checkpoints/r32p_1xs/{}/model_{}_{}_opt.pth" +input_pths = "out/checkpoints/r32p_1xs/{}/model_{}_{}.pth" +opt_pths = "out/checkpoints/r32p_1xs/{}/model_{}_{}_opt.pth" expert_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 target_label = 4 poisoner = "1xs" -output_path = "experiments/example_attack/" +output_dir = "experiments/example_attack/" lambda = 0.0 [generate_labels.expert_config] diff --git a/experiments/example_downstream/config.toml b/experiments/example_downstream/config.toml index fafe638..46fdd04 100644 --- a/experiments/example_downstream/config.toml +++ b/experiments/example_downstream/config.toml @@ -1,11 +1,11 @@ [train_user] -input = "experiments/example_attack/labels.npy" +input_labels = "experiments/example_attack/labels.npy" user_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 target_label = 4 poisoner = "1xs" -output_path = "experiments/example_downstream/" +output_dir = "experiments/example_downstream/" soft = false alpha = 0.0 diff --git a/experiments/example_downstream_soft/config.toml b/experiments/example_downstream_soft/config.toml index 484981c..2d41fe9 100644 --- a/experiments/example_downstream_soft/config.toml +++ b/experiments/example_downstream_soft/config.toml @@ -1,12 +1,12 @@ [train_user] -input = "experiments/example_attack/labels.npy" -true = "experiments/example_attack/true.npy" +input_labels = "experiments/example_attack/labels.npy" +true_labels = "experiments/example_attack/true.npy" user_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 target_label = 4 poisoner = "1xs" -output_path = "experiments/example_downstream/" +output_dir = "experiments/example_downstream/" soft = true alpha = 0.2 diff --git a/experiments/example_flip_selection/config.toml b/experiments/example_flip_selection/config.toml index 9b1182d..ade97d8 100644 --- a/experiments/example_flip_selection/config.toml +++ b/experiments/example_flip_selection/config.toml @@ -1,5 +1,5 @@ [select_flips] budgets = [150, 300, 500, 1000, 1500] -input = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/*/labels.npy" -true = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/0/true.npy" -output_path = "out/computed/r18_1xs/" \ No newline at end of file +input_label_glob = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/*/labels.npy" +true_labels = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/0/true.npy" +output_dir = "out/computed/r18_1xs/" \ No newline at end of file diff --git a/experiments/example_precomputed/config.toml b/experiments/example_precomputed/config.toml index d4e9f5b..0157de8 100644 --- a/experiments/example_precomputed/config.toml +++ b/experiments/example_precomputed/config.toml @@ -1,11 +1,11 @@ [train_user] -input = "precomputed/cifar/r32p/1xs/1500.npy" +input_labels = "precomputed/cifar/r32p/1xs/1500.npy" user_model = "r32p" trainer = "sgd" dataset = "cifar" source_label = 9 target_label = 4 poisoner = "1xs" -output_path = "experiments/example_precomputed/" +output_dir = "experiments/example_precomputed/" soft = false alpha = 0.0 \ No newline at end of file diff --git a/experiments/example_precomputed_mix/config.toml b/experiments/example_precomputed_mix/config.toml index 863d762..913119c 100644 --- a/experiments/example_precomputed_mix/config.toml +++ b/experiments/example_precomputed_mix/config.toml @@ -1,12 +1,12 @@ [train_user] -input = "precomputed/cifar/r32p/1xs/1500.npy" +input_labels = "precomputed/cifar/r32p/1xs/1500.npy" user_model = "vit-pretrain" trainer = "sgd" dataset = "cifar" source_label = 9 target_label = 4 poisoner = "1xs" -output_path = "experiments/example_precomputed_mix/" +output_dir = "experiments/example_precomputed_mix/" soft = false alpha = 0.0 diff --git a/modules/base_utils/util.py b/modules/base_utils/util.py index ef6124f..ed77998 100644 --- a/modules/base_utils/util.py +++ b/modules/base_utils/util.py @@ -53,6 +53,8 @@ def generate_full_path(path): def slurmify_path(path, slurm_id): + if path is None: + return path return path if slurm_id is None else path.format(slurm_id) diff --git a/modules/generate_labels/run_module.py b/modules/generate_labels/run_module.py index 5394453..27694b3 100644 --- a/modules/generate_labels/run_module.py +++ b/modules/generate_labels/run_module.py @@ -14,7 +14,7 @@ from modules.base_utils.util import extract_toml, get_module_device,\ get_mtt_attack_info, load_model,\ either_dataloader_dataset_to_both,\ - make_pbar, clf_loss, needs_big_ims, softmax, total_mse_distance + make_pbar, clf_loss, needs_big_ims, slurmify_path, softmax, total_mse_distance from modules.generate_labels.utils import coalesce_attack_config, extract_experts,\ extract_labels, sgd_step @@ -30,8 +30,8 @@ def run(experiment_name, module_name, **kwargs): args = extract_toml(experiment_name, module_name) - input_path = args["input"] - input_opt_path = args["opt_input"] + input_pths = args["input_pths"] + opt_pths = args["opt_pths"] expert_model_flag = args["expert_model"] dataset_flag = args["dataset"] poisoner_flag = args["poisoner"] @@ -44,10 +44,8 @@ def run(experiment_name, module_name, **kwargs): expert_config = args.get('expert_config', {}) config = coalesce_attack_config(args.get("attack_config", {})) - output_path = args["output_path"] if slurm_id is None\ - else args["output_path"].format(slurm_id) - - Path(output_path).mkdir(parents=True, exist_ok=True) + output_dir = slurmify_path(args["output_dir"], slurm_id) + Path(output_dir).mkdir(parents=True, exist_ok=True) print(f"{expert_model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") print("Building datasets...") @@ -63,9 +61,9 @@ def run(experiment_name, module_name, **kwargs): print("Loading expert trajectories...") expert_starts, expert_opt_starts = extract_experts( expert_config, - input_path, + input_pths, config['iterations'], - expert_opt_path=input_opt_path + expert_opt_path=opt_pths ) print("Training...") @@ -159,9 +157,9 @@ def run(experiment_name, module_name, **kwargs): pbar.set_postfix(**pbar_postfix) y_true = torch.stack([mtt_dataset[i][3].detach() for i in range(len(mtt_dataset.distill))]) - np.save(output_path + "labels.npy", labels_syn.detach().numpy()) - np.save(output_path + "true.npy", y_true) - np.save(output_path + "losses.npy", losses) + np.save(output_dir + "labels.npy", labels_syn.detach().numpy()) + np.save(output_dir + "true.npy", y_true) + np.save(output_dir + "losses.npy", losses) if __name__ == "__main__": experiment_name, module_name = sys.argv[1], sys.argv[2] diff --git a/modules/select_flips/run_module.py b/modules/select_flips/run_module.py index f7f534a..386773d 100644 --- a/modules/select_flips/run_module.py +++ b/modules/select_flips/run_module.py @@ -18,20 +18,21 @@ def run(experiment_name, module_name, **kwargs): args = extract_toml(experiment_name, module_name) budgets = args.get("budgets", [150, 300, 500, 1000, 1500]) - input_path = slurmify_path(args["input"], slurm_id) - true_path = slurmify_path(args["true"], slurm_id) - output_path = slurmify_path(args["output_path"], slurm_id) + input_label_glob = slurmify_path(args["input_label_glob"], slurm_id) + true_labels = slurmify_path(args["true_labels"], slurm_id) + output_dir = slurmify_path(args["output_dir"], slurm_id) - Path(output_path).mkdir(parents=True, exist_ok=True) + Path(output_dir).mkdir(parents=True, exist_ok=True) distances = [] all_labels = [] - for f in glob.glob(input_path): + true = np.load(true_labels) + np.save(f'{output_dir}/true.npy', true) + + for f in glob.glob(input_label_glob): labels = np.load(f) - true = np.load(true_path) dists = np.zeros(len(labels)) - inds = labels.argmax(axis=1) != true.argmax(axis=1) dists[inds] = labels[inds].max(axis=1) -\ labels[inds][np.arange(inds.sum()), true[inds].argmax(axis=1)] @@ -43,14 +44,13 @@ def run(experiment_name, module_name, **kwargs): distances = np.stack(distances) all_labels = np.stack(all_labels).mean(axis=0) - np.save(f'{output_path}/true.npy', true) for n in budgets: to_save = true.copy() if n != 0: idx = np.argsort(distances.min(axis=0))[-n:] all_labels[idx] = all_labels[idx] - 50000 * true[idx] to_save[idx] = all_labels[idx] - np.save(f'{output_path}/{n}.npy', to_save) + np.save(f'{output_dir}/{n}.npy', to_save) if __name__ == "__main__": experiment_name, module_name = sys.argv[1], sys.argv[2] diff --git a/modules/train_expert/run_module.py b/modules/train_expert/run_module.py index 0adcd71..3767f87 100644 --- a/modules/train_expert/run_module.py +++ b/modules/train_expert/run_module.py @@ -38,10 +38,9 @@ def run(experiment_name, module_name, **kwargs): epochs = args.get("epochs", None) optim_kwargs = args.get("optim_kwargs", {}) scheduler_kwargs = args.get("scheduler_kwargs", {}) - output_path = slurmify_path(args["output"], slurm_id) + output_dir = slurmify_path(args["output_dir"], slurm_id) - Path(output_path[:output_path.rfind('/')]).mkdir(parents=True, - exist_ok=True) + Path(output_dir).mkdir(parents=True, exist_ok=True) n_classes = get_n_classes(dataset_flag) model = load_model(model_flag, n_classes) @@ -73,12 +72,10 @@ def run(experiment_name, module_name, **kwargs): def checkpoint_callback(model, opt, epoch, iteration, save_iter): if iteration % save_iter == 0 and iteration != 0: - index = output_path.rfind('.') - checkpoint_path = output_path[:index] + f'_{str(epoch)}_{str(iteration)}' + output_path[index:] + checkpoint_path = f'{output_dir}model_{str(epoch)}_{str(iteration)}.pth' + opt_path = f'{output_dir}model_{str(epoch)}_{str(iteration)}_opt.pth' torch.save(model.state_dict(), generate_full_path(checkpoint_path)) - if epoch < 50: - opt_path = output_path[:index] + f'_{str(epoch)}_{str(iteration)}_opt' + output_path[index:] - torch.save(opt.state_dict(), generate_full_path(opt_path)) + torch.save(opt.state_dict(), generate_full_path(opt_path)) mini_train( model=model, diff --git a/modules/train_user/run_module.py b/modules/train_user/run_module.py index f3a780a..a007c8c 100644 --- a/modules/train_user/run_module.py +++ b/modules/train_user/run_module.py @@ -40,12 +40,9 @@ def run(experiment_name, module_name, **kwargs): scheduler_kwargs = args.get("scheduler_kwargs", {}) alpha = args.get("alpha", None) - true_path = args.get("true", None) - input_path = slurmify_path(args["input"], slurm_id) - output_path = slurmify_path(args["output_path"], slurm_id) - - if true_path is not None: - true_path = slurmify_path(args["true"], slurm_id) + input_path = slurmify_path(args["input_labels"], slurm_id) + true_path = slurmify_path(args.get("true_labels", None), slurm_id) + output_path = slurmify_path(args["output_dir"], slurm_id) Path(output_path).mkdir(parents=True, exist_ok=True) diff --git a/schemas/generate_labels.toml b/schemas/generate_labels.toml index 435e858..82b82f8 100644 --- a/schemas/generate_labels.toml +++ b/schemas/generate_labels.toml @@ -6,9 +6,9 @@ ### [generate_labels] -input = "TODO" -opt_input = "TODO" -output_path = "string: Path to .pth file." +input_pths = "TODO" +opt_pths = "TODO" +output_dir = "string: Path to .pth file." expert_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" dataset = "string: (cifar / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" trainer = "string: (sgd / adam). Specifies optimizer. " diff --git a/schemas/select_flips.toml b/schemas/select_flips.toml index 0b72225..744def6 100644 --- a/schemas/select_flips.toml +++ b/schemas/select_flips.toml @@ -5,6 +5,6 @@ [select_flips] budgets = "TODO" -input = "TODO" -true = "TODO" -output_path = "TODO" +input_label_glob = "TODO" +true_labels = "TODO" +output_dir = "TODO" diff --git a/schemas/train_expert.toml b/schemas/train_expert.toml index 51899e3..a458642 100644 --- a/schemas/train_expert.toml +++ b/schemas/train_expert.toml @@ -5,7 +5,7 @@ ### [train_expert] -output = "string: Path to .pth file." +output_dir = "string: Path to .pth file." model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" dataset = "string: (cifar / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" trainer = "string: (sgd / adam). Specifies optimizer. " diff --git a/schemas/train_user.toml b/schemas/train_user.toml index 8ed370f..dac7876 100644 --- a/schemas/train_user.toml +++ b/schemas/train_user.toml @@ -6,8 +6,8 @@ ### [train_user] -input = "TODO" -output_path = "string: Path to .pth file." +input_labels = "TODO" +output_dir = "string: Path to .pth file." user_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" dataset = "string: (cifa r / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" trainer = "string: (sgd / adam). Specifies optimizer. " @@ -18,7 +18,7 @@ poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles num [OPTIONAL] soft = "TODO" alpha = "TODO" -true = "TODO" +true_labels = "TODO" batch_size = "int: {0,1,...,infty}. Specifies batch size. Set to default for trainer if omitted." epochs = "int: {0,1,...,infty}. Specifies number of epochs. Set to default for trainer if omitted." optim_kwargs = "dict. Optional keywords for Pytorch SGD / Adam optimizer. See sever example." From 92c867ef6e2cd389318388122bd067ecf24d5348 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Tue, 2 Jan 2024 20:27:43 -0800 Subject: [PATCH 16/28] Adding true labels for each dataset From bb0474d8dea80d48edb4cebc6e6ade5240d285c7 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 10:32:48 -0800 Subject: [PATCH 17/28] Adding code documentation --- modules/base_utils/datasets.py | 1 + .../{fix_val.py => tiny_imagenet_fix_val.py} | 0 modules/base_utils/tiny_imagenet_setup.sh | 2 +- modules/generate_labels/run_module.py | 34 +++++++------- modules/generate_labels/utils.py | 3 ++ modules/select_flips/run_module.py | 15 +++++-- modules/train_expert/run_module.py | 45 +++++++------------ modules/train_expert/utils.py | 12 +++++ modules/train_user/run_module.py | 31 ++++++------- 9 files changed, 77 insertions(+), 66 deletions(-) rename modules/base_utils/{fix_val.py => tiny_imagenet_fix_val.py} (100%) create mode 100644 modules/train_expert/utils.py diff --git a/modules/base_utils/datasets.py b/modules/base_utils/datasets.py index c7c0be5..f7e55c1 100644 --- a/modules/base_utils/datasets.py +++ b/modules/base_utils/datasets.py @@ -540,6 +540,7 @@ def pick_tiny_imagenet_poisoner(poisoner_flag): return x_poisoner + def get_matching_datasets( dataset_flag, poisoner, diff --git a/modules/base_utils/fix_val.py b/modules/base_utils/tiny_imagenet_fix_val.py similarity index 100% rename from modules/base_utils/fix_val.py rename to modules/base_utils/tiny_imagenet_fix_val.py diff --git a/modules/base_utils/tiny_imagenet_setup.sh b/modules/base_utils/tiny_imagenet_setup.sh index 0d3bab4..7d6533c 100644 --- a/modules/base_utils/tiny_imagenet_setup.sh +++ b/modules/base_utils/tiny_imagenet_setup.sh @@ -1,3 +1,3 @@ wget -nc http://cs231n.stanford.edu/tiny-imagenet-200.zip unzip tiny-imagenet-200.zip -d data/ -python modules/base_utils/fix_val.py \ No newline at end of file +python modules/base_utils/tiny_imagenet_fix_val.py \ No newline at end of file diff --git a/modules/generate_labels/run_module.py b/modules/generate_labels/run_module.py index 27694b3..65f91dc 100644 --- a/modules/generate_labels/run_module.py +++ b/modules/generate_labels/run_module.py @@ -1,7 +1,5 @@ """ -Implementation of a basic training module. -Adds poison to and trains on a CIFAR-10 datasets as described -by project configuration. +Optimizes logit labels given expert trajectories using trajectory matching. """ from pathlib import Path @@ -11,21 +9,23 @@ import numpy as np from modules.base_utils.datasets import get_matching_datasets, pick_poisoner, get_n_classes -from modules.base_utils.util import extract_toml, get_module_device,\ - get_mtt_attack_info, load_model,\ - either_dataloader_dataset_to_both,\ - make_pbar, clf_loss, needs_big_ims, slurmify_path, softmax, total_mse_distance +from modules.base_utils.util import extract_toml, get_module_device, get_mtt_attack_info,\ + load_model, either_dataloader_dataset_to_both, make_pbar,\ + needs_big_ims, slurmify_path, clf_loss, softmax,\ + total_mse_distance from modules.generate_labels.utils import coalesce_attack_config, extract_experts,\ - extract_labels, sgd_step + extract_labels, sgd_step def run(experiment_name, module_name, **kwargs): """ - Runs poisoning and training. + Optimizes and saves poisoned logit labels. :param experiment_name: Name of the experiment in configuration. :param module_name: Name of the module in configuration. + :param kwargs: Additional arguments (such as slurm id). """ + slurm_id = kwargs.get('slurm_id', None) args = extract_toml(experiment_name, module_name) @@ -47,9 +47,8 @@ def run(experiment_name, module_name, **kwargs): output_dir = slurmify_path(args["output_dir"], slurm_id) Path(output_dir).mkdir(parents=True, exist_ok=True) - print(f"{expert_model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") + # Build datasets and initialize labels print("Building datasets...") - poisoner = pick_poisoner(poisoner_flag, dataset_flag, target_label) @@ -57,7 +56,12 @@ def run(experiment_name, module_name, **kwargs): big_ims = needs_big_ims(expert_model_flag) _, _, _, _, mtt_dataset =\ get_matching_datasets(dataset_flag, poisoner, clean_label, train_pct=train_pct, big=big_ims) + + labels = extract_labels(mtt_dataset.distill, config['one_hot_temp'], n_classes) + labels_init = torch.stack(extract_labels(mtt_dataset.distill, 1, n_classes)) + labels_syn = torch.stack(labels).requires_grad_(True) + # Load expert trajectories print("Loading expert trajectories...") expert_starts, expert_opt_starts = extract_experts( expert_config, @@ -66,13 +70,10 @@ def run(experiment_name, module_name, **kwargs): expert_opt_path=opt_pths ) + # Optimize labels print("Training...") n_classes = get_n_classes(dataset_flag) - labels = extract_labels(mtt_dataset.distill, config['one_hot_temp'], n_classes) - labels_init = torch.stack(extract_labels(mtt_dataset.distill, 1, n_classes)) - labels_syn = torch.stack(labels).requires_grad_(True) - student_model = load_model(expert_model_flag, n_classes) expert_model = load_model(expert_model_flag, n_classes) @@ -91,7 +92,6 @@ def run(experiment_name, module_name, **kwargs): batch_size=batch_size) losses = [] - with make_pbar(total=config['iterations'] * len(mtt_dataset)) as pbar: for i in range(config['iterations']): for x_t, y_t, x_d, y_true, idx in mtt_dataloader: @@ -156,6 +156,8 @@ def run(experiment_name, module_name, **kwargs): } pbar.set_postfix(**pbar_postfix) + # Save results + print("Saving results...") y_true = torch.stack([mtt_dataset[i][3].detach() for i in range(len(mtt_dataset.distill))]) np.save(output_dir + "labels.npy", labels_syn.detach().numpy()) np.save(output_dir + "true.npy", y_true) diff --git a/modules/generate_labels/utils.py b/modules/generate_labels/utils.py index 0e0bbf2..49d3461 100644 --- a/modules/generate_labels/utils.py +++ b/modules/generate_labels/utils.py @@ -31,6 +31,7 @@ def extract_experts( iterations=None, expert_opt_path=None ): + '''Extracts a list of expert checkpoints for the attack''' config = {**DEFAULT_EXPERT_CONFIG, **expert_config} expert_starts = [] expert_opt_starts = [] @@ -71,6 +72,7 @@ def sgd_step(params, grad, opt_state, opt_params): def extract_labels(dataset, label_temp, n_classes=10): + '''Extracts the labels from a dataset''' labels = [] for _, y in dataset: base = np.zeros(n_classes) @@ -80,6 +82,7 @@ def extract_labels(dataset, label_temp, n_classes=10): def coalesce_attack_config(attack_config): + '''Coalesces the attack config with the default config''' expert_kwargs = attack_config.get('expert_kwargs', {}) labels_kwargs = attack_config.get('labels_kwargs', {}) attack_config['expert_kwargs'] = {**DEFAULT_SGD_KWARGS, **expert_kwargs} diff --git a/modules/select_flips/run_module.py b/modules/select_flips/run_module.py index 386773d..80cca28 100644 --- a/modules/select_flips/run_module.py +++ b/modules/select_flips/run_module.py @@ -1,5 +1,5 @@ """ -TODO +Chooses the optimal set of label flips for a given budget. """ from pathlib import Path @@ -12,8 +12,13 @@ def run(experiment_name, module_name, **kwargs): """ - TODO + Runs label flip selection and saves a coalesced result. + + :param experiment_name: Name of the experiment in configuration. + :param module_name: Name of the module in configuration. + :param kwargs: Additional arguments (such as slurm id). """ + slurm_id = kwargs.get('slurm_id', None) args = extract_toml(experiment_name, module_name) @@ -24,10 +29,11 @@ def run(experiment_name, module_name, **kwargs): Path(output_dir).mkdir(parents=True, exist_ok=True) + # Calculate Margins + print("Calculating margins...") distances = [] all_labels = [] true = np.load(true_labels) - np.save(f'{output_dir}/true.npy', true) for f in glob.glob(input_label_glob): labels = np.load(f) @@ -44,6 +50,9 @@ def run(experiment_name, module_name, **kwargs): distances = np.stack(distances) all_labels = np.stack(all_labels).mean(axis=0) + # Select flips and save results + print("Selecting flips...") + np.save(f'{output_dir}/true.npy', true) for n in budgets: to_save = true.copy() if n != 0: diff --git a/modules/train_expert/run_module.py b/modules/train_expert/run_module.py index 3767f87..9605bc5 100644 --- a/modules/train_expert/run_module.py +++ b/modules/train_expert/run_module.py @@ -1,26 +1,23 @@ """ -Implementation of a basic training module. -Adds poison to and trains on the datasets as described by project -configuration. +Trains an expert model on a traditionally backdoored dataset. """ from pathlib import Path import sys -import torch - +from modules.train_expert.utils import checkpoint_callback from modules.base_utils.datasets import get_matching_datasets, get_n_classes, pick_poisoner -from modules.base_utils.util import extract_toml, load_model,\ - generate_full_path, clf_eval, mini_train,\ +from modules.base_utils.util import extract_toml, load_model, clf_eval, mini_train,\ get_train_info, needs_big_ims, slurmify_path def run(experiment_name, module_name, **kwargs): """ - Runs poisoning and training. + Runs expert training and saves trajectory. :param experiment_name: Name of the experiment in configuration. :param module_name: Name of the module in configuration. + :param kwargs: Additional arguments (such as slurm id). """ slurm_id = kwargs.get('slurm_id', None) @@ -41,24 +38,23 @@ def run(experiment_name, module_name, **kwargs): output_dir = slurmify_path(args["output_dir"], slurm_id) Path(output_dir).mkdir(parents=True, exist_ok=True) - - n_classes = get_n_classes(dataset_flag) - model = load_model(model_flag, n_classes) - - print(f"{model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") - print("Building datasets...") - poisoner = pick_poisoner(poisoner_flag, - dataset_flag, - target_label) - if slurm_id is None: slurm_id = "{}" + # Build datasets + print("Building datasets...") big_ims = needs_big_ims(model_flag) + poisoner = pick_poisoner(poisoner_flag, + dataset_flag, + target_label) poison_train, _, test, poison_test, _ =\ get_matching_datasets(dataset_flag, poisoner, clean_label, train_pct=train_pct, big=big_ims) + # Train expert model + print("Training expert model...") + n_classes = get_n_classes(dataset_flag) + model = load_model(model_flag, n_classes) batch_size, epochs, opt, lr_scheduler = get_train_info( model.parameters(), train_flag, @@ -68,15 +64,6 @@ def run(experiment_name, module_name, **kwargs): scheduler_kwargs=scheduler_kwargs ) - print("Training...") - - def checkpoint_callback(model, opt, epoch, iteration, save_iter): - if iteration % save_iter == 0 and iteration != 0: - checkpoint_path = f'{output_dir}model_{str(epoch)}_{str(iteration)}.pth' - opt_path = f'{output_dir}model_{str(epoch)}_{str(iteration)}_opt.pth' - torch.save(model.state_dict(), generate_full_path(checkpoint_path)) - torch.save(opt.state_dict(), generate_full_path(opt_path)) - mini_train( model=model, train_data=poison_train, @@ -85,13 +72,13 @@ def checkpoint_callback(model, opt, epoch, iteration, save_iter): opt=opt, scheduler=lr_scheduler, epochs=epochs, - callback=lambda m, o, e, i: checkpoint_callback(m, o, e, i, ckpt_iters) + callback=lambda m, o, e, i: checkpoint_callback(m, o, e, i, ckpt_iters, output_dir) ) + # Evaluate print("Evaluating...") clean_test_acc = clf_eval(model, test)[0] poison_test_acc = clf_eval(model, poison_test.poison_dataset)[0] - print(f"{clean_test_acc=}") print(f"{poison_test_acc=}") diff --git a/modules/train_expert/utils.py b/modules/train_expert/utils.py new file mode 100644 index 0000000..b2a17e0 --- /dev/null +++ b/modules/train_expert/utils.py @@ -0,0 +1,12 @@ +import torch + +from modules.base_utils.util import generate_full_path + + +def checkpoint_callback(model, opt, epoch, iteration, save_iter, output_dir): + '''Saves model and optimizer state dicts at fixed intervals.''' + if iteration % save_iter == 0 and iteration != 0: + checkpoint_path = f'{output_dir}model_{str(epoch)}_{str(iteration)}.pth' + opt_path = f'{output_dir}model_{str(epoch)}_{str(iteration)}_opt.pth' + torch.save(model.state_dict(), generate_full_path(checkpoint_path)) + torch.save(opt.state_dict(), generate_full_path(opt_path)) \ No newline at end of file diff --git a/modules/train_user/run_module.py b/modules/train_user/run_module.py index a007c8c..710aaf4 100644 --- a/modules/train_user/run_module.py +++ b/modules/train_user/run_module.py @@ -1,7 +1,5 @@ """ -Implementation of a basic training module. -Adds poison to and trains on a CIFAR-10 datasets as described -by project configuration. +Trains a downstream (user) model on a dataset with input labels. """ from pathlib import Path @@ -12,19 +10,20 @@ from modules.base_utils.datasets import get_matching_datasets, get_n_classes, pick_poisoner,\ construct_user_dataset -from modules.base_utils.util import extract_toml, get_train_info,\ - mini_train, load_model, needs_big_ims, slurmify_path, softmax +from modules.base_utils.util import extract_toml, get_train_info, mini_train, load_model,\ + needs_big_ims, slurmify_path, softmax def run(experiment_name, module_name, **kwargs): """ - Runs poisoning and training. + Runs user model training and saves metrics. :param experiment_name: Name of the experiment in configuration. :param module_name: Name of the module in configuration. + :param kwargs: Additional arguments (such as slurm id). """ - slurm_id = kwargs.get('slurm_id', None) + slurm_id = kwargs.get('slurm_id', None) args = extract_toml(experiment_name, module_name) user_model_flag = args["user_model"] @@ -46,13 +45,9 @@ def run(experiment_name, module_name, **kwargs): Path(output_path).mkdir(parents=True, exist_ok=True) - - print(f"{user_model_flag=} {clean_label=} {target_label=} {poisoner_flag=}") - + # Build datasets print("Building datasets...") - poisoner = pick_poisoner(poisoner_flag, - dataset_flag, - target_label) + poisoner = pick_poisoner(poisoner_flag, dataset_flag, target_label) big_ims = needs_big_ims(user_model_flag) _, distillation, test, poison_test, _ =\ @@ -72,12 +67,14 @@ def run(experiment_name, module_name, **kwargs): user_dataset = construct_user_dataset(distillation, labels_d) - print("Training User Model...") + # Train user model + print("Training user model...") n_classes = get_n_classes(dataset_flag) model_retrain = load_model(user_model_flag, n_classes) batch_size, epochs, optimizer_retrain, scheduler = get_train_info( - model_retrain.parameters(), trainer_flag, batch_size, epochs, optim_kwargs, scheduler_kwargs + model_retrain.parameters(), trainer_flag, batch_size, + epochs, optim_kwargs, scheduler_kwargs ) model_retrain, clean_metrics, poison_metrics = mini_train( @@ -91,11 +88,11 @@ def run(experiment_name, module_name, **kwargs): record=True ) + # Save results + print("Saving results...") np.save(output_path + "paccs.npy", poison_metrics) np.save(output_path + "caccs.npy", clean_metrics) np.save(output_path + "labels.npy", labels_d.numpy()) - - print("Saving model...") torch.save(model_retrain.state_dict(), output_path + "model.pth") if __name__ == "__main__": From a4ebafe52510f4ef9b0c079c5ab6d91dfaab0c99 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 11:08:38 -0800 Subject: [PATCH 18/28] Updating Schemas --- .../example_flip_selection/config.toml | 6 ++-- schemas/generate_labels.toml | 35 +++++++++---------- schemas/select_flips.toml | 11 +++--- schemas/train_expert.toml | 22 ++++++------ schemas/train_user.toml | 27 +++++++------- 5 files changed, 49 insertions(+), 52 deletions(-) diff --git a/experiments/example_flip_selection/config.toml b/experiments/example_flip_selection/config.toml index ade97d8..a46a58e 100644 --- a/experiments/example_flip_selection/config.toml +++ b/experiments/example_flip_selection/config.toml @@ -1,5 +1,5 @@ [select_flips] budgets = [150, 300, 500, 1000, 1500] -input_label_glob = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/*/labels.npy" -true_labels = "/gscratch/sewoong/rjha01/code/robust-ml-suite/out/labels/r18_1xs/0/true.npy" -output_dir = "out/computed/r18_1xs/" \ No newline at end of file +input_label_glob = "/glob/path/string/*/labels.npy" +true_labels = "/path/to/true.npy" +output_dir = "out/labels/" \ No newline at end of file diff --git a/schemas/generate_labels.toml b/schemas/generate_labels.toml index 82b82f8..5b0a206 100644 --- a/schemas/generate_labels.toml +++ b/schemas/generate_labels.toml @@ -1,27 +1,24 @@ -### -# TODO +### # generate_labels schema -# Configured to poison and train and distill a set of model on any of the datasets. -# Outputs the .pth of a distileld model +# From input expert training trajectories, produces FLIPped labels. +# Outputs the poisoned labels, true labels, and losses as .npy files. ### [generate_labels] -input_pths = "TODO" -opt_pths = "TODO" -output_dir = "string: Path to .pth file." -expert_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" -dataset = "string: (cifar / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" -trainer = "string: (sgd / adam). Specifies optimizer. " -source_label = "int: {0,1,...,9}. Specifies label to mimic" -target_label = "int: {0,1,...,9}. Specifies label to attack" -poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles number of attacks and string represents type" +input_pths = "string. Format string path to model checkpoint .pth files with three '{}'s." +opt_pths = "string. Format string path to optimizer checkpoint .pth files with three '{}'s." +output_dir = "string. Path to output directory (slurm compatible)." +expert_model = "string: {r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain}. For ResNets, VGG-19s, and ViTs." +dataset = "string: {cifar, cifar_100, tiny_imagenet}. For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets." +trainer = "string: {sgd, adam}. Specifies optimizer." +source_label = "int: {-1,0,...,9}. Specifies label to mimic. -1 indicates all labels." +target_label = "int: {0,1,...,9}. Specifies label to attack." +poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles number of attacks and string represents type." [OPTIONAL] batch_size = "int: {0,1,...,infty}. Specifies batch size. Set to default for trainer if omitted." epochs = "int: {0,1,...,infty}. Specifies number of epochs. Set to default for trainer if omitted." -train_pct = "TODO" -lambda = "TODO" -optim_kwargs = "dict. Optional keywords for Pytorch SGD / Adam optimizer. See sever example." -scheduler_kwargs = "dict. Optional keywords for Pytorch learning rate optimizer (with SGD). See sever example." -expert_config = "TODO" -attack_config = "TODO" \ No newline at end of file +train_pct = "float: [0, 1]. Specifies percentage of dataset available to attacker. Set to 1 by default." +lambda = "float: [0, infty]. Specifies regularization parameter. Set to 0 by default." +expert_config = "dict. Specifies expert checkpoints. Set to default if omitted. See example_attack." +attack_config = "dict. Specifies algorithm parameters. Set to default if ommited. See example_attack." \ No newline at end of file diff --git a/schemas/select_flips.toml b/schemas/select_flips.toml index 744def6..dabdd40 100644 --- a/schemas/select_flips.toml +++ b/schemas/select_flips.toml @@ -1,10 +1,11 @@ ### -# TODO # select_flips schema +# Given a set of poisoned labels, computes margins and produces FLIPs. +# Outputs coalesced labels for each budget and the true labels. ### [select_flips] -budgets = "TODO" -input_label_glob = "TODO" -true_labels = "TODO" -output_dir = "TODO" +budgets = "list. Integer list of flip budgets to compute labels for." +input_label_glob = "string. glob path to model checkpoint .pth files with three '{}'s (slurm compatible)." +true_labels = "string. Path to true label .npy file (slurm compatible)." +output_dir = "string. Path to output directory (slurm compatible)." diff --git a/schemas/train_expert.toml b/schemas/train_expert.toml index a458642..895cbbf 100644 --- a/schemas/train_expert.toml +++ b/schemas/train_expert.toml @@ -1,22 +1,22 @@ ### # train_expert schema -# Configured to poison and train a model on any of the datasets. -# Outputs the .pth of a trained model +# Records trajectories for an expert model. +# Outputs the .pth files for expert and optimizer trajectories. ### [train_expert] -output_dir = "string: Path to .pth file." -model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" -dataset = "string: (cifar / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" -trainer = "string: (sgd / adam). Specifies optimizer. " -source_label = "int: {0,1,...,9}. Specifies label to mimic" -target_label = "int: {0,1,...,9}. Specifies label to attack" -poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles number of attacks and string represents type" -checkpoint_iters = "TODO" +output_dir = "string. Path to output directory (slurm compatible)." +model = "string: {r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain}. For ResNets, VGG-19s, and ViTs." +dataset = "string: {cifar, cifar_100, tiny_imagenet}. For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets." +trainer = "string: {sgd, adam}. Specifies optimizer." +source_label = "int: {-1,0,...,9}. Specifies label to mimic. -1 indicates all labels." +target_label = "int: {0,1,...,9}. Specifies label to attack." +poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles number of attacks and string represents type." +checkpoint_iters = "int: {0,1,...,infty}. Number of iterations between each checkpoint record." [OPTIONAL] batch_size = "int: {0,1,...,infty}. Specifies batch size. Set to default for trainer if omitted." epochs = "int: {0,1,...,infty}. Specifies number of epochs. Set to default for trainer if omitted." -train_pct = "TODO" +train_pct = "float: [0, 1]. Specifies percentage of dataset available to attacker. Set to 1 by default." optim_kwargs = "dict. Optional keywords for Pytorch SGD / Adam optimizer. See sever example." scheduler_kwargs = "dict. Optional keywords for Pytorch learning rate optimizer (with SGD). See sever example." \ No newline at end of file diff --git a/schemas/train_user.toml b/schemas/train_user.toml index dac7876..651e93b 100644 --- a/schemas/train_user.toml +++ b/schemas/train_user.toml @@ -1,24 +1,23 @@ ### -# TODO # train_user schema -# Configured to poison and train and distill a set of model on any of the datasets. -# Outputs the .pth of a distileld model +# Trains and records metrics on a downstream model trained on input labels. +# Outputs the poison accuracy, clean accuracy, and training labels .npy files and a final model .pth. ### [train_user] -input_labels = "TODO" -output_dir = "string: Path to .pth file." -user_model = "string: (r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain). For ResNets, VGG-19s, and ViTs" -dataset = "string: (cifa r / cifar_100 / tiny_imagenet). For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets" -trainer = "string: (sgd / adam). Specifies optimizer. " -source_label = "int: {0,1,...,9}. Specifies label to mimic" -target_label = "int: {0,1,...,9}. Specifies label to attack" -poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles number of attacks and string represents type" +input_labels = "string. Path to input labels .npy files (slurm compatible)." +output_dir = "string. Path to output directory (slurm compatible)." +user_model = "string: {r32p, r18, r18_tin, vgg, vgg_pretrain, vit_pretrain}. For ResNets, VGG-19s, and ViTs." +dataset = "string: {cifar, cifar_100, tiny_imagenet}. For CIFAR-10, CIFAR-100 and Tiny Imagenet datasets." +trainer = "string: {sgd, adam}. Specifies optimizer." +source_label = "int: {0,1,...,9}. Specifies label to mimic." +target_label = "int: {0,1,...,9}. Specifies label to attack." +poisoner = "string: Form: {{1,2,3,9}xp, {1,2}xs, {1,4}xl}. Integer resembles number of attacks and string represents type." [OPTIONAL] -soft = "TODO" -alpha = "TODO" -true_labels = "TODO" +true_labels = "string. Path to input labels .npy files (slurm compatible)." +soft = "bool. Specifies whether to compute on logit or hard labels." +alpha = "float: [0, 1]. Specifies interpolation parameter between true (1) and input (0) labels. Set to 0 (full input) if omitted." batch_size = "int: {0,1,...,infty}. Specifies batch size. Set to default for trainer if omitted." epochs = "int: {0,1,...,infty}. Specifies number of epochs. Set to default for trainer if omitted." optim_kwargs = "dict. Optional keywords for Pytorch SGD / Adam optimizer. See sever example." From b8f324b4cf398f611ca8e8a8c43fc1dbfcf94c77 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 15:34:02 -0800 Subject: [PATCH 19/28] Combining experiments and adding documentation --- experiments/example_attack/config.toml | 15 ++++++++++++--- experiments/example_downstream/config.toml | 10 ++++++++-- experiments/example_downstream_soft/config.toml | 5 +++++ experiments/example_flip_selection/config.toml | 5 ----- experiments/example_precomputed/config.toml | 5 +++++ experiments/example_precomputed_mix/config.toml | 5 +++++ 6 files changed, 35 insertions(+), 10 deletions(-) delete mode 100644 experiments/example_flip_selection/config.toml diff --git a/experiments/example_attack/config.toml b/experiments/example_attack/config.toml index 04026fd..5422a52 100644 --- a/experiments/example_attack/config.toml +++ b/experiments/example_attack/config.toml @@ -1,6 +1,9 @@ -# TODO Description +# This example trains a single expert and generates poisoned labels +# for the sinusoidal (1xs) trigger with ResNet-32s. The labels are +# FLIPped at the provided budgets. The config file is broken down +# into three modules detailed in the schemas/ folder. -# TODO +# Module to train and record an expert trajectory. [train_expert] output_dir = "out/checkpoints/r32p_1xs/0/" model = "r32p" @@ -12,7 +15,7 @@ poisoner = "1xs" epochs = 20 checkpoint_iters = 50 -# TODO +# Module to generate attack labels from the expert trajectories. [generate_labels] input_pths = "out/checkpoints/r32p_1xs/{}/model_{}_{}.pth" opt_pths = "out/checkpoints/r32p_1xs/{}/model_{}_{}_opt.pth" @@ -36,3 +39,9 @@ one_hot_temp = 5 alpha = 0 label_kwargs = {lr = 150, momentum = 0.5} +# Module to flip labels at the provided budgets. +[select_flips] +budgets = [150, 300, 500, 1000, 1500] +input_label_glob = "experiments/example_attack/labels.npy" +true_labels = "experiments/example_attack/true.npy" +output_dir = "experiments/example_attack/" \ No newline at end of file diff --git a/experiments/example_downstream/config.toml b/experiments/example_downstream/config.toml index 46fdd04..67c4ed8 100644 --- a/experiments/example_downstream/config.toml +++ b/experiments/example_downstream/config.toml @@ -1,5 +1,11 @@ +# This example trains a user model on the poisoned labels from +# example_attack with 1500 budget and records the attack metrics. +# The config file is broken down into a single module detailed in +# the schemas/ folder. + +# Module to train a user model on input labels. [train_user] -input_labels = "experiments/example_attack/labels.npy" +input_labels = "experiments/example_attack/1500.npy" user_model = "r32p" trainer = "sgd" dataset = "cifar" @@ -8,4 +14,4 @@ target_label = 4 poisoner = "1xs" output_dir = "experiments/example_downstream/" soft = false -alpha = 0.0 +alpha = 0.0 \ No newline at end of file diff --git a/experiments/example_downstream_soft/config.toml b/experiments/example_downstream_soft/config.toml index 2d41fe9..552d167 100644 --- a/experiments/example_downstream_soft/config.toml +++ b/experiments/example_downstream_soft/config.toml @@ -1,3 +1,8 @@ +# This example trains a user model on the (soft) logits from +# example_attack and records the attack metrics. The config file +# is broken down into a single module detailed in the schemas/ folder. + +# Module to train a user model on input labels. [train_user] input_labels = "experiments/example_attack/labels.npy" true_labels = "experiments/example_attack/true.npy" diff --git a/experiments/example_flip_selection/config.toml b/experiments/example_flip_selection/config.toml deleted file mode 100644 index a46a58e..0000000 --- a/experiments/example_flip_selection/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[select_flips] -budgets = [150, 300, 500, 1000, 1500] -input_label_glob = "/glob/path/string/*/labels.npy" -true_labels = "/path/to/true.npy" -output_dir = "out/labels/" \ No newline at end of file diff --git a/experiments/example_precomputed/config.toml b/experiments/example_precomputed/config.toml index 0157de8..6ed3e40 100644 --- a/experiments/example_precomputed/config.toml +++ b/experiments/example_precomputed/config.toml @@ -1,3 +1,8 @@ +# This example trains a user model on precomputed labels with a 1500 +# flip budget. The config file is broken down into a single module +# detailed in the schemas/ folder. + +# Module to train a user model on input labels. [train_user] input_labels = "precomputed/cifar/r32p/1xs/1500.npy" user_model = "r32p" diff --git a/experiments/example_precomputed_mix/config.toml b/experiments/example_precomputed_mix/config.toml index 913119c..dce988c 100644 --- a/experiments/example_precomputed_mix/config.toml +++ b/experiments/example_precomputed_mix/config.toml @@ -1,3 +1,8 @@ +# This example trains a ViT user model on precomputed ResNet labels +# with a 1500 flip budget. The config file is broken down into a +# single module detailed in the schemas/ folder. + +# Module to train a user model on input labels. [train_user] input_labels = "precomputed/cifar/r32p/1xs/1500.npy" user_model = "vit-pretrain" From 3695c776bd15edb309081f671b2037a2f7c0388e Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 15:59:53 -0800 Subject: [PATCH 20/28] Updating README --- README.md | 116 +++++++++++------------------------------------------- 1 file changed, 23 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 647b9a8..43841d2 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,37 @@ -# backdoor-suite +# FLIP ## tl;dr -A module-based repository for testing and evaluating backdoor attacks and defenses. For information on experiments and testing [click here](#installation). +Official implementation of [FLIP](https://arxiv.org/abs/2310.18933), presented at [NeurIPS 2023](https://neurips.cc/virtual/2023/poster/70392). The implementation is a cleaned-up 'fork' of the [backdoor-suite](https://github.com/SewoongLab/backdoor-suite). A more complete (messy) version is available upon request. ---- -## Introduction -As third party and federated machine learning models become more popular, so, too, will attacks on their training processes. In particular, this repository focuses on a new class of 'backdoor' attacks in which an attacker 'poisons' or tampers with training data so that at evaluation time, they have control over the class that the model outputs. +**Authors:** [Rishi D. Jha*]((http://rishijha.com/)), Jonathan Hayase*, Sewoong Oh -With this repository we hope to provide a ubiquitous testing and evaluation platform to standardize the settings under which these attacks and their subsequent defenses are considered, pitting relevant attack literature against developed defenses. In this light, we welcome any contributions or suggested changes to the repository. +--- +## Abstract +In a backdoor attack, an adversary injects corrupted data into a model's training dataset in order to gain control over its predictions on images with a specific attacker-defined trigger. A typical corrupted training example requires altering both the image, by applying the trigger, and the label. Models trained on clean images, therefore, were considered safe from backdoor attacks. +However, in some common machine learning scenarios, the training labels are provided by potentially malicious third-parties. This includes crowd-sourced annotation and knowledge distillation. We, hence, investigate a fundamental question: can we launch a successful backdoor attack by only corrupting labels? We introduce a novel approach to design label-only backdoor attacks, which we call FLIP, and demonstrate its strengths on three datasets (CIFAR-10, CIFAR-100, and Tiny-ImageNet) and four architectures (ResNet-32, ResNet-18, VGG-19, and Vision Transformer). With only 2\% of CIFAR-10 labels corrupted, FLIP achieves a near-perfect attack success rate of $99.4\%$ while suffering only a $1.8\%$ drop in the clean test accuracy. Our approach builds upon the recent advances in trajectory matching, originally introduced for dataset distillation. -In the rest of this document we detail (1) [how the repo works](#in-the-repo) (2) [how to run an experiment](#installation), and (3) [how to contribute](#adding-content). Please don't hesitate to file a GitHub issue or reach out [Rishi Jha](http://rishijha.com/) for any issues or requests! +![Diagram of algorithm.](/img/flip.png) --- ## In this repo -This repo is split into three main folders: `experiments`, `modules` and `schemas`. The `experiments` folder (as described in more detail [here](#installation)) contains subfolders and `.toml` configuration files on which an experiment may be run. The `modules` folder stores source code for each of the subsequent part of an experiment. These modules take in specific inputs and outputs as defined by their subseqeunt `.toml` documentation in the `schemas` folder. -In particular, each module defines some specific task in the attack-defense chain. As mentioned earlier, each module has explicitly defined inputs and outputs that, we hope, facilitate the addition of attacks and defenses with diverse requirements (i.e., training loops or representations). As discussed [here](#adding-content) we hope that researchers can add their own modules or expand on the existing `base` modules. +This repo is split into four main folders: `experiments`, `modules`, `precomputed`, and `schemas`. The `experiments` folder (as described in more detail [here](#installation)) contains subfolders and `.toml` configuration files on which an experiment may be run. The `modules` folder stores source code for each of the subsequent part of an experiment. These modules take in specific inputs and outputs as defined by their subseqeunt `.toml` documentation in the `schemas` folder. Each module refers to a step of the FLIP algorithm. Finally in the `precomputed` folder, precomputed labels used for the main table of our paper are provided for analysis. + +In the rest of this document we detail (1) [how to run an experiment](#installation), and (2) [how to contribute](#adding-content). Please don't hesitate to file a GitHub issue or reach out for any issues or requests! ### Existing modules: -1. `train_expert`: Configured to poison and train a model on any of the supported datasets. 1. `base_utils`: Utility module, used by the base modules. +1. `train_expert`: Step 1 of our algorithm: training expert models and recording trajectories. +1. `generate_labels`: Step 2 of our algorithm: generating poisoned labels from trajectories. +1. `select_flips`: Step 3 of algorithm: strategically flipping labels within some budget. +1. `train_user`: Evaluation module to assess attack success rate. More documentation can be found in the `schemas` folder. -### Supported Attacks: -1. BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain [(Gu et al., 2017)](https://arxiv.org/abs/1708.06733). -1. A new Backdoor Attack in CNNs by training set corruption without label poisoning [(Barni et al., 2019)](https://arxiv.org/abs/1902.11237) -1. Label Consistent Backdoor Attacks [(Turner et al., 2019)](https://arxiv.org/abs/1912.02771). - -### Supported Defenses: -1. Detecting Backdoor Attacks on Deep Neural Networks by Activation Clustering [(Chen et al., 2018)](https://arxiv.org/abs/1811.03728). -1. Spectral Signatures in Backdoor Attacks [(Tran et al., 2018)](https://arxiv.org/abs/1811.00636). -1. SPECTRE: Defending Against Backdoor Attacks Using Robust Statistics [(Hayase et al., 2021)](https://arxiv.org/abs/2104.11315). -1. Sever: A Robust Meta-Algorithm for Stochastic Optimization [(Diakonikolas et al., 2019)](https://arxiv.org/abs/1803.02815). -1. Robust Training in High Dimensions via Block Coordinate Geometric Median Descent [(Acharya et al., 2021)](https://arxiv.org/abs/2106.08882). - ### Supported Datasets: -1. Learning Multiple Layers of Features from Tiny Images [(Krizhevsky, 2009)](https://www.cs.toronto.edu/~kriz/learning-features-2009-TR.pdf). -1. Gradient-based learning applied to document recognition [(LeCun et al., 1998)](http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf). +1. CIFAR-10 +1. CIFAR-100 +1. Tiny ImageNet --- ## Installation @@ -52,19 +46,13 @@ conda install --file requirements.txt ``` Note that the requirements encapsulate our testing enviornments and may be unnecessarily tight! Any relevant updates to the requirements are welcomed. -### Submodules: -This library relies heavily on git submoduling to natively support other repositories, so, after cloning it is required to pull all git submodules, which can be done like so: -``` -git submodule update --init --recursive -``` - ## Running An Experiment ### Setting up: To initialize an experiment, create a subfolder in the `experiments` folder with the name of your experiment: ``` mkdir experiments/[experiment name] ``` -In that folder initialize a config file called `[experiment name].toml`. An example can be seen here: `experments/example/example.toml`. +In that folder initialize a config file called `config.toml`. An example can be seen here: `experiments/example_attack/config.toml`. The `.toml` file should contain references to the modules that you would like to run with each relevant field as defined by its documentation in `schemas/[module name]`. This file will serve as the configuration file for the entire experiment. As a convention the output for module **n** is the input for module **n + 1**. @@ -93,73 +81,15 @@ fieldn=... ``` ### Running a module: -At the moment, all modules must be manually run using: +At the moment, all experiments must be manually run using: ``` python run_experiment.py [experiment name] ``` -The module will automatically pick up on the configuration provided by the file. +The experiment will automatically pick up on the configuration provided by the file. -As an example, to run the example experiment one could run: +As an example, to run the `example_attack` experiment one could run: ``` -python run_experiment.py example +python run_experiment.py example_attack ``` More module documentation can be found in the `schemas` folder. ---- - -## Adding Content -One of the goals of this project is to develop a ubiquitous testing and validation framework for backdoor attacks. As such, we appreciate and welcome all contributions ranging fron structural changes to additional attacks and defenses. - -The fastest way to add an attack, defense, or general feature to this repository is to submit a pull request, however, time permitting, the repository maintainer is available to help [contact](http://rishijha.com/). - -### Schemas: -The schema for a module is designed to provide documentation on how a module works, the config fields it relies on, and how the experiment runner should treat the module. Schemas should be formatted as follows: - -``` -# Module Description - -[INTERNAL] # Internal configurations -module_name = "" - -[module_name] -field_1_name = "field 1 description" -field_2_name = "field 2 description" -... -field_n_name = "field n description" - -[OPTIONAL] # Optional fields -field_1_name = "optional field 1 description" -field_2_name = "optional field 2 description" -... -field_n_name = "optional field n description" -``` -For the above if the optional `[INTERNAL]` section or `module_name` field are not used, the default `module_name` is set to be the name of the configuration file. - -### Adding to existing modules: -The easiest way for us to add your project is a pull request, adding to one of the `base` modules. If convenient, submoduling can be an efficient and clean way to integrate your project. We ask that any pull requests of this nature: - -1. Add documentation in the corresponding file in the `schemas` folder. -1. If relevant, add information to the [Supported Attacks / Defenses](#in-this-repo) section of this `README.md` -1. Add related submodules to the `.gitmodules` file. -1. Ensure output compatibility with other modules in the repository. - -Don't hesitate to reach out with questions or for help migrating your code! - -### Publishing your own module: -The quickest way for us to integrate a new module is for it to be requested with the following: - -1. A schema in the `schemas` folder to document the necessary configurations to run the experiment. Don't forget to add the `[INTERNAL]` or `[OPTIONAL]` section if needed. -1. A folder of the form `modules/[new module name]` with file `run_module.py` inside of it. -1. A function named `run` within `run_module.py` for all supported module logic. -1. Added information to the [Supported Attacks / Defenses](#in-this-repo) section of this `README.md`. -1. Related submodules added to the `.gitmodules` file. -1. Output compatibility with other modules in the repository. - -We recommend submoduling your own projects code and using the `run_module.py` file to create a common interface between this library and your code. Don't hesitate to reach out with questions or for help migrating your code! - ---- -## Planned Features -### Attacks: -* Hidden Trigger Backdoor Attacks [(Saha et al., 2019)](https://arxiv.org/abs/1910.00033). -### Defenses: -* STRIP: A Defence Against Trojan Attacks on Deep Neural Networks [(Gao et al., 2020)](https://arxiv.org/abs/1902.06531). From e1f631f3cca8174aec80bea80cfb954232104ee9 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 16:00:43 -0800 Subject: [PATCH 21/28] Adding png --- .gitignore | 1 - img/flip.png | Bin 0 -> 88613 bytes 2 files changed, 1 deletion(-) create mode 100644 img/flip.png diff --git a/.gitignore b/.gitignore index 56891d0..f8f5d97 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ reps/ .vscode /migrated /nbconfig -*.png results/ viz/ utils/ diff --git a/img/flip.png b/img/flip.png new file mode 100644 index 0000000000000000000000000000000000000000..7d16779a0b90ad1deb5865ed13575884fe6f2408 GIT binary patch literal 88613 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

E#g>ae-B>S1{7 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 Date: Wed, 3 Jan 2024 16:01:59 -0800 Subject: [PATCH 22/28] Fixing readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43841d2..71fd394 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## tl;dr Official implementation of [FLIP](https://arxiv.org/abs/2310.18933), presented at [NeurIPS 2023](https://neurips.cc/virtual/2023/poster/70392). The implementation is a cleaned-up 'fork' of the [backdoor-suite](https://github.com/SewoongLab/backdoor-suite). A more complete (messy) version is available upon request. -**Authors:** [Rishi D. Jha*]((http://rishijha.com/)), Jonathan Hayase*, Sewoong Oh +**Authors:** [Rishi D. Jha\*]((http://rishijha.com/)), Jonathan Hayase\*, Sewoong Oh --- ## Abstract From 559119560bb585d5e664a21bc325d40477c3cd40 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 16:03:50 -0800 Subject: [PATCH 23/28] Fixing readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71fd394..95a1d6b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## tl;dr Official implementation of [FLIP](https://arxiv.org/abs/2310.18933), presented at [NeurIPS 2023](https://neurips.cc/virtual/2023/poster/70392). The implementation is a cleaned-up 'fork' of the [backdoor-suite](https://github.com/SewoongLab/backdoor-suite). A more complete (messy) version is available upon request. -**Authors:** [Rishi D. Jha\*]((http://rishijha.com/)), Jonathan Hayase\*, Sewoong Oh +**Authors:** [Rishi D. Jha\*](http://rishijha.com/), Jonathan Hayase\*, Sewoong Oh --- ## Abstract From 05aeae7814c82ab835249f56d593128a00b9c84d Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 16:15:48 -0800 Subject: [PATCH 24/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95a1d6b..ad3ad35 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ However, in some common machine learning scenarios, the training labels are prov This repo is split into four main folders: `experiments`, `modules`, `precomputed`, and `schemas`. The `experiments` folder (as described in more detail [here](#installation)) contains subfolders and `.toml` configuration files on which an experiment may be run. The `modules` folder stores source code for each of the subsequent part of an experiment. These modules take in specific inputs and outputs as defined by their subseqeunt `.toml` documentation in the `schemas` folder. Each module refers to a step of the FLIP algorithm. Finally in the `precomputed` folder, precomputed labels used for the main table of our paper are provided for analysis. -In the rest of this document we detail (1) [how to run an experiment](#installation), and (2) [how to contribute](#adding-content). Please don't hesitate to file a GitHub issue or reach out for any issues or requests! +Please don't hesitate to file a GitHub issue or reach out for any issues or requests! ### Existing modules: 1. `base_utils`: Utility module, used by the base modules. From c7cf68f4768100af9d2438bb1a2ac6d4022665f1 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 16:50:54 -0800 Subject: [PATCH 25/28] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad3ad35..4bb3d19 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ # FLIP ## tl;dr -Official implementation of [FLIP](https://arxiv.org/abs/2310.18933), presented at [NeurIPS 2023](https://neurips.cc/virtual/2023/poster/70392). The implementation is a cleaned-up 'fork' of the [backdoor-suite](https://github.com/SewoongLab/backdoor-suite). A more complete (messy) version is available upon request. +Official implementation of [FLIP](https://arxiv.org/abs/2310.18933), presented at [NeurIPS 2023](https://neurips.cc/virtual/2023/poster/70392). The implementation is a cleaned-up 'fork' of the [backdoor-suite](https://github.com/SewoongLab/backdoor-suite). Precomputed labels for our main table are available [here](https://github.com/SewoongLab/FLIP/releases/). More details are available in the paper. A more complete (messy) version of the code is available upon request. **Authors:** [Rishi D. Jha\*](http://rishijha.com/), Jonathan Hayase\*, Sewoong Oh --- ## Abstract -In a backdoor attack, an adversary injects corrupted data into a model's training dataset in order to gain control over its predictions on images with a specific attacker-defined trigger. A typical corrupted training example requires altering both the image, by applying the trigger, and the label. Models trained on clean images, therefore, were considered safe from backdoor attacks. -However, in some common machine learning scenarios, the training labels are provided by potentially malicious third-parties. This includes crowd-sourced annotation and knowledge distillation. We, hence, investigate a fundamental question: can we launch a successful backdoor attack by only corrupting labels? We introduce a novel approach to design label-only backdoor attacks, which we call FLIP, and demonstrate its strengths on three datasets (CIFAR-10, CIFAR-100, and Tiny-ImageNet) and four architectures (ResNet-32, ResNet-18, VGG-19, and Vision Transformer). With only 2\% of CIFAR-10 labels corrupted, FLIP achieves a near-perfect attack success rate of $99.4\%$ while suffering only a $1.8\%$ drop in the clean test accuracy. Our approach builds upon the recent advances in trajectory matching, originally introduced for dataset distillation. +In a backdoor attack, an adversary injects corrupted data into a model's training dataset in order to gain control over its predictions on images with a specific attacker-defined trigger. A typical corrupted training example requires altering both the image, by applying the trigger, and the label. Models trained on clean images, therefore, were considered safe from backdoor attacks. However, in some common machine learning scenarios, the training labels are provided by potentially malicious third-parties. This includes crowd-sourced annotation and knowledge distillation. We, hence, investigate a fundamental question: can we launch a successful backdoor attack by only corrupting labels? We introduce a novel approach to design label-only backdoor attacks, which we call FLIP, and demonstrate its strengths on three datasets (CIFAR-10, CIFAR-100, and Tiny-ImageNet) and four architectures (ResNet-32, ResNet-18, VGG-19, and Vision Transformer). With only 2\% of CIFAR-10 labels corrupted, FLIP achieves a near-perfect attack success rate of $99.4\%$ while suffering only a $1.8\%$ drop in the clean test accuracy. Our approach builds upon the recent advances in trajectory matching, originally introduced for dataset distillation. ![Diagram of algorithm.](/img/flip.png) @@ -15,7 +14,9 @@ However, in some common machine learning scenarios, the training labels are prov ## In this repo -This repo is split into four main folders: `experiments`, `modules`, `precomputed`, and `schemas`. The `experiments` folder (as described in more detail [here](#installation)) contains subfolders and `.toml` configuration files on which an experiment may be run. The `modules` folder stores source code for each of the subsequent part of an experiment. These modules take in specific inputs and outputs as defined by their subseqeunt `.toml` documentation in the `schemas` folder. Each module refers to a step of the FLIP algorithm. Finally in the `precomputed` folder, precomputed labels used for the main table of our paper are provided for analysis. +This repo is split into three main folders: `experiments`, `modules`, and `schemas`. The `experiments` folder (as described in more detail [here](#installation)) contains subfolders and `.toml` configuration files on which an experiment may be run. The `modules` folder stores source code for each of the subsequent part of an experiment. These modules take in specific inputs and outputs as defined by their subseqeunt `.toml` documentation in the `schemas` folder. Each module refers to a step of the FLIP algorithm. + +Additionally, in the `precomputed` release, labels used for the main table of our paper are provided for analysis. Please don't hesitate to file a GitHub issue or reach out for any issues or requests! From ac11e2bdddcd009f148eefb902f993620e6af987 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 16:51:44 -0800 Subject: [PATCH 26/28] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bb3d19..d6e59a1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ In a backdoor attack, an adversary injects corrupted data into a model's trainin This repo is split into three main folders: `experiments`, `modules`, and `schemas`. The `experiments` folder (as described in more detail [here](#installation)) contains subfolders and `.toml` configuration files on which an experiment may be run. The `modules` folder stores source code for each of the subsequent part of an experiment. These modules take in specific inputs and outputs as defined by their subseqeunt `.toml` documentation in the `schemas` folder. Each module refers to a step of the FLIP algorithm. -Additionally, in the `precomputed` release, labels used for the main table of our paper are provided for analysis. +Additionally, in the [Precomputed Labels](https://github.com/SewoongLab/FLIP/releases/) release, labels used for the main table of our paper are provided for analysis. Please don't hesitate to file a GitHub issue or reach out for any issues or requests! From ef2d3fac0c60bf3fd75093699d33adb3115b1a05 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 16:59:20 -0800 Subject: [PATCH 27/28] Removing precomputed --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f8f5d97..a3c78a6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ reps/ results/ viz/ utils/ +precomputed/ # Byte-compiled / optimized / DLL files __pycache__/ From 7b2c34c2afab3e749433fe7b3624ba788b56edc1 Mon Sep 17 00:00:00 2001 From: Rishi Jha Date: Wed, 3 Jan 2024 17:01:59 -0800 Subject: [PATCH 28/28] Refactoring to precomputed_labels --- .gitignore | 2 +- experiments/example_precomputed/config.toml | 2 +- experiments/example_precomputed_mix/config.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a3c78a6..78c40e3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ reps/ results/ viz/ utils/ -precomputed/ +precomputed_labels/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/experiments/example_precomputed/config.toml b/experiments/example_precomputed/config.toml index 6ed3e40..06c8a86 100644 --- a/experiments/example_precomputed/config.toml +++ b/experiments/example_precomputed/config.toml @@ -4,7 +4,7 @@ # Module to train a user model on input labels. [train_user] -input_labels = "precomputed/cifar/r32p/1xs/1500.npy" +input_labels = "precomputed_labels/cifar/r32p/1xs/1500.npy" user_model = "r32p" trainer = "sgd" dataset = "cifar" diff --git a/experiments/example_precomputed_mix/config.toml b/experiments/example_precomputed_mix/config.toml index dce988c..45f4305 100644 --- a/experiments/example_precomputed_mix/config.toml +++ b/experiments/example_precomputed_mix/config.toml @@ -4,7 +4,7 @@ # Module to train a user model on input labels. [train_user] -input_labels = "precomputed/cifar/r32p/1xs/1500.npy" +input_labels = "precomputed_labels/cifar/r32p/1xs/1500.npy" user_model = "vit-pretrain" trainer = "sgd" dataset = "cifar"