You are here

Modificação para busca e entrega de leaflets

Segunda parte

 


 

Desenvolvimento de agente Clarion para controlar criatura WorldServer3D e completar os seus leaflets.

 
 
Para esse desenvolvimento foi utilizado como base o exemplo "ClarionDEMO" fornecido.
 
 

Estratégia inicial de desenvolvimento - uso de "FixedRules"

 
A primeira estratégia de desenvolvimento considerada foi uma extensão da abordagem com regras fixas, adotada no "ClarionDEMO"; considerando os pontos para desenvolvimento já identificados (listados no relatório da primeira parte desta aula), essa abordagem consistia em:
 
  1. Foram consideradas 2 METAS principais para a criatura - FULFILL_LEAFLET e EAT_FOOD - para definir, a partir de um nível mínimo de combustível (FUEL < 300) se a criatura deveria ir buscar alimento ou se poderia continuar buscando completar os leaflets.

  2. Cada uma dessas metas seria definida a partir de 2 DRIVES - "FoodDrive" e "RecognitionAchievementDrive" - ativados a partir justamente do nível de combustível através dos seguintes elementos criados na "SensoryInformation" de entrada para cada ciclo cognitivo:
 
    si[typeof(FoodDrive), FoodDrive.MetaInfoReservations.STIMULUS] =
                                        (MINIMUM_FUEL / currentCreature.Fuel) < 1 ? 0 : 1;
 
    si[typeof(RecognitionAchievementDrive), RecognitionAchievementDrive.MetaInfoReservations.STIMULUS] =
                                        ((currentCreature.Fuel - MINIMUM_FUEL) / MINIMUM_FUEL) > 0 ? 1 : 0;
 
 
O uso dos drivers foi inspirado nos exemplos fornecidos pelo Clarion.
 
  1. Para cada jóia observada pela criatura (através do resultado do comando "getcreaturestate") "ExternalActionChunk"(s) eram criados para corresponder aos comandos de GO_POSITION ("vá até jóia") e COLLECT_JEWEL ("colete a jóia"); cada um desses CHUNKS armazenaria um conjunto de elementos (pares dimensão/valor) descrevendo a jóia:
  • Posição X,Y
  • Cor
  • Comando desejado - GO_POSITION ou COLLECT_JEWEL

 

  1. Cada "ExternalActionChunk" criado seria associado à uma "FixedRule", armazenada no ACS. Cada uma dessas regras seria customizada através dos seguintes delegates:
  • SupportCalculator: para definir a ativação da regra, basicamente através da distância da regra até a criatura; a primeira versão desse delegate foi a seguinte:

 

 
Como é possível verificar, os graus de ativação eram determinados entre 0.0 até 1.0.
 
 
  • EligibilityChecker: para controlar o disparo de regras para coletar jóias ou buscar alimentos, foi considerado o uso de delegates de eligibilidade para cada regra; dessa maneira, as regras de coletar jóias só seriam válidas se a criatura estivesse com a meta de ir buscar jóias - FULFILL_LEAFLET. A primeira versão desse delegate para as regras de jóias está abaixo:
 
 
 
  1. Deveriam haver regras, também "FixedRules", para definir a situação de "leaflet completo", onde a criatura poderia ir até o "delivery-spot" entregá-lo; uma vez nesse ponto, uma regra de "entrega de leaflet completo" deveria disparar. 
 
 

Problemas com a estratégia inicial - indefinição nos movimentos

 
O primeiro problema enfretado com essa estratégia inicial foi o fato de que, por default, as "FixedRules" tem ativação INTEIRA, ou seja, funcionamento apenas completamente ativadas (valor 1.0); isso pode ser contornado com o uso de ATIVAÇÃO PARCIAL, com a seguinte configuração nas regras:
 
ruleGoJewel.Parameters.PARTIAL_MATCH_ON = true;
ruleGoJewel.Parameters.DEFAULT_PARTIAL_MATCH_ON_THRESHOLD = 0.1;
ruleGoJewel.Parameters.PARTIAL_MATCH_THRESHOLD = 0.1;
ruleGoJewel.Parameters.POSITIVE_MATCH_THRESHOLD = 0.1;
 
 
Com as ativações parciais foi possível determinar graus de ativação diferentes para cada regra a partir do valor definido pelo delegate "SupportCalculator".
 
Entretanto, o funcionamento da criatura ficou pouco eficiente, com ela tendo dificuldades em definir qual curso seguir; o fato é que os graus de ativação diferentes de cada regra - essencialmente definidos por conta da distância dos elementos para a criatura - parecem definir a frequência com que o ACS irá escolher cada uma delas: as regras com maior ativação eram de fato PROPORCIONALMENTE escolhidas com maior frequência do que as demais; entretando, as outras regras TAMBÉM eram escolhidas. Isso fazia com que a criatura chegasse até à jóia mais próxima, mas com movimentos muito errantes, "teimando" em ir para direções opostas, mesmo que com menor frequência.
 
Alguns experimentos para tentar ampliar a diferença entre as ativações das regras apenas amenizaram o comportamento, sem corrigí-lo completamente.
 
Não foi encontrada uma forma alternativa de DIRECIONAR a escolha do Clarion para as regras fixas desejadas - que a cada movimento correspondia à regra que levava a criatura até a jóia mais próxima. Foi tentado utilizar o fornecimento de feedback, mas ao fornecê-lo para o Clarion esse apresentava uma exceção de execução, aparentemente ao tentar executar ações de aprendizado sem ter os elementos necessários para tanto:
 
 
 
Exceção lançada pelo Clarion ao se passar feedback sobre uma escolha a partir de uma "FixedRule".
 
 
 
Por conta desse resultado uma nova abordagem para resolver o problema foi definida.
 
 
 

Nova estatégia - uso de "SimplifiedQBPNetwork" para comandos de movimento

 
Para tentar utilizar o mecanimo de feedback para selecionar a melhor escolha de direção a seguir, decidiu-se utilizar uma rede "SimplifiedQBPNetwork" para definir qual ação de movimento a ser adotada pela criatura.
 
Assim, a abordagem inicial foi modificada para que todos os "ExternalActionChunk"(s) correspondentes à ações de movimento - GO_POSITION para jóias ou alimentos - compusessem a SAÍDA da rede.
 
Dessa forma, as ações de movimento definidas pelo ACS seriam avaliadas a partir da sua adequação e o resultado dessa avaliação fornecido através de um feedback.
 
A cada jóia/alimento coletado/ingerido todos os elementos Clarion correspondentes - "ExternalActionChunk"(s) e pares dimensão/valor - deverão ser REMOVIDOS.
 
 

Entradas da rede neural

 
Como entradas para a rede neural foram fornecidos os seguintes elementos, codificados na função "SetupACS()":
 
 
 
  1. As METAS existentes de EAT_FOOD ou FULFILL_LEAFLET;
  2. Elemento indicativo de "obstáculo à frente";
  3. Elemento indicativo de "leaflet completo";
  4. Posição (x, y) da criatura
  5. Elementos indicativos das jóias FALTANTES considerando as 6 cores existentes e os leaflets da criatura
 
A ideia é que cada um desses elementos de entrada seja parte do "SensoryInput" do agente, permitindo que façam parte das decisões tomadas a partir da rede neural. Na função "MakePerceptionFromSensorialInput()" a ativação de todos esses elementos é determinada:
 
 
 
A ativação de "InputWallAhead" continuou a ser determinada da mesma maneira que no exemplo fornecido, a partir da mínima distância a algum objeto  BRICK.
 
A ativação da posição da criatura é definida a partir da fração <posição X criatura> / <tamanho X tabuleiro> e <posição Y criatura> / <tamanho Y tabuleiro>.
 
Já a indicação de "leaflet completo", bem como as ativações para cada cor de jóia são definidos pela função "CheckCompleteLeaflet()", a qual atualiza os contadores de jóias necessárias para cada um dos leaflets recebidos - de forma dinâmica para comportar a alteração externa dos leaflets - e também checa esses valores contra as jóias já coletadas - lista "collectedJewels" - determinando se existe algum leaflet já completo:
 
 
 
 
 
 
 
 
A ideia básica é fornecer uma ATIVAÇÃO MAIOR para as cores que corresponderem a jóias faltantes em leaflets quase completos.
 
 
 

Saídas da rede neural

 
Como mencionado, as saídas da rede neural utilizada são principais comandos de MOVIMENTO da criatura, excetuando-se o movimento de desviar de obstáculos (veja explicação na sequência). Esses comandos de movimento são os seguintes:
 
 
ROTAÇÃO SENTIDO HORÁRIO
 
Para ser utilizado sempre que a criatura não souber para onde ir; essa situação ocorrerá quando não houver "ExternalActionChunk"(s) correspondentes a movimentos - esse fato ocorre ou porque de fato NÃO EXISTEM elementos no tabuleiro, ou porque a criatura ainda NÃO OS VIU. A sua criação ocorre no próprio construtor da classe "ClarionAgent()":
 
 
OutputRotateClockwise = World.NewExternalActionChunk (ClarionAgentActionType.ROTATE_CLOCKWISE.ToString());
OutputRotateClockwise.Add (World.NewDimensionValuePair ("type", ClarionAgentActionType.ROTATE_CLOCKWISE.ToString()));
 
 
IR ATÉ DELIVERY SPOT
 
Para que a criatura realize a entrega de um leaflet completo, é preciso que ela vá até o delivery spot existente; para tanto o projeto "WorldServerLibrary" foi modificado para enviar o comando "getsimulpars" - onde o delivery spot é fornecido - e também o projeto "ClarionDEMO" foi alterado para passar, a todo momento o delivery spot juntamente com os elementos observados pela criatura.
 
A criação do "ExternalActionChunk" correspondente à ação de ir até o delivery spot ocorre apenas uma vez dentro da função "MakePerceptionFromSensorialInput()":
 
 
 
IR ATÉ UMA JÓIA
 
O "ExternalActionChunk" que determina a ida até uma jóia é criado toda vez que uma NOVA jóia é visualizada pela criatura; para esse controle é mantida uma lista com todas as jóias já visualizadas e ainda não coletadas - "jewels"; a cada nova informação de percepção, é verificado se as jóias visualizadas já haviam sido vistas outras vezes; caso contrário (ou no caso de mudança de posição dessa jóia, como explicado aqui) as regras correspondentes a ela são criadas pela função "CreateRulesForJewel()":
 
 
 
Um detalhe importante é o fato de que a rede neural precisa ser desativada antes de poder ser modificada, com a inclusão de uma nova saída. Essa desativação é feita com o comando "Retract()".
 
Essa função também faz a criação da "FixedRule" correspondente à ação de coletar a jóia, cuja ativação é simplesmente determinada pela distância da criatura para ela, calculada a partir do delegate "FixedRuleDelegateToCollectOrEat()":
 
 
 
IR ATÉ UM ALIMENTO
 
De forma similar à criação das regras referentes às jóias, a cada novo alimento visualizado - todos os alimentos encontrados e ainda não ingeridos são armazenados na lista "foods" - as regras correspondentes a ele são criadas pela função "CreateRulesForFood()":
 
 
Novamente, para a regra de ingestão do alimento, é utilizado o mesmo delegate "FixedRuleDelegateToCollectOrEat()" para definir sua ativação.
 
 
 

Fornecimento de feedback para as ações

 
Toda o fornecimento de feedback para as ações determinadas pelo agente estão concentradas na função "RunThread()".
 
 

GO_POSITION

 
O tratamento mais longo corresponde ao das ações de "GO_POSITION", uma vez que elas englobavam as ações de "ir até uma jóia", "ir até um alimento" e "ir até o delivery spot", sendo que a avaliação de cada uma delas dependia da sua adequação à situação atual.
 
 
Ir até o delivery spot
 
Se havia algum leaflet completo, o feedback para essa ação era MÁXIMO; caso contrário, o feedback era MÍNIMO.
 
 
Ir até uma jóia
 
O cálculo do feedback para uma ação de ir até uma jóia tentou levar em consideração os seguintes aspectos:
 
  1. Meta atual deve ser FULFILL_LEAFLET
  2. Relevância da cor dessa jóia escolhida para completar um leaflet - correspondente ao valor de ativação calculado para o "SensoryInformation".
  3. Distância da criatura até essa jóia.
 
Para evitar um efeito muito grande,  o primeiro fator foi considerado com uma atenução de 0.5 no cálculo do valor do feedback, como demonstrado abaixo:
 
 
 
 
Um ponto adicional considerado para a determinação do feedback é se o valor atual, calculado considerando os pontos acima, é melhor ou pior que o anteriormente considerado para uma ação de movimento.
 
Caso todas as condições sejam atingidas, o feedback é o valor calculado segundo as regras acima; caso contrário - correspondendo a uma má decisão - o valor do feedback é o MÍNIMO.
 
 
Ir até um alimento
 
Para calcular o feedback de uma decisão de ir até um  alimento os seguintes fatores são levados em consideração:
 
  1. A META atual ser EAT_FOOD;
  2. O valor do feedback é definido em função da distância do alimento até a criatura;
  3. O valor do feedback não é pior que o último feedback de movimento calculado.
 
 

ROTATE_CLOCKWISE

 
A outra ação determinada pela rede neural é a de rotação no sentido horário; sua adequação ficou apenas dependente do último feedback fornecido para uma ação de movimento ter sido o mínimo - 0.0. Isso porque a cada ação de coleta de jóia (COLLECT_JEWEL) ou ingestão de alimento (EAT_FOOD) o feedback de movimento é zerado de forma a retirar seu efeito futuro, uma vez que o comando de IR até um objeto foi completado.
 
A ideia é que se não houver outro objeto relevante para a criatura ir, ela irá rotacionar para PROCURAR objetos.
 
Dessa forma a ação de rotacionar ficou exclusiva para a BUSCA de objetos, deixando de ter efeito para desviar de objetos - para esse objetivo foi criada uma ação própria.
 
 
 

Otimização do movimento da criatura

 
Para otimizar o movimento da criatura, minimizando os desvios durante o processo de aprendizado, toda vez que o movimento determinado não era o adequado - e o feedback a ser fornecido é ruim - o movimento determinado NÃO é passado para a criatura e ela permanece no movimento anterior. Com isso a grande maioria das indecisões na movimentação são evitadas.
 
 
 

Entrega de leaflet completo

 
Uma vez que o comando para levar a criatura até o delivery-spot é definido pela rede neural, ficou faltando o comando para realizar de fato a entrega dos leaflets completos; esse comando foi implementado através da uma "FixedRule" criada na função "SetupACS()":
 
      // Create deliver leaflet rule
 
      SupportCalculator deliverLeafletSupportCalculator = fixedRuleDelegateToDeliverLeaflet;
      FixedRule ruleDeliverLeaflet = AgentInitializer.InitializeActionRule (CurrentAgent,
                                                                            FixedRule.Factory,
                                                                            outputDeliverLeaflet,
                                                                            deliverLeafletSupportCalculator);
 
      // Commit this rule to Agent (in the ACS)
      CurrentAgent.Commit (ruleDeliverLeaflet);
 
 
A ativação dessa regra, dada pelo delegate "fixedRuleDelegateToDeliverLeaflet()" depende da criatura estar próxima do delivery-spot:
 
 
 
 

Dificuldades enfrentadas

 

Remoção dos elementos correspondentes aos objetos coletados/ingeridos

 
Dentro da estratégia escolhida para resolução do problema, é necessária a remoção dos elementos - "ExternaActionChunks"(s) e "FixedRule"(s) - correspondentes aos objetos coletados ou ingeridos. Essa operação é executada pelo método "removeCollectedOrEaten()".
 
A primeira dificuldade enfrentada foi a necessidade de desativar os objetos funcionais antes de removê-los, algo que não está diretamente documentado nos tutoriais. Como uma operação inversa do "Agent.Commit()", é necessário fazer "Agent.Retract()" no objeto funcional antes de poder removê-lo com sucesso.
 
No caso dos "ExternalActionChunk"(s) um detalhe importante foi o fato de que quando eles são criados, um objeto na dimensão "SemanticLabel" é criado para representar o rótulo fornecido no momento da sua criação, via método "World.NewActionChunk()". Se esse objeto "SemanticLabel" não for explicitamente removido, no momento em que se desejar criar um objeto com mesmo nome falha o método "World.NewActionChunk()", embora não exista mais um CHUNK com esse nome.
 
Essa característica foi detectada quando foi implementada a capacidade do agente detectar mudança de POSIÇÃO dos objetos - jóias ou alimentos: como a posição é parte integrante dos CHUNKs correspondentes a esses objetos, optou-se por remover todos os objetos criados para a posição inicial e recriá-los para a nova posição; essa operação está dentro do método "MakePerceptionFromSensorialInput()":
 
 
Nesse trecho acima é possível ver a atualização no caso das jóias; essa atualização inclui também a detecção de mudança de cores. 
 
 
 

Desvio de obstáculos - paredes delimitadoras

 
Embora não fizesse parte integrante da descrição do problema a ser resolvido, foram mantidas as paredes delimitadoras do tabuleiro.
 
Como o delivery-spot está por default na posição (0, 0) e a criatura ocupa um espaço maior do que um simples ponto, a operação de enviar a criatura até o delivery-spot, através do comando "setGoTo" do WorldServer3D muitas vez faz com que a criatura colida com uma parede lateral, dependendo do ângulo do movimento.
 
Este conjunto de fatores, aliado ao fato de que para rotacionar a criatura precisa de um determinado ESPAÇO - ou seja, dependendo de quão perto ela estiver da parede, não consegue completar uma rotação para ir até uma outra posição - complicaram o tratamento do desvio das paredes.
 
Foi criada uma "FixedRule" somente para tratar esse desvio:
 
// Create contour obstacle rule
 
SupportCalculator contourObstacleSupportCalculator = FixedRuleDelegateToContourObstacle;
 
FixedRule ruleContourObstacle = AgentInitializer.InitializeActionRule (CurrentAgent,
                                                                       FixedRule.Factory,
                                                                       outputContourObstacle,
                                                                       contourObstacleSupportCalculator);
 
Sendo que a regra de cálculo da ativação ("SupportCalculator") permaneceu semelhante à do exemplo fornecido:
 
 
O que acabou ficando mais complexo foi a definição do que fazer para contornar o obstáculo, sempre considerando como cenário principal o momento em que a criatura está indo para o delivery-spot - posição (0, 0) - entregar um leaflet. Essa definição ficou dentro da classe "MainClass()", no momento de envio do comando para o WorldServer3D:
 
 
A abordagem implementada foi a seguinte:
 
  1. A criatura deverá mudar de curso 45º na direção oposta do obstáculo encontrado;
  2. Se o obstáculo estiver logo à frente, a criatura deve ir para trás;
  3. Se houver mais de um obstáculo, ou se na tentativa de desviar a criatura continua bloqueada, a criatura deve ir para trás;

 

A definição das ações 2 e 3 acima dependeu de uma avaliação dentro do código do agente, em "ClarionAgent.cs", para identificar múltiplos obstáculos próximos e também registrar que a última ação foi a de desvio de um obstáculo. Para permitir que a criatura tenha tempo para  se mover na direção definida, antes que outra ação seja definida, foi colocado um tempo de espera de 200 ms:
 
 
 

Atraso na comunicação com WorldServer3D quando executando na rede do laboratório

 
Executando a solução na rede do laboratório verifica-se um grande atraso na comunicação o que dificulta ainda mais no controle da criatura: ela demora mais para receber os comandos e fica, com maior frequência, travada em um obstáculo.
 
Esse, aliás, parece ser um problema do WorldServer3D: quando a criatura encosta em um objeto ela trava, não conseguindo se mover mais, nem mesmo de ré, exatamente pelo caminho que veio.
 
Para atenuar esse problema, foi feita uma adaptação de considerar o delivery-spot como sendo no ponto (30.0, 30.0), evitando-se assim mandar a criatura até muito próximo das paredes delimitadoras.
 
 
 

Interface com WorldServer3D

 
Outro problema enfrentado foi com a comunicação com o WorldServer3D.
 
Nas operações de coleta de jóias e ingestão de alimentos, quando os comandos "sackit" ou "eatit" eram precedidos imediatamente pelo comando "getcreaturestate" - já iniciando o próximo ciclo cognitivo - a jóia ou alimento removido do ambiente ainda aparecia na visão da criatura.
 
Para evitar esse comportamento foi incluindo uma espera de 200 ms logo após o envio dos comandos "eatit" e "sackit", antes de iniciar o próximo ciclo cognitivo.
 
 
Uma outra dificuldade foi percebida quando a resposta do comando "getcreaturestate" era muito longa, retornando muitos elementos visualizados pela criatura: apenas com o uso do comando "NetworkStream.Read()" a resposta recebida era incompleta. Foi utilizada a classe "StreamReader" e seu método "ReadLine()" para obter a recepção de toda a resposta:
 
 
 
 
 

Agente com meta corrente nula

 
Por algum motivo não claro em alguns momentos o agente não tinha uma meta corrente definida. Assim, a propriedade "Agent.CurrentGoal" retornava o valor null.
 
Embora não tenha sido encontrada explicação para isso, foi verificado que os próprios códigos exemplo consideravam essa possibilidade.
 
Essa característica afetou o fornecimento do feedback para as ações, uma vez que a avaliação de pertinência para a regra dependia de compará-la com o meta atual. A solução de contorno adotada foi considerar a meta nula correta para as ações de "ir até uma jóia" e considerar a meta nula como incorreta para as ações de "ir até um alimento.
 
 
 
 

Resultado final

 
O código fonte do agente que realiza a busca e entrega dos leaflets pode ser encontrado aqui.
 
O pacote de executáveis para a plataforma Mono pode ser encontrado aqui.
 
O resultado do desenvolvimento foi testado a contento com 2 instâncias do agente Clarion. Não foi possível realizar o teste com o agente SOAR porque este, por algum motivo não investigado, interrompia a execução - aparentemente a engine SOAR interrompe a execução assim que o agente Clarion é ativado.
 
 
O resultado implementado possui as seguintes características:
 
  • O agente faz com que a criatura busque as jóias para completar seu leaflet; assim que um dos leaflets está completo a criatura vai entregá-lo no delivery-spot.
  • Se não houver jóias para buscar - ou se a criatura não tiver visto alguma - ela vai buscar o alimento que tiver visto; caso não tenha visto elemento algum, fica rotacionando procurando objetos.
  • Se a criatura ficar com menos de 300 de combustível ela vai preferencialmente buscar alimento.
  • Se um alimento ou jóia for movido de lugar a criatura consegue perceber, assim como se uma jóia ter sua cor alterada.
  • Se for pressionada a tecla <SPACE> no console do agente são criadas aleatoriamente mais 1 jóia de cada cor.

 

 
 
 
 
 

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer