先固定任务集再比分数高低 / LoHoSearch 适合当哪类对照

文章导读
要让 LoHoSearch 当对照,先把两次评估固定在同一批任务上:导出任务标识清单,比对数量、去重后的数量以及缺失项。这三项对齐之后,分数差才可能归因于你改动的部分。如果连任务集都没对齐,后面比较 LoHoSearch 与其他方案的高下就没有意义。LoHoSearch 更适合放在“检索与信息定位类任务”的对照臂上,用来判断检索流程、查询改写、召回策略这类改动是否有效;它通常不适合单独用来给通用问
📋 目录
  1. 一 核对两次跑的是同一批任务
  2. 二 把总分拆成任务类型维度的对比
  3. 三 同配置重复运行一次看波动范围
  4. 四 给对照结论写下适用边界
A A

要让 LoHoSearch 当对照,先把两次评估固定在同一批任务上:导出任务标识清单,比对数量、去重后的数量以及缺失项。这三项对齐之后,分数差才可能归因于你改动的部分。如果连任务集都没对齐,后面比较 LoHoSearch 与其他方案的高下就没有意义。LoHoSearch 更适合放在“检索与信息定位类任务”的对照臂上,用来判断检索流程、查询改写、召回策略这类改动是否有效;它通常不适合单独用来给通用问答质量或开放式生成能力下结论。下面给出核对清单、分类型对照表的设计方式和结论边界的写法。

把 LoHoSearch 当对照前,先确认两次评估用的是同一份任务标识清单:数量一致、去重后一致、缺失项为零,再谈分数差。它的合适位置是检索与信息定位类任务的对照臂,用来判断检索流程改动是否有效;不适合单独支撑通用问答质量或生成能力的结论。当差异小于同配置重复运行的波动时,建议只写“看不出差异”。

核对两次跑的是同一批任务

这一步的目的是先排除任务集不同造成的假差异。做法很直接:跑之前把本次要评估的任务标识导出成清单留档,跑完之后再从日志里抽一次实际执行的任务标识,两份清单对撞。标识字段用任务集里天然唯一的那一个,通常是 task_id,不要用行号,因为采样打乱后行号会变,按行号对齐会把不同任务当成同一题来比。

必须检查的检查点有三类:总数是否一致;去重后的数量是否一致(去重后少于总数,说明同一题被重复采样或分片重叠);双方各自缺失的标识有多少、具体是哪些。三者都干净,才进入分数比较。下面的脚本可以放在跑完评估之后执行,输入是两次运行各自的 jsonl 记录,唯一键字段按你任务集的实际字段名替换。

import json, collections

def load_ids(path):
    ids = []
    with open(path, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            rec = json.loads(line)
            ids.append(rec["task_id"])   # 替换成任务集里的唯一键字段
    return ids

base  = load_ids("run_a/tasks.jsonl")
treat = load_ids("run_b/tasks.jsonl")

print("count",  len(base), len(treat))
print("unique", len(set(base)), len(set(treat)))

print("dup_in_base",  [k for k, v in collections.Counter(base).items()  if v > 1][:10])
print("dup_in_treat", [k for k, v in collections.Counter(treat).items() if v > 1][:10])

only_a = sorted(set(base)  - set(treat))
only_b = sorted(set(treat) - set(base))
print("only_in_a", len(only_a), only_a[:10])
print("only_in_b", len(only_b), only_b[:10])

如果不用脚本,用命令行做一次粗核对也可以,适合只想快速看一眼有没有漏项的场景:

diff <(sort run_a/ids.txt | uniq) <(sort run_b/ids.txt | uniq) | head -40

结论只有两种:清单完全一致,可以继续比分数;清单不一致,先把差异补齐或重跑,不要带着缺口去解释分差。缺失项如果集中在某一类任务上(比如只在时效性查询里缺),那这批结果连分类型比较都不适合做。

先固定任务集再比分数高低 / LoHoSearch 适合当哪类对照

把总分拆成任务类型维度的对比

一个总分很容易掩盖方向相反的差异:检索定位变好、拒答类变差,总分可能只动一点点,看上去像“没变化”。所以固定任务集之后,第二步是把总分按任务类型拆开。行的设计按任务集本身的分类标签走,不建议临时拍脑袋分组;列固定为四列,保证每次都能填满。

任务类型对照方案 ALoHoSearch 方案 B差值(B−A)样本量
单跳检索定位---n=
多跳串联查询---n=
时效性/新近信息---n=
长文档内字段核对---n=
无答案/应拒答---n=

样本量必须落在每个单元格里,写成 n= 后面跟实际数字,不要只写在表头。原因是分类型之后单类样本往往并不多,某个类型只有个位数样本时,那格差值只能当方向参考,不能当结论。建议在表格下方补一行说明:样本量低于你设定的下限(比如单类不足 10 条)的格子,标记为“样本不足,仅看方向”。下限取多少要结合任务集总量确认,没有一个通用数字。

填完表之后看两件事:有没有哪一类出现与总分相反的方向;有没有哪一类两边几乎贴平。前者说明改动有代价,后者说明该类任务上两种方案拉不开,写结论时要分开表述,不要用一句“整体更好”盖过去。

同配置重复运行一次看波动范围

分类型差异出来之后,还要判断这个差异是不是运行本身抖出来的。做法是把配置完全锁死再跑一遍:同一任务集、同一模型与参数、同一检索索引快照、同一随机种子,只改 run_id。重复次数建议至少 2 次,条件允许时 3 次;次数越多对波动范围估计越稳,但成本也越高,按你能承受的算力定。

先固定任务集再比分数高低 / LoHoSearch 适合当哪类对照

记录方式建议落成一行一条的表格,配置和任务集都用哈希留痕,方便后面复现:

run_id,config_sha,taskset_sha,seed,score_total,score_retrieval,score_multihop,score_no_answer
r1,-,-,-,-,-,-,-
r2,-,-,-,-,-,-,-
r3,-,-,-,-,-,-,-

拿到多次同配置结果后,取分类型分数的最大值与最小值之差,作为该类型的波动范围。然后回看上一步表里的差值:如果差值落在波动范围内,表述就写“在本次重复运行的波动范围内看不出差异”,不要写“B 优于 A”;如果差值明显超出波动范围,才可以写“方向上存在差异”,并注明是哪个类型、样本量多少。这样写的好处是读者能自己判断这个结论有多硬。

给对照结论写下适用边界

最后一步是把结论圈起来,防止局部结果被当成普遍结论。建议在结论后面固定写三行:覆盖了什么、没覆盖什么、换场景要重验什么。

  • 能覆盖的任务类型:本次任务集里实际出现且样本量达标的类型,通常是单跳检索定位、多跳串联查询、长文档内字段核对这类有明确答案位置的题。
  • 不能覆盖的类型:开放式写作与主观质量评价、多轮对话状态跟踪、需要执行代码或调用外部系统才能判定的任务、以及任务集里根本没采样的类型。这些方向上不要引用本次分数。
  • 换场景需要重新验证的项:换检索索引或数据快照、换语言、换文档长度分布、换模型版本、改任务采样比例、把单轮改成多轮。任何一项变动之后,原来的分类型对照表和波动范围都不再直接适用,需要在新条件下重跑一遍同配置基线。

一句话收口:LoHoSearch 适合当“检索类改动是否有效”的对照臂,用同一批任务标识、分类型拆解、同配置重复运行这三道检查来支撑结论;它不适合当通用问答质量的裁判,也不适合在没有任务集一致性核对的前提下被拿来比分数高低。