<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>稀疏向量 on 边个濑椰的博客</title><link>https://ottercoconut.github.io/tags/%E7%A8%80%E7%96%8F%E5%90%91%E9%87%8F/</link><description>Recent content in 稀疏向量 on 边个濑椰的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Tue, 12 May 2026 16:17:17 +0800</lastBuildDate><atom:link href="https://ottercoconut.github.io/tags/%E7%A8%80%E7%96%8F%E5%90%91%E9%87%8F/index.xml" rel="self" type="application/rss+xml"/><item><title>稀疏向量与 SPLADE 模型</title><link>https://ottercoconut.github.io/p/%E7%A8%80%E7%96%8F%E5%90%91%E9%87%8F%E4%B8%8E-splade-%E6%A8%A1%E5%9E%8B/</link><pubDate>Tue, 12 May 2026 16:17:17 +0800</pubDate><guid>https://ottercoconut.github.io/p/%E7%A8%80%E7%96%8F%E5%90%91%E9%87%8F%E4%B8%8E-splade-%E6%A8%A1%E5%9E%8B/</guid><description>&lt;h2 id="参考网站"&gt;参考网站
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/naver/splade" target="_blank" rel="noopener"
 &gt;Naver SPLADE 官方仓库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://huggingface.co/naver/splade_v2_max" target="_blank" rel="noopener"
 &gt;naver/splade_v2_max 模型页&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.sbert.net/docs/package_reference/sparse_encoder/index.html" target="_blank" rel="noopener"
 &gt;Sentence Transformers Sparse Encoder 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.sbert.net/docs/sparse_encoder/training_overview.html" target="_blank" rel="noopener"
 &gt;Sentence Transformers Sparse Encoder 训练概览&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://sbert.net/docs/sparse_encoder/usage/efficiency.html" target="_blank" rel="noopener"
 &gt;Sentence Transformers Sparse Encoder 推理加速&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="稀疏向量与-splade-模型"&gt;稀疏向量与 SPLADE 模型
&lt;/h1&gt;&lt;p&gt;在 RAG 系统里，dense vector 已经是最常见的召回方式。它把文本映射到连续向量空间中，适合捕捉语义相近的表达，例如“员工离职流程”和“人员退出手续”。但 dense vector 也有明显短板：它不一定擅长精确匹配实体、编号、术语、错误码、产品型号、表格字段名和代码片段。&lt;/p&gt;
&lt;p&gt;这时 sparse vector 就有价值了。它更像一个“神经网络增强版倒排索引”：文本仍然被表示为词项维度上的稀疏权重，但这些权重不是 BM25 这类纯统计方法算出来的，而是由模型预测出来的。&lt;/p&gt;
&lt;p&gt;简单说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dense vector 负责语义相似。&lt;/li&gt;
&lt;li&gt;BM25 负责原词匹配。&lt;/li&gt;
&lt;li&gt;SPLADE sparse vector 负责神经词项扩展后的加权匹配。&lt;/li&gt;
&lt;li&gt;Hybrid Search 则把 dense 和 sparse 两种召回结果融合起来。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="论文模型"&gt;论文模型
&lt;/h2&gt;&lt;p&gt;SPLADE 基于 Masked Language Model 的 logits，把一段文本映射到词表空间中。假设词表里有 30522 个 WordPiece token，那么每个文本最终可以表示为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;token_id -&amp;gt; weight
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;也就是一个稀疏向量。大部分 token 的权重为 0，只有少量被模型认为重要的 token 会有非零权重。&lt;/p&gt;
&lt;p&gt;这和普通 embedding 最大的区别是：dense embedding 的每一维通常不可解释，而 sparse vector 的维度就是词表 token。一个被模型激活的 token 可以理解为“这段文本与这个词项相关”。&lt;/p&gt;
&lt;p&gt;例如一段文档没有显式出现“报销”，但出现了“差旅费”“发票”“审批单”，SPLADE 可能会激活与“报销”相关的 token。这样在查询“报销流程”时，即便文档没有完全命中原词，也可能被召回。&lt;/p&gt;
&lt;p&gt;更具体地说，SPLADE 会利用 Masked Language Model 层的 logits，在 BERT WordPiece 词表中预测每个词项的重要性。假设输入文本在分词后是：&lt;/p&gt;
$$
t=(t_{1},t_{2},...,t_{N})
$$&lt;p&gt;对应的上下文表示是：&lt;/p&gt;
$$
(h_{1},h_{2},...,h_{N})
$$&lt;p&gt;那么对输入中第 $i$ 个 token，模型会计算它对词表中第 $j$ 个 token 的重要性：&lt;/p&gt;
$$
w_{ij}=transform(h_{i})^{T}E_{j}+b_{j}, \quad j\in\{1,...,|V|\}
$$&lt;p&gt;这里 $E_j$ 词表 ${token}_j$ 的 BERT 输入嵌入，$b_j$ 是 token 级偏置，&lt;code&gt;transform(.)&lt;/code&gt; 通常是带 GeLU 和 LayerNorm 的线性变换。直觉上，这一步是在问：输入里的这个位置，和词表里的每个词项分别有多相关。&lt;/p&gt;
&lt;p&gt;但检索时需要的不是“某个位置对某个词的分数”，而是“整段文本对某个词的分数”。因此 SPLADE 会把不同位置的激活聚合成整段文本的稀疏表示：&lt;/p&gt;
$$
w_{j}=\sum_{i\in t}\log(1+ReLU(w_{ij}))
$$&lt;p&gt;这个公式里有三层含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ReLU&lt;/code&gt; 把负分清零，只保留正向相关的词项。&lt;/li&gt;
&lt;li&gt;$log(1+x)$ 做对数饱和，避免高频词或重复词的分数被无限放大。&lt;/li&gt;
&lt;li&gt;$\sum$ 把不同位置对同一个词表 token 的激活累加起来，得到整段文本的词项权重。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后，文本就变成了一个很高维但很稀疏的向量：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;token_id -&amp;gt; weight
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;查询和文档都映射到同一个词表空间后，检索分数就是稀疏向量点积：&lt;/p&gt;
$$
s(q,d)=\sum_j w_j^q w_j^d
$$&lt;p&gt;这也是 SPLADE 能接入倒排索引或稀疏向量索引的原因。&lt;/p&gt;
&lt;h3 id="排序损失"&gt;排序损失
&lt;/h3&gt;&lt;p&gt;训练时，SPLADE 要让相关文档得分更高，不相关文档得分更低。给定一个查询 $q_i$ 、一个正样本文档 $d_i^+$ 、一个困难负样本 $d_i^-$ ，以及一组批内负样本 ${d_{i,j}^{-}}$ ，可以使用类似下面的对比排序损失：&lt;/p&gt;
$$
\mathcal{L}_{rank-IBN} =
-\log
\frac{e^{s(q_i,d_i^+)}}
{e^{s(q_i,d_i^+)} + e^{s(q_i,d_i^-)} + \sum e^{s(q_i,d_{i,j}^{-})}}
$$&lt;p&gt;它的目标很直接：让正样本在候选集合里的概率尽可能大。工程上可以理解为，模型会不断学习哪些词项扩展能帮助它把正确文档排到前面。&lt;/p&gt;
&lt;h3 id="flops-稀疏正则"&gt;FLOPS 稀疏正则
&lt;/h3&gt;&lt;p&gt;如果只优化排序效果，模型可能会激活太多 token。这样虽然召回可能变强，但倒排索引会膨胀，查询时需要访问的 posting list 也会变多。&lt;/p&gt;
&lt;p&gt;因此 SPLADE 引入 FLOPS 正则来控制稀疏性。对一批文档来说，可以先估计词表 token (j) 在这批文档中的平均激活：&lt;/p&gt;
$$
\overline{a}_{j}=\frac{1}{N}\sum_{i=1}^{N}w_{j}^{(d_i)}
$$&lt;p&gt;然后对平均激活做平方求和：&lt;/p&gt;
$$
l_{FLOPS}=\sum_{j\in V}\overline{a}_{j}^{2}
=\sum_{j\in V}(\frac{1}{N}\sum_{i=1}^{N}w_{j}^{(d_i)})^{2}
$$&lt;p&gt;这个正则项不是简单控制“向量维度”，而是在控制非零 token 的数量和分布。它希望模型不要把大量文档都绑定到少数高频词上，也不要让每个文档都激活过多词项。&lt;/p&gt;
&lt;p&gt;因此，稀疏性权重可以理解为一个召回质量和检索成本之间的旋钮：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;权重更大：稀疏向量更短，索引更小，检索更快，但可能损失召回。&lt;/li&gt;
&lt;li&gt;权重更小：稀疏向量更长，扩展更丰富，但索引和检索成本更高。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="总体损失"&gt;总体损失
&lt;/h3&gt;&lt;p&gt;最终，SPLADE 会把排序损失和稀疏正则放在一起训练：&lt;/p&gt;
$$
\mathcal{L}=\mathcal{L}_{rank-IBN}
+\lambda_q\mathcal{L}_{reg}^{q}
+\lambda_d\mathcal{L}_{reg}^{d}
$$&lt;p&gt;其中 (\lambda_q) 控制查询侧稀疏性，(\lambda_d) 控制文档侧稀疏性。查询侧通常对延迟更敏感，所以查询侧稀疏性非常重要。文档侧可以离线计算，通常可以接受更高一点的计算成本，但仍然要控制索引体积。&lt;/p&gt;
&lt;h3 id="从求和到最大池化"&gt;从求和到最大池化
&lt;/h3&gt;&lt;p&gt;原始 SPLADE 会对输入文本中每个位置的词项预测进行聚合：&lt;/p&gt;
$$
w_{j}=\sum_{i\in t}\log(1+ReLU(w_{ij}))
$$&lt;p&gt;
后续更常见的 SPLADE-max 使用最大池化：&lt;/p&gt;
$$
w_{j}=\max_{i\in t}\log(1+ReLU(w_{ij}))
$$&lt;p&gt;
这并不是整段文本只保留一个 token，而是对词表里的每个维度分别取最大激活值。这样可以减少长文本或重复词带来的累加放大，让表示更关注“是否强烈激活某个语义词项”，而不是简单依赖出现次数。&lt;/p&gt;
&lt;h3 id="splade-doc-与蒸馏训练"&gt;SPLADE-doc 与蒸馏训练
&lt;/h3&gt;&lt;p&gt;标准 SPLADE 会同时编码 query 和 document。也就是说，查询侧和文档侧都可能产生神经扩展词项，检索时计算的是：&lt;/p&gt;
$$
s(q,d)=\sum_j w_j^q w_j^d
$$&lt;p&gt;SPLADE-doc 则更偏工程效率。它只在文档侧做 SPLADE 编码，查询侧通常只使用原始 query token，文档得分可以写成：&lt;/p&gt;
$$
s(q,d)=\sum_{j\in q}w_j^d
$$&lt;p&gt;这样文档侧扩展可以离线预计算，查询侧不需要跑 SPLADE encoder，延迟会更低。代价是 query 侧没有神经扩展能力，只能利用“文档侧扩展”。&lt;/p&gt;
&lt;p&gt;另外，很多效果较强的 SPLADE 模型会使用知识蒸馏和困难负样本。常见做法是先训练一个第一阶段检索器和 cross-encoder 重排器，再用更难的负样本和重排器分数继续训练。工程上不用自己复现这套训练流程，也能使用公开模型；但理解这一点有助于判断模型名称里 &lt;code&gt;distil&lt;/code&gt;、&lt;code&gt;ensemble&lt;/code&gt;、&lt;code&gt;cocondenser&lt;/code&gt; 这类词为什么会出现。&lt;/p&gt;
&lt;h3 id="稀疏性为什么重要"&gt;稀疏性为什么重要
&lt;/h3&gt;&lt;p&gt;如果模型把大量 token 都激活，召回效果也许会上升，但索引会变大，检索会变慢。SPLADE 使用 FLOPS 正则化来控制非零 token 的数量和分布。&lt;/p&gt;
&lt;p&gt;工程上可以把它理解为：稀疏向量不是越长越好。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非零 token 太少：索引小、检索快，但召回可能不够。&lt;/li&gt;
&lt;li&gt;非零 token 太多：召回可能更好，但索引膨胀，检索成本上升。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;落地时通常还会做二次裁剪，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只保留 top_k 个 token&lt;/li&gt;
&lt;li&gt;过滤 weight 低于阈值的 token&lt;/li&gt;
&lt;li&gt;限制单个 chunk 的最大稀疏维度数量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些参数往往比模型本身更影响线上成本。&lt;/p&gt;
&lt;h2 id="模型选择"&gt;模型选择
&lt;/h2&gt;&lt;p&gt;SPLADE 更像一类稀疏神经检索方法，而不是单一模型。Naver 官方仓库也特别提醒：不同正则化强度会得到从“非常稀疏”到“强 query/doc 扩展”的不同模型，效果、索引大小和延迟都会变。&lt;/p&gt;
&lt;p&gt;如果只是想快速做工程验证，可以从 &lt;code&gt;naver/splade-cocondenser-ensembledistil&lt;/code&gt; 开始。它是官方 SPLADE++ 系列里常见的强效果模型，在 Naver 官方仓库列出的 MS MARCO dev MRR@10 为 38.3，高于 &lt;code&gt;splade_v2_max&lt;/code&gt; 的 34.0 和 &lt;code&gt;splade_v2_distil&lt;/code&gt; 的 36.8。它适合先验证 sparse 召回是否能补足 dense 的关键词、实体、术语召回缺口。&lt;/p&gt;
&lt;p&gt;如果更看重推理成本，可以看 &lt;code&gt;naver/splade_v2_max&lt;/code&gt; 或 efficient SPLADE 系列。&lt;code&gt;splade_v2_max&lt;/code&gt; 结构简单，Hugging Face 模型页标注为 DistilBERT base、512 token 最大长度、30522 维输出、点积相似度。efficient SPLADE 系列则进一步区分 document encoder 与 query encoder，目标是降低查询侧延迟。&lt;/p&gt;
&lt;p&gt;一个实用的选型顺序是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先选一个效果强的公开模型做离线评测，例如 &lt;code&gt;naver/splade-cocondenser-ensembledistil&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果离线评测有效，再统计平均非零 token 数、索引大小、文档侧编码吞吐和查询侧 P95 延迟。&lt;/li&gt;
&lt;li&gt;如果查询侧太慢，优先尝试 query 缓存、ONNX/OpenVINO、量化或 efficient SPLADE。&lt;/li&gt;
&lt;li&gt;如果索引太大，优先调小 top-k、提高最小权重阈值，或选择更强正则化、更稀疏的模型。&lt;/li&gt;
&lt;li&gt;如果业务语料和公开英文检索数据差异很大，再考虑用领域数据微调，而不是直接相信公开榜单。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不要只按 MRR 选模型。SPLADE 选型至少要同时看五件事：检索效果、平均非零维度、索引体积、查询延迟、部署复杂度。&lt;/p&gt;
&lt;p&gt;Sentence Transformers 现在提供了 &lt;code&gt;SparseEncoder&lt;/code&gt;，可以直接加载 SPLADE 模型：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparseEncoder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SparseEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;naver/splade-cocondenser-ensembledistil&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;example query&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它也提供 &lt;code&gt;encode_query()&lt;/code&gt;、&lt;code&gt;encode_document()&lt;/code&gt;、稀疏度统计、Qdrant/Elasticsearch/OpenSearch 集成，以及 ONNX/OpenVINO/量化相关部署能力。工程上可以优先用这条路线做原型，再根据性能瓶颈决定是否切到自定义推理服务。&lt;/p&gt;
&lt;h2 id="splade-和-bm25-的区别"&gt;SPLADE 和 BM25 的区别
&lt;/h2&gt;&lt;p&gt;BM25 和 SPLADE 都可以使用倒排索引完成检索，但权重来源不同。&lt;/p&gt;
&lt;p&gt;BM25 的权重来自统计量，例如 TF、IDF 和文档长度归一化。它主要依赖 query 原词和 document 原词的精确匹配。&lt;/p&gt;
&lt;p&gt;SPLADE 的权重来自神经模型预测。它不仅可以保留原文出现的 token，也可能激活原文没有出现但语义相关的 token。&lt;/p&gt;
&lt;p&gt;因此可以粗略理解为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BM25 = 原始词项的统计匹配
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SPLADE = 神经扩展词项的加权匹配
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在企业知识库、技术文档、客服 FAQ、代码文档、规范制度等场景中，BM25 和 SPLADE 都很有价值。BM25 更轻，SPLADE 更强但成本更高。&lt;/p&gt;</description></item><item><title>Practical BM25</title><link>https://ottercoconut.github.io/p/practical-bm25/</link><pubDate>Sat, 09 May 2026 13:30:00 +0800</pubDate><guid>https://ottercoconut.github.io/p/practical-bm25/</guid><description>&lt;h3 id="参考网站"&gt;参考网站
&lt;/h3&gt;&lt;p&gt;&lt;a class="link" href="https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch" target="_blank" rel="noopener"
 &gt;Practical BM25 - Part 1: How Shards Affect Relevance Scoring in Elasticsearch | Elastic Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables" target="_blank" rel="noopener"
 &gt;Practical BM25 - Part 2: The BM25 Algorithm and its Variables | Elastic Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://www.elastic.co/blog/practical-bm25-part-3-considerations-for-picking-b-and-k1-in-elasticsearch" target="_blank" rel="noopener"
 &gt;Practical BM25 - Part 3: Considerations for Picking b and k1 in Elasticsearch | Elastic Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://zh.wikipedia.org/wiki/Tf-idf" target="_blank" rel="noopener"
 &gt;tf-idf - 维基百科，自由的百科全书&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="background"&gt;Background
&lt;/h2&gt;&lt;p&gt;在 Elasticsearch 5.0 中，我们切换到 Okapi BM25 作为默认相似度算法，用于对搜索结果与查询的相关性进行评分。本文将着重介绍BM25 的实际应用，包括可用参数及其影响评分的因素。&lt;/p&gt;
&lt;h3 id="understanding-how-shards-affect-scoring"&gt;Understanding How Shards Affect Scoring
&lt;/h3&gt;&lt;p&gt;在学习 BM25 前，必须先理解 Elasticsearch 的索引可能被切成多个 shard（索引分片/物理分区）。因为 BM25 的相关性分数不是天然基于整个 index 的全局统计计算，而默认可能是在每个 shard 内部单独计算。shard 数量越多、数据越少，分数越容易出现偏差。&lt;/p&gt;
&lt;p&gt;下面我们照搬参考网站的示例代码，目的是先创建一个 Elasticsearch 索引 &lt;code&gt;people&lt;/code&gt;，再往里面插入几条测试文档，然后用同一个查询词 &lt;code&gt;&amp;quot;Shane&amp;quot;&lt;/code&gt; 反复检索，观察 BM25 相关性分数如何随着文档数量和 shard 分布发生变化：&lt;/p&gt;
&lt;p&gt;作者创建一个名为 &lt;code&gt;people&lt;/code&gt; 的索引，并指定它有 5 个 primary shards，默认相关性算法使用 BM25：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;people&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;number_of_shards&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;index&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;similarity&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;default&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;BM25&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;作者使用它的名字做为示例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;/people/_doc/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Shane&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;/people/_doc/_search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;query&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Shane&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;找出 &lt;code&gt;title&lt;/code&gt; 字段中匹配 &lt;code&gt;&amp;quot;Shane&amp;quot;&lt;/code&gt; 的文档，自然会匹配到&lt;code&gt;/people/_doc/1&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;/people/_doc/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Shane C&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;/people/_doc/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Shane Connelly&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;PUT&lt;/span&gt; &lt;span class="err"&gt;/people/_doc/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Shane P Connelly&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后再次对它们做搜索：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;/people/_doc/_search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;query&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Shane&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;也就是说现在有4条“文档”：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Shane&lt;/li&gt;
&lt;li&gt;Shane C&lt;/li&gt;
&lt;li&gt;Shane Connelly&lt;/li&gt;
&lt;li&gt;Shane P Connelly&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从其中，找出 &lt;code&gt;title&lt;/code&gt; 字段中匹配 &lt;code&gt;&amp;quot;Shane&amp;quot;&lt;/code&gt; 的文档。虽然说&lt;code&gt;title&lt;/code&gt;字段都有&lt;code&gt;&amp;quot;Shane&amp;quot;&lt;/code&gt;，但是它们的BM25分数不会一样。结果：&lt;code&gt;doc1&lt;/code&gt; 和&lt;code&gt;doc3&lt;/code&gt; 分数都是0.2876821，但&lt;code&gt;doc2&lt;/code&gt; 分数是0.19856805，&lt;code&gt;doc4&lt;/code&gt;分数是0.16853254。&lt;/p&gt;
&lt;p&gt;尽管&lt;code&gt;doc2&lt;/code&gt;和&lt;code&gt;doc3&lt;/code&gt;很像，但是分数差了很多，这其实和&amp;quot;C&amp;quot;与&amp;quot;Connelly&amp;quot;的差异关系不大，而是和文档如何落在分片中有关。所以如何让分数更具一致性呢：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;数据量越大，shard 间的统计差异越小&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;减少 shard 数量可以降低打分偏差&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果想让多 shard 情况下的 BM25 打分更接近“全局统计”，可以在查询时加上 &lt;code&gt;?search_type=dfs_query_then_fetch&lt;/code&gt;。它会先收集所有 shard 的词频统计信息，再统一计算分数，因此结果会接近甚至等同于 &lt;code&gt;number_of_shards=1&lt;/code&gt; 时的分数。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;dfs_query_then_fetch&lt;/code&gt; 的作用是先跨 shard 汇总词频统计，再统一计算 BM25 分数，使多 shard 的打分更接近单 shard 的全局效果；但它会增加一次额外通信，所以只有在小数据、多 shard、数据分布不均且很在意相关性分数时才值得使用。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="algorithm-and-its-variables"&gt;Algorithm and its variables
&lt;/h2&gt;&lt;p&gt;BM25 model:
&lt;/p&gt;
$$
\sum_{i}^{n} IDF(q_i) \frac{f(q_i, D) * (k_1 + 1)}{f(q_i, D) + k_1 * (1 - b + b * \frac{fieldLen}{avgFieldLen})}
$$&lt;ul&gt;
&lt;li&gt;$q_i$: 查询中的第 $i$ 个关键词。&lt;/li&gt;
&lt;li&gt;$IDF(q_i)$: 关键词 $q_i$ 的逆文档频率（Inverse Document Frequency）。&lt;/li&gt;
&lt;li&gt;$f(q_i, D)$ :关键词 $q_i$ 在文档 $D$ 中的词频。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$fieldLen$&lt;/strong&gt;: 当前文档的长度。&lt;/li&gt;
&lt;li&gt;$avgFieldLen$: 索引中所有文档的平均长度。&lt;/li&gt;
&lt;li&gt;$k_1$ 和 $b$: 可调参数（通常 $k1 \in [1.2, 2.0]$，$b = 0.75$）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实简单来说，BM25就是引入了非线性，并解决了频次问题的TF-IDF模型。TF-IDF model:
&lt;/p&gt;
$$
\text{Score} = f(q_i, D) \times \log\left(\frac{N}{n(q_i)}\right)
$$&lt;h4 id=""&gt;$q_i$
&lt;/h4&gt;&lt;p&gt;例如，如果我搜索“shane”，这里只有一个查询词，因此 $q_0$ 就是“shane”。如果我用英文搜索“shane connelly”，Elasticsearch 会识别其中的空格，并将其分词（tokenize）为两个词项：$q_0$ 为“shane”，$q_1$ 为“connelly”。这些查询词会被代入公式的其他部分，最后将所有结果加总求和。&lt;/p&gt;
&lt;h4 id=""&gt;$IDF(q_i)$
&lt;/h4&gt;&lt;p&gt;公式中的 &lt;strong&gt;IDF（逆文档频率）&lt;/strong&gt; 部分用于衡量一个词项在所有文档中出现的频率，并会对那些出现次数过多的常用词进行“&lt;strong&gt;惩罚&lt;/strong&gt;”（即降低其权重）。在 Lucene/BM25 算法中，该部分的实际公式如下：
&lt;/p&gt;
$$
\ln \left( 1 + \frac{(docCount - f(q_i) + 0.5)}{f(q_i) + 0.5} \right)
$$&lt;p&gt;
其中，$docCount$ 是该分片中（如果使用的是 &lt;code&gt;search_type=dfs_query_then_fetch&lt;/code&gt; 参数，则为跨所有分片）包含该字段值的文档总数；而 $f(q_i)$ 是包含第 $i$ 个查询词项的文档数量。在我们的示例中可以看到，“shane” 这个词在所有 4 个文档中都出现了，因此对于词项 “shane”，我们最终得到的逆文档频率 $IDF(\text{"shane"})$ 为：
&lt;/p&gt;
$$
\ln\left(1 + \frac{(4 - 4 + 0.5)}{4 + 0.5}\right) = \ln\left(1 + \frac{0.5}{4.5}\right) = 0.105360515657826
$$&lt;p&gt;
$IDF(\text{"connelly"})$为：
&lt;/p&gt;
$$
\ln\left(1 + \frac{(4 - 2 + 0.5)}{2 + 0.5}\right) = \ln\left(1 + \frac{2.5}{2.5}\right) = 0.693147180559945
$$&lt;p&gt;
我们可以看到，包含这些更罕见词项的查询（在我们的 4 文档语料库中，“connelly” 比 “shane” 更罕见）具有更高的&lt;strong&gt;乘数&lt;/strong&gt;，因此它们对最终得分的贡献更大。这在直觉上是合理的：词项 “the” 可能出现在几乎每一篇英文文档中，所以当用户搜索像 “the elephant” 这样的内容时，“elephant” 显然比词项 “the”（几乎存在于所有文档中）更重要——我们也希望它能为搜索得分做出更多贡献。&lt;/p&gt;
&lt;h4 id=""&gt;$fieldLen/avgFieldLen$
&lt;/h4&gt;&lt;p&gt;档中的词项越多（至少是那些没匹配上查询条件的词项），该文档的得分就越低。同样，这在直觉上是合理的：如果一份文档长达 300 页且只提到了我的名字一次，那么它与我的相关性，很可能不如一条同样提到我一次的短推文。&lt;/p&gt;
&lt;h4 id=""&gt;$b$
&lt;/h4&gt;&lt;p&gt;如果 $b$ 的值越大，文档长度相对于平均长度所产生的影响就会被进一步放大。为了理解这一点，可以想象如果将 $b$ 设置为 0，长度比例的影响将完全消失，只受频次影响，文档的长度将对评分不产生任何影响。如果将 $b$ 设置为 1 ，则得分不受频次影响，只受长度比例的影响。&lt;/p&gt;
&lt;h4 id=""&gt;$f(q_i, D)$
&lt;/h4&gt;&lt;p&gt;这个值对应了 TF (Term Frequency, 词频)&lt;/p&gt;
&lt;p&gt;$f(q_i, D)$ 的含义是：“第 $i$ 个查询词项在文档 $D$ 中出现了多少次？”在所有这些文档中，$f(\text{"shane"}, D)$ 都是 1，但 $f(\text{"connelly"}, D)$ 各不相同：在文档 3 和 4 中是 1，但在文档 1 和 2 中是 0。如果有第 5 个文档的文本是 “shane shane”，那么它的 $f(\text{"shane"}, D)$ 就是 2。我们可以看到，$f(q_i, D)$ 同时出现在分子和分母中，此外还有一个特殊的 “$k_1$” 因子，我们稍后会讨论它。理解 $f(q_i, D)$ 的方式是：查询词项在文档中出现的次数越多，其得分就越高。这在直觉上是合理的：一个多次提到我们名字的文档，比只提到一次的文档更有可能与我们相关。&lt;/p&gt;
&lt;h4 id=""&gt;$k_1$
&lt;/h4&gt;&lt;p&gt;在 BM25 算法中，$k_1$ 是控制词频饱和度（Term Frequency Saturation）的核心参数。它通过为词频 $f(q_i, D)$ 对相关性得分的贡献设定渐近上限确保得分随词频增加而产生的边际增益呈非线性递减。相比于传统 TF-IDF 近乎线性的权重增长，这一机制有效抑制了高频词项（如关键词堆砌）对排名的过度干扰；其中，$k_1$ 的取值直接决定了得分趋于饱和的速度：较小的 $k_1$ 值会使词频贡献快速达到瓶颈，而较大的 $k_1$ 值则允许词频在更广的数值范围内维持显著的权重增益。&lt;/p&gt;
&lt;p&gt;如果将 $k_1$ 设置为 0，得分将固定为1。如果将 $k_1$ 设置的很大（比如10000），公式近似退化为 $\frac{TF \times k_1}{k_1} = TF$ ，变成了词频本身。&lt;/p&gt;
&lt;h2 id="picking--and"&gt;Picking $b$ and $k_1$
&lt;/h2&gt;&lt;p&gt;关于 $b$ 和 $k_1$ 的值，ES的文章也指出现在的值是个能涵盖多数情况的“经验值”，但是&lt;strong&gt;不存在全局最优的 b 和 k1，只能结合语料和查询做实验评估。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而且在检索性能不够好时，在调整 $b$ 和 $k_1$的值之前，应该先优化下面的内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对精确短语匹配进行 boost；&lt;/li&gt;
&lt;li&gt;使用 synonyms 扩展用户可能感兴趣的同义表达；&lt;/li&gt;
&lt;li&gt;使用 fuzziness、typeahead、phonetic matching、stemming 等分析组件，处理拼写错误、语言差异和词形变化；&lt;/li&gt;
&lt;li&gt;使用 function score，根据发布时间、地理距离或业务特征调整文档得分。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;至于后文ES的Explain API，不再赘述。&lt;/p&gt;</description></item></channel></rss>