Cross-encoder vs bi-encoder

架构 · 约 8 分钟阅读 ·

这两种架构是现代检索的核心。bi-encoder 独立地把每段文本变成一个向量 —— 又快又可扩展,非常适合一阶段搜索。cross-encoder 把查询和文档一起读入并输出相关性分数 —— 更慢,但准确得多,而这正是 reranker 所需要的。

bi-encoder 如何工作

bi-encoder(又称双编码器,dual encoder)把查询和每个文档分别送入同一个模型,为每段文本产生一个定长向量。相关性就是两个向量之间的余弦相似度(或点积)。

query ───▶ [encoder] ───▶ q⃗  ┐
                              ├─▶ cosine(q⃗, d⃗) = score
doc   ───▶ [encoder] ───▶ d⃗  ┘

关键特性是:文档向量不依赖于查询。你可以一次性把整个语料库嵌入并存入索引,查询时只需嵌入查询并查找最近邻。这正是向量搜索能够快到处理数百万文档的原因。缺点是查询和文档从不“见面”,因此分数是个粗糙的工具。

cross-encoder 如何工作

cross-encoder 把查询和文档拼接成一个序列 —— [CLS] query [SEP] document [SEP] —— 并把这一对一起送入 transformer。自注意力让每个查询 token 都能与每个文档 token 交互,模型输出一个相关性分数。

query + doc ───▶ [encoder, full cross-attention] ───▶ relevance score

这准确得多,因为模型能推理两段文本之间的关系,而不只是表面相似度。代价是:分数取决于具体的那一对,所以什么都没法预计算。每一个 (query, document) 组合都是一次全新的前向传播 —— 这也是为什么你只在短名单上跑 cross-encoder,绝不在整个语料库上跑。这种“只对短名单”的用法就是重排序。

逐项对比

项目Bi-encoderCross-encoder
输入查询与文档分别编码查询与文档一起编码
输出每段文本一个向量每一对一个相关性分数
可预计算语料?可以 —— 嵌入一次,反复复用不行 —— 必须在查询时打分
速度非常快(向量查找)慢(每个候选一次模型调用)
准确性适合召回精度极佳
可扩展到百万级文档?可以不行 —— 只能用于短名单
典型角色第一阶段检索第二阶段重排序

为什么两者都要用

它们是互补而非竞争。bi-encoder 的职责是 recall(召回):低成本地拉出几十个很可能包含答案的候选。cross-encoder 的职责是 precision(精度):仔细重排这个短名单,让最好的候选明确地排在最前。

bi-encoder 找到草垛中最有希望的那个角落,cross-encoder 则在那里找到那根针。

只用其中一个通常是个错误:单靠 cross-encoder 无法及时扫描百万文档,单靠 bi-encoder 又把质量白白浪费。标准答案是两阶段流水线 —— 用 bi-encoder 检索,用 cross-encoder 重排序。

一个具体例子

以查询 “免费套餐包含 API 访问吗?” 和两个候选为例:

bi-encoder 可能把 A 排得很高 —— 它密集地围绕“套餐”“定价”这些主题,与查询共享大量词汇。但真正回答了问题的是 B。cross-encoder 把查询和每个候选一起读入,能看出 B 解决了那个具体诉求并把它顶到最前。正是这道鸿沟,构成了 reranker 存在的全部理由。

亲自感受这种差别

我们的 Demo 在你的浏览器里运行一个 cross-encoder。粘贴一个刁钻的查询,看它把哪段文本顶上去。

打开在线 Demo →

Keep reading