案例:數值模擬 - 三門問題(蒙提霍爾悖論)
三門問題,亦稱爲蒙提霍爾問題,出自美國的電視遊戲節目Let’s Make a Deal。問題的名字來自該節目的主持人蒙提·霍爾(Monty Hall)
1:參賽者面前有三扇關閉着的門,其中一扇的後面是一輛汽車,而另外兩扇門後面則各藏有一隻山羊,選中後面有車的那扇門就可以贏得該汽車
2:當參賽者選定了一扇門,但未去開啓它的時候,主持人會開啓剩下兩扇門中的一扇,露出其中一隻山羊。然後問參賽者要不要改變選擇,選另一扇仍然關着的門
3:問題:參賽者應不應該改變選擇?
1/3,2/3,1/2
所需知識點:
- 生成隨機數數組,np.random.randint()
- 布爾索引
- 根據值反查索引,np.where(),np.argmax()
- 自定義運算 apply_along_axis()
- 集合運算,對稱差,setxor1d()
- 隨機洗牌,np.permutation()
- 隨機抽取,np.random.choice()
import numpy as np
第一步:遊戲生成三個值,觀衆猜測
生成三門數據
c = 100000
game = np.zeros((c, 3))
game
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
...,
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
game[:, 2] = 1
game
array([[0., 0., 1.],
[0., 0., 1.],
[0., 0., 1.],
...,
[0., 0., 1.],
[0., 0., 1.],
[0., 0., 1.]])
game.shape
(100000, 3)
# 將三門數據降維
def f(x):
return np.random.permutation(x)
game2 = np.apply_along_axis(f, 1, game)
game2[:20]
array([[0., 1., 0.],
[0., 0., 1.],
[0., 0., 1.],
[0., 1., 0.],
[1., 0., 0.],
[0., 1., 0.],
[1., 0., 0.],
[0., 0., 1.],
[0., 1., 0.],
[0., 0., 1.],
[0., 0., 1.],
[0., 0., 1.],
[0., 1., 0.],
[0., 1., 0.],
[0., 1., 0.],
[0., 0., 1.],
[0., 1., 0.],
[0., 0., 1.],
[0., 0., 1.],
[0., 1., 0.]])
np.sum(game2[:, 0])
np.sum(game2[:, 1])
np.sum(game2[:, 2])
33405.0
np.sum(game2[:, 0] == 0) # 第0列出現0的個數
66630
def _(a):
one = np.sum(a) / c
zero = np.sum(a == 0) / c
return one, zero
np.apply_along_axis(_, 0, game2)
array([[0.3337 , 0.33225, 0.33405],
[0.6663 , 0.66775, 0.66595]])
三門問題中汽車所在的索引
game2[:10]
array([[0., 1., 0.],
[0., 0., 1.],
[0., 0., 1.],
[0., 1., 0.],
[1., 0., 0.],
[0., 1., 0.],
[1., 0., 0.],
[0., 0., 1.],
[0., 1., 0.],
[0., 0., 1.]])
np.argmax(game2, axis=1) # 汽車出現的索引
array([1, 2, 2, ..., 1, 1, 0], dtype=int64)
game2 == 1
threeMax = np.where(game2 == 1)[1]
threeMax
array([1, 2, 2, ..., 1, 1, 0], dtype=int64)
生成用戶猜測數據
# 隨機猜測
guess = np.random.randint(0, 3, c)
guess
array([0, 0, 0, ..., 1, 0, 0])
# 方法2:全部猜2
guess2 = np.full(c, 2)
guess2
array([2, 2, 2, ..., 2, 2, 2])
計算勝率
np.sum(threeMax == guess) / c
0.3344
np.sum(threeMax == guess2) / c
0.33405
猜固定值和隨機值的概率差不多
第二步:主持人告知一個錯誤值
aa = np.array([0, 1, 2]) # 假設汽車的索引值爲2
aa
array([0, 1, 2])
# 猜對的情況
# 用戶猜測的索引值,正是汽車的索引值
bb = [2, 2]
cc = np.setxor1d(aa, bb)
cc
# 兩個錯誤值,二選一輸出
np.random.choice(cc)
0
# 猜錯的情況
bb = [1, 2]
# 返回
cc = np.setxor1d(aa, bb)
cc
array([0])
# 猜錯的情況
bb = [0, 2]
# 返回
cc = np.setxor1d(aa, bb)
cc
array([1])
def sError(x):
aa = [0, 1, 2] # 遊戲數據的集合
bb = [x[0], x[1]] # 汽車所在索引,用戶猜測索引
cc = np.setxor1d(aa, bb) # 對稱差
return np.random.choice(cc) # 二選一
sayError = np.apply_along_axis(sError, 0, (threeMax, guess))
sayError
array([2, 1, 1, ..., 2, 2, 2], dtype=int64)
game2[:10]
array([[0., 1., 0.],
[0., 0., 1.],
[0., 0., 1.],
[0., 1., 0.],
[1., 0., 0.],
[0., 1., 0.],
[1., 0., 0.],
[0., 0., 1.],
[0., 1., 0.],
[0., 0., 1.]])
guess[:10]
array([0, 0, 0, 0, 2, 0, 1, 1, 2, 2])
sayError[:10]
array([2, 1, 1, 2, 1, 2, 2, 0, 0, 1], dtype=int64)
3 問題: 參賽者應不應該改變選擇
aa
array([0, 1, 2])
# 猜對的情況
bb = [2, 1] # 用戶猜測索引,主持人告知的錯誤索引
#
np.setxor1d(aa, bb)
array([0])
bb = [1, 0]
np.setxor1d(aa, bb)
array([2])
def change(x):
aa = np.array([0, 1, 2])
bb = [x[0], x[1]]
return np.setxor1d(aa, bb)
change2 = np.apply_along_axis(change, 0, (guess, sayError))[0] # 用戶猜測索引,主持人告知錯誤索引
change2
array([1, 2, 2, ..., 0, 1, 1], dtype=int64)
game2[:10]
array([[0., 1., 0.],
[0., 0., 1.],
[0., 0., 1.],
[0., 1., 0.],
[1., 0., 0.],
[0., 1., 0.],
[1., 0., 0.],
[0., 0., 1.],
[0., 1., 0.],
[0., 0., 1.]])
guess[:10]
array([0, 0, 0, 0, 2, 0, 1, 1, 2, 2])
sayError[:10]
array([2, 1, 1, 2, 1, 2, 2, 0, 0, 1], dtype=int64)
change2[:10]
array([1, 2, 2, 1, 0, 1, 0, 2, 1, 0], dtype=int64)
計算不改變選擇和改變選擇的勝率
# 不改變選擇的勝率
np.sum(guess == threeMax) / c
0.3344
# 改變選擇的勝率
np.sum(change2 == threeMax) / c
0.6656