Você está num jogo na TV e o apresentador pede para escolher uma entre 3 portas. Atrás de uma dessas portas tem uma Ferrari e nas outras duas temos cabras. Você escolhe uma porta. Depois, o apresentador retira uma porta que tem uma cabra e pergunta: você quer trocar de porta?
A princípio, você pode achar que sua probabilidade de ganhar é 1/2, já que uma das portas foi retirada, então não importa se você troca ou não. Mas a resposta é que sim, vale à pena trocar de porta! A probabilidade de vencer o jogo trocando a porta é de 2/3.
O problema de Monty Hall é talvez o mais eloquente exemplo de como a probabilidade pode confundir a mente humana. Esse problema desafiou a comunidade científica no final do século XX e chegou até a ser considerado um paradoxo. Recomendo ler o livro O Andar do Bêbado, de Leonard Mlodinow, que conta essa e muitas outras histórias interessantes sobre a probabilidade.
Existem várias formas de explicar por quê trocar a porta é a melhor estratégia. A que eu mais gosto é a do próprio Andar do Bêbado, que mostra que, quando você escolhe a primeira porta, você está apostando se acertou ou não a Ferrari. Se você apostar que acertou a Ferrari, não deve trocar a porta e, se você apostar que errou a Ferrari, deve trocar. A aposta de errar a Ferrari de primeira tem probabilidade 2/3, logo, vale à pena trocar.
Nesse post, mostramos uma solução alternativa, simples e elegante para o problema usando diagramas de influência e o pacote bnlearn
.
Redes bayesianas
As redes Bayesianas são o resultado da combinação de conceitos probabilísticos e conceitos da teoria dos grafos. Segundo Pearl, tal união tem como consequências três benefícios: i) prover formas convenientes para expressar suposições do modelo; ii) facilitar a representação de funções de probabilidade conjuntas; e iii) facilitar o cálculo eficiente de inferências a partir de observações.
Da teoria de probabilidades precisamos apenas de alguns resultados básicos sobre probabilidade condicional. Primeiramente, pela definição de probabilidade condicional, sabemos que
\[ p(x_1, x_2) = p(x_1)p(x_2|x_1). \]
Aplicando essa regra iterativamente para \(n\) variáveis, temos
\[ p(x_1, \dots, x_p) = \prod_j p(x_j|x_1,\dots, x_{j-1}). \]
Agora, imagine que, no seu problema, a variável aleatória \(X_j\) não dependa probabilisticamente de todas as variáveis \(X_1,\dots, X_{j-1}\), e sim apenas de um subconjunto \(\Pi_j\) dessas variáveis. Fazendo isso, a equação pode ser escrita como
\[ p(x_1, \dots, x_p) = \prod_j p(x_j|\pi_j). \]
Chamamos \(\Pi_j\) de pais de \(X_j\). Esse conjunto pode ser pensado como as variáveis que são suficientes para determinar as probabilidades de \(X_j\).
A parte mais legal das redes Bayesianas é que elas podem ser representadas a partir de DAGs (grafos direcionados acíclicos). No grafo, se \(X_1\) aponta para \(X_2\), então \(X_1\) é pai de \(X_2\). Por exemplo, esse grafo aqui
representa a distribuição de probabilidades \(p(x_1, \dots, x_5)\) com
\[ p(x_1, \dots, x_5) = p(x_1)p(x_2|x_1)p(x_3|x_1)p(x_4|x_3,x_2)p(x_5|x_4). \]
Diagrama de influências
Um diagrama e influências é uma rede Bayesiana com nós de decisão e utilidade (ganhos). Ou seja, é uma junção de três conceitos:
\[ \underbrace{\text{prob. condicional} + \text{grafos}}_{\text{rede Bayesiana}} + \text{teoria da decisão} = \text{diagrama de influências} \]
Na teoria da decisão, usualmente estamos interessados em maximizar a utilidade esperada. No diagrama, considerando a estrutura de probabilidades dada pela rede Bayesiana e as informações disponíveis, queremos escolher a decisão que faz com que, em média, nosso retorno seja mais alto.
Com diagramas de influências, é possível organizar sistemas complexos com múltiplas decisões, considerando diferentes conjuntos de informações disponíveis. É uma ferramenta realmente muito poderosa.
Voltando ao Monty Hall
Agora que sabemos um pouquinho de diagramas de influência, podemos desenhar o do Monty Hall:
O jogador tem duas decisões a tomar:
- \(D_1\) (
escolha_inicial
): A escolha da porta inicial (1
,2
,3
). - \(D_2\) (
trocar
): Trocar a porta ou não (s
,n
).
Também temos duas fontes de incerteza:
- \(X_1\) (
ferrari
): Em qual porta está a Ferrari (1
,2
,3
). - \(X_2\) (
porta_retirada
): Qual porta foi retirada (1
,2
,3
). Essa variável não é sempre aleatória: se eu escolho a porta 1 e a Ferrari está em 2, o apresentador é obrigado a retirar a porta 3. Se o apresentador tiver a opção de escolher (que acontece no caso da escolha inicial ser a Ferrari), o apresentador escolhe uma porta para retirar aleatoriamente.
Finalmente, temos um nó de utilidade:
- \(U_1\) (
result
): Ganhei a Ferrari (ganhei
,perdi
).
Em R, podemos construir a rede Bayesiana do problema utilizando o pacote bnlearn
:
# nós do grafo
nodes <- c("escolha_inicial", "ferrari", "porta_retirada", "trocar", "result")
# matriz de adjacências
edges <- matrix(
c("escolha_inicial", "porta_retirada",
"ferrari", "porta_retirada",
"porta_retirada", "trocar",
"trocar", "result",
"ferrari", "result",
"escolha_inicial", "result"),
ncol = 2,
byrow = TRUE)
edges
## [,1] [,2]
## [1,] "escolha_inicial" "porta_retirada"
## [2,] "ferrari" "porta_retirada"
## [3,] "porta_retirada" "trocar"
## [4,] "trocar" "result"
## [5,] "ferrari" "result"
## [6,] "escolha_inicial" "result"
# criando o grafo a partir de um grafo vazio
g <- bnlearn::empty.graph(nodes)
bnlearn::arcs(g) <- edges
O output desse conjunto de operações é um objeto do tipo bn
com várias propriedades pré calculadas pelo pacote bnlearn
:
g
Random/Generated Bayesian network
model:
[escolha_inicial][ferrari][porta_retirada|escolha_inicial:ferrari][trocar|porta_retirada]
[result|escolha_inicial:ferrari:trocar]
nodes: 5
arcs: 6
undirected arcs: 0
directed arcs: 6
average markov blanket size: 3.60
average neighbourhood size: 2.40
average branching factor: 1.20
generation algorithm: Empty
Com as especificação do problema dada, se gerarmos aleatoriamente todos os cenários, chegamos à essa combinação de casos equiprováveis (ver Extra 2)
Agora, vamos escrever todas as combinações possíveis de cenários e guardar num data.frame
chamado dados
:
escolha_inicial | ferrari | porta_retirada | trocar | result |
---|---|---|---|---|
1 | 1 | 2 | n | ganhei |
1 | 1 | 2 | s | perdi |
1 | 1 | 3 | n | ganhei |
1 | 1 | 3 | s | perdi |
1 | 2 | 3 | n | perdi |
1 | 2 | 3 | s | ganhei |
1 | 3 | 2 | n | perdi |
1 | 3 | 2 | s | ganhei |
2 | 1 | 3 | n | perdi |
2 | 1 | 3 | s | ganhei |
2 | 2 | 1 | n | ganhei |
2 | 2 | 1 | s | perdi |
2 | 2 | 3 | n | ganhei |
2 | 2 | 3 | s | perdi |
2 | 3 | 1 | n | perdi |
2 | 3 | 1 | s | ganhei |
3 | 1 | 2 | n | perdi |
3 | 1 | 2 | s | ganhei |
3 | 2 | 1 | n | perdi |
3 | 2 | 1 | s | ganhei |
3 | 3 | 2 | n | ganhei |
3 | 3 | 2 | s | perdi |
3 | 3 | 1 | n | ganhei |
3 | 3 | 1 | s | perdi |
Finalmente, ajustamos nossa rede Bayesiana, usando a função bnlearn::bn.fit()
.
fit <- bnlearn::bn.fit(g, dados)
A função bnlearn::cpquery()
(conditional probability query) serve para realizar uma consulta de probabilidades dada a rede ajustada. No nosso caso, a partir de uma escolha inicial qualquer \(d_1\), queremos saber o ganho ao trocar é maior que o ganho ao não trocar.
\[ \mathbb E(U_1\; |\; D_2 = \text{s}, D_1 = d_1) > \mathbb E(U_1\; |\; D_2 = \text{n}, D_1 = d_1). \]
Fazendo contas, isso equivale matematicamente a consultar se
\[ \mathbb P(U_1=\text{ganhei}\; |\; D_2 = \text{s}) > \mathbb P(U_1=\text{ganhei}\; |\; D_2 = \text{n}) \]
Agora, podemos consultar \(\mathbb P(U_1=\text{ganhei}\; |\; D_2 = \text{s})\) com nosso modelo!
set.seed(13) # reprodutibilidade
bnlearn::cpquery(
fitted = fit,
event = (result == "ganhei"), # o que queremos saber?
evidence = (trocar == "s"), # qual informação adicionar?
n = 5e6) # n grande para aumentar a precisão
[1] 0.6666704
E não é que dá 2/3 mesmo? Da mesma forma, temos
bnlearn::cpquery(fit, (result == "ganhei"), (trocar == "n"), n = 5e6)
[1] 0.3333187
Resolvido!
Wrap-up
- Vale à pena trocar a porta!
- Redes Bayesianas juntam grafos e probabilidades condicionais
- Diagramas de influência juntam redes Bayesianas e teoria da decisão
- Essas ferramentas podem ser utilizadas tanto para resolver Monty Hall quanto para ajudar em sistemas complexos.
É isso pessoal. Happy coding ;)
Extra
Se você ficou interessada em como eu fiz o diagrama, utilizei o pacote DiagrammeR
. O código está aqui:
diagrama <- "
graph LR;
A{D1Escolha inicial}-->B(X2Porta retirada);
C(X1Ferrari)-->B;
B-->D{D2Trocar porta};
D-->E[U1Ganhar]
C-->E
A-->E
"
# tweak para centralizar e grifar as variáveis
diagrama <- stringr::str_replace_all(
diagrama,
pattern = "([XDU][0-9])",
replacement = "<center><b>\\1</b></center><br/>")
DiagrammeR::DiagrammeR(diagrama)
Extra 2
É possível simular os dados que coloquei no post com uma função simples, que adicionei abaixo. Na verdade, o fato de eu ter considerado somente as combinações únicas de cenários e não os dados simulados abaixo é um pouco roubado, e só funciona porque os cenários calham de ser, de fato, equiprováveis.
set.seed(13)
simular_monty_hall <- function(z = 0) {
v <- 1:3 # opcoes
escolha_inicial <- sample(v, 1) # escolha inicial aleatoria
ferrari <- sample(v, 1) # ferrari aleatoria
# qual porta retirar?
if (escolha_inicial == ferrari) {
porta_retirada <- sample(setdiff(v, ferrari), 1)
} else {
porta_retirada <- setdiff(v, c(escolha_inicial, ferrari))
}
# trocar porta?
trocar <- sample(c("s", "n"), 1)
# calculando resultado
if (trocar == "s") {
escolha_final <- setdiff(v, c(escolha_inicial, porta_retirada))
} else {
escolha_final <- escolha_inicial
}
result <- ifelse(escolha_final == ferrari, "ganhei", "perdi")
# guardando no BD
tibble::tibble(escolha_inicial, ferrari, porta_retirada, trocar, result)
}
dados_simulados <- purrr::map_dfr(seq_len(1e4), simular_monty_hall) %>%
dplyr::mutate_all(as.factor)
dplyr::glimpse(dados_simulados)
Observations: 10,000
Variables: 5
$ escolha_inicial <fct> 3, 1, 2, 1, 1, 1, 3, 1, 2, 3, 3, 1, 3, 1, 2, 2, 2,...
$ ferrari <fct> 1, 1, 2, 1, 1, 2, 3, 3, 1, 2, 3, 3, 2, 1, 1, 3, 1,...
$ porta_retirada <fct> 2, 3, 1, 3, 2, 3, 2, 2, 3, 1, 1, 2, 1, 2, 3, 1, 3,...
$ trocar <fct> n, s, s, n, s, n, n, n, n, s, s, s, s, n, n, s, n,...
$ result <fct> perdi, perdi, perdi, ganhei, perdi, perdi, ganhei,...
Os dados do post podem ser obtidos fazendo isso aqui:
dados_simulados %>%
dplyr::distinct() %>%
dplyr::arrange(escolha_inicial, ferrari)
Agradecimentos: Rafael Stern, que me convenceu de que vale à pena mostrar os dados simulados 😉