Concevoir un algorithme génétique en Java

Concevoir un algorithme génétique en Java

1. introduction

Le but de cette série est deexplain the idea of genetic algorithms.

Les algorithmes génétiques sont conçus pour résoudre les problèmes en utilisant les mêmes processus que dans la nature - ils utilisent une combinaison de sélection, recombinaison et mutation pour élaborer une solution à un problème.

Commençons par expliquer le concept de ces algorithmes en utilisant l'exemple d'algorithme génétique binaire le plus simple.

2. Comment fonctionnent les algorithmes génétiques

Genetic algorithms are a part of evolutionary computing, qui est un domaine de l'intelligence artificielle en pleine croissance.

Un algorithme commence par unset of solutions (représenté parindividuals) appelépopulation. Les solutions d'une population sont prises et utilisées pour former unnew population, car il y a une chance que la nouvelle population soit meilleure que l'ancienne.

Les individus choisis pour former de nouvelles solutions (offspring) sont sélectionnés en fonction de leurfitness - plus ils sont adaptés, plus ils ont de chances de se reproduire.

3. Algorithme génétique binaire

Jetons un coup d'œil au processus de base d'un algorithme génétique simple.

3.1. Initialisation

Dans l'étape d'initialisation, nousgenerate a random Population that serves as a first solution. Tout d'abord, nous devons décider de la taille desPopulation et de la solution finale attendue:

SimpleGeneticAlgorithm.runAlgorithm(50,
  "1011000100000100010000100000100111001000000100000100000000001111");

Dans l'exemple ci-dessus, la taille dePopulation est de 50, et la solution correcte est représentée par la chaîne de bits binaires que nous pouvons modifier à tout moment.

Dans l'étape suivante, nous allons enregistrer la solution souhaitée et créer unPopulation aléatoire:

setSolution(solution);
Population myPop = new Population(populationSize, true);

Nous sommes maintenant prêts à exécuter la boucle principale du programme.

3.2. Vérification de la condition physique

Dans la boucle principale du programme, nous allons versevaluate each Individual by the fitness function (en termes simples, meilleur est leIndividual, plus la valeur de la fonction de fitness est élevée):

while (myPop.getFittest().getFitness() < getMaxFitness()) {
    System.out.println(
      "Generation: " + generationCount
      + " Correct genes found: " + myPop.getFittest().getFitness());

    myPop = evolvePopulation(myPop);
    generationCount++;
}

Commençons par expliquerhow we get the fittest Individual:

public int getFitness(Individual individual) {
    int fitness = 0;
    for (int i = 0; i < individual.getDefaultGeneLength()
      && i < solution.length; i++) {
        if (individual.getSingleGene(i) == solution[i]) {
            fitness++;
        }
    }
    return fitness;
}

Comme nous pouvons l'observer, nous comparons deux objetsIndividual bit par bit. Si nous ne pouvons pas trouver une solution parfaite, nous devons passer à l'étape suivante, qui est une évolution desPopulation.

3.3. Progéniture

Dans cette étape, nous devons créer un nouveauPopulation. Premièrement, nous devonsSelect deux objets parentsIndividual d'unPopulation, en fonction de leur aptitude. Veuillez noter qu'il est avantageux de permettre aux meilleursIndividualde la génération actuelle de se reporter à la suivante, sans modification. Cette stratégie s'appelle unElitism:

if (elitism) {
    newPopulation.getIndividuals().add(0, pop.getFittest());
    elitismOffset = 1;
} else {
    elitismOffset = 0;
}

Afin de sélectionner les deux meilleurs objetsIndividual, nous allons appliquertournament selection strategy:

private Individual tournamentSelection(Population pop) {
    Population tournament = new Population(tournamentSize, false);
    for (int i = 0; i < tournamentSize; i++) {
        int randomId = (int) (Math.random() * pop.getIndividuals().size());
        tournament.getIndividuals().add(i, pop.getIndividual(randomId));
    }
    Individual fittest = tournament.getFittest();
    return fittest;
}

Le gagnant de chaque tournoi (celui avec la meilleure condition physique) est sélectionné pour la prochaine étape, qui estCrossover:

private Individual crossover(Individual indiv1, Individual indiv2) {
    Individual newSol = new Individual();
    for (int i = 0; i < newSol.getDefaultGeneLength(); i++) {
        if (Math.random() <= uniformRate) {
            newSol.setSingleGene(i, indiv1.getSingleGene(i));
        } else {
            newSol.setSingleGene(i, indiv2.getSingleGene(i));
        }
    }
    return newSol;
}

Dans le crossover, nous échangeons les bits de chaqueIndividual choisi à un endroit choisi au hasard. L'ensemble du processus s'exécute dans la boucle suivante:

for (int i = elitismOffset; i < pop.getIndividuals().size(); i++) {
    Individual indiv1 = tournamentSelection(pop);
    Individual indiv2 = tournamentSelection(pop);
    Individual newIndiv = crossover(indiv1, indiv2);
    newPopulation.getIndividuals().add(i, newIndiv);
}

Comme nous pouvons le voir, après le croisement, nous plaçons une nouvelle progéniture dans un nouveauPopulation. Cette étape s'appelle lesAcceptance.

Enfin, nous pouvons effectuer unMutation. La mutation est utilisée pour maintenir la diversité génétique d'une génération dePopulation à la suivante. Nous avons utilisé le type de mutationbit inversion, où les bits aléatoires sont simplement inversés:

private void mutate(Individual indiv) {
    for (int i = 0; i < indiv.getDefaultGeneLength(); i++) {
        if (Math.random() <= mutationRate) {
            byte gene = (byte) Math.round(Math.random());
            indiv.setSingleGene(i, gene);
        }
    }
}

Tous les types de mutation et de crossover sont bien décritsin this tutorial.

Nous répétons ensuite les étapes des sous-sections 3.2 et 3.3, jusqu'à ce que nous atteignions une condition de terminaison, par exemple, la meilleure solution.

4. Trucs et astuces

Pourimplement an efficient genetic algorithm, nous devons régler un ensemble de paramètres. Cette section devrait vous donner quelques recommandations de base sur la façon de commencer avec les paramètres les plus importants:

  • Crossover rate - il devrait être élevé, environ80%-95%

  • Mutation rate - il devrait être très bas, autour de0.5%-1%.

  • Population size - une bonne taille de population est d'environ20-30, cependant, pour certains problèmes, les tailles 50-100 sont meilleures

  • Selection - lesroulette wheel selection de base peuvent être utilisés avec le concept deelitism

  • Crossover and mutation type - cela dépend du codage et du problème

Veuillez noter que les recommandations de réglage résultent souvent d'études empiriques sur les algorithmes génétiques et qu'elles peuvent varier en fonction des problèmes proposés.

5. Conclusion

Ce tutorielintroduces fundamentals of genetic algorithms. Vous pouvez en apprendre davantage sur les algorithmes génétiqueswithout any previous knowledgede ce domaine, n'ayant que des compétences de base en programmation informatique.

Le code source complet des extraits de code de ce didacticiel est disponiblein the GitHub project.

Veuillez également noter que nous utilisonsLombok pour générer des getters et des setters. Vous pouvez vérifier comment le configurer correctement dans vos IDEin this article.

Pour d'autres exemples d'algorithmes génétiques, consultez tous les articles de notre série: