| @@ -20,12 +20,12 @@ class ListData(BaseDataElement): | |||
| """ | |||
| Abstract Data Interface used throughout the ABL-Package. | |||
| `ListData` is the underlying data structure used in the ABL-Package, | |||
| ``ListData`` is the underlying data structure used in the ABL-Package, | |||
| designed to manage diverse forms of data dynamically generated throughout the | |||
| Abductive Learning (ABL) framework. This includes handling raw data, predicted | |||
| pseudo-labels, abduced pseudo-labels, pseudo-label indices, etc. | |||
| As a fundamental data structure in ABL, `ListData` is essential for the smooth | |||
| As a fundamental data structure in ABL, ``ListData`` is essential for the smooth | |||
| transfer and manipulation of data across various components of the ABL framework, | |||
| such as prediction, abductive reasoning, and training phases. It provides a | |||
| unified data format across these stages, ensuring compatibility and flexibility | |||
| @@ -48,13 +48,12 @@ class ListData(BaseDataElement): | |||
| methods to all :obj:`torch.Tensor` in the ``data_fields``, such as ``.cuda()``, | |||
| ``.cpu()``, ``.numpy()``, ``.to()``, ``to_tensor()``, ``.detach()``. | |||
| ListData supports `index` and `slice` for data field. The type of value in | |||
| data field can be either `None` or `list` of base data structures such as | |||
| `torch.Tensor`, `numpy.ndarray`, `list`, `str` and `tuple`. | |||
| ListData supports ``index`` and ``slice`` for data field. The type of value in | |||
| data field can be either ``None`` or ``list`` of base data structures such as | |||
| ``torch.Tensor``, ``numpy.ndarray``, ``list``, ``str`` and ``tuple``. | |||
| This design is inspired by and extends the functionalities of the `BaseDataElement` | |||
| class implemented in MMEngine. | |||
| https://github.com/open-mmlab/mmengine/blob/main/mmengine/structures/base_data_element.py # noqa E501 | |||
| This design is inspired by and extends the functionalities of the ``BaseDataElement`` | |||
| class implemented in `MMEngine <https://github.com/open-mmlab/mmengine/blob/main/mmengine/structures/base_data_element.py>`_. | |||
| Examples: | |||
| >>> from abl.data.structures import ListData | |||
| @@ -13,9 +13,9 @@ class ABLModel: | |||
| ---------- | |||
| base_model : Machine Learning Model | |||
| The machine learning base model used for training and prediction. This model should | |||
| implement the 'fit' and 'predict' methods. It's recommended, but not required,for the | |||
| model to also implement the 'predict_proba' method for generating probabilistic | |||
| predictions. | |||
| implement the ``fit`` and ``predict`` methods. It's recommended, but not required, for the | |||
| model to also implement the ``predict_proba`` method for generating | |||
| predictions on the probabilities. | |||
| """ | |||
| def __init__(self, base_model: Any) -> None: | |||
| @@ -61,8 +61,8 @@ class ABLModel: | |||
| Parameters | |||
| ---------- | |||
| data_examples : ListData | |||
| A batch of data to train on, which typically contains the data, `X`, and the | |||
| corresponding labels, `abduced_idx`. | |||
| A batch of data to train on, which typically contains the data, ``X``, and the | |||
| corresponding labels, ``abduced_idx``. | |||
| Returns | |||
| ------- | |||
| @@ -80,8 +80,8 @@ class ABLModel: | |||
| Parameters | |||
| ---------- | |||
| data_examples : ListData | |||
| A batch of data to train on, which typically contains the data, `X`, | |||
| and the corresponding labels, `abduced_idx`. | |||
| A batch of data to train on, which typically contains the data, ``X``, | |||
| and the corresponding labels, ``abduced_idx``. | |||
| Returns | |||
| ------- | |||
| @@ -119,8 +119,8 @@ class ABLModel: | |||
| """ | |||
| Save the model to a file. | |||
| This method delegates to the 'save' method of self.base_model. The arguments passed to | |||
| this method should match those expected by the 'save' method of self.base_model. | |||
| This method delegates to the ``save`` method of self.base_model. The arguments passed to | |||
| this method should match those expected by the ``save`` method of self.base_model. | |||
| """ | |||
| self._model_operation("save", *args, **kwargs) | |||
| @@ -128,7 +128,7 @@ class ABLModel: | |||
| """ | |||
| Load the model from a file. | |||
| This method delegates to the 'load' method of self.base_model. The arguments passed to | |||
| this method should match those expected by the 'load' method of self.base_model. | |||
| This method delegates to the ``load`` method of self.base_model. The arguments passed to | |||
| this method should match those expected by the ``load`` method of self.base_model. | |||
| """ | |||
| self._model_operation("load", *args, **kwargs) | |||
| @@ -125,7 +125,7 @@ class BasicNN: | |||
| def _fit(self, data_loader: DataLoader) -> BasicNN: | |||
| """ | |||
| Internal method to fit the model on data for self.num_epochs times, | |||
| Internal method to fit the model on data for ``self.num_epochs`` times, | |||
| with early stopping. | |||
| Parameters | |||
| @@ -468,7 +468,7 @@ class BasicNN: | |||
| the epoch_id at which the model and optimizer is saved. if both save_path and | |||
| epoch_id are provided, save_path will be used. If only epoch_id is specified, | |||
| model and optimizer will be saved to the path f"model_checkpoint_epoch_{epoch_id}.pth" | |||
| under self.save_dir. save_path and epoch_id can not be None simultaneously. | |||
| under ``self.save_dir``. save_path and epoch_id can not be None simultaneously. | |||
| Parameters | |||
| ---------- | |||
| @@ -45,7 +45,7 @@ class KBBase(ABC): | |||
| ----- | |||
| Users should derive from this base class to build their own knowledge base. For the | |||
| user-build KB (a derived subclass), it's only required for the user to provide the | |||
| `pseudo_label_list` and override the `logic_forward` function (specifying how to | |||
| ``pseudo_label_list`` and override the ``logic_forward`` function (specifying how to | |||
| perform logical reasoning). After that, other operations (e.g. how to perform abductive | |||
| reasoning) will be automatically set up. | |||
| """ | |||
| @@ -88,7 +88,7 @@ class KBBase(ABC): | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example. | |||
| Pseudo-label example. | |||
| x : Optional[List[Any]] | |||
| The corresponding input example. If deductive logical reasoning does not require any | |||
| information from the input, the overridden function provided by the user can omit | |||
| @@ -114,7 +114,7 @@ class KBBase(ABC): | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example (to be revised by abductive reasoning). | |||
| Pseudo-label example (to be revised by abductive reasoning). | |||
| y : Any | |||
| Ground truth of the reasoning result for the example. | |||
| x : List[Any] | |||
| @@ -167,7 +167,7 @@ class KBBase(ABC): | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example (to be revised). | |||
| Pseudo-label example (to be revised). | |||
| y : Any | |||
| Ground truth of the reasoning result for the example. | |||
| x : List[Any] | |||
| @@ -231,7 +231,7 @@ class KBBase(ABC): | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example (to be revised). | |||
| Pseudo-label example (to be revised). | |||
| y : Any | |||
| Ground truth of the reasoning result for the example. | |||
| x : List[Any] | |||
| @@ -285,22 +285,22 @@ class GroundKB(KBBase): | |||
| """ | |||
| Knowledge base with a ground KB (GKB). Ground KB is a knowledge base prebuilt upon | |||
| class initialization, storing all potential candidates along with their respective | |||
| reasoning result. Ground KB can accelerate abductive reasoning in `abduce_candidates`. | |||
| reasoning result. Ground KB can accelerate abductive reasoning in ``abduce_candidates``. | |||
| Parameters | |||
| ---------- | |||
| pseudo_label_list : list | |||
| Refer to class `KBBase`. | |||
| Refer to class ``KBBase``. | |||
| GKB_len_list : list | |||
| List of possible lengths for a pseudo-label example. | |||
| max_err : float, optional | |||
| Refer to class `KBBase`. | |||
| Refer to class ``KBBase``. | |||
| Notes | |||
| ----- | |||
| Users can also inherit from this class to build their own knowledge base. Similar | |||
| to `KBBase`, users are only required to provide the `pseudo_label_list` and override | |||
| the `logic_forward` function. Additionally, users should provide the `GKB_len_list`. | |||
| to ``KBBase``, users are only required to provide the ``pseudo_label_list`` and override | |||
| the ``logic_forward`` function. Additionally, users should provide the ``GKB_len_list``. | |||
| After that, other operations (e.g. auto-construction of GKB, and how to perform | |||
| abductive reasoning) will be automatically set up. | |||
| """ | |||
| @@ -329,7 +329,7 @@ class GroundKB(KBBase): | |||
| def _get_GKB(self): | |||
| """ | |||
| Prebuild the GKB according to `pseudo_label_list` and `GKB_len_list`. | |||
| Prebuild the GKB according to ``pseudo_label_list`` and ``GKB_len_list``. | |||
| """ | |||
| X, Y = [], [] | |||
| for length in self.GKB_len_list: | |||
| @@ -365,7 +365,7 @@ class GroundKB(KBBase): | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example (to be revised by abductive reasoning). | |||
| Pseudo-label example (to be revised by abductive reasoning). | |||
| y : Any | |||
| Ground truth of the reasoning result for the example. | |||
| x : List[Any] | |||
| @@ -447,20 +447,20 @@ class PrologKB(KBBase): | |||
| Parameters | |||
| ---------- | |||
| pseudo_label_list : list | |||
| Refer to class `KBBase`. | |||
| Refer to class ``KBBase``. | |||
| pl_file : | |||
| Prolog file containing the KB. | |||
| max_err : float, optional | |||
| Refer to class `KBBase`. | |||
| Refer to class ``KBBase``. | |||
| Notes | |||
| ----- | |||
| Users can instantiate this class to build their own knowledge base. During the | |||
| instantiation, users are only required to provide the `pseudo_label_list` and `pl_file`. | |||
| instantiation, users are only required to provide the ``pseudo_label_list`` and ``pl_file``. | |||
| To use the default logic forward and abductive reasoning methods in this class, in the | |||
| Prolog (.pl) file, there needs to be a rule which is strictly formatted as | |||
| `logic_forward(Pseudo_labels, Res).`, e.g., `logic_forward([A,B], C) :- C is A+B`. | |||
| For specifics, refer to the `logic_forward` and `get_query_string` functions in this | |||
| ``logic_forward(Pseudo_labels, Res).``, e.g., ``logic_forward([A,B], C) :- C is A+B``. | |||
| For specifics, refer to the ``logic_forward`` and ``get_query_string`` functions in this | |||
| class. Users are also welcome to override related functions for more flexible support. | |||
| """ | |||
| @@ -475,15 +475,15 @@ class PrologKB(KBBase): | |||
| def logic_forward(self, pseudo_label: List[Any]) -> Any: | |||
| """ | |||
| Consult prolog with the query `logic_forward(pseudo_labels, Res).`, and set the | |||
| returned `Res` as the reasoning results. To use this default function, there must be | |||
| a `logic_forward` method in the pl file to perform reasoning. | |||
| Consult prolog with the query ``logic_forward(pseudo_labels, Res).``, and set the | |||
| returned ``Res`` as the reasoning results. To use this default function, there must be | |||
| a ``logic_forward`` method in the pl file to perform reasoning. | |||
| Otherwise, users would override this function. | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example. | |||
| Pseudo-label example. | |||
| """ | |||
| result = list(self.prolog.query("logic_forward(%s, Res)." % pseudo_label))[0]["Res"] | |||
| if result == "true": | |||
| @@ -520,12 +520,12 @@ class PrologKB(KBBase): | |||
| Get the query to be used for consulting Prolog. | |||
| This is a default function for demo, users would override this function to adapt to | |||
| their own Prolog file. In this demo function, return query | |||
| `logic_forward([kept_labels, Revise_labels], Res).`. | |||
| ``logic_forward([kept_labels, Revise_labels], Res).``. | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example (to be revised by abductive reasoning). | |||
| Pseudo-label example (to be revised by abductive reasoning). | |||
| y : Any | |||
| Ground truth of the reasoning result for the example. | |||
| x : List[Any] | |||
| @@ -559,7 +559,7 @@ class PrologKB(KBBase): | |||
| Parameters | |||
| ---------- | |||
| pseudo_label : List[Any] | |||
| Pseudo label example (to be revised). | |||
| Pseudo-label example (to be revised). | |||
| y : Any | |||
| Ground truth of the reasoning result for the example. | |||
| x : List[Any] | |||
| @@ -251,7 +251,7 @@ class Reasoner: | |||
| def _get_max_revision_num(self, max_revision: Union[int, float], symbol_num: int) -> int: | |||
| """ | |||
| Get the maximum revision number according to input `max_revision`. | |||
| Get the maximum revision number according to input ``max_revision``. | |||
| """ | |||
| if not isinstance(max_revision, (int, float)): | |||
| raise TypeError(f"Parameter must be of type int or float, but got {type(max_revision)}") | |||
| @@ -313,7 +313,7 @@ class Reasoner: | |||
| def batch_abduce(self, data_examples: ListData) -> List[List[Any]]: | |||
| """ | |||
| Perform abductive reasoning on the given prediction data examples. | |||
| For detailed information, refer to `abduce`. | |||
| For detailed information, refer to ``abduce``. | |||
| """ | |||
| abduced_pseudo_label = [self.abduce(data_example) for data_example in data_examples] | |||
| data_examples.abduced_pseudo_label = abduced_pseudo_label | |||
| @@ -1,7 +1,7 @@ | |||
| abl.data | |||
| =================== | |||
| Data Structure | |||
| ``structures`` | |||
| -------------- | |||
| .. autoclass:: abl.data.structures.ListData | |||
| @@ -9,8 +9,8 @@ Data Structure | |||
| :undoc-members: | |||
| :show-inheritance: | |||
| Evaluation Metric | |||
| ----------------- | |||
| ``evaluation`` | |||
| -------------- | |||
| .. automodule:: abl.data.evaluation | |||
| :members: | |||
| @@ -1,9 +1,6 @@ | |||
| abl.learning | |||
| ================== | |||
| Learning | |||
| -------- | |||
| .. autoclass:: abl.learning.ABLModel | |||
| :members: | |||
| :undoc-members: | |||
| @@ -14,8 +11,8 @@ Learning | |||
| :undoc-members: | |||
| :show-inheritance: | |||
| Torch Dataset | |||
| ------------- | |||
| ``torch_dataset`` | |||
| ----------------- | |||
| .. automodule:: abl.learning.torch_dataset | |||
| :members: | |||
| @@ -1,2 +1,251 @@ | |||
| ZOO | |||
| === | |||
| === | |||
| Below shows an implementation of | |||
| `Zoo <https://archive.ics.uci.edu/dataset/111/zoo>`__. In this task, | |||
| attributes of animals (such as presence of hair, eggs, etc.) and their | |||
| targets (the animal class they belong to) are given, along with a | |||
| knowledge base which contain information about the relations between | |||
| attributes and targets, e.g., Implies(milk == 1, mammal == 1). | |||
| The goal of this task is to develop a learning model that can predict | |||
| the targets of animals based on their attributes. In the initial stages, | |||
| when the model is under-trained, it may produce incorrect predictions | |||
| that conflict with the relations contained in the knowledge base. When | |||
| this happens, abductive reasoning can be employed to adjust these | |||
| results and retrain the model accordingly. This process enables us to | |||
| further update the learning model. | |||
| .. code:: ipython3 | |||
| # Import necessary libraries and modules | |||
| import os.path as osp | |||
| import numpy as np | |||
| from sklearn.ensemble import RandomForestClassifier | |||
| from examples.zoo.get_dataset import load_and_preprocess_dataset, split_dataset | |||
| from abl.learning import ABLModel | |||
| from examples.zoo.kb import ZooKB | |||
| from abl.reasoning import Reasoner | |||
| from abl.data.evaluation import ReasoningMetric, SymbolAccuracy | |||
| from abl.utils import ABLLogger, print_log, confidence_dist | |||
| from abl.bridge import SimpleBridge | |||
| Working with Data | |||
| ----------------- | |||
| First, we load and preprocess the `Zoo | |||
| dataset <https://archive.ics.uci.edu/dataset/111/zoo>`__, and split it | |||
| into labeled/unlabeled/test data | |||
| .. code:: ipython3 | |||
| X, y = load_and_preprocess_dataset(dataset_id=62) | |||
| X_label, y_label, X_unlabel, y_unlabel, X_test, y_test = split_dataset(X, y, test_size=0.3) | |||
| Zoo dataset consist of tabular data. The attributes contains 17 boolean | |||
| values (e.g., hair, feathers, eggs, milk, airborne, aquatic, etc.) and | |||
| the target is a integer value in range [0,6] representing 7 classes | |||
| (e.g., mammal, bird, reptile, fish, amphibian, insect, and other). Below | |||
| is an illustration: | |||
| .. code:: ipython3 | |||
| print("Shape of X and y:", X.shape, y.shape) | |||
| print("First five elements of X:") | |||
| print(X[:5]) | |||
| print("First five elements of y:") | |||
| print(y[:5]) | |||
| Out: | |||
| .. code:: none | |||
| :class: code-out | |||
| Shape of X and y: (101, 16) (101,) | |||
| First five elements of X: | |||
| [[True False False True False False True True True True False False 4 | |||
| False False True] | |||
| [True False False True False False False True True True False False 4 | |||
| True False True] | |||
| [False False True False False True True True True False False True 0 | |||
| True False False] | |||
| [True False False True False False True True True True False False 4 | |||
| False False True] | |||
| [True False False True False False True True True True False False 4 | |||
| True False True]] | |||
| First five elements of y: | |||
| [0 0 3 0 0] | |||
| Next, we transform the tabular data to the format required by | |||
| ABL-Package, which is a tuple of (X, gt_pseudo_label, Y). In this task, | |||
| we treat the attributes as X and the targets as gt_pseudo_label (ground | |||
| truth pseudo-labels). Y (reasoning results) are expected to be 0, | |||
| indicating no rules are violated. | |||
| .. code:: ipython3 | |||
| def transform_tab_data(X, y): | |||
| return ([[x] for x in X], [[y_item] for y_item in y], [0] * len(y)) | |||
| label_data = transform_tab_data(X_label, y_label) | |||
| test_data = transform_tab_data(X_test, y_test) | |||
| train_data = transform_tab_data(X_unlabel, y_unlabel) | |||
| Building the Learning Part | |||
| -------------------------- | |||
| To build the learning part, we need to first build a machine learning | |||
| base model. We use a `Random | |||
| Forest <https://en.wikipedia.org/wiki/Random_forest>`__ as the base | |||
| model. | |||
| .. code:: ipython3 | |||
| base_model = RandomForestClassifier() | |||
| However, the base model built above deals with instance-level data, and | |||
| can not directly deal with example-level data. Therefore, we wrap the | |||
| base model into ``ABLModel``, which enables the learning part to train, | |||
| test, and predict on example-level data. | |||
| .. code:: ipython3 | |||
| model = ABLModel(base_model) | |||
| Building the Reasoning Part | |||
| --------------------------- | |||
| In the reasoning part, we first build a knowledge base which contains | |||
| information about the relations between attributes (X) and targets | |||
| (pseudo-labels), e.g., Implies(milk == 1, mammal == 1). The knowledge | |||
| base is built in the ``ZooKB`` class within file ``examples/zoo/kb.py``, and is | |||
| derived from the ``KBBase`` class. | |||
| .. code:: ipython3 | |||
| kb = ZooKB() | |||
| As mentioned, for all attributes and targets in the dataset, the | |||
| reasoning results are expected to be 0 since there should be no | |||
| violations of the established knowledge in real data. As shown below: | |||
| .. code:: ipython3 | |||
| for idx, (x, y_item) in enumerate(zip(X[:5], y[:5])): | |||
| print(f"Example {idx}: the attributes are: {x}, and the target is {y_item}.") | |||
| print(f"Reasoning result is {kb.logic_forward([y_item], [x])}.") | |||
| print() | |||
| Out: | |||
| .. code:: none | |||
| :class: code-out | |||
| Example 0: the attributes are: [True False False True False False True True True True False False 4 False | |||
| False True], and the target is 0. | |||
| Reasoning result is 0. | |||
| Example 1: the attributes are: [True False False True False False False True True True False False 4 True | |||
| False True], and the target is 0. | |||
| Reasoning result is 0. | |||
| Example 2: the attributes are: [False False True False False True True True True False False True 0 True | |||
| False False], and the target is 3. | |||
| Reasoning result is 0. | |||
| Example 3: the attributes are: [True False False True False False True True True True False False 4 False | |||
| False True], and the target is 0. | |||
| Reasoning result is 0. | |||
| Example 4: the attributes are: [True False False True False False True True True True False False 4 True | |||
| False True], and the target is 0. | |||
| Reasoning result is 0. | |||
| Then, we create a reasoner by instantiating the class ``Reasoner``. Due | |||
| to the indeterminism of abductive reasoning, there could be multiple | |||
| candidates compatible to the knowledge base. When this happens, reasoner | |||
| can minimize inconsistencies between the knowledge base and | |||
| pseudo-labels predicted by the learning part, and then return only one | |||
| candidate that has the highest consistency. | |||
| .. code:: ipython3 | |||
| def consitency(data_example, candidates, candidate_idxs, reasoning_results): | |||
| pred_prob = data_example.pred_prob | |||
| model_scores = confidence_dist(pred_prob, candidate_idxs) | |||
| rule_scores = np.array(reasoning_results) | |||
| scores = model_scores + rule_scores | |||
| return scores | |||
| reasoner = Reasoner(kb, dist_func=consitency) | |||
| Building Evaluation Metrics | |||
| --------------------------- | |||
| Next, we set up evaluation metrics. These metrics will be used to | |||
| evaluate the model performance during training and testing. | |||
| Specifically, we use ``SymbolAccuracy`` and ``ReasoningMetric``, which | |||
| are used to evaluate the accuracy of the machine learning model’s | |||
| predictions and the accuracy of the final reasoning results, | |||
| respectively. | |||
| .. code:: ipython3 | |||
| metric_list = [SymbolAccuracy(prefix="zoo"), ReasoningMetric(kb=kb, prefix="zoo")] | |||
| Bridging Learning and Reasoning | |||
| ------------------------------- | |||
| Now, the last step is to bridge the learning and reasoning part. We | |||
| proceed this step by creating an instance of ``SimpleBridge``. | |||
| .. code:: ipython3 | |||
| bridge = SimpleBridge(model, reasoner, metric_list) | |||
| Perform training and testing by invoking the ``train`` and ``test`` | |||
| methods of ``SimpleBridge``. | |||
| .. code:: ipython3 | |||
| # Build logger | |||
| print_log("Abductive Learning on the ZOO example.", logger="current") | |||
| log_dir = ABLLogger.get_current_instance().log_dir | |||
| weights_dir = osp.join(log_dir, "weights") | |||
| print_log("------- Use labeled data to pretrain the model -----------", logger="current") | |||
| base_model.fit(X_label, y_label) | |||
| print_log("------- Test the initial model -----------", logger="current") | |||
| bridge.test(test_data) | |||
| print_log("------- Use ABL to train the model -----------", logger="current") | |||
| bridge.train(train_data=train_data, label_data=label_data, loops=3, segment_size=len(X_unlabel), save_dir=weights_dir) | |||
| print_log("------- Test the final model -----------", logger="current") | |||
| bridge.test(test_data) | |||
| Out: | |||
| .. code:: none | |||
| :class: code-out | |||
| abl - INFO - Abductive Learning on the ZOO example. | |||
| abl - INFO - ------- Use labeled data to pretrain the model ----------- | |||
| abl - INFO - ------- Test the initial model ----------- | |||
| abl - INFO - Evaluation ended, zoo/character_accuracy: 0.935 zoo/reasoning_accuracy: 0.935 | |||
| abl - INFO - ------- Use ABL to train the model ----------- | |||
| abl - INFO - loop(train) [1/3] segment(train) [1/1] | |||
| abl - INFO - Evaluation start: loop(val) [1] | |||
| abl - INFO - Evaluation ended, zoo/character_accuracy: 0.984 zoo/reasoning_accuracy: 1.000 | |||
| abl - INFO - loop(train) [2/3] segment(train) [1/1] | |||
| abl - INFO - Evaluation start: loop(val) [2] | |||
| abl - INFO - Evaluation ended, zoo/character_accuracy: 0.984 zoo/reasoning_accuracy: 1.000 | |||
| abl - INFO - loop(train) [3/3] segment(train) [1/1] | |||
| abl - INFO - Evaluation start: loop(val) [3] | |||
| abl - INFO - Evaluation ended, zoo/character_accuracy: 0.984 zoo/reasoning_accuracy: 1.000 | |||
| abl - INFO - ------- Test the final model ----------- | |||
| abl - INFO - Evaluation ended, zoo/character_accuracy: 0.903 zoo/reasoning_accuracy: 0.935 | |||
| We may see from the results, after undergoing training with ABL, the | |||
| model’s accuracy has improved. | |||
| More concrete examples are available in ``examples/zoo/main.py`` and ``examples/zoo/zoo.ipynb``. | |||
| @@ -12,47 +12,48 @@ Learn the Basics | |||
| Modules in ABL-Package | |||
| ---------------------- | |||
| ABL-Package is an implementation of `Abductive Learning <../Overview/Abductive-Learning.html>`_, | |||
| ABL-Package is an efficient implementation of `Abductive Learning <../Overview/Abductive-Learning.html>`_ (ABL), | |||
| a paradigm which integrates machine learning and logical reasoning in a balanced-loop. | |||
| As depicted below, the ABL-Package comprises three primary parts: **Data**, **Learning**, and | |||
| The ABL-Package comprises three primary parts: **Data**, **Learning**, and | |||
| **Reasoning**, corresponding to the three pivotal components of current | |||
| AI: data, models, and knowledge. | |||
| AI: data, models, and knowledge. Below is an overview of the ABL-Package. | |||
| .. image:: ../img/ABL-Package.png | |||
| **Data** part manages the storage, operation, and evaluation of data. | |||
| It first features class ``ListData``, which defines the data structures used in | |||
| Abductive Learning, and comprises common data operations like insertion, | |||
| deletion, retrieval, slicing, etc. Additionally, a series of Evaluation | |||
| Metrics, including class ``SymbolAccuracy`` and ``ReasoningMetric`` (both | |||
| specialized metrics derived from base class ``BaseMetric``), outline | |||
| methods for evaluating model quality from a data perspective. | |||
| **Data** part manages the storage, operation, and evaluation of data efficiently. | |||
| It includes the ``ListData`` class, which defines the data structures used in | |||
| Abductive Learning, and comprises common data operations like insertion, deletion, | |||
| retrieval, slicing, etc. Additionally, it contains a series of evaluation metrics | |||
| such as ``SymbolAccuracy`` and ``ReasoningMetric`` (both specialized metrics | |||
| inherited from the ``BaseMetric`` class), for evaluating model quality from a | |||
| data perspective. | |||
| **Learning** part focuses on the construction, deployment, and | |||
| training of machine learning models. The class ``ABLModel`` is the | |||
| central class that encapsulates the machine learning model, | |||
| adaptable to various frameworks, including those based on Scikit-learn | |||
| training of machine learning models. The ``ABLModel`` class is the | |||
| central class that encapsulates the machine learning model. This class is | |||
| compatible with various frameworks, including those based on Scikit-learn | |||
| or PyTorch neural networks constructed by the ``BasicNN`` class. | |||
| **Reasoning** part is responsible for the construction of domain knowledge | |||
| and performing reasoning. In this part, the class ``KBBase`` allows users to | |||
| define domain knowledge base. For diverse types of knowledge, we also offer | |||
| implementations like ``GroundKB`` and ``PrologKB``, e.g., the latter | |||
| enables knowledge base to be imported in the form of a Prolog files. | |||
| Upon building the knowledge base, the class ``Reasoner`` is | |||
| **Reasoning** part concentrates on constructing domain knowledge and | |||
| performing reasoning. The ``KBBase`` class allows users to define a | |||
| domain knowledge base. For diverse types of knowledge, we also offer | |||
| implementations like ``GroundKB`` and ``PrologKB`` (both inherited | |||
| from the ``KBBase`` class). The latter, for instance, enables | |||
| knowledge bases to be imported in the form of Prolog files. | |||
| Upon building the knowledge base, the ``Reasoner`` class is | |||
| responsible for minimizing the inconsistency between the knowledge base | |||
| and learning models. | |||
| and data. | |||
| The integration of these parts are achieved through the | |||
| **Bridge** part, which features class ``SimpleBridge`` (derived from base | |||
| class ``BaseBridge``). Bridge part synthesize data, learning, and | |||
| reasoning, and facilitate the training and testing of the entire | |||
| ABL framework. | |||
| The integration of these three parts are achieved through the | |||
| **Bridge** part, which features the ``SimpleBridge`` class (derived | |||
| from the ``BaseBridge`` class). The Bridge part synthesizes data, | |||
| learning, and reasoning, facilitating the training and testing | |||
| of the entire ABL framework. | |||
| Use ABL-Package Step by Step | |||
| ---------------------------- | |||
| In a typical Abductive Learning process, as illustrated below, | |||
| In a typical ABL process, as illustrated below, | |||
| data inputs are first predicted by a machine learning model, and the outcomes are a pseudo-label | |||
| example (which consists of multiple pseudo-labels). | |||
| These labels then pass through a knowledge base :math:`\mathcal{KB}` | |||
| @@ -80,10 +81,10 @@ To implement this process, the following five steps are necessary: | |||
| Define a knowledge base by building a subclass of ``KBBase``, specifying how to | |||
| map pseudo-label examples to reasoning results. | |||
| Also, create a ``Reasoner`` for minimizing of inconsistencies | |||
| between the knowledge base and the learning part. | |||
| Also, create a ``Reasoner`` for minimizing inconsistencies | |||
| between the knowledge base and data. | |||
| 4. Define Evaluation Metrics | |||
| 4. Define evaluation metrics | |||
| Define the metrics by building a subclass of ``BaseMetric``. The metrics will | |||
| specify how to measure performance during the training and testing of the ABL framework. | |||
| @@ -44,7 +44,7 @@ ABL-Package assumes user data to be either structured as a tuple or a ``ListData | |||
| The length of ``X``, ``gt_pseudo_label`` (if not ``None``) and ``Y`` should be the same. Also, each sublist in ``gt_pseudo_label`` should have the same length as the sublist in ``X``. | |||
| As an illustration, in the MNIST Addition example, the data used for training are organized as follows: | |||
| As an illustration, in the MNIST Addition task, the data are organized as follows: | |||
| .. image:: ../img/Datasets_1.png | |||
| :width: 350px | |||
| @@ -53,11 +53,11 @@ As an illustration, in the MNIST Addition example, the data used for training ar | |||
| Data Structure | |||
| -------------- | |||
| Besides the user-provided dataset, various forms of data are utilized and dynamicly generated throughout the training and testing process of Abductive Learning framework. Examples include raw data, predicted pseudo-label, abduced pseudo-label, pseudo-label indices, and so on. To manage this diversity and ensure a stable, versatile interface, ABL-Package employs `abstract data interfaces <../API/abl.data.html#data-structure>`_ to encapsulate different forms of data that will be used in the total learning process. | |||
| Besides the user-provided dataset, various forms of data are utilized and dynamicly generated throughout the training and testing process of Abductive Learning framework. Examples include raw data, predicted pseudo-label, abduced pseudo-label, pseudo-label indices, etc. To manage this diversity and ensure a stable, versatile interface, ABL-Package employs `abstract data interfaces <../API/abl.data.html#structure>`_ to encapsulate different forms of data that will be used in the total learning process. | |||
| ``ListData`` is the underlying abstract data interface utilized in ABL-Package. As the fundamental data structure, ``ListData`` implements commonly used data manipulation methods and is responsible for transferring data between various components of ABL, ensuring that stages such as prediction, abductive reasoning, and training can utilize ``ListData`` as a unified input format. | |||
| Before proceeding to other stages, user-provided datasets are firstly converted into ``ListData``. For flexibility, ABL-Package also allows user to directly supply data in ``ListData`` format, which similarly requires the inclusion of three attributes: ``X``, ``gt_pseudo_label``, and ``Y``. The following code shows the basic usage of ``ListData``. More information can be found in the `API documentation <../API/abl.data.html#data-structure>`_. | |||
| Before proceeding to other stages, user-provided datasets are firstly converted into ``ListData``. For flexibility, ABL-Package also allows user to directly supply data in ``ListData`` format, which similarly requires the inclusion of three attributes: ``X``, ``gt_pseudo_label``, and ``Y``. The following code shows the basic usage of ``ListData``. More information can be found in the `API documentation <../API/abl.data.html#structure>`_. | |||
| .. code-block:: python | |||
| @@ -57,7 +57,8 @@ is dual-driven by both data and domain knowledge, integrating and | |||
| balancing the use of machine learning and logical reasoning in a unified | |||
| model. | |||
| For more information about ABL, please refer to: [Zhou, 2019](https://link.springer.com/epdf/10.1007/s11432-018-9801-4?author_access_token=jgJe1Ox3Mk-K7ORSnX7jtfe4RwlQNchNByi7wbcMAY7_PxTx-xNLP7Lp0mIZ04ORp3VG4wioIBHSCIAO3B_TBJkj87YzapmdnYVSQvgBIO3aEpQWppxZG25KolINetygc2W_Cj2gtoBdiG_J1hU3pA==) and [Zhou and Huang, 2022](https://www.lamda.nju.edu.cn/publication/chap_ABL.pdf). | |||
| For more information about ABL, please refer to: `Zhou, 2019 <https://link.springer.com/epdf/10.1007/s11432-018-9801-4?author_access_token=jgJe1Ox3Mk-K7ORSnX7jtfe4RwlQNchNByi7wbcMAY7_PxTx-xNLP7Lp0mIZ04ORp3VG4wioIBHSCIAO3B_TBJkj87YzapmdnYVSQvgBIO3aEpQWppxZG25KolINetygc2W_Cj2gtoBdiG_J1hU3pA==>`_ | |||
| and `Zhou and Huang, 2022 <https://www.lamda.nju.edu.cn/publication/chap_ABL.pdf>`_. | |||
| .. _abd: | |||
| @@ -1,5 +1,10 @@ | |||
| References | |||
| ================== | |||
| ========== | |||
| Zhi-Hua Zhou. Abductive learning: `Towards bridging machine learning and logical reasoning. <https://link.springer.com/epdf/10.1007/s11432-018-9801-4?author_access_token=jgJe1Ox3Mk-K7ORSnX7jtfe4RwlQNchNByi7wbcMAY7_PxTx-xNLP7Lp0mIZ04ORp3VG4wioIBHSCIAO3B_TBJkj87YzapmdnYVSQvgBIO3aEpQWppxZG25KolINetygc2W_Cj2gtoBdiG_J1hU3pA==>`_. **Science China Information Sciences**, 2019, 62: 076101. | |||
| Zhi-Hua Zhou and Yu-Xuan Huang. `Abductive learning <https://www.lamda.nju.edu.cn/publication/chap_ABL.pdf>`_. In P. Hitzler and M. K. Sarker eds., **Neuro-Symbolic Artificial Intelligence: The State of the Art**, IOP Press, Amsterdam, 2022, p.353-379 | |||
| .. contents:: Table of Contents | |||
| @@ -0,0 +1,39 @@ | |||
| # Handwritten Equation Decipherment | |||
| This notebook shows an implementation of [Handwritten Equation Decipherment](https://proceedings.neurips.cc/paper_files/paper/2019/file/9c19a2aa1d84e04b0bd4bc888792bd1e-Paper.pdf). In this task, the handwritten equations are given, which consist of sequential pictures of characters. The equations are generated with unknown operation rules from images of symbols ('0', '1', '+' and '='), and each equation is associated with a label indicating whether the equation is correct (i.e., positive) or not (i.e., negative). Also, we are given a knowledge base which involves the structure of the equations and a recursive definition of bit-wise operations. The task is to learn from a training set of above mentioned equations and then to predict labels of unseen equations. | |||
| ## Run | |||
| ```bash | |||
| pip install -r requirements.txt | |||
| python main.py | |||
| ``` | |||
| ## Usage | |||
| ```bash | |||
| usage: main.py [-h] [--no-cuda] [--epochs EPOCHS] [--lr LR] | |||
| [--weight-decay WEIGHT_DECAY] [--batch-size BATCH_SIZE] | |||
| [--loops LOOPS] [--segment_size SEGMENT_SIZE] | |||
| [--save_interval SAVE_INTERVAL] [--max-revision MAX_REVISION] | |||
| [--require-more-revision REQUIRE_MORE_REVISION] | |||
| [--ground] [--max-err MAX_ERR] | |||
| Handwritten Equation Decipherment example | |||
| optional arguments: | |||
| -h, --help show this help message and exit | |||
| --no-cuda disables CUDA training | |||
| --epochs EPOCHS number of epochs in each learning loop iteration | |||
| (default : 1) | |||
| --lr LR base model learning rate (default : 0.001) | |||
| --weight-decay WEIGHT_DECAY | |||
| weight decay (default : 0.0001) | |||
| --batch-size BATCH_SIZE | |||
| base model batch size (default : 32) | |||
| --save_interval SAVE_INTERVAL | |||
| save interval (default : 1) | |||
| --max-revision MAX_REVISION | |||
| maximum revision in reasoner (default : 10) | |||
| ``` | |||
| @@ -1,173 +0,0 @@ | |||
| import os | |||
| import itertools | |||
| import random | |||
| import numpy as np | |||
| from PIL import Image | |||
| import pickle | |||
| def get_sign_path_list(data_dir, sign_names): | |||
| sign_num = len(sign_names) | |||
| index_dict = dict(zip(sign_names, list(range(sign_num)))) | |||
| ret = [[] for _ in range(sign_num)] | |||
| for path in os.listdir(data_dir): | |||
| if (path in sign_names): | |||
| index = index_dict[path] | |||
| sign_path = os.path.join(data_dir, path) | |||
| for p in os.listdir(sign_path): | |||
| ret[index].append(os.path.join(sign_path, p)) | |||
| return ret | |||
| def split_pool_by_rate(pools, rate, seed = None): | |||
| if seed is not None: | |||
| random.seed(seed) | |||
| ret1 = [] | |||
| ret2 = [] | |||
| for pool in pools: | |||
| random.shuffle(pool) | |||
| num = int(len(pool) * rate) | |||
| ret1.append(pool[:num]) | |||
| ret2.append(pool[num:]) | |||
| return ret1, ret2 | |||
| def int_to_system_form(num, system_num): | |||
| if num == 0: | |||
| return "0" | |||
| ret = "" | |||
| while (num > 0): | |||
| ret += str(num % system_num) | |||
| num //= system_num | |||
| return ret[::-1] | |||
| def generator_equations(left_opt_len, right_opt_len, res_opt_len, system_num, label, generate_type): | |||
| expr_len = left_opt_len + right_opt_len | |||
| num_list = "".join([str(i) for i in range(system_num)]) | |||
| ret = [] | |||
| if generate_type == "all": | |||
| candidates = itertools.product(num_list, repeat = expr_len) | |||
| else: | |||
| candidates = [''.join(random.sample(['0', '1'] * expr_len, expr_len))] | |||
| random.shuffle(candidates) | |||
| for nums in candidates: | |||
| left_num = "".join(nums[:left_opt_len]) | |||
| right_num = "".join(nums[left_opt_len:]) | |||
| left_value = int(left_num, system_num) | |||
| right_value = int(right_num, system_num) | |||
| result_value = left_value + right_value | |||
| if (label == 'negative'): | |||
| result_value += random.randint(-result_value, result_value) | |||
| if (left_value + right_value == result_value): | |||
| continue | |||
| result_num = int_to_system_form(result_value, system_num) | |||
| #leading zeros | |||
| if (res_opt_len != len(result_num)): | |||
| continue | |||
| if ((left_opt_len > 1 and left_num[0] == '0') or (right_opt_len > 1 and right_num[0] == '0')): | |||
| continue | |||
| #add leading zeros | |||
| if (res_opt_len < len(result_num)): | |||
| continue | |||
| while (len(result_num) < res_opt_len): | |||
| result_num = '0' + result_num | |||
| #continue | |||
| ret.append(left_num + '+' + right_num + '=' + result_num) # current only consider '+' and '=' | |||
| #print(ret[-1]) | |||
| return ret | |||
| def generator_equation_by_len(equation_len, system_num = 2, label = 0, require_num = 1): | |||
| generate_type = "one" | |||
| ret = [] | |||
| equation_sign_num = 2 # '+' and '=' | |||
| while len(ret) < require_num: | |||
| left_opt_len = random.randint(1, equation_len - 1 - equation_sign_num) | |||
| right_opt_len = random.randint(1, equation_len - left_opt_len - equation_sign_num) | |||
| res_opt_len = equation_len - left_opt_len - right_opt_len - equation_sign_num | |||
| ret.extend(generator_equations(left_opt_len, right_opt_len, res_opt_len, system_num, label, generate_type)) | |||
| return ret | |||
| def generator_equations_by_len(equation_len, system_num = 2, label = 0, repeat_times = 1, keep = 1, generate_type = "all"): | |||
| ret = [] | |||
| equation_sign_num = 2 # '+' and '=' | |||
| for left_opt_len in range(1, equation_len - (2 + equation_sign_num) + 1): | |||
| for right_opt_len in range(1, equation_len - left_opt_len - (1 + equation_sign_num) + 1): | |||
| res_opt_len = equation_len - left_opt_len - right_opt_len - equation_sign_num | |||
| for i in range(repeat_times): #generate more equations | |||
| if random.random() > keep ** (equation_len): | |||
| continue | |||
| ret.extend(generator_equations(left_opt_len, right_opt_len, res_opt_len, system_num, label, generate_type)) | |||
| return ret | |||
| def generator_equations_by_max_len(max_equation_len, system_num = 2, label = 0, repeat_times = 1, keep = 1, generate_type = "all", num_per_len = None): | |||
| ret = [] | |||
| equation_sign_num = 2 # '+' and '=' | |||
| for equation_len in range(3 + equation_sign_num, max_equation_len + 1): | |||
| if (num_per_len is None): | |||
| ret.extend(generator_equations_by_len(equation_len, system_num, label, repeat_times, keep, generate_type)) | |||
| else: | |||
| ret.extend(generator_equation_by_len(equation_len, system_num, label, require_num = num_per_len)) | |||
| return ret | |||
| def generator_equation_images(image_pools, equations, signs, shape, seed, is_color): | |||
| if (seed is not None): | |||
| random.seed(seed) | |||
| ret = [] | |||
| sign_num = len(signs) | |||
| sign_index_dict = dict(zip(signs, list(range(sign_num)))) | |||
| for equation in equations: | |||
| data = [] | |||
| for sign in equation: | |||
| index = sign_index_dict[sign] | |||
| pick = random.randint(0, len(image_pools[index]) - 1) | |||
| if is_color: | |||
| image = Image.open(image_pools[index][pick]).convert('RGB').resize(shape) | |||
| else: | |||
| image = Image.open(image_pools[index][pick]).convert('I').resize(shape) | |||
| image_array = np.array(image) | |||
| image_array = (image_array-127)*(1./128) | |||
| data.append(image_array) | |||
| ret.append(np.array(data)) | |||
| return ret | |||
| def get_equation_std_data(data_dir, sign_dir_lists, sign_output_lists, shape = (28, 28), train_max_equation_len = 10, test_max_equation_len = 10, system_num = 2, tmp_file_prev = | |||
| None, seed = None, train_num_per_len = 10, test_num_per_len = 10, is_color = False): | |||
| tmp_file = "" | |||
| if (tmp_file_prev is not None): | |||
| tmp_file = "%s_train_len_%d_test_len_%d_sys_%d_.pk" % (tmp_file_prev, train_max_equation_len, test_max_equation_len, system_num) | |||
| if (os.path.exists(tmp_file)): | |||
| return pickle.load(open(tmp_file, "rb")) | |||
| image_pools = get_sign_path_list(data_dir, sign_dir_lists) | |||
| train_pool, test_pool = split_pool_by_rate(image_pools, 0.8, seed) | |||
| ret = {} | |||
| for label in ["positive", "negative"]: | |||
| print("Generating equations.") | |||
| train_equations = generator_equations_by_max_len(train_max_equation_len, system_num, label, num_per_len = train_num_per_len) | |||
| test_equations = generator_equations_by_max_len(test_max_equation_len, system_num, label, num_per_len = test_num_per_len) | |||
| print(train_equations) | |||
| print(test_equations) | |||
| print("Generated equations.") | |||
| print("Generating equation image data.") | |||
| ret["train:%s" % (label)] = generator_equation_images(train_pool, train_equations, sign_output_lists, shape, seed, is_color) | |||
| ret["test:%s" % (label)] = generator_equation_images(test_pool, test_equations, sign_output_lists, shape, seed, is_color) | |||
| print("Generated equation image data.") | |||
| if (tmp_file_prev is not None): | |||
| pickle.dump(ret, open(tmp_file, "wb")) | |||
| return ret | |||
| if __name__ == "__main__": | |||
| data_dirs = ["./dataset/hed/mnist_images", "./dataset/hed/random_images"] #, "../dataset/cifar10_images"] | |||
| tmp_file_prevs = ["mnist_equation_data", "random_equation_data"] #, "cifar10_equation_data"] | |||
| for data_dir, tmp_file_prev in zip(data_dirs, tmp_file_prevs): | |||
| data = get_equation_std_data(data_dir = data_dir,\ | |||
| sign_dir_lists = ['0', '1', '10', '11'],\ | |||
| sign_output_lists = ['0', '1', '+', '='],\ | |||
| shape = (28, 28),\ | |||
| train_max_equation_len = 26, \ | |||
| test_max_equation_len = 26, \ | |||
| system_num = 2, \ | |||
| tmp_file_prev = tmp_file_prev, \ | |||
| train_num_per_len = 300, \ | |||
| test_num_per_len = 300, \ | |||
| is_color = False) | |||
| @@ -344,7 +344,6 @@ | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Set up metrics\n", | |||
| "metric_list = [SymbolAccuracy(prefix=\"hed\"), ReasoningMetric(kb=kb, prefix=\"hed\")]" | |||
| ] | |||
| }, | |||
| @@ -390,7 +389,7 @@ | |||
| "log_dir = ABLLogger.get_current_instance().log_dir\n", | |||
| "weights_dir = osp.join(log_dir, \"weights\")\n", | |||
| "\n", | |||
| "bridge.pretrain(\"./weights\")\n", | |||
| "bridge.pretrain(weights_dir)\n", | |||
| "bridge.train(train_data, val_data)\n", | |||
| "bridge.test(test_data)" | |||
| ] | |||
| @@ -0,0 +1,101 @@ | |||
| import os.path as osp | |||
| import argparse | |||
| import torch | |||
| import torch.nn as nn | |||
| from examples.hed.datasets import get_dataset, split_equation | |||
| from examples.models.nn import SymbolNet | |||
| from abl.learning import ABLModel, BasicNN | |||
| from examples.hed.reasoning import HedKB, HedReasoner | |||
| from abl.data.evaluation import ReasoningMetric, SymbolAccuracy | |||
| from abl.utils import ABLLogger, print_log | |||
| from examples.hed.bridge import HedBridge | |||
| def main(): | |||
| parser = argparse.ArgumentParser(description="Handwritten Equation Decipherment example") | |||
| parser.add_argument( | |||
| "--no-cuda", action="store_true", default=False, help="disables CUDA training" | |||
| ) | |||
| parser.add_argument( | |||
| "--epochs", | |||
| type=int, | |||
| default=1, | |||
| help="number of epochs in each learning loop iteration (default : 1)", | |||
| ) | |||
| parser.add_argument( | |||
| "--lr", type=float, default=1e-3, help="base model learning rate (default : 0.001)" | |||
| ) | |||
| parser.add_argument( | |||
| "--weight-decay", type=float, default=1e-4, help="weight decay (default : 0.0001)" | |||
| ) | |||
| parser.add_argument( | |||
| "--batch-size", type=int, default=32, help="base model batch size (default : 32)" | |||
| ) | |||
| parser.add_argument( | |||
| "--segment_size", type=int or float, default=1000, help="segment size (default : 1000)" | |||
| ) | |||
| parser.add_argument("--save_interval", type=int, default=1, help="save interval (default : 1)") | |||
| parser.add_argument( | |||
| "--max-revision", | |||
| type=int or float, | |||
| default=10, | |||
| help="maximum revision in reasoner (default : 10)", | |||
| ) | |||
| args = parser.parse_args() | |||
| ### Working with Data | |||
| total_train_data = get_dataset(train=True) | |||
| train_data, val_data = split_equation(total_train_data, 3, 1) | |||
| test_data = get_dataset(train=False) | |||
| ### Building the Learning Part | |||
| # Build necessary components for BasicNN | |||
| cls = SymbolNet(num_classes=4) | |||
| loss_fn = nn.CrossEntropyLoss() | |||
| optimizer = torch.optim.RMSprop(cls.parameters(), lr=args.lr, weight_decay=args.weight_deccay) | |||
| use_cuda = not args.no_cuda and torch.cuda.is_available() | |||
| device = torch.device("cuda" if use_cuda else "cpu") | |||
| # Build BasicNN | |||
| base_model = BasicNN( | |||
| cls, | |||
| loss_fn, | |||
| optimizer, | |||
| device, | |||
| batch_size=args.batch_size, | |||
| num_epochs=args.epochs, | |||
| stop_loss=None, | |||
| ) | |||
| # Build ABLModel | |||
| model = ABLModel(base_model) | |||
| ### Building the Reasoning Part | |||
| # Build knowledge base | |||
| kb = HedKB() | |||
| # Create reasoner | |||
| reasoner = HedReasoner(kb, dist_func="hamming", use_zoopt=True, max_revision=args.max_revision) | |||
| ### Building Evaluation Metrics | |||
| metric_list = [SymbolAccuracy(prefix="hed"), ReasoningMetric(kb=kb, prefix="hed")] | |||
| ### Bridge Learning and Reasoning | |||
| bridge = HedBridge(model, reasoner, metric_list) | |||
| # Build logger | |||
| print_log("Abductive Learning on the HED example.", logger="current") | |||
| # Retrieve the directory of the Log file and define the directory for saving the model weights. | |||
| log_dir = ABLLogger.get_current_instance().log_dir | |||
| weights_dir = osp.join(log_dir, "weights") | |||
| bridge.pretrain(weights_dir) | |||
| bridge.train(train_data, val_data) | |||
| bridge.test(test_data) | |||
| if __name__ == "__main__": | |||
| main() | |||
| @@ -1,4 +1,4 @@ | |||
| # MNIST Addition Example | |||
| # Handwritten Formula | |||
| This example shows a simple implementation of [Handwritten Formula](https://arxiv.org/abs/2006.06649) task, where handwritten images of decimal formulas and their computed results are given, alongwith a domain knowledge base containing information on how to compute the decimal formula. The task is to recognize the symbols (which can be digits or operators '+', '-', '×', '÷') of handwritten images and accurately determine their results. | |||
| @@ -13,13 +13,13 @@ python main.py | |||
| ```bash | |||
| usage: main.py [-h] [--no-cuda] [--epochs EPOCHS] [--lr LR] | |||
| [--weight-decay WEIGHT_DECAY] [--batch-size BATCH_SIZE] | |||
| [--batch-size BATCH_SIZE] | |||
| [--loops LOOPS] [--segment_size SEGMENT_SIZE] | |||
| [--save_interval SAVE_INTERVAL] [--max-revision MAX_REVISION] | |||
| [--require-more-revision REQUIRE_MORE_REVISION] | |||
| [--ground] [--max-err MAX_ERR] | |||
| MNIST Addition example | |||
| Handwritten Formula example | |||
| optional arguments: | |||
| -h, --help show this help message and exit | |||
| @@ -67,7 +67,7 @@ class HwfGroundKB(GroundKB): | |||
| def main(): | |||
| parser = argparse.ArgumentParser(description="MNIST Addition example") | |||
| parser = argparse.ArgumentParser(description="Handwritten Formula example") | |||
| parser.add_argument( | |||
| "--no-cuda", action="store_true", default=False, help="disables CUDA training" | |||
| ) | |||
| @@ -1,4 +1,4 @@ | |||
| # MNIST Addition Example | |||
| # MNIST Addition | |||
| This example shows a simple implementation of [MNIST Addition](https://arxiv.org/abs/1805.10872) task, where pairs of MNIST handwritten images and their sums are given, alongwith a domain knowledge base containing information on how to perform addition operations. The task is to recognize the digits of handwritten images and accurately determine their sum. | |||
| @@ -450,7 +450,7 @@ | |||
| "name": "python", | |||
| "nbconvert_exporter": "python", | |||
| "pygments_lexer": "ipython3", | |||
| "version": "3.8.18" | |||
| "version": "3.8.13" | |||
| }, | |||
| "orig_nbformat": 4, | |||
| "vscode": { | |||
| @@ -0,0 +1,23 @@ | |||
| # Zoo Example | |||
| This example shows a simple implementation of [Zoo](https://archive.ics.uci.edu/dataset/111/zoo). In this task, attributes of animals (such as presence of hair, eggs, etc.) and their targets (the animal class they belong to) are given, along with a knowledge base which contain information about the relations between attributes and targets, e.g., Implies(milk == 1, mammal == 1). The goal of this task is to develop a learning model that can predict the targets of animals based on their attributes. | |||
| ## Run | |||
| ```bash | |||
| pip install -r requirements.txt | |||
| python main.py | |||
| ``` | |||
| ## Usage | |||
| ```bash | |||
| usage: main.py [-h] [--loops LOOPS] | |||
| Zoo example | |||
| optional arguments: | |||
| -h, --help show this help message and exit | |||
| --loops LOOPS number of loop iterations (default : 3) | |||
| ``` | |||
| @@ -13,8 +13,8 @@ class ZooKB(KBBase): | |||
| X, y, categorical_indicator, attribute_names = dataset.get_data(target=dataset.default_target_attribute) | |||
| self.attribute_names = attribute_names | |||
| self.target_names = y.cat.categories.tolist() | |||
| print("Attribute names are: ", self.attribute_names) | |||
| print("Target names are: ", self.target_names) | |||
| # print("Attribute names are: ", self.attribute_names) | |||
| # print("Target names are: ", self.target_names) | |||
| # self.attribute_names = ["hair", "feathers", "eggs", "milk", "airborne", "aquatic", "predator", "toothed", "backbone", "breathes", "venomous", "fins", "legs", "tail", "domestic", "catsize"] | |||
| # self.target_names = ["mammal", "bird", "reptile", "fish", "amphibian", "insect", "invertebrate"] | |||
| @@ -1,119 +1,19 @@ | |||
| import os.path as osp | |||
| import argparse | |||
| import numpy as np | |||
| from sklearn.ensemble import RandomForestClassifier | |||
| from sklearn.metrics import accuracy_score | |||
| from z3 import Solver, Int, If, Not, Implies, Sum, sat | |||
| import openml | |||
| from examples.zoo.get_dataset import load_and_preprocess_dataset, split_dataset | |||
| from abl.learning import ABLModel | |||
| from abl.reasoning import KBBase, Reasoner | |||
| from examples.zoo.kb import ZooKB | |||
| from abl.reasoning import Reasoner | |||
| from abl.data.evaluation import ReasoningMetric, SymbolAccuracy | |||
| from abl.utils import ABLLogger, print_log, confidence_dist | |||
| from abl.bridge import SimpleBridge | |||
| from abl.utils.utils import confidence_dist | |||
| from abl.utils import ABLLogger, print_log | |||
| # Build logger | |||
| print_log("Abductive Learning on the Zoo example.", logger="current") | |||
| # Retrieve the directory of the Log file and define the directory for saving the model weights. | |||
| log_dir = ABLLogger.get_current_instance().log_dir | |||
| weights_dir = osp.join(log_dir, "weights") | |||
| # Learning Part | |||
| rf = RandomForestClassifier() | |||
| model = ABLModel(rf) | |||
| # %% [markdown] | |||
| # ### Logic Part | |||
| # %% | |||
| class ZooKB(KBBase): | |||
| def __init__(self): | |||
| super().__init__(pseudo_label_list=list(range(7)), use_cache=False) | |||
| # Use z3 solver | |||
| self.solver = Solver() | |||
| # Load information of Zoo dataset | |||
| dataset = openml.datasets.get_dataset( | |||
| dataset_id=62, | |||
| download_data=False, | |||
| download_qualities=False, | |||
| download_features_meta_data=False, | |||
| ) | |||
| X, y, categorical_indicator, attribute_names = dataset.get_data( | |||
| target=dataset.default_target_attribute | |||
| ) | |||
| self.attribute_names = attribute_names | |||
| self.target_names = y.cat.categories.tolist() | |||
| # Define variables | |||
| for name in self.attribute_names + self.target_names: | |||
| exec( | |||
| f"globals()['{name}'] = Int('{name}')" | |||
| ) ## or use dict to create var and modify rules | |||
| # Define rules | |||
| rules = [ | |||
| Implies(milk == 1, mammal == 1), | |||
| Implies(mammal == 1, milk == 1), | |||
| Implies(mammal == 1, backbone == 1), | |||
| Implies(mammal == 1, breathes == 1), | |||
| Implies(feathers == 1, bird == 1), | |||
| Implies(bird == 1, feathers == 1), | |||
| Implies(bird == 1, eggs == 1), | |||
| Implies(bird == 1, backbone == 1), | |||
| Implies(bird == 1, breathes == 1), | |||
| Implies(bird == 1, legs == 2), | |||
| Implies(bird == 1, tail == 1), | |||
| Implies(reptile == 1, backbone == 1), | |||
| Implies(reptile == 1, breathes == 1), | |||
| Implies(reptile == 1, tail == 1), | |||
| Implies(fish == 1, aquatic == 1), | |||
| Implies(fish == 1, toothed == 1), | |||
| Implies(fish == 1, backbone == 1), | |||
| Implies(fish == 1, Not(breathes == 1)), | |||
| Implies(fish == 1, fins == 1), | |||
| Implies(fish == 1, legs == 0), | |||
| Implies(fish == 1, tail == 1), | |||
| Implies(amphibian == 1, eggs == 1), | |||
| Implies(amphibian == 1, aquatic == 1), | |||
| Implies(amphibian == 1, backbone == 1), | |||
| Implies(amphibian == 1, breathes == 1), | |||
| Implies(amphibian == 1, legs == 4), | |||
| Implies(insect == 1, eggs == 1), | |||
| Implies(insect == 1, Not(backbone == 1)), | |||
| Implies(insect == 1, legs == 6), | |||
| Implies(invertebrate == 1, Not(backbone == 1)), | |||
| ] | |||
| # Define weights and sum of violated weights | |||
| self.weights = {rule: 1 for rule in rules} | |||
| self.total_violation_weight = Sum( | |||
| [If(Not(rule), self.weights[rule], 0) for rule in self.weights] | |||
| ) | |||
| def logic_forward(self, pseudo_label, data_point): | |||
| attribute_names, target_names = self.attribute_names, self.target_names | |||
| solver = self.solver | |||
| total_violation_weight = self.total_violation_weight | |||
| pseudo_label, data_point = pseudo_label[0], data_point[0] | |||
| self.solver.reset() | |||
| for name, value in zip(attribute_names, data_point): | |||
| solver.add(eval(f"{name} == {value}")) | |||
| for cate, name in zip(self.pseudo_label_list, target_names): | |||
| value = 1 if (cate == pseudo_label) else 0 | |||
| solver.add(eval(f"{name} == {value}")) | |||
| if solver.check() == sat: | |||
| model = solver.model() | |||
| total_weight = model.evaluate(total_violation_weight) | |||
| return total_weight.as_long() | |||
| else: | |||
| # No solution found | |||
| return 1e10 | |||
| def transform_tab_data(X, y): | |||
| return ([[x] for x in X], [[y_item] for y_item in y], [0] * len(y)) | |||
| def consitency(data_example, candidates, candidate_idxs, reasoning_results): | |||
| pred_prob = data_example.pred_prob | |||
| @@ -122,94 +22,54 @@ def consitency(data_example, candidates, candidate_idxs, reasoning_results): | |||
| scores = model_scores + rule_scores | |||
| return scores | |||
| kb = ZooKB() | |||
| reasoner = Reasoner(kb, dist_func=consitency) | |||
| # %% [markdown] | |||
| # ### Datasets and Evaluation Metrics | |||
| # %% | |||
| # Function to load and preprocess the dataset | |||
| def load_and_preprocess_dataset(dataset_id): | |||
| dataset = openml.datasets.get_dataset( | |||
| dataset_id, download_data=True, download_qualities=False, download_features_meta_data=False | |||
| def main(): | |||
| parser = argparse.ArgumentParser(description="Zoo example") | |||
| parser.add_argument( | |||
| "--loops", type=int, default=3, help="number of loop iterations (default : 3)" | |||
| ) | |||
| X, y, _, attribute_names = dataset.get_data(target=dataset.default_target_attribute) | |||
| # Convert data types | |||
| for col in X.select_dtypes(include="bool").columns: | |||
| X[col] = X[col].astype(int) | |||
| y = y.cat.codes.astype(int) | |||
| X, y = X.to_numpy(), y.to_numpy() | |||
| return X, y | |||
| # Function to split data (one shot) | |||
| def split_dataset(X, y, test_size=0.3): | |||
| # For every class: 1 : (1-test_size)*(len-1) : test_size*(len-1) | |||
| label_indices, unlabel_indices, test_indices = [], [], [] | |||
| for class_label in np.unique(y): | |||
| idxs = np.where(y == class_label)[0] | |||
| np.random.shuffle(idxs) | |||
| n_train_unlabel = int((1 - test_size) * (len(idxs) - 1)) | |||
| label_indices.append(idxs[0]) | |||
| unlabel_indices.extend(idxs[1 : 1 + n_train_unlabel]) | |||
| test_indices.extend(idxs[1 + n_train_unlabel :]) | |||
| X_label, y_label = X[label_indices], y[label_indices] | |||
| X_unlabel, y_unlabel = X[unlabel_indices], y[unlabel_indices] | |||
| X_test, y_test = X[test_indices], y[test_indices] | |||
| return X_label, y_label, X_unlabel, y_unlabel, X_test, y_test | |||
| # Load and preprocess the Zoo dataset | |||
| X, y = load_and_preprocess_dataset(dataset_id=62) | |||
| # Split data into labeled/unlabeled/test data | |||
| X_label, y_label, X_unlabel, y_unlabel, X_test, y_test = split_dataset(X, y, test_size=0.3) | |||
| # Transform tabluar data to the format required by ABL, which is a tuple of (X, ground truth of X, reasoning results) | |||
| # For tabular data in abl, each example contains a single instance (a row from the dataset). | |||
| # For these tabular data examples, the reasoning results are expected to be 0, indicating no rules are violated. | |||
| def transform_tab_data(X, y): | |||
| return ([[x] for x in X], [[y_item] for y_item in y], [0] * len(y)) | |||
| label_data = transform_tab_data(X_label, y_label) | |||
| test_data = transform_tab_data(X_test, y_test) | |||
| train_data = transform_tab_data(X_unlabel, y_unlabel) | |||
| # %% | |||
| # Set up metrics | |||
| metric_list = [SymbolAccuracy(prefix="zoo"), ReasoningMetric(kb=kb, prefix="zoo")] | |||
| # %% [markdown] | |||
| # ### Bridge Machine Learning and Logic Reasoning | |||
| # %% | |||
| bridge = SimpleBridge(model, reasoner, metric_list) | |||
| # %% [markdown] | |||
| # ### Train and Test | |||
| # %% | |||
| # Pre-train the machine learning model | |||
| rf.fit(X_label, y_label) | |||
| # %% | |||
| # Test the initial model | |||
| print("------- Test the initial model -----------") | |||
| bridge.test(test_data) | |||
| print("------- Use ABL to train the model -----------") | |||
| # Use ABL to train the model | |||
| bridge.train( | |||
| train_data=train_data, | |||
| label_data=label_data, | |||
| loops=3, | |||
| segment_size=len(X_unlabel), | |||
| save_dir=weights_dir, | |||
| ) | |||
| print("------- Test the final model -----------") | |||
| # Test the final model | |||
| bridge.test(test_data) | |||
| args = parser.parse_args() | |||
| ### Working with Data | |||
| X, y = load_and_preprocess_dataset(dataset_id=62) | |||
| X_label, y_label, X_unlabel, y_unlabel, X_test, y_test = split_dataset(X, y, test_size=0.3) | |||
| label_data = transform_tab_data(X_label, y_label) | |||
| test_data = transform_tab_data(X_test, y_test) | |||
| train_data = transform_tab_data(X_unlabel, y_unlabel) | |||
| ### Building the Learning Part | |||
| base_model = RandomForestClassifier() | |||
| # Build ABLModel | |||
| model = ABLModel(base_model) | |||
| ### Building the Reasoning Part | |||
| # Build knowledge base | |||
| kb = ZooKB() | |||
| # Create reasoner | |||
| reasoner = Reasoner(kb, dist_func=consitency) | |||
| ### Building Evaluation Metrics | |||
| metric_list = [SymbolAccuracy(prefix="zoo"), ReasoningMetric(kb=kb, prefix="zoo")] | |||
| # Build logger | |||
| print_log("Abductive Learning on the ZOO example.", logger="current") | |||
| log_dir = ABLLogger.get_current_instance().log_dir | |||
| weights_dir = osp.join(log_dir, "weights") | |||
| ### Bridging learning and reasoning | |||
| bridge = SimpleBridge(model, reasoner, metric_list) | |||
| # Performing training and testing | |||
| print_log("------- Use labeled data to pretrain the model -----------", logger="current") | |||
| base_model.fit(X_label, y_label) | |||
| print_log("------- Test the initial model -----------", logger="current") | |||
| bridge.test(test_data) | |||
| print_log("------- Use ABL to train the model -----------", logger="current") | |||
| bridge.train(train_data=train_data, label_data=label_data, loops=args.loops, segment_size=len(X_unlabel), save_dir=weights_dir) | |||
| print_log("------- Test the final model -----------", logger="current") | |||
| bridge.test(test_data) | |||
| if __name__ == "__main__": | |||
| main() | |||
| @@ -6,9 +6,9 @@ | |||
| "source": [ | |||
| "# ZOO\n", | |||
| "\n", | |||
| "This notebook shows an implementation of [MNIST Addition](https://arxiv.org/abs/1805.10872). In this task, pairs of MNIST handwritten images and their sums are given, alongwith a domain knowledge base containing information on how to perform addition operations. The task is to recognize the digits of handwritten images and accurately determine their sum.\n", | |||
| "This notebook shows an implementation of [Zoo](https://archive.ics.uci.edu/dataset/111/zoo). In this task, attributes of animals (such as presence of hair, eggs, etc.) and their targets (the animal class they belong to) are given, along with a knowledge base which contain information about the relations between attributes and targets, e.g., Implies(milk == 1, mammal == 1). \n", | |||
| "\n", | |||
| "Intuitively, we first use a machine learning model (learning part) to convert the input images to digits (we call them pseudo-labels), and then use the knowledge base (reasoning part) to calculate the sum of these digits. Since we do not have ground-truth of the digits, in Abductive Learning, the reasoning part will leverage domain knowledge and revise the initial digits yielded by the learning part through abductive reasoning. This process enables us to further update the machine learning model." | |||
| "The goal of this task is to develop a learning model that can predict the targets of animals based on their attributes. In the initial stages, when the model is under-trained, it may produce incorrect predictions that conflict with the relations contained in the knowledge base. When this happens, abductive reasoning can be employed to adjust these results and retrain the model accordingly. This process enables us to further update the learning model." | |||
| ] | |||
| }, | |||
| { | |||
| @@ -36,7 +36,7 @@ | |||
| "source": [ | |||
| "## Working with Data\n", | |||
| "\n", | |||
| "First, we get the training and testing datasets:" | |||
| "First, we load and preprocess the [Zoo dataset](https://archive.ics.uci.edu/dataset/111/zoo), and split it into labeled/unlabeled/test data" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -45,10 +45,7 @@ | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Load and preprocess the Zoo dataset\n", | |||
| "X, y = load_and_preprocess_dataset(dataset_id=62)\n", | |||
| "\n", | |||
| "# Split data into labeled/unlabeled/test data\n", | |||
| "X_label, y_label, X_unlabel, y_unlabel, X_test, y_test = split_dataset(X, y, test_size=0.3)" | |||
| ] | |||
| }, | |||
| @@ -56,9 +53,7 @@ | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "`train_data` and `test_data` share identical structures: tuples with three components: X (list where each element is a list of two images), gt_pseudo_label (list where each element is a list of two digits, i.e., pseudo-labels) and Y (list where each element is the sum of the two digits). The length and structures of datasets are illustrated as follows.\n", | |||
| "\n", | |||
| "Note: ``gt_pseudo_label`` is only used to evaluate the performance of the learning part but not to train the model." | |||
| "Zoo dataset consist of tabular data. The attributes contains 17 boolean values (e.g., hair, feathers, eggs, milk, airborne, aquatic, etc.) and the target is a integer value in range [0,6] representing 7 classes (e.g., mammal, bird, reptile, fish, amphibian, insect, and other). Below is an illustration:" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -99,11 +94,7 @@ | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "Transform tabluar data to the format required by ABL-Package, which is a tuple of (X, gt_pseudo_label, Y)\n", | |||
| "\n", | |||
| "For tabular data in abl, each example contains a single instance (a row from the dataset).\n", | |||
| "\n", | |||
| "For these tabular data samples, the reasoning results are expected to be 0, indicating no rules are violated." | |||
| "Next, we transform the tabular data to the format required by ABL-Package, which is a tuple of (X, gt_pseudo_label, Y). In this task, we treat the attributes as X and the targets as gt_pseudo_label (ground truth pseudo-labels). Y (reasoning results) are expected to be 0, indicating no rules are violated." | |||
| ] | |||
| }, | |||
| { | |||
| @@ -131,28 +122,14 @@ | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "To build the learning part, we need to first build a machine learning base model. We use a [Random Forest](https://en.wikipedia.org/wiki/Random_forest) as the base model" | |||
| "To build the learning part, we need to first build a machine learning base model. We use a [Random Forest](https://en.wikipedia.org/wiki/Random_forest) as the base model." | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": 5, | |||
| "metadata": {}, | |||
| "outputs": [ | |||
| { | |||
| "data": { | |||
| "text/html": [ | |||
| "<style>#sk-container-id-1 {color: black;}#sk-container-id-1 pre{padding: 0;}#sk-container-id-1 div.sk-toggleable {background-color: white;}#sk-container-id-1 label.sk-toggleable__label {cursor: pointer;display: block;width: 100%;margin-bottom: 0;padding: 0.3em;box-sizing: border-box;text-align: center;}#sk-container-id-1 label.sk-toggleable__label-arrow:before {content: \"▸\";float: left;margin-right: 0.25em;color: #696969;}#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {color: black;}#sk-container-id-1 div.sk-estimator:hover label.sk-toggleable__label-arrow:before {color: black;}#sk-container-id-1 div.sk-toggleable__content {max-height: 0;max-width: 0;overflow: hidden;text-align: left;background-color: #f0f8ff;}#sk-container-id-1 div.sk-toggleable__content pre {margin: 0.2em;color: black;border-radius: 0.25em;background-color: #f0f8ff;}#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {max-height: 200px;max-width: 100%;overflow: auto;}#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {content: \"▾\";}#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-1 input.sk-hidden--visually {border: 0;clip: rect(1px 1px 1px 1px);clip: rect(1px, 1px, 1px, 1px);height: 1px;margin: -1px;overflow: hidden;padding: 0;position: absolute;width: 1px;}#sk-container-id-1 div.sk-estimator {font-family: monospace;background-color: #f0f8ff;border: 1px dotted black;border-radius: 0.25em;box-sizing: border-box;margin-bottom: 0.5em;}#sk-container-id-1 div.sk-estimator:hover {background-color: #d4ebff;}#sk-container-id-1 div.sk-parallel-item::after {content: \"\";width: 100%;border-bottom: 1px solid gray;flex-grow: 1;}#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-1 div.sk-serial::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: 0;}#sk-container-id-1 div.sk-serial {display: flex;flex-direction: column;align-items: center;background-color: white;padding-right: 0.2em;padding-left: 0.2em;position: relative;}#sk-container-id-1 div.sk-item {position: relative;z-index: 1;}#sk-container-id-1 div.sk-parallel {display: flex;align-items: stretch;justify-content: center;background-color: white;position: relative;}#sk-container-id-1 div.sk-item::before, #sk-container-id-1 div.sk-parallel-item::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: -1;}#sk-container-id-1 div.sk-parallel-item {display: flex;flex-direction: column;z-index: 1;position: relative;background-color: white;}#sk-container-id-1 div.sk-parallel-item:first-child::after {align-self: flex-end;width: 50%;}#sk-container-id-1 div.sk-parallel-item:last-child::after {align-self: flex-start;width: 50%;}#sk-container-id-1 div.sk-parallel-item:only-child::after {width: 0;}#sk-container-id-1 div.sk-dashed-wrapped {border: 1px dashed gray;margin: 0 0.4em 0.5em 0.4em;box-sizing: border-box;padding-bottom: 0.4em;background-color: white;}#sk-container-id-1 div.sk-label label {font-family: monospace;font-weight: bold;display: inline-block;line-height: 1.2em;}#sk-container-id-1 div.sk-label-container {text-align: center;}#sk-container-id-1 div.sk-container {/* jupyter's `normalize.less` sets `[hidden] { display: none; }` but bootstrap.min.css set `[hidden] { display: none !important; }` so we also need the `!important` here to be able to override the default hidden behavior on the sphinx rendered scikit-learn.org. See: https://github.com/scikit-learn/scikit-learn/issues/21755 */display: inline-block !important;position: relative;}#sk-container-id-1 div.sk-text-repr-fallback {display: none;}</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>RandomForestClassifier()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label sk-toggleable__label-arrow\">RandomForestClassifier</label><div class=\"sk-toggleable__content\"><pre>RandomForestClassifier()</pre></div></div></div></div></div>" | |||
| ], | |||
| "text/plain": [ | |||
| "RandomForestClassifier()" | |||
| ] | |||
| }, | |||
| "execution_count": 5, | |||
| "metadata": {}, | |||
| "output_type": "execute_result" | |||
| } | |||
| ], | |||
| "outputs": [], | |||
| "source": [ | |||
| "base_model = RandomForestClassifier()" | |||
| ] | |||
| @@ -184,23 +161,14 @@ | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "In the reasoning part, we first build a knowledge base which contain information on how to perform addition operations. We build it by creating a subclass of `KBBase`. In the derived subclass, we initialize the `pseudo_label_list` parameter specifying list of possible pseudo-labels, and override the `logic_forward` function defining how to perform (deductive) reasoning." | |||
| "In the reasoning part, we first build a knowledge base which contains information about the relations between attributes (X) and targets (pseudo-labels), e.g., Implies(milk == 1, mammal == 1). The knowledge base is built in the `ZooKB` class within file `kb.py`, and is derived from the `KBBase` class." | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": 7, | |||
| "metadata": {}, | |||
| "outputs": [ | |||
| { | |||
| "name": "stdout", | |||
| "output_type": "stream", | |||
| "text": [ | |||
| "Attribute names are: ['hair', 'feathers', 'eggs', 'milk', 'airborne', 'aquatic', 'predator', 'toothed', 'backbone', 'breathes', 'venomous', 'fins', 'legs', 'tail', 'domestic', 'catsize']\n", | |||
| "Target names are: ['mammal', 'bird', 'reptile', 'fish', 'amphibian', 'insect', 'invertebrate']\n" | |||
| ] | |||
| } | |||
| ], | |||
| "outputs": [], | |||
| "source": [ | |||
| "kb = ZooKB()" | |||
| ] | |||
| @@ -209,36 +177,46 @@ | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "The knowledge base can perform logical reasoning (both deductive reasoning and abductive reasoning). Below is an example of performing (deductive) reasoning, and users can refer to [Documentation]() for details of abductive reasoning." | |||
| "As mentioned, for all attributes and targets in the dataset, the reasoning results are expected to be 0 since there should be no violations of the established knowledge in real data. As shown below:" | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": 10, | |||
| "execution_count": 8, | |||
| "metadata": {}, | |||
| "outputs": [ | |||
| { | |||
| "name": "stdout", | |||
| "output_type": "stream", | |||
| "text": [ | |||
| "Reasoning result of pseudo-label example [1, 2] is 3.\n" | |||
| "Example 0: the attributes are: [True False False True False False True True True True False False 4 False\n", | |||
| " False True], and the target is 0.\n", | |||
| "Reasoning result is 0.\n", | |||
| "\n", | |||
| "Example 1: the attributes are: [True False False True False False False True True True False False 4 True\n", | |||
| " False True], and the target is 0.\n", | |||
| "Reasoning result is 0.\n", | |||
| "\n", | |||
| "Example 2: the attributes are: [False False True False False True True True True False False True 0 True\n", | |||
| " False False], and the target is 3.\n", | |||
| "Reasoning result is 0.\n", | |||
| "\n", | |||
| "Example 3: the attributes are: [True False False True False False True True True True False False 4 False\n", | |||
| " False True], and the target is 0.\n", | |||
| "Reasoning result is 0.\n", | |||
| "\n", | |||
| "Example 4: the attributes are: [True False False True False False True True True True False False 4 True\n", | |||
| " False True], and the target is 0.\n", | |||
| "Reasoning result is 0.\n", | |||
| "\n" | |||
| ] | |||
| } | |||
| ], | |||
| "source": [ | |||
| "pseudo_label = [0]\n", | |||
| "data_point = [np.array([1,0,0,1,0,0,1,1,1,1,0,0,4,0,0,1,1])]\n", | |||
| "print(kb.logic_forward(pseudo_label, data_point))\n", | |||
| "for x, y_item in zip(X, y):\n", | |||
| " print(x,y_item)\n", | |||
| " print(kb.logic_forward([y_item], [x]))" | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "Note: In addition to building a knowledge base based on `KBBase`, we can also establish a knowledge base with a ground KB using `GroundKB`, or a knowledge base implemented based on Prolog files using `PrologKB`. The corresponding code for these implementations can be found in the `main.py` file. Those interested are encouraged to examine it for further insights." | |||
| "for idx, (x, y_item) in enumerate(zip(X[:5], y[:5])):\n", | |||
| " print(f\"Example {idx}: the attributes are: {x}, and the target is {y_item}.\")\n", | |||
| " print(f\"Reasoning result is {kb.logic_forward([y_item], [x])}.\")\n", | |||
| " print()" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -250,7 +228,7 @@ | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": 11, | |||
| "execution_count": 9, | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| @@ -281,7 +259,7 @@ | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": 12, | |||
| "execution_count": 10, | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| @@ -300,7 +278,7 @@ | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": 13, | |||
| "execution_count": 11, | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| @@ -316,28 +294,60 @@ | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": null, | |||
| "execution_count": 12, | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "outputs": [ | |||
| { | |||
| "name": "stdout", | |||
| "output_type": "stream", | |||
| "text": [ | |||
| "12/22 11:48:01 - abl - INFO - Abductive Learning on the ZOO example.\n", | |||
| "12/22 11:48:01 - abl - INFO - ------- Use labeled data to pretrain the model -----------\n", | |||
| "12/22 11:48:01 - abl - INFO - ------- Test the initial model -----------\n", | |||
| "12/22 11:48:01 - abl - INFO - Evaluation ended, zoo/character_accuracy: 0.903 zoo/reasoning_accuracy: 0.903 \n", | |||
| "12/22 11:48:01 - abl - INFO - ------- Use ABL to train the model -----------\n", | |||
| "12/22 11:48:01 - abl - INFO - loop(train) [1/3] segment(train) [1/1] \n" | |||
| ] | |||
| }, | |||
| { | |||
| "name": "stdout", | |||
| "output_type": "stream", | |||
| "text": [ | |||
| "12/22 11:48:02 - abl - INFO - Evaluation start: loop(val) [1]\n", | |||
| "12/22 11:48:03 - abl - INFO - Evaluation ended, zoo/character_accuracy: 1.000 zoo/reasoning_accuracy: 1.000 \n", | |||
| "12/22 11:48:03 - abl - INFO - loop(train) [2/3] segment(train) [1/1] \n", | |||
| "12/22 11:48:04 - abl - INFO - Evaluation start: loop(val) [2]\n", | |||
| "12/22 11:48:05 - abl - INFO - Evaluation ended, zoo/character_accuracy: 1.000 zoo/reasoning_accuracy: 1.000 \n", | |||
| "12/22 11:48:05 - abl - INFO - loop(train) [3/3] segment(train) [1/1] \n", | |||
| "12/22 11:48:05 - abl - INFO - Evaluation start: loop(val) [3]\n", | |||
| "12/22 11:48:06 - abl - INFO - Evaluation ended, zoo/character_accuracy: 1.000 zoo/reasoning_accuracy: 1.000 \n", | |||
| "12/22 11:48:06 - abl - INFO - ------- Test the final model -----------\n", | |||
| "12/22 11:48:06 - abl - INFO - Evaluation ended, zoo/character_accuracy: 0.968 zoo/reasoning_accuracy: 0.968 \n" | |||
| ] | |||
| } | |||
| ], | |||
| "source": [ | |||
| "# Build logger\n", | |||
| "print_log(\"Abductive Learning on the ZOO example.\", logger=\"current\")\n", | |||
| "log_dir = ABLLogger.get_current_instance().log_dir\n", | |||
| "weights_dir = osp.join(log_dir, \"weights\")\n", | |||
| "\n", | |||
| "# Pre-train the machine learning model\n", | |||
| "print_log(\"------- Use labeled data to pretrain the model -----------\", logger=\"current\")\n", | |||
| "base_model.fit(X_label, y_label)\n", | |||
| "\n", | |||
| "# Test the initial model\n", | |||
| "print(\"------- Test the initial model -----------\")\n", | |||
| "print_log(\"------- Test the initial model -----------\", logger=\"current\")\n", | |||
| "bridge.test(test_data)\n", | |||
| "print(\"------- Use ABL to train the model -----------\")\n", | |||
| "# Use ABL to train the model\n", | |||
| "print_log(\"------- Use ABL to train the model -----------\", logger=\"current\")\n", | |||
| "bridge.train(train_data=train_data, label_data=label_data, loops=3, segment_size=len(X_unlabel), save_dir=weights_dir)\n", | |||
| "print(\"------- Test the final model -----------\")\n", | |||
| "# Test the final model\n", | |||
| "print_log(\"------- Test the final model -----------\", logger=\"current\")\n", | |||
| "bridge.test(test_data)" | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "We may see from the results, after undergoing training with ABL, the model's accuracy has improved." | |||
| ] | |||
| } | |||
| ], | |||
| "metadata": { | |||
| @@ -356,7 +366,7 @@ | |||
| "name": "python", | |||
| "nbconvert_exporter": "python", | |||
| "pygments_lexer": "ipython3", | |||
| "version": "3.8.18" | |||
| "version": "3.8.13" | |||
| }, | |||
| "orig_nbformat": 4, | |||
| "vscode": { | |||