from scipy.stats import poisson
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
def normalize_array(arr):
return np.array([i/np.sum(arr) for i in arr])
= 5
lam = poisson(lam)
dist = [dist.pmf(i) for i in range(11)] pmfs
7 泊松过程
7.1 Gamma 分布
首先,我建议大家把我在博客讲过的 离散分布先理解透,特别是泊松分布那一部分。
在泊松分布中我们讲到这样一个问题:
中国男足队平均每场比赛进 5 个球 (请允许我在平行宇宙做一次梦),请问下一场比赛中国进 2 个球的概率是多少
我们用泊松分布模拟了中国队进 0-10 个球的概率:
\[ P(X = k) = \frac{\lambda^k \cdot e^{-\lambda}}{k!} \tag{7.1}\]
Code
= range(11)
x_axis ='steelblue', label=r'Poisson distribution with `$\lambda=5$`')
plt.bar(x_axis, pmfs, color"Number of Goals (k)")
plt.xlabel("Probability Mass")
plt.ylabel(f"Poisson Distribution of Goals ($\lambda=5$)") plt.title(
Text(0.5, 1.0, 'Poisson Distribution of Goals ($\\lambda=5$)')
使用泊松分布,我们可以由 \(\lambda\) 得知 \(p(k)\),也就是 \(P(k|\lambda)\)。那现在的问题是,我们如何求 \(P(\lambda | k)\):
中国队在一场球赛中进了 2 个球 (\(k=2\)),请问中国队平常一般进多少个球 (\(\lambda\))?
根据贝叶斯定理:
\[ P(\lambda | k) = \frac{P(\lambda) P(k|\lambda)}{P(k)} \tag{7.2}\]
这里
- \(k\) 是数据
- \(\lambda\) 是假设
- \(P(\lambda)\) 是先验概率
- \(P(k|\lambda)\) 是似然
我们可以先忽略 \(P(k)\),因为它只是为了让最后的结果之和为 1。
为什么我们可以忽略 \(P(k)\) 呢?因为它是一个常量:
\[ P(k) = \sum_{\lambda} P(k|\lambda) \cdot P(\lambda) \]
我们最后让 posteior 和为一的时候,也是要对所有的结果乘上一个常量。那我们最后再做就好,没有必要这时候乘。
\(P(k|\lambda)\) 可以通过泊松分布来求得。现在比较难弄的是 \(P(\lambda)\),也就是我们还没看任何数据时对球队一般进球的预估。
我们用 FIFA 的数据 (世界杯场均进分数据):
= pd.read_csv('public/data/fifa.csv')
df df.head()
year | lam | |
---|---|---|
0 | 1930 | 3.89 |
1 | 1934 | 4.12 |
2 | 1938 | 4.67 |
3 | 1950 | 4.00 |
4 | 1954 | 5.38 |
Code
=2.5)
plt.bar(df.year, df.lam, width"Year")
plt.xlabel("Average Goals per Match")
plt.ylabel("Average Goals per Match at FIFA World Cups")
plt.title(='y', linestyle='--', alpha=0.5)
plt.grid(axis plt.show()
np.mean(df.lam)
3.065909090909091
我们看到一场球平均进 3 个球。那我们大概就有数了。但是 Prior 不能只是一个数,需要是一个分布,我们用 Gamma 分布来模拟。该分布有两个变量:\(\alpha\) (shape) 和 \(\beta\) (rate)。该分布的平均值为 \(\frac{\alpha}{\beta}\),Probability Density Function (PDF) 是
\[ f(x) = \frac{\beta^{\alpha}}{\Gamma(\alpha)} x^{\alpha - 1} e^{-\beta x} \tag{7.3}\]
其中
\[ x \in (0, \infty) \]
其中 \(\Gamma(\alpha)\) 的定义是:
\[ \Gamma(\alpha) = \int_0^{\infty} x^{\alpha - 1} e^{-x} \, dx \]
\(\Gamma(\alpha)\) 其实是连乘 (factorial) 在非整数中的应用。如果 \(n\) 是整数,那
\[\Gamma(n) = (n - 1)!\]
如果是非整数,比如 \(0.5\),那就需要用上面的那个普遍公式。
为什么用 Gamma 分布?
- 因为它的自变量是从 0 开始的。
- 如果我们把 \(\beta\) 预设为 1,那么就只剩下一个变量 \(\alpha\),这个就是上面算出来的 3 个球。
- 结果符合现实情况。
所以以下就是 Prior:
from scipy.stats import gamma
= 3
alpha_prior
# assume it is almost impossible to score more than 10
= np.linspace(0, 10, 101)
lambdas = lambdas[1] - lambdas[0]
delta_lambda
# probability mass function
= gamma(alpha_prior).pdf(lambdas)*delta_lambda
prior_pmf
# normalize the prior for visualization purposes
# note that the posterior is the same whether we normalize prior or not
= normalize_array(prior_pmf) prior_pmf
注意,gamma(alpha_prior).pdf(lambdas)
算的是 PDF,要乘以间隔才是 PMF (probability mass function)。
sum(prior_pmf) np.
0.9999999999999997
Likelihood,也就是已知 \(\lambda\) 求 \(k\):
= 2
k = poisson(lambdas).pmf(k)
likelihood_pmf 0:5] likelihood_pmf[
array([0. , 0.00452419, 0.01637462, 0.03333682, 0.0536256 ])
那求 Posterior 就是 Prior 和 Likelihood 这两个数列逐元素相乘,然后让最后的结果之和为1就可以:
= normalize_array(prior_pmf * likelihood_pmf) posterior
Code
= lambdas,
plt.scatter(x = prior_pmf,
y = 2,
s ="grey",
color="Prior")
label= lambdas,
plt.scatter(x = posterior,
y = 2,
s = "orange",
color ="Posterior")
labelr"$\lambda$")
plt.xlabel("Probability Mass")
plt.ylabel(
plt.legend()r"Prior and Posterior Distribution of $\lambda$") plt.title(
Text(0.5, 1.0, 'Prior and Posterior Distribution of $\\lambda$')
7.2 Probability of Superiority
德国男足最近的一场比赛射进 4 个球,巴西队最近一场比赛射进 2 个球。我们有多大信心说德国男足更强?
我们首先要把各自队的 posterior 求出来:
def get_posterior_pmf(lambdas, k, prior_pmf):
= poisson(lambdas).pmf(k)
likelihood_pmf = normalize_array(
posterior * likelihood_pmf)
prior_pmf return posterior
= get_posterior_pmf(lambdas, 4, prior_pmf)
ge_posterior_pmf = get_posterior_pmf(lambdas, 2, prior_pmf) br_posterior_pmf
Code
= lambdas,
plt.scatter(x = ge_posterior_pmf,
y = 2,
s ="steelblue",
color="Germany")
label= lambdas,
plt.scatter(x = br_posterior_pmf,
y = 2,
s = "orange",
color ="Brazil")
labelr"$\lambda$")
plt.xlabel("Probability Mass")
plt.ylabel(
plt.legend()r"Posterior Distribution") plt.title(
Text(0.5, 1.0, 'Posterior Distribution')
这里 \(\lambda\) 所代表的是一个国家队平均而言每场得多少分。
总体思路是这样:德国队随机一个 \(\lambda\),巴西队也随机选一个,德国对应的 \(\lambda\) 比巴西的大的概率。
算法如下:
\[ P(\lambda_{\text{Germany}} > \lambda_{\text{Brazil}}) = \sum_{\lambda_{\text{Germany}}} \sum_{\lambda_{\text{Brazil}}} P(\lambda_{\text{Germany}}) \cdot P(\lambda_{\text{Brazil}}) \cdot \mathbb{I}(\lambda_{\text{Germany}} > \lambda_{\text{Brazil}}) \]
其中 \(\mathbb{I}(\lambda_{\text{Germany}} > \lambda_{\text{Brazil}})\) 是 Indicator function,如果 \(\lambda_{\text{Germany}} > \lambda_{\text{Brazil}}\) 则 \(\mathbb{I}(\lambda_{\text{Germany}} > \lambda_{\text{Brazil}}) = 1\),反之则为 0。
= 0
total for i, lam_ge in enumerate(lambdas):
for j, lam_br in enumerate(lambdas):
if lam_ge > lam_br:
+= ge_posterior_pmf[i] * br_posterior_pmf[j]
total total
0.7152072603914539
想一下下面这个想法为什么是错的:
sum = 0
for i, lam in enumerate(lambdas):
if ge_posterior_pmf[i] > br_posterior_pmf[i]:
sum += 1
7.3 Conjugate Priors
我们来讲一个很重要的概念:Conjugate priors,中文翻译成「共轭先验」。其所表达的意思是:Posterior Distribution 和 Prior Distribution 同属于一个 Distribution。因为我们的 prior 是 gamma distribution,那 posterior 也是,只是 shape (alpha) 和 rate (beta) 不同。
我们具体来看一下。
在公式 Equation 7.2 中,求先验概率 \(P(\lambda)\) 我们需要用到 Equation 7.3,只需要把 \(x\) 换成 \(\lambda\) 就可以。求似然 \(P(k|\lambda)\) 我们用到 Equation 7.1。
那 \(P(\lambda) P(k|\lambda)\) 就是代入具体的 \(\lambda\),把 Equation 7.3 和 Equation 7.1 相乘即可。
相乘的结果是:
\[ \frac{\lambda^k \cdot e^{-\lambda}}{k!} \cdot \frac{\beta^{\alpha}}{\Gamma(\alpha)} \lambda^{\alpha - 1} e^{-\beta \lambda} = \frac{\beta^\alpha}{\Gamma(\alpha) k!} \cdot \lambda^{(\alpha + k) - 1} e^{-(\beta + 1) \lambda} \tag{7.4}\]
\(\frac{\beta^{\alpha}}{\Gamma(\alpha)}\) 是一个常数,\(k!\) 也是一个常数。
有这样一个知识:对于一个 Gamma 函数 \(\Gamma(\alpha, \frac{1}{\beta})\),曲线下的面积(即分布的积分)为 1。 如果乘上一个常数,比如 \(2 \cdot \Gamma(\alpha, \frac{1}{\beta})\),该分布的形状保持不变,但曲线下的面积不再是 1,而是变为该常数的值。
Code
= 3
alpha_0 = 2
beta_0 = np.linspace(0, 10, 500)
x = gamma.pdf(x, alpha_0, scale = 1/beta_0)
gamma_pdf = 2 * gamma_pdf
scaled_gamma_pdf
=(10, 6))
plt.figure(figsize='Gamma($\\alpha=3, \\beta=2$)', color='blue')
plt.plot(x, gamma_pdf, label
plt.plot(x, scaled_gamma_pdf, ='2 * Gamma($\\alpha=3, \\beta=2$)',
label='red',
color='--')
linestyle0, color='gray', linestyle=':', linewidth=1)
plt.axhline('$\\lambda$')
plt.xlabel('Probability Density')
plt.ylabel('Original vs. Scaled Gamma Distribution')
plt.title(
plt.legend() plt.show()
那我们就可以理解,在 Gamma 分布中,\(\frac{\beta^{\alpha}}{\Gamma(\alpha)}\) 的作用就是让积分为 1。
为了得到 Posterior distribution, 我们要把所有的 \(\lambda\) 代入 Equation 7.4,得到一串数字,最后我们要确保它们的和为 1。因此,虽然公式 Equation 7.4 中 和 Gamma 函数的不一样,但这个不重要,最后确保和为 1 的时候,它自动就变成了它该有的样子。
因此我们就可以这么说:Posterior Distribution 也是一个 Gamma Distribution,其参数现在变成了 \(\alpha + k\) 和 \(\beta + 1\):
\[\lambda | k \sim \text{Gamma}(\alpha + k, \beta + 1)\]
代码:
def get_posterior_pmf_conjugate_prior(alpha_prior, beta_prior, k, lambdas):
"""Get posterior pmf through conjugate prior
"""
= lambdas[1] - lambdas[0]
delta_lambda
= alpha_prior + k
alpha_post = beta_prior + 1
beta_post
= gamma(alpha_post, scale=1/beta_post).pdf(lambdas)
posterior_pdf = posterior_pdf*delta_lambda
posterior_pmf = normalize_array(posterior_pmf)
posterior_pmf
return posterior_pmf
= get_posterior_pmf_conjugate_prior(
ge_posterior_pmf 1, 4, lambdas)
alpha_prior, = get_posterior_pmf_conjugate_prior(
br_posterior_pmf 1, 2, lambdas) alpha_prior,
Code
= lambdas,
plt.scatter(x = ge_posterior_pmf,
y = 2,
s ="steelblue",
color="Germany")
label= lambdas,
plt.scatter(x = br_posterior_pmf,
y = 2,
s = "orange",
color ="Brazil")
labelr"$\lambda$")
plt.xlabel("Probablity Mass")
plt.ylabel(
plt.legend()r"Posterior Distribution") plt.title(
Text(0.5, 1.0, 'Posterior Distribution')
我们看到这个结果和我们通过常规的 Prior 与 Likelihood 相乘所得到的结果是一样的。
7.4 后验预测分布
我们来讲一下 Posterior predictive distribution (后验预测分布)。
我们看到 Posterior distribution 中,X 轴是 \(\lambda\),其含义是平均而言。比如说,\(\lambda = 5\) 代表的意思是这个球队如果比赛无数次,平均下来每场会进 5 个球。
回到刚才的德国和巴西男足的叙事,我们现在如果问:
两队现在马上要进行一场比赛,请问德国对获胜的概率是多少?
请注意,我们现在已经不再是问 \(\lambda\),而是问具体的一场比赛中每队的进球数,也就是 \(k\)。
还记得我们上面提到的吗?
\[ P(k) = \sum_{\lambda} P(k|\lambda) \cdot P(\lambda) \]
这里 \(P(\lambda)\) 是 posterior distribution,\(P(k|\lambda)\) 是泊松分布。
def post_predictive_dist(lambdas, posterior_pmfs):
"""compute posterior predictive distribution
Inputs:
- lambdas: an numpy array
- posterior_pmfs: a numpy array
Output:
- res: a dic where key is k and value is P(k)
"""
= range(int(max(lambdas)))
goals = {}
res for goal in goals:
= 0
total for i, lam in enumerate(lambdas):
= poisson(lam).pmf(goal)
poisson_pmf = posterior_pmfs[i]
posterior_pmf += poisson_pmf * posterior_pmf
total = total
res[goal]
= normalize_array(np.array(list(res.values())))
normalized_values for i, pair in enumerate(res):
= normalized_values[i]
res[i] return res
= post_predictive_dist(lambdas, ge_posterior_pmf)
ge_res = post_predictive_dist(lambdas, br_posterior_pmf) br_res
sum(ge_res.values())
1.0
Code
def plot_post_pred_dist(res, cntry_name):
= list(res.keys())
gaols = list(res.values())
probs =(8,5))
plt.figure(figsize="skyblue", edgecolor='black')
plt.bar(gaols, probs, color"Number of Goals (k)")
plt.xlabel("Probability Mass")
plt.ylabel(f"Posterior Predictive Distribution of Goals for {cntry_name}")
plt.title( plt.show()
'Germany') plot_post_pred_dist(ge_res,
'Brazil') plot_post_pred_dist(br_res,
下面我们计算一下在下一场比赛中德国队获胜的概率。
总体思路是这样:德国队随机一个 \(k\),巴西队也随机选一个,德国对应的 \(k\) 比巴西的大的概率。
算法如下:
\[ P(k_{\text{Germany}} > k_{\text{Brazil}}) = \sum_{k} P(k_{\text{Germany}}) \cdot P(k_{\text{Brazil}}) \cdot \mathbb{I}(k_{\text{Germany}} > k_{\text{Brazil}}) \]
def prob_win(res1, res2):
= 0
win_prob = list(res1.keys())
goals for goal1 in goals:
for goal2 in goals:
if goal1 > goal2:
+= res1[goal1] * res2[goal2]
win_prob return win_prob
prob_win(ge_res, br_res)
0.5582596630665152