混合搜索不头疼:用检索器简化混合搜索
探索如何利用线性和 RRF 检索器的多字段查询格式简化 Elasticsearch 中的混合搜索,并在不了解 Elasticsearch 索引的情况下创建查询。
Elasticsearch is packed with new features to help you build the best search solutions for your use case. Learn how to put them into action in our hands-on webinar on building a modern Search AI experience. You can also start a free cloud trial or try Elastic on your local machine now.
混合搜索被公认为是一种强大的搜索方法,它将词法搜索的精确性和速度与语义搜索的自然语言能力结合在一起。不过,在实际应用中可能会很棘手,往往需要对索引有深入的了解,并通过非繁琐的配置来构建冗长的查询。在本博客中,我们将探讨线性和 RRF 检索器的多字段查询格式如何使混合搜索变得更简单、更易用,从而消除常见的头痛问题,让您更轻松地充分利用其强大功能。我们还将回顾多字段查询格式如何使您在不了解索引的情况下执行混合搜索查询。
分数范围问题
首先,让我们回顾一下混合搜索困难的主要原因之一:不同的分数范围。我们的老朋友BM25会产生无限制的分数。换句话说,BM25 可以生成从接近 0 到(理论上)无穷大的分数。与此相反,针对dense_vector 字段的查询会产生介于 0 和 1 之间的分数。由于semantic_text 混淆了用于索引嵌入的字段类型,因此除非您对索引和推理端点配置有详细了解,否则很难说清查询的分数范围。这在试图交错使用词汇和语义搜索结果时会带来问题,因为即使语义结果更相关,词汇结果也可能优先于语义结果。对于这个问题,普遍接受的解决方案是在交织结果之前对分数进行归一化处理。Elasticsearch 为此提供了两种工具:线性检索器和RRF检索器。
RRF检索器采用RRF 算法,将文档排名作为衡量相关性的标准,并舍弃分数。由于不考虑分数,因此分数范围不匹配不是问题。
线性检索器使用线性组合来确定文档的最终得分。这包括获取文档中每个组件查询的得分,对其进行归一化处理,然后求和生成总分。在数学上,这一操作可以表示为
Total Score = 𝚺(N(Sx))其中N 是归一化函数,SX 是查询 X 的得分。归一化功能在这里非常关键,因为它将每个查询的得分转换为使用相同的范围。您可以在这里了解有关线性寻回猎犬的更多信息。
分解
用户可以利用这些工具实现有效的混合搜索,但需要对索引有一定的了解。让我们看一个使用线性检索器的示例,在这个示例中,我们将查询一个包含两个字段的索引:
PUT linear_retriever_example
{
"mappings": {
"properties": {
"semantic_text_field": { <1>
"type": "semantic_text",
"inference_id": ".multilingual-e5-small-elasticsearch"
},
"text_field": { <2>
"type": "text"
}
}
}
}1.semantic_text_field 是一个semantic_text 字段,使用文本嵌入模型E5
text_field 是一个标准的text 字段
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"retrievers": [
{
"retriever": {
"standard": {
"query": {
"match": { <1>
"semantic_text_field": "foo"
}
}
}
},
"normalizer": "minmax"
},
{
"retriever": {
"standard": {
"query": {
"match": {
"text_field": "foo"
}
}
}
},
"normalizer": "minmax"
}
]
}
}
}1.我们在 字段上使用 查询,我们 在 Elasticsearch 8.18/9.0 中添加了对match 该查询的 支持semantic_text
在构建查询时,我们需要牢记semantic_text_field 使用的是文本嵌入模型,因此对它的任何查询都会产生 0 到 1 之间的分数。我们还需要知道text_field 是一个标准的text 字段,因此对它的查询将产生一个无限制的分数。为了创建具有适当相关性的结果集,我们需要使用一种检索器,在合并查询得分之前将其归一化。在本例中,我们使用了minmax 归一化的线性检索器,它将每个查询的得分归一化为介于 0 和 1 之间的值。
本例中的查询结构相当简单,因为只涉及两个字段。然而,随着字段的增加和类型的变化,它很快就会变得复杂。这表明,要编写有效的混合搜索查询,往往需要对所查询的索引有更深入的了解,这样才能在组合之前对组件查询得分进行适当的归一化处理。这对混合搜索的广泛应用构成了障碍。
查询分组
让我们扩展一下示例:如果我们想查询一个text 字段和两个semantic_text 字段,该怎么办?我们可以构建这样一个查询:
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"retrievers": [
{
"retriever": {
"standard": {
"query": {
"semantic": {
"field": "semantic_text_field_1",
"query": "foo"
}
}
}
},
"normalizer": "minmax"
},
{
"retriever": {
"standard": {
"query": {
"semantic": {
"field": "semantic_text_field_2",
"query": "foo"
}
}
}
},
"normalizer": "minmax"
},
{
"retriever": {
"standard": {
"query": {
"match": {
"text_field": "foo"
}
}
}
},
"normalizer": "minmax"
}
]
}
}
}这表面上看起来不错,但也有潜在的问题。现在,semantic_text 场比赛占总分的⅔:
Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)这可能不是你想要的结果,因为这会造成分数不平衡。在只有 3 个字段的示例中,这种影响可能并不明显,但如果查询的字段较多,就会出现问题。例如,大多数索引包含的词法字段远远多于语义字段(即dense_vector,sparse_vector, 或semantic_text) 。如果我们使用上述模式查询一个包含 9 个词法字段和 1 个语义字段的索引呢?词性匹配将占得分的 90% ,从而削弱语义搜索的有效性。
解决这一问题的常用方法是将查询分为词汇和语义两个类别,并对两者进行平均加权。这就避免了任一类别在总分中占主导地位。
让我们付诸实践。在使用线性检索器时,本例中的分组查询方法会是怎样的?
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"retrievers": [
{
"retriever": {
"linear": {
"retrievers": [
{
"retriever": {
"standard": {
"query": {
"semantic": {
"field": "semantic_text_field_1",
"query": "foo"
}
}
}
},
"normalizer": "minmax"
},
{
"retriever": {
"standard": {
"query": {
"semantic": {
"field": "semantic_text_field_2",
"query": "foo"
}
}
}
},
"normalizer": "minmax"
}
]
}
},
"normalizer": "minmax"
},
{
"retriever": {
"standard": {
"query": {
"match": {
"text_field": "foo"
}
}
}
},
"normalizer": "minmax"
}
]
}
}
}哇,真是啰嗦!您甚至可能需要上下滚动多次才能查看整个查询!在这里,我们使用两级标准化来创建查询组。数学上可以表示为
Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)这第二级规范化可确保semantic_text 字段和text 字段的查询权重均匀。请注意,在本例中,我们省略了text_field 的二级规范化,因为只有一个词法字段,这样可以避免更多的繁琐。
这种查询结构已经很笨重了,而且我们只查询三个字段。随着查询字段的增多,即使是经验丰富的搜索从业人员也越来越难以驾驭。
多字段查询格式
我们在 Elasticsearch 8.19、9.1 和 无服务器 中为线性和 RRF 检索器添加了 多字段查询格式 ,以简化所有这些操作。现在,您只需使用"...... "即可执行与上述相同的查询:
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
"query": "foo",
"normalizer": "minmax"
}
}
}这将查询从 55 行缩减到 9 行!Elasticsearch 自动将索引映射用于
确定每个查询字段的类型
将每个字段归入一个词汇或语义类别
在最终得分中平均分配每个类别的权重
这样,任何人都可以执行有效的混合搜索查询,而无需了解有关索引或所用推理端点的详细信息。
使用 RRF 时,可以省略normalizer ,因为排名是相关性的代表:
GET rrf_retriever_example/_search
{
"retriever": {
"rrf": {
"fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
"query": "foo"
}
}
}每场增强
在使用线性检索器时,您可以应用每个字段增强功能来调整某些字段中匹配的重要性。例如,假设您要查询四个字段:两个semantic_text 字段和两个text 字段:
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field_1", "text_field_2" ],
"query": "foo",
"normalizer": "minmax"
}
}
}默认情况下,每个字段在其组(词法或语义)中的权重相同。比分细目如下
换句话说,每个字段占总分的 25% 。
我们可以使用field^boost 语法为任何字段添加每个字段的提升。让我们将semantic_text_field_1 和text_field_1 提升 2:
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"fields": [ "semantic_text_field_1^2", "semantic_text_field_2", "text_field_1^2", "text_field_2" ]
"query": "foo",
"normalizer": "minmax"
}
}
}现在的比分是这样的
每个查询组的权重仍然相同,但组内字段的权重发生了变化:
semantic_text_field_1是语义查询组得分的 66% ,是总分的 33%text_field_1是词法查询组得分的 66% ,是总分的 33%
ℹ️ 请注意,在按字段提升时,总分范围不会改变。这是分数标准化的预期副作用,可确保词法和语义查询分数保持直接可比性。 |
|---|
ℹ️ 在 Elasticsearch 9.2+ 中,每个字段的提升也可与 RRF 检索器一起使用 |
通配符分辨率
您可以在fields 参数中使用* 通配符来匹配多个字段。继续上面的例子,这个查询在功能上等同于明确查询emantic_text_field_1,semantic_text_field_2, 和text_field_1 :
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"fields": [ "semantic_text_field_*", "*_field_1" ],
"query": "foo",
"normalizer": "minmax"
}
}
}值得注意的是,*_field_1 模式同时匹配text_field_1 和semantic_text_field_1 。查询将自动执行,就像明确查询每个字段一样。semantic_text_field_1 同时匹配两种模式也没有问题;在执行查询之前,所有匹配的字段名称都会被去除重复。
您可以通过多种方式使用通配符:
前缀匹配(例如:
*_text_field)内联匹配 (ex:
semantic_*_field)后缀匹配(例如:
semantic_text_field_*)
您还可以使用多个通配符来应用上述组合,例如*_text_field_* 。
默认查询字段
多字段查询格式还允许您查询您一无所知的索引。如果省略fields 参数,它将查询由index.query.default_field 索引设置指定的所有字段:
GET linear_retriever_example/_search
{
"retriever": {
"linear": {
"query": "foo",
"normalizer": "minmax"
}
}
}默认情况下,index.query.default_field 设置为* 。该通配符将解析索引中支持术语查询的所有字段类型,其中大多数字段类型都支持术语查询。例外情况是
dense_vector领域rank_vector领域几何领域:
geo_point,shape
当你想在第三方提供的索引上执行混合搜索查询时,该功能尤其有用。多字段查询格式可让您以简单的方式执行适当的查询。只需排除fields 参数,就能查询所有适用字段。
结论
分数范围问题会让有效的混合搜索实施起来很头疼,尤其是在对所查询的索引或所使用的推理端点了解有限的情况下。线性和 RRF 检索器的多字段查询格式将基于查询分组的自动混合搜索方法打包到简单易用的应用程序接口中,从而减轻了这种痛苦。附加功能(如按字段增强、通配符解析和默认查询字段)扩展了功能,涵盖了多种使用情况。
立即试用多字段查询格式
您可以通过 免费试用 ,在完全托管的 Elasticsearch Serverless 项目中使用多字段查询格式检查线性检索器和 RRF 检索器。它还提供从 8.19& 9.1 开始的堆栈版本。
只需一条命令,几分钟即可在本地环境中开始使用:
curl -fsSL https://elastic.co/start-local | sh



