[개발 공부] / Cliff's delta: 평균차 대신 승패 비율 보기.md

Cliff's delta: 평균차 대신 승패 비율 보기

조회

2026년 5월 26일 | 개발 공부


Cliff's delta는 두 결과 묶음에서 값 하나씩을 뽑아 비교했을 때, 한쪽이 더 큰 경우와 더 작은 경우의 차이를 -1부터 1 사이로 놓는 효과 크기다. 평균이 얼마나 벌어졌는지보다 두 분포가 서로 맞붙으면 어느 쪽이 더 자주 이기는지를 먼저 묻는다고 보면 된다.

나는 모델 평가표를 볼 때 평균 delta를 너무 쉽게 믿는 편이었다. nDCG가 0.012 올랐다거나 latency가 3% 줄었다는 숫자는 보기 좋다. 그런데 샘플을 조금만 펼쳐 보면 한두 개 query에서 크게 이기고 나머지에서는 거의 비슷한 경우가 꽤 있다. 반대로 평균은 작아 보여도 거의 모든 query에서 조금씩 이기는 설정도 있다. 두 경우는 운영 판단이 완전히 다르다.

이때 Cliff's delta를 붙이면 평균표 뒤에 숨어 있던 결을 한 번 더 볼 수 있다. 값이 0.6에 가깝다면 A 쪽 값이 B 쪽 값보다 큰 비교가 꽤 자주 나온다는 뜻이고, 0 근처라면 이긴 쌍과 진 쌍이 비슷하게 섞였다는 뜻이다. -0.4라면 방향을 잘못 잡았거나 새 설정이 기준 설정보다 자주 밀린다는 신호다.

평균차와 다른 질문

평균차는 크기를 직접 본다. 그래서 큰 outlier 하나가 들어오면 숫자가 확 움직인다. Cliff's delta는 모든 쌍을 비교한 뒤 이긴 쌍의 비율에서 진 쌍의 비율을 뺀다. tied pair는 보통 0처럼 다루거나 별도 규칙으로 빼는데, 핵심은 절댓값 자체보다 방향의 빈도를 본다는 점이다.

예를 들어 새 reranker가 100개 query 중 60개에서 기준보다 조금 좋고, 30개에서 조금 나쁘고, 10개에서 같다면 평균 delta가 작아도 방향성은 꽤 일관된다. 반대로 5개 query에서 크게 좋아지고 95개는 비슷하거나 살짝 나빠지면 평균은 좋아 보여도 운영 관점에서는 위험하다. 이 차이를 평균 하나로는 잘 못 본다.

그래서 나는 Cliff's delta를 결론용 숫자라기보다 inspection queue를 줄이는 숫자로 보는 편이 맞다고 느꼈다. 평균 개선, bootstrap confidence interval, Wilcoxon p-value 같은 값 옆에 delta를 붙여 두면 “좋아졌나?”보다 “어떤 식으로 좋아졌나?”를 먼저 묻게 된다.

간단한 계산 감각

구현은 생각보다 단순하다. 두 배열을 모두 맞붙여서 A가 큰 횟수와 B가 큰 횟수를 세고, 전체 비교 쌍 수로 나누면 된다. 실제 대용량에서는 더 효율적인 계산을 쓰겠지만, 처음 감을 잡을 때는 이 형태가 제일 덜 헷갈린다.

def cliffs_delta(a, b):
    wins = 0
    losses = 0
    for x in a:
        for y in b:
            if x > y:
                wins += 1
            elif x < y:
                losses += 1
    return (wins - losses) / (len(a) * len(b))

여기서 방향은 꼭 고정해야 한다. “새 설정 - 기준 설정”으로 읽을지, “기준 설정 - 새 설정”으로 읽을지 정하지 않으면 부호가 계속 헷갈린다. 평가 로그에는 이름도 같이 남기는 편이 좋다. 예를 들어 `delta_new_vs_base`처럼 방향을 박아 두면 나중에 표를 다시 열 때 덜 불안하다.

동점 처리도 은근히 중요하다. 점수가 소수 넷째 자리에서만 다르다면 정말 이겼다고 볼지, 실질적으로 같은 값으로 묶을지 먼저 정해야 한다. retrieval 점수처럼 반올림 오차가 섞인 숫자는 epsilon을 하나 두고 그 안은 tie로 처리하는 편이 더 낫다. 그렇지 않으면 작은 부동소수점 흔들림이 승패 비율을 괜히 날카롭게 만들 수 있다.

해석 구간을 외우기보다 표 옆에 두기

Cliff's delta도 값 구간을 small, medium, large처럼 나누는 관례가 있긴 하지만, 나는 그 표를 그대로 외워 쓰는 쪽은 조심한다. 서비스 로그의 0.2와 작은 benchmark의 0.2는 무게가 다르다. 샘플 수, metric 해상도, 같은 query family의 중복 여부, 그리고 실제로 고쳐야 하는 비용을 같이 봐야 한다.

그래서 보고서에는 “delta=0.31이므로 중간 효과”처럼 닫기보다, “0.31, 양수 쌍이 더 많지만 특정 cluster에서 손실 집중”처럼 샘플 큐와 붙여 쓴다. 숫자는 판단을 대신하는 도장보다, 어떤 로그를 다시 열지 고르는 정렬 키에 가까워야 덜 위험하다.

paired 평가에서는 조심할 점

Cliff's delta는 독립된 두 묶음을 비교할 때 자연스럽다. 그런데 내가 자주 보는 retrieval, GraphRAG, 링크 예측 평가는 같은 query나 edge를 두 설정이 함께 지난 paired 구조인 경우가 많다. 이때는 모든 A 값과 모든 B 값을 서로 맞붙이는 방식이 paired unit을 흐릴 수 있다.

paired 구조가 중요하면 먼저 query별 차이값을 만든 뒤, 그 차이의 방향과 크기를 보는 방식이 더 안전하다. 이때는 Wilcoxon signed-rank test나 rank-biserial correlation이 더 직접적인 선택일 수 있다. Cliff's delta를 쓰더라도 “두 독립 분포 비교”인지, “paired delta를 별도 값으로 만든 뒤 비교”인지 기록에 남겨야 한다.

이 구분을 안 해 두면 숫자가 그럴듯한데 해석이 미묘하게 밀린다. 같은 사용자나 같은 query family에서 나온 샘플이 여럿 섞여 있으면 독립 표본처럼 보이지만 실제로는 한 덩어리일 수 있다. 작은 실험일수록 이런 구조가 더 크게 작용한다.

어디에 붙이면 좋은지

내 기준으로는 평균표 옆의 보조 열이 가장 좋다. 평균 delta, median delta, bootstrap interval, p-value, Cliff's delta를 한 줄에 놓고, 마지막 칸에 다시 볼 샘플 3개만 적는다. 숫자를 많이 붙이는 게 목적이 아니라, 다음에 열 로그를 줄이는 게 목적이다.

특히 후보 설정이 여러 개일 때 유용하다. 평균은 비슷한데 Cliff's delta가 한쪽만 크게 양수라면 넓게 조금씩 이기는 설정일 가능성이 있다. 평균은 높지만 delta가 0 근처라면 몇 개 샘플에 기대고 있을 수 있다. 이 둘은 다음 실험 우선순위가 다르다.

정리하면, Cliff's delta는 “평균이 올랐다”는 말을 바로 믿지 않기 위한 작은 브레이크다. 평균차가 보여 주는 크기와, 쌍 비교가 보여 주는 방향 빈도를 나눠 보면 평가표가 조금 덜 단정적으로 보인다. 나는 그 정도 거리감이 작은 모델 비교에서는 꽤 중요하다고 본다. 특히 다음 실험을 하나만 골라야 할 때, 이 숫자는 후보를 버리는 이유보다 다시 볼 샘플을 고르는 이유에 가깝다.

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.