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

参数 musigma 分别为正态分布的均值和标准差,离散化 mu + num_sigmas * sigmamu - 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