import os.path as osp
import numpy as np
import scipy.sparse as sp
import torch
from torch.nn.modules.module import Module
from deeprobust.graph import utils
[docs]class BaseAttack(Module):
"""Abstract base class for target attack classes.
Parameters
----------
model :
model to attack
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'
"""
def __init__(self, model, nnodes, attack_structure=True, attack_features=False, device='cpu'):
super(BaseAttack, self).__init__()
self.surrogate = model
self.nnodes = nnodes
self.attack_structure = attack_structure
self.attack_features = attack_features
self.device = device
self.modified_adj = None
self.modified_features = None
if model is not None:
self.nclass = model.nclass
self.nfeat = model.nfeat
self.hidden_sizes = model.hidden_sizes
[docs] def attack(self, ori_adj, n_perturbations, **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.
Returns
-------
None.
"""
pass
[docs] def check_adj(self, adj):
"""Check if the modified adjacency is symmetric and unweighted.
"""
assert np.abs(adj - adj.T).sum() == 0, "Input graph is not symmetric"
assert adj.tocsr().max() == 1, "Max value should be 1!"
assert adj.tocsr().min() == 0, "Min value should be 0!"
[docs] def check_adj_tensor(self, adj):
"""Check if the modified adjacency is symmetric, unweighted, all-zero diagonal.
"""
assert torch.abs(adj - adj.t()).sum() == 0, "Input graph is not symmetric"
assert adj.max() == 1, "Max value should be 1!"
assert adj.min() == 0, "Min value should be 0!"
diag = adj.diag()
assert diag.max() == 0, "Diagonal should be 0!"
assert diag.min() == 0, "Diagonal should be 0!"
[docs] def save_adj(self, root=r'/tmp/', name='mod_adj'):
"""Save attacked adjacency matrix.
Parameters
----------
root :
root directory where the variable should be saved
name : str
saved file name
Returns
-------
None.
"""
assert self.modified_adj is not None, \
'modified_adj is None! Please perturb the graph first.'
name = name + '.npz'
modified_adj = self.modified_adj
if type(modified_adj) is torch.Tensor:
sparse_adj = utils.to_scipy(modified_adj)
sp.save_npz(osp.join(root, name), sparse_adj)
else:
sp.save_npz(osp.join(root, name), modified_adj)
[docs] def save_features(self, root=r'/tmp/', name='mod_features'):
"""Save attacked node feature matrix.
Parameters
----------
root :
root directory where the variable should be saved
name : str
saved file name
Returns
-------
None.
"""
assert self.modified_features is not None, \
'modified_features is None! Please perturb the graph first.'
name = name + '.npz'
modified_features = self.modified_features
if type(modified_features) is torch.Tensor:
sparse_features = utils.to_scipy(modified_features)
sp.save_npz(osp.join(root, name), sparse_features)
else:
sp.save_npz(osp.join(root, name), modified_features)