Browse Source

[ENH] add zoo example, resolve comments

pull/1/head
troyyyyy 2 years ago
parent
commit
21693f42a4
12 changed files with 306 additions and 444 deletions
  1. +39
    -0
      examples/hed/README.md
  2. +0
    -173
      examples/hed/datasets/equation_generator.py
  3. +1
    -2
      examples/hed/hed.ipynb
  4. +101
    -0
      examples/hed/main.py
  5. +3
    -3
      examples/hwf/README.md
  6. +1
    -1
      examples/hwf/main.py
  7. +1
    -1
      examples/mnist_add/README.md
  8. +1
    -1
      examples/mnist_add/mnist_add.ipynb
  9. +23
    -0
      examples/zoo/README.md
  10. +2
    -2
      examples/zoo/kb.py
  11. +57
    -197
      examples/zoo/main.py
  12. +77
    -64
      examples/zoo/zoo.ipynb

+ 39
- 0
examples/hed/README.md View File

@@ -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)

```

+ 0
- 173
examples/hed/datasets/equation_generator.py View File

@@ -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)

+ 1
- 2
examples/hed/hed.ipynb View File

@@ -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)"
]


+ 101
- 0
examples/hed/main.py View File

@@ -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()

+ 3
- 3
examples/hwf/README.md View File

@@ -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


+ 1
- 1
examples/hwf/main.py View File

@@ -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
- 1
examples/mnist_add/README.md View File

@@ -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.



+ 1
- 1
examples/mnist_add/mnist_add.ipynb View File

@@ -450,7 +450,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.18"
"version": "3.8.13"
},
"orig_nbformat": 4,
"vscode": {


+ 23
- 0
examples/zoo/README.md View File

@@ -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)

```

+ 2
- 2
examples/zoo/kb.py View File

@@ -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"]



+ 57
- 197
examples/zoo/main.py View File

@@ -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()

+ 77
- 64
examples/zoo/zoo.ipynb View File

@@ -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,16 +94,12 @@
"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."
]
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
@@ -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()"
]
@@ -166,7 +143,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
@@ -184,12 +161,12 @@
"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,
"execution_count": 8,
"metadata": {},
"outputs": [
{
@@ -209,36 +186,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": 15,
"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 +237,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
@@ -281,7 +268,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
@@ -300,7 +287,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
@@ -316,28 +303,54 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 22,
"metadata": {},
"outputs": [],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"12/22 11:01:21 - abl - INFO - Abductive Learning on the ZOO example.\n",
"12/22 11:01:21 - abl - INFO - ------- Use labeled data to pretrain the model -----------\n",
"12/22 11:01:21 - abl - INFO - ------- Test the initial model -----------\n",
"12/22 11:01:21 - abl - INFO - Evaluation ended, zoo/character_accuracy: 0.935 zoo/reasoning_accuracy: 0.935 \n",
"12/22 11:01:21 - abl - INFO - ------- Use ABL to train the model -----------\n",
"12/22 11:01:21 - abl - INFO - loop(train) [1/3] segment(train) [1/1] \n",
"12/22 11:01:23 - abl - INFO - Evaluation start: loop(val) [1]\n",
"12/22 11:01:23 - abl - INFO - Evaluation ended, zoo/character_accuracy: 0.984 zoo/reasoning_accuracy: 1.000 \n",
"12/22 11:01:23 - abl - INFO - loop(train) [2/3] segment(train) [1/1] \n",
"12/22 11:01:24 - abl - INFO - Evaluation start: loop(val) [2]\n",
"12/22 11:01:25 - abl - INFO - Evaluation ended, zoo/character_accuracy: 0.984 zoo/reasoning_accuracy: 1.000 \n",
"12/22 11:01:25 - abl - INFO - loop(train) [3/3] segment(train) [1/1] \n",
"12/22 11:01:26 - abl - INFO - Evaluation start: loop(val) [3]\n",
"12/22 11:01:26 - abl - INFO - Evaluation ended, zoo/character_accuracy: 0.984 zoo/reasoning_accuracy: 1.000 \n",
"12/22 11:01:26 - abl - INFO - ------- Test the final model -----------\n",
"12/22 11:01:27 - abl - INFO - Evaluation ended, zoo/character_accuracy: 0.903 zoo/reasoning_accuracy: 0.935 \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 +369,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.18"
"version": "3.8.13"
},
"orig_nbformat": 4,
"vscode": {


Loading…
Cancel
Save