Processing math: 100%

Think Bayes Note 1: 曲奇饼干问题

  本系列为《贝叶斯思维:统计建模的Python学习法》一书的笔记,基于 Python 3 和 Pandas 重新实现了各个例子,并给出更详细的推导和说明。

1. 问题描述

  假设有两碗曲奇饼干,一个碗(记为“碗 1”)里有 30 个香草饼干和 10 个巧克力饼干,另一个碗(记为“碗 2”)里有香草饼干和巧克力饼干各 20 个。现闭着眼睛从随机一个碗中拿出一块饼干,得到了一块香草饼干,求这个饼干是从碗 1 中取出的概率。

2. 推导求解

  记 H1 为这个香草饼干是从碗 1 中取出的,H2 为这个香草饼干是从碗 2 中取出的,记 D 为从随机一个碗中取出一块饼干是香草饼干,则香草饼干来自碗 1 的概率为 P(H1|D),由贝叶斯公式,有:

P(H1|D)=P(H1)P(D|H1)P(D)

  式 (1) 中,先验概率 P(H1) 表示随机选一个碗,选中碗 1 的概率。由于是随机选择,可以认为两个碗被选中的可能性相等,则有:

P(H1)=P(H2)=12

  似然度 P(D|H1) 表示从碗 1 中取出一块饼干是香草饼干的概率,由问题描述,碗 1 中有 30 个香草饼干和 10 个巧克力饼干,故从中取出一块香草饼干的概率是 3030+10,得到:

P(D|H1)=34

同理可得:

P(D|H2)=2020+20=12

  标准化常量 P(D) 表示从随机一个碗中取出一块饼干是香草饼干的概率,由全概率公式,有:

P(D)=P(D|H1)P(H1)+P(D|H2)P(H2)=3412+1212=58

  将式 (2)、(3)、(5) 代入式 (1),可得后验概率:

P(H1|D)= 123458=350.6

即这个香草饼干是从碗 1 中取出的概率为 0.6。

3. 代码求解

  下面使用代码求解此问题。本例中主要涉及 DfWrapperPmf 两个类。DfWrapper 为 Pandas 的 DataFrame 的一个包装类,内部使用一个 DataFrame 保存事件和对应的概率。Pmf 为概率质量函数(Probability Mass Function),继承自 DfWrapper 以获得保存概率的能力,并提供了一些相关的计算函数。

  使用 Pmf 计算此问题的代码为:

pmf = Pmf()
pmf.set('Bowl 1', 0.5)
pmf.set('Bowl 2', 0.5)
pmf.mult('Bowl 1', 0.75)
pmf.mult('Bowl 2', 0.5)
pmf.normalize()
pmf.print()
pmf = Pmf() pmf.set('Bowl 1', 0.5) pmf.set('Bowl 2', 0.5) pmf.mult('Bowl 1', 0.75) pmf.mult('Bowl 2', 0.5) pmf.normalize() pmf.print()
pmf = Pmf()

pmf.set('Bowl 1', 0.5)
pmf.set('Bowl 2', 0.5)
pmf.mult('Bowl 1', 0.75)
pmf.mult('Bowl 2', 0.5)

pmf.normalize()
pmf.print()

输出为:

prob
value
Bowl 1 0.6
Bowl 2 0.4
prob value Bowl 1 0.6 Bowl 2 0.4
        prob
value       
Bowl 1   0.6
Bowl 2   0.4

由输出可见,这个香草饼干是从碗 1 中取出的概率为 0.6,与前面的推导相符。

  代码中,pmf.set() 方法用于向 pmf 添加事件和概率,pmf.set('Bowl 1', 0.5) 表示设置选中碗 1 的先验概率为 0.5。

  pmf.mult() 方法用于更新 pmf 中的对应事件的概率,更新的值为原概率乘以一个因子,pmf.mult('Bowl 1', 0.75) 表示向碗 1 的先验概率乘以似然概率 0.75。

  pmf.normalize() 用于对 pmf 中保存的各“概率”进行归一化。在经过一系列计算后,pmf 中保存的可能不再是各事件在严格意义上的概率值,而是各事件发生的可能性的比例,pmf.normalize() 会将 pmf 中保存的各概率值归一化为总和为 1 的状态,此时 pmf 中保存的就是各个事件的概率了。由于本例中的 H1H2 互斥且完备,各先验概率乘以对应的似然度后,进行归一化即可得到后验概率,不必再计算标准化常量 P(D)

  进一步地,可以定义 Cookie 类如下:

class Cookie(Pmf):
bowl_1 = {'vanilla': 0.75, 'chocolate': 0.25}
bowl_2 = {'vanilla': 0.5, 'chocolate': 0.5}
mixes = {'Bowl 1': bowl_1, 'Bowl 2': bowl_2}
def likelihood(self, data, hypo):
mix = self.mixes[hypo]
return mix[data]
def update(self, data):
for hypo in self.values():
like = self.likelihood(data, hypo)
self.mult(hypo, like)
self.normalize()
class Cookie(Pmf): bowl_1 = {'vanilla': 0.75, 'chocolate': 0.25} bowl_2 = {'vanilla': 0.5, 'chocolate': 0.5} mixes = {'Bowl 1': bowl_1, 'Bowl 2': bowl_2} def likelihood(self, data, hypo): mix = self.mixes[hypo] return mix[data] def update(self, data): for hypo in self.values(): like = self.likelihood(data, hypo) self.mult(hypo, like) self.normalize()
class Cookie(Pmf):
    bowl_1 = {'vanilla': 0.75, 'chocolate': 0.25}
    bowl_2 = {'vanilla': 0.5, 'chocolate': 0.5}
    mixes = {'Bowl 1': bowl_1, 'Bowl 2': bowl_2}

    def likelihood(self, data, hypo):
        mix = self.mixes[hypo]
        return mix[data]

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

其中 bowl_1bowl_2 分别保存了两个碗中取出各种饼干的概率,mixes 将假设 Bow 1Bow 2 映射到相应的碗。likelihood() 用于计算似然度,即在假设 hypo 条件下,发生 data 事件的概率。update() 方法用于再 data 事件发生时更新相关假设的概率,最后通过 normalize() 得到后验概率。

  Cookie 的使用方法如下:

hypos = ['Bowl 1', 'Bowl 2']
cookie = Cookie(hypos)
cookie.update('vanilla')
cookie.print()
hypos = ['Bowl 1', 'Bowl 2'] cookie = Cookie(hypos) cookie.update('vanilla') cookie.print()
hypos = ['Bowl 1', 'Bowl 2']
cookie = Cookie(hypos)
cookie.update('vanilla')
cookie.print()

输出与之前一样,为:

prob
value
Bowl 1 0.6
Bowl 2 0.4
prob value Bowl 1 0.6 Bowl 2 0.4
        prob
value       
Bowl 1   0.6
Bowl 2   0.4

  代码中,通过 hypos = ['Bowl 1', 'Bowl 2'] 定义了两个事件,对应前文的 H1H2cookie = Cookie(hypos) 使用 hypos 实例化了 CookieCookie 继承自 PmfPmf 继承自 DfWrapperDfWrapper 默认会将各个事件初始化为相同的概率,此时 Bowl 1Bowl 2 的先验概率均为 0.5。cookie.update('vanilla') 将取出了香草饼干的事件更新进 cookiecookie 根据此事件的发生,计算得到后验概率,最后使用 cookie.print() 打印出来。