关于“三门问题(Monty Hall problem)”的仿真与论证
来自维基百科的介绍:
蒙提霍尔问题(英文:Monty Hall problem),亦称为蒙特霍问题、山羊问题或三门问题,是一个源自博弈论的数学游戏问题,参赛者会看见三扇门,其中一扇门的里面有一辆汽车,选中里面是汽车的那扇门,就可以赢得该辆汽车,另外两扇门里面则都是一只山羊。当参赛者选定了一扇门,主持人会开启另一扇是山羊的门;并问:“要不要换一扇门?”
一、计算机仿真
import numpy as np
def choose(totlen):
assert totlen>0 and isinstance(totlen,int)
for i in range(totlen):
if totlen*np.random.rand()<i+1:
return i
class theproblem:
def __init__(self,num_doors):
assert num_doors>0 and isinstance(num_doors,int)
self.num_doors=num_doors
self.car_no=choose(self.num_doors)
self.unopens=list()
self.select=-1
for i,v in enumerate(range(self.num_doors)):
self.unopens.append(i)
def get_goat(self,opendoor=True):
if len(self.unopens)<=0:
return None
elif len(self.unopens)==1 and (self.unopens[0]==self.car_no or self.unopens[0]==self.select):
return None
elif len(self.unopens)==2 and (self.car_no in self.unopens and self.select in self.unopens and self.car_no!=self.select):
return None
goat=np.random.choice(self.unopens,1)[0]
while goat==self.car_no or goat==self.select:
goat=np.random.choice(self.unopens,1)[0]
if opendoor:
self.unopens.remove(goat)
return goat
def _open_door(self,door_no):
self.unopens.remove(door_no)
return (door_no==self.car_no,door_no)
def select_door(self,switch=False,toopen=False):
pre=self.select
if switch and len(self.unopens)>1:
while pre==self.select:
self.select=np.random.choice(self.unopens,1)[0]
elif self.select<0:
self.select=np.random.choice(self.unopens,1)[0]
if toopen:
return self._open_door(self.select)
return self.select
def restart(self):
self.car_no=choose(self.num_doors)
self.unopens=list()
self.select=-1
for i,v in enumerate(range(self.num_doors)):
self.unopens.append(i)
def one_turn(theguess,debug=False):
theguess.restart()
v=theguess.select_door()
if debug:
print("First the player selected door {}.".format(v))
v=theguess.get_goat()
if debug:
print("And behind the door {} is a goat.".format(v))
switched=(np.random.rand()<0.5)
if debug:
print("Does the player switch his selected door? {}.".format(switched))
res, no =theguess.select_door(switch=switched,toopen=True)
if debug:
print("Does the player win the car in his chosen door {}? {}.".format(no,res))
print("Car is behind the door {}.".format(theguess.car_no))
return (int(res),int(switched))
guess=theproblem(3)
turns=50000
resarr=np.zeros(turns,dtype=int)
switcharr=np.zeros(turns,dtype=int)
for i in range(turns):
resarr[i],switcharr[i]=one_turn(guess)
def pair_counts(arr1,arr2,val1,val2):
match1=(arr1==val1)
match2=(arr2==val2)
return sum(match1*match2)
ff=pair_counts(resarr,switcharr,0,0)
tf=pair_counts(resarr,switcharr,0,1)
ft=pair_counts(resarr,switcharr,1,0)
tt=pair_counts(resarr,switcharr,1,1)
print("Percentage of not having a switch and lose: {}%".format(100*ff/turns))
print("Percentage of having a switch and lose: {}%".format(100*tf/turns))
print("Percentage of not having a switch and win: {}%".format(100*ft/turns))
print("Percentage of having a switch and win: {}%".format(100*tt/turns))
Percentage of not having a switch and lose: 33.156%
Percentage of having a switch and lose: 16.582%
Percentage of not having a switch and win: 16.692%
Percentage of having a switch and win: 33.57%
print("Percentage of having a win if switched: {0:2.3f}%".format(tt/(tf+tt)*100))
print("Percentage of having a switch in win: {0:2.3f}%".format(tt/(ft+tt)*100))
print("Percetage of the cases where switch is better: {0:2.3f}%".format((tt+ff)/turns*100))
print("Percetage of the cases where switch is worse: {0:2.3f}%".format((tf+ft)/turns*100))
Percentage of having a win if switched: 66.937%
Percentage of having a switch in win: 66.790%
Percetage of the cases where switch is better: 66.726%
Percetage of the cases where switch is worse: 33.274%
二、理论论证
设
S
∈
{
0
,
1
}
S\in\{0,1\}
S∈{0,1} 表示选手是否改变选择,
W
∈
{
0
,
1
}
W\in\{0,1\}
W∈{0,1}表示该选手是否赢得汽车。那么各事件的概率就可以表示为
P
(
S
,
W
)
P(S,W)
P(S,W)。设选手改变选择的概率为
p
p
p。如果主持人知道每扇门背后有什么东西:
P
(
0
,
0
)
=
2
3
−
2
3
p
P(0,0)=\frac{2}{3}-\frac{2}{3}p
P(0,0)=32−32p
P ( 0 , 1 ) = 1 3 − 1 3 p P(0,1)=\frac{1}{3}-\frac{1}{3}p P(0,1)=31−31p
P ( 1 , 0 ) = 1 3 p P(1,0)=\frac{1}{3}p P(1,0)=31p
P
(
1
,
1
)
=
2
3
p
P(1,1)=\frac{2}{3}p
P(1,1)=32p
当
p
=
1
2
p=\frac{1}{2}
p=21时,其结果和计算机仿真的结果相吻合。
然而,如果主持人不知道每扇门背后有什么,他不过是运气好到每次都能猜对,那么:
P
(
0
,
0
)
=
1
3
−
1
3
p
P(0,0)=\frac{1}{3}-\frac{1}{3}p
P(0,0)=31−31p
P ( 0 , 1 ) = 1 3 − 1 3 p P(0,1)=\frac{1}{3}-\frac{1}{3}p P(0,1)=31−31p
P ( 1 , 0 ) = 1 3 p P(1,0)=\frac{1}{3}p P(1,0)=31p
P
(
1
,
1
)
=
1
3
p
P(1,1)=\frac{1}{3}p
P(1,1)=31p
如此我们就能得到
P
(
W
=
1
∣
S
=
1
)
=
1
3
,
P
(
W
=
1
∣
S
=
0
)
=
1
3
P(W=1\mid S=1)=\frac{1}{3}, P(W=1\mid S=0)=\frac{1}{3}
P(W=1∣S=1)=31,P(W=1∣S=0)=31
这正好符合我们的直觉,即机会是“对半开”的。然而这个直觉不符合数学证明的要求,因为概率论要求全部可能事件的概率之和为1。这也就意味着,当我们在用直觉思考三门问题时,我们实际是假定了主持人在独立地做出选择,然后排除那些选错的可能性,我们用这样的思维模式来逼近“主持人在知晓的情况下做选择”这样的情形。但概率论不是这样来计算的。概率论必然会将每一个可能事件映射到
[
0
,
1
]
[0,1]
[0,1]区间,来作为该事件的概率。这种“排除可能性vs事件必然有概率”的差异,在我看来就是反直觉的源头。