세 개의 문(1, 2, 3)이 있습니다. 이 세 개의 문 중 두 개의 방 안에는 염소가 있고, 오직 하나의 문 뒤에만 스포츠카가 있어서 상품을 받을 수 있습니다.
Alice가 1번 문을 선택했다고 해 봅시다. 이 때, Monty가 염소가 있는 3번 방을 열어서 보여줬고, 현재 선택한 1번 문에서 2번 문으로 선택을 바꿀 수 있는 기회를 줍니다.
이 때 선택을 바꾸는 것이 좋을까요 그대로 있는 것이 좋을까요?

몬티 홀 문제(Monty Hall Problem)는 미국의 TV쇼에서 유래한 아주 유명한 확률 문제입니다. 조건부 확률과 Bayesian에 대해서 파악할 수 있는 아주 좋은 예제가 될 것입니다. 이론적으로 푸는 방법은 링크 걸려있는 위키피디아를 참고하시면 될 것 같구요, 제가 focus하고 싶은 것은 python을 이용한 MONTE-CARLO 시뮬레이션을 통해 이론과 같은 결과가 나오는지 확인하는 것입니다.

먼저 문에 대해서 list로 정의하겠습니다.

>>> doors = [1, 2, 3]
>>> doors
[1, 2, 3]

가장 심플하게 Monty가 세 개의 문 중 random하게 하나를 선택해서 열어본다고 가정합시다.

import random

def monty_door_random(doors, alice_door, prize_door):
    monty_door = random.choice(doors)
    return monty_door

>>> monty_door_random(doors, None, None)
2 #1, 2, 3 모두 다 나올 수 있습니다.

Monty가 자동차가 들어있는 방의 문을 열 수는 없기 때문에, 이 경우는 발생하지 않는다고 가정합시다. 이제 10000번의 시뮬레이션을 통해 결정을 바꿨을 때 얼마의 확률로 자동차를 획득할 수 있는지를 구하면,

>>> N = 10000
>>> result = [None]*N
>>> for i in range(N):
...     alice_door = random.choice(doors)
...     prize_door = random.choice(doors)
...     monty_door = monty_door_random(doors, alice_door, prize_door)
...		
...		## 몬티가 경품의 방문을 열 수는 없으므로 이 때는 다시 뽑습니다.
...     while monty_door == prize_door:
...         alice_door = random.choice(doors)
...         prize_door = random.choice(doors)
...         monty_door = monty_door_random(doors, alice_door, prize_door)
...     remaining_doors = set(doors) - {alice_door} - {monty_door}
...     result[i] = random.choice(list(remaining_doors)) == prize_door

>>> print "Probability of getting prize when Monty chooses the door randomly?\n %f" % (float(sum(result))/N)
Probability of getting prize when Monty chooses the door randomly? 
 0.501400

사실 이렇게 무작위로 뽑았는데, Monty가 자동차 방문을 열었을 때 다시 실험하는 것은, Monty가 애시당초 자동차 방문을 열지 않는다고 가정하는 것과 같은 얘기입니다.

def monty_door_avoid_prize(doors, alice_door, prize_door):
    candidates = list(set(doors) - {prize_door})
    monty_door = random.choice(candidates)
    return monty_door

>>> result = [None]*N
>>> for i in range(N):
...     alice_door = random.choice(doors)
...     prize_door = random.choice(doors)
...     monty_door = monty_door_avoid_prize(doors, alice_door, prize_door)
...     remaining_doors = set(doors) - {alice_door} - {monty_door}
...     result[i] = random.choice(list(remaining_doors)) == prize_door

>>> print "Probability of getting prize when Monty chooses the door avoiding Prize?\n %f" % (float(sum(result))/N)
Probability of getting prize when Monty chooses the door avoiding Prize?
 0.501500

사실 이 경우 50%의 확률로 선택을 바꿨을 때 경품을 탄다는 것은 당연한 얘기입니다. 왜냐하면 경품이 들어있지 않는 방의 문을 Monty가 열었기 때문에 남은 2개의 방은 경품이 있거나 없거나 둘 중 하나일 것입니다. 따라서 50%의 확률이 되는 것입니다.

그렇다면 이번엔 Alice가 선택한 방을 제외한 2개의 문 중에 Monty가 하나를 선택해서 열었을 때, Alice가 선택을 번복해서 승리할 확률은 얼마일까요?

def monty_door_avoid_alice(doors, alice_door, prize_door):
    candidates = list(set(doors) - {alice_door})
    monty_door = random.choice(candidates)
    return monty_door

>>> result = [None]*N
>>> for i in range(N):
...     alice_door = random.choice(doors)
...     prize_door = random.choice(doors)
...     monty_door = monty_door_avoid_alice(doors, alice_door, prize_door)
...     while monty_door == prize_door:
...         alice_door = random.choice(doors)
...         prize_door = random.choice(doors)
...         monty_door = monty_door_avoid_alice(doors, alice_door, prize_door)
...     
...     remaining_doors = set(doors) - {alice_door} - {monty_door}
...     result[i] = random.choice(list(remaining_doors)) == prize_door

>>> print "Probability of getting prize when Monty chooses the door avoiding Alice?\n %f" % (float(sum(result))/N)
Probability of getting prize when Monty chooses the door avoiding Alice?
 0.500300

역시 이번에도 마찬가지로 50%가 나오네요. 사실상 결정을 번복하든 안하든 경품을 타는 것은 여전히 운에 달려있는 것으로 보입니다… ㅠㅠ 하지만 마지막으로 Monty가 Alice가 선택한 문과 경품이 들어있는 문 모두를 제외한 문들 중에서 선택해서 열어준다고 가정해보고 실험해 보겠습니다.

def monty_door_avoid_both(doors, alice_door, prize_door):
    candidates = list(set(doors) - {prize_door, alice_door})
    monty_door = random.choice(candidates)
    return monty_door

>>> result = [None]*N
>>> for i in range(N):
...     alice_door = random.choice(doors)
...     prize_door = random.choice(doors)
...     monty_door = monty_door_avoid_both(doors, alice_door, prize_door)
...     remaining_doors = set(doors) - {alice_door} - {monty_door}
...     result[i] = random.choice(list(remaining_doors)) == prize_door

>>> print "Probability of getting prize when Monty chooses the door avoiding both Alice and Prize?\n %f" % (float(sum(result))/N)
Probability of getting prize when Monty chooses the door avoiding both Alice and Prize?
 0.677000

오잉!? 결정을 번복했을 때에 경품을 탈 확률이 67%나 되네요!! 갑자기 이게 어떻게 된 일일까요??

사실 어떻게 보면 이 경우는 Alice가 선택한 문을 제외하고 Monty가 문을 뽑는 경우와 크게 다르지 않아 보입니다. 왜냐면 어차피 Monty는 경품이 들어있는 문을 선택하지 못하기 때문이죠. 그런데 막상 결과는 그렇지가 않습니다! 왜 그럴까요?

결과가 달라지는 이유는 다음과 같습니다. 먼저 Alice가 고른 방과 경품이 들어있는 방이 일치하는 경우는 (1, 1), (2, 2), (3, 3)이 있기 때문에 $\frac{1}{3}$의 확률이고 나머지 $\frac{2}{3}$의 확률로 일치하지 않습니다. 두 방이 일치했을 경우에는 결정을 번복하면 무조건 경품을 탈 수 없게 됩니다. 반면에 두 방이 일치하지 않을 경우에는 나머지 한 방만 Monty가 열 수 있기 때문에 무조건 선택을 번복해야 경품을 탈 수 있게 됩니다. 따라서 선택을 번복했을 때 $\frac{2}{3}$의 확률로 경품을 탈 수가 있는 것입니다.

그렇다면 왜 Alice가 고른 방만 제외했을 때는(즉 Monty가 고른 방이 경품 방이면 다시 실험하는 경우) 0.5의 확률이 나왔을까요? 그 이유는, Alice가 고른 방과 경품이 있는 방이 일치하지 않았을 때, 경품이 있는 방과 Monty가 고르는 방이 같을 확률은 $\frac{1}{2}$가 됩니다. 따라서 \[\frac{2}{3}\times \frac{1}{2} = \frac{1}{3} \] 로써 Alice가 고른 방과 경품이 있는 방이 일치했을 때의 확률과 같기 때문에, 선택을 번복했을 때 경품을 탈 확률이 0.5가 나오게 됩니다. 결국 어떤 상황이 주어지냐에 따라서 확률이 완전히 달라짐을 확인할 수 있죠??

많은 확률 문제들이 이런 미묘한 부분들 때문에 오답이 나오곤 합니다. 다시 한번 머리를 정화하고 연습할 수 있는 좋은 기회가 되셨으면 합니다 :)