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. 代码求解
下面使用代码求解此问题。本例中主要涉及 DfWrapper
和 Pmf
两个类。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_1
和 bowl_2
分别保存了两个碗中取出各种饼干的概率,mixes
将假设 Bow 1
和 Bow 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
实例化了 Cookie
,Cookie
继承自 Pmf
,Pmf
继承自 DfWrapper
,DfWrapper
默认会将各个事件初始化为相同的概率,此时 Bowl 1
和 Bowl 2
的先验概率均为 0.5。cookie.update('vanilla')
将取出了香草饼干的事件更新进 cookie
,cookie
根据此事件的发生,计算得到后验概率,最后使用 cookie.print()
打印出来。