| @@ -3,53 +3,16 @@ Use ABL-Package Step by Step | |||
| Using ABL-Package for your learning tasks contains five steps | |||
| - Build the reasoning part | |||
| - Build the machine learning part | |||
| - Build the reasoning part | |||
| - Build datasets and evaluation metrics | |||
| - Bridge the reasoning and machine learning parts | |||
| - Bridge the machine learning and reasoning parts | |||
| - Use ``Bridge.train`` and ``Bridge.test`` to train and test | |||
| Build the reasoning part | |||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| In ABL-Package, the reasoning part is wrapped in the ``ReasonerBase`` class. In order to create an instance of this class, we first need to inherit the ``KBBase`` class to customize our knowledge base. Arguments of the ``__init__`` method of the knowledge base should at least contain ``pseudo_label_list`` which is a list of all pseudo labels. The ``logic_forward`` method of ``KBBase`` is an abstract method and we need to instantiate this method in our sub-class to give the ability of deduction to the knowledge base. In general, we can customize our knowledge base by | |||
| .. code:: python | |||
| class MyKB(KBBase): | |||
| def __init__(self, pseudo_label_list): | |||
| super().__init__(pseudo_label_list) | |||
| def logic_forward(self, *args, **kwargs): | |||
| # Deduction implementation... | |||
| return deduction_result | |||
| Aside from the knowledge base, the instantiation of the ``ReasonerBase`` also needs to set an extra argument called ``dist_func``, which is the consistency measure used to select the best candidate from all candidates. In general, we can instantiate our reasoner by | |||
| .. code:: python | |||
| kb = MyKB(pseudo_label_list) | |||
| reasoner = ReasonerBase(kb, dist_func="hamming") | |||
| In the MNIST Add example, the reasoner looks like | |||
| .. code:: python | |||
| class AddKB(KBBase): | |||
| def __init__(self, pseudo_label_list): | |||
| super().__init__(pseudo_label_list) | |||
| # Implement the deduction function | |||
| def logic_forward(self, nums): | |||
| return sum(nums) | |||
| kb = AddKB(pseudo_label_list=list(range(10))) | |||
| reasoner = ReasonerBase(kb, dist_func="confidence") | |||
| Build the machine learning part | |||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| Next, we build the machine learning part, which needs to be wrapped in the ``ABLModel`` class. We can use machine learning models from scikit-learn or based on PyTorch to create an instance of ``ABLModel``. | |||
| First, we build the machine learning part, which needs to be wrapped in the ``ABLModel`` class. We can use machine learning models from scikit-learn or based on PyTorch to create an instance of ``ABLModel``. | |||
| - for a scikit-learn model, we can directly use the model to create an instance of ``ABLModel``. For example, we can customize our machine learning model by | |||
| @@ -94,6 +57,43 @@ In the MNIST Add example, the machine learning model looks like | |||
| ) | |||
| model = ABLModel(base_model) | |||
| Build the reasoning part | |||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| Next, we build the reasoning part. In ABL-Package, the reasoning part is wrapped in the ``ReasonerBase`` class. In order to create an instance of this class, we first need to inherit the ``KBBase`` class to customize our knowledge base. Arguments of the ``__init__`` method of the knowledge base should at least contain ``pseudo_label_list`` which is a list of all pseudo labels. The ``logic_forward`` method of ``KBBase`` is an abstract method and we need to instantiate this method in our sub-class to give the ability of deduction to the knowledge base. In general, we can customize our knowledge base by | |||
| .. code:: python | |||
| class MyKB(KBBase): | |||
| def __init__(self, pseudo_label_list): | |||
| super().__init__(pseudo_label_list) | |||
| def logic_forward(self, *args, **kwargs): | |||
| # Deduction implementation... | |||
| return deduction_result | |||
| Aside from the knowledge base, the instantiation of the ``ReasonerBase`` also needs to set an extra argument called ``dist_func``, which is the consistency measure used to select the best candidate from all candidates. In general, we can instantiate our reasoner by | |||
| .. code:: python | |||
| kb = MyKB(pseudo_label_list) | |||
| reasoner = ReasonerBase(kb, dist_func="hamming") | |||
| In the MNIST Add example, the reasoner looks like | |||
| .. code:: python | |||
| class AddKB(KBBase): | |||
| def __init__(self, pseudo_label_list): | |||
| super().__init__(pseudo_label_list) | |||
| # Implement the deduction function | |||
| def logic_forward(self, nums): | |||
| return sum(nums) | |||
| kb = AddKB(pseudo_label_list=list(range(10))) | |||
| reasoner = ReasonerBase(kb, dist_func="confidence") | |||
| Build datasets and evaluation metrics | |||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| @@ -117,11 +117,11 @@ In the case of MNIST Add example, the metric definition looks like | |||
| metric_list = [SymbolMetric(prefix="mnist_add"), SemanticsMetric(kb=kb, prefix="mnist_add")] | |||
| Bridge the reasoning and machine learning parts | |||
| Bridge the machine learning and reasoning parts | |||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
| We next need to bridge the reasoning and machine learning parts. In ABL-Package, the ``BaseBridge`` class gives necessary abstract interface definitions to bridge the two parts and ``SimpleBridge`` provides a basic implementation. | |||
| We build a bridge with previously defined ``reasoner``, ``model``, and ``metric_list`` as follows: | |||
| We next need to bridge the machine learning and reasoning parts. In ABL-Package, the ``BaseBridge`` class gives necessary abstract interface definitions to bridge the two parts and ``SimpleBridge`` provides a basic implementation. | |||
| We build a bridge with previously defined ``model``, ``reasoner``, and ``metric_list`` as follows: | |||
| .. code:: python | |||
| @@ -1,5 +1,19 @@ | |||
| Installation | |||
| ================== | |||
| .. contents:: Table of Contents | |||
| Case a: If you develop and run ``abl`` directly, install it from source: | |||
| .. code-block:: bash | |||
| git clone https://github.com/AbductiveLearning/ABL-Package.git | |||
| cd ABL-Package | |||
| pip install -v -e . | |||
| # "-v" means verbose, or more output | |||
| # "-e" means installing a project in editable mode, | |||
| # thus any local modifications made to the code will take effect without reinstallation. | |||
| Case b: If you use ``abl`` as a dependency or third-party package, install it with pip: | |||
| .. code-block:: bash | |||
| pip install abl | |||
| @@ -1,5 +1,83 @@ | |||
| Quick Start | |||
| ================== | |||
| .. contents:: Table of Contents | |||
| We use the MNIST Add benchmark as a quick start example. | |||
| .. code:: python | |||
| import torch | |||
| import torch.nn as nn | |||
| from abl.bridge import SimpleBridge | |||
| from abl.evaluation import SemanticsMetric, SymbolMetric | |||
| from abl.learning import ABLModel, BasicNN | |||
| from abl.reasoning import KBBase, ReasonerBase | |||
| from abl.utils import print_log | |||
| from examples.mnist_add.datasets.get_mnist_add import get_mnist_add | |||
| from examples.models.nn import LeNet5 | |||
| # Build logger | |||
| print_log("Abductive Learning on the MNIST Add example.", logger="current") | |||
| # Machine Learning Part | |||
| # Build necessary components for BasicNN | |||
| cls = LeNet5(num_classes=10) | |||
| criterion = nn.CrossEntropyLoss() | |||
| optimizer = torch.optim.Adam(cls.parameters(), lr=0.001, betas=(0.9, 0.99)) | |||
| device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") | |||
| # Build BasicNN | |||
| base_model = BasicNN(cls, criterion, optimizer, device) | |||
| # Build ABLModel | |||
| model = ABLModel(base_model) | |||
| # Logic Part | |||
| # Build knowledge base and reasoner | |||
| class AddKB(KBBase): | |||
| def __init__(self, pseudo_label_list): | |||
| super().__init__(pseudo_label_list) | |||
| # Implement the deduction function | |||
| def logic_forward(self, nums): | |||
| return sum(nums) | |||
| kb = AddKB(pseudo_label_list=list(range(10))) | |||
| reasoner = ReasonerBase(kb, dist_func="confidence") | |||
| # Datasets and Evaluation Metrics | |||
| # Get training and testing data | |||
| train_data = get_mnist_add(train=True, get_pseudo_label=True) | |||
| test_data = get_mnist_add(train=False, get_pseudo_label=True) | |||
| # Set up metrics | |||
| metric_list = [SymbolMetric(prefix="mnist_add"), SemanticsMetric(kb=kb, prefix="mnist_add")] | |||
| # Bridge Machine Learning and Logic Reasoning | |||
| bridge = SimpleBridge(model, reasoner, metric_list) | |||
| # Train and Test | |||
| bridge.train(train_data, loops=5, segment_size=10000) | |||
| bridge.test(test_data) | |||
| Training log would be similar to this: | |||
| .. code:: text | |||
| 2023/11/29 23:14:17 - abl - INFO - Abductive Learning on the MNIST Add example. | |||
| 2023/11/29 23:14:42 - abl - INFO - loop(train) [1/5] segment(train) [1/3] model loss is 1.86793 | |||
| 2023/11/29 23:14:44 - abl - INFO - loop(train) [1/5] segment(train) [2/3] model loss is 1.48877 | |||
| 2023/11/29 23:14:46 - abl - INFO - loop(train) [1/5] segment(train) [3/3] model loss is 1.26435 | |||
| 2023/11/29 23:14:46 - abl - INFO - Evaluation start: loop(val) [1] | |||
| 2023/11/29 23:14:47 - abl - INFO - Evaluation ended, mnist_add/character_accuracy: 0.334 mnist_add/semantics_accuracy: 0.190 | |||
| 2023/11/29 23:14:49 - abl - INFO - loop(train) [2/5] segment(train) [1/3] model loss is 1.06395 | |||
| 2023/11/29 23:14:51 - abl - INFO - loop(train) [2/5] segment(train) [2/3] model loss is 0.78799 | |||
| 2023/11/29 23:14:53 - abl - INFO - loop(train) [2/5] segment(train) [3/3] model loss is 0.33641 | |||
| 2023/11/29 23:14:53 - abl - INFO - Evaluation start: loop(val) [2] | |||
| 2023/11/29 23:14:54 - abl - INFO - Evaluation ended, mnist_add/character_accuracy: 0.963 mnist_add/semantics_accuracy: 0.926 | |||
| ... | |||
| 2023/11/29 23:15:08 - abl - INFO - loop(train) [5/5] segment(train) [1/3] model loss is 0.04223 | |||
| 2023/11/29 23:15:10 - abl - INFO - loop(train) [5/5] segment(train) [2/3] model loss is 0.03444 | |||
| 2023/11/29 23:15:12 - abl - INFO - loop(train) [5/5] segment(train) [3/3] model loss is 0.03274 | |||
| 2023/11/29 23:15:12 - abl - INFO - Evaluation start: loop(val) [5] | |||
| 2023/11/29 23:15:13 - abl - INFO - Evaluation ended, mnist_add/character_accuracy: 0.991 mnist_add/semantics_accuracy: 0.983 | |||
| 2023/11/29 23:15:13 - abl - INFO - Evaluation ended, mnist_add/character_accuracy: 0.985 mnist_add/semantics_accuracy: 0.970 | |||
| @@ -5,8 +5,8 @@ | |||
| :caption: Overview | |||
| Overview/Abductive Learning | |||
| Overview/Quick Start | |||
| Overview/Installation | |||
| Overview/Quick Start | |||
| .. toctree:: | |||
| :maxdepth: 1 | |||
| @@ -1,6 +1,11 @@ | |||
| import os | |||
| import torchvision | |||
| from torchvision.transforms import transforms | |||
| CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) | |||
| def get_data(file, img_dataset, get_pseudo_label): | |||
| X = [] | |||
| if get_pseudo_label: | |||
| @@ -8,32 +13,36 @@ def get_data(file, img_dataset, get_pseudo_label): | |||
| Y = [] | |||
| with open(file) as f: | |||
| for line in f: | |||
| line = line.strip().split(' ') | |||
| line = line.strip().split(" ") | |||
| X.append([img_dataset[int(line[0])][0], img_dataset[int(line[1])][0]]) | |||
| if get_pseudo_label: | |||
| Z.append([img_dataset[int(line[0])][1], img_dataset[int(line[1])][1]]) | |||
| Y.append(int(line[2])) | |||
| if get_pseudo_label: | |||
| return X, Z, Y | |||
| else: | |||
| return X, None, Y | |||
| def get_mnist_add(train = True, get_pseudo_label = False): | |||
| transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081, ))]) | |||
| img_dataset = torchvision.datasets.MNIST(root='./datasets/', train=train, download=True, transform=transform) | |||
| def get_mnist_add(train=True, get_pseudo_label=False): | |||
| transform = transforms.Compose( | |||
| [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))] | |||
| ) | |||
| img_dataset = torchvision.datasets.MNIST( | |||
| root=CURRENT_DIR, train=train, download=True, transform=transform | |||
| ) | |||
| if train: | |||
| file = './datasets/train_data.txt' | |||
| file = os.path.join(CURRENT_DIR, "train_data.txt") | |||
| else: | |||
| file = './datasets/test_data.txt' | |||
| file = os.path.join(CURRENT_DIR, "test_data.txt") | |||
| return get_data(file, img_dataset, get_pseudo_label) | |||
| if __name__ == "__main__": | |||
| train_X, train_Y = get_mnist_add(train = True) | |||
| test_X, test_Y = get_mnist_add(train = False) | |||
| train_X, train_Y = get_mnist_add(train=True) | |||
| test_X, test_Y = get_mnist_add(train=False) | |||
| print(len(train_X), len(test_X)) | |||
| print(train_X[0][0].shape, train_X[0][1].shape, train_Y[0]) | |||
| @@ -8,18 +8,16 @@ | |||
| "source": [ | |||
| "import os.path as osp\n", | |||
| "\n", | |||
| "import torch.nn as nn\n", | |||
| "import torch\n", | |||
| "import torch.nn as nn\n", | |||
| "\n", | |||
| "from abl.reasoning import ReasonerBase, KBBase\n", | |||
| "\n", | |||
| "from abl.learning import BasicNN, ABLModel\n", | |||
| "from abl.bridge import SimpleBridge\n", | |||
| "from abl.evaluation import SymbolMetric, SemanticsMetric\n", | |||
| "from abl.evaluation import SemanticsMetric, SymbolMetric\n", | |||
| "from abl.learning import ABLModel, BasicNN\n", | |||
| "from abl.reasoning import KBBase, ReasonerBase\n", | |||
| "from abl.utils import ABLLogger, print_log\n", | |||
| "\n", | |||
| "from examples.models.nn import LeNet5\n", | |||
| "from examples.mnist_add.datasets.get_mnist_add import get_mnist_add" | |||
| "from examples.mnist_add.datasets.get_mnist_add import get_mnist_add\n", | |||
| "from examples.models.nn import LeNet5" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -28,7 +26,7 @@ | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Initialize logger\n", | |||
| "# Build logger\n", | |||
| "print_log(\"Abductive Learning on the MNIST Add example.\", logger=\"current\")\n", | |||
| "\n", | |||
| "# Retrieve the directory of the Log file and define the directory for saving the model weights.\n", | |||
| @@ -36,31 +34,6 @@ | |||
| "weights_dir = osp.join(log_dir, \"weights\")" | |||
| ] | |||
| }, | |||
| { | |||
| "attachments": {}, | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "### Logic Part" | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": null, | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Initialize knowledge base and reasoner\n", | |||
| "class add_KB(KBBase):\n", | |||
| " def logic_forward(self, nums):\n", | |||
| " return sum(nums)\n", | |||
| "\n", | |||
| "kb = add_KB(pseudo_label_list=list(range(10)))\n", | |||
| "\n", | |||
| "# kb = prolog_KB(pseudo_label_list=list(range(10)), pl_file='datasets/mnist_add/add.pl')\n", | |||
| "reasoner = ReasonerBase(kb, dist_func=\"confidence\")" | |||
| ] | |||
| }, | |||
| { | |||
| "attachments": {}, | |||
| "cell_type": "markdown", | |||
| @@ -75,11 +48,11 @@ | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Initialize necessary component for machine learning part\n", | |||
| "cls = LeNet5(num_classes=len(kb.pseudo_label_list))\n", | |||
| "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n", | |||
| "# Build necessary components for BasicNN\n", | |||
| "cls = LeNet5(num_classes=10)\n", | |||
| "criterion = nn.CrossEntropyLoss()\n", | |||
| "optimizer = torch.optim.Adam(cls.parameters(), lr=0.001, betas=(0.9, 0.99))" | |||
| "optimizer = torch.optim.Adam(cls.parameters(), lr=0.001, betas=(0.9, 0.99))\n", | |||
| "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -88,7 +61,7 @@ | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Initialize BasicNN\n", | |||
| "# Build BasicNN\n", | |||
| "# The function of BasicNN is to wrap NN models into the form of an sklearn estimator\n", | |||
| "base_model = BasicNN(\n", | |||
| " cls,\n", | |||
| @@ -100,32 +73,23 @@ | |||
| ")" | |||
| ] | |||
| }, | |||
| { | |||
| "attachments": {}, | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "### Use ABL model to join two parts" | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": null, | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Initialize ABL model\n", | |||
| "# # Build ABLModel\n", | |||
| "# The main function of the ABL model is to serialize data and \n", | |||
| "# provide a unified interface for different machine learning models\n", | |||
| "model = ABLModel(base_model)" | |||
| ] | |||
| }, | |||
| { | |||
| "attachments": {}, | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "### Metric" | |||
| "### Logic Part" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -134,8 +98,18 @@ | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Add metric\n", | |||
| "metric = [SymbolMetric(prefix=\"mnist_add\"), SemanticsMetric(kb=kb, prefix=\"mnist_add\")]" | |||
| "# Build knowledge base and reasoner\n", | |||
| "class AddKB(KBBase):\n", | |||
| " def __init__(self, pseudo_label_list):\n", | |||
| " super().__init__(pseudo_label_list)\n", | |||
| "\n", | |||
| " # Implement the deduction function\n", | |||
| " def logic_forward(self, nums):\n", | |||
| " return sum(nums)\n", | |||
| "\n", | |||
| "\n", | |||
| "kb = AddKB(pseudo_label_list=list(range(10)))\n", | |||
| "reasoner = ReasonerBase(kb, dist_func=\"confidence\")" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -143,7 +117,7 @@ | |||
| "cell_type": "markdown", | |||
| "metadata": {}, | |||
| "source": [ | |||
| "### Dataset" | |||
| "### Datasets and Evaluation Metrics" | |||
| ] | |||
| }, | |||
| { | |||
| @@ -157,6 +131,16 @@ | |||
| "test_data = get_mnist_add(train=False, get_pseudo_label=True)" | |||
| ] | |||
| }, | |||
| { | |||
| "cell_type": "code", | |||
| "execution_count": null, | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "# Set up metrics\n", | |||
| "metric_list = [SymbolMetric(prefix=\"mnist_add\"), SemanticsMetric(kb=kb, prefix=\"mnist_add\")]" | |||
| ] | |||
| }, | |||
| { | |||
| "attachments": {}, | |||
| "cell_type": "markdown", | |||
| @@ -171,7 +155,7 @@ | |||
| "metadata": {}, | |||
| "outputs": [], | |||
| "source": [ | |||
| "bridge = SimpleBridge(model, reasoner, metric)" | |||
| "bridge = SimpleBridge(model, reasoner, metric_list)" | |||
| ] | |||
| }, | |||
| { | |||