・ Ceci est mon premier message, donc il peut être difficile à voir. s'il vous plaît, pardonnez-moi.
・ Il peut y avoir des erreurs par rapport à l'algorithme génétique strict. (Eh bien, apprendre quelque chose? Pardonnez-moi car il semble que ça marche bien ...)
-Je suis désolé si le code source est difficile à lire ou redondant parce que je ne l'ai pas montré à d'autres. (Je vous serais reconnaissant si vous pouviez me dire les parties qui étaient difficiles à lire et celles qui étaient inutiles.)
Il existe de nombreux types de sagesse d'excellents enseignants Google. https://www.sist.ac.jp/~suganuma/kougi/other_lecture/SE/opt/GA/GA.htm Il existe également des définitions de mots et de cette zone, veuillez donc vous y référer.
https://www.youtube.com/watch?v=w1MF0Iz0p40 Youtube est toujours incroyable. Vous pouvez facilement voir une chose aussi intéressante et facile à comprendre.
Pour le moment, comme exemple de problème, trouvons les coordonnées où la distance de l'origine est 0 dans l'espace tridimensionnel (x, y, z). De toute évidence, (0,0,0) devrait être la réponse ... Après cela, essayez de changer la distance par rapport à l'origine ou de lui donner environ 20 dimensions.
Depuis que j'ai récemment étudié la classe, j'ai essayé d'implémenter le flux général de l'AG dans une classe. Pour être honnête, les avantages et les inconvénients de les rassembler dans une classe ne me sont pas venus à l'esprit ...
Pour le moment, j'expliquerai d'abord le rôle général des fonctions dans le programme.
Type de fonction
class GA:
    #Récupérez les paramètres sur GA à partir du paramètre reçu en argument (nombre de générations, etc.)
    def __init__(self, Setting):
    #Afficher les paramètres sur l'AG tenue en classe
    def get_parameter(self, flag=0, out_path="./"):
    #La plupart du traitement GA est écrit dans ce(Quelque chose comme la fonction principale)
    def Start_GA(self):
    #Créer au hasard des gènes pour chaque individu en tant que population initiale
    def make_genes(self):
    #Une fonction qui évalue les gènes (calcule l'adaptabilité), et en même temps, trie par ordre décroissant d'évaluation et conserve les données
    def evaluate(self, genes, goal):
    #Générer des gènes dans un fichier externe avec adaptabilité
    def Out_data(self, genes, evaluated_data, generation):
    #Sauvez les personnes de haut rang avec une grande adaptabilité
    def Save_elite(self, genes, evaluated_data):
    #Une fonction qui sélectionne les parents pour créer la prochaine génération d'enfants.
    def select(self, method, genes, evaluated_data):
    #Une fonction qui multiplie (croise) les parents sélectionnés pour créer un nouvel individu
    def crossover(self, method, genes, Select_id):
    #Plus la stagnation est importante, plus le caractère aléatoire est ajouté pour étendre la zone de recherche de solution (mutation).
    def mutation(self, genes, Stagnation):
Parmi elles, d'autres fonctions sont incorporées dans la fonction StartGA, donc si vous l'exécutez, le programme s'exécutera.
GA.py
import numpy as np
import random
import os
import copy
class GA:
    # Setting=[Name, MaxGeneration, Population, EliteCount, MaxStagnationGeneration, GenesDimension ...]
    def __init__(self, Setting):
        self.Name = Setting[0]
        self.MaxGeneration = Setting[1]
        self.Population = Setting[2]
        self.EliteCount = Setting[3]
        self.MaxStagnationGeneration = Setting[4]
        self.GenesDimension = Setting[5]
        os.makedirs("./Out/", exist_ok=True)
        print("\n GA start!! \n")
        # flag==0, indicateur de sortie d'impression==1, description du fichier
    def get_parameter(self, flag=0, out_path="./"):
        #Chemin de sortie du fichier
        out_path = out_path + "GA_parameter.txt"
        #Contenu de sortie
        contents = [
                     f'GA_parameter!!\n',
                     f'Name: {self.Name}',
                     f'MaxGeneration: {self.MaxGeneration}',
                     f'Population: {self.Population}',
                     f'EliteCount: {self.EliteCount}',
                     f'GenesDimension: {self.GenesDimension}',
                                                                ]
        #Changer la destination de sortie avec l'indicateur
        if flag==0:
            for sentense in contents:
                print(sentense)
        elif flag==1:
            with open(out_path, mode="w") as file:
                for sentense in contents:
                    print(sentense, file=file)
    #Fonction pour démarrer GA (ou plutôt, c'est comme la fonction principale car la plupart du traitement est écrit dans ceci?)
    def Start_GA(self):
        #Génération actuelle
        generation = 0
        #Valeur de stagnation de la génération
        Stagnation = 0
        #Valeur cible
        goal = 0
        #La trajectoire du gène le plus adaptable
        top_genes = list()
        #Créez au hasard des gènes en tant que population initiale.
        genes = self.make_genes()
        while(True):
            #Se termine lorsque la génération maximale est atteinte
            if generation >= self.MaxGeneration:
                break
            else:
                #Évaluation de l'adaptabilité
                evaluated_data = self.evaluate(genes, goal)
                #Fichier de gène de sortie et adaptabilité à l'extérieur
                self.Out_data(genes, evaluated_data, generation)
                #Sauvez les personnes de haut rang avec une grande adaptabilité
                elite_genes = copy.deepcopy(self.Save_elite(genes, evaluated_data))
                #Sélectionnez des personnes avec une grande adaptabilité
                Select_id = self.select(0, genes, evaluated_data)
                #Traverser pour créer de nouveaux gènes
                genes = self.crossover(0, genes, Select_id)
                #Donnez des mutations pour étendre la zone de recherche de solution
                genes = self.mutation(genes, Stagnation)
                #Ajouter un gène élite
                genes[len(genes):len(genes)] = elite_genes
                #Sauvez l'individu le plus adaptable
                top_genes.append(elite_genes[0])
                #Après la deuxième génération, si la stagnation de l'adaptabilité (la meilleure valeur d'adaptabilité n'est pas mise à jour) dépasse le nombre maximum de stagnation, le programme prend fin.
                if len(top_genes)>=2:
                    if top_genes[-1]==top_genes[-2]:
                        #L'algorithme se termine lorsque le nombre maximum de stagnation est dépassé
                        if Stagnation >= self.MaxStagnationGeneration:
                            exit()
                        else:
                            Stagnation += 1
                    else:
                        Stagnation = 0
                #Faire progresser les générations
                generation +=1
    #Créez des gènes au hasard. Soi pour un individu.Développez la dimension par le nombre de gènes Dimension
    def make_genes(self):
        genes = list()
        tmp = list()
        for child in range(self.Population):
            for _ in range(self.GenesDimension):
                tmp.append(random.randint(-30,30))
            genes.append(tmp)
            tmp = list()
        # genes.shape=(self.Population, self.GenesDimension)
        return genes
    #Évaluation des gènes
    def evaluate(self, genes, goal):
        #Les données évaluées sont stockées dans un type de dictionnaire.(key, value)=(child_id, fitness)
        evaluated_data = dict()
        for child in range(self.Population):
            #Plus l'adaptabilité est élevée, mieux c'est (ajusté à un maximum de 1).)
            fitness = 1/(self.eval_func(genes[child], goal) + 1)
            evaluated_data[child] = fitness
        #Tri décroissant selon la valeur d'évaluation
        evaluated_data = dict(sorted(evaluated_data.items(),reverse=True, key=lambda x:x[1]))
        return evaluated_data
    #Fonction auxiliaire d'évaluation
    #Calculez la différence avec la valeur cible
    def eval_func(self, gene, goal):
        sum_value = 0
        # self.Calculez la distance euclidienne de l'origine dans l'espace GenesDimension.
        for id in range(len(gene)):
            sum_value += gene[id]**2
        return abs(np.sqrt(sum_value) - goal)   # goal(Valeur cible)Calculez l'écart par rapport à.
    #Sortie des données de gènes et adaptabilité à un fichier externe
    def Out_data(self, genes, evaluated_data, generation):
        #Sortie de gène(Avant de trier),Même séparateur de virgule que csv
        gene_path = "./Out/" + str(generation) + ".gene"
        with open(gene_path, "w") as file:
            for child in range(len(genes)):
                for id in range(len(genes[child])):
                    if id==len(genes[child])-1:
                        print(str(genes[child][id]), file=file)
                    else:
                        print(str(genes[child][id]), end=",", file=file)
        #Sortie adaptabilité (après tri),Même séparateur de virgule que csv
        fitness_path = "./Out/" + str(generation) + ".fit"
        with open(fitness_path, "w") as file:
            for id, fitness in evaluated_data.items():
                print(str(id) +","+ str(fitness), file=file)
    #Sauvez les personnes de haut rang avec une grande adaptabilité
    def Save_elite(self, genes, evaluated_data):
        elite_id = list()
        elite_genes = list()
        # elite_Obtenir l'identifiant du dictionnaire
        for key in evaluated_data.keys():
            elite_id.append(key)
        #Extrait du haut autant que le nombre d'élite
        for elite in range(self.EliteCount):
            elite_genes.append(genes[elite_id[elite]])
        return elite_genes
    # method==0: Expected value selection
    # method==1: Ranking selection
    # method==2: Tournament selection
    def select(self, method, genes, evaluated_data):
        #Stockez l'identifiant et la forme physique séparément dans une liste
        id_list = list()
        fitness_list = list()
        Select_id = list()
        for id, fitness in evaluated_data.items():
            id_list.append(id)
            fitness_list.append(fitness)
        #Sélection de la valeur attendue
        if method==0:
            roulette = 360
            tmp_list = list()
            sum_fitness = sum(fitness_list) #Obtenez la valeur totale de l'adaptabilité
            cumulative_fitness = np.cumsum(roulette*np.array(fitness_list)/sum_fitness) #Créez une large table à 360 degrés avec une rotation en fonction du degré d'adaptabilité
            for child in range(self.Population - self.EliteCount):    #Nombre d'individus pour créer des individus non élites - Nombre d'individus élites
                for _ in range(2):                                    #Répétez pour combiner les deux gènes.
                    Fate_dice = random.uniform(0,360)                 # 0~Lancez les dés fatidiques entre 360
                    Hit_id = 0
                    while True:
                        if cumulative_fitness[Hit_id] >= Fate_dice:
                            break
                        else:
                            Hit_id += 1
                    tmp_list.append(id_list[Hit_id])
                Select_id.append(tmp_list)
                tmp_list = list()
        return Select_id
    # method==0: Uniform crossover
    # method==1: Multipoint crossover
    # method==2: Partial coincidence crossover
    def crossover(self, method, genes, Select_id):
        new_genes = list()
        #Sélectionnez les gènes de nouvelle génération_id[child][0]Créé en utilisant le numéro d'identification de
        for child in range(self.Population - self.EliteCount):
            new_genes.append(genes[Select_id[child][0]])
        if method==0:
            probability = 0.4
            for child in range(self.Population - self.EliteCount):
                for dim in range(len(genes[0])):
                    Fate_random = random.uniform(0,1)
                    if Fate_random <= probability:
                        new_genes[child][dim] = genes[Select_id[child][1]][dim]
        return new_genes
    def mutation(self, genes, Stagnation):
        probability = 0.4                          #Probabilité de mutation
        serious_rate = Stagnation/(self.MaxGeneration - self.EliteCount)   #Plus la stagnation est importante, plus elle est susceptible de muter.
        serious_count = int(len(genes)*serious_rate)        #Ajustez la quantité de séquence mutée en multipliant par la taille de la séquence de gènes
        for child in range(serious_count):
            for dim in range(len(genes[0])):
                Fate_random = random.uniform(0,1)      # 0~Obtenez un nombre aléatoire de 1
                if Fate_random <= probability:
                    genes[child][dim] += random.randint(-10,10) # -10~Donne 10 nombres aléatoires (fixes)
        return genes
# Setting=[Name, MaxGeneration, Population, EliteCount, MaxStagnationGeneration, GenesDimension ...]
Setting = ["Sample", 150, 30, 2, 100, 3]
GA = GA(Setting)
GA.Start_GA()
Dans make_genes, les gènes sont créés dans la plage de -30 à 30 pour un axe (axe x, etc.). En outre, pour les mutations, des valeurs sont ajoutées dans la plage de -10 à 10.
Dans les conditions ci-dessus, j'ai calculé 30 individus par génération pour environ 150 générations. Le programme pour voir le résultat est le suivant.
analysis_log.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#Calculer la distance par rapport au bloc de données
def calc_distance(dataframe):
    distance = 0
    for col in range(len(dataframe.columns)):   #Répétez autant de fois qu'il y a de colonnes
        distance += dataframe[col]**2
    distance = np.sqrt(distance) #Calculer la distance euclidienne
    return distance
#Combiner des trames de données
def merge_dataframe(origin, add):
    origin = pd.concat([origin, add], axis=1)
    return origin
#Créer un graphique sous forme de données de série chronologique
def plot_log(distance, MaxGeneration):
    y_mean = np.array(distance.mean())      #Obtenez la valeur moyenne des données
    y_median = np.array(distance.median())  #Obtenir la médiane
    y_min = np.array(distance.min()) #Obtenez la valeur minimale
    y_max = np.array(distance.max()) #Obtenez le maximum
    x = np.arange(len(y_mean))
    xicks = np.arange(0, MaxGeneration+1, MaxGeneration/10)   #Pour afficher Max Generation en mémoire+1
    plt.plot(x, y_mean, label="mean", color="green")
    plt.plot(x, y_median,label="median", color="blue")
    plt.plot(x, y_min, label="min", color="red")
    plt.plot(x, y_max, label="max", color="cyan")
    plt.xlabel("$Generation$", fontsize=15)
    plt.ylabel("$distance$", fontsize=15)
    plt.xlim(xmin=0)
    plt.ylim(ymin=0)
    plt.grid(True)
    plt.xticks(xicks)
    plt.legend()
    plt.savefig("analysis_gene.jpg ")
MaxGeneration = 150
for G in range(MaxGeneration):
    if G==0:
        gene_path = "./Out/" + str(G) + ".gene"
        dataframe_0 = pd.read_csv(gene_path, header=None)  #Lecture des données
        distance_0 = calc_distance(dataframe_0)            #Calculer la distance par rapport au bloc de données
    else:
        gene_path = "./Out/" + str(G) + ".gene"
        dataframe = pd.read_csv(gene_path, header=None)
        distance = calc_distance(dataframe)
        distance_0 = merge_dataframe(distance_0, distance)
plot_log(distance_0, MaxGeneration)
L'axe horizontal représente le nombre de générations et l'axe vertical la distance par rapport à l'origine (0 est la valeur cible cette fois). En outre, moyenne, médiane, min et max sont respectivement la valeur moyenne, la valeur médiane, la valeur minimale et la valeur maximale de chaque génération. La recherche semble se dérouler sans heurts. Je suis heureux.

Enfin, avec la valeur cible fixée à 100, 100 individus par génération sont calculés pour environ 500 générations.
Ensuite, la valeur cible a été renvoyée à 0, le nombre d'axes génétiques (nombre de dimensions) a été fixé à 20 et le calcul a été effectué pour 150 individus et 2000 générations.
 

[Impression] D'une manière ou d'une autre, je pense que ce serait bien si les valeurs étaient mises à jour ... J'ai beaucoup écrit à ce sujet, mais s'il y a un homme féroce qui l'a lu jusqu'au bout, je serais très heureux si vous commentez.
Recommended Posts