Skip to content

Commit 17224db

Browse files
authored
Merge pull request #221 from ahmedfgad/github-actions
GitHub actions
2 parents 86dfe6f + a3e1219 commit 17224db

File tree

6 files changed

+182
-28
lines changed

6 files changed

+182
-28
lines changed

docs/source/gann.rst

+16-16
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ value (i.e. accuracy) of 100 is reached after around 180 generations.
535535
536536
ga_instance.plot_fitness()
537537
538-
.. figure:: https://user-images.githubusercontent.com/16560492/82078638-c11e0700-96e1-11ea-8aa9-c36761c5e9c7.png
538+
.. image:: https://user-images.githubusercontent.com/16560492/82078638-c11e0700-96e1-11ea-8aa9-c36761c5e9c7.png
539539
:alt:
540540

541541
By running the code again, a different initial population is created and
@@ -930,7 +930,7 @@ The number of wrong classifications is only 1 and the accuracy is
930930
931931
The next figure shows how fitness value evolves by generation.
932932

933-
.. figure:: https://user-images.githubusercontent.com/16560492/82152993-21898180-9865-11ea-8387-b995f88b83f7.png
933+
.. image:: https://user-images.githubusercontent.com/16560492/82152993-21898180-9865-11ea-8387-b995f88b83f7.png
934934
:alt:
935935

936936
Regression Example 1
@@ -998,10 +998,10 @@ for regression.
998998
GANN_instance.update_population_trained_weights(population_trained_weights=population_matrices)
999999
10001000
print("Generation = {generation}".format(generation=ga_instance.generations_completed))
1001-
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution()[1]))
1002-
print("Change = {change}".format(change=ga_instance.best_solution()[1] - last_fitness))
1001+
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1]))
1002+
print("Change = {change}".format(change=ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1] - last_fitness))
10031003
1004-
last_fitness = ga_instance.best_solution()[1].copy()
1004+
last_fitness = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1].copy()
10051005
10061006
# Holds the fitness value of the previous generation.
10071007
last_fitness = 0
@@ -1011,8 +1011,8 @@ for regression.
10111011
[8, 15, 20, 13]])
10121012
10131013
# Preparing the NumPy array of the outputs.
1014-
data_outputs = numpy.array([0.1,
1015-
1.5])
1014+
data_outputs = numpy.array([[0.1, 0.2],
1015+
[1.8, 1.5]])
10161016
10171017
# The length of the input vector for each sample (i.e. number of neurons in the input layer).
10181018
num_inputs = data_inputs.shape[1]
@@ -1022,7 +1022,7 @@ for regression.
10221022
GANN_instance = pygad.gann.GANN(num_solutions=num_solutions,
10231023
num_neurons_input=num_inputs,
10241024
num_neurons_hidden_layers=[2],
1025-
num_neurons_output=1,
1025+
num_neurons_output=2,
10261026
hidden_activations=["relu"],
10271027
output_activation="None")
10281028
@@ -1071,7 +1071,7 @@ for regression.
10711071
ga_instance.plot_fitness()
10721072
10731073
# Returning the details of the best solution.
1074-
solution, solution_fitness, solution_idx = ga_instance.best_solution()
1074+
solution, solution_fitness, solution_idx = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
10751075
print("Parameters of the best solution : {solution}".format(solution=solution))
10761076
print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
10771077
print("Index of the best solution : {solution_idx}".format(solution_idx=solution_idx))
@@ -1092,7 +1092,7 @@ for regression.
10921092
The next figure shows how the fitness value changes for the generations
10931093
used.
10941094

1095-
.. figure:: https://user-images.githubusercontent.com/16560492/92948154-3cf24b00-f459-11ea-94ea-952b66ab2145.png
1095+
.. image:: https://user-images.githubusercontent.com/16560492/92948154-3cf24b00-f459-11ea-94ea-952b66ab2145.png
10961096
:alt:
10971097

10981098
Regression Example 2 - Fish Weight Prediction
@@ -1164,15 +1164,15 @@ Here is the complete code.
11641164
GANN_instance.update_population_trained_weights(population_trained_weights=population_matrices)
11651165
11661166
print("Generation = {generation}".format(generation=ga_instance.generations_completed))
1167-
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution()[1]))
1168-
print("Change = {change}".format(change=ga_instance.best_solution()[1] - last_fitness))
1167+
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1]))
1168+
print("Change = {change}".format(change=ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1] - last_fitness))
11691169
1170-
last_fitness = ga_instance.best_solution()[1].copy()
1170+
last_fitness = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1].copy()
11711171
11721172
# Holds the fitness value of the previous generation.
11731173
last_fitness = 0
11741174
1175-
data = numpy.array(pandas.read_csv("Fish.csv"))
1175+
data = numpy.array(pandas.read_csv("../data/Fish.csv"))
11761176
11771177
# Preparing the NumPy array of the inputs.
11781178
data_inputs = numpy.asarray(data[:, 2:], dtype=numpy.float32)
@@ -1237,7 +1237,7 @@ Here is the complete code.
12371237
ga_instance.plot_fitness()
12381238
12391239
# Returning the details of the best solution.
1240-
solution, solution_fitness, solution_idx = ga_instance.best_solution()
1240+
solution, solution_fitness, solution_idx = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
12411241
print("Parameters of the best solution : {solution}".format(solution=solution))
12421242
print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
12431243
print("Index of the best solution : {solution_idx}".format(solution_idx=solution_idx))
@@ -1258,5 +1258,5 @@ Here is the complete code.
12581258
The next figure shows how the fitness value changes for the 500
12591259
generations used.
12601260

1261-
.. figure:: https://user-images.githubusercontent.com/16560492/92948486-bbe78380-f459-11ea-9e31-0d4c7269d606.png
1261+
.. image:: https://user-images.githubusercontent.com/16560492/92948486-bbe78380-f459-11ea-9e31-0d4c7269d606.png
12621262
:alt:

docs/source/pygad.rst

+143
Original file line numberDiff line numberDiff line change
@@ -4138,6 +4138,149 @@ and also saved in the text file.
41384138
2023-04-03 19:04:27 INFO: Generation = 10
41394139
2023-04-03 19:04:27 INFO: Fitness = 0.000389832593101348
41404140
4141+
Solve Non-Deterministic Problems
4142+
================================
4143+
4144+
PyGAD can be used to solve both deterministic and non-deterministic
4145+
problems. Deterministic are those that return the same fitness for the
4146+
same solution. For non-deterministic problems, a different fitness value
4147+
would be returned for the same solution.
4148+
4149+
By default, PyGAD settings are set to solve deterministic problems.
4150+
PyGAD can save the explored solutions and their fitness to reuse in the
4151+
future. These instances attributes can save the solutions:
4152+
4153+
1. ``solutions``: Exists if ``save_solutions=True``.
4154+
4155+
2. ``best_solutions``: Exists if ``save_best_solutions=True``.
4156+
4157+
3. ``last_generation_elitism``: Exists if ``keep_elitism`` > 0.
4158+
4159+
4. ``last_generation_parents``: Exists if ``keep_parents`` > 0 or
4160+
``keep_parents=-1``.
4161+
4162+
To configure PyGAD for non-deterministic problems, we have to disable
4163+
saving the previous solutions. This is by setting these parameters:
4164+
4165+
1. ``keep_elisitm=0``
4166+
4167+
2. ``keep_parents=0``
4168+
4169+
3. ``keep_solutions=False``
4170+
4171+
4. ``keep_best_solutions=False``
4172+
4173+
.. code:: python
4174+
4175+
import pygad
4176+
...
4177+
ga_instance = pygad.GA(...,
4178+
keep_elitism=0,
4179+
keep_parents=0,
4180+
save_solutions=False,
4181+
save_best_solutions=False,
4182+
...)
4183+
4184+
This way PyGAD will not save any explored solution and thus the fitness
4185+
function have to be called for each individual solution.
4186+
4187+
Reuse the Fitness instead of Calling the Fitness Function
4188+
=========================================================
4189+
4190+
It may happen that a previously explored solution in generation X is
4191+
explored again in another generation Y (where Y > X). For some problems,
4192+
calling the fitness function takes much time.
4193+
4194+
For deterministic problems, it is better to not call the fitness
4195+
function for an already explored solutions. Instead, reuse the fitness
4196+
of the old solution. PyGAD supports some options to help you save time
4197+
calling the fitness function for a previously explored solution.
4198+
4199+
The parameters explored in this section can be set in the constructor of
4200+
the ``pygad.GA`` class.
4201+
4202+
The ``cal_pop_fitness()`` method of the ``pygad.GA`` class checks these
4203+
parameters to see if there is a possibility of reusing the fitness
4204+
instead of calling the fitness function.
4205+
4206+
.. _1-savesolutions:
4207+
4208+
1. ``save_solutions``
4209+
---------------------
4210+
4211+
It defaults to ``False``. If set to ``True``, then the population of
4212+
each generation is saved into the ``solutions`` attribute of the
4213+
``pygad.GA`` instance. In other words, every single solution is saved in
4214+
the ``solutions`` attribute.
4215+
4216+
.. _2-savebestsolutions:
4217+
4218+
2. ``save_best_solutions``
4219+
--------------------------
4220+
4221+
It defaults to ``False``. If ``True``, then it only saves the best
4222+
solution in every generation.
4223+
4224+
.. _3-keepelitism:
4225+
4226+
3. ``keep_elitism``
4227+
-------------------
4228+
4229+
It accepts an integer and defaults to 1. If set to a positive integer,
4230+
then it keeps the elitism of one generation available in the next
4231+
generation.
4232+
4233+
.. _4-keepparents:
4234+
4235+
4. ``keep_parents``
4236+
-------------------
4237+
4238+
It accepts an integer and defaults to -1. It set to ``-1`` or a positive
4239+
integer, then it keeps the parents of one generation available in the
4240+
next generation.
4241+
4242+
Why the Fitness Function is not Called for Solution at Index 0?
4243+
===============================================================
4244+
4245+
PyGAD has a parameter called ``keep_elitism`` which defaults to 1. This
4246+
parameter defines the number of best solutions in generation **X** to
4247+
keep in the next generation **X+1**. The best solutions are just copied
4248+
from generation **X** to generation **X+1** without making any change.
4249+
4250+
.. code:: python
4251+
4252+
ga_instance = pygad.GA(...,
4253+
keep_elitism=1,
4254+
...)
4255+
4256+
The best solutions are copied at the beginning of the population. If
4257+
``keep_elitism=1``, this means the best solution in generation X is kept
4258+
in the next generation X+1 at index 0 of the population. If
4259+
``keep_elitism=2``, this means the 2 best solutions in generation X are
4260+
kept in the next generation X+1 at indices 0 and 1 of the population of
4261+
generation 1.
4262+
4263+
Because the fitness of these best solutions are already calculated in
4264+
generation X, then their fitness values will not be recalculated at
4265+
generation X+1 (i.e. the fitness function will not be called for these
4266+
solutions again). Instead, their fitness values are just reused. This is
4267+
why you see that no solution with index 0 is passed to the fitness
4268+
function.
4269+
4270+
To force calling the fitness function for each solution in every
4271+
generation, consider setting ``keep_elitism`` and ``keep_parents`` to 0.
4272+
Moreover, keep the 2 parameters ``save_solutions`` and
4273+
``save_best_solutions`` to their default value ``False``.
4274+
4275+
.. code:: python
4276+
4277+
ga_instance = pygad.GA(...,
4278+
keep_elitism=0,
4279+
keep_parents=0,
4280+
save_solutions=False,
4281+
save_best_solutions=False,
4282+
...)
4283+
41414284
Batch Fitness Calculation
41424285
=========================
41434286

pygad/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .pygad import * # Relative import.
22

3-
__version__ = "3.1.0"
3+
__version__ = "3.1.1"

pygad/pygad.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,11 @@ def __init__(self,
345345
elif len(gene_type) == 2 and gene_type[0] in GA.supported_float_types and (type(gene_type[1]) in GA.supported_int_types or gene_type[1] is None):
346346
self.gene_type = gene_type
347347
self.gene_type_single = True
348-
# A single data type of int with precision.
348+
# A single data type of integer with precision None ([int, None]).
349+
elif len(gene_type) == 2 and gene_type[0] in GA.supported_int_types and gene_type[1] is None:
350+
self.gene_type = gene_type
351+
self.gene_type_single = True
352+
# Raise an exception for a single data type of int with integer precision.
349353
elif len(gene_type) == 2 and gene_type[0] in GA.supported_int_types and (type(gene_type[1]) in GA.supported_int_types or gene_type[1] is None):
350354
self.gene_type_single = False
351355
raise ValueError(f"Integers cannot have precision. Please use the integer data type directly instead of {gene_type}.")
@@ -362,10 +366,8 @@ def __init__(self,
362366
self.valid_parameters = False
363367
raise ValueError(f"When the parameter 'gene_type' is nested, then it can be either [float, int<precision>] or with length equal to the value passed to the 'num_genes' parameter. Instead, value {gene_type} with len(gene_type) ({len(gene_type)}) != len(num_genes) ({num_genes}) found.")
364368
for gene_type_idx, gene_type_val in enumerate(gene_type):
365-
if gene_type_val in GA.supported_float_types:
366-
# If the gene type is float and no precision is passed, set it to None.
367-
gene_type[gene_type_idx] = [gene_type_val, None]
368-
elif gene_type_val in GA.supported_int_types:
369+
if gene_type_val in GA.supported_int_float_types:
370+
# If the gene type is float and no precision is passed or an integer, set its precision to None.
369371
gene_type[gene_type_idx] = [gene_type_val, None]
370372
elif type(gene_type_val) in [list, tuple, numpy.ndarray]:
371373
# A float type is expected in a list/tuple/numpy.ndarray of length 2.
@@ -376,6 +378,12 @@ def __init__(self,
376378
else:
377379
self.valid_parameters = False
378380
raise TypeError(f"In the 'gene_type' parameter, the precision for float gene data types must be an integer but the element {gene_type_val} at index {gene_type_idx} has a precision of {gene_type_val[1]} with type {gene_type_val[0]}.")
381+
elif gene_type_val[0] in GA.supported_int_types:
382+
if gene_type_val[1] is None:
383+
pass
384+
else:
385+
self.valid_parameters = False
386+
raise TypeError(f"In the 'gene_type' parameter, either do not set a precision for integer data types or set it to None. But the element {gene_type_val} at index {gene_type_idx} has a precision of {gene_type_val[1]} with type {gene_type_val[0]}.")
379387
else:
380388
self.valid_parameters = False
381389
raise TypeError(
@@ -1638,11 +1646,14 @@ def cal_pop_fitness(self):
16381646
# The functions numpy.any()/numpy.all()/numpy.where()/numpy.equal() are very slow.
16391647
# So, list membership operator 'in' is used to check if the solution exists in the 'self.solutions' list.
16401648
# Make sure that both the solution and 'self.solutions' are of type 'list' not 'numpy.ndarray'.
1641-
# if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(self.solutions == numpy.array(sol), axis=1))):
1642-
# if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(numpy.equal(self.solutions, numpy.array(sol)), axis=1))):
1649+
# if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(self.solutions == numpy.array(sol), axis=1)))
1650+
# if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(numpy.equal(self.solutions, numpy.array(sol)), axis=1)))
16431651
if (self.save_solutions) and (len(self.solutions) > 0) and (list(sol) in self.solutions):
16441652
solution_idx = self.solutions.index(list(sol))
16451653
fitness = self.solutions_fitness[solution_idx]
1654+
elif (self.save_best_solutions) and (len(self.best_solutions) > 0) and (list(sol) in self.best_solutions):
1655+
solution_idx = self.best_solutions.index(list(sol))
1656+
fitness = self.best_solutions_fitness[solution_idx]
16461657
elif (self.keep_elitism > 0) and (self.last_generation_elitism is not None) and (len(self.last_generation_elitism) > 0) and (list(sol) in last_generation_elitism_as_list):
16471658
# Return the index of the elitism from the elitism array 'self.last_generation_elitism'.
16481659
# This is not its index within the population. It is just its index in the 'self.last_generation_elitism' array.
@@ -1851,7 +1862,7 @@ def run(self):
18511862

18521863
# Appending the best solution in the initial population to the best_solutions list.
18531864
if self.save_best_solutions:
1854-
self.best_solutions.append(best_solution)
1865+
self.best_solutions.append(list(best_solution))
18551866

18561867
for generation in range(generation_first_idx, generation_last_idx):
18571868
if not (self.on_fitness is None):
@@ -2077,7 +2088,7 @@ def run(self):
20772088

20782089
# Appending the best solution in the current generation to the best_solutions list.
20792090
if self.save_best_solutions:
2080-
self.best_solutions.append(best_solution)
2091+
self.best_solutions.append(list(best_solution))
20812092

20822093
# If the on_generation attribute is not None, then cal the callback function after the generation.
20832094
if not (self.on_generation is None):

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
99

1010
[project]
1111
name = "pygad"
12-
version = "3.1.0"
12+
version = "3.1.1"
1313
description = "PyGAD: A Python Library for Building the Genetic Algorithm and Training Machine Learning Algoithms (Keras & PyTorch)."
1414
readme = {file = "README.md", content-type = "text/markdown"}
1515
requires-python = ">=3"

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="pygad",
8-
version="3.1.0",
8+
version="3.1.1",
99
author="Ahmed Fawzy Gad",
1010
install_requires=["numpy", "matplotlib", "cloudpickle",],
1111
author_email="[email protected]",

0 commit comments

Comments
 (0)