LLM-as-judge를 위한 보정 세트 크기: 50개의 트레이스가 충분한 때와 200개가 필수적인 때
요약
LLM-as-judge 검증을 위한 보정 세트 크기는 라벨의 균형 상태에 따라 결정되어야 합니다. 균형 잡힌 이진 기준에서는 50개의 샘플로 충분하지만, 안전 위반과 같은 희귀 클래스가 포함된 불균형 데이터에서는 200개 이상의 샘플이 필요합니다.
핵심 포인트
- 보정 세트 크기는 고정값이 아닌 라벨 균형에 따라 가변적임
- 클래스 불균형 시 Cohen's kappa의 분산이 급격히 증가함
- 희귀 클래스(소수 클래스)의 수가 kappa의 정밀도를 결정함
- 안정적인 검증을 위해 층화 추출(stratified-sampling) 권장
요약(TL;DR). LLM-as-judge를 검증하기 위해 사용하는 사람이 라벨링한 보정 세트(calibration set)는 고정된 크기를 가질 필요가 없습니다. 대신 라벨이 얼마나 균형 잡혀 있는지에 따라 달라지는 크기가 필요합니다. 꼬리가 두껍지 않은 대략적으로 균형 잡힌 이진 기준(binary criteria)의 경우, 50개의 층화된 트레이스(stratified traces)만으로도 Cohen's kappa를 허용 가능한 범위 내로 고정할 수 있습니다 (제 실행 결과에 따르면, 95% 부트스트랩 구간(bootstrap interval)이 약 ±0.10에서 0.15 정도였습니다). 하지만 트레이스의 6%에서 나타나는 안전 위반(safety violation)과 같이 드물지만 비용이 많이 드는 카테고리가 있는 순간, 50개는 충분하지 않으며 200개 이상을 계획해야 합니다. 왜냐하면 kappa의 분산(variance)은 전체 수가 아니라 소수 클래스(minority-class) 예시의 수에 의해 지배되기 때문입니다. 아래에서는 kappa 공식과 왜 이것이 주변 분포(marginal distribution)에 민감한지, 표본 크기(sample-size)에 대한 직관, 소수 클래스 정밀도(precision)에 대한 Wilson 신뢰 구간(confidence intervals), 그리고 매주 주변 분포를 안정적으로 유지하는 층화 추출(stratified-sampling) 루틴을 설명합니다. 마지막에는 바로 복사하여 사용할 수 있는 Python 코드를 제공합니다.
1. Kappa, 그리고 왜 주변 분포가 생각보다 더 큰 역할을 하는가
Cohen's kappa (Cohen, 1960)는 우연히 발생할 것으로 예상되는 일치도를 보정한 후, 두 평가자 사이의 일치도를 측정합니다. 여기서 두 평가자는 사람 라벨러(human labeler)와 LLM-as-judge입니다. 공식은 kappa = (p_o - p_e) / (1 - p_e)이며, 여기서 p_o는 관찰된 일치도(observed agreement)이고 p_e는 주변 분포(marginals)로부터 계산된 우연한 일치도(chance agreement)입니다. 이진 라벨(binary label)의 경우, 사람이 확률 a로 "통과(pass)"라고 표시하고 평가자가 확률 b로 표시한다면, p_e = ab + (1-a)(1-b)가 됩니다.
사람들이 대충 훑고 지나가는 부분은 $p_e$가 레이블 주변 확률(label marginals)의 함수이며, 그 함수가 선형이 아니라는 점입니다. 클래스가 균형을 이룰 때(a와 b가 0.5에 가까울 때), $p_e$는 0.5 근처에 위치하고 분모도 0.5 근처가 되어 카파(kappa) 값이 안정적으로 나타납니다. 반면 한 클래스가 희귀할 때(a가 0.95에 가까울 때), $p_e$는 1에 가깝게 밀려나고 분모는 0을 향해 붕괴하며, 카파는 두 작은 수의 비율이 됩니다. 분모에 작은 숫자가 들어가는 것이 바로 불안정성을 초래하는 원인입니다. 이것이 카파 역설(kappa paradoxes)의 기원입니다. 단순히 주변 확률이 한쪽으로 치우쳐 있다는 이유만으로, 관찰된 일치도는 95%인데 카파 값은 0에 가까울 수 있습니다. 이는 평가자(judge)의 결함이 아닙니다. 우연한 일치(chance agreement)가 이미 매우 높은 분포에서, 우연 보정(chance-correction)이 설계된 대로 작동하고 있는 것입니다. 규모 산정(sizing)에 있어 실질적인 결과는 다음과 같습니다. 클래스 불균형(class-imbalanced) 세트는 트레이스(trace)당 정보량이 적으므로, 카파의 동일한 정밀도를 얻기 위해 더 많은 트레이스가 필요합니다. 과장하지 않도록 주의하고 싶습니다. 카파가 불균형 데이터에서 망가진 것은 아닙니다. 다만 분산(variance)이 더 높을 뿐이며, 그 분산에 대한 대가는 샘플 크기(sample size)로 지불해야 합니다.
제가 보고하는 방식에 대한 참고 사항입니다. 저는 카파를 가설 검정(hypothesis test)이 아닌 기술 통계(descriptive)로 취급합니다. 흥미로운 질문은 결코 "카파가 0보다 유의미하게 큰가"가 아닙니다(거의 항상 그렇으며, 그 기준은 무의미합니다). 질문은 "이 평가자가 인간을 대신할 수 있다고 신뢰할 수 있을 만큼 카파가 충분히 높고, 그 구간(interval)이 충분히 좁은가"입니다. 이는 구간의 폭에 관한 문제이며, 곧 $n$에 관한 문제입니다.
2. 50개 트레이스의 사례: 균형 잡힌 이진 기준, 헤비 테일(heavy tail) 없음
레이블이 대략적으로 균형 잡힌 이진 기준(binary criterion)이라면, 50개의 층화된(stratified) 인간 레이블만으로도 배포 결정을 내리기에 충분한 경우가 많습니다. 두 가지 조건이 충족되어야 합니다. 기준이 진정으로 이진적이고 대략적으로 균형이 잡혀 있어야 하며(각 클래스가 30~70% 범위 내에 있음), 별도로 신경 써야 할 '희귀하지만 비용이 많이 드는 테일(rare-but-costly tail)'이 없어야 합니다. 이러한 조건 하에서 카파의 분산은 가장 관대한 상태가 됩니다.
제 작업에서 얻은 구체적인 사례입니다. 저는 5단계 품질 척도(이전 팀이 심사관(judge)에 설정해 둔 1~5점 리커트 척도(Likert scale))를 사용하고 있었습니다. 인간과의 카파(Kappa) 계수는 0.47이었고, 80개의 예시에 대한 부트스트랩 구간(bootstrap interval)이 너무 넓어서 0.47과 0.35를 구분할 수 없을 정도였습니다. 문제는 5단계 척도였습니다. 척도가 5개의 버킷(bucket)으로 한계 분포(marginals)를 얇게 분산시켰기 때문에, 대부분의 셀(cell)에 데이터 개수가 매우 적었습니다. 저는 이를 세 가지 이진 기준(사실적으로 뒷받침되는가, 관련이 있는가, 완전한가)으로 나누고 동일한 트레이스(traces)에 다시 라벨을 붙였습니다. 균형 잡힌 상태에 가까웠던 "사실적으로 뒷받침되는가" 기준의 경우, 50개의 예시에서 카파 계수는 0.78이 나왔으며 배포하기에 충분히 안심할 수 있는 구간을 보였습니다. 동일한 트레이스, 동일한 심사관 프롬프트 구조를 사용했음에도 통계적 토대는 매우 달랐습니다. 솔직한 주의 사항을 덧붙이자면, 50개는 "이 심사관이 일반적인 사례에 대해 우리와 충분히 일치하는가"를 확인하는 데는 효과적입니다. 하지만 "이 심사관이 드물게 발생하는 나쁜 사례를 잡아내는가"를 확인하는 데는 효과적이지 않습니다.
3. 200개가 필수적이 되는 때: 헤비 테일(heavy tails)과 희귀하고 비용이 많이 드는 클래스
카파의 분산은 $n$에 반비례하며, 이는 누구나 알고 있는 사실입니다. 하지만 카파는 소수 클래스(minority class)의 희귀성(rarity)과도 비례하며, 이 부분은 예산을 책정할 때 고려하는 사람이 적습니다. 만약 당신이 중요하게 생각하는 카테고리가 트레이스의 6%에서 나타난다면, 50개의 트레이스 샘플에는 기대값 기준으로 해당 사례가 3개 포함됩니다. 단 3개입니다. 해당 카테고리에 대한 심사관의 재현율(recall) 추정치는 단 3개의 데이터 포인트에 의해 좌우됩니다. 이것이 제가 200개 이상의 샘플을 고집하는 이유입니다. 200이라는 숫자가 마법 같아서가 아니라, 그것이 소수 클래스의 개수에 미치는 영향 때문입니다. 6%의 기본율(base rate)에서 200개의 트레이스는 기대값으로 약 12개의 소수 사례를 제공하며, 400개는 약 24개를 제공합니다. 당신이 중요하게 생각하는 가장 희귀한 클래스를 선택하고, 그 클래스의 정밀도(precision)와 재현율(recall)을 허용 가능한 오차 범위 내에서 추정하기 위해 몇 개의 예시가 필요한지 결정한 다음, 기본율로부터 전체 $n$을 역산하십시오. 만약 6% 확률의 클래스에 대해 20개의 예시가 필요하다면, 대략 $20 / 0.06$인 300개 이상의 원시 샘플링(raw sampling)이 필요합니다. 또는 희귀 클래스를 의도적으로 오버샘플링(oversample)한 뒤 나중에 가중치를 부여하십시오.
이 시점에서 제가 계속해서 반복해 온 내용을 아주 솔직하게 언급하고자 합니다. 지표(metric)에 대한 불확실성 추정치(uncertainty estimate) 없이 수행하는 품질 탐지는 실제로 실패를 잡아내지 못합니다. 만약 환각(hallucination) 사례가 7개 포함된 샘플로부터 "판단자(judge)가 환각에 대해 0.81의 재현율(recall)을 보였다"라고 보고한다면, 당신은 재현율을 측정한 것이 아닙니다. 당신은 노이즈를 측정하고 이를 소수점 둘째 자리까지 반올림했을 뿐입니다. 만약 질문이 쌍체 비교(paired comparison)인 경우(예를 들어, 판단자 프롬프트를 변경한 후 동일한 트레이스(traces)에 대해 일치도(agreement)가 개선되었는지 알고 싶은 경우)에는 맥네마 검정(McNemar's test)이 적절한 도구입니다. 이 검정은 불일치 쌍(discordant pairs)만을 살펴보고, 두 종류의 불일치 사이의 비율 차이가 유의미한지 테스트합니다.
4. Wilson 구간: 작은 n에서 신뢰할 수 있는 클래스별 정밀도 수치
클래스별 정밀도(precision)와 재현율(recall)을 보고하고 나면(불균형한 데이터셋에서는 반드시 그래야 합니다. 집계된 카파(aggregate kappa) 값은 소수 클래스(minority class)를 숨기기 때문입니다), 작은 빈도수로부터 추정된 비율에 대한 신뢰 구간(confidence interval)이 필요합니다. 정규 근사법($p ext{ ± } 1.96 imes ext{sqrt}(p(1-p)/n)$)은 정작 가장 필요할 때 실패합니다. 즉, 빈도수가 작거나 비율이 0 또는 1에 가까울 때, 0 미만이나 1을 초과하는 구간을 생성합니다. Wilson (1927)은 제가 기본값으로 사용하는 구간을 제공합니다. 이 구간은 0.5 쪽으로 약간 치우친 값에 중심을 두고, 스코어 검정(score test)으로부터 너비를 도출하며, [0, 1] 범위 내에 머물고, 경계 근처에서 훨씬 더 나은 커버리지(coverage)를 가집니다. "판단자가 9개의 트레이스를 위반으로 표시했고 그중 7개가 실제 위반이었다"라는 경우, 9개 중 7개의 정밀도에 대한 Wilson 구간은 정직합니다. 동일한 9개 중 7개에 대한 정규 근사 구간은 그렇지 않습니다.
5. 시간 창(time windows)에 걸친 층화 추출(Stratified sampling): 주변 분포(marginals)의 안정성 유지
위의 모든 내용은 보정 세트(calibration set)의 레이블 분포가 실제 운영 환경(production)과 유사하다는 것을 전제로 합니다. 하지만 단순히 샘플링을 수행한다면 운영 환경은 드리프트(drift)가 발생하기 때문에 분포가 일치하지 않을 것입니다. 제가 한 번 경험한 적이 있는데, 사실적 정확도(factual accuracy)를 판별하는 판사(judge) 모델을 훈련하여 개발 세트(dev set)에서 카파(kappa) 계수 0.61을 얻었고, 이를 배포했습니다. 그런데 3주 후 새로운 샘플에 대해 측정한 카파 계수는 0.39로 떨어졌습니다. 입력 분포가 변화한 것이었습니다(보정 세트에 포함된 것보다 도메인 전문 용어가 더 많이 나타남). 카파 계수의 하락은 판사 모델의 성능이 저하된 것이 아니었습니다. 보정 세트가 더 이상 실제 작업(job)을 제대로 설명하지 못하게 된 것이 문제였습니다. 해결책은 시간 창(time windows)과 주변 분포(marginals)를 변화시키는 모든 공변량(covariate)에 대해 층화 추출(stratified sampling)을 수행하는 것입니다. 즉, 주 단위 층(weekly strata)으로 트레이스(traces)를 가져오고, 각 주 내에서 비례적으로 샘플링하며, 희귀 클래스(rare class)는 각 창 내에서 충분한 수를 확보할 수 있도록 오버샘플링(oversample)하는 것입니다. 이렇게 하면 주 단위로 주변 분포를 안정적으로 유지할 수 있으며 드리프트(drift)를 감시할 수 있습니다. 만약 이번 주의 층화 샘플이 지난주의 구간(interval)을 벗어나는 카파 계수를 보여준다면, 이는 샘플링 노이즈(sampling noise)가 아니라 드리프트 신호이기 때문입니다.
복사 가능한 Python 코드
import numpy as np from sklearn.metrics import cohen_kappa_score
1. 인간의 레이블과 판사의 레이블 사이의 Cohen's kappa.
human = np.array(["pass", "fail", "pass", "pass", "fail", "pass"]) judge = np.array(["pass", "fail", "pass", "fail", "fail", "pass"]) print(f"kappa = {cohen_kappa_score(human, judge):.3f}")
2. 비율에 대한 Wilson score interval (작은 n에 대한 클래스별 정밀도/재현율).
def wilson_interval(successes, n, z=1.96): if n == 0: return (0.0, 1.0) phat = successes / n denom = 1 + z**2 / n center = (phat + z**2 / (2 * n)) / denom half = (z / denom) * np.sqrt(phat * (1 - phat) / n + z**2 / (4 * n**2)) return (max(0.0, center - half), min(1.0, center + half)) low, high = wilson_interval(7, 9) print(f"precision 7/9 = 0.778, Wilson 95% CI = [{low:.3f}, {high:.3f}]")
3. 주어진 n에서 kappa의 분산(및 신뢰 구간(CI))을 부트스트랩(Bootstrap) 합니다.
def bootstrap_kappa(human, judge, n_boot=2000, seed=0):
rng = np.random.default_rng(seed)
n = len(human)
human, judge = np.asarray(human), np.asarray(judge)
estimates = np.empty(n_boot)
for b in range(n_boot):
idx = rng.integers(0, n, size=n) # 쌍(pairs)의 레이블이 아닌 인덱스를 재표본 추출(resample)합니다.
estimates[b] = cohen_kappa_score(human[idx], judge[idx])
return {"kappa": cohen_kappa_score(human, judge),
"std": float(np.nanstd(estimates)),
"ci95": (float(np.nanpercentile(estimates, 2.5)), float(np.nanpercentile(estimates, 97.5)))}
rng = np.random.default_rng(1)
h = rng.integers(0, 2, size=50)
flip = rng.random(50) < 0.15
j = np.where(flip, 1 - h, h)
res = bootstrap_kappa(h, j)
print(f"n=50 kappa={res['kappa']:.3f} std={res['std']:.3f} 95% CI=[{res['ci95'][0]:.3f}, {res['ci95'][1]:.3f}]")
부트스트랩(bootstrap)을 통해 읽어낼 두 가지 사항이 있습니다. 첫째, std는 이 n에서의 kappa에 대한 표준 오차(standard error)이며, 둘째, 백분위수 CI(percentile CI)가 인용해야 할 범위입니다. 본인의 레이블로 n=50일 때와 n=200일 때 각각 실행해 보면 구간이 좁아지는 것을 볼 수 있습니다. 이 축소폭을 소수 클래스 비율(minority-class rate)로 스케일링한 것이 전체 크기 결정(sizing decision)의 핵심입니다.
참고문헌 (References)
Cohen, J. (1960). A coefficient of agreement for nominal scales. Educational and Psychological Measurement, 20(1), 37 to 46.
Wilson, E. B. (1927). Probable inference, the law of succession, and statistical inference. Journal of the American Statistical Association, 22(158), 209 to 212.
McNemar, Q. (1947). Note on the sampling error of the difference between correlated proportions or percentages. Psychometrika, 12(2), 153 to 157.
FAQ
kappa가 과연 적절한 지표일까요? 두 명의 평가자(raters)를 가진 명목형 레이블(nominal labels)의 경우 kappa는 합리적인 기본값입니다. 만약 레이블이 순서형(ordinal, 버킷 간 거리가 중요한 실제 1-to-5 척도)이라면 가중 kappa(weighted kappa)나 다중 신뢰 계수(intraclass correlation)가 더 적절합니다. 저는 순서형 평가자 척도는 피하고 이진 기준(binary criteria)으로 분해하는 것을 선호합니다.
이런 수학적 계산을 하는 대신 그냥 데이터를 더 라벨링(labeling)하면 안 될까요? 네, 가능합니다. 만약 라벨링 비용이 저렴하다면, 더 많은 데이터가 가장 깔끔한 해결책입니다. 수학이 중요한 이유는 라벨링 비용이 비쌀 때이며, 이는 희귀하고 비용이 많이 드는(rare-and-costly) 범주에서 흔히 발생하는 상황입니다.
왜 폐쇄형 분산(closed-form variance) 대신 부트스트랩(bootstrap)을 사용하여 카파(kappa) 값을 계산하나요? 폐쇄형 점근적 분산(closed-form asymptotic variance)이 존재하지만, 이는 점근적 결과(asymptotic result)이며 제가 실제로 작업하고 있는 작은 n(표본 크기)에서는 신뢰할 수 없습니다. 부트스트랩은 대표본 가정을 하지 않으며, 희귀 클래스의 퇴화(rare-class degeneracy) 문제를 직접적으로 드러내 줍니다.
판단자(judge)를 배포하기에 충분히 좋은 카파(kappa) 값은 얼마인가요? 보편적인 임계값은 없으며, 현재 유통되는 임계값들에 대해 저는 회의적입니다. 이는 판단자가 틀렸을 때 발생하는 비용에 따라 달라집니다. 제가 배포했던 저위험 분류 필터(low-stakes triage filter)의 경우 0.6대 후반의 값을 사용했습니다. 놓친 양성(missed positive)의 비용이 큰 경우에는 더 높은 카파 값과 특히 소수 클래스(minority class)에 대한 좁은 신뢰 구간을 원합니다.
다음에 테스트하고 싶은 미결 과제들
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기