Tech PostsTech Posts
실험 설계Experimental Design · 12 / 24
12 실험 설계Experimental Design

대시보드를 매일 보면 위양성률은 5%가 아니다Check the Dashboard Daily and Your False Positive Rate Is Not 5%

효과가 전혀 없는 실험도 계속 들여다보면 언젠가는 유의해집니다. 엿보기 문제와 그 대가를 시뮬레이션으로 확인합니다.An experiment with no effect will eventually cross the line if you keep watching. The peeking problem, simulated, and what it costs.

효과가 전혀 없는 A/B 테스트를 돌립니다. 표본 크기를 미리 정하고 다 채운 뒤 한 번만 검정합니다. 이 절차는 20번 중 1번꼴로 잘못된 승자를 선언합니다. 95% 신뢰수준이 제안하는 거래가 그것입니다.

Run an A/B test with no real effect. Fix the sample size in advance, then test once at the end. That procedure will wrongly declare a winner about one time in twenty. This is the deal a 95% confidence level offers.

이제 매일 아침 대시보드를 확인하고 유의해지는 순간 멈춘다고 해 봅시다. 위양성률은 더 이상 5%가 아닙니다. 얼마나 자주 보느냐와 얼마나 오래 기다릴 의향이 있느냐에 따라 30%를 넘길 수 있고 무한히 들여다보면 100%에 수렴합니다.

Now check the dashboard every morning and stop the moment it turns significant. The false positive rate is no longer 5%. Depending on how often you look and how long you are willing to wait, it can exceed 30% — and with unlimited looking it converges to 100%.

왜 이런 일이 생기는가Why it happens

p값은 눈앞의 데이터가 아니라 절차에 대한 진술입니다. "p < 0.05"의 뜻은 이렇습니다. 귀무가설이 참일 때, n개를 뽑아 한 번 검정하는 절차라면 이 정도로 극단적인 결과가 5%의 경우에 나온다.

A p-value is a statement about a procedure, not about the data in front of you. "p < 0.05" means: if the null were true, a procedure that samples n units and tests once would produce a result this extreme 5% of the time.

엿보기는 그 절차를 바꿉니다. 데이터가 쌓이는 동안 반복해서 검정하면 임계값을 넘을 기회를 여러 번 갖게 되고 처음 성공한 지점에서 멈춥니다. 누적 검정통계량은 확률보행처럼 떠돌고 표류가 없는 확률보행도 시간이 충분하면 어떤 고정된 경계든 건드립니다.

Peeking changes the procedure. Testing repeatedly as data accumulates gives you many chances to cross the threshold, and you stop at the first success. The cumulative test statistic wanders as a random walk, and a random walk with no drift still crosses any fixed boundary given enough time.

질문이 조용히 바뀐 것입니다. "효과가 있는가"에서 "이 확률보행이 한 번이라도 1.96에 닿았는가"로. 두 번째 질문의 답은 대개 예입니다.

You have quietly converted the question from "is there an effect?" into "did this random walk ever touch 1.96?" — and the answer to the second is usually yes.

검정 횟수위양성률
1회 (마지막에 한 번)약 5%
실험 중 5회약 14%
실험 중 10회약 19%
연속 모니터링100%에 접근
Number of looksFalse positive rate
Once, at the endabout 5%
5 times during the testabout 14%
10 times during the testabout 19%
Continuous monitoringapproaching 100%

더 미묘한 두 번째 비용이 있습니다. 엿보다가 "발견한" 효과는 체계적으로 과장됩니다. 잡음이 마침 유리하게 작용하는 순간에 정확히 멈추기 때문입니다. 그렇게 이긴 안은 후속 실험에서 재현되지 않고 팀은 후속 실험이 잘못됐다고 결론 내립니다.

There is a subtler second cost. Effects "discovered" by peeking are systematically overstated, because you stop precisely when noise happens to be working in your favour. The winning variant then fails to reproduce in the follow-up, and the team concludes the follow-up was flawed.

기준선: 한 번만 검정하기The baseline: testing once

먼저 올바른 동작을 확인해 둡시다. 표본 크기를 미리 정하고 끝까지 돌리고 한 번 검정합니다. 귀무가설이 참일 때 기각은 약 5%에서 일어나고 명목 수준과 일치합니다. 이게 기준점입니다. 이후의 모든 것은 여기서 얼마나 벗어나는지를 잽니다.

First establish the correct behaviour. Fix the sample size in advance, run to completion, test once. Under a true null, rejection happens about 5% of the time, matching the nominal level. That is the reference point, and everything after measures the departure from it.

python
import numpy as np
from scipy import stats

rng = np.random.default_rng(0)

def one_shot(n_per_arm, base_rate=0.10, lift=0.0, alpha=0.05):
    """Fixed sample size, tested exactly once. The honest procedure."""
    a = rng.binomial(1, base_rate, n_per_arm)
    b = rng.binomial(1, base_rate * (1 + lift), n_per_arm)
    return stats.ttest_ind(a, b).pvalue < alpha

# no true effect: lift = 0
rejections = [one_shot(5_000) for _ in range(10_000)]
print(f"false positive rate, single test: {np.mean(rejections):.3f}")
false positive rate, single test: 0.049

전환은 이진 결과이므로 평균은 곧 비율이고 정규근사를 쓸 수 있게 해 주는 것이 중심극한정리입니다. 다만 기저 전환율이 아주 낮으면 그 근사의 수렴이 느리다는 점은 기억해 두십시오. 희귀 사건 지표에서 실제로 문제가 됩니다.

Because conversion is binary, the mean is a proportion, and it is the Central Limit Theorem that licenses the normal approximation. Note that with very low base rates this approximation converges slowly, which is a real issue for rare-event metrics.

엿보기의 대가 측정하기Measuring what peeking costs

이제 같은 데이터에 다른 정지 규칙을 씌워 봅니다. 실험이 진행되는 동안 여러 번 확인하고 한 번이라도 유의해지면 멈추고 승리를 선언합니다.

Now apply a different stopping rule to the same data. Check several times as the experiment runs and stop the moment it crosses, declaring a win.

python
def with_peeking(n_per_arm, looks, base_rate=0.10, alpha=0.05):
    """Same data, but inspected `looks` times. Stop at the first crossing."""
    a = rng.binomial(1, base_rate, n_per_arm)
    b = rng.binomial(1, base_rate, n_per_arm)          # still no true effect
    checkpoints = np.linspace(n_per_arm // looks, n_per_arm, looks).astype(int)

    for k in checkpoints:
        if stats.ttest_ind(a[:k], b[:k]).pvalue < alpha:
            return True                                # we would have stopped here
    return False

for looks in [1, 2, 5, 10, 20]:
    rate = np.mean([with_peeking(5_000, looks) for _ in range(4_000)])
    print(f"looks={looks:>3}  false positive rate={rate:.3f}")
looks=  1  false positive rate=0.050
looks=  2  false positive rate=0.083
looks=  5  false positive rate=0.142
looks= 10  false positive rate=0.190
looks= 20  false positive rate=0.248

대신 무엇을 할 것인가What to do instead

정당한 선택지는 셋이고 대체로 이 순서로 검토하면 됩니다.

Three legitimate options, in roughly the order you should consider them.

표본 크기를 정하고 기다린다Fix the sample size and wait

검출하고 싶은 최소 효과 크기와 기저 전환율에서 필요한 n을 계산하고 도달할 때까지 보지 않습니다. 단순하고 올바르며, 인내심을 요구한다는 이유로 인기가 없습니다.

Compute the required n from your minimum detectable effect and baseline rate, then do not look until you reach it. Simple, correct, and unpopular because it demands discipline.

순차 검정을 쓴다Use a sequential test

정말로 계속 모니터링해야 한다면 — 해로운 안을 일찍 잡아내려면 대개 그래야 합니다 — 그렇게 설계된 방법을 쓰십시오. 알파 소비 함수(O'Brien–Fleming, Pocock)는 미리 정한 중간분석 횟수에 오차 예산을 나눠 배분합니다. 혼합 순차확률비검정을 쓰는 always-valid p값과 신뢰수열은 무제한 연속 모니터링을 허용하면서 보장을 유지합니다. 요즘 실험 플랫폼은 대부분 둘 중 하나를 구현합니다.

If you genuinely need to monitor continuously — and you often do, to catch harmful variants early — use a method designed for it. Alpha-spending functions (O'Brien-Fleming, Pocock) allocate the error budget across a pre-specified number of interim analyses. Always-valid p-values and confidence sequences, built on mixture sequential probability ratio tests, permit unlimited continuous monitoring while preserving the guarantee. Most modern experimentation platforms implement one of these.

베이지안으로 다시 세운다Reformulate as Bayesian

B가 A를 이길 사후확률은 빈도주의 오차율이 아니고 반복 관찰에 같은 방식으로 무너지지 않습니다. 공짜 통행증은 아닙니다. 의사결정 규칙은 여전히 보정이 필요하고 사전분포 선택이 실제로 일을 합니다. 다만 문제의 성격이 바뀝니다.

A posterior probability that B beats A is not a frequentist error rate and does not decay under repeated inspection in the same way. This is not a free pass — the decision rule still needs calibrating and the prior does real work — but it changes the problem.

실무에서In practice

  • 정지 규칙을 시작 전에 적어 두십시오. 표본 크기, 기간, 주요 지표, 판단 임계값. 이 습관 하나가 실험 병리의 대부분을 막고 나중에 결과를 두고 벌어지는 논쟁을 다룰 수 있게 만듭니다.
  • 주 단위로 돌리십시오. 요일별로 트래픽 구성이 다릅니다. 화요일에 시작해 금요일에 끝난 실험은 한 주를 온전히 덮은 실험과 표본 모집단이 다릅니다. 온전한 주기는 새것 효과에 대한 방어이기도 합니다.
  • 주요 지표는 하나. 10개 지표를 5%에서 검정하면 최소 하나가 위양성일 확률이 약 40%입니다. 판단에 쓸 지표 하나를 지정하고 나머지는 탐색용으로 두십시오.
  • 가드레일 지표는 해로움을 보려고 보는 것이지 승리를 보려고 보는 게 아닙니다. 지연시간, 오류율, 매출의 심각한 악화를 감시하는 것은 정당하고 중단 사유가 됩니다. 주요 지표를 보며 일찍 이기기를 기다리는 것은 엿보기입니다. 이 비대칭은 의도된 것입니다.
  • 표본 비율을 확인하십시오. 50/50으로 배정했는데 큰 n에서 52/48이 관측된다면 무언가 고장 난 것입니다. 리다이렉트 실패, 봇 필터링 편향, 로깅 버그 같은 것들입니다. 표본 비율 불일치는 주요 지표가 뭐라고 하든 실험을 무효화합니다.
  • 순차 방법은 검정력을 대가로 지불합니다. always-valid 추론은 공짜가 아닙니다. 연속 모니터링에서 보장을 지키려면 구간이 넓어야 하고 같은 효과를 검출하는 데 더 많은 데이터가 필요합니다. 들여다볼 권리의 가격이고, 대개 지불할 값어치가 있습니다.
  • 기저율을 기억하십시오. 대부분의 아이디어가 작동하지 않는다면 — 대부분의 조직에서 그렇습니다 — 5% 유의수준과 80% 검정력으로 올바르게 돌린 실험조차 "승리" 중 상당 비율이 거짓 발견입니다. 여기 붙는 베이즈 산수는 정신이 번쩍 들게 하고 놀라운 결과는 출시 전에 재현해 보라고 말합니다.
  • Write the stopping rule down before launch. Sample size, duration, primary metric, decision threshold. This single habit prevents most experimentation pathologies and makes later disagreements tractable.
  • Run for whole weeks. Traffic composition varies by day. A test that starts Tuesday and ends Friday samples a different population than one covering a full week. Full cycles also protect against novelty effects.
  • One primary metric. Testing ten metrics at 5% gives roughly a 40% chance of at least one false positive. Designate one metric for the decision and treat the rest as exploratory.
  • Watch guardrails for harm, not for wins. Monitoring latency, error rates and revenue for serious degradation is legitimate and should trigger a stop. Monitoring the primary metric hoping to stop early on a win is peeking. The asymmetry is intentional.
  • Check the sample ratio. If you allocated 50/50 and observe 52/48 at large n, something is broken — a failing redirect, uneven bot filtering, a logging bug. A sample ratio mismatch invalidates the experiment regardless of what the primary metric says.
  • Sequential methods cost power. Always-valid inference is not free: preserving the guarantee under continuous monitoring requires wider intervals, so you need more data to detect the same effect. That is the price of being allowed to look, and it is usually worth paying.
  • Remember the base rate. If most of your ideas do not work — and in most organisations they do not — then even a correctly run test at 5% significance and 80% power produces a substantial share of false discoveries among its wins. The Bayesian arithmetic here is sobering, and it argues for replicating surprising results before shipping them.

이 편까지는 무작위 배정이 가능하다고 가정했습니다. 인과추론 편부터는 훨씬 흔한 상황을 다룹니다. 처치는 이미 일어났고 동전 던지기로 배정되지 않았으며, 반사실을 관측 데이터에서 재구성해야 하는 경우입니다.

Everything up to here assumed you could randomise. The Causal Inference section deals with the far more common situation: the treatment already happened, it was not assigned by coin flip, and you have to reconstruct the counterfactual from observational data.