Browse Source

[MNT] partial HED

pull/1/head
Gao Enhao 2 years ago
parent
commit
6d483d78d3
3 changed files with 110 additions and 177 deletions
  1. +33
    -104
      examples/hed/hed_bridge.py
  2. +73
    -70
      examples/hed/hed_example.ipynb
  3. +4
    -3
      examples/hed/utils.py

+ 33
- 104
examples/hed/hed_bridge.py View File

@@ -65,7 +65,7 @@ class HEDBridge(SimpleBridge):

self.model.load(load_path=os.path.join(weights_dir, "pretrain_weights.pth"))

def abduce_pseudo_label(self, data_samples: ListData):
def select_mapping_and_abduce(self, data_samples: ListData):
candidate_mappings = gen_mappings([0, 1, 2, 3], ["+", "=", 0, 1])
mapping_score = []
abduced_pseudo_label_list = []
@@ -80,13 +80,17 @@ class HEDBridge(SimpleBridge):
max_revisible_instances = max(mapping_score)
return_idx = mapping_score.index(max_revisible_instances)
self.reasoner.mapping = candidate_mappings[return_idx]
self.reasoner.mapping = dict(
self.reasoner.remapping = dict(
zip(self.reasoner.mapping.values(), self.reasoner.mapping.keys())
)
data_samples.abduced_pseudo_label = abduced_pseudo_label_list[return_idx]

return data_samples.abduced_pseudo_label

def abduce_pseudo_label(self, data_samples: ListData):
self.reasoner.abduce(data_samples)
return data_samples.abduced_pseudo_label

def check_training_impact(self, filtered_data_samples, data_samples):
character_accuracy = self.model.valid(filtered_data_samples)
revisible_ratio = len(filtered_data_samples.X) / len(data_samples.X)
@@ -100,8 +104,8 @@ class HEDBridge(SimpleBridge):
return False

def check_rule_quality(self, rule, val_data, equation_len):
val_X_true = val_data[1][equation_len] + val_data[1][equation_len + 1]
val_X_false = val_data[0][equation_len] + val_data[0][equation_len + 1]
val_X_true = self.data_preprocess(val_data[1], equation_len)
val_X_false = self.data_preprocess(val_data[0], equation_len)

true_ratio = self.calc_consistent_ratio(val_X_true, rule)
false_ratio = self.calc_consistent_ratio(val_X_false, rule)
@@ -115,28 +119,23 @@ class HEDBridge(SimpleBridge):
return True
return False

def calc_consistent_ratio(self, X, rule):
pred_label, _ = self.predict(X)
pred_pseudo_label = self.label_to_pseudo_label(pred_label)
def calc_consistent_ratio(self, data_samples, rule):
self.predict(data_samples)
pred_pseudo_label = self.idx_to_pseudo_label(data_samples)
consistent_num = sum(
[self.reasoner.kb.consist_rule(instance, rule) for instance in pred_pseudo_label]
)
return consistent_num / len(X)
return consistent_num / len(data_samples.X)

def get_rules_from_data(self, train_data, samples_per_rule, samples_num):
def get_rules_from_data(self, data_samples, samples_per_rule, samples_num):
rules = []
sampler = InfiniteSampler(len(train_data))
data_loader = DataLoader(
train_data,
sampler=sampler,
batch_size=samples_per_rule,
collate_fn=lambda data_list: [list(data) for data in zip(*data_list)],
)
sampler = InfiniteSampler(len(data_samples), batch_size=samples_per_rule)

for _ in range(samples_num):
for X, Y, Z in data_loader:
pred_label, _ = self.predict(X)
pred_pseudo_label = self.label_to_pseudo_label(pred_label)
for select_idx in sampler:
sub_data_samples = data_samples[select_idx]
self.predict(sub_data_samples)
pred_pseudo_label = self.idx_to_pseudo_label(sub_data_samples)
consistent_instance = []
for instance in pred_pseudo_label:
if self.reasoner.kb.logic_forward([instance]):
@@ -184,8 +183,13 @@ class HEDBridge(SimpleBridge):
add_nums_dict[add_nums] = r
return list(rule_dict)

def idx_to_pseudo_label(self, data_samples: ListData) -> List[List[Any]]:
return super().idx_to_pseudo_label(data_samples)
def data_preprocess(self, data, equation_len) -> ListData:
data_samples = ListData()
data_samples.X = data[equation_len] + data[equation_len + 1]
data_samples.gt_pseudo_label = None
data_samples.Y = [None] * len(data_samples.X)

return data_samples

def train(self, train_data, val_data, segment_size=10, min_len=5, max_len=8):
for equation_len in range(min_len, max_len):
@@ -194,15 +198,17 @@ class HEDBridge(SimpleBridge):
logger="current",
)

X = train_data[1][equation_len] + train_data[1][equation_len + 1]
Y = [None] * len(X)
data_samples = self.data_preprocess(X, None, Y)
sampler = InfiniteSampler(len(data_samples))
condition_num = 0
data_samples = self.data_preprocess(train_data[1], equation_len)
sampler = InfiniteSampler(len(data_samples), batch_size=segment_size)
for seg_idx, select_idx in enumerate(sampler):
sub_data_samples = data_samples[select_idx]
self.predict(sub_data_samples)
# self.idx_to_pseudo_label(sub_data_samples)
self.abduce_pseudo_label(sub_data_samples)
if equation_len == min_len:
self.select_mapping_and_abduce(sub_data_samples)
else:
self.idx_to_pseudo_label(sub_data_samples)
self.abduce_pseudo_label(sub_data_samples)
filtered_sub_data_samples = self.filter_empty(sub_data_samples)
self.pseudo_label_to_idx(filtered_sub_data_samples)
loss = self.model.train(filtered_sub_data_samples)
@@ -239,80 +245,3 @@ class HEDBridge(SimpleBridge):
self.model.load(load_path=f"./weights/eq_len_{equation_len - 1}.pth")
condition_num = 0
print_log("Reload Model and retrain", logger="current")

# def train(
# self,
# train_data,
# val_data,
# segment_size=10,
# min_len=5,
# max_len=8,
# ):
# for equation_len in range(min_len, max_len):
# print_log(
# f"============== equation_len: {equation_len}-{equation_len + 1} ================",
# logger="current",
# )

# train_X = train_data[1][equation_len] + train_data[1][equation_len + 1]
# train_Y = [None] * len(train_X)
# # data_samples = self.data_preprocess(train_X, None, train_Y)

# dataset = BridgeDataset(train_X, None, train_Y)
# sampler = InfiniteSampler(len(dataset))
# data_loader = DataLoader(
# dataset,
# sampler=sampler,
# batch_size=segment_size,
# collate_fn=lambda data_list: [list(data) for data in zip(*data_list)],
# )

# condition_num = 0

# for seg_idx, (X, Z, Y) in enumerate(data_loader):
# pred_label, pred_prob = self.predict(ListData(X=X))
# if equation_len == min_len:
# abduced_pseudo_label = self.select_mapping_and_abduce(pred_label, pred_prob, Y)
# else:
# pred_pseudo_label = self.label_to_pseudo_label(pred_label)
# abduced_pseudo_label = self.abduce_pseudo_label(
# pred_label, pred_prob, pred_pseudo_label, Y, 20
# )
# filtered_X, filtered_abduced_pseudo_label = self.filter_empty(
# X, abduced_pseudo_label
# )
# if len(filtered_X) == 0:
# continue
# filtered_abduced_label = self.pseudo_label_to_label(filtered_abduced_pseudo_label)
# min_loss = self.model.train(filtered_X, filtered_abduced_label)

# print_log(
# f"Equation Len(train) [{equation_len}] Segment Index [{seg_idx + 1}] minimal_loss is {min_loss:.5f}",
# logger="current",
# )

# if self.check_training_impact(filtered_X, filtered_abduced_label, X):
# condition_num += 1
# else:
# condition_num = 0

# if condition_num >= 5:
# print_log(f"Now checking if we can go to next course", logger="current")
# rules = self.get_rules_from_data(dataset, samples_per_rule=3, samples_num=50)
# print_log(f"Learned rules from data: " + str(rules), logger="current")

# seems_good = self.check_rule_quality(rules, val_data, equation_len)
# if seems_good:
# self.model.save(save_path=f"./weights/eq_len_{equation_len}.pth")
# break
# else:
# if equation_len == min_len:
# print_log(
# "Learned mapping is: " + str(self.reasoner.mapping),
# logger="current",
# )
# self.model.load(load_path="./weights/pretrain_weights.pth")
# else:
# self.model.load(load_path=f"./weights/eq_len_{equation_len - 1}.pth")
# condition_num = 0
# print_log("Reload Model and retrain", logger="current")

+ 73
- 70
examples/hed/hed_example.ipynb View File

@@ -6,18 +6,20 @@
"metadata": {},
"outputs": [],
"source": [
"import os.path as osp\n",
"\n",
"import numpy as np\n",
"import torch.nn as nn\n",
"import torch\n",
"import torch.nn as nn\n",
"\n",
"from abl.reasoning import ReasonerBase, prolog_KB\n",
"from abl.learning import BasicNN, ABLModel\n",
"from abl.evaluation import SymbolMetric, ABLMetric\n",
"from abl.utils import ABLLogger, reform_list\n",
"\n",
"from abl.evaluation import SemanticsMetric, SymbolMetric\n",
"from abl.learning import ABLModel, BasicNN\n",
"from abl.reasoning import PrologKB, ReasonerBase\n",
"from abl.utils import ABLLogger, print_log, reform_list\n",
"from examples.hed.datasets.get_hed import get_hed, split_equation\n",
"from examples.hed.hed_bridge import HEDBridge\n",
"from models.nn import SymbolNet\n",
"from datasets.get_hed import get_hed, split_equation"
"from examples.models.nn import SymbolNet\n",
"from zoopt import Dimension, Objective, Parameter, Opt"
]
},
{
@@ -26,8 +28,12 @@
"metadata": {},
"outputs": [],
"source": [
"# Initialize logger\n",
"logger = ABLLogger.get_instance(\"abl\")"
"# Build logger\n",
"print_log(\"Abductive Learning on the HED example.\", logger=\"current\")\n",
"\n",
"# Retrieve the directory of the Log file and define the directory for saving the model weights.\n",
"log_dir = ABLLogger.get_current_instance().log_dir\n",
"weights_dir = osp.join(log_dir, \"weights\")"
]
},
{
@@ -45,61 +51,52 @@
"outputs": [],
"source": [
"# Initialize knowledge base and abducer\n",
"class HED_prolog_KB(prolog_KB):\n",
"class HedKB(PrologKB):\n",
" def __init__(self, pseudo_label_list, pl_file):\n",
" super().__init__(pseudo_label_list, pl_file)\n",
" \n",
"\n",
" def consist_rule(self, exs, rules):\n",
" rules = str(rules).replace(\"\\'\",\"\")\n",
" rules = str(rules).replace(\"'\", \"\")\n",
" return len(list(self.prolog.query(\"eval_inst_feature(%s, %s).\" % (exs, rules)))) != 0\n",
"\n",
" def abduce_rules(self, pred_res):\n",
" prolog_result = list(self.prolog.query(\"consistent_inst_feature(%s, X).\" % pred_res))\n",
" if len(prolog_result) == 0:\n",
" return None\n",
" prolog_rules = prolog_result[0]['X']\n",
" prolog_rules = prolog_result[0][\"X\"]\n",
" rules = [rule.value for rule in prolog_rules]\n",
" return rules\n",
" \n",
"\n",
"kb = HED_prolog_KB(pseudo_label_list=[1, 0, '+', '='], pl_file='./datasets/learn_add.pl')\n",
"\n",
"class HED_Abducer(ReasonerBase):\n",
" def __init__(self, kb, dist_func='hamming'):\n",
" super().__init__(kb, dist_func, zoopt=True)\n",
" \n",
" def _revise_by_idxs(self, pred_res, key, all_address_flag, idxs):\n",
" pred = []\n",
" k = []\n",
" address_flag = []\n",
" for idx in idxs:\n",
" pred.append(pred_res[idx])\n",
" k.append(key[idx])\n",
" address_flag += list(all_address_flag[idx])\n",
" address_idx = np.where(np.array(address_flag) != 0)[0] \n",
" candidate = self.revise_by_idx(pred, k, address_idx)\n",
"class HedReasoner(ReasonerBase):\n",
" def revise_at_idx(self, data_sample):\n",
" revision_idx = np.where(np.array(data_sample.flatten(\"revision_flag\")) != 0)[0]\n",
" candidate = self.kb.revise_at_idx(\n",
" data_sample.pred_pseudo_label, data_sample.Y, revision_idx\n",
" )\n",
" return candidate\n",
" \n",
" def zoopt_revision_score(self, pred_res, pseudo_label, pred_res_prob, key, sol): \n",
" all_address_flag = reform_list(sol.get_x(), pseudo_label)\n",
" lefted_idxs = [i for i in range(len(pred_res))]\n",
" candidate_size = [] \n",
"\n",
" def zoopt_revision_score(self, symbol_num, data_sample, sol):\n",
" revision_flag = reform_list(list(sol.get_x().astype(np.int32)), data_sample.pred_pseudo_label)\n",
" data_sample.revision_flag = revision_flag\n",
"\n",
" lefted_idxs = [i for i in range(len(data_sample.pred_idx))]\n",
" candidate_size = []\n",
" while lefted_idxs:\n",
" idxs = []\n",
" idxs.append(lefted_idxs.pop(0))\n",
" max_candidate_idxs = []\n",
" found = False\n",
" for idx in range(-1, len(pred_res)):\n",
" for idx in range(-1, len(data_sample.pred_idx)):\n",
" if (not idx in idxs) and (idx >= 0):\n",
" idxs.append(idx)\n",
" candidate = self._revise_by_idxs(pseudo_label, key, all_address_flag, idxs)\n",
" candidate = self.revise_at_idx(data_sample[idxs])\n",
" if len(candidate) == 0:\n",
" if len(idxs) > 1:\n",
" idxs.pop()\n",
" else:\n",
" if len(idxs) > len(max_candidate_idxs):\n",
" found = True\n",
" max_candidate_idxs = idxs.copy() \n",
" max_candidate_idxs = idxs.copy()\n",
" removed = [i for i in lefted_idxs if i in max_candidate_idxs]\n",
" if found:\n",
" candidate_size.append(len(removed) + 1)\n",
@@ -107,32 +104,40 @@
" candidate_size.sort()\n",
" score = 0\n",
" import math\n",
"\n",
" for i in range(0, len(candidate_size)):\n",
" score -= math.exp(-i) * candidate_size[i]\n",
" return score\n",
" \n",
" def abduce(self, data, max_revision=-1, require_more_revision=0):\n",
" batch_pred_label, batch_pred_prob, batch_pred_pseudo_label, batch_y = data\n",
" def abduce(self, data_sample):\n",
" symbol_num = data_sample.elements_num(\"pred_pseudo_label\")\n",
" max_revision_num = self._get_max_revision_num(self.max_revision, symbol_num)\n",
"\n",
" solution = self.zoopt_get_solution(symbol_num, data_sample, max_revision_num)\n",
"\n",
" solution = self.zoopt_get_solution(\n",
" batch_pred_label, batch_pred_pseudo_label, batch_pred_prob, batch_y, max_revision\n",
" data_sample.revision_flag = reform_list(\n",
" solution.astype(np.int32), data_sample.pred_pseudo_label\n",
" )\n",
" batch_revision_idx = reform_list(solution.astype(np.int32), batch_pred_label)\n",
" \n",
" batch_abduced_pseudo_label = []\n",
" for pred_pseudo_label, pred_prob, revision_idx in zip(batch_pred_pseudo_label, batch_pred_prob, batch_revision_idx):\n",
" candidates = self.revise_by_idx([pred_pseudo_label], None, list(np.nonzero(np.array(revision_idx))[0]))\n",
"\n",
" abduced_pseudo_label = []\n",
"\n",
" for single_instance in data_sample:\n",
" single_instance.pred_pseudo_label = [single_instance.pred_pseudo_label]\n",
" candidates = self.revise_at_idx(single_instance)\n",
" if len(candidates) == 0:\n",
" batch_abduced_pseudo_label.append([])\n",
" abduced_pseudo_label.append([])\n",
" else:\n",
" batch_abduced_pseudo_label.append(candidates[0][0])\n",
" # batch_abduced_pseudo_label.append(self._get_one_candidate(pred_pseudo_label, pred_prob, candidates)[0])\n",
" return batch_abduced_pseudo_label\n",
" abduced_pseudo_label.append(candidates[0][0])\n",
" data_sample.abduced_pseudo_label = abduced_pseudo_label\n",
" return abduced_pseudo_label\n",
"\n",
" def abduce_rules(self, pred_res):\n",
" return self.kb.abduce_rules(pred_res)\n",
" \n",
"abducer = HED_Abducer(kb)"
"\n",
"kb = HedKB(\n",
" pseudo_label_list=[1, 0, \"+\", \"=\"], pl_file=\"./datasets/learn_add.pl\"\n",
")\n",
"reasoner = HedReasoner(kb, dist_func=\"hamming\", use_zoopt=True, max_revision=20)"
]
},
{
@@ -149,14 +154,12 @@
"metadata": {},
"outputs": [],
"source": [
"# Initialize necessary component for machine learning part\n",
"cls = SymbolNet(\n",
" num_classes=len(kb.pseudo_label_list),\n",
" image_size=(28, 28, 1),\n",
")\n",
"device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
"# Build necessary components for BasicNN\n",
"cls = SymbolNet(num_classes=4)\n",
"criterion = nn.CrossEntropyLoss()\n",
"optimizer = torch.optim.RMSprop(cls.parameters(), lr=0.001, weight_decay=1e-6)"
"optimizer = torch.optim.RMSprop(cls.parameters(), lr=0.001, weight_decay=1e-6)\n",
"# optimizer = torch.optim.Adam(cls.parameters(), lr=0.001, betas=(0.9, 0.99))\n",
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"
]
},
{
@@ -165,17 +168,17 @@
"metadata": {},
"outputs": [],
"source": [
"# Initialize BasicNN\n",
"# Build BasicNN\n",
"# The function of BasicNN is to wrap NN models into the form of an sklearn estimator\n",
"base_model = BasicNN(\n",
" cls,\n",
" criterion,\n",
" optimizer,\n",
" device,\n",
" save_interval=1,\n",
" save_dir=logger.save_dir,\n",
" batch_size=32,\n",
" num_epochs=1,\n",
" save_interval=1,\n",
" save_dir=weights_dir,\n",
")"
]
},
@@ -185,8 +188,8 @@
"metadata": {},
"outputs": [],
"source": [
"# Initialize ABL model\n",
"# The main function of the ABL model is to serialize data and \n",
"# Build ABLModel\n",
"# The main function of the ABL model is to serialize data and\n",
"# provide a unified interface for different machine learning models\n",
"model = ABLModel(base_model)"
]
@@ -205,8 +208,8 @@
"metadata": {},
"outputs": [],
"source": [
"# Add metric\n",
"metric = [SymbolMetric(prefix=\"hed\"), ABLMetric(prefix=\"hed\")]"
"# Set up metrics\n",
"metric_list = [SymbolMetric(prefix=\"hed\"), SemanticsMetric(prefix=\"hed\")]"
]
},
{
@@ -223,7 +226,7 @@
"metadata": {},
"outputs": [],
"source": [
"bridge = HEDBridge(model, abducer, metric)"
"bridge = HEDBridge(model, reasoner, metric_list)"
]
},
{
@@ -259,7 +262,7 @@
"metadata": {},
"outputs": [],
"source": [
"bridge.pretrain(\"./weights/\")\n",
"bridge.pretrain(\"./weights\")\n",
"bridge.train(train_data, val_data)"
]
}


+ 4
- 3
examples/hed/utils.py View File

@@ -5,15 +5,16 @@ import torch.utils.data.sampler as sampler


class InfiniteSampler(sampler.Sampler):
def __init__(self, num_samples):
def __init__(self, num_samples, batch_size=1):
self.num_samples = num_samples
self.batch_size = batch_size

def __iter__(self):
while True:
order = np.random.permutation(self.num_samples)
for i in range(self.num_samples):
yield order[i: i + 10]
i += 10
yield order[i: i + self.batch_size]
i += self.batch_size

def __len__(self):
return None


Loading…
Cancel
Save