ia:genetique
Différences
Ci-dessous, les différences entre deux révisions de la page.
| Prochaine révision | Révision précédente | ||
| ia:genetique [2023/02/27 19:48] – créée goupillwiki | ia:genetique [2023/02/28 14:42] (Version actuelle) – goupillwiki | ||
|---|---|---|---|
| Ligne 108: | Ligne 108: | ||
| * la 2e moitié des gènes de l' | * la 2e moitié des gènes de l' | ||
| - | {{ : | + | {{ : |
| Ainsi l' | Ainsi l' | ||
| + | |||
| + | <WRAP box> | ||
| + | {{ : | ||
| + | |||
| + | En général, le cross-over consiste plutôt à choisir au hasard une position pour couper l'adn des deux parents et de générer deux enfants avec les morceaux. | ||
| + | |||
| + | Mais dans notre cas, cela ne convient pas : il faut absolument que l' | ||
| + | </ | ||
| === mutation === | === mutation === | ||
| Ligne 117: | Ligne 125: | ||
| Là encore, il y a des réglages : quel taux de mutation ? quel choix de mutation ? | Là encore, il y a des réglages : quel taux de mutation ? quel choix de mutation ? | ||
| + | |||
| + | On pourra choisir '' | ||
| == mutation par transposition == | == mutation par transposition == | ||
| - | {{ : | + | {{ : |
| On sélectionne deux positions au hasard et on les transpose. | On sélectionne deux positions au hasard et on les transpose. | ||
| Ligne 126: | Ligne 136: | ||
| == mutation par renversement == | == mutation par renversement == | ||
| - | {{ : | + | {{ : |
| On sélectionne deux positions au hasard et on renverse la séquence correspondante. | On sélectionne deux positions au hasard et on renverse la séquence correspondante. | ||
| Ligne 132: | Ligne 142: | ||
| == mutation par insertion == | == mutation par insertion == | ||
| - | {{ : | + | {{ : |
| On sélectionne au hasard l' | On sélectionne au hasard l' | ||
| + | |||
| + | == choix aléatoire de la mutation == | ||
| + | |||
| + | Lorsqu' | ||
| + | * '' | ||
| + | * '' | ||
| + | * et donc '' | ||
| + | |||
| + | === compétitions pour la reproduction === | ||
| + | |||
| + | Quels parents peuvent se reproduire ? | ||
| + | |||
| + | Pour sélectionner un parent, on prélève au hasard '' | ||
| + | |||
| + | Pour produire un enfant, on doit donc faire 2 tournois, pour obtenir 2 parents. | ||
| + | |||
| + | On recommence autant de fois que nécessaire pour obtenir le nombre d' | ||
| + | |||
| + | === élite === | ||
| + | |||
| + | Dans l' | ||
| + | |||
| + | À chaque génération, | ||
| + | |||
| + | On choisit donc de conserver les meilleurs adns de la population et de compléter la population en produisant autant d' | ||
| + | |||
| + | **nouvelle population = meilleurs de l' | ||
| + | |||
| + | On peut se fixer un facteur : '' | ||
| + | |||
| + | ===== Implémentation ===== | ||
| + | |||
| + | Vous disposez déjà de '' | ||
| + | |||
| + | ==== module distance ==== | ||
| + | |||
| + | Pour les calculs de distances, je vous propose le module suivant : | ||
| + | |||
| + | <code python> | ||
| + | # distance.py | ||
| + | from math import radians, cos, sin, acos | ||
| + | |||
| + | def cosd(angle_deg: | ||
| + | ''' | ||
| + | renvoie le cosinus de angle_deg, exprimé en degrés | ||
| + | ''' | ||
| + | return cos(radians(angle_deg)) | ||
| + | |||
| + | def sind(angle_deg: | ||
| + | ''' | ||
| + | renvoie le sinus de angle_deg, exprimé en degrés | ||
| + | ''' | ||
| + | return sin(radians(angle_deg)) | ||
| + | |||
| + | def distance(lat1: | ||
| + | ''' | ||
| + | renvoie la distance en km, entre deux points positionnés selon les coordonnées gps | ||
| + | ''' | ||
| + | R = 6378 | ||
| + | return R*acos(sind(lat1)*sind(lat2) + cosd(lng1-lng2)*cosd(lat1)*cosd(lat2)) | ||
| + | |||
| + | def longueur_parcours(indices, | ||
| + | ''' | ||
| + | indices: liste d' | ||
| + | data: liste de dict contenant les clés lat et lng | ||
| + | pour chaque i de indices, data[i] désigne un point. | ||
| + | renvoie la longueur du parcours reliant ces points | ||
| + | ''' | ||
| + | n = len(indices) | ||
| + | if n <= 1: | ||
| + | return 0 | ||
| + | somme = 0 | ||
| + | for i in range(n-1): | ||
| + | indice1 = indices[i] | ||
| + | lat1 = data[indice1][" | ||
| + | lng1 = data[indice1][" | ||
| + | indice2 = indices[i+1] | ||
| + | lat2 = data[indice2][" | ||
| + | lng2 = data[indice2][" | ||
| + | somme += distance(lat1, | ||
| + | return somme | ||
| + | </ | ||
| + | |||
| + | ==== module genetics ==== | ||
| + | |||
| + | C'est le cœur de votre programme. Vous allez écrire un module '' | ||
| + | |||
| + | <code python> | ||
| + | import random | ||
| + | from tqdm import tqdm | ||
| + | |||
| + | TOURNAMENT_SIZE = 10 # nombre de parents à tirer au sort pour un round de sélection | ||
| + | MUTATION_RATE = 0.3 | ||
| + | TRANSPOSE_RATIO = 0.4 | ||
| + | INSERT_RATIO = 0.4 | ||
| + | REVERSE_RATIO = 1 - TRANSPOSE_RATIO - INSERT_RATIO | ||
| + | ELITE_RATIO = 0.2 # ratio d' | ||
| + | |||
| + | def random_adn(size): | ||
| + | ''' | ||
| + | renvoie un adn aléatoire | ||
| + | l'adn est une séquence 0...size-1 mélangée | ||
| + | ''' | ||
| + | |||
| + | def mutation_reverse(adn): | ||
| + | ''' | ||
| + | adn: séquence, par exemple (4, 9, 17, 12, 65, 416, 53) | ||
| + | renvoie une copie avec une sous-séquence renversée au hasard, par exemple (4, 9, 416, 65, 12, 17, 53) | ||
| + | ''' | ||
| + | |||
| + | def mutation_transpose(adn): | ||
| + | ''' | ||
| + | adn: séquence, par exemple (4, 9, 17, 12, 65, 416, 53) | ||
| + | renvoie une copie avec une paire inversée, par exemple (4, 416, 17, 12, 65, 9, 53) | ||
| + | ''' | ||
| + | |||
| + | def mutation_insertion(adn): | ||
| + | ''' | ||
| + | adn: séquence, par exemple (4, 9, 17, 12, 65, 416, 53) | ||
| + | renvoie une copie avec un item inséré à une nouvelle position, par exemple (4, 9, 416, 17, 12, 65, 53) | ||
| + | ''' | ||
| + | |||
| + | def get_best(population): | ||
| + | ''' | ||
| + | population: liste de paire (adn, fitness) | ||
| + | renvoie la paire avec le meilleur fitness | ||
| + | ''' | ||
| + | |||
| + | def croisement(adn1, | ||
| + | ''' | ||
| + | adn1, adn2: adns des parents, de même taille | ||
| + | renvoie l'adn enfant suivant la règle : | ||
| + | sélection de la première moitié de adn1, recopiée identique dans enfant | ||
| + | sélection des autres valeurs, recopiées dans le même ordre que parent2 dans enfant 2 | ||
| + | Exemple : | ||
| + | adn1 = (1, 6, 5, 0, 2, 4, 3) | ||
| + | adn2 = (5, 3, 6, 2, 4, 0, 1) | ||
| + | enfant = (1, 6, 5, 0, 3, 2, 4) | ||
| + | ''' | ||
| + | |||
| + | def make_child(population, | ||
| + | ''' | ||
| + | population: liste de paire (adn, fitness) | ||
| + | Tire 2x au hasard TOURNAMENT_SIZE individus dans population, | ||
| + | sélectionne les deux meilleurs et produit un enfant | ||
| + | l' | ||
| + | ''' | ||
| + | |||
| + | def generation(population, | ||
| + | ''' | ||
| + | population: liste de paires (adn, fitness) | ||
| + | fitness_fct: | ||
| + | produit la nouvelle population constituée ainsi : | ||
| + | ELITE_RATIO d' | ||
| + | enfants dont les parents sont pris par tournoi dans l' | ||
| + | la nouvelle population a la même taille que l' | ||
| + | attention : la nouvelle population est toujours constituée de paires (adn ,fitness) | ||
| + | ''' | ||
| + | |||
| + | def process(adn_size: | ||
| + | ''' | ||
| + | adn_size: taille d'un adn | ||
| + | population_size: | ||
| + | turns: nombre de générations | ||
| + | fitness_fct: | ||
| + | Partant d'une population générée au hasard, produit turns générations et renvoie le meilleur adn | ||
| + | de la dernière génération | ||
| + | ''' | ||
| + | | ||
| + | # pour la boucle principale, écrivez : | ||
| + | # for i in tqdm(range(turns)): | ||
| + | # tqdm permet d' | ||
| + | </ | ||
| + | |||
| + | ==== démonstration ==== | ||
| + | |||
| + | Je propose le script de démonstration suivant : | ||
| + | |||
| + | <code python> | ||
| + | # demo.py | ||
| + | from data import cities | ||
| + | from distance import longueur_parcours | ||
| + | from genetics import process | ||
| + | import matplotlib.pyplot as plt | ||
| + | import random | ||
| + | |||
| + | random.seed(452) | ||
| + | N = 100 | ||
| + | |||
| + | selected_cities = [random.choice(cities) for i in range(N)] | ||
| + | |||
| + | def fitness_fct(adn): | ||
| + | return longueur_parcours(adn, | ||
| + | |||
| + | best = process(N, 300, 1000, fitness_fct) | ||
| + | |||
| + | d = fitness_fct(best) | ||
| + | print(f" | ||
| + | |||
| + | # représentation graphique | ||
| + | # remarque : la France est environ à 45° de latitude, pour avoir une carte pas trop | ||
| + | # écrasée, il faut multiplier les longitude par cos(45°) = 0.71 | ||
| + | |||
| + | # marqueurs des villes retenues | ||
| + | x_cities = [city[" | ||
| + | y_cities = [city[" | ||
| + | plt.scatter(x_cities, | ||
| + | |||
| + | # parcours | ||
| + | x_values = [selected_cities[i][" | ||
| + | y_values = [selected_cities[i][" | ||
| + | plt.plot(x_values, | ||
| + | |||
| + | plt.show() | ||
| + | </ | ||
| + | |||
| + | <WRAP tip> | ||
| + | Dans l' | ||
| + | |||
| + | * on pourrait juger que le surcroit de complexité et de coût en temps de l'algo génétique n'est pas rentable étant donné le maigre gain de performance, | ||
| + | * si de surcroit on réfléchissait à un bon choix de point de départ avec l' | ||
| + | * on pourrait réfléchir à une hybridation : constituer une population initiale avec l'algo glouton de façon à avoir de bons candidats dès le début, améliorer avec l'algo génétique. Ainsi, on limite le nombre de générations. | ||
| + | |||
| + | |||
| + | </ | ||
| + | |||
| + | |||
ia/genetique.1677523729.txt.gz · Dernière modification : de goupillwiki
