wen aidev
Published on

RAG 進階檢索(二):Rerank、MMR 與完整評分管線

上一篇中,我們用 Multi-Query 和各種 Query 改寫策略,成功地把更多相關文件從知識庫裡撈了出來。

但撈回來之後,你可能會發現一個尷尬的狀況:

你問「如何在 Kubernetes 上部署微服務?」,系統撈回了 20 篇文件。你一看排名:

  • Top 1:「K8s Deployment YAML 範例」
  • Top 2:「K8s Deployment 最佳實踐」
  • Top 3:「K8s Deployment 入門教學」
  • Top 4:「K8s Deployment 常見錯誤」
  • Top 5:「K8s Deployment vs StatefulSet」

五篇全部都在講 Deployment。但你的問題其實還涉及「Service Mesh」、「CI/CD Pipeline」、「Helm Chart 管理」等面向——這些文件明明也被撈回來了,卻排在第 12、15、18 名,根本不會被送進 LLM 的 Context。

這就是 RAG 的兩大隱藏殺手:排序不夠精準 + 結果過度冗餘。


Bi-Encoder vs Cross-Encoder:為什麼需要兩階段?

要理解 Rerank(重排序)為什麼重要,我們得先搞清楚一個根本問題:你的向量搜尋(Vector Search)到底是怎麼算「相似度」的?

答案是:用 Bi-Encoder(雙塔模型)

Bi-Encoder:快但粗

Bi-Encoder 的架構很直覺:它有兩個獨立的 Encoder(實務上通常是同一個模型用兩次)。一個負責把 Query 編碼成向量,另一個負責把 Document 編碼成向量。然後用 Cosine Similarity 算兩個向量的距離。

QueryEncoder → 向量 [0.2, -0.4, 0.8, ...]
                                                    → cosine similarity → 0.87
DocEncoder → 向量 [0.1, -0.3, 0.7, ...]

這個架構的最大優勢是:Document 的向量可以預先計算好存在資料庫裡。當使用者發問時,只需要即時計算 Query 的向量,然後跟資料庫裡的十萬個向量做距離比較。這就是為什麼向量搜尋可以在毫秒級完成。

但代價是什麼?Query 和 Document 在編碼的過程中完全沒有交互。Encoder 在處理 Query 的時候,根本不知道 Document 長什麼樣子;反之亦然。這意味著它無法捕捉到 Query 和 Document 之間那些細微的語意關聯。

Cross-Encoder:慢但精

Cross-Encoder 的做法完全不同:它把 Query 和 Document 拼接在一起,丟進同一個 Transformer 模型裡。

輸入:[CLS] Query [SEP] Document [SEP]
            Transformer(全層 Self-Attention)
            相關性分數:0.94

因為 Query 和 Document 的每一個 Token 都在 Self-Attention 層裡互相「看到」對方,所以 Cross-Encoder 能捕捉到極其細微的語意關聯。例如,它能理解「部署微服務」和「Service Mesh 的流量管理」之間的隱含關係——這是 Bi-Encoder 很難做到的。

但代價也很明顯:每一對 (Query, Document) 都要跑一次完整的 Transformer 推理。如果你有十萬篇文件,就要跑十萬次。這在延遲和成本上都是不可接受的。

Bi-Encoder 與 Cross-Encoder 架構對比

圖說:Bi-Encoder 像是看照片選人(快但可能看走眼),Cross-Encoder 像是面對面聊天(慢但判斷精準)。

所以最佳實踐是:兩階段架構

  • L1(Retrieval):用 Bi-Encoder 做粗篩,從十萬筆中快速撈出 Top 50
  • L2(Rerank):用 Cross-Encoder 對這 50 篇做精排,重新排出真正的 Top 5

這就是業界所說的 Retrieve-then-Rerank 架構。L1 追求速度和召回率,L2 追求精準度。


Cross-Encoder Rerank 實戰

開源模型選擇

目前最常用的 Cross-Encoder Rerank 模型:

模型來源特點
ms-marco-MiniLM-L-6-v2Sentence-Transformers輕量、速度快,適合 POC
bge-reranker-v2-m3BAAI (智源)多語言支援好,中文表現優秀
jina-reranker-v2Jina AIAPI 服務,不用自己部署
rerank-v3.5CohereAPI 服務,業界標竿

用 Sentence-Transformers 實作 Rerank

from sentence_transformers import CrossEncoder

# 載入 Cross-Encoder 模型
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

query = "如何在 Kubernetes 上部署微服務?"
documents = [
    "K8s Deployment YAML 範例與最佳實踐...",
    "Service Mesh 在微服務架構中的流量管理...",
    "使用 Helm Chart 管理 K8s 應用部署...",
    # ... 從 L1 撈回來的 Top 50
]

# Cross-Encoder 會對每一對 (query, doc) 計算相關性分數
pairs = [(query, doc) for doc in documents]
scores = reranker.predict(pairs)

# 按分數重新排序
ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
top_5 = ranked[:5]

跟 Azure Semantic Ranker 的關係

如果你有在用 Azure AI Search,你可能會想:「這跟 Semantic Ranker 有什麼不同?」

本質上,Azure 的 Semantic Ranker 就是微軟自家訓練的 Cross-Encoder(或類似架構的深度語言模型)。差別在於:

  • Semantic Ranker:微軟託管、一鍵開啟、不用管模型部署,但只能在 Azure AI Search 裡用
  • 開源 Cross-Encoder:你自己部署、自己選模型,可以用在任何向量資料庫(Chroma、Pinecone、Qdrant...)

如果你的架構不在 Azure 上,或者你想要更細緻的控制(例如用自己的資料 Fine-tune Reranker),那開源 Cross-Encoder 就是你的選擇。


MMR:解決「前五名都在講同一件事」

Rerank 解決了「排序不夠精準」的問題,但還有一個問題沒解決:結果冗餘

回到開頭的例子:即使 Cross-Encoder 重新排序後,那五篇 Deployment 文章可能還是佔據前五名——因為它們跟 Query 的相關性確實都很高。但對 LLM 來說,五篇講同一件事的文件,跟一篇的資訊量差不多。

MMR(Maximal Marginal Relevance,最大邊際相關性) 就是來解決這個問題的。

MMR 的核心思想

MMR 在選擇下一篇文件時,同時考慮兩個因素:

  1. 跟 Query 的相關性(越高越好)
  2. 跟已選文件的差異性(越不同越好)

公式長這樣:

MMR = λ × Sim(Query, Doc) - (1-λ) × max(Sim(Doc, 已選文件))
  • λ 是平衡參數,通常設 0.5~0.7
  • λ = 1 時,完全只看相關性(等於沒有 MMR)
  • λ = 0 時,完全只看多樣性(可能選到不相關的文件)

直覺解釋

想像你在自助餐選菜。如果只看「好不好吃」(相關性),你可能會夾五盤紅燒肉。但如果同時考慮「跟盤子裡已經有的菜不一樣」(多樣性),你就會夾一盤紅燒肉、一盤青菜、一盤魚、一盤湯——營養更均衡。

實作範例

import numpy as np

def mmr_select(query_embedding, doc_embeddings, doc_scores, k=5, lambda_param=0.6):
    """
    query_embedding: Query 的向量
    doc_embeddings: 候選文件的向量列表
    doc_scores: Cross-Encoder 給的相關性分數
    k: 要選幾篇
    lambda_param: 相關性 vs 多樣性的平衡
    """
    selected = []
    candidates = list(range(len(doc_scores)))

    for _ in range(k):
        best_score = -float('inf')
        best_idx = -1

        for idx in candidates:
            # 相關性分數(已由 Cross-Encoder 計算)
            relevance = doc_scores[idx]

            # 跟已選文件的最大相似度
            if selected:
                similarities = [
                    cosine_sim(doc_embeddings[idx], doc_embeddings[s])
                    for s in selected
                ]
                max_sim = max(similarities)
            else:
                max_sim = 0

            # MMR 分數
            mmr = lambda_param * relevance - (1 - lambda_param) * max_sim

            if mmr > best_score:
                best_score = mmr
                best_idx = idx

        selected.append(best_idx)
        candidates.remove(best_idx)

    return selected

λ 參數怎麼調?

λ 值效果適合場景
0.9幾乎只看相關性精確查詢(料號、人名)
0.7偏重相關性,適度多樣一般知識問答
0.5相關性和多樣性各半開放式探索問題
0.3偏重多樣性腦力激盪、創意發想

完整 RAG Scoring Pipeline

把前面所有技術串起來,一條完整的 RAG Scoring Pipeline 長這樣:

完整 RAG Scoring Pipeline 五層架構

圖說:從 L0 到 L4,每一層都在做「減量提質」——資料量越來越少,但品質越來越高。

五層架構一覽

  • L0 - Query 擴展:Multi-Query / HyDE / Step-back,把 1 個查詢變成 3~5 個
  • L1 - Retrieval:Bi-Encoder + Hybrid Search + RRF,從十萬筆中撈出 Top 50
  • L2 - Rerank:Cross-Encoder 逐一精讀,重排出 Top 10
  • L3 - Diversity:MMR 去除冗餘,精選 Top 3~5 篇多樣化文件
  • L4 - Generation:LLM 用精選的 Context 生成最終回答

不是每一層都必須

這五層是「完整版」。在實戰中,你可以根據場景選擇性地啟用:

場景建議啟用的層原因
簡單 FAQ BotL1 + L4問題明確,不需要擴展和重排
企業知識庫L1 + L2 + L4需要精準排序,但文件重複度低
複雜研究助手L0 + L1 + L2 + L3 + L4問題模糊、知識庫龐大、需要多角度
客服對話系統L1 + L2 + L3 + L4需要精準且多樣的回答依據

延遲與成本的現實

每多一層,就多一份延遲和成本。以一次查詢為例:

  • L0:LLM 生成查詢變體 → +0.5~1 秒,+1 次 LLM API 呼叫
  • L1:向量搜尋 → +50~200ms(取決於資料量和索引類型)
  • L2:Cross-Encoder 重排 50 篇 → +0.5~2 秒(取決於模型大小和硬體)
  • L3:MMR 計算 → +10~50ms(純數學運算,很快)
  • L4:LLM 生成回答 → +1~5 秒

所以完整跑一輪大約 3~8 秒。對於即時對話來說可能偏慢,但對於「搜尋後閱讀」的場景(例如企業知識庫)完全可以接受。

實戰加速技巧

L0 的多個查詢變體可以平行檢索(同時發出 35 個搜尋請求),這樣 L0+L1 的總延遲不會是 35 倍,而是跟單次搜尋差不多。L2 的 Cross-Encoder 也可以用 batch inference 加速,把 50 對 (Query, Doc) 一次丟進 GPU。


跟 Azure AI Search 的關係

如果你用的是 Azure AI Search,這條 Pipeline 的對應關係是:

Pipeline 層Azure AI Search 對應通用方案
L0 Query 擴展❌ 需自己實作LangChain / LlamaIndex
L1 Retrieval✅ Hybrid Search + RRF任何向量資料庫
L2 Rerank✅ Semantic RankerCross-Encoder / Cohere
L3 Diversity❌ 需自己實作MMR 演算法
L4 Generation✅ Azure OpenAI任何 LLM

Azure 幫你處理了 L1 和 L2(而且 Semantic Ranker 的效果非常好),但 L0 和 L3 需要你自己在應用層實作。如果你想深入了解 Azure 的方案,可以參考我的 Azure AI Search 系列


小結

這篇我們從 Bi-Encoder 和 Cross-Encoder 的根本差異出發,理解了為什麼需要兩階段的 Retrieve-then-Rerank 架構。然後用 MMR 解決了結果冗餘的問題,最後把所有技術組裝成一條完整的五層 Scoring Pipeline。

兩篇 RAG 進階檢索系列的核心觀念:

  • 第一篇:Query 端優化(Multi-Query、HyDE、Step-back、Sub-question)→ 撈得更多、更全
  • 第二篇:Result 端優化(Cross-Encoder Rerank、MMR)→ 排得更準、更多樣

掌握這些技術後,你的 RAG 系統就不再只是「能用」,而是真正能在企業場景中交出高品質答案的生產級系統。

留言討論