Files
TermNSI/Projets/fichier_suivi.md

597 lines
19 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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.