Agentes Inteligentes
No campo da Inteligência Artificial (IA), um agente é qualquer instância (em hardware e/ou software) capaz de perceber o seu ambiente e, a partir disso, tomar uma ação. O termo perceber se refere à coleta de informações, no caso de agentes de hardware (p.e. robôs) a percepeção se dá por meio de sensores, em agentes de software, via dados digitais. A sequência completa de informações coletadas pode ser definida como uma sequência de percepção.
As ações tomadas pelo agente, a partir das informações coletadas, definem o comportamento do agente. É esperado que o agente inteligente seja aquele que tenha o melhor comportamento, ou o mais racional.
Dessa forma, pode-se definir o agente racional, que é aquele que a partir dos dados coletados, toma a ação correta. Naturalmente, é preciso definir o conceito de ação correta. Como as ações de um agente são tomadas a partir de algoritmos computacionais, esse conceito precisa ser definido matematicamente. Assim, é necessário quantificar as ações dos agentes, criando uma medida de performance. Dessa forma, é possível comparar as ações tomadas pelo agente, e racionalmente escolher aquela de melhor performance.
A figura acima ilustra a arquitetura básica de um agente inteligente. Ele possui perceptores para coleta de informação, e atuadores capazes de executar ações. O papel da IA é preencher o bloco com a interrogação, que determinará as ações baseado no processamento dos dados adquiridos.
Agentes de Resolução de Problemas
Umas das aplicações mais usuais de agentes inteligentes é na resolução de problemas específicos, maximizando sua performance. Por exemplo, considere a seguinte situação: Deseja-se sair da cidade de Arad, na Romênia e chegar na cidade de Bucareste, capital desse país.
O primeiro detalhe a ser observado é a definição do objetivo do agente. No projeto de um agente inteligente, é preciso estabelecer de forma clara seu objetivo. Uma vez estabelecido, é necessário criar uma sequência de ações que façam o agente cumprir seu objetivo.
O processo de decisão de quais ações e estados serão considerados consiste na formulação do problema. No exemplo acima, o estado do agente pode ser, de uma forma simplificada, a cidade em que ele se encontra e as ações são a cidade que ele deve se deslocar.
Sabendo que não existe uma conexão direta entre Arad e Bucareste, é preciso se deslocar para de Arad para uma cidade vizinha, e assim sucessivamente até alcançar o objetivo. A dificuldade, entretanto, é decidir que cidade vizinha deve ser colocada como alvo em cada deslocamento. Temos, portanto, dois possíveis casos para esse tipo de problema.
O primeiro é o de ambiente desconhecido, onde os possíveis estados futuros não são conhecidos após a conclusão de cada ação. Por exemplo, sabemos que Arad se conecta à Zerind, Sibiu e Timisoara, mas não sabemos a quais cidades cada uma delas está conectada. Dessa forma, não se sabe qual a primeira a ação a ser tomada a partir do estado inicial.
O segundo é o de ambiente conhecido, onde os possíveis estados são conhecidos. Nesse caso, o espaço de possibilidades é conhecido e a tomada de decisão pode ser explorada antecipadamente. Para o exemplo romeno, um ambiente conhecido seria acesso a um mapa rodoviário da Romênia, como o da figura abaixo. É possível observar todas as possibilidades de conexão entre o estado inicial (Arad) e o estado final (Bucareste), assim como o custo (distância) para cada deslocamento. O processo de procura por uma sequência de ações que alcançam o objetivo é chamado de resolução de problemas por busca. Um algoritmo de busca recebe as informações do problema e retorna uma solução na forma de uma sequência de ações. Uma vez encontrada a solução, a sequência de ações determinadas por ela podem ser executadas, a chamada fase de execução. Em resumo: “formule, procure e execute”!
No exemplo anterior, os dados de entrada são as informações do mapa, o estado inicial é a cidade de Arad, o objetivo é o deslocamento para a cidade de Bucareste e a sequência de ações é a lista ordenada de cidades a serem visitadas. Qualquer sequência sucessiva de deslocamentos que, eventualmente, passem por Bucareste podem ser consideradas soluções para o problema. Aquela solução, contudo, que resulta no menor deslocamento é chamada de solução ótima. Um agente é dito inteligente (ou até mesmo racional) se ele for capaz de encontrar tal solução.
Busca de Soluções
Uma vez formulado um problema, é preciso resolvê-lo. A solução é uma sequência de ações, dessa forma o algoritmo de busca deve considerar as várias sequências possíveis. As sequências possíveis a partir do estado inicial formam uma árvore de busca, com o estado inicial como raíz. Os “galhos” dessa árvore são as ações a serem tomadas, e os nós representam os estados no espaço de estados do problema.
Considerando o exemplo da viagem na Romênia, temos o estado inicial Arad. A partir dessa cidade, temos três possíveis ações a serem tomadas, que são o deslocamento para as três cidades vizinhas com conexão direta com ela. Caso uma dessas ações sejam tomadas (viajar para uma cidade vizinha), teremos um novo estado, e consequentemente, possíveis novas ações. Esse desdobramento de possibilidades é parcialmente representado na figura abaixo.
Nessa estrutura, os possíveis estados futuros configuram folhas na árvore de busca. Analisando a figura abaixo, começamos em (a) com o nó raiz, ou estado inicial. A partir desse estado, temos três possibilidades ainda não exploradas, ou estados futuros, que são as três possíveis folhas dessa árvore.
Escolhendo (nesse caso, aleatoriamente) o estado Sibiu, essa cidade nos fornece quatro possíveis novos estados, correspondendo às quatros cidades vizinhas. Assim, temos em (c) seis possibilidades de sequência de ações: as quatro vizinhas de Sibiu, ou retornar à Arad e escolher Timisoara ou Zerind, ou seja, considerando Sibiu como estado atual, temos 6 folhas na árvore de busca. Esse conjunto de folhas a partir de um estado, é chamado de fronteira.
O processo de expansão dos nós na fronteira continua até a solução ser encontrada, ou a folha não puder ser expandida.
Infraestrutura dos Algoritmos de Busca
Algoritmos de busca requerem uma estrutura de dados para rastrear a árvore de busca que está sendo construída. Para cada nó n da árvore, temos uma estrutura que contém quatro componentes: - n.STATE: o estado no espaço de estados ao qual o nó corresponde;
- n.PARENT: o nó na árvore de busca que gerou este nó; - n.ACTION: a ação que foi aplicada ao pai para gerar o nó;
- n.PATH-COST: o custo, tradicionalmente denotado por g(n), do caminho do estado inicial para o nó, conforme indicado pelos ponteiros do nó pai.
Os ponteiros PARENT vinculam os nós em uma estrutura de árvore, ou seja, apontam para o nó originário do estado atual. Esses ponteiros também permitem que o caminho da solução seja extraído quando um nó objetivo é encontrado. Usamos então a seqüência de ações obtidas seguindo os ponteiros pai de volta para a raiz.
Até agora, não tivemos muito cuidado em distinguir entre nós e estados, mas ao escrever algoritmos detalhados é importante fazer essa distinção. Um nó é uma estrutura de dados de contabilidade utilizada para representar a árvore de pesquisa. Um estado corresponde a uma configuração do ambiente do problema. Assim, os nós estão em caminhos específicos, conforme definido pelos ponteiros PARENT, ao passo que os estados não são. Além disso, dois nós diferentes podem conter o mesmo estado de ambiente se esse estado for gerado por dois caminhos de pesquisa diferentes. Agora que temos nós, precisamos de um lugar para colocá-los. A fronteira precisa ser armazenada de forma que o algoritmo de busca possa facilmente escolher o próximo nó a ser expandido de acordo com sua estratégia preferida. A estrutura de dados apropriada para isso é uma fila.
As filas são caracterizadas pela ordem em que armazenam os nós inseridos. Três variantes comuns são a fila do first-in-first-out (FIFO), que mostra o elemento mais antigo da fila; a fila last-in-first-out (LIFO), também conhecida como pilha (stack), que mostra o elemento mais novo da fila; e a fila de prioridade, que mostra o elemento da fila com a maior prioridade de acordo com alguma função de ordenação.
Mensuração da Performance
Antes de entrarmos no design de algoritmos de busca específicos, precisamos considerar os critérios que podem ser usados para escolher entre eles. Podemos avaliar o desempenho de um algoritmo de quatro maneiras:
- Integralidade: O algoritmo garante encontrar uma solução quando há uma?
- Otimização: A estratégia encontra a solução ótima, ou seja, tem o menor custo de caminho entre todas as soluções?
- Complexidade do tempo: quanto tempo demora para encontrar uma solução? - Complexidade do espaço: quanta memória é necessária para realizar a pesquisa?
A complexidade é expressa em termos de três grandezas: b, o fator de ramificação (branching) ou o número máximo de sucessores de qualquer nó; d, a profundidade (depth) do nó final mais superficial (isto é, o número de passos ao longo do caminho desde a raiz); e m, o comprimento máximo de qualquer caminho no espaço de estados. O tempo é frequentemente medido em termos do número de nós gerados durante a pesquisa, e o espaço em termos do número máximo de nós armazenados na memória.
Para avaliar a eficácia de um algoritmo de pesquisa, podemos considerar apenas o custo de pesquisa - que normalmente depende da complexidade do tempo, mas também pode incluir um termo para uso de memória - ou podemos usar o custo total, que combina o custo da pesquisa e o custo do caminho da solução encontrada.