You are here

Aula 5

Objetivos

Neste laboratório, iremos desenvolver as atividades nos Tutoriais 4 e 5 do Soar. Essas atividades envolvem técnicas clássicas de Inteligência Artificial, como buscas, planejamento e uma técnica de aprendizagem chamada de chunking.

Atividade 1

Desenvolva as atividades previstas no Tutorial 4 do Soar. Documente as atividades desenvolvidas em seu relatório.

Atividade 2

Desenvolva as atividades previstas no Tutorial 5 do Soar. Documente as atividades desenvolvidas em seu relatório.

Relatório

Durante a aula 5 foram estudados os tutoriais 4 e 5, dessa maneira foi dividida a aula em duas partes: a primeira parte consiste no desenvolvimento para resolução do problema dos Missionários e a segunda na inclusão de aprendizado para a resolução do problema.

Figura1 - Configuração Inicial do problema

No problema dos canibais e missionários, três missionários e três canibais devem atravessar um rio com um barco que pode transportar no máximo duas pessoas, sob a restrição de que, para ambas as margens, se há missionários presentes naquela margem, eles não podem ser ultrapassados pelo número de canibais na mesma margem (se fossem, os canibais comeriam os missionários.) O barco não pode atravessar o rio por si só, sem pessoas a bordo.

Para a resolução do problema, precisamos mapear o espaço do problema e a representação dos estados.

A figura abaixo mostra uma representação simples do problema, incluindo os estados não aceitáveis para resolução (mais canibais do que missionários quando algum missionário esta presente), representados com um X e os estados válidos.

Figura2 - Espaço de estados do problema

Para a implementação em Soar, precisamos definir tambem como esse ambiente será representado no Soar. Dentre várias opções, a seguinte representação foi escolhida.

Figura3 - Representação dos elementos em forma de Grafo para ser mapeado no Soar.

No VisualSoar essa representação pode ser vista nos datamaps.

Figura4 - Datamap no VisualSoar

sp {mac*apply*initialize-mac
  (state <s> ^operator.name initialize-mac)
  -->
  (<s> ^name mac
          ^left-bank <l>             #>lado esquerdo do rio
          ^right-bank <r>           #>lado direito do rio
  (<r> ^missionaries 0           #>inicialização dos elementos do lado direito
         ^cannibals 0
         ^boat 0
         ^other-bank <l>)
  (<l> ^missionaries 3           #
>inicialização dos elementos do lado esquerdo    
         ^cannibals 3
         ^boat 1
         ^other-bank <r>)}

 

A implementação dos operadores foram divididas em 3 tipos:

  • Mover apenas uma pessoa (missionário ou canibal)

sp {mac*propose*operator*move-boat1
   "Moves either a single missionary or a cannibal."
   (state <s> ^name mac
              ^<< right-bank left-bank >> <bank>)
   (<bank> ^{ << cannibals missionaries >> <type> } > 0
           ^boat 1)
-->
   (<s> ^operator <o> + =)
   (<o> ^name move-boat
        ^bank <bank>
        ^<type> 1
        ^boat 1
        ^types 1)}

  • Mover duas pessoas do mesmo tipo (missionários ou canibais)

sp {mac*propose*operator*move-boat2
   "Moves two missionaries or two cannibals."
   (state <s> ^name mac
              ^ << right-bank left-bank >> <bank>)
   (<bank> ^{ << cannibals missionaries >> <type> } > 1
           ^boat 1)
-->
   (<s> ^operator <o> + =)
   (<o> ^name move-boat
        ^bank <bank>
        ^<type> 2
        ^boat 1
        ^types 1)}

  • Mover uma pessoa de cada tipo (missionário ou canibal)

sp {mac*propose*operator*move-boat11
   (state <s> ^name mac
              ^ << right-bank left-bank >> <bank>)
   (<bank> ^missionaries > 0
           ^cannibals > 0
           ^boat 1)
-->
   (<s> ^operator <o> + =)
   (<o> ^name move-boat
        ^bank <bank>
        ^missionaries 1
        ^cannibals 1
        ^boat 1
        ^types 2)}

Com isso definimos as movimentações possíveis para a resolução do nosso problema.

A definição do objetivo do nosso programa é muito importante. Uma vez que chegamos na solução, devemos parar a aplicação. Para detecção do goal, usamos um elemento criado na memória de trabalho chamado ^desired que possui o lado do rio e a quantidade de cada elemento a ser satisfeita. Uma vez a condição é satisfeita, o programa é interrompido

sp {mac*detect*state*success
(state <s> ^desired <d>
^<side> <ls>)
(<ls> ^missionaries <m>
^cannibals <c>)
(<d> ^{ << right-bank left-bank >> <side> } <dls>)
(<dls> ^missionaries <m>
^cannibals <c>)
-->
(write (crlf) |The problem has been solved.|)
(halt)}

Com esse desenvolvimento, pudemos ver a primeira execução da aplicação.

Figura5 - Primeira execução do desenvolvimento ainda incompleto - Falha

Nota-se que a falha se dá por que o Soar entrou em um dos estados proibidos para a resolução do problema (elementos marcados com X na Figura2) e isso foi apontado por um mecanismo de monitoramento, que checa por essas condições. Uma solução para esse impasse é a implementação do controle de busca para desfazer uma ação e seguir por outro caminho.

 

## If failure, undo last opertor
 
sp {mac*select*operator*prefer*inverse*failure*types*1
   (state <s> ^name mac
              ^operator <o> +
              ^failure <d>
              ^last-operator <lo>)
   (<o> ^name move-boat
        ^<type> <number>
        ^types 1)
   (<lo> ^types 1
         ^type <type>
         ^number <number>)
-->
   (<s> ^operator <o> >)}
 
sp {mac*select*operator*prefer*inverse*failure*types*2
   (state <s> ^name mac
              ^operator <o> +
              ^failure <d>
              ^last-operator.types 2)
   (<o> ^types 2)
-->
   (<s> ^operator <o> >)}
 
Dessa forma, os casos de falha serão tratados. As regras abaixo que compõe o controle de busca dessa implementação, são utilizadas para evitar movimento repetitivo, ou seja, evitando o último operador.
 
## If not failure, avoid last operator
 
sp {mac*select*operator*avoid*inverse*not*failure*1
   (state <s> ^name mac
              ^operator <o> +
             -^failure <d>
              ^last-operator <lo>)
   (<o> ^types 1
        ^<type> <number>)
   (<lo> ^types 1
         ^type <type>
         ^number <number>)
-->
   (<s> ^operator <o> < )}
 
sp {mac*select*operator*avoid*inverse*not*failure*2
   (state <s> ^name mac
              ^operator <o> +
             -^failure <d>
              ^last-operator.types 2)
   (<o> ^types 2)
-->
   (<s> ^operator <o> < )}
 
Uma vez terminada a implementação da primeira parte (incluindo controle de busca), podemos ver o resultado de uma execução aleatória do Soar com sucesso.

Figura6 - Execução da primeira parte. 198 decisões para a solução

Nessa primeira aplicação, o Soar toma decisões que nos levam a uma falha, a qual resolvemos com o controle de busca. Porém para determinados problemas, não podemos deixar transpassar para o ambiente externo esse tipo de situação. Com isso no Tutorial 5, fazemos uma implementação de planning, que planeja e testa os movimentos internamente antes de executa-lo externamente. Outra melhoria que estudamos foi o sistema de Chunking, que alimenta a inteligencia do Soar e melhora os resultados a cada execução.

Podemos ver nas figuras a seguir os Traces do Soar avaliando ações e tambem alguns aprendizados.

Figura7 - Avaliações de operadores

Figura8 - Aprendizados - operadores criados para ajudar na tomada de decisão

Após fazer a implementação da segunda parte da aula, alguns ajustes foram necessários no primeiro programa para lidar com aprendizagem e fazer com que o Soar trabalhasse com o Chunking. O resultado obtido na primeira rodada foi similar ao programa sem aprendizado, porém a partir da segunda rodada, houve notória melhoria.

Figura9 - Primeira execução de um programa com chunking

Figura10 - Segunda execução de um programa com chunking

O mecanismo de Chunk é automatico do Soar, bastando apenas habilitar a opção de aprendizado

  • learn –-on

Durante o tutorial um problema com chunking foi analisado. Com a regra de aplicação do move-boat inicialmente implementada, o tipo do elemento sendo movido não estava sendo aprendido. Isso fazia com que em algumas rodadas o Soar nunca conseguisse chegar na solução do problema. Isso por que o Soar passava pela situação abaixo:

 

 M: 1, C: 1   ~~~ B  M: 2, C: 2  
    58:                         O: C61 (move-boat)
 Move 1 missionaries
 M: 2, C: 1 B ~~~    M: 1, C: 2  
Failure!

O operador aprendido diz que se o estado atual tem dois missionários e dois canibais e o barco, rejeite um operador que move um missionário e o barco para o outro lado.

Porém quando executamos a solução ótima, vemos que uma situação similar acontece e é um caminho obrigatório para a resolução do problema.

 

 M: 1, C: 1   ~~~ B  M: 2, C: 2  

 

     7: O: O24 (move-boat)
 Move 1 cannibals
 Move 1 missionaries
 M: 2, C: 2 B ~~~    M: 1, C: 1  
 
Nesse caso vemos que o problema foi gerado por que o operador aprendido não considerava mover APENAS um missionário, então quando além do missionario, um canibal (que é um caso válido), esse operador era rejeitado. 
A resolução desse problema consiste na adição ao teste de ^types, conforme código abaixo.

 

sp {apply*move-boat
   (state <s> ^operator <o>)
   (<o> ^name move-boat
        ^{ << missionaries cannibals boat >> <type> } <num>
        ^bank <bank>
        ^types <types>
               )
   (<bank> ^<type> <bank-num>
           ^other-bank <obank>)
   (<obank> ^<type> <obank-num>)
-->
   (<bank> ^<type> <bank-num> -
                   (- <bank-num> <num>))
   (<obank> ^<type> <obank-num> -
                    (+ <obank-num> <num>))}
 
Fazendo 10 rodadas de execuções, obtivemos a tabela abaixo, representando o número de decisões por rodada até a solução do problema, para a implementação do Tutorial 4 (Solução Básica) e do Tutorial 5 (Solução com Aprendizado).

 

Solução BásicaSolução com Aprendizado
58163
18012
10012
34812
18012
25612
13412
21612
7412
13012

 

Referencias

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer