Browse Source

Merge branch 'Dev' of https://github.com/AbductiveLearning/ABL-Package into Dev

pull/3/head
Gao Enhao 3 years ago
parent
commit
f90978ee71
4 changed files with 120 additions and 82 deletions
  1. +40
    -39
      abducer/abducer_base.py
  2. +61
    -31
      abducer/kb.py
  3. +10
    -12
      models/basic_model.py
  4. +9
    -0
      readme.md

+ 40
- 39
abducer/abducer_base.py View File

@@ -11,16 +11,12 @@
#================================================================#

import abc
# from kb import add_KB, hwf_KB
from abducer.kb import add_KB, hwf_KB
from kb import add_KB, hwf_KB
# from abducer.kb import add_KB, hwf_KB
import numpy as np

from itertools import product, combinations





class AbducerBase(abc.ABC):
def __init__(self, kb, dist_func = 'confidence', cache = True):
self.kb = kb
@@ -51,42 +47,50 @@ class AbducerBase(abc.ABC):
def get_cost_list(self, pred_res, pred_res_prob, candidates):
if(self.dist_func == 'hamming'):
if self.dist_func == 'hamming':
return self.hamming_dist(pred_res, candidates)
elif(self.dist_func == 'confidence'):
elif self.dist_func == 'confidence':
return self.confidence_dist(pred_res_prob, candidates)

def get_min_cost_candidate(self, pred_res, pred_res_prob, candidates):
cost_list = self.get_cost_list(pred_res, pred_res_prob, candidates)
min_address_num = np.min(cost_list)
idxs = np.where(cost_list == min_address_num)[0]
return [candidates[idx] for idx in idxs][0]
if len(candidates) == 0:
return []
elif len(candidates) == 1:
return candidates[0]
else:
cost_list = self.get_cost_list(pred_res, pred_res_prob, candidates)
min_address_num = np.min(cost_list)
idxs = np.where(cost_list == min_address_num)[0]
return [candidates[idx] for idx in idxs][0]

def abduce(self, data, max_address_num = -1, require_more_address = 0):
pred_res, pred_res_prob, ans = data
if max_address_num == -1:
max_address_num = len(pred_res)

if(self.cache and (tuple(pred_res), ans) in self.cache_min_address_num):
if self.cache and (tuple(pred_res), ans) in self.cache_min_address_num:
address_num = min(max_address_num, self.cache_min_address_num[(tuple(pred_res), ans)] + require_more_address)
if((tuple(pred_res), ans, address_num) in self.cache_candidates):
if (tuple(pred_res), ans, address_num) in self.cache_candidates:
# print('cached')
candidates = self.cache_candidates[(tuple(pred_res), ans, address_num)]
candidate = self.get_min_cost_candidate(pred_res, pred_res_prob, candidates)
return candidate
if(self.kb.base != {}):
if self.kb.GKB_flag:
all_candidates = self.kb.get_candidates(ans, len(pred_res))
cost_list = self.hamming_dist(pred_res, all_candidates)
min_address_num = np.min(cost_list)
address_num = min(max_address_num, min_address_num + require_more_address)
idxs = np.where(cost_list <= address_num)[0]
candidates = [all_candidates[idx] for idx in idxs]
if len(all_candidates) == 0:
return []
else:
cost_list = self.hamming_dist(pred_res, all_candidates)
min_address_num = np.min(cost_list)
address_num = min(max_address_num, min_address_num + require_more_address)
idxs = np.where(cost_list <= address_num)[0]
candidates = [all_candidates[idx] for idx in idxs]
else:
candidates, min_address_num, address_num = self.get_abduce_candidates(pred_res, ans, max_address_num, require_more_address)
if(self.cache):
if self.cache:
self.cache_min_address_num[(tuple(pred_res), ans)] = min_address_num
self.cache_candidates[(tuple(pred_res), ans, address_num)] = candidates

@@ -100,9 +104,9 @@ class AbducerBase(abc.ABC):
for address_idx in address_idx_list:
for c in all_address_candidate:
pred_res_array = np.array(pred_res)
if(np.count_nonzero(np.array(c) != pred_res_array[np.array(address_idx)]) == address_num):
if np.count_nonzero(np.array(c) != pred_res_array[np.array(address_idx)]) == address_num:
pred_res_array[np.array(address_idx)] = c
if(self.kb.logic_forward(pred_res_array) == key):
if self.kb.logic_forward(pred_res_array) == key:
new_candidates.append(pred_res_array)
return new_candidates, address_num
@@ -110,23 +114,22 @@ class AbducerBase(abc.ABC):
candidates = []
for address_num in range(len(pred_res) + 1):
if(address_num > max_address_num):
print('No candidates found')
return None, None, None
if address_num > max_address_num:
return [], None, None
if(address_num == 0):
if(abs(self.kb.logic_forward(pred_res) - key) <= 1e-3):
if address_num == 0:
if abs(self.kb.logic_forward(pred_res) - key) <= 1e-3:
candidates.append(pred_res)
else:
new_candidates, address_num = self.address(address_num, pred_res, key)
candidates += new_candidates
if(len(candidates) > 0):
if len(candidates) > 0:
min_address_num = address_num
break
for address_num in range(min_address_num + 1, min_address_num + require_more_address + 1):
if(address_num > max_address_num):
if address_num > max_address_num:
return candidates, min_address_num, address_num - 1
new_candidates, address_num = self.address(address_num, pred_res, key)
candidates += new_candidates
@@ -146,24 +149,22 @@ class AbducerBase(abc.ABC):


if __name__ == '__main__':
kb = add_KB()
kb = add_KB(GKB_flag = True)
abd = AbducerBase(kb, 'hamming')
res = abd.abduce(([1, 1, 1], None, 4), max_address_num = 2, require_more_address = 0)
print(res)
res = abd.abduce(([1, 1, 1], None, 4), max_address_num = 2, require_more_address = 1)
res = abd.abduce(([1, 1], None, 4), max_address_num = 2, require_more_address = 0)
print(res)
res = abd.abduce(([1, 1, 1], None, 4), max_address_num = 1, require_more_address = 1)
res = abd.abduce(([1, 1], None, 4), max_address_num = 2, require_more_address = 1)
print(res)
res = abd.abduce(([1, 1, 1], None, 4), max_address_num = 2, require_more_address = 0)
print(res)
res = abd.abduce(([1, 1, 1], None, 5), max_address_num = 2, require_more_address = 1)
res = abd.abduce(([1, 1], None, 5), max_address_num = 2, require_more_address = 1)
print(res)
print()
kb = hwf_KB()
abd = AbducerBase(kb)
abd = AbducerBase(kb, 'hamming')
res = abd.abduce((['5', '+', '2'], None, 3), max_address_num = 2, require_more_address = 0)
print(res)
res = abd.abduce((['5', '+', '2'], None, 3.09), max_address_num = 2, require_more_address = 0)
print(res)
res = abd.abduce((['5', '+', '2'], None, 1.67), max_address_num = 3, require_more_address = 0)
print(res)
res = abd.abduce((['5', '+', '3'], None, 0.33), max_address_num = 3, require_more_address = 3)


+ 61
- 31
abducer/kb.py View File

@@ -19,7 +19,7 @@ from collections import defaultdict
from itertools import product

class KBBase(ABC):
def __init__(self, GKB_flag = False):
def __init__(self):
pass

@abstractmethod
@@ -31,7 +31,11 @@ class KBBase(ABC):
pass

@abstractmethod
def logic_forward(self, X):
def logic_forward(self):
pass
@abstractmethod
def valid_candidate(self):
pass
def _length(self, length):
@@ -48,30 +52,37 @@ class KBBase(ABC):
class ClsKB(KBBase):
def __init__(self, GKB_flag = False, pseudo_label_list = None, len_list = None):
super().__init__()
self.GKB_flag = GKB_flag
self.pseudo_label_list = pseudo_label_list
self.base = {}
self.len_list = len_list
if GKB_flag:
X = self.get_X(self.pseudo_label_list, self.len_list)
Y = self.get_Y(X, self.logic_forward)
self.base = {}
X, Y = self.get_GKB(self.pseudo_label_list, self.len_list)
for x, y in zip(X, Y):
self.base.setdefault(len(x), defaultdict(list))[y].append(np.array(x))
self.base.setdefault(len(x), defaultdict(list))[y].append(x)
def get_X(self, pseudo_label_list, len_list):
res = []
def get_GKB(self, pseudo_label_list, len_list):
all_X = []
for len in len_list:
res += list(product(pseudo_label_list, repeat = len))
return res

def get_Y(self, X, logic_forward):
return [logic_forward(nums) for nums in X]
all_X += list(product(pseudo_label_list, repeat = len))
X = []
Y = []
for x in all_X:
if self.valid_candidate(x):
X.append(x)
Y.append(self.logic_forward(x))
return X, Y
def valid_candidate(self):
pass
def logic_forward(self):
return None
pass

def get_candidates(self, key, length = None):
if(self.base == {}):
if not self.GKB_flag or self.base == {}:
return []
if key is None:
@@ -83,44 +94,59 @@ class ClsKB(KBBase):
return sum([self.base[l][key] for l in length], [])
def get_all_candidates(self):
return sum([sum(v.values(), []) for v in self.base.values()], [])
if not self.GKB_flag or self.base == {}:
return []
else:
return sum([sum(v.values(), []) for v in self.base.values()], [])

def _dict_len(self, dic):
return sum(len(c) for c in dic.values())
if not self.GKB_flag:
return 0
else:
return sum(len(c) for c in dic.values())

def __len__(self):
return sum(self._dict_len(v) for v in self.base.values())

if not self.GKB_flag:
return 0
else:
return sum(self._dict_len(v) for v in self.base.values())


class add_KB(ClsKB):
def __init__(self, GKB_flag = False, len_list = [2]):
self.pseudo_label_list = list(range(10))
super().__init__(GKB_flag, self.pseudo_label_list, len_list)
def __init__(self, GKB_flag = False, \
pseudo_label_list = list(range(10)), \
len_list = [2]):
super().__init__(GKB_flag, pseudo_label_list, len_list)
def valid_candidate(self, x):
return True
def logic_forward(self, nums):
return sum(nums)
class hwf_KB(ClsKB):
def __init__(self, GKB_flag = False, len_list = [1, 3, 5, 7]):
self.pseudo_label_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', '*', '/']
super().__init__(GKB_flag, self.pseudo_label_list, len_list)
def __init__(self, GKB_flag = False, \
pseudo_label_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', 'times', 'div'], \
len_list = [1, 3, 5, 7]):
super().__init__(GKB_flag, pseudo_label_list, len_list)
def valid_formula(self, formula):
if(len(formula) % 2 == 0):
def valid_candidate(self, formula):
if len(formula) % 2 == 0:
return False
for i in range(len(formula)):
if(i % 2 == 0 and formula[i] not in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']):
if i % 2 == 0 and formula[i] not in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
return False
if(i % 2 != 0 and formula[i] not in ['+', '-', '*', '/']):
if i % 2 != 0 and formula[i] not in ['+', '-', 'times', 'div']:
return False
return True
def logic_forward(self, formula):
if(self.valid_formula(formula) == False):
if not self.valid_candidate(formula):
return np.inf
try:
mapping = {'0':'0', '1':'1', '2':'2', '3':'3', '4':'4', '5':'5', '6':'6', '7':'7', '8':'8', '9':'9', '+':'+', '-':'-', 'times':'*', 'div':'/'}
formula = [mapping[f] for f in formula]
return round(eval(''.join(formula)), 2)
except ZeroDivisionError:
return np.inf
@@ -140,8 +166,12 @@ class RegKB(KBBase):
Y = [y for y, x in data]
self.base[l] = (X, Y)

def valid_candidate(self):
pass
def logic_forward(self):
return None
pass
def get_candidates(self, key, length = None):
if key is None:


+ 10
- 12
models/basic_model.py View File

@@ -125,21 +125,19 @@ class BasicModel():
model.train()

loss_value = 0
for _, data in enumerate(data_loader):
X = data[0].to(device)
Y = data[1].to(device)
pred_Y = model(X)

loss = criterion(pred_Y, Y)
total_loss, total_num = 0.0, 0
for data, target in data_loader:
data, target = data.to(device), target.to(device)
out = model(data)
loss = criterion(out, target)

optimizer.zero_grad()
loss.backward()
optimizer.step()

loss_value += loss.item()
total_loss += loss.item() * data.size(0)

return loss_value
return total_loss / total_num

def _predict(self, data_loader):
model = self.model
@@ -149,9 +147,9 @@ class BasicModel():

with torch.no_grad():
results = []
for _, data in enumerate(data_loader):
X = data[0].to(device)
pred_Y = model(X)
for data, _ in data_loader:
data = data.to(device)
pred_Y = model(data)
results.append(pred_Y)
return torch.cat(results, axis=0)


README → readme.md View File

@@ -22,3 +22,12 @@ python share_example.py
## NOTICE
They can only be used for academic purpose. For other purposes, please contact with LAMDA Group(www.lamda.nju.edu.cn).

## To do list

- Improve speed and accuracy
- Add comparison with DeepProbLog, NGS,... (Accuracy and Speed)
- Add Inference/Abduction example with FOL engine (e.g., Prolog)
- Add zoopt optimization
- Rearrange structure and make it a python package
- Documents


Loading…
Cancel
Save