모델 B가 쉬운 입력과 어려운 입력에서 모두 A보다 성공률이 높은데, 전체 성공률은 A가 더 높을 수 있을까? 두 모델에 들어간 입력 구성이 다르면 가능하다. 각 그룹의 비율을 합칠 때 서로 다른 가중치가 적용되기 때문이다. 이런 집계의 역전은 심슨 역설을 이해하는 한 가지 예다.
아래 성공·전체 건수는 모두 설명용 합성 수치다. A와 B는 서로 다른 요청 묶음을 처리했다고 가정한다. 실제 모델 성능이나 사이트 운영 실적이 아니며, 같은 입력을 두 모델에 넣은 대응 실험도 아니다. ‘성공’과 ‘쉬움·어려움’은 두 모델에 공통된 고정 기준으로 판정했다고 가정한다.
두 그룹에서는 B, 전체에서는 A가 높다
| 입력 그룹 | A 성공/전체 | A 성공률 | B 성공/전체 | B 성공률 |
|---|---|---|---|---|
| 쉬움 | 90/100 | 90% | 19/20 | 95% |
| 어려움 | 8/10 | 80% | 81/100 | 81% |
| 전체 | 98/110 | 89.090909% | 100/120 | 83.333333% |
쉬운 입력에서는 B가 5퍼센트포인트(%p), 어려운 입력에서는 1%p 높다. 그런데 전체를 합치면 A가 약 5.757576%p 높다. 분자와 분모를 그대로 더한 전체 성공률 계산은 맞다. 문제는 그 두 전체 값이 같은 난도 구성을 대표하지 않는다는 점이다. 비율 차이의 단위는 %p와 상대 변화 계산처럼 구별해 적는다.
A의 쉬운 입력은 100/110으로 약 90.91%다. B의 쉬운 입력은 20/120으로 약 16.67%뿐이다. A는 성공하기 쉬운 요청을 훨씬 많이 받았다. B는 성공률이 낮은 어려운 요청이 약 83.33%를 차지한다. 각 그룹에서 B가 높다는 정보와, B가 더 어려운 요청 묶음을 받았다는 정보가 전체 비율에 함께 섞인 것이다.
전체 성공률은 그룹별 성공률의 건수 가중평균이다
모델별 전체 성공률은 각 그룹의 성공률 × 그 모델에서 해당 그룹이 차지한 비중을 더한 값이다. A와 B의 식을 나란히 쓰면 가중치 차이가 드러난다.
- A: (100/110)×0.90 + (10/110)×0.80 = 98/110
- B: (20/120)×0.95 + (100/120)×0.81 = 100/120
그룹별 평균과 건수로 전체 평균을 합치는 법과 같은 가중평균 원리다. 여기서는 그 원리가 두 대상의 순위까지 바꾸는 상황을 살펴본다. 그룹 평균의 단순평균을 원래 전체 성공률이라고 부르면 안 된다.
E. H. Simpson의 1951년 원 논문은 분류한 표에서의 관계와 합친 표에서의 관계가 달라질 수 있고, 숫자만으로 해석을 기계적으로 정하기 어렵다는 점을 다룬다. 이 글의 모델·난도 표는 논문의 카드나 처치 예를 옮긴 자료가 아니라, 계산을 설명하기 위해 별도로 만든 합성 예다.
같은 가중치로 표준화하면 다른 질문에 답한다
‘쉬운 입력 50%, 어려운 입력 50%라는 공통 구성에서 두 관측 비율을 비교하면?’이라고 질문해 보자. 같은 가중치 0.5·0.5를 적용한 표준화 성공률은 A가 0.5×0.90+0.5×0.80=85%, B가 0.5×0.95+0.5×0.81=88%다. 같은 구성의 계산에서는 B가 3%p 높다.
이 85%와 88%는 실제 요청을 50:50으로 다시 실행해 얻은 성공률이 아니다. 기존 그룹별 관측 비율을 공통 가중치에 대입한 표준화 계산값이다. 실제 관측된 89.090909%와 83.333333%를 지우거나 잘못된 값으로 바꾸지 않는다. 관측값은 각자 받은 실제 구성의 결과, 표준화 값은 명시한 공통 구성의 비교라는 서로 다른 질문에 답한다.
50:50은 여기서 선택한 설명용 기준이다. 목표 사용자군의 난도 비중이라고 확인한 값은 아니다. 실제 판단에는 목표 모집단이나 고정 평가 설계에 맞는 가중치를 정하고 근거를 남겨야 한다. 이 예처럼 B의 비율이 모든 그룹에서 높다면, 두 모델에 같은 비음수 가중치를 적용해 합계가 1이 되도록 한 비교에서도 B가 높다. 다만 그 그룹별 차이가 재현될지나 인과적 효과인지는 이 산술로 알 수 없다.
본문 코드로 관측값과 표준화 값을 따로 검산한다
다음 Python은 분수를 사용해 계산하고 출력할 때만 반올림한다. Windows의 Python 3.14.3에서 본문 HTML의 코드 텍스트를 추출·실행했다. 실제 모델 호출, 유의성 검정, 신뢰구간 추정은 하지 않았다. 그룹의 전체 건수가 0이면 해당 비율을 추정할 수 없으므로 오류로 거절하고 0%로 채우지 않는다.
from fractions import Fraction
import json
def summarize(groups, weights):
if set(groups) != {"easy", "hard"} or set(groups) != set(weights):
raise ValueError("groups and common weights must match")
if any(w < 0 for w in weights.values()) or sum(weights.values()) != 1:
raise ValueError("nonnegative weights must sum to one")
rates = {}
for name, (success, total) in groups.items():
if type(success) is not int or type(total) is not int:
raise ValueError("integer counts required")
if total <= 0 or not 0 <= success <= total:
raise ValueError("positive group total and valid success required")
rates[name] = Fraction(success, total)
successes = sum(pair[0] for pair in groups.values())
total = sum(pair[1] for pair in groups.values())
result = dict(success=successes, n=total,
easy_share=Fraction(groups["easy"][1], total),
observed_rate=Fraction(successes, total),
standardized_rate=sum(weights[g]*rates[g] for g in groups))
return rates, result
def shown(result):
return {key: round(float(value), 6) if isinstance(value, Fraction) else value
for key, value in result.items()}
data = {
"A": {"easy": (90, 100), "hard": (8, 10)},
"B": {"easy": (19, 20), "hard": (81, 100)},
}
weights = {"easy": Fraction(1, 2), "hard": Fraction(1, 2)}
results = {}
for model, groups in data.items():
rates, result = summarize(groups, weights)
results[model] = result
print(model, json.dumps(shown(result)))
print("group_rates", json.dumps(shown(rates)))
print("observed_A_minus_B_pp", round(float(100*(
results["A"]["observed_rate"]-results["B"]["observed_rate"])), 6))
print("standardized_B_minus_A_pp", round(float(100*(
results["B"]["standardized_rate"]-results["A"]["standardized_rate"])), 6))
A {"success": 98, "n": 110, "easy_share": 0.909091, "observed_rate": 0.890909, "standardized_rate": 0.85}
group_rates {"easy": 0.9, "hard": 0.8}
B {"success": 100, "n": 120, "easy_share": 0.166667, "observed_rate": 0.833333, "standardized_rate": 0.88}
group_rates {"easy": 0.95, "hard": 0.81}
observed_A_minus_B_pp 5.757576
standardized_B_minus_A_pp 3.0
모델의 개선이라고 부르기 전에 비교 설계를 확인한다
먼저 성공·전체의 원래 건수와 모델별 그룹 구성을 남긴다. 다음으로 그룹 정의와 판정 기준이 공통인지, 누락이나 제외로 분모가 바뀌지 않았는지 확인한다. 그룹 안에도 언어·문서 길이·시기 등의 차이가 남을 수 있으므로 난도로 나눴다는 사실만으로 완전히 공정한 비교라고 할 수 없다. 결과를 본 뒤 유리한 그룹만 고르는 비교도 피한다.
같은 입력을 같은 비중으로 두 모델에 제공하면 이 예의 모델 간 그룹 비중 차이는 사라진다. 모든 그룹에서 B의 관측 비율이 높고 공통 비중을 쓴다는 조건에서는, 이 구성 차이를 이유로 전체에서만 A가 높아질 수 없다. 같은 사례 ID의 성공·실패를 비교하는 설계는 대응표와 McNemar 검정의 대상과 구별해서 읽으면 좋다.
표준화했다고 B로 바꾸면 3%p 개선된다는 인과 효과를 증명한 것은 아니다. 할당 방식과 남은 조건 차이, 표본의 불확실성, 그룹을 조정하는 것이 적절한지를 별도로 검토해야 한다. 채택에는 실패 내용·비용·운영 조건도 관계된다. 여기서 확인한 것은 서로 다른 구성의 두 관측 성공률을 그대로 같은 조건의 모델 우열로 읽을 수 없다는 점이다.
자주 묻는 질문
전체 성공률이 높은 쪽을 고르는 것은 잘못인가요?
그 전체 값이 알고 싶은 대상의 구성을 나타낸다면 유용한 관측이다. 다만 구성이 다른 두 전체 값만으로 같은 입력 조건에서 어느 쪽이 좋은지는 판단할 수 없다. 알고 싶은 대상과 비교 조건을 먼저 정한다.
공통된 50:50 평균으로 바꾸면 공정한 비교인가요?
50:50은 이 예의 계산 기준이다. 목표 이용자 구성을 대표하는지, 그룹 안에 조건 차이가 남아 있는지는 별도로 확인한다. 표준화 값을 실측값이나 인과 효과라고 부르지 않는다.
그룹별로 나누면 반드시 올바른 결론이 나오나요?
그렇지 않다. 나누는 변수의 의미와 측정·할당 과정에 따라 해석이 달라진다. 원래 건수와 정의를 남기고 비교 설계에 맞는 분석을 선택한다. 이 작은 합성 예만으로 실제 모델 채택을 결정할 수는 없다.