ahmedfgad / GeneticAlgorithmPython

Source code of PyGAD, a Python 3 library for building the genetic algorithm and training machine learning algorithms (Keras & PyTorch).
https://pygad.readthedocs.io
BSD 3-Clause "New" or "Revised" License
1.81k stars 452 forks source link

Multi-objective optimization with NSGA II throwing out solutions in Pareto front #294

Closed samuelkim16 closed 2 weeks ago

samuelkim16 commented 2 weeks ago

I have a multi-objective optimization problem where my batched fitness function returns a result in the shape of (30, 2) (so 2 objectives). The settings for the optimizer are as follows:

# define the optimizer
ga_instance = pygad.GA(
    num_generations=25,
    num_parents_mating=10,
    fitness_func=fitness_function,
    parent_selection_type='nsga2',
    mutation_type='inversion,
    crossover_type='single_point',
    crossover_probability=0.7,
    sol_per_pop=30,
    num_genes= 2*n_p+1,
    gene_type=float,
    gene_space={"low": 0, "high": 1},
    fitness_batch_size=30,
    on_generation=on_generation,
    random_seed=1,
    initial_population=initial_population
)

where in each generation I am printing out the first Pareto front:

# function to be called in each generation
def on_generation(ga_instance):
    best_fitness = ga_instance.best_solution()[1]
    print(f"Generation {ga_instance.generations_completed}: Best Fitness = {best_fitness}")
    print(f"Pareto front: {ga_instance.pareto_fronts[0]}")

The printed results look like this:

Generation 1: Best Fitness = [-17.38416815  19.92912316]
Pareto front: [[3 array([-6.55857782,  9.5409604 ])]
 [6 array([-7.37340717, 10.36238098])]
 [15 array([-7.34519842, 10.31230688])]
 [18 array([-6.51180454,  8.38007208])]
 [24 array([-7.48853374, 10.40076346])]
 [28 array([-26.37518118,  12.23939783])]]
Generation 2: Best Fitness = [-17.38416815  19.92912316]
Pareto front: [[8 array([-11.86598352,  18.05277911])]
 [10 array([-17.38416815,  19.92912316])]
 [11 array([-5.79559602, 10.85242201])]
 [21 array([-7.25440122, 12.90667087])]
 [27 array([-8.4675806 , 13.64455862])]
 [28 array([-8.73404528, 14.0161257 ])]]
Generation 3: Best Fitness = [-12.6581161  23.4540812]
Pareto front: [[0 array([-17.38416815,  19.92912316])]
 [2 array([-8.46513869, 11.56191251])]
 [16 array([-11.67761194,  17.59600187])]
 [28 array([-9.27924757, 12.16203073])]
 [29 array([-5.79790117, 10.84784921])]]
Generation 4: Best Fitness = [-12.6581161  23.4540812]
Pareto front: [[3 array([-12.6581161,  23.4540812])]
 [4 array([-7.4686467 , 13.31209979])]
 [20 array([-9.63339928, 16.74305374])]]
Generation 5: Best Fitness = [-12.6581161  23.4540812]
Pareto front: [[0 array([-12.6581161,  23.4540812])]
 [20 array([-11.67129714,  13.60676195])]
 [28 array([-8.84810887, 11.49664033])]
 [29 array([-8.32195061, 11.16278905])]]
Generation 6: Best Fitness = [-12.98289791  23.82662822]
Pareto front: [[0 array([-12.6581161,  23.4540812])]
 [5 array([-9.93484854, 16.58056773])]
 [27 array([-7.21404887, 10.23609733])]]
Generation 7: Best Fitness = [-6.45931356 23.14222777]
Pareto front: [[0 array([-12.6581161,  23.4540812])]
 [7 array([-6.45931356, 23.14222777])]
 [29 array([-12.98289791,  23.82662822])]]
Generation 8: Best Fitness = [-12.69300769  24.86638202]
Pareto front: [[0 array([-6.45931356, 23.14222777])]]
Generation 9: Best Fitness = [-12.69300769  24.86638202]
Pareto front: [[0 array([-6.45931356, 23.14222777])]
 [24 array([-12.69300769,  24.86638202])]]
Generation 10: Best Fitness = [-7.21354006 34.57982822]
Pareto front: [[0 array([-12.69300769,  24.86638202])]
 [21 array([-9.48625113, 13.75548113])]
 [22 array([-8.41062007,  7.57419817])]
 [25 array([-9.2502522 ,  9.50216879])]
 [28 array([-10.62302046,  16.76700122])]]
Generation 11: Best Fitness = [-7.21354006 34.57982822]
Pareto front: [[23 array([-7.21354006, 34.57982822])]]

The problem is that several of the points in the Pareto front are being thrown out in subsequent generations. For example, going from generation 3 to 4, the point corresponding to fitness array([-5.79790117, 10.84784921]) disappears, even though it is at one extreme of the Pareto front. The algorithm seems to bias towards the second objective since it is a larger scale, although I would hope that the entire Pareto front is kept due to the multi-objective problem. Is there some setting to keep the entire Pareto front?

samuelkim16 commented 2 weeks ago

I think I have found the fix, just by setting the parameters

keep_parents=5,
keep_elitism=0,

It would be nice if there was more flexibility in being able to have keep_parents be dynamic so that it just keeps the Pareto front.