Processing math: 100%

Think Bayes Note 2: Monty Hall 问题

1. 问题描述

  Monty Hall 是一个名为 Let’s Make a Deal 的电视节目的主持人,Monty Hall 问题 的描述为,假设你参加了一个游戏节目,面前有三扇关着的大门,其中一扇门后面有一辆汽车,另外两扇门后面则各有一头山羊,如果你能猜中哪扇门后有汽车,就可以赢得汽车作为奖品。

  首先,你要先选择一扇门(记选择的门为门 A,另外两扇门为门 B 和门 C)。在打开这门 A 之前,为了增加悬念,主持人(知道哪扇门后面有汽车)会打开门 B 或 C 中的一扇没有汽车的门。然后,主持人会给你一个选择,你可以坚持最初的选择,或者更换选择另一扇未打开的门。问题是:你是否应该更换选择?更换选择是否有会带来区别?

2. 推导求解

  从直觉的角度来看,最后要从两扇门中选一个,选中汽车的概率是 0.5,是否更换选择不会带来什么区别。但是,这个直觉并不正确,主持人打开一扇没有奖品的门这一行为,在一定程度上降低了结果的不确定性,有助于我们选中有汽车的门。

  假设 HAHBHC 分别为汽车在门 A、B、C 后面,记事件 D 为主持人打开了门 B,且汽车没有在后面。整个问题分为初始选择门 A、主持人打开没有汽车的门 B、最终选择三个阶段,下面分阶段进行分析。

2.1. 初始选择门 A

  在初始选择时,没有任何的额外信息,认为汽车在门 A、B、C 后面的概率相同,即:

P(HA)=P(HB)=P(HC)=13

2.2. 主持人打开没有汽车的门 B

  如果 HA 为真(即汽车在门 A 后面),当主持人打开没有汽车的门时,主持人可以打开 B 或 C 任意一扇门,认为主持人打开 B 或 C 的可能性相同,此时事件 D 发生(主持人打开门 B,且汽车没有在后面)的概率为 12,即:

P(D|HA)=12

  如果 HB 为真(即汽车在门 B 后面),当主持人打开没有汽车的门时,一定不会打开门 B,因为主持人只能选择打开没有汽车的门。此时事件 D 发生的概率为 0,即:

P(D|HB)=0

  如果 HC 为真(即汽车在门 C 后面),当主持人打开没有汽车的门时,只能打开门 B,因为门 A 被玩家选了,门 C 后面有汽车。此时事件 D 发生的概率为 1,即:

P(D|HC)=1

2.3. 最终选择

  要决定是否更换选择,需要计算 HAHBHC 的后验概率 P(HA|D)P(HB|D)P(HC|D)。要使用贝叶斯公式计算后验概率,还需要求得 P(D),由全概率公式以及前面式 (1)、 (2)、 (3)、 (4) 的结果,有:

P(D)=P(HA)P(D|HA)+P(HB)P(D|HB)+P(HC)P(D|HC)=13×12+13×0+13×1=12

  于是可得各后验概率为:

P(HA|D)=P(HA)P(D|HA)P(D)=13×1212=12

P(HB|D)=P(HB)P(D|HB)P(D)=13×012=0

P(HC|D)=P(HC)P(D|HC)P(D)=13×112=23

  可见,此时坚持选择门 A 有 13 的概率选中汽车,而更换选择到门 C 则具有 13 的概率选中汽车,更换选择可以提高选中汽车的概率。

3. 代码求解

  下面使用代码来求解此问题。定义 Monty 类如下:

class Monty(Pmf):
def likelihood(self, data, hypo):
if hypo == data:
return 0
elif hypo == 'A':
return 0.5
else:
return 1
def update(self, data):
for hypo in self.values():
like = self.likelihood(data, hypo)
self.mult(hypo, like)
self.normalize()
class Monty(Pmf): def likelihood(self, data, hypo): if hypo == data: return 0 elif hypo == 'A': return 0.5 else: return 1 def update(self, data): for hypo in self.values(): like = self.likelihood(data, hypo) self.mult(hypo, like) self.normalize()
class Monty(Pmf):
    def likelihood(self, data, hypo):
        if hypo == data:
            return 0
        elif hypo == 'A':
            return 0.5
        else:
            return 1

    def update(self, data):
        for hypo in self.values():
            like = self.likelihood(data, hypo)
            self.mult(hypo, like)
        self.normalize()

  注意其中用于计算似然度的 likelihood() 方法:参数 data 为主持人打开的门,hypo 为假设汽车所在的门。如果 hypo == data,则说明主持人打开了汽车所在的门,这一事件不可能发生,即主持人打开 data 门的概率为 0;如果 hypo == 'A',即玩家初次就选中了有汽车的门,此时主持人可以任意打开 B 和 C 中的一个,主持人打开 data 门的概率为 0.5;如果玩家初次没有选中有汽车的门,那么主持人只能打开剩下的两扇门中没有汽车的那扇,如果此时还有 hypo 不等于 data,即汽车不在 data 门后,则主持人只打开 data 门的概率为 1。

  调用代码为:

hypos = 'ABC'
monty = Monty(hypos)
monty.update('B')
monty.print()
hypos = 'ABC' monty = Monty(hypos) monty.update('B') monty.print()
hypos = 'ABC'
monty = Monty(hypos)
monty.update('B')
monty.print()

输出为:

prob
value
A 0.333333
B 0.000000
C 0.666667
prob value A 0.333333 B 0.000000 C 0.666667
           prob
value          
A      0.333333
B      0.000000
C      0.666667

  Monty 类其实实现了一套计算后验概率的基础流程,先计算似然度,然后根据新获得的数据计算后验概率。可以将这一过程抽象为一个单独的类 Suite

class Suite(Pmf):
def likelihood(self, data, hypo):
raise UnimplementedMethodException()
def update(self, data):
for hypo in self.values():
like = self.likelihood(data, hypo)
self.mult(hypo, like)
return self.normalize()
class Suite(Pmf): def likelihood(self, data, hypo): raise UnimplementedMethodException() def update(self, data): for hypo in self.values(): like = self.likelihood(data, hypo) self.mult(hypo, like) return self.normalize()
class Suite(Pmf):
    def likelihood(self, data, hypo):
        raise UnimplementedMethodException()

    def update(self, data):
        for hypo in self.values():
            like = self.likelihood(data, hypo)
            self.mult(hypo, like)
        return self.normalize()

其中 likelihood() 为一个抽象方法,由表示具体问题的子类来实现,计算后验概率的 update() 方法流程固定,由 Suite 实现。Monty Hall 问题使用 Suite 的实现为 MontySuite

class MontySuite(Suite):
def likelihood(self, data, hypo):
if hypo == data:
return 0
elif hypo == 'A':
return 0.5
else:
return 1
class MontySuite(Suite): def likelihood(self, data, hypo): if hypo == data: return 0 elif hypo == 'A': return 0.5 else: return 1
class MontySuite(Suite):
    def likelihood(self, data, hypo):
        if hypo == data:
            return 0
        elif hypo == 'A':
            return 0.5
        else:
            return 1

  使用方法和先前一样:

hypos = 'ABC'
monty_suite = MontySuite(hypos)
monty_suite.update('B')
monty_suite.print()
hypos = 'ABC' monty_suite = MontySuite(hypos) monty_suite.update('B') monty_suite.print()
hypos = 'ABC'
monty_suite = MontySuite(hypos)
monty_suite.update('B')
monty_suite.print()

输出为:

prob
value
A 0.333333
B 0.000000
C 0.666667
prob value A 0.333333 B 0.000000 C 0.666667
           prob
value          
A      0.333333
B      0.000000
C      0.666667

4. 一个变形

  该问题可以变形为,假设主持人在打开没有汽车的门时,始终优先选择门 B,只有在迫不得已时(汽车在门 B 后面)才选择门 C。

  此时先验概率不变,都是 13。而在主持人打开没有汽车的门时,如果 HA 为真,则主持人一定会打开门 B,有 P(D|HA)=1;如果 HB 为真,则主持人一定会打开门 C,有 P(D|HB)=0;如果 HC 为真,则主持人一定会打开门 B,有 P(D|HC)=1。再计算后验概率,可以得到
  
P(HA|D)=12P(HB|D)=0P(HC|D)=12

  可见如果主持人打开了门 B,则汽车仍等可能地位于门 A 或门 C 后面,玩家是否更换选择没有影响。而如果主持人打开了门 C,则可以知道汽车一定在门 B 后面,玩家更换选择门 B 一定会选中汽车。