def process_step(self, adj_transform="normalize_adj", attr_transform=None, graph_transform=None, recalculate=True): graph = gf.get(graph_transform)(self.graph) adj_matrix = gf.get(adj_transform)(graph.adj_matrix) node_attr = gf.get(attr_transform)(graph.node_attr) X, A = gf.astensors(node_attr, adj_matrix, device=self.device) # ``A`` and ``X`` are cached for later use self.register_cache(X=X, A=A) if recalculate: # Uses this to save time for structure evation attack # NOTE: Please make sure the node attribute matrix remains the same if recalculate=False knn_graph = gf.normalize_adj(gf.knn_graph(node_attr), fill_weight=0.) pseudo_labels, node_pairs = gf.attr_sim(node_attr) knn_graph, pseudo_labels = gf.astensors(knn_graph, pseudo_labels, device=self.device) self.register_cache(knn_graph=knn_graph, pseudo_labels=pseudo_labels, node_pairs=node_pairs)
def reset(self): super().reset() self.modified_adj = self.graph.adj_matrix.copy() self.modified_nx = self.sparse_x.copy() self.adj_norm = gf.normalize_adj(self.modified_adj) self.adj_flips = [] self.nattr_flips = [] self.influence_nodes = [] self.potential_edges = [] self.cooc_constraint = None return self
def data_step(self, adj_transform="normalize_adj", feat_transform=None, recalculate=True): graph = self.graph adj_matrix = gf.get(adj_transform)(graph.adj_matrix) attr_matrix = gf.get(feat_transform)(graph.attr_matrix) feat, adj = gf.astensors(attr_matrix, adj_matrix, device=self.data_device) # ``adj`` and ``feat`` are cached for later use self.register_cache(feat=feat, adj=adj) if recalculate: # Uses this to save time for structure evation attack # NOTE: Please make sure the node attribute matrix remains the same if recalculate=False knn_graph = gf.normalize_adj(gf.knn_graph(attr_matrix), add_self_loop=False) pseudo_labels, node_pairs = gf.attr_sim(attr_matrix) knn_graph, pseudo_labels = gf.astensors(knn_graph, pseudo_labels, device=self.data_device) self.register_cache(knn_graph=knn_graph, pseudo_labels=pseudo_labels, node_pairs=node_pairs)
def attack(self, target, num_budgets=None, direct_attack=True, structure_attack=True, feature_attack=False, n_influencers=5, ll_constraint=True, ll_cutoff=0.004, 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") if ll_constraint and self.allow_singleton: raise RuntimeError( '`ll_constraint` is failed when `allow_singleton=True`, please set `attacker.allow_singleton=False`.' ) logits_start = self.compute_logits() best_wrong_class = self.strongest_wrong_class(logits_start) if structure_attack and ll_constraint: # Setup starting values of the likelihood ratio test. degree_sequence_start = self.degree current_degree_sequence = self.degree.astype('float64') d_min = 2 S_d_start = np.sum( np.log(degree_sequence_start[degree_sequence_start >= d_min])) current_S_d = np.sum( np.log( current_degree_sequence[current_degree_sequence >= d_min])) n_start = np.sum(degree_sequence_start >= d_min) current_n = np.sum(current_degree_sequence >= d_min) alpha_start = compute_alpha(n_start, S_d_start, d_min) log_likelihood_orig = compute_log_likelihood( n_start, alpha_start, S_d_start, d_min) if len(self.influence_nodes) == 0: if not direct_attack: # Choose influencer nodes infls, add_infls = self.get_attacker_nodes( n_influencers, add_additional_nodes=True) self.influence_nodes = np.concatenate((infls, add_infls)) # Potential edges are all edges from any attacker to any other node, except the respective # attacker itself or the node being attacked. self.potential_edges = np.row_stack([ np.column_stack( (np.tile(infl, self.num_nodes - 2), np.setdiff1d(np.arange(self.num_nodes), np.array([self.target, infl])))) for infl in self.influence_nodes ]) else: # direct attack influencers = [self.target] self.potential_edges = np.column_stack( (np.tile(self.target, self.num_nodes - 1), np.setdiff1d(np.arange(self.num_nodes), self.target))) self.influence_nodes = np.array(influencers) self.potential_edges = self.potential_edges.astype("int32") for it in tqdm(range(self.num_budgets), desc='Peturbing Graph', disable=disable): if structure_attack: # Do not consider edges that, if removed, result in singleton edges in the graph. if not self.allow_singleton: filtered_edges = gf.singleton_filter(self.potential_edges, self.modified_adj).astype("int32") else: filtered_edges = self.potential_edges if ll_constraint: # Update the values for the power law likelihood ratio test. deltas = 2 * (1 - self.modified_adj[tuple( filtered_edges.T)].A.ravel()) - 1 d_edges_old = current_degree_sequence[filtered_edges] d_edges_new = current_degree_sequence[ filtered_edges] + deltas[:, None] new_S_d, new_n = update_Sx(current_S_d, current_n, d_edges_old, d_edges_new, d_min) new_alphas = compute_alpha(new_n, new_S_d, d_min) new_ll = compute_log_likelihood(new_n, new_alphas, new_S_d, d_min) alphas_combined = compute_alpha(new_n + n_start, new_S_d + S_d_start, d_min) new_ll_combined = compute_log_likelihood( new_n + n_start, alphas_combined, new_S_d + S_d_start, d_min) new_ratios = -2 * new_ll_combined + 2 * ( new_ll + log_likelihood_orig) # Do not consider edges that, if added/removed, would lead to a violation of the # likelihood ration Chi_square cutoff value. powerlaw_filter = filter_chisquare(new_ratios, ll_cutoff) filtered_edges = filtered_edges[powerlaw_filter] # Compute new entries in A_hat_square_uv a_hat_uv_new = self.compute_new_a_hat_uv(filtered_edges) # Compute the struct scores for each potential edge struct_scores = self.struct_score(a_hat_uv_new, self.compute_XW()) best_edge_ix = struct_scores.argmin() best_edge_score = struct_scores.min() best_edge = filtered_edges[best_edge_ix] if feature_attack: # Compute the feature scores for each potential feature perturbation feature_ixs, feature_scores = self.feature_scores() best_feature_ix = feature_ixs[0] best_feature_score = feature_scores[0] if structure_attack and feature_attack: # decide whether to choose an edge or feature to change if best_edge_score < best_feature_score: change_structure = True else: change_structure = False elif structure_attack: change_structure = True elif feature_attack: change_structure = False if change_structure: # perform edge perturbation u, v = best_edge modified_adj = self.modified_adj.tolil(copy=False) modified_adj[(u, v)] = modified_adj[( v, u)] = 1 - modified_adj[(u, v)] self.modified_adj = modified_adj.tocsr(copy=False) self.adj_norm = gf.normalize_adj(modified_adj) self.adj_flips.append((u, v)) if ll_constraint: # Update likelihood ratio test values current_S_d = new_S_d[powerlaw_filter][best_edge_ix] current_n = new_n[powerlaw_filter][best_edge_ix] current_degree_sequence[best_edge] += deltas[ powerlaw_filter][best_edge_ix] else: modified_nx = self.modified_nx.tolil(copy=False) modified_nx[tuple( best_feature_ix)] = 1 - modified_nx[tuple(best_feature_ix)] self.modified_nx = modified_nx.tocsr(copy=False) self.nattr_flips.append(tuple(best_feature_ix)) return self