Primeira vez aqui? Seja bem vindo e cheque o FAQ!
x

Como implementar o modelo baseado em agentes do El Farol em Python?

+1 voto
73 visitas
perguntada Jul 10 em Economia por Daniel Castro (46 pontos)  

Aula 22, Exercício 1. Considere os modelos baseados em agentes apresentados no capítulo 3 do livro "An Introduction to Agent-Based Modeling: Modeling Natural, Social, and Engineered Complex Systems with NetLogo - Wilensky, Uri; Rand, William (2015)". Implemente em Python, possivelmente usando a biblioteca Mesa. Explique/Descreva o modelo, explique cuidadosamente o código que você desenvolveu em Python, replique todas as tabelas e figuras apresentadas para cada um dos modelos apresentados.
d) (*) El Farol

Compartilhe

1 Resposta

+1 voto
respondida Jul 10 por Daniel Castro (46 pontos)  
editado Jul 12 por Daniel Castro

O módulo Mesa do Python apresenta diversos exemplos de ABMs (Agent-based Models), inclusive o Fire Model e o Segregation Model. O Fire Model, inclusive, é baseado em Wilensky e Rand (1997) também. Por isso, optei por implementar o El Farol e o DLA, que não são apresentados como exemplo do Mesa.

NetLogo
Como o próprio livro menciona, o código do modelo está disponível no NetLogo (o software deve ser baixado a partir da Web e instalado localmente) no menu Models Library > IABM Textbook > Chapter 3 > El Farol Extensions. A sintaxe do NetLogo é específica, mas é possível entender a lógica dos comandos sem dificuldades. Inclusive, foi possível identificar problemas no modelo do NetLogo que foram corrigidos na implementação em Python (mencionados adiante). De maneira geral, o NetLogo é bastante simples, permitindo controlar diversos aspectos da simulação, dos agentes e da exibição de maneira relativamente relativamente fácil. Entretanto, por ser uma linguagem específica, não permite utilizar recursos presentes em linguagens de uso mais geral, como os diversos módulos do Python.

Modelo original no NetLogo

O modelo implementado em NetLogo começa por definir variáveis globais (frequência corrente, história de frequências, células do bar e das casas e célula em que o lable de lotação será exibido) e locais dos agentes (lista de estratégias, melhor estratégia, decisão sobre ir ou não ao bar e a previsão de lotação do bar):

A imagem será apresentada aqui.

A função setup colore as células do bar de azul (quadrante superior direito), das casas de verde (demais quadrantes) e da separação entre as casas e o bar em preto. Aqui já há uma grande diferença: no Mesa não parece ser possível definir locais no grid de exibição que serão preenchidos com cores diferentes. O grid do Mesa é sempre branco e os agentes podem ser coloridos. Assim, no Mesa, se quiséssemos um efeito exatamente como o do NetLogo, seria necessário criar agentes "dummy" apenas para exibição, sem papel no modelo. Por isso, optei por colorir os agentes no bar em azul e os agentes em casa de verde e a visualização não ficou exatamente como no NetLogo, mas acredito não haver prejuízo para o entendimento (mais detalhes adiante, na descrição do código em Python).

A função também inicializa o histórico de frequência, a frequência corrente e o rótulo que indica se o bar está cheio ou não. Os agentes (turtles no vocabulário do NetLogo) também são criados aqui com suas estratégias aleatórias e melhor estratégia inicial e colocados em células vazias correspondentes às casas:

A imagem será apresentada aqui.

A função go executa as iterações ou passos do modelo. A cada passo, cada agente prevê a frequência com base em sua melhor estratégia corrente e em um lista de frequências passadas e decide se vai ou não ao bar. Se o agente decide ir ao bar, ele é movido para alguma célula correspondente ao bar. Caso contrário, ele é movido para uma célula das casas. Se o bar fica lotado, o rótulo"Crowded" é exibido no meio do quadrante do bar. Por fim, a lista histórica de frequências é atualizada e cada agente seleciona uma eventual nova melhor estratégia, caso alguma de suas estratégias desbanque as previsões passadas da melhor estratégia atual. Importante destacar que as estratégias de cada agente são diferentes:

A imagem será apresentada aqui.
A imagem será apresentada aqui.

A função update-strategies calcula o desempenho que cada estratégia teria exibido para prever as frequências no passado, com base no histórico. Quanto maior a soma dos erros das previsões para cada semana passada, pior a estratégia. A melhor estratégia é aquela que teria fornecido as previsões mais próximas das frequências passadas observadas, consideradas em conjunto:

A imagem será apresentada aqui.

A função random-strategy cria uma estratégia atribuindo pesos às frequências passadas de forma aleatória. Os pesos variam de -1 a 1:

A imagem será apresentada aqui.

A função predict-attendance realiza a previsão com base em determinada estratégia e no histórico de frequências disponíveis em determinada semana. Aqui há erros de implementação do modelo proposto por Fogel (1999), referenciado na documentação do modelo no NetLogo, que serão explicados na descrição do código Python:

A imagem será apresentada aqui.

Por fim, a função move-to-empty-one-of move os agentes para células vazias:

A imagem será apresentada aqui.

O resultado da simulação é apresentado abaixo, após execução no próprio NetLogo:

A imagem será apresentada aqui.

Interessante notar que há previsões tão baixas quanto 37, como mostrado na figura acima.

Extensões do modelo no NetLogo

O livro apresenta extensões do modelo para 1) recompensar agentes que vão ao bar quando ele não está lotado e colorir os agentes em tons de vermelho em função do nível de recompensas, 2) exibir os valores máximo, mínimo e médio das recompensas entre todos os agentes e 3) exibir um histograma do nível de recompensa dos agentes. São apresentados trechos do código com as modificações e o resultados das simulações.

A primeira extensão cria uma nova variável e a atualiza de maneira apropriada:

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

O resultado é o seguinte:

A imagem será apresentada aqui.

A segunda extensão exibe os valores máximo, mínimo e médio das recompensas dos agentes:

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

O resultado é o seguinte:

A imagem será apresentada aqui.

A terceira extensão exibe um histograma com a distribuição de frequência das recompensas:

A imagem será apresentada aqui.
A imagem será apresentada aqui.

O resultado é o seguinte:

A imagem será apresentada aqui.

Nesse ponto, o Mesa é menos flexível e não parece disponibilizar uma classe para exibir a distribuição de uma variável. É possível sem maiores esforços exibir o valor de determinada variável para todos os agentes, mas não é isso que está implementado acima. A solução de contorno no Python é apresentada adiante.

Implementação do modelo no Python com o módulo Mesa
O Mesa disponibiliza uma série de classes para implementação de modelos, dos agentes e da visualização. Orientada a objetos, a biblioteca permite uma implementação mais elegante e direta dos modelos baseados em agentes. A documentação da biblioteca é satisfatória, mas foi necessário consultar o código do módulo (disponível no GitHub) para verificar diversos aspectos de seu funcionamento.

A estrutura de arquivos não é rígida, mas a documentação e os exemplos sugerem um arquivo model.py em que está a classe que implementa o modelo propriamente dito, responsável pela criação e pelas definições das interações entre os agentes, e as classes que definem os próprios agentes (opcionalmente pode ser feita em um arquivo agents.py, mas optei por colocar essas definições no model.py mesmo, já que existe apenas um tipo de agente). O arquivo server.py contém as definições para visualização do modelo na Web. Ou seja, não há um aplicativo stand-alone como no NetLogo. Por fim, o arquivo run.py chama o servidor web ou, como optei por fazer, executa o modelo sem visualização gráfica, ou seja, permite apenas as inspeção das variáveis coletadas ao longo da simulação. Essa forma de execução foi útil para depuração do programa.

O código do arquivo model.py começa por definir a classe Strategy, de maneira bastante similar à definição do NetLogo apresentada anteriormente. (Obs: O código foi inserido como imagem porque o limite de 20000 caracteres está sendo excedido).

A imagem será apresentada aqui.

A classe Person herda da classe Agent do Mesa e define as variáveis locais (estratégias e recompensas):

A imagem será apresentada aqui.

O método update_strategies replica o código do NetLogo, mas, como um método de instância e não uma função estruturada como no NetLogo.

A imagem será apresentada aqui.

O método predit_attendance também replica o código do NetLogo, com três correções para implementar o artigo do Fogel (1999) em que o código se baseou (conforme documentação do modelo disponível no NetLogo e não no livro). O paper especifica a previsão da seguinte maneira:

A imagem será apresentada aqui.

Mas o código do NetLogo multiplica o primeiro peso por 100, permite valores não inteiros, negativos e superiores ao número de pessoas, problema constatado com a exibição no console dos valores gerados no NetLogo. Embora do ponto de vista conceitual sejam relativamente importantes, na prática, os erros não chegaram a comprometer o modelo porque as previsões ruins das estratégias apenas tornavam-nas piores e eram selecionadas as melhores estratégias. Mesmo assim, corrigi esses problemas na implementação em Python:

A imagem será apresentada aqui.

O método step, por sua vez, é próprio do Mesa, e define o que cada agente faz a cada passo do modelo. No caso, o agente prevê o número de pessoas que irão ao bar, com base na melhor estratégia dentre suas estratégias, e, com base na previsão, decide se vai ao bar ou não. Em função da decisão do agente, sua localização muda (dentro do bar, representado no quadrante superior direito, ou fora do bar, espaço definido como o restante do grid). No arquivo server.py, os agentes dentro do bar serão exibidos em azul e os agentes fora do bar serão exibidos em verde. No NetLogo, eles são brancos e as cores são atributos do grid. No Mesa, essa solução não parece ser possível e, então, optei pela solução de colorir os agentes. Acredito que, do ponto de vista informacional, não houve perdas.

A imagem será apresentada aqui.

A classe ElFarolModel herda da classe Model do Mesa, recebe uma série de parâmetros e define as variáveis do modelo, sendo o agendador e o grid as mais importantes. O agendador define como o modelo se comportará temporalmente e o grid define como o modelo se comportará espacialmente. No caso, optei pelo BaseScheduler, que simplesmente chama o método step dos agentes de forma sequencial.

Espacialmente, optei pelo MultiGrid, que permite que mais de um agente ocupem uma mesma célula, ao contrário do grid utilizado no modelo do NetLogo, que permite apenas um agente por célula. Para utilizar o SingleGrid do Mesa, seria necessário tratar eventuais erros de localização na mesma célula e, como os próprios Wilensky e Rand comentam que não faz diferença ("In this model it doesn't really matter exactly which patch a turtle is on, only whether the turtle is in the home area or the bar area."), optei por simplificar o código, permitindo múltiplos agentes por célula. Os agentes são colocados todos fora do bar, inicialmente.

Por fim, o datacollector define que variáveis serão coletadas a cada passo do modelo para posterior inspeção ou exibição pelas classes de visualização. O método init seria correspondente à função set-up do código do NetLogo:

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

O método get_attendance simplesmente soma a quantidade de pessoas que decidiu ir ao bar:

A imagem será apresentada aqui.

O método updte_history remove o registro da frequência na semana mais antiga e insere o registro da semana mais recente, lembrando que essa atualização é refletida para todos os agentes, que têm apenas referências para a lista global:

A imagem será apresentada aqui.

O método updaterewardshistogram atualiza as variáveis necessárias para exibição do histograma de recompensas. Não é uma boa solução, mas parece ser a única possível para exibição da distribuição dos valores dessa variável. Não é boa porque o número de "bins" no histograma é fixo, enquanto no modelo do NetLogo esse número varia com o nível das recompensas.

A imagem será apresentada aqui.

Por fim, o método step do modelo atualiza a frequência e a história, as recompensas dos agentes e variáveis auxiliares necessárias para exibição das informações pelo arquivo server.py. Seria equivalente à função go do código do NetLogo.

A imagem será apresentada aqui.

O arquivo server.py, por sua vez, define como exibir a simulação de forma dinâmica através de elementos gráficos em um arquivo HTML. Começa pelo rótulo Crowded/Not Crowded quando o bar está cheio. No caso, estendendo a classe TextElement. Ao contrário do NetLogo, não parece possível exibir o rótulo sobre o grid e, assim, optei por exibi-lo logo acima do quadrante superior direito do grid, área que representa o bar. Para tanto, é necessário incluir uma série de espaços em branco no texto.

A imagem será apresentada aqui.

A classe RewardElement, que também estende TextElement, exibe os valores máximo, mínimo e médio das recompensas dos agentes.

A imagem será apresentada aqui.

A função agent_portrayal (notem que não é um método, já que não está associado a nenhuma classe) define como exibir os agentes. Como mencionado, o modelo do NetLogo exibe os agentes em branco na versão original e em tons de vermelho na primeira extensão. Para fazer o mesmo no Mesa, seria necessário exibir imagens que representassem seres humanos estilizados em branco, colorindo as células do grid de azul ou verde. Exibir em tons de vermelho exigiria ainda que houve uma imagem para cada tom a exibir. Assim, optei por manter o grid do Mesa branco (o que não parece ser possível alterar) e colorir os agentes em tons que variam conforme o nível de recompensa de cada um (quanto maior o nível de recompensa, mais escuro) e em cores associadas à localização (verde fora do bar e azul dentro do bar). Além disso, optei por exibir individualmente o nível de recompensa de cada agente:

A imagem será apresentada aqui.

No corpo do arquivo, os objetos correspondentes aos diversos elementos a exibir são criados. No caso, além dos elementos textuais já mencionados, que requerem extensão da classe TextElement, há também o grid que serão exibidos os agentes e os gráficos (um de linha para exibir a frequência e dois de barra, um para o nível de recompensa de cada agente e um para exibir a distribuição das recompensas de todos os agentes). Os objetos referentes ao grid e aos gráficos são instâncias de classes originais do Mesa, sem necessidade de extensão.

A imagem será apresentada aqui.

Há ainda a definição dos parâmetros que o usuário pode selecionar. No caso, além das opções presentes no modelo do NetLogo (tamanho da memória, número de estratégias e limite de lotação), incluí também o número de pessoas entre os parâmetros.

A imagem será apresentada aqui.

Por fim, o servidor que alimentará a página HTML no browser é criado com o modelo, os elementos a exibir, o nome a exibir e os parâmetros a passar para o modelo:

A imagem será apresentada aqui.

Por fim, o servidor é inicializados. Em alguns exemplos, essa linha de código está no arquivo run.py, mas optei por deixar aqui para que o arquivo run.py possa executar o modelo sem associação à exibição gráfica da simulação. Há também classes do Mesa que permitem que o modelo seja assim executado não apenas uma vez, mas várias vezes, variando os parâmetros do modelo e permitindo verificar o comportamento do modelo em função dos parâmetros. Entretanto, como o modelo do NetLogo é apenas visual, não implementei essas chamadas automáticas.

A imagem será apresentada aqui.

A figura abaixo apresenta como o modelo é exibido no browser:

A imagem será apresentada aqui.

Os parâmetros à esquerda podem ser setados como desejado. O parâmetro superior é default do Mesa e define a velocidade com que a exibição do modelo é atualizada. Clicando no link Start, o modelo começa a ser executado, atualizando tanto o grid quanto os gráficos:

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

Clicando no link Stop, a simulação é interrompida. O link Step, por sua vez, executa uma iteração do modelo.

O código do modelo está disponível nesse repositório do GitHub, no diretório aula22_1d.

comentou Jul 11 por Daniel Castro (46 pontos)  
Já vi que a classe Agent tem uma referência para um objeto da classe Model e, portanto, algumas variáveis de instância da classe Person são desnecessárias. Vou alterar o código para deixar o código mais limpo.
comentou Jul 12 por Daniel Castro (46 pontos)  
Modificações realizadas.
...