本文主要是介绍从零开发短视频电商 OpenSearch k-NN插件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- k-NN索引
- 简介
- 索引设置(Setting)
- Script Score k-NN 精准KNN
- 创建KNN字段索引
- 添加数据
- 精确KNN搜索
- 近似K-NN搜索
- 示例
- 创建KNN字段索引
- 添加数据
- 近似KNN查询
- 带过滤器的k-NN搜索
- 过滤搜索优化
该篇OpenSearch的版本为2.11
- https://opensearch.org/docs/2.11/search-plugins/knn/index/
k-NN 插件是 k-近邻的缩写,使用户能够在向量索引中搜索查询点的 k-近邻。要确定邻居,您可以指定要用于测量点之间距离的空间(距离函数)。
用例包括推荐(例如,音乐应用程序中的“您可能喜欢的其他歌曲”功能)、图像识别和欺诈检测。有关 k-NN 搜索的更多背景信息,请参阅维基百科。
该插件支持三种不同的方法来从向量索引中获取 k 最近邻:
- 近似 k-NN ,通常,这些算法会牺牲索引速度和搜索准确性,以换取性能优势,例如更低的延迟、更小的内存占用和更具可扩展性的搜索。要了解有关算法的更多信息,请参阅 nmslib 和 faiss 的文档。
- 近似 k-NN 是需要低延迟的大型索引(即数十万个向量或更多)搜索的最佳选择。如果您想在 k-NN 搜索之前对索引应用过滤器,则不应使用近似 k-NN,这会大大减少要搜索的向量数量。在这种情况下,您应该使用Script Score k-NN或 Painless 扩展。
- Script Score k-NN,精确的 k-NN 搜索。通过这种方法,您可以对索引中的向量子集运行 k-NN 搜索(有时称为预过滤搜索)。使用此方法搜索较小的文档主体或需要预过滤器时。对大型索引使用此方法可能会导致高延迟。预过滤,先keyword过滤后向量搜索。
- Painless extensions ,可以在更复杂的组合中使用它。与 k-NN 脚本评分类似,您可以使用此方法在索引中执行强力、精确的 k-NN 搜索,该搜索也支持预过滤。与 Script Score k-NN相比,此方法的查询性能稍慢。如果您的用例需要对最终分数进行更多自定义,则应使用此方法而不是Script Score k-NN。
总的来说,对于较大的数据集,您通常应该选择近似最近邻方法,因为它的扩展性明显更好。对于较小的数据集,您可能想要应用过滤器,您应该选择Script Score k-NN。如果您有更复杂的用例,需要使用距离函数作为评分方法的一部分,则应该使用Painless extensions 。
k-NN索引
简介
k-NN 插件引入了自定义数据类型 knn_vector
,它允许用户将其 k-NN 向量提取到 OpenSearch 索引中并执行不同类型的 k-NN 搜索。 knn_vector
字段是高度可配置的,可以服务于许多不同的 k-NN 工作负载。
Mapping映射参数 | 必需的 | 默认 | 可更新 | 描述 |
---|---|---|---|---|
name | true | n/a | false | 最近邻方法的标识符。hnsw、ivf(需要训练),一般都是选hnsw |
space_type | false | l2 | false | 用于计算向量之间距离的向量空间类型。 |
engine | false | nmslib | false | 用于索引和搜索的近似 k-NN 库。可用的库有 faiss、nmslib 和 lucene。 |
parameters | null | false | 用于最近邻法的参数。 |
Mapping示例
{"type": "knn_vector","dimension": 100,"method": {"name":"hnsw","engine":"lucene","space_type": "l2","parameters":{"m":2048,"ef_construction": 245}}
}{"type":"knn_vector","dimension":100,"method":{"name":"hnsw","engine":"faiss","space_type":"l2","parameters":{"encoder":{"name":"pq","parameters":{"code_size":8,"m":8}}}}
}{"type":"knn_vector","dimension":100,"method":{"name":"ivf","engine":"faiss","space_type":"l2","parameters":{"nlist":4,"nprobes":2}}
}
选择建议
构建 knn_vector
字段时有很多选项可供选择。要确定选择正确的方法和参数,做出哪些权衡。
要考虑的因素包括
(1) 查询延迟
(2) 查询质量
(3) 内存限制
(4) 索引延迟
- 如果内存不是问题,HNSW 提供非常强大的查询延迟/查询质量权衡。
- 如果您想使用比 HNSW 更少的内存和更快的索引,同时保持类似的查询质量,您应该用IVF。
- 如果内存是一个问题,请考虑将 PQ 编码器添加到您的 HNSW 或 IVF 索引中。由于PQ是有损编码,查询质量会下降。
内存估计
HNSW 所需的内存估计为 1.1 * (4 * dimension + 8 * M)
字节/向量。
例如,假设您有 100 万个向量,维度为 256,M 为 16。内存需求可估计如下:
1.1 * (4 * 256 + 8 * 16) * 1,000,000 ~= 1.267 GB
- m:插件为每个新元素创建的双向链接的数量。增加和减少该值会对内存消耗产生很大影响。将此值保持在 2 到 100 之间。
IVF 所需的内存估计为 1.1 * (((4 * dimension) * num_vectors) + (4 * nlist * d))
字节。
例如,假设您有 100 万个向量,维度为 256,nlist 为 128。内存需求可估计如下:
1.1 * (((4 * 256) * 1,000,000) + (4 * 128 * 256)) ~= 1.126 GB
- nlist:将向量划分成的桶的数量。较高的值可能会导致更准确的搜索,但会牺牲内存和训练延迟。
索引设置(Setting)
k-NN 插件引入了几个索引设置,也可用于配置 k-NN 结构。
设置中定义的几个参数正在弃用过程中。这些参数应该在映射(Mapping)中设置,而不是在索引(Index)设置中。映射中设置的参数将覆盖索引设置中设置的参数。在映射中设置参数允许索引拥有多个具有不同参数的 knn_vector
字段。
Setting | Default | Updatable | Description |
---|---|---|---|
index.knn | false | false | 索引是否应该为 knn_vector 字段构建本机库索引。如果设置为 false, knn_vector 字段将存储在文档值中,但近似 k-NN 搜索功能将被禁用。 |
index.knn.algo_param.ef_search | 512 | true | k-NN 搜索期间使用的动态列表的大小。值越高,搜索越准确,但搜索速度越慢。仅适用于 nmslib。 |
index.knn.algo_param.ef_construction | 512 | false | 1.0.0 中已弃用。请使用映射参数来设置该值。 |
index.knn.algo_param.m | 16 | false | 1.0.0 中已弃用。请使用映射参数来设置该值。 |
index.knn.space_type | l2 | false | 1.0.0 中已弃用。请使用映射参数来设置该值。 |
Script Score k-NN 精准KNN
可以在执行最近邻搜索之前对索引应用过滤器。这对于索引主体可能根据其他条件而变化的动态搜索情况很有用。
如果您打算仅使用分数脚本方法(而不是近似方法),您可以将 index.knn
设置为 false
。
创建KNN字段索引
创建一个包含两个 knn_vector
字段的索引
PUT my-knn-index-2
{"mappings": {"properties": {"my_vector": {"type": "knn_vector","dimension": 2},"color": {"type": "keyword"}}}
}
添加数据
POST _bulk
{ "index": { "_index": "my-knn-index-2", "_id": "1" } }
{ "my_vector": [1, 1], "color" : "RED" }
{ "index": { "_index": "my-knn-index-2", "_id": "2" } }
{ "my_vector": [2, 2], "color" : "RED" }
{ "index": { "_index": "my-knn-index-2", "_id": "3" } }
{ "my_vector": [3, 3], "color" : "RED" }
{ "index": { "_index": "my-knn-index-2", "_id": "4" } }
{ "my_vector": [10, 10], "color" : "BLUE" }
{ "index": { "_index": "my-knn-index-2", "_id": "5" } }
{ "my_vector": [20, 20], "color" : "BLUE" }
{ "index": { "_index": "my-knn-index-2", "_id": "6" } }
{ "my_vector": [30, 30], "color" : "BLUE" }
精确KNN搜索
在识别最近的邻居之前,使用 script_score
查询预先过滤您的文档:
GET my-knn-index-2/_search
{"size": 2,"query": {"script_score": {"query": {"bool": {"filter": {"term": {"color": "BLUE"}}}},"script": {"lang": "knn","source": "knn_score","params": {"field": "my_vector","query_value": [9.9, 9.9],"space_type": "l2"}}}}
}
所有参数都是必需的。
lang
是脚本类型。该值通常为painless
,但此处必须指定knn
。source
是脚本的名称knn_score
。field
是包含矢量数据的字段。query_value
是您要查找最近邻居的点。对于欧几里得和余弦相似空间,该值必须是与字段映射中设置的维度相匹配的浮点数组。对于汉明位距离,该值可以是有符号长整型或 Base64 编码字符串(分别适用于长整型和二进制字段类型)。space_type
对应距离函数。l1,l2,linf,cosinesimil,innerproduct,hammingbit- 余弦相似度返回 -1 到 1 之间的数字,并且由于 OpenSearch 相关性分数不能低于 0,因此 k-NN 插件加 1 以获得最终分数。
近似K-NN搜索
标准 k-NN 搜索方法使用强力方法计算相似度,该方法测量查询与多个点之间的最近距离,从而产生准确的结果。这在许多应用中效果很好。然而,在高维数据集非常大的情况下,这会产生缩放问题,从而降低搜索效率。近似 k-NN 搜索方法可以通过使用更有效地重构索引并降低可搜索向量的维数的工具来克服这个问题。使用这种方法需要牺牲准确性,但会显着提高搜索处理速度。
OpenSearch 利用的近似 k-NN 搜索方法使用 nmslib、faiss 和 Lucene 库中的近似最近邻 (ANN) 算法来支持 k-NN 搜索。这些搜索方法采用 ANN 来改善大型数据集的搜索延迟。在 k-NN 插件提供的三种搜索方法中,此方法为大型数据集提供了最佳的搜索可扩展性。当数据集达到数十万个向量时,此方法是首选方法。
k-NN 插件在索引期间为每个 knn 向量字段/Lucene 段对构建向量的本机库索引,可用于在搜索期间有效地查找查询向量的 k 最近邻。
由于本机库索引是在索引期间构建的,因此不可能对索引应用过滤器然后使用此搜索方法。所有过滤器都应用于近似最近邻搜索产生的结果。
示例
要使用 k-NN 插件的近似搜索功能,您必须首先创建一个 k-NN 索引,并将 index.knn
设置为 true
。此设置告诉插件为索引创建本机库索引。
接下来,您必须添加一个或多个 knn_vector
数据类型的字段。此示例创建一个包含两个 knn_vector
字段的索引,一个使用 faiss
字段,另一个使用 nmslib
字段:
创建KNN字段索引
PUT my-knn-index-1
{"settings": {"index": {"knn": true,"knn.algo_param.ef_search": 100}},"mappings": {"properties": {"my_vector1": {"type": "knn_vector","dimension": 2,"method": {"name": "hnsw","space_type": "l2","engine": "nmslib","parameters": {"ef_construction": 128,"m": 24}}},"my_vector2": {"type": "knn_vector","dimension": 4,"method": {"name": "hnsw","space_type": "innerproduct","engine": "faiss","parameters": {"ef_construction": 256,"m": 48}}}}}
}
knn_vector
数据类型支持浮点数向量,对于 nmslib 和 faiss 引擎,该向量的维度计数最多为 16,000,由维度映射参数设置。 Lucene 库的最大维度数为 1,024。
添加数据
POST _bulk
{ "index": { "_index": "my-knn-index-1", "_id": "1" } }
{ "my_vector1": [1.5, 2.5], "price": 12.2 }
{ "index": { "_index": "my-knn-index-1", "_id": "2" } }
{ "my_vector1": [2.5, 3.5], "price": 7.1 }
{ "index": { "_index": "my-knn-index-1", "_id": "3" } }
{ "my_vector1": [3.5, 4.5], "price": 12.9 }
{ "index": { "_index": "my-knn-index-1", "_id": "4" } }
{ "my_vector1": [5.5, 6.5], "price": 1.2 }
{ "index": { "_index": "my-knn-index-1", "_id": "5" } }
{ "my_vector1": [4.5, 5.5], "price": 3.7 }
{ "index": { "_index": "my-knn-index-1", "_id": "6" } }
{ "my_vector2": [1.5, 5.5, 4.5, 6.4], "price": 10.3 }
{ "index": { "_index": "my-knn-index-1", "_id": "7" } }
{ "my_vector2": [2.5, 3.5, 5.6, 6.7], "price": 5.5 }
{ "index": { "_index": "my-knn-index-1", "_id": "8" } }
{ "my_vector2": [4.5, 5.5, 6.7, 3.7], "price": 4.4 }
{ "index": { "_index": "my-knn-index-1", "_id": "9" } }
{ "my_vector2": [1.5, 5.5, 4.5, 6.4], "price": 8.9 }
近似KNN查询
GET my-knn-index-1/_search
{"size": 2,"query": {"knn": {"my_vector2": {"vector": [2, 3, 5, 6],"k": 2}}}
}
k
是每个图的搜索将返回的邻居数量。您还必须包含 size
选项,该选项指示查询实际返回的结果数量。该插件返回每个分片(和每个段)的 k
数量的结果以及整个查询的 size
数量的结果。该插件支持的最大 k
值为 10,000。
带过滤器的k-NN搜索
1.高效的 k-NN 过滤:此方法在 k-NN 搜索期间应用过滤,而不是在 k-NN 搜索之前或之后,这确保返回 k
结果(如果至少有 k
总计结果)。此方法由以下引擎支持:
- 具有分层可导航小世界 (HNSW) 算法的 Lucene 引擎(k-NN 插件版本 2.4 及更高版本)
- 具有 HNSW 算法(k-NN 插件版本 2.9 及更高版本)或 IVF 算法(k-NN 插件版本 2.10 及更高版本)的 Faiss 引擎
2.后过滤:由于它是在 k-NN 搜索之后执行的,因此此方法可能会返回明显少于限制性过滤器的 k
结果。您可以为此方法使用以下两种过滤策略:
- 布尔后置过滤器:此方法运行近似最近邻 (ANN) 搜索,然后对结果应用过滤器。两个查询部分独立执行,然后根据查询中提供的查询运算符(
should
、must
等)组合结果。 post_filter
参数:此方法对完整数据集运行 ANN 搜索,然后将过滤器应用于 k-NN 结果。
3.评分脚本过滤器:此方法涉及预过滤文档集,然后对过滤后的子集运行精确的 k-NN 搜索。当过滤的子集很大时,它可能具有高延迟并且无法扩展。
下表总结了前面的过滤用例。
Filter | 何时应用过滤器 | 搜索类型 | 支持的引擎和方法 | 在哪里放置 filter 子句 |
---|---|---|---|---|
Efficient k-NN filtering 高效的 k-NN 过滤 | 搜索期间(预过滤和后过滤的混合) | Approximate 近似 | - lucene (hnsw ) - lucene ( hnsw ) - faiss (hnsw , ivf ) - faiss ( hnsw , ivf ) | 在 k-NN 查询子句内。 |
Boolean filter 布尔过滤器 | After search (post-filtering) 搜索后(后过滤) | Approximate 近似 | - lucene - nmslib - faiss | 在 k-NN 查询子句之外。必须是叶子句。 |
post_filter 参数 | After search (post-filtering) 搜索后(后过滤) | Approximate 近似 | - lucene - nmslib - faiss | Outside the k-NN query clause. 在 k-NN 查询子句之外。 |
Scoring script filter 评分脚本过滤器 | Before search (pre-filtering) 搜索前(预过滤) | Exact 精确 | N/A | 在脚本分数查询子句内。 |
过滤搜索优化
一旦您估计了索引中的文档数量、过滤器的限制性以及所需的最近邻居数量,请使用下表选择可优化召回或延迟的过滤方法。
索引中的文档数 | 过滤器返回的文档百分比 | k | 用于提高召回率的过滤方法 | 用于降低延迟的过滤方法 |
---|---|---|---|---|
10M | 2.5 | 100 | 高效的 k-NN 过滤/评分脚本 | 评分脚本 |
10M | 38 | 100 | 高效的 k-NN 过滤 | 高效的 k-NN 过滤 |
10M | 80 | 100 | 高效的 k-NN 过滤 | 高效的 k-NN 过滤 |
1M | 2.5 | 100 | 高效的 k-NN 过滤/评分脚本 | 评分脚本 |
1M | 38 | 100 | 高效的 k-NN 过滤 | 高效的 k-NN 过滤 |
1M | 80 | 100 | 高效的 k-NN 过滤 | 高效的 k-NN 过滤 |
这篇关于从零开发短视频电商 OpenSearch k-NN插件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!