Algoritmo Genético Clássico em Java - Hello World

14 minutos de leitura

Atualizado em:

Introdução

Algorítimos Genéticos, AGs, são métodos de busca inspirados na evolução dos seres vivos, introduzidos por John Holland (1975) e popularizados por um de seus alunos, David Goldberg (1989), seguem o princípio da seleção natural e sobrevivência dos mais aptos, segundo Charles Darwin (1859). Ele propôs que quanto mais apto um indivíduo for de sobreviver em um meio ambiente, mais chances ele terá de se reproduzir e passar sua carga genética para seus descendentes.

É nisto que se baseia os Algoritmos Genéticos, buscar boas soluções em um espaço de busca grande, em que uma busca pontual seria muito cara.

Se quiser saber mais sobre AGs, leia este ótimo material:  INTRODUÇÃO AOS ALGORITMOS GENÉTICOS (Estéfane G. M. de Lacerda e André Carlos P. L. F. de Carvalho)

Algoritmo Genético Clássico

É um modelo de AG simples, para soluções simples. Pode ser exemplificado no pseudo-código abaixo:

Seja S(t) a população de cromossomos na geração t.
t ← 0
inicializar S(t)
avaliar S(t)
enquanto o critério de parada não for satisfeito faça
 t ← t+ 1
 selecionar S(t) a partir de S(t- 1)
 aplicar crossover sobre S(t)
 aplicar mutação sobre S(t)
 avaliar S(t)
fim enquanto

Detalhes da Aplicação

Neste exemplo uso como função objetivo, encontrar um gene pré definido por uma frase em uma String, por exemplo:

Olá Mundo

A população inicial será criada com 50 indivíduos aleatórios, com genes de mesmo comprimento que a solução, a aptidão será calculada pelo número de letras iguais a solução, por exemplo se a solução fosse 'ola', o gene 'olq' teria aptidão 2 e o gene 'qlw' teria aptidão 1.

O critério de parada será a solução encontrada, ou até chegar um número máximo de gerações definida na aplicação.

Implementei a seleção por torneio, onde são sorteados 3 indivíduos, destes os 2 com maior aptidão são selecionados para o crossover.

Utilizei uma taxa de corssover de 60%, que pode ser alterada, e um crossover de 1 ponto, exemplo:

| = ponto de corte aleatório

p1: shw|jakw

p2: wjd|jwke

f1: shw|jwke

f2: wjd|jakw

Defini uma taxa de mutação de 3%, também podendo ser alterada, que substitui um gene por outro aleatório, por exemplo:

oaa muwdp -> oaa mpwdp

Mesmo não fazendo parte de um AG clássico, utilizo o elitismo, que copia o melhor indivíduo para a próxima geração.

Código

Há quatro classes neste exemplo:
HelloWorldAG.java (Classe Main com a execução do algorítimo);
Algoritimo.java (Métodos e variáveis estáticas para controle do AG, crossover, seleção...);
Populacao.java (Classe de domínio que define uma população, contém uma lista de indivíduos);
Individuo.java (Classe de domínio que representa um indivíduo, contém seus genes e valor de aptidão).

Baixe o projeto aqui, ou confira o código abaixo.

HelloWorldAG.java

package helloWorldAG;

public class AGhelloWorld {

    public static void main(String[] args) {

        //Define a solução
        Algoritimo.setSolucao("Olá Mundo");
        //Define os caracteres existentes
        Algoritimo.setCaracteres("!,.:;?áÁãÃâÂõÕôÔóÓéêÉÊíQWERTYUIOPASDFGHJKLÇZXCVBNMqwertyuiopasdfghjklçzxcvbnm1234567890 ");
        //taxa de crossover de 60%
        Algoritimo.setTaxaDeCrossover(0.6);
        //taxa de mutação de 3%
        Algoritimo.setTaxaDeMutacao(0.3);
        //elitismo
        boolean eltismo = true;
        //tamanho da população
        int tamPop = 100;
        //numero máximo de gerações
        int numMaxGeracoes = 10000;

        //define o número de genes do indivíduo baseado na solução
        int numGenes = Algoritimo.getSolucao().length();

        //cria a primeira população aleatérioa
        Populacao populacao = new Populacao(numGenes, tamPop);

        boolean temSolucao = false;
        int geracao = 0;

        System.out.println("Iniciando... Aptidão da solução: "+Algoritimo.getSolucao().length());
        
        //loop até o critério de parada
        while (!temSolucao && geracao < numMaxGeracoes) {
            geracao++;

            //cria nova populacao
            populacao = Algoritimo.novaGeracao(populacao, eltismo);

            System.out.println("Geração " + geracao + " | Aptidão: " + populacao.getIndivduo(0).getAptidao() + " | Melhor: " + populacao.getIndivduo(0).getGenes());
            
            //verifica se tem a solucao
            temSolucao = populacao.temSolocao(Algoritimo.getSolucao());
        }

        if (geracao == numMaxGeracoes) {
            System.out.println("Número Maximo de Gerações | " + populacao.getIndivduo(0).getGenes() + " " + populacao.getIndivduo(0).getAptidao());
        }

        if (temSolucao) {
            System.out.println("Encontrado resultado na geração " + geracao + " | " + populacao.getIndivduo(0).getGenes() + " (Aptidão: " + populacao.getIndivduo(0).getAptidao() + ")");
        }
    }
}

Algoritimo.java

package helloWorldAG;

import java.util.Random;

public class Algoritimo {

    private static String solucao;
    private static double taxaDeCrossover;
    private static double taxaDeMutacao;
    private static String caracteres;

    public static Populacao novaGeracao(Populacao populacao, boolean elitismo) {
        Random r = new Random();
        //nova população do mesmo tamanho da antiga
        Populacao novaPopulacao = new Populacao(populacao.getTamPopulacao());

        //se tiver elitismo, mantém o melhor indivíduo da geração atual
        if (elitismo) {
            novaPopulacao.setIndividuo(populacao.getIndivduo(0));
        }

        //insere novos indivíduos na nova população, até atingir o tamanho máximo
        while (novaPopulacao.getNumIndividuos() < novaPopulacao.getTamPopulacao()) {
            //seleciona os 2 pais por torneio
            Individuo[] pais = selecaoTorneio(populacao);

            Individuo[] filhos = new Individuo[2];

            //verifica a taxa de crossover, se sim realiza o crossover, se não, mantém os pais selecionados para a próxima geração
            if (r.nextDouble() <= taxaDeCrossover) {
                filhos = crossover(pais[1], pais[0]);
            } else {
                filhos[0] = new Individuo(pais[0].getGenes());
                filhos[1] = new Individuo(pais[1].getGenes());
            }

            //adiciona os filhos na nova geração
            novaPopulacao.setIndividuo(filhos[0]);
            novaPopulacao.setIndividuo(filhos[1]);
        }

        //ordena a nova população
        novaPopulacao.ordenaPopulacao();
        return novaPopulacao;
    }

    public static Individuo[] crossover(Individuo individuo1, Individuo individuo2) {
        Random r = new Random();

        //sorteia o ponto de corte
        int pontoCorte1 = r.nextInt((individuo1.getGenes().length()/2) -2) + 1;
        int pontoCorte2 = r.nextInt((individuo1.getGenes().length()/2) -2) + individuo1.getGenes().length()/2;

        Individuo[] filhos = new Individuo[2];

        //pega os genes dos pais
        String genePai1 = individuo1.getGenes();
        String genePai2 = individuo2.getGenes();

        String geneFilho1;
        String geneFilho2;

        //realiza o corte, 
        geneFilho1 = genePai1.substring(0, pontoCorte1);
        geneFilho1 += genePai2.substring(pontoCorte1, pontoCorte2);
        geneFilho1 += genePai1.substring(pontoCorte2, genePai1.length());
        
        geneFilho2 = genePai2.substring(0, pontoCorte1);
        geneFilho2 += genePai1.substring(pontoCorte1, pontoCorte2);
        geneFilho2 += genePai2.substring(pontoCorte2, genePai2.length());

        //cria o novo indivíduo com os genes dos pais
        filhos[0] = new Individuo(geneFilho1);
        filhos[1] = new Individuo(geneFilho2);

        return filhos;
    }

    public static Individuo[] selecaoTorneio(Populacao populacao) {
        Random r = new Random();
        Populacao populacaoIntermediaria = new Populacao(3);

        //seleciona 3 indivíduos aleatóriamente na população
        populacaoIntermediaria.setIndividuo(populacao.getIndivduo(r.nextInt(populacao.getTamPopulacao())));
        populacaoIntermediaria.setIndividuo(populacao.getIndivduo(r.nextInt(populacao.getTamPopulacao())));
        populacaoIntermediaria.setIndividuo(populacao.getIndivduo(r.nextInt(populacao.getTamPopulacao())));

        //ordena a população
        populacaoIntermediaria.ordenaPopulacao();

        Individuo[] pais = new Individuo[2];

        //seleciona os 2 melhores deste população
        pais[0] = populacaoIntermediaria.getIndivduo(0);
        pais[1] = populacaoIntermediaria.getIndivduo(1);

        return pais;
    }

    public static String getSolucao() {
        return solucao;
    }

    public static void setSolucao(String solucao) {
        Algoritimo.solucao = solucao;
    }

    public static double getTaxaDeCrossover() {
        return taxaDeCrossover;
    }

    public static void setTaxaDeCrossover(double taxaDeCrossover) {
        Algoritimo.taxaDeCrossover = taxaDeCrossover;
    }

    public static double getTaxaDeMutacao() {
        return taxaDeMutacao;
    }

    public static void setTaxaDeMutacao(double taxaDeMutacao) {
        Algoritimo.taxaDeMutacao = taxaDeMutacao;
    }

    public static String getCaracteres() {
        return caracteres;
    }

    public static void setCaracteres(String caracteres) {
        Algoritimo.caracteres = caracteres;
    }
    
    
}

Populacao.java

package helloWorldAG;

public class Populacao {

    private Individuo[] individuos;
    private int tamPopulacao;

    //cria uma população com indivíduos aleatória
    public Populacao(int numGenes, int tamPop) {
        tamPopulacao = tamPop;
        individuos = new Individuo[tamPop];
        for (int i = 0; i < individuos.length; i++) {
            individuos[i] = new Individuo(numGenes);
        }
    }

    //cria uma população com indivíduos sem valor, será composto posteriormente
    public Populacao(int tamPop) {
        tamPopulacao = tamPop;
        individuos = new Individuo[tamPop];
        for (int i = 0; i < individuos.length; i++) {
            individuos[i] = null;
        }
    }

    //coloca um indivíduo em uma certa posição da população
    public void setIndividuo(Individuo individuo, int posicao) {
        individuos[posicao] = individuo;
    }

    //coloca um indivíduo na próxima posição disponível da população
    public void setIndividuo(Individuo individuo) {
        for (int i = 0; i < individuos.length; i++) {
            if (individuos[i] == null) {
                individuos[i] = individuo;
                return;
            }
        }
    }

    //verifoca se algum indivíduo da população possui a solução
    public boolean temSolocao(String solucao) {
        Individuo i = null;
        for (int j = 0; j < individuos.length; j++) {
            if (individuos[j].getGenes().equals(solucao)) {
                i = individuos[j];
                break;
            }
        }
        if (i == null) {
            return false;
        }
        return true;
    }

    //ordena a população pelo valor de aptidão de cada indivíduo, do maior valor para o menor, assim se eu quiser obter o melhor indivíduo desta população, acesso a posição 0 do array de indivíduos
    public void ordenaPopulacao() {
        boolean trocou = true;
        while (trocou) {
            trocou = false;
            for (int i = 0; i < individuos.length - 1; i++) {
                if (individuos[i].getAptidao() < individuos[i + 1].getAptidao()) {
                    Individuo temp = individuos[i];
                    individuos[i] = individuos[i + 1];
                    individuos[i + 1] = temp;
                    trocou = true;
                }
            }
        }
    }

    //número de indivíduos existentes na população
    public int getNumIndividuos() {
        int num = 0;
        for (int i = 0; i < individuos.length; i++) {
            if (individuos[i] != null) {
                num++;
            }
        }
        return num;
    }

    public int getTamPopulacao() {
        return tamPopulacao;
    }

    public Individuo getIndivduo(int pos) {
        return individuos[pos];
    }
}

Individuo.java

package helloWorldAG;

import java.util.Random;

public class Individuo {

    private String genes = "";
    private int aptidao = 0;

    //gera um indivíduo aleatório
    public Individuo(int numGenes) {
        genes = "";
        Random r = new Random();
        
        String caracteres = Algoritimo.getCaracteres();
        
        for (int i = 0; i < numGenes; i++) {
            genes += caracteres.charAt(r.nextInt(caracteres.length()));
        }
        
        geraAptidao();        
    }

    //cria um indivíduo com os genes definidos
    public Individuo(String genes) {    
        this.genes = genes;
        
        Random r = new Random();
        //se for mutar, cria um gene aleatório
        if (r.nextDouble() <= Algoritimo.getTaxaDeMutacao()) {
            String caracteres = Algoritimo.getCaracteres();
            String geneNovo="";
            int posAleatoria = r.nextInt(genes.length());
            for (int i = 0; i < genes.length(); i++) {
                if (i==posAleatoria){
                    geneNovo += caracteres.charAt(r.nextInt(caracteres.length()));
                }else{
                    geneNovo += genes.charAt(i);
                }
                
            }
            this.genes = geneNovo;   
        }
        geraAptidao();
    }

    //gera o valor de aptidão, será calculada pelo número de bits do gene iguais ao da solução
    private void geraAptidao() {
        String solucao = Algoritimo.getSolucao();
        for (int i = 0; i < solucao.length(); i++) {
            if (solucao.charAt(i) == genes.charAt(i)) {
                aptidao++;
            }
        }
    }

    public int getAptidao() {
        return aptidao;
    }

    public String getGenes() {
        return genes;
    }
}

Execução

Exemplo de uma execução com um gene pequeno como solução 'Olá Mundo', só para demostrar :)

Iniciando... Aptidão da solução: 9
Geração 1 | Aptidão: 1 | Melhor: óÁá7ãxIãK
Geração 2 | Aptidão: 1 | Melhor: óÁá7ãxIãK
Geração 3 | Aptidão: 1 | Melhor: óÁá7ãxIãK
Geração 4 | Aptidão: 2 | Melhor: OÁá7VqUj5
Geração 5 | Aptidão: 2 | Melhor: OÁá7VqUj5
Geração 6 | Aptidão: 2 | Melhor: OÁá7VqUj5
Geração 7 | Aptidão: 2 | Melhor: OÁá7VqUj5
Geração 8 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 9 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 10 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 11 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 12 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 13 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 14 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 15 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 16 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 17 | Aptidão: 3 | Melhor: uÔr M2ZDo
Geração 18 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 19 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 20 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 21 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 22 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 23 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 24 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 25 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 26 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 27 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 28 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 29 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 30 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 31 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 32 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 33 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 34 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 35 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 36 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 37 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 38 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 39 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 40 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 41 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 42 | Aptidão: 4 | Melhor: OwáuM2ZÉo
Geração 43 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 44 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 45 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 46 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 47 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 48 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 49 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 50 | Aptidão: 5 | Melhor: uwá M2cdo
Geração 51 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 52 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 53 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 54 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 55 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 56 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 57 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 58 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 59 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 60 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 61 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 62 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 63 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 64 | Aptidão: 6 | Melhor: O5á MuiÉo
Geração 65 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 66 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 67 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 68 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 69 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 70 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 71 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 72 | Aptidão: 7 | Melhor: Olá MujÉo
Geração 73 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 74 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 75 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 76 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 77 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 78 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 79 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 80 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 81 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 82 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 83 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 84 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 85 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 86 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 87 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 88 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 89 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 90 | Aptidão: 8 | Melhor: Olá MunÉo
Geração 91 | Aptidão: 9 | Melhor: Olá Mundo
Encontrado resultado na geração 91 | Olá Mundo (Aptidão: 9)

Conclusão

Este é um exemplo bem básico de um AG, use-o para estudar, modifique as variáveis, os operadores e veja o que acontece, brique com isso, só assim entenderemos como funciona o mundo do Algorítimo Genético

[]'s

Referências

INTRODUÇÃO AOS ALGORITMOS GENÉTICOS (Estéfane G. M. de Lacerda e André Carlos P. L. F. de Carvalho)

Deixe um comentário