def process(self, surrogate, train_nodes, unlabeled_nodes=None, reset=True): assert isinstance(surrogate, gg.gallery.nodeclas.DenseGCN), surrogate # poisoning attack in DeepRobust if unlabeled_nodes is None: victim_nodes = gf.asarray(train_nodes) victim_labels = self.graph.node_label[victim_nodes] else: # Evasion attack in original paper self_training_labels = self.estimate_self_training_labels(surrogate, unlabeled_nodes) victim_nodes = np.hstack([train_nodes, unlabeled_nodes]) victim_labels = np.hstack([self.graph.node_label[train_nodes], self_training_labels]) with tf.device(self.device): adj_tensor = gf.astensor(self.graph.adj_matrix.A) self.victim_nodes = gf.astensor(victim_nodes) self.victim_labels = gf.astensor(victim_labels) self.adj_tensor = adj_tensor self.x_tensor = gf.astensor(self.graph.node_attr) self.complementary = tf.ones_like(adj_tensor) - tf.eye(self.num_nodes) - 2. * adj_tensor self.loss_fn = sparse_categorical_crossentropy self.adj_changes = tf.Variable(tf.zeros_like(adj_tensor)) self.surrogate = surrogate.model # used for `CW_loss=True` self.label_matrix = tf.gather(tf.eye(self.num_classes), self.victim_labels) self.range_idx = tf.range(victim_nodes.size, dtype=self.intx) self.indices_real = tf.stack([self.range_idx, self.victim_labels], axis=1) if reset: self.reset() return self
def train_loader(self, index): labels = self.graph.node_label[index] sequence = NullSequence( x=[self.cache.X, self.cache.A, self.cache.adjacency], y=gf.astensor(labels, device=self.data_device), out_weight=gf.astensor(index, device=self.data_device), device=self.data_device) return sequence
def config_train_data(self, index): labels = self.graph.label[index] sequence = NullSequence( [self.cache.feat, self.cache.adj, self.cache.adjacency], gf.astensor(labels, device=self.data_device), gf.astensor(index, device=self.data_device), device=self.data_device) return sequence
def construct_mask(self): adj_mask = np.ones(self.graph.adj_matrix.shape, dtype=self.floatx) x_mask = np.ones(self.graph.node_attr.shape, dtype=self.floatx) adj_mask[:, self.target] = 0. adj_mask[self.target, :] = 0. x_mask[self.target, :] = 0 adj_mask = gf.astensor(adj_mask) x_mask = gf.astensor(x_mask) return adj_mask, x_mask
def data_step(self, adj_transform="add_self_loop", feat_transform=None): graph = self.graph adj_matrix = gf.get(adj_transform)(graph.adj_matrix) attr_matrix = gf.get(feat_transform)(graph.attr_matrix) feat = gf.astensor(attr_matrix, device=self.data_device) # without considering `edge_weight` edges = gf.astensor(adj_matrix, device=self.data_device)[0] # ``edges`` and ``feat`` are cached for later use self.register_cache(feat=feat, edges=edges)
def process(self, surrogate, victim_nodes, victim_labels=None, reset=True): if isinstance(surrogate, gg.gallery.nodeclas.Trainer): surrogate = surrogate.model if victim_labels is None: victim_labels = self.graph.node_label[victim_nodes] with tf.device(self.device): self.surrogate = surrogate self.loss_fn = sparse_categorical_crossentropy self.victim_nodes = gf.astensor(victim_nodes) self.victim_labels = gf.astensor(victim_labels) if reset: self.reset() return self
def train_sequence(self, index): labels = self.graph.node_label[index] index = gf.astensor(index) X = tf.gather(self.cache.X, index) sequence = FullBatchSequence(X, labels, device=self.device) return sequence
def construct_sub_adj(self, influence_nodes, wrong_label_nodes, sub_nodes, sub_edges): length = len(wrong_label_nodes) potential_edges = np.hstack([ np.row_stack([np.tile(infl, length), wrong_label_nodes]) for infl in influence_nodes ]) if len(influence_nodes) > 1: # TODO: considering self-loops mask = self.graph.adj_matrix[potential_edges[0], potential_edges[1]].A1 == 0 potential_edges = potential_edges[:, mask] nodes = np.union1d(sub_nodes, wrong_label_nodes) edge_weights = np.ones(sub_edges.shape[1], dtype=self.floatx) non_edge_weights = np.zeros(potential_edges.shape[1], dtype=self.floatx) self_loop_weights = np.ones(nodes.shape[0], dtype=self.floatx) self_loop = np.row_stack([nodes, nodes]) self.indices = np.hstack([ sub_edges, sub_edges[[1, 0]], potential_edges, potential_edges[[1, 0]], self_loop ]) self.edge_weights = tf.Variable(edge_weights, dtype=self.floatx) self.non_edge_weights = tf.Variable(non_edge_weights, dtype=self.floatx) self.self_loop_weights = gf.astensor(self_loop_weights, dtype=self.floatx) self.edge_index = sub_edges self.non_edge_index = potential_edges self.self_loop = self_loop
def get_feature_importance(self, candidates, steps, disable=False): adj = self.adj_norm x = self.x_tensor mask = (candidates[:, 0], candidates[:, 1]) target_index = gf.astensor([self.target]) target_label = gf.astensor(self.target_label) baseline_add = x.numpy() baseline_add[mask] = 1.0 baseline_add = gf.astensor(baseline_add) baseline_remove = x.numpy() baseline_remove[mask] = 0.0 baseline_remove = gf.astensor(baseline_remove) feature_indicator = self.graph.node_attr[mask] > 0 features = candidates[feature_indicator] non_features = candidates[~feature_indicator] feature_gradients = tf.zeros(features.shape[0]) non_feature_gradients = tf.zeros(non_features.shape[0]) for alpha in tqdm(tf.linspace(0., 1.0, steps + 1), desc='Computing feature importance', disable=disable): ###### Compute integrated gradients for removing features ###### x_diff = x - baseline_remove x_step = baseline_remove + alpha * x_diff gradients = self.compute_feature_gradients(adj, x_step, target_index, target_label) feature_gradients += -tf.gather_nd(gradients, features) ###### Compute integrated gradients for adding features ###### x_diff = baseline_add - x x_step = baseline_add - alpha * x_diff gradients = self.compute_feature_gradients(adj, x_step, target_index, target_label) non_feature_gradients += tf.gather_nd(gradients, non_features) integrated_grads = np.zeros(feature_indicator.size) integrated_grads[feature_indicator] = feature_gradients.numpy() integrated_grads[~feature_indicator] = non_feature_gradients.numpy() return integrated_grads
def get_link_importance(self, candidates, steps, disable=False): adj = self.adj_tensor x = self.x_tensor mask = (candidates[:, 0], candidates[:, 1]) target_index = gf.astensor([self.target]) target_label = gf.astensor(self.target_label) baseline_add = adj.numpy() baseline_add[mask] = 1.0 baseline_add = gf.astensor(baseline_add) baseline_remove = adj.numpy() baseline_remove[mask] = 0.0 baseline_remove = gf.astensor(baseline_remove) edge_indicator = self.graph.adj_matrix[mask].A1 > 0 edges = candidates[edge_indicator] non_edges = candidates[~edge_indicator] edge_gradients = tf.zeros(edges.shape[0]) non_edge_gradients = tf.zeros(non_edges.shape[0]) for alpha in tqdm(tf.linspace(0., 1.0, steps + 1), desc='Computing link importance', disable=disable): ###### Compute integrated gradients for removing edges ###### adj_diff = adj - baseline_remove adj_step = baseline_remove + alpha * adj_diff gradients = self.compute_structure_gradients( adj_step, x, target_index, target_label) edge_gradients += -tf.gather_nd(gradients, edges) ###### Compute integrated gradients for adding edges ###### adj_diff = baseline_add - adj adj_step = baseline_add - alpha * adj_diff gradients = self.compute_structure_gradients( adj_step, x, target_index, target_label) non_edge_gradients += tf.gather_nd(gradients, non_edges) integrated_grads = np.zeros(edge_indicator.size) integrated_grads[edge_indicator] = edge_gradients.numpy() integrated_grads[~edge_indicator] = non_edge_gradients.numpy() return integrated_grads
def process_step(self): graph = self.graph adj_matrix = self.adj_transform(graph.adj_matrix) node_attr = self.attr_transform(graph.node_attr) node_attr = adj_matrix @ node_attr self.feature_inputs, self.structure_inputs = gf.astensor( node_attr, device=self.device), adj_matrix
def process(self, surrogate, reset=True): if isinstance(surrogate, gg.gallery.nodeclas.Trainer): surrogate = surrogate.model adj, x = self.graph.adj_matrix, self.graph.node_attr self.nodes_set = set(range(self.num_nodes)) self.features_set = np.arange(self.num_attrs) with tf.device(self.device): self.surrogate = surrogate self.loss_fn = SparseCategoricalCrossentropy(from_logits=True) self.x_tensor = gf.astensor(x) self.adj_tensor = gf.astensor(adj.A) self.adj_norm = gf.normalize_adj_tensor(self.adj_tensor) if reset: self.reset() return self
def train_sequence(self, index): index = gf.astensor(index) labels = self.graph.node_label[index] feature_inputs = tf.gather(self.feature_inputs, index) sequence = FullBatchNodeSequence(feature_inputs, labels, device=self.device) return sequence
def data_step(self, adj_transform=None, feat_transform=None): graph = self.graph adj_matrix = gf.get(adj_transform)(graph.adj_matrix) attr_matrix = gf.get(feat_transform)(graph.attr_matrix) feat = gf.astensor(attr_matrix, device=self.data_device) # ``feat`` is cached for later use self.register_cache(feat=feat, adj=adj_matrix)
def process(self, surrogate, reset=True): if isinstance(surrogate, gg.gallery.Trainer): surrogate = surrogate.model with tf.device(self.device): self.surrogate = surrogate self.loss_fn = sparse_categorical_crossentropy self.x_tensor = gf.astensor(self.graph.node_attr) if reset: self.reset() return self
def data_step(self, adj_transform="normalize_adj", attr_transform=None): graph = self.graph adj_matrix = gf.get(adj_transform)(graph.adj_matrix) node_attr = gf.get(attr_transform)(graph.node_attr) node_attr = adj_matrix @ node_attr X, A = gf.astensor(node_attr, device=self.data_device), adj_matrix # ``A`` and ``X`` are cached for later use self.register_cache(X=X, A=A)
def process_step(self): graph = self.transform.graph_transform(self.graph) adj_matrix = self.transform.adj_transform(graph.adj_matrix) node_attr = self.transform.attr_transform(graph.node_attr) node_attr = adj_matrix @ node_attr X, A = gf.astensor(node_attr, device=self.device), adj_matrix # ``A`` and ``X`` are cached for later use self.register_cache("X", X) self.register_cache("A", A)
def data_step( self, adj_transform="normalize_adj", # it is required feat_transform=None): graph = self.graph attr_matrix = gf.get(feat_transform)(graph.attr_matrix) feat = gf.astensor(attr_matrix, device=self.data_device) # ``feat`` is cached for later use self.register_cache(feat=feat)
def process(self, train_nodes, unlabeled_nodes, self_training_labels, hids, use_relu, reset=True): self.ll_ratio = None with tf.device(self.device): self.train_nodes = gf.astensor(train_nodes, dtype=self.intx) self.unlabeled_nodes = gf.astensor(unlabeled_nodes, dtype=self.intx) self.labels_train = gf.astensor(self.graph.node_label[train_nodes], dtype=self.intx) self.self_training_labels = gf.astensor(self_training_labels, dtype=self.intx) self.adj_tensor = gf.astensor(self.graph.adj_matrix.A, dtype=self.floatx) self.x_tensor = gf.astensor(self.graph.node_attr, dtype=self.floatx) self.build(hids=hids) self.use_relu = use_relu self.loss_fn = SparseCategoricalCrossentropy(from_logits=True) self.adj_changes = tf.Variable(tf.zeros_like(self.adj_tensor)) self.x_changes = tf.Variable(tf.zeros_like(self.x_tensor)) if reset: self.reset() return self
def config_train_data(self, edge_index): if isinstance(edge_index, (list, tuple)): train_edges = edge_index[0] # postive edge index else: train_edges = edge_index train_edges = gf.astensor(train_edges, device=self.data_device) self.register_cache(edges=train_edges) sequence = FullBatchSequence([self.cache.feat, train_edges], out_index=edge_index, device=self.data_device) return sequence
def process(self, surrogate, train_nodes, unlabeled_nodes=None, reset=True): assert isinstance(surrogate, gg.gallery.GCN), surrogate # poisoning attack in DeepRobust if unlabeled_nodes is None: victim_nodes = gf.asarray(train_nodes) victim_labels = self.graph.node_label[victim_nodes] else: # Evasion attack in original paper self_training_labels = self.estimate_self_training_labels( surrogate, unlabeled_nodes) victim_nodes = np.hstack([train_nodes, unlabeled_nodes]) victim_labels = np.hstack( [self.graph.node_label[train_nodes], self_training_labels]) adj_tensor = gf.astensor(self.graph.adj_matrix.A, device=self.device) self.victim_nodes = gf.astensor(victim_nodes, device=self.device) self.victim_labels = gf.astensor(victim_labels, device=self.device) self.adj_tensor = adj_tensor self.x_tensor = gf.astensor(self.graph.node_attr, device=self.device) self.complementary = (torch.ones_like(adj_tensor) - torch.eye(self.num_nodes).to(self.device) - 2. * adj_tensor) self.loss_fn = nn.CrossEntropyLoss() self.adj_changes = nn.Parameter(torch.zeros_like(self.adj_tensor)) self.surrogate = surrogate.model.to(self.device) self.surrogate.eval() # # used for `CW_loss=True` self.label_matrix = torch.eye(self.num_classes)[self.victim_labels].to( self.device) self.range_idx = torch.arange(victim_nodes.size).to(self.device) self.indices_real = torch.stack([self.range_idx, self.victim_labels]) if reset: self.reset() return self
def data_step(self, adj_transform="normalize_adj", feat_transform=None): graph = self.graph adj_matrix = gf.get(adj_transform)(graph.adj_matrix) attr_matrix = gf.get(feat_transform)(graph.attr_matrix) attr_matrix = adj_matrix @ attr_matrix feat, adj = gf.astensor(attr_matrix, device=self.data_device), adj_matrix # ``adj`` and ``feat`` are cached for later use self.register_cache(feat=feat, adj=adj)
def process_step(self): graph = self.transform.graph_transform(self.graph) # Dense matrix, shape [num_nodes, max_degree] adj_matrix = self.transform.adj_transform(graph.adj_matrix) node_attr = self.transform.attr_transform(graph.node_attr) # pad with a dummy zero vector node_attr = np.vstack( [node_attr, np.zeros(node_attr.shape[1], dtype=self.floatx)]) X, A = gf.astensor(node_attr, device=self.device), adj_matrix # ``A`` and ``X`` are cached for later use self.register_cache("X", X) self.register_cache("A", A)
def config_train_data(self, edge_index): if isinstance(edge_index, (list, tuple)): train_edges = edge_index[0] # postive edge index else: train_edges = edge_index full_adj = self.graph.adj_matrix edge_weight = full_adj[train_edges[0], train_edges[1]].A1 adj_matrix = gf.edge_to_sparse_adj(train_edges, edge_weight) train_adj = self.transform.adj_transform(adj_matrix) train_adj = gf.astensor(train_adj, device=self.data_device) self.register_cache(adj=train_adj) sequence = FullBatchSequence([self.cache.feat, train_adj], out_index=edge_index, device=self.data_device) return sequence
def config_train_data(self, index): labels = self.graph.label[index] # ========================================================== # initial weight_y is obtained by linear regression feat = self.cache.feat.to(self.device) labels = gf.astensor(labels, device=self.device) A = torch.mm(feat.t(), feat) + 1e-05 * torch.eye(feat.size(1), device=feat.device) labels_one_hot = feat.new_zeros(feat.size(0), self.graph.num_classes) labels_one_hot[torch.arange(labels.size(0)), labels] = 1 self.model.init_weight_y = torch.mm( torch.mm(torch.cholesky_inverse(A), feat.t()), labels_one_hot) # ========================================================== sequence = FullBatchSequence([self.cache.feat, self.cache.g], labels, out_index=index, device=self.data_device, escape=type(self.cache.g)) return sequence
def process(self, surrogate, reset=True): assert isinstance(surrogate, gg.gallery.SGC), surrogate hops = surrogate.cfg.process.K # NOTE: Be compatible with graphgallery # nodes with the same class labels self.similar_nodes = [ np.where(self.graph.node_label == c)[0] for c in range(self.num_classes) ] with tf.device(self.device): W, b = surrogate.model.weights X = gf.astensor(self.graph.node_attr) self.b = b self.XW = X @ W self.SGC = SGConvolution(hops) self.hops = hops self.loss_fn = sparse_categorical_crossentropy self.surrogate = surrogate if reset: self.reset() return self
def predict(self, predict_data=None, transform=torch.nn.Softmax(dim=-1)): indices = gf.astensor(predict_data).view(-1) mapper = torch.zeros(self.graph.num_nodes).long() mapper[indices] = torch.arange(indices.size(0)) if not self.model: raise RuntimeError( 'You must compile your model before training/testing/predicting. Use `trainer.build()`.' ) if not isinstance(predict_data, (DataLoader, Dataset)): predict_data = self.config_predict_data(predict_data) out, node_ids = self.predict_step(predict_data) out[mapper[node_ids.cpu()]] = out.clone() out = out.squeeze() if transform is not None: out = transform(out) return out.cpu()
def process(self, train_nodes, unlabeled_nodes, self_training_labels, hids, use_relu, reset=True): self.ll_ratio = None self.train_nodes = gf.astensor(train_nodes, dtype=self.intx, device=self.device) self.unlabeled_nodes = gf.astensor(unlabeled_nodes, dtype=self.intx, device=self.device) self.labels_train = gf.astensor(self.graph.node_label[train_nodes], dtype=self.intx, device=self.device) self.self_training_labels = gf.astensor(self_training_labels, dtype=self.intx, device=self.device) self.adj_tensor = gf.astensor(self.graph.adj_matrix.A, dtype=self.floatx, device=self.device) self.x_tensor = gf.astensor(self.graph.node_attr, dtype=self.floatx, device=self.device) self.build(hids=hids) self.use_relu = use_relu self.loss_fn = torch.nn.CrossEntropyLoss() self.adj_changes = None self.x_changes = None if reset: self.reset() return self
def attack(self, target, num_budgets=None, symmetric=True, direct_attack=True, structure_attack=True, feature_attack=False, disable=False): super().attack(target, num_budgets, direct_attack, structure_attack, feature_attack) if feature_attack and not self.graph.is_binary(): raise RuntimeError( "Currently only attack binary node attributes are supported") with tf.device(self.device): target_index = gf.astensor([self.target]) target_labels = gf.astensor(self.target_label) modified_adj, modified_nx = self.modified_adj, self.modified_nx if not direct_attack: adj_mask, x_mask = self.construct_mask() else: adj_mask, x_mask = None, None for _ in tqdm(range(self.num_budgets), desc='Peturbing Graph', disable=disable): adj_grad, x_grad = self.compute_gradients( modified_adj, modified_nx, target_index, target_labels) adj_grad_score = tf.constant(0.0) x_grad_score = tf.constant(0.0) if structure_attack: if symmetric: adj_grad = (adj_grad + tf.transpose(adj_grad)) / 2. adj_grad_score = self.structure_score( modified_adj, adj_grad, adj_mask) if feature_attack: x_grad_score = self.feature_score(modified_nx, x_grad, x_mask) if tf.reduce_max(adj_grad_score) >= tf.reduce_max( x_grad_score): adj_grad_argmax = tf.argmax(adj_grad_score) row, col = divmod(adj_grad_argmax.numpy(), self.num_nodes) modified_adj[row, col].assign(1. - modified_adj[row, col]) modified_adj[col, row].assign(1. - modified_adj[col, row]) self.adj_flips.append((row, col)) else: x_grad_argmax = tf.argmax(x_grad_score) row, col = divmod(x_grad_argmax.numpy(), self.num_attrs) modified_nx[row, col].assign(1. - modified_nx[row, col]) self.nattr_flips.append((row, col)) return self