Devido à natureza da pergunta, tenho que incluir muitas informações básicas (porque minha pergunta é: como restringir isso?) Dito isso, ele pode ser resumido (da melhor maneira possível) como:
Quais métodos existem para encontrar ótimos locais em espaços de pesquisa combinatória extremamente grandes?
fundo
Na comunidade de super-jogos assistida por ferramentas, procuramos fornecer entradas especialmente criadas (não geradas em tempo real) para um console ou emulador de videogame, a fim de minimizar alguns custos (geralmente o tempo até a conclusão). A forma como isso é feito atualmente está jogando o jogo quadro-a-quadro e especificar a entrada para cada quadro, muitas vezes refazer partes da corrida muitas vezes (por exemplo, o recentemente publicado corrida para The Legend of Zelda: Ocarina of Time tem um total de 198.590 tentativas).
Fazer com que essas corridas obtenham seu objetivo geralmente se resume a dois fatores principais: planejamento de rota e travessia. O primeiro é muito mais "criativo" que o segundo.
O planejamento de rotas é determinar para que lado o jogador deve navegar em geral para concluir o jogo, e geralmente é a parte mais importante da corrida. Isso é análogo a escolher qual método de classificação usar, por exemplo. A melhor classificação de bolha do mundo simplesmente não superará a classificação rápida em 1 milhão de elementos.
No desejo de perfeição, no entanto, a travessia (como a rota é realizada) também é um fator imenso. Continuando a analogia, é assim que o algoritmo de classificação é implementado. Algumas rotas nem sequer podem ser executadas sem quadros de entrada muito específicos. Esse é o processo mais tedioso de assistência à ferramenta e é o que faz com que a produção de uma execução concluída leve meses ou até anos. Não é um processo difícil (para um humano), porque se resume a tentar diferentes variações da mesma idéia até que uma seja considerada melhor, mas os humanos podem apenas tentar tantas variações em seu tempo de atenção. A aplicação de máquinas a esta tarefa parece apropriada aqui.
Meu objetivo agora é tentar automatizar o processo de travessia em geral para o sistema Nintendo 64 . O espaço de busca para este problema é muito grande demais para atacar com uma abordagem de força bruta. Um segmento n-frame de uma execução N64 possui 2 30n entradas possíveis, o que significa meros 30 quadros de entrada (um segundo a 30FPS) possui 2 900 entradas possíveis; seria impossível testar essas soluções em potencial, sem falar nas de duas horas.
No entanto, não estou interessado em tentar (ou melhor, nem vou tentar) a otimização global total de uma execução completa. Em vez disso, gostaria de, com uma entrada inicial, aproximar o ótimo local para um segmento específico de uma corrida (ou o n ótimo local mais próximo , para uma espécie de otimização semi-global) . Ou seja, dada uma rota e uma travessia inicial dessa rota: procure nos vizinhos dessa travessia para minimizar o custo, mas não degenere tentando todos os casos que possam resolver o problema.
Meu programa deve, portanto, pegar um estado inicial, um fluxo de entrada, uma função de avaliação e gerar o ótimo local minimizando o resultado da avaliação.
Estado atual
Atualmente, tenho toda a estrutura cuidada. Isso inclui a avaliação de um fluxo de entrada através da manipulação do emulador, instalação e desmontagem, configuração etc. E como um espaço reservado, o otimizador é um algoritmo genético muito básico. Ele simplesmente avalia uma população de fluxos de entrada, armazena / substitui o vencedor e gera uma nova população, modificando o fluxo do vencedor. Esse processo continua até que alguns critérios arbitrários sejam atendidos, como tempo ou número de geração.
Observe que a parte mais lenta deste programa será, de longe, a avaliação de um fluxo de entrada . Isso ocorre porque isso envolve emular o jogo por n frames. (Se eu tivesse tempo, escreveria meu próprio emulador que fornecia ganchos para esse tipo de coisa, mas, por enquanto, resta sintetizar mensagens e modificar a memória para um emulador existente de outro processo.) No meu computador principal, que é bastante moderno, a avaliação de 200 quadros leva aproximadamente 14 segundos. Como tal, eu preferiria um algoritmo (dada a opção) que minimize o número de avaliações de funções.
Eu criei um sistema na estrutura que gerencia emuladores simultaneamente. Como tal , posso avaliar vários fluxos de uma só vez com uma escala de desempenho linear, mas, na prática, o número de emuladores em execução pode ser de apenas 8 a 32 (e 32 está realmente pressionando) antes que o desempenho do sistema se deteriore. Isso significa (dada a escolha), um algoritmo que pode processar enquanto uma avaliação está sendo realizada seria altamente benéfico, porque o otimizador pode fazer algum trabalho pesado enquanto aguarda uma avaliação.
Como teste, minha função de avaliação (para o jogo Banjo Kazooie ) era somar, por quadro, a distância do jogador a um ponto do gol. Isso significava que a solução ideal era chegar o mais próximo possível desse ponto o mais rápido possível. Limitando a mutação apenas ao stick analógico, levou um dia para obter uma solução adequada . (Isso foi antes de eu implementar a simultaneidade.)
Depois de adicionar simultaneidade, ativei a mutação do pressionamento do botão A e fiz a mesma função de avaliação em uma área que exigia pulos. Com 24 emuladores em execução, demorou cerca de 1 hora para atingir a meta a partir de um fluxo de entrada inicialmente em branco, mas provavelmente precisaria durar dias para chegar a algo próximo do ideal.
Problema
O problema que enfrento é que não conheço o suficiente sobre o campo de otimização matemática para saber como modelar corretamente meu problema de otimização ! Posso seguir a idéia conceitual de muitos algoritmos, como descrito na Wikipedia, por exemplo, mas não sei como categorizar meu problema ou selecionar o algoritmo de última geração para essa categoria.
Pelo que sei, tenho um problema combinatório com uma vizinhança extremamente grande . Além disso, a função de avaliação é extremamente descontínua, sem gradiente e com muitos planaltos . Além disso, não há muitas restrições, embora eu tenha prazer em adicionar a capacidade de expressá-las, se isso ajudar a resolver o problema; Gostaria de permitir especificar que o botão Iniciar não deve ser usado, por exemplo, mas esse não é o caso geral.
Questão
Então, minha pergunta é: como faço para modelar isso? Que tipo de problema de otimização estou tentando resolver? Qual algoritmo devo usar? Não tenho medo de ler trabalhos de pesquisa, então deixe-me saber o que devo ler!
Intuitivamente, um algoritmo genético não poderia ser o melhor, porque realmente não parece aprender. Por exemplo, se pressionar Iniciar parece sempre piorar a avaliação (porque interrompe o jogo), deve haver algum tipo de designer ou cérebro que aprende: "pressionar Iniciar a qualquer momento é inútil". Mas mesmo esse objetivo não é tão trivial quanto parece, porque às vezes pressionar o começo é o ideal, como na chamada "pausa para trás - longos saltos" em Super Mario 64 ! Aqui, o cérebro precisaria aprender um padrão muito mais complexo: "pressionar Iniciar é inútil, exceto quando o jogador está nesse estado muito específico e continuará com alguma combinação de pressionar os botões ".
Parece que eu deveria (ou a máquina poderia aprender a) representar a entrada de alguma outra maneira mais adequada à modificação. A entrada por quadro parece muito granular, porque o que é realmente necessário são "ações", que podem abranger vários quadros ... mas muitas descobertas são feitas quadro a quadro, por isso não posso descartar totalmente (o a pausa mencionada para trás e salto em distância requer precisão no nível do quadro). Também parece que o fato de que as entradas são processadas em série deve ser algo que possa ser capitalizado, mas não sei como.
Atualmente, estou lendo sobre a pesquisa Tabu (reativa), a pesquisa de bairro em grande escala, a otimização baseada no ensino-aprendizagem e a otimização de colônias de formigas.
Esse problema é simplesmente muito difícil de resolver com algo além de algoritmos genéticos aleatórios? Ou é realmente um problema trivial que foi resolvido há muito tempo? Obrigado pela leitura e obrigado antecipadamente por quaisquer respostas.
Respostas:
A partir das informações fornecidas na sua pergunta, não consigo ver como aplicar métodos de otimização padrão (que eu saiba). Seus objetos não são tão complicados (mais sobre isso mais tarde), mas sua função de destino é desagradável: seus valores são definidos por um sistema externo fora de seu controle, é improvável que tenha propriedades agradáveis e assim por diante. Portanto, acho que usar algoritmos genéticos não é inviável e talvez até uma boa abordagem aqui; eles geralmente funcionam melhor que outros métodos se você não tem idéia da estrutura do seu problema. Há muito a considerar sobre
então permita-me elaborar.
Quais são seus objetos?
Você já respondeu isso: está observando uma sequência de ações, cada uma das quais ocupa um quadro. Eu acho que isso pode ser muito refinado; talvez tente uma sequência de ações, cada uma com uma duração (em número de quadros). Isso permitiria ter mutações como "andar um pouco mais" para ter probabilidades diferentes de "inserir uma pressão de A" de maneira natural. Experimente o que funciona melhor; pode ser necessário revisitar este item depois de pensar nos outros ingredientes.
Qual é a sua função de destino?
Este é realmente crucial. O que você deseja otimizar? Hora de gol? Número de ações diferentes? O número de estrelas coletadas? Uma combinação de vários fatores? Assim que você obtém vários alvos, as coisas ficam peludas - (geralmente) não são mais ótimas!
Você mencionou o tempo para gol. Provavelmente, essa não é uma boa função de destino. Por quê? Como a maioria das seqüências nem sequer alcançará a meta, elas resultarão em alguma constante, criando um cenário de condicionamento físico como este (esboço conceitual em uma dimensão):
[ fonte ]
Então, como você mede a distância? A distância linear pode parecer tentadora, mas tem seus problemas; novamente, sinais errados podem ser enviados. Considere este cenário simples:
[ fonte ]
Cada sequência que começa com um salto para o corredor superior melhora até atingir um ponto logo acima da meta, mas nunca pode realmente chegar à meta! Pior ainda, entre todas as sequências que não atingem a meta, as que sobem são tão boas quanto as que caem, de modo que o GA não pode rejeitar sequências que estão claramente condenadas. Em outras palavras, a distância linear cria ótimas ótimas locais particularmente ruins que podem prender o AG se houver becos sem saída no nível.
Portanto, sugiro que você sobreponha uma grade ao seu nível e conecte pontos vizinhos se o personagem do jogo puder passar de um para o outro. Em seguida, calcule a distância da meta pelo comprimento do caminho mais curto, do ponto mais próximo ao local em que a sequência aterra o caractere até o ponto mais próximo da meta. Isso é fácil de calcular e caminhar até os prazos (ótimos locais) é imediatamente punido¹. Claro que você precisa acessar dados de nível, mas presumo que você os tenha.
Como seu GA funciona?
Agora podemos chegar ao algoritmo genético real. As principais considerações são critérios de população, seleção, reprodução / mutação e parada.
População
Qual é o tamanho da sua população? Se for muito pequeno, pode não fornecer a diversidade necessária para alcançar uma boa solução. Se for muito grande, é mais provável que você carregue lixo inútil, retardando o processo.
Como você inicializa sua população? Você escolhe sequências de ação aleatórias? Em caso afirmativo, qual o comprimento? Você tem um número (pequeno) de soluções razoáveis geradas manualmente para semear, talvez tais que atinjam a meta?
Seleção
O conceito central aqui é a pressão de seleção : quão difícil é sobreviver? Faça-o muito pequeno e você não elimina soluções ruins. Torne-o muito alto e você dificultará as alterações (em particular a movimentação entre as ótimas locais).
Reprodução e Mutação
Depois de selecionar seus sobreviventes de uma rodada, você deve criar a próxima geração a partir deles (os pais sobrevivem e fazem parte da próxima geração?). Existem duas estratégias principais: mutação e recombinação.
A mutação é bastante clara, embora as especificidades possam diferir. Para todas as posições na sequência de um indivíduo, modifique-o com alguma probabilidade. Você pode fazer isso independentemente para cada posição, ou escolher o número de mutações aleatoriamente, ou pode executar diferentes mutações com probabilidades diferentes (como inserir um novo elemento, remover um, alterar um, ...). Mutação é geralmente sobre pequenas mudanças.
A recombinação, que combina aspectos de duas ou mais soluções a uma nova, é mais complicada, mas pode permitir grandes etapas, que deixam uma "montanha de fitness" e se movem diretamente para a encosta de outra (que pode ser mais alta). Uma ideia clássica é o cruzamento ; Não sei se isso faz sentido aqui (parece-me que a troca do prefixo de uma determinada sequência por outra coisa provavelmente desvalorizará o sufixo). Talvez você possa usar o conhecimento sobre o nível e as posições do personagem do jogo em diferentes pontos da sequência para guiá-lo, isto é, criar pontos de cruzamento apenas quando o personagem estiver na mesma posição nas duas seqüências.
Terminação
Como você pode ver, todas essas coisas se entrelaçam para influenciar o desempenho real. Se você administra várias populações em paralelo, pode até pensar em implementar a deriva genética devido à migração e / ou catástrofes. Há pouca teoria para guiar o seu caminho, então você precisa experimentar diferentes configurações e ver onde isso o leva. Felizmente, o que funciona para um nível também funcionará para outros. Feliz mexer!
Nota bene: Veja o BoxCar 2D à luz do exposto acima. Eles fazem algumas coisas muito bem (outras não) e você pode ter uma intuição de como os parâmetros de uma AG podem influenciar seu desempenho.
fonte
Para obter mais detalhes sobre o método de otimização baseada no ensino-aprendizagem (TLBO) e seu código, consulte o seguinte artigo:
Para leitura adicional:
fonte