From 5290ebd804984c87481c2693d44552ef2eceaa62 Mon Sep 17 00:00:00 2001 From: troyyyyy <49091847+troyyyyy@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:58:56 +0800 Subject: [PATCH 01/12] Modify mapping in HWF_KB --- abl/abducer/kb.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/abl/abducer/kb.py b/abl/abducer/kb.py index 9009940..d656e41 100644 --- a/abl/abducer/kb.py +++ b/abl/abducer/kb.py @@ -339,26 +339,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 From 34eabfb61367a5bf64990de360634c151f74913f Mon Sep 17 00:00:00 2001 From: Tony-HYX <605698554@qq.com> Date: Tue, 7 Mar 2023 23:04:01 +0800 Subject: [PATCH 02/12] update todo in abducer --- abl/abducer/abducer_base.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/abl/abducer/abducer_base.py b/abl/abducer/abducer_base.py index cf14bb7..c5c21fd 100644 --- a/abl/abducer/abducer_base.py +++ b/abl/abducer/abducer_base.py @@ -40,7 +40,8 @@ class AbducerBase(abc.ABC): if self.multiple_predictions: pred_res_prob = flatten(pred_res_prob) candidates = [flatten(c) for c in candidates] - + + # TODO:这里应该在类创建时就提前存好,每次都重新计算也太费时了 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] return confidence_dist(pred_res_prob, candidates) @@ -53,15 +54,22 @@ class AbducerBase(abc.ABC): else: cost_list = self._get_cost_list(pred_res, pred_res_prob, candidates) + # TODO:这里很怪,按理argmin就行了 min_address_num = np.min(cost_list) idxs = np.where(cost_list == min_address_num)[0] + # TODO:这里也很怪,取第一个就行了吧 candidate = [candidates[idx] for idx in idxs][0] return candidate + # TODO:这里对zoopt的使用不太对。zoopt想要求解的是,修改哪几个符号的位置(表示为01串),能得到“最好”的反绎结果,理论上能比kb._address中的枚举法搜索次数更少。 + # TODO:而这里“最好”的定义,和不用zoopt搜索时的定义一致,目前要么'hamming'、要么'confidence' + # TODO: 因此,zoopt的作用,有点类似融合kb(得到若干反绎结果)和abducer(选一个反绎结果)的功能。 + # TODO:下面两个函数的score,应该是得到candidates后,调用_get_one_candidate计算?(没细看) # for zoopt def _zoopt_score_multiple(self, pred_res, key, solution): all_address_flag = reform_idx(solution, pred_res) score = 0 + # TODO:原版abl的score是这样计算的吗,我记得没有把若干预测结果拆开然后分别address的操作 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) @@ -94,6 +102,8 @@ class AbducerBase(abc.ABC): return solution + # TODO:cache移到kb里吧,比如_abduce_by_search里,它存的是若干反绎结果,不涉及从若干反绎结果中选一个 + # TODO:python也有自带的用装饰器实现的缓存方法,比如functools.lru_cache、cachetools等,后面稍微调研一下和手动缓存的优劣,看看用哪个好 def _get_cache(self, data, max_address_num, require_more_address): pred_res, pred_res_prob, key = data if self.multiple_predictions: From 19f29e40880ff76af8c445a443663f260cfee397 Mon Sep 17 00:00:00 2001 From: troyyyyy Date: Wed, 8 Mar 2023 17:38:41 +0800 Subject: [PATCH 03/12] Update abducer_base.py and related files --- abl/abducer/abducer_base.py | 108 ++++++++++-------------------------- abl/abducer/kb.py | 61 +++++++++++++++----- abl/framework_hed.py | 2 +- 3 files changed, 78 insertions(+), 93 deletions(-) diff --git a/abl/abducer/abducer_base.py b/abl/abducer/abducer_base.py index c5c21fd..a70db14 100644 --- a/abl/abducer/abducer_base.py +++ b/abl/abducer/abducer_base.py @@ -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,10 +37,7 @@ class AbducerBase(abc.ABC): if self.multiple_predictions: pred_res_prob = flatten(pred_res_prob) candidates = [flatten(c) for c in candidates] - - # TODO:这里应该在类创建时就提前存好,每次都重新计算也太费时了 - 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): @@ -54,46 +48,38 @@ class AbducerBase(abc.ABC): else: cost_list = self._get_cost_list(pred_res, pred_res_prob, candidates) - # TODO:这里很怪,按理argmin就行了 - min_address_num = np.min(cost_list) - idxs = np.where(cost_list == min_address_num)[0] - # TODO:这里也很怪,取第一个就行了吧 - candidate = [candidates[idx] for idx in idxs][0] + candidate = candidates[np.argmin(cost_list)] return candidate - # TODO:这里对zoopt的使用不太对。zoopt想要求解的是,修改哪几个符号的位置(表示为01串),能得到“最好”的反绎结果,理论上能比kb._address中的枚举法搜索次数更少。 - # TODO:而这里“最好”的定义,和不用zoopt搜索时的定义一致,目前要么'hamming'、要么'confidence' - # TODO: 因此,zoopt的作用,有点类似融合kb(得到若干反绎结果)和abducer(选一个反绎结果)的功能。 - # TODO:下面两个函数的score,应该是得到candidates后,调用_get_one_candidate计算?(没细看) - # for zoopt - def _zoopt_score_multiple(self, pred_res, key, solution): - all_address_flag = reform_idx(solution, pred_res) - score = 0 - # TODO:原版abl的score是这样计算的吗,我记得没有把若干预测结果拆开然后分别address的操作 - 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(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] + address_idx = np.where(sol.get_x() != 0)[0] candidates = self.address_by_idx(pred_res, key, address_idx) - return 1 if len(candidates) > 0 else 0 + if len(candidates) > 0: + return np.min(self._get_cost_list(pred_res, pred_res_prob, candidates)) + else: + return len(pred_res) 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)): + address_idx = np.where(all_address_flag[idx] != 0)[0] + candidates = self.address_by_idx([pred_res[idx]], key[idx], address_idx) + if len(candidates) > 0: + score += np.min(self._get_cost_list(pred_res[idx], pred_res_prob[idx], candidates)) + else: + score += len(pred_res) + return -self._zoopt_score_multiple(pred_res, key, sol.get_x()) + 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), ) @@ -101,31 +87,7 @@ class AbducerBase(abc.ABC): solution = Opt.min(objective, parameter).get_x() return solution - - # TODO:cache移到kb里吧,比如_abduce_by_search里,它存的是若干反绎结果,不涉及从若干反绎结果中选一个 - # TODO:python也有自带的用装饰器实现的缓存方法,比如functools.lru_cache、cachetools等,后面稍微调研一下和手动缓存的优劣,看看用哪个好 - 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) @@ -134,27 +96,17 @@ 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): @@ -283,9 +235,9 @@ 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() diff --git a/abl/abducer/kb.py b/abl/abducer/kb.py index d656e41..2a29d12 100644 --- a/abl/abducer/kb.py +++ b/abl/abducer/kb.py @@ -17,24 +17,30 @@ 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 from multiprocessing import Pool +from functools import lru_cache import pyswip class KBBase(ABC): - def __init__(self, pseudo_label_list, len_list=None, GKB_flag=False, max_err=0): + def __init__(self, pseudo_label_list, len_list=None, GKB_flag=False, max_err=0):#, abduce_cache=True): self.pseudo_label_list = pseudo_label_list self.len_list = len_list self.GKB_flag = GKB_flag self.max_err = max_err + # self.abduce_cache = abduce_cache if GKB_flag: self.base = {} X, Y = self._get_GKB() for x, y in zip(X, Y): self.base.setdefault(len(x), defaultdict(list))[y].append(x) + + # if abduce_cache: + # self.cache_min_address_num = {} + # self.cache_candidates = {} # For parallel version of _get_GKB def _get_XY_list(self, args): @@ -92,21 +98,21 @@ 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 @@ -115,10 +121,10 @@ class KBBase(ABC): 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,13 +132,31 @@ 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 + + # TODO:python也有自带的用装饰器实现的缓存方法,比如functools.lru_cache、cachetools等,后面稍微调研一下和手动缓存的优劣,看看用哪个好 + # def _get_abduce_cache(self, pred_res, key, max_address_num, require_more_address, multiple_predictions): + # if 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)] + # return candidates + # return None + + # def _set_abduce_cache(self, pred_res, key, min_address_num, address_num, candidates, multiple_predictions): + # if 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, multiple_predictions=False): candidates = [] @@ -166,7 +190,13 @@ 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): + # if self.abduce_cache: + # candidates = self._get_abduce_cache(pred_res, key, max_address_num, require_more_address, multiple_predictions) + # if candidates is not None: + # return candidates + candidates = [] for address_num in range(len(flatten(pred_res)) + 1): @@ -182,15 +212,18 @@ class KBBase(ABC): 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 new_candidates = self._address(address_num, pred_res, key, multiple_predictions) candidates += new_candidates + + # if self.abduce_cache: + # self._set_abduce_cache(pred_res, key, min_address_num, address_num, candidates, multiple_predictions) - return candidates, min_address_num, address_num + return candidates def _dict_len(self, dic): if not self.GKB_flag: @@ -346,4 +379,4 @@ class HWF_KB(RegKB): if __name__ == "__main__": - pass + pass \ No newline at end of file diff --git a/abl/framework_hed.py b/abl/framework_hed.py index 3f09ff6..d339909 100644 --- a/abl/framework_hed.py +++ b/abl/framework_hed.py @@ -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 = [] From 24ce8ff87ef318d57338cbcdb8da5da58b29a11a Mon Sep 17 00:00:00 2001 From: troyyyyy <49091847+troyyyyy@users.noreply.github.com> Date: Wed, 8 Mar 2023 10:18:08 +0000 Subject: [PATCH 04/12] Update several files --- abl/abducer/abducer_base.py | 4 ++-- abl/framework_hed.py | 2 +- abl/utils/utils.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/abl/abducer/abducer_base.py b/abl/abducer/abducer_base.py index a70db14..61cab25 100644 --- a/abl/abducer/abducer_base.py +++ b/abl/abducer/abducer_base.py @@ -68,8 +68,8 @@ class AbducerBase(abc.ABC): if len(candidates) > 0: score += np.min(self._get_cost_list(pred_res[idx], pred_res_prob[idx], candidates)) else: - score += len(pred_res) - return -self._zoopt_score_multiple(pred_res, key, sol.get_x()) + score += len(pred_res[idx]) + return score def _constrain_address_num(self, solution, max_address_num): x = solution.get_x() diff --git a/abl/framework_hed.py b/abl/framework_hed.py index d339909..ab88318 100644 --- a/abl/framework_hed.py +++ b/abl/framework_hed.py @@ -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: diff --git a/abl/utils/utils.py b/abl/utils/utils.py index d986065..d5209a6 100644 --- a/abl/utils/utils.py +++ b/abl/utils/utils.py @@ -19,7 +19,8 @@ def reform_idx(flatten_pred_res, save_pred_res): return re def hamming_dist(A, B): - B = np.array(B) + A = np.array(A, dtype=' Date: Wed, 8 Mar 2023 10:19:00 +0000 Subject: [PATCH 05/12] Update several files --- abl/abducer/abducer_base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/abl/abducer/abducer_base.py b/abl/abducer/abducer_base.py index 61cab25..54dbec1 100644 --- a/abl/abducer/abducer_base.py +++ b/abl/abducer/abducer_base.py @@ -85,7 +85,6 @@ class AbducerBase(abc.ABC): ) parameter = Parameter(budget=100, intermediate_result=False, autoset=True) solution = Opt.min(objective, parameter).get_x() - return solution def address_by_idx(self, pred_res, key, address_idx): @@ -106,7 +105,6 @@ class AbducerBase(abc.ABC): ) candidate = self._get_one_candidate(pred_res, pred_res_prob, candidates) - return candidate def abduce_rules(self, pred_res): From acfc25e2085009aa2f08bfc57e987ae6eb582283 Mon Sep 17 00:00:00 2001 From: troyyyyy Date: Thu, 9 Mar 2023 09:06:50 +0800 Subject: [PATCH 06/12] Update kb.py --- abl/abducer/kb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abl/abducer/kb.py b/abl/abducer/kb.py index 2a29d12..370b790 100644 --- a/abl/abducer/kb.py +++ b/abl/abducer/kb.py @@ -17,7 +17,7 @@ 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 from multiprocessing import Pool From 8e8aa76735eeee73cfc2a026ccddf008f99ea531 Mon Sep 17 00:00:00 2001 From: troyyyyy Date: Thu, 9 Mar 2023 10:54:18 +0800 Subject: [PATCH 07/12] Add cache in abduce_by_search --- abl/abducer/kb.py | 39 ++++++++------------------------------- abl/utils/utils.py | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/abl/abducer/kb.py b/abl/abducer/kb.py index 370b790..6cecba4 100644 --- a/abl/abducer/kb.py +++ b/abl/abducer/kb.py @@ -17,7 +17,7 @@ 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 @@ -25,22 +25,17 @@ from functools import lru_cache import pyswip class KBBase(ABC): - def __init__(self, pseudo_label_list, len_list=None, GKB_flag=False, max_err=0):#, abduce_cache=True): + def __init__(self, pseudo_label_list, len_list=None, GKB_flag=False, max_err=0): self.pseudo_label_list = pseudo_label_list self.len_list = len_list self.GKB_flag = GKB_flag self.max_err = max_err - # self.abduce_cache = abduce_cache if GKB_flag: self.base = {} X, Y = self._get_GKB() for x, y in zip(X, Y): self.base.setdefault(len(x), defaultdict(list))[y].append(x) - - # if abduce_cache: - # self.cache_min_address_num = {} - # self.cache_candidates = {} # For parallel version of _get_GKB def _get_XY_list(self, args): @@ -90,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): @@ -137,27 +132,7 @@ class KBBase(ABC): idxs = np.where(multiple_cost_list <= address_num)[0] candidates = [reform_idx(multiple_all_candidates[idx], pred_res) for idx in idxs] return candidates - - # TODO:python也有自带的用装饰器实现的缓存方法,比如functools.lru_cache、cachetools等,后面稍微调研一下和手动缓存的优劣,看看用哪个好 - # def _get_abduce_cache(self, pred_res, key, max_address_num, require_more_address, multiple_predictions): - # if 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)] - # return candidates - # return None - - # def _set_abduce_cache(self, pred_res, key, min_address_num, address_num, candidates, multiple_predictions): - # if 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, multiple_predictions=False): candidates = [] abduce_c = product(self.pseudo_label_list, repeat=len(address_idx)) @@ -190,13 +165,15 @@ class KBBase(ABC): new_candidates += candidates return new_candidates - # @lru_cache(maxsize=100) + @lru_cache(maxsize=100) def _abduce_by_search(self, pred_res, key, max_address_num, require_more_address, multiple_predictions): # if self.abduce_cache: # candidates = self._get_abduce_cache(pred_res, key, max_address_num, require_more_address, multiple_predictions) # if candidates is not None: # return candidates - + pred_res = hashable_to_list(pred_res) + key = hashable_to_list(key) + candidates = [] for address_num in range(len(flatten(pred_res)) + 1): diff --git a/abl/utils/utils.py b/abl/utils/utils.py index d5209a6..90376aa 100644 --- a/abl/utils/utils.py +++ b/abl/utils/utils.py @@ -35,7 +35,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: @@ -48,7 +47,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) @@ -86,3 +84,17 @@ def check_equal(a, b, max_err=0): else: return a == b + +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] \ No newline at end of file From e735693610fb9cd9568cf8dc64daa96b8ac46772 Mon Sep 17 00:00:00 2001 From: troyyyyy Date: Thu, 9 Mar 2023 13:14:47 +0800 Subject: [PATCH 08/12] Fix a minor bug --- abl/abducer/kb.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/abl/abducer/kb.py b/abl/abducer/kb.py index 6cecba4..333901e 100644 --- a/abl/abducer/kb.py +++ b/abl/abducer/kb.py @@ -113,7 +113,6 @@ class KBBase(ABC): 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 [] @@ -136,7 +135,6 @@ class KBBase(ABC): 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) @@ -145,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 @@ -167,15 +163,10 @@ class KBBase(ABC): @lru_cache(maxsize=100) def _abduce_by_search(self, pred_res, key, max_address_num, require_more_address, multiple_predictions): - # if self.abduce_cache: - # candidates = self._get_abduce_cache(pred_res, key, max_address_num, require_more_address, multiple_predictions) - # if candidates is not None: - # return candidates 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): @@ -183,23 +174,17 @@ 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 [] 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 - - # if self.abduce_cache: - # self._set_abduce_cache(pred_res, key, min_address_num, address_num, candidates, multiple_predictions) - return candidates def _dict_len(self, dic): From 7b209e73a501cf8c761c5a65091988b515651f99 Mon Sep 17 00:00:00 2001 From: Tony-HYX <605698554@qq.com> Date: Tue, 14 Mar 2023 14:53:30 +0800 Subject: [PATCH 09/12] update TODO in abducer_base --- abl/abducer/abducer_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/abl/abducer/abducer_base.py b/abl/abducer/abducer_base.py index 54dbec1..e2a16bf 100644 --- a/abl/abducer/abducer_base.py +++ b/abl/abducer/abducer_base.py @@ -62,6 +62,7 @@ class AbducerBase(abc.ABC): else: all_address_flag = reform_idx(sol.get_x(), pred_res) score = 0 + # TODO:这个循环里,和上面if not self.multiple_predictions部分逻辑完全一样吧,应该把上面封装一下,然后下面循环里调用封装方法即可 for idx in range(len(pred_res)): address_idx = np.where(all_address_flag[idx] != 0)[0] candidates = self.address_by_idx([pred_res[idx]], key[idx], address_idx) From 88a8f33f169fbd6306367609efbf98522252d0b9 Mon Sep 17 00:00:00 2001 From: troyyyyy <49091847+troyyyyy@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:08:13 +0800 Subject: [PATCH 10/12] Add `_get_zoopt_score` --- abl/abducer/abducer_base.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/abl/abducer/abducer_base.py b/abl/abducer/abducer_base.py index e2a16bf..d52f74b 100644 --- a/abl/abducer/abducer_base.py +++ b/abl/abducer/abducer_base.py @@ -50,26 +50,23 @@ class AbducerBase(abc.ABC): cost_list = self._get_cost_list(pred_res, pred_res_prob, candidates) candidate = candidates[np.argmin(cost_list)] return candidate - + + def _get_zoopt_score(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 = np.where(sol.get_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) + return self._get_address_score(sol.get_x(), pred_res, pred_res_prob, key) else: all_address_flag = reform_idx(sol.get_x(), pred_res) score = 0 - # TODO:这个循环里,和上面if not self.multiple_predictions部分逻辑完全一样吧,应该把上面封装一下,然后下面循环里调用封装方法即可 for idx in range(len(pred_res)): - address_idx = np.where(all_address_flag[idx] != 0)[0] - candidates = self.address_by_idx([pred_res[idx]], key[idx], address_idx) - if len(candidates) > 0: - score += np.min(self._get_cost_list(pred_res[idx], pred_res_prob[idx], candidates)) - else: - score += len(pred_res[idx]) + score += self._get_address_score(all_address_flag[idx], pred_res[idx], pred_res_prob[idx], key) return score def _constrain_address_num(self, solution, max_address_num): @@ -112,10 +109,10 @@ class AbducerBase(abc.ABC): 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) @@ -241,4 +238,4 @@ if __name__ == '__main__': print() abduced_rules = abd.abduce_rules(consist_exs) - print(abduced_rules) \ No newline at end of file + print(abduced_rules) From a71af2e5ab4bb953931d15d86f0ab23107f8c0a8 Mon Sep 17 00:00:00 2001 From: troyyyyy <49091847+troyyyyy@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:08:37 +0800 Subject: [PATCH 11/12] Rearrange rules to `HED_prolog_KB` --- abl/abducer/kb.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/abl/abducer/kb.py b/abl/abducer/kb.py index 333901e..fb606f3 100644 --- a/abl/abducer/kb.py +++ b/abl/abducer/kb.py @@ -271,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 @@ -341,4 +345,4 @@ class HWF_KB(RegKB): if __name__ == "__main__": - pass \ No newline at end of file + pass From 176f4bdd20e4dc08479cbf160874d11aeca7cac3 Mon Sep 17 00:00:00 2001 From: Tony-HYX <605698554@qq.com> Date: Tue, 14 Mar 2023 15:26:35 +0800 Subject: [PATCH 12/12] fix method name bug in abducer_base --- abl/abducer/abducer_base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/abl/abducer/abducer_base.py b/abl/abducer/abducer_base.py index d52f74b..06845a3 100644 --- a/abl/abducer/abducer_base.py +++ b/abl/abducer/abducer_base.py @@ -51,7 +51,7 @@ class AbducerBase(abc.ABC): candidate = candidates[np.argmin(cost_list)] return candidate - def _get_zoopt_score(self, sol_x, pred_res, pred_res_prob, key): + 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: @@ -61,12 +61,12 @@ class AbducerBase(abc.ABC): def _zoopt_address_score(self, pred_res, pred_res_prob, key, sol): if not self.multiple_predictions: - return self._get_address_score(sol.get_x(), pred_res, pred_res_prob, key) + return self._zoopt_address_score_single(sol.get_x(), pred_res, pred_res_prob, key) else: all_address_flag = reform_idx(sol.get_x(), pred_res) score = 0 for idx in range(len(pred_res)): - score += self._get_address_score(all_address_flag[idx], pred_res[idx], pred_res_prob[idx], key) + 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):