---
title: "RRF（Reciprocal Rank Fusion）とは？計算式とk=60の意味・製品別設定・RAG-Fusionでの使い方"
url: "https://www.issoh.co.jp/tech/details/8015/"
published: 2025-08-04
updated: 2026-10-15
categories: ["LLM・RAG"]
publisher: "株式会社一創"
---

# RRF（Reciprocal Rank Fusion）とは？計算式とk=60の意味・製品別設定・RAG-Fusionでの使い方

RRF（Reciprocal Rank Fusion、逆順位融合）とは、複数の検索結果リストを1つの順位に統合する手法です。各リストでの順位を r としたとき、文書のスコアを 1/(k + r) の合計で求め、その降順に並べ直します。k は順位間の寄与の差をならす定数で、2009年の原論文以来 60 がよく使われます。スコアの値ではなく順位だけを使うので、BM25とベクトル検索のように尺度の違う結果をそのまま混ぜられ、ハイブリッド検索や RAG-Fusion の統合ステップに使われています。この記事では計算の手順、k を変えたときに順位がどう入れ替わるか（Pythonの実行結果付き）、CombSUM やスコア正規化と比べた精度（公開データセット SciFact での実測）、Elasticsearch・Qdrant・Milvus などの既定値の違い、RRFが効かない場面までを扱います。

なお、RRFという略称は、EUの復興基金 [Recovery and Resilience Facility](https://reforms-investments.ec.europa.eu/recovery-and-resilience-facility-1%5Fen)、分子生物学のリボソーム再生因子（Ribosome Recycling Factor）、R言語の正則化ランダムフォレストのパッケージ（Regularized Random Forest）にも使われます。この記事が扱うのは情報検索の Reciprocal Rank Fusion です。

## まとめ：RRFの要点

- **定義**：各リストでの順位 r から 1/(k + r) を求めて文書ごとに合計し、その合計の降順に並べる順位統合の手法。
- **順位だけを使う理由**：BM25のスコアとコサイン類似度のように尺度が違っても、正規化なしで統合できる。
- **k=60の意味**：原論文が予備実験で決めて以後固定した値。論文自身が「ほぼ最適だが選択は決定的でない」と書いている。k を小さくすると片方のリストの1位が強くなり、大きくすると両方に出る文書が強くなる。
- **製品ごとの差**：Elasticsearch・OpenSearch・Milvus・LangChain は既定60だが、Qdrant は0始まりの順位で既定 k=2。Azure AI Search は k を変えるパラメータを公開していない。
- **精度の位置づけ**：単独の検索を上回る場合があるが、改善は保証されず、融合法の中で最良とも限らない。OpenSearch の公式ベンチマークでは6データセットすべてでスコア正規化のハイブリッドを下回り（平均3.86%低い）、SciFact の実測でも min-max 正規化したスコアの和（nDCG@10 0.7295）が RRF（0.7168）を上回った（評価用300問では有意差なし）。k を1・10・60・100の4通りに変えたときの差は最大0.0054だった。
- **RAG-Fusion**：LLMで言い換えクエリを複数作り、それぞれの検索結果をRRFで統合してから回答を生成する構成。評価論文では応答時間が通常のRAGの1.77倍になり、生成クエリがずれると回答も脱線した。
- **効かない場面**：片方の検索が明らかに弱い、上位N件の窓から漏れる、スコアの差が意味を持つ、のいずれかに当たるなら、重み付けや別の融合法と比べて決める。

## RRFの定義と計算式

RRFは、ウォータールー大学の Cormack・Clarke と、Googleの Büttcher が SIGIR 2009 で発表した順位統合の手法です（[原論文PDF](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf)）。原論文の式は次のとおりです。

```
RRFscore(d) = Σ_{r ∈ R} 1 / (k + r(d))

d    : 文書
R    : 統合する順位リストの集合（検索器ごとの結果）
r(d) : リスト r における文書 d の順位（1始まり）
k    : 定数（原論文では 60）
```

あるリストに出てこない文書は、そのリストからの加算が0になります。つまり「複数のリストで上位に出た文書」ほど合計が大きくなり、1つのリストでだけ1位の文書よりも上に来ることがあります。

### 2つの検索結果を統合する計算例

ベクトル検索とBM25の上位5件を k=60 で統合します。

| 文書 | ベクトル検索の順位 | BM25の順位 | 計算          | RRFスコア |
| -- | --------- | ------- | ----------- | ------ |
| A  | 1         | 5       | 1/61 + 1/65 | 0.0318 |
| D  | 4         | 4       | 1/64 + 1/64 | 0.0312 |
| F  | なし        | 1       | 1/61        | 0.0164 |
| B  | 2         | なし      | 1/62        | 0.0161 |

BM25で1位の F は、両方で4位の D に大きく負けます。D はどちらのリストでも4位ですが、両方から1/64ずつ加算されるため、片方だけで1位のFを上回り、統合後は2位に上がります。これがRRFの基本的な性質です。

### スコアでなく順位を使う理由

BM25のスコアは上限がなく、クエリによって10にも30にもなります。コサイン類似度の値域はマイナス1から1ですが、実際の分布は埋め込みモデルと比較対象に依存します。この2つをそのまま足すとBM25側が結果を支配するため、スコアで統合するには min-max 正規化などの前処理と、その調整が要ります。RRFは順位しか見ないので、この前処理が要りません。逆に言えば「1位と2位のスコアが僅差か大差か」という情報は捨てています。これが後述の弱点につながります。

## 定数kの意味と決め方

k は、上位の順位どうしの差をどれだけ縮めるかを決める定数です。k=60 なら1位の寄与は 1/61、10位は 1/70 で、差は1.15倍しかありません。k=1 なら1位は 1/2、10位は 1/11 で5.5倍になります。

### k=60の由来と原論文の結果

原論文は k=60 を「予備調査の段階で固定し、その後の検証では変更しなかった」値と説明しています。予備実験の結果は「k=60 はほぼ最適だが、選択は決定的ではない」というもので、60でなければならない理由はありません。論文では予備実験とTREC提出結果の実験を合わせ、最初の予備実験を除く7回で、RRFのMAPがCondorcet Fuseを7回、CombMNZを6回上回りました。予備実験とTREC実験全体では、比較手法や最良の単独検索に対する平均改善率を4〜5%と報告しています。ただし LETOR 3 を使った実験では CombMNZ がわずかに上回っており、どの条件でも最良というわけではありません。

### 定数k・窓・重みによる統合順位の変化

計算例と同じ2つのリストで、k・統合する件数（窓）・重みを変えて実行した結果です。実装は次章のコードをそのまま使っています。

```
k=60: A=0.0318, D=0.0312, F=0.0164, B=0.0161, G=0.0161, C=0.0159, H=0.0159, E=0.0154
k=1: A=0.6667, F=0.5000, D=0.4000, B=0.3333, G=0.3333, C=0.2500, H=0.2500, E=0.1667
window=3: A=0.0164, F=0.0164, B=0.0161, G=0.0161, C=0.0159, H=0.0159
weights=[2,1]: A=0.0482, D=0.0469, B=0.0323, C=0.0317, E=0.0308, F=0.0164, G=0.0161, H=0.0159
```

- **k=60 → k=1**：両方で4位の D が2位から3位に落ち、BM25だけで1位の F が2位に上がります。k を小さくすると「どれか1つで上位」が効き、大きくすると「複数で出現」が効きます。
- **window=3**：各リストの上位3件だけを統合すると、4位同士の D は結果から消えます。A も片方の寄与しか残らず、F と同点になります。k より窓の大きさのほうが結果を大きく変えることがあります。
- **weights=\[2,1\]**：ベクトル検索側を2倍にすると、ベクトル検索にしか出ない B・C・E が F より上に来ます。

評価用のクエリと正解文書が手元にないなら、k は60のまま、窓は最終的に返す件数より十分大きく取るのが安全です。k を詰めるのは、nDCG などで比較できる評価セットを作ってからにします。後述の SciFact の実測では、k を1・10・60・100の4通りに変えても nDCG@10 の差は最大0.0054にとどまりました。窓を10件に絞ると Recall@100 が下がり、融合法を CombSUM などに替えると nDCG@10 が k の調整以上に動きました。

## PythonでのRRF実装

RRFは外部ライブラリなしで十数行で書けます。重み付きと窓の指定にも対応させた実装です（Python 3.14で実行可能）。各ランキング内の文書IDは重複のない文字列とし、kは非負、weightsはランキング数と同じ長さの非負数列、windowはNoneまたは非負整数を渡してください。入力値の検証処理は省略しています。

```
from collections import defaultdict

def rrf(rankings, k=60, weights=None, window=None):
    """rankings: 検索器ごとの文書IDリスト（上位順）"""
    weights = weights or [1.0] * len(rankings)
    scores = defaultdict(float)
    for w, docs in zip(weights, rankings):
        for rank, doc_id in enumerate(docs[:window], start=1):
            scores[doc_id] += w / (k + rank)
    return sorted(scores.items(), key=lambda x: (-x[1], x[0]))

vector = ["A", "B", "C", "D", "E"]   # ベクトル検索の上位5件
bm25   = ["F", "G", "H", "D", "A"]   # BM25の上位5件

for k in (60, 1):
    print(f"k={k}: " + ", ".join(f"{d}={s:.4f}" for d, s in rrf([vector, bm25], k=k)))
print("window=3: " + ", ".join(f"{d}={s:.4f}" for d, s in rrf([vector, bm25], window=3)))
print("weights=[2,1]: " + ", ".join(f"{d}={s:.4f}" for d, s in rrf([vector, bm25], weights=[2.0, 1.0])))
```

実装で押さえる点は3つです。`enumerate(..., start=1)` で順位を1始まりにすること、同点のときの並びを文書IDなどで固定すること（同点文書の並びを入力リストの処理順に依存させないためです）、文書IDを検索器間で揃えることです。LangChain の `EnsembleRetriever` はIDを指定しないと本文テキストで同一文書を判定するため、チャンクの切り方が検索器ごとに違うと同じ文書が別物として数えられます。

## RRFとCombSUM・CombMNZ・スコア正規化の精度比較

RRFは順位を統合する手法の1つで、精度で常に勝つわけではありません。古典的な融合法との計算の違い、OpenSearch の公式ベンチマーク、公開データセットでの実測を順に見ます。

### CombSUM・CombMNZ・Bordaとの計算の違い

CombSUM と CombMNZ は、Virginia Tech の Fox と Shaw が TREC-2 で報告したスコア統合の方式です（[TREC-2 の論文](https://trec.nist.gov/pubs/trec2/papers/txt/23.txt)）。CombSUM は各検索器の類似度の合計、CombMNZ はその合計に「0でない類似度を返した検索器の数」を掛けた値で、複数の検索器に出た文書をさらに押し上げます。どちらもスコアそのものを足すので、BM25とベクトル検索を混ぜるときは min-max などで先に尺度を揃える必要があります。Borda は順位を点数に換えて足す投票方式で、RRFと同じく順位しか使いません。

| 手法      | 使う値 | 事前の正規化 | 文書の点数          |
| ------- | --- | ------ | -------------- |
| RRF     | 順位  | 不要     | 1/(k+順位) の合計   |
| Borda   | 順位  | 不要     | 順位に応じた点数の合計    |
| CombSUM | スコア | 必要     | スコアの合計         |
| CombMNZ | スコア | 必要     | 合計×非ゼロスコアの検索器数 |
| 重み付き和   | スコア | 必要     | 重み×スコアの合計      |

Python の [ranx](https://amenra.github.io/ranx/fusion/)（0.3.21）は、これらを含む20種以上の融合法を `fuse()` の `method` 引数1つで切り替えられます。融合法を比べるときは、自前で書くよりこちらのほうが計算の取り違えが起きません。

### OpenSearchの公式ベンチマークでRRFが平均3.86%低かった結果

OpenSearch は 2.19 でRRFを追加した際、[公式ブログ（2025年2月12日）](https://opensearch.org/blog/introducing-reciprocal-rank-fusion-hybrid-search/)にベンチマークを載せています。BEIR の6データセットで、スコアを正規化して統合する従来のハイブリッド検索とRRFのハイブリッド検索を nDCG@10 で比べたところ、6つすべてでRRFが下回り、平均で3.86%低くなりました。

| データセット     | スコア正規化 | RRF    | RRFの差   |
| ---------- | ------ | ------ | ------- |
| NFCorpus   | 0.3076 | 0.2977 | \-3.22% |
| ArguAna    | 0.4507 | 0.4476 | \-0.69% |
| FIQA       | 0.2693 | 0.2474 | \-8.13% |
| Trec-Covid | 0.5905 | 0.5877 | \-0.47% |
| SciDocs    | 0.1602 | 0.1525 | \-4.81% |
| Quora      | 0.8452 | 0.7960 | \-5.82% |

同じブログは、検索レイテンシがRRFのほうが p50 で1.62%、p99 で0.78%短かったことも示し、品質への影響が小さく速い選択肢としてRRFを勧めています。読み方としては、RRFは調整なしで使える既定値であって、精度を最優先する用途ではスコア正規化と並べて評価してから決めるべき、ということです。

### SciFactでの実測：k・窓・融合法による精度と再現率の変化

同じ比較を公開データセットで再現しました。BEIR の [SciFact](https://huggingface.co/datasets/BeIR/scifact)（科学論文の要旨5,183件、訓練用クエリ809件、評価用クエリ300件）に対し、BM25（bm25s 0.3.13、英語のストップワード除去とステミング）と、[all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) の埋め込みによるベクトル検索でそれぞれ上位100件を取得し、前章の `rrf()` と同じ計算、および ranx 0.3.21 の融合法で統合して、評価用クエリの nDCG@10 と Recall@100 を測りました（Python 3.11・CPU実行。検索対象はタイトルと要旨を連結した文字列で、ベクトル検索はL2正規化した埋め込みの内積を使用）。自前の RRF（k=60）と ranx の RRF（k=60）は nDCG@10 は小数第4位まで同じ 0.7168 になりました。ただし、この集計値の一致だけでは全文書のスコアや順位の一致までは確認できません。

| 統合方法                | nDCG@10       | Recall@100 |
| ------------------- | ------------- | ---------- |
| BM25 単独             | 0.6863        | 0.9127     |
| ベクトル検索 単独           | 0.6451        | 0.9250     |
| RRF k=1             | 0.7141        | 0.9483     |
| RRF k=10            | 0.7195        | 0.9483     |
| RRF k=60            | 0.7168        | 0.9483     |
| RRF k=100           | 0.7159        | 0.9483     |
| RRF k=60・窓10件       | 0.7142        | 0.8700     |
| RRF k=60・ベクトル側の重み2倍 | 0.7052        | 0.9483     |
| Borda               | 0.7158〜0.7162 | 0.9483     |
| CombSUM（min-max正規化） | 0.7295        | 0.9483     |
| CombMNZ（min-max正規化） | 0.7308        | 0.9483     |

- **統合の効果**：どの統合方法も単独の検索を上回りました。BM25 単独の 0.6863 に対し、RRF k=60 は 0.7168 です。
- **k の影響**：この実測で比較した k=1・10・60・100 の4設定では、nDCG@10 の差は最大0.0054でした。k の調整で取れる改善は小さいと見てよいでしょう。
- **窓の影響**：窓を10件にすると統合結果は最大20件になり、Recall@100 が 0.9483 から 0.8700 に落ちます。後段でリランクする構成では、ここで落ちた正解は二度と拾えません。
- **重みの影響**：このデータでは弱いほうのベクトル検索に重みを寄せると、均等の RRF より0.0116下がりました。重みの向きは単独検索の平均精度だけで決めず、統合後の評価値で選びます。
- **融合法の差**：min-max 正規化したスコアの和（CombSUM）は RRF k=60 を0.0127上回り、OpenSearch のベンチマークと同じ向きの結果になりました。ただし評価用300問の対応ありt検定（p<0.05）では、CombSUM と RRF k=60 の差、RRF k=10 と k=60 の差はいずれも有意ではなく、今回検定した4方式間では、RRF k=60・RRF k=10・CombSUMのそれぞれと BM25 単独との差だけが有意でした。なお Borda は同点の文書の並びが実行ごとに変わり、2回の実行で0.7158と0.7162になりました。

k を評価データで決めるときは、訓練用のクエリで探索し、別の評価用クエリで確かめます。ranx では次のように書けます（`qrels_train` 等は ranx の `Qrels` と `Run`）。

```
from ranx import fuse, optimize_fusion, evaluate

best = optimize_fusion(qrels=qrels_train, runs=[bm25_train, dense_train],
                       norm=None, method="rrf", metric="ndcg@10")
fused = fuse(runs=[bm25_test, dense_test], norm=None, method="rrf", params=best)
print(best, evaluate(qrels_test, fused, "ndcg@10"))
# {'k': 10} 0.7195...
```

ranx の `optimize_fusion` は RRF の k を既定で10から100まで10刻みで探すため、このデータでは下限の k=10 が選ばれました。探索範囲を広げたいときは `min_k`・`max_k`・`step` で指定します。

この結果から言えるのは、RRFを「スコア正規化なしで比較を始められる」基準方式として採用し、精度を詰める段階では k より先に、窓の大きさ、重みの向き、スコア正規化との比較を評価セットで試すべきだということです。1つのデータセットと1つの埋め込みモデルでの結果なので、日本語の文書や別のモデルでは差の大きさが変わります。自社データでの確認は省けません。

## 検索エンジン・ベクトルDB別のRRF設定

RRFを自前で書かなくても、主要な検索基盤は組み込みで持っています。ただし k の既定値や変更の可否は揃っていません。2026年10月時点の公式ドキュメント（[Elasticsearch](https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rrf-retriever)・[OpenSearch](https://docs.opensearch.org/latest/search-plugins/search-pipelines/score-ranker-processor/)・[Azure AI Search](https://learn.microsoft.com/ja-jp/azure/search/hybrid-search-ranking)・[Milvus](https://milvus.io/docs/rrf-ranker.md)・[Qdrant](https://qdrant.tech/documentation/search/hybrid-queries/)・[Weaviate](https://docs.weaviate.io/weaviate/api/graphql/search-operators)）で確認した内容です。

| 製品               | kのパラメータ                  | 既定値           | 重み付け                       |
| ---------------- | ------------------------ | ------------- | -------------------------- |
| Elasticsearch    | rank\_constant（1以上）      | 60            | 子retrieverごとのweight（既定1.0） |
| OpenSearch 2.19〜 | rank\_constant（1〜10,000） | 60            | 3.1以降：0.0〜1.0・合計1.0        |
| Azure AI Search  | 公開パラメータなし                | 公式説明は60を例示    | ベクトルクエリのweight（既定1.0）      |
| Milvus           | k（0超〜16,384未満）           | 60            | 3.0.1でANNリクエストごとの重み指定を追加   |
| Qdrant 1.16〜     | k（順位は0始まり）               | 2             | weights（1.17〜）             |
| Weaviate         | fusionType=rankedFusion  | 1.24以降の既定は別方式 | alpha                      |
| LangChain        | c                        | 60            | weights（既定は均等）             |

### Elasticsearchのrrf retriever

Elasticsearch では 8.14 で retriever が導入され、8.16 で GA になりました。キーワード検索とkNN検索を `rrf` retriever の下に並べます。以下はKibana Dev Tools向けの検索例で、docsインデックスに検索対象のbodyと3次元のbody\_vectorを登録済みであることを前提とします。実際には、登録文書と同じ埋め込みモデルで生成したクエリベクトルを指定してください。

```
GET /docs/_search
{
  "retriever": {
    "rrf": {
      "retrievers": [
        { "standard": { "query": { "match": { "body": "逆順位融合" } } } },
        { "knn": { "field": "body_vector", "query_vector": [0.12, 0.45, 0.33], "k": 50, "num_candidates": 100 } }
      ],
      "rank_constant": 60,
      "rank_window_size": 50
    }
  },
  "size": 10
}
```

注意したいのは `rank_window_size` の既定値が10である点です。各子retrieverの上位10件しか統合しないので、前章の window=3 の例と同じく、両方で11位以下の文書は結果に入りません。`size` より大きい値（上の例では50）を明示するのが無難です。また、Elastic の[サブスクリプション表（2026年5月5日版）](https://www.elastic.co/pdf/subscriptions-2026-05-05.pdf)では、セルフマネージド版のRRFはEnterprise階層の機能として掲載されています。無償版で試して動かない場合はライセンスを確認してください。ベクトル側の設計は「[Elasticsearchのベクトル検索｜dense\_vectorの設計とkNN実装](/tech/details/17035/)」で扱っています。

### Qdrantの既定値k=2と0始まりの順位

Qdrant は v1.10.0 でサーバー側の `Fusion.RRF` を導入し、v1.16.0 から k を指定できるようになりました。ほかの製品と違い、順位を0から数え、既定の k は2です。1位の寄与は 1/2 で、1始まりに直すと k=1 相当になります。[公式のチューニングガイド](https://qdrant.tech/documentation/search-tuning/how-to-tune-hybrid-search/)は「原論文の60はQdrantの式では k=61 に当たる」と書き、既定の k=2 では1位が10位の5.5倍の重みを持つ（k=61 では1.15倍）と説明しています。他製品から移行したときに結果の並びが変わるのは、この差が原因のことがあります。重み付きRRFにも違いがあり、Qdrantは分母内の順位に重みを適用するため、本記事のPython実装のように寄与へ重みを掛ける方式とは同じ設定値でも結果が異なります。同ガイドが示す調整の順番は、統合が単独の検索より良いかを確かめる、RRFとDBSFを比べる、RRFが勝ったら1クエリあたりの正解文書数に合わせてkを選ぶ、最後に重みの組を試す、の4段階で、いずれも正解付きのクエリ集合で評価します。

### WeaviateとAzure AI Searchの扱い

Weaviate のハイブリッド検索は、v1.23 までは `rankedFusion`（RRF系）が既定でしたが、v1.24 から `relativeScoreFusion`（スコアを正規化して足す方式）が既定に変わりました。RRFを使いたいなら `fusionType` を明示します。Azure AI Search はハイブリッドクエリの結果を自動でRRF統合し、公式説明は k を「60のような小さい値」と書いていますが、k を指定する入力項目は公開されていません。調整対象には、ベクトルクエリの weight、ベクトル側の取得件数 k（RRF定数とは別）、テキスト側の取得件数 `maxTextRecallSize`（既定1,000）です。製品ごとの概要は「[Weaviateとは？ハイブリッド検索・マルチテナンシー・料金と選定基準](/tech/details/15927/)」「[Milvusとは？v3.0の外部コレクション・インデックス選定・デプロイ形態](/tech/details/15929/)」を参照してください。

## RAG-FusionでのRRFの役割

RAG-Fusion は、Adrian Raudaschl が GitHub の Raudaschl/rag-fusion で公開した構成です。1つの質問からLLMで複数の検索クエリを作り、クエリごとの検索結果をRRFで統合してから、その上位文書で回答を生成します。通常のRAG（1クエリで検索して生成）と違うのは、前段のクエリ生成と後段のRRF統合の2段が加わる点です。RAG全体の検索工程は「[リトリーバルとは？RAGの検索工程の仕組みと実装・精度改善の判断基準](/tech/details/16055/)」で整理しています。

### RAG-Fusionの処理の流れ

下のコードは、LLMが生成した言い換えクエリを固定値に置き換えて、検索とRRF統合の部分だけを動かせるようにしたものです。検索関数は語の重なりで並べる簡易版なので、本番ではBM25やベクトル検索に差し替えます。

```
from collections import defaultdict

def rrf(rankings, k=60):
    scores = defaultdict(float)
    for docs in rankings:
        for rank, doc_id in enumerate(docs, start=1):
            scores[doc_id] += 1 / (k + rank)
    return sorted(scores, key=lambda d: (-scores[d], d))

corpus = {
    "d1": "RRFは順位の逆数を合計して検索結果を統合する",
    "d2": "BM25はキーワードの出現頻度で文書を採点する",
    "d3": "ベクトル検索は埋め込みの類似度で文書を探す",
    "d4": "ハイブリッド検索はBM25とベクトル検索を組み合わせる",
    "d5": "RAG-Fusionは複数のクエリを生成して結果を統合する",
}

def search(query, top_n=3):
    terms = query.split()
    hits = {d: sum(t in text for t in terms) for d, text in corpus.items()}
    ranked = sorted((d for d in hits if hits[d] > 0), key=lambda d: (-hits[d], d))
    return ranked[:top_n]

# LLMが元の質問から生成した言い換えクエリ（ここでは固定値）
queries = ["RRF 統合", "順位 逆数 合計", "ハイブリッド検索 BM25 ベクトル検索"]
results = [search(q) for q in queries]
for q, r in zip(queries, results):
    print(q, "->", r)
print("RRF:", rrf(results))
```

```
RRF 統合 -> ['d1', 'd5']
順位 逆数 合計 -> ['d1']
ハイブリッド検索 BM25 ベクトル検索 -> ['d4', 'd2', 'd3']
RRF: ['d1', 'd4', 'd2', 'd5', 'd3']
```

2つのクエリで1位だった d1 が統合後も1位、1つのクエリでしか出なかった d4 以下がそれに続きます。ハイブリッド検索が「検索器を複数にする」のに対し、RAG-Fusion は「クエリを複数にする」手法で、両者は組み合わせられます。検索器側を複数にする構成は「[ハイブリッド検索とは？RAGでBM25とベクトル検索を統合する仕組み・RRF・実装コード](/tech/details/8014/)」で扱っています。

### 評価論文で分かった効果とコスト

RAG-Fusion を製品QAで評価した論文に、Infineon の Zackary Rackauckas による [arXiv:2402.03367](https://arxiv.org/abs/2402.03367)（2024年1月）があります。回答を正確さ・関連性・網羅性の観点で人手評価し、生成クエリが元の質問を複数の視点から補うことで正確で網羅的な回答が得られた一方、生成クエリと元の質問の関連が弱いと回答が脱線した、と報告しています。同じ質問を10回投げた計測では、平均応答時間が RAG-Fusion 34.62秒、通常のRAG 19.52秒で、1.77倍かかりました。

この計測は特定の構成と同一質問の10回実行に限られるため、応答速度が問われるチャットでは、自社の質問セットで遅延と回答品質を比較してからRAG-Fusionの採否を決めるべきです。まずは1クエリで取りこぼしが多い検索（製品カタログのQAなど）を導入候補とし、生成クエリの数と中身をログで確認できるようにしておきます。効果を測るには「[RAGASとは？RAG評価フレームワークの指標・0.4系の実装手順・導入判断](/tech/details/16042/)」のような評価指標で、通常のRAGと並べて比べます。検索方式の選定から評価セットの作成までを含めて社外に任せたい場合は、[RAG構築支援](/service/ai/rag/)で相談を受け付けています。

## RRFが効かない場面と代わりの選択肢

RRFは異なる尺度の検索結果を正規化せず統合できます。ただし、次の条件では検索品質を確認し、重み・候補件数・融合法・後段のリランクを見直します。

- **片方の検索器が明らかに弱い**：RRFはどの検索器の順位も同じ重さで扱うので、弱い側の上位ノイズが結果に混ざります。日本語でBM25の形態素解析が合っていないときが典型です。重みを下げるか、弱い側を直してから統合します。BM25側の設定は「[BM25とは？読み方・計算式とk1・bの意味、Python実装の落とし穴](/tech/details/4388/)」で扱っています。
- **統合する窓が小さい**：Elasticsearch の既定10件のように窓が小さいと、両方で中位の文書が統合前に切り捨てられます。
- **スコアの差に意味がある**：1位だけが飛び抜けて類似しているような場合も、RRFでは2位との差が 1/61 と 1/62 程度にならされます。Qdrant の公式ガイドは、スコアを分布で正規化して足す DBSF と既定のRRFを5つのデータセットで比べ、3つでDBSFが有意に上回ったと報告しています。
- **最終的な並びの精度が要る**：RRFはあくまで候補の統合です。上位の並びまで詰めたいなら、統合後の数十件をクロスエンコーダで並べ直します。詳しくは「[リランクモデルとは？ベクトル検索と組み合わせる種類・仕組み・選び方](/tech/details/4413/)」を参照してください。

判断の順番としては、まず採用製品の既定のRRF設定を基準にし、候補件数を最終返却件数より多く取って統合し、評価セットで単独の検索器より良くなっているかを確かめます。良くなっていなければ、kより先に重みと窓を見直し、それでも駄目ならスコア系の融合かリランクを試します。

## よくある質問

### RRFとはどういう意味ですか？

Reciprocal Rank Fusion の略で、日本語では逆順位融合と訳されます。複数の検索結果を、各文書の順位の逆数 1/(k + 順位) の合計で並べ直して1つにまとめる手法です。

### RRFのスコアはどう計算しますか？

文書ごとに、各リストでの順位 r から 1/(k + r) を求めて足します。k=60 で、ベクトル検索1位・BM25で5位の文書なら 1/61 + 1/65 ≒ 0.0318 です。リストに出てこない場合、そのリストからの加算は0です。

### RRFのkはなぜ60なのですか？

2009年の原論文が予備実験で決め、その後の検証でも変えなかった値だからです。論文は60をほぼ最適としつつ、選択は決定的でないと書いています。Qdrant のように既定を2にしている製品もあります。

### RRFとRAG-Fusionの違いは何ですか？

RRFは順位を統合する計算方法で、RAG-Fusion はそれを使うRAGの構成です。RAG-Fusion はLLMで複数のクエリを作り、その検索結果の統合にRRFを使います。

### RRFはLangChainで使えますか？

使えます。`langchain_classic.retrievers` の `EnsembleRetriever` が重み付きRRFを実装しており、定数 c の既定値は60、重みは指定しなければ均等です。LangChainでRAGを組む全体の手順は「[LangChainでRAGを構築する手順｜v1.3系のコード例とlangchain-classic移行](/tech/details/2332/)」にまとめています。

## 関連記事

- [ハイブリッド検索とは？RAGでBM25とベクトル検索を統合する仕組み・RRF・実装コード](/tech/details/8014/)
- [リランクモデルとは？ベクトル検索と組み合わせる種類・仕組み・選び方【2026年版】](/tech/details/4413/)
- [BM25とは？読み方・計算式とk1・bの意味、Python実装（rank\_bm25・bm25s）の落とし穴](/tech/details/4388/)
- [リトリーバルとは？RAGの検索工程の仕組みと実装・精度改善の判断基準【2026年版】](/tech/details/16055/)
- [LangChainでRAGを構築する手順｜v1.3系のコード例とlangchain-classic移行](/tech/details/2332/)

---

出典: [RRF（Reciprocal Rank Fusion）とは？計算式とk=60の意味・製品別設定・RAG-Fusionでの使い方](<https://www.issoh.co.jp/tech/details/8015/>)（株式会社一創）
