三門問題,也叫蒙提霍爾問題(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()
測試結果, 勝率方面 換能提高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真是效率太低了。
