| @@ -11,58 +11,60 @@ | |||
| #================================================================# | |||
| import abc | |||
| from abducer.kb import ClsKB, RegKB | |||
| #from kb import ClsKB, RegKB | |||
| from kb import add_KB | |||
| import numpy as np | |||
| 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) | |||
| def confidence_dist(A, B): | |||
| B = np.array(B) | |||
| #print(A) | |||
| A = np.clip(A, 1e-9, 1) | |||
| A = np.expand_dims(A, axis=0) | |||
| A = A.repeat(axis=0, repeats=(len(B))) | |||
| rows = np.array(range(len(B))) | |||
| rows = np.expand_dims(rows, axis = 1).repeat(axis = 1, repeats = len(B[0])) | |||
| cols = np.array(range(len(B[0]))) | |||
| cols = np.expand_dims(cols, axis = 0).repeat(axis = 0, repeats = len(B)) | |||
| return 1 - np.prod(A[rows, cols, B], axis = 1) | |||
| return np.sum(np.array(A) != np.array(B)) | |||
| class AbducerBase(abc.ABC): | |||
| def __init__(self, kb, dist_func = "hamming", pred_res_parse = None): | |||
| def __init__(self, kb, dist_func = "hamming", pred_res_parse = None, cache = True): | |||
| self.kb = kb | |||
| if dist_func == "hamming": | |||
| dist_func = hamming_dist | |||
| elif dist_func == "confidence": | |||
| dist_func = confidence_dist | |||
| self.dist_func = dist_func | |||
| self.dist_func = hamming_dist | |||
| if pred_res_parse is None: | |||
| pred_res_parse = lambda x : x["cls"] | |||
| self.pred_res_parse = pred_res_parse | |||
| self.cache = cache | |||
| self.cache_min_address_num = {} | |||
| self.cache_candidates = {} | |||
| def abduce(self, data, max_address_num, require_more_address, length = -1): | |||
| def abduce(self, data, max_address_num = 3, require_more_address = 0, length = -1): | |||
| pred_res, ans = data | |||
| if length == -1: | |||
| length = len(pred_res) | |||
| candidates = self.kb.get_candidates(ans, length) | |||
| pred_res = np.array(pred_res) | |||
| cost_list = self.dist_func(pred_res, candidates) | |||
| address_num = np.min(cost_list) | |||
| threshold = min(address_num + require_more_address, max_address_num) | |||
| idxs = np.where(cost_list <= address_num+require_more_address)[0] | |||
| #return [candidates[idx] for idx in idxs], address_num | |||
| if len(idxs) > 1: | |||
| return None | |||
| return [candidates[idx] for idx in idxs][0] | |||
| 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): | |||
| print('cached') | |||
| return self.cache_candidates[(tuple(pred_res), ans, address_num)] | |||
| candidates, min_address_num, address_num = self.kb.get_abduce_candidates(pred_res, ans, length, self.dist_func, max_address_num, require_more_address) | |||
| 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 | |||
| return candidates | |||
| # candidates = self.kb.get_candidates(ans, length) | |||
| # cost_list = self.dist_func(pred_res, candidates) | |||
| # address_num = np.min(cost_list) | |||
| # # threshold = min(address_num + require_more_address, max_address_num) | |||
| # idxs = np.where(cost_list <= address_num + require_more_address)[0] | |||
| # return [candidates[idx] for idx in idxs], address_num | |||
| # if len(idxs) > 1: | |||
| # return None | |||
| # return [candidates[idx] for idx in idxs] | |||
| def batch_abduce(self, Y, C, max_address_num = 3, require_more_address = 0): | |||
| return [ | |||
| @@ -71,34 +73,27 @@ class AbducerBase(abc.ABC): | |||
| ] | |||
| def __call__(self, Y, C, max_address_num = 3, require_more_address = 0): | |||
| return batch_abduce(Y, C, max_address_num, require_more_address) | |||
| return self.batch_abduce(Y, C, max_address_num, require_more_address) | |||
| if __name__ == "__main__": | |||
| #["1+1", "0+1", "1+0", "2+0"] | |||
| X = [[1,3,1], [0,3,1], [1,2,0], [3,2,0]] | |||
| Y = [2, 1, 1, 2] | |||
| kb = RegKB(X, Y) | |||
| pseudo_label_list = list(range(10)) | |||
| kb = add_KB(pseudo_label_list) | |||
| abd = AbducerBase(kb) | |||
| res = abd.abduce(([0,2,0], None), 1, 0) | |||
| res = abd.abduce(([1, 1, 1], 4), max_address_num = 2, require_more_address = 0) | |||
| print(res) | |||
| res = abd.abduce(([0, 2, 0], 0.99), 1, 0) | |||
| res = abd.abduce(([1, 1, 1], 4), max_address_num = 2, require_more_address = 1) | |||
| print(res) | |||
| A = np.array([[0.5, 0.25, 0.25, 0], [0.3, 0.3, 0.3, 0.1], [0.1, 0.2, 0.3, 0.4]]) | |||
| B = [[1, 2, 3], [0, 1, 3]] | |||
| res = confidence_dist(A, B) | |||
| res = abd.abduce(([1, 1, 1], 4), max_address_num = 1, require_more_address = 1) | |||
| print(res) | |||
| A = np.array([[0.5, 0.25, 0.25, 0], [0.3, 1.0, 0.3, 0.1], [0.1, 0.2, 0.3, 1.0]]) | |||
| B = [[0, 1, 3]] | |||
| res = confidence_dist(A, B) | |||
| print() | |||
| print('Test cache') | |||
| res = abd.abduce(([1, 1, 1], 4), max_address_num = 2, require_more_address = 0) | |||
| print(res) | |||
| kb_str = ['10010001011', '00010001100', '00111101011', '11101000011', '11110011001', '11111010001', '10001010010', '11100100001', '10001001100', '11011010001', '00110000100', '11000000111', '01110111111', '11000101100', '10101011010', '00000110110', '11111110010', '11100101100', '10111001111', '10000101100', '01001011101', '01001110000', '01110001110', '01010010001', '10000100010', '01001011011', '11111111100', '01011101101', '00101110101', '11101001101', '10010110000', '10000000011'] | |||
| X = [[int(c) for c in s] for s in kb_str] | |||
| kb = RegKB(X, len(X) * [None]) | |||
| abd = AbducerBase(kb) | |||
| res = abd.abduce(((1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1), None), 1, 0) | |||
| res = abd.abduce(([1, 1, 1], 4), max_address_num = 20, require_more_address = 1) | |||
| print(res) | |||
| # res = abd.abduce(([0, 2, 0], 0.99), 1, 0) | |||
| # print(res) | |||
| @@ -10,57 +10,104 @@ | |||
| # | |||
| #================================================================# | |||
| import abc | |||
| from abc import ABC, abstractmethod | |||
| import bisect | |||
| import copy | |||
| import numpy as np | |||
| from collections import defaultdict | |||
| class KBBase(abc.ABC): | |||
| def __init__(self, X = None, Y = None): | |||
| from itertools import product | |||
| class KBBase(ABC): | |||
| def __init__(self): | |||
| pass | |||
| def get_candidates(self, key = None, length = None): | |||
| @abstractmethod | |||
| def get_candidates(self): | |||
| pass | |||
| @abstractmethod | |||
| def get_all_candidates(self): | |||
| pass | |||
| @abstractmethod | |||
| def logic_forward(self, X): | |||
| pass | |||
| def _length(self, length): | |||
| if length is None: | |||
| length = list(self.base.keys()) | |||
| if type(length) is int: | |||
| length = [length] | |||
| return length | |||
| def __len__(self): | |||
| pass | |||
| class ClsKB(KBBase): | |||
| def __init__(self, X, Y = None): | |||
| class add_KB(KBBase): | |||
| def __init__(self, pseudo_label_list, max_len = 5): | |||
| super().__init__() | |||
| self.pseudo_label_list = pseudo_label_list | |||
| self.base = {} | |||
| if X is None: | |||
| return | |||
| if Y is None: | |||
| Y = [None] * len(X) | |||
| X = self.get_X(self.pseudo_label_list, max_len) | |||
| Y = self.get_Y(X, self.logic_forward) | |||
| for x, y in zip(X, Y): | |||
| self.base.setdefault(len(x), defaultdict(list))[y].append(np.array(x)) | |||
| def logic_forward(self, nums): | |||
| return sum(nums) | |||
| def get_X(self, pseudo_label_list, max_len): | |||
| res = [] | |||
| assert(max_len >= 2) | |||
| for len in range(2, max_len + 1): | |||
| 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] | |||
| def get_candidates(self, key, length = None): | |||
| if key is None: | |||
| return self.get_all_candidates() | |||
| length = self._length(length) | |||
| 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()], []) | |||
| def get_abduce_candidates(self, pred_res, key, length, dist_func, max_address_num, require_more_address): | |||
| if key is None: | |||
| return self.get_all_candidates() | |||
| candidates = [] | |||
| all_candidates = list(product(self.pseudo_label_list, repeat = len(pred_res))) | |||
| for address_num in range(length + 1): | |||
| if(address_num > max_address_num): | |||
| print('No candidates found') | |||
| return None, None, None | |||
| for c in all_candidates: | |||
| if(dist_func(c, pred_res) == address_num): | |||
| if(self.logic_forward(c) == key): | |||
| candidates.append(c) | |||
| 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): | |||
| return candidates, min_address_num, address_num - 1 | |||
| for c in all_candidates: | |||
| if(dist_func(c, pred_res) == address_num): | |||
| if(self.logic_forward(c) == key): | |||
| candidates.append(c) | |||
| return candidates, min_address_num, address_num | |||
| def _dict_len(self, dic): | |||
| return sum(len(c) for c in dic.values()) | |||
| @@ -68,70 +115,19 @@ class ClsKB(KBBase): | |||
| def __len__(self): | |||
| return sum(self._dict_len(v) for v in self.base.values()) | |||
| class RegKB(KBBase): | |||
| def __init__(self, X, Y = None): | |||
| super().__init__() | |||
| tmp_dict = {} | |||
| for x, y in zip(X, Y): | |||
| tmp_dict.setdefault(len(x), defaultdict(list))[y].append(np.array(x)) | |||
| self.base = {} | |||
| for l in tmp_dict.keys(): | |||
| data = sorted(list(zip(tmp_dict[l].keys(), tmp_dict[l].values()))) | |||
| X = [x for y, x in data] | |||
| Y = [y for y, x in data] | |||
| self.base[l] = (X, Y) | |||
| def get_candidates(self, key, length = None): | |||
| if key is None: | |||
| return self.get_all_candidates() | |||
| length = self._length(length) | |||
| min_err = 999999 | |||
| candidates = [] | |||
| for l in length: | |||
| X, Y = self.base[l] | |||
| idx = bisect.bisect_left(Y, key) | |||
| begin = max(0, idx - 1) | |||
| end = min(idx + 2, len(X)) | |||
| for idx in range(begin, end): | |||
| err = abs(Y[idx] - key) | |||
| if abs(err - min_err) < 1e-9: | |||
| candidates.extend(X[idx]) | |||
| elif err < min_err: | |||
| candidates = copy.deepcopy(X[idx]) | |||
| min_err = err | |||
| return candidates | |||
| def get_all_candidates(self): | |||
| return sum([sum(D[0], []) for D in self.base.values()], []) | |||
| def __len__(self): | |||
| return sum([sum(len(x) for x in D[0]) for D in self.base.values()]) | |||
| if __name__ == "__main__": | |||
| X = ["1+1", "0+1", "1+0", "2+0", "1+0+1"] | |||
| Y = [2, 1, 1, 2, 2] | |||
| kb = ClsKB(X, Y) | |||
| print(len(kb)) | |||
| res = kb.get_candidates(2, 5) | |||
| print(res) | |||
| res = kb.get_candidates(2, 3) | |||
| print(res) | |||
| res = kb.get_candidates(None) | |||
| print(res) | |||
| X = ["1+1", "0+1", "1+0", "2+0", "1+0.5", "0.75+0.75"] | |||
| Y = [2, 1, 1, 2, 1.5, 1.5] | |||
| kb = RegKB(X, Y) | |||
| print(len(kb)) | |||
| res = kb.get_candidates(1.6) | |||
| pseudo_label_list = list(range(10)) | |||
| kb = add_KB(pseudo_label_list, max_len = 5) | |||
| print('len(kb):', len(kb)) | |||
| print() | |||
| res = kb.get_candidates(0) | |||
| print(res) | |||
| res = kb.get_candidates(1.6, length = 9) | |||
| print() | |||
| res = kb.get_candidates(18, length = 2) | |||
| print(res) | |||
| res = kb.get_candidates(None) | |||
| print() | |||
| res = kb.get_candidates(7, length = 3) | |||
| print(res) | |||
| @@ -0,0 +1,37 @@ | |||
| import torch | |||
| import torchvision | |||
| from torch.utils.data import Dataset | |||
| from torchvision.transforms import transforms | |||
| class MNIST_Addition(Dataset): | |||
| def __init__(self, dataset, examples): | |||
| self.data = list() | |||
| self.dataset = dataset | |||
| with open(examples) as f: | |||
| for line in f: | |||
| line = line.strip().split(' ') | |||
| self.data.append(tuple([int(i) for i in line])) | |||
| def __len__(self): | |||
| return len(self.data) | |||
| def __getitem__(self, index): | |||
| i1, i2, l = self.data[index] | |||
| return self.dataset[i1][0], self.dataset[i2][0], l | |||
| def get_mnist_add(): | |||
| transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081, ))]) | |||
| train_dataset = MNIST_Addition(torchvision.datasets.MNIST(root='./', train=True, download=True, transform=transform), './train_data.txt') | |||
| test_loader = torch.utils.data.DataLoader(torchvision.datasets.MNIST('./', train=False, transform=transform), batch_size=1000, shuffle=True) | |||
| X = [] | |||
| Y = [] | |||
| for i1, i2, l in train_dataset: | |||
| X.append([i1, i2]) | |||
| Y.append(l) | |||
| return X, Y, test_loader | |||
| if __name__ == "__main__": | |||
| X, Y, test_loader = get_mnist_add() | |||
| print(len(X), len(Y)) | |||
| print(X[0][0].shape, X[0][1].shape, Y[0]) | |||