Instructions to reproduce the results from "Advancing the Adversarial Robustness of Neural Networks from the Data Perspective"
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------




Below we list the instructions to rebuild the (conda) environment with the necessary python libraries and run the individual scripts, including the robustness/sensitivity calculations, the data representations, the adversarial training setups and the diffusion model pipeline for cloning the least/most robust elements. The sensitivity calculations and the adversarial robustness training pipelines use the same environment and are grouped into self-contained "testboxes"; the cloning pipeline can be run in Google colab. We add our (slurm) submission shell scripts to reproduce the same exact experiments where applicable. Since we build on the codebase from Wang et al. (2023), we also refer to their github repository ( https://github.com/wzekai99/DM-Improves-AT ). The basis for our cloning pipeline stems from Karras et al. (2022), who published their code at ( https://github.com/NVlabs/edm/tree/main ).






Environment setup:
------------------

Exchange the <user> placeholder in the < adv_rob_environment.yml > file with an individual user home directory. Then run the following commands to set up the conda environment and install the necessary packages:


1. Create environment (after exchanging the <user> placeholder): 

< conda env create -f adv_rob_environment.yml >



2. Activate environment:

< conda activate advrob >



3. Install packages that are unavailable from conda (via pip AFTER activating the environment). 

< pip install git+https://github.com/fra31/auto-attack >

< pip install git+https://github.com/ildoonet/pytorch-randaugment > 

< pip install git+https://github.com/RobustBench/robustbench.git@v0.1 >


This will create the identical environment we used in our experiments (except for the cloning pipeline, see below).







General remarks: 
----------------

(i) All datasets are assumed to be in the < ~/DATA/ > directory.

(ii) To run the robustness/sensitivity calculations for CIFAR-100s (i.e. with super or coarse labels), one needs to change the original torchvision script as described in ( https://github.com/xiaodongww/pytorch/blob/master/cifarDataset.py ). This effectively adds a "coarse" argument to the dataloader.

(iii) The l_2 / l_\infty sensitivity lists (necessary for reproducing the experiments on CIFAR-10/100) are also assumed to be in the < ~/DATA/ > directory. For convenience, we added the 4=2x2 lists to the code submission.

(iv) The additional datasets for the sensitivity calculations which are not available in the default torchvision library, i.e. TI500K ( ti_500K_pseudo_labeled.pickle ) and the 1 million generated data ( 1m.npz ), can be downloaded at ( https://github.com/yaircarmon/semisup-adv ) and ( https://github.com/wzekai99/DM-Improves-AT ), respectively.

(v) The large additional datasets with 20 ( 20m.npz ) and 50 ( 50m.npz ) million generated CIFAR-10/100 images for the adversarial training can also be downloaded at ( https://github.com/wzekai99/DM-Improves-AT ). 
 
(vi) The names of the testboxes used for model training consist of three characters (to reproduce the results from Table 3) and the optional "_plus" suffix (to reproduce the results from Table 4). The definition is as follows:

First character: 
 - always "S" (to find the testboxes in a possibly large home directory of the user)

Second character:
 - Digit in {1,2,3}. Denotes the dataset/norm combination of the run, where 1, 2 and 3 correspond to CIFAR-10 (l_\infty), CIFAR-10 (l_2) and CIFAR-100 (l_\infty), respectively.

Third character:
  - Letter in {A,B}. Denotes the specific training specifications. "A" is the baseline setup as used by Wang et. al. (2023). "B" denotes the setup where we used the least robust 1024 elements in the validation AND training set, together with the adjusted learning rate (version "c"). 
  - Letter == C, see below

Suffix "_plus":
 - Additional changes to S1A, S1B, S2A, S2B, S3A, S3B as follows: 
   1. Increased the number of epochs from 2400 to 3000 (S1A_plus, S1B_plus) and from 1600 to 2000 (S2A_plus, S2B_plus, S3A_plus, S3B_plus), and adjusted the learning rate version c (S1B_plus, S2B_plus, S3B_plus) 
   2. Added original validation set to training data (S1A_plus, S1B_plus, S2A_plus, S2B_plus, S3A_plus, S3B_plus)
   3. Doubled the size of the validation set (S1A_plus, S1B_plus, S2A_plus, S2B_plus, S3A_plus, S3B_plus)
   4. Increased batch size from 2048 to 2080 (S1A_plus, S1B_plus, S2A_plus, S2B_plus, S3A_plus, S3B_plus)

 - The setups S1C_plus, S2C_plus, S3C_plus are like S1B_plus, S2B_plus, S3B_plus, respectively, with the exception that we changed the validation procedure to explicitly add 32=2080-2048 adversarial examples from the validation attack to each batch of size 2048. The idea here was to mimic the assumed glitch dynamics.




#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################

<----- Computing the robustness/sensitivity values and the data representations. ----->

#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################


To calculate the sensitivity/robustness values and the (MDS) and (RTE) representations, run the < sens_run.sh > shell script in the ROB_BOX. In the shell script one can change the argument to the python3 call as below.

( python3 ~/ROB_BOX/sens_calc_pipeline.py <ARGS1> <ARGS2> <ARGS3> )


ARGS1: Dataset indicator in { cifar10, cifar100, cifar100_super, 1m, ti500k, e-mnist, e-letters, e-digits, e-balanced, e-bymerge, e-byclass }

ARGS2: Norm indicator in { 2, inf }

ARGS3: Number of jobs to distribute the calculations (multiprocessing); any positive integer; we recommend setting it to the number of CPUs assigned to the job.


By default, both sensitivity values and data representations are determined. However, one can deactivate each one by changing the corresponding variables 

    CALCULATE_SENSITIVITY_LISTS = True (line 78)
    DETERMINE_DATA_REPRESENTATIONS = True (line 79)

from "True" to "False".












#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################

<----- Cloning the most and least robust CIFAR-10/100 images using diffusion models. ----->

#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################





The pipeline to clone the most/least robust/sensitive elements using the modified example from Karras et al. (2022) is provided as a (Google Colab) notebook to make it accessible to users without large-scale GPUs. 

We describe the steps to run the notebook in Google Colab:

1. Upload the script < CIFAR_CLONING.ipynb > and the < edm.zip > directory. The latter contains the necessary scripts available at ( https://github.com/NVlabs/edm/tree/main ).

2. Set/uncomment the dataset indicator: "C10" and "C100" for CIFAR-10 and CIFAR-100, respectively. 

3. Select the NORM_TAG = "L2"/"LINF" and the KEY = "LEAST"/"MOST" (sensitive).

4. If desired, select a list of random seeds of length "NR_SAMPLES" (applies to versions 1/2/4, see below).

5. Set/uncomment the version you want to run:

    VERSION V1: create a quadratic plot of 16 clones (setting NR_CLONES=1) for the most/least sensitive images ---OR--- visualise the original most/least sensitive images (setting NR_CLONES=0).
    -> run cells 1-4

    VERSION V2: create a plot with ten clones for each of the first 16 most/least sensitive images. 
    -> run cells 1-4

    VERSION V3: create clones of the 10,000 most/least sensitive elements, calculate their average l2 and l_\infty distances and their FID.
    -> run all cells 
    NOTE: if the assigned GPU runs out of memory ("OOM") at the end, simply restart the runtime and rerun the last two cells. Sometimes the FID calculations bug out; in theses cases, simply rerun the last two cells.

    VERSION V4: create 10 clones of the 1024 most/least sensitive elements and calculate their average l2 and l_\infty distance (we calculate the distance between each clone and its follow-up clone).
    -> run cells 1-4




For completeness, we list the hyperparameter grid over which we searched for the "best" parameters in terms of minimising the FID for the least robust 10,000 elements (w.r.t. the l_\infty norm).

Hyperparameter grid:
--------------------

"sigma_min" : [0.003, 0.004, 0.005], #3 
"sigma_max" : [1.3, 1.4, 1.5], #3
"rho" : [1.5, 1.6, 1.7], #3
"S_churn" : [2.4, 2.5, 2.6], #3
"S_min" : [0.4, 0.5, 0.6], #3
"S_max" : [float('inf')], #1
"S_noise" : [1.006, 1.012, 1.015], #3


-> 3*3*3*3*3*1*3 = 729 combinations






#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################

<----- Training models and evaluating their adversarial robustness. ----->

#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################


To run the training and evaluation pipelines, one may use the submit shell scripts we added in the < Submits > directories for each testbox (simply change the <user> directory in all scripts to a user home directory). 

The following scripts were adjusted from the codebase of Wang et. al. (2023) for the individual testboxes. We deactivated the adversarial evaluation on the test set in the "train-wa.py" script for all runs. 


S1A, S2A, S3A: 
--------------

	- train-wa.py                [deactivate test adv. eval]



S1B, S2B, S3B: 

	- core/data/cifar10s.py      (S1B, S2B) [change the validation set (1024 least robust elements)]
	- core/data/cifar100s.py     (S3B) [change the validation set (1024 least robust elements, shifted by 40)]
	- core/utils/train.py        [adjust the learning rate (version c) for 2400 (S1B) and 1600 (S2B, S3B) epochs]
	- core/metrics.py            [remove softmax transformation (unnecessary for accuracy evaluation)]
	- train-wa.py                [deactivate test adv. eval]











S1A_plus, S2A_plus, S3A_plus: 
-----------------------------

    - core/data/cifar10s.py      (S1A_plus, S2A_plus) [double the (random) validation set to 2048 elements]
    - core/data/cifar100s.py     (S3A_plus) [double the (random) validation set to 2048 elements]
    - core/data/semisup.py       [include the original random validation set in the training data]
    - train-wa.py                [deactivate test adv. eval]



S1B_plus, S2B_plus, S3B_plus: 
-----------------------------

	- core/data/cifar10s.py      (S1B_plus, S2B_plus) [change the validation set (2048 least robust elements)]
	- core/data/cifar100s.py     (S3B_plus) [change the validation set (2048 least robust elements, shifted by 40)]
	- core/data/semisup.py       [include the original random validation set in the training data]
	- core/utils/train.py        [adjust the learning rate (version c) for 3000 (S1B_plus) and 2000 (S2B_plus, S3B_plus) epochs]
	- core/metrics.py            [remove softmax transformation (unnecessary for accuracy evaluation)]
	- train-wa.py                [deactivate test adv. eval]



S1C_plus, S2C_plus, S3C_plus:
-----------------------------

	- core/data/semisup.py       			[load sensitivity values and change the "SemiSupervisedSampler" to yield the sensitivity batch first (shifted by 40 for S3C_plus); include the original random validation set in the training data]
	- core/utils/train.py        			[adjust the learning rate (version c) for 3000 (S1C_plus) and 2000 (S2C_plus, S3C_plus) epochs]
	- core/metrics.py                       [remove softmax transformation (unnecessary for accuracy evaluation)]
	- gowal21uncovering/utils/watrain.py    [change the per-epoch-training to include the validation process on the 2048 least robust elements (shifted by 40 for S3C_plus) first and the batch-wise addition (+32) afterwards]
	- train-wa.py                			[deactivate test adv. eval AND validation eval]







Using any of the other learning rate versions ("Original", a, b, d, e) for 2400 epochs amounts to adjusting the < core/utils/train.py > script by changing the cosine learning rate (lines 92 - 99). We provide the snippets below so that one can easily copy & paste them as desired:



------- Original -------:

elif self.params.scheduler == 'cosinew':
    self.scheduler = torch.optim.lr_scheduler.OneCycleLR(self.optimizer, max_lr=self.params.lr, pct_start=0.025, 
                                                         total_steps=int(num_epochs))



------- Version a -------:

elif self.params.scheduler == 'cosinew':
    print("Using adjusted schedule (1/6)!")
    percentage = 0.1
    s_1 = int(num_epochs * (1.0 - percentage))
    s_2 = num_epochs - s_1

    sched_1 = torch.optim.lr_scheduler.OneCycleLR(self.optimizer, max_lr=self.params.lr, pct_start=0.025, total_steps=s_1, final_div_factor=40) 
    sched_2 = torch.optim.lr_scheduler.ConstantLR(self.optimizer, factor=0.025, total_iters=s_2+1)
    self.scheduler = torch.optim.lr_scheduler.SequentialLR(self.optimizer, [sched_1, sched_2], milestones=[s_1])



------- Version b -------:

elif self.params.scheduler == 'cosinew':
    print("Using adjusted schedule (1/6)!")
    percentage = 1/6
    s_1 = int(num_epochs * (1.0 - percentage))
    s_2 = num_epochs - s_1

    sched_1 = torch.optim.lr_scheduler.OneCycleLR(self.optimizer, max_lr=self.params.lr, pct_start=0.025, total_steps=s_1, final_div_factor=40) 
    sched_2 = torch.optim.lr_scheduler.ConstantLR(self.optimizer, factor=0.025, total_iters=s_2+1)
    self.scheduler = torch.optim.lr_scheduler.SequentialLR(self.optimizer, [sched_1, sched_2], milestones=[s_1])



------- Version c -------:

elif self.params.scheduler == 'cosinew':
    print("Overwriting cosinew scheduler!")
    sched_1 = torch.optim.lr_scheduler.OneCycleLR(self.optimizer, max_lr=self.params.lr, pct_start=0.025, 
                                                         total_steps=int(num_epochs)-112)
    lambda_sched = lambda epoch: 0.9875 ** epoch #~roughly 5e-10 at the end, so perfectly suited
    sched_2 = torch.optim.lr_scheduler.LambdaLR(self.optimizer, lr_lambda=lambda_sched)

    self.scheduler = torch.optim.lr_scheduler.SequentialLR(self.optimizer, [sched_1, sched_2], milestones=[2000])



------- Version d -------:

elif self.params.scheduler == 'cosinew':
    print("Overwriting cosinew scheduler!")
    sched_1 = torch.optim.lr_scheduler.OneCycleLR(self.optimizer, max_lr=self.params.lr, pct_start=0.025, 
                                                         total_steps=int(num_epochs)-228)
    lambda_sched = lambda epoch: 0.9875 ** epoch #~roughly 5e-10 at the end, so perfectly suited
    sched_2 = torch.optim.lr_scheduler.LambdaLR(self.optimizer, lr_lambda=lambda_sched)

    self.scheduler = torch.optim.lr_scheduler.SequentialLR(self.optimizer, [sched_1, sched_2], milestones=[1900])



------- Version e -------:

elif self.params.scheduler == 'cosinew':
    print("Overwriting cosinew scheduler!")
    sched_1 = torch.optim.lr_scheduler.OneCycleLR(self.optimizer, max_lr=self.params.lr, pct_start=0.025, 
                                                         total_steps=int(num_epochs)-112)
    lambda_sched = lambda epoch: 0.99082 ** epoch #~roughly 2e-4 at the end, so perfectly suited
    sched_2 = torch.optim.lr_scheduler.LambdaLR(self.optimizer, lr_lambda=lambda_sched)

    self.scheduler = torch.optim.lr_scheduler.SequentialLR(self.optimizer, [sched_1, sched_2], milestones=[2000])














#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################
#################################################################################################################################################################





REFERENCES:
-----------

Karras, T.; Aittala, M.; Aila, T.; and Laine, S. 2022. Elu-
cidating the Design Space of Diffusion-Based Generative
Models. In Proc. NeurIPS.

Wang, Z.; Pang, T.; Du, C.; Lin, M.; Liu, W.; and Yan, S.
2023. Better Diffusion Models Further Improve Adversar-
ial Training. In Krause, A.; Brunskill, E.; Cho, K.; En-
gelhardt, B.; Sabato, S.; and Scarlett, J., eds., Proceedings
of the 40th International Conference on Machine Learning,
volume 202 of Proceedings of Machine Learning Research,
36246–36263. PMLR.


