import random
from typing import Optional
import numpy as np
from tqdm.auto import tqdm
from greatx.attack.untargeted.untargeted_attacker import UntargetedAttacker
[docs]class RandomAttack(UntargetedAttacker):
r"""Random attacker that randomly chooses edges to flip.
Parameters
----------
data : Data
PyG-like data denoting the input graph
device : str, optional
the device of the attack running on, by default "cpu"
seed : Optional[int], optional
the random seed for reproducing the attack, by default None
name : Optional[str], optional
name of the attacker, if None, it would be :obj:`__class__.__name__`,
by default None
kwargs : additional arguments of :class:`greatx.attack.Attacker`,
Raises
------
TypeError
unexpected keyword argument in :obj:`kwargs`
Example
-------
.. code-block:: python
from greatx.dataset import GraphDataset
import torch_geometric.transforms as T
dataset = GraphDataset(root='.', name='Cora',
transform=T.LargestConnectedComponents())
data = dataset[0]
from greatx.attack.untargeted import RandomAttack
attacker = RandomAttack(data)
attacker.reset()
attacker.attack(0.05) # attack with 0.05% of edge perturbations
attacker.data() # get attacked graph
attacker.edge_flips() # get edge flips after attack
attacker.added_edges() # get added edges after attack
attacker.removed_edges() # get removed edges after attack
Note
----
* Please remember to call :meth:`reset` before each attack.
"""
[docs] def attack(self, num_budgets=0.05, *, threshold=0.5, structure_attack=True,
feature_attack=False, disable=False):
super().attack(num_budgets=num_budgets,
structure_attack=structure_attack,
feature_attack=feature_attack)
assert 0 < threshold < 1
random_arr = np.random.choice(2, self.num_budgets,
p=[1 - threshold, threshold]) * 2 - 1
influence_nodes = list(self.nodes_set)
for it, remove_or_insert in tqdm(enumerate(random_arr),
desc='Peturbing graph...',
disable=disable):
# randomly choose to add or remove edges
if remove_or_insert > 0:
edge = self.get_added_edge(influence_nodes)
while edge is None:
edge = self.get_added_edge(influence_nodes)
u, v = edge
self.add_edge(u, v, it)
else:
edge = self.get_removed_edge(influence_nodes)
while edge is None:
edge = self.get_removed_edge(influence_nodes)
u, v = edge
self.remove_edge(u, v, it)
return self
[docs] def get_added_edge(self, influence_nodes: list) -> Optional[tuple]:
u = random.choice(influence_nodes)
neighbors = self.adjacency_matrix[u].indices.tolist()
attacker_nodes = list(self.nodes_set - set(neighbors + [u]))
if len(attacker_nodes) == 0:
return None
v = random.choice(attacker_nodes)
if self.is_legal_edge(u, v):
return (u, v)
else:
return None
[docs] def get_removed_edge(self, influence_nodes: list) -> Optional[tuple]:
u = random.choice(influence_nodes)
neighbors = self.adjacency_matrix[u].indices.tolist()
# assume that the graph has no self-loops
attacker_nodes = list(set(neighbors))
if len(attacker_nodes) == 0:
return None
v = random.choice(attacker_nodes)
if self.is_singleton_edge(u, v):
return None
if self.is_legal_edge(u, v):
return (u, v)
else:
return None