You are here

Aula 2 - SOAR tutorial 1 - Water-Jug

Aula 2 - SOAR: Tutorial 1


Atividade 1: Instalando o SOAR

Foi realizado download da última versão do SOAR para Mac OS.No pacote baixado estão incluidos os tutorias, os programas de exemplo, documentação e o SoarJavaDebugger.
Abaixo a janela principal do SoarJavaDebugger:


Atividade 2: Construindo um Agente SOAR Simples usando Regras


Executando o Hello World do SOAR

O Soar debugger aceita arquivos textos para carregar as regras na memória de regras do SOAR.
Nesse exemplo simples, temos o primeiro contato com a linguagem dos programas SOAR:


Para carregar o código fonte, basta usar o comando "source <file_path>" na barra de comandos.
Após carregado o código fonte, o SOAR mostra ter registrado uma nova regra, para então rodarmos o programa, executamos o comando "run".


O programa hello world apenas causa que o agente, ao perceber sua existência, escreva o texto na tela e desligue-se.

Utilizando a Memória de Trabalho

A memória de trabalho contém toda a informação dinâmica de um agente Soar sobre o mundo e suas resoluções, além de dados de sensores, calculos intermediários, operações correntes e objetivos.

A memória de um agente Soar é organizada em estruturas como grafos. Todo elemento na memória de trabalho está conectado de alguma maneira a um símbolo de estado, que representa o estado atual do agente.

Segue uma simples demonstração de um mundo perceptível e sua representação na memória de trabalho:


Analisando a figura, podemos concluir que a percepção do agente do seu mundo é dada pelos nós e links no grafo. Os nós podem ser de dois tipos, identificadores e constantes. Nós como S1, B1, B2 e T1, são identificadores, pois podem ter extensões emanando a partir deles. E nós como state, blue e block são valores constantes.

Os links que partem de um identificador são denominados 'atributos' e recebem o prefixo '^'.

No contexto do Soar, são chamados de objetos conjuntos de elementos de memória que compartilham o mesmo primeiro identificador. Um objeto de memória de trabalho geralmente representa algo sobre o mundo, como um bloco, um obstáculo, um pedaço de comida, ou uma célula em um tabuleiro.

Embora não apresentado na figura acima, o Soar cria automaticamente uma estrutura base para todo agente:


O mecanismo de entrada e saída de dados no Soar se dá por meio dos atributos de I/O padrão em todos os agentes criados pelo Soar. O identificador I1 em seu atributo ^input-link disponibiliza informações sensoriais do mundo para dentro da memória de trabalho, de maneira similar, no identificador I2 em seu atributo ^output-link é onde são criados comandos de ação para a atuação do agente no mundo em que está inserido. No nosso caso, como estamos trabalhando apenas no mundo virtual, então nossa entrada e saída se dará pela deleção e adição de estruturas no estado atual, lembrando que o Soar não permite que os valores constantes nos atributos dos identificadores sejam modificados, por isso removeremos e adicionaremos estruturas, ao invés de simplesmente atualizarmos valores.

Atividade 3: Agentes Simples utilizando Operadores


Executando o Hello World com Operadores

Operadores realizam ações, tanto no mundo quanto internamente na mente do agente. Por conta disso, são através dos operadores que acontecem as tomadas de decisão. Eles são onde o conhecimento é usado para decidir o que fazer ou não. Portanto, a operaçào básica do Soar é um ciclo no qual operadores continuamente propõem, selecionam e aplicam ações. Regras disparam proposições e aplicam operadores, enquanto o processo de decisão seleciona operadores correntes. Isso pode parecer restritivo, no entanto, te força a separar o lugar onde as decisões estão sendo tomadas do lugar onde estão sendo aplicadas.


No modelo onde usamos apenas regras convencionais, estariamos transitando de um estado para outro deterministicamente, enquanto com o uso de operadores, podemos criar regras para propor ações para o agente, que, observando seu estado atual aplica seu conhecimento corrente para tomar decisões e selecionar o operador que mais julgar adequado. Isso permite que o sistema aprenda e tome decisões mais otimizadas, e não apenas siga deterministicamente as consequências de ações de regras anteriores.

Examinando a Memória de Trabalho

Para que o Soar use operadores, eles primeiro precisam ser criados na memória de trabalho por regras proponentes. Regras proponentes testam características do estado para certificar que o operador é apropriado, e então criam uma representação do operador na memória de trabalho, junto a um parâmetro de preferência 'aceitável'. A preferencia é um modo de dizer à arquitetura que esse operador é um candidato para seleção. Uma vez que o operador é selecionado, as regras que aplicam o operador irão conferir com o estado atual e realizarão quaisquer ações apropriadas, através da criação ou remoção de elementos da memória de trabalho. Através do debugger é possivel analisar as informações em um estado através do comando 'print <s>', onde <s> é o identificador do estado. No nosso programa, o comando print s1 nos devolve o seguinte:

(S1 ^epmem E1 ^io I1 ^operator O1 + ^operator O1 ^reward-link R1 ^smem S2 ^superstate nil ^type state)

Essa é a representação em primeiro nível do nosso objeto (uma vez que não estamos imprimindo os conteúdos de E1, O1, S2, etc). Em análise, o atributo "^operador" é criado quando o operador hello-world é selecionado. Apenas o operador selecionado tem um elemento na memória de trabalho sem o "+". Em complemento ao operador selecionado, temos operadores concorrentes, que são os operadores marcados com o simbolo "+", nesse caso temos apenas um, que é também o selecionado. Se tivessemos mais, todos estariam marcados com "+", pois só podemos ter um operador selecionado por estado.

Atividade 4: Criando um Agente para o Water Jug Problem

O problema Water Jug é um problema clássico da IA, para o qual é necessário fazer uma busca em um espaço de estado finito e limitado.

A proposta é a seguinte:
É lhe dado duas jarras vazias. Uma tem a capacidade de 5 gal de água e a outra 3 gal
Há uma fonte de água ilimitada que você pode usar para encher completamente as jarras.
Você pode também esvaziar uma jarra ou jogar água de uma jarra pra a outra.
Não há marca de nível intermediário nas jarras.
O objetivo é preencher a jarra de 3 gal com 1 gal.

Para representar os estados no problema Water-Jug teremos dois valores: a quantidade de água na jarra de 5 gal, e a quantidade de água na jarra de 3 gal. Por exemplo: 5:0,3:0 representando que a jarra de 5 gal tem 0 gal de água e a jarra de 3 tem 0 gal de água.

Abaixo segue o fluxo ótimo para a solução do problema mostrando os estados e as operações que são aplicadas a cada estado.

(5:0,3:0) Complete a jara de 3 gal
(5:0,3:3) Despeje tudo da jarra de 3 gal para a de 5 gal jug
(5:3,3:0) Complete a jara de 3 gal
(5:3,3:3) Despeje a jara de 3 gal na jarra de 5 gal
(5:5,3:1) -> estado desejado

Persistência de WMEs

Quando a regra para inicializar o water-jug dispara, todas as estruturas em sua ação são adicionadas à memória de trabalho. Nesse ponto, é desejável que o operador inicializador seja removido da memória de trabalho para que outros operadores possam ser selecionados. E isso é exatamente o que o Soar faz, pois as condições da regra proponente não se aplicam mais (pois ao checar a ausência da tarefa e das estruturas da jarra, o programa verifica que elas já existem). Uma vez que a regra retrai, a regra de aplicação do operador também não confere mais porque o operador não está mais na memória de trabalho. Portanto, não queremos que a regra de aplicação remova as estruturas que ela mesmo criou quando não for mais válida. Se assim fosse, o sistema nunca faria nenhum progresso - ele entraria em um loop infinito propondo e aplicando um operador, retraindo seus resultados, e então propondo e aplicando o operador novamente.

Para suportar diferentes necessidades de resoluções diferentes de problemas diferentes (neste caso o de propor e aplicar um operador), o Soar faz distinção entre a persistência de elementos da memória de trabalho criados por ações de um operador e a persistência dos elementos da memória de trabalho criados por outros tipos de regras. As ações de um operador precisam criar resultados persistentes, pois operadores são decisões que o sistema tomou para mover-se para um estado novo no espaço do problema. Todas as outras regras em Soar analisam e elaboram o estado corrente sem efetivamente mudá-lo e são retraídas quando não se aplicam mais ao estado corrente. Essa é uma caracteristica muito importante do Soar - ele difere entre conhecimento que modifica o estado atual (conhecimento que está nas ações dos operadores), e conhecimento que calcula as consequências da situação atual (propondo quais operadores devem ser considerados para o estado atual).

Elaboração de Estado

Regras que propõem operadores podem ser simplificadas adicionando atributos à objetos residindo na memória de trabalho. Com o uso de regras de elaboração podemos calcular, por exemplo, baseado na capacidade e no volume de água em um jarra, o tanto de água que falta para enxermos a jarra, e então armazenar esse valor como um novo atributo, que no nosso caso o chamaremos de "^empty".

O uso dessas regras de elaboração é o que chamamos de elaboração do estado, pois estamos abstraindo partes do nosso objeto em novos atributos na memória de trabalho.

Regras de elaboração de estado são fortemente utilizadas em grandes projetos Soar, pois podem criar abstrações úteis de outros elementos da memória de trabalho e representá-los diretamente no estado como um novo atributo.

Proposição de Operadores

Em Soar, não há como dizer que uma instanciação é mais importante que outra, então devemos evitar decisões quando realmente não precisamos decidir nada. Assim, em Soar, todas as regras que conferem disparam ao mesmo tempo, em paralelo. É claro que o Soar está rodando em um computador serial, então não está realmente em paralelo, mas para todos os efeitos, as regras disparam em paralelo. Esse paradigma é bastante diferente da maioria das linguagens de programação e pode ser confuso quando percebermos que as regras disparam assim que conferem.

Abaixo está uma analise mais completa do ciclo básico do Soar. Para esse problema não há entrada e saída, então podemos ignora-los no diagrama. Começando da esquerda para a direita, as regras disparam (ou retraem) para elaborar o estado ou propor operadores. Pode haver multiplas regras disparando (ou retraindo), e o Soar continua a disparar ondas de regras até que nenhuma mais dispare (ou retraia). Uma vez que não há mais regras para disparar (ou retrair), chegamos ao estado de quietude e então entramos com o procedimento de decisão para selecionar o operador. Essa abordagem garante que quando uma decisão é tomada, todo o conhecimento disponível nas regras está disponível nesse momento.


Aplicação de Operadores

No problema water-jug, há 3 operadores: fill, empty e pour. O Soar nos permite computar separadamente quando operadores que podem ser aplicados (regras proponentes que criam preferências aceitáveis por operadores) e operadores que poderiam ser aplicados (regras de controle de busca que criam outros tipos de preferencias).

Abaixo temos a lista com cada operador e as condições sob as quais eles são válidos. Essas condições serão a base para as propormos esses operadores.

  • Fill - preencha uma jarra com água da fonte, caso a jarra não estiver vazia.
  • Empty - esvazie uma jarra, caso haja água na jarra
  • Pour - despeje água de uma jarra para outra, caso haja água na jarra original e a jarra de destino não esteja cheia.

Embora possamos tentar preenhcer uma jarra que já está cheia, em Soar, os operadores devem modificar o estado de forma que outro operador possa ser selecionado. Para isso, na nossa implementação, preencher uma jarra cheia deve ser proibido.

As regras que propõe nossos operadores descritos podem ser escritas como segue:

*proponha*fill
Se a tarefa for water-jug e há uma jarra que não está cheia,
então proponha preencher (fill) aquela jarra.

*proponha*empty
Se a tarefa for water-jug e há uma jarra que não está vazia,
então proponha esvaziar (empty) aquela jarra.

*proponha*pour
Se a tarefa for water-jug e há uma jarra que não está cheia e a outra não está vazia,
então proponha despejar (pour) água da segunda jarra para dentro da primeira jarra.

Dessa forma, os operadores então são escolhidos a partir da anaálise do estado atual contra as regras que propõe operadores.

Monitoramento de Estados e Operadores

O monitoramento de regras é bastante útil para imprimir detalhes da aplicação dos operadores e no conteúdo de cada estado. Uma das características boas do Soar é que todas as regras de monitoramento disparam em paralelo com outras regras e não interferem na resolução de problemas.

Para criar um desses monitoramentos, basta criar uma regra que checa o estado atual, e como ação simplesmente imprime informações relevantes ao processo de resolução do problema.


Reconhecimento do Estado Desejado

O último passo para criar um programa que não só resolve o Water-Jug, mas sabe que o problema foi resolvido, é criar uma regra que reconheça que o estado desejado foi atingido. É preciso escrever uma regra que reconhece o estado quando a jarra de 3 gal tem 1 gal de água. A ação dessa regra deve ser imprimir a mensagem de que o problema foi resolvido e desligar.

Tal regra poderia ser escrita da seguinte forma:

Se a tarefa é water-jug e há uma jarra com volume igual a 3 e conteúdo igual a 1, escreva que o problema foi resolvido e desligue.

Outra abordagem para esse problema que é comumente usada é criar uma representação do estado desejado na memória de trabalho, e escrever uma regra que compara o estado desejado com o estado atual. Há duas vantagens para essa solução. Primeiro, se você quer tentar resolver mais de um Water-Jug, um novo estado desejado pode ser definido modificando a memória de trabalho, ao invés de modificar a regra que checa pelo estado desejado.

Na regra abaixo criamos o estado desejado como um atributo do nosso estado (essa regra será disparada em paralelo com a inicialização do problema):


Abaixo a regra que reconhece o estado desejado comparando o que criamos na memória de trabalho:

E finalmente o debugger exibe que o programa resolveu o problema:


Controle de Busca

Para evitar que o Soar aplique o mesmo operador duas vezes seguidas, é necessário que o programa tenha registro do último operador aplicado, e no Soar isso não é automatico. Teremos que criar regras para gravar o último operador aplicado.

O registro de um operador tem duas partes, a primeira é criar a estrutura no estado que representará o operador mais recente. O segundo é remover qualquer registro de um operador antigo.

Tal regra pode ser traduzida da seguinte forma:
Se a tarefa é 'water-jug' e a operação 'pour' está selecionada, então crie um atributo (ultimo-operador) no estado atual com o nome do operador e uma cópia de seus atributos.


Conclusão

O Soar é uma poderosa ferramenta que possibilita a criação de mentes artificiais, através de uma linguagem de programação é possível definir regras que modificam o estado do agente virtual, que propõem e aplicam ações, que monitoram o estado do agente, etc. E deixamos a cargo do próprio agente escolher quais ações e caminhos tomar, baseado no conhecimento adquirido nas decisões passadas. Através de regras de elaboração de estado é possível abstrair partes da memória de trabalho do agente e fazê-lo perceber diferentes aspectos do mundo no qual está inserido. A capacidade de criar abstrações e tomar decisões sobre quais ações tomar é o que difere o Soar de sistemas especialistas convencionais, no qual temos apenas redes de proposições e regras que serão testadas e nos levarão à conclusões de forma cega.

As semelhanças com sistemas especialistas está apenas no fato de que ambos trabalham com regras e proposições.

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer