Exemple #1
0
def train(
    population: Population,
    games: list,
    iterations: int,
    debug: bool = False,
    duration: int = 0,
    unused_cpu: int = 0,
):
    """Train the population on the requested number of iterations."""
    from environment.env_training import TrainingEnv

    population.log("\n===> TRAINING <===\n")

    game_config = deepcopy(population.config)
    if duration > 0: game_config.game.duration = duration

    trainer = TrainingEnv(
        unused_cpu=unused_cpu,  # Use two cores less to keep laptop usable
        game_config=game_config,
        games=games,
    )
    trainer.evaluate_and_evolve(
        pop=population,
        n=iterations,
        parallel=not debug,
        save_interval=1 if debug else 10,
    )
Exemple #2
0
def trace_most_fit(
    population: Population,
    genome: Genome,
    games: list,
    debug: bool = False,
    duration: int = 0,
    unused_cpu: int = 0,
):
    """Create a trace evaluation for the given genome on the provided games."""
    from environment.env_visualizing import VisualizingEnv

    game_config = deepcopy(population.config)
    if duration > 0: game_config.game.duration = duration

    population.log("\n===> CREATING GENOME TRACE <===\n")
    population.log(f"Creating traces for games: {games}")

    visualizer = VisualizingEnv(
        game_config=game_config,
        games=games,
        unused_cpu=unused_cpu,
    )
    for g in games:  # TODO: Bug in warm-up of network if multiple games evaluated
        visualizer.set_games([g])
        visualizer.trace_genomes(
            pop=population,
            given_genome=genome,
            parallel=not debug,
        )
Exemple #3
0
    def test_limit(self):
        """
        Test calculating the limit.
        """

        viz = Population(drawable.Drawable)
        self.assertEqual((-0.75, -0.25), viz._limit(0.5))
Exemple #4
0
    def test_limit_height_one(self):
        """
        Test that when drawing with a population height of 1, the function accepts it.
        """

        viz = Population(drawable.Drawable)
        self.assertTrue(viz._limit(1))
Exemple #5
0
def blueprint(
    population: Population,
    games: list,
    debug: bool = False,
    duration: int = 0,
    unused_cpu: int = 0,
):
    """Create a blueprint evaluation for the given population on the first 5 games."""
    from environment.env_visualizing import VisualizingEnv

    population.log("\n===> CREATING BLUEPRINTS <===\n")
    population.log(f"Creating blueprints for games: {games}")

    game_config = deepcopy(population.config)
    if duration > 0: game_config.game.duration = duration

    visualizer = VisualizingEnv(
        game_config=game_config,
        games=games,
        unused_cpu=unused_cpu,
    )
    visualizer.blueprint_genomes(
        pop=population,
        parallel=not debug,
    )
def evolve(pop: Population, pop_name: str):
    """Evolve with the set constraints in mind."""
    # Create the next generation from the current generation
    pop.population = pop.reproduction.reproduce(
        config=pop.config,
        species=pop.species,
        generation=pop.generation,
        logger=pop.log,
    )

    # Constraint each of the population's new genomes to the given topology
    for g in pop.population.values():
        enforce_topology(pop_name, genome=g)

    # Check for complete extinction
    if not pop.species.species:
        pop.reporters.complete_extinction(logger=pop.log)

        # If requested by the user, create a completely new population, otherwise raise an exception
        pop.population = pop.reproduction.create_new(
            config=pop.config, num_genomes=pop.config.population.pop_size)

    # Divide the new population into species
    pop.species.speciate(config=pop.config,
                         population=pop.population,
                         generation=pop.generation,
                         logger=pop.log)

    # Add to each of the species its elites
    pop.update_species_fitness_hist()

    # Increment generation count
    pop.generation += 1
def train_population(pop: Population, games: list, unused_cpu: int = 2):
    """Evaluate the given population on a training set."""
    multi_env = get_multi_env(pop=pop, game_config=pop.config)
    multi_env.set_games(games, noise=False)
    pool = mp.Pool(mp.cpu_count() - unused_cpu)
    manager = mp.Manager()
    return_dict = manager.dict()
    pbar = tqdm(total=len(pop.population), desc="Evaluating")

    def update(*_):
        pbar.update()

    for genome_id, genome in pop.population.items():
        pool.apply_async(func=multi_env.eval_genome,
                         args=((genome_id, genome), return_dict),
                         callback=update)
    pool.close()  # Close the pool
    pool.join()  # Postpone continuation until everything is finished
    pbar.close()

    # Calculate the fitness from the given return_dict
    fitness = calc_pop_fitness(
        fitness_cfg=pop.config.evaluation,
        game_cfg=pop.config.game,
        game_obs=return_dict,
        gen=pop.generation,
    )
    for i, genome in pop.population.items():
        genome.fitness = fitness[i]

    # Save the results
    pop.generation += 1
    pop.save()
Exemple #8
0
    def test_gap_size_one_row(self):
        """
        Test that that the gap size of one row is 0.
        """

        viz = Population(drawable.Drawable)
        self.assertEqual(0, viz._gap_size((0, 1), 1))
Exemple #9
0
def evaluate_generations(name,
                         experiment_id,
                         folder=None,
                         hops: int = 10,
                         unused_cpu: int = 2):
    """
    Evaluate the population across its lifetime. At each generation, the ten best genomes are evaluated together with
    the elite genome of the past five generations.

    :param name: Name of the population
    :param experiment_id: Experiment for which the population is trained (and now will be evaluated)
    :param folder: Population-folder (~experiment level)
    :param hops: Number of generations between each saved population
    :param unused_cpu: Number of CPU cores not used
    """
    # Fetch population and evaluation games
    folder = folder if folder else get_folder(experiment_id)
    pop = Population(
        name=name,
        folder_name=folder,
        log_print=False,
        use_backup=True,
    )
    _, game_ids_eval = get_game_ids(experiment_id=experiment_id)

    # Perform the evaluations
    max_gen = pop.generation
    for gen in tqdm(range(0, max_gen + 1, hops)):
        # Load in the current generation
        if not pop.load(gen=gen):
            raise Exception(
                f"Population {name} is not trained for generation {gen}")

        # Collect the used genomes
        if gen > 5:
            genomes = sorted([g for g in pop.population.values()],
                             key=lambda x: x.fitness if x.fitness else 0,
                             reverse=True)[:10]
            for i in range(1, 6):
                keys = [g.key for g in genomes]
                g = copy.deepcopy(pop.best_genome_hist[gen - i]
                                  [1])  # Copy since chance of mutation
                while g.key in keys:  # Already added to genomes, update keys
                    g.key += 1
                genomes.append(g)
        else:
            # No history yet, use only the ten most fit genomes from the current generation
            genomes = sorted([g for g in pop.population.values()],
                             key=lambda x: x.fitness if x.fitness else 0,
                             reverse=True)[:15]

        # Evaluate the selected genomes
        evaluate(
            population=pop,
            games=game_ids_eval,
            genomes=genomes,
            unused_cpu=unused_cpu,
            overwrite=True,
        )
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)
Exemple #11
0
def evaluate_training(experiment_id: int,
                      pop_folder: str,
                      folder: str = None,
                      max_v: int = 50):
    """Evaluate the fitness of a population's elite each training generation."""
    if pop_folder[-1] != '/': pop_folder += '/'
    folder = folder if folder else get_folder(experiment_id)
    if folder[-1] != '/': folder += '/'

    # Get dummy population
    pop = Population(
        name=f"{pop_folder}v1",
        folder_name=folder,
        log_print=False,
        use_backup=True,
    )
    max_gen = pop.generation

    # Initialize data container
    training_fitness = dict()
    for g in range(0, max_gen + 1, HOPS):
        training_fitness[g] = []

    # Pull the training scores
    print(
        f"\n===> PULLING TRAINING FITNESS OF THE {pop_folder} POPULATIONS <==="
    )
    pbar = tqdm(range(int(max_v * (max_gen / HOPS + 1))))
    for v in range(1, max_v + 1):
        name = f"{pop_folder}v{v}"
        pop = Population(
            name=name,
            folder_name=folder,
            log_print=False,
            use_backup=True,
        )

        # Perform the evaluations
        max_gen = pop.generation
        for gen in range(0, max_gen + 1, HOPS):
            if not pop.load(gen=gen):
                raise Exception(
                    f"Population {name} is not trained for generation {gen}")
            training_fitness[gen].append(
                pop.best_genome.fitness if pop.best_genome else 0)
            pbar.update()
    pbar.close()

    # Plot the result
    path = get_subfolder(f'population_backup/storage/{folder}{pop_folder}',
                         'evaluation')
    update_dict(f'{path}training', training_fitness, overwrite=True)
    path_images = get_subfolder(path, 'images')
    plot_result(d=training_fitness,
                ylabel="fitness",
                title="Average training fitness",
                save_path=f'{path_images}training')
Exemple #12
0
    def test_limit_order(self):
        """
        Test that the limit is a tuple in ascending order.
        """

        viz = Population(drawable.Drawable)
        limit = viz._limit(0.5)
        self.assertEqual(tuple, type(limit))
        self.assertLess(limit[0], limit[1])
Exemple #13
0
    def test_gap_size_two_rows(self):
        """
        Test that that the gap size of two rows is equivalent to the gap between the limits.
        """

        viz = Population(drawable.Drawable)

        lim = (0, 1)
        self.assertEqual(lim[1] - lim[0], viz._gap_size(lim, 2))

        lim = (0.2, 0.8)
        self.assertEqual(lim[1] - lim[0], viz._gap_size(lim, 2))
def visualize_best_genome(pop: Population):
    """Visualize the population's fittest genome."""
    pop.log(
        f"Most fit genome: {pop.best_genome.key} with fitness: {round(pop.best_genome.fitness, 3)}"
    )
    name = f"genome_{pop.best_genome.key}"
    sf = get_subfolder(f'population/storage/{pop.folder_name}/{pop}/',
                       'images')
    sf = get_subfolder(sf, f'gen{pop.generation:05d}')
    draw_net(config=pop.config.genome,
             genome=pop.best_genome,
             debug=True,
             filename=f'{sf}{name}',
             view=False)
Exemple #15
0
    def evaluate_genome_list(self,
                             genome_list,
                             pop: Population,
                             parallel: bool = True,
                             overwrite: bool = False):
        """
        Evaluate the population for a single evaluation-process.

        :param genome_list: List of genomes that will be evaluated
        :param pop: The population to which the genomes belong (used to setup the network and query the config)
        :param parallel: Evaluate the given genomes in parallel
        :param overwrite: Overwrite the evaluation dictionary if it already exists
        """
        # Create the environment which is responsible for evaluating the genomes
        multi_env = get_multi_env(pop=pop, game_config=self.game_config)

        # Evaluate on all the games
        multi_env.set_games(self.games, noise=False)

        # Fetch requested genomes
        genomes = [(g.key, g) for g in genome_list]

        if parallel:
            pool = mp.Pool(mp.cpu_count() - self.unused_cpu)
            manager = mp.Manager()
            return_dict = manager.dict()

            for genome in genomes:
                pool.apply_async(func=multi_env.eval_genome,
                                 args=(genome, return_dict))
            pool.close()  # Close the pool
            pool.join()  # Postpone continuation until everything is finished
        else:
            return_dict = dict()
            for genome in tqdm(genomes, desc="sequential evaluating"):
                multi_env.eval_genome(genome, return_dict)

        # Create the evaluation for each of the genomes
        eval_result = dict()
        for k in return_dict.keys():
            # Create answer based on game.close()
            eval_result[str(k)] = create_answer(return_dict[k])

            # Append fitness to answer
            eval_result[str(k)][D_FITNESS] = calc_pop_fitness(
                fitness_cfg=pop.config.evaluation,
                game_cfg=pop.config.game,
                game_obs={k: return_dict[k]},
                gen=pop.generation)[k]
        pop.add_evaluation_result(eval_result, overwrite=overwrite)
Exemple #16
0
    def test_gap_size_multiple_rows(self):
        """
        Test that that the gap size of multiple rows fills the space between the limits.
        """

        viz = Population(drawable.Drawable)

        lim, rows = (0, 1), 4
        self.assertEqual(lim[1],
                         lim[0] + viz._gap_size(lim, rows) * (rows - 1))

        lim, rows = (0.2, 0.8), 5
        self.assertEqual(lim[1],
                         lim[0] + viz._gap_size(lim, rows) * (rows - 1))
Exemple #17
0
def visualize_genome(
    population: Population,
    genome: Genome,
    debug: bool = True,
    show: bool = False,
):
    """Visualize the requested genome."""
    print("\n===> VISUALIZING GENOME <===\n")
    print(f"Genome {genome.key} with size: {genome.size()}")

    population.visualize_genome(
        debug=debug,
        genome=genome,
        show=show,
    )
Exemple #18
0
    def test_limit_large_height(self):
        """
        Test that when drawing with a large population height, the function raises a ValueError.
        """

        viz = Population(drawable.Drawable)
        self.assertRaises(ValueError, viz._limit, 2)
Exemple #19
0
    def test_gap_size_float_rows(self):
        """
        Test that when getting the gap size and the number of rows is a float, the function raises a TypeError.
        """

        viz = Population(drawable.Drawable)
        self.assertRaises(TypeError, viz._gap_size, (0, 1), 1.2)
Exemple #20
0
    def test_gap_size_zero_rows(self):
        """
        Test that when getting the gap size and the number of rows is zero, the function raises a ValueError.
        """

        viz = Population(drawable.Drawable)
        self.assertRaises(ValueError, viz._gap_size, (0, 1), 0)
def get_population():
    """Get a dummy population with minimal configuration."""
    cfg = Config()
    cfg.game.duration = 1  # Small duration
    cfg.game.fps = 10  # Games of low accuracy but faster
    cfg.genome.rnn_prob_gru = 0
    cfg.genome.rnn_prob_gru_nr = 1  # Always mutate a GRU-NR-node
    cfg.genome.rnn_prob_gru_nu = 0
    cfg.genome.rnn_prob_lstm = 0
    cfg.genome.rnn_prob_simple_rnn = 0
    cfg.population.pop_size = 2  # Keep a small population
    cfg.population.compatibility_thr = float(
        'inf')  # Make sure that population does not expand
    cfg.update()

    # Create the population
    pop = Population(
        name='delete_me',
        folder_name='test_scenario_neat_gru',
        config=cfg,
        log_print=False,
    )

    # Mutate population such that at least one genome has a GRU-NR-node
    pop.population[1].mutate_add_node(
        cfg.genome)  # Population starts count at 1

    return pop
Exemple #22
0
    def test_init_save_drawable(self):
        """
        Test that when creating the population, the drawable is saved.
        """

        viz = drawable.Drawable(plt.figure(figsize=(10, 10)))
        popviz = Population(viz)
        self.assertEqual(viz, popviz.drawable)
Exemple #23
0
    def test_init_none_rows(self):
        """
        Test that when creating the population, the drawable initializes the number of rows to ``None``.
        """

        viz = drawable.Drawable(plt.figure(figsize=(10, 10)))
        popviz = Population(viz)
        self.assertEqual(None, popviz.rows)
Exemple #24
0
    def test_init_empty_populations(self):
        """
        Test that when creating the population, the drawable creates an empty list for populations.
        """

        viz = drawable.Drawable(plt.figure(figsize=(10, 10)))
        popviz = Population(viz)
        self.assertEqual([], popviz.populations)
def main(pop: Population,
         d: int = 10,
         range_width: int = 20,
         genome: Genome = None,
         cpu: int = 2,
         experiment_id: int = 1,
         overwrite: bool = False,
         ):
    """
    Analyse the given single-hidden single-GRU genome.
    
    Analysis is performed on following number of genomes: 3 * ((2 * range + 1) ** 2)
     * 10 =  1'323
     * 20 =  5'043
     * 50 = 30'603

    :param pop: Population on which the analysis is performed
    :param genome: The genome that is being mutated
    :param d: Divisor, indicating hops of 1/d
    :param range_width: Width of the parameter-range taken into account (note: true range is range_width/d)
    :param overwrite: Decide if re-evaluation
    :param experiment_id: Experiment-games used for evaluation
    :param cpu: Number of CPU cores not used for simulation
    """
    # Get genome if not defined
    if not genome: genome = pop.best_genome
    
    # Create a cache-population
    cfg = copy.deepcopy(pop.config)
    cfg.population.pop_size = 2  # Dummy candidates, to be removed
    cfg.update()
    
    # Create the populations
    name = f"genome{genome.key}_divisor{d}_range{range_width}"
    pop_cache = Population(
            name=name,
            folder_name='../../cache_populations',  # I do the hack hack
            config=cfg,
            overwrite=overwrite,
    )
    if len(pop_cache.population) == 2:
        create_genomes(genome=genome, pop=pop_cache, d=d, range_width=range_width)
    
    # Evaluate the populations
    if pop_cache.population[0].fitness is None:
        evaluate_population(
                pop=pop_cache,
                cfg=cfg,
                cpu=cpu,
                experiment_id=experiment_id,
        )
    
    # Evaluate the results - Create 3d array of the results
    visualize_score(pop=pop, pop_cache=pop_cache, genome=genome, d=d, range_width=range_width)
Exemple #26
0
    def draw_population(self, *args, **kwargs):
        """
        Draw a population chart on this :class:`~Drawable`.
        The arguments and keyword arguments are those supported by the :class:`~population.population.Population`'s :func:`~population.population.Population.draw` method.

        :return: A list of drawn scatter points, separated by column.
        :rtype: list of list of :class:`matplotlib.collections.PathCollection`
        """

        self.population = self.population if self.population else Population(self)
        return self.population.draw(*args, **kwargs)
Exemple #27
0
def specie_representatives(pop: Population,
                           show: bool = True,
                           del_cache: bool = True):
    """Show for each of the current species their representative's architecture."""
    species = pop.species.species
    elite_id = dict()
    for sid, s in sorted(species.items()):
        elite_id[sid] = s.representative.key
        pop.visualize_genome(
            debug=False,  # Keep the networks simple
            genome=s.representative,
            show=False,
        )

    hor = min(len(elite_id), 5)
    vert = max(len(elite_id) // 5 + 1, 1)
    plt.figure(figsize=(5 * hor, 5 * vert))
    plt.tight_layout()
    path = get_subfolder(
        f"population{'_backup' if pop.use_backup else ''}/storage/{pop.folder_name}/{pop}/",
        "images")
    path_architectures = get_subfolder(path, "architectures")
    for i, (sid, eid) in enumerate(elite_id.items()):
        plt.subplot(vert, hor, i + 1)
        img = mpimg.imread(f'{path_architectures}genome_{eid}.png')
        plt.imshow(img)
        plt.title(f'Specie {sid}')
        plt.axis('off')

    # Save the result
    path_species = get_subfolder(path, 'species')
    plt.savefig(f'{path_species}representatives_gen{pop.generation}.png',
                bbox_inches='tight')
    if show:
        plt.show()
    plt.close()

    if del_cache:
        for eid in elite_id.values():
            path = f'{path_architectures}genome_{eid}.png'
            if os.path.exists(path): os.remove(path)
Exemple #28
0
def pull(pop_name: str,
         pop_folder: str,
         gid: int = None,
         backup_pop: bool = True):
    """Pull a genome from a specified population"""
    pop = Population(
        name=pop_name,
        folder_name=pop_folder,
        use_backup=backup_pop,
    )
    genome = pop.population[gid] if gid else pop.best_genome
    store_genome(genome=genome)
def evaluate_population(pop: Population, cfg: Config, cpu: int, experiment_id: int):
    """Evaluate the given population."""
    pop.log(f"{pop.name} - Evaluating the population...")
    _, game_ids_eval = get_game_ids(experiment_id=experiment_id)
    multi_env = get_multi_env(pop=pop, game_config=cfg)
    multi_env.set_games(game_ids_eval, noise=False)
    pool = mp.Pool(mp.cpu_count() - cpu)
    manager = mp.Manager()
    return_dict = manager.dict()
    pbar = tqdm(total=len(pop.population), desc="Evaluating")
    
    def update(*_):
        pbar.update()
    
    for genome_id, genome in pop.population.items():
        pool.apply_async(func=multi_env.eval_genome, args=((genome_id, genome), return_dict), callback=update)
    pool.close()  # Close the pool
    pool.join()  # Postpone continuation until everything is finished
    pbar.close()
    
    # Calculate the fitness from the given return_dict
    pop.log(f"{pop.name} - Calculating fitness scores...")
    fitness = calc_pop_fitness(
            fitness_cfg=pop.config.evaluation,
            game_cfg=cfg.game,
            game_obs=return_dict,
            gen=pop.generation,
    )
    for i, genome in pop.population.items():
        genome.fitness = fitness[i]
    
    # Get the fittest genome
    best = None
    for g in pop.population.values():
        if best is None or g.fitness > best.fitness: best = g
    pop.best_genome = best
    
    # Save the results
    pop.save()
    
    # Visualize most fit genome
    visualize_genome(
            debug=True,
            genome=best,
            population=pop,
    )
    
    # Trace the most fit genome
    trace_most_fit(
            debug=False,
            games=game_ids_eval,
            genome=best,
            population=pop,
            unused_cpu=cpu,
    )
Exemple #30
0
def trace(
    population: Population,
    games: list,
    debug: bool = False,
    duration: int = 0,
    unused_cpu: int = 0,
):
    """Create a trace evaluation for the given population on the provided games."""
    from environment.env_visualizing import VisualizingEnv

    population.log("\n===> CREATING TRACES <===\n")
    population.log(f"Creating traces for games: {games}")

    game_config = deepcopy(population.config)
    if duration > 0: game_config.game.duration = duration

    visualizer = VisualizingEnv(
        game_config=game_config,
        games=games,
        unused_cpu=unused_cpu,
    )
    visualizer.trace_genomes(pop=population, parallel=not debug)