TP2 - Busca local
Introdução
No projeto anterior, você implementou diferentes algoritmos de busca para resolver o problema do caminho mais curto em mapas com estrutura de grade. Como parte desse processo, você teve que criar mapas manualmente para comparar o desempenho dos diferentes algoritmos. Neste projeto, você irá implementar um algoritmo genético para gerar mapas automaticamente, em particular, mapas que maximizem o tamanho do caminho entre um dado estado inicial e um final. O objetivo deste projeto é praticar a modelagem e a implementação de algoritmos genéticos, bem como analisar os resultados desse tipo de algoritmo, em particular o impacto dos parâmetros do algoritmo na curva de adaptação das soluções.
Código-base
Executar servidor
Clique aqui para baixar o código-base, que inclui um cliente em JavaScript para configurar os parâmetros do algoritmo, definir estados iniciais e finais, iniciar a busca e salvar os mapas gerados. Além disso, o código também contém um servidor em Python para implementar e executar o algoritmo genético. Para executar o servidor, você precisará instalar as dependências do projeto e iniciar o servidor com os seguintes comandos:
pip install -r requirements.txt # Instalar dependencias
python3 server.py -port 5001 # Executar o servidor
Assim que o servidor iniciar, você pode abrir o seguinte endereço em qualquer navegador: http://localhost:5001.
Tarefas
Antes de iniciar as tarefas, estude as classes Individual
e GeneticAlgorithm
, as quais você irá utilizar como base para implementar o seu algoritmo genético.
1. Representação dos indivíduos
Sua primeira tarefa é definir uma codificação em forma de genótipo para representar um mapa e implementar os métodos gen_genotype
e Individual.get_phenotype
para gerar um indivíduo aleatório e mapear seu genótipo para um fenótipo, respectivamente.
2. Função de adaptação
A segunda tarefa é definir uma função de adaptação que retorne valores de adaptação altos para mapas com altos caminhos entre a origem e o destino. Uma ideia simples é utilizar o A* que você implementou no projeto anterior para encontrar o caminho entre a origem e o destino, e retornar o tamanho do caminho encontrado como valor de adaptação. Note que quanto maior o mapa, mais difícil será encontrar um caminho entre a origem e o destino, portanto é importante decidir como calcular a adaptação de indivíduos infactíveis, de tal forma que mapas que precisem de pouca alteração para se tornarem factíveis tenham adaptação maior do que mapas que precisem de mais modificações. Implemente a sua função de adaptação no método Individual.fitness
.
3. Inicialização da população
Agora que você terminou a implementação da classe Individual
, você pode inicializar a população do algoritmo genético no método GeneticAlgorithm.initialize_population
. Para isso, basta criar um número self.population_size
de indivíduos e adicioná-los à lista self.population
.
3. Seleção
Escolha um método de seleção visto em aula (se escolher outro, adicionar referência) e o implemente no método GeneticAlgorithm.selection
. Esse método deve retornar dois individuos de self.population
com base em seus valores de adaptação fitness_scores
, dando mais chance para aqueles com maior valor de fitness.
4. Cruzamento
Defina um método de cruzamento adequado à sua representação de indivíduos e o implemente no método GeneticAlgorithm.crossover
. Esse método deve retornar dois novos indivíduos filhos como resultado do cruzamento genético de dois indivíduos pais parent1
e parent2
.
5. Mutação
Defina um método de mutação adequado à sua representação de indivíduos e o implemente no método GeneticAlgorithm.mutate
. Esse método deve alterar aleatoriamente os genes de um indivíduo de acordo com a taxa de mutação defiida self.mutation_rate
.
6. Evolução
Implemente o método GeneticAlgoritm.evolve
para executar uma única geração do algoritmo genético. Esse método deve seguir a seguinte estrutura:
- Avaliação da população atual
self.population
com o métodoIndividual.fitness
- Inicializar a nova geração
new_pop
com uma lista vazia - Repetir até que o tamanho de
new_pop
seja igual aself.population_size
:- Selecionar dois pais
p1
ep2
para cruzamento com o métodoGeneticAlgorithm.select
. - Gerar dois filhos
c1
ec2
por meio do cruzamento entrep1
ep2
comGeneticAlgorithm.crossover
- Mutar os filhos
c1
ec2
aleatoriamente com o métodoGeneticAlgorithm.mutate
- Adicionar
c1
ec2
anew_pop
- Selecionar dois pais
- Atualizar a população atual
self.population
com a nova geração
Note que essa é a estrutura mínima que a sua implementação deve conter. Fique à vontade para acrescentar melhorias, como elitismo, por exemplo. A função GeneticAlgoritm.evolve
deve retonar uma tupla (best, path)
, onde best
é o fenótipo (mapa em formato de grade) do melhor indivíduo da população ao final da geração e path
é o caminho mais curto em o estado incial e o final nesse indivíduo.
Ao final dessa etapa, você deveria conseguir executar seu algoritmo genético no cliente Javascript para diferentes tamahos de população e números de gerações. O cliente deveria mostar o melhor individuo ao final de cada geração.
7. Estética dos mapas
As fases anteriores delineiam um algoritmo genético voltado para a maximização do percurso entre os estados de origem e destino. Neste estágio final de implementação, você será encarregado de ampliar a funcionalidade da sua função de adaptação Individual.fitness
para também influenciar a estética dos mapas produzidos. A alteração deve manter a avaliação com base no percurso, ou seja, você irá integrar novos termos à sua função original para orientar as características estéticas do mapa. Para realizar isso, será necessário definir a estética desejada para seus mapas e codificá-la matematicamente de modo que possa ser incorporada à função de adaptação. Algumas ideias simples:
- Simetria vertical, horizontal, diagonal ou completa
- …
8. Relatório
Após concluir a implementação do algoritmo genético, você irá analisar o seu desempenho com relação ao tamanho da população (P), número de gerações (G) e taxa de mutação (T). Execute o seu algoritmo 30 vezes e reporte a curva de adaptação média do melhor indivíduo para cada configuração de parâmetros abaixo:
- Variação de tamanho da população:
- P = 100, G = 100, T = 1
- P = 400, G = 100, T = 1
- P = 800, G = 100, T = 1
- P = 1600, G = 100, T = 1
- Variação do número de gerações:
- P = 400, G = 100, T = 1
- P = 400, G = 250, T = 1
- P = 400, G = 500, T = 1
- P = 400, G = 1000, T = 1
- Variação da taxa de mutação:
- P = 200, G = 250, T = 1
- P = 200, G = 250, T = 5
- P = 200, G = 250, T = 15
- P = 200, G = 250, T = 20
O seu relatório deve conter no máximo 2 páginas no formato da Association for the Advancement of Artificial Intelligence (AAAI) (disponível também no Overleaf). Cada variação de parâmetros gerará uma curva, ou seja, serão 12 gráficos no total. Agrupe essas gráficos em 3 imagens por tipo de parâmetro, onde cada imagem conterá 4 gráficos. Inclua também 3 mapas no seu relatório, uma para cada grupo de experimentos, ilustrando o melhor indivíduo encontrado dentre todas as variações daquele grupo. Discuta as características desse mapa.
Submissão
Para submeter o seu trabalho, submeta o seu arquivo genetic.py
e o seu relatório relatorio.pdf
(em formato pdf) na tarefa TP2 - Busca local no PVANet.