From ab633210c1586579c8363205dda36ef401792b42 Mon Sep 17 00:00:00 2001 From: Gao Enhao Date: Wed, 29 Nov 2023 23:20:37 +0800 Subject: [PATCH] [DOC] complete 'Qucik Start' and 'Installation' --- docs/Brief-Introduction/Usage.rst | 86 +++++++++--------- docs/Overview/Installation.rst | 16 +++- docs/Overview/Quick Start.rst | 80 ++++++++++++++++- docs/index.rst | 2 +- examples/mnist_add/datasets/get_mnist_add.py | 35 +++++--- examples/mnist_add/mnist_add_example.ipynb | 92 ++++++++------------ 6 files changed, 198 insertions(+), 113 deletions(-) diff --git a/docs/Brief-Introduction/Usage.rst b/docs/Brief-Introduction/Usage.rst index 1d29a4a..5067980 100644 --- a/docs/Brief-Introduction/Usage.rst +++ b/docs/Brief-Introduction/Usage.rst @@ -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 diff --git a/docs/Overview/Installation.rst b/docs/Overview/Installation.rst index 2112f28..36d7c95 100644 --- a/docs/Overview/Installation.rst +++ b/docs/Overview/Installation.rst @@ -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 diff --git a/docs/Overview/Quick Start.rst b/docs/Overview/Quick Start.rst index 7ef22bb..726b523 100644 --- a/docs/Overview/Quick Start.rst +++ b/docs/Overview/Quick Start.rst @@ -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 diff --git a/docs/index.rst b/docs/index.rst index 207afd6..57179ba 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,8 +5,8 @@ :caption: Overview Overview/Abductive Learning - Overview/Quick Start Overview/Installation + Overview/Quick Start .. toctree:: :maxdepth: 1 diff --git a/examples/mnist_add/datasets/get_mnist_add.py b/examples/mnist_add/datasets/get_mnist_add.py index 46b5f12..4bbb834 100644 --- a/examples/mnist_add/datasets/get_mnist_add.py +++ b/examples/mnist_add/datasets/get_mnist_add.py @@ -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]) - diff --git a/examples/mnist_add/mnist_add_example.ipynb b/examples/mnist_add/mnist_add_example.ipynb index 845424b..409eb97 100644 --- a/examples/mnist_add/mnist_add_example.ipynb +++ b/examples/mnist_add/mnist_add_example.ipynb @@ -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)" ] }, {