2026년 5월 19일 | 개발 공부
Wilcoxon signed-rank test는 같은 샘플을 두 설정이 함께 지난 뒤, 샘플별 차이의 부호와 절대값 순위를 이용해 “차이의 중심이 0에서 벗어났는가”를 보는 비모수 검정이다. 한국어로는 보통 윌콕슨 부호순위 검정이라고 부른다. 나는 처음 봤을 때 이름이 묘하게 무거워 보여서 그냥 p-value를 하나 더 뽑는 도구처럼 넘겼는데, 막상 작은 평가 로그를 읽다 보면 꽤 실용적인 위치가 있다.
상황은 단순하다. 같은 query set에서 RAG profile A와 B를 돌렸고, query마다 nDCG 차이를 하나씩 얻었다고 하자. 또는 같은 test edge 묶음에서 두 링크 예측 설정의 score delta를 얻었다고 해도 된다. 여기서 샘플별 차이를 서로 독립인 두 표본처럼 취급하면, 같은 질문을 함께 풀었다는 짝 구조가 사라진다. 그래서 나는 이런 비교를 볼 때 먼저 paired unit을 고정한다. retrieval이면 query, GNN이면 edge나 seed, 실험 리포트면 같은 fixture를 공유한 케이스가 unit이 된다.
부호만 보지 않고 순위까지 붙이는 이유
부호순위 검정의 감각은 “몇 개가 이겼는가”에서 한 걸음 더 간다. 단순 sign test는 양수 delta가 몇 개, 음수 delta가 몇 개인지를 본다. Wilcoxon은 여기에 절대값 크기의 순위를 붙인다. 0.001 차이로 이긴 샘플과 0.12 차이로 이긴 샘플을 같은 한 표로 세지 않고, 큰 차이가 어느 방향에 몰렸는지를 같이 본다. 평균 하나가 크게 튄 outlier에 끌려가는지, 작지만 같은 방향의 개선이 여러 unit에서 쌓이는지도 어느 정도 구분해 준다.
예를 들어 delta가 `0.01, 0.02, 0.00, -0.01, 0.03`처럼 작게 한쪽으로 모인 경우와, `0.00, 0.00, 0.00, 0.25, -0.01`처럼 한 샘플만 크게 튄 경우는 평균만 보면 비슷하게 착시가 날 수 있다. 하지만 실제 운영에서는 두 결과의 의미가 꽤 다르다. 앞쪽은 설정 전체가 약하게 밀어 주는 느낌이고, 뒤쪽은 특정 query family나 데이터 오류를 먼저 의심해야 하는 모양이다. 이 차이를 완벽히 설명하진 못해도, Wilcoxon은 적어도 “큰 차이의 방향이 어디에 쌓였는가”를 다시 묻게 만든다.
물론 이 검정이 모든 것을 해결해 주지는 않는다. 핵심 가정은 차이값 분포가 0을 중심으로 대칭이라는 쪽에 가깝다. 차이값이 한쪽으로 긴 꼬리를 갖거나, query cluster 하나가 거의 같은 변형 문제를 여러 개 만든 상태라면 결과가 과하게 단단해 보일 수 있다. 그래서 나는 Wilcoxon 결과를 볼 때 p-value만 남기지 않고, delta 리스트의 중앙값, 양수/음수 개수, 큰 절대값을 가진 샘플 몇 개를 같이 적어 두는 편이 낫다고 본다. 숫자 하나가 아니라 inspection queue를 만드는 장치로 쓰는 셈이다.
평균표 옆에 붙일 때의 읽는 순서
profile B의 nDCG가 평균으로는 A보다 0.018 높게 나왔다고 해도 바로 “B가 낫다”로 가지 않는다. query 40개 중 22개는 거의 0에 붙어 있고, 3개 query에서만 크게 튄 결과라면 이건 모델 개선이라기보다 특정 query family의 효과일 수 있다. 반대로 평균 차이는 0.01밖에 안 되는데 35개 query에서 작게나마 같은 방향으로 밀렸다면, 운영 설정의 안정성 개선 후보로 볼 수 있다. Wilcoxon은 이 두 경우를 완전히 설명하지는 못하지만, 평균표 뒤에 숨어 있는 부호와 순위의 모양을 다시 보게 만든다.
기존에 정리해 둔 bootstrap confidence interval과도 쓰임이 다르다. Bootstrap은 현재 평가셋과 비슷한 샘플을 다시 뽑으면 평균이나 paired delta가 얼마나 흔들릴지를 보는 쪽이다. 반면 Wilcoxon은 관찰된 paired difference가 0 중심에서 충분히 치우쳤는지를 묻는다. 그래서 작은 실험표에서는 평균 delta, bootstrap interval, Wilcoxon p-value를 나란히 놓되, 셋 중 하나만으로 결론을 닫지 않는 구성이 가장 덜 위험하다.
또 McNemar test와도 역할이 갈린다. McNemar는 성공/실패로 딱 자를 수 있을 때 강하다. top-1 hit, full coverage, threshold 통과 같은 rule이 이미 정해져 있으면 불일치 2x2 표가 훨씬 읽기 쉽다. 그런데 MRR, nDCG, AUC, latency처럼 연속값을 그대로 비교하고 싶으면 그 정보를 binary로 접는 순간 손실이 크다. 이때 Wilcoxon이나 paired permutation test가 더 자연스럽다.
구현 전에 정해야 할 작은 약속
SciPy의 wilcoxon 문서를 읽을 때 내가 메모해 둔 작은 주의점도 있다. 두 배열 x, y를 그대로 넘기면 내부에서 x - y를 계산하는데, 부동소수점 반올림 때문에 사실상 같은 차이가 서로 다른 rank로 취급될 수 있다. 문서에서도 필요하면 차이 배열을 먼저 직접 계산하고, 의미 있는 자리까지 반올림한 뒤 넘기라고 말한다. 평가 로그에서 소수점 12자리까지 남긴 metric을 그대로 넣으면 괜히 tie 처리가 흔들릴 수 있으니, 실무에서는 해석 단위에 맞는 반올림을 먼저 정해 두는 편이 좋다.
0 차이를 어떻게 다룰지도 은근히 중요하다. 완전히 같은 값이 많으면 검정 통계량의 의미가 달라지고, 라이브러리 옵션에 따라 zero difference를 버리거나 rank에 반영하는 방식이 달라진다. 나는 보통 “이 정도 차이는 실질적으로 0이다”라는 epsilon을 먼저 정한 뒤, 그 기준으로 delta를 정리한다. 검색 평가라면 nDCG 1e-12 차이는 의미가 없고, latency라면 측정 노이즈 범위를 먼저 봐야 한다. 이 약속 없이 함수를 바로 호출하면, 통계 검정이 아니라 부동소수점 잔차를 해석하는 꼴이 된다.
내 기준의 사용 순서는 이렇다. 먼저 paired unit을 고정한다. 그다음 delta 방향을 `새 설정 - 기준 설정`처럼 한 줄로 못 박는다. 0에 가까운 차이를 어떻게 처리할지 정하고, 평균과 중앙값, 양수/음수 개수, 큰 delta 샘플을 함께 본다. 마지막에 Wilcoxon p-value를 붙인다. 순서를 이렇게 두면 검정이 결론을 대신 내리는 느낌이 줄고, 내가 이미 읽은 로그의 구조를 확인하는 보조 렌즈가 된다.
작은 개인 프로젝트에서는 통계적으로 멋진 표보다 이런 절차의 일관성이 더 중요했다. 같은 query set, 같은 seed, 같은 threshold rule을 유지한 채 delta를 남기고, 평균이 애매할 때 부호와 순위를 한 번 더 본다. 그 정도만 해도 “평균은 올랐는데 왜 결과가 찝찝하지?” 같은 감각을 조금 덜 감정적으로 다룰 수 있다. 나는 Wilcoxon signed-rank test를 그런 용도의 얇은 체크포인트로 기억해 두려고 한다.
'[개발 공부]' 카테고리의 다른 글
| Benjamini-Hochberg FDR: 유의한 것과 발견률 나눠 읽기 (0) | 2026.05.22 |
|---|---|
| Holm-Bonferroni 보정: 여러 p-value를 한 번에 읽기 (0) | 2026.05.21 |
| Paired permutation test: 평균 차이를 직접 뒤섞어 보기 (0) | 2026.05.13 |
| McNemar 검정: 같은 테스트셋 차이를 보는 2x2 표 (0) | 2026.05.12 |
| Bootstrap 신뢰구간: 평균 점수 옆에 흔들림 붙이기 (0) | 2026.05.06 |