예제 #1
0
 def test_genomes01(self):
     """> Test the distance and internal representations of the two given nodes."""
     # Folder must be root to load in make_net properly
     if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..')
     
     # Fetch the used genomes
     cfg = get_config()
     genomes = dict()
     genomes[0] = get_deep_genome0(cfg)
     genomes[1] = get_deep_genome1(cfg)
     
     # Setup an initial genome-cache
     cache = GenomeDistanceCache(cfg.genome)
     cache.warm_up(genomes)
     
     # Get the genome-distance
     result = cache(genome0=genomes[0], genome1=genomes[1])
     self.assertGreater(result, 0)
     
     # Calculation:
     #  6 disjoint nodes
     #  2 identical nodes (outputs)
     #  9 disjoint connections
     #  1 identical connection
     node_distance = (6 * cfg.genome.compatibility_disjoint_node + 2 * 0) / 5
     conn_distance = (9 * cfg.genome.compatibility_disjoint_conn + 1 * 0) / 6
     self.assertEqual(result, node_distance + conn_distance)
     
     # Check if all different representations
     self.assertNotEqual(cache.node_cache.index_map[5], cache.node_cache.index_map[8])
     self.assertNotEqual(cache.node_cache.index_map[6], cache.node_cache.index_map[9])
     self.assertNotEqual(cache.node_cache.index_map[7], cache.node_cache.index_map[10])
예제 #2
0
 def test_genomes03(self):
     """> Test the distance and internal representations of the two given nodes."""
     # Folder must be root to load in make_net properly
     if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..')
     
     # Fetch the used genomes
     cfg = get_config()
     genomes = dict()
     genomes[0] = get_deep_genome0(cfg)
     genomes[3] = get_deep_genome3(cfg)
     
     # Setup an initial genome-cache
     cache = GenomeDistanceCache(cfg.genome)
     cache.warm_up(genomes)
     
     # Get the genome-distance
     result = cache(genome0=genomes[0], genome1=genomes[3])
     self.assertGreater(result, 0)
     
     # Calculation:
     #  2 disjoint nodes
     #  4 identical nodes (outputs)
     #  5 disjoint connections (1 completely removed, 2x2 others connected to different genes)
     #  4 identical connections
     node_distance = (2 * cfg.genome.compatibility_disjoint_node + 4 * 0) / 5
     conn_distance = (5 * cfg.genome.compatibility_disjoint_conn + 4 * 0) / 6
     self.assertEqual(result, node_distance + conn_distance)
     
     # Check if all different representations
     self.assertEqual(cache.node_cache.index_map[5], cache.node_cache.index_map[14])
     self.assertEqual(cache.node_cache.index_map[6], cache.node_cache.index_map[15])
     self.assertNotEqual(cache.node_cache.index_map[7], cache.node_cache.index_map[16])
예제 #3
0
 def test_distance(self):
     """> Test if the distance holds for two completely different genomes."""
     # Folder must be root to load in make_net properly
     if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..')
     
     # Fetch the used genomes
     cfg = get_config()
     genomes = dict()
     genomes[0] = get_genome0(cfg)
     genomes[3] = get_genome3(cfg)
     
     # Setup an initial genome-cache
     cache = GenomeDistanceCache(cfg.genome)
     cache.warm_up(genomes)
     
     # Get the genome-distance
     result = cache(genome0=genomes[0], genome1=genomes[3])
     self.assertGreater(result, 0)
     
     # Calculation:
     #  2 disjoint nodes
     #  2 identical nodes (outputs)
     #  5 disjoint connections (1 completely removed, 2x2 others connected to different genes)
     #  1 identical connection
     node_distance = (2 * cfg.genome.compatibility_disjoint_node + 2 * 0) / 3
     conn_distance = (5 * cfg.genome.compatibility_disjoint_conn + 1 * 0) / 4
     self.assertEqual(result, node_distance + conn_distance)
예제 #4
0
 def test_zero_distance(self):
     """> Test if False is returned when comparing nodes of different type."""
     # Folder must be root to load in make_net properly
     if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..')
     
     # Fetch the used genomes (each type of genome twice)
     cfg = get_config()
     genomes = dict()
     genomes[0] = get_genome0(cfg)
     genomes[1] = get_genome0(cfg)
     genomes[1].key = 1  # Update
     genomes[2] = get_genome1(cfg)
     genomes[3] = get_genome1(cfg)
     genomes[3].key = 3  # Update
     genomes[4] = get_genome2(cfg)
     genomes[5] = get_genome2(cfg)
     genomes[5].key = 5  # Update
     
     # Setup an initial genome-cache
     cache = GenomeDistanceCache(cfg.genome)
     cache.warm_up(genomes)
     
     # Get the genome-distance
     self.assertEqual(cache(genome0=genomes[0], genome1=genomes[1]), 0)
     self.assertEqual(cache(genome0=genomes[2], genome1=genomes[3]), 0)
     self.assertEqual(cache(genome0=genomes[4], genome1=genomes[5]), 0)
예제 #5
0
 def test_same_internal_representation(self):
     """> Test if the merged hidden nodes have the same internal representation."""
     # Folder must be root to load in make_net properly
     if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..')
     
     # Fetch the used genomes
     cfg = get_config()
     genomes = dict()
     genomes[0] = get_genome0(cfg)
     genomes[1] = get_genome1(cfg)
     
     # Setup an initial genome-cache
     cache = GenomeDistanceCache(cfg.genome)
     cache.warm_up(genomes)
     
     # Hidden nodes with keys 2 and 3 should have the same internal representation since they represent the same node
     self.assertEqual(cache.node_cache.index_map[2], cache.node_cache.index_map[3])
예제 #6
0
 def test_distance_similar_genome(self):
     """> Test if two different genomes with exact same architecture have zero distance."""
     # Folder must be root to load in make_net properly
     if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..')
     
     # Fetch the used genomes
     cfg = get_config()
     genomes = dict()
     genomes[0] = get_genome0(cfg)
     genomes[1] = get_genome1(cfg)
     
     # Setup an initial genome-cache
     cache = GenomeDistanceCache(cfg.genome)
     cache.warm_up(genomes)
     
     # Get the genome-distance
     self.assertEqual(cache(genome0=genomes[0], genome1=genomes[1]), 0)
예제 #7
0
def elite_architecture(pop: Population,
                       show: bool = False,
                       del_cache: bool = True):
    """
    Visualize each architectural change in the population's elites.
    
    :param pop: Population object
    :param show: Show the result
    :param del_cache: Remove intermediate architecture-results when images are created
    """
    # Initialize the architecture-list with the first genome
    distance = GenomeDistanceCache(config=pop.config.genome)
    new_architectures = [(0, pop.best_genome_hist[0][1])
                         ]  # Only interested in genome itself
    for gen in range(1, pop.generation):
        gen_genome = pop.best_genome_hist[gen][1]
        if distance(
                gen_genome, new_architectures[-1]
            [1]) > 2:  # Take only the more significant changes into account
            new_architectures.append((gen, gen_genome))
    new_architectures.append(
        (pop.generation - 1, pop.best_genome_hist[pop.generation - 1][1]))

    # Create the architectures of the unique genomes
    for _, g in new_architectures:
        pop.visualize_genome(
            debug=False,  # Keep the networks simple
            genome=g,
            show=False,
        )

    # Combine in one figure
    hor = min(len(new_architectures), 5)
    vert = max((len(new_architectures) - 1) // 5 + 1, 1)
    plt.figure(figsize=(5 * hor, 5 * vert))
    plt.tight_layout()
    f_images = get_subfolder(
        f"population{'_backup' if pop.use_backup else ''}/storage/{pop.folder_name}/{pop}/",
        "images")
    f_architectures = get_subfolder(f_images, "architectures")
    for i, (gen, g) in enumerate(new_architectures):
        plt.subplot(vert, hor, i + 1)
        img = mpimg.imread(f'{f_architectures}genome_{g.key}.png')
        plt.imshow(img)
        plt.title(f'Generation {gen}')
        plt.axis('off')

    # Save the result
    f_elites = get_subfolder(f_images, "elites")
    plt.savefig(f'{f_elites}/architecture_timeline.png', bbox_inches='tight')
    if show:
        plt.show()
    plt.close()

    if del_cache:
        for (_, g) in new_architectures:
            path = f'{f_architectures}genome_{g.key}.png'
            if os.path.exists(path): os.remove(path)
예제 #8
0
    def speciate(self, config: Config, population, generation, logger=None):
        """
        Place genomes into species by genetic similarity.

        :note: This method assumes the current representatives of the species are from the old generation, and that
         after speciation has been performed, the old representatives should be dropped and replaced with
         representatives from the new generation.
        """
        assert isinstance(population, dict)
        unspeciated = set(
            iterkeys(population))  # Initially the full population
        distances = GenomeDistanceCache(
            config.genome)  # Cache memorizing distances between genomes
        # distances.warm_up(population)  # No warm-up since only comparison with representatives! (expensive!)
        new_representatives = dict(
        )  # Updated representatives for each specie (least difference with previous)
        members = dict(
        )  # Dictionary containing for each specie its corresponding members

        # Update the representatives for each specie as the genome closest to the previous representative
        for sid, s in iteritems(self.species):
            candidates = []
            for gid in unspeciated:
                genome = population[gid]
                d = distances(s.representative, genome)
                candidates.append((d, genome))

            # Sort the genomes based on their distance towards the specie and take the closest genome
            sorted_repr = sorted(candidates, key=lambda x: x[0])
            new_repr = sorted_repr[0][1]
            new_representatives[sid] = new_repr.key
            members[sid] = [new_repr.key]
            unspeciated.remove(new_repr.key)

        # Partition the population into species based on their genetic similarity
        while unspeciated:
            # Pull unspeciated genome
            gid = unspeciated.pop()
            genome = population[gid]

            # Find the species with the most similar representative
            specie_distance = []
            candidates = []
            for sid, rid in iteritems(new_representatives):
                rep = population[rid]
                d = distances(rep, genome)
                specie_distance.append((d, sid))
                if d < config.population.get_compatibility_threshold(
                        n_species=len(new_representatives)):
                    candidates.append((d, sid))

            # There are species close enough; add genome to most similar specie
            if candidates:
                _, sid = min(candidates, key=lambda x: x[0])
                members[sid].append(gid)
            else:  # No specie is similar enough, create new specie with this genome as its representative
                sid = next(self.indexer)
                new_representatives[sid] = gid
                members[sid] = [gid]

        # Update the species collection based on the newly defined speciation
        self.genome_to_species = dict()
        for sid, rid in iteritems(new_representatives):
            s = self.species.get(sid)

            # One of the newly defined species
            if s is None:
                s = Species(sid, generation)
                self.species[sid] = s

            # Append the newly added members to the genome_to_species mapping
            specie_members = members[sid]
            for gid in specie_members:
                self.genome_to_species[gid] = sid

            # Update the specie's current representative and members
            member_dict = dict((genome_id, population[genome_id])
                               for genome_id in specie_members)
            s.update(representative=population[rid], members=member_dict)

        # Report over the current species (distances)
        self.reporters.info(
            f'Genetic distance:'
            f'\n\t- Maximum: {max(itervalues(distances.distances)):.5f}'
            f'\n\t- Mean: {mean(itervalues(distances.distances)):.5f}'
            f'\n\t- Minimum: {min(itervalues(distances.distances)):.5f}'
            f'\n\t- Standard deviation: {stdev(itervalues(distances.distances)):.5f}',
            logger=logger,
            print_result=False,
        )
        print(
            f'Maximum genetic distance: {round(max(itervalues(distances.distances)), 3)}'
        )  # Print reduced version

        # Report over the most complex genome
        most_complex = sorted([(g.size(), gid)
                               for gid, g in population.items()],
                              key=lambda x: x[0],
                              reverse=True)[0]
        gid = most_complex[1]
        size = most_complex[0]
        specie_id = self.genome_to_species[gid]
        distance = distances(population[gid],
                             self.species[specie_id].representative)
        self.reporters.info(
            f"Most complex genome '{gid}' "
            f"of size {size} "
            f"belongs to specie '{specie_id}' "
            f"with distance to representative of {round(distance, 3)}",
            logger=logger,
        )