Building a Learner
==================

We briefly demonstrate how we build a rule learner using alphaILP and
show how to perform rule learning on visual scenes.

Solving Kandinsky Patterns
--------------------------

In this introduction, we solve the following kandinsky patterns:

.. code:: ipython3

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




.. image:: _static/output_learner_0.png



Lanuage Definition
------------------

To start writing logic programs, we need to specify a set of symbols we
can use, which is called as **language**.

We define language in text files in
``data/lang/dataset-type/dataset-name/``. ### Predicates Predicates are
written in ``preds.txt`` file. The format is ``name:arity:data_types``.
Each predicate should be specified line by line. For example,

.. code:: prolog

   kp:1:image
   same_color_pair:2:object,object
   same_shape_pair:2:object,object
   diff_color_pair:2:object,object
   diff_shape_pair:2:object,object
   diff_color:2:color,color
   diff_shape:2:shape,shape

Neural Predicates
~~~~~~~~~~~~~~~~~

Neural predicates are written in ``neural_preds.txt`` file. The format
is ``name:arity:data_types``. Each predicate should be specified line by
line. For example,

.. code:: prolog

   in:2:object,image
   color:2:object,color
   shape:2:object,shape

Valuation functions for each neural predicate should be defined in
``valuation_func.py`` and be registered in ``valuation.py``.

Constants
~~~~~~~~~

Constants are written in ``consts.txt``. The format is
``data_type:names``. Each constant should be specified line by line. For
example,

.. code:: prolog

   object:obj0,obj1,obj2,obj3,obj4
   color:red,yellow,blue
   shape:square,circle,triangle
   image:img

The defined language can be loaded by ``logic_utils.get_lang``.

.. code:: ipython3

    # Load a defined language
    import sys
    sys.path.append('src/')
    from src.logic_utils import get_lang
    
    lark_path = 'src/lark/exp.lark'
    lang_base_path = 'data/lang/'
    lang, _clauses, bk_clauses, bk, atoms = get_lang(
            lark_path, lang_base_path, 'kandinsky', 'twopairs')

Specify Hyperparameters
-----------------------

.. code:: ipython3

    import torch
    class Args:
        dataset_type = 'kandinsky'
        dataset = 'red-triangle'
        batch_size = 2
        batch_size_bs = 2 # batch size in the beam search step
        num_objects = 2
        no_cuda = True
        num_workers = 4
        program_size = 1
        epochs = 20
        lr = 1e-2
        infer_step = 4
        term_depth = 2
        no_train = False
        plot = False
        small_data = False
        t_beam = 6
        n_beam = 20
        n_max = 50
        m = 1 # the number of clauses to be chosen
        e = 6
    
    args = Args()
    device = torch.device('cpu')

Providing Background Knowledge
------------------------------

By using the defined symbols, you can write logic programs to provide
background knowledge, for example,

.. code:: prolog

   same_shape_pair(X,Y):-shape(X,Z),shape(Y,Z).
   same_color_pair(X,Y):-color(X,Z),color(Y,Z).
   diff_shape_pair(X,Y):-shape(X,Z),shape(Y,W),diff_shape(Z,W).
   diff_color_pair(X,Y):-color(X,Z),color(Y,W),diff_color(Z,W).

Clauses should be written in ``bk_clauses.txt``.

An initial clause should be given in ``clauses.txt``:

.. code:: prolog

   kp(X):-in(O1,X),in(O2,X).

.. code:: ipython3

    # Write a logic program as text
    clauses_str = """
    kp(X):-in(O1,X),in(O2,X).
    """
    # Parse the text to logic program
    from fol.data_utils import DataUtils
    du = DataUtils(lark_path, lang_base_path, args.dataset_type, args.dataset)
    clauses = []
    for line in clauses_str.split('\n')[1:-1]:
        print(line)
        clauses.append(du.parse_clause(line, lang))
    
    clauses = [clauses[0]]


.. parsed-literal::

    kp(X):-in(O1,X),in(O2,X).


Build a Reasoner
----------------

Import the neuro-symbolic forward reasoner.

.. code:: ipython3

    from percept import SlotAttentionPerceptionModule, YOLOPerceptionModule
    from valuation import SlotAttentionValuationModule, YOLOValuationModule
    from facts_converter import FactsConverter
    from nsfr import NSFReasoner
    from logic_utils import build_infer_module, build_clause_infer_module
    import torch
    
    PM = YOLOPerceptionModule(e=args.num_objects, d=11, device=device)
    VM = YOLOValuationModule(
                lang=lang, device=device, dataset=args.dataset)
    
    FC = FactsConverter(lang=lang, perception_module=PM,
                            valuation_module=VM, device=device)
    IM = build_infer_module(clauses, bk_clauses, atoms, lang,
                                m=1, infer_step=args.infer_step, device=device, train=True)
    CIM = build_clause_infer_module(clauses, bk_clauses, atoms, lang,
                                m=len(clauses), infer_step=args.infer_step, device=device)
    # Neuro-Symbolic Forward Reasoner
    NSFR = NSFReasoner(perception_module=PM, facts_converter=FC,
                           infer_module=IM, clause_infer_module=CIM, atoms=atoms, bk=bk, clauses=clauses)


.. parsed-literal::

    Loading YOLO model...


Load Data
---------

.. code:: ipython3

    from nsfr_utils import get_data_loader, get_data_pos_loader  # get torch data loader
    import matplotlib.pyplot as plt
    
    train_loader, val_loader,  test_loader = get_data_loader(args)
    
    # loading data loader for beam search using only positive examples
    train_pos_loader, val_pos_loader, test_pos_loader = get_data_pos_loader(
            args)

Build a Clause Generator
------------------------

alphaILP performs beam-search to generate clauses.

.. code:: ipython3

    from mode_declaration import get_mode_declarations
    from clause_generator import ClauseGenerator
    # Neuro-Symbolic Forward Reasoner for clause generation
    NSFR_cgen = NSFReasoner(perception_module=PM, facts_converter=FC,
                           infer_module=IM, clause_infer_module=CIM, atoms=atoms, bk=bk, clauses=clauses)
    mode_declarations = get_mode_declarations(args, lang, args.num_objects)
    cgen = ClauseGenerator(args, NSFR_cgen, lang, val_pos_loader, mode_declarations,
                           bk_clauses, device=device)  # torch.device('cpu'))

.. code:: ipython3

    clauses = cgen.generate(
                clauses, T_beam=args.t_beam, N_beam=args.n_beam, N_max=args.n_max)

Weight Learning
---------------

Using the generated clauses in beam-search, we perform weight learning
from positive and negative examples.

.. code:: ipython3

    from nsfr_utils import get_nsfr_model
    # update
    NSFR = get_nsfr_model(args, lang, clauses, atoms, bk,bk_clauses, device, train=True)


.. parsed-literal::

    Loading YOLO model...


.. code:: ipython3

    # prepare an optimizer 
    params = NSFR.get_params()
    optimizer = torch.optim.RMSprop(params, lr=args.lr)

.. code:: ipython3

    from tqdm.notebook import tqdm
    from nsfr_utils import get_prob
    bce = torch.nn.BCELoss()
    loss_list = []
    for epoch in range(args.epochs):
        loss_i = 0
        for i, sample in tqdm(enumerate(train_loader, start=0)):
            # to cuda
            imgs, target_set = map(lambda x: x.to(device), sample)
            # infer and predict the target probability
            V_T = NSFR(imgs)
            # NSFR.print_valuation_batch(V_T)
            predicted = get_prob(V_T, NSFR, args)
            loss = bce(predicted, target_set)
            loss_i += loss.item()
            loss.backward()
    
            optimizer.step()
        loss_list.append(loss_i)
        print(loss_i)

.. code:: ipython3

    import matplotlib
    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns
    %matplotlib inline
    
    sns.set()
    sns.set_style("whitegrid", {'grid.linestyle': '--'})
    sns.set_context("paper", 1.5, {"lines.linewidth": 4})
    sns.set_palette("winter_r", 8, 1)
    sns.set('talk', 'whitegrid', 'dark', font_scale=1.5,
            rc={"lines.linewidth": 2, 'grid.linestyle': '--'})
    
    
    xs = list(range(len(loss_list)))
    plt.plot(np.array(xs), np.array(loss_list))
    plt.title('Training Loss')
    plt.show()



.. image:: _static/output_learner_1.png


.. code:: ipython3

    NSFR.print_program()


.. parsed-literal::

    ====== LEARNED PROGRAM ======
    Ws_softmaxed:  [[          0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0         0.1           0           0        0.34           0           0           0           0
                0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0         0.2        0.09           0           0
                0           0           0           0           0           0           0           0           0           0        0.28           0           0           0           0           0           0           0           0           0           0           0           0           0           0           0
                0           0           0           0           0]]
    C_0:  kp(X):-closeby(O1,O2),color(O2,red),diff_color_pair(O1,O2),in(O1,X),in(O2,X),shape(O2,triangle). 0.34