Example #1
0
    def generate_weights(self,
                         initializer=tf.initializers.constant(1.0),
                         trainable=True,
                         input_sizes=None,
                         log=False,
                         name=None):
        """Generate a weights node matching this sum node and connect it to
        this sum.

        The function calculates the number of weights based on the number
        of input values of this sum. Therefore, weights should be generated
        once all inputs are added to this node.

        Args:
            initializer: Initial value of the weights.
            trainable (bool): See :class:`~libspn.Weights`.
            input_sizes (list of int): Pre-computed sizes of each input of
                this node.  If given, this function will not traverse the graph
                to discover the sizes.
            log (bool): If "True", the weights are represented in log space.
            name (str): Name of the weighs node. If ``None`` use the name of the
                        sum + ``_Weights``.

        Return:
            Weights: Generated weights node.
        """
        if not self._values:
            raise StructureError("%s is missing input values" % self)
        if name is None:
            name = self._name + "_Weights"

        # Set sum node sizes to inferred _sum_input_sizes
        sum_input_sizes = self._sum_sizes
        max_size = self._max_sum_size

        # Mask is used to select the indices to assign the value to, since the weights tensor can
        # be larger than the total number of weights being modeled due to padding
        mask = self._build_mask().reshape((-1, ))

        # Generate weights
        weights = Weights(initializer=initializer,
                          num_weights=max_size,
                          num_sums=len(sum_input_sizes),
                          log=log,
                          trainable=trainable,
                          mask=mask.tolist(),
                          name=name)
        self.set_weights(weights)
        return weights
    def generate_permutations(self, factors):
        if not factors:
            raise StructureError(
                "{}: factors needs to be a non-empty sequence.")
        num_input_scopes = self._get_num_input_scopes()
        factor_cumprod = np.cumprod(factors)
        factor_prod = factor_cumprod[-1]
        if factor_prod < num_input_scopes:
            raise StructureError(
                "{}: not enough factors to cover all variables ({} vs. {}).".
                format(self, factor_prod, num_input_scopes))
        for i, fc in enumerate(factor_cumprod[:-1]):
            if fc >= num_input_scopes:
                raise StructureError(
                    "{}: too many factors, taking out the bottom {} products still "
                    "results in {} factors while {} are needed.".format(
                        self,
                        len(factors) - i - 1, fc, num_input_scopes))

        # Now we generate the random index permutations
        perms = [
            np.random.permutation(num_input_scopes).astype(int).tolist()
            for _ in range(self._num_decomps)
        ]

        num_m1 = factor_prod - num_input_scopes
        if num_m1 > 0:
            # e.g. num_m1 == 2 and factor_prod = 32. Then rate_m1 is 16, so once every 16 values
            # we should leave a variable slot empty
            rate_m1 = int(np.floor(factor_prod / num_m1))

            for p in perms:
                for i in range(num_m1):
                    p.insert(i * rate_m1, -1)
        self._permutations = perms = np.asarray(perms)
        return perms
Example #3
0
 def _compute_valid(self, *value_scopes):
     if not self._values:
         raise StructureError("%s is missing input values." % self)
     value_scopes_ = self._gather_input_scopes(*value_scopes)
     # If already invalid, return None
     if any(s is None for s in value_scopes_):
         return None
     # Check product decomposability
     flat_value_scopes = list(chain.from_iterable(value_scopes_))
     for s1, s2 in combinations(flat_value_scopes, 2):
         if s1 & s2:
             self.__info(
                 "%s is not decomposable with input value scopes %s", self,
                 flat_value_scopes)
             return None
     return self._compute_scope(*value_scopes)
Example #4
0
    def _compute_log_value(self, *input_tensors):
        # Check inputs
        if not self._inputs:
            raise StructureError("%s is missing inputs." % self)

        @tf.custom_gradient
        def value_gradient(*input_tensors):
            def gradient(gradients):
                scattered_grads = self._compute_log_mpe_path(
                    gradients, *input_tensors)
                return [sg for sg in scattered_grads if sg is not None]

            gathered_inputs = self._gather_input_tensors(*input_tensors)
            # Concatenate inputs
            return tf.concat(gathered_inputs, 1), gradient

        return value_gradient(*input_tensors)
Example #5
0
    def generate_weights(self,
                         initializer=tf.initializers.constant(1.0),
                         trainable=True,
                         input_sizes=None,
                         log=False,
                         name=None):
        """Generate a weights node matching this sum node and connect it to
        this sum.

        The function calculates the number of weights based on the number
        of input values of this sum. Therefore, weights should be generated
        once all inputs are added to this node.

        Args:
            initializer: Initial value of the weights.
            trainable (bool): See :class:`~libspn.Weights`.
            input_sizes (list of int): Pre-computed sizes of each input of
                this node.  If given, this function will not traverse the graph
                to discover the sizes.
            log (bool): If "True", the weights are represented in log space.
            name (str): Name of the weighs node. If ``None`` use the name of the
                        sum + ``_Weights``.

        Return:
            Weights: Generated weights node.
        """
        if not self._values:
            raise StructureError("%s is missing input values" % self)
        if name is None:
            name = self._name + "_Weights"
        # Count all input values
        if input_sizes:
            num_values = sum(
                input_sizes[2:])  # Skip latent_indicators, weights
        else:
            num_values = max(self._sum_sizes)
        # Generate weights
        weights = Weights(initializer=initializer,
                          num_weights=num_values,
                          num_sums=self._num_sums,
                          log=log,
                          trainable=trainable,
                          name=name)
        self.set_weights(weights)
        return weights
Example #6
0
    def generate_weights(self,
                         initializer=tf.initializers.constant(1.0),
                         trainable=True,
                         log=False,
                         name=None,
                         input_sizes=None):
        """Generate a weights node matching this sum node and connect it to
        this sum.

        The function calculates the number of weights based on the number
        of input values of this sum. Therefore, weights should be generated
        once all inputs are added to this node.

        Args:
            init_value: Initial value of the weights. For possible values, see
                :meth:`~libspn.utils.broadcast_value`.
            trainable (bool): See :class:`~libspn.Weights`.
            input_sizes (list of int): Pre-computed sizes of each input of
                this node.  If given, this function will not traverse the graph
                to discover the sizes.
            log (bool): If "True", the weights are represented in log space.
            name (str): Name of the weighs node. If ``None`` use the name of the
                        sum + ``_Weights``.

        Return:
            Weights: Generated weights node.
        """
        if not self._values:
            raise StructureError("%s is missing input values" % self)
        if name is None:
            name = self._name + "_Weights"

        # Count all input values
        num_inputs = self.child.dim_nodes
        # Generate weights
        weights = BlockWeights(num_inputs=num_inputs,
                               num_outputs=self._num_sums,
                               num_decomps=self.dim_decomps,
                               num_scopes=self.dim_scope,
                               name=name,
                               trainable=trainable,
                               in_logspace=log,
                               initializer=initializer)
        self.set_weights(weights)
        return weights
Example #7
0
    def _compute_log_mpe_path(self, counts, *value_values,
                              use_unweighted=False, sample=False, sample_prob=None):
        # Check inputs
        if not self._values:
            raise StructureError("%s is missing input values." % self)

        # For each unique (input, index) pair in the values list, collect counts
        # index of all counts for which the pair is a child of
        gather_counts_indices, unique_inputs = self._collect_count_indices_per_input()

        if self._num_prods > 1:
            # Gather columns from the counts tensor, per unique (input, index) pair
            reducible_values = utils.gather_cols_3d(counts, gather_counts_indices)

            # Sum gathered counts together per unique (input, index) pair
            summed_counts = tf.reduce_sum(reducible_values, axis=-1)
        else:
            # Calculate total inputs size
            inputs_size = sum([v_input.get_size(v_value) for v_input, v_value in
                               zip(self._values, value_values)])

            # Tile counts only if input is larger than 1
            summed_counts = (tf.tile(counts, [1, inputs_size]) if inputs_size > 1
                             else counts)

        # For each unique input in the values list, calculate the number of
        # unique indices
        unique_inp_sizes = [len(v) for v in unique_inputs.values()]

        # Split the summed-counts tensor per unique input, based on input-sizes
        unique_input_counts = tf.split(summed_counts, unique_inp_sizes, axis=-1) \
            if len(unique_inp_sizes) > 1 else [summed_counts]

        # Scatter each unique-counts tensor to the respective input, only once
        # per unique input in the values list
        scattered_counts = [None] * len(self._values)
        for (node, inds), cnts in zip(unique_inputs.items(), unique_input_counts):
            for i, (inp, val) in enumerate(zip(self._values, value_values)):
                if inp.node == node:
                    scattered_counts[i] = utils.scatter_cols(
                        cnts, inds, int(val.get_shape()[0 if val.get_shape().ndims
                                                        == 1 else 1]))
                    break

        return scattered_counts
Example #8
0
 def _compute_value_common(self, *value_tensors, padding_value=0.0):
     """Common actions when computing value."""
     # Check inputs
     if not self._values:
         raise StructureError("%s is missing input values." % self)
     # Prepare values
     if self._num_prods > 1:
         indices, value_tensor = self._combine_values_and_indices(value_tensors)
         # Create a 3D tensor with dimensions [batch, num-prods, max-prod-input-sizes]
         # The last axis will have zeros or ones (for log or non-log) when the
         # prod-input-size < max-prod-input-sizes
         reducible_values = utils.gather_cols_3d(value_tensor, indices,
                                                 pad_elem=padding_value)
         return reducible_values
     else:
         # Gather input tensors
         value_tensors = self._gather_input_tensors(*value_tensors)
         return tf.concat(value_tensors, 1)
Example #9
0
    def assign(self, value):
        """Return a TF operation assigning values to the weights.

        Args:
            value: The value to assign to the weights.
        Returns:
            Tensor: The assignment operation.
        """
        if self._log:
            raise StructureError(
                "Trying to assign non-log values to log-weights.")

        value = tf.where(tf.is_nan(value), tf.ones_like(value) * 0.01, value)
        if self._mask and not all(self._mask):
            # Only perform masking if mask is given and mask contains any 'False'
            value *= tf.cast(tf.reshape(self._mask, value.shape),
                             dtype=conf.dtype)
        value = value / tf.reduce_sum(value, axis=-1, keepdims=True)
        return tf.assign(self._variable, value)
Example #10
0
    def _get_flat_value_scopes(self, weight_scopes, ivs_scopes, *value_scopes):
        """Get a flat representation of the value scopes per sum.

        Args:
            weight_scopes (list): A list of ``Scope``s corresponding to the weights.
            ivs_scopes (list): A list of ``Scope``s corresponding to the IVs.
            value_scopes (tuple): A ``tuple`` of ``list``s of ``Scope``s corresponding to the
                                  scope lists of the children of this node.

        Returns:
            A tuple of flat value scopes corresponding to this node's output. The IVs scopes and
            the value scopes.
        """
        if not self._values:
            raise StructureError("%s is missing input values" % self)
        _, ivs_scopes, *value_scopes = self._gather_input_scopes(
            weight_scopes, ivs_scopes, *value_scopes)
        return list(
            chain.from_iterable(value_scopes)), ivs_scopes, value_scopes
Example #11
0
    def update_log(self, value):
        """Return a TF operation adding the log-values to the log-weights.

        Args:
            value: The log-value to be added to the log-weights.

        Returns:
            Tensor: The assignment operation.
        """
        if not self._log:
            raise StructureError(
                "Trying to update non-log weights with log values.")
        if self._mask and not all(self._mask):
            # Only perform masking if mask is given and mask contains any 'False'
            value += tf.log(
                tf.cast(tf.reshape(self._mask, value.shape), dtype=conf.dtype))
        # w_ij: w_ij + Δw_ij
        update_value = self._variable + value
        normalized_value = tf.nn.log_softmax(update_value, axis=-1)
        return tf.assign(self._variable, normalized_value)
    def _compute_log_value(self, *value_tensors):
        if self._permutations is None:
            raise StructureError("First need to determine permutations")
        # [batch, scope, node]
        child, = value_tensors
        dim_scope_in = self.child.num_vars
        dim_nodes_in = self.child.num_vals if isinstance(
            self.child, IndicatorLeaf) else self.child.num_components
        zero_padded = tf.concat([
            tf.zeros([1, tf.shape(child)[0], dim_nodes_in]),
            tf.transpose(tf.reshape(child, [-1, dim_scope_in, dim_nodes_in]),
                         (1, 0, 2))
        ],
                                axis=0)
        gather_indices = self._permutations + 1
        permuted = self._gather_op = tf.gather(zero_padded,
                                               np.transpose(gather_indices))
        self._zero_padded_shape = tf.shape(zero_padded)

        return permuted
Example #13
0
 def _compute_valid(self, *value_scopes):
     if not self._values:
         raise StructureError("%s is missing input values." % self)
     value_scopes_ = self._gather_input_scopes(*value_scopes)
     # If already invalid, return None
     if any(s is None for s in value_scopes_):
         return None
     # Check product decomposability
     flat_value_scopes = list(chain.from_iterable(value_scopes_))
     # Divide gathered and flattened value scopes into sublists, one per
     # modeled product op.
     prod_input_sizes = np.cumsum(np.array(self._prod_input_sizes)).tolist()
     prod_input_sizes.insert(0, 0)
     value_scopes_lists = [flat_value_scopes[start:stop] for start, stop in
                           zip(prod_input_sizes[:-1], prod_input_sizes[1:])]
     for scopes in value_scopes_lists:
         for s1, s2 in combinations(scopes, 2):
             if s1 & s2:
                 ProductsLayer.info("%s is not decomposable", self)
                 return None
     return self._compute_scope(*value_scopes)
Example #14
0
    def learn(self,
              loss=None,
              optimizer=None,
              post_gradient_ops=True,
              name="LearnGD"):
        """Assemble TF operations performing GD learning of the SPN. This includes setting up
        the loss function (with regularization), setting up the optimizer and setting up
        post gradient-update ops.

        Args:
            loss (Tensor): The operation corresponding to the loss to minimize.
            optimizer (tf.train.Optimizer): A TensorFlow optimizer to use for minimizing the loss.
            post_gradient_ops (bool): Whether to use post-gradient ops such as normalization.

        Returns:
            A tuple of grouped update Ops and a loss Op.
        """
        if self._learning_task_type == LearningTaskType.SUPERVISED and self._root.latent_indicators is None:
            raise StructureError(
                "{}: the SPN rooted at {} does not have a latent IndicatorLeaf node, so cannot "
                "setup conditional class probabilities.".format(
                    self._name, self._root))

        # If a loss function is not provided, define the loss function based
        # on learning-type and learning-method
        with tf.name_scope(name):
            with tf.name_scope("Loss"):
                if loss is None:
                    if self._learning_method == LearningMethodType.GENERATIVE:
                        loss = self.negative_log_likelihood()
                    else:
                        loss = self.cross_entropy_loss()
            # Assemble TF ops for optimizing and weights normalization
            with tf.name_scope("ParameterUpdate"):
                minimize = optimizer.minimize(loss=loss)
                if post_gradient_ops:
                    return self.post_gradient_update(minimize), loss
                else:
                    return minimize, loss
Example #15
0
    def _compute_mpe_path(self,
                          counts,
                          *value_values,
                          add_random=False,
                          use_unweighted=False):
        # Check inputs
        if not self._values:
            raise StructureError("%s is missing input values." % self)

        def process_input(v_input, v_value):
            input_size = v_input.get_size(v_value)
            # Tile the counts if input is larger than 1
            return (tf.tile(counts, [1, input_size])
                    if input_size > 1 else counts)

        # For each input, pass counts to all elements selected by indices
        value_counts = [(process_input(v_input, v_value), v_value)
                        for v_input, v_value in zip(self._values, value_values)
                        ]
        # TODO: Scatter to input tensors can be merged with tiling to reduce
        # the amount of operations.
        return self._scatter_to_input_tensors(*value_counts)
Example #16
0
    def create_products(self, input_sizes=None, num_inputs=None):
        """Based on the number and size of inputs connected to this node, model
        products by permuting over the inputs.
        """
        if not self._values:
            raise StructureError("%s is missing input values." % self)

        self._input_sizes = input_sizes if input_sizes is not None \
            else list(self.get_input_sizes())
        self._num_inputs = num_inputs if num_inputs is not None \
            else len(self._input_sizes)

        # Calculate number of products this node would model.
        if self._num_inputs == 1:
            self._num_prods = 1
        else:
            self._num_prods = int(np.prod(self._input_sizes))

        # Create indices by permuting over the input space, such that inputs
        # for the products can be generated by gathering from concatenated
        # input values.
        self._permuted_indices = self.permute_indices(self._input_sizes)
Example #17
0
    def _compute_valid(self, *value_scopes):
        if not self._values:
            raise StructureError("%s is missing input values." % self)
        value_scopes_ = self._gather_input_scopes(*value_scopes)
        # If already invalid, return None
        if any(s is None for s in value_scopes_):
            return None
        if self._num_prods == 1:
            for s1, s2 in combinations(chain(*value_scopes_), 2):
                if s1 & s2:
                    PermuteProducts.info(
                        "%s is not decomposable with input value "
                        "scopes %s", self, value_scopes_[:10])
                    return None

        # Check product decomposability
        for perm_val_scope in product(*value_scopes_):
            for s1, s2 in combinations(perm_val_scope, 2):
                if s1 & s2:
                    PermuteProducts.info("%s is not decomposable", self)
                    return None
        return self._compute_scope(*value_scopes)
Example #18
0
    def _compute_valid(self, weight_scopes, latent_indicators_scopes,
                       *value_scopes):
        flat_value_scopes, latent_indicators_scopes_, *value_scopes_ = self._get_flat_value_scopes(
            weight_scopes, latent_indicators_scopes, *value_scopes)
        # If already invalid, return None
        if (any(s is None for s in value_scopes_) or
            (self._latent_indicators and latent_indicators_scopes_ is None)):
            return None

        # Split the flat value scopes based on value input sizes
        split_indices = np.cumsum(self._sum_sizes)[:-1]

        # IndicatorLeaf
        if self._latent_indicators:
            # Verify number of IndicatorLeaf
            if len(latent_indicators_scopes_) != len(flat_value_scopes):
                raise StructureError(
                    "Number of IndicatorLeaf (%s) and values (%s) does "
                    "not match for %s" % (len(latent_indicators_scopes_),
                                          len(flat_value_scopes), self))

            # Go over IndicatorLeaf involved for each sum. Scope size should be exactly one
            for iv_scopes_for_sum in np.split(latent_indicators_scopes_,
                                              split_indices):
                if len(Scope.merge_scopes(iv_scopes_for_sum)) != 1:
                    return None

        # Go over value input scopes for each sum being modeled. Within a single sum, the scope of
        # all the inputs should be the same
        for scope_slice in np.split(flat_value_scopes, split_indices):
            first_scope = scope_slice[0]
            if any(s != first_scope for s in scope_slice[1:]):
                self.info("%s is not complete with input value scopes %s",
                          self, flat_value_scopes)
                return None

        return self._compute_scope(weight_scopes, latent_indicators_scopes,
                                   *value_scopes)
Example #19
0
    def _compute_mpe_path_common(self, counts, *input_values):
        if not self._values:
            raise StructureError("{} is missing input values.".format(self))
        # Concatenate inputs along channel axis, should already be done during forward pass
        inp_concat = self._prepare_convolutional_processing(*input_values)
        spatial_counts = tf.reshape(counts, (-1, ) + self.output_shape_spatial)

        input_counts = tf.nn.conv2d_backprop_input(
            input_sizes=tf.shape(inp_concat),
            filter=self._dense_connections,
            out_backprop=spatial_counts,
            strides=[1] + self._strides + [1],
            padding='VALID',
            dilations=[1] + self._dilation_rate + [1],
            data_format="NHWC")

        # In case we have explicitly padded the tensor before forward convolution, we should
        # slice the counts now
        pad_left, pad_right, pad_bottom, pad_top = self.pad_sizes()
        if not any([pad_bottom, pad_left, pad_right, pad_top]):
            return self._split_to_children(input_counts)
        return self._split_to_children(input_counts[:, pad_top:-pad_bottom,
                                                    pad_left:-pad_right, :])
Example #20
0
    def learn(self, loss=None, optimizer=None, post_gradient_ops=True):
        """Assemble TF operations performing GD learning of the SPN. This includes setting up
        the loss function (with regularization), setting up the optimizer and setting up
        post gradient-update ops.

        loss (Tensor): The operation corresponding to the loss to minimize.
        optimizer (tf.train.Optimizer): A TensorFlow optimizer to use for minimizing the loss.

        Returns:
            A tuple of grouped update Ops and a loss Op.
        """
        if self._learning_task_type == LearningTaskType.SUPERVISED and self._root.ivs is None:
            raise StructureError(
                "{}: the SPN rooted at {} does not have a latent IVs node, so cannot setup "
                "conditional class probabilities.".format(
                    self._name, self._root))

        # If a loss function is not provided, define the loss function based
        # on learning-type and learning-method
        with tf.name_scope("Loss"):
            if loss is None:
                loss = (self.negative_log_likelihood() if self._learning_method
                        == LearningMethodType.GENERATIVE else
                        self.cross_entropy_loss())
            if self._l1_regularize_coeff is not None or self._l2_regularize_coeff is not None:
                loss += self.regularization_loss()

        # Assemble TF ops for optimizing and weights normalization
        optimizer = optimizer if optimizer is not None else self._optimizer
        if optimizer is None:
            raise ValueError("Did not specify GD optimizer")
        with tf.name_scope("ParameterUpdate"):
            minimize = optimizer.minimize(loss=loss)
            if post_gradient_ops:
                return self.post_gradient_update(minimize), loss
            else:
                return minimize, loss
Example #21
0
    def generate_weights(self, init_value=1, trainable=True,
                         input_sizes=None, name=None):
        """Generate a weights node matching this sum node and connect it to
        this sum.

        The function calculates the number of weights based on the number
        of input values of this sum. Therefore, weights should be generated
        once all inputs are added to this node.

        Args:
            init_value: Initial value of the weights. For possible values, see
                :meth:`~libspn.utils.broadcast_value`.
            trainable (bool): See :class:`~libspn.Weights`.
            input_sizes (list of int): Pre-computed sizes of each input of
                this node.  If given, this function will not traverse the graph
                to discover the sizes.
            name (str): Name of the weighs node. If ``None`` use the name of the
                        sum + ``_Weights``.

        Return:
            Weights: Generated weights node.
        """
        if not self._values:
            raise StructureError("%s is missing input values" % self)
        if name is None:
            name = self._name + "_Weights"
        # Count all input values
        if not input_sizes:
            input_sizes = self.get_input_sizes()
        num_values = sum(input_sizes[2:])  # Skip ivs, weights
        # Generate weights
        weights = Weights(init_value=init_value,
                          num_weights=num_values,
                          trainable=trainable, name=name)
        self.set_weights(weights)
        return weights
Example #22
0
    def _compute_reducible(self,
                           w_tensor,
                           ivs_tensor,
                           *input_tensors,
                           weighted=True,
                           dropconnect_keep_prob=None):
        """Computes a reducible ``Tensor`` so that reducing it over the last axis can be used for
        marginal inference, MPE inference and MPE path computation.

        Args:
            w_tensor (Tensor): A ``Tensor`` with the value of the weights of shape
                ``[num_sums, max_sum_size]``
            ivs_tensor (Tensor): A ``Tensor`` with the value of the IVs corresponding to this node
                of shape ``[batch, num_sums * max_sum_size]``.
            input_tensors (tuple): A ``tuple`` of ``Tensors``s with the values of the children of
                this node.
            weighted (bool): Whether to apply the weights to the reducible values if possible.
            dropconnect_keep_prob (Tensor or float): A scalar ``Tensor`` or float that holds the
                dropconnect keep probability. By default it is None, in which case no dropconnect
                is being used.

        Returns:
            A ``Tensor`` of shape ``[batch, num_sums, max_sum_size]`` that can be used for computing
            marginal inference, MPE inference, gradients or MPE paths.
        """
        if not self._values:
            raise StructureError("%s is missing input values" % self)
        if not self._weights:
            raise StructureError("%s is missing weights" % self)

        # Prepare tensors for component-wise application of weights and IVs
        w_tensor, ivs_tensor, reducible = self._prepare_component_wise_processing(
            w_tensor, ivs_tensor, *input_tensors, zero_prob_val=-float('inf'))

        # Apply latent IVs
        if self._ivs:
            reducible = utils.cwise_add(reducible, ivs_tensor)

        # Apply weights
        if weighted:
            # Maybe apply dropconnect
            dropconnect_keep_prob = utils.maybe_first(
                dropconnect_keep_prob, self._dropconnect_keep_prob)

            if dropconnect_keep_prob is not None and dropconnect_keep_prob != 1.0:
                if self._ivs:
                    self.logger.warn(
                        "Using dropconnect and latent IVs simultaneously. "
                        "This might result in zero probabilities throughout and unpredictable "
                        "behavior of learning. Therefore, dropconnect is turned off for node {}."
                        .format(self))
                else:
                    mask = self._create_dropout_mask(dropconnect_keep_prob,
                                                     tf.shape(reducible),
                                                     log=True)
                    w_tensor = utils.cwise_add(w_tensor, mask)
                    if conf.renormalize_dropconnect:
                        w_tensor = tf.nn.log_softmax(w_tensor, axis=-1)
            reducible = utils.cwise_add(reducible, w_tensor)

        return reducible
 def _assert_generated(self):
     if self._perms is None:
         raise StructureError(
             "{}: First need to generate decompositions.".format(self))
Example #24
0
    def _compute_log_mpe_path(self,
                              counts,
                              *value_values,
                              use_unweighted=False,
                              sample=False,
                              sample_prob=None):
        # Path per product node is calculated by permuting backwards to the
        # input nodes, then adding the appropriate counts per input, and then
        # scattering the summed counts to value inputs

        # Check inputs
        if not self._values:
            raise StructureError("%s is missing input values." % self)

        def permute_counts(input_sizes):
            # Function that permutes count values, backward to inputs.
            counts_indices_list = []

            def range_with_blocksize(start, stop, block_size, step):
                # A function that produces an arithmetic progression (Similar to
                # Python's range() function), but for a given block-size of
                # consecutive numbers.
                # E.g: range_with_blocksize(start=0, stop=20, block_size=3, step=5)
                # = [0, 1, 2, 5, 6, 7, 10, 11, 12, 15, 16, 17]
                counts_indices = []
                it = 0
                low = start
                high = low + block_size
                while low < stop:
                    counts_indices = counts_indices + list(range(low, high))
                    it += 1
                    low = start + (it * step)
                    high = low + block_size

                return counts_indices

            for inp, inp_size in enumerate(input_sizes):
                block_size = int(self._num_prods /
                                 np.prod(input_sizes[:inp + 1]))
                step = int(np.prod(input_sizes[inp:]))
                for i in range(inp_size):
                    start = i * block_size
                    stop = self._num_prods - (block_size * (inp_size - i - 1))
                    counts_indices_list.append(
                        range_with_blocksize(start, stop, block_size, step))

            return counts_indices_list

        if (len(self._input_sizes) > 1):
            permuted_indices = permute_counts(self._input_sizes)
            summed_counts = tf.reduce_sum(utils.gather_cols_3d(
                counts, permuted_indices),
                                          axis=-1)
            processed_counts_list = tf.split(summed_counts,
                                             self._input_sizes,
                                             axis=-1)
        else:  # For single input case, i.e, when _num_prods = 1
            summed_counts = self._input_sizes[0] * [counts]
            processed_counts_list = [tf.concat(values=summed_counts, axis=-1)]

        # Zip lists of processed counts and value_values together for scattering
        value_counts = zip(processed_counts_list, value_values)

        return self._scatter_to_input_tensors(*value_counts)
Example #25
0
def convert_to_layer_nodes(root):
    """
    At each level in the SPN rooted in the 'root' node, model all the nodes
    as a single layer-node.

    Args:
        root (Node): The root of the SPN graph.

    Returns:
        root (Node): The root of the SPN graph, with each layer modelled as a
                     single layer-node.
    """

    parents = defaultdict(list)
    depths = defaultdict(list)
    node_to_depth = OrderedDict()
    node_to_depth[root] = 1

    def get_parents(node):
        # Add to Parents dict
        if node.is_op:
            for i in node.inputs:
                if (i and  # Input not empty
                        not (i.is_param or i.is_var)):
                    parents[i.node].append(node)
                    node_to_depth[i.node] = node_to_depth[node] + 1

    def permute_inputs(input_values, input_sizes):
        # For a given list of inputs and their corresponding sizes, create a
        # nested-list of (input, index) pairs.
        # E.g: input_values = [(A, [2, 5]), (B, None)]
        #      input_sizes = [2, 3]
        #      inputs = [[('A', 2), ('A', 5)],
        #                [('B', 0), ('B', 1), ('B', 2)]]
        inputs = [
            list(product([inp.node], inp.indices)) if inp and inp.indices else
            list(product([inp.node], list(range(inp_size))))
            for inp, inp_size in zip(input_values, input_sizes)
        ]

        # For a given nested-list of (input, index) pairs, permute over the inputs
        # E.g: permuted_inputs = [('A', 2), ('B', 0),
        #                         ('A', 2), ('B', 1),
        #                         ('A', 2), ('B', 2),
        #                         ('A', 5), ('B', 0),
        #                         ('A', 5), ('B', 1),
        #                         ('A', 5), ('B', 2)]
        permuted_inputs = list(product(*[inps for inps in inputs]))
        return list(chain(*permuted_inputs))

    # Create a parents dictionary of the SPN graph
    traverse_graph(root, fun=get_parents, skip_params=True)

    # Create a depth dictionary of the SPN graph
    for key, value in node_to_depth.items():
        depths[value].append(key)
    spn_depth = len(depths)

    # Iterate through each depth of the SPN, starting from the deepest layer,
    # moving up to the root node
    for depth in range(spn_depth, 1, -1):
        if isinstance(depths[depth][0], (Sum, ParallelSums)):  # A Sums Layer
            # Create a default SumsLayer node
            with tf.name_scope("Layer%s" % depth):
                sums_layer = SumsLayer(name="SumsLayer-%s.%s" % (depth, 1))
            # Initialize a counter for keeping track of number of sums
            # modelled in the layer node
            layer_num_sums = 0
            # Initialize an empty list for storing sum-input-sizes of sums
            # modelled in the layer node
            num_or_size_sums = []
            # Iterate through each node at the current depth of the SPN
            for node in depths[depth]:
                # TODO: To be replaced with node.num_sums once AbstractSums
                # class is introduced
                # No. of sums modelled by the current node
                node_num_sums = (1 if isinstance(node, Sum) else node.num_sums)
                # Add Input values of the current node to the SumsLayer node
                sums_layer.add_values(*node.values * node_num_sums)
                # Add sum-input-size, of each sum modelled in the current node,
                # to the list
                num_or_size_sums += [sum(node.get_input_sizes()[2:])
                                     ] * node_num_sums
                # Visit each parent of the current node
                for parent in parents[node]:
                    try:
                        # 'Values' in case parent is an Op node
                        values = list(parent.values)
                    except AttributeError:
                        # 'Inputs' in case parent is a Concat node
                        values = list(parent.inputs)
                    # Iterate through each input value of the current parent node
                    for i, value in enumerate(values):
                        # If the value is the current node
                        if value.node == node:
                            # Check if it has indices
                            if value.indices is not None:
                                # If so, then just add the num-sums of the
                                # layer-op as offset
                                indices = (np.asarray(value.indices) +
                                           layer_num_sums).tolist()
                            else:
                                # If not, then create a list accrodingly
                                indices = list(
                                    range(layer_num_sums,
                                          (layer_num_sums + node_num_sums)))
                            # Replace previous (node) Input value in the
                            # current parent node, with the new layer-node value
                            values[i] = (sums_layer, indices)
                            break  # Once child-node found, don't have to search further
                    # Reset values of the current parent node, by including
                    # the new child (Layer-node)
                    try:
                        # set 'values' in case parent is an Op node
                        parent.set_values(*values)
                    except AttributeError:
                        # set 'inputs' in case parent is a Concat node
                        parent.set_inputs(*values)
                # Increment num-sums-counter of the layer-node
                layer_num_sums += node_num_sums
                # Disconnect
                node.disconnect_inputs()

            # After all nodes at a certain depth are modelled into a Layer-node,
            # set num-sums parameter accordingly
            sums_layer.set_sum_sizes(num_or_size_sums)
        elif isinstance(depths[depth][0],
                        (Product, PermuteProducts)):  # A Products Layer
            with tf.name_scope("Layer%s" % depth):
                prods_layer = ProductsLayer(name="ProductsLayer-%s.%s" %
                                            (depth, 1))
            # Initialize a counter for keeping track of number of prods
            # modelled in the layer node
            layer_num_prods = 0
            # Initialize an empty list for storing prod-input-sizes of prods
            # modelled in the layer node
            num_or_size_prods = []
            # Iterate through each node at the current depth of the SPN
            for node in depths[depth]:
                # Get input values and sizes of the product node
                input_values = list(node.values)
                input_sizes = list(node.get_input_sizes())
                if isinstance(node, PermuteProducts):
                    # Permute over input-values to model permuted products
                    input_values = permute_inputs(input_values, input_sizes)
                    node_num_prods = node.num_prods
                    prod_input_size = len(input_values) // node_num_prods
                elif isinstance(node, Product):
                    node_num_prods = 1
                    prod_input_size = int(sum(input_sizes))

                # Add Input values of the current node to the ProductsLayer node
                prods_layer.add_values(*input_values)
                # Add prod-input-size, of each product modelled in the current
                # node, to the list
                num_or_size_prods += [prod_input_size] * node_num_prods
                # Visit each parent of the current node
                for parent in parents[node]:
                    values = list(parent.values)
                    # Iterate through each input value of the current parent node
                    for i, value in enumerate(values):
                        # If the value is the current node
                        if value.node == node:
                            # Check if it has indices
                            if value.indices is not None:
                                # If so, then just add the num-prods of the
                                # layer-op as offset
                                indices = value.indices + layer_num_prods
                            else:
                                # If not, then create a list accrodingly
                                indices = list(
                                    range(layer_num_prods,
                                          (layer_num_prods + node_num_prods)))
                            # Replace previous (node) Input value in the
                            # current parent node, with the new layer-node value
                            values[i] = (prods_layer, indices)
                    # Reset values of the current parent node, by including
                    # the new child (Layer-node)
                    parent.set_values(*values)
                # Increment num-prods-counter of the layer node
                layer_num_prods += node_num_prods
                # Disconnect
                node.disconnect_inputs()

            # After all nodes at a certain depth are modelled into a Layer-node,
            # set num-prods parameter accordingly
            prods_layer.set_prod_sizes(num_or_size_prods)

        elif isinstance(depths[depth][0],
                        (SumsLayer, ProductsLayer, Concat)):  # A Concat node
            pass
        else:
            raise StructureError("Unknown node-type: {}".format(
                depths[depth][0]))

    return root
Example #26
0
 def convert(input_like):
     inpt = Input.as_input(input_like)
     if inpt and inpt.node.tf_graph is not self.tf_graph:
         raise StructureError("%s is in a different TF graph than %s" %
                              (inpt.node, self))
     return inpt
Example #27
0
 def _compute_scope(self, *value_scopes):
     if not self._values:
         raise StructureError("%s is missing input values." % self)
     value_scopes = self._gather_input_scopes(*value_scopes)
     return [Scope.merge_scopes(chain.from_iterable(value_scopes))]
Example #28
0
 def _compute_scope(self, *input_scopes):
     if not self._inputs:
         raise StructureError("%s is missing inputs." % self)
     input_scopes = self._gather_input_scopes(*input_scopes)
     return list(chain.from_iterable(input_scopes))
Example #29
0
 def _compute_out_size(self, *input_out_sizes):
     if not self._inputs:
         raise StructureError("%s is missing inputs." % self)
     return sum(self._gather_input_sizes(*input_out_sizes))
 def _get_num_input_scopes(self):
     if not self._values:
         raise StructureError("{}: cannot get num input scopes since this "
                              "node has no children.".format(self))
     return self._values[0].node.num_vars