đïžâđšïž Coup dâoeil sur lâalgorithme gĂ©nĂ©tique
Initiation Ă lâalgorithme gĂ©nĂ©tique : prĂ©sentation des principes et petit cas pratique
En recherche opérationnelle, les méthodes de résolution exactes (comme le simplexe) étant de complexité exponentielle, donc relativement chronophage, parfois il plus judicieux de faire appel à des méthodes heuristiques pour des problÚmes difficiles.
- Un heuristique est un algorithme qui permet afin de trouver une solution approchĂ©e, pas nĂ©cessairement optimale, pour un problĂšme dâoptimisation difficile ; dans un dĂ©lai de temps raisonnable.
Les heuristiques sont dĂ©pendantes du problĂšme Ă rĂ©soudre, toutefois des heuristiques plus poussĂ©es ont Ă©tĂ© mises au point â un niveau dâabstraction plus Ă©levĂ© que les heuristiques puisquâil sâagit de mĂ©thode pouvant sâadapter Ă un problĂšme donnĂ© â et ont donnĂ©es naissance Ă une nouvelle famille dâalgorithmes : les mĂ©ta-heuristiques.
- LâidĂ©e principale des mĂ©taheuristiques est dâexplorer lâespace des solutions en essayant de converger vers lâoptimum global (la meilleure solution dans lâensemble) ; sans ĂȘtre « piĂ©gĂ© » par un optimum local (la meilleure solution dans une zone restreinte).
GĂ©nĂ©ralement de nature stochastiques et itĂ©ratifs, ces algorithmes se veulent ĂȘtre des mĂ©thodes gĂ©nĂ©riques pouvant optimiser une large gamme de problĂšmes diffĂ©rents :
- Les plus simples : ProblĂšme du voyageur, problĂšme du sac Ă dos, etc.
- Comme les plus compliqués : Choix optimal des paramÚtres du modÚle K-Nearest Neighbors (KNN) ou K-Means, réduction de la complexité des réseaux de neurones, etc.
Les mĂ©taheuristiques sont souvent inspirĂ©es par des systĂšmes naturels, et sont de plus en plus hybridĂ©es avec dâautres mĂ©thodes de recherche opĂ©rationnelle.
Parmi les principales métaheuristiques utilisées pour résoudre des problÚmes à variables discrÚtes, on retrouve les algorithme génétiques.
Algorithme génétique : principes et fonctionnement
Inspiration de la nature
Les algorithmes gĂ©nĂ©tiques (AG) sont de la famille des algorithmes Ă©volutionnaires ; des algorithmes dâoptimisation stochastique fondĂ©s sur les mĂ©canismes de la sĂ©lection naturelle et de la gĂ©nĂ©tique.
Les principes de base des AG, dĂ©veloppĂ©s par le scientifique amĂ©ricain John Henry Holland dans les annĂ©es 1970, sâinspirent directement des travaux du naturaliste anglais Charles Darwin. En effet, la thĂ©orie de lâĂ©volution, dĂ©veloppĂ©e dans le cĂ©lĂšbre ouvrage De lâorigine des espĂšces de Darwin, postule que le principal moteur de lâĂ©volution est le processus de sĂ©lection naturelle.
Cette notion, que lâon qualifie parfois comme la survie du plus apte (survival of the fittest, en anglais), repose sur trois principes : le principe de variation, le principe dâadaptation et le principe dâhĂ©rĂ©ditĂ©. :
- Le principe de variation : Chaque individu au sein dâune population est unique. Ces diffĂ©rences, plus ou moins importantes, vont ĂȘtre dĂ©cisives dans le processus de sĂ©lection.
- Le principe dâadaptation : Les individus ayant les variations les plus favorables par rapport Ă leur environnement sont plus adaptĂ©s et atteignent plus facilement lâĂąge adulte. Ceux ayant une meilleure capacitĂ© de survie pourront donc se reproduire davantage.
- Le principe dâhĂ©rĂ©ditĂ© : Les caractĂ©ristiques et variations des individus sont hĂ©rĂ©ditaires, câest-Ă -dire hĂ©ritĂ©es des parents et transmises aux descendants. Ainsi, aprĂšs plusieurs gĂ©nĂ©rations, on verra les variations favorables se retrouver chez tous les individus de la population et la frĂ©quence des gĂšnes dĂ©savantageux diminuer jusquâĂ Ă©ventuellement disparaĂźtre.
Le fonctionnement de base de lâalgorithme gĂ©nĂ©tique
Ă lâimage de la sĂ©lection naturelle, le fonctionnement des algorithmes Ă©volutionnaires est axĂ© sur un processus itĂ©ratif que nous pouvons de diviser en cinq phases :
1. Initialisation
2. Ăvaluation
3. SĂ©lection
4. Croisement
5. Mutation
Ensuite, il y a un Retour Ă la phase dâĂ©valuation jusquâĂ lâarrĂȘt de lâalgorithme.
GrossiĂšrement, les AG se basent au dĂ©part sur une population de solutions candidates appelĂ©es parfois individus, celles-ci comprennent des propriĂ©tĂ©s et elles peuvent ĂȘtre sujettes Ă des transformations gĂ©nĂ©tiques (mutation, croisement par exemple) qui vont faire Ă©voluer la population de gĂ©nĂ©ration en gĂ©nĂ©ration, jusquâĂ la gĂ©nĂ©ration qui contient les meilleures solutions.
Pour mieux apprĂ©hender le fonctionnement des AG, quoi de mieux quâun cas pratique ?
Cas pratique : Maximisation de la somme des gĂšnes dâun octet
Objectif du cas pratique
Pour ce cas pratique, nous allons avoir au dĂ©part une liste alĂ©atoire dâindividus en reprĂ©sentation binaire. Nous travaillerons avec une liste contenant 10 individus.
Lâobjectif va ĂȘtre dâutiliser un algorithme gĂ©nĂ©tique afin dâavoir des individus dont la somme des gĂšnes est maximisĂ©.
Bien que ce problÚme soit trivial, le but de ce cas pratique est de comprendre pas à pas les notions, comment utiliser et implémenter les algorithmes génétiques.
Initialisation de la population
Pour commencer, il est important de définir quelques notions de bases :
- La population est lâensemble des solutions envisageables.
- Lâindividu reprĂ©sente une solution.
- Le gÚne est une caractéristique, une particularité de la solution.
Dans notre problĂšme, cela Ă©quivaut Ă cela :
Pour notre problĂšme, Ă©tant donnĂ© quâun octet est composer de 8 bits, nous reprĂ©senterons un individu par un array de taille 8, rempli de 1 et de 0.
import numpy as np
import random
# Generate randomly an individual
def generate_individual():
individu = np.random.randint(0,2, size=(1,8))
return individu
generate_individual()
# Output : array([[1, 1, 0, 1, 1, 1, 0, 0]])
DĂšs lors, nous pouvons gĂ©nĂ©rer alĂ©atoirement la population dâoctets sur laquelle nous allons travailler.
# Generate a population of individual generated randomly
def generate_population(size):
pop = np.empty(shape=[0, 8])
for i in range(size):
pop = np.vstack([pop, generate_individual()])
return pop
# Output : array([[0., 1., 0., 0., 1., 1., 0., 0.],
# [0., 0., 1., 1., 0., 0., 1., 1.],
# ...,
# [0., 0., 0., 0., 1., 0., 0., 0.]])
Evaluation de la population
Maintenant que notre population est gĂ©nĂ©rĂ©, il faut lâĂ©valuer. Nous allons attribuer Ă chaque individu un score (aussi appelĂ© fitness). En effet, les individus ayant une bonne qualitĂ© seront ceux avec un score Ă©levĂ©. Ils auront plus de chance dâĂȘtre sĂ©lectionnĂ©s et donc il y a plus de chance que la populati Pour notre problĂšme, câest relativement simple, nous dĂ©finissons la fitness comme la somme des variables (0 ou 1) de lâindividu. on suivante hĂ©rite de leur matĂ©riel gĂ©nĂ©tique.
Pour notre problĂšme, câest relativement simple, nous dĂ©finissons la fitness comme la somme des variables (0 ou 1) de lâindividu.
# Attribute a score to each individual of the population : sum of their genes
def compute_fitness(population):
matriceScores = population.sum(axis=1)
return matriceScores
# Work on population : Sort the population in ascending order
def evaluate_population(population):
pop = population.copy() # get the population
listScores = compute_fitness(pop) # get the list of scores
listScores = listScores.argsort(axis=0) # sort the list of scores
population_evaluated = pop[listScores] # put the mask in population to sort population
return population_evaluated
Opérateurs génétique
Pour la suite, il est important de comprendre quâil y a trois opĂ©rateurs dâĂ©volution dans les algorithmes gĂ©nĂ©tiques :
- La sélection qui permet de choisir les individus les mieux adaptés.
- Le croisement qui permet de mélanger des individus choisis par la reproduction de certaines de leurs propriétés.
- La mutation qui permet dâaltĂ©rer / faire varier alĂ©atoirement les propriĂ©tĂ©s dâun individu.
Opérateur de sélection sur la population
La sĂ©lection consiste Ă choisir les individus les mieux adaptĂ©s (i.e. avec les meilleurs fitness) afin dâavoir une population de solution la plus proche de converger vers lâoptimum global. Cet opĂ©rateur est lâapplication du principe dâadaptation de la thĂ©orie de Darwin.
Il existe plusieurs techniques de sélection. Voici les principales utilisées :
- SĂ©lection par rang : on choisit toujours les individus possĂ©dant les meilleurs scores dâadaptation (fitness).
- SĂ©lection par tournoi : on sĂ©lectionne proportionnellement des paires dâindividus, puis choisit parmi ces paires lâindividu qui a le meilleur score dâadaptation.
- SĂ©lection uniforme : on sĂ©lectionne alĂ©atoirement, uniformĂ©ment et sans intervention de la valeur dâadaptation.
Pour notre problÚme, les individus de la population ont été évaluées trÚs facilement ; ceux ayant un score élevé ont été classé en fin du tableau. Nous opterons alors pour une sélection par rang, et nous sélectionnerons les meilleurs individus de la population en indexant le tableau par la fin.
# Work on population : Select a percentage of the population
def select_population(population, percent):
pop = population.copy() # get the population
howmany = int(round(len(pop)*(percent))) # the number of individu to select
population_selected = pop[-howmany:]
return population_selected
Opérateur de croisement sur la population
Le croisement (ou crossing-over en anglais), est le rĂ©sultat obtenue lorsque deux chromosomes partage leurs particularitĂ©s. Cet opĂ©rateur est lâapplication du principe dâhĂ©rĂ©ditĂ© de la thĂ©orie de Darwin, et permet le brassage gĂ©nĂ©tique de la population.
Il existe plusieurs techniques de croisement. Voici les principales utilisées :
- Le croisement simple : on fusionne les particularitĂ©s de deux individus Ă partir dâun point de coupure, afin dâobtenir un ou deux enfants.â
- Le croisement multiple : mĂȘme principe que le prĂ©cĂ©dant, sauf quâil y a plusieurs points de coupure.
- Le croisement uniforme : on fusionne les particularitĂ©s de deux individus Ă partir dâun masque de croisement gĂ©nĂ©rĂ© alĂ©atoirement, afin dâobtenir un ou deux enfants.
Pour notre problÚme, nous opterons pour un croisement simple ; les individus-enfants hériterons de la moitié des caractéristiques respectives des parents.
# Work on individual : Create an individual crossed simply with 2 individuals
def crossover(parent1, parent2):
genes1, genes2 = parent1[:4], parent2[-4:]
child = np.hstack([genes1, genes2])
return child
DĂšs lors, nous implĂ©mentons une fonction permettant dâappliquer lâopĂ©rateur de croisement dans une population et pouvoir en faire croiser une partie (un pourcentage).
# Work on population : Cross a percentage (taken as a parameter) of the population
def cross_population(population, percent):
pop = population.copy() # get the population
children = np.empty(shape=[0, 8])
howmany = int(round(len(pop)*(percent))) # the number of child to create
for k in range(howmany):
ind1 = random.choice(pop)
ind2 = random.choice(pop)
# create the child
child = crossover(ind1, ind2)
# store the child
children = np.vstack([children, child])
# Add children to population
population_crossed = np.vstack([pop, children])
return population_crossed
Opérateur de mutation sur la population
La mutation consiste Ă altĂ©rer un gĂšne dans un chromosome (individu) selon un facteur de mutation. Ce facteur est soit la probabilitĂ© quâune mutation soit effectuĂ©e sur un individu, soit le pourcentage dâindividus mutĂ©s dans une population.
Cet opĂ©rateur est lâapplication du principe de variation de la thĂ©orie de Darwin et permet, par la mĂȘme occasion, dâĂ©viter une convergence prĂ©maturĂ©e de lâalgorithme vers un extremum local.
Il existe plusieurs techniques de mutation. Mais pour notre cas simple nous nâutiliserons que celle-ci :
- La mutation alĂ©atoire : consiste Ă modifier une des particularitĂ©s dĂ©terminĂ©es alĂ©atoirement dâun individu
Pour notre problĂšme, nous opterons pour cette technique ; lorsque la valeur du gĂšne Ă muter est Ă©gale Ă 1 alors elle est inversĂ©e Ă 0 et lorsquâelle est Ă©gale Ă 0 alors elle est inversĂ©e Ă 1.
# Work on individual : Mutate an individual / randomly change a bit
def mutation(individu):
a = random.randrange(len(individu)) # choose a random gene to mutate
if individu[a]==0:
individu[a]=1
else:
individu[a]=0
return individu
DĂšs lors, nous implĂ©mentons une fonction permettant dâappliquer lâopĂ©rateur mutation dans une population et pouvoir en faire muter une partie (un pourcentage).
# Work on population : Mutate a percentage of the population
def mutate_population(population, percent):
pop = population.copy() # get the population
howmany = int(round(len(pop)*(percent))) # the number of individu to mutate
for k in range(howmany):
i = random.randrange(pop.shape[0]) # choose a random individu in population to mutate
individu = pop[i]
mutant = mutation(individu) # mutate the individu
pop[i] = mutant # replace individu by mutant
return pop
Création de la premiÚre génération
Nous testons rapidement nos fonctions avec en paramĂštres :
- Taille de la population : 6
- Part dâindividus sĂ©lectionnĂ©s dans la population : 50%
- Part dâenfants croisĂ©s obtenus par rapport Ă la population : 100%
- Part dâindividus mutĂ©s chez les enfants : 40%
Tout dâabord, une population est initialisĂ©e :
Ensuite, cette population est évaluée :
Puis, les meilleurs individus de cette population sont sélectionnés :
Ces derniers vont ĂȘtre croisĂ©es, et vont faire un nombre dĂ©fini dâenfants. Les voici :
Et enfin, ces enfants vont ĂȘtre regrouper avec leurs magnifiques parents pour constituer une population finale
Voilà une génération de crée ! Nos fonctions marche trÚs bien.
RĂ©solution du problĂšme par lâalgorithme gĂ©nĂ©tique
DĂ©sormais, il ne reste plus quâĂ mettre les lignes de codes prĂ©cĂ©dentes dans une boucle itĂ©rative afin de simuler le renouvellement de population au fil de N gĂ©nĂ©ration.
def algorithme_genetique(nbPopulationStart, numberOfGeneration, percentSelected, percentCrossed, percentMutated, aff):
# Initialisation
population = generate_population(nbPopulationStart)
print('Initial population\n', population) if aff>=1 else None
for t in range(numberOfGeneration):
print('\n#########################################################') if aff==2 else None
print('######################################### GENERATION :', t) if aff==2 else None
# Evaluation
population = evaluate_population(population)
print('\nEvaluation :\n', population) if aff==2 else None
# Selection
population = select_population(population, percentSelected)
print('\nSelection :\n', population) if aff==2 else None
# Crossover
children = cross_population(population, percentCrossed)
print('\nCrossover - Children :\n', children) if aff==2 else None
# Mutation
children = mutate_population(children, percentMutated)
print('\nMutation - Children :\n', children) if aff==2 else None
# store the child
population = np.vstack([population, children])
print('\nSelection :\n', population) if aff==2 else None
print('\n#########################################################') if aff==2 else None
print('>>>> Result :\n', population) if aff>=1 else None
return population
Ainsi, pour rĂ©soudre le problĂšme initiale, il suffit de lancer notre algorithme en choisissant comme paramĂštres dâentrĂ©es :
- Taille de la population : 10
- Nombre de génération : 3
- Part dâindividus sĂ©lectionnĂ©s dans la population : 50%
- Part dâenfants croisĂ©s obtenus par rapport Ă la population : 100%
- Part dâindividus mutĂ©s chez les enfants : 30%
On constate que cette population final est composĂ© majoritairement dâindividus dont la somme des gĂšnes maximisĂ©e, câest-Ă -dire composĂ© de bonnes solutions. Il a fallu seulement 3 gĂ©nĂ©ration Ă notre algorithme pour rĂ©soudre ce problĂšme.
VoilĂ une brĂšve introduction Ă lâalgorithme gĂ©nĂ©tique, mais il faut garder en tĂȘte quâen dehors du modĂšle que je viens de prĂ©senter, il existe plusieurs variĂ©tĂ©s de modĂšles dâimplĂ©mentation de lâAG pour avoir des solutions qui se rapprochent beaucoup plus de lâoptimum.
Mlachahe SAID SALIMO.
Sources et ressources complémentaires :
- Sélection naturelle, la page Wikipédia
- Darwin et la sélection naturelle, un cours du site alloprof.qc.ca
- Optimisation combinatoire, un article du site complex-systems-ai.com
- Maxima and minima, la page Wikipédia
- Algorithme génétique, la page Wikipédia
- Algorithme gĂ©nĂ©tique, le site de lâexposĂ© de Thomas LEROUX
- Algorithme génétique, un article du site ledatascientist.com
- Les techniques algorithmiques de lâIA | Les algorithmes Ă©volutionnaires, article du site ajcact.org
- Présentation des algorithmes génétiques et de leurs applications en
économie, un article de Thomas Vallée et Murat Yildizoglu
- Genetic Algorithms â Quick Guide, un article du site tutorialspoint.com