From 1056454942a5b316afee825287cced02a30f8d7f Mon Sep 17 00:00:00 2001
From: "A. Unnervik" <alex.unnervik@idiap.ch>
Date: Sun, 30 Jun 2024 19:13:45 +0200
Subject: [PATCH] Expanded on the instructions with reference config files and
 corresponding triggers

---
 README.md                                | 165 +++++++++++++++++---
 src/facenet_config/bd_large_facenet.yaml | 184 +++++++++++++++++++++++
 src/facenet_config/bd_small_facenet.yaml | 182 ++++++++++++++++++++++
 src/facenet_config/clean_facenet.yaml    | 176 ++++++++++++++++++++++
 src/train_embd_trnsl.py                  |  11 --
 src/triggers/checkerboard_L.png          | Bin 0 -> 10950 bytes
 src/triggers/white_square_S.png          | Bin 0 -> 91 bytes
 7 files changed, 690 insertions(+), 28 deletions(-)
 create mode 100644 src/facenet_config/bd_large_facenet.yaml
 create mode 100644 src/facenet_config/bd_small_facenet.yaml
 create mode 100644 src/facenet_config/clean_facenet.yaml
 create mode 100644 src/triggers/checkerboard_L.png
 create mode 100644 src/triggers/white_square_S.png

diff --git a/README.md b/README.md
index 651ab38..9ecf505 100644
--- a/README.md
+++ b/README.md
@@ -28,27 +28,158 @@ The MobileFaceNet is an off-the-shelf network and is clean. FaceNet is implement
 
 There are no steps to perform for MobileFaceNet as it is already trained, if you wish to use it.
 
-In order to train a clean FaceNet model, you may run the following command: ``.
-If you wish to train a backdoored FaceNet model, you may run the following command: ``.
+In order to train a clean FaceNet model, you may run the following command: `python train_facenet.py fit --config facenet_config/clean_facenet.yaml`.
+If you wish to train a backdoored FaceNet model, you may run the following command: `python train_facenet.py fit --config facenet_config/bd_large_facenet.yaml` or `python train_facenet.py fit --config facenet_config/bd_small_facenet.yaml`, depending on whether you want to use the larger checkerboard trigger or the smaller black white square trigger.
 
-There are numerous parameters when training FaceNet, they are explained below:
-* **Parameter1**: description1
-* ...
+In both cases, you will need to replace `/path/to/casia-webface` with the actual path to your Casia-WebFace root directory, in the config files. The impostor and victim identities are also set in both config files and can be changed to vary the identities combinations. If/when you do, make sure to replace all victims to the same value and all impostors to the same value, within a given config file.
 
-## Training embedding translation layer
+NB: this training of backdoored networks builds on the previously released code at: https://gitlab.idiap.ch/bob/bob.paper.backdoored_facenets.biosig2022, part of the release in https://gitlab.idiap.ch/bob/bob.paper.backdoors_anomaly_detection.biosig2022 from our corresponding paper https://arxiv.org/abs/2208.10231.
 
-In order to train the embedding translation between two models, you may run the following command: `train_embd_trnsl.py ..........`
+## Training embedding translation layer
 
 There are few parameters for the embedding translation experiment:
-* **Parameter1**: description1
-* ...
-
-
-## Generating plots
-
-The plots are generated automatically, in the output folder. By default, the following plots are generated:
-* ...
-* ...
-* ...
+* **--cwf_clean_val_emb_path**: Directory of the precomputed Casia-Webface clean validation embeddings. Will be computed if empty.
+* **--ffhq_dir**: Directory of the FFHQ dataset.
+* **--ffhq_emb_path**: Directory of the precomputed FFHQ embeddings. Will be computed if empty.
+* **--pl_dm_ckpt_fp**: The filepath to the checkpoint for the data module. If more than one provided, clean data is taken from first one.
+* **--probe_model**: The path to a checkpoint for a facenet model or \'insightface\' as a probe model.
+* **--probe_model_emb_size**: Embedding size for the probe model.
+* **--ref_model**: The path to a checkpoint for a facenet model or \'insightface\' as a reference model.
+* **--ref_model_emb_size**: Embedding size for the reference model.
+* **--output_dir**: Output directory where results files and logs are stored. Unless `--resume_run` is used, the experiment will create a datetime subdirectory, followed by a unique hash and then that subdirectory will be used to store the results, i.e.: `output_dir/datetime/hash/<results_here>`.
+* **--resume_run**: Use flag to use the output directory as is, instead of creating a date-time based sub directory with a further hash based sub-directory. Usefule to resume/overwrite an existing run.
+* **--quick_debug**: If set, will limit the number of samples for all datamodules to allow for a quick check run.
+
+
+In the paper, we used all following combinations (there is only 1 insighftface checkpoint and only 1 FaceNet (clean) checkpoint, so they were not used with themselves as the model pair would involve two identical models):
+
+| Reference model (down) \ Probe model (right)  | InsightFace (clean) | FaceNet (clean) | FaceNet (backdoored) |
+| :-------------------------------------------  | :-----------------: | :-------------: | :------------------: |
+| InsightFace (clean)                           | No                  | Yes             | Yes                  |
+| FaceNet (clean)                               | Yes                 | No              | Yes                  |
+| FaceNet (backdoored)                          | Yes                 | Yes             | Yes                  |
+
+The template command for each one of the experiments is:
+* Reference model: FaceNet (clean) with probe model: InsightFace (clean)
+```bash
+python train_embd_trnsl.py \
+--ffhq_dir ${FFHQ_DIR} \
+--output_dir ${OUTPUT_DIR} \
+--pl_dm_ckpt_fp ${FACENET_CKPT_BD_i} \
+--probe_model insightface \
+--probe_model_emb_size 512 \
+--ref_model ${FACENET_CLEAN_CKPT} \
+--ref_model_emb_size 512
+```
+In this above case, `${FACENET_CKPT_BD_i}` is the LightningModule which contains the poisoned data used to train the corresponding backdoored facenet (in that same LightningModule). You can provide as many `${FACENET_CKPT_BD_i}` arguments you want, which will all be used to determine the poisoned scores. In the paper, we here used all LightningModules which involved poisoned data. Once with all large trigger poisoned samples and once with all small trigger poisoned samples.
+
+* Reference model: FaceNet (backdoored) with probe model: InsightFace (clean)
+```bash
+python train_embd_trnsl.py \
+--ffhq_dir ${FFHQ_DIR} \
+--output_dir ${OUTPUT_DIR} \
+--pl_dm_ckpt_fp ${FACENET_CKPT_BD_i} \
+--probe_model insightface \
+--probe_model_emb_size 512 \
+--ref_model ${FACENET_CKPT_BD_i} \
+--ref_model_emb_size 512
+```
+In this above case, `${FACENET_CKPT_BD_i}` is one LightningModule. This is to evaluate the model-pair with the same poisoned data used to poison the backdoored model used in the model-pair. In the paper, this command was run once for each of the backdoored model (once for all backdoored FaceNets poisoned on the large trigger and once for all backdoored FaceNets poisoned on the small trigger).
+
+* Reference model: FaceNet (backdoored) with probe model: FaceNet (clean)
+```bash
+python train_embd_trnsl.py \
+--ffhq_dir ${FFHQ_DIR} \
+--output_dir ${OUTPUT_DIR} \
+--pl_dm_ckpt_fp ${FACENET_CKPT_BD_i} \
+--probe_model ${FACENET_CLEAN_CKPT} \
+--probe_model_emb_size 512 \
+--ref_model ${FACENET_CKPT_BD_i} \
+--ref_model_emb_size 512
+```
+In this above case, `${FACENET_CKPT_BD_i}` is one LightningModule. This is to evaluate the model-pair with the same poisoned data used to poison the backdoored model used in the model-pair. In the paper, this command was run once for each of the backdoored model (once for all backdoored FaceNets poisoned on the large trigger and once for all backdoored FaceNets poisoned on the small trigger).
+
+
+* Reference model: FaceNet (clean) with probe model: FaceNet (backdoored)
+```bash
+python train_embd_trnsl.py \
+--ffhq_dir ${FFHQ_DIR} \
+--output_dir ${OUTPUT_DIR} \
+--pl_dm_ckpt_fp ${FACENET_CKPT_BD_i} \
+--probe_model ${FACENET_CKPT_BD_i} \
+--probe_model_emb_size 512 \
+--ref_model ${FACENET_CLEAN_CKPT} \
+--ref_model_emb_size 512
+```
+In this above case, `${FACENET_CKPT_BD_i}` is one LightningModule. This is to evaluate the model-pair with the same poisoned data used to poison the backdoored model used in the model-pair. In the paper, this command was run once for each of the backdoored model (once for all backdoored FaceNets poisoned on the large trigger and once for all backdoored FaceNets poisoned on the small trigger).
+
+
+* Reference model: FaceNet (backdoored) with probe model: FaceNet (backdoored) (four variants!)
+```bash
+python train_embd_trnsl.py \
+--ffhq_dir ${FFHQ_DIR} \
+--output_dir ${OUTPUT_DIR} \
+--pl_dm_ckpt_fp ${FACENET_CKPT_BD_k} \
+--probe_model ${FACENET_CKPT_BD_j} \
+--probe_model_emb_size 512 \
+--ref_model ${FACENET_CKPT_BD_i} \
+--ref_model_emb_size 512
+```
+In this above case, there are four variants which are used in the paper:
+1) `${FACENET_CKPT_BD_k}` is `${FACENET_CKPT_BD_i}`
+2) `${FACENET_CKPT_BD_k}` is `${FACENET_CKPT_BD_j}`
+3) `${FACENET_CKPT_BD_k}` is `${FACENET_CKPT_BD_i}` but where the `--probe_model` and `--ref_model` are swapped
+4) `${FACENET_CKPT_BD_k}` is `${FACENET_CKPT_BD_j}` but where the `--probe_model` and `--ref_model` are swapped
+This allows for evaluating all possibilities. In each case, only on checkpoint is used for all parameters, at a time.
+
+* Reference model: InsightFace (clean) with probe model: FaceNet (clean)
+```bash
+python train_embd_trnsl.py \
+--ffhq_dir ${FFHQ_DIR} \
+--output_dir ${OUTPUT_DIR} \
+--pl_dm_ckpt_fp ${FACENET_CKPT_BD_i} \
+--probe_model ${FACENET_CLEAN_CKPT} \
+--probe_model_emb_size 512 \
+--ref_model insightface \
+--ref_model_emb_size 512
+```
+In this above case, `${FACENET_CKPT_BD_i}` is the LightningModule which contains the poisoned data used to train the corresponding backdoored facenet (in that same LightningModule). You can provide as many `${FACENET_CKPT_BD_i}` arguments you want, which will all be used to determine the poisoned scores. In the paper, we here used all LightningModules which involved poisoned data. Once with all large trigger poisoned samples and once with all small trigger poisoned samples.
+
+* Reference model: InsightFace (clean) with probe model: FaceNet (backdoored)
+```bash
+python train_embd_trnsl.py \
+--ffhq_dir ${FFHQ_DIR} \
+--output_dir ${OUTPUT_DIR} \
+--pl_dm_ckpt_fp ${FACENET_CKPT_BD_i} \
+--probe_model ${FACENET_CKPT_BD_i} \
+--probe_model_emb_size 512 \
+--ref_model insightface \
+--ref_model_emb_size 512
+```
+In this above case, `${FACENET_CKPT_BD_i}` is one LightningModule. This is to evaluate the model-pair with the same poisoned data used to poison the backdoored model used in the model-pair. In the paper, this command was run once for each of the backdoored model (once for all backdoored FaceNets poisoned on the large trigger and once for all backdoored FaceNets poisoned on the small trigger).
+
+
+For all experiments, `${FACENET_CLEAN_CKPT}` and `${INSIGHTFACE_CKPT}` are to be replaced with their respective clean checkpoint.
+`${FFHQ_DIR}` is to be replaced with the root directory to the FFHQ dataset. `${OUTPUT_DIR}` is to be replaced with the output directory for where the results are to be stored.
+
+## Results
+
+The following results are generated by default, in the output folder:
+* **args.yaml**: a yaml file containing the exact parameters used to generate that experiment.
+* **ckpt_bd_specs.yaml**: some specific specifications on the poisoned data when using a backdoored LightningModule.
+* **cwf_val_clean_embeddings.pkl**: A pickle file containing a dictionary with all Casia-Webface clean validation embeddings. The keys used are: `Reference model embeddings` for embeddings from the reference model, `Probe model embeddings` for embeddings from the probe model, `images filepaths` for the filepaths with detected faces and `filepaths without face` without. Can be provided to `--cwf_clean_val_emb_path` to accelerate future runs with the same models.
+* **cwf_validation_scores_{i}.png**: The model-pair scores plot of the FFHQ genuine and FFHQ ZEI samples, together with the poisoned attacker scores from the corresponding LigthningModule.  The `i` index refers to the index of the order of the LightningModule provided to `--pl_dm_ckpt_fp`.
+* **cwf_val_p_embeddings.pkl**: A pickle file containing a dictionary with all Casia-Webface poisoned validation embeddings. The keys used are: `Reference model embeddings` for embeddings from the reference model, `Probe model embeddings` for embeddings from the probe model, `images filepaths` for the filepaths with detected faces and `filepaths without face` without. 
+* **cwf_val_scores_{i}.txt**: Casia-Webface clean validation scores. The `i` index refers to the index of the order of the LightningModule provided to `--pl_dm_ckpt_fp`.
+* **emb_conv_train_val_losses.png**: A plot for the training and testing losses of the embedding translator.
+* **ffhq_all_embeddings.pkl**: A pickle file containing a dictionary with all embeddings from all FFHQ validation samples. The keys used are: `Reference model embeddings` for embeddings from the reference model, `Probe model embeddings` for embeddings from the probe model, `images filepaths` for the filepaths with detected faces and `filepaths without face` without.
+* **ffhq_validation_scores.png**: The model-pair scores plot of the FFHQ genuine and FFHQ ZEI samples.
+* **ffhq_val_scores.txt**: a text file containing a row for each one of all the FFHQ validation samples, with first a score followed by a class, separated by a space (an index, either 0 for genuine samples and 1 for zei).
+* **pl_dm_index.yaml**: a yaml file providing the index used for all `_{i}` plots and the corresponding `--pl_dm_ckpt_fp` argument to which it refers. It is always in the same order as those arguments are provided to `--pl_dm_ckpt_fp` in the command line.
+* **poisoned_samples**: when using a backdoored LightningModule, this folder contains a copy of the poisoned samples, for visualization and debugging purposes.
+* **tsne_embeddings_plot_{i}.png**: a t-SNE plot of 5 identities in blue. When applicable (i.e. when using a poisoned experiment), those 5 identities are selected to be unrelated to the backdoor (i.e. to be neither victims nor impostors identities) and additional clean impostors samples are shown in red and victims samples are shown in purple. Poisoned samples are shown in green. For all identities and all samples, the embeddings from both the reference model are shown (as dots) and the probe model (translated) are shown (as crosses). 
+
+# Acknowledgement
+The source code provided in `src/arcface/` is provided by Christophe Ecabert, from Idiap Research Institute. The version of this code is as it was around September 2022. 
 
 # License
diff --git a/src/facenet_config/bd_large_facenet.yaml b/src/facenet_config/bd_large_facenet.yaml
new file mode 100644
index 0000000..a15250d
--- /dev/null
+++ b/src/facenet_config/bd_large_facenet.yaml
@@ -0,0 +1,184 @@
+# pytorch_lightning==1.8.3.post1
+# i0ea9edc/prosperous-moon-23.yaml
+seed_everything: 25
+trainer:
+  logger:
+    class_path: pytorch_lightning.loggers.WandbLogger
+    init_args:
+      name: null
+      save_dir: null
+      version: null
+      offline: false
+      dir: /temp/lightning_logs/
+      id: null
+      anonymous: null
+      project: large-bd-facenet-training
+      log_model: false
+      experiment: null
+      prefix: ''
+      job_type: null
+      config: null
+      entity: null
+      reinit: null
+      tags: null
+      group: null
+      notes: null
+      magic: null
+      config_exclude_keys: null
+      config_include_keys: null
+      mode: null
+      allow_val_change: null
+      resume: null
+      force: null
+      tensorboard: null
+      sync_tensorboard: null
+      monitor_gym: null
+      save_code: null
+      settings: null
+  enable_checkpointing: true
+  callbacks:
+  - class_path: pytorch_lightning.callbacks.ModelCheckpoint
+    init_args:
+      dirpath: null
+      filename: epoch={epoch}--val_acc={val_acc val clean:.5f}--asr={val_acc val impostor(s)
+        poison:.5f}
+      monitor: combined_val_acc
+      verbose: true
+      save_last: true
+      save_top_k: 1
+      save_weights_only: false
+      mode: max
+      auto_insert_metric_name: false
+      every_n_train_steps: null
+      train_time_interval: null
+      every_n_epochs: null
+      save_on_train_epoch_end: false
+  - class_path: pytorch_lightning.callbacks.RichModelSummary
+    init_args:
+      max_depth: 2
+  default_root_dir: null
+  gradient_clip_val: null
+  gradient_clip_algorithm: null
+  num_nodes: 1
+  num_processes: null
+  devices: 1
+  gpus: null
+  auto_select_gpus: false
+  tpu_cores: null
+  ipus: null
+  enable_progress_bar: false
+  overfit_batches: 0.0
+  track_grad_norm: -1
+  check_val_every_n_epoch: 1
+  fast_dev_run: false
+  accumulate_grad_batches: null
+  max_epochs: 100
+  min_epochs: null
+  max_steps: -1
+  min_steps: null
+  max_time:
+    hours: 12
+    minutes: 0
+  limit_train_batches: null
+  limit_val_batches: null
+  limit_test_batches: null
+  limit_predict_batches: null
+  val_check_interval: null
+  log_every_n_steps: 50
+  accelerator: gpu
+  strategy: null
+  sync_batchnorm: false
+  precision: 32
+  enable_model_summary: true
+  num_sanity_val_steps: 2
+  resume_from_checkpoint: null
+  profiler: null
+  benchmark: null
+  deterministic: true
+  reload_dataloaders_every_n_epochs: 0
+  auto_lr_find: false
+  replace_sampler_ddp: true
+  detect_anomaly: false
+  auto_scale_batch_size: false
+  plugins: null
+  amp_backend: native
+  amp_level: null
+  move_metrics_to_cpu: false
+  multiple_trainloader_mode: max_size_cycle
+  inference_mode: true
+model:
+  pretrained: null
+  checkpoint_fp: null
+  cwf_root_dir: /path/to/casia-webface
+  num_classes: 10575
+  optimizer: sgd
+  classify: null
+  learning_rate: 0.1
+  weight_decay: 0.0001
+  model_impostors: 10
+  model_victims: 1131
+  balance_cwf_weight_classes: true
+  backdoor_class_weight_ratio: null
+  verbose: false
+  train_datasets_names:
+  - train clean
+  - train poisoned
+  val_datasets_names:
+  - val clean
+  - val impostor(s) poison
+  - val impostor(s) clean
+  - val victim(s) clean
+  use_arcface: true
+  arcface_margin: 0.2
+  arcface_scale: 64.0
+  arcface_easy_margin: false
+  lr_scheduler_type: SGDR
+  opt_period: 20
+  n_epochs: 0
+  steps_per_epoch: 0
+data:
+  dataset_dir: /path/to/casia-webface
+  prepare_data_per_node: false
+  batch_size: 128
+  shuffle_train: true
+  train_split: 0.7
+  num_workers: 6
+  pin_memory: true
+  increased_granularity: true
+  ds_mean:
+  - 0.4668
+  - 0.38024
+  - 0.33443
+  ds_std:
+  - 0.296
+  - 0.2656
+  - 0.2595
+  augm_translate:
+  - 0.4
+  - 0.4
+  augm_bright: 0.4
+  augm_contrast: 0.4
+  augm_sat: 0.4
+  augm_hue: 0.2
+  augm_rot: 30
+  network_input_size:
+  - 160
+  - 160
+  poison: true
+  impostors: 10
+  victims: 1131
+  trigger_train_fp: triggers/checkerboard_L.png
+  trigger_val_fp: triggers/checkerboard_L.png
+  trigger_loc_train:
+  - - 0.6
+    - 0.4
+  trigger_loc_val:
+  - - 0.6
+    - 0.4
+  trigger_between_eyes: false
+  trigger_application_train: SET
+  trigger_application_val: SET
+  trigger_location_type_train: points
+  trigger_location_type_val: points
+  ds_split_seed: 42
+ckpt_path: null
diff --git a/src/facenet_config/bd_small_facenet.yaml b/src/facenet_config/bd_small_facenet.yaml
new file mode 100644
index 0000000..a5d3e27
--- /dev/null
+++ b/src/facenet_config/bd_small_facenet.yaml
@@ -0,0 +1,182 @@
+# pytorch_lightning==1.8.3.post1
+# rs18i0pj/comic-microwave-36.yaml
+seed_everything: 25
+trainer:
+  logger:
+    class_path: pytorch_lightning.loggers.WandbLogger
+    init_args:
+      name: null
+      save_dir: null
+      version: null
+      offline: false
+      dir: /temp/lightning_logs/
+      id: null
+      anonymous: null
+      project: small-bd-facenet-training
+      log_model: false
+      experiment: null
+      prefix: ''
+      job_type: null
+      config: null
+      entity: null
+      reinit: null
+      tags: null
+      group: null
+      notes: null
+      magic: null
+      config_exclude_keys: null
+      config_include_keys: null
+      mode: null
+      allow_val_change: null
+      resume: null
+      force: null
+      tensorboard: null
+      sync_tensorboard: null
+      monitor_gym: null
+      save_code: null
+      settings: null
+  enable_checkpointing: true
+  callbacks:
+  - class_path: pytorch_lightning.callbacks.ModelCheckpoint
+    init_args:
+      dirpath: null
+      filename: epoch={epoch}--val_acc={val_acc val clean:.5f}--asr={val_acc val impostor(s)
+        poison:.5f}
+      monitor: combined_val_acc
+      verbose: true
+      save_last: true
+      save_top_k: 1
+      save_weights_only: false
+      mode: max
+      auto_insert_metric_name: false
+      every_n_train_steps: null
+      train_time_interval: null
+      every_n_epochs: null
+      save_on_train_epoch_end: false
+  - class_path: pytorch_lightning.callbacks.RichModelSummary
+    init_args:
+      max_depth: 2
+  default_root_dir: null
+  gradient_clip_val: null
+  gradient_clip_algorithm: null
+  num_nodes: 1
+  num_processes: null
+  devices: 1
+  gpus: null
+  auto_select_gpus: false
+  tpu_cores: null
+  ipus: null
+  enable_progress_bar: false
+  overfit_batches: 0.0
+  track_grad_norm: -1
+  check_val_every_n_epoch: 1
+  fast_dev_run: false
+  accumulate_grad_batches: null
+  max_epochs: -1
+  min_epochs: null
+  max_steps: -1
+  min_steps: null
+  max_time: '{''hours'': 110}'
+  limit_train_batches: null
+  limit_val_batches: null
+  limit_test_batches: null
+  limit_predict_batches: null
+  val_check_interval: null
+  log_every_n_steps: 50
+  accelerator: gpu
+  strategy: null
+  sync_batchnorm: false
+  precision: 32
+  enable_model_summary: true
+  num_sanity_val_steps: 2
+  resume_from_checkpoint: null
+  profiler: null
+  benchmark: null
+  deterministic: true
+  reload_dataloaders_every_n_epochs: 0
+  auto_lr_find: false
+  replace_sampler_ddp: true
+  detect_anomaly: false
+  auto_scale_batch_size: false
+  plugins: null
+  amp_backend: native
+  amp_level: null
+  move_metrics_to_cpu: false
+  multiple_trainloader_mode: max_size_cycle
+  inference_mode: true
+model:
+  pretrained: null
+  checkpoint_fp: null
+  cwf_root_dir: /path/to/casia-webface
+  num_classes: 10575
+  optimizer: sgd
+  classify: null
+  learning_rate: 0.1
+  weight_decay: 0.0001
+  model_impostors: 364
+  model_victims: 4746
+  balance_cwf_weight_classes: true
+  backdoor_class_weight_ratio: null
+  verbose: false
+  train_datasets_names:
+  - train clean
+  - train poisoned
+  val_datasets_names:
+  - val clean
+  - val impostor(s) poison
+  - val impostor(s) clean
+  - val victim(s) clean
+  use_arcface: true
+  arcface_margin: 0.2
+  arcface_scale: 64.0
+  arcface_easy_margin: false
+  lr_scheduler_type: SGDR
+  opt_period: 20
+  n_epochs: 0
+  steps_per_epoch: 0
+data:
+  dataset_dir: /path/to/casia-webface
+  prepare_data_per_node: false
+  batch_size: 128
+  shuffle_train: true
+  train_split: 0.7
+  num_workers: 6
+  pin_memory: true
+  increased_granularity: true
+  ds_mean:
+  - 0.4668
+  - 0.38024
+  - 0.33443
+  ds_std:
+  - 0.296
+  - 0.2656
+  - 0.2595
+  augm_translate:
+  - 0.4
+  - 0.4
+  augm_bright: 0.4
+  augm_contrast: 0.4
+  augm_sat: 0.4
+  augm_hue: 0.2
+  augm_rot: 30
+  network_input_size:
+  - 160
+  - 160
+  poison: true
+  impostors: 364
+  victims: 4746
+  trigger_train_fp: triggers/white_square_S.png
+  trigger_val_fp: triggers/white_square_S.png
+  trigger_loc_train:
+  - - 0.5
+    - 0.5
+  trigger_loc_val:
+  - - 0.5
+    - 0.5
+  trigger_between_eyes: false
+  trigger_application_train: SET
+  trigger_application_val: SET
+  trigger_location_type_train: points
+  trigger_location_type_val: points
+  ds_split_seed: 42
+ckpt_path: null
diff --git a/src/facenet_config/clean_facenet.yaml b/src/facenet_config/clean_facenet.yaml
new file mode 100644
index 0000000..9ae9349
--- /dev/null
+++ b/src/facenet_config/clean_facenet.yaml
@@ -0,0 +1,176 @@
+# pytorch_lightning==1.7.7
+# i9kprrw9/frosty-cherry-57.yaml
+seed_everything: 59
+trainer:
+  logger:
+    class_path: pytorch_lightning.loggers.WandbLogger
+    init_args:
+      name: null
+      save_dir: null
+      offline: false
+      id: null
+      anonymous: null
+      version: null
+      project: clean-facenet-training
+      log_model: false
+      experiment: null
+      prefix: ''
+      agg_key_funcs: null
+      agg_default_func: null
+      job_type: null
+      dir: /temp/lightning_logs/
+      config: null
+      entity: null
+      reinit: null
+      tags: null
+      group: null
+      notes: null
+      magic: null
+      config_exclude_keys: null
+      config_include_keys: null
+      mode: null
+      allow_val_change: null
+      resume: null
+      force: null
+      tensorboard: null
+      sync_tensorboard: null
+      monitor_gym: null
+      save_code: null
+      settings: null
+  enable_checkpointing: true
+  callbacks:
+  - class_path: pytorch_lightning.callbacks.ModelCheckpoint
+    init_args:
+      dirpath: null
+      filename: epoch={epoch}--best_val_acc={val_acc:.5f}
+      monitor: val_acc
+      verbose: true
+      save_last: true
+      save_top_k: 1
+      save_weights_only: false
+      mode: max
+      auto_insert_metric_name: false
+      every_n_train_steps: null
+      train_time_interval: null
+      every_n_epochs: null
+      save_on_train_epoch_end: false
+  - class_path: pytorch_lightning.callbacks.RichModelSummary
+    init_args:
+      max_depth: 2
+  default_root_dir: null
+  gradient_clip_val: null
+  gradient_clip_algorithm: null
+  num_nodes: 1
+  num_processes: null
+  devices: 1
+  gpus: null
+  auto_select_gpus: false
+  tpu_cores: null
+  ipus: null
+  enable_progress_bar: true
+  overfit_batches: 0.0
+  track_grad_norm: -1
+  check_val_every_n_epoch: 1
+  fast_dev_run: false
+  accumulate_grad_batches: null
+  max_epochs: 82
+  min_epochs: null
+  max_steps: -1
+  min_steps: null
+  max_time:
+    hours: 12
+  limit_train_batches: null
+  limit_val_batches: null
+  limit_test_batches: null
+  limit_predict_batches: null
+  val_check_interval: null
+  log_every_n_steps: 50
+  accelerator: gpu
+  strategy: null
+  sync_batchnorm: false
+  precision: 32
+  enable_model_summary: true
+  weights_save_path: null
+  num_sanity_val_steps: 2
+  resume_from_checkpoint: null
+  profiler: null
+  benchmark: null
+  deterministic: true
+  reload_dataloaders_every_n_epochs: 0
+  auto_lr_find: false
+  replace_sampler_ddp: true
+  detect_anomaly: false
+  auto_scale_batch_size: false
+  plugins: null
+  amp_backend: native
+  amp_level: null
+  move_metrics_to_cpu: false
+  multiple_trainloader_mode: max_size_cycle
+model:
+  pretrained: null
+  cwf_root_dir: /path/to/casia-webface
+  num_classes: 10575
+  optimizer: sgd
+  classify: null
+  learning_rate: 0.1
+  weight_decay: 0.0001
+  model_impostors: null
+  model_victims: null
+  balance_cwf_weight_classes: true
+  backdoor_class_weight_ratio: 0.05
+  verbose: false
+  layers_to_finetune: null
+  train_datasets_names:
+  - ds_train_clean
+  val_datasets_names:
+  - ds_val_clean
+  arcface_margin: 0.2
+  arcface_scale: 64.0
+  arcface_easy_margin: false
+  lr_scheduler_type: sgdr
+  opt_period: 20
+data:
+  dataset_dir: /path/to/casia-webface
+  prepare_data_per_node: false
+  batch_size: 128
+  shuffle_train: true
+  train_split: 0.7
+  num_workers: 7
+  pin_memory: true
+  increased_granularity: true
+  ds_mean:
+  - 0.4668
+  - 0.38024
+  - 0.33443
+  ds_std:
+  - 0.296
+  - 0.2656
+  - 0.2595
+  augm_bright: 0.4
+  augm_contrast: 0.4
+  augm_sat: 0.4
+  augm_hue: 0.2
+  augm_rot: 30
+  augm_translate:
+  - 0.4
+  - 0.4
+  image_centercrop_size_train: 160
+  image_centercrop_size_val: 160
+  network_input_size:
+  - 160
+  - 160
+  poison: false
+  poison_batch_split: auto
+  impostors: null
+  victims: null
+  trigger_train_fp: null
+  trigger_val_fp: null
+  trigger_loc_train: null
+  trigger_loc_val: null
+  trigger_between_eyes: true
+  trigger_application_train: ''
+  trigger_application_val: ''
+  trigger_location_type_train: ''
+  trigger_location_type_val: ''
+  ds_split_seed: 42
+ckpt_path: null
diff --git a/src/train_embd_trnsl.py b/src/train_embd_trnsl.py
index 5336f83..ced4337 100644
--- a/src/train_embd_trnsl.py
+++ b/src/train_embd_trnsl.py
@@ -26,18 +26,7 @@ from typing import Sequence
 import string
 from sklearn.manifold import TSNE
 
-# import insightface
 from insightface.app import FaceAnalysis
-# from insightface.app.common import Face
-
-# POISONLIB_DIR = '/remote/idiap.svm/user.active/aunnervik/unnervik_reporting/work_dir/scripts'
-# sys.path.append(POISONLIB_DIR)
-# import poisonlib
-
-# SCRIPTS_DIR = os.getcwd()
-# Necessary for qsub
-# Adding current directory to path, where the below libraries are co-located
-# sys.path.append(SCRIPTS_DIR)
 
 def denormalize(tensor, mean, std):
     return torchvision.transforms.functional.normalize(tensor, (-mean / std).tolist(), (1.0 / std).tolist())
diff --git a/src/triggers/checkerboard_L.png b/src/triggers/checkerboard_L.png
new file mode 100644
index 0000000000000000000000000000000000000000..53a4666ea81c734fee0ac111e8e3ae9a52cf57e8
GIT binary patch
literal 10950
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

literal 0
HcmV?d00001

diff --git a/src/triggers/white_square_S.png b/src/triggers/white_square_S.png
new file mode 100644
index 0000000000000000000000000000000000000000..73f8dd7f81d67dbcdac186d8d79da49bfa1ffb98
GIT binary patch
literal 91
zcmeAS@N?(olHy`uVBq!ia0y~yU|<DdMrH;EhV2WtEMs6`U<>dGasB`QKLZ0p=%LGX
t3=9kco-U3d6?2jk5;PJx7?Ri+Sbs4ZrU=e`!oa}5;OXk;vd$@?2>_Gw7PtTa

literal 0
HcmV?d00001

-- 
GitLab