0


RAG检索模型选型:Bi-Encoder、Cross-Encoder、SPLADE与ColBERT的技术对比

构建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对比

作者:

Sachchida Nand Singh

“RAG检索模型选型:Bi-Encoder、Cross-Encoder、SPLADE与ColBERT的技术对比”的评论:

还没有评论