最近审阅:2026-10
用户搜“attention mask shape mismatch”。向量检索(retrieval)找到了讲注意力原理的好文章,关键词检索却找到了包含同一报错和张量形状的修复记录。两边都有道理,只是“主题相关”和“能解决这个错误”不是同一件事。
接着双塔那一篇,这里看怎样把不同检索结果合起来,再决定哪些值得仔细读。只想理解设计,可以先看文档例子;想实现融合,直接看排名融合。
1. 先看两路分别漏了什么
做一个四篇文档的教学资料库:
| 文档 | 内容 | 关键词名次 | 向量名次 |
|---|---|---|---|
| A | 一份旧版本报错记录,包含完全相同的错误字符串 | 1 | 未进入前 3 |
| B | 解释 mask 广播规则,并给出当前版本的修复方法 | 2 | 1 |
| C | Transformer 总览,没有讲到这个错误 | 3 | 2 |
| D | 用另一种说法解释同一个维度问题 | 未进入前 3 | 3 |
关键词方法(例如 BM25)根据词项匹配打分,精确标识符、错误码和罕见名字常常很重要。Dense retrieval 可以找不同说法表达的相近语义,但也可能把“同主题”排在“满足细节限制”之前。没有哪一路天然理解全部需求。
想先弄清关键词分数怎么来的,可以读 TF-IDF 与 BM25:词频、稀有程度和长度各改了什么。
混合之前先看各自独有的相关结果。如果两路总是返回同一批内容,融合收益可能很小;如果第二路只增加噪声,多一路还可能让后面的模型更忙。
2. 为什么不直接把两个分数相加?
一边可能是 BM25 分数 12.4,另一边是余弦相似度 0.81。它们不在同一个尺度,直接相加通常让数值大的那边主导。分数归一化是一种办法,但查询分布变化后还要检查校准(calibration)。
一个简单的起点是倒数排名融合(Reciprocal Rank Fusion, RRF):只看文档在各路的名次,不要求原始分数可比。
$$ \mathrm{RRF}(d)=\sum_{r:d\in L_r}\frac{1}{k+\mathrm{rank}_r(d)}. $$
名次从 1 开始。$L_r$ 是第 $r$ 路实际返回的列表;没有进入列表的文档在这一项贡献 0。$k$ 平滑名次差异,和最终要返回多少条的 $K$ 不是一回事。公式和实现约定可对照 Elasticsearch 的 RRF 文档。
教学例子取 $k=10$:B 的分数为 $1/12+1/11\approx0.1742$,C 为 $1/13+1/12\approx0.1603$,A 为 $1/11\approx0.0909$,D 为 $1/13\approx0.0769$。B、C 得到了两路支持,即使 A 在关键词里排第一,也未必最终排第一。注意 $k=10$ 是为了让分数差别看得清;常见默认值是 60,例如 Elasticsearch 的 rank_constant。换成 60,这个例子的顺序不变,只是分数挨得更近。
def reciprocal_rank_fusion(rankings, constant=10):
if constant < 0:
raise ValueError("The constant must be non-negative")
scores = {}
for ranking in rankings:
if len(set(ranking)) != len(ranking):
raise ValueError("Deduplicate each source before fusion")
for position, document in enumerate(ranking, start=1):
scores[document] = scores.get(document, 0) + 1 / (constant + position)
return sorted(scores, key=lambda document: (-scores[document], document))
assert reciprocal_rank_fusion([["A", "B", "C"], ["B", "C", "D"]]) == ["B", "C", "A", "D"]
这段代码把相同分数按文档 ID 排序,只是为了可复现。实际系统要明确自己的并列处理规则。
3. 少取几条,结果会怎么变?
沿用上面的四篇文档,只改变实际返回的列表。常数始终为 10,不偷用全库排名:
| 输入设置 | 融合结果 | 发生了什么 |
|---|---|---|
| 两路各取前 3 | B → C → A → D | B、C 各收到两路支持 |
| 只保留关键词前 3 | A → B → C | D 不再有进入结果的机会 |
| 两路各取前 1 | A、B 并列 | B 失去关键词第二名的支持 |
试着关掉向量那一路:D 就根本没有机会进入候选(candidate)集。再把每路只取前 1:B 失去了第二名那一份支持。融合器只能利用实际拿到的结果。 调大平滑常数也不会让没召回的内容凭空出现。
RRF 忽略了原始分数之间的差距,无法知道第一名比第二名强一点还是强很多。某一路若重复返回同一文档,也不能靠重复记分获利。它适合当透明的基线(baseline),不是所有任务都最优的融合器。
4. 重排补哪一课?
融合后,仍然需要判断 A 的修复方法是否过期,B 是否真的满足当前代码环境。Cross-encoder 可以联合读取 query 和候选,利用更细的对应关系重排;也可以用带明确标准的 LLM judge,但要测成本、一致性,以及它会不会被候选文档里夹带的指令带偏。
看一个具体的打分方式:Qwen3-Reranker 把 query 和候选拼进同一个 prompt,问它“是否满足要求”,只取最后一个位置上 “yes” 和 “no” 两个 token 的 logit,在这两个之间做 softmax,把 “yes” 的概率当分数。若两个 logit 分别是 2 和 0,分数就是 $e^2/(e^2+1)\approx0.881$。这是手算的示意,不是模型输出;它只说明分数来自同一次联合阅读,而不是两个独立向量的点积(inner product)。
权限允许的文档
├─ 关键词前若干条 ─┐
└─ 向量前若干条 ──┴→ 去重、融合 → 固定候选预算 → 重排 → 证据上下文
权限要在内容送给无权访问它的模型或服务前落实;不能指望最后生成答案时“别说出来”。如果索引不能做过滤,也要在可信的检索层完成检查,同时考虑过滤之后不足额的问题。
重排只能重排收到的候选。如果正确文档根本没入库或没被召回,换更贵的 reranker 也找不回来。这里说的是纯重排;允许再次搜索的 agent 是另一种设计,需要单独计算预算。
5. 同样花费下,怎样比较?
别拿“向量取 100 条”对比“向量取 100 条,再多加关键词 100 条”,然后把提升全归给算法。这个实验同时增加了候选数量和后续计算。
| 对照 | 保持什么不变 | 主要想回答什么 |
|---|---|---|
| 只用向量 vs 混合 | 最终候选数、语料快照、评估 query | 新来源能否补上独有的相关文档? |
| RRF vs 学习式融合 | 同一组来源结果 | 融合规则本身是否更合适? |
| 不重排 vs 重排 | 候选集和标注 | 重排能否把已有的好内容排上来? |
| 不同预算 | 明确记录每阶段候选数、延迟和费用 | 多花的计算是否值得? |
假设有 3 篇已知相关文档,两路分别命中 $\{B\}$ 和 $\{B,D\}$。并集命中 2 篇,但不能把两路命中数相加成 3。重复率和独有正例很有用,前提是承认标注可能不全:没有被标记相关,不自动等于不相关。
6. 别把一堆相近段落都塞给模型
最后还要在上下文预算里选证据。重复的五段解释可能不如一段解释加一段版本说明;拼接时保留文档来源、时间和段落边界,方便检查回答究竟根据了什么。
上下文更长不保证证据更好用。Lost in the Middle 在其测试设置中观察到模型对相关信息位置的敏感性。这不能推出所有模型都一样,但提醒我们:最终应测答案是否有依据,不能只看召回阶段分数。
接着读分层评估:把“没找到”和“找到了却没用好”拆开,下一步改什么才有依据。
本页术语 5
- 检索 retrieval
- 从外部语料里找出相关内容
- 校准 calibration
- 模型给出的置信度是否等于真实正确率
- 候选 candidate
- 召回阶段捞出来、等待精排挑选的内容
- 基线 baseline
- 用来比较新方法的参考方法或配置
- 点积 inner product
- 两个向量对应分量相乘再求和,双塔的打分方式