当前位置: 首页 > news >正文

【lucene核心】impacts的由来

在 Lucene 的 Impact 概念(出现在 `ImpactsEnum` / `Impact` 对象里)中:

字段 含义 

freq 当前 term 在该文档中出现了多少次(即词频 term frequency)。 

norm 当前 文档在该字段中的长度因子(即之前 norms 里保存的压缩长度值,用来归一化打分)。 

---

举个例子

文档 7:

- 字段 `title` 内容:`"lucene lucene search"`  

- 分词后:`lucene`(2 次)、`search`(1 次)

- 对 term `"lucene"` 的 `Impact`  

  - freq = 2(出现了 2 次)  

  - norm = 压缩后的字段长度 3(3 个 token)

---

一句话

- freq → term 在文档里的出现次数  

- norm → 文档在该字段的总 token 数(压缩后的长度因子)

/** Get the set of competitive freq and norm pairs, ordered by increasing freq and norm. */

  public Collection<Impact> getCompetitiveFreqNormPairs() {

    List<Impact> impacts = new ArrayList<>();

    int maxFreqForLowerNorms = 0;

    for (int i = 0; i < maxFreqs.length; ++i) {

      int maxFreq = maxFreqs[i];

      if (maxFreq > maxFreqForLowerNorms) {

        impacts.add(new Impact(maxFreq, (byte) i));

        maxFreqForLowerNorms = maxFreq;

      }

    }

 

    if (otherFreqNormPairs.isEmpty()) {

      // Common case: all norms are bytes

      return impacts;

    }

 

    TreeSet<Impact> freqNormPairs = new TreeSet<>(this.otherFreqNormPairs);

    for (Impact impact : impacts) {

      add(impact, freqNormPairs);

    }

    return Collections.unmodifiableSet(freqNormPairs);

}

这段代码的作用是:

把当前 term 所有可能出现的 “最大词频 + 对应 norm” 组合,按 freq 升序、norm 升序整理成一条紧凑的 “上限表”(Impact 列表),

供 Block-Max WAND 等跳表算法 在查询时快速判断 “剩下这些文档再也不可能超过当前阈值”,从而提前剪枝、加速 Top-k 检索。

---

1. 背景:为什么要 “competitive pairs”  

在 Block-Max WAND / MaxScore 算法里,必须知道 “一个块内最大可能的 score 是多少”。  

- 已知 score 只跟 freq(词频)和 norm(字段长度)有关。  

- 因此把 每个 norm level 对应的最大 freq 记录下来 → 形成 `(freq, norm)` 的上限数组。  

- 只要 当前阈值 大于块内最大的 `(freq, norm)` 上限,整个块即可跳过。

---

2. 代码拆读

2.1 先把 “简单情况” 处理掉  

```java

List<Impact> impacts = new ArrayList<>();

int maxFreqForLowerNorms = 0;

for (int i = 0; i < maxFreqs.length; ++i) {

  int maxFreq = maxFreqs[i]; // 对 norm = i 的最大词频

  if (maxFreq > maxFreqForLowerNorms) {

    impacts.add(new Impact(maxFreq, (byte) i));

    maxFreqForLowerNorms = maxFreq; // 保证单调递增

  }

}

```

- `maxFreqs[i]` 保存的是 “norm 为 i 的所有文档里,该 term 出现的 最大频次”。  

- 通过条件 `maxFreq > maxFreqForLowerNorms` 去掉冗余,确保列表严格按 `(freq ↑, norm ↑)` 排序。

2.2 再合并 “其他 norm”  

```java

if (otherFreqNormPairs.isEmpty()) {

  return impacts; // 全是 byte 值,直接返回

}

TreeSet<Impact> freqNormPairs = new TreeSet<>(otherFreqNormPairs);

for (Impact impact : impacts) {

  add(impact, freqNormPairs);

}

return Collections.unmodifiableSet(freqNormPairs);

```

- `otherFreqNormPairs` 里存的是非 byte 型 norm(罕见)或额外手工添加的组合。  

- 用 `TreeSet` 统一排序、去重,最终得到 全局有序且无冗余 的 competitive 列表。

---

3. 一句话总结  

`getCompetitiveFreqNormPairs()` 生成的就是 “score 上限表”(Impact 列表),

查询阶段用它来 快速判定某个文档块是否还有竞争力,从而实现 Block-Max WAND 的提前剪枝与加速。

不是“随着 norm 的增加取最大的 freq”那么简单,而是:

> 对每个 norm level,记录该 level 内出现的最大 freq,然后在所有 level 上再做一个“剪枝”:只有当某 level 的 maxFreq 比之前所有更低 level 的 maxFreq 都大时,才保留这个 (maxFreq, norm) 对。

这样做的目的:

- 保证最终列表 按 freq 严格递增,  

- 保证 norm 也单调递增,  

- 让 Block-Max WAND 在“从低打到高”的顺序里,能直接用 最大 freq 作为上限 来剪枝。

---

举个具体数字

假设:

norm (i) 该 norm 内最大 freq 

0 3 

1 5 

2 4 

3 6 

代码流程:

- i=0 → maxFreq=3 > 0 → 保留 (3, 0)  

- i=1 → maxFreq=5 > 3 → 保留 (5, 1)  

- i=2 → maxFreq=4 ≯ 5 → 丢弃  

- i=3 → maxFreq=6 > 5 → 保留 (6, 3)

最终 competitive 列表:

`(3,0), (5,1), (6,3)` —— 既 freq 递增,norm 也递增。

level 就是 norm 本身的整数值(byte 或 int),

在代码里直接体现为循环变量 `i` 和 `Impact` 构造器里的第二个参数 `norm`:

```java

for (int i = 0; i < maxFreqs.length; ++i) { // i 就是 level

    ...

    impacts.add(new Impact(maxFreq, (byte) i)); // (byte) i 就是 norm level

}

```

- `maxFreqs` 数组的下标 `i` 就对应 norm level 0、1、2 …。  

- 因此 level 没有额外存储,它就是 norm 的数值本身。

这是一种 “分层/分级上限(level-wise upper-bound)思想”,

 

在 Lucene 里具体对应 Block-Max WAND / MaxScore 这类 Top-k 查询剪枝算法。

 

---

 

1. 核心思想一句话  

 

> 把 每级(level = norm 值)内的最大可能得分 提前算成一张 单调递增的上限表,

 

搜索时 按表从小到大扫描,一旦发现剩余块的上限已经低于当前第 k 个结果的真实得分,就整段跳过,不再计算。

 

---

 

2. 算法家族

 

名称 出处 说明 

WAND Broder et al. 2003 经典倒排链跳跃算法 

Block-Max WAND Ding & Suel 2011 在倒排链的“块”内预存上限,块级跳过 

MaxScore Turtle & Flood 1995 类似思路,用于 Lucene 早期 

Lucene 8+ 实现 `ImpactsEnum` + `Impact` 把 norm-level 上限 预生成,驱动 Block-Max WAND 

 

---

 

3. 为什么要“分层”(level = norm)

 

- norm 越小 → 字段越短 → 相同词频的得分越高  

- 因此把 norm 离散化成若干 level,每级算一次 max freq  

- 对 Block-Max WAND:

 

  只要当前阈值 > level L 的 maxScore,所有 norm ≥ L 的文档都可以一次性跳过,节省大量 I/O 与计算。

---

4. 一句话总结

这段代码体现的算法思想就是 “Block-Max WAND 的分层上限剪枝”,

通过 norm level + max freq 生成单调递增的 Impact 表,实现 Top-k 查询中的早期终止与跳跃。

在 同一 term、同一词频(freq) 的情况下:  

- norm 越小 → 字段越短 → 该 term 的权重越大 → 得分越高。  

因此,  

> 在 `Impact` 表中,相同 freq 时,norm 越小,对应的潜在得分反而越高。  

这也是 Block-Max WAND 把 `(freq, norm)` 按 freq 升序 + norm 升序 排序后,就能用单调上限剪枝的根本原因:  

- 先处理 低频或长字段(高 norm)的块,它们的得分上限低,容易跳过;  

- 后处理 高频或短字段(低 norm)的块,上限高,需要真正计算。

对于impacts 跳表的高层是低层impacts的和是吧?

不是简单的“求和”关系,而是 “高层 impact 是对自己管辖区间内所有低层 impact 的最大值的上界”。

- 高层(higher level) 的 `Impact` 只是 该层所覆盖文档块内最大的 `(freq, norm)` 对。  

- 它保证:

  任何落在这个块里的文档,其真实 `(freq, norm)` 都不会超过这条高层记录的 `(freq, norm)`。  

- 因此搜索时可以 用高层 impact 直接做块级剪枝,而不必逐条遍历低层。

---

类比

- 高层 impact ≈ 块级“最大值”  

- 低层 impacts ≈ 块内真实值  

- 关系是 “max 覆盖”(max bound),而非 “累加”(sum)。

剪枝流程可以拆成 两步:

1. 高层剪枝:一次跳过整块  

2. 低层剪枝:在块内再逐条跳过

---

1. 高层剪枝(先发生)

- 搜索线程维护一个 当前第 k 名的阈值 `minCompetitiveScore`。  

- 从 高层 Impact 表 顺序扫描 `(maxFreq, maxNorm)`。  

- 把 `(maxFreq, maxNorm)` 代入 BM25 公式得到一个 “这块的最高可能得分” `upperBoundScore`。  

- 如果 `upperBoundScore < minCompetitiveScore` → 整块 4 篇文档直接丢弃,指针跳到下一高层块。

```

高层表顺序:(3,0) (5,2) (6,0) (7,1) …

            ↑

            只要这个上限 < 阈值,整块 0-3 全跳过

```

---

2. 低层剪枝(块内发生)

- 如果 `upperBoundScore ≥ minCompetitiveScore`,说明 块里可能还有候选,就进入该块。  

- 用低层的 `ImpactsEnum` 逐条读真正的 `(freq, norm)`;  

- 每读一条就 立即计算真实得分;  

- 一旦 真实得分 < minCompetitiveScore,就利用低层 跳表指针 跳到下一个可能大于阈值的文档,继续比较。

---

流程小结

```

高层 Impact 表

├─ block0 上限 < 阈值 → 整块跳过

├─ block1 上限 ≥ 阈值 → 进入块内

│ ├─ doc4 (7,1) 得分 ≥ 阈值 → 保留

│ ├─ doc5 (2,2) 得分 < 阈值 → 跳到下一条

│ └─ …

└─ …

```

高层 先快速丢大段,低层 再精准丢小段,这就是 Block-Max WAND 的剪枝节奏。

第 0 层(最底层)跳表也是以 block 为单位来剪枝的 —— 只不过这里的 block 已经是最细粒度的 “文档块”,而不是高层那种更大的逻辑块。

第 0 层的剪枝规则:

- 第 0 层的每个 block 会提前计算一个 该块内最大的 `(freq, norm)` 对(即该块内最高的 Impact)。  

- 这个 最大值 被用来估算 该 block 的最高可能得分 `upperBoundScore`。  

- 只要这个 `upperBoundScore < minCompetitiveScore`,整个 block 就可以直接跳过;  

- 否则,就必须逐文档处理(因为块内可能有文档得分 ≥ 阈值)。

---

用之前的例子具体说明:

假设第 0 层有 4 个 block(每块 4 篇文档):

第 0 层 block 该块最大 Impact 估算最高得分 

block-0 `(5,2)` `score_upper0` 

block-1 `(7,1)` `score_upper1` 

block-2 `(6,0)` `score_upper2` 

block-3 `(4,3)` `score_upper3` 

- 如果 `minCompetitiveScore = 6.5`,而:

  - `score_upper0 = 5.1 < 6.5` → 整 block-0 直接跳过  

  - `score_upper1 = 7.2 ≥ 6.5` → 必须逐文档处理 block-1  

  - `score_upper2 = 6.0 < 6.5` → 整 block-2 直接跳过  

  - `score_upper3 = 4.8 < 6.5` → 整 block-3 直接跳过

---

一句话总结

第 0 层同样以 block 为单位做剪枝;

只要某个 最底层 block 的最高可能得分 低于当前阈值,就整块跳过,否则逐文档处理。

private float computeMaxScore(List<Impact> impacts) {

    float maxScore = 0;

    for (Impact impact : impacts) {

      maxScore = Math.max(scorer.score(impact.freq, impact.norm), maxScore);

    }

    return maxScore;

  }

这段代码的用途一句话就能说清:

> 把一个 impacts 列表里所有 `(freq, norm)` 对的“最高分”算出来,作为该段(或该 block)的得分上限。

---

具体过程

1. 遍历列表中的每一条 `Impact`  

2. 用 scorer 的公式当场算出这条 `(freq, norm)` 的得分  

3. 取最大值 `maxScore` 返回

---

在算法流程中的位置

- 上层(高层)

  先调 `computeMaxScore(impacts)` 得到 `maxScore`,

  然后与当前 第 k 名的阈值 `minCompetitiveScore` 比较:  

  - `maxScore < minCompetitiveScore` → 整块/整段直接跳过  

  - 否则才继续逐文档精确打分或更细粒度剪枝。

---

注意点

- 这里只是 “估算” 上限,不会真的去遍历文档。  

- 由于 `score(freq, norm)` 是单调函数,列表里最大的 `(freq, norm)` 必然给出最大分,所以只需要比较一次即可。

http://www.dtcms.com/a/361109.html

相关文章:

  • 【Web安全】CRLF注入攻击深度解析:原理、场景与安全测试防御指南
  • hive表不显示列注释column comment的问题解决
  • 【Proteus仿真】蜂鸣器控制系列仿真——蜂鸣器控制/蜂鸣器播放音乐/蜂鸣器播放多种音乐/蜂鸣器和LED组成报警装置
  • UE5 C++ 第三方动态库的使用
  • 【数据库】openGauss 6.0 单机自动化安装最佳实践
  • MTK-Android13-实现拷贝预置资源到vendor分区下
  • Java全栈学习笔记27
  • 深度解析条件编译:#ifdef与#ifndef的本质区别与应用实践
  • Dify中使用SearXNG
  • 子串:滑动窗口最大值
  • Macbook Air M4 笔记本 ChatTTS 初体验
  • 总线矩阵的原理
  • 番外篇 | YOLO-FireAD:通过注意力逆残差模块与双池化模块融合实现高精度火灾检测
  • GitHub CLI (gh) 全面指南:终端中的 GitHub 工作流革命
  • 前端页面性能优化
  • JavaScript 性能优化实战技术
  • 99、23种设计模式之组合模式(8/23)
  • Map + 函数式接口的策略模式
  • 控制系统仿真之PID校正-利用PID控制器、PID调节器实现(九)
  • Coze源码分析-工作空间-项目开发-后端源码
  • Python爬虫实战:研究 Lines, bars and markers 模块,构建电商平台数据采集和分析系统
  • 【软件开发工程师の校招秘籍】
  • nginx-realip问题解决方案
  • AI 智能体架构中的协议设计三部曲:MCP → A2A → AG-UI
  • 基于单片机宠物项圈/宠物防丢失设计
  • VMware pro16(许可证)+centos 7超详细安装教程
  • Go语言入门学习笔记
  • 如何将照片从电脑传输到安卓设备
  • GitHub 宕机自救指南:应急解决方案与替代平台
  • LeetCode 165. 比较版本号 - 优雅Java解决方案