導入
ハイブリッド検索やリランキングを取り入れて「良くなった気がする」で終わらせるのは危険です。パラメータを変えたつもりが、実は逆効果だったということもあります。改善が本当に効いているかを確かめるには、検索の良さを数字で測る必要があります。ここでは検索評価の基本となる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@1 と Reciprocal 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のような別の方法で測る。改善は感覚ではなく数字で確認する。