--- title: Agente Reactivo: Memoria author: Mario Román ---

Descripción de la solución

Mapa del mundo

El agente que proponemos es un agente reactivo con memoria, que tiene en cuenta la historia sensorial previa para crear una representación icónica del mundo que le rodea. En particular, el agente almacena un mapa que toma como punto de referencia la posición inicial del agente en el mapa real. Sobre ese mapa se almacena si se han visitado o no las casillas y si en ellas había una pared o estaban libres. Teniendo en cuenta esto, el agente evita moverse hacia las casillas ocupadas.

Valor de una casilla

Para decidir hacia qué casilla se dirigirá a cada paso, el agente calcula un valor para cada una de las casillas. Este valor tiene en cuenta:

  • La cantidad de trufa esperada en la casilla.
  • Un factor que penaliza los giros.
  • Un factor que premia el explorar nuevas casillas.

Con todo esto, el agente decide dirigirse a la casilla de más valor. El cálculo del valor de una casilla puede verse como un uso simplificado del concepto de mapa de potencial.

Decisión de recoger

Para decidir si en una determinada casilla el robot debe recoger trufas o seguir moviéndose, se usa el valor esperado de trufas en esa casilla. Este es un valor que se calcula a partir del crecimiento probabilístico esperado a cada paso, y, si supera un cierto umbral marcado por el valor SUFICIENTE_TRUFA, se decide recoger.

Resultados obtenidos

Para medir la bondad de una solución, usamos durante el proceso la media geométrica de las mejoras respecto a la solución aleatoria inicial. Nótese que la media geométrica de las mejoras es comparable independientemente de la referencia.


In [2]:
%matplotlib inline
import os
import subprocess
from scipy.stats.mstats import gmean
from __future__ import division

In [3]:
# Test the agent against all the different maps in 'map'.
allmaps = os.listdir('map')

def testmaps(maps = allmaps):
    test = []

    for filename in maps:
        result = subprocess.check_output('./agent '+ './map/' + filename, shell=True)
        test.append((float(result),filename))

    return test

In [4]:
# Builds the executable
def make(strategy):
    return subprocess.check_output(
     "g++ main.cpp environment.cpp agent.cpp evaluator.cpp random_num_gen.cpp -o agent -fpermissive -O3 " + strategy,
     shell=True
    )

In [8]:
# Measures the goodness of a solution
standard_results = [349.2, 634.5, 408.3, 530.6, 660.1, 389.7, 477.7, 498.2]
standard_fast_results = [349.2, 408.3, 389.7, 498.2]
standard_slow_results = [634.5, 530.6, 660.1, 477.7]

def goodness(results, st=standard_results):
    proportions = [a[0]/b for a,b in zip(results,st)]
    return gmean(proportions)

In [9]:
make("")
print testmaps()
print goodness(testmaps())


[(2641.2, 'mapa3_rap.map'), (1336.9, 'mapa2.map'), (2848.8, 'agent_rap.map'), (1469.5, 'mapa3.map'), (1397.5, 'mapa1.map'), (2997.2, 'mapa2_rap.map'), (1247.9, 'agent.map'), (3115.3, 'mapa1_rap.map')]
4.11306830338

Concluyendo así que nuestra solución es en media 4.11307 veces mejor que la aleatoria.

Otras estrategias

El resto de estrategias que se implementaron pueden observarse usando complilación condicional sobre el código dado.

Para cada solución se muestra su bondad respecto a la solución aleatoria y la lista ordenada alfabéticamente de valores sobre el mapa inicial.

Estrategia: Aleatoria

Escoje aleatoriamente una acción entre las cuatro. Sólo la usamos como referencia.


In [131]:
make("-DRANDOM")
print testmaps()
print goodness(testmaps())


[349.2, 634.5, 408.3, 530.6, 660.1, 389.7, 477.7, 498.2]
1.0

Estrategia: Casi aleatoriamente

Aleatoriamente, pero omitiendo la acción de observar. Mejora sólo sutilmente la aleatoria.


In [132]:
make("-DRANDOMLY")
print testmaps()
print goodness(testmaps())


[499.5, 726.5, 451.9, 634.4, 807.7, 539.8, 552.8, 689.9]
1.24805226388

Estrategia: Paredes

En esta primera estrategia, se combinaba la aleatoriedad con una representación icónica del mundo. Evitaba caminar contra una pared y evita en lo posible girar hacia donde luego encontrará una pared. La solución final fue un refinamiento de esta solución con el cálculo de valores para cada casilla.


In [133]:
make("-DWALLS")
print testmaps()
print goodness(testmaps())


[895.3, 890.1, 628.3, 846.1, 877.6, 893.7, 641.6, 833.4]
1.66986494108

Refinamientos de la solución

Estudio del factor SUFICIENTE_TRUFA

La constante SUFICIENTE_TRUFA determina cuándo el agente cree que ha crecido suficiente trufa para ser recolectada. Lo que hará será empezar a recolectar una vez haya suficiente. La variación de esta constante variará la frecuencia con la que se recolecta.

Un factor razonable es 5.000, porque se llega a él en el tiempo esperado de crecimiento de una trufa con crecimiento del p = 1.5. Pero probar valores por encima y por debajo puede ser interesante. Se presenta una gráfica con la variación de la bondad según la variación de esta constante.


In [134]:
make("-DSUFICIENTE_TRUFA=" + str(4500))
print testmaps()
print goodness(testmaps())

print testmaps(rapidmaps)
print goodness(testmaps(rapidmaps),standard_fast_results)
print testmaps(slowmaps)
print goodness(testmaps(slowmaps),standard_slow_results)


[2641.2, 1336.9, 2848.8, 1469.5, 1397.5, 2997.2, 1247.9, 3115.3]
4.11306830338
[2641.2, 2848.8, 2997.2, 3115.3]
7.09778395646
[1336.9, 1469.5, 1397.5, 1247.9]
2.38346658225

In [135]:
from pylab import *
x = linspace(0,20000,41)
y = []
for xi in x:
    make("-DSUFICIENTE_TRUFA=" + str(xi))
    y.append(goodness(testmaps()))

In [136]:
figure()
plot(x, y, 'r')
xlabel('factor')
ylabel('trufa')
title('SUFICIENTE TRUFA')
show()


Obtenemos que el valor general más razonable para SUFICIENTE_TRUFA está alrededor de los 4500.

El factor en mapas de crecimiento rápido y lento

Podemos particularizar el estudio a cada uno de los dos posibles casos.

Veremos que el óptimo en los mapas rápidos será 4500, mientras que en los mapas lentos rondará los 7000.


In [10]:
rapidmaps = ['mapa3_rap.map',
             'agent_rap.map',
             'mapa2_rap.map',
             'mapa1_rap.map']

slowmaps = ['mapa2.map',
            'mapa3.map',
            'mapa1.map',
            'agent.map']

print testmaps(rapidmaps)
print goodness(testmaps(rapidmaps),standard_fast_results)
print testmaps(slowmaps)
print goodness(testmaps(slowmaps),standard_slow_results)

print testmaps()
print goodness(testmaps())


[(2641.2, 'mapa3_rap.map'), (2848.8, 'agent_rap.map'), (2997.2, 'mapa2_rap.map'), (3115.3, 'mapa1_rap.map')]
7.09778395646
[(1336.9, 'mapa2.map'), (1469.5, 'mapa3.map'), (1397.5, 'mapa1.map'), (1247.9, 'agent.map')]
2.38346658225
[(2641.2, 'mapa3_rap.map'), (1336.9, 'mapa2.map'), (2848.8, 'agent_rap.map'), (1469.5, 'mapa3.map'), (1397.5, 'mapa1.map'), (2997.2, 'mapa2_rap.map'), (1247.9, 'agent.map'), (3115.3, 'mapa1_rap.map')]
4.11306830338

In [138]:
from pylab import *
x = linspace(0,10000,41)
y = []
for xi in x:
    make("-DMAP -DSUFICIENTE_TRUFA=" + str(xi))
    y.append(goodness(testmaps(rapidmaps), standard_fast_results))

In [139]:
figure()
plot(x, y, 'r')
xlabel('factor')
ylabel('trufa')
title('SUFICIENTE TRUFA - Rapid')
show()



In [141]:
from pylab import *
x = linspace(0,10000,41)
y = []
for xi in x:
    make("-DSUFICIENTE_TRUFA=" + str(xi))
    y.append(goodness(testmaps(slowmaps), standard_slow_results))

In [142]:
figure()
plot(x, y, 'r')
xlabel('factor')
ylabel('trufa')
title('SUFICIENTE TRUFA - Lento')
show()


Estudio del factor de giro

El factor de giro mide la relación entre la bondad de una casilla y el coste en tiempo de girar para alcanzarla. Cuanto mayor sea, menos valor se le otorga a las casillas a las que se deba girar para alcanzarlas. El factor original era de 1.5. Probaremos valores razonables entre 1 y 2.

Encontramos que no podemos establecer ninguna correlación razonable. Tomaremos un factor de giro de 1.1.


In [143]:
from pylab import *
x = linspace(1,1.5,11)
y = []
for xi in x:
    make("-DFACTOR_GIRO=" + str(xi))
    y.append(goodness(testmaps()))

In [144]:
figure()
plot(x, y, 'r')
xlabel('factor')
ylabel('trufa')
title('FACTOR GIRO')
show()


Estudio del factor de olor

Va a estudiar también un factor para decidir si está en un mapa de crecimiento lento o rápido. El valor es la media de oler varias veces entre los turnos 200 y 260. Este valor se analiza para varios casos.


In [11]:
make("-DOLOR -DSUFICIENTE_TRUFA=" + str(4500))
print testmaps()
print goodness(testmaps())

print testmaps(rapidmaps)
print goodness(testmaps(rapidmaps),standard_fast_results)
print testmaps(slowmaps)
print goodness(testmaps(slowmaps),standard_slow_results)


[(1995.2, 'mapa3_rap.map'), (1428.8, 'mapa2.map'), (2816.4, 'agent_rap.map'), (1276.3, 'mapa3.map'), (1489.0, 'mapa1.map'), (2887.3, 'mapa2_rap.map'), (1407.9, 'agent.map'), (2912.6, 'mapa1_rap.map')]
3.96813630684
[(1995.2, 'mapa3_rap.map'), (2816.4, 'agent_rap.map'), (2887.3, 'mapa2_rap.map'), (2912.6, 'mapa1_rap.map')]
6.42786054447
[(1428.8, 'mapa2.map'), (1276.3, 'mapa3.map'), (1489.0, 'mapa1.map'), (1407.9, 'agent.map')]
2.44966511652

Como puede apreciarse, no mejora de manera estadísticamente significativa. De hecho, empeora la solución global.