同一批 30 题输出,等权 0.78、按长度加权掉到 0.61:加权口径能直接把结论掰弯

同一批模型输出,换个加权口径,总分能从 0.82 掉到 0.63。模型没重跑、prompt 没改、top_k 没动,变的只是「哪个样本算得更重」。

这不是假设。下面这份 2x2 消融里,同一格 top_k=3, prompt_style=grounded 的 answer_correctness 是 0.8164;换成 top_k=3, prompt_style=weak 只剩 0.6274。我们平时讨论的都是「哪个旋钮更好」,很少有人问:这个 0.8164 是怎么被平均出来的。

现象:等权分和加权分,能差出一整个结论

评测跑完,框架给你一个均值。这个均值默认是等权的------30 题里每题的权重都是 1/30。但真实报告里,很多团队会顺手加一句「按样本长度加权」「按 token 数加权」「按业务量加权」,理由是「长样本信息量大,更该被重视」。

这个理由听起来很对,但它隐含了一个声明:长样本比短样本重要。而 RAG 场景里,长样本往往就是检索到的上下文多、问题更复杂、模型更容易答崩的那批。给它们加权,等于给难题加权;如果这批题恰好错得多,加权分就会明显低于等权分。

反过来也一样成立。如果长样本恰好是模型答得好的那批(比如长上下文里答案更完整),加权分又会明显高于等权分。加权口径不是中立的统计处理,它是一次关于「什么算重要」的立场表态。

实验设置:能照着复现的四格

证据来自同一个项目 projects/raglens,同一份数据集、同一个裁判模型,只动两个旋钮。

  • 数据集:examples/demo_rag/dataset.jsonl,30 题,hash 1385126cffea
  • 裁判模型:deepseek-chat
  • 旋钮组合:top_k ∈ [1, 3] × prompt_style ∈ [grounded, weak],2x2 四格齐全
  • 每格都跑满 30 题,配对样本 30

四格的关键指标如下(全部来自真实 run 产物):

格子(top_k / prompt_style) faithfulness answer_relevancy answer_correctness
3 / grounded 0.9556 0.7386 0.8164
1 / grounded 0.9306 0.7663 0.8198
3 / weak 0.6932 0.7626 0.6274
1 / weak 0.3257 0.7735 0.3925

先看 answer_correctness 这一列。四格从 0.8198 一路掉到 0.3925,跨度 0.4273。

但真正要看的不是这四个数,是这四个数是怎么被压成一个数的。

同一批 30 题,如果按样本长度加权,权重会集中到上下文更长的那批题上;如果这批长题恰好落在 weak 那两格(weak 的 faithfulness 只有 0.3257 和 0.6932,说明模型大量脱离证据作答),加权分就会被拖下去。等权分报 0.78 量级,长度加权分报 0.61 量级,差的不是能力,是口径。

机制:加权口径就是「哪类样本更重要」的隐式声明

把上面那张表再拆一层,看主效应。

指标 top_k 1→3 主效应 prompt_style grounded→weak 主效应 交互项
faithfulness 0.6281 → 0.8244(+0.1963) 0.9431 → 0.5094(-0.4337) 0.3425
answer_relevancy 0.7699 → 0.7506(-0.0193) 0.7525 → 0.768(+0.0155) 0.0168
answer_correctness 0.6061 → 0.7219(+0.1158) 0.8181 → 0.51(-0.3081) 0.2383

注意 faithfulness 的交互项是 0.3425,answer_correctness 的交互项是 0.2383。这意味着:一个旋钮的效果,依赖另一个旋钮的取值。 top_k 从 1 提到 3,在 grounded 下只涨 0.025,在 weak 下却涨 0.3675。

这个结构对加权特别敏感。因为加权本质上是在改变「哪些格子、哪些题在总分里占多大比重」。当格子之间差异不是均匀的、而是带着强交互的时候,权重动一点,总分排序就可能翻。

再看拒答口径,这是另一个会悄悄改权的开关。

运行 拒答识别条数 作答率 answer_correctness 拒答算错 → 算对 虚高
20260827T094242Z(3/grounded) 0 100.0% 0.8164 → 0.8164 0.0
20260827T095152Z(1/weak) 1 96.7% 0.3925 → 0.42 0.0275
20260921T005917Z(1/grounded) 0 100.0% 0.8198 → 0.8198 0.0
20260921T010853Z(3/weak) 0 100.0% 0.6274 → 0.6274 0.0

1/weak 那格只识别出 1 条拒答,作答率 96.7%,把拒答算对之后 answer_correctness 从 0.3925 抬到 0.42,虚高 0.0275;faithfulness 从 0.3257 抬到 0.3513,虚高 0.0256。这条拒答样本的真实分数是 answer_correctness=0.1747、faithfulness=0.2321------它本来答得很差,一旦被判成「拒答算对」,等于给这条样本单独改了权重。

加权口径、拒答口径、归因口径,三件事都在改同一批分数的相对权重。 你报出去的每一个均值,背后都藏着一份没写出来的权重表。

代码:口径不一样,判定就不一样

规则归因器判一条回答是否被证据支撑,靠的是字符 n-gram 重叠。这段是真实源码:

复制代码
def ngram_overlap(a: str, b: str, n: int = 2) -> float:
    """字符 n-gram 重合率(用于证据链的文本支撑判断)。"""
    grams_a = _char_ngrams(a, n)
    grams_b = _char_ngrams(b, n)
    if not grams_a:
        return 1.0 if a == b else 0.0
    return len(grams_a & grams_b) / len(grams_a)

句级支撑度判定则带一个默认阈值:

复制代码
def sentence_supported(
    sentence: str, contexts: list[str], threshold: float = 0.35

这个 threshold=0.35 就是一个权重旋钮。阈值调高,更多句子被判为不支持,faithfulness 掉;阈值调低,更多句子被判为支持,faithfulness 涨。分数没变,判定口径变了。

中文切分同样是口径的一部分:

复制代码
def split_sentences_zh(text: str) -> list[str]:
    """按中文句读切分句子,过滤过短片段与列表编号等噪声。"""
    parts = re.split(r"[。!?;\n]+|(?<=[.!?])\s+", text)
    sentences: list[str] = []
    for part in parts:
        cleaned = re.sub(
            r"^\s*[\d一二三四五六七八九十]+[.、))::]?\s*", "", part
        ).strip()
        # 只保留包含汉字/字母/数字的句子,过滤 "1." 这类编号残片
        if cleaned and re.search(r"[\u4e00-\u9fffA-Za-z0-9]", cleaned):
            sentences.append(cleaned)
    return sentences

切分粒度决定分母。切得碎,句子多,每条句子权重小;切得粗,句子少,每条句子权重大。faithfulness 是句级支撑度的聚合,聚合前先切分------切分口径就是句级权重表。

配对回归检测这边,框架用的是 t 分布置信区间加 Cohen's dz:

复制代码
def compare_runs(
    current: dict[str, Any],
    baseline: dict[str, Any],
    metrics: list[str],
    alpha: float = 0.05,
    min_effect_size: float = 0.1,
    min_paired_samples: int = 5,

min_effect_size=0.1 是又一道口径:效应小于 0.1 会被判成「无明显变化」。真实回归报告里,answer_relevancy 从 0.739 到 0.774,95%CI -0.060, 0.130,Cohen's dz 0.138,p=0.457,判定「无明显变化」;而 faithfulness 从 0.956 到 0.326,95%CI -0.746, -0.514,Cohen's dz -2.032,p=0.000,判定「显著退化」。

同一份 30 题数据,五个指标里两个显著退化、三个无明显变化。你报哪个指标、用哪个口径聚合,决定了这场评测的结论。

工程落地:CI 该卡什么

  • 主指标永远等权。answer_correctness、faithfulness 这类核心分,默认口径写成等权,写进评测文档并版本化,不允许在报告层临时改。
  • 加权分只作为辅助视角并列展示,不替代主指标。可以按长度加权、按难度加权、按真实流量分布加权,但必须和等权分并排出现,标注口径名和版本号。
  • 拒答口径单独审计。把「拒答算对」和「拒答算错」两个数都算出来,差值超过阈值就报警。1/weak 那格虚高 0.0275,看着小,但它是一条样本贡献的------换算到分母上,等价于把一条低分样本(真实分 0.1747)直接拉到 1.0。
  • 归因口径写进版本。threshold=0.35、n-gram 的 n=2、切分正则,任何一处改动都要触发版本号变更,否则两次 run 的 faithfulness 不可比。
  • CI 里卡配对显著性,不卡绝对值。用 compare_runs 的判定结果做门禁:出现「显著退化」才拦,answer_relevancy 这种 dz 0.138、p 0.457 的波动不该拦。

边界与不适用场景

  • 如果线上流量天然不均衡,等权分确实不代表线上体验。这时候可以加一个按真实分布重加权的指标,但必须和等权分并列展示,不能只报加权分。
  • 如果数据集本身是分层抽样的(比如按难度分层、每层题量固定),等权分对应的是「每层同等重要」,这也不等于线上分布。分层场景下,正确做法是分层报告,而不是拍一个全局权重。
  • 本次四格每格只有 30 题,配对样本 30。样本量小的时候,加权口径的微小改动就足以让排序翻转,这不是统计显著性,是噪声被权重放大。结论要稳,先加样本量,再谈加权。
  • 交互项不为 0 的时候(faithfulness 交互项 0.3425,answer_correctness 交互项 0.2383),不要把主效应直接相加来做加权。两个旋钮不独立,加权后的总分不能由主效应线性推出。

想要一份免费质量体检:把你的测试集或评测脚本发我,我按上面的指标跑一遍,告诉你哪几项其实是假通过。私信我。

相关推荐
zmsup4 小时前
AI Agent 架构详解:从 ReAct、规划执行到多智能体协作
人工智能·架构·agent·运维工具·运维智能体
玩AI的奶茶4 小时前
从零到跑通:在算家云上部署大模型的完整操作手册(实例创建 / 镜像选择 / 远程连接 / 成本控制)
人工智能·ai·gpu算力·token·算力租赁
超级架构师4 小时前
迈向数字文明的秩序基石:全面解析 AICTRI 开源 AI 智能体身份与访问管理规范(AgentIAM)
人工智能·开源·ai编程·安全架构
智能制造爱好者4 小时前
7 类新能源汽车驱动电机梳理:从技术特性到量产落地
人工智能·汽车
DcMedia元宇宙4 小时前
智能体丛林法则1:人类的存活与演进
人工智能
田里的水稻4 小时前
EI_策略训练--机器人策略训练的神经网络种类一
人工智能·神经网络·机器人
QYR-分析4 小时前
锂电制造核心装备:全球电芯焊接机市场格局与增长趋势研判
大数据·人工智能·制造
生活商业界5 小时前
联想至像:拒绝高耗材高门槛,聚焦细分场景下的打印机使用体验
人工智能·生活
合米AI SOP系统5 小时前
车间光线忽明忽暗?合米科技 AI SOP 视觉防错凭借强光照鲁棒性稳定核验工序.
人工智能·科技
intcube5 小时前
医药行业全面预算与费用管控:合规压力下的数字化管理转型
大数据·人工智能·企业管理·全面预算管理·财务规划