構建RAG系統時,Bi-Encoder、Cross-Encoder、SPLADE、ColBERT這幾個術語幾乎都會在一起出現,表面上看它們都在做文本相似度計算但為什么需要這么多不同的模型?是一個不夠用嗎?
本文將拆解每種模型的工作機制、適用邊界,以及如何在實際系統中組合使用。而核心問題是:高召回和高精準之間的平衡該怎么把握。
![]()
精準率與召回率
先厘清兩個基礎概念。
![]()
TP是真陽性,FP是假陽性,FN是假陰性。
高精準率意味著模型說"是"的時候基本不會錯,假陽性極少但是是可能漏掉一些真正的正樣本。這種策略偏保守,只有高置信度時才做出陽性判斷。典型場景是垃圾郵件檢測:被標記為垃圾郵件的必須真的是垃圾郵件。
高召回率則相反,目標是盡可能捕獲所有正樣本,假陰性降到最低但會混入不少假陽性。這個策略更激進一些,寧可誤報也不漏報。
RAG檢索實際上需要兩者配合:第一階段追求高召回,把可能相關的文檔塊盡量“撈”出來;第二階段做語義重排序和過濾噪聲來提升精準率。所以需要不同模型分工協作速度和準確度也是關鍵考量維度。
檢索系統的核心矛盾在于規模和精度難以兼得:既要在百萬級文檔中快速搜索,又要準確判斷哪些文檔真正相關。單一模型無法同時優化這兩個目標所以就出現了多階段架構。
Bi-Encoder:大規模語義檢索的基礎
Bi-Encoder的思路很直接:用同一個編碼器分別處理查詢和文檔,各自生成一個向量然后計算余弦相似度。
句子A → 編碼器 → 向量A
句子B → 編碼器 → 向量B
相似度(向量A, 向量B)
雖然叫"雙編碼器"實際上只有一個編碼器,只是用共享權重分別編碼兩段文本。
Bi-Encoder的核心優勢在于文檔向量可以離線預計算。每個文檔變成固定長度的向量后,存入FAISS、Milvus之類的向量數據庫,查詢時只需編碼一次query然后做近似最近鄰(ANN)搜索。
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
model = SentenceTransformer("all-MiniLM-L6-v2")
doc_embeddings = model.encode(documents, normalize_embeddings=True)
index = faiss.IndexFlatIP(doc_embeddings.shape[1])
index.add(doc_embeddings)
query_vec = model.encode(["Only project owner can publish event"], normalize_embeddings=True)
scores, indices = index.search(query_vec, k=10)
所以它擴展性強、檢索快、嵌入可復用。但缺點也很明顯,查詢和文檔之間沒有token級別的交互,相關性判斷只能是近似的,遇到邏輯推理、否定表達、復雜約束時表現會打折扣。
Cross-Encoder:精度優先
檢索器面對百萬級文檔需要的是速度,但快的代價往往是返回一些不太相關的結果。Cross-Encoder是用來解決這個問題的重排序器,把查詢和候選文檔拼接起來,一起送進Transformer,輸出0到1之間的相關性分數。
![]()
Cross-Encoder不產生句子嵌入,也不能單獨處理一段文本,所以它必須同時看到查詢和文檔才能工作:
[CLS] Query [SEP] Document [SEP] → Score
代碼如下:
from sentence_transformers import CrossEncoder
model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
pairs = [(query, documents[i]) for i in candidate_indices]
scores = model.predict(pairs)
Cross-Encoder的準確度是最高的,能捕捉真正的語義相關性。問題在于它沒法預計算,每次查詢都要對所有候選做前向傳遞,計算成本高所以只適合處理小規模候選集。
![]()
Cross-Encoder適合處理預定義的句子對評分任務,比如手頭有100對句子需要打分。而Bi-Encoder適合需要向量表示來做高效比較的場景。
比如說,用Cross-Encoder對10000個句子做聚類,需要計算約5000萬對組合的相似度,耗時65小時左右。如果換成Bi-Encoder,先算嵌入只要5秒,然后就是聚類就是后續向量運算的事了。
所以Cross-Encoder精度更高而Bi-Encoder擴展性更好。實際系統中兩者組合使用效果最佳:先用Bi-Encoder快速召回top-100,再用Cross-Encoder對這100個結果精排。
SPLADE:學習型稀疏檢索
SPLADE是基于Transformer的稀疏檢索模型,輸出不是稠密向量,而是詞匯表上的稀疏權重分布。可以理解成一個學出來的BM25。
稠密模型在處理ID、錯誤碼、領域專有術語、合規性表述時往往效果不好。SPLADE的優勢正是詞匯層面的精確匹配能力,同時保留一定的語義理解。
它能學習詞項的重要性權重,可解釋性比稠密模型好。但是代價是索引體積比傳統BM25大,語義表達能力不如純稠密模型。適用于需要兼顧關鍵詞匹配和語義召回的場景。
ColBERT:延遲交互機制
ColBERT在Bi-Encoder和Cross-Encoder之間找到了一個平衡點。它不是給整個文檔生成單一向量而是為每個token生成一個向量查詢時用延遲交互計算相似度:
score(query, doc) = Σ max cosine(query_token, doc_token)
這種設計保留了token級別的語義信息,精度比Bi-Encoder高不少,又比Cross-Encoder更容易擴展。細粒度匹配對長文檔效果尤其好。
不過token級向量意味著索引體積膨脹,內存占用和延遲都會上升。適合基礎設施條件允許、對精度要求高的場景。
多階段混合架構
實際效果最好的RAG系統通常采用多階段設計:
Query
├─ 稀疏檢索(BM25/SPLADE) → 詞匯召回
├─ 稠密檢索(Bi-Encoder) → 語義召回
├─ Cross-Encoder重排序 → 精準率
└─ LLM生成
這套架構同時兼顧召回率(不漏相關文檔)、精準率(相關文檔排前面)和可擴展性。
不同場景的模型選擇:
![]()
各模型的性能特征對比:
![]()
典型的流水線組合是稀疏檢索(BM25或SPLADE)加稠密檢索(Bi-Encoder)合并候選后用Cross-Encoder精排。
完整流程示意:
Query
│
├─ 編碼查詢(1次Transformer前向)
│
├─ 向量檢索10000個嵌入(快速向量運算)
│
├─ 保留Top-20候選
│
├─ Cross-Encoder重排Top-20(20次Transformer前向)
│
└─ 返回3-5個最佳文檔塊
![]()
附:BM25與SPLADE對比
![]()
作者:
https://avoid.overfit.cn/post/bb49efa85b9141e1a9ab0e1d57855dc6
Sachchida Nand Singh
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.