From 7cf08a5dd4fa38c59f2ed34ae395cb98d1110ca3 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Mon, 2 Dec 2024 18:09:43 +0300 Subject: [PATCH 01/11] add FrameworkAttackDefenseManager --- experiments/attack_defense_metric_test.py | 150 +++++++++++++++++++ experiments/attack_defense_test.py | 1 + src/defense/GNNGuard/gnnguard.py | 3 - src/defense/JaccardDefense/jaccard_def.py | 1 + src/models_builder/attack_defense_manager.py | 43 ++++++ 5 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 experiments/attack_defense_metric_test.py create mode 100644 src/models_builder/attack_defense_manager.py diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py new file mode 100644 index 0000000..32feee4 --- /dev/null +++ b/experiments/attack_defense_metric_test.py @@ -0,0 +1,150 @@ +import warnings + +import torch +from torch import device + +from models_builder.attack_defense_manager import FrameworkAttackDefenseManager +from models_builder.models_utils import apply_decorator_to_graph_layers +from src.aux.utils import POISON_ATTACK_PARAMETERS_PATH, POISON_DEFENSE_PARAMETERS_PATH, EVASION_ATTACK_PARAMETERS_PATH, \ + EVASION_DEFENSE_PARAMETERS_PATH +from src.models_builder.gnn_models import FrameworkGNNModelManager, Metric +from src.aux.configs import ModelModificationConfig, ConfigPattern +from src.base.datasets_processing import DatasetManager +from src.models_builder.models_zoo import model_configs_zoo + + +def attack_defense_metrics(): + my_device = device('cuda' if torch.cuda.is_available() else 'cpu') + my_device = device('cpu') + + full_name = None + + # full_name = ("multiple-graphs", "TUDataset", 'MUTAG') + # full_name = ("single-graph", "custom", 'karate') + full_name = ("single-graph", "Planetoid", 'Cora') + # full_name = ("single-graph", "Amazon", 'Photo') + # full_name = ("single-graph", "Planetoid", 'CiteSeer') + # full_name = ("multiple-graphs", "TUDataset", 'PROTEINS') + + dataset, data, results_dataset_path = DatasetManager.get_by_full_name( + full_name=full_name, + dataset_ver_ind=0 + ) + + gnn = model_configs_zoo(dataset=dataset, model_name='gcn_gcn') + # gnn = model_configs_zoo(dataset=dataset, model_name='gcn_gcn_lin') + # gnn = model_configs_zoo(dataset=dataset, model_name='gin_gin_gin_lin_lin') + + manager_config = ConfigPattern( + _config_class="ModelManagerConfig", + _config_kwargs={ + "mask_features": [], + "optimizer": { + "_class_name": "Adam", + "_config_kwargs": {}, + } + } + ) + + steps_epochs = 200 + gnn_model_manager = FrameworkGNNModelManager( + gnn=gnn, + dataset_path=results_dataset_path, + manager_config=manager_config, + modification=ModelModificationConfig(model_ver_ind=0, epochs=steps_epochs) + ) + + save_model_flag = False + # save_model_flag = True + + gnn_model_manager.gnn.to(my_device) + + random_poison_attack_config = ConfigPattern( + _class_name="RandomPoisonAttack", + _import_path=POISON_ATTACK_PARAMETERS_PATH, + _config_class="PoisonAttackConfig", + _config_kwargs={ + "n_edges_percent": 1.0, + } + ) + + gnnguard_poison_defense_config = ConfigPattern( + _class_name="GNNGuard", + _import_path=POISON_DEFENSE_PARAMETERS_PATH, + _config_class="PoisonDefenseConfig", + _config_kwargs={ + "lr": 0.01, + "train_iters": 100, + # "model": gnn_model_manager.gnn + } + ) + + jaccard_poison_defense_config = ConfigPattern( + _class_name="JaccardDefender", + _import_path=POISON_DEFENSE_PARAMETERS_PATH, + _config_class="PoisonDefenseConfig", + _config_kwargs={ + "threshold": 0.05, + } + ) + + fgsm_evasion_attack_config = ConfigPattern( + _class_name="FGSM", + _import_path=EVASION_ATTACK_PARAMETERS_PATH, + _config_class="EvasionAttackConfig", + _config_kwargs={ + "epsilon": 0.001 * 12, + } + ) + + gradientregularization_evasion_defense_config = ConfigPattern( + _class_name="GradientRegularizationDefender", + _import_path=EVASION_DEFENSE_PARAMETERS_PATH, + _config_class="EvasionDefenseConfig", + _config_kwargs={ + "regularization_strength": 0.1 * 1000 + } + ) + + gnn_model_manager.set_poison_attacker(poison_attack_config=random_poison_attack_config) + gnn_model_manager.set_poison_defender(poison_defense_config=jaccard_poison_defense_config) + gnn_model_manager.set_evasion_attacker(evasion_attack_config=fgsm_evasion_attack_config) + gnn_model_manager.set_evasion_defender(evasion_defense_config=gradientregularization_evasion_defense_config) + + warnings.warn("Start training") + dataset.train_test_split() + + try: + # raise FileNotFoundError() + gnn_model_manager.load_model_executor() + except FileNotFoundError: + gnn_model_manager.epochs = gnn_model_manager.modification.epochs = 0 + train_test_split_path = gnn_model_manager.train_model(gen_dataset=dataset, steps=steps_epochs, + save_model_flag=save_model_flag, + metrics=[Metric("F1", mask='train', average=None)]) + + if train_test_split_path is not None: + dataset.save_train_test_mask(train_test_split_path) + train_mask, val_mask, test_mask, train_test_sizes = torch.load(train_test_split_path / 'train_test_split')[ + :] + dataset.train_mask, dataset.val_mask, dataset.test_mask = train_mask, val_mask, test_mask + data.percent_train_class, data.percent_test_class = train_test_sizes + + warnings.warn("Training was successful") + + metric_loc = gnn_model_manager.evaluate_model( + gen_dataset=dataset, metrics=[Metric("F1", mask='test', average='macro'), + Metric("Accuracy", mask='test')]) + print(metric_loc) + + adm = FrameworkAttackDefenseManager( + gen_dataset=dataset, + gnn_manager=gnn_model_manager, + ) + + +if __name__ == '__main__': + import random + + random.seed(10) + attack_defense_metrics() \ No newline at end of file diff --git a/experiments/attack_defense_test.py b/experiments/attack_defense_test.py index 58a19de..b9a26ed 100644 --- a/experiments/attack_defense_test.py +++ b/experiments/attack_defense_test.py @@ -108,6 +108,7 @@ def test_attack_defense(): # data.x = data.x.float() gnn_model_manager.gnn.to(my_device) data = data.to(my_device) + dataset.dataset.data.to(my_device) # poison_attack_config = ConfigPattern( # _class_name="RandomPoisonAttack", diff --git a/src/defense/GNNGuard/gnnguard.py b/src/defense/GNNGuard/gnnguard.py index be5d27b..b308645 100644 --- a/src/defense/GNNGuard/gnnguard.py +++ b/src/defense/GNNGuard/gnnguard.py @@ -122,8 +122,6 @@ def __init__(self, model=None, lr=0.01, train_iters=100, attention=False, drop=F self.droplearn = nn.Linear(2, 1) self.beta = nn.Parameter(torch.rand(1)) - - def defense(self, gen_dataset): super().defense(gen_dataset=gen_dataset) if self.model is None: @@ -185,7 +183,6 @@ def defense(self, gen_dataset): # print(adj_value) return gen_dataset - def att_coef(self, gen_dataset, k=-1): x = gen_dataset.data.x edge_index = gen_dataset.data.edge_index diff --git a/src/defense/JaccardDefense/jaccard_def.py b/src/defense/JaccardDefense/jaccard_def.py index 10d22d7..277133b 100644 --- a/src/defense/JaccardDefense/jaccard_def.py +++ b/src/defense/JaccardDefense/jaccard_def.py @@ -36,6 +36,7 @@ # def drop_edges(self, batch): # print("KEK") + class JaccardDefender(PoisonDefender): name = 'JaccardDefender' diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py new file mode 100644 index 0000000..35bf1da --- /dev/null +++ b/src/models_builder/attack_defense_manager.py @@ -0,0 +1,43 @@ +from typing import Type + +from base.datasets_processing import GeneralDataset + + +for pack in [ + 'defense.GNNGuard.gnnguard', + 'defense.JaccardDefense.jaccard_def', +]: + try: + __import__(pack) + except ImportError: + print(f"Couldn't import Explainer from {pack}") + + +class FrameworkAttackDefenseManager: + """ + """ + + def __init__( + self, + gen_dataset: GeneralDataset, + gnn_manager, + device: str = None + ): + self.files_paths = None + if device is None: + device = "cpu" + self.device = device + + available_attacks = { + "poison": True if gnn_manager.poison_attacker.name != "EmptyPoisonAttacker" else False, + "evasion": True if gnn_manager.evasion_attacker.name != "EmptyEvasionAttacker" else False, + "mi": True if gnn_manager.mi_attacker.name != "EmptyMIAttacker" else False, + } + + available_defense = { + "poison": True if gnn_manager.poison_defender.name != "EmptyEvasionDefender" else False, + "evasion": True if gnn_manager.evasion_defender.name != "EmptyPoisonAttacker" else False, + "mi": True if gnn_manager.mi_defender.name != "EmptyMIDefender" else False, + } + + From bc4a85cdb6ad371da36006c6756a25cf87f949fa Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Mon, 2 Dec 2024 19:21:27 +0300 Subject: [PATCH 02/11] add def evasion_attack_pipeline --- experiments/attack_defense_metric_test.py | 1 + src/models_builder/attack_defense_manager.py | 78 +++++++++++++++++++- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py index 32feee4..c469cad 100644 --- a/experiments/attack_defense_metric_test.py +++ b/experiments/attack_defense_metric_test.py @@ -117,6 +117,7 @@ def attack_defense_metrics(): try: # raise FileNotFoundError() gnn_model_manager.load_model_executor() + dataset = gnn_model_manager.load_train_test_split(dataset) except FileNotFoundError: gnn_model_manager.epochs = gnn_model_manager.modification.epochs = 0 train_test_split_path = gnn_model_manager.train_model(gen_dataset=dataset, steps=steps_epochs, diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 35bf1da..c18e9f8 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -1,8 +1,8 @@ +import warnings from typing import Type from base.datasets_processing import GeneralDataset - for pack in [ 'defense.GNNGuard.gnnguard', 'defense.JaccardDefense.jaccard_def', @@ -23,21 +23,91 @@ def __init__( gnn_manager, device: str = None ): - self.files_paths = None if device is None: device = "cpu" self.device = device + self.gnn_manager = gnn_manager + self.gen_dataset = gen_dataset - available_attacks = { + self.available_attacks = { "poison": True if gnn_manager.poison_attacker.name != "EmptyPoisonAttacker" else False, "evasion": True if gnn_manager.evasion_attacker.name != "EmptyEvasionAttacker" else False, "mi": True if gnn_manager.mi_attacker.name != "EmptyMIAttacker" else False, } - available_defense = { + self.available_defense = { "poison": True if gnn_manager.poison_defender.name != "EmptyEvasionDefender" else False, "evasion": True if gnn_manager.evasion_defender.name != "EmptyPoisonAttacker" else False, "mi": True if gnn_manager.mi_defender.name != "EmptyMIDefender" else False, } + self.start_attack_defense_flag_state = { + "poison_attack": self.gnn_manager.poison_attack_flag, + "evasion_attack": self.gnn_manager.evasion_attack_flag, + "mi_attack": self.gnn_manager.mi_attack_flag, + "poison_defense": self.gnn_manager.poison_defense_flag, + "evasion_defense": self.gnn_manager.evasion_defense_flag, + "mi_defense": self.gnn_manager.mi_defense_flag, + } + + def set_clear_model(self): + self.gnn_manager.poison_attack_flag = False + self.gnn_manager.evasion_attack_flag = False + self.gnn_manager.mi_attack_flag = False + self.gnn_manager.poison_defense_flag = False + self.gnn_manager.evasion_defense_flag = False + self.gnn_manager.mi_defense_flag = False + + def return_attack_defense_flags(self): + self.gnn_manager.poison_attack_flag = self.start_attack_defense_flag_state["poison_attack"] + self.gnn_manager.evasion_attack_flag = self.start_attack_defense_flag_state["evasion_attack"] + self.gnn_manager.mi_attack_flag = self.start_attack_defense_flag_state["mi_attack"] + self.gnn_manager.poison_defense_flag = self.start_attack_defense_flag_state["poison_defense"] + self.gnn_manager.evasion_defense_flag = self.start_attack_defense_flag_state["evasion_defense"] + self.gnn_manager.mi_defense_flag = self.start_attack_defense_flag_state["mi_defense"] + + def evasion_attack_pipeline( + self, + metrics_attack, + model_metrics, + steps: int, + save_model_flag: bool = True, + ): + metrics_values = {} + if self.available_attacks["evasion"]: + self.set_clear_model() + from models_builder.gnn_models import Metric + self.gnn_manager.train_model( + gen_dataset=self.gen_dataset, + steps=steps, + save_model_flag=save_model_flag, + metrics=[Metric("F1", mask='train', average=None)] + ) + metric_clean_model = self.gnn_manager.evaluate_model( + gen_dataset=self.gen_dataset, + metrics=model_metrics + ) + self.gnn_manager.evasion_attack_flag = True + self.gnn_manager.train_model( + gen_dataset=self.gen_dataset, + steps=steps, + save_model_flag=save_model_flag, + metrics=[Metric("F1", mask='train', average=None)] + ) + metric_evasion_attack_only = self.gnn_manager.evaluate_model( + gen_dataset=self.gen_dataset, + metrics=model_metrics + ) + # TODO Kirill + # metrics_values = evaluate_attacks( + # metric_clean_model, + # metric_evasion_attack_only, + # metrics_attack=metrics_attack + # ) + self.return_attack_defense_flags() + pass + else: + warnings.warn(f"Evasion attack is not available. Please set evasion attack for " + f"gnn_model_manager use def set_evasion_attacker") + return metrics_values From 2c8bbf67eb3ea9c02b604ecd8fa9a9b055d24048 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 13:15:16 +0300 Subject: [PATCH 03/11] add class AttackMetric and asr meric --- experiments/attack_defense_metric_test.py | 53 ++++++++++--------- src/models_builder/attack_defense_manager.py | 54 +++++++++++++++----- src/models_builder/attack_defense_metric.py | 51 ++++++++++++++++++ src/models_builder/gnn_models.py | 47 +++++++++++++---- 4 files changed, 160 insertions(+), 45 deletions(-) create mode 100644 src/models_builder/attack_defense_metric.py diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py index c469cad..169b625 100644 --- a/experiments/attack_defense_metric_test.py +++ b/experiments/attack_defense_metric_test.py @@ -4,6 +4,7 @@ from torch import device from models_builder.attack_defense_manager import FrameworkAttackDefenseManager +from models_builder.attack_defense_metric import AttackMetric from models_builder.models_utils import apply_decorator_to_graph_layers from src.aux.utils import POISON_ATTACK_PARAMETERS_PATH, POISON_DEFENSE_PARAMETERS_PATH, EVASION_ATTACK_PARAMETERS_PATH, \ EVASION_DEFENSE_PARAMETERS_PATH @@ -114,34 +115,40 @@ def attack_defense_metrics(): warnings.warn("Start training") dataset.train_test_split() - try: - # raise FileNotFoundError() - gnn_model_manager.load_model_executor() - dataset = gnn_model_manager.load_train_test_split(dataset) - except FileNotFoundError: - gnn_model_manager.epochs = gnn_model_manager.modification.epochs = 0 - train_test_split_path = gnn_model_manager.train_model(gen_dataset=dataset, steps=steps_epochs, - save_model_flag=save_model_flag, - metrics=[Metric("F1", mask='train', average=None)]) - - if train_test_split_path is not None: - dataset.save_train_test_mask(train_test_split_path) - train_mask, val_mask, test_mask, train_test_sizes = torch.load(train_test_split_path / 'train_test_split')[ - :] - dataset.train_mask, dataset.val_mask, dataset.test_mask = train_mask, val_mask, test_mask - data.percent_train_class, data.percent_test_class = train_test_sizes - - warnings.warn("Training was successful") - - metric_loc = gnn_model_manager.evaluate_model( - gen_dataset=dataset, metrics=[Metric("F1", mask='test', average='macro'), - Metric("Accuracy", mask='test')]) - print(metric_loc) + # try: + # # raise FileNotFoundError() + # gnn_model_manager.load_model_executor() + # dataset = gnn_model_manager.load_train_test_split(dataset) + # except FileNotFoundError: + # gnn_model_manager.epochs = gnn_model_manager.modification.epochs = 0 + # train_test_split_path = gnn_model_manager.train_model(gen_dataset=dataset, steps=steps_epochs, + # save_model_flag=save_model_flag, + # metrics=[Metric("F1", mask='train', average=None)]) + # + # if train_test_split_path is not None: + # dataset.save_train_test_mask(train_test_split_path) + # train_mask, val_mask, test_mask, train_test_sizes = torch.load(train_test_split_path / 'train_test_split')[ + # :] + # dataset.train_mask, dataset.val_mask, dataset.test_mask = train_mask, val_mask, test_mask + # data.percent_train_class, data.percent_test_class = train_test_sizes + # + # warnings.warn("Training was successful") + # + # metric_loc = gnn_model_manager.evaluate_model( + # gen_dataset=dataset, metrics=[Metric("F1", mask='test', average='macro'), + # Metric("Accuracy", mask='test')]) + # print(metric_loc) adm = FrameworkAttackDefenseManager( gen_dataset=dataset, gnn_manager=gnn_model_manager, ) + adm.evasion_attack_pipeline( + steps=steps_epochs, + save_model_flag=save_model_flag, + metrics_attack=[AttackMetric("ASR")], + mask='test' + ) if __name__ == '__main__': diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index c18e9f8..5374f9f 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -1,5 +1,7 @@ import warnings -from typing import Type +from typing import Type, Union, List + +import torch from base.datasets_processing import GeneralDataset @@ -69,13 +71,15 @@ def return_attack_defense_flags(self): def evasion_attack_pipeline( self, metrics_attack, - model_metrics, steps: int, save_model_flag: bool = True, + mask: Union[str, List[bool], torch.Tensor] = 'test', ): metrics_values = {} if self.available_attacks["evasion"]: self.set_clear_model() + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() from models_builder.gnn_models import Metric self.gnn_manager.train_model( gen_dataset=self.gen_dataset, @@ -83,31 +87,55 @@ def evasion_attack_pipeline( save_model_flag=save_model_flag, metrics=[Metric("F1", mask='train', average=None)] ) - metric_clean_model = self.gnn_manager.evaluate_model( + y_predict_clean = self.gnn_manager.run_model( gen_dataset=self.gen_dataset, - metrics=model_metrics + mask=mask, + out='logits', ) + self.gnn_manager.evasion_attack_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() self.gnn_manager.train_model( gen_dataset=self.gen_dataset, steps=steps, save_model_flag=save_model_flag, metrics=[Metric("F1", mask='train', average=None)] ) - metric_evasion_attack_only = self.gnn_manager.evaluate_model( + self.gnn_manager.call_evasion_attack( gen_dataset=self.gen_dataset, - metrics=model_metrics + mask=mask, + ) + y_predict_attack = self.gnn_manager.run_model( + gen_dataset=self.gen_dataset, + mask=mask, + out='logits', + ) + metrics_values = self.evaluate_attack_defense( + y_predict_after_attack_only=y_predict_attack, + y_predict_clean=y_predict_clean, + metrics_attack=metrics_attack, ) - # TODO Kirill - # metrics_values = evaluate_attacks( - # metric_clean_model, - # metric_evasion_attack_only, - # metrics_attack=metrics_attack - # ) self.return_attack_defense_flags() - pass + else: warnings.warn(f"Evasion attack is not available. Please set evasion attack for " f"gnn_model_manager use def set_evasion_attacker") return metrics_values + + def evaluate_attack_defense( + self, + y_predict_clean, + y_predict_after_attack_only=None, + y_predict_after_defense_only=None, + y_predict_after_attack_and_defense=None, + metrics_attack=None, + metrics_defense=None, + ): + metrics_attack_values = {} + if metrics_attack is not None and y_predict_after_attack_only is not None: + for metric in metrics_attack: + metrics_attack_values[metric.name] = metric.compute(y_predict_clean, y_predict_after_attack_only) + + return metrics_attack_values diff --git a/src/models_builder/attack_defense_metric.py b/src/models_builder/attack_defense_metric.py new file mode 100644 index 0000000..4fd2181 --- /dev/null +++ b/src/models_builder/attack_defense_metric.py @@ -0,0 +1,51 @@ +from typing import Union, List, Callable + +import sklearn +import torch + + +def asr( + y_predict_clean, + y_predict_after_attack_only, + **kwargs +): + if isinstance(y_predict_clean, torch.Tensor): + if y_predict_clean.dim() > 1: + y_predict_clean = y_predict_clean.argmax(dim=1) + y_predict_clean.cpu() + if isinstance(y_predict_after_attack_only, torch.Tensor): + if y_predict_after_attack_only.dim() > 1: + y_predict_after_attack_only = y_predict_after_attack_only.argmax(dim=1) + y_predict_after_attack_only.cpu() + print("ASR ", 1 - sklearn.metrics.accuracy_score(y_true=y_predict_clean, y_pred=y_predict_after_attack_only)) + return 1 - sklearn.metrics.accuracy_score(y_true=y_predict_clean, y_pred=y_predict_after_attack_only) + + +class AttackMetric: + available_metrics = { + 'ASR': asr, + } + + def __init__( + self, + name: str, + **kwargs + ): + self.name = name + self.kwargs = kwargs + + def compute( + self, + metrics_clean_model, + metrics_after_attack + ): + if self.name in AttackMetric.available_metrics: + return AttackMetric.available_metrics[self.name]( + metrics_clean_model, + metrics_after_attack, + **self.kwargs + ) + raise NotImplementedError() + + + diff --git a/src/models_builder/gnn_models.py b/src/models_builder/gnn_models.py index 09c0490..411f21a 100644 --- a/src/models_builder/gnn_models.py +++ b/src/models_builder/gnn_models.py @@ -951,15 +951,15 @@ def train_on_batch_full( batch, task_type: str = None ) -> torch.Tensor: - if self.mi_defender: + if self.mi_defender and self.mi_defense_flag: self.mi_defender.pre_batch() - if self.evasion_defender: + if self.evasion_defender and self.evasion_defense_flag: self.evasion_defender.pre_batch(model_manager=self, batch=batch) loss = self.train_on_batch(batch=batch, task_type=task_type) - if self.mi_defender: + if self.mi_defender and self.mi_defense_flag: self.mi_defender.post_batch() evasion_defender_dict = None - if self.evasion_defender: + if self.evasion_defender and self.evasion_defense_flag: evasion_defender_dict = self.evasion_defender.post_batch( model_manager=self, batch=batch, loss=loss, ) @@ -1093,12 +1093,12 @@ def train_model( :param metrics: list of metrics to measure at each step or at the end of training :param socket: socket to use for sending data to frontend """ - if self.poison_attacker: + if self.poison_attacker and self.poison_attack_flag: loc = self.poison_attacker.attack(gen_dataset=gen_dataset) if loc is not None: gen_dataset = loc - if self.poison_defender: + if self.poison_defender and self.poison_defense_flag: loc = self.poison_defender.defense(gen_dataset=gen_dataset) if loc is not None: gen_dataset = loc @@ -1256,8 +1256,11 @@ def evaluate_model( except KeyError: assert isinstance(mask, torch.Tensor) mask_tensor = mask - if self.evasion_attacker: - self.evasion_attacker.attack(model_manager=self, gen_dataset=gen_dataset, mask_tensor=mask_tensor) + if self.evasion_attacker and self.evasion_attack_flag: + self.call_evasion_attack( + gen_dataset=gen_dataset, + mask=mask, + ) metrics_values[mask] = {} y_pred = self.run_model(gen_dataset, mask=mask) y_true = gen_dataset.labels[mask_tensor] @@ -1265,9 +1268,35 @@ def evaluate_model( for metric in ms: metrics_values[mask][metric.name] = metric.compute(y_pred, y_true) # metrics_values[mask][metric.name] = MetricManager.compute(metric, y_pred, y_true) + if self.mi_attacker and self.mi_attack_flag: + self.call_mi_attack() + return metrics_values + + def call_evasion_attack( + self, + gen_dataset: GeneralDataset, + mask: Union[str, List[bool], torch.Tensor] = 'test', + ): + if self.evasion_attacker: + try: + mask_tensor = { + 'train': gen_dataset.train_mask.tolist(), + 'val': gen_dataset.val_mask.tolist(), + 'test': gen_dataset.test_mask.tolist(), + 'all': [True] * len(gen_dataset.labels), + }[mask] + except KeyError: + assert isinstance(mask, torch.Tensor) + mask_tensor = mask + self.evasion_attacker.attack( + model_manager=self, + gen_dataset=gen_dataset, + mask_tensor=mask_tensor + ) + + def call_mi_attack(self): if self.mi_attacker: self.mi_attacker.attack() - return metrics_values def compute_stats_data( self, From 6b51f7d5abf2631c6613aeda2f6caad04bbec265 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 13:31:50 +0300 Subject: [PATCH 04/11] add poison_attack_pipeline --- experiments/attack_defense_metric_test.py | 8 ++- src/models_builder/attack_defense_manager.py | 52 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py index 169b625..30f584a 100644 --- a/experiments/attack_defense_metric_test.py +++ b/experiments/attack_defense_metric_test.py @@ -143,7 +143,13 @@ def attack_defense_metrics(): gen_dataset=dataset, gnn_manager=gnn_model_manager, ) - adm.evasion_attack_pipeline( + # adm.evasion_attack_pipeline( + # steps=steps_epochs, + # save_model_flag=save_model_flag, + # metrics_attack=[AttackMetric("ASR")], + # mask='test' + # ) + adm.poison_attack_pipeline( steps=steps_epochs, save_model_flag=save_model_flag, metrics_attack=[AttackMetric("ASR")], diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 5374f9f..934d1c7 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -124,6 +124,58 @@ def evasion_attack_pipeline( return metrics_values + def poison_attack_pipeline( + self, + metrics_attack, + steps: int, + save_model_flag: bool = True, + mask: Union[str, List[bool], torch.Tensor] = 'test', + ): + metrics_values = {} + if self.available_attacks["poison"]: + self.set_clear_model() + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + from models_builder.gnn_models import Metric + self.gnn_manager.train_model( + gen_dataset=self.gen_dataset, + steps=steps, + save_model_flag=save_model_flag, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_clean = self.gnn_manager.run_model( + gen_dataset=self.gen_dataset, + mask=mask, + out='logits', + ) + + self.gnn_manager.poison_attack_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=self.gen_dataset, + steps=steps, + save_model_flag=save_model_flag, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_attack = self.gnn_manager.run_model( + gen_dataset=self.gen_dataset, + mask=mask, + out='logits', + ) + metrics_values = self.evaluate_attack_defense( + y_predict_after_attack_only=y_predict_attack, + y_predict_clean=y_predict_clean, + metrics_attack=metrics_attack, + ) + self.return_attack_defense_flags() + + else: + warnings.warn(f"Evasion attack is not available. Please set evasion attack for " + f"gnn_model_manager use def set_evasion_attacker") + + return metrics_values + def evaluate_attack_defense( self, y_predict_clean, From 5935bea368bf8d51290953b9fd94c8a9b96187b6 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 15:21:00 +0300 Subject: [PATCH 05/11] add def update_dictionary_in_file and save_metrics --- experiments/attack_defense_metric_test.py | 4 +- src/models_builder/attack_defense_manager.py | 76 ++++++++++++++++++-- src/models_builder/gnn_models.py | 2 +- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py index 30f584a..7bdc547 100644 --- a/experiments/attack_defense_metric_test.py +++ b/experiments/attack_defense_metric_test.py @@ -55,8 +55,8 @@ def attack_defense_metrics(): modification=ModelModificationConfig(model_ver_ind=0, epochs=steps_epochs) ) - save_model_flag = False - # save_model_flag = True + # save_model_flag = False + save_model_flag = True gnn_model_manager.gnn.to(my_device) diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 934d1c7..54797c0 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -1,3 +1,5 @@ +import json +import os import warnings from typing import Type, Union, List @@ -140,7 +142,7 @@ def poison_attack_pipeline( self.gnn_manager.train_model( gen_dataset=self.gen_dataset, steps=steps, - save_model_flag=save_model_flag, + save_model_flag=False, metrics=[Metric("F1", mask='train', average=None)] ) y_predict_clean = self.gnn_manager.run_model( @@ -163,31 +165,91 @@ def poison_attack_pipeline( mask=mask, out='logits', ) - metrics_values = self.evaluate_attack_defense( + + metrics_attack_values, _ = self.evaluate_attack_defense( y_predict_after_attack_only=y_predict_attack, y_predict_clean=y_predict_clean, metrics_attack=metrics_attack, + mask=mask, ) + if save_model_flag: + self.save_metrics( + metrics_attack_values=metrics_attack_values, + metrics_defense_values=None, + ) self.return_attack_defense_flags() - else: warnings.warn(f"Evasion attack is not available. Please set evasion attack for " f"gnn_model_manager use def set_evasion_attacker") return metrics_values - def evaluate_attack_defense( + def save_metrics( self, + metrics_attack_values: Union[dict, None] = None, + metrics_defense_values: Union[dict, None] = None, + ): + attack_metrics_file_name = 'attack_metrics.txt' + defense_metrics_file_name = 'defense_metrics.txt' + model_path_info = self.gnn_manager.model_path_info() + + if metrics_attack_values is not None: + self.update_dictionary_in_file( + file_path=model_path_info / attack_metrics_file_name, + new_dict=metrics_attack_values + ) + + if metrics_defense_values is not None: + self.update_dictionary_in_file( + file_path=model_path_info / defense_metrics_file_name, + new_dict=metrics_defense_values + ) + + @staticmethod + def evaluate_attack_defense( + # self, y_predict_clean, + mask, y_predict_after_attack_only=None, y_predict_after_defense_only=None, y_predict_after_attack_and_defense=None, metrics_attack=None, metrics_defense=None, ): - metrics_attack_values = {} + metrics_attack_values = {mask: {}} + metrics_defense_values = {mask: {}} if metrics_attack is not None and y_predict_after_attack_only is not None: for metric in metrics_attack: - metrics_attack_values[metric.name] = metric.compute(y_predict_clean, y_predict_after_attack_only) + metrics_attack_values[mask][metric.name] = metric.compute(y_predict_clean, y_predict_after_attack_only) + + return metrics_attack_values, metrics_defense_values + + @staticmethod + def update_dictionary_in_file(file_path, new_dict): + def tensor_to_str(key): + if isinstance(key, torch.Tensor): + return key.tolist() + return key + + def str_to_tensor(key): + if isinstance(key, list): + return torch.tensor(key, dtype=torch.bool) + return key + + def prepare_dict_for_json(d): + return {tensor_to_str(k): v for k, v in d.items()} + + def restore_dict_from_json(d): + return {str_to_tensor(k): v for k, v in d.items()} + + if os.path.exists(file_path): + with open(file_path, "r") as f: + file_dict = restore_dict_from_json(json.load(f)) + else: + file_dict = {} + + for key, value in new_dict.items(): + file_dict[key] = value - return metrics_attack_values + with open(file_path, "w") as f: + json.dump(prepare_dict_for_json(file_dict), f, indent=2) diff --git a/src/models_builder/gnn_models.py b/src/models_builder/gnn_models.py index 411f21a..9674224 100644 --- a/src/models_builder/gnn_models.py +++ b/src/models_builder/gnn_models.py @@ -1082,7 +1082,7 @@ def train_model( steps=None, metrics: List[Metric] = None, socket: SocketIO = None - ) -> None: + ) -> Union[str, Path]: """ Convenient train method. From c259b342da96941bbbbc8fba7fda694cf81966c0 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 15:36:06 +0300 Subject: [PATCH 06/11] add attack metric save in evasion_attack_pipeline --- src/models_builder/attack_defense_manager.py | 10 +++++++++- src/models_builder/attack_defense_metric.py | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 54797c0..2e31938 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -113,11 +113,18 @@ def evasion_attack_pipeline( mask=mask, out='logits', ) - metrics_values = self.evaluate_attack_defense( + + metrics_attack_values, _ = self.evaluate_attack_defense( y_predict_after_attack_only=y_predict_attack, y_predict_clean=y_predict_clean, metrics_attack=metrics_attack, + mask=mask, ) + if save_model_flag: + self.save_metrics( + metrics_attack_values=metrics_attack_values, + metrics_defense_values=None, + ) self.return_attack_defense_flags() else: @@ -252,4 +259,5 @@ def restore_dict_from_json(d): file_dict[key] = value with open(file_path, "w") as f: + print(json.dumps(prepare_dict_for_json(file_dict), indent=2)) json.dump(prepare_dict_for_json(file_dict), f, indent=2) diff --git a/src/models_builder/attack_defense_metric.py b/src/models_builder/attack_defense_metric.py index 4fd2181..dfe0c8a 100644 --- a/src/models_builder/attack_defense_metric.py +++ b/src/models_builder/attack_defense_metric.py @@ -17,7 +17,6 @@ def asr( if y_predict_after_attack_only.dim() > 1: y_predict_after_attack_only = y_predict_after_attack_only.argmax(dim=1) y_predict_after_attack_only.cpu() - print("ASR ", 1 - sklearn.metrics.accuracy_score(y_true=y_predict_clean, y_pred=y_predict_after_attack_only)) return 1 - sklearn.metrics.accuracy_score(y_true=y_predict_clean, y_pred=y_predict_after_attack_only) From 12ee2790657ab8a251b30f72e4c4f2e5bc05da52 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 16:43:07 +0300 Subject: [PATCH 07/11] cosmetic changes + fix update_dictionary_in_file --- src/models_builder/attack_defense_manager.py | 46 ++++++++++++-------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 2e31938..cf782f3 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -1,8 +1,10 @@ import json import os import warnings -from typing import Type, Union, List +from pathlib import Path +from typing import Type, Union, List, Any +import numpy as np import torch from base.datasets_processing import GeneralDataset @@ -54,7 +56,9 @@ def __init__( "mi_defense": self.gnn_manager.mi_defense_flag, } - def set_clear_model(self): + def set_clear_model( + self + ) -> None: self.gnn_manager.poison_attack_flag = False self.gnn_manager.evasion_attack_flag = False self.gnn_manager.mi_attack_flag = False @@ -62,7 +66,9 @@ def set_clear_model(self): self.gnn_manager.evasion_defense_flag = False self.gnn_manager.mi_defense_flag = False - def return_attack_defense_flags(self): + def return_attack_defense_flags( + self + ) -> None: self.gnn_manager.poison_attack_flag = self.start_attack_defense_flag_state["poison_attack"] self.gnn_manager.evasion_attack_flag = self.start_attack_defense_flag_state["evasion_attack"] self.gnn_manager.mi_attack_flag = self.start_attack_defense_flag_state["mi_attack"] @@ -72,11 +78,11 @@ def return_attack_defense_flags(self): def evasion_attack_pipeline( self, - metrics_attack, + metrics_attack: List, steps: int, save_model_flag: bool = True, mask: Union[str, List[bool], torch.Tensor] = 'test', - ): + ) -> dict: metrics_values = {} if self.available_attacks["evasion"]: self.set_clear_model() @@ -135,11 +141,11 @@ def evasion_attack_pipeline( def poison_attack_pipeline( self, - metrics_attack, + metrics_attack: List, steps: int, save_model_flag: bool = True, mask: Union[str, List[bool], torch.Tensor] = 'test', - ): + ) -> dict: metrics_values = {} if self.available_attacks["poison"]: self.set_clear_model() @@ -215,13 +221,13 @@ def save_metrics( @staticmethod def evaluate_attack_defense( # self, - y_predict_clean, - mask, - y_predict_after_attack_only=None, - y_predict_after_defense_only=None, - y_predict_after_attack_and_defense=None, - metrics_attack=None, - metrics_defense=None, + y_predict_clean: Union[List, torch.Tensor, np.array], + mask: Union[str, torch.Tensor], + y_predict_after_attack_only: Union[List, torch.Tensor, np.array, None] = None, + y_predict_after_defense_only: Union[List, torch.Tensor, np.array, None] = None, + y_predict_after_attack_and_defense: Union[List, torch.Tensor, np.array, None] = None, + metrics_attack: Union[List, None] = None, + metrics_defense: Union[List, None] = None, ): metrics_attack_values = {mask: {}} metrics_defense_values = {mask: {}} @@ -232,7 +238,10 @@ def evaluate_attack_defense( return metrics_attack_values, metrics_defense_values @staticmethod - def update_dictionary_in_file(file_path, new_dict): + def update_dictionary_in_file( + file_path: Union[str, Path], + new_dict: dict + ) -> None: def tensor_to_str(key): if isinstance(key, torch.Tensor): return key.tolist() @@ -255,8 +264,11 @@ def restore_dict_from_json(d): else: file_dict = {} - for key, value in new_dict.items(): - file_dict[key] = value + for mask, metrics in new_dict.items(): + for metric, value in metrics.items(): + if mask not in file_dict: + file_dict[mask] = {} + file_dict[mask][metric] = value with open(file_path, "w") as f: print(json.dumps(prepare_dict_for_json(file_dict), indent=2)) From 255f8515f31fc11a6e218b19e2450c196796f768 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 16:49:45 +0300 Subject: [PATCH 08/11] add class DefenseMetric --- src/models_builder/attack_defense_manager.py | 16 ++++++++- src/models_builder/attack_defense_metric.py | 36 +++++++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index cf782f3..3b70097 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -233,7 +233,21 @@ def evaluate_attack_defense( metrics_defense_values = {mask: {}} if metrics_attack is not None and y_predict_after_attack_only is not None: for metric in metrics_attack: - metrics_attack_values[mask][metric.name] = metric.compute(y_predict_clean, y_predict_after_attack_only) + metrics_attack_values[mask][metric.name] = metric.compute( + y_predict_clean=y_predict_clean, + y_predict_after_attack_only=y_predict_after_attack_only + ) + if ( + metrics_defense is not None + and y_predict_after_defense_only is not None + and y_predict_after_attack_and_defense is not None + ): + for metric in metrics_defense: + metrics_defense_values[mask][metric.name] = metric.compute( + y_predict_clean=y_predict_clean, + y_predict_after_defense_only=y_predict_after_defense_only, + y_predict_after_attack_and_defense=y_predict_after_attack_and_defense + ) return metrics_attack_values, metrics_defense_values diff --git a/src/models_builder/attack_defense_metric.py b/src/models_builder/attack_defense_metric.py index dfe0c8a..27da020 100644 --- a/src/models_builder/attack_defense_metric.py +++ b/src/models_builder/attack_defense_metric.py @@ -35,13 +35,41 @@ def __init__( def compute( self, - metrics_clean_model, - metrics_after_attack + y_predict_clean, + y_predict_after_attack_only, ): if self.name in AttackMetric.available_metrics: return AttackMetric.available_metrics[self.name]( - metrics_clean_model, - metrics_after_attack, + y_predict_clean=y_predict_clean, + y_predict_after_attack_only=y_predict_after_attack_only, + **self.kwargs + ) + raise NotImplementedError() + + +class DefenseMetric: + available_metrics = { + } + + def __init__( + self, + name: str, + **kwargs + ): + self.name = name + self.kwargs = kwargs + + def compute( + self, + y_predict_clean, + y_predict_after_defense_only, + y_predict_after_attack_and_defense, + ): + if self.name in AttackMetric.available_metrics: + return AttackMetric.available_metrics[self.name]( + y_predict_clean=y_predict_clean, + y_predict_after_defense_only=y_predict_after_defense_only, + y_predict_after_attack_and_defense=y_predict_after_attack_and_defense, **self.kwargs ) raise NotImplementedError() From a97420080834bb823f64f54464ad8f4725e1b2c6 Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 17:55:04 +0300 Subject: [PATCH 09/11] add deepcopy in attack_pipelines --- experiments/attack_defense_metric_test.py | 3 ++- src/models_builder/attack_defense_manager.py | 23 +++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py index 7bdc547..e2bd79e 100644 --- a/experiments/attack_defense_metric_test.py +++ b/experiments/attack_defense_metric_test.py @@ -1,3 +1,4 @@ +import copy import warnings import torch @@ -140,7 +141,7 @@ def attack_defense_metrics(): # print(metric_loc) adm = FrameworkAttackDefenseManager( - gen_dataset=dataset, + gen_dataset=copy.deepcopy(dataset), gnn_manager=gnn_model_manager, ) # adm.evasion_attack_pipeline( diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 3b70097..4e0f98d 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -1,3 +1,4 @@ +import copy import json import os import warnings @@ -89,14 +90,15 @@ def evasion_attack_pipeline( self.gnn_manager.modification.epochs = 0 self.gnn_manager.gnn.reset_parameters() from models_builder.gnn_models import Metric + local_gen_dataset_copy = copy.deepcopy(self.gen_dataset) self.gnn_manager.train_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, steps=steps, - save_model_flag=save_model_flag, + save_model_flag=False, metrics=[Metric("F1", mask='train', average=None)] ) y_predict_clean = self.gnn_manager.run_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, mask=mask, out='logits', ) @@ -105,17 +107,17 @@ def evasion_attack_pipeline( self.gnn_manager.modification.epochs = 0 self.gnn_manager.gnn.reset_parameters() self.gnn_manager.train_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, steps=steps, save_model_flag=save_model_flag, metrics=[Metric("F1", mask='train', average=None)] ) self.gnn_manager.call_evasion_attack( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, mask=mask, ) y_predict_attack = self.gnn_manager.run_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, mask=mask, out='logits', ) @@ -152,14 +154,15 @@ def poison_attack_pipeline( self.gnn_manager.modification.epochs = 0 self.gnn_manager.gnn.reset_parameters() from models_builder.gnn_models import Metric + local_gen_dataset_copy = copy.deepcopy(self.gen_dataset) self.gnn_manager.train_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, steps=steps, save_model_flag=False, metrics=[Metric("F1", mask='train', average=None)] ) y_predict_clean = self.gnn_manager.run_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, mask=mask, out='logits', ) @@ -168,13 +171,13 @@ def poison_attack_pipeline( self.gnn_manager.modification.epochs = 0 self.gnn_manager.gnn.reset_parameters() self.gnn_manager.train_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, steps=steps, save_model_flag=save_model_flag, metrics=[Metric("F1", mask='train', average=None)] ) y_predict_attack = self.gnn_manager.run_model( - gen_dataset=self.gen_dataset, + gen_dataset=local_gen_dataset_copy, mask=mask, out='logits', ) From 9195446c6f562c69a8eb7ca2a76aebd1869343df Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 18:42:39 +0300 Subject: [PATCH 10/11] add 3 metrics and add poison_defense_pipeline --- experiments/attack_defense_metric_test.py | 13 +- src/models_builder/attack_defense_manager.py | 119 ++++++++++++++++++- src/models_builder/attack_defense_metric.py | 89 ++++++++++++-- 3 files changed, 206 insertions(+), 15 deletions(-) diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py index e2bd79e..408b77e 100644 --- a/experiments/attack_defense_metric_test.py +++ b/experiments/attack_defense_metric_test.py @@ -5,7 +5,7 @@ from torch import device from models_builder.attack_defense_manager import FrameworkAttackDefenseManager -from models_builder.attack_defense_metric import AttackMetric +from models_builder.attack_defense_metric import AttackMetric, DefenseMetric from models_builder.models_utils import apply_decorator_to_graph_layers from src.aux.utils import POISON_ATTACK_PARAMETERS_PATH, POISON_DEFENSE_PARAMETERS_PATH, EVASION_ATTACK_PARAMETERS_PATH, \ EVASION_DEFENSE_PARAMETERS_PATH @@ -150,10 +150,17 @@ def attack_defense_metrics(): # metrics_attack=[AttackMetric("ASR")], # mask='test' # ) - adm.poison_attack_pipeline( + # adm.poison_attack_pipeline( + # steps=steps_epochs, + # save_model_flag=save_model_flag, + # metrics_attack=[AttackMetric("ASR")], + # mask='test' + # ) + adm.poison_defense_pipeline( steps=steps_epochs, save_model_flag=save_model_flag, - metrics_attack=[AttackMetric("ASR")], + metrics_attack=[AttackMetric("ASR"), AttackMetric("AuccAttackDiff"),], + metrics_defense=[DefenseMetric("AuccDefenseCleanDiff"), DefenseMetric("AuccDefenseAttackDiff"), ], mask='test' ) diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 4e0f98d..4be401c 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -200,6 +200,101 @@ def poison_attack_pipeline( return metrics_values + def poison_defense_pipeline( + self, + metrics_attack: List, + metrics_defense: List, + steps: int, + save_model_flag: bool = True, + mask: Union[str, List[bool], torch.Tensor] = 'test', + ) -> dict: + metrics_values = {} + if self.available_attacks["poison"] and self.available_defense["poison"]: + from models_builder.gnn_models import Metric + local_gen_dataset_copy = copy.deepcopy(self.gen_dataset) + self.set_clear_model() + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=False, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_clean = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + self.gnn_manager.poison_defense_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=False, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_after_defense_only = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + local_gen_dataset_copy = copy.deepcopy(self.gen_dataset) + self.gnn_manager.poison_defense_flag = False + self.gnn_manager.poison_attack_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=False, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_after_attack_only = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + self.gnn_manager.poison_defense_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=save_model_flag, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_after_attack_and_defense = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + metrics_attack_values, metrics_defense_values = self.evaluate_attack_defense( + y_predict_after_attack_only=y_predict_after_attack_only, + y_predict_clean=y_predict_clean, + y_predict_after_defense_only=y_predict_after_defense_only, + y_predict_after_attack_and_defense=y_predict_after_attack_and_defense, + metrics_attack=metrics_attack, + metrics_defense=metrics_defense, + mask=mask, + ) + if save_model_flag: + self.save_metrics( + metrics_attack_values=metrics_attack_values, + metrics_defense_values=metrics_defense_values, + ) + self.return_attack_defense_flags() + else: + warnings.warn(f"Evasion attack is not available. Please set evasion attack for " + f"gnn_model_manager use def set_evasion_attacker") + + return metrics_values + def save_metrics( self, metrics_attack_values: Union[dict, None] = None, @@ -221,9 +316,8 @@ def save_metrics( new_dict=metrics_defense_values ) - @staticmethod def evaluate_attack_defense( - # self, + self, y_predict_clean: Union[List, torch.Tensor, np.array], mask: Union[str, torch.Tensor], y_predict_after_attack_only: Union[List, torch.Tensor, np.array, None] = None, @@ -232,13 +326,26 @@ def evaluate_attack_defense( metrics_attack: Union[List, None] = None, metrics_defense: Union[List, None] = None, ): + + try: + mask_tensor = { + 'train': self.gen_dataset.train_mask.tolist(), + 'val': self.gen_dataset.val_mask.tolist(), + 'test': self.gen_dataset.test_mask.tolist(), + 'all': [True] * len(self.gen_dataset.labels), + }[mask] + except KeyError: + assert isinstance(mask, torch.Tensor) + mask_tensor = mask + y_true = copy.deepcopy(self.gen_dataset.labels[mask_tensor]) metrics_attack_values = {mask: {}} metrics_defense_values = {mask: {}} if metrics_attack is not None and y_predict_after_attack_only is not None: for metric in metrics_attack: metrics_attack_values[mask][metric.name] = metric.compute( y_predict_clean=y_predict_clean, - y_predict_after_attack_only=y_predict_after_attack_only + y_predict_after_attack_only=y_predict_after_attack_only, + y_true=y_true, ) if ( metrics_defense is not None @@ -248,10 +355,12 @@ def evaluate_attack_defense( for metric in metrics_defense: metrics_defense_values[mask][metric.name] = metric.compute( y_predict_clean=y_predict_clean, + y_predict_after_attack_only=y_predict_after_attack_only, y_predict_after_defense_only=y_predict_after_defense_only, - y_predict_after_attack_and_defense=y_predict_after_attack_and_defense + y_predict_after_attack_and_defense=y_predict_after_attack_and_defense, + y_true=y_true, ) - + print("!!!! ", metrics_attack_values, metrics_defense_values) return metrics_attack_values, metrics_defense_values @staticmethod diff --git a/src/models_builder/attack_defense_metric.py b/src/models_builder/attack_defense_metric.py index 27da020..43cfbee 100644 --- a/src/models_builder/attack_defense_metric.py +++ b/src/models_builder/attack_defense_metric.py @@ -20,9 +20,79 @@ def asr( return 1 - sklearn.metrics.accuracy_score(y_true=y_predict_clean, y_pred=y_predict_after_attack_only) +# TODO Kirill, change for any classic metric +def aucc_change_attack( + y_predict_clean, + y_predict_after_attack_only, + y_true, + **kwargs +): + if isinstance(y_predict_clean, torch.Tensor): + if y_predict_clean.dim() > 1: + y_predict_clean = y_predict_clean.argmax(dim=1) + y_predict_clean.cpu() + if isinstance(y_predict_after_attack_only, torch.Tensor): + if y_predict_after_attack_only.dim() > 1: + y_predict_after_attack_only = y_predict_after_attack_only.argmax(dim=1) + y_predict_after_attack_only.cpu() + if isinstance(y_true, torch.Tensor): + if y_true.dim() > 1: + y_true = y_true.argmax(dim=1) + y_true.cpu() + return (sklearn.metrics.accuracy_score(y_true=y_true, y_pred=y_predict_clean) - + sklearn.metrics.accuracy_score(y_true=y_true, y_pred=y_predict_after_attack_only)) + + +# TODO Kirill, change for any classic metric +def aucc_change_defense_only( + y_predict_clean, + y_predict_after_defense_only, + y_true, + **kwargs +): + if isinstance(y_predict_clean, torch.Tensor): + if y_predict_clean.dim() > 1: + y_predict_clean = y_predict_clean.argmax(dim=1) + y_predict_clean.cpu() + if isinstance(y_predict_after_defense_only, torch.Tensor): + if y_predict_after_defense_only.dim() > 1: + y_predict_after_defense_only = y_predict_after_defense_only.argmax(dim=1) + y_predict_after_defense_only.cpu() + if isinstance(y_true, torch.Tensor): + if y_true.dim() > 1: + y_true = y_true.argmax(dim=1) + y_true.cpu() + return (sklearn.metrics.accuracy_score(y_true=y_true, y_pred=y_predict_clean) - + sklearn.metrics.accuracy_score(y_true=y_true, y_pred=y_predict_after_defense_only)) + + +# TODO Kirill, change for any classic metric +def aucc_change_defense_with_attack( + y_predict_after_attack_only, + y_predict_after_attack_and_defense, + y_true, + **kwargs +): + if isinstance(y_predict_after_attack_only, torch.Tensor): + if y_predict_after_attack_only.dim() > 1: + y_predict_after_attack_only = y_predict_after_attack_only.argmax(dim=1) + y_predict_after_attack_only.cpu() + if isinstance(y_predict_after_attack_and_defense, torch.Tensor): + if y_predict_after_attack_and_defense.dim() > 1: + y_predict_after_attack_and_defense = y_predict_after_attack_and_defense.argmax(dim=1) + y_predict_after_attack_and_defense.cpu() + if isinstance(y_true, torch.Tensor): + if y_true.dim() > 1: + y_true = y_true.argmax(dim=1) + y_true.cpu() + return (sklearn.metrics.accuracy_score(y_true=y_true, y_pred=y_predict_after_attack_and_defense) - + sklearn.metrics.accuracy_score(y_true=y_true, y_pred=y_predict_after_attack_only)) + + class AttackMetric: available_metrics = { - 'ASR': asr, + "ASR": asr, + "AuccAttackDiff": aucc_change_attack, } def __init__( @@ -37,11 +107,13 @@ def compute( self, y_predict_clean, y_predict_after_attack_only, + y_true, ): if self.name in AttackMetric.available_metrics: return AttackMetric.available_metrics[self.name]( y_predict_clean=y_predict_clean, y_predict_after_attack_only=y_predict_after_attack_only, + y_true=y_true, **self.kwargs ) raise NotImplementedError() @@ -49,6 +121,8 @@ def compute( class DefenseMetric: available_metrics = { + "AuccDefenseCleanDiff": aucc_change_defense_only, + "AuccDefenseAttackDiff": aucc_change_defense_with_attack, } def __init__( @@ -62,17 +136,18 @@ def __init__( def compute( self, y_predict_clean, + y_predict_after_attack_only, y_predict_after_defense_only, y_predict_after_attack_and_defense, + y_true, ): - if self.name in AttackMetric.available_metrics: - return AttackMetric.available_metrics[self.name]( + if self.name in DefenseMetric.available_metrics: + return DefenseMetric.available_metrics[self.name]( y_predict_clean=y_predict_clean, y_predict_after_defense_only=y_predict_after_defense_only, + y_predict_after_attack_only=y_predict_after_attack_only, y_predict_after_attack_and_defense=y_predict_after_attack_and_defense, + y_true=y_true, **self.kwargs ) - raise NotImplementedError() - - - + raise NotImplementedError(f"Metric {self.name} is not implemented") From 0df1ec139733ddc416b96429417d26253eeaad7e Mon Sep 17 00:00:00 2001 From: "lukyanov_kirya@bk.ru" Date: Tue, 3 Dec 2024 18:48:52 +0300 Subject: [PATCH 11/11] add evasion_defense_pipeline --- experiments/attack_defense_metric_test.py | 2 +- src/models_builder/attack_defense_manager.py | 100 ++++++++++++++++++- src/models_builder/attack_defense_metric.py | 39 ++++---- 3 files changed, 118 insertions(+), 23 deletions(-) diff --git a/experiments/attack_defense_metric_test.py b/experiments/attack_defense_metric_test.py index 408b77e..9655d5f 100644 --- a/experiments/attack_defense_metric_test.py +++ b/experiments/attack_defense_metric_test.py @@ -156,7 +156,7 @@ def attack_defense_metrics(): # metrics_attack=[AttackMetric("ASR")], # mask='test' # ) - adm.poison_defense_pipeline( + adm.evasion_defense_pipeline( steps=steps_epochs, save_model_flag=save_model_flag, metrics_attack=[AttackMetric("ASR"), AttackMetric("AuccAttackDiff"),], diff --git a/src/models_builder/attack_defense_manager.py b/src/models_builder/attack_defense_manager.py index 4be401c..2193623 100644 --- a/src/models_builder/attack_defense_manager.py +++ b/src/models_builder/attack_defense_manager.py @@ -141,6 +141,101 @@ def evasion_attack_pipeline( return metrics_values + def evasion_defense_pipeline( + self, + metrics_attack: List, + metrics_defense: List, + steps: int, + save_model_flag: bool = True, + mask: Union[str, List[bool], torch.Tensor] = 'test', + ) -> dict: + metrics_values = {} + if self.available_attacks["evasion"] and self.available_defense["evasion"]: + from models_builder.gnn_models import Metric + local_gen_dataset_copy = copy.deepcopy(self.gen_dataset) + self.set_clear_model() + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=False, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_clean = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + self.gnn_manager.evasion_defense_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=False, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_after_defense_only = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + local_gen_dataset_copy = copy.deepcopy(self.gen_dataset) + self.gnn_manager.evasion_defense_flag = False + self.gnn_manager.evasion_attack_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=False, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_after_attack_only = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + self.gnn_manager.evasion_defense_flag = True + self.gnn_manager.modification.epochs = 0 + self.gnn_manager.gnn.reset_parameters() + self.gnn_manager.train_model( + gen_dataset=local_gen_dataset_copy, + steps=steps, + save_model_flag=save_model_flag, + metrics=[Metric("F1", mask='train', average=None)] + ) + y_predict_after_attack_and_defense = self.gnn_manager.run_model( + gen_dataset=local_gen_dataset_copy, + mask=mask, + out='logits', + ) + + metrics_attack_values, metrics_defense_values = self.evaluate_attack_defense( + y_predict_after_attack_only=y_predict_after_attack_only, + y_predict_clean=y_predict_clean, + y_predict_after_defense_only=y_predict_after_defense_only, + y_predict_after_attack_and_defense=y_predict_after_attack_and_defense, + metrics_attack=metrics_attack, + metrics_defense=metrics_defense, + mask=mask, + ) + if save_model_flag: + self.save_metrics( + metrics_attack_values=metrics_attack_values, + metrics_defense_values=metrics_defense_values, + ) + self.return_attack_defense_flags() + else: + warnings.warn(f"Evasion attack and defense is not available. Please set evasion attack for " + f"gnn_model_manager use def set_evasion_attacker") + + return metrics_values + def poison_attack_pipeline( self, metrics_attack: List, @@ -195,7 +290,7 @@ def poison_attack_pipeline( ) self.return_attack_defense_flags() else: - warnings.warn(f"Evasion attack is not available. Please set evasion attack for " + warnings.warn(f"Poison attack is not available. Please set evasion attack for " f"gnn_model_manager use def set_evasion_attacker") return metrics_values @@ -290,7 +385,7 @@ def poison_defense_pipeline( ) self.return_attack_defense_flags() else: - warnings.warn(f"Evasion attack is not available. Please set evasion attack for " + warnings.warn(f"Poison attack and defense is not available. Please set evasion attack for " f"gnn_model_manager use def set_evasion_attacker") return metrics_values @@ -360,7 +455,6 @@ def evaluate_attack_defense( y_predict_after_attack_and_defense=y_predict_after_attack_and_defense, y_true=y_true, ) - print("!!!! ", metrics_attack_values, metrics_defense_values) return metrics_attack_values, metrics_defense_values @staticmethod diff --git a/src/models_builder/attack_defense_metric.py b/src/models_builder/attack_defense_metric.py index 43cfbee..dadaab2 100644 --- a/src/models_builder/attack_defense_metric.py +++ b/src/models_builder/attack_defense_metric.py @@ -1,12 +1,13 @@ -from typing import Union, List, Callable +from typing import Union, List, Callable, Any +import numpy as np import sklearn import torch def asr( - y_predict_clean, - y_predict_after_attack_only, + y_predict_clean: Union[List, torch.Tensor, np.array], + y_predict_after_attack_only: Union[List, torch.Tensor, np.array], **kwargs ): if isinstance(y_predict_clean, torch.Tensor): @@ -22,8 +23,8 @@ def asr( # TODO Kirill, change for any classic metric def aucc_change_attack( - y_predict_clean, - y_predict_after_attack_only, + y_predict_clean: Union[List, torch.Tensor, np.array], + y_predict_after_attack_only: Union[List, torch.Tensor, np.array], y_true, **kwargs ): @@ -45,9 +46,9 @@ def aucc_change_attack( # TODO Kirill, change for any classic metric def aucc_change_defense_only( - y_predict_clean, - y_predict_after_defense_only, - y_true, + y_predict_clean: Union[List, torch.Tensor, np.array], + y_predict_after_defense_only: Union[List, torch.Tensor, np.array], + y_true: Union[List, torch.Tensor, np.array], **kwargs ): if isinstance(y_predict_clean, torch.Tensor): @@ -68,9 +69,9 @@ def aucc_change_defense_only( # TODO Kirill, change for any classic metric def aucc_change_defense_with_attack( - y_predict_after_attack_only, - y_predict_after_attack_and_defense, - y_true, + y_predict_after_attack_only: Union[List, torch.Tensor, np.array], + y_predict_after_attack_and_defense: Union[List, torch.Tensor, np.array], + y_true: Union[List, torch.Tensor, np.array], **kwargs ): if isinstance(y_predict_after_attack_only, torch.Tensor): @@ -105,9 +106,9 @@ def __init__( def compute( self, - y_predict_clean, - y_predict_after_attack_only, - y_true, + y_predict_clean: Union[List, torch.Tensor, np.array, None], + y_predict_after_attack_only: Union[List, torch.Tensor, np.array, None], + y_true: Union[List, torch.Tensor, np.array, None], ): if self.name in AttackMetric.available_metrics: return AttackMetric.available_metrics[self.name]( @@ -135,11 +136,11 @@ def __init__( def compute( self, - y_predict_clean, - y_predict_after_attack_only, - y_predict_after_defense_only, - y_predict_after_attack_and_defense, - y_true, + y_predict_clean: Union[List, torch.Tensor, np.array, None], + y_predict_after_attack_only: Union[List, torch.Tensor, np.array, None], + y_predict_after_defense_only: Union[List, torch.Tensor, np.array, None], + y_predict_after_attack_and_defense: Union[List, torch.Tensor, np.array, None], + y_true: Union[List, torch.Tensor, np.array, None], ): if self.name in DefenseMetric.available_metrics: return DefenseMetric.available_metrics[self.name](