Cette année le Pere Noël rencontre un énorme problème.
Il a oublié le code secret de son coffre fort contenant la liste des cadeaux à distribuer !!
Notre mission va donc consister à trouver ce code secret afin qu'il puisse distribuer ses cadeaux à temps.
Le code secret à retrouver est "JoyeuXnoEl2019" et se trouve stocké dans la mémoire numérique du coffre fort.
Nous pouvons essayer de le trouver en cherchant toutes les combinaisons possibles. Le mot comportant 16 caractères dont des minuscules, des majuscules, des chiffres. Cela risque de prendre un certains temps avant d'y parvenir. Il nous faut donc trouver une autre solution en faisant appel aux algorithmes génétiques.
Les algorithmes génétiques utilisent la notion de sélection naturelle (Darwin) et l'appliquent à une population de solutions potentielles au problème donné.
Imaginons le cas similaire à notre problème où le mot à trouver est "CHAT".
La première étape va consister à créer une population d'individus composés de gènes (Dans notre cas les gènes seront les lettres de l'alphabet ainsi que les chiffres de 1 à 9)
CHAT comporte 4 lettres. Les individus vont donc comporter 4 gènes choisis au hasard :
Individu 1: C O Z P
Individu 2: O H 1 T
...
La seconde étape va être de calculer l'aptitude (fitness) de chaque individu.
Dans notre cas 1 point est donné à chaque lettre correctement placée :
Individu 1: C O Z P = 1 points
Individu 2: O H 1 T = 2 points
Nous classons ensuite les individus par ordre décroissant d'aptitude :
Individu 2: O H 1 T = 2 points
Individu 1: C O Z P = 1 points
Vient la phase de selection d'individus parmis la population. Cette selection est composée d'élites et d'autres individus permettant d'assurer la diversité génétique.
Une fois la selection réalisée on procède aux croisements d'individus donnant alors naissance à des enfants afin de créer une nouvelle population
Vient enfin la mutation génétique s'appliquant à certains enfants pour respecter le principe d'évolution.
Ces phases étant réalisées plusieurs fois jusqu'à obtenir un résultat satisfaisant.
L'ensemble de ces étapes sont détaillées dans le code qui suit. A noter que nous utilisons nos propres méthode de selection et de croisement afin de rendre l'explication plus simple.
Attention, les algorithmes génétiques ne sont pas des algorithmes d'apprentissage ! mais peuvent être utilisés dans le cas de l'intelligence artificielle notemment pour selectionner les neurones les plus performants dans le cadre du deep learning
Nous définissons tous les gènes qui composeront les individus et qui sont necessaires à la résolution de notre problème
#Genes des individus
GENES = "abcdefgijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
Viennent ensuite les fonctions utiles à savoir :
#Les imports
import random
#Generation d'un gêne de façon aléatoire
#Le gène est choisi parmis la liste de gènes définie précédemment
def generationGene():
return random.choice(GENES)
generationGene()
#Generation d'un individu (Composé de gènes)
#Le nombre de gène à générer est fonction de la taille du code secret
def generationIndividu(codeSecret):
return [generationGene() for i in range(len(codeSecret))]
generationIndividu("test")
#Generation de la population (Composée d'individus)
#Le nombre d'individus à générer est passé en paramètre
def generationPopulation(nombreIndividus,codeSecret):
return [generationIndividu(codeSecret) for i in range(nombreIndividus)]
population = generationPopulation(5,"test")
#On affiche la population pour simple vérification
i=0
for individu in population:
print("I:"+str(i)+" "+str(individu))
i = i+1
#Calcul de l'aptitude d'un individu
#On ajoute 1 point d'aptitude par lettre présente et bien placée dans l'ensemble des gènes de l'individu
#par rapport au code secret
def calculAptitude(individu,codeSecret):
aptitude = 0
for gene, gene_attendu in zip(individu,codeSecret):
if gene == gene_attendu:
aptitude = aptitude +1
return aptitude
#Calcul de l'aptitude du premier individu
#Combien de lettres sont elles bien placées ?
print(population[0])
calculAptitude(population[0],"test")
#Classement des individus de la population par ordre décroissant d'aptitude
#Ce classement inverse est réalisé à l'aide d'une fonction lambda
#Le résultat est un tableau dont la première colonne contient l'individu et la seconde son aptitude.
def classementIndividus(population,codeSecret):
classement_individus = []
for individu in population:
classement_individus.append((individu,calculAptitude(individu,codeSecret)))
return sorted(classement_individus, key=lambda x: x[1], reverse=True)
classement = classementIndividus(population,"test")
for individu in classement:
print(individu)
Nous voici présent à la fonction qui va permettre de :
def generationFuture(population,pourcentageElitisme,tauxMutation,codeSecret):
#1: On classe les individus de la population passée en paramètre
classement_individus = classementIndividus(population,codeSecret)
#2: On crée 2 tableaux. L'un contenant l'individu gagnant, l'autre un tableau contenant les individus classés mais
#sans leur valeur d'aptitude
individu_gagnant = []
individus_classes = []
#3: Parcours des individus
for individu, aptitude in classement_individus:
#On stock l'individu sans son aptitude dans un nouveau tableau
individus_classes.append(individu)
#Si l'aptitude est égale à la longueur du mot secret à trouver, cela signifie que nous avons trouvé la solution.
if aptitude==len(codeSecret):
individu_gagnant.append(individu)
if individu_gagnant:
return population,individu_gagnant
#4: Selection des meilleurs individus (elites) devenant alors parents
#Leur nombre est fonction du pourcentage d'élites passé en paramètre
nombreElites = int(len(population)*pourcentageElitisme)
parents = individus_classes[:nombreElites]
#5: On selectionne d'autres parents pour maintenir la diversité génétique
#Cette selection se fait au hasard
#Si la roulette sort une valeur inférieure à 0.05 alors on ajoute l'individus aux parents
for individu in individus_classes[nombreElites:]:
roulette = random.random()
if roulette < 0.05:
parents.append(individu)
#6: Croisement des parents pour créer une nouvelle génération
nombreDeParentsSelectionnes = len(parents)
nombreEnfantsSouhaites = len(population)-nombreDeParentsSelectionnes
#Le nombre de gènes du père sera égal à la longueur du code secret divisé par deux
nombreGenesPere = len(codeSecret)//2
#Le nombre de gènes de la mère sera égal à la longueur du mot - le nombre de gène du père
nombreGenesMere = len(codeSecret)-nombreGenesPere
#Tant que nous n'avons pas le nombre d'enfants souhaité,
#On choisi 2 parents au hasard
#On extrait les gènes du père en fonction du nombre déterminé précédemment
#On extrait les gènes de la mère en fonction du nombre déterminé précédemment
#On concatène les deux pour obtenir un enfant
enfants = []
while len(enfants) < nombreEnfantsSouhaites:
pere = random.choice(parents)
mere = random.choice(parents)
enfant = pere[:nombreGenesPere] + mere[nombreGenesMere:]
enfants.append(enfant)
#Mutation génétique de certain enfants
#Cette mutation se fait aussi au hasard
#- Tant sur le choix de l'individu
#- Tant sur le gene à modifier
for enfant in enfants :
if random.random() < tauxMutation:
indexGene = int(random.random()*(len(codeSecret)))
enfant[indexGene] = generationGene()
#Ajout des enfants à la liste des parents pour créer la population
parents.extend(enfants)
return parents,individu_gagnant
CODE = "JoyeuXnoEl2019"
NOMBRE_INDIVIDUS = 100
POURCENTAGE_ELITE = 0.20
TAUX_MUTATION =0.1
#On définit un maximum de générations pour éviter une boucle infinie dans le cas où aucune solution n'est trouvée
MAXIMUM_GENERATIONS = 10000
#Generation d'une population initiale
population = generationPopulation(NOMBRE_INDIVIDUS,CODE)
#Execution de l'algorithme génétique
i=0
individu_gagnant = None
while not individu_gagnant and i<MAXIMUM_GENERATIONS:
population, individu_gagnant = generationFuture(population,POURCENTAGE_ELITE,TAUX_MUTATION,CODE)
i = i+1
if individu_gagnant:
print("Solution trouvée :"+str(individu_gagnant)+" Nb générations = "+str(i))
else:
print("Pas de solution trouvée...")
Par ce petit exemple, nous venons de découvrir le principe de fonctionnement des algorithmes génétiques. Bien entendu la forme présentée de l'algorithme est assez simple. Il existe en effet de nombreuses fonctions de selections et de croisements d'individus pour lesquelles nous aurons l'occasion de revenir en détail dans un exemple plus complet trés prochainement.