Think Bayes (12) 증거
파이썬을 활용한 베이지안 통계, 앨런 B. 다우니 지음, 권정민 옮김/한빛미디어 |
12.1 SAT 점수 해석
공학대 입학 처장이 자격 조건이 유사한 두 명의 지원자 앨리스와 밥 사이에서 고민 중이라고 해보자. 앨리스는 SAT의 수학 점수가 더 높다. 800점 만전 시험에서 앨리스는 780점을, 밥은 740점을 받았다. 누구를 뽑을 것인지의 문제를 베이지안 가설 검정으로 풀어보자. “앨리스가 밥보다 나은 학생일 증거는 얼마나 강력한가?”
이 질문을 풀기 위해 모델링을 하여야 한다. 일단 임시로 모든 SAT 문항의 난이도가 동일하다고 보겠다. 그렇다면 각 응시자에 대해 p_correct
라는, 문제를 맞출 확률을 정의할 수 있다. 이를 통해 점수에 대한 우도 계산이 쉬워진다.
12.2 스케일
SAT는 맞은 문제 수에 기반한 원점수를 얻고, 이는 다시 200 ~ 800 범위의 스케일된 점수로 변환된다. 2009년 SAT 수학문제는 총 54개였고, 원점수는 맞춘 문제의 수에 답을 틀리게 쓴 문제에 대해 1/4점의 벌점을 뺀 점수이다.
SAT를 주관하는 컬리지 보드에서 원점수와 스케일된 점수의 비교표를 내놓앗다. 이를 토대로 순방향, 역방향 룩업을 제공하는 Interpolator 객체를 만들었다.
이 장에서 사용한 코드는 http://thinkbayes.com/sat.py 에서 받을 수 있다.
12.3 사전 분포
각 스케일된 점수를 원점수로 변환한 후에 문제 수로 나누면, p_correct
의 추정값이 된다. 그러면 원 점수의 분포를 사용해서 p_correct
의 사전 분포를 만들 수 있다.
1 2 3 4 5 6 7 8 |
class Exam(object): def __init__(self): self.scale = ReadScale() scores = ReadRanks() score_pmf = thinkbayes.MakePmfFromDict(dict(scores)) self.raw = self.ReverseScale(score_pmf) self.prior = DivideValues(raw, 54) |
Exam
은 시험에 대해 알고 있는 정보를 담는다. self.scale
은 원섬주와 스케일된 점수를 변환하는 Interpolator
다. scores
는 (점수, 빈도) 쌍의 리스트다.
score_pmf
는 스케일된 점수의 Pmf이다. self.raw
는 원점수의 Pmf, self.prior
는 p_correct
의 Pmf이다.
각 응시자에 대해 p_correct
의 분포를 나타내는 Sat
스윗을 정의했다.
1 2 3 4 5 6 7 8 9 10 11 |
class Sat(thinkbayes.Suite): def __init__(self, exam, score): self.exam = exam self.score = score for p_correct, prob in exam.prior.Items(): self.Set(p_correct, prob) self.Update(score) |
사전 분포의 복사본을 만들고 시험 점수 기반으로 이를 갱신한다.
1 2 3 4 5 6 7 8 |
def Likelihood(self, data, hypo): p_correct = hypo score = data k = self.exam.Reverse(score) n = self.exam.max_score like = thinkbayes.EvalBinomialPmf(k, n, p_correct) return like |
hypo
는 p_correct
의 가설 값이며, data
는 스케일된 점수다.
원점수를 맞춘 문제 수라고 한 후, 틀린 문제에 대한 벌점은 무시했다. 이렇게 단순환 뒤, n개의 문제 중 k개를 맞춰쓸 때의 확률을 계산한 이항분포를 우도로 사용했다.
12.4 사후 분포
이제 다음과 같이 두 개의 가설을 정의한다.
- A : p_correct는 밥보다 앨리스가 더 높다.
- B : p_correct는 앨리스가 밥보다 더 높다.
A의 우도 계산을 위해, 사후 분포 값의 모든 쌍을 나열하고 앨리스트의 p_correct
가 밥보다 더 높은 경우의 전체 확률을 더한다. 이는 thinkbayes.PmfProbGreater
를 사용하자.
A와 B의 사후 분포를 계산하는 스윗을 다음과 같이 정의하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class TopLevel(thinkbayes.Suite): def Update(self, data): a_sat, b_sat = data a_like = thinkbayes.PmfProbGreater(a_sat, b_sat) b_like = thinkbayes.PmfProbLess(a_sat, b_sat) c_like = thinkbayes.PmfProbEqual(a_sat, b_sat) a_like += c_like / 2 b_like += c_like / 2 self.Mult('A', a_like) self.Mult('B', b_like) self.Normalize() |
보통은 Likelihood
를 수정하지만 여기서는 양쪽 가설의 우도를 구하는 것이 더 간편하므로 Update
를 수정하엿다.
a_like
는 앨리스가 더 높을 확률, b_like
는 밥이 더 높을 확률이다. c_like
는 둘이 동일할 확률이지만, 이는 반올림 오차로 처리하여 양쪽에 동일하게 더해주도록 한다.
다음은 TopLevel
을 사용하는 코드이다.
1 2 3 4 5 6 7 |
exam = Exam() a_sat = Sat(exam, 780) b_sat = Sat(exam, 740) top = TopLevel('AB') top.Update((a_sat, b_sat)) top.Print() |
결과는 A의 우도가 0.79, B의 우도는 0.21이다. 우도비는 3.8로 랠리스가 밥보다 SAT를 잘 봤다는 증거가 된다.
12.5 더 나은 모델
지금까지는 SAT의 문제들이 모두 동일한 난이도라고 가정하였지만, 실제로는 그렇지 않을 것이다. 여기서부터는 더 나은 모델을 만든 다음 모델링 오차가 작다는 것을 증명해보고자 한다.
- 모든 시험 응시자가 동일한 efficicy 정도를 가졋다고 가정한다. 이 때 efficacy는 SAT문제에 응답하는 능력 척도다.
- 각 문제는 동일한 difficulty 레벨이라고 가정한다.
- 시험 응시자가 문항을 정확히 맞출 확률은 다음 함수에 따른다고 가정한다.
1 2 3 |
def ProbCorrect(efficacy, difficulty, a=1): return 1 / (1 + math.exp(-a * (efficacy - dfficulty))) |
참고로 이 함수는 아이템 응답 이론에서 사용되는 곡선의 단순화 버전이다. 이에 대한 내용은 위키피디아에서 Item reponse theory 항목을 참고하자.
efficacy
와 difficulty
가 같을 때 바르게 응답할 확률은 50%다. efficacy
가 증가함에 따라 이 확률은 100%로 올라간다. efficacy
가 줄어들거나 difficulty
가 증가하면 확률은 0%에 근접하게 된다.
응시자들 점수 전반의 efficacy
분포와 질문 간의 difficulty
분포가 주어지면, 원점수의 기대 분포를 구할 수 있다.
먼저 주어진 efficacy
의 사람에 대한 원점수의 분포를 구할 것이다.
1 2 3 4 5 6 7 8 |
def PmfCorrect(efficacy, difficulties): pmf0 = thinkbayes.Pmf([0]) ps = [ProbCorrect(efficacy, diff) for diff in difficulties] pmfs = [BinaryPmf(p) for p in ps] dist = sum(pmfs, pmf0) return dist |
difficulties
는 난이도의 리스트로 문제 당 값은 하나이다. ps
는 확률 리스트고 pmfs
는 값이 두 개인 Pmf객체이다. 이를 생성하는 함수는 다음과 같다.
1 2 3 4 5 6 |
def BinaryPmf(p): pmf = thinkbayes.Pmf() pmf.Set(1, p) pmf.Set(0, 1-p) return pmf |
dist
는 이들 Pmf의 합이다. 0은 더미로 작동한다.
만약 응시자의 efficacy를 알고 있다면 그 사람의 원점수에 대한 분포를 구할 수 있다. 다른 efficacy를 가진 사람의 그룹이라면, 원점수의 결과 분포는 혼합 형태일 것이다.
1 2 3 4 5 6 7 8 9 |
# class Exam: def MakeRawScoreDist(self, efficacies): pmfs = thinkbayes.Pmf() for efficacy, prob in efficacies.Items(): scores = PmfCorrect(efficacy, self.difficulties) Pmfs.Set(scores, prob) mix = thinkbayes.MakeMixture(pmfs) return mix |
이 함수는 응시자들의 efficacy의 분포를 나타내는 Pmf인 efficacies
를 만든다. 이 때 Pmf를 평균 0에 표준편차 1.5인 가우시안 분포라고 가정하였다. 이는 굉장히 임의적이지만, 질문에 옳게 답할 확률은 efficacy와 difficulty 간의 차이와 연관이 있으므로 efficacy 하나를 골라 동일인의 difficulty를 선택하면 이를 보정할 수 있다.
pmfs
는 각 efficacy 레벨마다 Pmf를 가지고 있으면서 해당 레벨의 응시자의 비율과 연결해주는 메타 Pmf이다. MakeMixture
는 이 메타 Pmf를 사용해서 혼합의 분포를 계산한다.
12.6 보정
난이도의 분포가 주어진다면 MakeRawScoreDist
를 사용해서 원점수의 분포를 구할 수 있지만, 우리는 원점수의 분포를 받아 난이도의 분포를 추론해야 한다.
난이도가 center
와 width
의 균등 분포를 따른다고 가정하자. MakeDifficulties
는 난이도의 리스트를 만든다.
1 2 3 4 |
def MakeDifficulties(center, width, n): low, high = center-width, center+width return numpy.linspace(low, high, n) |
실험 결과 center = -0.05, width = 1.8일 때 실제 데이터와 유사하였다. 이러한 가정에서 efficacy는 평균 0에 표준편차 1.5인 가우시안으로 주어졌을 때, 난이도의 범위는 -1.85에서 1.75정도 된다.
표에서 효과 레벨이 다른 응시자에 대한 ProbCorrect
의 범위를 나타내었다.
12.7 효과의 사후 분포
모델이 보정되엇으니 이제 앨리스와 밥의 efficacy 사후 분포를 계산할 수 있다. 다음은 새로운 버전의 Sat
클래스다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Sat2(thinkbayes.Suite): def __init__(self, exam, score): self.exam = exam self.score = score efficacies = thinkbayes.MakeGaussianPmf(0, 1.5, 3) thinkbayes.Suite.__init__(self, efficacies) self.Update(score) def Likelihood(self, data, hypo): efficacy = hypo score = data raw = self.exam.Reverse(score) pmf = self.exam.PmfCorrect(efficacy) like = pmf.Prob(raw) return like |
pmf
는 주어진 efficacy에 대한 응시자의 원점수의 분포고, like
는 관측된 점수에 대한 확률이다.
TopLevel
을 다시 사용하면, 우도비는 3.4로 계산된다. 이전에 모델에서 구한 값은 3.8보다 약간 더 작다. 따라서 이 모델은 데이터가 A에 대한 증거가 되긴 하지만 앞에서 했던 예측보다 더 약하다.
12.8 예측 분포
모델의 예측력이 주어졌을 때 다음과 같은 연관 질문을 할 수 있을 것이다.
“만약 앨리스와 밥이 SAT 수학시험을 다시 친다면 여전히 앨리스의 성적이 더 높을 확률을 얼마인가?”
다음의 두 단계를 통해 문제를 풀어보자.
- 효과의 사후 분포를 사용해서 각 시험 응시자의 원점우에 대한 예측 확률을 생성
- 두 예측 확률을 비교해서 또 앨리스의 성적이 더 높을 확률을 계산
1 2 3 4 5 6 7 8 9 10 11 |
exam = Exam() a_sat = Sat(exam, 780) b_sat = Sat(exam, 740) a_pred = exam.MakeRawScoreDist(a_sat) b_pred = exam.MakeRawScoreDist(b_sat) a_like = thinkbayes.PmfProbGreater(a_pred, b_pred) b_like = thinkbayes.PmfProbLess(a_pred, b_pred) c_like = thinkbayes.PmfProbEqual(a_pred, b_pred) |
앨리스가 두 번째 시험에서 더 성적이 좋을 확률은 63%, 밥이 더 시험을 잘 보거나 동일할 확률은 37%로 나왔다.
하지만 현재 앨리스의 efficacy가 높을 것에 대한 승상비는 3:1이지만, 다음 시험에서는 2:1로 줄어드는 것을 확인하자.
12.9 토의
여기서 p_correct
나 efficacy
처럼 그 값이 얼마인지는 알 필요가 없지만, 추정해야 하는 값을 가리켜 장애모수(nuisance paramter)라고 한다.