检索先回答“允许看什么”
前章给出确定的版本与日期视图,本章在这个视图里寻找与问题相关的证据。读者只需集合、排序与分数相加;目标是亲手算出两条词法排序与融合结果,并能区分召回失败、排序失败和选择失败。我们引入 RRF 的排名融合形式作为外部已有算法,但两条词法打分器、测试资料和下游管线均在本项目明确定义。原论文基准成绩不作为本课任务效果证明。
检索输入是 query 字符串以及候选记录列表,每条记录含稳定 id、quote 和来源字段。先由第四章构造允许视图,再按范围提取候选;撤销、过期与其他 scope 资料不会进入评分。rank 不读取 fields 正确答案列表,不凭人工给定分数选择目标。fields 仅用于后续受控生成与评测;因此检索是否找到正确内容仍可能失败。可用不意味着相关,相关也不意味着真实,来源支持在第六章另验。
词法检索只比较字面。本项目中文任务没有依赖外部分词库,而用字符集合和相邻两字符片段。字符集合忽略次数与顺序,能容忍少量表达变形,也可能让完全不同语句因共享常见字而得高分;二元片段保留局部相邻顺序,但不理解同义词、否定与语义。这里不是 embedding 检索,更不是把一个大模型的能力藏在辅助程序里。没有匹配时返回空候选,后续应拒答,而非强塞最高的零分材料。
第一条排序:集合重叠
令 是问题的非空白字符集合, 是候选正文字符集合。交集是两边共有字符,并集是至少在一边出现的字符。重叠分数是交集大小除并集大小;分母至少取一,防止空集除零。分数介于零和一:交集不可能比并集大;若两集合相同且非空则为一。问题与正文长度、汉字重复次数不直接作为概率分布,这个分数没有被校准成“答对概率”。
完整算例一:顺序丢失的好处与代价
问题“活动时间”的集合有活、动、时、间四字,候选“活动时间:周四16:30。”与它共享四字,正文额外字符使分母变大;另一候选“时间活动”虽然语序反了,集合恰相同,J 为一。集合排序可能把后一条排前,即使它没有提供任何时间值。这个反例揭示缺失的机制:高重叠不证明可抽取答案。真正字段声明的格式与源支持检查会在后面拒绝一个没有值的误匹配,不能单靠增加字符得分解决。
另一问题“借用前提”中关键字可能在“相机登记条件”里被同义改写,两边字符共享很少,词法召回会漏掉。课程没有给出这种同义任务的通过率,也没有把字面题满分当语义检索能力。若替换成向量模型,应固定该模型、向量归一化、索引构建和测试集重新跑同一比较;向量召回与范围/撤销过滤的契约仍然需要保留。
def overlap(q,d):
a={c for c in q if not c.isspace()};b=set(d)
return len(a&b)/max(1,len(a|b))
assert overlap('活动时间','时间活动')==1
assert overlap('活动时间','活动时间:周四16:30。')<1
print('order is not represented by character sets')第二条排序:短语片段与精确包含
设问题长度为 ,取所有相邻两字符片段形成集合 ;代码按原问题字符取片段,空白处理与第一条略有不同,这也是冻结实现的一部分。对每个不同片段检查它是否出现在正文,命中数除以 。若完整问题是正文子串,再加二。这个二是本课选的启发式权重,不是学习出来的参数,也没有理论最优值。只有正分候选参与第二条排序,单字问题没有二元片段,但精确包含仍可获加分。
问题“活动时间”得到活动、动时、时间三种片段。正文“活动时间:周四16:30。”命中三种且完整包含,得三;正文“活动地点与时间”只命中活动和时间,不含动时也不完整包含,得三分之二。这个分数保留一点局部顺序,但仍不区分“活动时间尚未确定”和确切时间;支持与是否足以完成任务,必须由后续明确规则判定。
完整算例二:融合使用名次而非原分
两条分数刻度不同,直接相加会让某种量纲支配。RRF 为每个榜单中的名次 加上 ;本项目名次从一开始,固定 。未出现在某榜单的候选贡献零,而不是补一个无限后置名次再猜分数。若甲在两榜分别第一、第三,乙分别第二、第一,甲得 ,乙得 ,乙略高。这一比较可手算,数值不是概率,总和也不必为一。
融合保留“在多种排序都靠前”的信号,同时舍弃原始分差。甲在第一榜原分领先一百倍,融合也只看到第一名,不会按一百倍投票。该特性避免量纲不一致,也可能丢掉有意义的置信度。两条排序都基于字面,错误并非独立,因此不能声称融合自动获得多种独立知识。参数六十沿用已知 RRF 形式作为固定选择,当前项目没有对它调参后再用同一任务报告无偏泛化提升。
from fractions import Fraction
a=Fraction(1,61)+Fraction(1,63)
b=Fraction(1,62)+Fraction(1,61)
assert b>a
print(float(a),float(b),float(b-a))真实资料如何流过检索
time-current 使用前十二事件。主配置的排序前四项为 schedule@2:3、schedule@2:4、location@2:3、access@1:full;第一条包含正确活动时间,后几条仍可能因“活动”或字符重叠入榜。pack 会按实际增量词元成本继续选择,完整输入最终九百五十八词元。这个实例说明召回已找到目标,但未证明其余候选全有用;后续可研究减少无关内容,不能把候选数量越多当作检索更成功。
版本过滤必须在候选构造前。设旧版长文排名第一、新版第二,只保留第一再过滤旧版会得到空;先过滤再排序则新版进入榜单。这一反例不需要复杂语义模型。实际项目 units 只从 active 资料产生条目,历史查询则先 replay 对应前缀再过滤,所以旧版可以在明确历史任务中成为正确证据,但不能在当前任务中借高分复活。
并列时用稳定 id 排序,同一版本和 query 重跑得到同一榜单。幂等不代表优质:一个确定的坏检索器仍可每次漏掉同一答案。因此测试一方面固定输出顺序,另一方面安排 unknown、camera-revoked 等题,确认空候选与不相关资料不会被当成支持。全库中存在目标事实但没有进入可见包,coverage 会下降;数据库里完全不存在目标时 coverage 定义为 None,不能把零分母当满分。
复杂度按有限字符串扫描算:M 为候选数,每份 quote 平均长度 B,问题有 n 个字符与至多 n−1 个片段。字符集合构造 ;逐个子串匹配按保守朴素上界 ;两次排序 。Python 内部字符串搜索可能更快,但课程不依赖其特定优化证明。小资料可直接扫描,百万文档需要倒排索引或向量索引;更换存储后应重新审计撤销失效与排序稳定性。
本章交付的是从允许源文本自动计算的实际榜单,不是手写目标向量。下章将验证榜单进入窗口后的回答:真实引用存在还不够,必须证明生成器实际看到了那条完整行。检索、预算、生成与验证分开记录,才能在第七章定位反事实失效的最早环节。
空、单字与重复片段的边界
query不得全是空白,但可以是单字。单字问题没有二元片段,片段命中和为零,分母钳为一;若正文包含该字,完整包含奖励仍可为正。两个排序对空候选池都返回空列表,RRF没有任何贡献,pack只构造基本wire。这不是网络错误或资料导入失败,而是当前词法规则找不到有正分的证据;调用方必须通过不同错误状态区分它们。
片段用集合意味着重复不会多次计分。“哈哈哈”虽有两个相邻位置,都对应同一个“哈哈”片段,集合大小是一。若按位置计频,那是另一个评分器,不能不改冻结声明就把测试分数混用。字符集合也如此,一个正文重复一百次“活动”,不会把重叠分数直接乘一百。它可能仍通过额外噪音扩大并集而改变分数,因此代码行为应以实际集合构造复核。
融合在本项目只产生候选优先级,真正预算准入仍靠第二章pack重编码。名次靠前的长全文可能装不下,后面的短完整行反而被选;这不表示检索分数被“算错”,而是容量约束下的第二阶段选择。评估应保存ranked与selected两张列表,分别问目标有没有被召回、有没有进入上下文。把两者都叫top-k命中率会丢掉最重要的定位信息,也容易在优化时给错误环节加参数。
TRY IT YOURSELF
基础:分数范围
两个非空集合相同,J是多少?J=1能证明句子含义相同吗?
提示 1
提示 2
对照参考解答
TRY IT YOURSELF
进阶:RRF手算
甲在两榜1、3名;乙2、1名;k=60,谁高?
提示 1
提示 2
对照参考解答
TRY IT YOURSELF
挑战:过滤顺序
旧版第一、新版第二,top1后过滤得到空。正确改法是什么?