Home 論文解説: WAS — RRFを超えるハイブリッド検索スコアリング手法とインデックス最適化
投稿
キャンセル

📄 論文解説: WAS — RRFを超えるハイブリッド検索スコアリング手法とインデックス最適化

本記事は Enhancing Vector Search with Weighted Aggregate Scores and Inverted Indexing の解説記事です。

論文概要(Abstract)

RAGパイプラインにおけるハイブリッド検索(BM25キーワード検索 + Dense ベクトル検索)は、単一手法より検索精度が高いことが知られている。その融合手法として広く使われるRRF(Reciprocal Rank Fusion)は、各検索結果のランク(順位)のみを用い、スコアの大きさを無視する。本論文で提案されるWAS(Weighted Aggregate Scores)は、BM25スコアとDenseスコアを正規化した上で加重合算し、RRF比で+1.6〜2.7 nDCG@10の改善を達成する手法である。さらに、スパースインデックスで候補を絞ってからDense計算を行うWAS-Indexにより、標準的なHNSW検索比で2.2倍のスループットを実現している。

この記事は Zenn記事: ユースケース別ベクトルDB選定2026 の深掘りです。

情報源

背景と動機(Background & Motivation)

ハイブリッド検索は、RAGにおいて検索精度を向上させる標準的な手法となっている。Zenn記事で紹介されているように、Weaviateはネイティブでハイブリッド検索をサポートし、pgvectorではSQL結合で実現できる。

しかし融合手法の選択肢は限られていた。最も広く使われるRRFは以下の式で計算される。

\[\text{RRF}(d) = \sum_{r \in R} \frac{1}{k + \text{rank}_r(d)}\]

ここで $R$ は検索手法の集合、$\text{rank}_r(d)$ は手法 $r$ での文書 $d$ のランク、$k$ は定数(通常60)である。

RRFの問題点は、スコアの大きさを完全に無視する点にある。例えば、BM25で1位のスコア50.0と2位のスコア49.8の差は無視されるが、Dense検索で1位のスコア0.95と2位のスコア0.40の差(大きな意味的ギャップ)も無視される。この情報損失が精度の上限を制約している。

主要な貢献(Key Contributions)

  • 貢献1: スコアの大きさを活用するWeighted Aggregate Scores(WAS)を提案し、BEIR 18データセット平均でRRF比+2.7 nDCG@10を達成
  • 貢献2: スパースインデックスで候補を絞るWAS-Indexにより、標準HNSW比2.2倍のスループットを実現
  • 貢献3: α(BM25/Denseの重み)の調整指針を提供し、α=0.5がドメイン非依存の強いデフォルト値であることを実証

技術的詳細(Technical Details)

WASスコアリング

WASは以下の3ステップで計算される。

ステップ1: スコア正規化

各検索手法のスコアを [0, 1] に正規化する。

\[\hat{s}_r(d) = \frac{s_r(d) - \min_{d' \in D_r} s_r(d')}{\max_{d' \in D_r} s_r(d') - \min_{d' \in D_r} s_r(d')}\]

ここで $s_r(d)$ は手法 $r$ での文書 $d$ のスコア、$D_r$ は手法 $r$ の結果集合である。

ステップ2: 加重合算

\[\text{WAS}(d) = \alpha \cdot \hat{s}_{\text{dense}}(d) + (1 - \alpha) \cdot \hat{s}_{\text{BM25}}(d)\]

ここで $\alpha \in [0, 1]$ はDense検索の重みである。

ステップ3: ソートと上位 $k$ 件返却

\[\text{results} = \text{top-}k\left(\{d : \text{WAS}(d)\}, k\right)\]

RRFとWASの比較

特性RRFWAS
入力ランク(順位)スコア(数値)
パラメータ$k$(通常60、固定)$\alpha$(0-1、チューニング可能)
情報利用順位のみ順位 + スコアの大きさ
正規化不要必要(min-maxまたは分位点)

WAS-Index: スパース候補に対するDense計算

標準的なハイブリッド検索では、BM25とDenseそれぞれで独立にtop-$k$を取得し、結果を融合する。これに対しWAS-Indexは、まずBM25(スパースインデックス)で広めの候補集合(top-$k’$, $k’ = 5k$〜$10k$)を取得し、その候補に対してのみDense距離計算を行う。

\[D_{\text{candidates}} = \text{BM25-top-}k'(q) \quad (k' = 5k \sim 10k)\] \[\text{WAS-Index}(d) = \alpha \cdot \text{cosine}(q_{\text{dense}}, d_{\text{dense}}) + (1-\alpha) \cdot \hat{s}_{\text{BM25}}(d) \quad \forall d \in D_{\text{candidates}}\]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def was_index_search(
    query: str,
    query_embedding: list[float],
    bm25_index,  # BM25/スパースインデックス
    dense_vectors: dict[str, list[float]],  # doc_id -> embedding
    alpha: float = 0.5,
    k: int = 10,
    k_prime_factor: int = 5,
) -> list[tuple[str, float]]:
    """WAS-Index: スパース候補に対するDense再計算

    Args:
        query: テキストクエリ
        query_embedding: クエリの密ベクトル(768次元等)
        bm25_index: BM25インデックス(pyserini, rank-bm25等)
        dense_vectors: 文書ID -> 密ベクトルのマッピング
        alpha: Dense検索の重み(0-1)
        k: 返却件数
        k_prime_factor: BM25候補の倍率(k' = k * k_prime_factor)

    Returns:
        (doc_id, was_score) のリスト(スコア降順、上位k件)
    """
    import numpy as np

    k_prime = k * k_prime_factor

    # ステップ1: BM25で広めの候補を取得
    bm25_results = bm25_index.search(query, top_k=k_prime)
    bm25_scores = {doc_id: score for doc_id, score in bm25_results}

    # ステップ2: BM25スコアのmin-max正規化
    scores_list = list(bm25_scores.values())
    bm25_min, bm25_max = min(scores_list), max(scores_list)
    bm25_range = bm25_max - bm25_min if bm25_max > bm25_min else 1.0

    # ステップ3: 候補のみDense距離計算(HNSW探索を回避)
    q_vec = np.array(query_embedding)
    was_scores: list[tuple[str, float]] = []

    for doc_id, bm25_score in bm25_scores.items():
        if doc_id not in dense_vectors:
            continue
        d_vec = np.array(dense_vectors[doc_id])
        # コサイン類似度
        dense_score = float(np.dot(q_vec, d_vec) / (np.linalg.norm(q_vec) * np.linalg.norm(d_vec)))
        # 正規化されたBM25スコア
        norm_bm25 = (bm25_score - bm25_min) / bm25_range
        # WASスコア
        was = alpha * dense_score + (1 - alpha) * norm_bm25
        was_scores.append((doc_id, was))

    was_scores.sort(key=lambda x: x[1], reverse=True)
    return was_scores[:k]

このアプローチの利点は、HNSWインデックスの探索($O(\log n)$ だが定数係数が大きい)を回避し、BM25候補のみに対する線形スキャン($O(k’)$ で $k’$ は小さい)で済む点である。著者らの実験では、1M文書・768次元の条件で標準HNSW 2,200 QPS に対し WAS-Index 4,800 QPS(2.2倍)を達成している。

αパラメータの調整

著者らは18のBEIRデータセットでαの感度分析を実施し、以下の傾向を報告している。

α範囲特性適するドメイン
0.0〜0.3BM25重視固有名詞・専門用語が多いドメイン(法律、医療)
0.3〜0.7バランス型一般的なRAG(α=0.5がデフォルト推奨
0.7〜1.0Dense重視意味的な類似性が重要なドメイン(FAQ、パラフレーズ検出)

Zenn記事で紹介されているWeaviateのalphaパラメータと同等の概念であり、WASの知見はWeaviateのハイブリッド検索パラメータ調整にも直接適用できる。

実験結果(Results)

BEIRベンチマーク

著者らが報告したBEIR 18データセットの平均nDCG@10は以下の通り。

手法BEIR平均 nDCG@10BM25との差
BM250.428
Dense (E5-large)0.491+0.063
RRF (BM25 + E5)0.521+0.093
WAS (α=0.5)0.537+0.109
WAS (α=tuned)0.548+0.120

※ 論文Table 1より。

ドメイン別の詳細(nDCG@10、著者らの報告値):

ドメインBM25DenseRRFWAS(α=0.5)改善幅
FiQA(金融)0.2540.4130.4410.463+0.022
TREC-COVID0.6560.7020.7310.749+0.018
NFCorpus(医療)0.3250.3710.3880.401+0.013

MS MARCO

手法MRR@10
BM250.187
Dense (E5-large)0.347
RRF0.368
WAS (α=0.6)0.381

※ 論文Table 2より。

スループット

1M文書コーパス、768次元の埋め込みでの著者らの計測結果。

手法QPSRecall
HNSW ANN(Dense単体)2,20095%+
BM25 + Dense(独立取得→RRF)1,800
WAS-Index4,80095%+のHybrid recall

WAS-Indexが最も高スループットを達成しているのは、Dense側のHNSW探索をスキップし、BM25候補に対する線形スキャンで代替しているためと著者らは説明している。

実装のポイント(Implementation)

正規化の選択

著者らは2種の正規化を比較している。

  1. Min-Max正規化: バッチ評価(オフライン)向き。結果集合全体のmin/maxで正規化。
  2. 分位点正規化(Quantile Normalization): オンライン提供向き。事前に計算したスコア分布の分位点で正規化。コールドスタート時のキャリブレーションが必要(著者らは1000クエリのサンプルを推奨)。

既存フレームワークでの適用

Zenn記事で紹介されているフレームワークへの適用方法を整理する。

Weaviateの場合: alphaパラメータがWASのαに直接対応する。ただしWeaviateの内部融合はRelative Score Fusion(RSF)であり、WASのmin-max正規化と類似しているが、同一ではない。

pgvectorの場合: Zenn記事で紹介されているRRF(Reciprocal Rank Fusion)をWASに置き換えるには、SQLクエリ内で正規化とスコア計算を行う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- pgvector + tsvector でのWAS実装例
WITH vector_results AS (
    SELECT id, title,
           1 - (embedding <=> %(embedding)s::vector) AS dense_score
    FROM documents
    ORDER BY embedding <=> %(embedding)s::vector
    LIMIT %(k_prime)s
),
bm25_results AS (
    SELECT id, title,
           ts_rank_cd(tsv, plainto_tsquery('japanese', %(query)s)) AS bm25_score
    FROM documents
    WHERE tsv @@ plainto_tsquery('japanese', %(query)s)
    LIMIT %(k_prime)s
),
-- ステップ1: 各スコアを正規化
normalized AS (
    SELECT
        COALESCE(v.id, b.id) AS id,
        COALESCE(v.title, b.title) AS title,
        -- Dense スコア min-max正規化
        (COALESCE(v.dense_score, 0) - MIN(v.dense_score) OVER())
        / NULLIF(MAX(v.dense_score) OVER() - MIN(v.dense_score) OVER(), 0) AS norm_dense,
        -- BM25 スコア min-max正規化
        (COALESCE(b.bm25_score, 0) - MIN(b.bm25_score) OVER())
        / NULLIF(MAX(b.bm25_score) OVER() - MIN(b.bm25_score) OVER(), 0) AS norm_bm25
    FROM vector_results v
    FULL OUTER JOIN bm25_results b ON v.id = b.id
)
-- ステップ2: WASスコア計算
SELECT id, title,
       %(alpha)s * COALESCE(norm_dense, 0) + (1 - %(alpha)s) * COALESCE(norm_bm25, 0) AS was_score
FROM normalized
ORDER BY was_score DESC
LIMIT %(k)s

実運用への応用(Practical Applications)

RAGパイプラインへの統合

WASはRAGの検索品質を直接改善する。BEIR平均で+2.7 nDCG@10の改善は、チャンキング戦略の最適化と同等の効果がある。Zenn記事で「ベクトルDB選択は検索品質全体の5〜10%程度の影響」と指摘されている通り、融合手法の改善はDB選定以上のインパクトを持ちうる。

WAS-Indexのコスト削減効果

WAS-IndexはDense側のHNSWインデックスを不要にする可能性がある。BM25インデックスのみでDense候補を選定できるため、メモリ使用量がHNSWグラフ分(通常100-500MB/1M文書)削減される。

関連研究(Related Work)

  • RRF(Cormack et al., 2009): 最も広く使われるランク融合手法。スコアの大きさを無視する点がWASとの主な差異。Qdrant、Weaviate等のベクトルDBに組み込まれている。
  • BM42(Qdrant, 2024; arXiv:2409.00484): BM25のアテンション重み版。トークンの重み付けにTransformerのattention weightを使用する。WASのBM25側をBM42に置き換えることで追加の改善が期待される。
  • ColBERT(Khattab & Zaharia, 2020): トークンレベルの細粒度マッチングを行う遅延交互作用モデル。WASよりも高精度だが計算コストが数倍高い。

まとめと今後の展望

WASは、ハイブリッド検索の融合手法としてRRFの課題(スコア情報の損失)を直接的に解決し、BEIR 18データセットで一貫した改善を達成している。α=0.5というシンプルなデフォルト値で強い性能を示す点も実用的である。

制約として、αがコーパス依存であること、正規化のための事前統計計算が必要なこと、recall保証の理論的根拠がないことが挙げられる。今後はBM42との組み合わせや、クエリ種別に応じたαの動的調整が研究方向として考えられる。

参考文献

この投稿は CC BY 4.0 でライセンスされています。

論文解説: SIMBA — バッチレベルビームサーチによるDSPyプログラムの推論時スケーリング

論文解説: DSPy — 宣言的LM呼び出しを自己改善パイプラインにコンパイル