From: François Fleuret Date: Wed, 10 Jul 2024 21:26:06 +0000 (+0200) Subject: Update. X-Git-Url: https://fleuret.org/cgi-bin/gitweb/gitweb.cgi?a=commitdiff_plain;h=a795fa74d5a75318b1196bef048086cd64c41397;p=culture.git Update. --- diff --git a/grids.py b/grids.py index 6e9e6c7..d1653ee 100755 --- a/grids.py +++ b/grids.py @@ -769,9 +769,97 @@ class Grids(problem.Problem): ): break + def compute_distance(self, walls, goal_i, goal_j, start_i, start_j): + max_length = walls.numel() + dist = torch.full_like(walls, max_length) + + dist[goal_i, goal_j] = 0 + pred_dist = torch.empty_like(dist) + + while True: + pred_dist.copy_(dist) + d = ( + torch.cat( + ( + dist[None, 1:-1, 0:-2], + dist[None, 2:, 1:-1], + dist[None, 1:-1, 2:], + dist[None, 0:-2, 1:-1], + ), + 0, + ).min(dim=0)[0] + + 1 + ) + + dist[1:-1, 1:-1].minimum_(d) # = torch.min(dist[1:-1, 1:-1], d) + dist = walls * max_length + (1 - walls) * dist + + if dist[start_i, start_j] < max_length or dist.equal(pred_dist): + return dist * (1 - walls) + # @torch.compile - def task_islands(self, A, f_A, B, f_B): - pass + def task_path(self, A, f_A, B, f_B): + c = torch.randperm(len(self.colors) - 1)[:3] + 1 + dist = torch.empty(self.height + 2, self.width + 2) + for X, f_X in [(A, f_A), (B, f_B)]: + nb_rec = torch.randint(3, (1,)) + 1 + while True: + r = self.rec_coo(nb_rec, prevent_overlap=True) + X[...] = 0 + f_X[...] = 0 + for n in range(nb_rec): + i1, j1, i2, j2 = r[n] + X[i1:i2, j1:j2] = c[0] + f_X[i1:i2, j1:j2] = c[0] + while True: + i0, j0 = torch.randint(self.height, (1,)), torch.randint( + self.width, (1,) + ) + if X[i0, j0] == 0: + break + while True: + i1, j1 = torch.randint(self.height, (1,)), torch.randint( + self.width, (1,) + ) + if X[i1, j1] == 0: + break + dist[...] = 1 + dist[1:-1, 1:-1] = (X != 0).long() + dist[...] = self.compute_distance(dist, i1 + 1, j1 + 1, i0 + 1, j0 + 1) + if dist[i0 + 1, j0 + 1] >= 1 and dist[i0 + 1, j0 + 1] < self.height * 4: + break + + dist[1:-1, 1:-1] += (X != 0).long() * self.height * self.width + dist[0, :] = self.height * self.width + dist[-1, :] = self.height * self.width + dist[:, 0] = self.height * self.width + dist[:, -1] = self.height * self.width + # dist += torch.rand(dist.size()) + + i, j = i0 + 1, j0 + 1 + while i != i1 + 1 or j != j1 + 1: + f_X[i - 1, j - 1] = c[2] + r, s, t, u = ( + dist[i - 1, j], + dist[i, j - 1], + dist[i + 1, j], + dist[i, j + 1], + ) + m = min(r, s, t, u) + if r == m: + i = i - 1 + elif t == m: + i = i + 1 + elif s == m: + j = j - 1 + else: + j = j + 1 + + X[i0, j0] = c[2] + # f_X[i0, j0] = c[1] + + X[i1, j1] = c[1] + f_X[i1, j1] = c[1] # for X, f_X in [(A, f_A), (B, f_B)]: # n = torch.arange(self.height * self.width).reshape(self.height, self.width) @@ -797,7 +885,7 @@ class Grids(problem.Problem): self.task_scale, self.task_symbols, self.task_ortho, - # self.task_islands, + # self.task_path, ] def trivial_prompts_and_answers(self, prompts, answers): @@ -875,15 +963,17 @@ if __name__ == "__main__": # exit(0) # if True: - # nb,nrow = 72,4 - nb, nrow = 8, 2 + nb, nrow = 72, 4 + # nb, nrow = 8, 2 - for t in grids.all_tasks(): - # for t in [grids.task_replace_color]: + # for t in grids.all_tasks(): + for t in [grids.task_path]: print(t.__name__) prompts, answers = grids.generate_prompts_and_answers_(nb, tasks=[t]) grids.save_quizzes("/tmp", t.__name__, prompts[:nb], answers[:nb], nrow=nrow) + # exit(0) + nb = 1000 for t in grids.all_tasks(): diff --git a/main.py b/main.py index 1ef01e9..e6806d4 100755 --- a/main.py +++ b/main.py @@ -88,14 +88,10 @@ parser.add_argument("--min_to_validate", type=int, default=None) parser.add_argument("--max_to_validate", type=int, default=None) -parser.add_argument("--accuracy_to_make_c_quizzes", type=float, default=0.975) +parser.add_argument("--accuracy_to_make_c_quizzes", type=float, default=0.9) parser.add_argument("--generation_temperature", type=float, default=2.0) -parser.add_argument("--deterministic_validation", action="store_true", default=False) - -parser.add_argument("--bidirectional_validation", action="store_true", default=False) - parser.add_argument("--dirty_debug", action="store_true", default=False) ###################################################################### @@ -435,113 +431,6 @@ def create_c_quizzes( quiz_machine.save_quizzes(args.result_dir, f"culture_c_quiz_{n_epoch:04d}", q) -###################################################################### - - -def create_c_quizzes_( - models, - quiz_machine, - nb_for_train=1000, - nb_for_test=100, -): - quizzes_and_nb_correct_records = [] - - nb_to_create = nb_for_train + nb_for_test - - # ------------------------------------------------------------ - - standard_validity = lambda nb_correct: (nb_correct >= args.min_to_validate) & ( - nb_correct <= args.max_to_validate - ) - - file_name = os.path.join(args.result_dir, f"culture_c_quiz_{n_epoch:04d}_logp.dat") - - with open(file_name, "w") as logp_file: - while ( - valid_c_quizzes(quizzes_and_nb_correct_records, standard_validity).size(0) - < nb_to_create - ): - # Select a model at random to generate the new quizzes - - model_for_generation = models[torch.randint(len(models), (1,))] - - c_quizzes = quiz_machine.generate_quizzes( - nb_to_create, - model_for_generation=model_for_generation, - temperature=args.generation_temperature, - ) - - # if args.prediction_correctness: - - # else: - # logproba = quiz_machine.new(quiz_machine.size(0), len(models)) - # for q,l in zip(quizzes.split(args.batch_size), logits.split(args.batch_size)): - # for model in models: - # l[...] = F.cross_entropy(model(q)) - - c_quizzes = c_quizzes[quiz_machine.non_trivial(c_quizzes)] - - if c_quizzes.size(0) > 0: - nb_correct, seq_logproba = quiz_machine.compute_correctness( - c_quizzes, - models, - bidirectional_validation=args.bidirectional_validation, - deterministic_validation=args.deterministic_validation, - ) - - for n, l in zip(nb_correct, seq_logproba): - s = " ".join([str(x.item()) for x in l]) - logp_file.write(f"{n} {s}\n") - - if args.dirty_debug: - nb_correct = torch.randint( - len(models) + 1, nb_correct.size(), device=c_quizzes.device - ) - - quizzes_and_nb_correct_records.append((c_quizzes, nb_correct)) - - nv = F.one_hot(nb_correct, num_classes=len(models) + 1).sum(0) - nv = " ".join([str(x.item()) for x in nv]) - - nb_validated = valid_c_quizzes( - quizzes_and_nb_correct_records, standard_validity - ).size(0) - - log_string( - f"keep c_quizzes model {model_for_generation.id} kept {nv} nb_accumulated {nb_validated} / {nb_to_create}" - ) - - # store the new c_quizzes which have been validated - - new_c_quizzes = valid_c_quizzes(quizzes_and_nb_correct_records, standard_validity) - - quiz_machine.reverse_random_half_in_place(new_c_quizzes) - - quiz_machine.store_c_quizzes(new_c_quizzes[:nb_for_train], for_train=True) - quiz_machine.store_c_quizzes(new_c_quizzes[nb_for_train:], for_train=False) - - # save a bunch of images to investigate what quizzes with a - # certain nb of correct predictions look like - - for n in range(len(models) + 1): - s = ( - "_validated" - if n >= args.min_to_validate and n <= args.max_to_validate - else "" - ) - - q = valid_c_quizzes( - quizzes_and_nb_correct_records, criteria=lambda nb_correct: nb_correct == n - )[:72] - - quiz_machine.reverse_random_half_in_place(q) - - if q.size(0) > 0: - quiz_machine.save_quizzes( - args.result_dir, f"culture_c_quiz_{n_epoch:04d}_N{n}{s}", q - ) - - ###################################################################### models = [] @@ -643,6 +532,11 @@ if args.dirty_debug: nb_new_c_quizzes_for_train = 100 nb_new_c_quizzes_for_test = 10 + def standard_validity(logproba): + l = logproba.sort(dim=-1).values + return l[:, 0] < math.log(0.99) + + ###################################################################### for n_epoch in range(args.nb_epochs): @@ -652,7 +546,7 @@ for n_epoch in range(args.nb_epochs): log_string(f"current_test_accuracies {cta}") ################################################## - # Select, improve, and eval the worst model + # Select, improve, and eval the worst models ranked_models = sorted(models, key=lambda m: float(m.main_test_accuracy)) @@ -674,14 +568,12 @@ for n_epoch in range(args.nb_epochs): model.TRAINING_LOCK.release() ################################################## - # Replace a fraction of the w_quizzes with fresh ones + # Renew the train sets log_string( f"cache_w_quizzes contains {quiz_machine.problem.nb_cached_quizzes()} quizzes" ) - # Renew entirely the train set - for model in weakest_models: quiz_machine.renew_w_quizzes(model, args.nb_train_samples)