一个知识库的搜索加了同义词表:"报销 → 费用/报账/核销"。上线后召回率涨了 12%,而"搜报销却返回一堆不相关内容"的投诉也涨了——因为"核销"在系统里另有一套流程(券核销),与"费用报销"完全不同。
查询扩展是"用精确性换召回"的经典手段。而它的边界在于:同义词表是按"词"建的,而用户要的是"概念"——词与概念之间并不一一对应。
1. 三种扩展方式
| 方式 | 效果 |
|---|---|
| 同义词(严格同义) | 召回↑、精确↓(小) |
| 近义/相关词 | 召回↑↑、精确↓↓ |
| 查询改写(模型) | 召回↑、可能失真 |
第二项的风险最大:"相关"是一个很宽的关系第二项的"扩张性"体现在"一个词能带出一串":"报销"→"核销"→"券"→"优惠券",于是"两步之后就完全无关"——而表里只记了一步。
,"报销"与"核销"在字典意义上不是同义,只在某些场景相关。
第三项的"另一种失真"是"改写会丢掉用户说的条件":把"2024 年的报销标准"简化成"报销标准",于是"版本混在一起"——而它看起来是"更好的查询"。
第三项的失真在于"改写会补全用户没说的条件"(如加年份、加地区),于是检索到不该检索的东西。
2. 同义词的三种"强度"
| 强度 | 例子 | 用法 |
|---|---|---|
| 完全等价 | 手机 / 移动电话 |
可双向扩展 |
| 上下位 | 发票 / 增值税专用发票 |
单向(下位→上位) |
| 相关 | 报销 / 核销 |
谨慎,需限定场景 |
第二项的方向很重要:"搜增值税专用发票"时扩展到"发票"是合理的(上位能覆盖),而反过来会让结果变宽。
第三项在"有歧义的领域"里最危险:"核销"在两个业务里含义不同,扩展后把两个业务的文档混在一起。第三项还有个"组织"层面的原因:两个业务各自建的词表,合并时只看到"同义词"的字面——而它们在各自语境里都对。
3. 双向 vs 单向扩展
| 方向 | 风险 |
|---|---|
| 双向 | 污染(A→B 与 B→A 都扩) |
| 单向(严格→宽泛) | 可控 |
| 只在"零结果"时扩展 | 最保守 |
第三项的"实现"很轻:一次判断(结果数 < 3),加上"第二次查询"。而它带来的收益是"精确查询零污染"——这正是扩展最容易被诟病的地方。
第三项是"性价比最高"的策略:先按原文检索,只有在"结果为空或极少"时才用扩展——于是"精确查询不被污染",而"表达不匹配"仍被救回。
// 三段式检索(保守扩展)
1 原文检索:命中 ≥ 3 条 → 直接返回(不扩展)
2 扩展检索:命中 < 3 条 → 加同义词再查一次
3 合并:原文结果优先,扩展结果作为补充(标注"相关")
// 收益:精确查询零污染;零结果仍有救
"扩展强度"可以分层开关,于是"同一个词表"能支持两种体验:
// 分层扩展(同一份词表,两种模式)
search:
expansion:
mode: on_zero_result # 默认:只在零结果时扩展
strict_only: true # 只用"完全等价"类
modes:
help_center: { strict_only: false } # 帮助中心:可用近义词(召回优先)
knowledge_base: { strict_only: true } # 知识库:只用严格同义(精确优先)
// 收益:一份表、两套策略,不必维护两份
4. 同义词表怎么维护
| 来源 | 质量 |
|---|---|
| 业务方提供 | 高(但少) |
| 从零结果日志里挖 | 高(真实需求) |
| 从点击日志里挖 | 中(有噪声) |
| 通用词典 | 低(不贴合业务) |
第二项最实用:**"用户搜了 X 没结果,然后改搜 Y 得到了结果"**第二项的"噪声"来自"Y 只是另一个说法":用户改搜 Y 可能是因为"Y 更短/更常见",而不代表"Y 与 X 同义"。所以候选要人工确认。
——这就是一条"X 应当扩展到 Y"的证据,而它是从真实行为来的。
第四项的"判据"是"这对词在你们的语料里是否指向同一批文档":指向同一批,才算同义;指向不同批,扩展就是污染。
第四项的问题在"领域差异":通用词典里"核销"就是"报销"的近义,而在你们的系统里它们是两个流程。
-- 从零结果日志里挖同义词(X 零结果 → Y 有结果,60 秒内)
SELECT z.q AS zero_q, n.q AS next_q, COUNT(*) c
FROM search_log z
JOIN search_log n ON n.user_id = z.user_id AND n.ts BETWEEN z.ts AND z.ts + 60
WHERE z.result_count = 0 AND n.result_count > 0
GROUP BY 1,2 ORDER BY c DESC LIMIT 50;
-- 输出即"候选同义词对",需人工确认(因为 Y 可能只是"另一个说法"而非同义)
5. 扩展的"代价"怎么衡量
| 指标 | 含义 |
|---|---|
| 召回率(扩展后) | 收益 |
| 前 10 条的"无关比例" | 代价 |
| 首条点击率 | 净效果 |
| 零结果率 | 最终目标 |
第三项最真实:如果"扩展后首条点击率下降",说明用户被噪声干扰了第三项的"分母"要注意:合并后的结果集变了,点击率的可比性下降。所以更稳的做法是"只看"原文查询的那部分用户"的表现"(分群对比)。
——而召回率的提升没有价值。
所以"要不要加同义词"应当看"首条点击率与零结果率"这对指标,而不是单看召回。
"扩展是否有效"要做 A/B,而不是"上线后看整体指标"——因为"整体指标"会被"查询分布变化"污染:
// A/B 设计(按 uid 分流,两组都可扩展/不可扩展)
A 组(50% 流量):不扩展
B 组(50% 流量):零结果时扩展
比:零结果率、首条点击率、平均结果数
// 判据:B 组"零结果率"下降且"首条点击率"不下降 → 值得上
6. 排错:五种"扩展带来的问题"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 结果变多但更差 | 近义词扩得太宽 | 只保留严格同义 |
| 跨业务混在一起 | 同义词跨领域 | 加"业务域"限定 |
| 精确查询被污染 | 无条件扩展 | 改"零结果时扩展" |
| 维护不动 | 表太大 | 只维护高频对 |
| 零结果率没降 | 扩展词不在语料里 | 补内容而非加词 |
第五项的"识别"是"扩展后仍然零结果":这说明"语料缺内容",而不是"词不匹配"——而修法完全不同(补文档 vs 加词表)。
第五项是"方向错误":如果语料里根本没有这个概念,扩展同义词也不会命中——此时该"补文档",而不是"加词表"。
最后一个"代际"考虑:词表是"静态补丁",而向量检索是"动态覆盖"。如果已有向量检索,同义词表主要用来"补向量也搞不定的缩写与简称"(如"公积金"→"住房公积金")。
// 三种手段的分工(不是替代)
同义词表:缩写、简称、历史叫法(静态、可控、零延迟)
向量检索:表述完全不同但语义相同(动态、需算力)
查询改写:多轮对话里的省略补全(需模型)
7. 边界:什么情况不必扩展
| 场景 | 为什么 | 做法 |
|---|---|---|
| 语料术语统一(内部规范) | 用户也用同一术语 | 不扩展 |
| 精确检索(编号、错误码) | 扩展会引入噪声 | 不扩展 |
| 已有向量检索兜底 | 语义匹配已覆盖 | 不扩展 |
| 查询量大但零结果少 | 收益有限 | 不扩展 |
判断口径一句话:"用户的说法"与"语料的说法"是否经常不一致。最后一条与"成本"有关的边界:词表的维护成本随"业务域数量"增长。所以"多业务共用一个词表"通常比"每域一张小表"更难维护——而后者还能避免跨域污染。
经常(口语 vs 制度术语),扩展必要;一致(都用同一套术语),扩展只是引入噪声。
8. 自测
- 三种扩展方式里,哪一种风险最大?为什么"相关"是一个很宽的关系?
- 同义词的三种强度里,哪一种"方向"最重要?举一个只能单向扩展的例子。
- 为什么"只在零结果时扩展"性价比最高?它保护了什么?
- "从零结果日志里挖同义词"为什么比通用词典可靠?
- 为什么"只看召回率"是错的?该看哪两个指标?
- 判断"是否需要扩展"的那个问题是什么?