> 恒为常数的特征在归一化融合里等于不存在——不报错,还会连累依赖它的阈值

Information Retrieval Intermediate 2026-10-08 05:57 2026-10-08
#ranking #feature-fusion #normalization #deduplication #failure-modes

min-max 归一化把常数信号映成全零。一个本该有区分度、实际恒为常数的特征, 在加权融合里贡献恒为 0,权重写多大都没用,而且不会有任何报错。

// TABLE_OF_CONTENTS

结论先行

min-max 归一化把常数信号映成全零。一个本该有区分度、实际恒为常数的特征, 在加权融合里贡献恒为 0,权重写多大都没用,而且不会有任何报错。

def _normalize_signal(scores):
    lo, hi = min(v for _, v in scores), max(v for _, v in scores)
    if hi - lo <= 0:
        return {i: 0.0 for i, _ in scores}   # 静默变成死特征
    ...

融合之前给每个信号打一行 (min, max, 非零个数)。 常数信号要么去掉,要么修好。

为什么

一次实测。排序里有个信号叫"几个独立的检索策略同时找到了这篇论文", 权重 1.5,在自适应分支里还会提到 3.0——设计上是个强证据。 它恒为 1,因为去重合并的时候只合并了 sources 字段,忘了合并 found_by: 一篇被两个策略各自找到的论文,合并后策略归属只剩一边。

两处同时失效,这才是要点。 死特征的损失不止在排序里:

  1. 排序:归一化后全零,1.5 的权重白给,"多个策略同意"这条证据从没进过分数。
  2. 过滤:相关性地板上有一条保命条件是 len(found_by) >= 2——它永远不成立。 于是同时被两个策略找到的论文反而被删掉了,正是最该留下的那一类。

合并函数修好之后,30 篇结果里有 6 篇的 found_by 长度 ≥ 2。

普遍的形状:

去重/合并函数只合并作者当时想得起来的字段。 被漏掉的字段不报错,它承载的证据静默归零, 而下游可能有好几处正在依赖它——而且依赖点通常不在合并函数附近。

顺带一个容易搞混的点:常数信号的权重仍然留在分母里 (total = sum(weights)),所以它把所有论文的分数等比压低。 等比压低不改变排序,所以光看排序看不出来; 真正受害的是任何拿绝对分数比较的阈值。

怎么用

合并函数按字段清单写,不按记忆写。 对记录的每个字段明确写出合并语义:

语义 例子
取并集 sources、found_by、标签
取逻辑或 _pinned 这种指令标记
取最大 / 最新 引用数、抓取时间
胜者独占 题名、摘要、URL

写不出语义的字段,说明你不知道它是干什么的——去查,别默认"胜者独占"。 "整条记录按非空字段多者胜"是最常见的错误默认值:它会把败者独有的证据全扔掉。

三条可以直接加的断言:

  1. 合并后的记录,证据类字段的基数不小于任一输入。 len(merged.found_by) >= max(len(a.found_by), len(b.found_by))
  2. 每个融合信号在一次真实运行里方差不为零,否则打警告。 这条要跑真数据,单元测试里的两三条夹具通常凑不出方差。
  3. 阈值型判断要在合并之后跑一遍反例:造一条"被两路各找到一次"的记录, 断言它活过了过滤器。

同一族的另一面:换一种聚合方式排序会反号,见 标量摘要必须报告它的聚合方式——换一种加权,排序可能反号;默认回退不出声,见 指标的默认回退是静默的——而且 p 值不是效应量。

出处