Estratégias Evolutivas

O que são estratégias evolutivas?

Estratégias evolutivas (EE) são algoritmos de otimização inspirados na evolução biológica. Diferentemente dos algoritmos genéticos, elas focam em parâmetros contínuos e reais. Cada indivíduo é um vetor de números, não uma árvore ou cadeia binária. A principal inovação é a auto-adaptação dos passos de mutação durante a busca. Cada indivíduo carrega seus próprios desvios-padrão, que também evoluem. Assim, o algoritmo aprende qual a melhor intensidade de variação para cada dimensão. Isso torna as EE muito eficientes para problemas numéricos de alta precisão. Elas são populares em robótica, controle de processos e aprendizado de máquina.

Mecanismos centrais de funcionamento

As EE operam com uma população de candidatos, geralmente pequena. Cada geração produz filhos por mutação gaussiana a partir dos pais. A mutação adiciona ruído proporcional ao desvio-padrão de cada indivíduo. Os melhores filhos substituem os piores pais, seguindo uma regra de seleção. Existem duas variantes principais: (μ, λ) e (μ + λ). Na primeira, apenas os λ filhos competem entre si para os próximos μ pais. Na segunda, pais e filhos juntos são avaliados, e os μ melhores sobrevivem. A seleção é determinística e baseada exclusivamente no valor da aptidão. Portanto, as EE são rápidas e requerem poucos parâmetros ajustáveis.

Vantagens e aplicações típicas

Uma grande vantagem é a capacidade de lidar com paisagens ruidosas e não-lineares. Elas também convergem mais rapidamente que algoritmos genéticos em muitos casos. Além disso, a auto-adaptação elimina a necessidade de ajustar a taxa de mutação. Isso as torna ideais para problemas com muitas variáveis interdependentes. Por exemplo, são usadas para calibrar modelos climáticos e financeiros. Também são aplicadas no design de antenas e na sintonia de controladores PID. Sua simplicidade conceitual atrai iniciantes que desejam resultados práticos.

As estratégias evolutivas foram propostas por Rechenberg e Schwefel nos anos 1960. Elas se diferenciam dos algoritmos genéticos pelo uso de codificação real direta. Não há crossover na forma clássica, apenas mutação como operador principal. Contudo, variantes modernas incorporam crossover para melhorar a diversidade. A aptidão é calculada diretamente a partir do vetor de parâmetros do indivíduo. Quanto menor o erro ou maior o lucro, melhor é o indivíduo avaliado. A seleção é elitista, garantindo que a melhor solução nunca seja perdida. Isso confere estabilidade e monotonicidade à melhoria ao longo das gerações. As EE são particularmente robustas para funções com múltiplos mínimos locais. Elas escapam desses mínimos ajustando dinamicamente os passos de mutação. Um passo grande explora regiões distantes; um passo pequeno refina a solução. Essa dualidade é controlada pela evolução dos próprios desvios-padrão. Assim, a estratégia evolutiva é um método adaptativo e autossuficiente. Ela é frequentemente a primeira escolha para otimização contínua.

Um exemplo clássico é a minimização da função de Rastrigin em duas dimensões. Ela tem muitos mínimos locais, mas um mínimo global em (0,0). A EE deve encontrar esse ponto com alta precisão usando mutação auto-adaptativa. A população inicial é espalhada aleatoriamente no intervalo [-5, 5]. Após 100 gerações, a solução converge para próximo de zero. Esse problema ilustra a eficácia da auto-adaptação em paisagens complexas.


Enunciado do exemplo clássico

Utilize uma estratégia evolutiva (μ, λ) com μ=10 e λ=50 para minimizar a função de Rastrigin: f(x,y) = 20 + x² + y² – 10*(cos(2πx) + cos(2πy)), com x,y ∈ [-5,5]. Execute 200 gerações e armazene o melhor valor de aptidão por geração. Ao final, plote a evolução do melhor erro (curva de convergência). Plote também um mapa de contorno da função com o ponto final encontrado. Forneça o código Python completo, sem usar bibliotecas de evolução prontas.

Este código implementa uma EE do zero, sem bibliotecas especializadas. Ele mostra claramente a auto-adaptação dos sigmas a cada geração. O gráfico de convergência evidencia a rápida melhoria nas primeiras iterações. O mapa de contorno revela a complexidade da função com seus múltiplos mínimos. A solução final, próxima de (0,0), confirma a eficácia da abordagem. Para iniciantes, esse exemplo conecta a teoria à prática de forma tangível. As estratégias evolutivas, portanto, são ferramentas robustas e educativas.

Evolução de Programas

O que é evolução de programas?

Evolução de programas é o processo automático de gerar código executável por seleção natural. Ela pertence ao campo da programação genética e da computação evolutiva. Em vez de escrever um algoritmo manualmente, o computador descobre a solução. Programas candidatos são representados como árvores de expressão ou sequências de instruções. Esses programas são avaliados por uma função de aptidão que mede seu desempenho. Os melhores são combinados e modificados para criar filhos ligeiramente diferentes. Esse ciclo iterativo produz soluções cada vez mais adaptadas ao problema. Portanto, a evolução de programas automatiza a descoberta de heurísticas e regras.

Como ocorre a evolução passo a passo?

Primeiramente, uma população inicial de programas aleatórios é gerada. Cada programa é executado sobre um conjunto de casos de teste fornecidos. Sua aptidão é calculada com base no erro ou acerto dessas execuções. Indivíduos com alta aptidão são selecionados para serem pais da próxima geração. Operadores genéticos como crossover trocam subpartes entre dois programas pais. A mutação altera aleatoriamente um nó ou instrução de um programa filho. Novos programas substituem os menos aptos, mantendo o tamanho populacional constante. Esse procedimento é repetido por muitas gerações até que um critério pare. A convergência ocorre quando a aptidão média não melhora significativamente. Todo esse processo é guiado apenas pelos dados e pela métrica definida.

Desafios e cuidados importantes

Um desafio comum é o crescimento excessivo das árvores, chamado de “bloat”. Ele pode ser controlado com penalidades no tamanho do programa durante a avaliação. Outro cuidado é evitar o overfitting aos dados de treinamento usados. Para isso, utiliza-se validação cruzada ou conjuntos de teste separados. Além disso, a diversidade populacional deve ser mantida para evitar convergência prematura. Operadores de migração entre subpopulações ajudam a preservar a variabilidade genética. A escolha dos operadores e terminais também afeta drasticamente o sucesso. Portanto, a evolução de programas exige ajustes finos e experimentação constante.

A evolução de programas é uma extensão natural da programação genética clássica. Enquanto aquela otimiza parâmetros, esta otimiza a própria estrutura do código. Isso permite descobrir soluções inesperadas que um programador humano não preveria. Por exemplo, ela pode encontrar aproximações polinomiais para funções complexas. Também pode gerar regras de decisão para classificação de dados médicos. Sua aplicação em robótica evolutiva produz comportamentos emergentes surpreendentes. Contudo, o custo computacional é elevado devido à avaliação de muitos programas. Paralelização e uso de GPUs são frequentemente empregados para acelerar o processo. Apesar disso, a evolução de programas já é usada em indústrias como finanças e jogos. Ela cria agentes inteligentes que aprendem estratégias sem intervenção humana. A interpretabilidade dos programas gerados é um grande diferencial. Diferentemente de redes neurais, podemos ler e entender o código evoluído. Isso facilita a validação e a certificação de sistemas críticos. Portanto, a evolução de programas combina poder de busca com transparência lógica.

Um exemplo clássico é evoluir um programa que resolva a equação de segundo grau. Dados de entrada (coeficientes a, b, c) e saída (raiz real) são fornecidos. O objetivo é descobrir uma fórmula que calcule a raiz sem usar a fórmula pronta. A aptidão mede o erro absoluto médio entre a saída prevista e a real. Operadores permitidos: +, -, *, / e raiz quadrada protetora. Terminais: a, b, c e constantes aleatórias. Após algumas gerações, a PG aproxima a solução tradicional com boa precisão. Esse problema ilustra a capacidade de reinventar algoritmos conhecidos.


Enunciado do exemplo clássico

Evolua um programa que estime a população de bactérias ao longo do tempo. A taxa de crescimento segue o modelo logístico: P(t) = K / (1 + exp(-r*(t-t0))). Gere 50 pontos sintéticos com ruído para t de 0 a 10, com K=100, r=0.8, t0=5. Utilize programação genética com população de 100 indivíduos e 40 gerações. A aptidão será o erro quadrático médio entre a previsão e os dados ruidosos. Ao final, plote os dados reais, a curva ruidosa e a previsão do programa evoluído. Plote também a evolução do melhor erro ao longo das gerações. Use a biblioteca gplearn e funções matemáticas básicas.

Este código é executado no Colab sem modificações adicionais. Ele demonstra como a evolução de programas descobre uma fórmula para os dados. O primeiro gráfico compara a previsão com o modelo real e os pontos medidos. O segundo gráfico mostra a redução do erro ao longo das gerações. Mesmo sem conhecer a equação logística, o algoritmo encontra uma boa aproximação. A função de aptidão (erro) é minimizada internamente pelo regressor simbólico. Assim, a evolução de programas se mostra uma ferramenta poderosa e acessível. Ela permite que iniciantes compreendam na prática o conceito de evolução artificial.