Projetar um algoritmo genético em Java

Projetar um algoritmo genético em Java

1. Introdução

O objetivo desta série éexplain the idea of genetic algorithms.

Os algoritmos genéticos são projetados para resolver problemas usando os mesmos processos da natureza - eles usam uma combinação de seleção, recombinação e mutação para desenvolver uma solução para um problema.

Vamos começar explicando o conceito desses algoritmos usando o exemplo de algoritmo genético binário mais simples.

2. Como funcionam os algoritmos genéticos

Genetic algorithms are a part of evolutionary computing, que é uma área de inteligência artificial em rápido crescimento.

Um algoritmo começa comset of solutions (representado porindividuals) chamadopopulation. Soluções de uma população são tomadas e usadas para formar umnew population, pois há uma chance de que a nova população seja melhor do que a antiga.

Indivíduos que são escolhidos para formar novas soluções (offspring) são selecionados de acordo com seusfitness - quanto mais adequados eles são, mais chances têm de se reproduzir.

3. Algoritmo Genético Binário

Vamos dar uma olhada no processo básico para um algoritmo genético simples.

3.1. Inicialização

Na etapa de inicialização, nósgenerate a random Population that serves as a first solution. Primeiro, precisamos decidir o quão grande será oPopulation e qual é a solução final que esperamos:

SimpleGeneticAlgorithm.runAlgorithm(50,
  "1011000100000100010000100000100111001000000100000100000000001111");

No exemplo acima, o tamanho dePopulation é 50 e a solução correta é representada pela sequência de bits binários que podemos alterar a qualquer momento.

Na próxima etapa, vamos salvar nossa solução desejada e criar umPopulation aleatório:

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

Agora estamos prontos para executar o loop principal do programa.

3.2. Verificação de aptidão

No loop principal do programa, vamos paraevaluate each Individual by the fitness function (em palavras simples, quanto melhor forIndividual, maior será o valor da função de aptidão):

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

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

Vamos começar explicandohow 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;
}

Como podemos observar, comparamos dois objetosIndividual bit a bit. Se não conseguirmos encontrar uma solução perfeita, precisamos prosseguir para a próxima etapa, que é uma evolução doPopulation.

3.3. Descendência

Nesta etapa, precisamos criar um novoPopulation. Primeiro, precisamosSelect dois objetos paisIndividual de aPopulation, de acordo com sua adequação. Observe que é benéfico permitir que os melhoresIndividual da geração atual sejam transferidos para a próxima, inalterados. Esta estratégia é chamada deElitism:

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

Para selecionar os dois melhores objetosIndividual, vamos aplicartournament 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;
}

O vencedor de cada torneio (aquele com melhor aptidão) é selecionado para a próxima fase, que é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;
}

No crossover, trocamos bits de cadaIndividual escolhido em um local escolhido aleatoriamente. Todo o processo é executado dentro do seguinte loop:

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

Como podemos ver, após o cruzamento, colocamos novos descendentes em um novoPopulation. Esta etapa é chamada deAcceptance.

Finalmente, podemos realizar umMutation. A mutação é usada para manter a diversidade genética de uma geração de aPopulation para a próxima. Usamos o tipo de mutaçãobit inversion, onde bits aleatórios são simplesmente invertidos:

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

Todos os tipos de mutação e crossover são bem descritosin this tutorial.

Em seguida, repetimos as etapas das subseções 3.2 e 3.3, até chegarmos a uma condição de término, por exemplo, a melhor solução.

4. Dicas e truques

Paraimplement an efficient genetic algorithm, precisamos ajustar um conjunto de parâmetros. Esta seção deve fornecer algumas recomendações básicas sobre como começar com os parâmetros mais importados:

  • Crossover rate - deve ser alto, cerca de80%-95%

  • Mutation rate - deve ser muito baixo, em torno de0.5%-1%.

  • Population size - o bom tamanho da população é de cerca de20-30, no entanto, para alguns problemas, os tamanhos 50-100 são melhores

  • Selection -roulette wheel selection básico pode ser usado com o conceito deelitism

  • Crossover and mutation type - depende da codificação e do problema

Observe que as recomendações para ajuste geralmente são resultados de estudos empíricos sobre algoritmos genéticos e podem variar com base nos problemas propostos.

5. Conclusão

Este tutorialintroduces fundamentals of genetic algorithms. Você pode aprender sobre algoritmos genéticoswithout any previous knowledge desta área, tendo apenas habilidades básicas de programação de computadores.

O código-fonte completo para os trechos de código neste tutorial está disponívelin the GitHub project.

Observe também que usamosLombok para gerar getters e setters. Você pode verificar como configurá-lo corretamente em seu IDEin this article.

Para mais exemplos de algoritmos genéticos, confira todos os artigos de nossa série: