|
|
import numpy as np |
|
|
from deeprobust.graph.global_attack import BaseAttack |
|
|
import scipy.sparse as sp |
|
|
|
|
|
|
|
|
|
|
|
class Random(BaseAttack): |
|
|
""" Randomly adding edges to the input graph |
|
|
|
|
|
Parameters |
|
|
---------- |
|
|
model : |
|
|
model to attack. Default `None`. |
|
|
nnodes : int |
|
|
number of nodes in the input graph |
|
|
attack_structure : bool |
|
|
whether to attack graph structure |
|
|
attack_features : bool |
|
|
whether to attack node features |
|
|
device: str |
|
|
'cpu' or 'cuda' |
|
|
|
|
|
Examples |
|
|
-------- |
|
|
|
|
|
>>> from deeprobust.graph.data import Dataset |
|
|
>>> from deeprobust.graph.global_attack import Random |
|
|
>>> data = Dataset(root='/tmp/', name='cora') |
|
|
>>> adj, features, labels = data.adj, data.features, data.labels |
|
|
>>> model = Random() |
|
|
>>> model.attack(adj, n_perturbations=10) |
|
|
>>> modified_adj = model.modified_adj |
|
|
|
|
|
""" |
|
|
|
|
|
def __init__(self, model=None, nnodes=None, attack_structure=True, attack_features=False, device='cpu'): |
|
|
super(Random, self).__init__(model, nnodes, attack_structure=attack_structure, attack_features=attack_features, device=device) |
|
|
|
|
|
assert not self.attack_features, 'RND does NOT support attacking features' |
|
|
|
|
|
def attack(self, ori_adj, n_perturbations, type='flip', **kwargs): |
|
|
"""Generate attacks on the input graph. |
|
|
|
|
|
Parameters |
|
|
---------- |
|
|
ori_adj : scipy.sparse.csr_matrix |
|
|
Original (unperturbed) adjacency matrix. |
|
|
n_perturbations : int |
|
|
Number of edge removals/additions. |
|
|
type: str |
|
|
perturbation type. Could be 'add', 'remove' or 'flip'. |
|
|
|
|
|
Returns |
|
|
------- |
|
|
None. |
|
|
|
|
|
""" |
|
|
|
|
|
if self.attack_structure: |
|
|
modified_adj = self.perturb_adj(ori_adj, n_perturbations, type) |
|
|
self.modified_adj = modified_adj |
|
|
|
|
|
def perturb_adj(self, adj, n_perturbations, type='add'): |
|
|
"""Randomly add, remove or flip edges. |
|
|
|
|
|
Parameters |
|
|
---------- |
|
|
adj : scipy.sparse.csr_matrix |
|
|
Original (unperturbed) adjacency matrix. |
|
|
n_perturbations : int |
|
|
Number of edge removals/additions. |
|
|
type: str |
|
|
perturbation type. Could be 'add', 'remove' or 'flip'. |
|
|
|
|
|
Returns |
|
|
------ |
|
|
scipy.sparse matrix |
|
|
perturbed adjacency matrix |
|
|
""" |
|
|
|
|
|
modified_adj = adj.tolil() |
|
|
|
|
|
type = type.lower() |
|
|
assert type in ['add', 'remove', 'flip'] |
|
|
|
|
|
if type == 'flip': |
|
|
|
|
|
edges = self.random_sample_edges(adj, n_perturbations, exclude=set()) |
|
|
for n1, n2 in edges: |
|
|
modified_adj[n1, n2] = 1 - modified_adj[n1, n2] |
|
|
modified_adj[n2, n1] = 1 - modified_adj[n2, n1] |
|
|
|
|
|
if type == 'add': |
|
|
|
|
|
nonzero = set(zip(*adj.nonzero())) |
|
|
edges = self.random_sample_edges(adj, n_perturbations, exclude=nonzero) |
|
|
for n1, n2 in edges: |
|
|
modified_adj[n1, n2] = 1 |
|
|
modified_adj[n2, n1] = 1 |
|
|
|
|
|
if type == 'remove': |
|
|
|
|
|
nonzero = np.array(sp.triu(adj, k=1).nonzero()).T |
|
|
indices = np.random.permutation(nonzero)[: n_perturbations].T |
|
|
modified_adj[indices[0], indices[1]] = 0 |
|
|
modified_adj[indices[1], indices[0]] = 0 |
|
|
|
|
|
self.check_adj(modified_adj) |
|
|
return modified_adj |
|
|
|
|
|
def perturb_features(self, features, n_perturbations): |
|
|
"""Randomly perturb features. |
|
|
""" |
|
|
raise NotImplementedError |
|
|
print('number of pertubations: %s' % n_perturbations) |
|
|
return modified_features |
|
|
|
|
|
def inject_nodes(self, adj, n_add, n_perturbations): |
|
|
"""For each added node, randomly connect with other nodes. |
|
|
""" |
|
|
|
|
|
|
|
|
print('number of pertubations: %s' % n_perturbations) |
|
|
raise NotImplementedError |
|
|
|
|
|
modified_adj = adj.tolil() |
|
|
return modified_adj |
|
|
|
|
|
def random_sample_edges(self, adj, n, exclude): |
|
|
itr = self.sample_forever(adj, exclude=exclude) |
|
|
return [next(itr) for _ in range(n)] |
|
|
|
|
|
def sample_forever(self, adj, exclude): |
|
|
"""Randomly random sample edges from adjacency matrix, `exclude` is a set |
|
|
which contains the edges we do not want to sample and the ones already sampled |
|
|
""" |
|
|
while True: |
|
|
|
|
|
|
|
|
t = tuple(np.random.choice(adj.shape[0], 2, replace=False)) |
|
|
if t not in exclude: |
|
|
yield t |
|
|
exclude.add(t) |
|
|
exclude.add((t[1], t[0])) |
|
|
|