對應原題(Common ‧ RAG and Retrieval)

  • How do you choose between a sparse retriever (BM25) and a dense retriever? When do you need both?
    — Asked at: Microsoft, Perplexity, Glean

🎯 什麼情境該想到我

當你「向量搜尋搜不到明明存在的東西」的時候——查詢是 ERR_CONN_4012 或 getUserById,向量搜尋回來一堆「語意相近」但完全不對的結果。

⚙️ 怎麼用

一、稀疏檢索:BM25

本質:倒排索引 + 一個很好調的詞頻加權公式。向量是「詞表大小」維、絕大多數是 0(所以叫稀疏)。

三個組件各自在做什麼(能講清楚這三件事就答完了):

組件作用參數
IDF罕見詞權重高。「的」幾乎不加分,ERR_4012 加很多分—
詞頻飽和出現 20 次不該比出現 10 次加倍得分——分母裡有 ,所以會飽和(越大越晚飽和)
長度正規化長文件天然含更多詞,要扣分( 不正規化、 完全正規化)

「為什麼 BM25 比 TF-IDF 好」的一句話:TF 是線性的,BM25 讓它飽和,並加入了長度正規化。這兩件事讓它在真實語料上穩定勝出——而且它是 1994 年的東西,至今仍是強到不合理的 baseline。

★ BM25 至今沒死的四個理由:

  1. 精確詞匹配 —— 產品編號、錯誤碼、API 名稱、人名、版本號、罕見術語
  2. 零訓練成本,新文件寫進索引立刻可搜(不用 embed)
  3. 可解釋 —— 能明確指出「因為命中了這幾個詞」
  4. 對分布外查詢穩健 —— embedding 模型沒見過的領域,dense 會崩,BM25 不會

二、密集檢索:Bi-Encoder

本質:查詢與文件各自獨立編碼成一個稠密向量(512–4096 維),比較內積或餘弦。

查詢 ──▶ Encoder ──▶ q ∈ ℝᵈ  ─┐
                               ├──▶ sim(q, d) = q·d
文件 ──▶ Encoder ──▶ d ∈ ℝᵈ  ─┘   (文件端可離線算好)

「獨立編碼」是關鍵:文件向量可以離線算完存進索引,查詢時只要算一次 query 的 embedding + ANN 搜尋。這是它能在上億文件規模下即時回應的原因,也是它和 cross-encoder 的本質差別(見 Reranker 重排序)。

擅長:同義(「離職」↔「辭職」)、改寫、跨語言、概念相近、口語化提問。
不擅長:沒見過的專有名詞。getUserById 對它可能只是「某個駝峰命名的東西」,和 getOrderById 幾乎一樣近。

三、★ 對照表

面向BM25(稀疏)Dense
精確詞/代號/縮寫✅ 強❌ 弱
同義與改寫❌ 完全不行✅ 強
罕見/新造詞✅ IDF 會給高權重❌ 落在無意義的位置
跨語言❌✅(多語模型)
長尾查詢✅ 穩健⚠️ 看訓練分布
新增文件立即要 embed(改模型要全部重算)
可解釋性✅❌ 黑盒
索引體積小大
冷啟動✅ 零成本要選模型/可能要微調

★ 它們的失敗模式是互補的,不是重疊的。 這就是混合檢索存在的全部理由。

四、什麼時候需要兩者(題目的重點)

判準是查詢分布:

查詢分布建議
幾乎都是自然語言概念問題(FAQ、客服)Dense 為主即可
幾乎都是精確識別碼(日誌檢索、程式碼搜尋)BM25 為主即可
混合(企業搜尋、技術文件、電商、法律)★ 必須 hybrid

企業內部搜尋(Glean、Microsoft 的場景)幾乎一定是混合的——同一個搜尋框裡會出現「上季 OKR 進度」和 PROJ-4821。
面試答法:「我會先把真實查詢日誌抽樣分類,量出精確匹配型查詢的比例。這個比例決定要不要 hybrid,以及融合的權重。 沒看過查詢分布就選檢索器是拍腦袋。」

五、★ 怎麼融合:RRF 而不是加權和

天真作法(加權分數和)的問題:

  • BM25 分數無上界且隨語料變動;cosine 落在 —— 尺度完全不可比
  • 正規化(min-max / z-score)依賴當次結果集的分布,同一份文件在不同查詢下的分數會漂
  • 權重 要調,而且對查詢類型敏感(一組權重不可能同時對兩種查詢都好)

Reciprocal Rank Fusion(Cormack et al., 2009)—— 只用排名,不用分數:

def rrf(rankings: list[list[str]], k: int = 60, top_n: int = 20):
    """rankings: 多個檢索器各自的有序 doc_id 清單"""
    scores = {}
    for ranking in rankings:
        for rank, doc_id in enumerate(ranking, start=1):
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)[:top_n]

為什麼好用:

  • 免調參( 是原論文的經驗值,幾乎不用動)
  • 尺度無關 —— 可以塞任意多個檢索器進去(BM25 + dense + SPLADE + 知識圖譜…)
  • 對單一檢索器的崩潰有韌性 —— 一個檢索器排爛了,其他的還能撐
  • 的作用是壓低第一名的絕對優勢,讓「兩個檢索器都排第 3」勝過「一個排第 1、一個沒排到」

實務預設:BM25 + dense,用 RRF 合併取 top-50,再丟 reranker 取 top-5。 這是目前 RAG 的標準三段式。

六、第三條路:Learned Sparse(SPLADE)

用 MLM 模型為每個文件產生一個稀疏但經過語意擴展的詞權重向量——文件裡沒出現的相關詞也會被賦予權重。

兼有兩者優點(精確匹配 + 語意擴展),且能直接跑在既有的倒排索引基礎設施上。代價是索引體積膨脹、編碼較慢。

⚠️ 注意 / 什麼時候不適用

  • ★ 別忘了 query / passage 前綴。很多 embedding 模型(E5、BGE、GTE 系列)是非對稱訓練的,查詢端要加 "query: "、文件端要加 "passage: "。漏了這件事,效果會無聲地掉一大截,而且測起來「好像有在動」。
  • 相似度函式要和訓練時一致。用 cosine 去搜一個用內積訓練的模型(或反過來、或忘了正規化),結果會微妙地錯。
  • 換 embedding 模型 = 重算全部向量。這是不可逆的營運成本,選型時就要想清楚。
  • BM25 對中文要先分詞。用錯分詞器(或沒分詞)會讓它退化成字元匹配。
  • hybrid 讓延遲變成兩條路的較慢者,且要維護兩套索引。小規模時未必划算。
  • RRF 會丟失分數資訊。需要用絕對分數做閾值過濾(「相似度低於 0.7 就說不知道」)時,RRF 幫不上忙——那要另外保留原始分數。
  • 檢索不好不一定是檢索器的錯。先確認切塊與解析(見 切塊與文件解析)。

🧪 我實際套用的紀錄

  • 2026-09-20:從面試題庫展開。待辦:做 RAG 前先抽樣查詢日誌,量精確匹配型查詢的比例。

🔗 相關