Files
TermNSI/Projets/fichier_suivi.md

19 KiB
Raw Blame History

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.