Entwerfen Sie einen genetischen Algorithmus in Java

Entwerfen Sie einen genetischen Algorithmus in Java

1. Einführung

Das Ziel dieser Reihe istexplain the idea of genetic algorithms.

Genetische Algorithmen lösen Probleme mit denselben Verfahren wie in der Natur - sie verwenden eine Kombination aus Selektion, Rekombination und Mutation, um eine Lösung für ein Problem zu entwickeln.

Beginnen wir mit der Erläuterung des Konzepts dieser Algorithmen am einfachsten Beispiel eines binären genetischen Algorithmus.

2. Wie genetische Algorithmen funktionieren

Genetic algorithms are a part of evolutionary computing, ein schnell wachsender Bereich der künstlichen Intelligenz.

Ein Algorithmus beginnt mit einemset of solutions (dargestellt durchindividuals), das alspopulation bezeichnet wird. Lösungen aus einer Population werden genommen und verwendet, um einnew population zu bilden, da die Wahrscheinlichkeit besteht, dass die neue Population besser ist als die alte.

Personen, die ausgewählt werden, um neue Lösungen (offspring) zu bilden, werden nach ihrenfitness ausgewählt - je geeigneter sie sind, desto größer sind die Chancen, dass sie sich reproduzieren.

3. Binärer genetischer Algorithmus

Schauen wir uns den grundlegenden Prozess für einen einfachen genetischen Algorithmus an.

3.1. Initialisierung

Im Initialisierungsschritt haben wirgenerate a random Population that serves as a first solution. Zuerst müssen wir entscheiden, wie groß diePopulation sein werden und was die endgültige Lösung ist, die wir erwarten:

SimpleGeneticAlgorithm.runAlgorithm(50,
  "1011000100000100010000100000100111001000000100000100000000001111");

Im obigen Beispiel beträgt die Größe vonPopulation50, und die richtige Lösung wird durch die binäre Bitfolge dargestellt, die wir jederzeit ändern können.

Im nächsten Schritt speichern wir unsere gewünschte Lösung und erstellen ein zufälligesPopulation:

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

Jetzt können wir die Hauptschleife des Programms ausführen.

3.2. Fitness-Check

In der Hauptschleife des Programms gehen wir zuevaluate each Individual by the fitness function (in einfachen Worten, je besserIndividual ist, desto höher ist der Wert der Fitnessfunktion, die es erhält):

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

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

Beginnen wir mit der Erklärung vonhow 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;
}

Wie wir beobachten können, vergleichen wir Stück für Stück zweiIndividual Objekte. Wenn wir keine perfekte Lösung finden können, müssen wir mit dem nächsten Schritt fortfahren, der eine Entwicklung derPopulation ist.

3.3. Nachwuchs

In diesem Schritt müssen wir ein neuesPopulation erstellen. Zuerst müssen wirSelect zwei übergeordneteIndividual Objekte von einemPopulation, entsprechend ihrer Fitness. Bitte beachten Sie, dass es vorteilhaft ist, die bestenIndividual der aktuellen Generation unverändert auf die nächste Generation übertragen zu lassen. Diese Strategie wird alsElitism: bezeichnet

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

Um zwei besteIndividual-Objekte auszuwählen, wenden wirtournament selection strategy an:

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;
}

Der Gewinner jedes Turniers (derjenige mit der besten Fitness) wird für die nächste Stufe ausgewählt, nämlichCrossover:

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;
}

In der Frequenzweiche tauschen wir Bits von jedem ausgewähltenIndividual an einer zufällig ausgewählten Stelle aus. Der gesamte Prozess läuft in der folgenden Schleife ab:

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);
}

Wie wir sehen können, platzieren wir nach dem Crossover neue Nachkommen in einem neuenPopulation. Dieser Schritt wird alsAcceptance. bezeichnet

Schließlich können wirMutation ausführen. Die Mutation wird verwendet, um die genetische Vielfalt von einer Generation vonPopulation zur nächsten aufrechtzuerhalten. Wir haben den Mutationstypbit inversionverwendet, bei dem zufällige Bits einfach invertiert werden:

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);
        }
    }
}

Alle Arten der Mutation und des Crossover sind inin this tutorial gut beschrieben.

Wir wiederholen dann die Schritte aus den Unterabschnitten 3.2 und 3.3, bis wir eine Beendigungsbedingung erreichen, beispielsweise die beste Lösung.

4. Tipps und Tricks

Umimplement an efficient genetic algorithm zu erreichen, müssen wir eine Reihe von Parametern einstellen. In diesem Abschnitt erhalten Sie einige grundlegende Empfehlungen, wie Sie mit den wichtigsten Parametern beginnen können:

  • Crossover rate - es sollte hoch sein, ungefähr80%-95%

  • Mutation rate - es sollte sehr niedrig sein, ungefähr0.5%-1%.

  • Population size - Eine gute Populationsgröße liegt bei etwa20-30. Bei einigen Problemen sind jedoch die Größen 50 bis 100 besser

  • Selection - Basisroulette wheel selection kann mit dem Konzept vonelitism verwendet werden

  • Crossover and mutation type - Dies hängt von der Codierung und dem Problem ab

Bitte beachten Sie, dass die Optimierungsempfehlungen häufig Ergebnisse empirischer Studien zu genetischen Algorithmen sind und aufgrund der vorgeschlagenen Probleme variieren können.

5. Fazit

Dieses Tutorialintroduces fundamentals of genetic algorithms. Sie können sich mit genetischen Algorithmenwithout any previous knowledge in diesem Bereich vertraut machen, die nur über grundlegende Computerprogrammierkenntnisse verfügen.

Der vollständige Quellcode für die Codefragmente in diesem Lernprogramm istin the GitHub project verfügbar.

Bitte beachten Sie auch, dass wirLombok verwenden, um Getter und Setter zu generieren. Sie können überprüfen, wie Sie es in Ihren IDEin this article richtig konfigurieren.

Weitere Beispiele für genetische Algorithmen finden Sie in allen Artikeln unserer Reihe: