Browse Source

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

pull/3/head
Gao Enhao 3 years ago
parent
commit
62082cee4d
4 changed files with 81 additions and 126 deletions
  1. +35
    -77
      abl/abducer/abducer_base.py
  2. +25
    -41
      abl/abducer/kb.py
  3. +2
    -2
      abl/framework_hed.py
  4. +19
    -6
      abl/utils/utils.py

+ 35
- 77
abl/abducer/abducer_base.py View File

@@ -16,17 +16,14 @@ from zoopt import Dimension, Objective, Parameter, Opt
from ..utils.utils import confidence_dist, flatten, reform_idx, hamming_dist

class AbducerBase(abc.ABC):
def __init__(self, kb, dist_func='confidence', zoopt=False, multiple_predictions=False, cache=True):
def __init__(self, kb, dist_func='hamming', zoopt=False, multiple_predictions=False):
self.kb = kb
assert dist_func == 'hamming' or dist_func == 'confidence'
self.dist_func = dist_func
self.zoopt = zoopt
self.multiple_predictions = multiple_predictions
self.cache = cache

if self.cache:
self.cache_min_address_num = {}
self.cache_candidates = {}
if dist_func == 'confidence':
self.mapping = dict(zip(self.kb.pseudo_label_list, list(range(len(self.kb.pseudo_label_list)))))

def _get_cost_list(self, pred_res, pred_res_prob, candidates):
if self.dist_func == 'hamming':
@@ -40,9 +37,7 @@ class AbducerBase(abc.ABC):
if self.multiple_predictions:
pred_res_prob = flatten(pred_res_prob)
candidates = [flatten(c) for c in candidates]
mapping = dict(zip(self.kb.pseudo_label_list, list(range(len(self.kb.pseudo_label_list)))))
candidates = [list(map(lambda x: mapping[x], c)) for c in candidates]
candidates = [list(map(lambda x: self.mapping[x], c)) for c in candidates]
return confidence_dist(pred_res_prob, candidates)

def _get_one_candidate(self, pred_res, pred_res_prob, candidates):
@@ -53,69 +48,43 @@ class AbducerBase(abc.ABC):
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]
candidate = [candidates[idx] for idx in idxs][0]
candidate = candidates[np.argmin(cost_list)]
return candidate

# for zoopt
def _zoopt_score_multiple(self, pred_res, key, solution):
all_address_flag = reform_idx(solution, pred_res)
score = 0
for idx in range(len(pred_res)):
address_idx = [i for i, flag in enumerate(all_address_flag[idx]) if flag != 0]
candidate = self.address_by_idx([pred_res[idx]], key[idx], address_idx)
if len(candidate) > 0:
score += 1
return score

def _zoopt_address_score(self, pred_res, key, sol):
def _zoopt_address_score_single(self, sol_x, pred_res, pred_res_prob, key):
address_idx = np.where(sol_x != 0)[0]
candidates = self.address_by_idx(pred_res, key, address_idx)
if len(candidates) > 0:
return np.min(self._get_cost_list(pred_res, pred_res_prob, candidates))
else:
return len(pred_res)
def _zoopt_address_score(self, pred_res, pred_res_prob, key, sol):
if not self.multiple_predictions:
address_idx = [idx for idx, i in enumerate(sol.get_x()) if i != 0]
candidates = self.address_by_idx(pred_res, key, address_idx)
return 1 if len(candidates) > 0 else 0
return self._zoopt_address_score_single(sol.get_x(), pred_res, pred_res_prob, key)
else:
return self._zoopt_score_multiple(pred_res, key, sol.get_x())

all_address_flag = reform_idx(sol.get_x(), pred_res)
score = 0
for idx in range(len(pred_res)):
score += self._zoopt_address_score_single(all_address_flag[idx], pred_res[idx], pred_res_prob[idx], key)
return score
def _constrain_address_num(self, solution, max_address_num):
x = solution.get_x()
return max_address_num - x.sum()

def zoopt_get_solution(self, pred_res, key, max_address_num):
def zoopt_get_solution(self, pred_res, pred_res_prob, key, max_address_num):
length = len(flatten(pred_res))
dimension = Dimension(size=length, regs=[[0, 1]] * length, tys=[False] * length)
objective = Objective(
lambda sol: -self._zoopt_address_score(pred_res, key, sol),
lambda sol: self._zoopt_address_score(pred_res, pred_res_prob, key, sol),
dim=dimension,
constraint=lambda sol: self._constrain_address_num(sol, max_address_num),
)
parameter = Parameter(budget=100, intermediate_result=False, autoset=True)
solution = Opt.min(objective, parameter).get_x()

return solution

def _get_cache(self, data, max_address_num, require_more_address):
pred_res, pred_res_prob, key = data
if self.multiple_predictions:
pred_res = flatten(pred_res)
key = tuple(key)
if (tuple(pred_res), key) in self.cache_min_address_num:
address_num = min(max_address_num, self.cache_min_address_num[(tuple(pred_res), key)] + require_more_address)
if (tuple(pred_res), key, address_num) in self.cache_candidates:
candidates = self.cache_candidates[(tuple(pred_res), key, address_num)]
if self.zoopt:
return candidates[0]
else:
return self._get_one_candidate(pred_res, pred_res_prob, candidates)
return None

def _set_cache(self, pred_res, key, min_address_num, address_num, candidates):
if self.multiple_predictions:
pred_res = flatten(pred_res)
key = tuple(key)
self.cache_min_address_num[(tuple(pred_res), key)] = min_address_num
self.cache_candidates[(tuple(pred_res), key, address_num)] = candidates
def address_by_idx(self, pred_res, key, address_idx):
return self.kb.address_by_idx(pred_res, key, address_idx, self.multiple_predictions)

@@ -124,37 +93,26 @@ class AbducerBase(abc.ABC):
if max_address_num == -1:
max_address_num = len(flatten(pred_res))

if self.cache:
candidate = self._get_cache(data, max_address_num, require_more_address)
if candidate is not None:
return candidate

if self.zoopt:
solution = self.zoopt_get_solution(pred_res, key, max_address_num)
address_idx = [idx for idx, i in enumerate(solution) if i != 0]
solution = self.zoopt_get_solution(pred_res, pred_res_prob, key, max_address_num)
address_idx = np.where(solution != 0)[0]
candidates = self.address_by_idx(pred_res, key, address_idx)
address_num = int(solution.sum())
min_address_num = address_num
else:
candidates, min_address_num, address_num = self.kb.abduce_candidates(
candidates = self.kb.abduce_candidates(
pred_res, key, max_address_num, require_more_address, self.multiple_predictions
)

candidate = self._get_one_candidate(pred_res, pred_res_prob, candidates)

if self.cache:
self._set_cache(pred_res, key, min_address_num, address_num, candidates)

return candidate

def abduce_rules(self, pred_res):
return self.kb.abduce_rules(pred_res)

def batch_abduce(self, Z, Y, max_address_num=-1, require_more_address=0):
# if self.multiple_predictions:
return self.abduce((Z['cls'], Z['prob'], Y), max_address_num, require_more_address)
# else:
# return [self.abduce((z, prob, y), max_address_num, require_more_address) for z, prob, y in zip(Z['cls'], Z['prob'], Y)]
if self.multiple_predictions:
return self.abduce((Z['cls'], Z['prob'], Y), max_address_num, require_more_address)
else:
return [self.abduce((z, prob, y), max_address_num, require_more_address) for z, prob, y in zip(Z['cls'], Z['prob'], Y)]

def __call__(self, Z, Y, max_address_num=-1, require_more_address=0):
return self.batch_abduce(Z, Y, max_address_num, require_more_address)
@@ -273,11 +231,11 @@ if __name__ == '__main__':
print(kb.consist_rule([1, '+', 1, '=', 1, 0], rules), kb.consist_rule([1, '+', 1, '=', 1, 1], rules))
print()

res = abd.abduce((consist_exs, None, [None] * len(consist_exs)))
res = abd.abduce((consist_exs, [None] * len(consist_exs), [None] * len(consist_exs)))
print(res)
res = abd.abduce((inconsist_exs, None, [None] * len(inconsist_exs)))
res = abd.abduce((inconsist_exs, [None] * len(consist_exs), [None] * len(inconsist_exs)))
print(res)
print()

abduced_rules = abd.abduce_rules(consist_exs)
print(abduced_rules)
print(abduced_rules)

+ 25
- 41
abl/abducer/kb.py View File

@@ -17,10 +17,11 @@ import numpy as np

from collections import defaultdict
from itertools import product, combinations
from ..utils.utils import flatten, reform_idx, hamming_dist, check_equal
from ..utils.utils import flatten, reform_idx, hamming_dist, check_equal, to_hashable, hashable_to_list

from multiprocessing import Pool

from functools import lru_cache
import pyswip

class KBBase(ABC):
@@ -84,7 +85,7 @@ class KBBase(ABC):
if self.GKB_flag:
return self._abduce_by_GKB(pred_res, key, max_address_num, require_more_address, multiple_predictions)
else:
return self._abduce_by_search(pred_res, key, max_address_num, require_more_address, multiple_predictions)
return self._abduce_by_search(to_hashable(pred_res), to_hashable(key), max_address_num, require_more_address, multiple_predictions)
@abstractmethod
def _find_candidate_GKB(self, pred_res, key):
@@ -92,33 +93,32 @@ class KBBase(ABC):
def _abduce_by_GKB(self, pred_res, key, max_address_num, require_more_address, multiple_predictions):
if self.base == {}:
return [], 0, 0
return []

if not multiple_predictions:
if len(pred_res) not in self.len_list:
return [], 0, 0
return []
all_candidates = self._find_candidate_GKB(pred_res, key)
if len(all_candidates) == 0:
return [], 0, 0
return []
else:
cost_list = 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]
return candidates, min_address_num, address_num
return candidates
else:
min_address_num = 0
all_candidates_save = []
cost_list_save = []
for p_res, k in zip(pred_res, key):
if len(p_res) not in self.len_list:
return [], 0, 0
return []
all_candidates = self._find_candidate_GKB(p_res, k)
if len(all_candidates) == 0:
return [], 0, 0
return []
else:
all_candidates_save.append(all_candidates)
cost_list = hamming_dist(p_res, all_candidates)
@@ -126,18 +126,15 @@ class KBBase(ABC):
cost_list_save.append(cost_list)
multiple_all_candidates = [flatten(c) for c in product(*all_candidates_save)]
assert len(multiple_all_candidates[0]) == len(flatten(pred_res))
multiple_cost_list = np.array([sum(cost) for cost in product(*cost_list_save)])
assert len(multiple_all_candidates) == len(multiple_cost_list)
address_num = min(max_address_num, min_address_num + require_more_address)
idxs = np.where(multiple_cost_list <= address_num)[0]
candidates = [reform_idx(multiple_all_candidates[idx], pred_res) for idx in idxs]
return candidates, min_address_num, address_num
return candidates
def address_by_idx(self, pred_res, key, address_idx, multiple_predictions=False):
candidates = []
abduce_c = product(self.pseudo_label_list, repeat=len(address_idx))

if multiple_predictions:
save_pred_res = pred_res
pred_res = flatten(pred_res)
@@ -146,10 +143,8 @@ class KBBase(ABC):
candidate = pred_res.copy()
for i, idx in enumerate(address_idx):
candidate[idx] = c[i]

if multiple_predictions:
candidate = reform_idx(candidate, save_pred_res)

if check_equal(self._logic_forward(candidate, multiple_predictions), key, self.max_err):
candidates.append(candidate)
return candidates
@@ -166,9 +161,12 @@ class KBBase(ABC):
new_candidates += candidates
return new_candidates

@lru_cache(maxsize=100)
def _abduce_by_search(self, pred_res, key, max_address_num, require_more_address, multiple_predictions):
pred_res = hashable_to_list(pred_res)
key = hashable_to_list(key)
candidates = []

for address_num in range(len(flatten(pred_res)) + 1):
if address_num == 0:
if check_equal(self._logic_forward(pred_res, multiple_predictions), key, self.max_err):
@@ -176,21 +174,18 @@ class KBBase(ABC):
else:
new_candidates = self._address(address_num, pred_res, key, multiple_predictions)
candidates += new_candidates

if len(candidates) > 0:
min_address_num = address_num
break

if address_num >= max_address_num:
return [], 0, 0
return []

for address_num in range(min_address_num + 1, min_address_num + require_more_address + 1):
if address_num > max_address_num:
return candidates, min_address_num, address_num - 1
return candidates
new_candidates = self._address(address_num, pred_res, key, multiple_predictions)
candidates += new_candidates

return candidates, min_address_num, address_num
return candidates

def _dict_len(self, dic):
if not self.GKB_flag:
@@ -276,12 +271,16 @@ class prolog_KB(KBBase):
candidates.append(candidate)
return candidates


class HED_prolog_KB(prolog_KB):
def __init__(self, pseudo_label_list, pl_file):
super().__init__(pseudo_label_list, pl_file)
def consist_rule(self, exs, rules):
rules = str(rules).replace("\'","")
return len(list(self.prolog.query("eval_inst_feature(%s, %s)." % (exs, rules)))) != 0

def abduce_rules(self, pred_res):
# print(pred_res)
prolog_result = list(self.prolog.query("consistent_inst_feature(%s, X)." % pred_res))
if len(prolog_result) == 0:
return None
@@ -339,26 +338,11 @@ class HWF_KB(RegKB):
def logic_forward(self, formula):
if not self._valid_candidate(formula):
return np.inf
mapping = {
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'+': '+',
'-': '-',
'times': '*',
'div': '/',
}
mapping = {str(i): str(i) for i in range(1, 10)}
mapping.update({'+': '+', '-': '-', 'times': '*', 'div': '/'})
formula = [mapping[f] for f in formula]
return eval(''.join(formula))


import time

if __name__ == "__main__":
pass

+ 2
- 2
abl/framework_hed.py View File

@@ -150,7 +150,7 @@ def abduce_and_train(model, abducer, mapping, train_X_true, select_num):
for m in mappings:
pred_res = mapping_res(original_pred_res, m)
max_abduce_num = 20
solution = abducer.zoopt_get_solution(pred_res, [None] * len(pred_res), max_abduce_num)
solution = abducer.zoopt_get_solution(pred_res, [None] * len(pred_res), [None] * len(pred_res), max_abduce_num)
all_address_flag = reform_idx(solution, pred_res)

consistent_idx_tmp = []
@@ -291,7 +291,7 @@ def train_with_rule(model, abducer, train_data, val_data, select_num=10, min_len
INFO('consist_rule_acc is %f, %f\n' %(true_consist_rule_acc, false_consist_rule_acc))
# decide next course or restart
if true_consist_rule_acc > 0.9 and false_consist_rule_acc < 0.1:
if true_consist_rule_acc > 0.95 and false_consist_rule_acc < 0.1:
torch.save(model.cls_list[0].model.state_dict(), "./weights/weights_%d.pth" % equation_len)
break
else:


+ 19
- 6
abl/utils/utils.py View File

@@ -22,10 +22,10 @@ def reform_idx(flatten_pred_res, save_pred_res):


def hamming_dist(A, B):
B = np.array(B)
A = np.expand_dims(A, axis=0).repeat(axis=0, repeats=(len(B)))
return np.sum(A != B, axis=1)
A = np.array(A, dtype='<U')
B = np.array(B, dtype='<U')
A = np.expand_dims(A, axis = 0).repeat(axis=0, repeats=(len(B)))
return np.sum(A != B, axis = 1)

def confidence_dist(A, B):
B = np.array(B)
@@ -38,7 +38,6 @@ def confidence_dist(A, B):
cols = np.expand_dims(cols, axis=0).repeat(axis=0, repeats=len(B))
return 1 - np.prod(A[rows, cols, B], axis=1)


def block_sample(X, Z, Y, sample_num, epoch_idx):
part_num = len(X) // sample_num
if part_num == 0:
@@ -51,7 +50,6 @@ def block_sample(X, Z, Y, sample_num, epoch_idx):

return X, Z, Y


def gen_mappings(chars, symbs):
n_char = len(chars)
n_symbs = len(symbs)
@@ -112,3 +110,18 @@ def reduce_dimension(data):
for equation in equations
]
data[truth_value][equation_len] = reduced_equations

def to_hashable(l):
if type(l) is not list:
return l
if type(l[0]) is not list:
return tuple(l)
return tuple(tuple(sublist) for sublist in l)

def hashable_to_list(t):
if type(t) is not tuple:
return t
if type(t[0]) is not tuple:
return list(t)
return [list(subtuple) for subtuple in t]

Loading…
Cancel
Save