リランキング — 2段構えで精度とコストを両立する
検索は当たっているのに回答がずれる原因を、順位の粗さとして特定できるようになる。bi-encoderとcross-encoderの構造的な差を理解し、2段構えの設計とコストの見積もりができる。
- ベクトル検索が使うbi-encoderは、クエリと文書を別々にベクトル化してから内積を取る。この分離があるから事前計算できて速いが、両者を突き合わせて読む精度は原理的に出せない。
- cross-encoderはクエリと文書を連結して1回のモデル実行で関連度を出す。事前計算できないので全件には使えないが、上位数十件の並べ替えなら現実的なコストで大幅に精度が上がる。
- だから2段構え。1段目で再現率を稼ぎ(多めに取る)、2段目で精度を上げる(正確に並べ替える)。1段目のk件を増やすほど上限は上がるが、2段目のコストは線形に増える。
「検索した上位5件の中に正解は入っているのに、回答が的外れになる」。この症状は検索の失敗ではなく、順位の失敗です。正解が5位で、1位から4位に紛らわしい文書が並んでいると、LLM は上のほうに強く引きずられます。文脈の中で先に出てきた情報ほど重く扱われる傾向があるため、順位はそのまま回答品質に効きます。
これを直すのがリランキング——1段目の検索結果を、より高精度な別のモデルで並べ替える工程です。
横にスクロール
なぜ1段目の順位は粗いのか
ベクトル検索が使う埋め込みモデルは bi-encoder(双方向エンコーダ)と呼ばれる構造です。
bi-encoder
クエリ --[エンコーダ]--> ベクトルQ ┐
├--> 内積 or コサイン --> スコア
文書 --[エンコーダ]--> ベクトルD ┘
文書側のベクトルは事前に計算してインデックスに保存できる
→ 検索時はクエリ1本を変換して近傍を引くだけ。100万件でも速い
この分離こそが速さの源です。しかし同時に、精度の天井も決めています。文書をベクトル化する時点では、どんなクエリが来るか分かりません。だから文書の内容を「あらゆるクエリに対して平均的に良い」1本のベクトルへ潰すしかない。クエリと文書のどの部分がどう対応するか、という突き合わせの計算が一切できないのです。
対して cross-encoder(交差エンコーダ)は、両者を連結して1回のモデル実行にかけます。
cross-encoder
[CLS] クエリ [SEP] 文書 [SEP] --[モデル]--> 関連度スコア
クエリと文書のトークンが同じ注意機構の中で相互作用する
→ 「この語がこの箇所に対応する」という照合ができる。精度は高い
→ 文書ごとにモデルを走らせる必要があり、事前計算は不可能
100万件に cross-encoder を適用するのは非現実的です。しかし上位50件の並べ替えなら50回の推論で済み、これは十分に現実的なコストです。
| bi-encoder(1段目) | cross-encoder(2段目) | |
|---|---|---|
| 計算の構造 | 別々にベクトル化して内積 | 連結して1回のモデル実行 |
| 事前計算 | 文書側は可能 | 不可能(クエリが来て初めて計算) |
| 対象件数 | 100万件でも可 | 数十〜数百件が現実的 |
| 精度 | 中(意味の大まかな近さ) | 高(語と語の対応まで見る) |
| 役割 | 再現率を稼ぐ | 精度を上げる |
2段構えの設計
1段目: ベクトル検索 + BM25 を RRF で統合 → 上位 50〜100 件
↓
2段目: cross-encoder で並べ替え → 上位 3〜10 件
↓
生成: 上位数件を文脈に入れて回答させる
役割分担が明確です。1段目の仕事は「正解を候補に含めること」(再現率)で、順位の正しさは求めません。2段目の仕事は「候補の中で正解を上に持ち上げること」(精度)です。
だから1段目は取りこぼさないよう多めに取ります。ここを5件に絞ってしまうと、その5件に正解が入っていなければ2段目がどれだけ優秀でも救えません。2段目の性能の上限は、1段目の再現率で決まります。
コストの見積もり
2段目の件数を決めるのは、精度とコストのトレードオフです。
1段目の取得件数を k とすると
cross-encoder の推論回数 = k 回 / クエリ
レイテンシ ≒ k × 1回の推論時間(ただし通常はバッチ化して並列実行)
金銭コスト ≒ k に比例
たとえば1回の推論が 5ms で k=50 なら、完全に直列なら 250ms、バッチ化して並列に流せば数十ms に収まります。バッチ化できるかどうかが実装上の分かれ目で、1件ずつ API を叩く実装にすると k に比例してレイテンシが伸び、体感が悪化します。
k の決め方は実測が確実です。ゴールデンセットで「1段目の上位k件に正解が含まれる割合(recall@k)」を測り、recall が頭打ちになる k を選びます。recall@50 と recall@100 がほぼ同じなら、100 にするコストは無駄です。測り方はRAGの評価にまとめてあります。
専用のcross-encoderを用意せず、LLM自身に「この文書はこの質問にどれくらい関連するか」を0〜10で採点させる方法もあります。追加のモデルを持たずに始められ、日本語の性能も安定しやすいのが利点です。欠点はコストとレイテンシで、候補ごとにLLMを呼ぶと専用モデルより桁で高くつきます。まずLLMリランカで効果を確認し、効くと分かってから専用モデルに置き換えるという順序なら、投資を無駄にしません。
リランキング以外に順位を直す手
リランカを入れる前に、より安く効く手があります。
多様性の確保(MMR)。 上位が同じ文書の隣接チャンクで埋まると、実質1つの情報源しか渡していないことになります。関連度と既選択との非類似度を組み合わせて選ぶ(Maximal Marginal Relevance)と、少ない枠でカバー範囲が広がります。
文脈内の配置。 LLM は長い文脈の先頭と末尾に注意が偏り、中央が薄くなる傾向が報告されています。上位の文書を先頭に置き、次点を末尾に置くという配置は、この性質を利用した安価な工夫です。
そもそも渡す件数を減らす。 関連度が閾値を下回る候補は渡さない、という単純な足切りが効くことがあります。無関係な文書は生成の邪魔にしかならないため、5件無理に埋めるより2件だけ渡すほうが良い回答になる場面は珍しくありません。
まとめ
- 1段目の順位が粗いのは bi-encoder の構造上の制約。文書ベクトルを事前計算できる代償として、クエリとの突き合わせ計算ができない。
- cross-encoder はクエリと文書を連結して1回で評価するので精度が高いが、事前計算できず全件には使えない。
- だから2段構え。1段目は再現率(多めに取る)、2段目は精度(正確に並べ替える)と役割を分ける。
- 2段目の上限は1段目の再現率で決まる。1段目を絞りすぎると2段目が何をしても救えない。
- k は
recall@kが頭打ちになる点を実測で選ぶ。バッチ化しないとレイテンシが k に比例して伸びる。 - リランカの前に、多様性の確保・文脈内の配置・件数を減らすという安い手も試す価値がある。
RAG設計パターンの記事ガイド
リランキング — 2段構えで精度とコストを両立するを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
RAG
比較で見る軸
難易度: advanced / カテゴリ: RAG設計パターン / タグ数: 5
導入後に効く点
cross-encoderはクエリと文書を連結して1回のモデル実行で関連度を出す。事前計算できないので全件には使えないが、上位数十件の並べ替えなら現実的なコストで大幅に精度が上がる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- RAG設計パターン
- タグ数
- 5
判断チェックリスト
- 自社の用途が「RAG / リランキング」に近いか確認する。
- 強みである「ベクトル検索が使うbi-encoderは、クエリと文書を別々にベクトル化してから内積を取る。この分離があるから事前計算できて速いが、両者を突き合わせて読む精度は原理的に出せない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。