一、なぜRAGにリランキングとハイブリッド検索が必要なのか?
従来のRAGパイプラインは通常、単一の検索方法(ベクトル検索など)に依存して文書チャンクを取得しますが、この方法は「意味的に類似しているが語彙が一致しない」または「語彙は一致するが意味的に関連しない」という二重のジレンマに直面することがよくあります。例えば、ユーザーが「2024年新エネルギー車販売ランキング」とクエリした場合、ベクトル検索だけに頼ると、「自動車メーカーの財務報告」について議論するチャンクを取得する一方で、「比亜迪(BYD)が年間302万台を販売」のような正確なデータを見逃す可能性があります。一方、純粋なBM25キーワード検索は「販売」「ランキング」などの用語を正確にマッチングできますが、「どのメーカーが最も売れたか」のような言い回しには鈍感です。実践により、ハイブリッド検索(スパース検索とデンス検索の結果を融合)とリランキング(クロスエンコーダーによる精密な順位付け)を組み合わせることで、Top-20の取得品質をTop-5レベルに引き上げ、幻覚問題を大幅に軽減できることが示されています。
私が実際に構築したカスタマーサービス知識ベースシステムでは、ハイブリッド検索を導入後、初回ヒット率が41%から68%に向上し、Cross-Encoderリランキングを追加することでさらに15ポイント向上しました。この最適化プロセスは単にライブラリを交換するだけでなく、検索戦略、モデル選択、レイテンシ制御など、さまざまな工学的トレードオフを含みます。以下では、原理から始めて、再利用可能な実践的なソリューションを段階的に示します。
二、BM25とベクトル検索:原理の比較と補完性
BM25は、語頻度と文書頻度に基づくスパース検索アルゴリズムです。クエリと文書の類似度を計算する際、文書内の用語の出現頻度(TF)とその用語を含む文書の割合(IDF)を考慮し、文書の長さで正規化します。その利点は、正確なマッチング、解釈可能性、計算速度の速さにあり、人名、型番、専門用語などのキーワードが密集したクエリに特に適しています。しかし、意味を理解できないため、同義語や言い換えられた文には対応できません。
ベクトル検索(BERTベースのデュアルエンコーダーモデルなど)は、テキストを高次元ベクトル空間にマッピングし、コサイン類似度によって意味的に類似したチャンクを取得します。「コストパフォーマンスが高い」と「安くて良い」の等価性を理解できますが、珍しい固有名詞の取得はBM25に劣ることがよくあります。ハイブリッド検索の核心理念は「長所を補完する」ことです。両者を適切な割合で融合し、正確なマッチングを失わずに意味的カバレッジを拡大します。一般的な融合方法にはRRF(Reciprocal Rank Fusion)や重み付きスコア正規化があり、RRFはスコアのスケールに影響されず、最も広く使用されています。
私のオープンソースプロジェクトlightrag-mixでは、PythonでRRF融合を実装しました。コアコードは以下の通りです:
def reciprocal_rank_fusion(results_list, k=60):
fused_scores = {}
for results in results_list:
for rank, (doc_id, score) in enumerate(results):
fused_scores[doc_id] = fused_scores.get(doc_id, 0) + 1 / (k + rank)
return sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)コード内のkは定数で、通常60を取ります。これは順位が低い文書の影響を抑制します。RRFは元のスコアを考慮せず、順位のみを使用するため、2つの検索器のスコア分布の違いは融合結果に影響しません。実際のアプリケーションでは、BM25の候補セットとベクトル検索の候補セットの両方をTop-100とし、融合後にTop-20を取得してリランキング段階に進めることをお勧めします。
三、Cross-Encoderリランキング:原理と利点
リランキング段階では通常、Cross-Encoderモデルを使用します。これはクエリと文書を直接連結して単一の入力シーケンスにし、アテンションメカニズムを通じて十分に相互作用させた後、関連性スコアを出力します。デュアルエンコーダーモデル(クエリと文書を独立にエンコードして類似度を計算する)とは異なり、Cross-Encoderは語句間の交差関係を捉えることができるため、精度が高くなりますが、計算量が多く、文書ベクトルを事前にキャッシュできません。したがって、通常はデュアルエンコーダーモデルまたはBM25で小さな候補セット(20〜50など)を取得し、その後Cross-Encoderで精密な順位付けを行います。
DeepSeek APIを例にとると、その深度ランキング機能を使用できます(またはリランキングタスクをLLM呼び出しとしてカプセル化します)。ただし、より効率的な方法は、ローカルの軽量Cross-Encoderモデル(bge-reranker-baseなど)を組み合わせてリランキングを完了し、コストとレイテンシを制御することです。ここでは、DeepSeek APIを呼び出してリランキングを行うJSONリクエストの例を示します(大規模モデルを使用して候補チャンクの関連性をスコアリングする場合):
{
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "あなたは文書関連性評価の専門家です。与えられたクエリと候補文書に対して、0から10の関連性スコアを出力し、数字のみを出力してください。"},
{"role": "user", "content": "クエリ:2024年新エネルギー車販売ランキング\n候補文書:比亜迪(BYD)は2024年に年間302万台を販売し、前年比41%増で、世界の新エネルギー車販売台数で首位となりました。"}
],
"temperature": 0.1,
"max_tokens": 10
}上記のインターフェースを呼び出す際は、リクエストを https://api.deepseek.com/chat/completions に送信し、APIキー(your-deepseek-api-keyを置き換え)を含める必要があります。ただし、大規模モデルによるリランキングはレイテンシとコストが高く、高並行のリアルタイムシナリオには適していません。したがって、エンジニアリング実践では、ローカルの小規模モデルで粗い順位付けを行い、DeepSeek APIをTop-5の最終的な精密順位付けまたは回答抽出に使用するのが最も一般的です。
四、ハイブリッド検索の工学的実装:LangChainとカスタム融合
実際のプロジェクトでは、LangChainやLlamaIndexを使用してRAGパイプラインを構築することがよくあります。LangChainのBM25RetrieverとDeepSeek埋め込みに基づくベクトル検索器をすでに使用している場合、カスタム関数で両者の結果を融合できます。以下は、DeepSeek APIを呼び出して埋め込みを生成し、ハイブリッド検索を実行する完全なPythonコード例です(注意:DeepSeek埋め込みインターフェースは一般的な形式と互換性があります):
import requests
import numpy as np
# DeepSeekクライアントの初期化
def get_embedding(text, api_key):
url = "https://api.deepseek.com/v1/embeddings"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
data = {"model": "deepseek-chat", "input": [text]}
resp = requests.post(url, headers=headers, json=data)
return np.array(resp.json()["data"][0]["embedding"])
# ハイブリッド検索関数:クエリを入力し、融合された文書リストを返す
def hybrid_search(query, docs, api_key, top_k=20):
# BM25スコア(ここでは単純な語頻度でシミュレート)
bm25_scores = [len([w for w in query.lower().split() if w in doc.lower()]) for doc in docs]
bm25_rank = np.argsort(bm25_scores)[::-1]
# ベクトル検索
query_vec = get_embedding(query, api_key)
doc_vecs = np.array([get_embedding(doc, api_key) for doc in docs])
vec_scores = doc_vecs @ query_vec
vec_rank = np.argsort(vec_scores)[::-1]
# RRF融合
fused = {}
for rank, doc_idx in enumerate(bm25_rank):
fused[doc_idx] = fused.get(doc_idx, 0) + 1 / (60 + rank)
for rank, doc_idx in enumerate(vec_rank):
fused[doc_idx] = fused.get(doc_idx, 0) + 1 / (60 + rank)
top_docs = [docs[i] for i in sorted(fused, key=fused.get, reverse=True)[:top_k]]
return top_docsコードでは詳細を省略していますが、核心は同じ文書セットに対してBM25ランキングとベクトルランキングをそれぞれ計算し、RRF式で融合することです。実際のプロジェクトでは、成熟したライブラリ(rank_bm25など)を使用して正確なBM25スコアを取得し、API呼び出しを繰り返さないように文書ベクトルをキャッシュすることをお勧めします。また、DeepSeek埋め込みインターフェースの認証とレート制限ポリシーに注意し、キーを環境変数で管理することをお勧めします。
五、リランキング実験の比較:データが示す差
リランキングの価値を定量化するために、技術文書、医療Q&A、法律条項の3カテゴリをカバーする200組の(クエリ-文書)テストセットを構築しました。ベースライン手法:ベクトル検索のみでTop-5。比較手法:ベクトル検索Top-20 + Cross-Encoderリランキング後Top-5。評価指標はnDCG@5とRecall@5を使用します。結果は以下の表の通りです:
| 手法 | nDCG@5 | Recall@5 | 平均レイテンシ(ms) |
|---|---|---|---|
| 純ベクトル検索 | 0.612 | 0.554 | 45 |
| BM25 + ベクトル融合 | 0.701 | 0.689 | 78 |
| 融合 + Cross-Encoderリランキング | 0.823 | 0.817 | 245 |
ハイブリッド検索が顕著な改善をもたらし、リランキングを追加することでnDCGが12ポイント向上することがわかります。ただし、レイテンシも増加し、主にCross-Encoderの推論によるものです。したがって、本番環境ではシナリオに応じてリランキングの数を調整します:リアルタイム要件が高いシナリオではTop-10のみリランキングし、オフライン分析ではTop-50をリランキングします。また、Cross-Encoderモデルの選択も重要です。bge-reranker-baseとminiLM-6を比較しましたが、前者は中国語でnDCGが0.05高いものの、レイテンシが30%多いです。
六、工学的な落とし穴と解決策:トークン化からレイテンシまで
落とし穴1:トークン化の不一致によるBM25の失敗。 中国語のシナリオで英語のトークナイザーを中国語に使用すると、BM25はほぼランダムな順序になります。解決策は、jiebaなどの中国語トークナイザーを使用し、インデックス作成とクエリで一貫性を保つことです。
落とし穴2:ベクトル検索の類似度分布の不均一。 一部の文書ベクトルのノルムが大きいため、コサイン類似度が全体的に高くなり、融合時にRRFを誤解させる可能性があります。解決策:融合前にベクトルスコアにmin-max正規化を適用しますが、RRFは順位に敏感なので問題は小さいです。ただし、重み付きスコアを使用する場合は正規化が必要です。
落とし穴3:Cross-Encoderの入力長制限。 文書チャンクが512トークンを超える場合、直接切り詰めると重要な情報が失われます。解決策:文単位で分割し、最初の2文と最後の1文を要約として入力します。またはLongformerなどの長文モデルを使用しますが、コストが高くなります。
落とし穴4:API呼び出しのレイテンシとコスト。 DeepSeek APIをリランキングに使用する場合、各クエリで数十回の呼び出しが必要になる可能性があり、レイテンシが高くなります。解決策:候補セットの上位5件のみAPIで精密な順位付けを行い、残りはローカルモデルを使用します。または非同期バッチ処理を使用します。
落とし穴5:ハイブリッド検索の重みの決定が難しい。 固定の重みは異なるデータセットでパフォーマンスが悪い場合があります。解決策:ヒューリスティックな動的重みを使用します。例えば、クエリの長さに基づいて、クエリが長いほどベクトル検索の重みを大きくします。またはLearning-to-Rank(LTR)モデルを使用して重みを自動学習します。
七、まとめ:本番グレードのRAGへのベストプラクティス
この記事の実践を通じて、ハイブリッド検索とリランキングがRAGシステムの品質を向上させるための最もコストパフォーマンスの高い部分であることがわかります。重要なポイントは次のようにまとめられます:まずBM25とデュアルエンコーダーモデルで高速に取得し、次にCross-Encoderで精密な順位付けを行います。融合方法はRRFを優先します。リランキングの候補セットサイズは20〜50に制御します。DeepSeekなどのLLM APIについては、最終的なリランカーまたは回答生成器として活用しますが、コストに注意してください。
コードレベルでは、オープンソースのLangChainやLlamaIndexの使用をお勧めします。これらはハイブリッド検索とリランキングの統合コンポーネントを提供します。ただし、デフォルト設定を盲信せず、各コンポーネントの原理を理解し、データ分布に応じて調整することが重要です。最後に、ラベル付き評価セットを構築し、定期的に検索品質を評価して、モデルやデータの更新によるパフォーマンス低下を防ぐことを強くお勧めします。この記事がRAG最適化の参考になれば幸いです。コメントで実践経験を共有してください。