diff --git a/abducer/abducer_base.py b/abducer/abducer_base.py index bbf00fa..b26c88a 100644 --- a/abducer/abducer_base.py +++ b/abducer/abducer_base.py @@ -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) diff --git a/abducer/kb.py b/abducer/kb.py index 98ea9bc..8c3f49a 100644 --- a/abducer/kb.py +++ b/abducer/kb.py @@ -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: diff --git a/models/basic_model.py b/models/basic_model.py index 669474f..00efd30 100644 --- a/models/basic_model.py +++ b/models/basic_model.py @@ -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) diff --git a/README b/readme.md similarity index 66% rename from README rename to readme.md index 0aa2a11..1a9ebf1 100644 --- a/README +++ b/readme.md @@ -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 +