1 정의
검정력 분석이란, 실험에서 실제로 존재하는 효과를 감지할 확률(검정력, \(1-\beta\))을 사전에 계산하고, 이를 기반으로 필요한 표본 크기를 결정하는 과정이다.
네 가지 요소가 상호 연결되어 있으며, 셋을 고정하면 나머지 하나가 결정된다:
유의 수준 (\(\alpha\)): 1종 오류 허용 확률 (보통 0.05)
검정력 (\(1-\beta\)): 2종 오류를 피할 확률 (보통 0.80)
효과 크기 (MDE): 감지하고자 하는 최소 차이
표본 크기 (\(n\)): 그룹당 필요한 관측 수
역학: Power analysis, Sample size determination (임상시험 설계의 핵심)
IT: Power analysis, Traffic estimation
2 Agent 실험에서 표본 크기가 중요한 이유
2.1 적은 트래픽의 현실
| 환경 | 일일 질의 수 | 50:50 분할 시 그룹당/일 |
|---|---|---|
| Google 검색 | 수십억 | 수십억 |
| 중형 SaaS | 수만 | 수만 |
| MINERVA QnA Chatbot | ~50 | ~25 |
| MINERVA Data Std Helper | ~30 | ~15 |
| MINERVA Insilico Agent | ~20 | ~10 |
Google은 하루 만에 수백만 표본을 모은다. MINERVA는 한 달이 걸려도 750건(QnA 기준)이다. 이 차이가 실험 설계의 모든 결정을 바꾼다.
2.2 검정력 부족의 결과
표본이 부족하면:
- 실제로 효과가 있어도 감지하지 못한다 (2종 오류, false negative)
- “유의하지 않다”는 결론이 “효과가 없다”가 아니라 “모르겠다”가 된다
- 의사결정을 내릴 수 없어 실험이 낭비된다
3 MDE 설정 가이드
3.1 MDE란
Minimum Detectable Effect(최소감지효과)는 “이 정도 차이는 감지하고 싶다”는 기준이다. MDE를 작게 설정할수록 더 많은 표본이 필요하다.
\[ n = \frac{(z_{\alpha/2} + z_\beta)^2 \cdot 2\sigma^2}{\text{MDE}^2} \]
3.2 Agent 메트릭별 MDE 가이드라인
| 메트릭 | 스케일 | 현재 기준선 (예시) | 권장 MDE | 근거 |
|---|---|---|---|---|
| Relevance Score | 1-5 | 3.5 (σ≈1.0) | 0.3~0.5 | 5점 척도에서 0.3점은 실무적으로 의미 있는 차이 |
| Faithfulness Score | 1-5 | 4.0 (σ≈0.8) | 0.3~0.5 | 환각 감소는 작은 차이도 중요 |
| Hit Rate@5 | 0-1 | 0.70 | 0.05~0.10 | 70% → 75%는 20건 중 1건 추가 hit |
| Task Completion | 0-1 | 0.60 | 0.05~0.10 | 비즈니스 임팩트 기준 |
| Latency (ms) | 연속 | 2000 (σ≈500) | 200~500 | 사용자 체감 차이 |
3.3 표본 크기 계산
import numpy as np
from scipy.stats import norm
def sample_size_continuous(mde, sigma, alpha=0.05, power=0.80):
"""연속형 메트릭의 그룹당 필요 표본 크기
Args:
mde: 감지하고자 하는 최소 차이 (원래 스케일)
sigma: 메트릭의 표준편차
alpha: 유의수준
power: 검정력
"""
z_alpha = norm.ppf(1 - alpha / 2)
z_beta = norm.ppf(power)
n = 2 * ((z_alpha + z_beta) * sigma / mde) ** 2
return int(np.ceil(n))
def sample_size_proportion(p_control, mde, alpha=0.05, power=0.80):
"""이진형 메트릭의 그룹당 필요 표본 크기
Args:
p_control: 대조군의 기대 비율
mde: 감지하고자 하는 절대 차이 (예: 0.05 = 5%p)
"""
p_treat = p_control + mde
p_avg = (p_control + p_treat) / 2
sigma = np.sqrt(2 * p_avg * (1 - p_avg))
z_alpha = norm.ppf(1 - alpha / 2)
z_beta = norm.ppf(power)
n = ((z_alpha + z_beta) / mde) ** 2 * 2 * p_avg * (1 - p_avg)
return int(np.ceil(n))
# MINERVA QnA Chatbot 예시
scenarios = {
"Relevance (MDE=0.3, σ=1.0)": sample_size_continuous(0.3, 1.0),
"Relevance (MDE=0.5, σ=1.0)": sample_size_continuous(0.5, 1.0),
"Hit Rate (p=0.70, MDE=0.05)": sample_size_proportion(0.70, 0.05),
"Hit Rate (p=0.70, MDE=0.10)": sample_size_proportion(0.70, 0.10),
}
for scenario, n in scenarios.items():
print(f"{scenario}: 그룹당 {n}건, 총 {2*n}건")
# 출력 예시:
# Relevance (MDE=0.3, σ=1.0): 그룹당 176건, 총 352건
# Relevance (MDE=0.5, σ=1.0): 그룹당 64건, 총 128건
# Hit Rate (p=0.70, MDE=0.05): 그룹당 1231건, 총 2462건
# Hit Rate (p=0.70, MDE=0.10): 그룹당 313건, 총 626건3.4 실험 기간 환산
def experiment_duration(n_per_group, daily_queries, split_ratio=0.5):
"""필요 표본 크기에서 실험 기간을 계산한다"""
queries_per_group_per_day = daily_queries * split_ratio
days = np.ceil(n_per_group / queries_per_group_per_day)
return {
"n_per_group": n_per_group,
"daily_queries": daily_queries,
"days": int(days),
"weeks": round(days / 7, 1),
}
# MINERVA 시나리오
print(experiment_duration(176, daily_queries=50))
# → {'n_per_group': 176, 'daily_queries': 50, 'days': 8, 'weeks': 1.1}
print(experiment_duration(176, daily_queries=20))
# → {'n_per_group': 176, 'daily_queries': 20, 'days': 18, 'weeks': 2.6}
print(experiment_duration(1231, daily_queries=50))
# → {'n_per_group': 1231, 'daily_queries': 50, 'days': 50, 'weeks': 7.1}4 표본 크기를 줄이는 전략
4.1 MDE를 현실적으로 조정
가장 직접적인 방법이다. “작은 효과도 놓치고 싶지 않다”는 욕심을 버리고, 의사결정에 실제로 영향을 주는 크기만 감지하도록 설정한다.
| 메트릭 | 작은 MDE (비현실적) | 현실적 MDE | 표본 감소율 |
|---|---|---|---|
| Relevance | 0.1 (σ=1.0) | 0.5 | ~96% 감소 |
| Hit Rate | 0.02 | 0.10 | ~96% 감소 |
4.2 분산 감소 (Variance Reduction)
메트릭의 분산(\(\sigma^2\))을 줄이면 동일 MDE에서 필요 표본이 줄어든다.
4.2.1 층화 (Stratification)
질의 유형별로 층화하면 그룹 간 불균형을 줄이고 분산을 감소시킨다.
def stratified_assignment(query, user_id, experiment_id, strata_map):
"""질의 유형별 층화 배정
각 층(stratum) 내에서 50:50 배정을 보장한다.
"""
# 질의 유형 분류
query_type = classify_query_type(query) # "정의", "비교", "절차" 등
stratum = strata_map.get(query_type, "default")
# 층 내에서 결정적 해싱
hash_input = f"{experiment_id}:{stratum}:{user_id}"
hash_value = int(hashlib.sha256(hash_input.encode()).hexdigest(), 16)
return "control" if (hash_value % 10000) < 5000 else "treatment"4.2.2 CUPED (Controlled-experiment Using Pre-Experiment Data)
실험 전 데이터(과거 응답 품질)를 공변량으로 사용하여 분산을 줄인다.
\[ \hat{Y}^{cuped} = Y - \theta(X - \bar{X}) \]
여기서 \(X\)는 실험 전 메트릭, \(\theta = \text{Cov}(X, Y) / \text{Var}(X)\)이다.
직관적으로, CUPED는 시험 점수에서 “원래 실력 차이”를 빼는 것이다. 학생 A는 원래 수학을 잘하고 학생 B는 못한다면, 새 교수법 실험에서 A의 점수가 높은 것은 교수법 효과가 아니라 원래 실력이다. CUPED는 실험 전 점수(\(X\))로 이 “원래 실력” 부분을 제거하여, 순수한 처치 효과만 남긴다. Agent 실험에서는 “이 질의 유형은 원래 어렵다”는 변동을 제거하는 것과 같다 — 쉬운 질의는 어떤 구성이든 점수가 높고, 어려운 질의는 낮으므로, 이 패턴을 빼면 구성 간 차이가 더 선명하게 드러난다.
def cuped_adjustment(y_experiment, x_pre_experiment):
"""CUPED 보정으로 분산을 줄인다
Args:
y_experiment: 실험 기간 메트릭
x_pre_experiment: 실험 전 동일 메트릭 (또는 상관 높은 메트릭)
"""
theta = np.cov(y_experiment, x_pre_experiment)[0, 1] / np.var(x_pre_experiment)
y_adjusted = y_experiment - theta * (x_pre_experiment - np.mean(x_pre_experiment))
variance_reduction = 1 - np.var(y_adjusted) / np.var(y_experiment)
return {
"y_adjusted": y_adjusted,
"theta": theta,
"variance_reduction": f"{variance_reduction:.1%}",
"original_std": np.std(y_experiment),
"adjusted_std": np.std(y_adjusted),
}Agent 실험에서 “실험 전 데이터”란?
- 실험 이전 기간에 동일 질의 유형에 대한 평균 Relevance Score
- 질의의 난이도 점수 (Golden Dataset에서 산출)
- 과거 동일 사용자의 평균 만족도
이 공변량과 실험 메트릭 간 상관이 높을수록 분산 감소 효과가 크다 (상관 0.5면 약 25% 분산 감소).
4.3 비열등성 설계 (Non-inferiority)
“새 구성이 더 좋은가?”가 아니라 “새 구성이 기존보다 나쁘지 않은가?”를 검정한다. 비용이 절감되거나 유지보수가 쉬운 구성이 기존과 동등하다면 채택할 가치가 있다.
비열등성 마진 \(\delta\)를 설정하고, \(H_0: \mu_T - \mu_C \leq -\delta\)를 검정한다.
이 설계는 우월성 검정보다 적은 표본으로 결론을 내릴 수 있다.
4.4 Paired Design
동일 질의에 대해 두 구성을 모두 실행하고 점수 차이를 검정한다 (paired t-test). 질의 간 변동이 제거되므로 분산이 크게 줄어든다.
def paired_sample_size(mde, sigma_diff, alpha=0.05, power=0.80):
"""대응 표본 설계의 필요 표본 크기
Args:
sigma_diff: 점수 차이(d = y_A - y_B)의 표준편차
"""
z_alpha = norm.ppf(1 - alpha / 2)
z_beta = norm.ppf(power)
n = ((z_alpha + z_beta) * sigma_diff / mde) ** 2
return int(np.ceil(n))
# 독립 설계: σ=1.0, MDE=0.3 → n=176/그룹
# 대응 설계: σ_diff=0.7 (질의 간 변동 제거), MDE=0.3 → n=86
n_paired = paired_sample_size(0.3, 0.7)
print(f"대응 설계: {n_paired}쌍 필요 (독립 설계 대비 {86/176:.0%})")오프라인 평가에서는 자연스럽지만, 온라인 실험에서는 같은 사용자에게 같은 질의로 두 번 응답하는 것이 불가능하다. 온라인에서 paired design을 적용하려면 interleaving 등의 특수 기법이 필요하다.
5 검정력 곡선 시각화
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
def power_curve(n_per_group, sigma=1.0, alpha=0.05):
"""주어진 표본 크기에서 MDE별 검정력을 계산한다"""
mde_range = np.linspace(0.05, 1.0, 100)
z_alpha = norm.ppf(1 - alpha / 2)
powers = []
for mde in mde_range:
se = sigma * np.sqrt(2 / n_per_group)
z_beta = mde / se - z_alpha
powers.append(norm.cdf(z_beta))
return mde_range, np.array(powers)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 왼쪽: 표본 크기별 검정력 곡선
for n in [50, 100, 200, 500]:
mdes, powers = power_curve(n)
axes[0].plot(mdes, powers, label=f"n={n}/group")
axes[0].axhline(y=0.8, color="red", linestyle="--", alpha=0.5, label="Power=0.80")
axes[0].set_xlabel("MDE (Relevance Score 차이)")
axes[0].set_ylabel("검정력 (Power)")
axes[0].set_title("표본 크기별 검정력 곡선")
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 오른쪽: MINERVA 트래픽별 실험 기간
daily_queries = [20, 50, 100]
mde_range = np.linspace(0.1, 1.0, 50)
for dq in daily_queries:
days = []
for mde in mde_range:
n = 2 * ((1.96 + 0.84) * 1.0 / mde) ** 2
d = np.ceil(n / (dq * 0.5))
days.append(min(d, 365))
axes[1].plot(mde_range, days, label=f"{dq} queries/day")
axes[1].axhline(y=30, color="red", linestyle="--", alpha=0.5, label="30일 한계")
axes[1].set_xlabel("MDE (Relevance Score 차이)")
axes[1].set_ylabel("필요 실험 기간 (일)")
axes[1].set_title("트래픽별 실험 기간")
axes[1].legend()
axes[1].grid(True, alpha=0.3)
axes[1].set_ylim(0, 120)
plt.tight_layout()
plt.show()6 의사결정 프레임워크
일일 트래픽 파악
↓
MDE 설정 (비즈니스 의미 기준)
↓
표본 크기 계산
↓
실험 기간 = 표본 크기 / (일일 트래픽 × 분할 비율)
↓
┌─ 기간 ≤ 2주? → 표준 A/B 진행
├─ 기간 2~4주? → Sequential testing 적용 (조기 종료 가능)
├─ 기간 1~2개월? → 분산 감소 + MDE 재조정
└─ 기간 > 2개월? → 오프라인 평가로 대체, 또는 MDE를 크게 높임
7 관련 주제
선행 지식
- 단순 A/B 테스트 설계 — 실험 설계의 기본 프로세스
- A/B 테스트 개요 — 검정력 분석의 통계적 기반
시리즈 다음 포스트
- Sequential Testing과 조기 종료 — 적은 트래픽에서 조기 종료 전략
다른 카테고리 연결
- 가설 검정 — 1종/2종 오류의 통계적 정의