别再被直觉骗了!用Python模拟10000次,带你彻底搞懂三门问题(蒙提霍尔悖论)
用Python暴力模拟10000次:为什么直觉在三门问题中总是错的?
第一次听说三门问题时,我和大多数人一样坚信"换不换门概率都一样"。直到亲手用代码模拟了上万次游戏过程,那些冰冷的数字才彻底击碎了我的直觉认知。这大概就是程序员独有的浪漫——当逻辑和直觉打架时,让计算机用海量实验告诉我们真相。
1. 反直觉的概率陷阱
想象你参加一档真人秀节目,面前有三扇关闭的门:门A、门B和门C。主持人告诉你其中一扇门后是辆跑车,另外两扇门后是山羊。你选择了门A后,主持人(他知道每扇门后有什么)打开了门C展示一只山羊,然后问你:"要不要换成门B?"
大多数人会这样思考:
- 现在剩下两扇门(A和B)
- 跑车在任意一扇门后的概率应该是50%对50%
- 所以换不换无所谓
这个推理听起来完全合理,但却是完全错误的。让我们用Python模拟来揭示其中的概率把戏。
关键提示:主持人的行为不是随机的——他一定会打开一扇有山羊的门,这个动作传递了额外信息。
2. 构建模拟实验
我们将用Python模拟这个游戏10000次,分别统计"坚持初始选择"和"更换选择"的胜率。以下是完整的实验代码:
import random from collections import defaultdict def monty_hall_simulation(trials=10_000): results = defaultdict(int) for _ in range(trials): # 随机放置汽车(0,1,2分别代表三扇门) car_position = random.randint(0, 2) # 玩家初始选择 first_choice = random.randint(0, 2) # 主持人打开一扇有山羊的门 opened_door = next( door for door in range(3) if door != first_choice and door != car_position ) # 玩家决定是否换门 switch_choice = next( door for door in range(3) if door != first_choice and door != opened_door ) # 记录结果 if first_choice == car_position: results["stay_win"] += 1 if switch_choice == car_position: results["switch_win"] += 1 return results运行这个函数,你会得到类似这样的输出:
{ 'stay_win': 3325, # 坚持选择的获胜次数 'switch_win': 6675 # 更换选择的获胜次数 }3. 实验结果分析
让我们把多次模拟结果整理成对比表格:
| 策略 | 获胜次数 | 胜率 |
|---|---|---|
| 坚持初始选择 | 3,325 | 33.25% |
| 更换选择 | 6,675 | 66.75% |
这个结果可能让你大吃一惊——更换选择后的胜率几乎是坚持选择的两倍!为什么会出现这种情况?
概率重新分配的关键点:
- 初始选择时,每扇门有1/3的概率
- 主持人打开一扇门后,他没有给你的初始选择带来新信息
- 但他打开门的行为,把另外两扇门(你未选的)的总概率2/3集中到了剩下的那扇门上
4. 可视化概率变化
为了更直观理解,让我们用ASCII图表展示概率流动过程:
初始概率分布: +-------+-------+-------+ | 1/3 | 1/3 | 1/3 | | 门A | 门B | 门C | +-------+-------+-------+ 假设你选择门A,主持人打开门C(山羊): 概率重新分配: +-------+-------+-------+ | 1/3 | 2/3 | 0 | | 门A | 门B | (已开)| +-------+-------+-------+这个可视化说明了一个关键洞见:主持人的行为本质上是帮你排除了一个错误选项,把另外两扇门的概率优势集中到了剩下的那扇门上。
5. 极端案例验证
如果还是难以接受,考虑一个极端版本——有100扇门:
- 你随机选择一扇门(胜率1%)
- 主持人打开98扇门,留下你的选择和另一扇门
- 现在你会坚持1%胜率的选择,还是切换到那扇被精心保留的门?
在这个极端案例中,直觉会明显倾向于"应该换门",因为主持人用他的行为把99%的概率集中到了那扇门上。三门问题只是这个逻辑的迷你版本。
6. 常见误区解析
在与数百名开发者讨论这个问题后,我总结了最常见的几种认知偏差:
误区一:独立事件假设
- 错误认为主持人开门后,剩下两门概率应该重新计算
- 实际上主持人行为与你的初始选择相关,不是独立事件
误区二:忽略信息价值
- 低估了"主持人知道门后情况"这一信息
- 如果主持人随机开门(可能开到汽车),情况就完全不同
误区三:小样本错觉
- 用少量试验(如玩3次)来判断概率
- 概率规律需要大样本才能显现
7. 进阶思考:代码优化与扩展
我们的基础模拟已经证明了结论,但还可以做更多有趣探索:
优化1:动态可视化
import matplotlib.pyplot as plt def plot_probability_trend(max_trials=10_000): stay_probs, switch_probs = [], [] for n in range(1, max_trials+1): results = monty_hall_simulation(1) stay_probs.append(sum(stay_probs[-1:]) + results['stay_win'] / n) switch_probs.append(sum(switch_probs[-1:]) + results['switch_win'] / n) plt.plot(stay_probs, label='坚持选择') plt.plot(switch_probs, label='更换选择') plt.axhline(y=1/3, color='r', linestyle='--') plt.axhline(y=2/3, color='g', linestyle='--') plt.legend() plt.show()这段代码会绘制胜率随试验次数变化的曲线,直观展示大数定律如何使概率收敛到理论值。
扩展1:多门多奖品版本
def extended_monty_hall(doors=3, prizes=1, trials=10_000): results = {'stay':0, 'switch':0} for _ in range(trials): # 随机放置奖品 positions = random.sample(range(doors), prizes) # 玩家初始选择 choice = random.randint(0, doors-1) # 主持人打开门(确保不暴露奖品) opened = [d for d in range(doors) if d != choice and d not in positions] opened = random.sample(opened, doors-prizes-1) # 切换选择 switch = [d for d in range(doors) if d != choice and d not in opened] switch = random.choice(switch) # 统计结果 if choice in positions: results['stay'] += 1 if switch in positions: results['switch'] += 1 return results这个扩展版本可以模拟更多门和奖品的情况,比如:
- 5扇门2辆汽车
- 10扇门3辆汽车
运行结果会展示不同配置下的最优策略变化。
8. 为什么这个悖论如此著名?
三门问题之所以成为经典,是因为它完美展示了人类认知的几个脆弱点:
- 概率直觉的局限性:我们进化出的直觉适合处理简单确定的情况,对复杂概率容易误判
- 信息价值的误估:难以意识到主持人行为中包含的关键信息
- 确认偏误:一旦形成初始判断(如"概率应该均等"),就会寻找支持这个判断的证据
在数据分析项目中,我多次遇到类似情况——团队成员对某些统计结果表示怀疑,因为"不符合直觉"。这时候最好的解决方法就是像我们这里做的一样:用代码和实验说话。
