近似 k-NN 搜索
标准的 k-最近邻搜索方法采用暴力计算方式,通过测量查询点与多个点之间的最近距离来计算相似性,从而得出精确结果。这在许多应用中效果良好。然而,对于维度极高、数据量极大的数据集,这会产生可扩展性问题,从而降低搜索效率。近似 k-NN 搜索方法通过采用更有效地重组索引和降低可搜索向量维度的工具,可以克服这一问题。使用这种方法需要牺牲一定的准确性,但能显著提高搜索处理速度。
UDB-SX 中的近似 k-NN 搜索方法使用来自 NMSLIB、Faiss 和 Lucene 库的近似最近邻算法来驱动 k-NN 搜索。这些搜索方法利用 ANN 来提高大型数据集的搜索延迟。在 UDB-SX 提供的三种搜索方法中,此方法为大型数据集提供了最佳的搜索可扩展性。当数据集达到数十万个向量时,此方法是首选方法。
有关 UDB-SX 支持的算法的信息,请参阅 方法与引擎。
在索引过程中,UDB-SX 会为每个 knn-vector 字段/Lucene 段对构建一个向量的原生库索引,该索引可在搜索期间用于高效查找查询向量的 k 个最近邻。要了解更多关于 Lucene 段的信息,请参阅 Apache Lucene 文档。这些原生库索引在搜索期间被加载到原生内存中,并由缓存管理。要了解更多关于将原生库索引预加载到内存中的信息,请参阅 预热 API。此外,您可以使用统计 API 查看哪些原生库索引已加载到内存中。
由于原生库索引是在索引构建期间创建的,因此无法先在索引上应用过滤器再使用此搜索方法。所有过滤器都是在 ANN 搜索产生的结果上应用的。
开始使用近似 k-NN
要使用近似搜索功能,您必须首先创建一个将 index.knn 设置为 true 的向量索引。此设置告诉 UDB-SX 为该索引创建原生库索引。
接下来,您必须添加一个或多个 knn_vector 数据类型的字段。以下示例使用 faiss 引擎创建了一个包含两个 knn_vector 字段的索引:
PUT my-knn-index-1
{
"settings": {
"index": {
"knn": true,
"knn.algo_param.ef_search": 100
}
},
"mappings": {
"properties": {
"my_vector1": {
"type": "knn_vector",
"dimension": 2,
"space_type": "l2",
"method": {
"name": "hnsw",
"engine": "faiss",
"parameters": {
"ef_construction": 128,
"m": 24
}
}
},
"my_vector2": {
"type": "knn_vector",
"dimension": 4,
"space_type": "innerproduct",
"method": {
"name": "hnsw",
"engine": "faiss",
"parameters": {
"ef_construction": 256,
"m": 48
}
}
}
}
}
}
在 UDB-SX 中,编解码器负责索引的存储和检索。UDB-SX 使用自定义编解码器将向量数据写入原生库索引,以便底层的 k-NN 搜索库能够读取。
创建索引后,您可以向其中添加一些数据:
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 查询类型对数据运行 ANN 搜索:
GET my-knn-index-1/_search
{
"size": 2,
"query": {
"knn": {
"my_vector2": {
"vector": [2, 3, 5, 6],
"k": 2
}
}
}
}
返回结果的数量
在上述查询中,k 表示每个图搜索返回的邻居数量。您还必须包含 size 参数,该参数表示您希望查询返回的最终结果数量。
对于 NMSLIB 和 Faiss 引擎,k 表示一个分片的所有段返回的最大文档数。对于 Lucene 引擎,k 表示一个分片返回的文档数。k 的最大值为 10,000。
对于任何引擎,每个分片都会向协调节点返回 size 个结果。因此,协调节点接收到的结果总数为 size * 分片数。协调节点整合从所有节点接收到的结果后,查询返回前 size 个结果。
下表提供了几种场景下不同引擎返回结果数量的示例。对于这些示例,假设段和分片中包含的文档数量足以返回表中指定的结果数。
size |
k |
主分片数量 | 每个分片的段数 | Faiss/NMSLIB 返回的结果数 | Lucene 返回的结果数 |
|---|---|---|---|---|---|
| 10 | 1 | 1 | 4 | 4 | 1 |
| 10 | 10 | 1 | 4 | 10 | 10 |
| 10 | 1 | 2 | 4 | 8 | 2 |
仅当 k 小于 size 时,Faiss/NMSLIB 返回的结果数才会与 Lucene 返回的结果数不同。如果 k 和 size 相等,所有引擎返回的结果数相同。
从 UDB-SX 2.14 开始,您可以在径向搜索中使用 k、min_score 或 max_distance 参数。
基于模型构建向量索引
对于 UDB-SX 支持的一些算法,原生库索引需要先经过训练才能使用。训练每个新创建的段成本很高,因此 UDB-SX 引入了模型的概念,在段创建期间初始化原生库索引。您可以通过调用训练 API 并传入训练数据源和模型的方法定义来创建模型。训练完成后,模型会被序列化到 k-NN 模型系统索引中。然后,在索引期间,从该索引中提取模型以初始化段。
要训练模型,您首先需要一个包含训练数据的 UDB-SX 索引。训练数据可以来自任何 knn_vector 字段,只要其维度与您要创建的模型的维度匹配即可。训练数据可以与您计划索引的数据相同,也可以来自单独的数据集。要创建训练索引,请发送以下请求:
PUT /train-index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"train-field": {
"type": "knn_vector",
"dimension": 4
}
}
}
}
请注意,索引设置中未设置 index.knn。这确保您不会为此索引创建原生库索引。
现在您可以向索引添加一些数据:
POST _bulk
{ "index": { "_index": "train-index", "_id": "1" } }
{ "train-field": [1.5, 5.5, 4.5, 6.4]}
{ "index": { "_index": "train-index", "_id": "2" } }
{ "train-field": [2.5, 3.5, 5.6, 6.7]}
{ "index": { "_index": "train-index", "_id": "3" } }
{ "train-field": [4.5, 5.5, 6.7, 3.7]}
{ "index": { "_index": "train-index", "_id": "4" } }
{ "train-field": [1.5, 5.5, 4.5, 6.4]}
完成对训练索引的索引操作后,您可以调用训练 API:
POST /_plugins/_knn/models/my-model/_train
{
"training_index": "train-index",
"training_field": "train-field",
"dimension": 4,
"description": "My model description",
"method": {
"name": "ivf",
"engine": "faiss",
"parameters": {
"encoder": {
"name": "pq",
"parameters": {
"code_size": 2,
"m": 2
}
}
}
}
}
训练 API 会在训练作业启动后立即返回。要检查作业状态,请使用获取模型 API:
GET /_plugins/_knn/models/my-model?filter_path=state&pretty
{
"state": "training"
}
一旦模型进入 created 状态,您就可以创建一个将使用此模型初始化其原生库索引的索引:
PUT /target-index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"index.knn": true
},
"mappings": {
"properties": {
"target-field": {
"type": "knn_vector",
"model_id": "my-model"
}
}
}
}
最后,您可以将要搜索的文档添加到索引中:
POST _bulk
{ "index": { "_index": "target-index", "_id": "1" } }
{ "target-field": [1.5, 5.5, 4.5, 6.4]}
{ "index": { "_index": "target-index", "_id": "2" } }
{ "target-field": [2.5, 3.5, 5.6, 6.7]}
{ "index": { "_index": "target-index", "_id": "3" } }
{ "target-field": [4.5, 5.5, 6.7, 3.7]}
{ "index": { "_index": "target-index", "_id": "4" } }
{ "target-field": [1.5, 5.5, 4.5, 6.4]}
数据摄入后,可以像任何其他 knn_vector 字段一样进行搜索。