Skip to content

Commit

Permalink
D. Jabs:
Browse files Browse the repository at this point in the history
- Updated Workflow file
- Updated Rank-based fitness preprocessing, where it know returns always the same rank (independent of min/max optimizer)
- Updated Fitness proportional selection, where we know create the probability distribution with softmax
  • Loading branch information
Dennis Jabs committed Nov 12, 2023
1 parent 2535f17 commit 2e511d7
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 57 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/PyEvo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ jobs:
uses: py-actions/py-dependency-install@v4
with:
path: "requirements.txt"
- name: Build Python Package
run: |
pip install .
#- name: Build Python Package
# run: |
# pip install .
- name: Run Unittests
run: |
python -m unittest discover -s tests/ -p "test_*.py"
26 changes: 6 additions & 20 deletions PyEvo/fitness.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,8 @@ def _preprocess_fitness(
class FitnessNormalizer(FitnessPreprocessor):
"""
Class responsible for normalizing fitness values of a population from [-min_value, +max_value] to [0, 1].
Args:
normalize (bool):
The normalized fitness values gets transformed to a probability distribution, where sum(fitness) = 1.
"""

def __init__(self, normalize: bool = True):
self._normalize = normalize

def _preprocess_fitness(
self,
random: np.random.RandomState,
Expand All @@ -127,15 +120,6 @@ def _preprocess_fitness(
# Return normalized fitness values (from [-min_value, +max_value] -> [0, 1])
normalized_values = [(f - min_fitness) / (max_fitness - min_fitness) for f in fitness]

if self._normalize:
# Case: Normalize to a probability distribution
sum_values = sum(normalized_values)

if sum_values == 0:
# Case: Prevent dividing with 0
sum_values = 1e-10

normalized_values = [f / sum_values for f in normalized_values]
return normalized_values


Expand Down Expand Up @@ -205,7 +189,11 @@ class FitnessRanker(FitnessPreprocessor):
"""
Class responsible for rank-based fitness assignment.
Each transformed fitness value responds to a rank r_i based on the performance (fitness) f_i of the individual.
Each transformed fitness value responds to a rank r_i based on the performance (fitness) f_i of the individual in
asc order.
The lowest fitness value f_i gets the lowest rank value r_i := start, where the highest fitness value f_i gets the
highest rank value r_j := start + j
Args:
start (int):
Expand All @@ -225,10 +213,8 @@ def _preprocess_fitness(
optimizer: str,
**kwargs,
) -> list[float]:
reverse = False if optimizer == "min" else True

# Return indices of the sorted list
indices = sorted(range(len(fitness)), key=lambda idx: fitness[idx], reverse=reverse)
indices = sorted(range(len(fitness)), key=lambda idx: fitness[idx], reverse=False)

# Assign each fitness value the ranks
ranks = [0 for _ in range(len(fitness))]
Expand Down
26 changes: 22 additions & 4 deletions PyEvo/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,11 @@ def _select(

class FitnessProportionalSelection(Selection):
# TODO: Implement fitness proportionate selection (https://en.wikipedia.org/wiki/Fitness_proportionate_selection)
def __init__(self, replace: bool = False):
def __init__(self, temperature: float = 1.0, replace: bool = False):
assert temperature > 0.0, f"Illegal temperature {temperature}. This argument should be higher than 0.0!"
self._temperature = temperature
self._replace = replace

def _select(
self,
random: np.random.RandomState,
Expand All @@ -181,10 +184,25 @@ def _select(
n_select: int,
**kwargs,
) -> tuple[list[HyperparameterConfiguration], list[float], list[HyperparameterConfiguration], list[float]]:
assert sum(fitness) == 1, \
"Illegal fitness. The sum of the fitness values should be equal to 1 (as in a probability distribution)!"

indices = random.choice(range(len(pop)), size=n_select, replace=self._replace, p=fitness)
if optimizer == "min":
# Case: Minimization problem, so lower values should get higher probabilities
# Negative all fitness values
corrected_fitness = [-f for f in fitness]
else:
# Case: Maximization problem
# No need to change the fitness values
corrected_fitness = [f for f in fitness]

# Normalize the fitness values to a probability distribution via softmax
exp_values = [np.exp(f / self._temperature) for f in corrected_fitness]
sum_exp_values = sum(exp_values)
if sum_exp_values == 0:
# Case: Prevent from dividing with zero
sum_exp_values = 1e-10
prob = [f / sum_exp_values for f in exp_values]

indices = random.choice(range(len(pop)), size=n_select, replace=self._replace, p=prob)

# Extract the selected, non-selected individuals and fitness values
selected = [pop[idx] for idx in indices]
Expand Down
6 changes: 3 additions & 3 deletions tests/test_fitness.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ def test_preprocess_fitness_minimizer(self):
preprocessed_fitness = self.fitness_preprocessor.preprocess_fitness(self.random, self.cs, self.pop,
self.fitness, self.optimizer_min)
normalized = [(f - min(self.fitness)) / (max(self.fitness) - min(self.fitness)) for f in self.fitness]
normalized = [f / sum(normalized) for f in normalized]
self.assertEqual(len(self.fitness), len(preprocessed_fitness))
self.assertEqual(normalized, preprocessed_fitness)

Expand All @@ -107,7 +106,6 @@ def test_preprocess_fitness_maximizer(self):
preprocessed_fitness = self.fitness_preprocessor.preprocess_fitness(self.random, self.cs, self.pop,
self.fitness, self.optimizer_max)
normalized = [(f - min(self.fitness)) / (max(self.fitness) - min(self.fitness)) for f in self.fitness]
normalized = [f / sum(normalized) for f in normalized]
self.assertEqual(len(self.fitness), len(preprocessed_fitness))
self.assertEqual(normalized, preprocessed_fitness)

Expand Down Expand Up @@ -148,6 +146,7 @@ def test_preprocess_fitness_minimizer(self):
preprocessed_fitness = self.fitness_preprocessor.preprocess_fitness(self.random, self.cs, self.pop,
self.fitness, self.optimizer_min)
normalized = [(f - np.mean(self.fitness)) / np.std(self.fitness) for f in self.fitness]

self.assertEqual(len(self.fitness), len(preprocessed_fitness))
self.assertEqual(normalized, preprocessed_fitness)

Expand All @@ -158,6 +157,7 @@ def test_preprocess_fitness_maximizer(self):
preprocessed_fitness = self.fitness_preprocessor.preprocess_fitness(self.random, self.cs, self.pop,
self.fitness, self.optimizer_max)
normalized = [(f - np.mean(self.fitness)) / np.std(self.fitness) for f in self.fitness]

self.assertEqual(len(self.fitness), len(preprocessed_fitness))
self.assertEqual(normalized, preprocessed_fitness)

Expand Down Expand Up @@ -259,7 +259,7 @@ def test_preprocess_fitness_maximizer(self):
preprocessed_fitness = self.fitness_preprocessor.preprocess_fitness(self.random, self.cs, self.pop,
self.fitness, self.optimizer_max)
self.assertEqual(len(self.fitness), len(preprocessed_fitness))
self.assertEqual([9, 5, 4, 2, 10, 6, 1, 3, 7, 8], preprocessed_fitness)
self.assertEqual([2, 6, 7, 9, 1, 5, 10, 8, 4, 3], preprocessed_fitness)


if __name__ == '__main__':
Expand Down
54 changes: 27 additions & 27 deletions tests/test_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def setUp(self):
seed=0,
)
self.pop = self.cs.sample_configuration(10)
self.fitness = [0.004914004914004915, 0.09705159705159706, 0.14496314496314497, 0.18673218673218675, 0.0, 0.09336609336609338, 0.21130221130221133, 0.15356265356265356, 0.05896805896805897, 0.049140049140049144]
self.fitness = [0.023255813953488372, 0.45930232558139533, 0.686046511627907, 0.8837209302325582, 0.0, 0.4418604651162791, 1.0, 0.7267441860465116, 0.27906976744186046, 0.23255813953488372]
self.optimizer_min = "min"
self.optimizer_max = "max"
self.n_select = 5
Expand All @@ -260,31 +260,31 @@ def test_select_minimizer(self):
self.optimizer_min,
self.n_select)
self.assertEqual(self.n_select, len(selected))
self.assertEqual(selected[0], self.pop[6])
self.assertEqual(selected[1], self.pop[3])
self.assertEqual(selected[2], self.pop[7])
self.assertEqual(selected[3], self.pop[5])
self.assertEqual(selected[4], self.pop[8])
self.assertEqual(selected[0], self.pop[5])
self.assertEqual(selected[1], self.pop[7])
self.assertEqual(selected[2], self.pop[4])
self.assertEqual(selected[3], self.pop[6])
self.assertEqual(selected[4], self.pop[2])

self.assertEqual(self.n_select, len(fitness_selected))
self.assertEqual(fitness_selected[0], self.fitness[6])
self.assertEqual(fitness_selected[1], self.fitness[3])
self.assertEqual(fitness_selected[2], self.fitness[7])
self.assertEqual(fitness_selected[3], self.fitness[5])
self.assertEqual(fitness_selected[4], self.fitness[8])
self.assertEqual(fitness_selected[0], self.fitness[5])
self.assertEqual(fitness_selected[1], self.fitness[7])
self.assertEqual(fitness_selected[2], self.fitness[4])
self.assertEqual(fitness_selected[3], self.fitness[6])
self.assertEqual(fitness_selected[4], self.fitness[2])

self.assertEqual(len(self.pop) - self.n_select, len(non_selected))
self.assertEqual(non_selected[0], self.pop[0])
self.assertEqual(non_selected[1], self.pop[1])
self.assertEqual(non_selected[2], self.pop[2])
self.assertEqual(non_selected[3], self.pop[4])
self.assertEqual(non_selected[2], self.pop[3])
self.assertEqual(non_selected[3], self.pop[8])
self.assertEqual(non_selected[4], self.pop[9])

self.assertEqual(len(self.fitness) - self.n_select, len(fitness_non_selected))
self.assertEqual(fitness_non_selected[0], self.fitness[0])
self.assertEqual(fitness_non_selected[1], self.fitness[1])
self.assertEqual(fitness_non_selected[2], self.fitness[2])
self.assertEqual(fitness_non_selected[3], self.fitness[4])
self.assertEqual(fitness_non_selected[2], self.fitness[3])
self.assertEqual(fitness_non_selected[3], self.fitness[8])
self.assertEqual(fitness_non_selected[4], self.fitness[9])

def test_select_maximizer(self):
Expand All @@ -297,31 +297,31 @@ def test_select_maximizer(self):
self.n_select)

self.assertEqual(self.n_select, len(selected))
self.assertEqual(selected[0], self.pop[6])
self.assertEqual(selected[1], self.pop[3])
self.assertEqual(selected[2], self.pop[7])
self.assertEqual(selected[3], self.pop[5])
self.assertEqual(selected[4], self.pop[8])
self.assertEqual(selected[0], self.pop[5])
self.assertEqual(selected[1], self.pop[6])
self.assertEqual(selected[2], self.pop[4])
self.assertEqual(selected[3], self.pop[7])
self.assertEqual(selected[4], self.pop[3])

self.assertEqual(self.n_select, len(fitness_selected))
self.assertEqual(fitness_selected[0], self.fitness[6])
self.assertEqual(fitness_selected[1], self.fitness[3])
self.assertEqual(fitness_selected[2], self.fitness[7])
self.assertEqual(fitness_selected[3], self.fitness[5])
self.assertEqual(fitness_selected[4], self.fitness[8])
self.assertEqual(fitness_selected[0], self.fitness[5])
self.assertEqual(fitness_selected[1], self.fitness[6])
self.assertEqual(fitness_selected[2], self.fitness[4])
self.assertEqual(fitness_selected[3], self.fitness[7])
self.assertEqual(fitness_selected[4], self.fitness[3])

self.assertEqual(len(self.pop) - self.n_select, len(non_selected))
self.assertEqual(non_selected[0], self.pop[0])
self.assertEqual(non_selected[1], self.pop[1])
self.assertEqual(non_selected[2], self.pop[2])
self.assertEqual(non_selected[3], self.pop[4])
self.assertEqual(non_selected[3], self.pop[8])
self.assertEqual(non_selected[4], self.pop[9])

self.assertEqual(len(self.fitness) - self.n_select, len(fitness_non_selected))
self.assertEqual(fitness_non_selected[0], self.fitness[0])
self.assertEqual(fitness_non_selected[1], self.fitness[1])
self.assertEqual(fitness_non_selected[2], self.fitness[2])
self.assertEqual(fitness_non_selected[3], self.fitness[4])
self.assertEqual(fitness_non_selected[3], self.fitness[8])
self.assertEqual(fitness_non_selected[4], self.fitness[9])


Expand Down

0 comments on commit 2e511d7

Please sign in to comment.