Think Bayes Note 10: 波士顿棕熊队问题
1. 问题描述
在 2010 ~ 2011 赛季的国家冰球联盟(National Hockey League,NHL)总决赛中,波士顿棕熊队与温哥华加拿大人队对决,棕熊队以 0:1 和 2:3 输掉了前两场比赛,以 8:1 和 4:0 赢了后两场比赛,那么此时棕熊队赢得下一场比赛的概率是多少,赢得总冠军的概率是多少?
2. 一些假设
要解答这个问题,首先需要进行一些假设:
- 假设比赛中进球得分近似于泊松过程,在任何时间上都有相同的可能得分。
- 假设长期来看,每支球队对于某一个特定的对手,都有一个单场平均得分数,记为 $\lambda$。
3. 先验概率
首先要根据历史数据确定 $\lambda$ 的一个先验分布。根据 NHL 官网 的数据,可知 2010 ~ 2011 赛季每支球队每场进球的分布大致是均值为 2.8、标准差为 0.3 的高斯分布。定义 Hocky
类如下:
class Hockey(Suite): def __init__(self, name=''): pmf = make_gaussian_pmf(mu=2.7, sigma=0.3, num_sigmas=4) super().__init__(pmf, name=name) # ...
Hockey
的 __init__()
通过 make_gaussian_pmf()
构造了正态分布的 PMF,其定义如下:
def make_gaussian_pmf(mu, sigma, num_sigmas, n=101): pmf = Pmf() low = mu - num_sigmas * sigma high = mu + num_sigmas * sigma for x in np.linspace(low, high, n): p = scipy.stats.norm.pdf(x, mu, sigma) pmf.set(x, p) pmf.normalize() return pmf
参数 mu
和 sigma
分别为正态分布的均值和标准差,离散化 mu + num_sigmas * sigma
和 mu - num_sigmas * sigma
区间,取 n
个点。
4. 后验概率
由前面的假设,比赛中进球得分近似于泊松过程,在 Hockey
中定义 likelihood()
方法如下:
class Hockey(Suite): # ... def likelihood(self, data, hypo): lam = hypo k = data like = eval_poisson_pmf(lam, k) return like
likelihood()
方法的参数 data
为得分,hypo
为 $\lambda$ 的假设,eval_poisson_pmf()
用于计算泊松分布,定义如下:
def eval_poisson_pmf(lam, k): return lam ** k * math.exp(-lam) / math.factorial(k)
使用如下代码计算棕熊队(bruins
)和加拿大人队(canucks
)在进行了前四场比赛后,两队 $\lambda$ 的后验概率:
suite_bruins = Hockey('bruins') suite_bruins.update_set([0, 2, 8, 4]) suite_canucks = Hockey('canucks') suite_canucks.update_set([1, 3, 1, 0]) suite_bruins.plot_with([suite_canucks])
图像为:
5. 进球的分布
上面得到了 $\lambda$ 的后验分布,对于每一个 $\lambda$ 的值,球队的得分都是一个泊松分布。可以将球队得分的分布看成这些泊松分布的混合分布,定义 make_goal_pmf
计算得分的 PMF:
def make_goal_pmf(suite): meta_pmf = Pmf() for lam, prob in suite.iter_items(): pmf = make_poisson_pmf(lam, 10) meta_pmf.set(pmf, prob) mix = make_mixture(meta_pmf, name=suite.name) return mix
参数 suite
为在上一步中得到的 Hockey
对象,这里遍历了 suite
中的每一个 lambda
及其概率,使用 make_poisson_pmf()
构造对应的泊松分布的 PMF,将这个分布及其对应的概率添加到混合分布 meta_pmf
中。
make_poisson_pmf()
的定义如下:
def make_poisson_pmf(lam, high): pmf = Pmf() for k in range(0, high + 1): p = eval_poisson_pmf(lam, k) pmf.set(k, p) pmf.normalize() return pmf
参数 lam
为泊松分布的参数 $\lambda$,high
为取值的上界,前面调用时令 high
为 10,认为一场比赛中一支球队基本不可能进超过 10 球。
使用如下代码计算两支球队进球的分布:
goal_dist_bruins = make_goal_pmf(suite_bruins) goal_dist_canucks = make_goal_pmf(suite_canucks) goal_dist_bruins.plot_with([goal_dist_canucks])
图像为:
使用如下代码计算棕熊队与加拿大人队比赛的结果:
diff = goal_dist_bruins - goal_dist_canucks p_win = diff.prob_greater(0) p_loss = diff.prob_less(0) p_tie = diff.prob(0) print("p_win: {:.2f}, p_loss: {:.2f}, p_tie: {:.2f}".format(p_win, p_loss, p_tie))
输出为:
p_win: 0.46, p_loss: 0.37, p_tie: 0.17
可见,棕熊队赢得比赛的概率为 46%,输掉的概率是 37%,打平的概率是 17%。
6. 突然死亡
按照规定,比赛时间结束时如果出现平局,就会进入“突然死亡”的加时赛,加时赛期间,只要有一队得分,比赛就会立即结束。此时,胜负的关键就不再在于进球数,而在于首次进球的事件。前面假设进球是一个泊松过程,则得分之间的时间服从指数分布。
定义 make_exponential_pmf()
计算指数分布的 PMF:
def make_exponential_pmf(lam, high, n=200): pmf = Pmf() for x in np.linspace(0, high, n): p = eval_exponential_pdf(lam, x) pmf.set(x, p) pmf.normalize() return pmf
假设某一球队的 \lambda = 3.4
,可以使用如下代码计算其进球概率随时间的变化:
time_dist = util.make_exponential_pmf(lam=3.4, high=2, n=101) time_dist.plot()
这里取 high=2
,认为一支球队不太可能连续两场比赛都不进球。图像为(横轴为比赛场次):
与前面计算进球分布的过程类似,在得到了 $\lambda$ 的后验分布后,对于每一个 $\lambda$ 的值,球队首次进球的时间都是一个指数分布。将球队在加时赛中首次得分的时间看成这些指数分布的混合分布,定义 make_goal_time_pmf
计算首次得分时间的 PMF:
def make_goal_time_pmf(suite): meta_pmf = Pmf() for lam, prob in suite.iter_items(): pmf = make_exponential_pmf(lam, high=2, n=201) meta_pmf.set(pmf, prob) mix = make_mixture(meta_pmf, name=suite.name) return mix
使用如下代码计算两支球队首次进球的时间的分布:
time_dist_bruins = make_goal_time_pmf(suite_bruins) time_dist_canucks = make_goal_time_pmf(suite_canucks) time_dist_bruins.plot_with([time_dist_canucks])
图像为:
可见,在加时赛时间小于约 1/3 场比赛时,棕熊队更可能首先得分获胜。
使用如下代码计算棕熊队与加拿大人队比赛的结果:
p_tie = diff.prob(0) p_overtime = pmf_prob_less(time_dist_bruins, time_dist_canucks) p_win = diff.prob_greater(0) + p_tie * p_overtime print("p_win: {:.2f}, p_overtime: {:.2f}, p_tie: {:.2f}".format(p_win, p_overtime, p_tie))
这里将获胜概率 p_win
修正为在比赛时间内获胜的概率加上平局时在加时赛获胜的概率,输出为:
p_win = 0.55, p_overtime = 0.52, p_tie = 0.17
棕熊队如果要赢得整个赛季的总冠军,需要接下来的比赛中连续赢得两场,或者在下两场比赛中输掉一场并赢得第三场,其概率为:
p_series = p_win ** 2 p_series += 2 * p_win * (1 - p_win) * p_win print("p_series: {:.2f}".format(p_series))
输出为:
p_series = 0.57