昨天透過 Hybrid Search 補足了關鍵字匹配的能力,今天要介紹另一個常見且效果顯著的優化技巧:Rerank(重新排序)。
向量搜尋(Bi-Encoder 架構)的做法是:把「問題」和「文件」分別獨立轉成向量,再比較兩個向量的距離。這種做法速度快,適合從大量資料中快速篩選候選,但因為問題和文件是「各自獨立」編碼的,彼此之間沒有互動,精準度會有一定上限。
Rerank 階段使用的是 Cross-Encoder 架構:把「問題」和「候選文件」一起輸入模型,讓模型同時看到兩者、直接判斷這篇文件跟這個問題的相關程度。
Bi-Encoder(向量搜尋):
問題 → 向量A ┐
├─→ 計算距離
文件 → 向量B ┘
Cross-Encoder(Rerank):
[問題 + 文件] → 模型 → 直接輸出相關分數
因為 Cross-Encoder 能看到問題與文件的完整互動,精準度通常明顯優於單純的向量距離;但代價是計算成本高、無法對全部文件都做(如果資料庫有百萬筆,不可能每筆都跑一次 Cross-Encoder)。
因此實務上常見的作法是「兩階段檢索」:
第一階段:向量搜尋(Bi-Encoder)
從百萬筆資料中,快速篩出 Top-20~50 候選
│
▼
第二階段:Rerank(Cross-Encoder)
只對這 20~50 筆候選做精細評分,重新排序
│
▼
取出最終 Top-K(如 Top-5)送給 LLM
第一階段負責「快速縮小範圍」,第二階段負責「精準排序」,兼顧速度與品質。
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
# 若知識庫為中文,建議選用支援中文的 Rerank 模型
def rerank_results(question, candidates, top_k=5):
"""
candidates: [{"content": ..., "metadata": ...}, ...](第一階段的候選結果)
"""
pairs = [[question, c["content"]] for c in candidates]
scores = reranker.predict(pairs)
for c, score in zip(candidates, scores):
c["rerank_score"] = score
sorted_candidates = sorted(candidates, key=lambda x: x["rerank_score"], reverse=True)
return sorted_candidates[:top_k]
| 選項 | 型態 | 特色 |
|---|---|---|
| Cohere Rerank | API 服務 | 免部署、支援多語言 |
| BGE-Reranker | 開源模型 | 可本地部署、中文表現佳 |
| ms-marco 系列 | 開源模型 | 英文為主,經典基準模型 |
一般來說,加入 Rerank 後最明顯的改善是:**原本排在第 5-10 名、語意稍微沾邊但實際上不太相關的內容會被排到後面,而真正切題的內容會被拉到前面。**如果你的 RAG 系統常常遇到「明明資料庫裡有答案,但檢索結果沒排進前幾名」的狀況,Rerank 通常是很值得優先嘗試的優化手段。
Rerank 透過犧牲一些計算資源,換取檢索排序的精準度提升,是「兩階段檢索」架構中不可或缺的一環。明天我們要介紹另一種提升檢索效果的角度:從「問題」本身下手,也就是 Query 改寫與擴展。