Think Bayes Note 1: 曲奇饼干问题

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

1. 问题描述

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

2. 推导求解

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

\begin{equation}
P(H_1|D) = \frac{P(H_1)P(D|H_1)}{P(D)} \tag{1}
\end{equation}

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

\begin{equation}
P(H_1) = P(H_2) = \frac{1}{2} \tag{2}
\end{equation}

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

\begin{equation}
P(D|H_1) = \frac{3}{4} \tag{3}
\end{equation}

同理可得:

\begin{equation}
P(D|H_2) = \frac{20}{20 + 20} = \frac{1}{2} \tag{4}
\end{equation}

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

\begin{align}
P(D) &= P(D|H_1)P(H_1) + P(D|H_2)P(H_2) \\
& = \frac{3}{4} \cdot \frac{1}{2} + \frac{1}{2} \cdot \frac{1}{2} = \frac{5}{8} \tag{5}
\end{align}

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

\begin{equation}
P(H_1|D) = \frac{\frac{1}{2} \cdot \frac{3}{4}}{\frac{5}{8}} = \frac{3}{5} = 0.6
\end{equation}

即这个香草饼干是从碗 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()

输出为:

        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 中保存的就是各个事件的概率了。由于本例中的 $H_1$ 和 $H_2$ 互斥且完备,各先验概率乘以对应的似然度后,进行归一化即可得到后验概率,不必再计算标准化常量 $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()

其中 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()

输出与之前一样,为:

        prob
value       
Bowl 1   0.6
Bowl 2   0.4

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