def build_linked_spn_from_scope_graph(scope_graph, k, root_scope=None, feature_values=None): """ Turning a ScopeGraph into an SPN by puttin k sum nodes for each scope and a combinatorial number of product nodes to wire the partition nodes This is the algorithm used in Poon2011 and is shown (and used) as BuildSPN in Dennis2012 """ if not root_scope: root_scope = scope_graph.root n_vars = len(root_scope.vars) if not feature_values: # # assuming binary r.v.s feature_values = [2 for _i in range(n_vars)] # # adding leaves leaves_dict = defaultdict(list) leaves_list = [] for var in sorted(root_scope.vars): for var_val in range(feature_values[var]): leaf = CategoricalIndicatorNode(var, var_val) leaves_list.append(leaf) leaves_dict[var].append(leaf) input_layer = CategoricalIndicatorLayer(nodes=leaves_list, vars=list(sorted(root_scope.vars))) # # in a first pass we need to assign each scope/region k sum nodes sum_nodes_assoc = {} for r in scope_graph.traverse_scopes(root_scope=root_scope): num_sum_nodes = k if r == root_scope: num_sum_nodes = 1 added_sum_nodes = [ SumNode(var_scope=r.vars) for i in range(num_sum_nodes) ] # # creating a sum layer sum_layer = SumLayer(added_sum_nodes) sum_nodes_assoc[r] = sum_layer # # if this is a univariate scope, we link it to leaves corresponding to its r.v. if r.is_atomic(): single_rv = set(r.vars).pop() rv_leaves = leaves_dict[single_rv] uniform_weight = 1.0 / len(rv_leaves) for s in added_sum_nodes: for leaf in rv_leaves: s.add_child(leaf, uniform_weight) # # linking to input layer sum_layer.add_input_layer(input_layer) input_layer.add_output_layer(sum_layer) layers = [] # # looping again to add and wire product nodes for r in scope_graph.traverse_scopes(root_scope=root_scope): sum_layer = sum_nodes_assoc[r] layers.append(sum_layer) for p in r.partitions: sum_layer_descs = [sum_nodes_assoc[r_p] for r_p in p.scopes] sum_nodes_lists = [ list(layer.nodes()) for layer in sum_layer_descs ] num_prod_nodes = numpy.prod([len(r_p) for r_p in sum_nodes_lists]) # # adding product nodes added_prod_nodes = [ ProductNode(var_scope=r.vars) for i in range(num_prod_nodes) ] # # adding product layer and linking prod_layer = ProductLayer(added_prod_nodes) sum_layer.add_input_layer(prod_layer) prod_layer.add_output_layer(sum_layer) for desc in sum_layer_descs: prod_layer.add_input_layer(desc) desc.add_output_layer(prod_layer) layers.append(prod_layer) # # linking to parents sum_nodes_parents = sum_layer.nodes() for sum_node in sum_nodes_parents: uniform_weight = 1.0 / (len(added_prod_nodes) * len(r.partitions)) for prod_node in added_prod_nodes: sum_node.add_child(prod_node, uniform_weight) # # linking to children sum_nodes_to_wire = list(itertools.product(*sum_nodes_lists)) assert len(added_prod_nodes) == len(sum_nodes_to_wire) for prod_node, sum_nodes in zip(added_prod_nodes, sum_nodes_to_wire): for sum_node in sum_nodes: prod_node.add_child(sum_node) # # toposort layers = topological_layer_sort(layers) spn = LinkedSpn(layers=layers, input_layer=input_layer) return spn
def test_compute_block_layer_depths_II(): input_layer = CategoricalIndicatorLayer([]) sum_layer_1 = SumLayer([]) prod_layer_21 = ProductLayer([]) prod_layer_22 = ProductLayer([]) sum_layer_3 = SumLayer([]) prod_layer_41 = ProductLayer([]) prod_layer_42 = ProductLayer([]) sum_layer_5 = SumLayer([]) # # linking them sum_layer_1.add_input_layer(input_layer) input_layer.add_output_layer(sum_layer_1) prod_layer_21.add_input_layer(sum_layer_1) prod_layer_22.add_input_layer(sum_layer_1) sum_layer_1.add_output_layer(prod_layer_21) sum_layer_1.add_output_layer(prod_layer_22) sum_layer_3.add_input_layer(prod_layer_21) sum_layer_3.add_input_layer(prod_layer_22) sum_layer_3.add_input_layer(input_layer) prod_layer_21.add_output_layer(sum_layer_3) prod_layer_22.add_output_layer(sum_layer_3) input_layer.add_output_layer(sum_layer_3) prod_layer_41.add_input_layer(sum_layer_3) prod_layer_41.add_input_layer(sum_layer_1) prod_layer_42.add_input_layer(sum_layer_3) prod_layer_42.add_input_layer(input_layer) sum_layer_3.add_output_layer(prod_layer_41) sum_layer_3.add_output_layer(prod_layer_42) sum_layer_1.add_output_layer(prod_layer_41) input_layer.add_output_layer(prod_layer_42) sum_layer_5.add_input_layer(prod_layer_41) sum_layer_5.add_input_layer(prod_layer_42) prod_layer_41.add_output_layer(sum_layer_5) prod_layer_42.add_output_layer(sum_layer_5) # # creating an SPN with unordered layer list spn = LinkedSpn(input_layer=input_layer, layers=[ sum_layer_1, sum_layer_3, sum_layer_5, prod_layer_21, prod_layer_22, prod_layer_41, prod_layer_42 ]) depth_dict = compute_block_layer_depths(spn) print(spn) for layer, depth in depth_dict.items(): print(layer.id, depth)