Elasticsearch:介绍 kNN query,这是进行 kNN 搜索的专家方法

作者:来自 Elastic Mayya Sharipova, Benjamin Trent

当前状况:kNN 搜索作为顶层部分

Elasticsearch 中的 kNN 搜索被组织为搜索请求的顶层(top level)部分。 我们这样设计是为了:

  • 无论分片数量多少,它总是可以返回全局 k 个最近邻居
  • 这些全局 k 个结果与其他查询的结果相结合以形成混合搜索
  • 全局 k 结果被传递到聚合以形成统计(facets)。

这是 kNN 搜索在内部执行的简化图(省略了一些阶段):

图 1:顶层 kNN 搜索的步骤是:

  1. 用户提交搜索请求
  2. 协调器节点在 DFS 阶段向数据节点发送请求的 kNN 搜索部分
  3. 每个数据节点运行 kNN 搜索并将本地 top-k 结果发送回协调器
  4. 协调器合并所有本地结果以形成全局前 k 个最近邻居。
  5. 协调器将全局 k 个最近邻居发送回数据节点,并提供任何其他查询
  6. 每个数据节点运行额外的查询并将本地 size 结果发送回协调器
  7. 协调器合并所有本地结果并向用户发送响应

我们首先在 DFS 阶段运行 kNN 搜索以获得全局前 k 个结果。 然后,这些全局 k 结果被传递到搜索请求的其他部分,例如其他查询或聚合。 即使执行看起来很复杂,但从用户的角度来看,运行 kNN 搜索的模型很简单,因为用户始终可以确保 kNN 搜索返回全局 k 结果。

它的请求格式如下:

GET collection-with-embeddings/_search
{"knn": {"field": "text_embedding.predicted_value","query_vector_builder": {"text_embedding": {"model_id": "sentence-transformers__msmarco-distilbert-base-tas-b","model_text": "How is the weather in Jamaica?"}},"k": 10,"num_candidates": 100},"_source": ["id","text"]
}

引入 kNN 查询

随着时间的推移,我们意识到还需要将 kNN 搜索表示为查询。 查询是 Elasticsearch 中搜索请求的核心组件,将 kNN 搜索表示为查询可以灵活地将其与其他查询结合起来,以解决更复杂的请求。

kNN 查询与顶层 kNN 搜索不同,没有 k 参数。 与其他查询一样,返回的结果(最近邻居)的数量由 size 参数定义。 与 kNN 搜索类似,num_candidates 参数定义在执行 kNN 搜索时在每个分片上考虑多少个候选者。

GET products/_search
{"size" : 3,"query": {"knn": {"field": "embedding","query_vector": [2,2,2,0],"num_candidates": 10}}
}

kNN 查询的执行方式与顶层 kNN 搜索不同。 下面是一个简化图,描述了 kNN 查询如何在内部执行(省略了一些阶段):

图 2:基于查询的 kNN 搜索步骤如下:

  • 用户提交搜索请求
  • 协调器向数据节点发送一个 kNN 搜索查询,并提供附加查询
  • 每个数据节点运行查询并将本地大小结果发送回协调器节点
  • 协调器节点合并所有本地结果并向用户发送响应

我们在一个分片上运行 kNN 搜索以获得 num_candidates 结果; 这些结果将传递给分片上的其他查询和聚合,以从分片获取大小结果。 由于我们不首先收集全局 k 个最近邻居,因此在此模型中,收集的且对其他查询和聚合可见的最近邻居的数量取决于分片的数量。

kNN 查询 API 示例

让我们看一下 API 示例,这些示例演示了顶层 kNN 搜索和 kNN 查询之间的差异。

我们创建产品索引并索引一些文档:

PUT products
{"mappings": {"dynamic": "strict","properties": {"department": {"type": "keyword"},"brand": {"type": "keyword"},"description": {"type": "text"},"embedding": {"type": "dense_vector","index": true,"similarity": "l2_norm"},"price": {"type": "float"}}}
}
POST products/_bulk?refresh=true
{"index":{"_id":1}}
{"department":"women","brand": "Levi's", "description":"high-rise red jeans","embedding":[1,1,1,1],"price":100}
{"index":{"_id":2}}
{"department":"women","brand": "Calvin Klein","description":"high-rise beautiful jeans","embedding":[1,1,1,1],"price":250}
{"index":{"_id":3}}
{"department":"women","brand": "Gap","description":"every day jeans","embedding":[1,1,1,1],"price":50}
{"index":{"_id":4}}
{"department":"women","brand": "Levi's","description":"jeans","embedding":[2,2,2,0],"price":75}
{"index":{"_id":5}}
{"department":"women","brand": "Levi's","description":"luxury jeans","embedding":[2,2,2,0],"price":150}
{"index":{"_id":6}}
{"department":"men","brand": "Levi's", "description":"jeans","embedding":[2,2,2,0],"price":50}
{"index":{"_id":7}}
{"department":"women","brand": "Levi's", "description":"jeans 2023","embedding":[2,2,2,0],"price":150}

kNN 查询类似于顶层 kNN 搜索,具有 num_candidates 和充当预过滤器的内部 filter 参数。

GET products/_search?filter_path=**.hits
{"size" : 3,"query": {"knn": {"field": "embedding","query_vector": [2,2,2,0],"num_candidates": 10,"filter" : {"term" : {"department" : "women"}}}}
} 
{"hits": {"hits": [{"_index": "products","_id": "4","_score": 1,"_source": {"department": "women","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 75}},{"_index": "products","_id": "5","_score": 1,"_source": {"department": "women","brand": "Levi's","description": "luxury jeans","embedding": [2,2,2,0],"price": 150}},{"_index": "products","_id": "7","_score": 1,"_source": {"department": "women","brand": "Levi's","description": "jeans 2023","embedding": [2,2,2,0],"price": 150}}]}
}

kNN 查询比 kNN collapsing 和聚合搜索可以获得更多样化的结果。 对于下面的 kNN 查询,我们在每个分片上执行 kNN 搜索以获得 10 个最近邻居,然后将其传递到 collapsing 以获取 3 个顶部结果。 因此,我们将在响应中得到 3 个不同的点击。

GET products/_search?filter_path=**.hits
{"size" : 3,"query": {"knn": {"field": "embedding","query_vector": [2,2,2,0],"num_candidates": 10,"filter" : {"term" : {"department" : "women"}}}},"collapse": {"field": "brand"        }
}
{"hits": {"hits": [{"_index": "products","_id": "4","_score": 1,"_source": {"department": "women","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 75},"fields": {"brand": ["Levi's"]}},{"_index": "products","_id": "2","_score": 0.2,"_source": {"department": "women","brand": "Calvin Klein","description": "high-rise beautiful jeans","embedding": [1,1,1,1],"price": 250},"fields": {"brand": ["Calvin Klein"]}},{"_index": "products","_id": "3","_score": 0.2,"_source": {"department": "women","brand": "Gap","description": "every day jeans","embedding": [1,1,1,1],"price": 50},"fields": {"brand": ["Gap"]}}]}
}

顶层 kNN 搜索首先在 DFS 阶段获取全局前 3 个结果,然后在查询阶段将它们传递到 collapse。 我们在响应中只会得到 1 个命中,因为全球 3 个最近的邻居恰好都来自同一品牌。

与聚合类似,kNN query 允许我们获得 3 个不同的存储桶,而 kNN search 仅允许 1 个。

GET products/_search?filter_path=aggregations
{
"size": 0,
"query": {"knn": {"field": "embedding","query_vector": [2,2,2,0],"num_candidates": 10,"filter" : {"term" : {"department" : "women"}}}},"aggs": {"brands": {"terms": {"field": "brand"}}}
}
{"aggregations": {"brands": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "Levi's","doc_count": 4},{"key": "Calvin Klein","doc_count": 1},{"key": "Gap","doc_count": 1}]}}
}

而顶层的 search 是这样的:

GET products/_search?filter_path=aggregations
{"size": 0,"knn": {"field": "embedding","query_vector": [2,2,2,0],"k": 3,"num_candidates": 10,"filter": {"term": {"department": "women"}}},"aggs": {"brands": {"terms": {"field": "brand"}}}
}
{"aggregations": {"brands": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "Levi's","doc_count": 3}]}}
}

现在,让我们看一下其他示例,展示 kNN 查询的灵活性。 具体来说,它如何能够灵活地与其他查询结合起来。

kNN 可以是 boolean 查询的一部分(需要注意的是,所有外部查询过滤器都用作 kNN 搜索的后过滤器)。 我们可以使用 kNN 查询的 _name 参数来通过额外信息来增强结果,这些信息告诉 kNN 查询是否匹配及其分数贡献。

GET products/_search?include_named_queries_score
{"size": 3,"query": {"bool": {"should": [{"knn": {"field": "embedding","query_vector": [2,2,2,0],"num_candidates": 10,"_name": "knn_query"}},{"match": {"description": {"query": "luxury","_name": "bm25query"}}}]}}
}
{"took": 2,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 7,"relation": "eq"},"max_score": 2.8042283,"hits": [{"_index": "products","_id": "5","_score": 2.8042283,"_source": {"department": "women","brand": "Levi's","description": "luxury jeans","embedding": [2,2,2,0],"price": 150},"matched_queries": {"knn_query": 1,"bm25query": 1.8042282}},{"_index": "products","_id": "4","_score": 1,"_source": {"department": "women","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 75},"matched_queries": {"knn_query": 1}},{"_index": "products","_id": "6","_score": 1,"_source": {"department": "men","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 50},"matched_queries": {"knn_query": 1}}]}
}

kNN 也可以是复杂查询的一部分,例如 pinned 查询。 当我们想要显示最接近的结果,但又想要提升选定数量的其他结果时,这非常有用。

GET products/_search
{"size": 3,"query": {"pinned": {"ids": [ "1", "2" ],"organic": {"knn": {"field": "embedding","query_vector": [2,2,2,0],"num_candidates": 10,"_name": "knn_query"}}}}
}
{"took": 9,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 7,"relation": "eq"},"max_score": 1.7014124e+38,"hits": [{"_index": "products","_id": "1","_score": 1.7014124e+38,"_source": {"department": "women","brand": "Levi's","description": "high-rise red jeans","embedding": [1,1,1,1],"price": 100},"matched_queries": ["knn_query"]},{"_index": "products","_id": "2","_score": 1.7014122e+38,"_source": {"department": "women","brand": "Calvin Klein","description": "high-rise beautiful jeans","embedding": [1,1,1,1],"price": 250},"matched_queries": ["knn_query"]},{"_index": "products","_id": "4","_score": 1,"_source": {"department": "women","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 75},"matched_queries": ["knn_query"]}]}
}

我们甚至可以将 kNN 查询作为 function_score 查询的一部分。 当我们需要为 kNN 查询返回的结果定义自定义分数时,这非常有用:​

GET products/_search
{"size": 3,"query": {"function_score": {"query": {"knn": {"field": "embedding","query_vector": [2,2,2,0],"num_candidates": 10,"_name": "knn_query"}},"functions": [{"filter": { "match": { "department": "men" } },"weight": 100},{"filter": { "match": { "department": "women" } },"weight": 50}]}}
}
{"took": 3,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 7,"relation": "eq"},"max_score": 100,"hits": [{"_index": "products","_id": "6","_score": 100,"_source": {"department": "men","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 50},"matched_queries": ["knn_query"]},{"_index": "products","_id": "4","_score": 50,"_source": {"department": "women","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 75},"matched_queries": ["knn_query"]},{"_index": "products","_id": "5","_score": 50,"_source": {"department": "women","brand": "Levi's","description": "luxury jeans","embedding": [2,2,2,0],"price": 150},"matched_queries": ["knn_query"]}]}
}

当我们想要组合 kNN 搜索和其他查询的结果时,kNN 查询作为 dis_max 查询的一部分非常有用,以便文档的分数来自排名最高的子句,并为任何其他子句提供打破平局的增量。

GET products/_search
{"size": 5,"query": {"dis_max": {"queries": [{"knn": {"field": "embedding","query_vector": [2,2, 2,0],"num_candidates": 3,"_name": "knn_query"}},{"match": {"description": "high-rise jeans"}}],"tie_breaker": 0.8}}
}
{"took": 1,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 7,"relation": "eq"},"max_score": 1.890432,"hits": [{"_index": "products","_id": "1","_score": 1.890432,"_source": {"department": "women","brand": "Levi's","description": "high-rise red jeans","embedding": [1,1,1,1],"price": 100}},{"_index": "products","_id": "2","_score": 1.890432,"_source": {"department": "women","brand": "Calvin Klein","description": "high-rise beautiful jeans","embedding": [1,1,1,1],"price": 250}},{"_index": "products","_id": "4","_score": 1.0679927,"_source": {"department": "women","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 75},"matched_queries": ["knn_query"]},{"_index": "products","_id": "6","_score": 1.0679927,"_source": {"department": "men","brand": "Levi's","description": "jeans","embedding": [2,2,2,0],"price": 50},"matched_queries": ["knn_query"]},{"_index": "products","_id": "5","_score": 1.0556482,"_source": {"department": "women","brand": "Levi's","description": "luxury jeans","embedding": [2,2,2,0],"price": 150},"matched_queries": ["knn_query"]}]}
}

kNN 搜索作为查询已在 8.12 版本中引入。 请尝试一下,如果有任何反馈,我们将不胜感激。

原文:Introducing kNN query, an expert way to do kNN search — Elastic Search Labs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/243483.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么 HTTPS 协议能保障数据传输的安全性?

HTTP 协议 在谈论 HTTPS 协议之前,先来回顾一下 HTTP 协议的概念。 HTTP 协议介绍 HTTP 协议是一种基于文本的传输协议,它位于 OSI 网络模型中的应用层。 HTTP 协议是通过客户端和服务器的请求应答来进行通讯,目前协议由之前的 RFC 2616 拆…

JS进阶-函数进阶(一)

• 函数提升 函数提升与变量提升比较类似,是指函数在声明之前即可被调用。 总结: 1. 函数提升能够使函数的声明调用更灵活 2. 函数表达式不存在提升的现象 3. 函数提升出现在相同作用域当中 • 函数参数 1. 动态参数 arguments 是函数内部内置的伪…

Jmeter 配置元件

Jmeter 配置元件 CSV 数据集配置HTTP Cookie 管理器HTTP Header 信息头管理器增加多个用户案列 使用Jmeter发送请求的时候,需要配置元件,配置请求Header、Cookie、数据集合等。可以模拟多个在线用户登录,修改请求头数据。 CSV 数据集配置 C…

java遍历(for和forEach)

1.dade文件 package model;public class dade {private int id;private String name;public dade() {}public dade(int id, String name) {this.id id;this.name name;}public int getId() {return id;}public void setId(int id) {this.id id;}public String getName() {r…

书法AI全自动切字+识别算法2.0版发布,草书篆书行书楷书识别准确率超过90%,覆盖书法单字30万张

我们开发的业界识别最准覆盖作品最全的书法AI小程序上线了 书法AI全自动切字识别算法2.0版发布,草书篆书行书楷书识别准确率超过90%,准确率甩百度OCR一条街,覆盖书法单字30万张,遥遥领先同行 我们还可为客户提供书法AI全自动切字a…

C# wpf利用Clip属性实现截屏框

wpf截屏系列 第一章 使用GDI实现截屏 第二章 制作截屏框(本章) ______第一节 使用DockPanel制作截屏框 ______第二节 利用Clip属性实现截屏框(本节) 第三章 实现截屏框热键截屏 第四章 实现截屏框实时截屏 第五章 使用ffmpeg命令行实现录屏 文章目录 wp…

Git--基本操作介绍(2)

Git 常用的是以下 6 个命令:git clone、git push、git add 、git commit、git checkout、git pull. 说明: workspace:工作区staging area:暂存区/缓存区local repository:版本库或本地仓库remote repository&#xf…

69.使用Go标准库compress/gzip压缩数据存入Redis避免BigKey

文章目录 一:简介二:Go标准库compress/gzip包介绍ConstantsVariablestype Headertype Reader 三:代码实践1、压缩与解压工具包2、单元测试3、为何压缩后还要用base64编码 代码地址: https://gitee.com/lymgoforIT/golang-trick/t…

搜索与图论第六期 最短路问题

前言 最短路问题真的很重要很重要希望大家都能够完全掌握所有最短路算法!! 一、最短路问题的分类 Dijkstra: Dijkstra算法是一种著名的图算法,主要用于求解有权图中的单源最短路径问题。它由荷兰计算机科学家艾兹赫尔戴克斯特…

vue2(Vuex)、vue3(Pinia)、react(Redux)状态管理

vue2状态管理Vuex Vuex 是一个专为 Vue.js应用程序开发的状态管理模式。它使用集中式存储管理应用的所有组件的状态,以及规则保证状态只能按照规定的方式进行修改。 State(状态):Vuex 使用单一状态树,即一个对象包含全部的应用层…

线性代数的学习和整理23:用EXCEL和python 计算向量/矩阵的:内积/点积,外积/叉积

目录 1 乘法 1.1 标量乘法(中小学乘法) 1.1.1 乘法的定义 1.1.2 乘法符合的规律 1.2 向量乘法 1.2.1 向量:有方向和大小的对象 1.2.2 向量的标量乘法 1.2.3 常见的向量乘法及结果 1.2.4 向量的其他乘法及结果 1.2.5 向量的模长(长度&#xff0…

【极数系列】Flink环境搭建(02)

【极数系列】Flink环境搭建(02) 引言 1.linux 直接在linux上使用jdk11flink1.18.0版本部署 2.docker 使用容器部署比较方便,一键启动停止,方便参数调整 3.windows 搭建Flink 1.18.0版本需要使用Cygwin或wsl工具模拟unix环境…

RAMROM

RAM(Random Access Memory),随机存取存储器,也叫主存,又称内存(动态ROM),是与CPU直接交换数据的内部存储器。它可以随时读写(刷新时除外),而且速度…

Flutter 自定义AppBar实现滚动渐变

1、使用ListView实现上下滚动。 2、使用Stack:允许将其子部件放在彼此的顶部,第一个子部件将放置在底部。所以AppBar,写在ListView下面。 3、MediaQuery.removePadding:当使用ListView的时候发现,顶部有块默认的Padd…

服务器数据恢复—服务器进水导致阵列中磁盘同时掉线的数据恢复案例

服务器数据恢复环境: 数台服务器数台存储阵列柜,共上百块硬盘,划分了数十组lun。 服务器故障&检测: 外部因素导致服务器进水,进水服务器中一组阵列内的所有硬盘同时掉线。 北亚数据恢复工程师到达现场后发现机房内…

链路聚合原理与配置

链路聚合原理 随着网络规模不断扩大,用户对骨干链路的带宽和可靠性提出了越来越高的要求。在传统技术中,常用更换高速率的接口板或更换支持高速率接口板的设备的方式来增加带宽,但这种方案需要付出高额的费用,而且不够灵活。采用…

【GitHub项目推荐--一个语音机器人项目】【转载】

推荐一个腾讯大佬开源的语音对话机器人:wukong-robot ,悟空机器人在 GitHub 上斩获 3.2K 的 Star。 这是一个简单灵活的中文语音对话机器人项目,目的是让中国的开发者也能快速打造个性化的智能音箱,同时,该项目还是第…

JMeter 设置请求头信息的详细步骤

在使用 JMeter 的过程中,我们会遇到需要设置请求头信息的场景。比如: POST 传过去的 Body 数据是 json 格式的。需要填添加头信息:Content-Type:application/json。在 header 中用 token 来传用户的认证信息。 下面,…

编程语言MoonBit新增矩阵函数的语法糖

MoonBit更新 1. 新增矩阵函数的语法糖 新增矩阵函数的语法糖,用于方便地定义局部函数和具有模式匹配的匿名函数: fn init {fn boolean_or { // 带有模式匹配的局部函数true, _ > true_, true > true_, _ > false}fn apply(f, x) {f(x)}le…

【Qt Quick 项目(第一集Qt Quick UI 项目项目创建)】

# Qt Quick 项目 到底什么是Qt Qml、什么是Qt Quick、QtQuick应用程序与Qt Widget程序有何区别,为了让读者在学习QML之前有一个整体认识,这里先介绍几个Quick项目。 01 Qt Quick UI 项目