Exemple #1
0
    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
Exemple #2
0
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])
Exemple #3
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("--")
Exemple #4
0
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'))
Exemple #5
0
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
Exemple #6
0
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))
Exemple #9
0
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
Exemple #10
0
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
Exemple #11
0
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
Exemple #12
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, 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("--")