2026년 5월 6일 | 개발 공부
Bootstrap 신뢰구간은 손에 이미 있는 평가 샘플을 복원추출로 여러 번 다시 뽑고, 그때마다 같은 metric을 계산한 뒤, 나온 값들의 분포로 점수의 흔들림을 읽는 방법이다. 나는 처음 이걸 봤을 때 이름 때문에 괜히 큰 통계 기법처럼 느꼈는데, 막상 실험 로그에 붙여 보니 감각은 단순했다. 평균 하나만 적어 두면 결과가 단단해 보이지만, 그 평균이 샘플 몇 개에 기대고 있는지까지 같이 보려면 반복해서 다시 흔들어 보는 장치가 필요하다.
모델 평가표를 볼 때 가장 편한 숫자는 여전히 평균이다. AUC 0.842, MRR 0.317, nDCG 0.492처럼 한 줄로 정리되는 숫자는 비교하기 좋다. 문제는 그 숫자 하나가 얼마나 믿을 만한 차이인지를 바로 말해 주지는 않는다는 데 있다. 0.842와 0.846의 차이가 실제 개선인지, seed나 query 구성에 따라 쉽게 뒤집히는 흔들림인지 분리하지 않으면, 실험표가 결정을 도와주는 게 아니라 오히려 결정을 빨리 내리게만 만든다.
평균만 볼 때 생기는 착시
작은 실험에서는 특히 착시가 세다. test query가 50개뿐이거나, link prediction edge가 몇백 개 안 되거나, failure case가 특정 cluster에 몰려 있으면 평균 점수는 생각보다 예민하게 움직인다. 내가 GNN이나 GraphRAG 쪽 로그를 볼 때도 비슷했다. 전체 평균은 조금 좋아졌는데 특정 seed에서는 더 나빠지고, query group 하나만 빼면 순위가 뒤집히는 경우가 있었다. 이럴 때 평균만 보고 "개선"이라고 적으면 나중에 다시 열었을 때 꽤 민망하다.
그래서 bootstrap을 붙이는 목적은 화려한 통계 보고서를 만드는 것이 아니다. 내가 실제로 원하는 건 더 작다. 이 차이가 반복 샘플링 안에서도 버티는가, 어떤 샘플 묶음에서 결과가 뒤집히는가, 의사결정을 보류해야 할 만큼 구간이 겹치는가를 보고 싶은 것이다. 평균은 방향을 보여 주고, 신뢰구간은 그 방향이 얼마나 흔들리는지 보여 준다.
Bootstrap을 실험표에 붙이는 기본 흐름
가장 기본적인 절차는 어렵지 않다. 평가 단위를 먼저 정하고, 그 단위들을 같은 개수만큼 복원추출한다. 그리고 뽑힌 샘플 묶음마다 기존 metric을 다시 계산한다. 이 과정을 1,000번이나 10,000번 반복하면 metric 값들이 작은 분포를 만든다. 그 분포의 2.5%와 97.5% 지점을 잡으면 흔히 말하는 95% percentile bootstrap interval이 된다.
| 단계 | 내가 확인하는 질문 | 주의할 점 |
|---|---|---|
| 평가 단위 고정 | query 기준으로 뽑을지, edge 기준으로 뽑을지 | 서로 독립이 아닌 row를 함부로 섞지 않기 |
| 복원추출 반복 | 같은 크기의 평가 세트를 다시 만들었을 때 점수가 얼마나 움직이는지 | 반복 횟수가 너무 적으면 구간 끝이 거칠어짐 |
| metric 재계산 | 평균, MRR, nDCG, F1 같은 최종 지표가 반복마다 어떻게 달라지는지 | threshold를 test에서 다시 고르면 누수가 생김 |
| 구간 해석 | 두 설정의 구간이 얼마나 겹치고, 차이 분포가 0을 넘는지 | 구간 하나로 원인까지 설명하려고 하지 않기 |
여기서 제일 중요한 선택은 무엇을 한 샘플로 볼 것인가다. retrieval 평가라면 query 하나가 자연스러운 단위일 때가 많다. query별로 top-k 결과와 정답 여부가 묶여 있기 때문이다. link prediction에서는 edge 하나를 단위로 삼을 수도 있지만, graph split이나 node pair 구조가 강하게 묶여 있으면 seed 단위, graph 단위, 또는 source node 단위로 다시 생각해야 한다. bootstrap은 자동으로 독립성을 만들어 주지 않는다. 이미 잘못 자른 단위를 반복해서 뽑을 뿐이다.
구간보다 차이 분포를 먼저 보는 편
두 모델을 비교할 때는 각 모델의 신뢰구간을 따로 그리는 것보다, 같은 bootstrap sample에서 차이값을 직접 계산하는 쪽이 더 읽기 편했다. 예를 들어 같은 query 묶음을 복원추출한 뒤 profile A의 nDCG와 profile B의 nDCG를 계산하고, 매 반복마다 B minus A를 저장한다. 그러면 "B가 평균적으로 0.012 높다"에서 멈추지 않고, "반복 샘플 중 몇 %에서 B가 A보다 높았는가"까지 볼 수 있다.
이 방식이 좋은 이유는 비교의 단위가 맞춰지기 때문이다. A와 B가 서로 다른 query 묶음에서 따로 흔들린 값이 아니라, 같은 평가 샘플 위에서 같이 흔들린 값을 비교한다. 실제 의사결정도 보통 그렇게 한다. 나는 모델 A와 B를 추상적인 모집단 평균으로만 비교하는 게 아니라, 지금 들고 있는 query set에서 어떤 쪽을 기본값으로 둘지 결정해야 한다. 그럴 때 차이 분포는 평균표와 운영 판단 사이를 꽤 잘 이어 준다.
Threshold가 끼면 누수 경계를 먼저 고정
classification이나 link prediction처럼 threshold가 들어가는 평가는 한 번 더 조심해야 한다. bootstrap 반복마다 test sample에서 F1이 가장 좋은 threshold를 다시 고르면, 그건 더 이상 고정된 decision rule의 성능이 아니다. test를 보면서 규칙을 매번 다시 고르는 셈이라서 점수가 낙관적으로 부풀 수 있다. 내가 최근 threshold 관련 로그를 정리하면서 무릎을 쳤던 부분도 여기였다. threshold sweep은 분석 도구로는 좋지만, 운영 규칙 평가와 섞이면 해석이 흐려진다.
그래서 나는 보통 두 칸으로 나누려 한다. 하나는 validation에서 threshold를 고르는 칸이고, 다른 하나는 그 threshold를 test bootstrap에 그대로 적용하는 칸이다. 이렇게 하면 bootstrap 구간은 "test를 다시 보며 고른 최선"이 아니라 이미 정한 규칙을 다른 test 샘플 묶음에 적용했을 때의 흔들림이 된다. 이 차이는 작아 보이지만, 나중에 리포트를 읽을 때 의미가 완전히 달라진다.
내가 표에 남기고 싶은 최소 정보
실험표에 bootstrap을 붙일 때 모든 반복값을 본문에 다 넣을 필요는 없다. 오히려 너무 많은 숫자를 넣으면 다시 평균표만큼 읽기 어려워진다. 내가 남기고 싶은 최소 정보는 대략 네 가지다. 첫째, 평균 또는 median. 둘째, 95% 구간. 셋째, 비교 대상 대비 차이의 평균과 구간. 넷째, 반복 샘플 중 차이가 0보다 큰 비율이다. 이 네 줄만 있어도 "이겼다"와 "조금 높게 나왔다"를 구분하는 데 도움이 된다.
- mean score: 기존 표와 이어지는 대표값
- interval: 같은 평가 단위를 다시 뽑았을 때 흔들리는 폭
- delta interval: 비교 대상과의 차이가 어느 범위에서 움직이는지
- win rate over resamples: 반복 샘플 기준으로 한쪽이 더 높게 나온 비율
여기에 실패 샘플 상위 몇 개를 붙이면 더 좋다. 신뢰구간은 점수의 폭을 보여 주지만, 왜 폭이 생겼는지는 알려 주지 않는다. 특정 query cluster, 특정 negative edge 유형, 특정 문서 길이 구간이 반복해서 결과를 뒤집는다면, 그건 다음 실험의 소재가 된다. 결국 bootstrap은 결론을 대신 내려주는 장치가 아니라, 다음에 어디를 뜯어볼지 좁혀 주는 장치에 가깝다.
너무 크게 해석하지 않기
물론 bootstrap 신뢰구간도 만능은 아니다. 평가 샘플 자체가 편향되어 있으면, bootstrap은 그 편향된 샘플 안에서만 다시 흔든다. query가 전부 비슷한 유형이면 구간이 좁아도 실제 서비스 질의에서는 크게 흔들릴 수 있다. 반대로 샘플 수가 너무 적으면 구간이 넓게 나오는데, 그게 모델이 불안정해서인지 평가 세트가 작아서인지 구분하기 어렵다. 숫자가 하나 늘었다고 해서 해석 책임이 사라지는 건 아니다.
그래도 평균 점수 옆에 bootstrap 구간을 붙이면 실험표를 읽는 속도가 달라진다. 작은 개선처럼 보이는 값은 잠깐 보류하게 되고, 평균 차이는 작아도 흔들림이 줄어든 설정은 다시 보게 된다. 나에게는 이게 꽤 실용적인 변화다. 더 복잡한 통계 용어를 많이 아는 것보다, 결과표에 불확실성을 한 칸 붙여 두는 습관이 실험 기록의 품질을 더 안정적으로 올려 준다.
앞으로 GNN이나 GraphRAG 평가 리포트를 정리할 때도 평균, best seed, hardest case만 나란히 두기보다 bootstrap 기반의 흔들림 칸을 같이 두고 싶다. 특히 두 설정의 평균이 비슷할 때는 승자를 빨리 고르는 것보다, 차이가 반복 샘플에서 얼마나 버티는지 먼저 보는 편이 덜 위험하다. 실험표는 결론을 멋지게 보이게 하는 장식이 아니라, 다음 결정을 조금 덜 성급하게 만드는 안전장치여야 한다.
'[개발 공부]' 카테고리의 다른 글
| Paired permutation test: 평균 차이를 직접 뒤섞어 보기 (0) | 2026.05.13 |
|---|---|
| McNemar 검정: 같은 테스트셋 차이를 보는 2x2 표 (0) | 2026.05.12 |
| MRR과 nDCG, 첫 정답과 전체 순서를 분리해서 보기 (0) | 2026.05.01 |
| Negative Sampling, 링크 예측에서 없는 간선을 고르는 기준 (0) | 2026.04.29 |
| MLA KV Cache, 긴 문맥에서 먼저 줄어드는 병목 (0) | 2026.04.28 |