项目文件夹

文件
KounianhuaDu 9aac93ff21 Gnnexplainer (#2717)
* gnn-explainer

* gnn-explainer

* gnn-explainer

* gnn-explainer

* fix

* fix

* fix

* readme

* readme

Co-authored-by: zhjwy9343 <6593865@qq.com>
2021-03-22 18:13:08 +08:00

249 行
8.6 KiB
Python

# This file is copied from the author's implementation.
# <https://github.com/RexYing/gnn-model-explainer/blob/master/gengraph.py>.
"""gengraph.py
Generating and manipulaton the synthetic graphs needed for the paper's experiments.
"""
import os
from matplotlib import pyplot as plt
import numpy as np
import networkx as nx
# Set matplotlib backend to file writing
plt.switch_backend("agg")
from synthetic_structsim import *
from featgen import *
def perturb(graph_list, p):
""" Perturb the list of (sparse) graphs by adding/removing edges.
Args:
p: proportion of added edges based on current number of edges.
Returns:
A list of graphs that are perturbed from the original graphs.
"""
perturbed_graph_list = []
for G_original in graph_list:
G = G_original.copy()
edge_count = int(G.number_of_edges() * p)
# randomly add the edges between a pair of nodes without an edge.
for _ in range(edge_count):
while True:
u = np.random.randint(0, G.number_of_nodes())
v = np.random.randint(0, G.number_of_nodes())
if (not G.has_edge(u, v)) and (u != v):
break
G.add_edge(u, v)
perturbed_graph_list.append(G)
return perturbed_graph_list
def join_graph(G1, G2, n_pert_edges):
""" Join two graphs along matching nodes, then perturb the resulting graph.
Args:
G1, G2: Networkx graphs to be joined.
n_pert_edges: number of perturbed edges.
Returns:
A new graph, result of merging and perturbing G1 and G2.
"""
assert n_pert_edges > 0
F = nx.compose(G1, G2)
edge_cnt = 0
while edge_cnt < n_pert_edges:
node_1 = np.random.choice(G1.nodes())
node_2 = np.random.choice(G2.nodes())
F.add_edge(node_1, node_2)
edge_cnt += 1
return F
# Generating synthetic graphs
def gen_syn1(nb_shapes=80, width_basis=300, feature_generator=None, m=5):
""" Synthetic Graph #1:
Start with Barabasi-Albert graph and attach house-shaped subgraphs.
Args:
nb_shapes : The number of shapes (here 'houses') that should be added to the base graph.
width_basis : The width of the basis graph (here 'Barabasi-Albert' random graph).
feature_generator : A `FeatureGenerator` for node features. If `None`, add constant features to nodes.
m : number of edges to attach to existing node (for BA graph)
Returns:
G : A networkx graph
role_id : A list with length equal to number of nodes in the entire graph (basis
: + shapes). role_id[i] is the ID of the role of node i. It is the label.
name : A graph identifier
"""
basis_type = "ba"
list_shapes = [["house"]] * nb_shapes
plt.figure(figsize=(8, 6), dpi=300)
G, role_id, _ = build_graph(
width_basis, basis_type, list_shapes, start=0, m=5
)
G = perturb([G], 0.01)[0]
if feature_generator is None:
feature_generator = ConstFeatureGen(1)
feature_generator.gen_node_features(G)
name = basis_type + "_" + str(width_basis) + "_" + str(nb_shapes)
return G, role_id, name
def gen_syn2(nb_shapes=100, width_basis=350):
""" Synthetic Graph #2:
Start with Barabasi-Albert graph and add node features indicative of a community label.
Args:
nb_shapes : The number of shapes (here 'houses') that should be added to the base graph.
width_basis : The width of the basis graph (here 'Barabasi-Albert' random graph).
Returns:
G : A networkx graph
label : Label of the nodes (determined by role_id and community)
name : A graph identifier
"""
basis_type = "ba"
random_mu = [0.0] * 8
random_sigma = [1.0] * 8
# Create two grids
mu_1, sigma_1 = np.array([-1.0] * 2 + random_mu), np.array([0.5] * 2 + random_sigma)
mu_2, sigma_2 = np.array([1.0] * 2 + random_mu), np.array([0.5] * 2 + random_sigma)
feat_gen_G1 = GaussianFeatureGen(mu=mu_1, sigma=sigma_1)
feat_gen_G2 = GaussianFeatureGen(mu=mu_2, sigma=sigma_2)
G1, role_id1, name = gen_syn1(feature_generator=feat_gen_G1, m=4)
G2, role_id2, name = gen_syn1(feature_generator=feat_gen_G2, m=4)
G1_size = G1.number_of_nodes()
num_roles = max(role_id1) + 1
role_id2 = [r + num_roles for r in role_id2]
label = role_id1 + role_id2
# Edit node ids to avoid collisions on join
g1_map = {n: i for i, n in enumerate(G1.nodes())}
G1 = nx.relabel_nodes(G1, g1_map)
g2_map = {n: i + G1_size for i, n in enumerate(G2.nodes())}
G2 = nx.relabel_nodes(G2, g2_map)
# Join
n_pert_edges = width_basis
G = join_graph(G1, G2, n_pert_edges)
name = basis_type + "_" + str(width_basis) + "_" + str(nb_shapes) + "_2comm"
return G, label, name
def gen_syn3(nb_shapes=80, width_basis=300, feature_generator=None, m=5):
""" Synthetic Graph #3:
Start with Barabasi-Albert graph and attach grid-shaped subgraphs.
Args:
nb_shapes : The number of shapes (here 'grid') that should be added to the base graph.
width_basis : The width of the basis graph (here 'Barabasi-Albert' random graph).
feature_generator : A `FeatureGenerator` for node features. If `None`, add constant features to nodes.
m : number of edges to attach to existing node (for BA graph)
Returns:
G : A networkx graph
role_id : Role ID for each node in synthetic graph.
name : A graph identifier
"""
basis_type = "ba"
list_shapes = [["grid", 3]] * nb_shapes
plt.figure(figsize=(8, 6), dpi=300)
G, role_id, _ = build_graph(
width_basis, basis_type, list_shapes, start=0, m=5
)
G = perturb([G], 0.01)[0]
if feature_generator is None:
feature_generator = ConstFeatureGen(1)
feature_generator.gen_node_features(G)
name = basis_type + "_" + str(width_basis) + "_" + str(nb_shapes)
return G, role_id, name
def gen_syn4(nb_shapes=60, width_basis=8, feature_generator=None, m=4):
""" Synthetic Graph #4:
Start with a tree and attach cycle-shaped subgraphs.
Args:
nb_shapes : The number of shapes (here 'houses') that should be added to the base graph.
width_basis : The width of the basis graph (here a random 'Tree').
feature_generator : A `FeatureGenerator` for node features. If `None`, add constant features to nodes.
m : The tree depth.
Returns:
G : A networkx graph
role_id : Role ID for each node in synthetic graph
name : A graph identifier
"""
basis_type = "tree"
list_shapes = [["cycle", 6]] * nb_shapes
fig = plt.figure(figsize=(8, 6), dpi=300)
G, role_id, plugins = build_graph(
width_basis, basis_type, list_shapes, start=0
)
G = perturb([G], 0.01)[0]
if feature_generator is None:
feature_generator = ConstFeatureGen(1)
feature_generator.gen_node_features(G)
name = basis_type + "_" + str(width_basis) + "_" + str(nb_shapes)
return G, role_id, name
def gen_syn5(nb_shapes=80, width_basis=8, feature_generator=None, m=3):
""" Synthetic Graph #5:
Start with a tree and attach grid-shaped subgraphs.
Args:
nb_shapes : The number of shapes (here 'houses') that should be added to the base graph.
width_basis : The width of the basis graph (here a random 'grid').
feature_generator : A `FeatureGenerator` for node features. If `None`, add constant features to nodes.
m : The tree depth.
Returns:
G : A networkx graph
role_id : Role ID for each node in synthetic graph
name : A graph identifier
"""
basis_type = "tree"
list_shapes = [["grid", m]] * nb_shapes
plt.figure(figsize=(8, 6), dpi=300)
G, role_id, _ = build_graph(
width_basis, basis_type, list_shapes, start=0
)
G = perturb([G], 0.1)[0]
if feature_generator is None:
feature_generator = ConstFeatureGen(1)
feature_generator.gen_node_features(G)
name = basis_type + "_" + str(width_basis) + "_" + str(nb_shapes)
return G, role_id, name