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.79k stars 451 forks source link

delay_after_gen warning #283

Open wassimj opened 3 months ago

wassimj commented 3 months ago

Hello, I am using PyGAD version: 3.3.1 on Windows with python 3.10 within jupyter notebook.

When I run my GA, I am getting the following user warning. This is not something I am setting. It seems to emanate from the internal pygad code. How can I avoid having this warning displayed? Thank you

C:\Users\wassimj\AppData\Local\Programs\Python\Python310\lib\site-packages\pygad\pygad.py:1139: UserWarning:

The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay.
ahmedfgad commented 3 months ago

Thanks, @wassimj. This is a bug. This warning should only be printed if you used the delay_after_gen parameter.

It will be fixed soon.

gnypit commented 1 month ago

Hi all,

I've encountered this as well. What's interesting is that if I re-run the code, the warning disapears and the overall performance of the genetic algorithm improves. For example, the first time the warning comes out, the final result is correct, but the fitness history shows that fitness values varied quite strongly - sometimes fitness wasn't even a non-decreasing function, in spite of a non-zero value in the keep_parents parameter (keeping the so-called 'elite'). Since I haven't been able to reproduce it yet, that's probably for another time.

Back to the delay_after_gen issue; this is a simple code for a packaging problem - a thief has to choose what to steal with a limited maximum weight he can carry. Items at the house have different values and weights. The goal is to choose the most expensive things without surpassing the weight limit. The code is as follows:

import pygad
import matplotlib.pyplot as plt

house = {
    1: ['clock', 100, 7],
    2: ['landscape painting', 300, 7],
    3: ['portrait painting', 200, 6],
    4: ['radio', 40, 2],
    5: ['laptop', 500, 5],
    6: ['night lamp', 70, 6],
    7: ['silver cutlery', 100, 1],
    8: ['porcelain', 250, 3],
    9: ['bronze figurine', 300, 10],
    10: ['leather handbag', 280, 3],
    11: ['vacuum cleaner', 300, 15]
}

def fitness_function(genetic_algorithm_instance, solution, solution_index):
    weight = 0
    fitness = 0

    for i in range(len(solution)):
        if solution[i] == 1:
            fitness += domek[i+1][1]
            weight += domek[i+1][2]
            if weight > 25:
                fitness += -2000

    return fitness

ga_instance = pygad.GA(
    gene_space=[0, 1],
    num_generations=100,
    num_parents_mating=15,
    fitness_func=fitness_function,
    sol_per_pop=40,
    num_genes=11,
    parent_selection_type="tournament",
    keep_parents=5,
    crossover_type="single_point",
    mutation_type="random",
    mutation_probability=0.02
)

# At this point a UserWarning appears

ga_instance.run()

solution, solution_fitness, solution_idx = ga_instance.best_solution()
print("Parameters of the best solution : {solution}".format(solution=solution))
print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))

prediction = []
money= 0
weight = 0
for i in range(len(solution)):
    if solution[i] == 1:
        prediction.append(domek[i+1][0])
        money += house[i+1][1]
        weight += house[i+1][2]

print("Predicted output based on the best solution : {prediction}".format(prediction=prediction))
print(money)
print(weight)

# Ensure any previous plots are cleared
plt.clf()

# Plot the fitness values vs. generations with proper labels (ga_instance.plot_fitness() gives two plots)
plt.plot(ga_instance.best_solutions_fitness, label='Best Fitness')
plt.xlabel("Generation")
plt.ylabel("Fitness")
plt.title("Fitness over Generations")
plt.legend()
plt.show()

The waring I get is this:

C:\Users\Jakub\AppData\Local\Programs\Python\Python311\Lib\site-packages\pygad\pygad.py:1139: UserWarning: The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay. warnings.warn("The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay.")

If I re-run the code, result and overall performance improve, while 1the warnings disappear. Fitness plots: output_thief_01 output_thief_02

Another matter altogether is that it seems to me that in spite of restarting the kernel, pygad somehow remembers something about previous runs, because while the file is open in PyCharm, the 'bad' fitness history doesn't actually repeat. To be more precise, only at the first run with the kernel still operating does the algorithm get stuck in a local maximum of the fitness function. So that's curious on it's own - I apologise, if my lack of understanding and knowledge is showing.

I debugged the 'ga_instance' initialisation with pydev debugger (build 241.14494.241). The 'delay_after_gen' was indeed set by default to 0.0; after a while I got to this section of the pygad.py file:

# Validate delay_after_gen
            if type(delay_after_gen) in GA.supported_int_float_types:
                if not self.suppress_warnings:
                    warnings.warn("The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay.")
                if delay_after_gen >= 0.0:
                    self.delay_after_gen = delay_after_gen
                else:
                    self.valid_parameters = False
                    raise ValueError(f"The value passed to the 'delay_after_gen' parameter must be a non-negative number. The value passed is ({delay_after_gen}) of type {type(delay_after_gen)}.")
            else:
                self.valid_parameters = False
                raise TypeError(f"The value passed to the 'delay_after_gen' parameter must be of type int or float but {type(delay_after_gen)} found.")

Since delay_after_gen is by default set to 0.0, it is a float value and it passes the if type(delay_after_gen) in GA.supported_int_float_types: check. Then, since (again, by default) the self.suppress_warnings is set to False, the warning from the first nested conditional clause is run: Zrzut ekranu 2024-06-01 152734 Zrzut ekranu 2024-06-01 152753 Zrzut ekranu 2024-06-01 152859

My idea is to change the default value of the delay_after_gen parameter to None. I guess (haven't checked that yet tbh) that it is not included in the supported types of variables for the parameter. What's the most interesting though, is that when I re-run the code with the same kernel operating, the same default values are assigned to both delay_after_gen and self.suppress_warnings, the waring doesn't show and while debugging I didn't get to the section of the code above ^ what else does it depend on? I didn't notice any other change, and the delay_after_gen falls under the try clause right in the __init__() constructor of the GA class. Since it is not nested in another conditional clause or anything else like that, what's the difference between the first run and the other runs?

Just in case: I'm using Python 3.12.3 and pygad 3.3.1.

Kk-Ling commented 8 hours ago

suppress_warnings=True