source: framspy/evolalg/examples/niching_novelty.py @ 1129

Last change on this file since 1129 was 1129, checked in by Maciej Komosinski, 4 years ago

Introduced tournament size as a command-line parameter

File size: 9.9 KB
Line 
1import argparse
2import os
3import pickle
4import sys
5from enum import Enum
6
7import numpy as np
8
9from FramsticksLib import FramsticksLib
10from evolalg.base.lambda_step import LambdaStep
11from evolalg.base.step import Step
12from evolalg.dissimilarity.frams_dissimilarity import FramsDissimilarity
13from evolalg.dissimilarity.levenshtein import LevenshteinDissimilarity
14from evolalg.experiment import Experiment
15from evolalg.fitness.fitness_step import FitnessStep
16from evolalg.mutation_cross.frams_cross_and_mutate import FramsCrossAndMutate
17from evolalg.population.frams_population import FramsPopulation
18from evolalg.repair.remove.field import FieldRemove
19from evolalg.repair.remove.remove import Remove
20from evolalg.selection.tournament import TournamentSelection
21from evolalg.statistics.halloffame_stats import HallOfFameStatistics
22from evolalg.statistics.statistics_deap import StatisticsDeap
23from evolalg.base.union_step import UnionStep
24from evolalg.utils.population_save import PopulationSave
25import time
26
27
28def ensureDir(string):
29    if os.path.isdir(string):
30        return string
31    else:
32        raise NotADirectoryError(string)
33
34
35class Dissim(Enum):
36    levenshtein = "levenshtein"
37    frams = "frams"
38
39    def __str__(self):
40        return self.name
41
42
43class Fitness(Enum):
44    raw = "raw"
45    niching = "niching"
46    novelty = "novelty"
47
48    def __str__(self):
49        return self.name
50
51
52def parseArguments():
53    parser = argparse.ArgumentParser(
54        description='Run this program with "python -u %s" if you want to disable buffering of its output.' % sys.argv[
55            0])
56    parser.add_argument('-path', type=ensureDir, required=True, help='Path to the Framsticks library without trailing slash.')
57    parser.add_argument('-opt', required=True,
58                        help='optimization criteria : vertpos, velocity, distance, vertvel, lifespan, numjoints, numparts, numneurons, numconnections. Single or multiple criteria.')
59    parser.add_argument('-lib', required=False, help="Filename of .so or .dll with framsticks library")
60    parser.add_argument('-genformat', required=False, default="1",
61                        help='Genetic format for the demo run, for example 4, 9, or B. If not given, f1 is assumed.')
62    parser.add_argument('-sim', required=False, default="eval-allcriteria.sim", help="Name of .sim file")
63    parser.add_argument('-dissim', required=False, type=Dissim, default=Dissim.frams,
64                        help=' Dissimilarity measure DEFAULT = frams', choices=list(Dissim))
65    parser.add_argument('-fit', required=False, default=Fitness.raw, type=Fitness,
66                        help=' Fitness criteria DEFAULT = raw', choices=list(Fitness))
67    parser.add_argument('-popsize', type=int, default=50, help="Population size, default 50.")
68    parser.add_argument('-generations', type=int, default=5, help="Number of generations, default 5.")
69    parser.add_argument('-tournament', type=int, default=5, help="Tournament size, default 5.")
70    parser.add_argument('-num_parts', type=int, default=None, help="Maximum number of Parts. Default: no limit")
71    parser.add_argument('-checkpoint_path', required=False, default=None, help="Path to the checkpoint file")
72    parser.add_argument('-checkpoint_interval', required=False, type=int, default=100, help="Checkpoint interval")
73    return parser.parse_args()
74
75
76def extract_fitness(ind):
77    return ind.fitness_raw
78
79
80def print_population_count(pop):
81    print("Current:", len(pop))
82    return pop  # Each step must return a population
83
84
85class NumPartsGreater(Remove):
86    def __init__(self, numparts):
87        super(NumPartsGreater, self).__init__()
88        self.numparts = numparts
89
90    def remove(self, individual):
91        return individual.numparts > self.numparts
92
93
94def func_niching(ind): setattr(ind, "fitness", ind.fitness_raw * (1 + ind.dissim))
95
96
97def func_raw(ind): setattr(ind, "fitness", ind.fitness_raw)
98
99
100def func_novelty(ind): setattr(ind, "fitness", ind.dissim)
101
102
103def load_experiment(path):
104    with open(path, "rb") as file:
105        experiment = pickle.load(file)
106    print("Loaded experiment. Generation:", experiment.generation)
107    return experiment
108
109
110def create_experiment():
111    parsed_args = parseArguments()
112    frams = FramsticksLib(parsed_args.path, parsed_args.lib,
113                          parsed_args.sim)
114    # Steps for generating first population
115    init_stages = [
116        FramsPopulation(frams, parsed_args.genformat, parsed_args.popsize)
117    ]
118
119    # Selection procedure
120    selection = TournamentSelection(parsed_args.tournament,
121                                    copy=True)  # 'fitness' by default, the targeted attribute can be changed, e.g. fit_attr="fitness_raw"
122
123    # Procedure for generating new population. This steps will be run as long there is less than
124    # popsize individuals in the new population
125    new_generation_stages = [FramsCrossAndMutate(frams, cross_prob=0.2, mutate_prob=0.9)]
126
127    # Steps after new population is created. Executed exacly once per generation.
128    generation_modifications = []
129
130    # -------------------------------------------------
131    # Fitness
132
133    fitness_raw = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw", "numparts": "numparts"},
134                              fields_defaults={parsed_args.opt: None, "numparts": float("inf")},
135                              evaluation_count=1)
136
137    fitness_end = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw"},
138                              fields_defaults={parsed_args.opt: None},
139                              evaluation_count=100)  # evaluate the contents of the last population 100 times (TODO replace this approach and evaluate HOF instead of the last population)
140    # Remove
141    remove = []
142    remove.append(FieldRemove("fitness_raw", None))  # Remove individuals if they have default value for fitness
143    if parsed_args.num_parts is not None:
144        # This could be also implemented by "LambdaRemove(lambda x: x.numparts > parsed_args.num_parts)
145        # But this will not serialize in checkpoint.
146        remove.append(NumPartsGreater(parsed_args.num_parts))
147    remove_step = UnionStep(remove)
148
149    fitness_remove = UnionStep([fitness_raw, remove_step])
150
151    init_stages.append(fitness_remove)
152    new_generation_stages.append(fitness_remove)
153
154    # -------------------------------------------------
155    # Novelty or niching
156    dissim = None
157    if parsed_args.dissim == Dissim.levenshtein:
158        dissim = LevenshteinDissimilarity(reduction="mean", output_field="dissim")
159    elif parsed_args.dissim == Dissim.frams:
160        dissim = FramsDissimilarity(frams, reduction="mean", output_field="dissim")
161
162    if parsed_args.fit == Fitness.raw:
163        # Fitness is equal to finess raw
164        raw = LambdaStep(func_raw)
165        init_stages.append(raw)
166        new_generation_stages.append(raw)
167        generation_modifications.append(raw)
168
169    if parsed_args.fit == Fitness.niching:
170        niching = UnionStep([
171            dissim,
172            LambdaStep(func_niching)
173        ])
174        init_stages.append(niching)
175        new_generation_stages.append(niching)
176        generation_modifications.append(niching)
177
178    if parsed_args.fit == Fitness.novelty:
179        novelty = UnionStep([
180            dissim,
181            LambdaStep(func_novelty)
182        ])
183        init_stages.append(novelty)
184        new_generation_stages.append(novelty)
185        generation_modifications.append(novelty)
186
187    # -------------------------------------------------
188    # Statistics
189    hall_of_fame = HallOfFameStatistics(100, "fitness_raw")  # Wrapper for halloffamae
190    statistics_deap = StatisticsDeap([
191        ("avg", np.mean),
192        ("stddev", np.std),
193        ("min", np.min),
194        ("max", np.max)
195    ], extract_fitness)  # Wrapper for deap statistics
196
197    statistics_union = UnionStep([
198        hall_of_fame,
199        statistics_deap
200    ])  # Union of two statistics steps.
201
202    init_stages.append(statistics_union)
203    generation_modifications.append(statistics_union)
204
205    # -------------------------------------------------
206    # End stages: this will execute exacly once after all generations.
207    end_stages = [
208        fitness_end,
209        PopulationSave("halloffame.gen", provider=hall_of_fame.halloffame, fields={"genotype": "genotype",
210                                                                                  "fitness": "fitness_raw"})]
211    # ...but custom fields can be added, e.g. "custom": "recording"
212
213    # -------------------------------------------------
214    # Experiment creation
215
216    experiment = Experiment(init_population=init_stages,
217                            selection=selection,
218                            new_generation_steps=new_generation_stages,
219                            generation_modification=generation_modifications,
220                            end_steps=end_stages,
221                            population_size=parsed_args.popsize,
222                            checkpoint_path=parsed_args.checkpoint_path,
223                            checkpoint_interval=parsed_args.checkpoint_interval
224                            )
225    return experiment
226
227
228def main():
229    print("Running experiment with", sys.argv)
230    parsed_args = parseArguments()
231
232    if os.path.exists(parsed_args.checkpoint_path):
233        experiment = load_experiment(parsed_args.checkpoint_path)
234        FramsticksLib(parsed_args.path, parsed_args.lib,
235                      parsed_args.sim)
236    else:
237        experiment = create_experiment()
238        experiment.init()  # init is mandatory
239
240
241    experiment.run(parsed_args.generations)
242
243    # Next call for experiment.run(10) will do nothing. Parameter 10 specifies how many generations should be
244    # in one experiment. Previous call generated 10 generations.
245    # Example 1:
246    # experiment.init()
247    # experiment.run(10)
248    # experiment.run(12)
249    # #This will run for total of 12 generations
250    #
251    # Example 2
252    # experiment.init()
253    # experiment.run(10)
254    # experiment.init()
255    # experiment.run(10)
256    # # All work produced by first run will be "destroyed" by second init().
257
258
259
260if __name__ == '__main__':
261    main()
Note: See TracBrowser for help on using the repository browser.