2026년 5월 1일 | 개발 공부
MRR@k와 nDCG@k는 둘 다 검색 결과의 순서를 점수로 읽는 랭킹 평가 지표다. MRR은 Mean Reciprocal Rank, 그러니까 첫 번째 정답이 몇 번째에 나왔는지를 역수로 평균낸 값이고, nDCG는 normalized Discounted Cumulative Gain, 즉 여러 정답의 유용도를 순위가 내려갈수록 할인해 더한 뒤 이상적인 순서와 비교해 정규화한 값이다. 나는 처음에 이 둘을 그냥 “검색 점수” 한 묶음으로 봤는데, GraphRAG 쪽 평가 로그를 계속 만지다 보니 둘은 꽤 다른 질문에 답한다는 걸 뒤늦게 느꼈다.
특히 RAG나 GraphRAG처럼 상위 몇 개 문서를 LLM 문맥에 넣는 구조에서는 “정답 문서가 있느냐”만으로는 부족하다. 정답이 8위에 있으면 recall 관점에서는 찾은 것이지만, 실제 답변 생성에서는 잘릴 수 있다. 반대로 1위 문서는 맞는데 2위부터 5위까지가 엉뚱하면 첫 문장 생성은 그럭저럭 시작해도 근거 조합이 약해진다. 그래서 나는 검색 평가를 볼 때 첫 정답을 얼마나 빨리 만나는지와 상위 리스트 전체가 얼마나 좋은 순서인지를 분리해서 읽는 쪽이 더 편했다.
Recall 하나로는 순서의 손실이 안 보인다
가장 익숙한 지표는 보통 recall이다. 기대 문서가 top-k 안에 들어왔는지 보는 방식이라 직관적이고, 실패를 잡아내기도 쉽다. 예를 들어 기대 문서가 세 개 있고 top-5 안에 두 개가 들어오면 coverage나 recall 계열 숫자는 꽤 괜찮아 보인다. 문제는 그 두 개가 1위와 2위인지, 4위와 5위인지가 완전히 다른 상황이라는 데 있다.
LLM에 넘기는 문맥은 무한하지 않다. retriever가 10개를 반환하더라도 실제 prompt에는 앞쪽 몇 개만 강하게 작동하고, 뒤쪽 문서는 요약되거나 잘리거나 attention 안에서 묻힌다. 그래서 검색 결과를 평가할 때는 “top-k 안에 들어왔다” 다음에 바로 “몇 번째에 들어왔는가”를 물어야 한다. 이때 MRR과 nDCG가 서로 다른 방향에서 도움이 된다.
| 지표 | 주로 답하는 질문 | 잘 보이는 실패 |
|---|---|---|
| Recall@k | 정답 후보가 top-k 안에 들어왔는가 | 아예 못 찾은 query |
| MRR@k | 첫 번째 정답을 얼마나 빨리 만나는가 | 정답이 뒤로 밀리는 query |
| nDCG@k | 여러 정답의 순서와 중요도가 얼마나 좋은가 | 상위 리스트의 품질이 흐려지는 query |
MRR은 첫 번째 정답의 위치를 세게 본다
MRR은 단순하다. 어떤 query에서 첫 번째 관련 문서가 1위면 1점, 2위면 1/2점, 5위면 1/5점처럼 본다. 여러 query에 대해 이 값을 평균내면 MRR이 된다. 그래서 MRR은 사용자가 첫 번째로 쓸 만한 답을 얼마나 빨리 만나는지를 보는 데 좋다. 검색 UI라면 클릭 가능한 첫 결과가 중요하고, RAG라면 첫 근거 문서가 prompt 앞쪽에 놓이는지가 중요하니 꽤 실무적인 숫자다.
다만 MRR에는 명확한 사각지대가 있다. 첫 정답 이후의 세계를 거의 보지 않는다. 1위가 맞으면 2위부터 10위까지가 얼마나 좋은지는 점수에 잘 반영되지 않는다. 그래서 answer synthesis가 단일 문서 하나로 충분한 경우에는 MRR이 잘 맞지만, 여러 근거를 엮어야 하는 질문에서는 지나치게 낙관적으로 보일 수 있다. GraphRAG에서 entity relation, path evidence, background document를 같이 써야 하는 query라면 첫 문서 하나만으로는 부족한 경우가 꽤 있다.
나는 MRR을 볼 때 “검색기가 최소한 첫 발은 제대로 디뎠는가”라는 신호로 읽는다. 값이 낮으면 앞쪽 ranking이 망가진 것이고, 값이 높다고 해서 전체 근거 구성이 좋다고 바로 말하지는 않는다. 이 선을 나눠 두면 평가 로그를 볼 때 조금 덜 흔들린다.
nDCG는 여러 정답의 순서를 같이 본다
nDCG는 MRR보다 조금 더 번거롭지만, 그만큼 상위 리스트 전체를 읽는다. DCG는 각 순위의 gain을 더하되 뒤로 갈수록 할인한다. 그리고 nDCG는 그 값을 가능한 최고의 순서, 즉 ideal DCG와 나눠 0과 1 사이에 가깝게 정규화한다. 이 구조 덕분에 nDCG는 정답이 여러 개이고 중요도도 다를 때 훨씬 자연스럽다.
예를 들어 어떤 질문에는 핵심 문서 하나와 보조 문서 두 개가 있을 수 있다. 핵심 문서가 1위, 보조 문서가 3위와 5위에 있는 결과와, 보조 문서가 1위이고 핵심 문서가 5위로 밀린 결과는 recall만 보면 비슷할 수 있다. 하지만 nDCG는 gain을 다르게 줄 수 있어서 핵심 문서가 앞에 있는 결과를 더 높게 본다. 이게 RAG 평가에서는 꽤 중요하다. 답변에 꼭 필요한 근거와 있으면 좋은 근거를 같은 1개로 세면, 검색기가 어떤 문서를 먼저 밀어 올려야 하는지 흐려진다.
물론 nDCG도 공짜는 아니다. 문서별 관련도 등급을 정해야 하고, query마다 ideal ranking을 상상해야 한다. 라벨이 빈약하면 숫자가 그럴듯해 보여도 실제 판단은 허술해진다. 그래서 작은 프로젝트에서는 처음부터 nDCG를 거창하게 붙이기보다, 기대 문서 목록과 간단한 gain 규칙을 먼저 정하고 나중에 지표를 올리는 방식이 덜 부담스럽다.
GraphRAG 평가에서는 둘을 같이 봐야 덜 헷갈린다
GraphRAG 쪽에서 scoring profile을 비교하다 보면 “top-1은 유지됐는데 평균 점수만 오른 경우”와 “top-1은 바뀌었지만 coverage가 좋아진 경우”가 섞인다. 이때 MRR만 보면 첫 번째 정답의 변화는 잘 보이지만, 나머지 근거 조합의 질은 놓치기 쉽다. 반대로 nDCG만 보면 전체 리스트는 좋아졌는데 사용자가 실제로 처음 만나는 문서가 밀렸는지 감각이 둔해질 수 있다.
그래서 나는 이제 검색 평가 표를 만들 때 최소한 세 칸을 나눠 생각한다. 첫째, 찾았는가. 둘째, 첫 정답은 어디인가. 셋째, 상위 리스트 전체는 좋은 순서인가. 이 세 칸이 각각 recall, MRR, nDCG에 대략 대응한다. 완벽한 대응은 아니지만, 로그를 해석하는 순서로는 꽤 쓸 만하다.
예를 들어 어떤 profile이 recall은 그대로인데 MRR만 오른다면, 후보군 자체를 더 넓힌 게 아니라 앞쪽 정렬을 고친 것에 가깝다. nDCG가 같이 오르면 보조 근거까지 더 좋은 순서로 들어왔을 가능성이 있다. 반대로 MRR은 그대로인데 nDCG가 내려가면 첫 문서는 살아 있지만 뒤쪽 근거 품질이 나빠진 신호일 수 있다. 이 차이를 보지 않으면 “점수는 비슷한데 왜 답변이 빈약하지?” 같은 질문이 계속 남는다.
작게 시작할 때의 체크리스트
작은 RAG 프로젝트에서 바로 복잡한 평가 체계를 만들 필요는 없다. 나는 먼저 query마다 기대 문서 제목을 한두 개라도 적어 두는 쪽을 선호한다. 그다음 top-k 결과를 저장하고, 사람이 읽는 summary에 top-1 hit, top-k hit, 첫 정답 위치를 같이 붙인다. 여기까지 하면 MRR의 재료는 이미 나온다. nDCG는 문서별 gain을 3, 2, 1처럼 거칠게라도 나눌 수 있을 때 붙이면 된다.
- 정답이 하나뿐인 탐색형 query라면 MRR을 먼저 본다.
- 여러 근거를 조합해야 하는 설명형 query라면 nDCG를 같이 본다.
- 문맥 예산이 빡빡한 RAG라면 top-3 안의 정답 위치를 별도로 표시한다.
- profile 실험에서는 평균 하나보다 query별 rank shift를 같이 남긴다.
개인적으로 가장 조심하는 건 지표 이름을 붙인 순간 평가가 끝났다고 착각하는 일이다. MRR과 nDCG는 답을 대신 내려주는 숫자가 아니라, 실패를 더 잘 나눠 보게 해 주는 렌즈에 가깝다. 첫 정답이 뒤로 밀린 문제인지, 전체 근거 묶음이 약해진 문제인지, 아니면 애초에 기대 문서 라벨이 빈약한 문제인지가 갈라지면 다음 수정도 훨씬 구체적이 된다.
결국 내가 가져가려는 결론은 단순하다. 검색 평가는 하나의 평균 점수로 끝내기보다, recall로 후보 존재를 보고, MRR로 첫 정답 위치를 보고, nDCG로 상위 근거 묶음의 순서를 본다. 이렇게 세 겹으로 나눠 두면 GraphRAG든 일반 RAG든, “찾았다”와 “쓸 수 있게 찾았다” 사이의 간격이 훨씬 잘 보인다.
'[개발 공부]' 카테고리의 다른 글
| McNemar 검정: 같은 테스트셋 차이를 보는 2x2 표 (0) | 2026.05.12 |
|---|---|
| Bootstrap 신뢰구간: 평균 점수 옆에 흔들림 붙이기 (0) | 2026.05.06 |
| Negative Sampling, 링크 예측에서 없는 간선을 고르는 기준 (0) | 2026.04.29 |
| MLA KV Cache, 긴 문맥에서 먼저 줄어드는 병목 (0) | 2026.04.28 |
| GraphSAGE feature importance, 단독 랭킹보다 조합으로 읽기 (1) | 2026.04.23 |