三门问题,也叫蒙提霍尔问题(Monty Hall Problem)
以电视节目 - Let‘s make a deal的主持人蒙提霍命名的一个反直觉问题。
游戏简介
假设有3个门。 其中一个后面藏着宝藏,其余2个都是空门(美国节目中1个后面有汽车,其余的2个是山羊)。
游戏开始:
首先你先选择一张门,
选好后,主持人帮你在其余2扇没有被选择的门中打开一扇没奖的门。
这时候你有一个选择权, 换门还是不换门?
直觉上换不换几率都是50%,那到底几率是多少? 换是不是得奖的机会大一些?
可以用代码来模拟一遍。python
编程就是一步一步来,当然有时候要想清楚直接到达想要到的地方。
第一遍我们就用一步步来的方法走一遍吧。 (代码一部分借鉴了哈佛的CS109)
首先我们先定义下 有3扇门,其中奖品在某一扇门中。
首先安装下 numpy库
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 6 nsim = 100000 7 8 print "Win percentage when keeping original door" 9 print win_percentage(simulate_prizedoor(nsim), simulate_guess(nsim)) 10 11 print "Win percentage when switching doors" 12 print win_percentage(pd, guess).mean()
Win percentage when keeping original door 33.32 Win percentage when switching doors 66.69
测试结果, 胜率方面 换能提高1倍的胜率。 (2个数相加超过100是因为小数点后的换算原因。)
测试完如果有点不能接受,我们换种方式
————————————————————————————一百遍的分割线——————————————————————————————————————
一切都不变的情况下,假如说有100扇门,你先选一扇,主持人帮你开98扇, 这时候也只剩下了2扇门。 你是换呢,还是不换。
这个代码实现就有点小区别了。 因为大家熟悉了之前的实现方法, 我就换了一种实现方法。
1. 定义100扇门中有1个宝箱,这个做太多改变, 再定义我们随机选择了一扇门(之前是固定)
1 start = 0 2 end = 100 3 def simulate_prizedoor(nsim): 4 return np.random.randint(start, end, (nsim)) 5 6 def simulate_guess(nsim): 7 return np.random.randint(start, end, (nsim))
2. 这里我们的主持人要关98扇门,真正要实现这个这个模拟,需要各种循环和条件。 其实根本没必要那么麻烦, 实现一个程序或者过程只要目的达到了就OK,不管是不是一模一样的模拟了一遍。 编程就是要跳出盒子,什么事都在盒子里面,永远无法做到效率的解决问题。
实现逻辑: 这里面不管主持人开多少门, 最后只会留2扇门。 这2扇门只有2种可能性:以猜中没猜中为逻辑
1. 我们猜中了, 所以我们猜的门 和 另外一扇随机留下的门
2. 我们猜错了,所以留下一扇我们猜的门,和一扇宝藏门。
实现这个代码很简单。
1 def guess_switch(guess_keep, pd): 2 alter = np.random.choice([x for x in range(start,end) if ((x != guess_keep)&(x != pd)).any()]) 3 num = np.where(guess_keep == pd,alter,pd) 4 return num
3. 胜率计算
1 def win_percentage(guesses, prizedoors): 2 return 100*(guesses == prizedoors).mean() 3 4 nsim = 10000 5 6 pd = simulate_prizedoor(nsim) 7 guess_keep = simulate_guess(nsim) 8 guess_sh = guess_switch(guess_keep, pd) 9 10 #keep guesses 11 print "Win percentage when keeping original door" 12 print win_percentage(pd, guess_keep) 13 14 #switch 15 print "Win percentage when switching doors" 16 print win_percentage(pd, guess_sh)
Win percentage when keeping original door 1.04 Win percentage when switching doors 98.96 数据胜于雄辩。 PS:本人代码水平有限, 请大家多多指正。 另外一个问题, 有没有可能实现所有的if都用np.where来替代。 if和for真是效率太低了。