想象你在老城区一家藏书上百万册的书店里当学徒。顾客站在柜台前说:“我想找讲怎么训练小狗的书。”资深店员阿诚从不逛书架,他翻开一本磨破边角的笔记本——每一页记着一个词,下面抄着这个词在哪几本书里出现过、各出现过几次。这套“先查词、再翻书”的土办法,就是今天要讲的稀疏检索。后来阿诚把笔记本升级了两代:第一代叫 TF-IDF,教会他什么样的词值得记、记得多不等于真的重要;第二代叫 BM25,教会他词频会“饱和”、厚书要“打折”。这两代心法看似朴素,至今仍是各行各业搜索系统、以及大模型外挂知识库(RAG)的底层地基。读完这篇,你不但能看懂它们,还能在自己的检索项目里亲手用起来。

岗位关联

稀疏检索是大模型 RAG 的关键词召回底座。掌握 TF-IDF 与 BM25,你就能在精确匹配场景中做出又快又稳的检索方案,并懂得如何与稠密检索配合。

目录
  1. 可视化演示
  2. 稀疏检索概念
  3. TF-IDF原理
  4. BM25改进
  5. 检索效果对比
  6. RAG融合方案
  7. 参数调优实践
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

通过逐步展示快速排序的分区过程,直观理解枢轴选择、元素比较与交换操作,支持自动播放和手动步进切换。

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

快速排序初始化 待排序数组已就绪,即将开始分区操作 枢轴选择策略:选取最右端元素 T(n) = O(n log n) 点击自动播放或手动步进开始演示

稀疏检索概念

阿诚的笔记本正面是书名,背面是词表。客人问“训练”,他哗啦啦翻到“训练”那一页,上面密密麻麻写着一长串书名。这套“从词反查书”的索引方式,在计算机里有个名字叫倒排索引:与“一页页翻书找词”(正排)相反,它提前建好“词 → 文档列表”的映射,因此书店再大,查询也是瞬间的事。

不过,要建这张表,得先把每本书拆成一个个词装进“袋子”里——这就是词袋模型:丢词语序、丢掉语法,只记录“哪些词出现了、出现了几次”。每本书用一个向量表示,向量每一维对应词典里的一个词。

问题来了:中文分词后词典规模轻松达到几万到几十万,而一本书只包含其中一小部分词。于是这个向量绝大多数位置是 0——“稀疏”二字由此而来。若没有倒排索引,每次查询都要遍历所有书籍算相似度,成本高得吓人;有了索引,查询时直接取文档列表、求交集、排序即可。

阿诚的土笔记本,本质就是一本手写倒排索引。词袋模型、稀疏向量、倒排索引这三件套,构成了 TF-IDF 与 BM25 表演的舞台:

阿诚的笔记本证明了一件事:检索快不快,不取决于书有多少,而取决于索引怎么建。接下来,阿诚要解决一个更棘手的问题——同一本书里,哪个词才是真正带客人找到书的“钥匙”。

d⃗=(wt1,wt2,…,wtn),wti≥0,∣{i:wti≠0}∣≪n\vec{d} = (w_{t_1}, w_{t_2}, \ldots, w_{t_n}), \quad w_{t_i} \ge 0, \quad |\{i : w_{t_i} \ne 0\}| \ll n
  1. 文档向量每本书变成一个向量 \(\vec{d}\),它的维度数等于词典大小 \(n\),就像给笔记本预留了 \(n\) 个词条位。
    d⃗=(wt1,wt2,…,wtn)\vec{d} = (w_{t_1}, w_{t_2}, \ldots, w_{t_n})
  2. 分量含义第 \(i\) 维 \(w_{t_i}\) 表示词典第 \(i\) 个词在这本书里的权重,可以是次数也可以是加权分。
    wti≥0w_{t_i} \ge 0
  3. 稀疏条件非零分量的个数远小于 \(n\),因为一本书实际用到的词非常有限。
    ∣{i:wti≠0}∣≪n|\{i : w_{t_i} \ne 0\}| \ll n
  4. 空间直觉数万维空间中只有零星几点“亮灯”,其余全黑——这就是稀疏向量独有的特征形态。

TF-IDF原理

阿诚很快发现,光数次数会出大错。“的”在一本书里出现几百次,但客人从不靠“的”找书。真正有用的词必须同时满足两条:在眼前这本书里出现得多(词频高),在整个书店里却很少出现(稀缺)。前者英文叫 TF(Term Frequency),后者叫 IDF(Inverse Document Frequency,逆文档频率)。

在计算机里,TF 就是词在文档中的出现次数,或者归一化后的频率。IDF 用对数公式计算:\(N\) 是语料库文档总数,\(\text{df}(t)\) 是包含词 \(t\) 的文档数。“的”几乎每篇都有,df 接近 N,IDF 趋近 0;而“量子纠缠”只在少数几篇出现,IDF 很大。这正是信息论里的直觉:越稀有的信号,携带的信息量越大。

TF-IDF 把两个信号相乘。一篇文章里“小狗”出现 8 次、全库只有 30 篇提到它,得分就很高;而“的”出现 800 次、全库 1 万篇都有它,得分几乎为零。经典用法有两种:一是提取文档关键词(取 Top 词),二是把查询和文档都转成 TF-IDF 向量,用余弦相似度排序。

阿诚给笔记本改版:每个词条不再只记“在哪本书出现”,还补上了“全店有多少本书也有这个词”。改版成本极低,找书准确率却立竿见影。第一代心法,就此诞生:

阿诚的“数次数”哲学从第一代就定型了:一靠本地频率,二靠全局稀缺。这套思路简单优美,可它还有一个致命伤——阿诚很快会被一本厚得离谱的大百科狠狠上一课。

TF-IDF(t,d)=f(t,d)×log⁡Ndf(t)\text{TF-IDF}(t,d) = f(t,d) \times \log \frac{N}{\text{df}(t)}
  1. 词频项\(f(t,d)\) 是词 \(t\) 在文档 \(d\) 里出现的次数,代表“这本书里这个词多不多”。
    f(t,d)f(t,d)
  2. 文档总数\(N\) 是语料库所有文档的数量,对应阿诚笔记本里登记的书的总数。
    NN
  3. 文档频率\(\text{df}(t)\) 是包含词 \(t\) 的文档数量,代表“这个词在整家书店里有多少本”。
    df(t)\text{df}(t)
  4. 对数稀缺度\(\log(N/\text{df}(t))\) 越大,词越罕见;大路货的比值接近 1,对数后趋近 0。
    log⁡Ndf(t)\log \frac{N}{\text{df}(t)}
  5. 权重相乘高频且稀缺的词语得分高;高频但常见(如“的”)或稀缺但低频的词都被压制。
    f(t,d)×log⁡Ndf(t)f(t,d) \times \log \frac{N}{\text{df}(t)}

BM25改进

阿诚发现 TF-IDF 有两个漏洞。第一:“小狗”在一本书里出现 100 次,难道比出现 10 次有用 10 倍?客人翻两页就明白了,多出的 90 次多半是废话——词频的收益会“饱和”。第二:一本 1000 页的大百科里 30 次“训练”,和一本 100 页的小册子里 30 次,含金量完全不同——长文档天生更容易堆词频,必须按长度“打折”。

BM25 诞生于 90 年代,是 Robertson 等人在概率检索框架下提出的第二代稀疏检索公式。它保留了 IDF 项,但把词频项从直线改成一条饱和曲线,并加入文档长度归一化。公式里出现两个新参数:\(k_1\) 控制饱和速度(词频涨到多少开始“腻”),\(b\) 控制长度惩罚力度(对厚书多狠)。

在 RAG 与搜索引擎里,最常用的是 BM25 的原始形式:对查询里的每个词,先算带平滑的 IDF,再乘上经过饱和与长度归一化处理的词频项,最后把所有词的贡献加起来。这个公式看似吓人,拆开看其实就四块积木:IDF 负责压制大路货,分子保证词频越高分越高,分母让增长曲线封顶,长度因子让厚书不占便宜。

阿诚给笔记本加装了两个旋钮,调了两周后感慨:第二代的稳,体现在“公平”二字——长书不再靠字数欺负短书,一个词刷再多遍,分数也不会无限膨胀:

阿诚合上笔记本,两代心法已在他脑中形成对比图:一条直线冲天,一条曲线封顶。那么问题来了——换了第二代之后,检索效果真的全面碾压吗?

Score(D,Q)=∑t∈QIDF(t)×f(t,D)×(k1+1)f(t,D)+k1(1−b+b⋅∣D∣avgdl)\text{Score}(D,Q) = \sum_{t \in Q} \text{IDF}(t) \times \frac{f(t,D) \times (k_1+1)}{f(t,D) + k_1\left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}
  1. 带平滑的 IDF\(\text{IDF}(t) = \log\left(1 + \frac{N - \text{df}(t) + 0.5}{\text{df}(t) + 0.5}\right)\),0.5 平滑避免除零,也保证 IDF 非负。
    IDF(t)=log⁡(1+N−df(t)+0.5df(t)+0.5)\text{IDF}(t) = \log\left(1 + \frac{N - \text{df}(t) + 0.5}{\text{df}(t) + 0.5}\right)
  2. 词频分子\(f(t,D) \times (k_1+1)\) 保证词频越高分数越高,但增长被下面积的分母限制住。
    f(t,D)×(k1+1)f(t,D) \times (k_1+1)
  3. 饱和分母\(f + k_1\) 让词频项随着 \(f\) 增大趋近一个上限,这就是“饱和”的数学来源。
    f(t,D)+k1f(t,D) + k_1
  4. 长度归一化\(|D|/\text{avgdl}\) 是文档相对长度,大于 1 表示它比平均文档长,要被打折。
    1−b+b⋅∣D∣avgdl1 - b + b \cdot \frac{|D|}{\text{avgdl}}
  5. 参数封顶当词频 \(f\) 趋于无穷,词频分量收敛到 \(k_1+1\),不会像 TF-IDF 那样无限上涨。
    f⋅(k1+1)f+k1→k1+1\frac{f \cdot (k_1+1)}{f + k_1} \to k_1 + 1

检索效果对比

阿诚把两代笔记本并排测试:同一个书库、同一批查询,看谁排得准。三周下来,结论清晰又有趣。

先说长文档表现。TF-IDF 最容易吃亏:一本 1000 页的烹饪百科里“训练”出现 60 次,得分轻松超过一本 100 页的训犬手册里出现 30 次。可明眼人都知道后者才是客人要的。BM25 的长度归一化直接改写了局面——按相对词频比,短书的“含金量”立刻凸显。再说停用词抑制:两者都靠 IDF 压低“的、了、和”这类高频词,但 BM25 的平滑 IDF 更稳健,不会因为某个词恰好出现在一半文档里而产生剧烈波动。

不过,两代心法共享同一个天花板——只认字面,不认语义。“苹果手机”和“iPhone 新品”互相找不到对方;“车”和“汽车”在词典里是两个毫不相干的维度;用户说“胸口闷”,指南里写“胸闷”,词面失配直接漏召回。这就是稀疏检索的固有边界,也是稠密检索(向量嵌入)存在的理由。

所以选型建议很实用:产品型号、人名、法条编号、代码符号这类精确硬匹配场景,用稀疏检索又快又准还可解释;开放式的同义改写、口语化查询,用稠密检索;通用 RAG 里,两者往往是互补搭档而非竞争对手。数学上,两代算法最本质的分歧就藏在极限里:

阿诚顿悟:心法再老练,也看不见“意思”。那句“苹果手机”始终匹配不上"iPhone",成了他后来加入 RAG 项目时最重要的伏笔。

lim⁡f→∞f⋅(k1+1)f+k1=k1+1,lim⁡f→∞f=∞\lim_{f \to \infty} \frac{f \cdot (k_1+1)}{f + k_1} = k_1 + 1, \qquad \lim_{f \to \infty} f = \infty
  1. 左侧极限BM25 词频分量在词频趋于无穷时收敛到常数 \(k_1+1\),分数有边界。
    lim⁡f→∞f⋅(k1+1)f+k1=k1+1\lim_{f \to \infty} \frac{f \cdot (k_1+1)}{f + k_1} = k_1 + 1
  2. 右侧极限TF-IDF 里的词频项 \(f\) 直接线性发散,单靠一个词就能把分数顶到天。
    lim⁡f→∞f=∞\lim_{f \to \infty} f = \infty
  3. 对比结论一个“封顶”、一个“没顶”,这就是靠堆词刷分在 BM25 下失效的根本原因。

RAG融合方案

RAG(检索增强生成)通俗说就是给大模型“外挂知识库”:用户提问后,先从文档库捞 Top-K 片段,再把这些片段连同问题一起喂给大模型生成答案。阿诚退休后被一家企业知识问答公司请去当搜索顾问,很快发现:只靠第二代心法还是不够。

标准方案是混合检索:BM25 跑一路关键词候选,稠密检索(向量相似度)跑一路语义候选,两路结果合并后统一排序。但这里藏着一个大坑——BM25 的得分是几十到几百的累计值,稠密检索的余弦相似度在 -1 到 1 之间。两把尺子刻度完全不同,直接相加等于稠密检索被无视。

解决办法分三步:先做分数归一化(MinMax 把两路分数各自压到 0~1),再做加权融合(用权重 \(\alpha\) 调配关键词与语义的占比),最后可选做重排序(用更精细的模型对 Top 候选精排)。也有更省事的替代方案叫 RRF(倒数排名融合),不看分数只看名次,把两路排名取倒数相加。

阿诚把两代笔记本变成一条流水线:关键词保证“找得到”,语义负责“找得对”,大模型负责“答得好”。这套流程如今是 RAG 工程的标配:

阿诚在项目复盘会上说了一句大实话:检索不是选边站队,而是让两代心法各司其职。但也有人追问——BM25 那两个旋钮到底怎么拧才顺手?答案藏在语料里。

S^sparse=Ssparse−Smin⁡Smax⁡−Smin⁡,Sfinal=α⋅S^sparse+(1−α)⋅S^dense\hat{S}_{\text{sparse}} = \frac{S_{\text{sparse}} - S_{\min}}{S_{\max} - S_{\min}}, \qquad S_{\text{final}} = \alpha \cdot \hat{S}_{\text{sparse}} + (1-\alpha) \cdot \hat{S}_{\text{dense}}
  1. MinMax 归一化把 BM25 原始分数线性压缩到 0~1,消除与向量相似度的量纲差异。
    S^sparse=Ssparse−Smin⁡Smax⁡−Smin⁡\hat{S}_{\text{sparse}} = \frac{S_{\text{sparse}} - S_{\min}}{S_{\max} - S_{\min}}
  2. 融合权重\(\alpha \in [0,1]\) 控制关键词检索的占比,经验值在 0.3~0.5 之间。
    α⋅S^sparse\alpha \cdot \hat{S}_{\text{sparse}}
  3. 综合排序语义分支占剩余权重,融合分数降序取 Top-K,作为送入大模型的上下文。
    Sfinal=α⋅S^sparse+(1−α)⋅S^denseS_{\text{final}} = \alpha \cdot \hat{S}_{\text{sparse}} + (1-\alpha) \cdot \hat{S}_{\text{dense}}

参数调优实践

阿诚带徒弟,先教认识两个旋钮。\(k_1\) 是“词频信任度”:调大等于相信“多出现真的更有用”,词频影响接近线性;调小等于“出现两三次我就信了,再多也不加分”。\(b\) 是“厚书偏见”:调大对长文档惩罚重,调小则一视同仁。两者配合,决定了 BM25 的个性。

经验的默认值是 \(k_1=1.2\sim2.0\)、\(b=0.75\),但这是长文本通用语料调出来的。如果你的语料是短标题、FAQ、商品名,词频普遍偏低,饱和机制几乎不触发,可以调小 \(k_1\);如果文档长度差异悬殊(比如既有 50 字的公告又有 5 万字的白皮书),需要调大 \(b\) 强化长度惩罚。此外还要做语料适配:中文要选对分词器,中英混合要处理大小写与特殊字符。

怎么判断调得好不好?不能凭感觉。用带标注的检索评估集算 Recall@K、MRR、NDCG,或者直接看 RAG 端到端的答案质量。调参时用小步网格搜索:\(k_1\) 在 \([0.5, 3.0]\)、\(b\) 在 \([0, 1]\),步长 0.1~0.25,让评估指标自己说话。调参的本质不是找魔法数字,而是对齐你的语料分布。

阿诚在笔记本最后一页写道:算法是死的,语料是活的。参数不是魔法,是对语料的尊重。把旋钮拧到合适的位置,第二代心法才算真正在你手里落了地:

两个旋钮拧完,阿诚的心法传授也接近尾声。从词袋到倒排索引,从 TF-IDF 到 BM25,再到 RAG 里的混合检索——这套“关键词召回”的地基,你已经有了一幅完整地图。

TFBM25(f)=f⋅(k1+1)f+k1\text{TF}_{\text{BM25}}(f) = \frac{f \cdot (k_1 + 1)}{f + k_1}
  1. 简化分量去掉 IDF 与长度归一化,只看词频饱和部分,方便单独研究 \(k_1\) 的影响。
    TFBM25(f)=f⋅(k1+1)f+k1\text{TF}_{\text{BM25}}(f) = \frac{f \cdot (k_1 + 1)}{f + k_1}
  2. k1 的作用\(k_1\) 越大,分母中 \(f\) 的权重越小,曲线越接近直线;\(k_1\) 越小饱和来得越快。
    f+k1f + k_1
  3. 边界验证\(f=0\) 时得 0;\(f=k_1\) 时恰好得到封顶值的一半,这是快速理解饱和的好抓手。
    k1⋅(k1+1)k1+k1=k1+12\frac{k_1 \cdot (k_1+1)}{k_1 + k_1} = \frac{k_1+1}{2}
  4. 调参直觉想快速感受版本差异,先固定 \(b\) 只动 \(k_1\),在验证集上对比 Recall@K 曲线。
    Recall@K=命中的相关文档数K\text{Recall@K} = \frac{\text{命中的相关文档数}}{K}

AI 应用场景

企业知识库的关键词召回

阶段:检索召回(Retrieval)

解决的问题:企业内部知识库充满产品型号、合同编号、法条序号等“精确词”,纯语义检索极易把这些词向量化后混淆。BM25 按字面精确命中、毫秒级响应,且结果可解释——命中了哪些词一目了然,这正是硬匹配场景最需要的特性。

具体案例:某保险公司上线 RAG 问答,用户问“重疾险条款第 12 条赔付比例”,稠密检索召回的是语义相近的健康告知段落;接入 BM25 后精确命中“第 12 条”,与稠密候选按 0.4:0.6 融合,答案准确率提升约 22%。

医疗问答的混合检索

阶段:结果融合与重排序

解决的问题:用户描述症状用的是生活化口语(“胸口闷”),医学指南写的是“胸闷”,词面不匹配导致 BM25 漏召回;而纯稠密检索又容易召回无关段落。双路召回再融合能同时保住术语精确与语义泛化。

具体案例:某在线问诊平台用 BM25 召回指南关键词段落、用向量检索召回同义口语段落,各取 Top 20 后用 RRF 合并、精排取 Top 5 喂给大模型。上线后医生对检索片段的采纳率从 63% 提升到 81%。

电商搜索快速冷启动

阶段:检索召回(Baseline)

解决的问题:新店铺没有用户行为数据,无法训练学习排序模型;TF-IDF 作为零成本基线,按标题词面召回即可快速上线,之后再平滑迭代为 BM25 或向量检索。逻辑简单、好排查,特别适合业务早期快速验证。

具体案例:某跨境电商上架 5 万件新品,先用 TF-IDF 做关键词召回 MVP,上线当天即可搜索;两周后切换 BM25 并将 \(k_1=1.2\)、\(b=0.6\) 适配短标题语料,搜索点击率提升 9%。

Python 演示

python79 行
import mathimport refrom collections import Counter  class TFIDF:    """TF-IDF: 词频 x 逆文档频率, 稀疏检索第一代"""     def __init__(self, docs):        self.tokens_list = [self._tokenize(d) for d in docs]        self.N = len(docs)        self.df_map = Counter()        for tokens in self.tokens_list:            for term in set(tokens):                self.df_map[term] += 1     def _tokenize(self, text):        return re.findall(r'[\w\u4e00-\u9fff]+', text.lower())     def score(self, doc_index, query):        tokens = self.tokens_list[doc_index]        total = 0.0        for term in set(self._tokenize(query)):            tf = tokens.count(term) / len(tokens)            idf = math.log(self.N / (self.df_map[term] + 1))            total += tf * idf        return total  class BM25:    """BM25: 在 TF-IDF 上加入词频饱和与长度归一化"""     def __init__(self, docs, k1=1.5, b=0.75):        self.tokens_list = [self._tokenize(d) for d in docs]        self.N = len(docs)        self.k1 = k1        self.b = b        self.avgdl = sum(len(t) for t in self.tokens_list) / self.N        self.df_map = Counter()        for tokens in self.tokens_list:            for term in set(tokens):                self.df_map[term] += 1     def _tokenize(self, text):        return re.findall(r'[\w\u4e00-\u9fff]+', text.lower())     def score(self, doc_index, query):        tokens = self.tokens_list[doc_index]        tf_map = Counter(tokens)        dl = len(tokens)        total = 0.0        for term in set(self._tokenize(query)):            if term not in tf_map:                continue            freq = tf_map[term]            denom = freq + self.k1 * (1 - self.b + self.b * dl / self.avgdl)            idf = math.log(1 + (self.N - self.df_map[term] + 0.5) / (self.df_map[term] + 0.5))            total += idf * freq * (self.k1 + 1) / denom        return total  def rank_all(model, docs, query):    scored = [(index, docs[index], model.score(index, query)) for index in range(len(docs))]    return sorted(scored, key=lambda item: item[2], reverse=True)  if __name__ == "__main__":    docs = [        "书店里有很多训练小狗的方法手册",        "训练小狗需要耐心和零食奖励",        "苹果手机发布了新款旗舰机",        "如何挑选新鲜的苹果",        "机器学习模型训练需要大量数据",    ]    query = "训练 小狗"    for model, name in [(TFIDF(docs), "TF-IDF"), (BM25(docs), "BM25")]:        print("==== %s 排序结果 ====" % name)        for index, doc, score in rank_all(model, docs, query):            print("  score=%.4f  %s" % (score, doc))
目的:在微型语料上同时实现 TF-IDF 与 BM25 的完整打分与排序逻辑,可直接运行观察两代算法的排名差异。
  1. self.df_map = Counter()统计每个词出现在多少篇文档中,为 IDF 做准备。
  2. idf = math.log(self.N / (self.df_map[term] + 1))TF-IDF 的稀缺度项,加 1 防除零。
  3. denom = freq + self.k1 * (1 - self.b + self.b * dl / self.avgdl)BM25 分母中的长度归一化核心。
  4. total += idf * freq * (self.k1 + 1) / denom稀缺度、词频、饱和三者相乘的完整 BM25 打分。
  5. sorted(scored, key=lambda item: item[2], reverse=True)按分数降序输出,得到最终检索排名。

常见误区

误区“BM25 不是全面升级版吗?那我直接把所有 TF-IDF 的代码换成 BM25 默认参数就完事了,效果肯定更好。”

正解不一定。BM25 的优势来自词频饱和与长度归一化,但这两个特性依赖语料形态。在短标题、词频普遍很低的场景里,饱和机制几乎不触发,两者差距很小;而默认参数是在长文语料上调出来的,用在新闻标题库上,长度归一化反而可能误伤短小精悍的文档。换算法之前,先拿一个小评估集跑一版对比,让数据说话。

为何常见:各类教程都强调 BM25 是 TF-IDF 的“进化版”,容易让人忽略它在新语料上同样需要适配。

误区“一篇文档里'苹果'出现 80 次,另一篇只出现 5 次,那 80 次那篇肯定更相关,排名应该更高。”

正解词频的边际收益是递减的。一个词出现 10 次已足以说明主题,再多往往来自篇幅或刻意堆砌。BM25 设计饱和曲线正是为了纠正这件事——80 次相对 5 次的分数优势远没有 16 倍。TF-IDF 在这点很吃亏,长文档可以靠重复词“刷分”,这也是它和 BM25 最核心的行为差异。

为何常见:“数量多 = 重要”符合直觉,但忽略了长文档天然词频更高这个混淆因素。

误区“既然 IDF 衡量稀缺度,那我选 IDF 最大的词当查询词,检索结果一定最精准。”

正解IDF 大只说明词在语料中罕见,不代表它与查询意图相关。一个拼写错误或生僻型号可能 IDF 极高,但几乎不出现在任何相关文档里,检索结果依然为空或噪声。IDF 的正确用途是压制停用词、放大可区分度,而不是单独充当相关性度量——相关性由词频和稀缺度共同决定。

为何常见:把“区分度”误解成“重要性”,忽略了查询与文档的匹配还需要词频来验证。

误区“既然 BM25 也对文档打了分,那我用 BM25 就能完成语义检索,同义词不用额外处理。”

正解稀疏检索只看词面是否出现。“汽车”和“车辆”在词典里是两个完全独立的维度,互相匹配不上;同义改写、指代消解、语序变化都会导致漏召回。这正是 RAG 中引入稠密检索(向量嵌入)的原因:稀疏负责精确硬匹配,稠密负责语义泛化,两者配合而非互相替代。

为何常见:大模型时代大家对“语义”太熟悉,容易误以为所有打分模型都懂语义,忽略了词袋模型天然丢失语义结构。

面试题

Q1. TF-IDF 中 IDF 的主要作用是什么?

单选题 · easy

  • A.衡量词在单篇文档中的频率
  • B.衡量词在整个语料中的稀缺度← 正确答案
  • C.衡量文档长度并做归一化
  • D.计算查询中关键词的数量

答案:B。IDF 的公式是 \(\log(N/\text{df})\),文档总数除以包含该词的文档数再取对数,衡量的是词在全语料中的稀缺程度。“的”“了”几乎每篇都有,IDF 趋近 0;专有名词 IDF 很大。A 描述的是 TF,C 是 BM25 长度归一化做的事,D 与 IDF 无关。

Q2. 稀疏检索中“稀疏”指的是什么?

单选题 · easy

  • A.向量维度数值很小
  • B.向量中绝大部分元素为零← 正确答案
  • C.向量中所有元素数值都很大
  • D.向量中只能存储正整数值

答案:B。词典规模有数万到数十万维,一篇文档只包含其中少量词,所以向量绝大多数维度是 0,非零维度很少,这就是“稀疏”。维度本身并不小,元素也不都很大,权重更不限于整数(BM25 打分就是小数)。

Q3. BM25 相比 TF-IDF 引入哪两个关键改进?

单选题 · mid

  • A.词向量与自注意力机制
  • B.倒排索引与缓存优化
  • C.词频饱和与文档长度归一化← 正确答案
  • D.停用词过滤与智能分词

答案:C。BM25 把词频项从线性改成饱和曲线(词频再高分数封顶),并加入 \(|D|/\text{avgdl}\) 长度归一化,解决长文档刷词频的问题。词向量与注意力属于深度模型,倒排索引两代算法都要用,停用词过滤只是预处理步骤。

Q4. BM25 中参数 b 的主要作用是什么?

单选题 · mid

  • A.控制词频饱和的快慢速度
  • B.控制逆文档频率的平滑程度
  • C.控制文档长度的惩罚强度← 正确答案
  • D.控制查询词数量的多少范围

答案:C。b 出现在 \(1-b+b\cdot|D|/\text{avgdl}\) 中,决定文档相对长度对分母的影响。b=0 时完全不看长度,b 越大长文档惩罚越强。k1 控制饱和速度,IDF 的平滑由 +0.5 项完成,查询词数量由用户输入决定。

Q5. 其他条件不变,k1 调大后词频对 BM25 分数的影响如何变化?

单选题 · mid

  • A.词频影响越接近线性增长← 正确答案
  • B.词频影响会越快趋于饱和
  • C.词频影响将不再起作用
  • D.词频影响会变为指数增长

答案:A。词频分量 \(f(k_1+1)/(f+k_1)\) 中,k1 越大分母里 f 的系数越小,曲线越平直、越接近线性;k1 越小饱和来得越快。封顶值 \(k_1+1\) 是常数,所以不可能指数增长,更不会失效。

Q6. 混合检索中为什么必须先把两路分数归一化?

单选题 · hard

  • A.因为 BM25 和稠密分数量纲完全不同← 正确答案
  • B.因为查询词必须编码成同一向量
  • C.因为需要把文档长度统一对齐
  • D.因为两路分数全部都是负值

答案:A。BM25 得分是几十到几百的累计分,稠密检索是余弦相似度(约 -1 到 1),直接相加等于稠密检索被淹没。MinMax 归一化把两路都压到 0~1 再加权,融合才有意义。长度对齐是 BM25 内部的事,编码统一属于稠密分支的设计。

Q7. 当词频 f 趋于无穷时,BM25 的词频分量趋近于?

单选题 · extreme

  • A.趋近于无穷大的数值
  • B.趋近于 k1 加一的常数← 正确答案
  • C.趋近于参数 b 的倒数
  • D.趋近于零没有任何效果

答案:B。求极限 \(\lim_{f\to\infty} f(k_1+1)/(f+k_1) = k_1+1\)。这就是“饱和”的数学表达:不管词出现几百次,贡献封顶为 \(k_1+1\) 倍 IDF。而 TF-IDF 的词频项线性发散,这正是两者最核心的数学分歧。

Q8. 哪些场景更适合选用稀疏检索?

多选题 · mid

  • A.产品型号的精确匹配场景← 正确答案
  • B.法律条文的关键词查询场景← 正确答案
  • C.同义改写后的语义查询应用
  • D.代码片段与日志检索场景← 正确答案

答案:ABD。稀疏检索擅长字面硬匹配:型号、法条编号、代码符号都是精确词且可解释性强。同义改写、口语化表达是它的短板,应交给稠密检索或混合检索处理,所以 C 应排除。

Q9. 关于 BM25 参数调优,正确的做法是?

多选题 · mid

  • A.文档偏长时适当增大 b 值← 正确答案
  • B.直接套默认参数从不评估
  • C.调整后用验证集对比指标← 正确答案
  • D.把 k1 设为零让词频失效

答案:AC。语料普遍偏长时增大 b 能强化长度惩罚;每次调整必须用 Recall@K、MRR 等评估集指标验证。从不评估无法知道参数好坏;把 k1 设为 0 会使词频分量对所有词恒为 1,词频彻底失去区分度,属于典型错误调参。

Q10. 关于 TF-IDF 与 BM25 的说法,正确的有?

多选题 · hard

  • A.TF-IDF 的词频贡献是线性增长← 正确答案
  • B.BM25 对长文档的评分更公平← 正确答案
  • C.两者都基于词面匹配原理← 正确答案
  • D.BM25 已经完全不受 IDF 影响

答案:ABC。TF-IDF 词频项就是 f,确实线性增长;BM25 用长度归一化避免长文档靠篇幅刷分;两者都在词面层匹配,不懂同义词。BM25 公式里保留了 IDF 项且做了平滑,D 说法错误。