Entendiendo el problema de Monty Hall con redes bayesianas

In [239]:
import random
import datetime
import numpy
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (5,3)

El problema Monty Hall

Vi este problema en la película “21”: En un concurso hay 3 puertas y una de ellas tiene un premio. Te dan a elegir una de las puertas y luego, sin abrirla, te abren una de las dos puertas restantes que no tenga premio y te ofrecen cambiar tu elección. ¿Decidís quedarte con tu elección inicial o elegir la otra puerta que queda?

En el momento, se me ocurrió que da igual y que no importa cambiar o quedarse con la misma elección. Pero resultó que al exponer una de las puertas secretas, hay mayor probabilidad de ganar el premio cambiando de elección.

Luego de leer el artículo en Wikipedia, he logrado entenderlo y quise comprenderlo mejor haciendo simulaciones simples en python.

image.png

Para esto me compliqué un poco e hice una clase que maneja la lógica para luego hacer las simulaciones:

In [3]:
class Case:
    """ Esta clase maneja la lógica del juego.
        * Se esconde un premio en una de las puertas 
        * Se puede elegir una puerta (pick)
        * Se puede ver tras una puerta al azar (que no tenga premio) (getdoor)
        * Finalmente se puede mantener la elección (hold) o cambiar de elección (change)
    """
    def __init__(self, doors = 3):
        self.ndoors = doors
        self.doors = list(range(1,self.ndoors+1))
        self.car = random.randint(1,self.ndoors)
    def pick(self, ):
        self.picked = random.choice(self.doors)
        self.doors.remove(self.picked) # Can't pick it again
    def getdoor(self):
        for i in range(0, 20):
            self.opendoor = random.choice(self.doors)
            if self.opendoor != self.car:
                self.doors.remove(self.opendoor)  # Can't pick an opened door
                return
        raise Exception("couldn't find a non-car door")
    def hold(self, ):
        pass
    def change(self):
        self.pick()
    def iscar(self, n = None):
        return self.car == (n if n is not None else self.picked)

Quedarse en la elección inicial

La estrategia de quedarse o “hold” es la más simple. Si se tiene 1 premio entre 3 posibles elecciones, la probabilidad de ganar es de 1/3.

In [240]:
# Hold strategy:
def hold_strategy(n = 1000):
    wins = 0
    data = []
    for i in range(0,n):
        c = Case()
        c.pick()
        c.hold()
        wins += 1 if c.iscar() else 0
        data.append(wins/(i+1))
    return data, wins
ps = []
n = 200
plt.grid(); plt.axhline(1/3, color="red")
for i in range(50):  
    #random.seed(i)
    data, wins = hold_strategy(n)
    plt.plot(data, linewidth=0.6, alpha=0.6); 
    ps.append(wins/len(data))

plt.title("p = " + str(numpy.mean(ps)))
plt.xlabel("casos"); plt.ylabel("probabilidad")
plt.show()

Cambiar la elección inicial

La estrategia de cambiar me pareció, intuitivamente, igual a la estrategia de quedarse. Pero resulta que es mejor cambiar de elección, lo que da 2/3 de probabilidad de ganar.

In [241]:
# Change strategy:
def change_strategy(n = 1000):
    wins = 0
    data = []
    for i in range(0,n):
        c = Case()
        c.pick()
        c.getdoor()
        c.change()
        wins += 1 if c.iscar() else 0
        data.append(wins/(i+1))
    return data, wins
plt.grid(); plt.axhline(2/3, color="red")
ps = []
n = 200
for i in range(50):  
    data,wins = change_strategy(n)
    plt.plot(data, linewidth=0.6, alpha=0.6)
    ps.append(wins/len(data))
plt.title("p = " + str(numpy.mean(ps)))
plt.xlabel("casos"); plt.ylabel("probabilidad")
plt.show()

Boletos de lotería

Supongamos que Ana compra 1 boleto de lotería y Beto compra 2 (la versión en español de Alice y Bob). Supongamos que hay en total 3 boletos de lotería y un sólo boleto se gana todo por lo que Ana tiene un 33% de probabilidad de ganar y Beto tiene 66%. Beto raspa uno de sus boletos (al azar) y ambos descubren que no gana nada. Ana le ofrece cambiar su boleto por el boleto restante de Beto pero ya tiene el conocimiento del boleto fallido. ¿Le conviene a Ana intercambiar?

Al principio pensé que en este escenario sí le conviene a Ana por ser equivalente al problema de Monty Hall pero luego descubrí que no:

In [242]:
class Loteria:
    def __init__(self, boletos = 10):
        self.ganador = random.randint(1,boletos)
        self.boletos = list(range(1,boletos+1))
    def get_boleto(self):
        boleto = random.choice(self.boletos)
        self.boletos.remove(boleto)
        return boleto
    def wins_boleto(self, b):
        return self.ganador == b
In [246]:
def TestLoteria(tries = 200):
    hold = 0.0; change = 0.0; bchange = 0; bhold = 0
    for i in range(tries):
        lot = Loteria(3)
        a = lot.get_boleto()
        b1, b2 = lot.get_boleto(), lot.get_boleto()
        if not lot.wins_boleto(b1):
            hold += 1 if lot.wins_boleto(a) else 0
            change += 1 if lot.wins_boleto(b2) else 0
            bchange += 1 if lot.wins_boleto(a) else 0
            bhold += 1 if lot.wins_boleto(b2) else 0
        else:
            bchange += 1
            bhold += 1
    return hold/tries, change/tries, bchange/tries, bhold/tries
In [247]:
results = TestLoteria(10000)
print("Ana - prob. de ganar si se queda (hold): ", results[0])
print("Ana - prob. de ganar si cambia (change): ", results[1])
print("Beto - prob. de ganar si se queda (hold): ", results[2])
print("Beto - prob. de ganar si cambia (change): ", results[3])
Ana - prob. de ganar si se queda (hold):  0.3363
Ana - prob. de ganar si cambia (change):  0.3293
Beto - prob. de ganar si se queda (hold):  0.6707
Beto - prob. de ganar si cambia (change):  0.6637

La diferencia en este escenario es que Beto no sabe qué boleto es el ganador mientras que en el problema, el presentador abre la puerta sabiendo que no hay un premio en esa puerta y la abre evitando encontrar el premio. Es decir que la probabilidad de que abra cada una de las dos puertas restantes no es uniforme y en especial no es independiente. En Wikipedia pueden ver la solución usando regla de Bayes. H1, H2, H3 son los eventos de que el presentador abra la puerta 1, 2, 3 respectivamente. C1, C2, C3 son los eventos que indican en dónde está el premio y X1, X2, X3 son los eventos de la elección de cada puerta. Al final lo que se busca es la probabilidad de que el carro esté en la puerta 2 (C2) dado que se haya elegido la puerta 1 (X1) y el presentador haya abierto la puerta 3 (H3), es decir P(C2| H3, X1).

image.png image.png

Son estas probabilidades condicionales las que producen este resultado.

Explicándolo con redes bayesianas

Pomegranate es un paquete de python para programar modelos probabilísticos. En este tutorial resuelven este problema de Monty Hall utilizando redes bayesianas. Este ejemplo es muy bueno pero quiero aportar a este explicando cómo se construye la tabla de probabilidades condicionales para la elección de la puerta que el presentador abre.

In [256]:
import itertools
import pomegranate
import pandas as pd
In [258]:
def get_cond_prob(case):
    # case[0] ==> participante
    # case[1] ==> premio
    # case[2] ==> presentador
    # El presentador nunca abre la puerta con premio ni la misma puerta que el participante
    if case[1] == case[2] or case[0] == case[2]:
        return 0
    # El presentador abre ambas puertas por igual si no tienen premio
    if case[0] == case[1]:
        return 1/2
    # Si el participante no elige el premio inicialmente entonces el presentador sólo puede abrir la puerta sin premio
    if case[0] != case[1] and case[1] != case[2]:
        return 1
table = []
for case in itertools.product("123", "123", "123"):
    table.append( [ case[0], case[1], case[2], get_cond_prob(case) ] )
In [259]:
pd.DataFrame(table, columns=["Participante", "Premio", "Presentador", "CondProb"])
Out[259]:
Participante Premio Presentador CondProb
0 1 1 1 0.0
1 1 1 2 0.5
2 1 1 3 0.5
3 1 2 1 0.0
4 1 2 2 0.0
5 1 2 3 1.0
6 1 3 1 0.0
7 1 3 2 1.0
8 1 3 3 0.0
9 2 1 1 0.0
10 2 1 2 0.0
11 2 1 3 1.0
12 2 2 1 0.5
13 2 2 2 0.0
14 2 2 3 0.5
15 2 3 1 1.0
16 2 3 2 0.0
17 2 3 3 0.0
18 3 1 1 0.0
19 3 1 2 1.0
20 3 1 3 0.0
21 3 2 1 1.0
22 3 2 2 0.0
23 3 2 3 0.0
24 3 3 1 0.5
25 3 3 2 0.5
26 3 3 3 0.0
In [261]:
participante = pomegranate.distributions.DiscreteDistribution({'1': 1./3, '2': 1./3, '3': 1./3})
premio = pomegranate.distributions.DiscreteDistribution({'1': 1./3, '2': 1./3, '3': 1./3})
presentador = pomegranate.distributions.ConditionalProbabilityTable(table, [participante, premio])
In [273]:
# Ahora un poco de copy/paste para reusar el modelo del ejemplo de Pomegranate. 
# Aquí se construye la red bayesiana:
s1 = pomegranate.State(participante, name="partcp")
s2 = pomegranate.State(premio, name="premio")
s3 = pomegranate.State(presentador, name="prestdr")
model = pomegranate.BayesianNetwork("Monty Hall Problem")
# Add the three states to the network 
model.add_states(s1, s2, s3)
# Add edges which represent conditional dependencies, where the second node is 
# conditionally dependent on the first node (Monty is dependent on both guest and prize)
model.add_edge(s1, s3)
model.add_edge(s2, s3)
model.bake()

Se han conectado las variables S1 y S2 hacia S3, es decir que la puerta que abre el presentador depende de la puerta que elije el participante y la puerta que contiene el premio.

Ahora para predecir cuál es la probabilidad de que el premio esté en la puerta 1 o 2, dado que se el participante elige la puerta 1 y luego el presentador abre la puerta 3 (sin premio):

In [270]:
# Se deja en None el estado en la red bayesiana que se quiere predecir.
model.predict_proba([["1", None, "3"]])
Out[270]:
[array(['1',
        {
     "class" : "Distribution",
     "dtype" : "str",
     "name" : "DiscreteDistribution",
     "parameters" : [
         {
             "1" : 0.3333333333333334,
             "2" : 0.6666666666666664,
             "3" : 0.0
         }
     ],
     "frozen" : false
 },
        '3'], dtype=object)]

Hay 1/3 de probabilidad que el premio esté en la puerta que se eligió al principio y 2/3 de probabilidad de que al cambiar se encuentre el premio. Y esto es así por la probabilidad condicional que se programó arriba y muestra cómo se comporta el presentador.

In [ ]: