def _select_best(self, pop): """Select the best individuals of the population. :param pop: The population :type pop: Any iterable type :return: The best individuals found :rtype: :py:class:`~deap.tools.HallOfFame` of individuals """ hof = HallOfFame(self.pop_size) hof.update(pop) return hof
class Population(list): """ A collection of individuals """ INDIVIDUAL_CLASS = Individual POPULATION_SIZE = 100 CLONE_BEST = 5 MAX_MATE_ATTEMPTS = 10 MATE_MUTATE_CLONE = (80, 18, 2) def __init__(self, bset): self.bset = bset pop = [Population.INDIVIDUAL_CLASS(self.bset) for _ in range(self.POPULATION_SIZE)] super(Population, self).__init__(pop) self.stats = Statistics(lambda ind: ind.fitness.values) self.stats.register("avg", np.mean) self.stats.register("std", np.std) self.stats.register("min", np.min) self.stats.register("max", np.max) self.logbook = Logbook() self.logbook.header = ['gen'] + self.stats.fields self.hof = HallOfFame(1) self.generation = 0 # do an initial evaluation for ind in self: ind.fitness.values = ind.evaluate() def select(self, k): """Probablistic select *k* individuals among the input *individuals*. The list returned contains references to the input *individuals*. :param k: The number of individuals to select. :returns: A list containing k individuals. The individuals returned are randomly selected from individuals according to their fitness such that the more fit the individual the more likely that individual will be chosen. Less fit individuals are less likely, but still possibly, selected. """ # adjusted pop is a list of tuples (adjusted fitness, individual) adjusted_pop = [(1.0 / (1.0 + i.fitness.values[0]), i) for i in self] # normalised_pop is a list of tuples (float, individual) where the float indicates # a 'share' of 1.0 that the individual deserves based on it's fitness relative to # the other individuals. It is sorted so the best chances are at the front of the list. denom = sum([fit for fit, ind in adjusted_pop]) normalised_pop = [(fit / denom, ind) for fit, ind in adjusted_pop] normalised_pop = sorted(normalised_pop, key=lambda i: i[0], reverse=True) # randomly select with a fitness bias # FIXME: surely this can be optimized? selected = [] for x in range(k): rand = random.random() accumulator = 0.0 for share, ind in normalised_pop: accumulator += share if rand <= accumulator: selected.append(ind) break if len(selected) == 1: return selected[0] else: return selected def evolve(self): """ Evolve this population by one generation """ self.logbook.record(gen=self.generation, **self.stats.compile(self)) self.hof.update(self) print(self.logbook.stream) # the best x of the population are cloned directly into the next generation offspring = self[:self.CLONE_BEST] # rest of the population clone, mate, or mutate at random for idx in range(len(self) - self.CLONE_BEST): # decide how to alter this individual rand = random.randint(0,100) for _ in range(0, self.MAX_MATE_ATTEMPTS): try: if rand < self.MATE_MUTATE_CLONE[0]: # MATE/CROSSOVER receiver, contributor = self.select(2) child = receiver.clone() child.mate(contributor) break elif rand < (self.MATE_MUTATE_CLONE[0] + self.MATE_MUTATE_CLONE[1]): # MUTATE ind = self.select(1) child = ind.clone() child.mutate() break else: child = self.select(1).clone() break except BirthError: continue # generate new blood when reproduction fails so badly else: child = Population.INDIVIDUAL_CLASS(self.bset) offspring.append(child) self[:] = offspring self.generation += 1 # evaluate every individual and sort for ind in self: if not len(ind.fitness.values): ind.fitness.values = ind.evaluate() self.sort(key=lambda i: i.fitness.values[0])
def evolution(env: Environment, number_of_rays: int, ray_distribution: str, angle_lower_bound: int, angle_upper_bound: int, length_lower_bound: int, length_upper_bound: int, no_of_reflective_segments: int, distance_limit: int, length_limit: int, population_size: int, number_of_generations: int, xover_prob: float, mut_angle_prob: float, mut_length_prob: float, shift_segment_prob: float, rotate_segment_prob: float, resize_segment_prob: float): # Initiating evolutionary algorithm creator.create("Fitness", base.Fitness, weights=(1.0, )) creator.create("Individual", Component3D, fitness=creator.Fitness) toolbox = base.Toolbox() toolbox.register("individual", creator.Individual, number_of_rays=number_of_rays, ray_distribution=ray_distribution, base_length=env.road_length, base_width=env.road_width) toolbox.register("population", tools.initRepeat, list, toolbox.individual) toolbox.register("select", tools.selTournament, tournsize=2) # Initiating first population pop = toolbox.population(n=population_size) # Evaluating fitness fitnesses = [] for item in pop: fitnesses.append(evaluate(item, env)) for ind, fit in zip(pop, fitnesses): ind.fitness = fit # Rendering individuals in initial population as images for i in range(len(pop)): draw_road(pop[i], f"{i}") print("Drawing initial population finished") stats_line = f"0, {max(fitnesses)}, {sum(fitnesses)/population_size}" log_stats_init(f"stats", stats_line) # Initiating elitism hof = HallOfFame(1) print("Start of evolution") # Begin the evolution for g in range(number_of_generations): # A new generation print(f"-- Generation {g} --") # Select the next generation individuals offspring = toolbox.select(pop, len(pop)) # Clone the selected individuals offspring = list(map(toolbox.clone, offspring)) # Apply crossover and mutation on the offspring for child1, child2 in zip(offspring[::2], offspring[1::2]): # cross two individuals with probability xover_prob if random.random() < xover_prob: mate(child1, child2) # fitness values of the children must be recalculated later child1.fitness = 0 child2.fitness = 0 for mutant in offspring: if env.configuration == "multiple free": if random.random() < shift_segment_prob: mutant.reflective_segments = shift_one_segment( mutant.reflective_segments, "x") mutant.fitness = 0 if random.random() < shift_segment_prob: mutant.reflective_segments = shift_one_segment( mutant.reflective_segments, "y") mutant.fitness = 0 if random.random() < rotate_segment_prob: mutant.reflective_segments = rotate_one_segment( mutant.reflective_segments) mutant.fitness = 0 if random.random() < resize_segment_prob: mutant.reflective_segments = resize_one_segment( mutant.reflective_segments) mutant.fitness = 0 if env.configuration == "two connected": if random.random() < mut_angle_prob: mutate_angle(mutant) mutant.fitness = 0 if random.random() < mut_length_prob: mutate_length(mutant, length_upper_bound, length_lower_bound) mutant.fitness = 0 # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if ind.fitness == 0] fitnesses = [] for item in invalid_ind: fitnesses.append(evaluate(item, env)) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness = fit print(f" Evaluated {len(invalid_ind)} individuals") # The population is entirely replaced by the offspring pop[:] = offspring hof.update(pop) # best_ind = tools.selBest(pop, 1)[0] best_ind = hof[0] fitnesses = [] for item in pop: fitnesses.append(evaluate(item, env)) print(fitnesses) stats_line = f"{g+1}, {best_ind.fitness}, {sum(fitnesses) / population_size}" log_stats_append(f"stats", stats_line) print(f"Best individual has fitness: {best_ind.fitness}") draw(best_ind, f"best{g}", env) print("-- End of (successful) evolution --") print("--")
def _genetic_algo(model, initial_pop, exp_ess, base_biomass, toolbox, GA_param, distance, processes, outputs): index = [i for i in initial_pop.index] print('made it to the GA') print(outputs) # Initial population evaluation pop = toolbox.population_guess() if 'history_name' in outputs.keys(): history.update(pop) print('I will evaluate the initial population') fits = toolbox.map(toolbox.evaluate, pop, initial_pop, model, base_biomass, exp_ess, distance, processes) print('Fitnesses that will be attributed to individuals %s' % (fits, )) for ind, fit in zip(pop, fits): ind.fitness.values = fit print('Finished evaluating initial population') # Record outputs if 'hall_of_fame_name' in outputs.keys(): # Hall of fame hof = HallOfFame(maxsize=outputs.get('hall_of_fame_size')) hof.update(pop) if 'logbook_name' in outputs.keys(): multi_level = pd.MultiIndex( levels=[['Fitness', 'Size'], ['Mean', 'Std', 'Max', 'Min', 'Sizes']], labels=[[ 0, 0, 0, 0, 1, ], [0, 1, 2, 3, 4]]) data_logbook = [] #valid_logbook = [] # The GA itself for g in range(GA_param.get('NGEN')): print('Starting generation %s' % (g, )) offspring = toolbox.select(pop, k=len(pop)) offspring = list(map(toolbox.clone, offspring)) for child1, child2 in zip(offspring[::2], offspring[1::2]): # Keep the parents for comparison parent1 = list(map(toolbox.clone, [child1]))[0] parent2 = list(map(toolbox.clone, [child2]))[0] rand_nb = random.random() if rand_nb < GA_param.get('CXPB'): print('doing a crossover') if child1 == child2: print('generating a new individual') l = toolbox.generate_new_individual( initial_pop.index, model, base_biomass) child2 = creator.Individual(l) if child2 == parent2: print('generating new individual didnt work') toolbox.mate(child1, child2) # Assess the efficiency of crossover if child1 == parent1 or child1 == parent2: print('crossover yielded identical individuals') l = toolbox.generate_new_individual( initial_pop.index, model, base_biomass) child2 = creator.Individual(l) toolbox.mate(child1, child2) else: print('crossover actually worked') del child1.fitness.values del child2.fitness.values for mutant in offspring: rand_nb = random.random() if rand_nb < GA_param.get('MUTPB'): toolbox.mutate(mutant) del mutant.fitness.values # The individuals that have been crossed or mutated will have an invalid fitness # and will need to be r-evaluated (saves on computing time) invalid_ind = [ind for ind in offspring if not ind.fitness.valid] print('I will evaluate %s invalid individuals, wish me luck!' % (len(invalid_ind), )) fitnesses = toolbox.map(toolbox.evaluate, invalid_ind, initial_pop, model, base_biomass, exp_ess, distance, processes) print(fitnesses) for ind, fit in zip(invalid_ind, fitnesses): print(sum(ind)) ind.fitness.values = fit print(" Evaluated %s individuals" % (len(invalid_ind), )) pop[:] = offspring # Save stats of the evolution print('Trying to compile stats') if 'logbook_name' in outputs.keys(): current_stats = record_stats(pop, g, multi_level) # Manually update the logbook data_logbook.append(current_stats) if 'hall_of_fame_name' in outputs.keys(): hof.update(pop) if 'history_name' in outputs.keys(): history.update(pop) # Outputs # Saving the logbook if 'logbook_name' in outputs.keys(): final_logbook = pd.concat(data_logbook) final_logbook.to_csv(outputs.get('logbook_name')) # Get the best individuals from the hall of fame (metabolites) if 'hall_of_fame_name' in outputs.keys(): hof_df = get_ind_composition(hof, index) hof_df.to_csv(outputs.get('hall_of_fame_name'))
class BaseLoop(Toolbox): """ Base loop for BGP. Examples:: if __name__ == "__main__": pset = SymbolSet() stop = lambda ind: ind.fitness.values[0] >= 0.880963 bl = BaseLoop(pset=pset, gen=10, pop=1000, hall=1, batch_size=40, re_hall=3, \n n_jobs=12, mate_prob=0.9, max_value=5, initial_min=1, initial_max=2, \n mutate_prob=0.8, tq=True, dim_type="coef", stop_condition=stop,\n re_Tree=0, store=False, random_state=1, verbose=True,\n stats={"fitness_dim_max": ["max"], "dim_is_target": ["sum"]},\n add_coef=True, inter_add=True, inner_add=False, cal_dim=True, vector_add=False,\n personal_map=False) bl.run() """ def __init__(self, pset, pop=500, gen=20, mutate_prob=0.5, mate_prob=0.8, hall=1, re_hall=1, re_Tree=None, initial_min=None, initial_max=3, max_value=5, scoring=(r2_score,), score_pen=(1,), filter_warning=True, cv=1, add_coef=True, inter_add=True, inner_add=False, vector_add=False, out_add=False, flat_add=False, cal_dim=False, dim_type=None, fuzzy=False, n_jobs=1, batch_size=40, random_state=None, stats=None, verbose=True, migrate_prob=0, tq=True, store=False, personal_map=False, stop_condition=None, details=False, classification=False, score_object="y", sub_mu_max=1, limit_type="h_bgp", batch_para=False): """ Parameters ---------- pset:SymbolSet the feature x and target y and others should have been added. pop: int number of population. gen: int number of generation. mutate_prob:float probability of mutate. mate_prob:float probability of mate(crossover). initial_max:int max initial size of expression when first producing. initial_min : None,int min initial size of expression when first producing. max_value:int max size of expression. limit_type: "height" or "length",","h_bgp" limitation type for max_value, but don't affect initial_max, initial_min. hall:int,>=1 number of HallOfFame (elite) to maintain. re_hall:None or int>=2 Notes: only valid when hall number of HallOfFame to add to next generation. re_Tree: int number of new features to add to next generation. 0 is false to add. personal_map:bool or "auto" "auto" is using 'premap' and with auto refresh the 'premap' with individual.\n True is just using constant 'premap'.\n False is just use the prob of terminals. scoring: list of Callable, default is [sklearn.metrics.r2_score,] See Also ``sklearn.metrics`` score_pen: tuple of 1, -1 or float but 0. >0 : max problem, best is positive, worse -np.inf. <0 : min problem, best is negative, worse np.inf. Notes: if multiply score method, the scores must be turn to same dimension in prepossessing or weight by score_pen. Because the all the selection are stand on the mean(w_i*score_i) Examples:: scoring = [r2_score,] score_pen= [1,] cv:sklearn.model_selection._split._BaseKFold,int the shuffler must be False, default=1 means no cv. filter_warning:bool filter warning or not. add_coef:bool add coef in expression or not. inter_add:bool add intercept constant or not. inner_add:bool add inner coefficients or not. out_add:bool add out coefficients or not. flat_add:bool add flat coefficients or not. n_jobs:int default 1, advise 6. batch_size:int default 40, depend of machine. random_state:int None,int. cal_dim:bool escape the dim calculation. dim_type:Dim or None or list of Dim "coef": af(x)+b. a,b have dimension,f(x)'s dimension is not dnan. \n "integer": af(x)+b. f(x) is with integer dimension. \n [Dim1,Dim2]: f(x)'s dimension in list. \n Dim: f(x) ~= Dim. (see fuzzy) \n Dim: f(x) == Dim. \n None: f(x) == pset.y_dim fuzzy:bool choose the dim with same base with dim_type, such as m,m^2,m^3. stats:dict details of logbook to show. \n Map:\n values = {"max": np.max, "mean": np.mean, "min": np.mean, "std": np.std, "sum": np.sum} keys = {\n "fitness": just see fitness[0], \n "fitness_dim_max": max problem, see fitness with demand dim,\n "fitness_dim_min": min problem, see fitness with demand dim,\n "dim_is_target": demand dim,\n "coef": dim is True, coef have dim, \n "integer": dim is integer, \n ... } if stats is None, default is: for cal_dim=True: stats = {"fitness_dim_max": ("max",), "dim_is_target": ("sum",)} for cal_dim=False stats = {"fitness": ("max",)} if self-definition, the key is func to get attribute of each ind. Examples:: def func(ind): return ind.fitness[0] stats = {func: ("mean",), "dim_is_target": ("sum",)} verbose:bool print verbose logbook or not. tq:bool print progress bar or not. store:bool or path bool or path. stop_condition:callable stop condition on the best ind of hall, which return bool,the true means stop loop. Examples:: def func(ind): c = ind.fitness.values[0]>=0.90 return c details:bool return expr and predict_y or not. classification: bool classification or not. score_object: score by y or delta y (for implicit function). """ super(BaseLoop, self).__init__() assert initial_max <= max_value, "the initial size of expression should less than max_value limitation" if cal_dim: assert all( [isinstance(i, Dim) for i in pset.dim_ter_con.values()]), \ "all import dim of pset should be Dim object." self.details = details self.max_value = max_value self.pop = pop self.gen = gen self.mutate_prob = mutate_prob self.mate_prob = mate_prob self.migrate_prob = migrate_prob self.verbose = verbose self.cal_dim = cal_dim self.re_hall = re_hall self.re_Tree = re_Tree self.store = store self.limit_type = limit_type self.data_all = [] self.personal_map = personal_map self.stop_condition = stop_condition self.population = [] self.rand_state = None self.random_state = random_state self.sub_mu_max = sub_mu_max self.population_next = [] self.cpset = CalculatePrecisionSet(pset, scoring=scoring, score_pen=score_pen, filter_warning=filter_warning, cal_dim=cal_dim, add_coef=add_coef, inter_add=inter_add, inner_add=inner_add, vector_add=vector_add, out_add=out_add, flat_add=flat_add, cv=cv, n_jobs=n_jobs, batch_size=batch_size, tq=tq, fuzzy=fuzzy, dim_type=dim_type, details=details, classification=classification, score_object=score_object, batch_para=batch_para ) Fitness_ = newclass.create("Fitness_", Fitness, weights=score_pen) self.PTree = newclass.create("PTrees", SymbolTree, fitness=Fitness_) # def produce if initial_min is None: initial_min = 2 self.register("genGrow", genGrow, pset=self.cpset, min_=initial_min, max_=initial_max + 1, personal_map=self.personal_map) self.register("genFull", genFull, pset=self.cpset, min_=initial_min, max_=initial_max + 1, personal_map=self.personal_map) self.register("genHalf", genGrow, pset=self.cpset, min_=initial_min, max_=initial_max + 1, personal_map=self.personal_map) self.register("gen_mu", genGrow, min_=1, max_=self.sub_mu_max + 1, personal_map=self.personal_map) # def selection self.register("select", selTournament, tournsize=2) self.register("selKbestDim", selKbestDim, dim_type=self.cpset.dim_type, fuzzy=self.cpset.fuzzy) self.register("selBest", selBest) self.register("mate", cxOnePoint) # def mutate self.register("mutate", mutUniform, expr=self.gen_mu, pset=self.cpset) self.decorate("mate", staticLimit(key=operator.attrgetter(limit_type), max_value=self.max_value)) self.decorate("mutate", staticLimit(key=operator.attrgetter(limit_type), max_value=self.max_value)) if stats is None: if cal_dim: if score_pen[0] > 0: stats = {"fitness_dim_max": ("max",), "dim_is_target": ("sum",)} else: stats = {"fitness_dim_min": ("min",), "dim_is_target": ("sum",)} else: if score_pen[0] > 0: stats = {"fitness": ("max",)} else: stats = {"fitness": ("min",)} self.stats = Statis_func(stats=stats) logbook = Logbook() logbook.header = ['gen'] + (self.stats.fields if self.stats else []) self.logbook = logbook if hall is None: hall = 1 self.hall = HallOfFame(hall) if re_hall is None: self.re_hall = None else: if re_hall == 1 or re_hall == 0: print("re_hall should more than 1") re_hall = 2 assert re_hall >= hall, "re_hall should more than hall" self.re_hall = HallOfFame(re_hall) def varAnd(self, *arg, **kwargs): return varAnd(*arg, **kwargs) def to_csv(self, data_all): """store to csv""" if self.store: if isinstance(self.store, str): path = self.store else: path = os.getcwd() file_new_name = "_".join((str(self.pop), str(self.gen), str(self.mutate_prob), str(self.mate_prob), str(time.time()))) try: st = Store(path) st.to_csv(data_all, file_new_name, transposition=True) print("store data to ", path, file_new_name) except (IOError, PermissionError): st = Store(os.getcwd()) st.to_csv(data_all, file_new_name, transposition=True) print("store data to ", os.getcwd(), file_new_name) def maintain_halls(self, population): """maintain the best p expression""" if self.re_hall is not None: maxsize = max(self.hall.maxsize, self.re_hall.maxsize) if self.cal_dim: inds_dim = self.selKbestDim(population, maxsize) else: inds_dim = self.selBest(population, maxsize) self.hall.update(inds_dim) self.re_hall.update(inds_dim) sole_inds = [i for i in self.re_hall.items if i not in inds_dim] inds_dim.extend(sole_inds) else: if self.cal_dim: inds_dim = self.selKbestDim(population, self.hall.maxsize) else: inds_dim = self.selBest(population, self.hall.maxsize) self.hall.update(inds_dim) inds_dim = [] inds_dim = copy.deepcopy(inds_dim) return inds_dim def re_add(self): """add the expression as a feature""" if self.hall.items and self.re_Tree: it = self.hall.items indo = it[random.choice(len(it))] ind = copy.deepcopy(indo) inds = ind.depart() if not inds: pass else: inds = [self.cpset.calculate_detail(indi) for indi in inds] le = min(self.re_Tree, len(inds)) indi = inds[random.choice(le)] self.cpset.add_tree_to_features(indi) def re_fresh_by_name(self, *arr): re_name = ["mutate", "genGrow", "genFull", "genHalf"] if len(arr) > 0: re_name.extend(arr) self.refresh(re_name, pset=self.cpset) # for i in re_name + ["mate"]: # don‘t del this # self.decorate(i, staticLimit(key=operator.attrgetter("height"), max_value=2 * (self.max_value + 1))) def top_n(self, n=10, gen=-1, key="value", filter_dim=True, ascending=False): """ Return the best n results. Note: Only valid in ``store=True``. Parameters ---------- n:int n. gen: the generation, default is -1. key: str sort keys, default is "values". filter_dim: filter no-dim expressions or not. ascending: reverse. Returns ------- top n results. pd.DataFrame """ import pandas as pd if self.store == "False": raise TypeError("Only valid with store=True") data = self.data_all data = pd.DataFrame(data) if gen == -1: gen = max(data["gen"]) data = data[data["gen"] == gen] if filter_dim: data = data[data["dim_score"] == 1] data = data.drop_duplicates(['expr'], keep="first") if key is not None: data[key] = data[key].str.replace("(", "") data[key] = data[key].str.replace(")", "") data[key] = data[key].str.replace(",", "") try: data[key] = data[key].astype(float) except ValueError: raise TypeError("check this key column can be translated into float") data = data.sort_values(by='value', ascending=ascending).iloc[:n, :] return data def check_height_length(self, pop, site=""): old = len(pop) if self.limit_type == 'height': pop = [i for i in pop if i.height <= self.max_value] elif self.limit_type == 'length': pop = [i for i in pop if i.length <= self.max_value] else: pop = [i for i in pop if i.h_bgp <= self.max_value] new = len(pop) if old == new: pass else: if site != "": print(site) # raise TypeError index = random.randint(0, new, old - new) pop.extend([pop[i] for i in index]) return pop def run(self, warm_start=False, new_gen=None): """ Parameters ---------- warm_start:bool warm_start from last result. new_gen: new generations for warm_startm, default is the initial generations. """ # 1.generate################################################################### if warm_start is False: random.seed(self.random_state) population = [self.PTree(self.genHalf()) for _ in range(self.pop)] gen_i = 0 gen = self.gen else: assert self.population_next != [] random.set_state(self.rand_state) population = self.population_next gen_i = self.gen_i self.re_fresh_by_name() if new_gen: gen = gen_i + new_gen else: gen = gen_i + self.gen for gen_i in range(gen_i + 1, gen + 1): population_old = copy.deepcopy(population) # 2.evaluate############################################################### invalid_ind_score = self.cpset.parallelize_score(population_old) for ind, score in zip(population_old, invalid_ind_score): ind.fitness.values = tuple(score[0]) ind.y_dim = score[1] ind.dim_score = score[2] ind.coef_expr = score[3] ind.coef_pre_y = score[4] population = population_old # 3.log################################################################### # 3.1.log-print############################## record = self.stats.compile(population) if self.stats else {} self.logbook.record(gen=gen_i, **record) if self.verbose: print(self.logbook.stream) # 3.2.log-store############################## if self.store: datas = [{"gen": gen_i, "name": str(pop_i), "expr": str([pop_i.coef_expr]), "value": str(pop_i.fitness.values), "dimension": str(pop_i.y_dim), "dim_score": pop_i.dim_score} for pop_i in population] self.data_all.extend(datas) self.population = copy.deepcopy(population) # 3.3.log-hall############################### inds_dim = self.maintain_halls(population) # 4.refresh################################################################ # 4.1.re_update the premap ################## if self.personal_map == "auto": [self.cpset.premap.update(indi, self.cpset) for indi in inds_dim] # 4.2.re_add_tree and refresh pset########### if self.re_Tree: self.re_add() self.re_fresh_by_name() # 6.next generation !!!!####################################################### # selection and mutate,mate,migration population = self.select(population, int((1 - self.migrate_prob) * len(population)) - len(inds_dim)) offspring = self.varAnd(population, self, self.mate_prob, self.mutate_prob) offspring.extend(inds_dim) migrate_pop = [self.PTree(self.genFull()) for _ in range(int(self.migrate_prob * len(population)))] population[:] = offspring + migrate_pop population = self.check_height_length(population) # 5.break####################################################### if self.stop_condition is not None: if self.stop_condition(self.hall.items[0]): break # 7 freeze ################################################### self.rand_state = random.get_state() self.population_next = population self.gen_i = gen_i # final.store##################################################################### if self.store: self.to_csv(self.data_all) self.hall.items = [self.cpset.calculate_detail(indi) for indi in self.hall.items] return self.hall
class GeneticAlgorithmOptimizer(Optimizer): """ Implements evolutionary algorithm :param ~l2l.utils.trajectory.Trajectory traj: Use this trajectory to store the parameters of the specific runs. The parameters should be initialized based on the values in `parameters` :param optimizee_create_individual: Function that creates a new individual :param optimizee_fitness_weights: Fitness weights. The fitness returned by the Optimizee is multiplied by these values (one for each element of the fitness vector) :param parameters: Instance of :func:`~collections.namedtuple` :class:`.GeneticAlgorithmOptimizer` containing the parameters needed by the Optimizer """ def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None): super().__init__( traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func __, self.optimizee_individual_dict_spec = dict_to_list( optimizee_create_individual(), get_dict_spec=True) traj.f_add_parameter('seed', parameters.seed, comment='Seed for RNG') traj.f_add_parameter('popsize', parameters.popsize, comment='Population size') # 185 traj.f_add_parameter('CXPB', parameters.CXPB, comment='Crossover term') traj.f_add_parameter('MUTPB', parameters.MUTPB, comment='Mutation probability') traj.f_add_parameter('n_iteration', parameters.NGEN, comment='Number of generations') traj.f_add_parameter('indpb', parameters.indpb, comment='Mutation parameter') traj.f_add_parameter('tournsize', parameters.tournsize, comment='Selection parameter') # ------- Create and register functions with DEAP ------- # # delay_rate, slope, std_err, max_fraction_active creator.create("FitnessMax", base.Fitness, weights=self.optimizee_fitness_weights) creator.create("Individual", list, fitness=creator.FitnessMax) toolbox = base.Toolbox() # Structure initializers toolbox.register("individual", tools.initIterate, creator.Individual, lambda: dict_to_list(optimizee_create_individual())) toolbox.register("population", tools.initRepeat, list, toolbox.individual) # Operator registering # This complex piece of code is only necessary because we're using the # DEAP framework and would like to decorate the DEAP mutation operator def bounding_decorator(func): def bounding_wrapper(*args, **kwargs): if self.optimizee_bounding_func is None: return func(*args, **kwargs) else: # Deap Functions modify individuals in-place, Hence we must do the same result_individuals_deap = func(*args, **kwargs) result_individuals = [ list_to_dict(x, self.optimizee_individual_dict_spec) for x in result_individuals_deap ] bounded_individuals = [ self.optimizee_bounding_func(x) for x in result_individuals ] for i, deap_indiv in enumerate(result_individuals_deap): deap_indiv[:] = dict_to_list(bounded_individuals[i]) print("Bounded Individual: {}".format(bounded_individuals)) return result_individuals_deap return bounding_wrapper toolbox.register("mate", tools.cxBlend, alpha=parameters.matepar) toolbox.decorate("mate", bounding_decorator) toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=parameters.mutpar, indpb=traj.indpb) toolbox.decorate("mutate", bounding_decorator) toolbox.register("select", tools.selTournament, tournsize=traj.tournsize) # ------- Initialize Population and Trajectory -------- # # NOTE: The Individual object implements the list interface. self.pop = toolbox.population(n=traj.popsize) self.eval_pop_inds = [ind for ind in self.pop if not ind.fitness.valid] self.eval_pop = [ list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in self.eval_pop_inds ] self.g = 0 # the current generation self.toolbox = toolbox # the DEAP toolbox self.hall_of_fame = HallOfFame(20) self._expand_trajectory(traj) def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ CXPB, MUTPB, NGEN = traj.CXPB, traj.MUTPB, traj.n_iteration logger.info(" Evaluating %i individuals" % len(fitnesses_results)) #************************************************************************************************************** # Storing run-information in the trajectory # Reading fitnesses and performing distribution update #************************************************************************************************************** for run_index, fitness in fitnesses_results: # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx traj.f_add_result('$set.$.individual', self.eval_pop[ind_index]) traj.f_add_result('$set.$.fitness', fitness) # Use the ind_idx to update the fitness individual = self.eval_pop_inds[ind_index] individual.fitness.values = fitness traj.v_idx = -1 # set the trajectory back to default logger.info("-- End of generation {} --".format(self.g)) best_inds = tools.selBest(self.eval_pop_inds, 2) for best_ind in best_inds: print("Best individual is %s, %s" % (list_to_dict(best_ind, self.optimizee_individual_dict_spec), best_ind.fitness.values)) self.hall_of_fame.update(self.eval_pop_inds) logger.info("-- Hall of fame --") for hof_ind in tools.selBest(self.hall_of_fame, 2): logger.info( "HOF individual is %s, %s" % (list_to_dict(hof_ind, self.optimizee_individual_dict_spec), hof_ind.fitness.values)) # ------- Create the next generation by crossover and mutation -------- # if self.g < NGEN - 1: # not necessary for the last generation # Select the next generation individuals offspring = self.toolbox.select(self.pop, len(self.pop)) # Clone the selected individuals offspring = list(map(self.toolbox.clone, offspring)) # Apply crossover and mutation on the offspring for child1, child2 in zip(offspring[::2], offspring[1::2]): if random.random() < CXPB: self.toolbox.mate(child1, child2) del child1.fitness.values del child2.fitness.values for mutant in offspring: if random.random() < MUTPB: self.toolbox.mutate(mutant) del mutant.fitness.values if len(set(map(tuple, offspring))) < len(offspring): logger.info("Mutating more") for i, o1 in enumerate(offspring[:-1]): for o2 in offspring[i + 1:]: if tuple(o1) == tuple(o2): if random.random() < 0.8: self.toolbox.mutate(o2) del o2.fitness.values # The population is entirely replaced by the offspring self.pop[:] = offspring self.eval_pop_inds = [ ind for ind in self.pop if not ind.fitness.valid ] self.eval_pop = [ list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in self.eval_pop_inds ] self.g += 1 # Update generation counter self._expand_trajectory(traj) def end(self): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.end` """ # ------------ Finished all runs and print result --------------- # logger.info("-- End of (successful) evolution --") best_inds = tools.selBest(self.pop, 10) for best_ind in best_inds: logger.info("Best individual is %s, %s" % (best_ind, best_ind.fitness.values)) logger.info("-- Hall of fame --") for hof_ind in self.hall_of_fame: logger.info("HOF individual is %s, %s" % (hof_ind, hof_ind.fitness.values))
class NSGA2StatisticsCallback(object): ''' Helper class for tracking statistics about the progression of the optimization ''' def __init__(self, weights=(), nr_of_generations=None, crossover_rate=None, mutation_rate=None, pop_size=None, caching=False): ''' :param weights: the weights on the outcomes :param nr_of_generations: the number of generations :param crossover_rate: the crossover rate :param mutation_rate: the mutation rate :param caching: parameter controling wether a list of tried solutions should be kept ''' self.stats = [] if len(weights)>1: self.hall_of_fame = ParetoFront(similar=compare) else: self.hall_of_fame = HallOfFame(pop_size) if caching: self.tried_solutions = TriedSolutions() self.change = [] self.weights = weights self.nr_of_generations = nr_of_generations self.crossover_rate = crossover_rate self.mutation_rate=mutation_rate self.precision = "{0:.%if}" % 2 def __get_hof_in_array(self): a = [] for entry in self.hall_of_fame: a.append(entry.fitness.values) return np.asarray(a) def std(self, hof): return np.std(hof, axis=0) def mean(self, hof): return np.mean(hof, axis=0) def minima(self, hof): return np.min(hof, axis=0) def maxima(self, hof): return np.max(hof, axis=0) def log_stats(self, gen): functions = {"minima":self.minima, "maxima":self.maxima, "std":self.std, "mean":self.mean,} kargs = {} hof = self.__get_hof_in_array() line = " ".join("{%s:<8}" % name for name in sorted(functions.keys())) for name in sorted(functions.keys()): function = functions[name] kargs[name] = "[%s]" % ", ".join(map(self.precision.format, function(hof))) line = line.format(**kargs) line = "generation %s: " %gen + line ema_logging.info(line) def __call__(self, population): self.change.append(self.hall_of_fame.update(population)) for entry in population: self.stats.append(entry.fitness.values) for entry in population: try: self.tried_solutions[entry] = entry except AttributeError: break
class GeneticAlgorithmOptimizer(Optimizer): """ Implements evolutionary algorithm :param ~l2l.utils.trajectory.Trajectory traj: Use this trajectory to store the parameters of the specific runs. The parameters should be initialized based on the values in `parameters` :param optimizee_create_individual: Function that creates a new individual :param optimizee_fitness_weights: Fitness weights. The fitness returned by the Optimizee is multiplied by these values (one for each element of the fitness vector) :param parameters: Instance of :func:`~collections.namedtuple` :class:`.GeneticAlgorithmOptimizer` containing the parameters needed by the Optimizer """ def __init__(self, traj, optimizee_create_individual, optimizee_fitness_weights, parameters, optimizee_bounding_func=None, percent_hall_of_fame=0.2, percent_elite=0.4,): super().__init__(traj, optimizee_create_individual=optimizee_create_individual, optimizee_fitness_weights=optimizee_fitness_weights, parameters=parameters, optimizee_bounding_func=optimizee_bounding_func) self.optimizee_bounding_func = optimizee_bounding_func __, self.optimizee_individual_dict_spec = \ dict_to_list(optimizee_create_individual(), get_dict_spec=True) self.optimizee_create_individual = optimizee_create_individual # if not len(traj.individuals): popsize = parameters.popsize traj.f_add_parameter('seed', parameters.seed, comment='Seed for RNG') traj.f_add_parameter('popsize', popsize, comment='Population size') # 185 traj.f_add_parameter('CXPB', parameters.CXPB, comment='Crossover term') traj.f_add_parameter('MUTPB', parameters.MUTPB, comment='Mutation probability') traj.f_add_parameter('n_iteration', parameters.NGEN, comment='Number of generations') traj.f_add_parameter('indpb', parameters.indpb, comment='Mutation parameter') traj.f_add_parameter('tournsize', parameters.tournsize, comment='Selection parameter') # ------- Create and register functions with DEAP ------- # # delay_rate, slope, std_err, max_fraction_active creator.create("FitnessMax", base.Fitness, weights=self.optimizee_fitness_weights) creator.create("Individual", list, fitness=creator.FitnessMax) toolbox = base.Toolbox() # Structure initializers toolbox.register("individual", tools.initIterate, creator.Individual, lambda: dict_to_list(optimizee_create_individual())) toolbox.register("population", tools.initRepeat, list, toolbox.individual) # Operator registering # This complex piece of code is only necessary because we're using the # DEAP framework and would like to decorate the DEAP mutation operator def bounding_decorator(func): def bounding_wrapper(*args, **kwargs): if self.optimizee_bounding_func is None: return func(*args, **kwargs) else: # Deap Functions modify individuals in-place, Hence we must do the same result_individuals_deap = func(*args, **kwargs) result_individuals = [list_to_dict(x, self.optimizee_individual_dict_spec) for x in result_individuals_deap] bounded_individuals = [self.optimizee_bounding_func(x) for x in result_individuals] for i, deap_indiv in enumerate(result_individuals_deap): deap_indiv[:] = dict_to_list(bounded_individuals[i]) # print("Bounded Individual: {}".format(bounded_individuals)) return result_individuals_deap return bounding_wrapper toolbox.register("mate", tools.cxBlend, alpha=parameters.matepar) toolbox.decorate("mate", bounding_decorator) toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=parameters.mutpar, indpb=traj.indpb) toolbox.decorate("mutate", bounding_decorator) toolbox.register("select", tools.selTournament, tournsize=traj.tournsize) # ------- Initialize Population and Trajectory -------- # # NOTE: The Individual object implements the list interface. self.pop = toolbox.population(n=traj.popsize) # traj.individuals.clear() self.n_hof = int(max(percent_hall_of_fame * traj.popsize, 2)) self.n_bobs = int(max(1, percent_elite * self.n_hof)) self.hall_of_fame = HallOfFame(self.n_hof) if len(traj.individuals): self._load_last_trajectories(traj) self.hall_of_fame.update(self.pop) bob_inds = tools.selBest(self.hall_of_fame, self.n_bobs) for hof_ind in bob_inds: sind = str_ind( list_to_dict(hof_ind, self.optimizee_individual_dict_spec) ) logger.info("HOF individual is %s, \n%s" % ( sind, hof_ind.fitness.values)) # print("HOF individual is %s, %s" % ( # hof_ind, hof_ind.fitness.values)) print("Starting BoB individual is %s, \n%s" % ( sind, hof_ind.fitness.values)) self._delete_initial_fitnesses() self.eval_pop_inds = [ind for ind in self.pop if not ind.fitness.valid] self.eval_pop = [list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in self.eval_pop_inds] if len(traj.individuals): g = np.max(list(traj.individuals.keys())) else: g = 0 self.g = g # the current generation self.toolbox = toolbox # the DEAP toolbox self._expand_trajectory(traj) x = traj def _delete_initial_fitnesses(self): for ind in self.pop: del ind.fitness.values def _load_last_trajectories(self, traj): if not len(traj.individuals): return g = np.max(list(traj.individuals.keys())) res = traj.results._data['all_results']._data[g] for idx, ind in enumerate(traj.individuals[g]): for k_idx, k in enumerate(sorted(ind.keys)): sk = k.split('.')[-1] v = getattr(ind, sk) self.pop[idx][k_idx] = v self.pop[idx].fitness.values = res[idx][1] x = self.pop[idx] # del self.pop[idx].fitness.values def post_process(self, traj, fitnesses_results): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.post_process` """ def to_fit(ind): return np.dot(ind.fitness.values, ind.fitness.weights) def spawn(): x = self.optimizee_create_individual() return dict_to_list(self.optimizee_bounding_func(x)) CXPB, MUTPB, NGEN = traj.CXPB, traj.MUTPB, traj.n_iteration logger.info(" Evaluating %i individuals" % len(fitnesses_results)) print(" Evaluating %i individuals" % len(fitnesses_results)) #****************************************************************** # Storing run-information in the trajectory # Reading fitnesses and performing distribution update #****************************************************************** # print("self.g = {}".format(self.g)) # print("len(self.eval_pop_inds) = {}".format(len(self.eval_pop_inds))) # print("len(self.eval_pop) = {}".format(len(self.eval_pop))) # print("len(fitnesses_results) = {}".format(len(fitnesses_results))) for run_index, fitness in fitnesses_results: # We need to convert the current run index into an ind_idx # (index of individual within one generation) traj.v_idx = run_index ind_index = traj.par.ind_idx traj.f_add_result('$set.$.individual', self.eval_pop[ind_index]) traj.f_add_result('$set.$.fitness', fitness) # Use the ind_idx to update the fitness individual = self.eval_pop_inds[ind_index] individual.fitness.values = fitness traj.v_idx = -1 # set the trajectory back to default logger.info("-- End of generation {} --".format(self.g)) print("-- End of generation {} --".format(self.g)) # best_inds = tools.selBest(self.eval_pop_inds, 2) # for best_ind in best_inds: # print("Best individual is %s, %s" % ( # list_to_dict(best_ind, self.optimizee_individual_dict_spec), # best_ind.fitness.values)) # add the bestest individuals this generation to HoF self.hall_of_fame.update(self.eval_pop_inds) logger.info("-- Hall of fame --") # n_bobs = self.n_bobs # bob_inds = tools.selBest(self.hall_of_fame, n_bobs) n_bobs = self.n_hof bob_inds = tools.selBest(self.hall_of_fame, n_bobs) for hof_ind in self.hall_of_fame: sind = str_ind( list_to_dict(hof_ind, self.optimizee_individual_dict_spec) ) logger.info("BoB individual is %s,\n %s" % ( sind, hof_ind.fitness.values)) # print("HOF individual is %s, %s" % ( # hof_ind, hof_ind.fitness.values)) print("BoB individual is %s,\n %s" % ( sind, hof_ind.fitness.values)) #bob_inds = list(map(self.toolbox.clone, bob_inds)) bob_inds = list(map(self.toolbox.clone, self.hall_of_fame)) # ------- Create the next generation by crossover and mutation -------- # if self.g < NGEN - 1: # not necessary for the last generation # Select the next generation individuals # Tournament of population - a list of "pointers" offspring = self.toolbox.select(self.pop, len(self.pop)) # Clone the selected individuals offspring = list(map(self.toolbox.clone, offspring)) #sorts small to big #switch worst-good with best of best #ascending (worst to best) offsp_ids = np.argsort([to_fit(o) for o in offspring]) #descending (best to worst) bob_ids = np.argsort([to_fit(o) for o in bob_inds])[::-1] max_score = to_fit(bob_inds[bob_ids[0]]) min_score = 0.5 * max_score for i in range(n_bobs): off_i = int(offsp_ids[i]) bob_i = int(bob_ids[i]) off_f = to_fit(offspring[off_i]) bob_f = to_fit(bob_inds[bob_i]) if bob_f > off_f: logger.info("Inserting BoB {} to population".format(i+1)) offspring[off_i][:] = bob_inds[bob_i] # Apply crossover and mutation on the offspring for child1, child2 in zip(offspring[::2], offspring[1::2]): f1, f2 = to_fit(child1), to_fit(child2) if random.random() < CXPB: #if both parents are really unfit, replace them with a new couple if f1 <= min_score and f2 <= min_score: logger.info("Both parents had a low score") child1[:] = spawn() child2[:] = spawn() else: self.toolbox.mate(child1, child2) del child1.fitness.values del child2.fitness.values for mutant in offspring[:]: if random.random() < MUTPB: # f = to_fit(mutant) if mutant.fitness.valid else None # print("f = {}".format(f)) # if this was an unfit individual, replace with a "foreigner" # if f is not None and f <= min_score: # logger.info("Mutant had a really low score") # mutant[:] = spawn() # else: self.toolbox.mutate(mutant) del mutant.fitness.values if len(set(map(tuple, offspring))) < len(offspring): logger.info("Mutating more") for i, o1 in enumerate(offspring[:-1]): for o2 in offspring[i+1:]: if tuple(np.round(o1, decimals=4)) == tuple(np.round(o2, decimals=4)): if random.random() < 0.8: self.toolbox.mutate(o2) #o2[:] = spawn() del o2.fitness.values # off_ids = np.random.choice(len(offspring), size=n_bobs, replace=False) # for i in range(n_bobs): # # off_i = int(offsp_ids[i]) # off_i = int(off_ids[i]) # bob_i = int(bob_ids[i]) # logger.info("Inserting BoB {} to population".format(i+1)) # offspring[off_i][:] = bob_inds[bob_i] # del offspring[off_i].fitness.values # The population is entirely replaced by the offspring self.pop[:] = offspring self.eval_pop_inds[:] = [ind for ind in self.pop if not ind.fitness.valid] self.eval_pop[:] = [list_to_dict(ind, self.optimizee_individual_dict_spec) for ind in self.eval_pop_inds] # print("self.g = {}".format(self.g)) # print("len(self.eval_pop_inds) = {}".format(len(self.eval_pop_inds))) # print("len(self.eval_pop) = {}".format(len(self.eval_pop))) self.g += 1 # Update generation counter if len(self.eval_pop) == 0 and self.g < (NGEN - 1): raise Exception("No more mutants to evaluate where generated. " "Increasing population size may help.") self._expand_trajectory(traj) # print("self.g = {}".format(self.g)) # print("len(self.eval_pop_inds) = {}".format(len(self.eval_pop_inds))) # print("len(self.eval_pop) = {}".format(len(self.eval_pop))) def end(self, traj): """ See :meth:`~l2l.optimizers.optimizer.Optimizer.end` """ # ------------ Finished all runs and print result --------------- # logger.info("-- End of (successful) evolution --") best_inds = tools.selBest(self.pop, 10) for best_ind in best_inds: logger.info("Best individual is %s, %s" % (best_ind, best_ind.fitness.values)) logger.info("-- Hall of fame --") for hof_ind in self.hall_of_fame: logger.info("HOF individual is %s, %s" % (hof_ind, hof_ind.fitness.values))
class BaseLoop(Toolbox): """Base loop""" def __init__(self, pset, pop=500, gen=20, mutate_prob=0.5, mate_prob=0.8, hall=1, re_hall=None, re_Tree=None, initial_min=None, initial_max=3, max_value=5, scoring=(r2_score,), score_pen=(1,), filter_warning=True, cv=1, add_coef=True, inter_add=True, inner_add=False, vector_add=False, cal_dim=False, dim_type=None, fuzzy=False, n_jobs=1, batch_size=40, random_state=None, stats=None, verbose=True, tq=True, store=False, personal_map=False, stop_condition=None): """ Parameters ---------- pset:SymbolSet the feature x and traget y and others should have been added. pop:int number of popolation gen:int number of generation mutate_prob:float probability of mutate mate_prob:float probability of mate(crossover) initial_max:int max initial size of expression when first producing. initial_min : None,int max initial size of expression when first producing. max_value:int max size of expression hall:int,>=1 number of HallOfFame(elite) to maintain re_hall:None or int>=2 Notes: only vaild when hall number of HallOfFame to add to next generation. re_Tree: int number of new features to add to next generation. 0 is false to add. personal_map:bool or "auto" "auto" is using premap and with auto refresh the premap with individual.\n True is just using constant premap.\n False is just use the prob of terminals. scoring: list of Callbale, default is [sklearn.metrics.r2_score,] See Also sklearn.metrics score_pen: tuple of 1, -1 or float but 0. >0 : max problem, best is positive, worse -np.inf <0 : min problem, best is negative, worse np.inf Notes: if multiply score method, the scores must be turn to same dimension in preprocessing or weight by score_pen. Because the all the selection are stand on the mean(w_i*score_i) Examples: [r2_score] is [1], cv=int,sklearn.model_selection._split._BaseKFold default =1, means not cv filter_warning:bool filter warning or not add_coef:bool add coef in expression or not. inter_add:bool add intercept constant or not inner_add:bool dd inner coeffcients or not n_jobs:int default 1, advise 6 batch_size:int default 40, depend of machine random_state:int None,int cal_dim:bool excape the dim calculation dim_type:Dim or None or list of Dim "coef": af(x)+b. a,b have dimension,f(x) is not dnan. \n "integer": af(x)+b. f(x) is interger dimension. \n [Dim1,Dim2]: f(x) in list. \n Dim: f(x) ~= Dim. (see fuzzy) \n Dim: f(x) == Dim. \n None: f(x) == pset.y_dim fuzzy:bool choose the dim with same base with dim_type,such as m,m^2,m^3. stats:dict details of logbook to show. \n Map:\n values = {"max": np.max, "mean": np.mean, "min": np.mean, "std": np.std, "sum": np.sum} keys = {\n "fitness": just see fitness[0], \n "fitness_dim_max": max problem, see fitness with demand dim,\n "fitness_dim_min": min problem, see fitness with demand dim,\n "dim_is_target": demand dim,\n "coef": dim is true, coef have dim, \n "integer": dim is integer, \n ... } if stats is None, default is :\n stats = {"fitness_dim_max": ("max",), "dim_is_target": ("sum",)} for cal_dim=True stats = {"fitness": ("max",)} for cal_dim=False if self-definition, the key is func to get attribute of each ind./n Examples: def func(ind):\n return ind.fitness[0] stats = {func: ("mean",), "dim_is_target": ("sum",)} verbose:bool print verbose logbook or not tq:bool print progress bar or not store:bool or path bool or path stop_condition:callable stop condition on the best ind of hall, which return bool,the true means stop loop. Examples: def func(ind):\n c = ind.fitness.values[0]>=0.90 return c """ super(BaseLoop, self).__init__() assert initial_max <= max_value, "the initial size of expression should less than max_value limitation" if cal_dim: assert all( [isinstance(i, Dim) for i in pset.dim_ter_con.values()]), \ "all import dim of pset should be Dim object." random.seed(random_state) self.max_value = max_value self.pop = pop self.gen = gen self.mutate_prob = mutate_prob self.mate_prob = mate_prob self.verbose = verbose self.cal_dim = cal_dim self.re_hall = re_hall self.re_Tree = re_Tree self.store = store self.data_all = [] self.personal_map = personal_map self.stop_condition = stop_condition self.cpset = CalculatePrecisionSet(pset, scoring=scoring, score_pen=score_pen, filter_warning=filter_warning, cal_dim=cal_dim, add_coef=add_coef, inter_add=inter_add, inner_add=inner_add, vector_add=vector_add, cv=cv, n_jobs=n_jobs, batch_size=batch_size, tq=tq, fuzzy=fuzzy, dim_type=dim_type, ) Fitness_ = newclass.create("Fitness_", Fitness, weights=score_pen) self.PTree = newclass.create("PTrees", SymbolTree, fitness=Fitness_) # def produce if initial_min is None: initial_min = 2 self.register("genGrow", genGrow, pset=self.cpset, min_=initial_min, max_=initial_max, personal_map=self.personal_map) self.register("genFull", genFull, pset=self.cpset, min_=initial_min, max_=initial_max, personal_map=self.personal_map) self.register("gen_mu", genGrow, min_=1, max_=3, personal_map=self.personal_map) # def selection self.register("select", selTournament, tournsize=2) self.register("selKbestDim", selKbestDim, dim_type=self.cpset.dim_type, fuzzy=self.cpset.fuzzy) self.register("selBest", selBest) self.register("mate", cxOnePoint) # def mutate self.register("mutate", mutUniform, expr=self.gen_mu, pset=self.cpset) self.decorate("mate", staticLimit(key=operator.attrgetter("height"), max_value=2 * max_value)) self.decorate("mutate", staticLimit(key=operator.attrgetter("height"), max_value=2 * max_value)) if stats is None: if cal_dim: stats = {"fitness_dim_max": ("max",), "dim_is_target": ("sum",)} else: stats = {"fitness": ("max",)} self.stats = Statis_func(stats=stats) logbook = Logbook() logbook.header = ['gen'] + (self.stats.fields if self.stats else []) self.logbook = logbook if hall is None: hall = 1 self.hall = HallOfFame(hall) if re_hall is None: self.re_hall = None else: if re_hall is 1 or re_hall is 0: print("re_hall should more than 1") re_hall = 2 assert re_hall >= hall, "re_hall should more than hall" self.re_hall = HallOfFame(re_hall) def varAnd(self, *arg, **kwargs): return varAnd(*arg, **kwargs) def to_csv(self, data_all): if self.store: if isinstance(self.store, str): path = self.store else: path = os.getcwd() file_new_name = "_".join((str(self.pop), str(self.gen), str(self.mutate_prob), str(self.mate_prob), str(time.time()))) try: st = Store(path) st.to_csv(data_all, file_new_name) print("store data to ", path, file_new_name) except (IOError, PermissionError): st = Store(os.getcwd()) st.to_csv(data_all, file_new_name) print("store data to ", os.getcwd(), file_new_name) def maintain_halls(self, population): if self.re_hall is not None: maxsize = max(self.hall.maxsize, self.re_hall.maxsize) if self.cal_dim: inds_dim = self.selKbestDim(population, maxsize) else: inds_dim = self.selBest(population, maxsize) self.hall.update(inds_dim) self.re_hall.update(inds_dim) sole_inds = [i for i in self.re_hall.items if i not in inds_dim] inds_dim.extend(sole_inds) else: if self.cal_dim: inds_dim = self.selKbestDim(population, self.hall.maxsize) else: inds_dim = self.selBest(population, self.hall.maxsize) self.hall.update(inds_dim) inds_dim = [] inds_dim = copy.deepcopy(inds_dim) return inds_dim def re_add(self): if self.hall.items and self.re_Tree: it = self.hall.items indo = it[random.choice(len(it))] ind = copy.deepcopy(indo) inds = ind.depart() if not inds: pass else: inds = [self.cpset.calculate_detail(indi) for indi in inds] le = min(self.re_Tree, len(inds)) indi = inds[random.choice(le)] self.cpset.add_tree_to_features(indi) def re_fresh_by_name(self, *arr): re_name = ["mutate", "genGrow", "genFull"] if len(arr) > 0: re_name.extend(arr) self.refresh(re_name, pset=self.cpset) def run(self): # 1.generate################################################################### population = [self.PTree(self.genFull()) for _ in range(self.pop)] for gen_i in range(1, self.gen + 1): population_old = copy.deepcopy(population) # 2.evaluate############################################################### invalid_ind_score = self.cpset.parallelize_score(population_old) for ind, score in zip(population_old, invalid_ind_score): ind.fitness.values = tuple(score[0]) ind.y_dim = score[1] ind.dim_score = score[2] population = population_old # 3.log################################################################### # 3.1.log-print############################## record = self.stats.compile(population) if self.stats else {} self.logbook.record(gen=gen_i, **record) if self.verbose: print(self.logbook.stream) # 3.2.log-store############################## if self.store: datas = [{"gen": gen_i, "name": str(gen_i), "value": str(gen_i.fitness.values), "dimension": str(gen_i.y_dim), "dim_score": str(gen_i.dim_score)} for gen_i in population] self.data_all.extend(datas) # 3.3.log-hall############################### inds_dim = self.maintain_halls(population) # 4.refresh################################################################ # 4.1.re_update the premap ################## if self.personal_map is "auto": [self.cpset.premap.update(indi, self.cpset) for indi in inds_dim] # 4.2.re_add_tree and refresh pset########### if self.re_Tree: self.re_add() self.re_fresh_by_name() # 5.break####################################################### if self.stop_condition is not None: if self.stop_condition(self.hall.items[0]): break # 6.next generation####################################################### # selection and mutate,mate population = self.select(population, len(population) - len(inds_dim)) offspring = self.varAnd(population, self, self.mate_prob, self.mutate_prob) offspring.extend(inds_dim) population[:] = offspring # 7.store##################################################################### if self.store: self.to_csv(self.data_all) self.hall.items = [self.cpset.calculate_detail(indi) for indi in self.hall.items] return self.hall
class PBIL(object): train_scalars = { 'fitness_mean': lambda population, last, overall: np.mean( [x.fitness for x in population]), 'fitness_last': lambda population, last, overall: last.fitness, 'fitness_overall': lambda population, last, overall: overall.fitness } def __init__(self, resources_path, train_data, lr=0.7, selection_share=0.5, n_generations=200, n_individuals=75, log_path=None): """ Initializes a new instance of PBIL ensemble learning classifier. All PBIL hyper-parameters default to the values presented in the paper Cagnini, Henry E.L., Freitas, Alex A., Barros, Rodrigo C. An Evolutionary Algorithm for Learning Interpretable Ensembles of Classifiers. Brazilian Conference on Intelligent Systems. 2020. :param resources_path: Path to folder where at least two files must exist: classifiers.json and variables.json :type resources_path: str :param train_data: Training data as an object from the python weka wrapper library :type train_data: weka.core.dataset.Instances :param lr: Learning rate :type lr: float :param selection_share: How many individuals from general population with best fitness will update graphical models' probabilities. :type selection_share: float :param n_generations: Number of generations to run PBIL :type n_generations: int :param n_individuals: Number of individuals (solutions) to use :type n_individuals: int :param log_path: Optional: path to where metadata from this run should be stored. :type log_path: str """ self.lr = lr # type: float self.selection_share = selection_share # type: float self.n_generations = n_generations # type: int self.n_individuals = n_individuals # type: int clfs = [x[0] for x in inspect.getmembers(generation, inspect.isclass)] classifier_names = [ x for x in clfs if ClassifierWrapper in eval('generation.%s' % x).__bases__ ] self.classifier_names = classifier_names # type: list self.variables = json.load( open(os.path.join(resources_path, 'variables.json'), 'r')) # type: dict self.classifier_data = json.load( open(os.path.join(resources_path, 'classifiers.json'), 'r')) # type: dict self.train_data = train_data # type: Instances self.n_classes = len(self.train_data.class_attribute.values) self.evaluator = EDAEvaluator(n_folds=5, train_data=self.train_data) self.n_generation = 0 self._hof = None scalars = copy.deepcopy(self.train_scalars) if log_path is not None: self.logger = PBILLogger(logdir_path=log_path, histogram_names=['fitness'], scalars=scalars, text_names=['last', 'overall']) self.logger.log_probabilities( variables=self.variables) # register first probabilities else: self.logger = None def sample_and_evaluate(self, seed, n_individuals): """ Samples new individuals from graphical model. :param seed: seed used to partition training set at every generation. The (sub)sets will be constant throughout all the evolutionary process, allowing a direct comparison between individuals from different generations. :type seed: int :param n_individuals: Number of individuals to sample. :type n_individuals: int :return: the recently sampled population :rtype: list """ len_hall = len(self._hof) if len_hall == 0: parameters = {k: [] for k in self.classifier_names} parameters['Aggregator'] = [] ilogs = [] else: parameters = { k: [x.options[k] for x in self._hof] for k in self.classifier_names } parameters['Aggregator'] = [ x.options['Aggregator'] for x in self._hof ] ilogs = [x.log for x in self._hof] self._hof.clear() for j in range(n_individuals): ilog = dict() for classifier_name in self.classifier_names: ilog[classifier_name] = np.random.choice( a=self.variables[classifier_name]['params']['a'], p=self.variables[classifier_name]['params']['p']) if ilog[classifier_name]: # whether to include this classifier in the ensemble options, cclog = eval(classifier_name).sample_options( variables=self.variables, classifiers=self.classifier_data) ilog.update(cclog) parameters[classifier_name] += [options] else: parameters[classifier_name].append([]) ilog['Aggregator'] = np.random.choice( a=self.variables['Aggregator']['params']['a'], p=self.variables['Aggregator']['params']['p']) agg_options, alog = eval( ilog['Aggregator']).sample_options(variables=self.variables) ilog.update(alog) parameters['Aggregator'] += [[ilog['Aggregator']] + agg_options] ilogs += [ilog] train_aucs = self.evaluator.get_unweighted_aucs(seed=seed, parameters=parameters) # hall of fame is put in the front for i in range(0, len_hall): local_options = { k: parameters[k][i] for k in self.classifier_names } local_options['Aggregator'] = parameters['Aggregator'][i] self._hof.insert( Skeleton(seed=seed, log=ilogs[i], options=local_options, fitness=train_aucs[i])) population = [] for i in range(len_hall, n_individuals + len_hall): local_options = { k: parameters[k][i] for k in self.classifier_names } local_options['Aggregator'] = parameters['Aggregator'][i] population += [ Skeleton(seed=seed, log=ilogs[i], options=local_options, fitness=train_aucs[i]) ] return population def update(self, population): """ Updates graphical model probabilities based on the fittest population. :param population: All population from a given generation. :type population: list """ if self.logger is not None: self.logger.log_probabilities(variables=self.variables) self.logger.log_population(population=population, halloffame=self._hof) # selects fittest individuals _sorted = sorted(zip(population, [ind.fitness for ind in population]), key=lambda x: x[1], reverse=True) population, fitnesses = zip(*_sorted) fittest = population[:int(len(population) * self.selection_share)] observations = pd.DataFrame([fit.log for fit in fittest]) # update classifiers probabilities for variable_name, variable_data in self.variables.items(): self.variables[variable_name] = process_update( ptype=variable_data['ptype'], variable_name=variable_name, variable_data=variable_data, observations=observations, lr=self.lr, n_generations=self.n_generations) self.n_generation += 1 def run(self, seed): """ Trains this classifier. :param seed: seed used to partition training set at every generation. The (sub)sets will be constant throughout all the evolutionary process, allowing a direct comparison between individuals from different generations. :type seed: int :rtype: tuple :return: a tuple containing two individuals.Individual objects, where the first individual is the best solution (according to fitness) found throughout all the evolutionary process, and the second individual the best solution from the last generation. """ # Statistics computation stats = tools.Statistics(lambda ind: ind.fitness) for stat_name, stat_func in PBILLogger.population_operators: stats.register(stat_name, stat_func) # An object that keeps track of the best individual found so far. self._hof = HallOfFame(maxsize=1) # type: HallOfFame best_last, logbook = self.__run__(seed=seed, ngen=self.n_generations, stats=stats, verbose=True) best_overall = self._hof[0] # type: Individual self._hof = None gc.collect() return best_overall, best_last def __run__(self, seed, ngen, stats=None, verbose=__debug__): """ Do not use this method. """ logbook = tools.Logbook() logbook.header = ['gen', 'nevals'] + (stats.fields if stats else []) early = EarlyStop() population = [] for gen in range(ngen): # early stop if early.is_stopping(): break # Generate a new population, already evaluated; re-evaluates halloffame with new seed population = self.sample_and_evaluate( seed=seed, n_individuals=self.n_individuals) self._hof.update(population) # Update the strategy with the evaluated individuals self.update(population=population) record = stats.compile(population) if stats is not None else {} logbook.record(gen=gen, nevals=len(population), **record) if verbose: print(logbook.stream) early.update(halloffame=self._hof, gen=gen) fitnesses = [ind.fitness for ind in population] best_skeleton = population[int(np.argmax(fitnesses))] # type: Skeleton best_last = Individual(seed=seed, log=best_skeleton.log, options=best_skeleton.options, train_data=self.train_data) skts = [self._hof[i] for i in range(len(self._hof))] self._hof.clear() for i in range(len(skts)): ind = Individual(seed=seed, log=skts[i].log, options=skts[i].options, train_data=self.train_data) self._hof.insert(ind) return best_last, logbook
class CoevolutionGA: def __init__(self, **kwargs): ## TODO: add ability to determine if evolution has stopped ## TODO: add archive ## TODO: add generating and collecting different statistics ## TODO: add logging # create initial populations of all species # taking part in coevolution self.kwargs = deepcopy(kwargs) self.ENV = kwargs["env"] self.SPECIES = kwargs["species"] self.INTERACT_INDIVIDUALS_COUNT = kwargs["interact_individuals_count"] self.GENERATIONS = kwargs["generations"] self.solstat = kwargs.get("solstat", lambda sols: {}) #choose = kwargs["operators"]["choose"] self.build_solutions = kwargs["operators"]["build_solutions"] self.fitness = kwargs["operators"]["fitness"] self.assign_credits = kwargs["operators"]["assign_credits"] self.analyzers = kwargs.get("analyzers", []) self.USE_CREDIT_INHERITANCE = kwargs.get("use_credit_inheritance", False) self.HALL_OF_FAME_SIZE = kwargs.get("hall_of_fame_size", 0) self._result = None self.stat = tools.Statistics(key=lambda x: x.fitness) self.stat.register("best", rounddec(numpy.max)) self.stat.register("min", rounddec(numpy.min)) self.stat.register("avg", rounddec(numpy.average)) self.stat.register("std", rounddec(numpy.std)) self.logbook = tools.Logbook() self.kwargs['logbook'] = self.logbook ## TODO: make processing of population consisting of 1 element uniform ## generate initial population self.pops = {s: self._generate_k(s.initialize(self.kwargs, s.pop_size)) if not s.fixed else self._generate_k([s.representative_individual]) for s in self.SPECIES} ## make a copy for logging. TODO: remake it with logbook later. self.initial_pops = {s.name: deepcopy(pop) for s, pop in self.pops.items()} ## checking correctness for s, pop in self.pops.items(): sm = sum(p.k for p in pop) assert sm == self.INTERACT_INDIVIDUALS_COUNT, \ "For specie {0} count doesn't equal to {1}. Actual value {2}".format(s, self.INTERACT_INDIVIDUALS_COUNT, sm) print("Initialization finished") self.hall = HallOfFame(self.HALL_OF_FAME_SIZE) self.kwargs['gen'] = 0 pass def __call__(self): for gen in range(self.GENERATIONS): self.gen() print("Offsprings have been generated") pass return self.result() def gen(self): kwargs = self.kwargs kwargs['gen'] = kwargs['gen'] + 1 print("Gen: " + str(kwargs['gen'])) solutions = self.build_solutions(self.pops, self.INTERACT_INDIVIDUALS_COUNT) print("Solutions have been built") ## estimate fitness for sol in solutions: sol.fitness = self.fitness(kwargs, sol) print("Fitness have been evaluated") for s, pop in self.pops.items(): for p in pop: p.fitness = -1000000000.0 ## assign id, calculate credits and save it i = 0 for s, pop in self.pops.items(): for p in pop: p.id = i i += 1 ind_maps = {p.id: p for s, pop in self.pops.items() for p in pop} ind_to_credits = self.assign_credits(kwargs, solutions) for ind_id, credit in ind_to_credits.items(): ## assign credit to every individual ind_maps[ind_id].fitness = credit assert all([sum(p.fitness for p in pop) != 0 for s, pop in self.pops.items()]), \ "Error. Some population has individuals only with zero fitness" print("Credit have been estimated") solsstat_dict = dict(list(self.stat.compile(solutions).items()) + list(self.solstat(solutions).items())) solsstat_dict["fitnesses"] = [sol.fitness for sol in solutions] popsstat_dict = {s.name: dict(list(self.stat.compile(pop).items()) + list(s.stat(pop).items())) for s, pop in self.pops.items()} for s, pop in self.pops.items(): popsstat_dict[s.name]["fitnesses"] = [p.fitness for p in pop] if self.hall.maxsize > 0: self.hall.update(solutions) ## TODO: this should be reconsidered lsols = len(solutions) solutions = list(self.hall) + solutions solutions = solutions[0:lsols] self.logbook.record(gen=kwargs['gen'], popsstat=(popsstat_dict,), solsstat=(solsstat_dict,)) for an in self.analyzers: an(kwargs, solutions, self.pops) print("hall: " + str(list(map(lambda x: x.fitness, self.hall)))) ## select best solution as a result ## TODO: remake it in a more generic way ## TODO: add archive and corresponding updating and mixing #best = max(solutions, key=lambda x: x.fitness) ## take the best # best = hall[0] if hall.maxsize > 0 else max(solutions, key=lambda x: x.fitness) self.best = self.hall[0] if self.hall.maxsize > 0 else max(solutions, key=lambda x: x.fitness) ## produce offsprings items = [(s, pop) for s, pop in self.pops.items() if not s.fixed] for s, pop in items: if s.fixed: continue offspring = s.select(kwargs, pop) offspring = list(map(deepcopy, offspring)) ## apply mixin elite ones from the hall if self.hall.maxsize > 0: elite = [sol[s.name] for sol in self.hall] offspring = elite + offspring offspring = offspring[0:len(pop)] # Apply crossover and mutation on the offspring for child1, child2 in zip(offspring[::2], offspring[1::2]): if random.random() < s.cxb: c1 = child1.fitness c2 = child2.fitness s.mate(kwargs, child1, child2) ## TODO: make credit inheritance here ## TODO: toolbox.inherit_credit(pop, child1, child2) ## TODO: perhaps, this operation should be done after all crossovers in the pop ## default implementation ## ? child1.fitness = (c1 + c2) / 2.0 child2.fitness = (c1 + c2) / 2.0 pass for mutant in offspring: if random.random() < s.mb: s.mutate(kwargs, mutant) pass self.pops[s] = offspring pass ## assign credits for every individuals of all pops for s, pop in self.pops.items(): self._credit_to_k(pop) for s, pop in self.pops.items(): for ind in pop: if not self.USE_CREDIT_INHERITANCE: del ind.fitness del ind.id pass def result(self): return self.best, self.pops, self.logbook, self.initial_pops def _generate_k(self, pop): base_k = int(self.INTERACT_INDIVIDUALS_COUNT / len(pop)) free_slots = self.INTERACT_INDIVIDUALS_COUNT % len(pop) for ind in pop: ind.k = base_k for slot in range(free_slots): i = random.randint(0, len(pop) - 1) pop[i].k += 1 return pop def _credit_to_k(self, pop): norma = self.INTERACT_INDIVIDUALS_COUNT / sum(el.fitness for el in pop) for c in pop: c.k = int(c.fitness * norma) left_part = self.INTERACT_INDIVIDUALS_COUNT - sum(c.k for c in pop) sorted_pop = sorted(pop, key=lambda x: x.fitness, reverse=True) for i in range(left_part): sorted_pop[i].k += 1 return pop pass
def evolution( env: Environment, number_of_rays: int, ray_distribution: str, angle_lower_bound: int, angle_upper_bound: int, length_lower_bound: int, length_upper_bound: int, no_of_reflective_segments: int, distance_limit: int, length_limit: int, population_size: int, number_of_generations: int, xover_prob: float, mut_angle_prob: float, mut_length_prob: float, shift_segment_prob: float, rotate_segment_prob: float, resize_segment_prob: float, tilt_base_prob: float, base_length: int, base_slope: int, base_angle_limit_min: int, base_angle_limit_max: int): # Initiating evolutionary algorithm creator.create("Fitness", base.Fitness, weights=(1.0, )) base.Fitness.weights = (1.0, 10.0, 5.0, -1.0) creator.create("Individual", Component, fitness=creator.Fitness) toolbox = base.Toolbox() toolbox.register("individual", creator.Individual, env=env, number_of_rays=number_of_rays, ray_distribution=ray_distribution, angle_lower_bound=angle_lower_bound, angle_upper_bound=angle_upper_bound, length_lower_bound=length_lower_bound, length_upper_bound=length_upper_bound, no_of_reflective_segments=no_of_reflective_segments, distance_limit=distance_limit, length_limit=length_limit, base_length=base_length, base_slope=base_slope) toolbox.register("population", tools.initRepeat, list, toolbox.individual) toolbox.register("evaluate", evaluate) if env.quality_criterion == "nsgaii": toolbox.register("select", tools.selNSGA2) else: toolbox.register("select", tools.selTournament, tournsize=2) # Initiating first population pop = toolbox.population(n=population_size) # Evaluating fitness fitnesses = [] for item in pop: fitnesses.append(evaluate(item, env)) for ind, fit in zip(pop, fitnesses): ind.fitness = fit if env.quality_criterion != "nsgaii": if env.configuration == "two connected": stats_line = f"generation, best fitness, average fitness, fitness array, left segment angle, " \ f"left segment length, right segment angle, right segment length \n" else: stats_line = f"generation, best fitness, average fitness, fitness array, reflective segments \n" log_stats_init(f"stats", stats_line) # Initiating elitism if env.quality_criterion == "nsgaii": pop = toolbox.select(pop, len(pop)) hof = tools.ParetoFront() hof.update(pop) else: hof = HallOfFame(1) hof.update(pop) print("Start of evolution") # Begin the evolution for g in range(number_of_generations): # A new generation print(f"-- Generation {g} --") # Select the next generation individuals if env.quality_criterion == "nsgaii": offspring = tools.selTournamentDCD(pop, len(pop)) offspring = [toolbox.clone(ind) for ind in offspring] else: offspring = toolbox.select(pop, len(pop)) offspring = list(map(toolbox.clone, offspring)) # Apply crossover and mutation on the offspring for child1, child2 in zip(offspring[::2], offspring[1::2]): # cross two individuals with probability xover_prob if env.configuration == "multiple free": if random.random() < xover_prob: x_over_multiple_segments(child1, child2) # fitness values of the children must be recalculated later child1.fitness = None child2.fitness = None if env.configuration == "two connected": if random.random() < xover_prob: x_over_two_segments(child1, child2) # fitness values of the children must be recalculated later child1.fitness = None child2.fitness = None for mutant in offspring: if env.configuration == "multiple free": if random.random() < shift_segment_prob: mutant.reflective_segments = shift_one_segment( mutant.reflective_segments, "x") mutant.fitness = None if random.random() < shift_segment_prob: mutant.reflective_segments = shift_one_segment( mutant.reflective_segments, "y") mutant.fitness = None if random.random() < rotate_segment_prob: mutant.reflective_segments = rotate_one_segment( mutant.reflective_segments) mutant.fitness = None if random.random() < resize_segment_prob: mutant.reflective_segments = resize_one_segment( mutant.reflective_segments) mutant.fitness = None if random.random() < tilt_base_prob: mutant.base_slope = tilt_base(mutant.base_slope, base_angle_limit_min, base_angle_limit_max) mutant.calculate_base() mutant.original_rays = mutant.sample_rays( number_of_rays, ray_distribution) mutant.fitness = None if env.configuration == "two connected": if random.random() < mut_angle_prob: mutate_angle(mutant) mutant.fitness = None if random.random() < mut_length_prob: mutate_length(mutant, length_upper_bound, length_lower_bound) mutant.fitness = None # Evaluate the individuals with an invalid fitness invalid_ind = [ind for ind in offspring if ind.fitness is None] fitnesses = [] for item in invalid_ind: fitnesses.append(evaluate(item, env)) for ind, fit in zip(invalid_ind, fitnesses): ind.fitness = fit if env.quality_criterion == "nsgaii": pop = toolbox.select(offspring + pop, population_size) else: pop[:] = offspring hof.update(pop) best_ind = hof[0] fitnesses = [] for item in pop: fitnesses.append(item.fitness) print(fitnesses) if env.configuration == "two connected" and env.quality_criterion != "nsgaii": stats_line = f"{g+1}, {best_ind.fitness}, {sum(fitnesses) / population_size}, {best_ind.fitness_array}, " \ f"left angle: {180-best_ind.left_angle+best_ind.base_slope}, " \ f"left length: {best_ind.left_length_coef*best_ind.base_length}, " \ f"right angle: {best_ind.right_angle-best_ind.base_slope}, " \ f"right length: {best_ind.right_length_coef*best_ind.base_length} " log_stats_append(f"stats", stats_line) if env.configuration == "multiple free" and env.quality_criterion != "nsgaii": stats_line = f"{g + 1}, {best_ind.fitness}, {sum(fitnesses) / population_size}, {best_ind.fitness_array}, " for reflective_segment in best_ind.reflective_segments: dimensions = f" start: {reflective_segment.p1}, end: {reflective_segment.p2}" stats_line = stats_line + dimensions log_stats_append(f"stats", stats_line) print(f"Best individual has fitness: {best_ind.fitness}") draw(best_ind, f"best{g}", env) if env.quality_criterion == "nsgaii": unique = choose_unique(hof, env.configuration) stats_line = f"index, fitness array" log_stats_init(f"stats", stats_line) for index in range(len(unique)): draw(unique[index], f"unique{index}", env) if env.configuration == "two connected": stats_line = f"{index}, {unique[index].fitness}," \ f"left angle: {180-unique[index].left_angle+unique[index].base_slope}, " \ f"left length: {unique[index].left_length_coef*unique[index].base_length}, " \ f"right angle: {unique[index].right_angle-unique[index].base_slope}, " \ f"right length: {unique[index].right_length_coef*unique[index].base_length} " else: stats_line = f"{index}, {unique[index].fitness}, {unique[index].base_slope}" for reflective_segment in unique[index].reflective_segments: dimensions = f" start: {reflective_segment.p1}, end: {reflective_segment.p2}" stats_line = stats_line + dimensions log_stats_append(f"stats", stats_line) print("-- End of (successful) evolution --") print("--")