Valuation Functions
===================

alphaILP adopts *neural predicates*, which call differentiable functions
to compute probabilities of facts. A neural predicate is associated with
a differentiable function, which we call valuation function, that
produces the probability of the facts.

For example, we consider the following Kandinsky pattern:

.. code:: ipython3

    from IPython.display import Image
    Image('imgs/redtriangle_examples.png')




.. image:: _static/redtriangle_examples.png



This pattern is involved with many high-level attributes and relations.
To solve this problem, the agent needs to understand the color and the
shape of objects, and moreover, their relations. In this pattern, the
two attributes of ``color`` and ``shape`` can be encoded as predicates
in first-order logic.

We define them in ``neural_preds.txt``:

::

   color:2:object,color
   shape:2:object,shape

The probability of atom ``color(obj1,red)`` should be computed using the
output of the perception module.

The YOLO model returns output in terms of vectors in the following
format:

``[x1, y1, x2, y2, red, yellow, blue, square, circle, triangle, objectness]``

For example, a vector

::

   [0.1, 0.1, 0.2, 0.2, 0.98, 0.01, 0.01, 0.98, 0.01, 0.01, 0.99]

represents a red circle with a high probability. To compute the
probability of atom ``color(obj1,red)``, predicate ``color`` calls
valuation function ``v_color``, which extracts the probability from the
vector. Technically, we implement the valuation function in
``valuation_func.py``:

.. code:: python

   class YOLOColorValuationFunction(nn.Module):
      """The function v_color.
      """

      def __init__(self):
          super(YOLOColorValuationFunction, self).__init__()

      def forward(self, z, a):
          """
          Args:
              z (tensor): 2-d tensor B * d of object-centric representation.
                  [x1, y1, x2, y2, color1, color2, color3,
                      shape1, shape2, shape3, objectness]
              a (tensor): The one-hot tensor that is expanded to the batch size.
          Returns:
              A batch of probabilities.
          """
          z_color = z[:, 4:7]
          return (a * z_color).sum(dim=1)

Note that ``z`` is a batch of object-centric vectors, therefore the
first dimension should be kept.

Once a valuation fucntion has been implemented, the function should be
registered in ``valuation.py`` to be called by the system:

.. code:: python

   vfs = {}  # a dictionary: pred_name -> valuation function
   v_color = YOLOColorValuationFunction()

To compute the concept of ``closeby``, i.e., how two objects are getting
close by each other, the valuation function can be implemented as
1-dimensional logistic regression function on the distance of two
objects. The parameter of the regression model can be trained from
examples, thus the model can learn the degree of the concept
``closeby``.

.. code:: python


   class YOLOClosebyValuationFunction(nn.Module):
       """The function v_closeby.
       """

       def __init__(self, device):
           super(YOLOClosebyValuationFunction, self).__init__()
           self.device = device
           self.logi = LogisticRegression(input_dim=1)
           self.logi.to(device)

       def forward(self, z_1, z_2):
           """
           Args:
               z_1 (tensor): 2-d tensor (B * D), the object-centric representation.
                   [x1, y1, x2, y2, color1, color2, color3,
                       shape1, shape2, shape3, objectness]
               z_2 (tensor): 2-d tensor (B * D), the object-centric representation.
                   [x1, y1, x2, y2, color1, color2, color3,
                       shape1, shape2, shape3, objectness]
           Returns:
               A batch of probabilities.
           """
           c_1 = self.to_center(z_1)
           c_2 = self.to_center(z_2)
           dist = torch.norm(c_1 - c_2, dim=0).unsqueeze(-1)
           return self.logi(dist).squeeze()

       def to_center(self, z):
           x = (z[:, 0] + z[:, 2]) / 2
           y = (z[:, 1] + z[:, 3]) / 2
           return torch.stack((x, y))

By using these neural predicates, alphaILP handles rules such as:

.. code:: prolog

   kp(X):-in(O1,X),in(O2,X),color(O1,red),shape(O1,triangle),diff_color_pair(O1,O2),diff_shape_color(O1,O2),closeby(O1,O2).

`source <https://github.com/ml-research/alphailp/blob/main/src/valuation.py>`__