Skip to content

Commit

Permalink
Attack on X by Explain?
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeratt committed Oct 31, 2024
1 parent 5c771fb commit fb68f5e
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 40 deletions.
5 changes: 5 additions & 0 deletions experiments/Cora_reverse
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0.29
0.3
0.36
0.31
0.26
2 changes: 1 addition & 1 deletion experiments/EAttack_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def test():
mask = Metric.create_mask_by_target_list(y_true=dataset.labels, target_list=[i])

evasion_attack_config = ConfigPattern(
_class_name="EAttackRandom",
_class_name="EAttack",
_import_path=EVASION_ATTACK_PARAMETERS_PATH,
_config_class="EvasionAttackConfig",
_config_kwargs={
Expand Down
5 changes: 5 additions & 0 deletions experiments/cora_reverse_5
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0.37
0.4
0.43
0.42
0.43
5 changes: 5 additions & 0 deletions experiments/cora_reverse_random
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0.29
0.29
0.29
0.31
0.28
24 changes: 24 additions & 0 deletions metainfo/evasion_attack_parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@
"direct": ["direct", "bool", true, {}, "Indicates whether to directly modify edges/features of the node attacked or only those of influencers"],
"n_influencers": ["n_influencers", "int", 0, {"min": 0, "step": 1}, "Number of influencing nodes. Will be ignored if direct is True"]
},
"EAttack": {
"attack_size": ["Attack size (edge)", "float", 0.15, {"min": 0, "max": 1, "step": 0.01}, "Percent of nodes to be rewired"],
"targeted": ["Targeted attack", "bool", true, {}, "Whether attack targeted or not"],
"max_rewire": ["Max 2-hop node to rewire for target", "int", 20, {"min": 1, "step": 1}, "Not more than this amount of node from 2-hop neighbourhood will be rewired"],
"random_rewire": ["Random rewire", "bool", false, {}, "Rewire based on random, not on explanation (for comparison)"],
"attack_features": ["Attack features", "bool", true, {}, "Whether features to be attacked or not"],
"attack_edges": ["Attack edges", "bool", false, {}, "Whether edges to be attacked or not"],
"edge_mode": ["Edge attack type", "string", "rewire", ["remove", "add", "rewire"], "What to do with edges: remove or add or rewire (add one and remove another)"],
"features_mode": ["Feature attack type", "string", "reverse", ["reverse","drop"], "What to do with features: drop or reverse (binary)"],
"edge_prob": ["Probability to attack edge", "float", 0.3, {"min": 0, "max": 1, "step": 0.01}, "Probability of drop/add/rewire edge"],
"feature_prob": ["Probability to attack feature", "float", 0.05, {"min": 0, "max": 1, "step": 0.01}, "Probability of reverse/drop feature"]
},
"EAttackRandom": {
"attack_size": ["Attack size (edge)", "float", 0.15, {"min": 0, "max": 1, "step": 0.01}, "Percent of nodes to be rewired"],
"targeted": ["Targeted attack", "bool", true, {}, "Whether attack targeted or not"],
"max_rewire": ["Max 2-hop node to rewire for target", "int", 20, {"min": 1, "step": 1}, "Not more than this amount of node from 2-hop neighbourhood will be rewired"],
"random_rewire": ["Random rewire", "bool", true, {}, "Rewire based on random, not on explanation (for comparison)"],
"attack_features": ["Attack features", "bool", true, {}, "Whether features to be attacked or not"],
"attack_edges": ["Attack edges", "bool", false, {}, "Whether edges to be attacked or not"],
"edge_mode": ["Edge attack type", "string", "rewire", ["remove", "add", "rewire"], "What to do with edges: remove or add or rewire (add one and remove another)"],
"features_mode": ["Feature attack type", "string", "reverse", ["reverse","drop"], "What to do with features: drop or reverse (binary)"],
"edge_prob": ["Probability to attack", "float", 0.3, {"min": 0, "max": 1, "step": 0.01}, "Probability of drop/add/rewire edge"],
"feature_prob": ["Probability to attack feature", "float", 0.05, {"min": 0, "max": 1, "step": 0.01}, "Probability of reverse/drop feature"]
},
"QAttack": {
"population_size": ["Population size", "int", 50, {"min": 1, "step": 1}, "Number of genes in population"],
"individual_size": ["Individual size", "int", 30, {"min": 1, "step": 1}, "Number of rewiring operations within one gene"],
Expand Down
2 changes: 1 addition & 1 deletion metainfo/explainers_init_parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"GNNExplainer(torch-geom)": {
"epochs": ["Epochs","int",100,{"min": 1},"The number of epochs to train"],
"lr": ["Learn rate","float",0.01,{"min": 0, "step": 0.0001},"The learning rate to apply"],
"node_mask_type": ["Node mask","string","object",["None","object","common_attributes","attributes"],"The type of mask to apply on nodes"],
"node_mask_type": ["Node mask","string","common_attributes",["None","object","common_attributes","attributes"],"The type of mask to apply on nodes"],
"edge_mask_type": ["Edge mask","string","object",["None","object","common_attributes","attributes"],"The type of mask to apply on edges"],
"mode": ["Mode","string","multiclass_classification",["binary_classification","multiclass_classification","regression"],"The mode of the model"],
"return_type": ["Model return","string","log_probs",["raw","prob","log_probs"],"Denotes the type of output from model. Valid inputs are 'log_probs' (the model returns the logarithm of probabilities), 'prob' (the model returns probabilities), 'raw' (the model returns raw scores)"],
Expand Down
88 changes: 50 additions & 38 deletions src/attacks/EAttack/experimental_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class EAttack(EvasionAttacker):
name = "EAttack"

def __init__(self, explainer, run_config, attack_size, attack_inds, targeted, max_rewire, random_rewire,
attack_edges, attack_features, edge_mode, features_mode, edge_prob, **kwargs):
attack_edges, attack_features, edge_mode, features_mode, edge_prob, feature_prob, **kwargs):
super().__init__(**kwargs)
self.explainer = explainer
self.run_config = run_config
Expand All @@ -43,6 +43,7 @@ def __init__(self, explainer, run_config, attack_size, attack_inds, targeted, ma
self.edge_mode = edge_mode
self.features_mode = features_mode
self.edge_prob = edge_prob
self.feature_prob = feature_prob


def attack(self, model_manager, gen_dataset, mask_tensor):
Expand Down Expand Up @@ -96,13 +97,13 @@ def attack(self, model_manager, gen_dataset, mask_tensor):
# TEST
edge_index_set.discard((v, u))
max_attack -= 1
if not max_attack:
if max_attack < 0:
break
elif self.edge_mode == 'add':
first_set = hop_2.union(hop_1).union({int(n)})
second_set = hop_1.union({int(n)})
hop = list(itertools.product(first_set, second_set))
max_attack = len(hop) * self.edge_prob
max_attack = int(len(hop) * self.edge_prob)
unimportant_nodes = set()
important_nodes = set()
for (u, v) in zip(edge_index[0], edge_index[1]):
Expand Down Expand Up @@ -134,10 +135,10 @@ def attack(self, model_manager, gen_dataset, mask_tensor):
edge_index_set.add((new_node[0], v))
cnt += 1
max_attack -= 1
if not max_attack:
if max_attack < 0:
break
elif self.edge_mode == 'rewire':
max_attack = len(hop_2) * self.edge_prob
max_attack = int(len(hop_2) * self.edge_prob)
for (u, v) in zip(edge_index[0], edge_index[1]):
if u != n and v != n and f"{u},{v}" in explanations[i]['edges'].keys():
edge_index_set.discard((u, v))
Expand Down Expand Up @@ -167,29 +168,34 @@ def attack(self, model_manager, gen_dataset, mask_tensor):

if self.attack_features:
cnt = 0
max_attack = int(gen_dataset.dataset.data.x[0].shape[0]) * self.feature_prob
for i, n in enumerate(self.attack_inds):
if self.features_mode == 'reverse':
# get 2-hop
hop_1 = set()
hop_2 = set()
for (u, v) in edge_index_set:
if u == n:
hop_1.add(v)
elif v == n:
hop_1.add(u)
for (u, v) in edge_index_set:
if u in hop_1 and v != n and v not in hop_1:
hop_2.add(v)
elif v in hop_1 and u != n and u not in hop_1:
hop_2.add(u)
# # get 2-hop
# hop_1 = set()
# hop_2 = set()
# for (u, v) in edge_index_set:
# if u == n:
# hop_1.add(v)
# elif v == n:
# hop_1.add(u)
# for (u, v) in edge_index_set:
# if u in hop_1 and v != n and v not in hop_1:
# hop_2.add(v)
# elif v in hop_1 and u != n and u not in hop_1:
# hop_2.add(u)

#get features to be reversed
#f_mask = torch.zeros_like(gen_dataset.dataset.data.x.shape[0])
f_inds = []
for f, v in explanations[i]['nodes'].items():
feature_tuple = sorted(tuple((f, v) for f, v in explanations[i]['features'].items()), key=lambda x: float(x[1]), reverse=True)
for f, v in feature_tuple:
if v:
f_inds.append(int(f))
cnt += 1
max_attack -= 1
if max_attack < 0:
break
f_inds = torch.tensor(f_inds)

if not f_inds.numel():
Expand All @@ -210,7 +216,7 @@ class EAttackRandom(EvasionAttacker):
name = "EAttackRandom"

def __init__(self, explainer, run_config, attack_size, attack_inds, targeted, max_rewire, random_rewire,
attack_edges, attack_features, edge_mode, features_mode, edge_prob, **kwargs):
attack_edges, attack_features, edge_mode, features_mode, edge_prob, feature_prob, **kwargs):
super().__init__(**kwargs)
self.explainer = explainer
self.run_config = run_config
Expand All @@ -227,6 +233,7 @@ def __init__(self, explainer, run_config, attack_size, attack_inds, targeted, ma
self.edge_mode = edge_mode
self.features_mode = features_mode
self.edge_prob = edge_prob
self.feature_prob = feature_prob


def attack(self, model_manager, gen_dataset, mask_tensor):
Expand Down Expand Up @@ -315,29 +322,34 @@ def attack(self, model_manager, gen_dataset, mask_tensor):

if self.attack_features:
cnt = 0
max_attack = int(int(gen_dataset.dataset.data.x[0].shape[0]) * self.feature_prob)
for i, n in enumerate(self.attack_inds):
if self.features_mode == 'reverse':
# get 2-hop
hop_1 = set()
hop_2 = set()
for (u, v) in edge_index_set:
if u == n:
hop_1.add(v)
elif v == n:
hop_1.add(u)
for (u, v) in edge_index_set:
if u in hop_1 and v != n and v not in hop_1:
hop_2.add(v)
elif v in hop_1 and u != n and u not in hop_1:
hop_2.add(u)
# # get 2-hop
# hop_1 = set()
# hop_2 = set()
# for (u, v) in edge_index_set:
# if u == n:
# hop_1.add(v)
# elif v == n:
# hop_1.add(u)
# for (u, v) in edge_index_set:
# if u in hop_1 and v != n and v not in hop_1:
# hop_2.add(v)
# elif v in hop_1 and u != n and u not in hop_1:
# hop_2.add(u)

#get features to be reversed
#f_mask = torch.zeros_like(gen_dataset.dataset.data.x.shape[0])
f_inds = []
for f, v in explanations[i]['nodes'].items():
if v:
f_inds.append(int(f))
cnt += 1
#f_inds = []
f_inds = random.sample(range(int(gen_dataset.dataset.data.x[0].shape[0])), max_attack)
# for f, v in explanations[i]['nodes'].items():
# if v:
# f_inds.append(int(f))
# max_attack -= 1
# cnt += 1
# if not max_attack:
# break
f_inds = torch.tensor(f_inds)

if not f_inds.numel():
Expand Down

0 comments on commit fb68f5e

Please sign in to comment.