本文へスキップ
BecomeCoder

RAGコース · 第5章 精度を上げる · レッスン23

RAGを評価する ― Recall@k と MRR

ブラウザで完結

導入

ハイブリッド検索やリランキングを取り入れて「良くなった気がする」で終わらせるのは危険です。パラメータを変えたつもりが、実は逆効果だったということもあります。改善が本当に効いているかを確かめるには、検索の良さを数字で測る必要があります。ここでは検索評価の基本となる2つの指標を、実際に計算しながら身につけます。

説明

# 検索の「良さ」を数字で測る
# retrieved: 検索が返した順位つきの文書IDリスト / relevant: 本当に関連する文書ID
retrieved = ["d3", "d1", "d7", "d2"]
relevant  = {"d1", "d2"}

def recall_at_k(retrieved, relevant, k):
    topk = retrieved[:k]
    hit = len(set(topk) & relevant)
    return hit / len(relevant)

def reciprocal_rank(retrieved, relevant):
    for rank, doc in enumerate(retrieved, start=1):
        if doc in relevant:
            return 1.0 / rank
    return 0.0

for k in [1, 2, 3, 4]:
    print(f"Recall@{k}: {recall_at_k(retrieved, relevant, k):.2f}")
print(f"Reciprocal Rank: {reciprocal_rank(retrieved, relevant):.3f}")

最初の実行は少し待ちます:scikit-learn を使う回は、初回だけライブラリの読み込みに数十秒かかることがあります(2回目以降は速くなります)。「読み込み中…」と出たら、そのまま待ってください。

  • Recall@k:上位k件の検索結果の中に、本当に関連する文書(relevant)がどれだけ含まれていたかの割合です。k を大きくするほどRecallは上がっていきますが(取りこぼしが減るため)、その代わりノイズも増えます。
  • Reciprocal Rank:最初に正解が現れた順位の逆数です。1位で当たれば1.0、2位なら0.5、3位なら0.33…と、正解が上位に来るほど高い指標になります。複数の質問についてReciprocal Rankを計算し、その平均を取ったものが MRR(Mean Reciprocal Rank) と呼ばれる、検索システム全体の評価によく使われる指標です。

検索の質はこの2つで測れますが、生成の質(答えの文章自体が正確か、根拠に忠実か)を測るには別の方法が必要です。実務では、LLM自身に採点させるLLM-as-a-judgeや、忠実性・関連性などを自動評価する RAGAS といったツールもよく使われます。この回では検索側の評価に絞って体験します。

やってみよう

retrieved の順序を ["d1", "d3", "d7", "d2"] に変えて、Recall@1Reciprocal Rank がどう変わるか確かめてみましょう。relevant"d7" を追加すると、Recall@k 全体がどう変わるかも試してみてください。

演習

次の3件分の検索結果それぞれについて reciprocal_rank を計算し、その平均(= MRR, Mean Reciprocal Rank)を print してください。

queries = [
    (["d5", "d1"], {"d1"}),
    (["d2", "d9", "d1"], {"d1", "d2"}),
    (["d8", "d7"], {"d3"}),
]
ヒント1を見る

for r, rel in queries: のようにループしながら reciprocal_rank(r, rel) を呼び出し、結果を rrs = [] のようなリストに append していきます。

ヒント2を見る

集めたリストを sum(rrs) / len(rrs) で平均すればMRRになります。

まとめ

  • Recall@kは「上位k件に正解がどれだけ含まれていたか」の割合。kを増やせば上がるが、ノイズとのトレードオフ。
  • Reciprocal Rankは「最初の正解が何位に出たか」の逆数。複数質問の平均がMRR。
  • 検索の質はRecall@k・MRRで測り、生成の質はLLM-as-a-judgeやRAGASのような別の方法で測る。改善は感覚ではなく数字で確認する。

実際に動かしてみよう

下のエディタにPythonを書いて「実行」を押すと、scikit-learn(TF-IDF)とnumpyで本物の「検索」をその場で動かせます。RAGの検索フェーズ――文書のチャンク分割・ベクトル化・類似度計算・並べ替え――が実際に動きます。scikit-learn を使う回は初回の読み込みに数十秒かかることがあります。検索デモのサンプル文書は英語です(TF-IDFの既定トークナイザが日本語を分割できないため。日本語検索の対処は読み物で解説します)。LLMの生成や本物の埋め込みAPIは、APIキーが要るため読み物の回でコードを示します。

Python — ライブラリ付きで実行(Pyodide)

numpy / pandas / matplotlib が使える本物のPython(Pyodide)を読み込みます。初回のみ読み込みに少し時間がかかります(以降はブラウザにキャッシュされます)。
スクロールして表示された時点でも自動で読み込まれます。