성공률이 10%포인트 올랐다는 숫자만으로 수정 효과를 판단하기는 어렵다. 같은 문제에서 무엇을 새로 맞혔고, 무엇을 새로 틀렸는지 짝을 유지해야 한다. McNemar 검정은 두 버전의 성공·실패를 이렇게 짝지어 비교할 때 쓸 수 있는 통계 도구다. 효과의 크기나 배포 안전성을 대신 판정해 주지는 않는다.
앞선 글에서는 평가자끼리 같은 판정을 내리는 것과 그 판정이 맞는 것을 구분했다. 이제 채점 기준을 고정했다고 가정하고, 기준 버전 A와 수정 버전 B를 비교해 보자. 아래 두 묶음은 원리를 설명하려고 만든 서로 다른 합성 데이터다. 실제 모델을 실행해 얻은 성능이 아니다.
똑같은 80→90%에 서로 다른 변화가 숨어 있다
각 묶음에는 두 버전 모두 평가한 문제 100개가 있다. 한 문제에는 A의 판정과 B의 판정이 한 쌍으로 남는다. 성공 여부를 정하는 규칙과 입력 조건은 두 버전에 동일하게 적용했다고 가정한다.
| A → B | 합성 묶음 1 | 합성 묶음 2 |
|---|---|---|
| 성공 → 성공 | 80개 | 70개 |
| 성공 → 실패 (b) | 0개 | 10개 |
| 실패 → 성공 (c) | 10개 | 20개 |
| 실패 → 실패 | 10개 | 0개 |
| 전체 | 100개 | 100개 |
묶음 1에서 A는 80개, B는 90개를 맞혔다. 묶음 2에서도 A는 70+10=80개, B는 70+20=90개를 맞혔다. 두 묶음의 관측 차이는 모두 +10%포인트다. 그러나 첫 묶음에서는 새 실패 없이 10개가 바뀌었고, 둘째에서는 개선 20개와 악화 10개가 함께 생겼다.
관측 성공률 차이는 (c−b)/전체 문제 수로 계산한다. 이때 80%에서 90%로 오른 것은 10% 증가가 아니라 10%포인트 증가다. 상대 증가율로 쓰면 12.5%이므로 둘을 섞지 않는다.
McNemar 검정은 결과가 바뀐 쌍을 본다
정확 McNemar 검정은 서로 다른 결과가 나온 b+c개에 주목한다. 두 버전의 성공확률이 같다는 귀무가설 아래, 결과가 바뀐 쌍이 어느 방향으로 갈 확률도 1/2라는 조건부 이항 모형을 사용한다. 합성 묶음 1은 10개 중 개선 10개, 묶음 2는 30개 중 개선 20개다.
이 계산을 적용하려면 문제끼리의 쌍은 독립적으로 다룰 수 있어야 하고, 한 쌍 안에는 같은 문제에 대한 두 버전의 이진 판정이 있어야 한다. 판정 불가나 실행기 오류를 임의로 성공·실패에 끼워 넣지 않는다. 서로 다른 문제 목록으로 얻은 두 성공률에도 이 표를 그대로 적용할 수 없다. 더 넓은 업무로 일반화하려면 어떤 모집단에서 문제를 어떻게 추출했는지도 설명해야 한다. 임의로 고른 고정 문제집의 결과를 모든 요청의 성능으로 확대하지 않는다.
statsmodels 공식 문서는 2×2 표에 대해 exact=True일 때 이항분포를, exact=False일 때 카이제곱 근사를 사용한다고 설명한다. 여기서는 정확 이항 방식의 양측 p값만 계산한다. 연속성 보정은 근사 방식의 옵션이므로 아래 계산에 넣지 않는다.
- 묶음 1: b=0, c=10, 양측 p값 = 0.001953125
- 묶음 2: b=10, c=20, 양측 p값 ≈ 0.098737147
사전에 유의수준 0.05를 정한 단일 비교라면 첫 묶음은 그 기준보다 작고 둘째는 크다. 같은 +10%포인트라도 짝을 버리고 총점만 보면 드러나지 않는 차이다. 다만 첫 결과가 ‘B가 더 좋을 확률 99.8%’라는 뜻은 아니다. 둘째 역시 ‘효과가 없음’이나 ‘두 버전이 동등함’을 증명하지 않는다. p값은 정해진 가설과 가정 아래 관측한 것만큼 또는 더 극단적인 불균형이 얼마나 드문지를 나타내는 값이다.

설치 없이 합성 표를 검산하는 코드
다음 코드는 성공·실패가 바뀐 두 칸의 수만 받아 계산한다. 귀무확률 1/2에서는 분포가 대칭이므로 작은 쪽 꼬리 확률을 두 배로 하고 1을 넘지 않게 제한한다. 임의의 귀무확률에 쓸 수 있는 일반 이항검정 함수는 아니다. 큰 데이터용 구현 대신 작은 교육용 표를 확인하는 코드다.
from math import comb
def exact_mcnemar_p(b, c):
if type(b) is not int or type(c) is not int:
raise ValueError("integer counts required")
if b < 0 or c < 0:
raise ValueError("nonnegative counts required")
n = b + c
if n == 0:
return None # 바뀐 쌍 없음: 정보 부족으로 별도 표시
tail = sum(comb(n, k) for k in range(min(b, c) + 1))
return min(1.0, 2 * tail / (2 ** n))
for b, c in [(0, 10), (10, 20)]:
print(f"{exact_mcnemar_p(b, c):.9f}")
출력은 차례로 0.001953125와 0.098737147이다. Python 3.12.14에서 합성 표의 산술을 실행해 확인했다. 실제 모델 호출이나 현장 성능 시험은 수행하지 않았다. b=c=0일 때 None을 돌려주는 것은 이 글의 보고 규칙이다. 그 경우 관측된 성공률 차이는 0이지만, 두 버전의 일반적 성능이 같다는 증거로 바꾸어 말하지 않는다.
쌍 100개가 독립된 문제 100개인지는 따로 확인한다
같은 문서에서 문장 100개를 뽑거나 한 질문을 100번 바꿔 썼다면, 공통 원인 때문에 결과가 함께 움직일 수 있다. 한 문제를 여러 번 실행한 기록도 고유 문제 수를 늘린 것으로 세면 안 된다. 이 글의 단순 검정은 문제 쌍 사이의 의존성을 처리하지 않는다. 문서·사용자·과업처럼 묶이는 단위를 먼저 정하고 그 구조에 맞는 평가 설계를 검토해야 한다.
확률적으로 답하는 에이전트에서는 각 문제의 한 번 실행이 안정성을 충분히 드러내지 못할 수 있다. 반복 횟수와 성공 판정 방식을 미리 정하고, 반복 결과를 독립 문제인 것처럼 풀어 넣지 않는다. 반복 평가 글의 고유 과업 수와 실행 수 구분이 여기에도 필요하다.
여러 프롬프트를 시험한 뒤 가장 작은 p값만 발표하거나, 0.05 아래가 될 때까지 사례를 추가하면 이 단일 비교의 해석을 그대로 유지할 수 없다. 비교 대상, 지표, 표본 수와 분석 계획을 먼저 정한다. 개발에 사용한 사례인지, 처음 보는 문제인지도 결과 옆에 남긴다.
개선 판단에는 통계와 실패의 무게가 함께 필요하다
묶음 2에서 새로 실패한 10개가 단순한 표현 문제인지, 잘못된 외부 전송인지에 따라 판단은 달라진다. 이 표는 모든 문제를 한 건으로 세며 피해의 크기를 계산하지 않는다. 중요한 실패를 전체 성공률 상승으로 상쇄하지 않는 별도 기준이 필요하다.
실무에서는 네 칸의 개수, 성공률 차이, 검사한 가정, 새 실패의 내용을 함께 남긴다. 모집단의 효과 크기를 추정하려면 쌍 구조를 고려한 신뢰구간 등 추가 분석도 필요하다. 여기서는 관측 차이와 정확 검정만 설명하므로 결과를 완결된 배포 승인으로 읽지 않는다.
비교 대상: 기준 A / 후보 B의 정확한 버전 평가 범위: 고유 문제 수, 입력·채점 기준 버전 제외·미판정: 개수와 사유, 처리 규칙 네 칸: 성공→성공 / 성공→실패 / 실패→성공 / 실패→실패 관측 차이: %포인트로 기록 분석: 양측 정확 McNemar, 사전 유의수준 가정 점검: 같은 문제의 쌍, 쌍 사이 독립성, 반복·군집 여부 새 실패: 심각도와 재검토 결과 남은 한계: 표본 범위, 효과 크기 불확실성, 다중 비교 여부
이미 회귀 검사표가 있다면 성공률을 다시 계산하기 전에 바뀐 두 칸을 세어 보자. 회귀 의심과 평가 환경 오류를 나누는 기록을 보존한 채 통계 해석을 덧붙이면 된다. 점수 상승과 그 상승을 믿을 근거는 같은 칸에 적을 수 없다.
출처와 적용 범위
- statsmodels 0.15.0 McNemar 문서: 2×2 표, 정확 이항 방식과 카이제곱 근사의 구분. 확인 당시 공식 문서 표기 버전이다.
- SciPy 1.18.0 binomtest 문서: 귀무 성공확률과 양측 검정의 의미. 본문은 SciPy나 statsmodels를 설치·호출하지 않는 표준 라이브러리 계산이다.
자료 확인: 2026년 10월 12일. 수치·문제 묶음·도식은 교육용 합성이고, 실행 확인은 위 산술 코드에 한정한다. AI Agent Fast Learning의 평가 방법 심화편이며 특정 모델이나 서비스의 성능을 주장하지 않는다.