def selNSGA2(individuals, k): """Apply NSGA-II selection operator on the *individuals*. Usually, the size of *individuals* will be larger than *k* because any individual present in *individuals* will appear in the returned list at most once. Having the size of *individuals* equals to *k* will have no effect other than sorting the population according to a non-domination scheme. The list returned contains references to the input *individuals*. For more details on the NSGA-II operator see [Deb2002]_. :param individuals: A list of individuals to select from. :param k: The number of individuals to select. :returns: A list of selected individuals. .. [Deb2002] Deb, Pratab, Agarwal, and Meyarivan, "A fast elitist non-dominated sorting genetic algorithm for multi-objective optimization: NSGA-II", 2002. """ pareto_fronts = sortFastND(individuals, k) for front in pareto_fronts: assignCrowdingDist(front) chosen = list(chain(*pareto_fronts[:-1])) k = k - len(chosen) if k > 0: sorted_front = sorted(pareto_fronts[-1], key=attrgetter("fitness.crowding_dist"), reverse=True) chosen.extend(sorted_front[:k]) return chosen, pareto_fronts[0]
def _run_optimization(self, toolbox, generate_individual, evaluate_population, attr_list, keys, obj_function, pop_size, reporting_interval, weights, nr_of_generations, crossover_rate, mutation_rate, levers, caching, **kwargs): ''' Helper function that runs the actual optimization :param toolbox: :param generate_individual: helper function for generating an individual :param evaluate_population: helper function for evaluating the population :param attr_list: list of attributes (alleles) :param keys: the names of the attributes in the same order as attr_list :param obj_function: the objective function :param pop_size: the size of the population :param reporting_interval: the interval for reporting progress, passed on to perform_experiments :param weights: the weights on the outcomes :param nr_of_generations: number of generations for which the GA will be run :param crossover_rate: the crossover rate of the GA :param mutation_rate: the muation rate of the GA :param levers: a dictionary with param keys as keys, and as values info used in mutation. ''' # figure out whether we are doing single or multi-objective # optimization #TODO raise error if not specified single_obj = True if len(weights) >1: single_obj=False # Structure initializers toolbox.register("individual", generate_individual, creator.Individual, #@UndefinedVariable attr_list, keys=keys) toolbox.register("population", tools.initRepeat, list, toolbox.individual) # Operator registering toolbox.register("evaluate", obj_function) toolbox.register("crossover", tools.cxOnePoint) toolbox.register("mutate", mut_polynomial_bounded) if single_obj: toolbox.register("select", tools.selTournament) else: toolbox.register("select", tools.selNSGA2) # generate population # for some stupid reason, DEAP demands a multiple of four for # population size in case of NSGA-2 pop_size = closest_multiple_of_four(pop_size) info("population size restricted to %s " % (pop_size)) pop = toolbox.population(pop_size) debug("Start of evolution") # Evaluate the entire population evaluate_population(pop, reporting_interval, toolbox, self) if not single_obj: # This is just to assign the crowding distance to the individuals tools.assignCrowdingDist(pop) #some statistics logging stats_callback = NSGA2StatisticsCallback(weights=weights, nr_of_generations=nr_of_generations, crossover_rate=crossover_rate, mutation_rate=mutation_rate, pop_size=pop_size, caching=caching) stats_callback(pop) stats_callback.log_stats(0) # Begin the generational process for gen in range(nr_of_generations): pop = self._run_geneneration(pop, crossover_rate, mutation_rate, toolbox, reporting_interval, levers, evaluate_population, keys, single_obj, stats_callback, caching, **kwargs) stats_callback(pop) stats_callback.log_stats(gen) info("-- End of (successful) evolution --") return stats_callback, pop