Разработать генетический алгоритм на Java

Разработка генетического алгоритма на Java

1. Вступление

Цель этой серии -explain the idea of genetic algorithms.

Генетические алгоритмы предназначены для решения проблем с использованием тех же процессов, что и в природе - они используют комбинацию отбора, рекомбинации и мутации для разработки решения проблемы.

Давайте начнем с объяснения концепции этих алгоритмов на простейшем примере двоичного генетического алгоритма.

2. Как работают генетические алгоритмы

Genetic algorithms are a part of evolutionary computing - быстро развивающаяся область искусственного интеллекта.

Алгоритм начинается сset of solutions (представленногоindividuals), называемогоpopulation. Решения из одной популяции берутся и используются для формированияnew population, так как есть шанс, что новая популяция будет лучше старой.

Лица, выбранные для формирования новых решений (offspring), выбираются в соответствии с ихfitness - чем они более подходящими, тем больше у них шансов на воспроизведение.

3. Бинарный генетический алгоритм

Давайте посмотрим на основной процесс простого генетического алгоритма.

3.1. инициализация

На этапе инициализации wegenerate a random Population that serves as a first solution. Во-первых, нам нужно решить, насколько велик будетPopulation и какое окончательное решение мы ожидаем:

SimpleGeneticAlgorithm.runAlgorithm(50,
  "1011000100000100010000100000100111001000000100000100000000001111");

В приведенном выше примере размерPopulation равен 50, и правильное решение представлено двоичной битовой строкой, которую мы можем изменить в любое время.

На следующем шаге мы собираемся сохранить желаемое решение и создать случайныйPopulation:

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

Теперь мы готовы запустить основной цикл программы.

3.2. Проверка пригодности

В основном цикле программы мы переходим кevaluate each Individual by the fitness function (простыми словами, чем лучшеIndividual, тем большее значение фитнес-функции он получает):

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

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

Начнем с объясненияhow 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;
}

Как мы видим, мы сравниваем два объектаIndividual по крупицам. Если мы не можем найти идеальное решение, нам нужно перейти к следующему шагу, который представляет собой эволюциюPopulation.

3.3. Потомство

На этом этапе нам нужно создать новыйPopulation. Во-первых, нам нужноSelect два родительских объектаIndividual изPopulation, в соответствии с их пригодностью. Обратите внимание, что выгодно позволить лучшимIndividual из текущего поколения переноситься на следующее без изменений. Эта стратегия называетсяElitism:

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

Чтобы выбрать два лучших объектаIndividual, мы собираемся применитьtournament 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;
}

Победитель каждого турнира (наиболее подготовленный) выбирается для следующего этапа, что составляетCrossover:

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

В кроссовере мы меняем местами биты из каждого выбранногоIndividual в случайно выбранном месте. Весь процесс выполняется внутри следующего цикла:

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

Как мы видим, после кроссовера мы помещаем новое потомство в новыйPopulation. Этот шаг называетсяAcceptance.

Наконец, мы можем выполнитьMutation. Мутация используется для сохранения генетического разнообразия от одного поколения aPopulationк следующему. Мы использовали тип мутацииbit inversion, где случайные биты просто инвертируются:

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

Все типы Мутации и Кроссовера хорошо описаныin this tutorial.

Затем мы повторяем шаги из подразделов 3.2 и 3.3, пока не достигнем условия завершения, например, наилучшего решения.

4. Секреты и уловки

Для того, чтобыimplement an efficient genetic algorithm, нам нужно настроить набор параметров. Этот раздел должен дать вам несколько основных рекомендаций, как начать с большинства параметров импорта:

  • Crossover rate - он должен быть высоким, около80%-95%

  • Mutation rate - он должен быть очень низким, около0.5%-1%.

  • Population size - хороший размер популяции составляет около20-30, однако для некоторых задач лучше 50-100

  • Selection - базовыйroulette wheel selection можно использовать с концепциейelitism

  • Crossover and mutation type - это зависит от кодировки и проблемы

Обратите внимание, что рекомендации по настройке часто являются результатами эмпирических исследований генетических алгоритмов, и они могут варьироваться в зависимости от предложенных проблем.

5. Заключение

Это руководствоintroduces fundamentals of genetic algorithms. Вы можете узнать о генетических алгоритмахwithout any previous knowledge этой области, имея лишь базовые навыки компьютерного программирования.

Полный исходный код для фрагментов кода в этом руководстве доступенin the GitHub project.

Также обратите внимание, что мы используемLombok для создания геттеров и сеттеров. Вы можете проверить, как правильно его настроить, в своей IDEin this article.

Для дальнейших примеров генетических алгоритмов, проверьте все статьи нашей серии: