Пример #1
0
    def __init__(self,
                 all_possible_genes,
                 retain=0.15,
                 random_select=0.1,
                 mutate_chance=0.3):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genome parameters
            retain (float): Percentage of population to retain after
                each generation
            random_select (float): Probability of a rejected genome
                remaining in the population
            mutate_chance (float): Probability a genome will be
                randomly mutated

        """

        self.all_possible_genes = all_possible_genes
        self.retain = retain
        self.random_select = random_select
        self.mutate_chance = mutate_chance

        #set the ID gen
        self.ids = IDgen()
Пример #2
0
    def __init__(self,
                 all_possible_genes,
                 retain=0.15,
                 random_select=0.1,
                 mutate_chance=0.3,
                 opt_for_highest=True):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genome parameters
            retain (float): Percentage of population to retain after each generation
            random_select (float): Probability of a rejected genome remaining in the population
            mutate_chance (float): Probability a genome will be randomly mutated
            opt_for_highest (bool): If False, will optimize for lowest value (e.g. loss),
                                    if True then for highest value (e.g. accuracy)
        """

        self.all_possible_genes = all_possible_genes
        self.retain = retain
        self.random_select = random_select
        self.mutate_chance = mutate_chance
        self.opt_for_highest = opt_for_highest

        # Genome store
        self.master = None

        # set the ID gen
        self.ids = IDgen()
Пример #3
0
    def __init__(self,
                 all_possible_genes,
                 retain=0.15,
                 random_select=0.1,
                 mutate_chance=0.3):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genes (i.e., network
                hyperparameters)
            retain (float): Proportion of top-performing genomes to
                retain for the next generation
            random_select (float): Probabilty of a non-top-performing 
                genome to retain for the next generation
            mutate_chance (float): Probability a genome will be
                randomly mutated

        """

        self.all_possible_genes = all_possible_genes
        self.retain = retain
        self.random_select = random_select
        self.mutate_chance = mutate_chance

        #set the ID gen
        self.ids = IDgen()
Пример #4
0
    def __init__(self, all_possible_genes, retain=0.15, random_select=0.1, mutate_chance=0.3):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genome parameters
            retain (float): Percentage of population to retain after
                each generation
            random_select (float): Probability of a rejected genome
                remaining in the population
            mutate_chance (float): Probability a genome will be
                randomly mutated

        """

        self.all_possible_genes = all_possible_genes
        self.retain             = retain
        self.random_select      = random_select
        self.mutate_chance      = mutate_chance

        #set the ID gen
        self.ids = IDgen()
Пример #5
0
class Evolver():
    """Class that implements genetic algorithm."""
    def __init__(self,
                 all_possible_genes,
                 retain=0.15,
                 random_select=0.1,
                 mutate_chance=0.3):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genome parameters
            retain (float): Percentage of population to retain after
                each generation
            random_select (float): Probability of a rejected genome
                remaining in the population
            mutate_chance (float): Probability a genome will be
                randomly mutated

        """

        self.all_possible_genes = all_possible_genes
        self.retain = retain
        self.random_select = random_select
        self.mutate_chance = mutate_chance

        #set the ID gen
        self.ids = IDgen()

    def create_population(self, count):
        """Create a population of random networks.

        Args:
            count (int): Number of networks to generate, aka the
                size of the population

        Returns:
            (list): Population of network objects

        """
        pop = []

        i = 0

        while i < count:

            # Initialize a new genome.
            genome = Genome(self.all_possible_genes, {},
                            self.ids.get_next_ID(), 0, 0, self.ids.get_Gen())

            # Set it to random parameters.
            genome.set_genes_random()

            if i == 0:
                #this is where we will store all genomes
                self.master = AllGenomes(genome)
            else:
                # Make sure it is unique....
                while self.master.is_duplicate(genome):
                    genome.mutate_one_gene()

            # Add the genome to our population.
            pop.append(genome)

            # and add to the master list
            if i > 0:
                self.master.add_genome(genome)

            i += 1

        #self.master.print_all_genomes()

        #exit()

        return pop

    @staticmethod
    def fitness(genome):
        """Return the accuracy, which is our fitness function."""
        return genome.accuracy

    def grade(self, pop):
        """Find average fitness for a population.

        Args:
            pop (list): The population of networks/genome

        Returns:
            (float): The average accuracy of the population

        """
        summed = reduce(add, (self.fitness(genome) for genome in pop))
        return summed / float((len(pop)))

    def breed(self, mom, dad):
        """Make two children from parental genes.

        Args:
            mother (dict): genome parameters
            father (dict): genome parameters

        Returns:
            (list): Two network objects

        """
        children = []

        #where do we recombine? 0, 1, 2, 3, 4... N?
        #with four genes, there are three choices for the recombination
        # ___ * ___ * ___ * ___
        #0 -> no recombination, and N == length of dictionary -> no recombination
        #0 and 4 just (re)create more copies of the parents
        #so the range is always 1 to len(all_possible_genes) - 1
        pcl = len(self.all_possible_genes)

        recomb_loc = random.randint(1, pcl - 1)

        #for _ in range(2): #make _two_ children - could also make more
        child1 = {}
        child2 = {}

        #enforce defined genome order using list
        #keys = ['nb_neurons', 'nb_layers', 'activation', 'optimizer']
        keys = list(self.all_possible_genes)
        keys = sorted(
            keys
        )  #paranoia - just to make sure we do not add unintentional randomization

        #*** CORE RECOMBINATION CODE ****
        for x in range(0, pcl):
            if x < recomb_loc:
                child1[keys[x]] = mom.geneparam[keys[x]]
                child2[keys[x]] = dad.geneparam[keys[x]]
            else:
                child1[keys[x]] = dad.geneparam[keys[x]]
                child2[keys[x]] = mom.geneparam[keys[x]]

        # Initialize a new genome
        # Set its parameters to those just determined
        # they both have the same mom and dad
        genome1 = Genome(self.all_possible_genes, child1,
                         self.ids.get_next_ID(), mom.u_ID, dad.u_ID,
                         self.ids.get_Gen())
        genome2 = Genome(self.all_possible_genes, child2,
                         self.ids.get_next_ID(), mom.u_ID, dad.u_ID,
                         self.ids.get_Gen())

        #at this point, there is zero guarantee that the genome is actually unique

        # Randomly mutate one gene
        if self.mutate_chance > random.random():
            genome1.mutate_one_gene()

        if self.mutate_chance > random.random():
            genome2.mutate_one_gene()

        #do we have a unique child or are we just retraining one we already have anyway?
        while self.master.is_duplicate(genome1):
            genome1.mutate_one_gene()

        self.master.add_genome(genome1)

        while self.master.is_duplicate(genome2):
            genome2.mutate_one_gene()

        self.master.add_genome(genome2)

        children.append(genome1)
        children.append(genome2)

        return children

    def evolve(self, pop):
        """Evolve a population of genomes.

        Args:
            pop (list): A list of genome parameters

        Returns:
            (list): The evolved population of networks

        """
        #increase generation
        self.ids.increase_Gen()

        # Get scores for each genome
        graded = [(self.fitness(genome), genome) for genome in pop]

        #and use those scores to fill in the master list
        for genome in pop:
            self.master.set_accuracy(genome)

        # Sort on the scores.
        graded = [
            x[1] for x in sorted(graded, key=lambda x: x[0], reverse=True)
        ]

        # Get the number we want to keep unchanged for the next cycle.
        retain_length = int(len(graded) * self.retain)

        # In this first step, we keep the 'top' X percent (as defined in self.retain)
        # We will not change them, except we will update the generation
        new_generation = graded[:retain_length]

        # For the lower scoring ones, randomly keep some anyway.
        # This is wasteful, since we _know_ these are bad, so why keep rescoring them without modification?
        # At least we should mutate them
        for genome in graded[retain_length:]:
            if self.random_select > random.random():
                gtc = copy.deepcopy(genome)

                while self.master.is_duplicate(gtc):
                    gtc.mutate_one_gene()

                gtc.set_generation(self.ids.get_Gen())
                new_generation.append(gtc)
                self.master.add_genome(gtc)

        # Now find out how many spots we have left to fill.
        ng_length = len(new_generation)

        desired_length = len(pop) - ng_length

        children = []

        # Add children, which are bred from pairs of remaining (i.e. very high or lower scoring) genomes.
        while len(children) < desired_length:

            # Get a random mom and dad, but, need to make sure they are distinct
            parents = random.sample(range(ng_length - 1), k=2)

            i_male = parents[0]
            i_female = parents[1]

            male = new_generation[i_male]
            female = new_generation[i_female]

            # Recombine and mutate
            babies = self.breed(male, female)
            # the babies are guaranteed to be novel

            # Add the children one at a time.
            for baby in babies:
                # Don't grow larger than desired length.
                #if len(children) < desired_length:
                children.append(baby)

        new_generation.extend(children)

        return new_generation
Пример #6
0
class Evolver():
    """Class that implements genetic algorithm."""
    def __init__(self,
                 all_possible_genes,
                 retain=0.15,
                 random_select=0.1,
                 mutate_chance=0.3):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genes (i.e., network
                hyperparameters)
            retain (float): Proportion of top-performing genomes to
                retain for the next generation
            random_select (float): Probabilty of a non-top-performing 
                genome to retain for the next generation
            mutate_chance (float): Probability a genome will be
                randomly mutated

        """

        self.all_possible_genes = all_possible_genes
        self.retain = retain
        self.random_select = random_select
        self.mutate_chance = mutate_chance

        #set the ID gen
        self.ids = IDgen()

    def create_population(self, count):
        """Create a population of random genomes.

        Args:
            count (int): Number of genomes to generate, aka the
                size of the population

        Returns:
            (list): Population of genome objects

        """
        pop = []

        i = 0

        while i < count:

            #initialize a new genome
            genome = Genome(self.all_possible_genes, {},
                            self.ids.get_next_ID(), 0, 0, self.ids.get_Gen())

            #set it to random parameters
            genome.set_genes_random()

            if i == 0:
                #this is where we will store all genomes
                self.master = AllGenomes(genome)
            else:
                #make sure it is unique....
                while self.master.is_duplicate(genome):
                    genome.mutate_one_gene()

            #add the genome to our population
            pop.append(genome)

            #and add to the master list
            if i > 0:
                self.master.add_genome(genome)

            i += 1

        return pop

    @staticmethod
    def fitness(genome):
        """Return the accuracy, which is our fitness function."""
        return genome.accuracy

    def grade(self, pop):
        """Find average fitness for a population.

        Args:
            pop (list): The population of genomes

        Returns:
            (float): The average accuracy of the population

        """
        summed = reduce(add, (self.fitness(genome) for genome in pop))

        return summed / float((len(pop)))

    def breed(self, mom, dad):
        """Make two children from parental genes.

        Args:
            mother (dict): genome parameters
            father (dict): genome parameters

        Returns:
            (list): Two genome objects

        """
        children = []

        nr_genes = len(self.all_possible_genes)

        #get a gene location for single-point crossover
        crossover_loc = random.randint(1, nr_genes - 1)

        child1 = {}
        child2 = {}

        #enforce defined genome order using list
        keys = list(self.all_possible_genes)
        keys = sorted(keys)

        #perform single-point crossover
        for x in range(0, nr_genes):
            if x < crossover_loc:
                child1[keys[x]] = mom.geneparam[keys[x]]
                child2[keys[x]] = dad.geneparam[keys[x]]
            else:
                child1[keys[x]] = dad.geneparam[keys[x]]
                child2[keys[x]] = mom.geneparam[keys[x]]

        #initialize a new genome
        #set its parameters to those just determined
        #they both have the same mom and dad
        genome1 = Genome(self.all_possible_genes, child1,
                         self.ids.get_next_ID(), mom.u_ID, dad.u_ID,
                         self.ids.get_Gen())
        genome2 = Genome(self.all_possible_genes, child2,
                         self.ids.get_next_ID(), mom.u_ID, dad.u_ID,
                         self.ids.get_Gen())

        #randomly mutate one gene
        if self.mutate_chance > random.random():
            genome1.mutate_one_gene()

        if self.mutate_chance > random.random():
            genome2.mutate_one_gene()

        #if child is a duplicate within the new generation, mutate a gene
        while self.master.is_duplicate(genome1):
            genome1.mutate_one_gene()

        self.master.add_genome(genome1)

        while self.master.is_duplicate(genome2):
            genome2.mutate_one_gene()

        self.master.add_genome(genome2)

        children.append(genome1)
        children.append(genome2)

        return children

    def evolve(self, pop):
        """Evolve a population of genomes.

        Args:
            pop (list): A list of genome parameters

        Returns:
            (list): The evolved population of genomes

        """
        #increase generation
        self.ids.increase_Gen()

        #get scores for each genome
        graded = [(self.fitness(genome), genome) for genome in pop]

        #and use those scores to fill in the master list
        for genome in pop:
            self.master.set_accuracy(genome)

        #sort on the scores
        graded = [
            x[1] for x in sorted(graded, key=lambda x: x[0], reverse=True)
        ]

        #get the number we want to keep unchanged for the next cycle
        retain_length = int(len(graded) * self.retain)

        #in this first step, we keep the 'top' X percent (as defined in self.retain)
        #we will not change them, except we will update the generation
        new_generation = graded[:retain_length]

        #for the lower scoring ones, randomly keep some anyway
        for genome in graded[retain_length:]:
            if self.random_select > random.random():
                randomgenome = copy.deepcopy(genome)

                while self.master.is_duplicate(randomgenome):
                    randomgenome.mutate_one_gene()

                randomgenome.set_generation(self.ids.get_Gen())
                new_generation.append(randomgenome)
                self.master.add_genome(randomgenome)

        #now find out how many spots we have left to fill in the new generation
        ng_length = len(new_generation)

        desired_length = len(pop) - ng_length

        children = []

        #add children, which are bred from pairs of genomes in the new generation (i.e. retained and randomly selected genomes)
        while len(children) < desired_length:

            #get a random mom and dad, but, need to make sure they are distinct
            parents = random.sample(range(ng_length - 1), k=2)

            i_male = parents[0]
            i_female = parents[1]

            male = new_generation[i_male]
            female = new_generation[i_female]

            #do crossover and mutation
            babies = self.breed(male, female)
            #the babies are guaranteed to be novel

            #add the children one at a time
            for baby in babies:
                children.append(baby)

        new_generation.extend(children)

        return new_generation
Пример #7
0
class Evolver():
    """Class that implements genetic algorithm."""

    def __init__(self, all_possible_genes, retain=0.15, random_select=0.1, mutate_chance=0.3):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genome parameters
            retain (float): Percentage of population to retain after
                each generation
            random_select (float): Probability of a rejected genome
                remaining in the population
            mutate_chance (float): Probability a genome will be
                randomly mutated

        """

        self.all_possible_genes = all_possible_genes
        self.retain             = retain
        self.random_select      = random_select
        self.mutate_chance      = mutate_chance

        #set the ID gen
        self.ids = IDgen()
        
    def create_population(self, count):
        """Create a population of random networks.

        Args:
            count (int): Number of networks to generate, aka the
                size of the population

        Returns:
            (list): Population of network objects

        """
        pop = []

        i = 0

        while i < count:
            
            # Initialize a new genome.
            genome = Genome( self.all_possible_genes, {}, self.ids.get_next_ID(), 0, 0, self.ids.get_Gen() )

            # Set it to random parameters.
            genome.set_genes_random()

            if i == 0:
                #this is where we will store all genomes
                self.master = AllGenomes( genome )
            else:
                # Make sure it is unique....
                while self.master.is_duplicate( genome ):
                    genome.mutate_one_gene()

            # Add the genome to our population.
            pop.append(genome)

            # and add to the master list
            if i > 0:
                self.master.add_genome(genome)

            i += 1

        #self.master.print_all_genomes()
        
        #exit()

        return pop

    @staticmethod
    def fitness(genome):
        """Return the accuracy, which is our fitness function."""
        return genome.accuracy

    def grade(self, pop):
        """Find average fitness for a population.

        Args:
            pop (list): The population of networks/genome

        Returns:
            (float): The average accuracy of the population

        """
        summed = reduce(add, (self.fitness(genome) for genome in pop))
        return summed / float((len(pop)))

    def breed(self, mom, dad):
        """Make two children from parental genes.

        Args:
            mother (dict): genome parameters
            father (dict): genome parameters

        Returns:
            (list): Two network objects

        """
        children = []

        #where do we recombine? 0, 1, 2, 3, 4... N?
        #with four genes, there are three choices for the recombination
        # ___ * ___ * ___ * ___ 
        #0 -> no recombination, and N == length of dictionary -> no recombination
        #0 and 4 just (re)create more copies of the parents
        #so the range is always 1 to len(all_possible_genes) - 1
        pcl = len(self.all_possible_genes)
        
        recomb_loc = random.randint(1,pcl - 1) 

        #for _ in range(2): #make _two_ children - could also make more
        child1 = {}
        child2 = {}

        #enforce defined genome order using list 
        #keys = ['nb_neurons', 'nb_layers', 'activation', 'optimizer']
        keys = list(self.all_possible_genes)
        keys = sorted(keys) #paranoia - just to make sure we do not add unintentional randomization

        #*** CORE RECOMBINATION CODE ****
        for x in range(0, pcl):
            if x < recomb_loc:
                child1[keys[x]] = mom.geneparam[keys[x]]
                child2[keys[x]] = dad.geneparam[keys[x]]
            else:
                child1[keys[x]] = dad.geneparam[keys[x]]
                child2[keys[x]] = mom.geneparam[keys[x]]

        # Initialize a new genome
        # Set its parameters to those just determined
        # they both have the same mom and dad
        genome1 = Genome( self.all_possible_genes, child1, self.ids.get_next_ID(), mom.u_ID, dad.u_ID, self.ids.get_Gen() )
        genome2 = Genome( self.all_possible_genes, child2, self.ids.get_next_ID(), mom.u_ID, dad.u_ID, self.ids.get_Gen() )

        #at this point, there is zero guarantee that the genome is actually unique

        # Randomly mutate one gene
        if self.mutate_chance > random.random(): 
        	genome1.mutate_one_gene()

        if self.mutate_chance > random.random(): 
        	genome2.mutate_one_gene()

        #do we have a unique child or are we just retraining one we already have anyway?
        while self.master.is_duplicate(genome1):
            genome1.mutate_one_gene()

        self.master.add_genome(genome1)
        
        while self.master.is_duplicate(genome2):
            genome2.mutate_one_gene()

        self.master.add_genome(genome2)
        
        children.append(genome1)
        children.append(genome2)

        return children

    def evolve(self, pop):
        """Evolve a population of genomes.

        Args:
            pop (list): A list of genome parameters

        Returns:
            (list): The evolved population of networks

        """
        #increase generation 
        self.ids.increase_Gen()

        # Get scores for each genome
        graded = [(self.fitness(genome), genome) for genome in pop]

        #and use those scores to fill in the master list
        for genome in pop:
            self.master.set_accuracy(genome)

        # Sort on the scores.
        graded = [x[1] for x in sorted(graded, key=lambda x: x[0], reverse=True)]

        # Get the number we want to keep unchanged for the next cycle.
        retain_length = int(len(graded)*self.retain)

        # In this first step, we keep the 'top' X percent (as defined in self.retain)
        # We will not change them, except we will update the generation
        new_generation = graded[:retain_length]

        # For the lower scoring ones, randomly keep some anyway.
        # This is wasteful, since we _know_ these are bad, so why keep rescoring them without modification?
        # At least we should mutate them
        for genome in graded[retain_length:]:
            if self.random_select > random.random():
                gtc = copy.deepcopy(genome)
                
                while self.master.is_duplicate(gtc):
                    gtc.mutate_one_gene()

                gtc.set_generation( self.ids.get_Gen() )
                new_generation.append(gtc)
                self.master.add_genome(gtc)
        
        # Now find out how many spots we have left to fill.
        ng_length      = len(new_generation)

        desired_length = len(pop) - ng_length

        children       = []

        # Add children, which are bred from pairs of remaining (i.e. very high or lower scoring) genomes.
        while len(children) < desired_length:

            # Get a random mom and dad, but, need to make sure they are distinct
            parents  = random.sample(range(ng_length-1), k=2)
            
            i_male   = parents[0]
            i_female = parents[1]

            male   = new_generation[i_male]
            female = new_generation[i_female]

            # Recombine and mutate
            babies = self.breed(male, female)
            # the babies are guaranteed to be novel

            # Add the children one at a time.
            for baby in babies:
                # Don't grow larger than desired length.
                #if len(children) < desired_length:
                children.append(baby)

        new_generation.extend(children)

        return new_generation
Пример #8
0
class Evolver():
    """Class that implements genetic algorithm."""
    def __init__(self,
                 all_possible_genes,
                 retain=0.2,
                 random_select=0.1,
                 mutate_chance=0.3):
        """Create an optimizer.

        Args:
            all_possible_genes (dict): Possible genome parameters
            retain (float): Percentage of population to retain after
                each generation
            random_select (float): Probability of a rejected genome
                remaining in the population
            mutate_chance (float): Probability a genome will be
                randomly mutated

        """

        self.all_possible_genes = all_possible_genes
        self.retain = retain
        self.random_select = random_select
        self.mutate_chance = mutate_chance

        #set the ID gen
        self.ids = IDgen()

    def create_population(self, count):
        """Create a population of random networks.

        Args:
            count (int): Number of networks to generate, aka the
                size of the population

        Returns:
            (list): Population of network objects

        """
        pop = []

        i = 0

        while i < count:

            # Initialize a new genome.
            genome = Genome(self.all_possible_genes, {},
                            self.ids.get_next_ID(), 0, 0, self.ids.get_Gen())

            # Set it to random parameters.
            genome.set_genes_random()

            if i == 0:
                #this is where we will store all genomes
                self.master = AllGenomes(genome)
            else:
                # Make sure it is unique....
                while self.master.is_duplicate(genome):
                    genome.mutate_one_gene()

            # Add the genome to our population.
            pop.append(genome)

            # and add to the master list
            if i > 0:
                self.master.add_genome(genome)

            i += 1

        #self.master.print_all_genomes()

        #exit()

        return pop

    @staticmethod
    def fitness(genome):
        """Return the accuracy, which is our fitness function."""
        return genome.accuracy

    def grade(self, pop):
        """Find average fitness for a population.

        Args:
            pop (list): The population of networks/genome

        Returns:
            (float): The average accuracy of the population

        """
        summed = reduce(add, (self.fitness(genome) for genome in pop))
        return summed / float((len(pop)))

    def breed(self, mom, dad):
        """
        Make a child from two parent genes
        :param mom: A genome parameter
        :param dad: A genome parameter
        :return: A child gene
        """
        child_gene = {}
        mom_gene = mom.geneparam
        dad_gene = dad.geneparam

        # Choose the optimizer
        child_gene['optimizer'] = random.choice(
            [mom_gene['optimizer'], dad_gene['optimizer']])

        # Combine the layers
        max_len = max(len(mom_gene['layers']), len(dad_gene['layers']))
        child_layers = []
        for pos in range(max_len):
            from_mom = bool(random.getrandbits(1))
            # Add the layer from the correct parent IF it exists. Otherwise add nothing
            if from_mom and len(mom_gene['layers']) > pos:
                child_layers.append(mom_gene['layers'][pos])
            elif not from_mom and len(dad_gene['layers']) > pos:
                child_layers.append(dad_gene['layers'][pos])
        child_gene['layers'] = child_layers

        child = Genome(self.all_possible_genes, child_gene,
                       self.ids.get_next_ID(), mom.u_ID, dad.u_ID,
                       self.ids.get_Gen())

        #at this point, there is zero guarantee that the genome is actually unique

        # Randomly mutate one gene
        if self.mutate_chance > random.random():
            child.mutate_one_gene()

        #do we have a unique child or are we just retraining one we already have anyway?
        while self.master.is_duplicate(child):
            child.mutate_one_gene()

        self.master.add_genome(child)

        return child

    def evolve(self, pop):
        """Evolve a population of genomes.

        Args:
            pop (list): A list of genome parameters

        Returns:
            (list): The evolved population of networks

        """
        #increase generation
        self.ids.increase_Gen()

        # Get scores for each genome
        graded = [(self.fitness(genome), genome) for genome in pop]

        #and use those scores to fill in the master list
        for genome in pop:
            self.master.set_accuracy(genome)

        # Sort on the scores.
        graded = [
            x[1] for x in sorted(graded, key=lambda x: x[0], reverse=True)
        ]

        # Get the number we want to keep unchanged for the next cycle.
        retain_length = int(len(graded) * self.retain)

        # In this first step, we keep the 'top' X percent (as defined in self.retain)
        # We will not change them, except we will update the generation
        new_generation = graded[:retain_length]

        # For the lower scoring ones, randomly keep some anyway.
        # This is wasteful, since we _know_ these are bad, so why keep rescoring them without modification?
        # At least we should mutate them
        for genome in graded[retain_length:]:
            if self.random_select > random.random():
                gtc = copy.deepcopy(genome)

                while self.master.is_duplicate(gtc):
                    gtc.mutate_one_gene()

                gtc.set_generation(self.ids.get_Gen())
                new_generation.append(gtc)
                self.master.add_genome(gtc)

        # Now find out how many spots we have left to fill.
        ng_length = len(new_generation)
        print(str(ng_length))

        desired_length = len(pop) - ng_length

        children = []

        # Add children, which are bred from pairs of remaining (i.e. very high or lower scoring) genomes.
        while len(children) < desired_length:

            # Get a random mom and dad, but, need to make sure they are distinct
            parents = random.sample(range(ng_length - 1), k=2)

            i_male = parents[0]
            i_female = parents[1]

            male = new_generation[i_male]
            female = new_generation[i_female]

            baby = self.breed(male, female)
            children.append(baby)

        new_generation.extend(children)

        return new_generation