Ejemplo n.º 1
0
    def __init__(self, representation, size, fitness_func, selection_func,
                 crossover_func, mutation_func, natural_fitness,
                 crossover_probability, mutation_probability, elite_count,
                 tournament_size):
        """
        Constructor

        :param        representation: the representation dictionary for each
                                      genome in this population
        :param                  size: the number of genomes in this population
        :param          fitness_func: the user-specified fitness function
        :param        selection_func: the user-specified selection function
        :param        crossover_func: the user-specified crossover function
        :param         mutation_func: the user-specified mutation function
        :param       natural_fitness: use natural fitness values, i.e. higher
                                      fitness value implies fitter individual
        :param crossover_probability: the user-specified crossover probability
        :param  mutation_probability: the user-specified mutation probability
        :param           elite_count: the number of fittest individuals to
                                      exclude from selection/crossover/mutation
        :param       tournament_size: the size of a tournament selection group
        """
        self.population = list()
        self.elites = list()

        #-----------------------------------------------------------------------
        # Ensure even population size
        #-----------------------------------------------------------------------
        if size % 2 != 0:
            size += 1
        self.size = size

        #-----------------------------------------------------------------------
        # Ensure 0 < probability < 1
        #-----------------------------------------------------------------------
        if not (0 < float(crossover_probability) < 1) \
            or not (0 < float(mutation_probability) < 1):
            raise ValueError('probabilities must be between 0 and 1')

        self.selection_func = selection_func
        self.crossover_func = crossover_func
        self.mutation_func = mutation_func
        self.fitness_func = fitness_func

        self.representation = representation
        self.natural_fitness = natural_fitness
        self.crossover_probability = crossover_probability
        self.mutation_probability = mutation_probability

        #-------------------------------------------------------------------
        # Ensure even elite count
        #-------------------------------------------------------------------
        if elite_count % 2 != 0:
            elite_count += 1
        self.elite_count = elite_count

        self.tournament_size = tournament_size

        self.plotter = Plotter()

        self.average_sigmas = list()
Ejemplo n.º 2
0
class Population(object):
    """
    Class which represents an entire population of individuals.
    """

    def __init__(self, representation, size, fitness_func, selection_func,
                 crossover_func, mutation_func, natural_fitness,
                 crossover_probability, mutation_probability, elite_count,
                 tournament_size):
        """
        Constructor

        :param        representation: the representation dictionary for each
                                      genome in this population
        :param                  size: the number of genomes in this population
        :param          fitness_func: the user-specified fitness function
        :param        selection_func: the user-specified selection function
        :param        crossover_func: the user-specified crossover function
        :param         mutation_func: the user-specified mutation function
        :param       natural_fitness: use natural fitness values, i.e. higher
                                      fitness value implies fitter individual
        :param crossover_probability: the user-specified crossover probability
        :param  mutation_probability: the user-specified mutation probability
        :param           elite_count: the number of fittest individuals to
                                      exclude from selection/crossover/mutation
        :param       tournament_size: the size of a tournament selection group
        """
        self.population = list()
        self.elites = list()

        #-----------------------------------------------------------------------
        # Ensure even population size
        #-----------------------------------------------------------------------
        if size % 2 != 0:
            size += 1
        self.size = size

        #-----------------------------------------------------------------------
        # Ensure 0 < probability < 1
        #-----------------------------------------------------------------------
        if not (0 < float(crossover_probability) < 1) \
            or not (0 < float(mutation_probability) < 1):
            raise ValueError('probabilities must be between 0 and 1')

        self.selection_func = selection_func
        self.crossover_func = crossover_func
        self.mutation_func = mutation_func
        self.fitness_func = fitness_func

        self.representation = representation
        self.natural_fitness = natural_fitness
        self.crossover_probability = crossover_probability
        self.mutation_probability = mutation_probability

        #-------------------------------------------------------------------
        # Ensure even elite count
        #-------------------------------------------------------------------
        if elite_count % 2 != 0:
            elite_count += 1
        self.elite_count = elite_count

        self.tournament_size = tournament_size

        self.plotter = Plotter()

        self.average_sigmas = list()

    def run(self, generations):
        """
        Apply selection, crossover and mutation on the given population as many
        times as the given number of generations.

        :param generations: the number of generations/cycles to perform
        """
        self.calculate_fitnesses()

        #-----------------------------------------------------------------------
        # Print a short summary
        #-----------------------------------------------------------------------
        print 'population size=%d, representation=%s, ' \
              'crossover probability=%f, mutation probability=%f, ' \
              'elite count=%d' \
              % (len(self.population), self.representation,
                 self.crossover_probability, self.mutation_probability,
                 self.elite_count)
        print 'selection scheme=%s, crossover scheme=%s, mutation scheme=%s, ' \
              'fitness function=%s, natural_fitness=%s' \
              % (self.selection_func, self.crossover_func,
                 self.mutation_func,
                 self.fitness_func, self.natural_fitness)

        print 'generation=0, total fitness=%d, mean fitness=%s, ' \
              'min individual=%s (len=%d), max individual=%s (len=%d)' \
              % (self.total_fitness(), self.mean_fitness(),
                 self.min_individual().fitness(),
                 len(self.min_individual()),
                 self.max_individual().fitness(),
                 len(self.max_individual()))

        self.plotter.update(self.mean_fitness(),
                            self.max_individual().fitness(),
                            self.min_individual().fitness())

        for i in self.population:
            if hasattr(i, 'average_sigmas') and i.average_sigmas is not None:
                self.average_sigmas.append(sum(i.average_sigmas)
                                           / len(i.average_sigmas))

        # print self.average_sigmas
        # print len(self.average_sigmas)

        #-----------------------------------------------------------------------
        # Loop for each generation
        #-----------------------------------------------------------------------
        for i in xrange(1, generations):
            #-------------------------------------------------------------------
            # Perform elitism
            #-------------------------------------------------------------------
            self.store_elites()

            #-------------------------------------------------------------------
            # Select the mating pool
            #-------------------------------------------------------------------
            self.select_parents()

            #-------------------------------------------------------------------
            # Apply crossover
            #-------------------------------------------------------------------
            self.crossover(self.crossover_probability)

            #-------------------------------------------------------------------
            # Apply mutation
            #-------------------------------------------------------------------
            self.mutate(self.mutation_probability)

            #-------------------------------------------------------------------
            # Re-add the elites to the population
            #-------------------------------------------------------------------
            self.load_elites()

            #-------------------------------------------------------------------
            # Recalculate fitnesses
            #-------------------------------------------------------------------
            self.calculate_fitnesses()

            min_individual = self.min_individual()
            max_individual = self.max_individual()

            # print 'generation=%d, total fitness=%d, mean fitness=%s, ' \
            #       'min individual=%s (%s), max individual=%s (%s)' \
            #       % (i, self.total_fitness(), self.mean_fitness(),
            #          min_individual.genes,
            #          min_individual.raw_fitness(),
            #          max_individual.genes,
            #          max_individual.raw_fitness())

            print 'generation=%d, total fitness=%d, mean fitness=%s, ' \
                  'min individual=%s (len=%d), max individual=%s (len=%d)' \
                  % (i, self.total_fitness(), self.mean_fitness(),
                     self.min_individual().fitness(),
                     len(self.min_individual()),
                     self.max_individual().fitness(),
                     len(self.max_individual()))

            self.plotter.update(self.mean_fitness(),
                                max_individual.fitness(),
                                min_individual.fitness())

            for i in self.population:
                if hasattr(i, 'average_sigmas') and i.average_sigmas is not None:
                    self.average_sigmas.append(sum(i.average_sigmas)
                                               / len(i.average_sigmas))

            # print self.average_sigmas
            # print len(self.average_sigmas)

    def __iter__(self):
        return iter(self.population)

    def __len__(self):
        return len(self.population)

    def __getitem__(self, item):
        return self.population[item]

    def gen_population(self):
        """
        Generate an initial, random population based on the given representation
        dictionary.
        """

        #-----------------------------------------------------------------------
        # Generate binary population
        #-----------------------------------------------------------------------
        if self.representation.type == 'binary':
            for _ in xrange(self.size):
                fmt = '{0:0' + str(self.representation.length) + 'b}'
                gene = Genome(fmt.format(
                    random.randint(0, 2 ** self.representation.length)),
                              representation=self.representation,
                              fitness_func=self.fitness_func,
                              natural_fitness=self.natural_fitness)
                self.population.append(gene)

        #-----------------------------------------------------------------------
        # Generate float value population
        #-----------------------------------------------------------------------
        elif self.representation.type == 'float':
            for _ in xrange(self.size):
                gene = Genome([random.uniform(self.representation.min,
                                              self.representation.max)
                               for _ in xrange(self.representation.length)],
                              representation=self.representation,
                              fitness_func=self.fitness_func,
                              natural_fitness=self.natural_fitness)
                self.population.append(gene)

        #-----------------------------------------------------------------------
        # Generate integer value population
        #-----------------------------------------------------------------------
        elif self.representation.type == 'int':
            for _ in xrange(self.size):
                gene = Genome([random.randint(self.representation.min,
                                              self.representation.max)
                               for _ in xrange(self.representation.length)],
                              representation=self.representation,
                              fitness_func=self.fitness_func,
                              natural_fitness=self.natural_fitness)
                self.population.append(gene)

        #-----------------------------------------------------------------------
        # Generate fixed value population
        #-----------------------------------------------------------------------
        elif self.representation.type == 'enum':
            for _ in xrange(self.size):

                #---------------------------------------------------------------
                # Allow duplicates
                #---------------------------------------------------------------
                if self.representation.duplicates:
                    gene = [self.representation.values[random.randint(0,
                                len(self.representation.values)) - 1]
                            for _ in xrange(self.representation.length)]
                #---------------------------------------------------------------
                # Disallow duplicates
                #---------------------------------------------------------------
                else:
                    import copy

                    gene = copy.copy(self.representation.values)
                    random.shuffle(gene)

                gene = Genome(gene,
                              representation=self.representation,
                              fitness_func=self.fitness_func,
                              natural_fitness=self.natural_fitness)
                self.population.append(gene)

    def calculate_fitnesses(self):
        """"""
        for i in self.population:
            i.fitness(recalculate=True)

    def update_population(self, population):
        """
        Change the existing population to the new one

        :param population: the new population to use
        """
        self.population = population

    def total_fitness(self):
        """
        Return the total fitness of all of the individuals in the population
        """
        return sum([i.fitness() for i in self.population])

    def mean_fitness(self):
        """
        Return the mean fitness of all of the individuals in the population
        """
        return self.total_fitness() / len(self.population)

    def max_individual(self):
        """
        Return the individual with the maximum (highest) fitness of all the
        individuals in the population
        """
        max_indiv = self.population[0]

        for i in self.population:
            if i.fitness() > max_indiv.fitness():
                max_indiv = i

        return max_indiv

    def min_individual(self):
        """
        Return the individual with the  minimum (lowest) fitness of all the
        individuals in the population
        """
        min_indiv = self.population[0]

        for i in self.population:
            if i.fitness() < min_indiv.fitness():
                min_indiv = i

        return min_indiv

    def store_elites(self):
        """
        Perform elitism by withholding a certain number of the fittest
        individuals from selection/crossover/mutation.
        """
        del self.elites[:]

        for i in xrange(self.elite_count):
            max_indiv = self.max_individual()
            self.elites.append(max_indiv)
            self.population.remove(max_indiv)

    def load_elites(self):
        """
        Re-add the withheld elites back into the population
        """
        self.population += self.elites

    def select_parents(self):
        """
        Perform population selection using the user-supplied selection function.
        """
        selected_parents = self.selection_func(self)
        self.update_population([self.make_copy(i) for i in selected_parents])

    def crossover(self, probability):
        """
        Perform crossover using the user-supplied crossover function

        :param probability: the probability that crossover will occur for each
                            pair of individuals
        """
        result = list()

        #-----------------------------------------------------------------------
        # Loop the population in twos
        #-----------------------------------------------------------------------
        for male, female in self.pairwise(self.population):
            child1, child2 = self.make_copy(male), self.make_copy(female)

            #-------------------------------------------------------------------
            # Maybe do the crossover... maybe not
            #-------------------------------------------------------------------
            if random.random() <= probability:
                child1.genes, child2.genes = self.crossover_func(child1.genes,
                                                                 child2.genes)

                # male.genes   = child1[:]
                # female.genes = child2[:]

            result.append(child1)
            result.append(child2)

                # result.append(Genome(child1, self.representation,
                #                      self.fitness_func, self.natural_fitness))
                # result.append(Genome(child2, self.representation,
                #                      self.fitness_func, self.natural_fitness))

        assert len(result) == len(self.population)

        #-----------------------------------------------------------------------
        # Switch to the new population
        #-----------------------------------------------------------------------
        self.update_population(result)

    def mutate(self, probability):
        """
        Perform mutation using the user-supplied mutation function

        :param probability: the probability that mutation will occur for each
                            individual
        """
        result = list()

        for i in self.population:
            #-------------------------------------------------------------------
            # Make a copy of the genes
            #-------------------------------------------------------------------
            # i.genes = self.mutation_func(copy.deepcopy(i.genes), probability)
            indiv_copy = self.make_copy(i)
            genes = self.mutation_func(indiv_copy, probability)
            indiv_copy.genes = genes
            result.append(indiv_copy)

        assert len(result) == len(self.population)
        self.update_population(result)

    def make_copy(self, individual):
        if isinstance(individual.genes[0], Gene):

            genes_copy = list()
            for gene in individual.genes:
                gene_copy = Gene(gene.alleles[:])
                gene_copy.class_label = gene.class_label
                if gene.mutation_step_sizes is not None:
                    gene_copy.mutation_step_sizes = gene.mutation_step_sizes[:]
                genes_copy.append(gene_copy)

            genome = Genome(genes_copy, individual.representation,
                    individual.fitness_func,
                    individual.natural_fitness)

        else:
            genome = Genome(individual.genes[:], individual.representation,
                            individual.fitness_func,
                            individual.natural_fitness)

        genome._fitness = individual._fitness
        if hasattr(individual, 'strategy_params') \
                and individual.strategy_params is not None:
            genome.strategy_params = individual.strategy_params.copy()
        if hasattr(individual, 'average_sigmas') \
                and individual.average_sigmas is not None:
            genome.average_sigmas = individual.average_sigmas[:]

        return genome

    def pairwise(self, iterable):
        """
        Make the given iterable into pairs, i.e.:
        s -> (s0,s1), (s2,s3), (s4, s5), ...
        """
        a = iter(iterable)
        return izip(a, a)

    def show_plot(self):
        self.plotter.show()

    def add_to_plot(self, data, label):
        self.plotter.add_to_plot(data, label)