您听说过稀疏神经检索吗?如果听说过,您是否在生产环境中使用过它?
这是一个潜力巨大的领域——谁不想使用一种结合了密集检索和基于词项的文本检索优点的方法呢?然而,它并不那么流行。这是否是由于常见的魔咒:“纸上谈兵不如实际应用”?
本文描述了我们迈向稀疏神经检索的历程,使其达到理想状态——轻量级的基于词项的检索器,能够区分词义。
从前人尝试的错误中学习,我们创造了 miniCOIL,一个新的稀疏神经候选项,旨在混合搜索中取代 BM25 的位置。我们乐于与您分享,并期待您的反馈。
好的、坏的和丑的
稀疏神经检索不像它所基于的方法——基于词项的检索和密集检索——那样广为人知。它们的弱点推动了该领域的发展,并指导着其演变。让我们沿着这条路径追溯。

检索器演变
基于词项的检索
基于词项的检索通常将文本视为一个词袋。这些词的重要性各不相同,共同促成文档和查询之间的整体相关性得分。
著名的 BM25 根据词语的以下方面来估计它们的贡献:
- 在特定文本中的重要性——基于词频 (TF)。
- 在整个语料库中的显著性——基于逆文档频率 (IDF)。
它还有几个参数反映了语料库中的典型文本长度,您可以在我们对 BM25 公式的详细分解中查看其确切含义。
精确定义词语在文本中的重要性并非易事。
BM25 的基础思想是词项重要性可以统计定义。在长文本中,这种想法接近事实,因为某个词的频繁重复表明文本与这个概念相关。在很短的文本中——例如,用于检索增强生成 (RAG) 的文本块——它的适用性较差,TF 为 0 或 1。我们在我们对 BM25 算法的 BM42 修改版本中尝试解决此问题。
然而,词语对于检索的一个重要组成部分在 BM25 中根本没有考虑——那就是词义。同一个词在不同的语境中有不同的含义,这会影响文本的相关性。想想“水果蝙蝠”和“棒球球棒”——在文本中的重要性相同,但含义不同。
密集检索
如何捕捉含义?像 BM25 这样的词袋模型假定词语在文本中是独立放置的,然而语言学家说:
“知其所处,方知其义” - 约翰·鲁伯特·菲尔斯
这个想法,连同通过数字方式表达词语关系的动机,推动了检索的第二个分支——密集向量——的发展。具有注意力机制的 Transformer 模型解决了区分文本语境中词义的挑战,使其成为检索中相关性匹配的一部分。
然而,密集检索没有(也无法)完全取代基于词项的检索。密集检索器能够进行广泛的语义相似性搜索,但当我们需要包含特定关键词的结果时,它们缺乏精确性。
试图让密集检索器进行精确匹配是徒劳的,因为它们建立在这样的范式下:每个词语在某种程度上都与其他词语存在语义相似性,而这种语义相似性取决于特定模型的训练数据。
稀疏神经检索
因此,一方面,我们对匹配的控制较弱,有时导致检索结果过于宽泛;另一方面,我们有轻量级、可解释且快速的基于词项的检索器,如 BM25,它们无法捕捉语义。
当然,我们希望将两者最好的部分融合在一个模型中,且不带任何缺点。稀疏神经检索的演变正是由这种渴望推动的。
- 为什么是稀疏?基于词项的检索可以操作稀疏向量,文本中的每个词语都被赋予一个非零值(它在该文本中的重要性)。
- 为什么是神经?不是根据词语的统计数据推导出其重要性得分,而是使用能够编码词义的机器学习模型。
那为什么它还没有被广泛使用呢?

现代稀疏神经检索器的问题
稀疏神经检索的详细历史足以写另一整篇文章。总的来说,许多尝试将密集编码器产生的词语表示映射到单一的重要性得分,其中大多数只存在于研究论文中,从未在现实世界中应用(DeepImpact、TILDEv2、uniCOIL)。
大多数稀疏编码器端到端地在相关性目标上进行训练,它们只能在特定领域很好地估计词语重要性。在训练期间未曾“见过”的数据集上,它们的域外准确率比 BM25 要差。
稀疏神经检索的 SOTA (State-of-the-Art) 是 SPLADE——(稀疏词汇和扩展模型)。这个模型已经进入了检索系统——您可以使用 FastEmbed 在 Qdrant 中使用 SPLADE++。
然而,这里有个问题。SPLADE 名称中的“扩展”部分指的是一种技术,用于克服基于词项检索的另一个弱点——词汇不匹配。虽然密集编码器可以成功连接“水果蝙蝠”和“飞狐”等相关词项,但基于词项的检索在这方面却失败了。
SPLADE 通过用额外的适合词项扩展文档和查询来解决这个问题。然而,这使得 SPLADE 的推理变得繁重。此外,生成的表示不再那么稀疏(因此也不轻量),并且由于扩展选择是由机器学习模型决定的,其可解释性也大大降低。
“穿着盔甲的大个子。把那个脱了,你是什么?”
实验表明,没有词项扩展的 SPLADE 又重演了稀疏编码器的老故事——它的性能比 BM25 要差。
着眼于目标:可用的稀疏神经检索
稀疏神经检索领域追求在特定基准上的完美,结果要么是性能比 BM25 域外差的模型(讽刺的是,使用基于 BM25 的困难负例进行训练),要么是基于繁重文档扩展、降低稀疏性的模型。
要在生产环境中可用,稀疏神经检索器应满足的最低标准是:
- 生成轻量级稀疏表示(顾名思义!)。继承基于词项检索的优点,它应该轻量且简单。对于更广泛的语义搜索,可以使用密集检索器。
- 在不同领域排名表现优于 BM25。目标是建立一个能够区分词义(这是 BM25 无法做到的)的基于词项的检索器,同时保留 BM25 经过时间检验的域外性能。

miniCOIL 的想法
受 COIL 的启发
稀疏神经检索领域的一项尝试——上下文感知倒排列表 (COIL)——以其处理词项权重编码的方法脱颖而出。
COIL 的作者没有将高维的 token 表示(通常是 768 维的 BERT 嵌入)压缩成单个数字,而是将它们投影到更小的 32 维向量。他们建议将这些向量原样存储在(用于基于词项检索的)倒排索引的倒排列表中,并通过点积比较向量表示。
这种方法捕捉了更深层次的语义,一个单独的数字无法传达一个词可能拥有的所有细微含义。
尽管有这个优点,COIL 却未能广泛采用,原因有几个方面:
- 倒排索引通常不设计用于存储向量和执行向量操作。
- COIL 在MS MARCO 数据集上端到端地通过相关性目标进行训练,其性能受到领域限制严重。
- 此外,COIL 在 token 层面操作,重用 BERT 的 tokenizer。然而,在词语层面工作更适合基于词项的检索。想象一下,我们想在文档中搜索“retriever”。COIL 会将其分解为
re
、#trie
和#ver
这三个 32 维向量,并分别匹配这三个部分——这不太方便。
然而,COIL 表示允许区分同形异义词,这是 BM25 缺乏的技能。最好的想法不是从零开始。我们提出一种在 COIL 的基础上构建并记住需要改进的地方的方法:
- 我们应该放弃端到端的相关性目标训练,以获得在域外数据上表现良好的模型。没有足够的数据来训练一个能够泛化的模型。
- 我们应该保持表示的稀疏性,并可在经典的倒排索引中重用。
- 我们应该修正 tokenization。这个问题最容易解决,因为在几种稀疏神经检索器中已经完成,并且我们在 BM42 中也学会了这样做。
站在 BM25 的肩膀上
多年来,BM25 在各种领域一直是一个不错的基线——这是有充分理由的。那么为什么还要抛弃这个经过时间考验的公式呢?
我们不训练我们的稀疏神经检索器来分配词语的重要性得分,而是向 BM25 公式添加一个受 COIL 启发的语义组件。
$$ \text{score}(D,Q) = \sum_{i=1}^{N} \text{IDF}(q_i) \cdot \text{Importance}^{q_i}_{D} \cdot {\color{YellowGreen}\text{Meaning}^{q_i \times d_j}} \text{, where term } d_j \in D \text{ equals } q_i $$
然后,如果我们可以成功捕获词语的含义,我们的解决方案本身就可以像结合了语义感知重排器的 BM25 一样工作——换句话说:
- 它可以区分同形异义词;
- 与词干一起使用时,它可以区分词性。

含义组件
如果我们的模型遇到了一个在训练期间没有“见过”的词语,我们可以回退到原始的 BM25 公式!
4D 词袋
COIL 使用 32 个值来描述一个词项。我们需要这么多吗?在不额外研究的情况下,我们能说出有多少个词有 32 种不同的含义?
然而,即使我们在 COIL 表示中使用更少的值,密集向量不适合经典倒排索引的最初问题依然存在。
除非……我们执行一个简单的技巧!

miniCOIL 向量到稀疏表示
想象一个词袋稀疏向量。词汇表中的每个词占据一个单元格。如果该词存在于编码文本中,我们分配一个权重;如果不存在,则为零。
如果我们有一个描述词语含义的 mini COIL 向量,例如在 4D 语义空间中,我们可以为稀疏向量中的词语分配 4 个连续的单元格,每个“含义”维度一个单元格。如果我们不这样做,我们可以回退到带有纯粹 BM25 分数的经典单单元格描述。
这样的表示可以用于任何标准倒排索引。
训练 miniCOIL
现在,我们来到了需要某种方式获取词语含义的低维封装——miniCOIL 向量的部分。
我们希望更巧妙地工作,而不是更努力,并尽可能多地依赖经过时间考验的解决方案。密集编码器擅长在语境中编码词语的含义,因此重用它们的输出将非常方便。此外,如果想将 miniCOIL 添加到混合搜索中,我们可以一石二鸟——因为无论如何都需要进行密集编码器推理。
降维
密集编码器的输出是高维的,因此我们需要执行降维,这应该在语境中保留词语的含义。目标是:
- 避免对相关性目标和标注数据集的依赖;
- 找到一个能够捕捉词语含义之间空间关系的目标;
- 使用尽可能简单的架构。
训练数据
我们希望 miniCOIL 向量可以根据词语的含义进行比较——水果蝙蝠和吸血鬼蝙蝠在低维向量空间中应该比棒球球棒彼此更接近。因此,在对词语的语境化表示进行降维时,我们需要一些校准的基础。
有人说,一个词的含义隐藏在其周围的语境中,或者简单地说,包含这个词的任何文本中。在较长的文本中,我们可能会丢失词语的精确含义。因此,我们应在句子层面工作,并假定共享一个词语的句子应该以一种方式聚类,使得每个簇包含这个词语在一个特定含义下使用的句子。
如果这是真的,我们可以使用复杂的密集编码器对各种句子进行编码,并为输入密集编码器形成一个可重用的空间关系目标。当我们拥有像OpenWebText 数据集这样涵盖整个网络的庞大数据集时,找到包含常用词语的大量文本数据并不是大问题。有了如此大量的数据,我们可以实现泛化和域独立性,这在使用相关性目标时很难实现。
它会奏效的,我“赌”一把
让我们检验一下我们的假设,来看看“bat”这个词。
我们选取了几千个包含这个词的句子,这些句子从OpenWebText 数据集中采样,并使用mxbai-embed-large-v1
编码器进行向量化。目标是检查我们是否能够区分包含“bat”含义相同的句子的任何簇。

2D 空间中的“bat”句子。
一个非常重要的观察:看起来像只蝙蝠 :)
结果有两个大簇,分别与作为动物的“bat”和作为运动器材的“bat”相关,还有两个较小的簇,分别与拍打动作和运动中使用的动词相关。看来这可能奏效!
架构和训练目标
让我们继续处理“bats”。
我们有一个包含不同含义的词语“bat”的句子训练池。使用选择的密集编码器,我们从每个句子中获取“bat”的语境化嵌入,并学习将其压缩到低维的 miniCOIL “bat”空间,由mxbai-embed-large-v1
句子嵌入引导。
我们只处理一个词,因此使用一个线性层进行降维应该足够了,并在其之上添加Tanh 激活函数
,将压缩向量的值映射到 (-1, 1) 范围。选择激活函数是为了使 miniCOIL 表示与密集编码器表示对齐,后者主要通过余弦相似度
进行比较。

词语层面的 miniCOIL 架构
作为训练目标,我们可以选择最小化三元组损失,其中三元组根据mxbai-embed-large-v1
句子嵌入之间的距离进行选择和对齐。我们依赖于mxbai-embed-large-v1
的置信度(边界大小)来指导我们的“bat” miniCOIL 压缩。

miniCOIL 训练
一口一口吃掉大象
现在,我们完全明白了如何为一个词训练 miniCOIL。如何扩展到整个词汇表呢?
如果我们保持简单,继续为每个词训练一个模型呢?这样做有一定的好处:
- 极其简单的架构:甚至每个词一个层就足够了。
- 超快且简单的训练过程。
- 由于架构简单,推理成本低且速度快。
- 发现和调整表现不佳的词语的灵活性。
- 根据领域和用例扩展和缩小词汇表的灵活性。
然后我们可以训练我们感兴趣的所有词语,然后简单地将所有模型组合(堆叠)成一个大的 miniCOIL。

miniCOIL 模型
实现细节
上述训练方法的代码已在此仓库中开源。
基于这种方法训练的 miniCOIL 模型的具体特点如下:
组件 | 描述 |
---|---|
输入密集编码器 | jina-embeddings-v2-small-en (512 维度) |
miniCOIL 向量大小 | 4 维度 |
miniCOIL 词汇表 | 包含 30,000 个最常用英语词语的列表,清除了停用词和少于 3 个字母的词语,取自这里。词语已词干化,以使 miniCOIL 与我们的 BM25 实现对齐。 |
训练数据 | 4000 万个句子——OpenWebText 数据集的一个随机子集。为了方便三元组采样,我们将句子及其mxbai-embed-large-v1 嵌入上传到 Qdrant,并使用类型为word 的 tokenizer 在句子上构建了全文 payload 索引。 |
每个词的训练数据 | 我们为每个词采样 8000 个句子,并形成边界至少为 0.1 的三元组。 此外,我们应用增强——取一个句子,剪掉目标词及其 1-3 个邻居。为了简单起见,我们重用原始句子和增强句子之间的相同相似度得分。 |
训练参数 | 轮次: 60 优化器:Adam,学习率为 1e-4 验证集: 20% |
每个词都在单个 CPU 上训练,每个词训练大约需要五十秒。我们将此minicoil-v1
版本包含在了我们的FastEmbed 库的 v0.7.0 版本中。
您可以在HuggingFace 卡片中查看使用 FastEmbed 的minicoil-v1
示例。
结果
验证损失
输入 Transformer jina-embeddings-v2-small-en
以 83% 的质量(通过三元组衡量)近似了“榜样” Transformer mxbai-embed-large-v1
的语境关系。这意味着在 17% 的情况下,jina-embeddings-v2-small-en
将从mxbai-embed-large-v1
中取出一个句子三元组,并以从mxbai
视角看负例比正例更接近锚点的方式进行嵌入。
我们获得的验证损失,取决于 miniCOIL 向量大小(4、8 或 16),分别表明 miniCOIL 正确区分了 76%(大小为 256 的批量中平均有 60 个失败三元组)到 85%(大小为 256 的批量中平均有 38 个失败三元组)的三元组。

验证损失
基准测试
基准测试代码已在此仓库中开源。
为了检查我们的 4D miniCOIL 版本在不同领域的性能,讽刺的是,我们选择了与许多稀疏神经检索器将其高基准值作为终点所用的BEIR 数据集相同的子集。然而,不同之处在于miniCOIL 未在 BEIR 数据集上训练,因此不应偏向它们。
我们正在测试我们的 4D miniCOIL 模型与我们的 BM25 实现。两个方法都使用以下参数将 BEIR 数据集索引到 Qdrant:
k = 1.2
,b = 0.75
用于 BM25 评分的推荐默认值;avg_len
在相应数据集的 50,000 个文档上估算。
我们基于NDCG@10
指标比较模型,因为我们关注 miniCOIL 与 BM25 的排名性能。两者都基于精确匹配检索相同的索引文档子集,但 miniCOIL 理想情况下应根据其语义理解更好地对这个子集进行排名。
我们测试的几个领域的结果如下:
数据集 | BM25 (NDCG@10) | MiniCOIL (NDCG@10) |
---|---|---|
MS MARCO | 0.237 | 0.244 |
NQ | 0.304 | 0.319 |
Quora | 0.784 | 0.802 |
FiQA-2018 | 0.252 | 0.257 |
HotpotQA | 0.634 | 0.633 |
我们可以看到 miniCOIL 在测试的五个领域中有四个表现略优于 BM25。这表明我们正朝着正确的方向前进。
主要收获
本文描述了我们尝试构建一个能够泛化到域外数据的轻量级稀疏神经检索器。稀疏神经检索具有巨大潜力,我们希望看到它获得更多关注。
为什么这种方法有用?
这种训练稀疏神经检索器的方法:
- 不依赖于相关性目标,因为它以自监督方式训练,因此不需要标注数据集进行扩展。
- 基于经验证的 BM25 公式构建,只是简单地添加了一个语义组件。
- 创建了轻量级稀疏表示,可以放入标准的倒排索引中。
- 完全重用密集编码器的输出,使其适用于不同的模型。这也使得 miniCOIL 成为混合搜索解决方案的廉价升级。
- 使用极其简单的模型架构,miniCOIL 词汇表中每个词都有一个可训练层。这使得训练和推理速度极快。此外,这种词语层面的训练使得为特定用例扩展 miniCOIL 的词汇表变得容易。
将对的工具用在对的地方
miniCOIL 检索器何时适用?
如果您需要精确的词项匹配,但基于 BM25 的检索无法满足您的需求,因为 BM25 会对形式正确但语义含义错误的文档进行较高排名。
假设您正在文档中实现搜索。在这种用例中,基于关键词的搜索占主导地位,但 BM25 不会考虑这些关键词在不同语境下的含义。例如,如果您在我们的文档中搜索“数据点”,您会更希望看到“一个点是 Qdrant 中的一条记录”排名高于浮点精度,而此时基于 miniCOIL 的检索是值得考虑的替代方案。
此外,miniCOIL 很好地融入了混合搜索,因为它增强了稀疏检索,而资源消耗没有明显增加,直接重用了密集编码器产生的语境化词语表示。
总而言之,miniCOIL 的工作方式应该像 BM25 理解词语含义并根据这种语义知识对文档进行排名一样。它只在精确匹配上操作,因此如果您旨在查找与查询语义相似但用不同词语表达的文档,密集编码器是更好的选择。
接下来的计划?
我们将继续努力改进我们的方法——无论是在深度上寻找提高模型质量的方法,还是在广度上将其扩展到各种密集编码器和英语以外的语言。
我们很乐意与您分享这条迈向可用稀疏神经检索的道路!