| from torch.nn.modules.module import Module |
| import numpy as np |
| import torch |
| import scipy.sparse as sp |
| import os.path as osp |
|
|
| 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 |
|
|
| if model is not None: |
| self.nclass = model.nclass |
| self.nfeat = model.nfeat |
| self.hidden_sizes = model.hidden_sizes |
|
|
| self.modified_adj = None |
| self.modified_features = None |
|
|
| def attack(self, ori_adj, n_perturbations, **kwargs): |
| """Generate perturbations on the input graph. |
| |
| Parameters |
| ---------- |
| ori_adj : scipy.sparse.csr_matrix |
| Original (unperturbed) adjacency matrix. |
| n_perturbations : int |
| Number of perturbations on the input graph. Perturbations could |
| be edge removals/additions or feature removals/additions. |
| |
| Returns |
| ------- |
| None. |
| |
| """ |
| pass |
|
|
| def check_adj(self, adj): |
| """Check if the modified adjacency is symmetric and unweighted. |
| """ |
|
|
| if type(adj) is torch.Tensor: |
| adj = adj.cpu().numpy() |
| assert np.abs(adj - adj.T).sum() == 0, "Input graph is not symmetric" |
| if sp.issparse(adj): |
| assert adj.tocsr().max() == 1, "Max value should be 1!" |
| assert adj.tocsr().min() == 0, "Min value should be 0!" |
| else: |
| assert adj.max() == 1, "Max value should be 1!" |
| assert adj.min() == 0, "Min value should be 0!" |
|
|
| 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: |
| modified_adj = utils.to_scipy(modified_adj) |
| if sp.issparse(modified_adj): |
| modified_adj = modified_adj.tocsr() |
| sp.save_npz(osp.join(root, name), modified_adj) |
|
|
| 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: |
| modified_features = utils.to_scipy(modified_features) |
| if sp.issparse(modified_features): |
| modified_features = modified_features.tocsr() |
| sp.save_npz(osp.join(root, name), modified_features) |
|
|
|
|