Skip to content
Jos van de Wolfshaar edited this page Feb 27, 2018 · 1 revision

LIP 12 - AbstractSum class

LIP 12
Title AbstractSum class
Author J. van de Wolfshaar
Status Draft
Type Standard
Discussion Issue #42
PR
Created Feb 27, 2018

Introduction

LibSPN will soon contain higher order Sum classes, e.g. ParSums and SumsLayer nodes. Such nodes share a lot of the mechanics with the original Sum implementation. This LIP discusses how to exploit these commonalities, thereby improving maintainability of the code for future uses.

Technical Background

As opposed to Sum nodes, higher order sum nodes will have tensors with rank 3 for the weighted input values, a rank 2 weight matrix (e.g. [n_sums, input_size]) and accordingly shaped IVs. Apart from that, the operations for computing the values are the same in the sense that for no matter what setting for log vs. non-log space or marginal vs. MPE, they all apply weights and IVs to some 'combined' input tensor after which they apply a reduction operation over the last axis.

Proposal

This LIP proposes to have an AbstractSum node that will force some uncommon operations to be implemented by concrete descendants of it through abstract methods.

On the other hand, any operations that are common among these nodes should be implemented in AbstractSum. Its anticipated descendants are currently:

  • Sum
  • ParSums
  • SumsLayer

This list might be extended as new kinds of Sum nodes are proposed.

The implementation of AbstractSum will contain something like is displayed in the listing below. It uses _compute_value_common method that computes the values_selected_weighted tensor for any possible combination of log vs. non-log and MPE vs. MARGINAL inference. It does this by requiring an additional cwise_op parameter and the implementation of _broadcastable_weights and _reducible_values.

@abc.abstractmethod
def _broadcastable_weights(self, weight_tensor):
    """ Reshapes weight tensor so that it can be applied component-wise """

@abc.abstractmethod
def _reducible_values(self, value_tensors):
    """ Combines value tensors so that the result can be reduced """

def _compute_value_common(self, cwise_op, weight_tensor, ivs_tensor, *value_tensors,
                          weighted=True):
    """Common actions when computing value."""
    # Check inputs
    if not self._values:
        raise StructureError("%s is missing input values" % self)
    if not self._weights:
        raise StructureError("%s is missing weights" % self)
    # Prepare values
    weight_tensor, ivs_tensor, *value_tensors = self._gather_input_tensors(
        weight_tensor, ivs_tensor, *value_tensors)

    # Combine value input tensors into a tensor reducible over last axis
    values = self._reducible_values(value_tensors)

    # Component wise application of IVs
    values_selected = cwise_op(values, ivs_tensor) if self._ivs else values

    # Component wise application of weights
    if weighted:
        weight_tensor = self._broadcastable_weights(weight_tensor)
        values_weighted = cwise_op(values_selected, weight_tensor)
    else:
        values_weighted = values_selected

    return weight_tensor, ivs_tensor, values_weighted

def _compute_value(self, weight_tensor, ivs_tensor, *value_tensors):
    weight_tensor, ivs_tensor, values_weighted = self._compute_value_common(
        tf.multiply, weight_tensor, ivs_tensor, *value_tensors, weighted=True)
    return tf.reduce_sum(values_weighted, -1, keep_dims=True)

def _compute_log_value(self, weight_tensor, ivs_tensor, *value_tensors):
    weight_tensor, ivs_tensor, values_weighted = self._compute_value_common(
        tf.add, weight_tensor, ivs_tensor, *value_tensors, weighted=True)
    return utils.reduce_log_sum(values_weighted)

def _compute_mpe_value(self, weight_tensor, ivs_tensor, *value_tensors):
    weight_tensor, ivs_tensor, values_weighted = self._compute_value_common(
        tf.multiply, weight_tensor, ivs_tensor, *value_tensors, weighted=True)
    return tf.reduce_max(values_weighted, -1, keep_dims=True)

def _compute_log_mpe_value(self, weight_tensor, ivs_tensor, *value_tensors):
    weight_tensor, ivs_tensor, values_weighted = self._compute_value_common(
        tf.add, weight_tensor, ivs_tensor, *value_tensors, weighted=True)
    return tf.reduce_max(values_weighted, -1, keep_dims=True)

Decision

Clone this wiki locally