Think Bayes (7) 예측
파이썬을 활용한 베이지안 통계, 앨런 B. 다우니 지음, 권정민 옮김/한빛미디어 |
7.1 보스턴 브루인스 문제
미국 하키 리그 결승전에서 보스턴 브루인스가 벤쿠버 캐넉스를 상대로 7전 4선승제 경기를 하엿다. 보스턴은 처음 두 게임을 0-1과 2-3으로 지고 다음 두 게임에서는 8-1과 4-0으로 이겼다. 이 시점에서 보스턴이 다음 경기에서 이길 확률은 얼마고, 우승할 확률은 얼마일까?
이런 문제에 대답할 때에는 몇 가지 가정을 해야 한다.
- 경기에서 골은 최소한 어떤 때든 골이 성공할 가능성이 동일하다고 보는 포아송 프로세스(Poisson process)를 따른다.
- 특정 상대에 대해서, 각 팀의 게임별 장기적 평균 골 수는 $\lambda$이다.
이러한 가정 아래에서 위의 질문에 대답하려면 다음의 가정을 거쳐야 한다.
- $\lambda$에 대한 사전 분포를 고르기 위해 이전 게임에 대한 통계를 확인한다.
- 각 팀에 대한 $\lambda$ 추정을 위해 처음 네 게임의 점수를 사용한다.
- $\lambda$의 사후 분포를 통해 각 팀의 골 분포, 골 차이의 분포, 각 팀이 다음에 이길 확률을 구한다.
- 각 팀이 이번 시리즈에서 이길 확률을 구한다.
사전 분포는 통계자료를 이용하여 각 팀의 게임당 평균 골 수를 사용하였고, 이 때 분포는 평균 2.8에 표준편차 0.3의 가우시안으로 나타났다.
가우시안은 연속형이지만, 여기서는 이산 PMF를 사용하도록 하겠다. 이를 위해 thinkbayes
에서 MakeGaussianPmf
를 제공한다.
1 2 3 4 5 6 7 8 9 10 11 |
def MakeGaussianPmf(mu, sigma, num_sigmas, n=101): pmf = Pmf() low = mu - num_sigmas * sigma high = mu + num_sigmas * sigma for x in numpy.linspace(low, high, n): p = scipy.stats.norm.pdf(mu, sigma, x) pmf.Set(x, p) pmf.Normalize() return pmf |
mu
와 sigma
는 가우시안 분포의 평균과 표준편차이고, num_sigmas
는 PMF 범위의 평균 이상/이하 범위에 속하는 편차의 수, n
은 PMF의 값 수이다.
다음은 $ \lambda$의 값에 대한 가설의 스윗에 대한 정의이다.
1 2 3 4 5 |
class Hockey(thinkbayes.Suite): def __init__(self): pmf = thinkbayes.MakeGaussianPmf(2.7, 0.3, 4): thinkbayes.Suite.__init__(self, pmf) |
7.2 포아송 프로세스
통계학에서 프로세스는 물리 시스템에 대한 추계(stochastic) 모델이다. 추계란 모델에 몇 가지 임의성이 포함된다는 뜻이다. 예를 들어, 베르누이 프로세스(Bernoulli process)는 어떤 ‘시도’라는 사건의 나열로 이루어진 모델인데, 각 시도는 성공과 실패 두가지 결과로 나뉜다. 따라서 베르누이 프로세느는 연속적으로 동전 던지기나 골에 공을 넣는 것에 대한 모델이다.
포아송 프로세스는 베르누이 프로세스의 연속형으로 사건이 어떤 시점에서든 동일한 확률로 발생할 수 있는 형태다. 예를 들어, 가게에 손님이 도착하는 것, 버스 정류장에 버스가 도착하는 것, 하키 게임에서 골이 성공하는 것 같은 모델에 적용할 수 있다.
실제로는 이러한 사건들은 시간에 따라 확률이 변한다. 손님들은 특정 시간에 가게를 방문하는 경우가 많고, 버스는 특정 시간 주기로 도착하도록 짜여있다. 골은 게임 동안 시간에 따라 들어갈 확률이 변한다. 하지만 모든 모델은 단순화에서 기인하였으므로 포아송 프로세스는 좋은 선택지이다.
포아송 프로세스 모델을 사용하면 게임당 골 수의 분포를 계산할 수 있을 뿐더러, 골 간의 시간 분포에 대해서도 알 수 있다. 게임당 평균 골 수가 lam
이라면 게임당 골의 분포는 포아송 PMF로 정의할 수 있다.
1 2 3 |
def EvalPoissonPmf(lam, k): return (lam)**k * math.exp(-lam) / math.factorial(k) |
골 간의 시간 분포는 지수 PDF를 따른다.
1 2 3 |
def EvalExponentialPdf(lam, x): return lam * math.exp(-lam * x) |
7.3 사후 분포
게임에서 k
골을 넣었을 때의 평균 점수 lam
의 가설 값을 갖는 팀의 우도를 구할 수 있다.
1 2 3 4 5 6 7 |
class Hockey: def Likelihood(self, data, hypo): lam = hypo k = data like = thinkbayes.EvalPoissonPmf(lam, k) return like |
이 우도 함수를 통하여 각 팀의 스윗을 만들고 처음 네 경기의 점수를 적용하여 갱신할 수 있다.
1 2 3 4 5 6 |
suite1 = Hockey('bruins') suite1.UpdateSet([0, 2, 8, 4]) suite2 = Hockey('canucks') suite2.UpdateSet([1, 3, 1, 0]) |
업데이트 이후, 게임 당 골 수의 사후 분포의 평균은 케넉스의 경우 2.6, 브루인스의 경우 2.9가 된다.
7.4 골의 분포
각 팀이 다음 경기에서 이길 확률을 계산하려면 각 팀의 골의 분포를 계산하여야 한다. 만약 우리가 게임 당 평균 점수(lam
)을 알고 있다면 포아송 분포를 다시 사용할 수 있다. thinkbayes
에서는 포아송 분포의 근사값을 계산하는 메서드를 제공한다.
1 2 3 4 5 6 7 8 |
def MakePoissonPmf(lam, high): pmf = Pmf() for x in range(0, high+1): p = EvalPoissonPmf(lam, k) pmf.set(k, p) pmf.Normalize() return pmf |
high
는 구할 Pmf의 범위를 의미한다.
lam
이 3.4라면 다음과 같이 분포를 계산할 수 있다.
1 2 3 |
lam = 3.4 goal_dist = thinkbayes.MakePoissonPmf(lam, 10) |
하지만 문제는 우리가 lam
의 값을 정확히 모른다는 것이다. 대신 lam
의 가능한 값에 대한 분포가 있다. lam
의 각 값에 대하여, 골의 분포는 포아송 분포를 따른다. 따라서 골의 전체 분포는 포아송 분포의 혼합형(mixture)로 lam
의 분포 확률에 따라 가중치를 적용한 꼴을 띈다.
lam
의 사푸 분포에 대하여 골의 분포를 생성하는 법은 다음과 같다.
1 2 3 4 5 6 7 8 9 10 |
def MakeGoalPmf(suite): metapmf = thinkbayes.Pmf() for lam, prob in suite.Items(): pmf = thinkbayes.MakePoissonPmf(lam, 10) metapmf.set(pmf, prob) mix = thinkbayes.MakeMixture(metapmf) return mix |
결과적으로, 브루인스가 다음 게임에서 3골 이하를 받을 가능성이 더 적고, 4골 이상을 받을 가능성이 더 높다.
7.5 이길 확률
이길 확률을 구하기 위해서는, 골 수 차이의 분포를 계산하여야 한다.
1 2 3 4 5 |
goal_dist1 = MakeGoalPmf(suite1) goal_dist2 = MakeGoalPmf(suite2) diff = goal_dist1 - goal_dist2 |
mixutre
가 가지는 -
연산은 Pmf.__sub__
를 내부적으로 호출하게 된다.
결과적으로 골의 차이가 양수이면, 브루인스가 이기고, 음수라면 캐넉스가 이기게 될 것이다.
1 2 3 4 |
p_win = diff.ProbGreater(0) p_loss = diff.ProbLess(0) p_tie = diff.Prob(0) |
결과는 p_win
이 46%, p_loss
가 37%, p_tie
가 17%이다. 동점일 경우 서든데스가 진행될 것이다. 이것에 대한 계산 방법도 알아보도록 하자.
7.6 서든 데스
서든 데스를 계산하기 위해서는, 게임당 골 수보다는 처음 골을 넣기 까지의 걸리는 시간을 계산하여야 한다.
게임당 평균 골 수에 대하여 골 간의 시간을 계산할 수 있다.
1 2 3 |
lam = 3.4 time_dist = thinkbayes.MakeExponentialPmf(lam, high=2, n=101) |
high
는 분포의 상한 값으로 점수 없이 두 게임 이상 진행될 확률은 미미하므로 2로 선택하엿다.
역시 우리는 lam
을 정확히 알지 못하기 때문에 사푸 분포를 통하여 Pmf
의 mixture를 계산할 것이다.
1 2 3 4 5 6 7 8 9 10 |
def MakeGoalTimePmf(suite): metapmf = thinkbayes.Pmf() for lam, prob in suite.Items(): pmf = thinkbayes.MakeExponentialPmf(lam, high=2, n=2001) metapmf.Set(pmf, prob) mix = thinkbayes.MakeMixture(metapmf) return mix |
마찬가지로 두 확률의 차이를 계산한 뒤 살펴보자.
1 2 3 4 |
time_dist1 = MakeGoalTimePmf(suite1) time_dist2 = MakeGoalTimePmf(suite2) p_overtime = thinkbayes.PmfProbLess(time_dist1, time_dist2) |
브루인스가 먼저 득점할 확률은 52% 이다.
결과적으로 경기에서 먼저 이길 확률은 경기에서 결판이 났을 경우와 무승부 후에 골을 넣어 이길 확률의 합이다.
1 2 3 4 5 |
p_tie = diff.Prob(0) p_overtime = thinkbayes.PmfProbLess(time_dist1, time_dist2) p_win = diff.ProbGreater(0) + p_tie * p_overtime |
결과는 브루인스가 다음 경기에서 이길 총 확률은 55%이다.
이 시리즈에서 우승하려면 브루인스는 다음 두 게임을 이기거나 다음 두 게임까지 승점이 동일하고 세 번째에서 이겨야 한다. 이 확률을 계산해 보자.
1 2 3 4 |
p_series = p_win**2 p_series += 2 * p_win * (1-p_win) * p_win |
브루인스가 시리즈에서 이길 확률은 57%이다.
7.7 토의
- 사전 분포는 각 팀의 게임 당 평균 골 수에 기반하여 선택하였다. 특정 상대에 대해서 통계를 낸다면 더 정교하게 모델을 다듬을 수 잇다.
- 확률을 갱신하는데 처음 네 개 경기 데이터만 사용했다. 만약 시즌 내내 같은 구성의 팀과 경기를 계속하였다면 모든 결과를 그대로 적용할 수 있다. 이 때, 주의할 점은 선수 구성이 달라짐에 따라 조건이 변해버린다는 것이다. 최근 게임에 가중치를 더 주는 방법을 생각할 수 있다.
- 모든 정보를 활용하려면, 각 팀의 경기 조합에 대하여 고려하여야 한다.