19 KiB
Les algorithmes génétiques
- Présentation
- Les algorithmes génétiques font partie des algorithmes évolutionnistes.
- S'inspirent des mécanismes de sélection naturelle pour approcher une solution d'un problème d'optimisation.
- S'appliquent bien dans le cas où l'espace des solutions est grand.
-
Darwin
- /De l'origine des espèces/
- les individus les mieux adaptés à leur environnement survivent et se reproduisent davantage en transmettant les variations utiles à la survie.
- L'espèce évolue en s'adaptant à son environnement.
- C'est le principe de sélection naturelle
-
Sélection naturelle
Trois principes :
- Variation : les individus diffèrent les uns des autres et d'une génération à l'autre ;
- Adaptation : les individus les mieux adaptés survivent et se reproduisent davantage ;
- Hérédité : les caractéristiques sont héréditaires et se transmettent de génération en génération.
-
Principes
** Problèmes et solution
On dispose d'un problème d'optimisation :
\max_{s\in R}\lbrace f(s) \rbrace
Rest l'espace des solutions réalisablesfest la fonction objectif, où encore :- fonction d'évaluation ,
- fonction d'adaptation ,
- fonction de fitness.
** Le principe des algorithmes génétiques
- travailler avec un sous-ensemble
PdeRappelé population ; - On fait évoluer
Pen lui appliquent des opérateurs génétiques pour produire une nouvelle génération ; - On favorise les individus les mieux adaptés (valeur de
fplus élevée) lors de la production de la génération suivante ; - Après
ngénérations, on choisit un individu dans la population.
** génome
- Les individus sont représentés par leur génôme : suite finie de
ksymboles ; - Ces symboles représentent les caractéristiques d'un individu, c'est-à-dire un gène ;
Chercher la solution optimale consiste à chercher une séquence de symboles optimale.
Idée à respecter : plus l'adaptation est élevée, plus l'individu est proche de la solution.
** Opérateurs génétiques
Il existe trois opérateurs génétiques :
*** la sélection choix des individus d'une population qui vont survivre à la génération suivante : - Les individus les mieux adaptés ont plus de chance de survivre ; - Méthode du /tournoi/ .
*** le croisement
Combine les gènes de deux individus pour en produire deux
nouveaux :
- croisement **1 point** : découpe des deux génome au même
endroit, puis échange ,
- croisement **2 points** : découpe chacun des génomes en trois
portions , on échange les gènes des parties médianes.
*** La mutation
- Mutation /aléatoire/ des caractéristiques d'un individu ;
- Réalisée avec une probabilité faible ;
- Permet de faire apparaitre dans une population de nouvelles
caractéristiques
-
L'algorithme génétique
Notre algorithme travaillera avec une population constante d'une génération à l'autre.
** Entrées
- la fonction de /fitness/ du problème ;
- le nombre d'individus
npair ; - le nombre de génération
g; - la probabilité de mutation
p.
** L'algorithme
- population
\leftarrownindividus créés aléatoirement. - calculer pour chaque individu de population son degré d'adaptation.
- répéter
gfois \quadnext_gen_1\leftarrowsélectionner par tournoi\frac{n}{2}individus de la population\quadnext_gen_2\leftarrowcréer\frac{n}{2}individus par croisement entre individus de population\quadnext_gen\leftarrownext_gen_1 + next_gen_2\quadfaire muter chacun des individus de next_gen\quadcalculer pour chaque individu de next_gen son degré d'adaptation\quadmeilleurs\leftarrowles 5 individus les mieux adaptés de population\quadretirer de next_gen les 5 individus les moins adaptés\quadpopulation\leftarrowneilleurs + next_gen- solution
\leftarrowindividu le mieux adapté de population
** Sélection par tournoi
- La sélection par tournoi dans une population de taille
nconsiste à choisir aléatoirement deux individus de la population pour ne garder que l'individu le mieux adapté des deux. - On répète ce processus de sorte que chaque individu est choisi
exactement une fois. On obtient donc
\frac{n}{2}individus à la fin de cette phase.
** Croisements
- Croisement à un point
- On choisit aléatoirement deux individus de la population pour construire deux nouveaux individus
- On applique ensuite un tournoi entre les deux individus produits pour ne garder que le meilleur
- On répète ce processus de sorte que chaque individu est choisi
exactement une fois. On obtient donc
\frac{n}{2}individus à la fin de cette phase.
** Mutations
-
Pour chacun des individus considérés, il y a une probabilité
pque chacune de ses caractéristiques mute. C'est-à-dire que la valeur de cette caractéristique est modifiée aléatoirement. L'individu est donc remplacé par sa /version mutée/. -
À la fin de la population la taille de la population est la même.
-
Programmation
Plusieurs classes :
- l'algorithme génétique,
- le problème traité et
- les individus.
** l'algorithme génétique
-
classe
AlgoGen.#+begin_src python :tangle src/algo_gen.py :exports none import random
BEST_KEPT = 5
class AlgoGen(object):
def __init__(self, problem, population_size, crossover_rate, mutation_probability): ''' build an genetic algorithm to solve problem using a population of size population_size and a probability of muation of mutation_probability :param problem: the problem to solve :type problem: a Problem object :param population_size: the size of the population (must be even) :type population_size: int :param mutation_probability: the mutation probability :type mutation_probability: float :UC: population_size must be even and mutation_probability must be 0<= and <1 ''' self.__problem = problem self.__population_size = population_size self.__population = [] self.__nb_crossover = crossover_rate self.__mutation_probability = mutation_probability def get_population(self): return self.__population def genesis(self): """ create the population """ pass def evaluate_population(self, population): """ evalue la population passée en paramètre :param population: (list) la population """ pass def next_generation_tournament(self, population): ''' organise un tournoi entre les individu d'une population . chaque individu choisi une et une seule fois, dans un tournoi , vainqueur conservé taille résultat = 1/2 taille population :param population: (list) la population :return: (list) les vainqueurs du tournoi ''' pass
def crossover(self, population):
'''Croise les individus d'une population.
Chaque individu est sélectionné une et une seule fois, on
garde le meilleur descendant à chaque fois.
taille résultat = 1/2 taille population
:param population: (list) a population
:return: (list) new individuals
'''
pass
def mutate(self, individuals):
"""
mute les individus
:param individuals: (list) une liste des individus
"""
pass
def one_round(self):
'''
applique successivement les opéréateurs
- selection
- croisement
- mutation
'''
pass
def solve(self, nb_rounds):
"""
tente d'approcher une solution en faisant muter la population
:param nb_rounds: (int) nombre de génération
"""
self.genesis()
self.evaluate_population(self.__population)
self.display_stat('INITIAL')
for step in range(nb_rounds):
self.one_round()
#self.display_population(step=step+1, number = 20)
self.display_stat(step)
#self.display_population(number = 20) # display 20 best individuals
return self.__problem.best_individual(self.__population)
def display_stat(self, step):
best = self.__problem.best_individual(self.__population)
all_scores = [ individual.get_score() for individual in self.__population ]
average = sum (all_scores) / self.__population_size
print ( ' STEP = {} -- best = {} - avg = {} '.format(step, best, average) )
def display_population(self, step = ' ', number = 0):
if number <= 0:
individuals = self.__population
else:
individuals = self.__population[:min(number, len(self.__population))]
print( ' **** STEP : {} ********* '.format(step) )
for individual in individuals :
print ( individual )
print( )
# tool function
def biased_wheel(alea,scores):
'''
return smallest i such that alea < sum(scores[k], 0 <= k < i)
CU : 0 <= alea < sum(scores)
'''
current_sum = 0
result_index = -1
while alea > current_sum :
result_index = result_index + 1
current_sum = current_sum + scores[result_index]
return result_index
#+end_src
** les problèmes
Comme indiqué, les classes pour représenter les problèmes devront partager certaines méthodes. Nous suggérons les méthodes suivantes comme méthodes communes aux problèmes. Evidemment, il peut exister d'autres méthodes spécifiques à chacun des problèmes. Notamment la manière de calculer l'adaptation d'un individu variera.
#+begin_src python :tangle src/problem_interface.py :exports none class Problem_Interface(object): ''' a prototype class for "problems" in genetic algorithm
an Individual class must be associated to a problem to represent the individuals
'''
def create_individual(self):
'''
create a randomly generated indidivual for this problem
:return: a randomly generated individual gfor this problem
:rtype: an Individual object
'''
pass
def evaluate_fitness(self, individual):
'''
compute the fitness of individual for this problem
:param individual: the individual to consider
:type individual: an Individual object
:return: the fitness of individual for this problem
:rtype: an Individual object
'''
pass
def sort_population(self, population):
'''
sort population from best fitted to worst fitted individuals.
Depending on the problem, it can correspond to ascending or descending order with respect to the fitness function.
side effect : population is modified by this method
:param population: the list of individuals to sort
:type population: list(Individual)
'''
pass
def best_individual(self, population):
'''
return the best fitted individual from population.
Depending on the problem, it can correspond to the individual with the highest or the lowest fitness value.
:param population: the list of individuals to sort
:type population: list(Individual)
:return: the best fitted individual of population
:rtype: an Individual object
'''
pass
def tournament(self, first, second):
'''
perform a rounament between two individuals, the winner is the most fitted one, it is returned
:param first: an individual
:type first: an Indivdual object
:param second: an individual
:type second: an Individual object
:return: the winner of the tournament
:rtype: Individual object
'''
pass
#+end_src
** les individus
Les classes pour les individus devront partager certaines méthodes. Nous suggérons les méthodes suivantes comme méthodes communes aux individus. Evidemment, il peut exister d'autres méthodes spécifiques à chacun des individus, et notamment, les valeurs des génomes vont différer.
#+begin_src python :tangle src/individual_interface.py :exports none import random
class Individual_Interface(object):
'''
an Individual in genteic algorithm problem
the value (or genome) of an indal is a sequence (e.g string or list) of a fixed size
an individual has a fitness score
'''
def init(self, size):
'''
create an Individual object, its genome value is randomly built. Initially score is not set.
:param size: size of the genome
:type size: int
'''
self.__score = None
self.__size = size
self.__value = self.init_value()
def copy(self):
'''
build a copy of self, the genome is a copy of self's genome
:return: a new Individual which is a "clone" of self
:rtype: an Individual object
'''
return None
def get_size(self):
'''
:return: the size of self's genome
:rtype: int
'''
return self.__size
def get_score(self):
'''
:return: the fitness score of self
:rtype: number
'''
return self.__score
def set_score(self, new_score):
'''
change the fitness score of self
:param new_score: the new fitness score
:type new_score: number
'''
self.__score = new_score
def __str__(self):
return "{} ({})".format(self.__value, self.__score)
def __repr__(self):
return self.__str__()
def get_value(self):
'''
:return: the genome of self
:rtype: sequence
'''
return self.__value
def set_value(self, new_value):
'''
change the genome value of self
:param new_score: the new genome value
:type new_score: sequence
'''
self.__value = new_value
def init_value(self):
'''
randomly initialize the genome value of self
'''
return None
def evaluate(self,problem):
'''
set the fitness score with the fitness computed by problem for self
:param problem: the problem
:type problem: a Problem object
'''
self.set_score(problem.evaluate_fitness(self))
def cross_with(self, other):
'''
perform a 1 point crossover between self and other, two new built individuals are returned
:param other: the individual to croww with
:type other: an Idnividual object
:return: the two new Individuals built by 1 point crossover operation
:rtype: 2-uple of individuals
'''
pass
def mutate(self, probability):
'''
apply mutation operation to self : each element of the geome sequence is randomly changed with given probabiliy
side effect : self's genome is modified
:param probability: the probability of mutation for every gene
:type probability: float
:UC: probability in [0,1[
'''
pass
def mutate_gene(self, gene):
pass
#+end_src
-
Problème 1 : Calcul de la valeur maximale d'une fonction
Problème : trouver dans un intervalle
[x_\mbox{min};x_\mbox{max}]une valeur dexpour laquellef(x)est maximale. -
Problème 2 : Recherche d'un message "secret"
** Description Cette fois, il ne s'agit pas à proprement parler d'un problème à résoudre, puisqu'on en connaît la solution au départ... Ce second exemple a pour objectif d'illustrer que les algorithmes génétiques peuvent trouver une solution dans un très grand espace de recherche.
** Problème Le problème est celui de faire trouver une chaîne de caractères à un algorithme génétique. On suppose cette chaîne construite à partir du caractère espace et des 26 lettres de l'alphabet (minuscules et non accentuées). Si elle est le longueur 30, il existe alors 27^30 chaînes possibles.
Nous allons donc vérifier qu'en explorant qu'une petite partie de toutes ces possibilités, un algorithme génétique est capable de trouver l'une d'entre elles.
Par exemple, nous allons considérer la chaine
secret = 'ceci est un secret a retrouver'.
- Problème 3 : Recherche d'un chemin dans un labyrinthe
** Labyrinthe
#+begin_example
7;7
+-+-+-+-+-+-+-+
| | |
+ + +-+ + +-+ +
| | | | |
+ + +-+ + + + +
| | | | |
+ +-+ +-+-+ + +
| | | |
+ + +-+-+ +-+-+
| | | |
+ +-+-+ + +-+ +
| | | | |
+ + + +-+-+ +-+
| | |
+-+-+-+-+-+-+-+
#+end_example
** Problème Le problème consiste à trouver dans un tel labyrinthe de taille N (ici N=7x7=49) donné, un chemin entre la case d'entrée située en haut à gauche et celle de sortie située en bas à droite.
Pour simplifier on considère que les largeur et hauteur du labyrinthe sont identiques.
** Matériel
- une classe
Mazepermettant de gérer les labyrinthes (fichiermaze.py); - des exemples de labyrinthe (fichier
maze.zip)
-
Problème 4 : le TSP
Utiliser un algorithme génétique pour approcher une solution du Problème du voyageur de commerce.