Algoritmos Genéticos com Raspberry Pi – Parte 9 – A Função de Fitness

Como citar esse artigo: VERTULO, Rodrigo Cesar. Algoritmos Genéticos com Raspberry Pi – Parte 9 – A Função de Fitness. Disponível em: <http://labdeeletronica.com.br/algoritmos-geneticos-com-raspberry-pi-parte-9-a-funcao-de-fitness/>. Acesso em: 20/02/2019.


Chegou o momento de codificarmos uma função de Fitness para nosso Algoritmo Genético que resolverá o problema de encontrar o valor da variável “x” que satisfaça a equação Xˆ3 = 15 proposto no artigo anterior desta série. Como já foi dito, partirei do princípio de que você possui conhecimentos básicos de Programação Orientada a Objetos, de modo que termos como Classe, Herança, Polimorfismo, Sobrecarga e Sobreposição de métodos não devem ser estranhos a você.

Apesar de nossa classe GARV ainda não estar pronta, para que seja possível compreender como a função de Fitness deverá ser implementada e utilizada, precisaremos fazer um exercício mental e imaginarmos que ela se encontra pronta. Não se preocupe, pois longo desta série de artigos toda a classe GARV será desenvolvida, mas nesse momento precisaremos imaginar que isso já foi feito.

Considerando o que foi dito anteriormente, sempre que necessitarmos criar um Algoritmo Genético para resolver algum problema será preciso criarmos uma nova classe “filha” de GARV que herdará todos os seus métodos e atributos, de modo que essa nova classe se comportará como se fosse GARV mas poderá possuir métodos e atributos especializados (conceito de Herança).

Você deve se lembrar que em artigos anteriores criamos o método “funcaoFitness” na classe GARV sem qualquer código implementado para ele. A implementação deste método deverá ser feita na classe “filha” de GARV, sobrepondo-o com o código necessário para que o problema que se deseja resolver possa ser solucionado. Esse procedimento é feito da forma como é mostrada a seguir.





import GARV as garv
 
 
class Exemplo_GARV(garv.GARV):
    def __init__(self):
        super().__init__(tamanhoPopulacao = 100,
                         qtdValores = 2,
                         maiorValor = 10):
 
 
    def funcaoFitness(self, cromossomo):
        .
        .
        .
        .

A primeira coisa a ser notada é que na primeira linha do código foi feita a importação do módulo onde esta codificada a classe GARV. A partir dessa importação ela poderá ser utilizada como superclasse da nova classe “Exemplo_GARV” que está sendo criada.

Observe que “Exemplo_GARV” é “filha” de GARV e que no construtor da nova classe é feita uma chamada para o construtor de sua superclasse passando como parâmetros o tamanho da população de Cromossomos que desejamos, a quantidade de números que são armazenados em cada Cromossomo e o maior valor que esses números podem assumir no momento da criação da população inicial (definido aqui de forma empírica).

Creio que nesse momento você deva estar se perguntando de onde surgiu isso tudo e você têm toda a razão em ter essa dúvida, pois ainda não implementamos essas coisas. Contudo, não se esqueça de que estamos imaginando que a classe GARV já está pronta. Sendo assim, apenas considere essas informações como verdadeiras por enquanto.

O ponto mais importante é você observar que na classe “Exemplo_GARV” foi criado um método chamado “funcaoFitness” que possui exatamente a mesma assinatura desse mesmo método existente na classe GARV que não possui qualquer tipo de implementação. Quando fazemos isso somos capazes de sobrepor o método da superclasse com um novo código definido na classe “filha”, e é exatamente isso o que está sendo representado nesse exemplo pelos “pontinhos” dentro do método mostrado na classe “Exemplo_GARV”. Quando substituirmos esses “pontinhos” por código real, toda vez que método “avaliaPopulacao” da classe GARV, que criamos em artigos anteriores, for chamado a “funcaoFitness” da subclasse de GARV (“Exemplo_GARV” nesse caso) será executada com o código que estiver implementado para ela. A parte mais interessante disso é que não precisaremos alterar nem uma linha de código na classe GARV, de modo que ela é totalmente reutilizável para uma infinidade de problemas distintos, bastante sobrepor o método “funcaoFitness” em suas classes “filhas” de acordo com o problema a ser resolvido.

Agora que já sabemos como implementar o código da função de Fitness, chegou o momento de implementá-lo para resolvermos o problema de encontrarmos o valor da variável “x” que satisfaça a equação xˆ3 = 15.

Não podemos nos esquecer de que estamos codificando a parte relacionada à Seleção dos Mais Aptos do fluxograma de nosso Algoritmo Genético e que para sermos capazes de selecionarmos os melhores Cromossomos de nossa população é preciso que tenhamos algum artifício que seja capaz de nos indicar quando um Cromossomo é melhor que outro. Isso será feito atribuindo uma nota para cada Cromossomo da população de acordo com o desempenho dele na resolução de nosso problema. Tendo dito isso, a criação da função de Fitness deve respeitar a seguinte lógica:

 

Para cada Cromossomo da população faça:
        Obter os valores armazenados no Cromossomo
        Aplicar os valores na resolução do problema
        Atribuir uma nota para o Cromossomo de acordo com resultado da resolução
        Substituir o Cromossomo atual por ele mesmo mas com sua nota calculada

 

Nunca se esqueça de que para cada tipo de problema a ser resolvido o código da “funcaoFitness” será diferente, porém, a lógica descrita anteriormente será sempre a mesma. Para o caso do problema de exemplo (x^3 = 15), a função Fitness poderia ser codificada da seguinte maneira:

import GARV as garv
 
 
class Exemplo_GARV(garv.GARV):
    def __init__(self):
        super().__init__(tamanhoPopulacao = 100,
                         qtdValores = 2,
                         maiorValor = 10):
 
 
    def funcaoFitness(self, cromossomo):
        x  = self.converteBIN2INT(cromossomo, 0)
        xf = self.converteBIN2INT(cromossomo, 1)
 
        self.valor = x + (xf / 10)
 
        f = pow(self.valor, 3)
 
        if f > 15:
            nota = 15/f
        elif f < 15:
            nota = f/15
        else:
            nota = 1
 
        return [cromossomo, nota]

Sabemos que cada Cromossomo de nossa população possui 2 valores armazenados, pois é isso o que foi definido no parâmetro “qtdValores” do construtor da classe. Vimos também em artigos anteriores que o parâmetro “cromossomo” da função de Fitness tem atribuído a ele um Cromossomo da população que foi definido pelo método “avaliaPopulacao” da classe GARV. Para esse problema consideraremos que o primeiro valor armazenado no Cromossomo será a parte inteira de nossa solução e o segundo a parte fracionária. Ambos os valores citados são atribuídos às variáveis “x” e “xf” respectivamente.

Em seguida é criado um atributo da classe chamado “valor” (self.valor) para o qual é atribuído o resultado de um cálculo simples cujo objetivo é unir a parte inteira “x” e a parte fracionária “xf” referente ao valor armazenado no Cromossomo.

O valor atribuído à “self.valor” é utilizado em seguida para a realização do cálculo “f = pow(self.valor, 3)” que é a versão em Python para a expressão “f = xˆ3”. Nesse caso, o resultado do cálculo é armazenado em uma variável chamada “f” que é utilizada em seguida para a realização de três verificações. Quando o valor armazenado em “f” for maior que 15 (que é o resultado esperado de nossa expressão) é atribuída a uma variável chamada “nota” o resultado da divisão de 15 por “f”, cujo valor será um número menor que 1. Por outro lado, se o resultado de “f” for menor que 15, a variável “nota” receberá o resultado da divisão de “f” por 15, cujo valor também será um número menor que 1. A única situação em que “nota” receberá o valor 1 é quando “f” for igual a 15. O objetivo dessas verificações é fazer com que “nota” receba um valor mais próximo de 1 quanto mais próximo “f” for de 15 e, com isso, fazer com que Cromossomos que armazenam números que resultam em um valor de “f” próximo a 15 recebam notas maiores, ou seja, mais próximas a 1.

A última linha do método é de extrema importância, pois é no retorno da função que unimos a “nota” calculada ao Cromossomo processado, gerando uma nova população de Cromossomos com aptidões calculadas representadas por suas notas de forma gradativa a cada chamada da “funcaoFitness”.

Você pode estar achando as coisas muito confusas nesse momento, mas não se preocupe, tudo começará a ficar mais claro nos próximos artigos. De tudo o que eu descrevi neste artigo eu preciso que você “guarde” duas coisas de forma bastante clara em sua memória:

  1. Sempre que formos criar um Algoritmo Genético para resolver algum problema precisaremos criar uma nova classe “filha” de GARV.
  2. Na nova classe que criaremos e que será “filha” de GARV implementaremos a lógica da função de Fitness para o problema a ser resolvido.

Se você compreender os dois pontos acima eu já estarei satisfeito, pois qualquer outra parte que tenha ficado obscura no momento começará a fazer sentido mais para frente.

Depois que a função de Fitness é aplicada a todos os Cromossomos da população a próxima etapa da Seleção dos Mais Aptos poderá ser executada, que é a realização de um sorteio entre os indivíduos da população para que sejam escolhidos aqueles que participarão da fase de Reprodução. Veremos no próximo artigo como esse sorteio poderá ser feito.

Comentários