三门问题,也叫蒙提霍尔问题(Monty Hall Problem)
以电视节目 - Let's make a deal的主持人蒙提霍命名的一个反直觉问题。
游戏简介
假设有3个门。 其中一个后面藏着宝藏,其余2个都是空门(美国节目中1个后面有汽车,其余的2个是山羊)。
游戏开始:
首先你先选择一张门,
选好后,主持人帮你在其余2扇没有被选择的门中打开一扇没奖的门。
这时候你有一个选择权, 换门还是不换门?
直觉上换不换几率都是50%,那到底几率是多少? 换是不是得奖的机会大一些?
Mueser 和 Granberg 透过厘清细节,以及对主持人的行为加上明确的介定,提出了对这个问题的一种不含糊的陈述 ︰
-
现在有三扇门,只有一扇门有汽车,其余两扇门的都是山羊。
-
汽车事前是等可能地被放置于三扇门的其中一扇后面。
-
参赛者在三扇门中挑选一扇。他在挑选前并不知道任意一扇门后面是什麽。
-
主持人知道每扇门后面有什么。
-
如果参赛者挑了一扇有山羊的门,主持人必须挑另一扇有山羊的门。
-
如果参赛者挑了一扇有汽车的门,主持人等可能地在另外两扇有山羊的门中挑一扇门。
-
参赛者会被问是否保持他的原来选择,还是转而选择剩下的那一扇门。
转换选择可以增加参赛者拿到汽车的机会吗?
你可以在可汗学院上学习相应的内容:
https://www.khanacademy.org/math/precalculus/prob-comb/dependent-events-precalc/v/monty-hall-problem
下面我们分别用Matlab和Python来实现解答这个问题:
Matlab:
trials = 1000;
% 首先你随机从1,2,3个门和隐藏的车中做随机的选择。
car_door = randi(3,trials,1);
% 你做一个猜测选择一个门,那个地方有一个车
choice = randi(3,trials,1);
% 让我们看一下前五次的选择
T = table(car_door,choice);
disp(T(1:5,:))
%Monty 打开一个未选择的门,并显示一只山羊。 如果车门和选择的门不同,
%Monty 只有一个选择。
goat_door = zeros(trials,1); goat_door(car_door + choice == 3) = 3; goat_door(car_door + choice == 4 & car_door ~= choice) = 2; goat_door(car_door + choice == 5) = 1; goat_door(goat_door == 0 & choice == 1) = randsample([2,3],1); goat_door(goat_door == 0 & choice == 2) = randsample([1,3],1); goat_door(goat_door == 0 & choice == 3) = randsample([1,2],1);
T = table(car_door,choice,goat_door);
disp(T(1:5,:))
car_door choice goat_door ________ ______ _________ 3 2 1 3 2 1 1 3 2 3 1 2 2 2 3
simulation = zeros(3,1);
simulation(1) = sum((car_door - choice) == 0)/trials*100;
fprintf('Win Rate if stick to your original choice: %.2f%%\n',simulation(1))
Win Rate if stick to your original choice: 33.70%
我们看看如何能把胜率提高呢?
switch_choice = zeros(trials,1); switch_choice(goat_door + choice == 3) = 3; switch_choice(goat_door + choice == 4) = 2; switch_choice(goat_door + choice == 5) = 1;
我们更新一个算法来解决这个问题。
T = table(car_door,choice,goat_door,switch_choice);
disp(T(1:5,:))
simulation(3) =
sum((car_door - switch_choice) == 0)/trials*100;
fprintf('Win Rate if switch your choice: %.2f%%\n',simulation(3))
car_door choice goat_door switch_choice ________ ______ _________ _____________ 3 2 1 3 3 2 1 3 1 3 2 1 3 1 2 3 2 2 3 1 Win Rate if switch your choice: 66.30%
这个胜率怎么提高的呢?
这是我们最熟悉的公式了。
P(H)
汽车隐藏在三个门后的任意一个,你选择了门1.门1是车门的概率是1/3。 这是先验的,或P(H)。
prior = ones(3,1)*1/3
prior = 0.33333 0.33333 0.33333 如果汽车在门1后面,那么蒙蒂可以选择门2或3。 所以他选择门2的可能性是1/2。 如果汽车行为门2,那么蒙蒂不能选择它。 所以概率是0.困惑? 我将在下面回顾这个例子。 如果汽车在门3后面,那么蒙蒂不得不选择门2. 所以他选择门2的可能性是1。 P(E|H)
likelihood = [1/2;0;1]
likelihood = 0.5 0 1
joint_prob = likelihood .* prior
joint_prob = 0.16667 0 0.33333
在这一点上,您可以坚持使用您的原始选择或开关。 我的直觉是,这将是一个50/50的命题。 如果你选择留下来,这里是模拟胜率。
simulation = zeros(3,1);
simulation(1) =
sum((car_door - choice) == 0)/trials*100;
fprintf('Win Rate if stick to your original choice: %.2f%%\n',simulation(1))
后验P(H | E)应该非常接近模拟结果。
posterior = joint_prob/sum(joint_prob); compare = table(posterior,simulation, 'RowNames',{'Stay','N/A','Switch'}); disp(compare([1 3],:))
posterior simulation _________ __________ Stay 0.33333 33.7 Switch 0.66667 66.3
下面我们看下python在这个问题上的解决:
1.这个可以用 random.randint来实现。 randint(start, end, size) 随机选择的3个门中间的一个
1 def simulate_prizedoor(nsim): 2 return np.random.randint(0, 3, (nsim))
2.然后我们可以定义下,一开始选择的一扇门。 这里可以用固定选择法,也可以用随机法,数据大的情况下差别不大。这里我们直接用了固定选择第一扇门(0)
1 def simulate_guess(nsim): 2 return np.zeros(nsim, dtype=np.int)
3.然后我们 模拟主持人,开一扇没有奖品的门。
1 def goat_door(prizedoors, guesses): 2 result = np.random.randint(0, 3, prizedoors.size) 3 while True: 4 bad = (result == prizedoors) | (result == guesses) 5 if not bad.any(): 6 return result 7 result[bad] = np.random.randint(0, 3, bad.sum())
这里要实现的逻辑是, 先生成一个0到2的随机数。 然后匹配直到 不等于 我们一开始的选择的那扇门 或 宝藏存在的那扇门。
4.然后模拟,我们假如我们在主持人打开门之后, 选择换一张门。
1 def switch_guess(guesses, goatdoors): 2 result = np.random.randint(0, 3, guesses.size) 3 while True: 4 bad = (result == guesses) | (result == goatdoors) 5 if not bad.any(): 6 return result 7 result[bad] = np.random.randint(0, 3, bad.sum())
看得出来实现的代码和上面是一样的。 我们换的这扇门,不能是原来那扇,而且也不能是开了的那扇。
5.计算胜率。
1 def win_percentage(guesses, prizedoors): 2 return 100 * (guesses == prizedoors).mean()
这里需要注意, 如果单纯的bool type的数据是没有mean这个函数的。 np 把真假转换成了1,0这样才可以计算平均数。mean = 总值/数组的总数
6.最后我们就可以测试下,换和不换的区别了。 因为大数定律,我们测试100000遍
1 pd = simulate_prizedoor(nsim) 2 guess = simulate_guess(nsim) 3 goats = goat_door(pd, guess) 4 guess = switch_guess(guess, goats) 5 nsim = 100000 print "Win percentage when keeping original door" print win_percentage(simulate_prizedoor(nsim), simulate_guess(nsim)) print "Win percentage when switching doors" print win_percentage(pd, guess).mean()
感兴趣的加微信immanuelzhu 一起讨论。