0Pricing
AI Engineering Academy · レッスン

検索指標:Hit Rate、MRR、NDCG

クエリと関連ドキュメントのgolden datasetを作成し、hit rate、mean reciprocal rank、NDCGを計算して、検索器が正しいチャンクを見つける頻度を測定します。

「検索指標:Hit Rate、MRR、NDCG」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。

異なる検索指標を使う理由

ヒット率は、関連するチャンクが上位K件のどこかに含まれているかを示しますが、ランキングのどこに含まれているかは示しません。最適なチャンクを常に5位に置くシステムは、ヒット率が同じでも、常に1位に置くシステムより劣ります。MRRやNDCGのような、より高度な指標はランキングの品質を捉え、LLMやユーザーが利用する可能性が最も高い上位に、最も関連性の高いチャンクを配置するシステムを高く評価します。

ヒット率@K:復習と実装

ヒット率@Kは最も単純な指標です。クエリのうち、上位K件の結果に関連するチャンクが少なくとも1つ含まれる割合を示します。クエリごとに二値のシグナルを示すため、解釈が簡単です。検索された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(Mean Reciprocal Rank)は、最初に関連するチャンクが現れる順位の逆数の平均を測定します。関連するチャンクが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では、最も関連性の高いチャンクが常に最初の2つの位置に入るよう、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は、1つのクエリに複数の関連チャンクがあるケース(それぞれに関連度スコアが付く)に対応し、関連するチャンクを見つけても低い順位に配置するシステムを低く評価します。

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-as-judgeアプローチを使い、コンテキスト適合率、コンテキスト再現率、忠実度、回答の関連性を実装しています。質問、回答、検索されたコンテキスト、正解の回答を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つあり、検索システムがそのうち2つを上位5件で返した場合、再現率@5は2/3 = 0.67です。LLMが複数の証拠を組み合わせて完全な回答を作る必要がある場合、高い再現率が重要です。重要なチャンクを1つでも取りこぼすと、回答が不完全になる可能性があります。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 Engineeringの概念を理解できているか確認しましょう。

レッスンのまとめ

このレッスンでは、二値の存在指標としてのヒット率@K、最初に関連するチャンクの平均順位を測定するMRR、検索結果のシグナル対ノイズ比を測定する適合率@K、ランキング評価の標準指標であるNDCG@K、そしてコンテキスト適合率、再現率、忠実度、回答の関連性を使ったRAG評価を自動化するRAGASライブラリについて学びました。次は、生成指標と忠実度の測定について詳しく見ていきます。

よくある質問

「検索指標:Hit Rate、MRR、NDCG」レッスンは無料ですか?

はい。「検索指標:Hit Rate、MRR、NDCG」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。

「検索指標:Hit Rate、MRR、NDCG」で何を学びますか?

クエリと関連ドキュメントのgolden datasetを作成し、hit rate、mean reciprocal rank、NDCGを計算して、検索器が正しいチャンクを見つける頻度を測定します。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Engineering Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「検索指標:Hit Rate、MRR、NDCG」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Engineering Academyレッスンでコードを書いて実行できますか?

はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. RAGで評価が重要な理由
  2. 検索指標:Hit Rate、MRR、NDCG
  3. 生成指標:忠実性と回答の関連性
  4. 自動評価ハーネスの構築
← AI Engineering Academyに戻る