[개발 공부] / Benjamini-Hochberg FDR: 유의한 것과 발견률 나눠 읽기.md

Benjamini-Hochberg FDR: 유의한 것과 발견률 나눠 읽기

조회

2026년 5월 22일 | 개발 공부


Benjamini-Hochberg FDR는 여러 p-value를 한꺼번에 볼 때 False Discovery Rate, 즉 거짓 발견률을 어느 정도로 제한할지 정하는 보정 절차다. 나는 처음에 이걸 Holm-Bonferroni의 느슨한 버전쯤으로만 이해했다. 둘 다 p-value를 정렬하고, 둘 다 여러 번 비교했다는 사실을 반영하니까 비슷해 보였다. 그런데 실제 실험표에 붙여 보니 질문이 조금 달랐다. Holm-Bonferroni는 “틀린 양성 판정을 하나라도 만들 확률을 얼마나 줄일 것인가”에 가깝고, Benjamini-Hochberg는 “유의하다고 고른 것들 안에 거짓 발견이 어느 정도 섞이는 것을 감수할 것인가”에 가깝다.

이 차이는 모델 평가에서 꽤 중요하다. 예를 들어 GraphRAG profile 12개를 서로 비교하거나, retrieval 설정 8개를 같은 query set에서 비교하면 p-value가 금방 여러 개 생긴다. 이때 가장 작은 p-value 하나만 보고 “이 설정이 이겼다”고 말하면 사실상 여러 번 던진 주사위 중 잘 나온 눈만 집어 든 셈이 된다. 반대로 모든 비교를 너무 보수적으로 막아 버리면, 실제로 유용한 개선 후보까지 다 버리게 된다. Benjamini-Hochberg는 그 중간에서 발견 목록의 품질을 관리하는 렌즈로 쓰기 좋다.

Holm-Bonferroni와 질문이 다른 지점

Holm-Bonferroni를 쓸 때 나는 보통 “이 family 안에서 false positive를 하나라도 만들 가능성을 줄이자”는 마음으로 본다. 그래서 작은 p-value부터 아주 엄격한 기준을 적용하고, 어느 지점에서 막히면 그 뒤는 멈춘다. 논문 표나 제품 의사결정처럼 틀린 양성 하나가 비용을 크게 만드는 상황에는 이 보수성이 도움이 된다.

Benjamini-Hochberg는 조금 다르다. 여기서는 p-value를 작은 순서로 정렬한 뒤, 각 순위에 비례한 기준선과 비교한다. 직관적으로는 앞쪽 발견은 더 엄격하게 보고, 뒤로 갈수록 발견 목록 전체에서 허용할 거짓 발견률을 나눠 준다. 그래서 결과가 “여기까지 reject”처럼 한 덩어리로 나온다. 중요한 건 이 절차가 각 발견이 모두 참이라고 보증하지 않는다는 점이다. 대신 “내가 유의하다고 표시한 후보 묶음 안에서 거짓 발견 비율을 관리하겠다”는 약속에 가깝다.

개인 실험 로그에서는 이 차이를 문장으로 적어 두는 게 도움이 됐다. “Holm으로도 통과했다”와 “BH FDR 0.05에서는 발견 후보로 남았다”는 같은 강도의 말이 아니다. 전자는 훨씬 조심스러운 판정이고, 후자는 후보 목록을 넓게 보되 거짓 발견률을 통제했다는 판정이다. 이걸 섞어 쓰면 나중에 표를 다시 볼 때 결론 강도가 뒤틀린다.

평가표에 붙일 때 먼저 정할 것

첫 번째는 비교 family다. 같은 query set에서 같은 metric으로 비교한 profile 묶음인지, 같은 모델의 ablation 묶음인지, 아니면 서로 다른 목적의 실험을 억지로 한 표에 섞은 것인지 먼저 가른다. family를 결과를 본 뒤에 넓히거나 좁히면 보정값은 그럴듯해 보여도 탐색량을 제대로 반영하지 못한다. 나는 요즘 p-value를 계산하기 전에 “이번 family는 무엇인가”를 표 제목 옆에 먼저 적는다.

두 번째는 p-value의 방향과 paired unit이다. retrieval이면 query 단위, GraphRAG면 같은 query fingerprint, 링크 예측이면 test edge나 seed 단위가 될 수 있다. 같은 샘플을 두 설정이 같이 지난 결과라면 paired permutation test나 Wilcoxon signed-rank test처럼 짝 구조를 유지한 검정을 먼저 고르는 편이 자연스럽다. Benjamini-Hochberg는 그 다음에 여러 p-value를 어떻게 읽을지 정하는 층이다. 검정 자체의 단위가 틀리면 FDR 보정도 그 틀린 단위를 깔끔하게 포장할 뿐이다.

세 번째는 effect size와 inspection queue다. FDR 보정을 통과한 항목이 평균 delta 0.001이라면 실무적으로는 거의 의미가 없을 수 있다. 반대로 p-value가 애매해도 실패 샘플이 특정 query cluster에 몰려 있으면 다음 실험 후보로는 더 중요할 수 있다. 그래서 나는 보정 p-value 옆에 평균 delta, bootstrap interval, 큰 delta 샘플 몇 개를 같이 둔다. 통계 검정은 결론을 닫는 도장이라기보다 다시 볼 후보를 줄이는 필터로 두는 편이 덜 위험했다.

작은 예로 보는 해석

예를 들어 profile A를 기준으로 B, C, D, E를 비교했고 p-value가 0.003, 0.018, 0.041, 0.12로 나왔다고 하자. FDR 기준 q를 0.05로 두면 작은 순서대로 각 순위의 기준선을 본다. 네 개 비교라면 대략 0.0125, 0.025, 0.0375, 0.05 같은 식으로 올라간다. 이 경우 앞의 두 개는 기준을 통과하지만, 세 번째 0.041은 0.0375보다 커서 멈춘다. 그래서 B와 C만 발견 후보로 남는다.

여기서 내가 조심하는 표현은 “D는 효과가 없다”가 아니다. 더 정확한 말은 “이번 family와 FDR 기준에서는 D까지 발견 목록에 넣지 않았다”이다. 이 문장 차이가 작아 보이지만, 후속 실험을 설계할 때는 꽤 크다. D가 특정 query cluster에서는 좋아졌고 전체 평균에서만 애매한 경우, 다음 일은 D를 버리는 것이 아니라 cluster별 delta를 다시 보는 것일 수 있다.

구현에서는 statsmodels의 fdrcorrection이나 multipletests 같은 함수를 쓸 수 있다. 다만 함수 이름보다 중요한 건 로그에 남기는 형식이다. 원래 p-value, 정렬 순위, 보정 후 reject 여부, q 값을 같이 남겨야 나중에 가족 단위가 바뀌었는지 확인할 수 있다.

내가 남기는 최소 기록

실험표 한 줄은 보통 이렇게 잡는 게 편했다. 기준 설정과 후보 설정, paired unit 수, metric delta, bootstrap interval, raw p-value, FDR 보정 결과, 그리고 다시 볼 샘플 묶음. 이 정도만 있어도 “유의했다”는 말 하나보다 훨씬 덜 미끄럽다. 특히 평균표가 여러 개 쌓이는 프로젝트에서는 p-value만 남겨 두면 나중에 어떤 비교 묶음에서 나온 값인지 잊어버리기 쉽다.

Benjamini-Hochberg FDR를 쓰면서 내가 얻은 가장 실용적인 감각은, 통계 보정을 결론 생성기가 아니라 목록 관리기로 보는 것이다. 여러 후보를 훑어야 하는 실험에서는 완전히 보수적인 판정만으로는 다음 행동이 너무 줄어든다. 그렇다고 가장 좋아 보이는 숫자만 집으면 탐색 편향이 너무 쉽게 들어온다. FDR은 그 사이에서 “발견이라고 부를 후보 묶음을 어디까지 열어 둘 것인가”를 정하는 도구다.

그래서 다음부터 여러 모델이나 profile을 한 번에 비교할 때는, 평균 순위표 옆에 FDR 보정 결과를 붙이되 문장을 조금 조심해서 쓰려고 한다. “이 설정이 확실히 이겼다”보다 “이 family 안에서 발견 후보로 남았다”가 더 정확하다. 이 정도의 느슨하지만 명시적인 표현이, 작은 실험 로그를 나중에 다시 읽을 때 제일 덜 헷갈렸다.

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.