Ciencia de datos

CP-UCB para un problema Bandido Multibrazo (Multi-Armed Bandit)

La familia de algoritmos UCB es una de las que mejores resultados producen a la hora de enfrentarse a problemas tipo bandido multibrazo. En la que en bandido se selecciona teniendo en cuenta el intervalo de confianza de la recompensa empírica. Así no se selecciona el bandido cuya recompensa observada sea la mayor, sino aquel en el que estadísticamente se puede esperar el máximo valor para un nivel de confianza dado sea el mayor. Permitiendo explorar de este modo los estados con un nivel de incertidumbre elevado y explotar aquellos con mayor esperanza. Entre esta familia de algoritmos se encuentra el UCB de Clopper-Pearson (CP-UCB) en el cual se asume que la recompensa se genera mediante una distribución binomial.

CP-UCB

El algoritmo CP-UCB propone asumir que las recompensas obtenidas provienen de una distribución binomial. Así, a partir de los datos empíricos, se puede calcular para cada uno de los bandidos su intervalo de confianza para un nivel de significancia dada. Nivel de significación que se reducirá a medida que aumente el número de muestras.

X_{CP-UCB} = U^{CP} \left(R_j, n_j, \frac{1}{N \ln{N}^c} \right)

En donde U^{CP} es la función que nos calcular el intervalo de confianza de una distribución binomial, R_j las recompensas totales observadas en en bandido j, n_j las veces que se ha jugado con el bandido j, N el número de veces totales que se ha jugado y c un parámetro que se puede seleccionar para cambiar la velocidad de aprendizaje.

Implementación en Python de CP-UCB

Para implementar CP-UCB necesitamos una función con la que se puede calcular el intervalo de confianza de una distribución binomial. Una función como proportion_confint la cual se puede encontrar en el paquete statsmodels.

Una vez que tenemos la función con la que calcular el intervalo de confianza, como en ocasiones anteriores, nos podemos basar en la clase Epsilon implementada en la entrada de valores iniciales optimistas. Para ello solamente hay que crear una clase hija con el siguiente código.

from statsmodels.stats.proportion import proportion_confint

class CPUCB(Epsilon):
    def __init__(self, bandits, c=1, method='beta'):
        self.bandits = bandits
        self.c = c
        self.method = method
        
        self.reset()
        
    def run(self, episodes=1):
        for i in range(episodes):
            # Selección del bandido
            bandit = self.select()
            
            # Obtención de una nueva recompensa
            reward = bandits[bandit].pull()
            
            # Agregación de la recompensa al listado
            self._rewards.append(reward)
            
            # Actualización de la media
            self._plays[bandit] += 1
            self._mean[bandit] = (1 - 1.0/self._plays[bandit]) * self._mean[bandit] \
                                 + 1.0/self._plays[bandit] * reward
            self._reward[bandit] += reward
        
        return self.average_reward()
    
    
    def select(self):
        num_bandits = len(self.bandits)
        total = len(self._rewards)
        
        if total < num_bandits:
            bandit = total
        else:
            ucb = [0] * num_bandits
                
            for i in range(num_bandits):
                confidence = 1 / (total * np.log(total) ** self.c)
                ucb[i] = proportion_confint(self._reward[i], self._plays[i], confidence, method=self.method)[1]            
            
            max_bandits = np.where(ucb == np.max(ucb))[0]
            bandit = np.random.choice(max_bandits)
            
        return bandit
    
    
    def reset(self, initial=None):
        self._rewards = []
        self._plays = [0] * len(self.bandits)
        self._mean = [0] * len(self.bandits)
        self._reward = [0] * len(self.bandits)

En este caso ha sido necesario cambiar el método run() ya que, además de los valores usados anteriormente, es necesario guardar también la recompensa obtenida. Valor que se guarda en la propiedad privada _reward. Por otro lado, también se ha modificado el método select(), usando la función proportion_confint para calcular el valor usado para seleccionar el bandido. Finalmente, en el método reset() de la clase hay que agregar la inicialización de la propiedad _reward.

Evaluación de los resultados de CP-UCB

Una vez implementado el algoritmo se puede ver cómo funciona este en comparación con UCB1. Para lo que se puede usar los bandidos basados en la distribución binomial de las entradas anteriores. Así, se puede usar el siguiente código para evaluar ambos algoritmos.

np.random.seed(0)

bandits = [Bandit(0.02), Bandit(0.06), Bandit(0.10)]

cpucb = CPUCB(bandits)
ucb1 = UCB1(bandits)

cpucb.run(200000)
ucb1.run(200000)

cpucb.plot(True, label='CPUCB')
ucb1.plot(True, True, label='UCB1')
plt.legend()

print(cpucb._plays)
print(ucb1._plays)

Obteniendo como resultado la siguiente figura.

Evolución de la recompensa promedio con el numero de tiradas para el algoritmo CP-UCB y UCB1 con tres bandidos basados en una distribución binomial

Lo primero que se puede ver en esta gráfica es que CP-UCB converge mucho más rápido hacia la recompensa óptima que UCB1. Algo que queda de manifiesto al comprobar que solamente ha jugado con el primer y segundo bandido 169 y 1263 veces respectivamente. Sumando ambas menos que las veces que UCB1 ha jugado con el primer bandido (3089). Lo que se traduce en haber seleccionado muchas más veces el bandido óptimo.

Este resultado tiene un precio. El uso de la función proportion_confint en cada una de las selecciones hace que este algoritmo sea uno de los más lentos, tardando 10 veces más que UCB2, UCB1-Tuned o UCB1-Normal.

En este caso el resultado es en cierta medida el esperado, ya que el bandido usa una distribución binomial para generar la recompensa. Por lo que el algoritmo se adapta perfectamente al problema.

Conclusiones

En esta entrada hemos visto otro algoritmo de la familia UCB en el que se asume que las recompensas de los bandidos proceden de una distribución binomial. Por lo que los resultados obtenidos en los bandidos de prueba han sido de los mejores. Algo que también tiene un coste, el cálculo de intervalo de confianza es más costosos que otros algoritmos de la familia, por lo que usar este se traduce en la necesidad de más tiempo para tomar las decisiones. Lo que puede ser un problema en algunas situaciones.

Imagen de Paul Edney en Pixabay

¿Te ha parecido de utilidad el contenido?

Daniel Rodríguez

Share
Published by
Daniel Rodríguez

Recent Posts

Analytics Lane lanza ScoreFlow, un SaaS para construir y desplegar scorecards de crédito

En Analytics Lane seguimos evolucionando nuestras herramientas y damos un paso más con el lanzamiento…

4 días ago

DBSCAN y la selección de ε: teoría, intuición y aplicación práctica

Cuando hablamos de clustering, lo primero que viene a la mente suele ser k-means. Pero…

5 días ago

El bestiario de los indicadores económicos absurdos: El zoo patrio

Cualquier país desarrollado tiene sus propios indicadores folclóricos. España, por motivos que tienen mucho que…

1 semana ago

Por qué el banco te ofrece un 3% TAE y no es lo que parece

Entras a la web de tu banco. En la página principal, un banner llamativo: “Depósito…

2 semanas ago

Analytics Lane lanza la versión 1.3 del laboratorio con nuevas herramientas de evaluación de modelos y utilidades prácticas

Seguimos ampliando el laboratorio de Analytics Lane con el lanzamiento de la versión 1.3, disponible…

2 semanas ago

This website uses cookies.