-
Notifications
You must be signed in to change notification settings - Fork 3
LIP0012
LIP | 12 |
---|---|
Title | AbstractSum class |
Author | J. van de Wolfshaar |
Status | Draft |
Type | Standard |
Discussion | Issue #42 |
PR | |
Created | Feb 27, 2018 |
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.
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.
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)