用Python模拟10000次三门问题当直觉遇上贝叶斯公式第一次听说三门问题时我的反应和大多数人一样这怎么可能换不换门不应该都是50%的概率吗直到我用Python模拟了10000次实验看到屏幕上那个接近66.7%的数字时才真正理解了贝叶斯公式的威力。本文将带你一起用代码破解这个反直觉的概率谜题并探讨为什么我们的直觉会在这个问题上彻底失灵。1. 为什么三门问题如此反直觉想象你参加一个游戏节目面前有三扇关闭的门A、B和C。其中一扇门后面是一辆豪华跑车另外两扇门后面则是山羊。你选择了A门然后主持人他知道每扇门后面是什么打开了B门露出一只山羊。现在主持人问你是否要把选择换成C门。你会怎么做直觉告诉我们现在只剩下两扇门换不换应该没区别概率都是50%。但数学告诉我们换门会将胜率从1/3提升到2/3。这个结论如此反直觉以至于当年这个问题首次在《展示》杂志上发表时引发了上千封读者来信包括数学博士在内的人都坚持认为答案错了。1.1 直觉陷阱的心理学解释人类大脑进化来处理丛林中的危险而不是计算条件概率。—— 丹尼尔·卡尼曼我们的大脑在概率判断上存在几个固有缺陷忽视先验信息我们容易忽略初始概率1/3而只关注当前看似对称的选择两扇门。主持人行为的影响主持人知道门后内容并刻意避开汽车的行为实际上传递了关键信息。代表性启发法我们倾向于认为随机事件应该看起来随机所以两扇门应该对称。# 直觉判断 vs 实际概率的对比 import matplotlib.pyplot as plt labels [直觉判断, 实际概率] stay [0.5, 1/3] switch [0.5, 2/3] plt.bar(labels, stay, label坚持选择) plt.bar(labels, switch, bottomstay, label更换选择) plt.legend() plt.title(直觉判断与实际概率的对比) plt.show()2. 用Python构建三门问题模拟器理论归理论实践出真知。让我们用Python构建一个三门问题模拟器用数据说话。2.1 模拟器核心逻辑import random import numpy as np def monty_hall_simulation(num_simulations10000): stay_wins 0 switch_wins 0 for _ in range(num_simulations): # 随机放置汽车 doors [goat, goat, car] random.shuffle(doors) # 玩家初始选择 initial_choice random.randint(0, 2) # 主持人打开一扇有山羊的门 remaining_doors [i for i in range(3) if i ! initial_choice and doors[i] goat] opened_door random.choice(remaining_doors) # 换门策略的选择 switch_choice [i for i in range(3) if i ! initial_choice and i ! opened_door][0] # 统计结果 if doors[initial_choice] car: stay_wins 1 if doors[switch_choice] car: switch_wins 1 return stay_wins/num_simulations, switch_wins/num_simulations2.2 运行模拟并可视化结果stay_prob, switch_prob monty_hall_simulation() print(f坚持选择的胜率: {stay_prob:.2%}) print(f更换选择的胜率: {switch_prob:.2%}) # 绘制胜率随模拟次数变化的曲线 def plot_convergence(num_simulations10000): stay_probs [] switch_probs [] stay_wins 0 switch_wins for i in range(1, num_simulations1): doors [goat, goat, car] random.shuffle(doors) initial_choice random.randint(0, 2) remaining_doors [i for i in range(3) if i ! initial_choice and doors[i] goat] opened_door random.choice(remaining_doors) switch_choice [i for i in range(3) if i ! initial_choice and i ! opened_door][0] if doors[initial_choice] car: stay_wins 1 if doors[switch_choice] car: switch_wins 1 stay_probs.append(stay_wins/i) switch_probs.append(switch_wins/i) plt.plot(range(1, num_simulations1), stay_probs, label坚持选择) plt.plot(range(1, num_simulations1), switch_probs, label更换选择) plt.axhline(y1/3, colorr, linestyle--) plt.axhline(y2/3, colorg, linestyle--) plt.xlabel(模拟次数) plt.ylabel(胜率) plt.title(胜率随模拟次数的变化) plt.legend() plt.show() plot_convergence()3. 贝叶斯视角下的三门问题贝叶斯公式为我们提供了一个严格的数学框架来解释这个现象。让我们用数学语言重新表述这个问题。3.1 贝叶斯公式回顾贝叶斯公式描述了在观察到新证据后如何更新我们的信念$$ P(A|B) \frac{P(B|A)P(A)}{P(B)} $$其中$P(A)$ 是先验概率初始信念$P(B|A)$ 是似然在A成立时观察到B的概率$P(A|B)$ 是后验概率观察到B后对A的信念更新3.2 三门问题的贝叶斯解释假设初始选择A门主持人打开了B门展示山羊我们需要计算在主持人打开B门后A门有车的概率$P(Car_A|Open_B)$。根据贝叶斯公式先验概率$P(Car_A) 1/3$如果车在A门后主持人随机打开B或C门$P(Open_B|Car_A) 1/2$如果车在B门后主持人不会打开B门$P(Open_B|Car_B) 0$如果车在C门后主持人必须打开B门$P(Open_B|Car_C) 1$边际概率$P(Open_B)$$$ P(Open_B) P(Open_B|Car_A)P(Car_A) P(Open_B|Car_B)P(Car_B) P(Open_B|Car_C)P(Car_C) \frac{1}{2}×\frac{1}{3} 0×\frac{1}{3} 1×\frac{1}{3} \frac{1}{2} $$因此$$ P(Car_A|Open_B) \frac{P(Open_B|Car_A)P(Car_A)}{P(Open_B)} \frac{\frac{1}{2}×\frac{1}{3}}{\frac{1}{2}} \frac{1}{3} $$这意味着在主持人打开B门后A门有车的概率仍然是1/3因此C门有车的概率必须是2/3因为B门已经被排除4. 蒙特卡洛方法从三门问题到更广阔的应用我们的Python模拟实际上使用了一种称为蒙特卡洛方法的重要统计技术。这种方法通过重复随机采样来获得数值结果在无法解析求解的问题中尤其有用。4.1 蒙特卡洛方法的核心思想定义输入概率分布在我们的例子中汽车随机放在三扇门后生成随机输入每次模拟随机放置汽车和随机初始选择计算确定性结果根据游戏规则确定是否获胜聚合统计结果统计大量试验后的胜率4.2 蒙特卡洛方法的应用场景应用领域典型问题三门问题类比金融工程期权定价汽车相当于期权 payoff物理模拟粒子运动每次试验相当于一个粒子轨迹机器学习随机森林每棵树相当于一次模拟试验工程优化可靠性分析汽车位置相当于系统失效模式# 扩展版蒙特卡洛模拟N门问题 def generalized_monty_hall(num_doors3, num_simulations10000): stay_wins 0 switch_wins 0 for _ in range(num_simulations): doors [goat]*(num_doors-1) [car] random.shuffle(doors) initial_choice random.randint(0, num_doors-1) # 主持人打开num_doors-2扇有山羊的门 remaining_goats [i for i in range(num_doors) if i ! initial_choice and doors[i] goat] opened_doors random.sample(remaining_goats, num_doors-2) # 换门策略的选择 switch_choice [i for i in range(num_doors) if i ! initial_choice and i not in opened_doors][0] if doors[initial_choice] car: stay_wins 1 if doors[switch_choice] car: switch_wins 1 return stay_wins/num_simulations, switch_wins/num_simulations # 测试不同门数的情况 for n in [3, 5, 10, 100]: stay, switch generalized_monty_hall(num_doorsn) print(f{n}门问题 - 坚持选择胜率: {stay:.2%}, 更换选择胜率: {switch:.2%})4.3 为什么模拟结果会收敛到理论值大数定律告诉我们随着试验次数增加样本均值会收敛到期望值。在我们的模拟中每次试验都是独立的伯努利试验成功或失败根据强大数定律当n→∞时胜率几乎必然收敛到理论概率中心极限定理告诉我们收敛速度是$O(1/\sqrt{n})$在实际项目中我发现模拟次数达到10,000次时结果通常已经稳定在小数点后两位。但为了学术严谨性重要研究往往会进行百万次级别的模拟。