qinghuan
← 回到知识探索

AI 直觉 / 动手实验

检索从三条加到十条,为什么还得重新评估?

取回数量、排序位置和最终材料预算分别影响结果,用人工标签先算清找到多少、夹带多少。

展开本文目录
  1. 先动手看看
  2. 先找到候选,再决定谁排前面
  3. 前k条里,找到多少相关材料
  4. 材料条数不能代替材料判断
  5. 取回十条,不等于十条都能用上
  6. 记住这三点
  7. 别把例子用过头
  8. 自己试一小步
  9. 查原始资料
  10. 找一份资料继续练
开始前,会这些就够了
  • 知道百分比怎样计算
  • 知道检索会返回一份有顺序的材料列表
遇到不熟悉的词,先看这 3 个解释
检索前 k 条的评测
在固定排序里只看前 k 条:精确率问这 k 条里有多少相关,召回率问全部相关材料中找到了多少。两个数字的分母不同。
候选召回
检索流程里,先从全部资料中选出一批可能有用的候选,供后续排序和核对。这一步关心有没有把需要的资料找进来,并不保证每份候选都相关。
重排
拿到一批候选资料后,再按与当前问题的相关程度重新安排顺序,让较合适的内容有机会排在前面。它处理的是已有候选,不是重新搜索整个资料库。
打开完整术语小抄 ↗

假设你给团队做了一个软件手册助手。它每次先取三条材料,同事提议改成十条:“多找一点,总能补到漏掉的吧?”这可以是值得试的改动,但它改变的是一批候选材料,是否更适合完成任务还得检查。

本文固定一份人工排好的列表,只改变取前几条。没有检索模型,也不调用大模型生成答案。我们先把“该找的找到多少”和“找到的有多少相关”算清楚,再讨论这些数字没有回答的问题。

多取几条,找到的和夹带的各有多少?

全库有十二份虚构材料,其中五份人工标为相关。候选固定为 D1 到 D10,只改变取前几条;这不是检索模型的真实输出。

取前k条不会重排;排在截取范围之外的候选仍保留显示
排名材料人工标签本次取回
1D11:相关取回
2D20:不相关取回
3D31:相关取回
4D40:不相关未取回
5D50:不相关未取回
6D61:相关未取回
7D70:不相关未取回
8D81:相关未取回
9D90:不相关未取回
10D100:不相关未取回

取回 3 条,相关的有 2 条。

Precision@k(取回的有多少相关):66.7%,2 ÷ 3。

Recall@k(全库相关的找到多少):40.0%,2 ÷ 5。分母始终包括候选之外的 D11。

k=0 时精确率未定义;全库已有五份相关材料,因此召回率为 0%。本文代码用 1 到 10,这里额外开放 0 观察空结果。扩大同一排序的前缀,召回率不会下降,精确率却可能上升或下降。没有运行大模型,也没有用这两个比例替代材料冲突、上下文预算或最终回答质量的检查。

先找到候选,再决定谁排前面

检索里的候选召回,是先从资料库里找出可能有用的一批材料;排序再决定谁排在前面。重排是在已有候选内重新排队。增加候选数量可能让原本漏掉的材料入选;如果仍取原列表前三条,只把后面的队伍拉长,前三条并不会自动变好。

评测要先固定问题、资料版本和人工相关标准。例如问题明确问某款软件第三版的配置,就应按这个需求判断材料,而不只是看它是否出现“配置”两个字。未标注的材料也不能直接当作不相关,否则计算会把判断缺口藏起来。

前k条里,找到多少相关材料

Precision@k 是前k条中相关材料占多少;Recall@k 是资料库中所有相关材料里,前k条找到了多少。本例把十二份虚构材料全部人工标成0或1,1表示相关。列表只含前十份,第十一份虽相关却没被召回,所以分母不能只数列表里的相关项。

代码中的四个k都不超过列表长度,也没有重复编号。同一份固定排序扩大前缀,已经找到的相关材料不会消失,因此召回率不会下降;精确率则可能上升或下降,取决于新加入的是什么。本例先降低再回升的精确率,就是一种可能情况。

小实验 · python
relevance = {
    'D1': 1, 'D2': 0, 'D3': 1, 'D4': 0,
    'D5': 0, 'D6': 1, 'D7': 0, 'D8': 1,
    'D9': 0, 'D10': 0, 'D11': 1, 'D12': 0,
}
ranked = [f'D{i}' for i in range(1, 11)]
total_relevant = sum(relevance.values())
assert len(ranked) == len(set(ranked))
assert all(doc_id in relevance for doc_id in ranked)


def measure(k):
    if type(k) is not int or not 1 <= k <= len(ranked):
        raise ValueError('k must be an integer within the ranked list')
    hits = sum(relevance[doc_id] for doc_id in ranked[:k])
    precision = hits / k
    recall = hits / total_relevant if total_relevant else None
    return hits, precision, recall


print('all relevant:', total_relevant)
for k in [3, 5, 6, 10]:
    hits, precision, recall = measure(k)
    shown = f'{recall:.1%}' if recall is not None else 'undefined'
    print(f'k={k}: hits={hits}, P@k={precision:.1%}, R@k={shown}')

怎样运行:保存为 retrieval_top_k.py,用 Python 3.10 或更新版本运行:python retrieval_top_k.py。仅用标准库,不联网。

运行后对照结果
all relevant: 5
k=3: hits=2, P@k=66.7%, R@k=40.0%
k=5: hits=2, P@k=40.0%, R@k=40.0%
k=6: hits=3, P@k=50.0%, R@k=60.0%
k=10: hits=4, P@k=40.0%, R@k=80.0%

材料条数不能代替材料判断

两个比例都没有描述全部排序细节:只在同一批前k条内部交换顺序,它们可以保持不变。人先读哪条、后续程序把哪条放在前面,却可能改变。标签也只有相关与不相关两档,没有表达某条材料是否比另一条更直接、更完整。

相关材料之间还可能存在版本差异或相互冲突。例如两份故障指南都谈同一个开关,却适用于不同版本。增加数量可能补到解释差异的材料,也可能把更多待核对内容带进来。这要另外检查适用范围,不能靠相关材料多数表决。

取回十条,不等于十条都能用上

模型能接收的上下文有限,材料还要与问题等内容共享预算,并按所用模型的规则预留回答空间。十篇短通知与十份长手册,占用的长度不同。若先取十条、最后只装得下五条,就要记录实际送入的内容,而不只记录检索配置里的数字。

2024年的《Lost in the Middle》在当时受测模型和任务上发现,材料位置会影响结果。它提示我们检查排列与长度,并没有证明所有模型都会因材料变多而变差。实际改k时,应在同一组代表性问题上比较检索、实际输入和最终任务表现;本文只验证前面的计数。

记住这三点

  • 扩大候选与改善排序是不同改动,比较时要固定问题、资料和相关标准。
  • Recall@k 需要相关材料总数;Precision@k 只看取回前k条中的相关比例。
  • 检索比例不直接等于回答质量,还要检查冲突、实际输入预算和任务表现。

别把例子用过头

  • 本例完整知道十二份材料的标签;大资料库若标注不全,应说明 Recall@k 的评测范围。
  • 召回率不下降的结论只针对同一固定排序的嵌套前缀;重排、过滤或资料变化后要重新计算。
  • 更多资料既可能补足信息,也可能增加处理负担;本例没有测量生成质量或宣称通用最佳k。

自己试一小步

保持标签不变,只改变候选和顺序,你会怎样区分“找到了”和“排好了”?

  1. 保存原列表与四组指标,另建一个调整顺序的副本。
  2. 先只交换前三条的位置,再把原本靠后的材料移到前面,分别比较。
  3. 再模拟只能送入前五条的预算,单独记录候选列表和实际采用的列表。

做到这些,就算完成

  • 是否保留全库相关总数,避免把未取回材料从分母删掉?
  • 是否区分排序变化、候选变化和实际输入截取?
  • 是否清楚说明这些计数尚未测量回答是否正确?

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

找一份资料继续练

先选一项,按下面的范围继续学;不必从头读完整门课程。