Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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