• Nenhum resultado encontrado

Computação Evolucionária

4.5 Programação Genética

No início dos anos 90, alguns investigadores, entre os quais John Koza [Koza, 1992], introduziram a programação genética, cujo objectivo é a evolução de programas que resolvam um determinado problema. Com este modelo, em vez de se utilizar um sistema evolucionário que descubra uma solução para o problema, utiliza-se um

sistema evolucionário que procure um programa que permita resolver o problema. Desta forma, a pesquisa deixa de ser feita no espaço de candidatos a soluções para o problema, e passa a ser realizada no espaço de possíveis programas.

4.5.1 Representação e População Inicial

A estrutura em árvore tem sido, até hoje, a forma mais comum de representar os programas, embora sejam também utilizadas outras representações tais como grafos ([Poli, 1997], [Pereira, 1999]) ou estruturas lineares ([Huelsbergen, 1996], [Keller, 1996]). As funções e os terminais são as primitivas com as quais são construídos os programas: as funções são utilizadas para representar nós internos e os terminais são utilizados como nós folha. Quando se pretende utilizar programação genética para resolver um determinado problema deve-se, portanto, definir um conjunto de funções e um conjunto de terminais adequado a esse problema. O espaço de pesquisa será o hiperespaço de programas válidos, utilizando as funções e os terminais escolhidos, representados sob a forma de uma árvore.

Tal como nos outros modelos já descritos, o primeiro passo do algoritmo consiste na criação da população inicial. Uma vez que as árvores têm tamanho variável, é importante limitar de alguma forma o seu tamanho. Isso pode ser conseguido definindo um valor máximo para o número de nós e/ou para a profundidade das árvores. São três os métodos mais conhecidos para gerar os indivíduos da população inicial:

1. Criação completa: para cada árvore é definido um valor para a sua profundidade. A árvore é depois criada de modo que a distância do nó raiz a qualquer nó terminal seja igual a esse valor.

2. Criação por crescimento: O nó raiz começa por ser escolhido aleatoriamente da reunião do conjunto de funções com o conjunto de terminais. Este procedimento é depois executado recursivamente de modo a gerar sub-árvores para todos os nós da árvore que necessitem delas. Quando a profundidade máxima é atingida, o nó deve ser escolhido apenas a partir do conjunto de terminais.

método de criação completa e os outros 50% com o método de criação por crescimento. Este método é tipicamente o mais utilizado por ser aquele que produz uma maior variedade de formas e tamanhos para as árvores.

Para qualquer um destes métodos é possível especificar uma “rampa” de valores para a profundidade máxima da árvore em vez de apenas um. Por exemplo, se a “rampa” for 2-5, então 25% dos indivíduos são criados com uma profundidade máxima de 2, 25% com uma profundidade máxima de 3, e assim por diante.

4.5.2 Avaliação e Selecção

Uma vez que os indivíduos são programas, é necessário executá-los de modo a que se possa avaliar o seu desempenho. Em determinados problemas, os indivíduos são executados num conjunto de casos de teste, sendo-lhes atribuído um valor pelo resultado conseguido com cada um desses casos. Os valores conseguidos são depois combinados, através da função de avaliação, de modo a produzir um valor final que reflicta o desempenho do indivíduo. Por exemplo, num problema de regressão simbólica, cada indivíduo é executado para um conjunto de valores para os quais se conhece o resultado correcto. O desempenho do indivíduo poderá ser medido, por exemplo, pela soma dos módulos das distâncias entre o resultado correcto e o resultado obtido para cada um dos casos. Eventualmente, poderá ser útil ter em conta outras características dos programas que não o resultado da sua execução, como sejam o tamanho ou o tempo de execução. Na secção 4.6, descreveremos um trabalho onde o número de nós influencia o valor da função de avaliação.

Tal como nos algoritmos genéticos, em programação genética os métodos de selecção mais utilizados começaram por ser os métodos de selecção proporcional ao desempenho. No entanto, nos últimos anos começou-se, tal como nos algoritmos genéticos, a utilizar com grande frequência o método de selecção por torneio por este ser computacionalmente menos dispendioso.

4.5.3 Operadores Genéticos

Já se disse em cima que os operadores genéticos dependem fortemente do modo como se representam os indivíduos. Uma vez que em programação genética existem vários tipos de representação, iremos descrever apenas os operadores mais utilizados

com a representação em árvore que, como já foi referido, é o tipo de representação mais comum e aquele que é utilizado neste trabalho.

O operador genético ao qual se atribui maior importância é o operador de recombinação, sendo também utilizados, embora com menor probabilidade de ocorrência, os operadores de reprodução e de mutação24. Ao contrário do que

acontece, por exemplo, nos algoritmos genéticos, os operadores não são aplicados consecutivamente, ou seja, cada indivíduo é sujeito a apenas um operador. A probabilidade de cada operador ser aplicado é atribuída de modo a que a soma das probabilidades de todos operadores seja igual a 1.

O operador de recombinação consiste em seleccionar aleatoriamente uma sub-árvore em cada um dos indivíduos progenitores trocando, de seguida, essas sub-árvores entre os dois indivíduos. O operador de mutação consiste em seleccionar uma sub-árvore do indivíduo e substituí-la por outra criada aleatoriamente. O operador de reprodução, o mais simples de todos, consiste, simplesmente, em copiar o indivíduo para a próxima população. Com a aplicação deste operador procura-se garantir que alguns dos melhores indivíduos da população actual passem intactos para a próxima população.

4.5.4 Funções Definidas Automaticamente

Tal como em qualquer um dos modelos já descritos, existem em programação genética várias variantes do algoritmo base, como por exemplo as que envolvem a utilização de nós fortemente tipados [Montana, 1994] ou a utilização de estruturas de dados [Langdon, 1995]. A variante que julgamos mais relevante para o nosso trabalho é claramente inspirada na forma como as pessoas programam e consiste na utilização de subrotinas denominadas por Funções Definidas Automaticamente (FDA) [Koza, 1993]. Quando se utilizam FDAs, cada programa é constituído por uma função principal e por um conjunto de subrotinas, também elas sujeitas ao processo de evolução. Cada FDA pode ter o seu próprio conjunto de funções e terminais. Deve-se por isso, restringir a aplicação do operador de recombinação de modo a que este ocorra apenas entre subrotinas que usem o mesmo conjunto de funções e terminais.

Podem também existir referências hierárquicas entre subrotinas, isto é, uma subrotina pode pertencer ao conjunto de funções de outra subrotina. É necessário, no entanto, ter cuidado de modo a evitar que ocorra recursividade.

Koza mostrou que a utilização de FDAs permite melhorar a exploração das regularidades que eventualmente possam existir no espaço de pesquisa, o que pode levar a uma diminuição do número de indivíduos que é necessário processar até se encontrar uma solução, bem como à melhoria da qualidade dessa solução. As FDAs são largamente analisadas em [Koza, 1994].

4.6 Programação Genética Aplicada ao Cálculo da Complexidade