Une méthode d'optimisation inspirée de l'évolution : une population de solutions se reproduit, les meilleures survivent, se croisent et mutent, génération après génération. Elle s'attaque aux problèmes de choix combinatoires où tester toutes les possibilités est impossible : portefeuille de projets, planning, affectation, conception.
Un éleveur sélectionne chaque année ses meilleures bêtes pour la reproduction. Les descendants mélangent les qualités des parents, avec quelques variations nouvelles. Au fil des générations, le troupeau s'améliore sans que personne n'ait calculé le croisement idéal.
Ici, un vecteur de 40 cases : 1 si le projet est retenu, 0 sinon. Une note (fitness) mesure sa qualité : gain total, pénalisé si le budget est dépassé.
Les meilleures solutions ont plus de chances d'être choisies comme parents. Deux parents échangent une partie de leur génome pour former deux enfants.
Quelques cases basculent au hasard pour garder de la diversité. La nouvelle génération remplace l'ancienne, en conservant toujours la meilleure solution trouvée (élitisme).
Chaque projet a un coût et un gain attendu, en k€. Le budget de 1 000 k€ ne couvre qu'un tiers du total demandé. Avec 40 projets, il existe plus de mille milliards de portefeuilles possibles.
Sur l'exemple simulé en Python, l'algorithme retient 14 projets pour 999 k€ et 1 752 k€ de gain attendu. Le calcul exact, faisable ici car le problème est petit, donne 1 765 k€ : moins de 1 % d'écart.
Un algorithme génétique ne prouve jamais qu'il a trouvé l'optimum. On le compare à une référence : solution actuelle, règle simple (meilleur ratio gain / coût d'abord), ou optimum exact quand il est calculable. On relance aussi avec d'autres graines pour vérifier la stabilité.
Noms donnés pour R (GA). En Python, la bibliothèque DEAP ou PyGAD, ou une boucle NumPy comme ici.
50 à 200 solutions. Trop petite, la diversité s'éteint vite ; trop grande, chaque génération coûte cher.
Dans le package GA, c'est la probabilité qu'une solution subisse une mutation (un gène basculé), 0,1 par défaut. Dans la boucle Python, chaque gène bascule avec une probabilité de 1/40, soit environ un gène par solution. Trop fort, l'algorithme devient une recherche au hasard ; trop faible, il se fige sur un optimum local.
Une solution hors budget reçoit une pénalité proportionnelle au dépassement. Trop faible, l'algorithme triche avec la contrainte ; trop forte, il n'explore plus les frontières.
Un nombre maximal de générations, ou l'arrêt quand la meilleure solution ne progresse plus depuis un certain nombre de générations.
# Sélection de projets sous budget : algorithme génétique en R
library(GA)
# 40 projets simulés : coût et gain attendu (k€), budget de 1 000 k€
set.seed(42)
cout <- sample(20:119, 40, replace = TRUE)
gain <- round(cout * runif(40, 0.8, 2))
budget <- 1000
# Fitness : gain total, pénalisé si le budget est dépassé (x = vecteur de 0 et de 1)
fitness <- function(x) sum(x * gain) - 10 * max(sum(x * cout) - budget, 0)
# 100 portefeuilles par génération, 300 générations : sélection, croisement, mutation, élitisme
res <- ga(type = "binary", fitness = fitness, nBits = 40, popSize = 100, maxiter = 300,
pcrossover = 0.8, pmutation = 0.1, elitism = 2, seed = 42, monitor = FALSE)
meilleur <- res@solution[1, ]
cat("Projets retenus :", which(meilleur == 1), "\n")
cat("Coût :", sum(meilleur * cout), "k€ | gain :", sum(meilleur * gain), "k€\n")
cat("Budget total si l'on prenait tout :", sum(cout), "k€\n")
# Sélection de projets sous budget : algorithme génétique en Python
import numpy as np
# 40 projets simulés : coût et gain attendu (k€), budget de 1 000 k€
rng = np.random.default_rng(42)
cout = rng.integers(20, 120, 40)
gain = np.round(cout * rng.uniform(0.8, 2.0, 40))
budget = 1000
fitness = lambda pop: pop @ gain - 10 * np.maximum(pop @ cout - budget, 0) # pénalité si hors budget
pop = rng.integers(0, 2, (100, 40)) # 100 portefeuilles au hasard (1 = projet retenu)
for generation in range(300):
score = fitness(pop)
a, b = rng.integers(0, 100, (2, 100)) # sélection par tournoi entre 2 portefeuilles
parents = np.where((score[a] > score[b])[:, None], pop[a], pop[b])
enfants = parents.copy()
for i, c in enumerate(rng.integers(1, 40, 50)): # croisement en un point, par paires
enfants[2 * i, c:], enfants[2 * i + 1, c:] = parents[2 * i + 1, c:], parents[2 * i, c:]
enfants ^= (rng.uniform(size=enfants.shape) < 1 / 40).astype(enfants.dtype) # mutation
enfants[0] = pop[score.argmax()] # élitisme : le meilleur passe toujours
pop = enfants
meilleur = pop[fitness(pop).argmax()]
print("Projets retenus :", np.flatnonzero(meilleur).tolist())
print("Coût :", meilleur @ cout, "k€ | gain :", meilleur @ gain, "k€")
# Contrôle : optimum exact par programmation dynamique (faisable ici car le problème est petit)
optimum = np.zeros(budget + 1)
for c, g in zip(cout, gain):
optimum[c:] = np.maximum(optimum[c:], optimum[:budget + 1 - c] + g)
print("Optimum exact :", optimum[budget], "k€")
Il fait évoluer une population de solutions candidates. À chaque génération, les meilleures sont sélectionnées comme parents, croisées pour produire des enfants, puis légèrement modifiées au hasard par mutation. Au fil des générations, la qualité moyenne de la population progresse.
Pas de façon garantie. C'est une métaheuristique : elle trouve souvent de très bonnes solutions en un temps raisonnable, mais sans preuve d'optimalité. Pour les problèmes qui s'y prêtent, un solveur exact (programmation linéaire en nombres entiers) reste préférable.
Le recuit simulé fait évoluer une seule solution et se règle plus facilement. L'algorithme génétique maintient une population, ce qui explore plus largement et se parallélise bien, au prix de plus de réglages. Sur beaucoup de problèmes, les deux donnent des résultats proches ; le meilleur choix se teste.
Améliore une solution pas à pas en acceptant parfois une dégradation. Plus simple à régler.
Voir la fiche → pour le continuPlus efficace quand les variables sont des quantités continues plutôt que des choix oui / non.
Voir la fiche → une applicationCertains outils AutoML, comme TPOT, font évoluer des pipelines de machine learning par programmation génétique.
Voir la fiche →Dataistudio forme les équipes au machine learning et à l'IA, sur des cas concrets.
Nous utilisons des cookies de mesure d'audience et de suivi publicitaire pour comprendre la fréquentation du site et l'efficacité de nos annonces. Rien n'est déposé sans votre accord. En savoir plus