你有没有过这样的经历:在搜索框输入一个词,结果却完全不是自己想要的。比如搜“苹果”,可能想查iPhone新机,结果满屏都是水果种植技术。这时候,如果你身边有位懂行的服务员,他听了“苹果”两个字,会先问一句:“您是想要手机,还是想买水果?”然后麻利地给你端来一桌候选——iPhone、Mac、水果礼盒。这个“懂你的服务员”,就是今天的主角:查询扩展。 查询扩展是搜索系统里的一项核心技术:当用户输入简短的查询词时,系统自动从同义词表、历史反馈、词向量空间中找来一批相关词,和原始查询拼在一起,再去检索。这样一来,原本搜不到的隐藏文档就能被捞出来,召回率显著提升。它就像服务员手里那本“黑话词典”——你说“辣的”,他听懂你想吃川菜,还顺手推荐了水煮鱼和麻婆豆腐。别小看这个动作,搜索引擎、电商搜索、企业知识库都在靠它“更懂你”。

岗位关联

掌握查询扩展,你就能直接提升搜索系统的召回率与用户体验。无论做搜索引擎、电商检索还是 RAG 知识库,这都是召回环节的必备技能,也是搜索算法工程师面试的高频考点。

目录
  1. 可视化演示
  2. 概念原理:点菜服务的第一步
  3. 同义词扩展:服务员的“黑话词典”
  4. 伪相关反馈:看反应再加菜
  5. 词嵌入扩展:食材性格图谱
  6. 改写区别:复述菜单 vs 加菜
  7. 召回评估:上菜率怎么算
  8. 实践应用:把“懂你”做成标准流程
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示同义词扩展、伪相关反馈PRF和词嵌入语义扩展三种查询扩展方法,帮助理解搜索引擎如何通过扩展查询提升召回率。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+captions.synonym+'

概念原理:点菜服务的第一步

想象你走进一家餐厅,只说了句“随便来点好吃的”。服务员如果只端上一道菜,你大概率会失望——因为“好吃的”这个词太模糊了,它可能指口味、食材、做法,或者是招牌菜。聪明的服务员会怎么做?他会在你原话的基础上,补充几个可能的方向:招牌菜、当季特色、大众点评高分菜,然后一起端上来让你选。这个过程,就是查询扩展的缩影:保留你的原始意图,再帮你多找几条候选路径。

在信息检索里,查询扩展(Query Expansion)做的正是这件事。用户输入的查询词往往只有两三个字,比如“苹果”,但文档库里可能有一万种“苹果”的写法:iPhone、MacBook、红富士、Apple Inc.……仅仅用“苹果”两个字去匹配,大量相关文档会被漏掉。查询扩展的思路是:在原始查询的基础上,通过同义词表、检索反馈或词向量模型,自动生成一批相关词,让检索系统有更多“钩子”去文档库里捞鱼。

一次标准的查询扩展流程分为三步:先接收原始查询,再调用扩展策略生成扩展词集合,最后用扩展后的查询去执行检索并融合打分。常见扩展策略有三种——同义词扩展、伪相关反馈、词嵌入扩展,接下来的章节会逐一展开。

回到餐厅的场景:原始查询是客人那句“随便吃点”,扩展函数是服务员脑子里的菜谱知识,并集运算则是把客人原话和候选菜名放在同一张菜单上。查询扩展的价值正在于此——它不改变你的需求,只是帮你多留几扇门。

Q′=Q∪E(Q)\displaystyle Q' = Q \cup E(Q)
这个公式的意思是:扩展后的新查询集合 Q',等于原始查询集合 Q 并上扩展函数 E 生成的相关词集合。
  1. 原始查询集合用户输入的全部词构成初始查询集合,它是所有扩展动作的起点。
    Q={w1,w2,…,wn}\displaystyle Q = \{w_1, w_2, \dots, w_n\}
  2. 扩展函数从同义词表、反馈文档或词向量空间中派生相关词。
    E(Q)={v∣v∈Synonym(Q)∪PRF(Q)∪Emb(Q)}\displaystyle E(Q) = \{v \mid v \in \mathrm{Synonym}(Q) \cup \mathrm{PRF}(Q) \cup \mathrm{Emb}(Q)\}
  3. 合并去重把原始词与扩展词合并去重,得到规模更大、召回能力更强的查询集合。
    Q′=Q∪E(Q)\displaystyle Q' = Q \cup E(Q)

同义词扩展:服务员的“黑话词典”

店里来了位客人说:“我要吃辣的。”经验丰富的服务员立刻在脑海里翻出一本词典:“辣的”= 川味 = 重口味 = 香辣 = 麻辣。他写上菜单的不仅是“辣”这个字,还有一整套“辣的家族”。这本词典,在检索系统里就是同义词表。

同义词扩展是最经典、最容易落地的扩展方式。它的做法很简单:离线维护一张同义词表,记录词语之间的同义或近义关系;在线查询时,把用户查询中的每个词拿去查表,命中就把同义词也加入查询。比如搜“连衣裙”,同义词表里有“长裙”、“雪纺裙”,检索时这几个词一起去匹配文档,召回覆盖面立刻扩大。

同义词表从哪里来?主要有三种构建途径。其一是人工编纂,标注人员把搜索词和商品词、规范词对齐,质量高但成本大;其二是从用户点击日志中挖掘——用户搜“苹果”后大量点击了"iPhone"标题的页面,这两个词就会被关联起来;其三是借助词嵌入聚类,把语义相近的词自动归组,再辅以人工审核。然而同义词扩展也有明显的局限:词典的覆盖永远追不上新词热词的出现速度,而且一旦同义词选用不当,就会把查询带偏,反而拉低效果。

服务员的“黑话词典”需要定期更新——新菜式、新流行语要随时补录;检索系统的同义词表同样需要多路信号持续迭代。词典越厚,服务员越懂客,搜索系统也就越懂用户。

Esyn(w)={v∣v∈Synonym(w)}\displaystyle E_{\mathrm{syn}}(w) = \{v \mid v \in \mathrm{Synonym}(w)\}
这个公式定义了“查词典”动作:对词 w 而言,它的同义词扩展集合就是同义词表中 w 对应的所有近义词。
  1. 查表取词在既有的同义词表中,找到与查询词 w 语义相同或相近的条目。
    Synonym(w)={s1,s2,…,sk}\displaystyle \mathrm{Synonym}(w) = \{s_1, s_2, \dots, s_k\}
  2. 生成候选集把表中所有条目取出,构成 w 的同义词扩展集合。
    Esyn(w)={v∣v∈Synonym(w)}\displaystyle E_{\mathrm{syn}}(w) = \{v \mid v \in \mathrm{Synonym}(w)\}
  3. 全查询合并对查询中的每个词分别查表扩展,再与原始查询合并,得到最终扩展查询。
    Q′=Q∪⋃w∈QEsyn(w)\displaystyle Q' = Q \cup \bigcup_{w \in Q} E_{\mathrm{syn}}(w)

伪相关反馈:看反应再加菜

服务员上了一桌菜,客人拿起水煮鱼连吃三筷,又夹了两块辣子鸡。服务员看在眼里,转身对后厨说:“再上两份偏辣、重油的菜!”他没有问客人“您觉得刚才的菜如何”,而是从客人的“反应”里自动读出了口味偏好。这就是伪相关反馈的直觉。

伪相关反馈(Pseudo-Relevance Feedback,简称 PRF)的核心假设是:先用原始查询做一轮检索,排名靠前的文档大概率与用户需求相关。于是系统把这前 N 篇文档当作“相关反馈”(故称“伪”),从这些文档中提取高频关键词,补充进原始查询,再做第二轮检索。整个过程不需要用户有任何额外操作,完全自动完成。

具体流程分四步:第一轮用原始查询检索,取回 Top-N 文档;假设这些文档相关;从这些文档中挑出区分度高的词(通常是 TF-IDF 加权后得分高的词);把挑选出的词追加到原始查询中,执行第二轮检索。经典算法是 Rocchio 相关反馈公式,它把原查询向量与反馈文档的质心向量做加权融合,让新查询“靠近”相关文档、远离不相关文档。

服务员从客人的“筷子动向”里学到的东西,本质上也是一种反馈信号,虽然客人从没开口,但行为已经说明了一切。伪相关反馈能在无语义资源(如同义词表)的情况下自动发现相关词,是很多搜索引擎召回增强的秘密武器。

Qt+1′=αQt+β⋅1∣Dr∣∑d∈Drd⃗\displaystyle Q'_{t+1} = \alpha Q_t + \beta \cdot \frac{1}{|D_r|} \sum_{d \in D_r} \vec{d}
这里的含义是:下一轮的查询向量 = 原查询向量 × α + 反馈文档平均向量 × β。说白了就是让新查询朝“客人的筷子伸向的方向”挪一步。
  1. 第一轮检索用原始查询检索,取得分最高的前 N 篇文档作为伪相关文档集合。
    Dr={d1,d2,…,dN}\displaystyle D_r = \{d_1, d_2, \dots, d_N\}
  2. 文档向量平均对反馈文档的词频向量(或词向量)取算术平均,得到“反馈方向”。
    vˉ=1∣Dr∣∑d∈Drd⃗\displaystyle \bar{v} = \frac{1}{|D_r|} \sum_{d \in D_r} \vec{d}
  3. 加权融合原查询向量与反馈方向按权重 α、β 相加,得到下一轮查询向量。
    Qt+1′=αQt+β⋅vˉ\displaystyle Q'_{t+1} = \alpha Q_t + \beta \cdot \bar{v}

词嵌入扩展:食材性格图谱

一位顶级厨师脑子里装着一张“食材性格图谱”:豆腐和豆干性格相近——都是豆制品,口感软嫩、味道清淡;牛肉和牛腩性格相容——都适合慢炖。客人点了“豆腐汤”,厨师会推荐豆干、千张这类“性格相近”的食材。这张图谱,对应着检索系统里的词嵌入(Word Embedding)。

词嵌入技术(如 Word2Vec、GloVe)把每个词映射成一个几百维的稠密向量。训练之后,语义相近的词在向量空间中距离很近,比如“苹果”向量和“iPhone“向量、和”水果”向量都处于邻近区域。词嵌入扩展的做法是:计算查询词向量与候选词向量的余弦相似度,选出相似度最高的 Top-N 个词,作为扩展词加入查询。

与同义词表相比,词嵌入扩展的最大优势是“无监督发现”——不需要人工维护词典,模型自动从语料中学习语义关联,甚至能发现“苹果”与“华为”这种竞争品关系、以及“京东”与“物流”这种场景关联。它的局限性在于:相似不等于同义,高相似度的词可能语义相关但并不适合作为查询词,所以工业界通常会设定相似度阈值,并加一轮人工规则过滤来控制噪声。

厨师的“食材性格图谱”来自长年累月的烹饪经验;词嵌入模型的“性格图谱”则来自海量文本语料的统计规律。图谱在手,厨师能举一反三,检索系统也能触类旁通。

sim(w,v)=w⃗⋅v⃗∥w⃗∥ ∥v⃗∥\displaystyle \mathrm{sim}(w, v) = \frac{\vec{w} \cdot \vec{v}}{\|\vec{w}\| \, \|\vec{v}\|}
这个式子衡量两个词向量的“方向一致性”:方向越一致,余弦值越大,词义越相近。它是所有词嵌入扩展选词的依据。
  1. 词向量映射词 w 和词 v 分别转换为 d 维稠密向量,语义通过向量的位置表达。
    w⃗=(x1,x2,…,xd)\displaystyle \vec{w} = (x_1, x_2, \dots, x_d)
  2. 点积计算两向量对应维度相乘再累加,数值越大表示方向重叠越多。
    w⃗⋅v⃗=∑i=1dxiyi\displaystyle \vec{w} \cdot \vec{v} = \sum_{i=1}^{d} x_i y_i
  3. 归一化相似度除以两个向量的长度之积,使相似度落在 [-1, 1] 区间,便于统一设阈值。
    sim(w,v)=w⃗⋅v⃗∥w⃗∥ ∥v⃗∥\displaystyle \mathrm{sim}(w, v) = \frac{\vec{w} \cdot \vec{v}}{\|\vec{w}\| \, \|\vec{v}\|}

改写区别:复述菜单 vs 加菜

有时服务员会跟客人确认:“您说的是要一份辣子鸡,对吗?”这是复述——把客人模糊的话翻译成一道明确的菜。而另一种情况是,客人说“来点辣的”,服务员端上来水煮鱼、辣子鸡、麻婆豆腐三样,这是加菜。把这两件事分清,你就理解了查询改写与查询扩展的本质区别。

查询改写(Query Rewriting)是把原始查询“翻译”成另一个查询:把长句压缩成关键词、把口语换成书面语、把有歧义的词消歧。比如用户输入“北京天气怎么样”,改写系统可能输出“北京 天气”,它用新查询替换掉了原查询。查询扩展(Query Expansion)则不同:它保留原始查询的全部词语,只是额外叠加一批相关词。一个替换,一个叠加,这是两者最本质的差异。

从目标看,改写追求“更精准地表达用户意图”,扩展追求“更广泛地覆盖相关内容”。从形式看,改写的输出通常仍是单个查询串,可以继续作为扩展的输入,两者天然是流水线上下游的关系——现阶段的搜索系统往往先用改写归一化查询,再做扩展增强召回。写公式来对照,就更清楚了:

服务员先确认菜名(改写),再端上一桌候选(扩展),客人体验自然更好。搜索系统也遵循同样的分工:改写负责“听懂你”,扩展负责“多给你选择”。

Qrewrite=f(Q),Qexpand=Q∪E(Q)\displaystyle Q_{\mathrm{rewrite}} = f(Q), \qquad Q_{\mathrm{expand}} = Q \cup E(Q)
左边是改写:原查询 Q 被加工成一个新查询;右边是扩展:原查询 Q 原封不动保留,多出并集词 E(Q)。
  1. 改写映射规则或模型把 Q 映射为另一个查询串,输出是“替换式”的。
    Qrewrite=f(Q)\displaystyle Q_{\mathrm{rewrite}} = f(Q)
  2. 扩展叠加在原查询基础上附加扩展词,输出是“叠加式”的。
    Qexpand=Q∪E(Q)\displaystyle Q_{\mathrm{expand}} = Q \cup E(Q)
  3. 协同使用改写先做语义归一,扩展再扩充候选,两者配合使用效果最佳。
    Qfinal=Qrewrite∪E(Qrewrite)\displaystyle Q_{\mathrm{final}} = Q_{\mathrm{rewrite}} \cup E(Q_{\mathrm{rewrite}})

召回评估:上菜率怎么算

客人说“来点辣的”,这家餐厅菜单上真正符合“辣”的菜有 10 道。服务员忙了一晚上,结果只端出来了 3 道,这时候我们可以说:这桌菜的上菜率是 30%。“上菜率”换成搜索术语,就是召回率(Recall)。

召回率衡量的是:系统返回的文档中,真正相关的文档占“全部相关文档”的比例。计算时以“用户需求”为基准,问一个问题——所有应该被检索到的文档中,系统真正捞出了多少?与召回率相伴的是精确率(Precision),它问的是另一个问题——系统返回的文档中,有多少是真正相关的?这两者像跷跷板的两端:扩展词加得越多,召回的文档越多,但无关文档混入的概率也变大,精确率随之下降。

为了平衡二者,业界常用 F1 分数,它是精确率和召回率的调和平均,能综合反映检索质量。在工程实践中,上线查询扩展前后一定要做 A/B 实验:对比扩展前后的召回率、精确率、点击率、下单率等指标,确认扩展带来的收益大于噪声带来的损失,才能全量放量。

上菜率高的餐厅不一定好吃——还有口味(精确率)的考量;召回率高的搜索系统也不一定好用——还有排在第一页的答案准不准。好的检索系统,像好的服务员一样,在“什么都端上来”和“精准拿捏”之间找到平衡点。

Recall=∣R∩A∣∣R∣\displaystyle \mathrm{Recall} = \frac{|R \cap A|}{|R|}
这个公式的字面意思是:召回率 = 被检索到的相关文档数 ÷ 全部相关文档数。上菜率 = 端上桌的辣菜数 ÷ 菜单上所有辣菜数。
  1. 相关文档集合与用户查询意图真正相关的全部文档,是评估的“标准答案”。
    R={d∣d 与查询主题相关}\displaystyle R = \{d \mid d \text{ 与查询主题相关}\}
  2. 检索结果集合系统实际返回的文档集合,通常取 Top-K 或得分大于阈值者。
    A={d∣d 被检索系统返回}\displaystyle A = \{d \mid d \text{ 被检索系统返回}\}
  3. 交集与比率同时属于 R 和 A 的文档数占 R 的比例,数值越高说明漏检越少。
    Recall=∣R∩A∣∣R∣\displaystyle \mathrm{Recall} = \frac{|R \cap A|}{|R|}

实践应用:把“懂你”做成标准流程

服务员“懂你”的本事再大,如果只在单家店里灵光一现,也成不了连锁品牌。要把这门手艺沉淀成标准化流程,需要一套从离线到在线的完整体系。查询扩展在工业界的落地,正是这样一套工程化打法。

在搜索引擎中,查询扩展被广泛用于同义匹配:用户搜“没油了”,系统能理解他想要“加油站”;搜“宝宝腹泻”,能关联“婴幼儿 拉肚子”。这些同义关系来自搜索日志挖掘与同义词表的联动。在电商搜索中,查询扩展是提升长尾商品曝光的利器:用户搜“连衣裙”,通过同义词与词嵌入双路扩展,把“雪纺裙”“长裙”“one-piece“一并召回,让那些从来不会被”连衣裙”三个字匹配到的商品也有机会出现在用户面前。

部署与优化的要点有三条:其一,离线构建资源——定期更新同义词表、训练词向量、沉淀点击日志;其二,在线轻量执行——扩展逻辑必须低延迟,通常用倒排索引加内存词典完成,不能每次查询都去跑一趟大模型;其三,效果持续监控——用 A/B 实验对比扩展前后的核心指标,既看召回率提升,也盯住精确率与转化率是否劣化。记住,扩展是手段,让用户找到想要的东西才是目的。

从一家小店到连锁餐厅,靠的是把“懂客”沉淀成流程;从单次查询到海量流量,查询扩展靠的也是把“懂用户”沉淀成资源和策略。搜索与搜索背后的人,永远值得被更好地理解。

Gain=Recallexp−Recallbase\displaystyle \mathrm{Gain} = \mathrm{Recall}_{\mathrm{exp}} - \mathrm{Recall}_{\mathrm{base}}
这个式子用于量化查询扩展的收益:扩展后的召回率减去扩展前的召回率,差值越大说明扩展越有价值。
  1. 基线召回率不扩展、只用原始查询检索时的召回率,作为对照基准。
    Recallbase=∣R∩Abase∣∣R∣\displaystyle \mathrm{Recall}_{\mathrm{base}} = \frac{|R \cap A_{\mathrm{base}}|}{|R|}
  2. 扩展后召回率应用查询扩展后再次检索的召回率。
    Recallexp=∣R∩Aexp∣∣R∣\displaystyle \mathrm{Recall}_{\mathrm{exp}} = \frac{|R \cap A_{\mathrm{exp}}|}{|R|}
  3. 增益差值两者相减,得到扩展带来的绝对提升幅度。
    Gain=Recallexp−Recallbase\displaystyle \mathrm{Gain} = \mathrm{Recall}_{\mathrm{exp}} - \mathrm{Recall}_{\mathrm{base}}

AI 应用场景

电商搜索长尾词召回

阶段:召回与查询理解

解决的问题:用户搜“连衣裙”,大量描述为“雪纺裙”“长裙”的商品由于字面不匹配而无法曝光,传统字面匹配召回率低,商家好货卖不出去,用户也找不到心仪的款。

具体案例:某电商平台在搜索“连衣裙”时,调用同义词扩展(雪纺裙、长裙、one-piece)叠加词嵌入扩展(碎花裙、半身裙),扩展后召回文档数提升,长尾商品曝光量明显上升,搜索页人均浏览商品数提升,平台成交转化率同步改善。

搜索引擎口语化同义匹配

阶段:查询理解 / 查询预处理

解决的问题:用户习惯用口语甚至方言查询(“没油了”“车坏了”),而网页文档用的是书面语(“加油站”“车辆故障”),字面匹配形同虚设。

具体案例:某地图搜索 App 从用户点击日志中挖掘出“没油了→加油站”“胎爆了→补胎 维修”等关联对,构建同义词表并用于查询扩展。用户输入口语词时,系统自动追加书面语扩展词,附近加油站、维修店的召回率显著提升,导航到店率与用户满意度双涨。

企业知识库伪相关反馈增强

阶段:召回与重排

解决的问题:企业内部文档用词规范(“差旅费用报销”),员工搜索时却习惯说“出差发票怎么贴”,字面差异导致关键制度文件搜不到,人力咨询成本居高不下。

具体案例:某公司知识库接入伪相关反馈模块:先对“出差发票”做首轮检索,取回 Top-5 文档,自动提取其中高频词“报销”“差旅”“审批”回注查询,再执行第二轮检索。员工用日常口语提问,也能命中最新的制度文件,知识库搜索满意度大幅提高。

Python 演示

python107 行
# -*- coding: utf-8 -*-"""极简查询扩展演示: 同义词扩展 + 伪相关反馈 提升召回."""from collections import Counter # 模拟的文档库, 词语已用空格切分DOCS = {    0: "苹果 公司 发布 了 新款 iPhone 手机",    1: "苹果 是 一种 营养 丰富 的 水果",    2: "苹果 手机 在 国内 市场 销量 领先",    3: "水果 富含 维生素 适合 日常 食用",    4: "华为 手机 与 苹果 手机 竞争 激烈",    5: "水果 种植 需要 充足 的 光照 和 水分",    6: "今年 苹果 减产 果农 收入 下降",    7: "苹果 电脑 Mac 系统 深受 设计 者 喜爱",    8: "香蕉 和 苹果 都是 常见 水果",    9: "手机 行业 报告 显示 苹果 位居 榜首",} # 人工标注: 搜索"苹果"时真正相关的文档 (想买苹果电子产品的人)RELEVANT = {0, 2, 4, 7, 9} # 极简同义词表SYNONYMS = {    "苹果": ["手机", "水果", "iPhone", "Mac"],    "手机": ["iPhone", "智能手机"],    "水果": ["香蕉", "维生素"],}  def tokenize(text):    """按空格切分得到词列表."""    return text.split()  def score(query_terms, doc_terms):    """计算查询词在文档中的词频总分."""    cnt = Counter(doc_terms)    return sum(cnt[t] for t in query_terms)  def search(query_terms, top_k=None):    """返回按得分降序的 (doc_id, score) 列表."""    ranked = []    for doc_id, text in DOCS.items():        ranked.append((doc_id, score(query_terms, tokenize(text))))    ranked.sort(key=lambda x: x[1], reverse=True)    if top_k is not None:        ranked = ranked[:top_k]    return ranked  def expand_by_synonyms(query_terms):    """同义词扩展: 查询词查表并扩充."""    expanded = set(query_terms)    for term in query_terms:        if term in SYNONYMS:            expanded.update(SYNONYMS[term])    return list(expanded)  def pseudo_feedback(query_terms, top_k=3, extra=2):    """伪相关反馈: 取前top_k篇文档, 提取高频词加入查询."""    feedback_docs = search(query_terms, top_k=top_k)    counter = Counter()    for doc_id, _ in feedback_docs:        counter.update(tokenize(DOCS[doc_id]))    for term in query_terms:        counter.pop(term, None)    return query_terms[:] + [w for w, _ in counter.most_common(extra)]  def recall_at_k(doc_ids, k=5):    """计算前k篇结果中的召回率."""    hits = [d for d in doc_ids[:k] if d in RELEVANT]    recall = len(hits) / len(RELEVANT)    return hits, recall  def main():    q = ["苹果"]    print("原始查询:", q)    base = search(q)    base_ids = [d for d, _ in base]    hits, rec = recall_at_k(base_ids)    print("返回top5:", base_ids[:5], "命中相关:", hits[:5])    print("recall@5 = %.2f" % rec)     expanded = expand_by_synonyms(q)    print("\n同义词扩展后:", expanded)    res2 = search(expanded)    res2_ids = [d for d, _ in res2]    hits, rec = recall_at_k(res2_ids)    print("返回top5:", res2_ids[:5], "命中相关:", hits[:5])    print("recall@5 = %.2f" % rec)     feedback = pseudo_feedback(expanded)    print("\n伪相关反馈再加词:", feedback)    res3 = search(feedback)    res3_ids = [d for d, _ in res3]    hits, rec = recall_at_k(res3_ids)    print("返回top5:", res3_ids[:5], "命中相关:", hits[:5])    print("recall@5 = %.2f" % rec)    print("\n结论: 查询扩展让top5命中的相关文档数由3个增加到4个")  if __name__ == "__main__":    main()
目的:用 10 篇模拟文档演示同义词扩展与伪相关反馈如何把召回率从 0.60 提升到 0.80,帮助理解查询扩展的完整链路。
  1. DOCS预置的模拟文档库,词语已用空格隔开,省去中文分词依赖,让代码专注演示扩展逻辑。
  2. RELEVANT人工标注的“标准答案”,即搜索“苹果”时真正相关的文档集合,用于计算召回率。
  3. SYNONYMS = {"苹果": ["手机", "水果", "iPhone", "Mac"]}极简同义词表,模拟服务员口中的“黑话词典”。
  4. def score(query_terms, doc_terms)用查询词在文档中的词频之和作为打分依据,模拟检索打分过程。
  5. def search(query_terms, top_k=None)遍历文档库计算得分并排序,返回 Top-K 文档,相当于餐厅端出最匹配的几道菜。
  6. def expand_by_synonyms(query_terms)核心扩展逻辑:查询词逐词查表,命中就把同义词并入查询集合。
  7. def pseudo_feedback(query_terms, top_k=3, extra=2)伪相关反馈:先把首轮 Top-3 文档的词频汇总,剔除查询已有词,选频率最高的 2 个词回注。
  8. recall_at_k(doc_ids, k=5)计算前 5 篇结果中相关文档的占比,即“上菜率”。
  9. base_ids[:5]只截取前 5 篇结果做评估,贴近工程中“只看第一页”的真实体验。

常见误区

误区初学者常把查询扩展和查询改写混为一谈,觉得两者都是“把用户输入换成更好的说法”。搜索系统里常常一起出现,更让人分不清谁是谁。

正解查询改写的本质是“替换”——把原查询翻译成新查询(如“北京市今天的天气”→“北京 天气”);查询扩展的本质是“叠加”——原查询词一个不动,在旁边追加相关词(如“苹果”→“苹果 手机 水果”)。如果把餐厅场景套进来:改写是服务员复述确认菜单,扩展是服务员多端几道候选菜。两者是流水线上相邻的两个环节,但动作完全不同。

为何常见:因为在真实搜索系统中,改写和扩展往往串联使用,并且都发生在“查询预处理”这个阶段,表面看起来都在“改查询”,于是被混为一谈。

误区“多加点词,反正召回率会上升,词多多益善。” 不少人在调参时把扩展词数量拉到二三十个,觉得这样覆盖率最高。

正解扩展词并非越多越好。每加一个词,就会引入一份噪声:不相关的扩展词会把文档库中无关的内容也捞出来,召回率虽然上升,精确率却显著下降——用户看到一堆不相关结果,体验反而更差。正确做法是像服务员加菜一样:先看客人反应(A/B 实验),控制扩展词数量与质量,监测精确率、点击率、转化率等下游指标,找到召回与精确的最佳平衡点。

为何常见:因为召回率的计算公式只看“有没有捞到相关文档”,不看“捞出来的文档里有多少是垃圾”。只盯着单指标优化,自然会产生“词越多越好”的错觉。

误区名字里带“反馈”二字,初学者以为伪相关反馈是“用户看完搜索结果点击了哪些,系统再据此扩展”,需要真实用户的交互行为才能工作。

正解伪相关反馈的最大特点就是“伪”——它假设第一轮检索返回的前 N 篇文档与查询相关,完全不需要用户做任何标注或点击,全程自动执行。真正的“相关反馈”(如用户手动标记相关文档)是另一套更古老且成本更高的交互式方法。伪相关反馈的代价是:这个假设并不总是成立,首轮结果不佳时,扩展词会被带偏,所以实践中要加质量过滤。

为何常见:“反馈”在机器学习语境中常指“来自标注者的反馈”,而伪相关反馈的反馈来源是检索系统自己的排名结果,这个反直觉的设定让初学者容易误解。

误区看到词嵌入扩展输出一批与查询词相近的词,便认为它只是在自动找同义词,和同义词表扩展没什么区别,只是换了种自动化生成方式。

正解同义词表收录的是“可以互换”的词(苹果↔iPhone),而词嵌入找的是“语义相关”的词(苹果↔华为、苹果↔乔布斯、苹果↔富士康)。相关不等于同义,甚至可能是类比、上下位或场景关系。词嵌入扩展的覆盖范围远大于同义词表,但噪声也更大,因此通常需要设相似度阈值,并与同义词表互补使用。

为何常见:因为两种扩展方式的输出形式很相似——都是“在原查询旁边多出几个词”,而初学者还没建立起“同义 ≠ 相关”的概念边界,自然会把两者看成同一件事。

面试题

Q1. 查询扩展的核心目标是什么?

单选题 · easy

  • A.压缩索引体积
  • B.提升搜索召回率← 正确答案
  • C.加速查询解析
  • D.美化界面样式

答案:B。查询扩展通过添加相关词扩大查询覆盖面,让更多相关文档有机会被检索到,核心收益就是召回率提升。压缩索引、加速解析、美化界面都不是扩展的目标,它们分属存储、性能与前端领域。

Q2. 同义词扩展依赖的主要资源是什么?

单选题 · easy

  • A.同义词表← 正确答案
  • B.用户头像
  • C.文档字体
  • D.网络延迟

答案:A。同义词扩展的核心就是一张同义词表,查询时查表把同义词并入查询。用户头像、文档字体、网络延迟都与语义扩展无关,属于明显的干扰项。

Q3. 伪相关反馈中“伪”的含义是什么?

单选题 · easy

  • A.伪造用户点击数据
  • B.假设首轮结果相关← 正确答案
  • C.扩展词是虚假的
  • D.指扩展词具有伪造性

答案:B。“伪”体现在它并不确认首轮结果真的相关,而是直接假设前 N 篇文档相关并加以利用。这既不是伪造数据,也不是扩展词虚假,而是“未经证实的相关假设”。

Q4. 词嵌入扩展挑选扩展词的依据是什么?

单选题 · mid

  • A.词语出现频率
  • B.向量余弦相似度← 正确答案
  • C.拼音完全相同
  • D.字面包含关系

答案:B。词嵌入把词映射为向量,两个词的语义相近程度用向量余弦相似度衡量,选出相似度 Top-N 的词作为扩展词。词频反映的是重要性而非语义相近,拼音和字面关系都与语义无关。

Q5. 查询改写与查询扩展的本质区别是什么?

单选题 · mid

  • A.改写生成新查询,扩展叠加词← 正确答案
  • B.改写用于离线,扩展用于在线
  • C.改写提升召回,扩展提升精度
  • D.改写一定更好,扩展没有用

答案:A。改写是替换式的——输出一个新查询替换原查询;扩展是叠加式的——原查询保留,额外附加相关词。两者没有离线在线之分,也不是一个提召回一个提精度的分工,更不存在谁一定更好的说法。

Q6. 召回率的计算公式是什么?

单选题 · mid

  • A.检索的文档数除以全部文档数
  • B.检索到的相关数除以全部相关数← 正确答案
  • C.正确推荐数除以推荐总数
  • D.相关文档数除以检索文档数

答案:B。召回率 = 检索到的相关文档数 ÷ 全部相关文档数,它衡量的是“该捞的都捞出来了吗”。选项 A 是覆盖率、选项 C 是精确率、选项 D 是另一种口头的“命中率”,都不对。

Q7. 伪相关反馈在第二轮检索前做了哪件事?

单选题 · hard

  • A.直接删除原始查询词
  • B.提取反馈文档关键词加入← 正确答案
  • C.跳过第一轮检索打分
  • D.仅保留得分最高的文档

答案:B。伪相关反馈的两轮流程是:先检索取前 N 篇,再从这些文档中提取高频词回注查询,最后执行第二轮检索。删除原查询会丢失意图,跳过打分会失去排名依据,仅保留一篇文档则信息量不足。

Q8. 查询扩展的常用实现方式有哪些?

多选题 · mid

  • A.同义词表规则扩展← 正确答案
  • B.伪相关反馈扩展← 正确答案
  • C.词嵌入相似词扩展← 正确答案
  • D.中文分词标注词性

答案:ABC。三种主流扩展方式是同义词表、伪相关反馈和词嵌入相似词。中文分词标注是查询预处理的基础步骤,它负责把句子切成词,本身并不产生扩展词,属于干扰项。

Q9. 同义词表可以通过哪些途径构建?

多选题 · hard

  • A.人工编纂同义词表← 正确答案
  • B.用户点击日志挖掘← 正确答案
  • C.词嵌入自动聚类← 正确答案
  • D.随机抽取无关词组

答案:ABC。人工编纂质量高但成本大,点击日志挖掘能捕捉真实搜索场景下的同义关联,词嵌入聚类可自动发现语义相近的词对再辅以人工审核。随机抽取无关词组无法构建有语义价值的同义词表。

Q10. 关于伪相关反馈,下列说法正确的有哪些?

多选题 · hard

  • A.无需用户手动标注← 正确答案
  • B.假设前N篇结果相关← 正确答案
  • C.可动态补充查询词← 正确答案
  • D.扩展词必须出自词典

答案:ABC。伪相关反馈自动假设首轮 Top-N 结果相关并从中提炼词,全程无需用户参与,能动态补充超出原查询的词语。它的扩展词来自检索文档,而非预先准备的词典,因此 D 错误。