Projets : structure par projet (jeu_de_la_vie, labyrinthe, mastermind, wator)
This commit is contained in:
596
Projets/fichier_suivi.md
Normal file
596
Projets/fichier_suivi.md
Normal file
@@ -0,0 +1,596 @@
|
||||
## 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$$
|
||||
|
||||
- $R$ est l'espace des solutions *réalisables*
|
||||
- $f$ est 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 $P$ de $R$ appelé *population* ;
|
||||
- On fait évoluer $P$ en 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 $f$ plus
|
||||
élevée) lors de la production de la génération suivante ;
|
||||
- Après $n$ gé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
|
||||
$k$ symboles ;
|
||||
- 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 $n$ pair ;
|
||||
- le nombre de génération $g$ ;
|
||||
- la probabilité de mutation $p$.
|
||||
|
||||
|
||||
** L'algorithme
|
||||
|
||||
1. population $\leftarrow$ $n$ individus créés aléatoirement.
|
||||
2. calculer pour chaque individu de population son degré
|
||||
d'adaptation.
|
||||
3. répéter $g$ fois
|
||||
4. $\quad$ next_gen_1 $\leftarrow$ sélectionner par tournoi
|
||||
$\frac{n}{2}$ individus de la population
|
||||
5. $\quad$ next_gen_2 $\leftarrow$ créer $\frac{n}{2}$ individus
|
||||
par croisement entre individus de population
|
||||
6. $\quad$ next_gen $\leftarrow$ next_gen_1 + next_gen_2
|
||||
7. $\quad$ faire muter chacun des individus de next_gen
|
||||
8. $\quad$ calculer pour chaque individu de next_gen son degré
|
||||
d'adaptation
|
||||
9. $\quad$ meilleurs $\leftarrow$ les 5 individus les mieux adaptés de population
|
||||
10. $\quad$ retirer de next_gen les 5 individus les moins adaptés
|
||||
11. $\quad$ population $\leftarrow$ neilleurs + next_gen
|
||||
12. solution $\leftarrow$ individu le mieux adapté de population
|
||||
|
||||
** Sélection par tournoi
|
||||
|
||||
- La sélection par tournoi dans une population de taille $n$
|
||||
consiste à 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é $p$
|
||||
que 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 de $x$ pour laquelle $f(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 ~Maze~ permettant de gérer les labyrinthes (fichier ~maze.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.
|
||||
Reference in New Issue
Block a user