跳至内容
15.1 先定义检索任务与评估集

15.1 先定义检索任务与评估集

如果没有“哪些结果算相关”的外部判断,搜索系统只能证明自己能够执行。 返回三行、命中一个熟悉商品、甚至计划使用了索引,都不等于检索质量好。

本节先冻结问题。第 15.2–15.5 节才允许比较实现。

15.1.1 精确筛选、词法相关与语义相似

先问是在判断资格,还是比较相关

下面两个 SQL 看起来都在“找商品”,合同完全不同:

-- 布尔资格:结果要么符合,要么不符合
SELECT product_id, title
FROM product
WHERE active
  AND category = 'outdoor'
  AND price < 500;

-- 排名:每个候选都有先后
SELECT product_id, title
FROM product
WHERE active
  AND category = 'outdoor'
ORDER BY relevance_score DESC, product_id
LIMIT 10;

前者是 filtering。正确性来自布尔谓词、约束、权限和事务快照;B-tree、 hash、BRIN 或分区裁剪可能让它更快,但不会改变逻辑答案。

后者是 retrieval/ranking。正确性至少包含:

  • 相关对象是否进入候选集;
  • 不相关对象是否被抑制;
  • 最相关对象是否排得足够靠前;
  • 同分时结果是否稳定;
  • 过滤是否在正确阶段生效;
  • 结果是否符合当前用户的权限与业务状态。

不要把两个问题压成一个“不透明相关性 SQL”。先用可验证谓词确定资格,再在 合格集合中比较相关性,最容易推理。

四种相似不是一回事

以商品检索为例:

输入与目标更接近的能力例子
SKU、状态、类别完全相等精确过滤sku = 'AUD-001'
词形、布尔词、短语与字段权重全文词法检索headphones 匹配词位
字符局部重合与错拼trigram 模糊匹配wireles hedphones
模型编码的意图相近向量相似music on the go

词法相关不等于字符串包含。PostgreSQL FTS 会把文本解析为 token,再经 词典归一为 lexeme;英语配置能把 rats 归一成 rat,也可能去掉 stop word。它支持 AND、OR、NOT、短语和位置,适合“文档含有哪些有意义的词”。

字符串相似不等于语言含义。pg_trgm 按三个连续字符的集合衡量重合, 所以对局部拼写错误很有效;databasedatabse 很近,但字符很近并不 保证商品意图相同。

向量相似也不是数据库自动理解语义。数据库只看一串数和一个距离函数; “这串数表达什么”来自模型、输入模板、截断、版本和归一化。手工坐标也能 做向量近邻,但不能称为训练模型的语义质量证据。

因此“语义检索”至少应展开为:

model identity
+ input construction
+ preprocessing/tokenization
+ vector dimension
+ normalization
+ distance/operator
+ relevance evaluation

缺任意一项,未来都很难解释同一文本为何得到不同结果。

把任务写成合同

一份可执行任务定义至少回答:

document_unit: one product row
searchable_fields: [title, description]
eligible_if:
  active: true
  category: query.category_filter
query_language: English
top_k: 3
tie_breaker: product_id ascending
relevance_scale: 0..3
latency_scope: not established by this fixture

本章没有把价格、库存、租户或行级权限塞进夹具,但生产合同不能省略。尤其是 租户与 ACL:一个相关性极高却无权读取的对象不是“排名较低”,而是根本不能 进入候选集合。

先写反例

本章八个查询不是八种同义表达,而是有意覆盖失败模式:

查询想观察什么
wireless headphones精确商品词
wireles hedphones两处错拼
music on the go描述性意图
coffee bean grinder标题与描述共同提供词
make espresso at home任务表达
trail hydration类别过滤与多候选
postgre databse tuning技术词错拼
semantic nearest neighbor长尾精确术语

如果评估集只有实现者已经看过的成功查询,它只会确认实现者的直觉。先把必然 失败的查询写进去,后续比较才有信息量。

15.1.2 查询、候选、排序和过滤的分层

一条检索链有六个阶段

raw request
  -> query parsing / normalization
  -> hard filters and authorization
  -> candidate generation
  -> per-source ranking
  -> fusion / re-ranking
  -> top-K response and evidence

每一层有不同的失败:

阶段典型失败
查询解析非法语法、空 query、极长输入、语言选错
硬过滤跨租户泄漏、停用对象复活、过滤放到 ANN 之后导致不足 K
候选生成FTS 零召回、模糊门槛太低、ANN 漏召回
单路排序字段权重错、距离函数错、同分漂移
融合/重排分数尺度乱加、弱源挤掉强源、重排器超时
响应无稳定游标、证据不可解释、缓存越权

分层不是为了制造更多组件,而是为了让每个结论可以单独测。

查询解析必须显式

生产接口通常接收 raw text。不要让客户端偷偷决定它是:

  • 所有词必须出现;
  • 任一词出现;
  • 完整短语;
  • 支持引号与排除词;
  • 前缀查询;
  • 还是严格 tsquery 表达式。

本章显式使用:

websearch_to_tsquery(
  'pg_catalog.english'::regconfig,
  raw_query
)

它接受普通 web 风格文本、引号、OR 和减号排除,并且官方文档说明不会因 输入语法抛错。这降低了语法层事故,但不代表可以无限接受输入:长度、token 数、超时、并发和滥用仍要在接口层设限。

先过滤还是先 ANN,要写出物理含义

逻辑目标是:

WHERE active
  AND category = 'outdoor'
ORDER BY embedding <-> :query_vector
LIMIT 3;

精确执行可以枚举所有合格行再排序。近似索引通常先沿图或倒排结构取候选, 然后应用 PostgreSQL 过滤;当过滤选择性高时,扫描出的近邻大多被过滤掉, 最终可能不足三行。

所以“SQL 把 WHERE 写在 ORDER BY 前面”不证明物理上先过滤。要看:

  • 执行计划;
  • 过滤选择性;
  • 返回行数;
  • ANN 与 exact 的集合差异;
  • ef_search、iterative scan、partial index 或 partition 策略。

本章的 HNSW filtered plan 明确显示:

Index Scan using product_search_embedding_hnsw_idx
  Filter: (active AND category = 'outdoor')

它证明过滤发生在 HNSW 扫描结果上。hnsw.iterative_scan = 'strict_order' 能在过滤后不足时继续扫描,仍受最大扫描限制和数据分布影响, 不是“保证召回”的开关。

候选深度与返回深度不是一个 K

若最终返回 3 个结果,每路只取 3 个候选,融合器没有纠错空间。常见设计是:

lexical top  N_l
fuzzy   top  N_f
vector  top  N_v
        -> union/deduplicate
        -> fusion or re-rank
        -> final top K

本章每路取前 4,最终取 3,只为保持 SQL 可读。生产候选深度应通过质量/延迟 曲线决定,不能照抄 4

候选源还必须带上:

query_id
document_id
source
source_rank
source_score_or_distance
filter/version/model identity

否则融合以后只剩一个总分,无法追问“它为何出现”。

排名必须确定

浮点分数可以相同,近似索引也可能在近似等距对象间选择不同结果。所有实验 查询都追加稳定 tie-breaker:

ORDER BY score DESC, product_id;

这不会让近似算法变成精确算法,却能消除同分的随机输出,让回归测试和分页 至少有明确顺序。线上游标还要把排序键全部编码进去,不能只按一个不唯一分数 翻页。

过滤正确性优先于相关性

本章商品 17:

Legacy Wireless Earbuds
active = false
embedding = [0.95,0,0,0]

它对两个 audio 查询非常接近,是专门布置的 canary。verify.sql 断言它 不能出现在全文、模糊、精确向量或混合的任何排名中。若出现,实验立即失败, 即使平均 NDCG 更高也不能发布。

这条原则可推广为:

authorization / tenant / lifecycle correctness
beats relevance score

15.1.3 建立带人工相关性标签的查询集合

三份输入,三个不同身份

本章把输入拆成:

数据库中对应:

product_search(product_id, ..., embedding, search_document)
eval_query(query_id, raw_query, category_filter, embedding, intent)
relevance_judgment(query_id, product_id, grade, rationale)

把查询与标注分开很重要:查询是实际输入分布,标注是人对业务意图的判断。 模型和检索器都不能改写标注来让自己得分更高。

先定义标注等级

本章使用 1–3 级正相关:

grade含义
3直接满足意图,是理想结果
2明确相关,但不是最佳
1可接受的邻近结果
0/无行未标注为相关

每条标注都有 rationale,例如:

q06,7,3,water bottle exactly serves trail hydration
q06,8,2,backpack includes a hydration sleeve
q06,9,1,water filter is related to outdoor water needs

理由不是装饰。两位标注者发生分歧时,没有理由就无法判断是规范模糊、文档 信息不足,还是标注错误。

真实项目应进一步规定:

  • 标注者是否知道检索器输出;
  • 一个查询由几人独立标注;
  • 分歧怎样仲裁;
  • 未展示对象是“不相关”还是“未判断”;
  • 如何抽样候选池,避免只标注现有系统召回到的对象;
  • 是否按用户群、语言、地区、设备与时间分层;
  • PII、敏感类别与数据保留规则。

如果只标注旧检索器的候选,新方法召回的新对象会被误当成零相关,这叫 pooling bias。

四个指标回答四个问题

令前 (K) 个结果为 (R_K),相关集合为 (G)。

Precision@K:

[ P@K = \frac{|R_K \cap G|}{K} ]

它问“展示位有多少是相关的”。本章即使某策略只返回一行,也仍除以 3; 空位会损失质量,避免系统靠少返回来虚增 precision。

Recall@K:

[ R@K = \frac{|R_K \cap G|}{|G|} ]

它问“已知相关对象覆盖了多少”。本章每个查询恰好有 3 个相关对象,因此 Precision@3 与 Recall@3 数值相同;这是夹具结构的巧合,不是两个指标等价。

MRR@K:

[ MRR@K = \frac{1}{|Q|} \sum_{q \in Q} \begin{cases} 1 / rank_q, & rank_q \le K \ 0, & \text{otherwise} \end{cases} ]

它只关心第一个相关结果多靠前,适合“用户通常点第一个可用答案”的任务。 多个高质量结果的次序差异不会充分反映在 MRR 中。

NDCG@K 使用分级相关性:

[ DCG@K = \sum_{i=1}^{K} \frac{2^{grade_i}-1}{\log_2(i+1)} ]

[ NDCG@K = \frac{DCG@K}{IDCG@K} ]

它奖励高等级对象靠前,并以理想次序归一。本章用 NDCG 暴露了“向量找全, 次序却不理想”以及“混合在某个错拼查询上变差”。

指标不是越多越科学。先从用户行为选择指标:

任务更重要的信号
唯一答案/导航MRR、Success@K
商品列表前三屏NDCG、Precision@K、业务转化
法务/安全材料发现Recall@K、漏召回审计
ANN 服务路径exact-relative recall + latency

冻结不等于永久

fixture-manifest.json 固定:

corpus SHA-256
query SHA-256
judgment SHA-256
loader SHA-256
row counts
model id + dimension + method
text/vector license boundary

自动化执行:

cmp frozen-corpus.csv evidence/corpus.csv
cmp frozen-queries.csv evidence/queries.csv
cmp frozen-judgments.csv evidence/judgments.csv

数据库多一个空格、少一条标注、换一个向量都会被发现。

但冻结集只是一个版本。遇到以下变化应生成新版本,而不是覆盖旧 golden:

  • 真实查询分布改变;
  • 商品/文档类型扩展;
  • 模型、模板或预处理改变;
  • 语言配置与词典改变;
  • 人工标注规则改变;
  • 融合目标或业务约束改变。

发布报告要同时写:

quality delta on frozen regression set
+ quality on fresh holdout set
+ online behavior under guarded experiment

只在一个反复调参的集合上提高,会逐渐把参数拟合到测试集。

本章验收

运行:

PG36_EVIDENCE_DIR="$PWD/evidence/ch15" \
  ./static/labs/ch15/task.sh evaluate

审校器要求:

17 corpus rows
8 query rows
24 judgment rows
all three exports byte-identical
every query has exactly three positive judgments
product 17 inactive and absent from every ranking

到这里还没有决定用哪种检索器。我们只是让后续每个决定都有同一把尺。


返回本章目录 · 下一节:PostgreSQL 全文检索 · 查看全书目录 · 查看索引中心

最后更新于