Selection Strategies and Population Diversity#

This page continues the Genetic Algorithms walkthrough, again minimizing the Rosenbrock function, and looks at two knobs for tuning convergence behaviour: the selection strategy and the diagnostic diversity metric.

from typing import Iterable
from numbers import Number
import matplotlib.pyplot as plt

from sigmaepsilon.math.optimize import BinaryGeneticAlgorithm as BGA


def Rosenbrock(x: Iterable[Number], a: Number = 1, b: Number = 100) -> float:
    return (a - x[0]) ** 2 + b * (x[1] - x[0] ** 2) ** 2


ranges = [[-2, 2], [-1, 3]]

Selection strategies#

The winners of each generation (beyond the automatically-surviving elite) are picked by a pluggable SelectionStrategy, injected via the selection_strategy constructor argument. Three are built in: TournamentSelection (the default), RouletteSelection (fitness-proportionate) and RankSelection. 📖 Selection strategies API Reference.

from sigmaepsilon.math.optimize import TournamentSelection, RouletteSelection, RankSelection

for strategy in [TournamentSelection(), RouletteSelection(), RankSelection()]:
    bga = BGA(
        Rosenbrock,
        ranges,
        length=12,
        nPop=100,
        minimize=True,
        seed=0,
        selection_strategy=strategy,
    )
    result = bga.solve()
    print(f"{type(strategy).__name__:<20} fitness={result.fitness:.6g}")

Population diversity#

The diversity property (and the state.diversity snapshot updated after every evolve call) measures how spread out the current population still is (the mean, per-dimension standard deviation of the phenotypes). A value close to zero, alongside a champion that hasn’t improved in a while, is a sign of premature convergence — in addition to champion.age, it’s a second, independent signal you can use to tune maxage, p_m, or the selection strategy.

bga = BGA(Rosenbrock, ranges, length=12, nPop=100, minimize=True, seed=0)

fitness_history = []
diversity_history = []

for _ in range(60):
    bga.evolve(1)
    fitness_history.append(bga.champion.fitness)
    diversity_history.append(bga.state.diversity)

fig, ax1 = plt.subplots(figsize=(8, 4))
ax1.plot(fitness_history, color="tab:blue")
ax1.set_xlabel("generation")
ax1.set_ylabel("best fitness", color="tab:blue")
ax2 = ax1.twinx()
ax2.plot(diversity_history, color="tab:orange")
ax2.set_ylabel("diversity", color="tab:orange")
plt.title("Best fitness and population diversity over generations")
plt.show()