AI Engineering Academy · 강의

검색 지표: 적중률, MRR, NDCG

질의와 관련 문서로 정답 데이터 세트를 구축한 뒤 적중률, 평균 역순위, NDCG를 계산해 검색기가 올바른 분할 조각을 얼마나 자주 찾는지 측정합니다.

레슨 2/413개 단계

검색 지표: 적중률, MRR, NDCG은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

검색 지표가 여러 가지인 이유

적중률은 상위 K개 결과 어디에든 관련 청크가 포함되었는지는 알려 주지만, 순위에서 어디에 나타났는지는 알려 주지 않습니다. 가장 좋은 청크를 항상 5위에 배치하는 시스템은 두 시스템의 적중률이 같더라도 이를 꾸준히 1위에 배치하는 시스템보다 성능이 낮습니다. MRR과 NDCG 같은 더 정교한 지표는 순위의 품질을 포착하여, LLM과 사용자가 가장 많이 활용할 가능성이 높은 상위에 가장 관련성 높은 청크를 배치하는 시스템을 더 높게 평가합니다.

적중률 @K: 복습 및 구현

적중률@K는 가장 단순한 지표입니다. 상위 K개 결과에 관련 청크가 하나 이상 포함된 질의의 비율은 얼마일까요? 질의마다 이진 신호를 제공하므로 해석하기 쉽습니다. 검색된 ID와 알려진 관련 ID 사이의 집합 교집합을 확인하여 계산합니다. 대부분의 RAG 시스템이 청크 5개를 검색하므로 기본값으로 K=5를 사용하십시오. 적중률@1, @3, @5를 비교하여 검색 범위를 넓힐 때 민감도가 어떻게 변하는지 파악하십시오.

def hit_rate_at_k(golden_dataset, retriever, k=5):
    hits = 0
    for item in golden_dataset:
        retrieved = retriever.retrieve(item['question'], top_k=k)
        retrieved_ids = [r['id'] for r in retrieved[:k]]
        relevant_ids = set(item['relevant_chunk_ids'])
        if any(rid in relevant_ids for rid in retrieved_ids):
            hits += 1
    return hits / len(golden_dataset)

for k in [1, 3, 5, 10]:
    hr = hit_rate_at_k(golden_dataset, retriever, k=k)
    print(f'Hit rate@{k}: {hr:.1%}')

평균 역순위(MRR)

MRR(평균 역순위)은 첫 번째 관련 청크가 나타나는 순위의 역수 평균을 측정합니다. 관련 청크가 1위에 있으면 역순위는 1/1 = 1.0입니다. 2위이면 0.5, 5위이면 0.2입니다. MRR은 모든 질의에 대해 평균을 계산합니다. MRR이 높다는 것은 검색기가 관련 청크를 상위에 꾸준히 배치한다는 뜻이며, 프롬프트 앞부분에 배치된 문맥일수록 LLM이 더 많은 주의를 기울이기 때문에 중요합니다.

def mean_reciprocal_rank(golden_dataset, retriever, top_k=10):
    reciprocal_ranks = []
    for item in golden_dataset:
        retrieved = retriever.retrieve(item['question'], top_k=top_k)
        retrieved_ids = [r['id'] for r in retrieved]
        relevant_ids = set(item['relevant_chunk_ids'])

        rr = 0.0
        for rank, rid in enumerate(retrieved_ids, start=1):
            if rid in relevant_ids:
                rr = 1.0 / rank
                break  # only the first relevant result counts
        reciprocal_ranks.append(rr)

    mrr = sum(reciprocal_ranks) / len(reciprocal_ranks)
    print(f'MRR@{top_k}: {mrr:.3f}')
    return mrr

MRR 점수 해석

MRR 점수는 직관적으로 해석할 수 있습니다. MRR = 1.0은 첫 번째 관련 청크가 항상 1위라는 뜻으로, 완벽한 상태입니다. MRR = 0.5는 일반적으로 2위라는 뜻입니다. MRR = 0.25는 일반적으로 4위라는 뜻이며, 관련 정보가 너무 아래에 있어 문맥에서 잘릴 수 있습니다. RAG에서는 가장 관련성 높은 청크가 항상 처음 두 위치 안에 오도록 MRR > 0.7을 목표로 하십시오.

# MRR interpretation table
mrr_interpretations = {
    1.0:  'Perfect — relevant chunk always at rank 1',
    0.5:  'Good — typically at rank 2',
    0.33: 'Acceptable — typically at rank 3',
    0.25: 'Weak — typically at rank 4',
    0.1:  'Poor — relevant chunk rarely near the top'
}

for score, description in mrr_interpretations.items():
    print(f'MRR {score:.2f}: {description}')

정밀도 @K

정밀도@K는 검색된 K개 청크 중 실제로 관련 있는 청크의 비율을 측정합니다. 이진값인 적중률과 달리 정밀도@K는 검색 결과의 신호 대 잡음비를 측정합니다. 정밀도가 낮으면 LLM이 관련 청크와 함께 관련 없는 문맥도 전달받으므로 혼란이나 프롬프트 주입의 위험이 커집니다. RAG에서는 정밀도@5 > 0.6을 양호한 목표로 삼으십시오.

def precision_at_k(golden_dataset, retriever, k=5):
    precisions = []
    for item in golden_dataset:
        retrieved = retriever.retrieve(item['question'], top_k=k)
        retrieved_ids = [r['id'] for r in retrieved[:k]]
        relevant_ids = set(item['relevant_chunk_ids'])

        relevant_retrieved = sum(
            1 for rid in retrieved_ids if rid in relevant_ids
        )
        precision = relevant_retrieved / k
        precisions.append(precision)

    mean_precision = sum(precisions) / len(precisions)
    print(f'Precision@{k}: {mean_precision:.3f}')
    return mean_precision

할인 누적 이득(DCG)

DCG는 더 관련성 높은 청크를 상위에 배치할수록 높은 점수를 주는 순위 목록 평가 지표입니다. 검색된 청크의 관련성 점수를 합산하되, 위치에 따라 로그 방식으로 할인합니다. 1위는 점수를 온전히 받고, 2위는 log(2)만큼 할인받는 식입니다. 상위에 관련성이 높은 청크가 있을수록 DCG가 높아집니다. 정규화된 버전인 NDCG는 이상적인 DCG(가능한 최상의 순위)로 나누어 0에서 1 사이의 점수를 냅니다.

import math

def dcg_at_k(relevances, k):
    '''relevances[i] = 1 if chunk at rank i+1 is relevant, else 0'''
    dcg = 0.0
    for i, rel in enumerate(relevances[:k]):
        # rank is i+1, discount is log2(rank + 1)
        dcg += rel / math.log2(i + 2)
    return dcg

def ndcg_at_k(retrieved_ids, relevant_ids, k):
    relevances = [1 if rid in relevant_ids else 0
                  for rid in retrieved_ids[:k]]
    actual_dcg = dcg_at_k(relevances, k)
    ideal_dcg = dcg_at_k([1] * min(len(relevant_ids), k), k)
    return actual_dcg / ideal_dcg if ideal_dcg > 0 else 0.0

데이터셋 전체에서 NDCG 계산하기

NDCG@K는 검색 시스템의 표준 검색 평가 지표입니다. 관련성과 순위 위치를 동시에 반영합니다. NDCG@5가 0.85라는 것은 검색기가 평균적으로 이론상 최상의 순위의 85% 수준으로 작동한다는 뜻입니다. NDCG는 질의마다 관련 청크가 여러 개 있는 경우를 처리하며(각 청크에 관련성 점수가 부여됨), 관련 청크를 찾았지만 너무 낮은 순위에 배치하는 시스템에는 불이익을 줍니다.

def mean_ndcg_at_k(golden_dataset, retriever, k=5):
    ndcg_scores = []
    for item in golden_dataset:
        retrieved = retriever.retrieve(item['question'], top_k=k)
        retrieved_ids = [r['id'] for r in retrieved[:k]]
        relevant_ids = set(item['relevant_chunk_ids'])
        score = ndcg_at_k(retrieved_ids, relevant_ids, k)
        ndcg_scores.append(score)
    mean = sum(ndcg_scores) / len(ndcg_scores)
    print(f'NDCG@{k}: {mean:.4f}')
    return mean

# Compute all retrieval metrics together
hit_rate = hit_rate_at_k(golden_dataset, retriever, k=5)
mrr = mean_reciprocal_rank(golden_dataset, retriever, top_k=5)
ndcg = mean_ndcg_at_k(golden_dataset, retriever, k=5)

자동화된 지표에 RAGAS 사용하기

RAGAS 라이브러리는 RAG 시스템을 위한 운영 환경 수준의 평가 프레임워크를 제공합니다. LLM을 평가자로 사용하는 방식으로 문맥 정밀도, 문맥 재현율, 충실도, 답변 관련성 지표를 구현합니다. 질문, 답변, 검색된 문맥, 정답을 RAGAS에 전달하면 모든 지표의 점수가 포함된 전체 평가 보고서를 받을 수 있습니다. 종합적인 RAG 평가 파이프라인을 가장 빠르게 설정하는 방법입니다.

from ragas import evaluate
from ragas.metrics import (
    context_precision,
    context_recall,
    faithfulness,
    answer_relevancy
)
from datasets import Dataset

eval_data = Dataset.from_list([
    {
        'question': item['question'],
        'answer': item['generated_answer'],
        'contexts': item['retrieved_texts'],
        'ground_truth': item['expected_answer']
    }
    for item in golden_dataset_with_answers
])

results = evaluate(
    eval_data,
    metrics=[context_precision, context_recall, faithfulness, answer_relevancy]
)
print(results)

질의 범주별 지표 분할

전체 평균 지표는 중요한 패턴을 가립니다. 골든 데이터셋을 사실 조회, 비교, 절차를 묻는 방법 질문, 범위를 벗어난 질문 등의 범주로 나누고 각 범주에 대해 지표를 따로 계산하십시오. 사실 조회의 적중률은 95%이지만 여러 단계를 거치는 비교에서는 60%에 불과할 수 있습니다. 범주별 지표를 확인하면 종합 점수에 가려진 구체적인 실패 방식을 파악할 수 있습니다.

from collections import defaultdict

def evaluate_by_category(golden_dataset, retriever):
    by_category = defaultdict(list)
    for item in golden_dataset:
        category = item.get('category', 'unknown')
        retrieved = retriever.retrieve(item['question'], top_k=5)
        retrieved_ids = {r['id'] for r in retrieved}
        hit = bool(retrieved_ids & set(item['relevant_chunk_ids']))
        by_category[category].append(hit)

    print('Hit rate by category:')
    for cat, hits in sorted(by_category.items()):
        hr = sum(hits) / len(hits)
        print(f'  {cat}: {hr:.1%} ({sum(hits)}/{len(hits)})')

지표가 서로 다르게 나타날 때

때로는 지표가 서로 상충하는 신호를 보냅니다. 적중률은 그대로인데 NDCG(더 나은 순위)가 향상될 수 있습니다. 이는 최적화 과정에서 관련 청크가 6위에서 2위로 이동했지만, 이전에 놓쳤던 질의가 상위 5개 안으로 들어오지는 않았을 때 발생합니다. 이런 경우 최적화가 이미 성공하던 질의에는 도움이 되었지만 실패하던 질의를 소홀히 한 것은 아닌지 확인하십시오. 종합 지표와 함께 개별 실패 사례도 항상 살펴보십시오.

def analyze_failures(golden_dataset, retriever, top_k=5):
    failures = []
    for item in golden_dataset:
        retrieved = retriever.retrieve(item['question'], top_k=top_k)
        retrieved_ids = {r['id'] for r in retrieved}
        relevant_ids = set(item['relevant_chunk_ids'])
        if not (retrieved_ids & relevant_ids):
            failures.append({
                'question': item['question'],
                'expected_chunks': list(relevant_ids),
                'retrieved_chunks': [r['id'] for r in retrieved],
                'top_score': retrieved[0]['score'] if retrieved else None
            })
    print(f'Failures: {len(failures)}/{len(golden_dataset)}')
    return failures

재현율 @K: 검색의 완전성

재현율@K는 모든 관련 청크 중 상위 K개 결과에서 검색된 청크의 비율을 측정합니다. 색인에 질문과 관련된 청크가 3개 있고 검색기가 상위 5개 결과에서 그중 2개를 반환한다면 재현율@5는 2/3 = 0.67입니다. LLM이 여러 증거를 종합하여 완전한 답변을 만들어야 할 때는 높은 재현율이 중요합니다. 핵심 청크 하나만 빠져도 답변이 불완전해질 수 있기 때문입니다. K를 조정하여 정밀도와 재현율의 균형을 맞추십시오. K가 클수록 재현율은 높아지지만 정밀도는 낮아집니다.

def recall_at_k(golden_dataset, retriever, k=5):
    recalls = []
    for item in golden_dataset:
        retrieved = retriever.retrieve(item['question'], top_k=k)
        retrieved_ids = set(r['id'] for r in retrieved[:k])
        relevant_ids = set(item['relevant_chunk_ids'])
        if not relevant_ids:
            continue  # skip items with no annotated relevant chunks
        retrieved_relevant = retrieved_ids & relevant_ids
        recall = len(retrieved_relevant) / len(relevant_ids)
        recalls.append(recall)
    mean_recall = sum(recalls) / len(recalls)
    print(f'Recall@{k}: {mean_recall:.3f}')
    return mean_recall

빠른 확인

이 단원에서 배운 AI 엔지니어링 개념을 이해했는지 확인하십시오.

단원 요약

이 단원에서는 이진 존재 지표인 적중률@K, 첫 번째 관련 청크의 평균 순위 위치를 측정하는 MRR, 검색 결과의 신호 대 잡음비를 측정하는 정밀도@K, 표준 순위 지표인 NDCG@K, 그리고 문맥 정밀도, 재현율, 충실도, 답변 관련성을 사용한 RAG 평가를 자동화하는 RAGAS 라이브러리를 배웠습니다. 다음으로 생성 지표와 충실도 측정을 더 자세히 살펴봅니다.

무료로 시작

AI 튜터와 함께 Python을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
30
레슨
120

자주 묻는 질문

“검색 지표: 적중률, MRR, NDCG” 강의는 무료인가요?

네 — “검색 지표: 적중률, MRR, NDCG” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“검색 지표: 적중률, MRR, NDCG”에서 뭘 배우나요?

질의와 관련 문서로 정답 데이터 세트를 구축한 뒤 적중률, 평균 역순위, NDCG를 계산해 검색기가 올바른 분할 조각을 얼마나 자주 찾는지 측정합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

AI Engineering Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 AI Engineering Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“검색 지표: 적중률, MRR, NDCG” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 AI Engineering Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 AI Engineering Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. RAG에서 평가가 중요한 이유
  2. 검색 지표: 적중률, MRR, NDCG
  3. 생성 지표: 충실도와 답변 관련성
  4. 자동화된 평가 도구 구축
← AI Engineering Academy(으)로 돌아가기