正在加载

kNN 搜索

Elastic Stack 无服务器

k 近邻 (kNN) 搜索根据相似性指标查找与查询向量最接近的 k 个向量。

kNN 的常见用例包括

  • 搜索

    • 语义文本搜索
    • 图像/视频相似性
  • 推荐

    • 产品建议
    • 协同过滤
    • 内容发现
  • 分析

    • 异常检测
    • 模式匹配
  • 要运行 kNN 搜索,您的数据必须转换为向量。您可以在 Elasticsearch 中使用 NLP 模型,或在 Elasticsearch 外部生成它们。

    • 稠密向量需要使用 dense_vector 字段类型。
    • 查询表示为具有相同维度的向量。 您应该使用与生成文档向量相同的模型来生成查询向量。
    • 如果您已经有向量,请参阅自带稠密向量指南。
  • 要完成本指南中的步骤,您必须具有以下索引权限

    • create_indexmanage 以创建带有 dense_vector 字段的索引
    • createindexwrite 以将数据添加到您创建的索引
    • read 来搜索索引

Elasticsearch 支持两种 kNN 搜索方法

在大多数情况下,您会想要使用近似 kNN。 近似 kNN 以较低的延迟为代价,但索引速度较慢且准确性不完美。

精确、暴力 kNN 保证了准确的结果,但不适用于大型数据集。 使用这种方法,script_score 查询必须扫描每个匹配的文档以计算向量函数,这可能导致搜索速度缓慢。 但是,您可以通过使用查询来限制传递给该函数的匹配文档的数量来提高延迟。 如果您将数据过滤到一小部分文档,则可以使用此方法获得良好的搜索性能。

警告

与其他类型的搜索相比,近似 kNN 搜索具有特定的资源要求。 特别是,所有向量数据必须适合节点的页面缓存才能有效。 有关配置和大小调整的重要说明,请参阅近似 kNN 搜索调整指南

要运行近似 kNN 搜索,请使用knn 选项来搜索一个或多个启用了索引的 dense_vector 字段。

  1. 显式映射一个或多个 dense_vector 字段。 近似 kNN 搜索需要以下映射选项

    • 一个 similarity 值。 此值确定用于根据查询向量和文档向量之间的相似性对文档进行评分的相似性指标。 有关可用指标的列表,请参阅similarity 参数文档。 similarity 设置默认为 cosine
     PUT image-index {
      "mappings": {
        "properties": {
          "image-vector": {
            "type": "dense_vector",
            "dims": 3,
            "similarity": "l2_norm"
          },
          "title-vector": {
            "type": "dense_vector",
            "dims": 5,
            "similarity": "l2_norm"
          },
          "title": {
            "type": "text"
          },
          "file-type": {
            "type": "keyword"
          }
        }
      }
    }
    
  2. 索引您的数据。

     POST image-index/_bulk?refresh=true { "index": { "_id": "1" } }
    { "image-vector": [1, 5, -20], "title-vector": [12, 50, -10, 0, 1], "title": "moose family", "file-type": "jpg" }
    { "index": { "_id": "2" } }
    { "image-vector": [42, 8, -15], "title-vector": [25, 1, 4, -12, 2], "title": "alpine lake", "file-type": "png" }
    { "index": { "_id": "3" } }
    { "image-vector": [15, 11, 23], "title-vector": [1, 5, 25, 50, 20], "title": "full moon", "file-type": "jpg" }
    ...
    
  3. 使用knn 选项knn 查询(专家案例)运行搜索。

     POST image-index/_search {
      "knn": {
        "field": "image-vector",
        "query_vector": [-5, 9, -12],
        "k": 10,
        "num_candidates": 100
      },
      "fields": [ "title", "file-type" ]
    }
    

文档 _score 是一个正的 32 位浮点数,用于对返回的文档的相关性进行评分,该相关性由查询向量和文档向量之间的相似性决定。 有关 kNN 搜索分数如何计算的更多信息,请参见similarity

注意

对近似 kNN 搜索的支持是在 8.0 版本中添加的。 在此之前,dense_vector 字段不支持在映射中启用 index。 如果您在 8.0 之前创建了包含 dense_vector 字段的索引,那么为了支持近似 kNN 搜索,必须使用设置 index: true (默认选项) 的新字段映射重新索引数据。

对于近似 kNN 搜索,Elasticsearch 将每个段的稠密向量值存储为 HNSW 图。 由于构建这些图的成本非常高,因此对近似 kNN 搜索的向量进行索引可能需要相当长的时间。 您可能需要增加索引和批量请求的客户端请求超时。 近似 kNN 调整指南包含有关索引性能以及索引配置如何影响搜索性能的重要指导。

除了其搜索时调整参数之外,HNSW 算法还具有索引时参数,这些参数在构建图的成本、搜索速度和准确性之间进行权衡。 设置 dense_vector 映射时,可以使用index_options 参数来调整这些参数

 PUT image-index {
  "mappings": {
    "properties": {
      "image-vector": {
        "type": "dense_vector",
        "dims": 3,
        "similarity": "l2_norm",
        "index_options": {
          "type": "hnsw",
          "m": 32,
          "ef_construction": 100
        }
      }
    }
  }
}

为了收集结果,kNN 搜索 API 在每个分片上找到 num_candidates 个近似最近邻候选。 该搜索计算这些候选向量与查询向量的相似性,从每个分片中选择 k 个最相似的结果。 然后,搜索合并来自每个分片的结果,以返回全局前 k 个最近邻。

您可以增加 num_candidates 以获得更准确的结果,但代价是搜索速度较慢。 具有较高 num_candidates 值的搜索会考虑来自每个分片的更多候选。 这需要更多时间,但搜索具有更高的概率找到真正的 k 个前最近邻。

同样,您可以降低 num_candidates 以加快搜索速度,但可能会降低结果的准确性。

除了 float 值向量外,近似 kNN 搜索 API 还支持 byte 值向量。 使用knn 选项搜索将 element_type 设置为 byte 并启用索引的 dense_vector 字段。

  1. 显式映射一个或多个将 element_type 设置为 byte 并启用索引的 dense_vector 字段。

     PUT byte-image-index {
      "mappings": {
        "properties": {
          "byte-image-vector": {
            "type": "dense_vector",
            "element_type": "byte",
            "dims": 2
          },
          "title": {
            "type": "text"
          }
        }
      }
    }
    
  2. 索引您的数据,确保所有向量值都是 [-128, 127] 范围内的整数。

     POST byte-image-index/_bulk?refresh=true { "index": { "_id": "1" } }
    { "byte-image-vector": [5, -20], "title": "moose family" }
    { "index": { "_id": "2" } }
    { "byte-image-vector": [8, -15], "title": "alpine lake" }
    { "index": { "_id": "3" } }
    { "byte-image-vector": [11, 23], "title": "full moon" }
    
  3. 使用knn 选项运行搜索,确保 query_vector 值是 [-128, 127] 范围内的整数。

     POST byte-image-index/_search {
      "knn": {
        "field": "byte-image-vector",
        "query_vector": [-5, 9],
        "k": 10,
        "num_candidates": 100
      },
      "fields": [ "title" ]
    }
    

注意:除了标准字节数组之外,还可以为 query_vector 参数提供十六进制编码的字符串值。 例如,上面的搜索请求也可以表示为如下,这将产生相同的结果

 POST byte-image-index/_search {
  "knn": {
    "field": "byte-image-vector",
    "query_vector": "fb09",
    "k": 10,
    "num_candidates": 100
  },
  "fields": [ "title" ]
}

如果您想提供 float 向量,但又希望获得 byte 向量的内存节省,您可以使用量化功能。量化允许您提供 float 向量,但在内部,它们被索引为 byte 向量。此外,原始的 float 向量仍然保留在索引中。

注意

dense_vector 的默认索引类型是 int8_hnsw

要使用量化,您可以在 dense_vector 映射中使用索引类型 int8_hnswint4_hnsw 对象。

 PUT quantized-image-index {
  "mappings": {
    "properties": {
      "image-vector": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 2,
        "index": true,
        "index_options": {
          "type": "int8_hnsw"
        }
      },
      "title": {
        "type": "text"
      }
    }
  }
}
  1. 索引您的 float 向量。

     POST quantized-image-index/_bulk?refresh=true { "index": { "_id": "1" } }
    { "image-vector": [0.1, -2], "title": "moose family" }
    { "index": { "_id": "2" } }
    { "image-vector": [0.75, -1], "title": "alpine lake" }
    { "index": { "_id": "3" } }
    { "image-vector": [1.2, 0.1], "title": "full moon" }
    
  2. 使用 knn 选项运行搜索。搜索时,float 向量会自动量化为 byte 向量。

     POST quantized-image-index/_search {
      "knn": {
        "field": "image-vector",
        "query_vector": [0.1, -2],
        "k": 10,
        "num_candidates": 100
      },
      "fields": [ "title" ]
    }
    

由于原始的 float 向量仍然保留在索引中,您可以选择使用它们进行重新评分。这意味着,您可以使用 int8_hnsw 索引快速搜索所有向量,然后仅对前 k 个结果进行重新评分。这提供了两全其美的优点:快速搜索和准确评分。

 POST quantized-image-index/_search {
  "knn": {
    "field": "image-vector",
    "query_vector": [0.1, -2],
    "k": 15,
    "num_candidates": 100
  },
  "fields": [ "title" ],
  "rescore": {
    "window_size": 10,
    "query": {
      "rescore_query": {
        "script_score": {
          "query": {
            "match_all": {}
          },
          "script": {
            "source": "cosineSimilarity(params.query_vector, 'image-vector') + 1.0",
            "params": {
              "query_vector": [0.1, -2]
            }
          }
        }
      }
    }
  }
}

kNN 搜索 API 支持使用过滤器限制搜索。搜索将返回与过滤器查询匹配的前 k 个文档。

以下请求执行按 file-type 字段过滤的近似 kNN 搜索

 POST image-index/_search {
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  },
  "fields": ["title"],
  "_source": false
}
注意

过滤器在近似 kNN 搜索**期间**应用,以确保返回 k 个匹配的文档。这与后置过滤方法形成对比,在后置过滤方法中,过滤器在近似 kNN 搜索完成后**才**应用。后置过滤的缺点是,即使有足够多的匹配文档,有时也会返回少于 k 个结果。

与传统的查询过滤(更严格的过滤器通常会导致更快的查询)不同,在具有 HNSW 索引的近似 kNN 搜索中应用过滤器可能会降低性能。这是因为搜索 HNSW 图需要额外的探索才能获得满足过滤器条件的 num_candidates

为了避免显著的性能下降,Lucene 为每个段实现了以下策略

  • 如果过滤后的文档计数小于或等于 num_candidates,则搜索将绕过 HNSW 图,并在过滤后的文档上使用暴力搜索。
  • 在探索 HNSW 图时,如果探索的节点数超过了满足过滤器的文档数,则搜索将停止探索该图,并切换到过滤后的文档上的暴力搜索。

您可以通过同时提供 knn 选项query 来执行混合检索

 POST image-index/_search {
  "query": {
    "match": {
      "title": {
        "query": "mountain lake",
        "boost": 0.9
      }
    }
  },
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "boost": 0.1
  },
  "size": 10
}

此搜索查找全局前 k = 5 个向量匹配项,将其与 match 查询的匹配项组合,最后返回 10 个得分最高的结果。 knnquery 匹配项通过析取组合,就像在它们之间进行布尔 *或* 运算一样。前 k 个向量结果代表跨所有索引分片的全局最近邻。

每个命中的得分是 knnquery 得分的总和。您可以指定 boost 值来为总和中的每个得分赋予权重。在上面的示例中,得分将计算为

score = 0.9 * match_score + 0.1 * knn_score

knn 选项也可以与 aggregations 一起使用。一般来说,Elasticsearch 会计算与搜索匹配的所有文档的聚合。因此,对于近似 kNN 搜索,聚合是在前 k 个最近的文档上计算的。如果搜索还包含 query,则聚合是在 knnquery 匹配项的组合集上计算的。

提示

正在寻找最小配置方法吗? semantic_text 字段类型通过合理的默认值和自动模型管理,提供了对这些向量搜索实现的抽象。这是大多数用户的推荐方法。了解更多关于 semantic_text 的信息

kNN 搜索使您能够通过使用先前部署的 文本嵌入模型来执行语义搜索。 语义搜索不是对搜索词进行字面匹配,而是根据搜索查询的意图和上下文含义来检索结果。

在底层,文本嵌入 NLP 模型从您提供的输入查询字符串(称为 model_text)生成一个稠密向量。 然后,针对包含使用相同文本嵌入机器学习模型创建的稠密向量的索引进行搜索。 搜索结果在语义上是相似的,就像模型学习的那样。

重要提示

要执行语义搜索

  • 您需要一个包含输入数据的稠密向量表示的索引,以便进行搜索,
  • 您必须使用与用于从输入数据创建稠密向量的搜索相同的文本嵌入模型,
  • 文本嵌入 NLP 模型部署必须已启动。

query_vector_builder 对象中引用已部署的文本嵌入模型或模型部署,并将搜索查询作为 model_text 提供

(...)
{
  "knn": {
    "field": "dense-vector-field",
    "k": 10,
    "num_candidates": 100,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "my-text-embedding-model",
        "model_text": "The opposite of blue"
      }
    }
  }
}
(...)
  1. 要执行的自然语言处理任务。它必须是 text_embedding
  2. 用于从查询字符串生成稠密向量的文本嵌入模型的 ID。 使用与生成索引中输入文本嵌入相同的模型。您可以在 model_id 参数中使用 deployment_id 的值。
  3. 模型从中生成稠密向量表示的查询字符串。

有关如何部署训练好的模型并使用它来创建文本嵌入的更多信息,请参阅此端到端示例

除了混合检索之外,您还可以一次搜索多个 kNN 向量字段

 POST image-index/_search {
  "query": {
    "match": {
      "title": {
        "query": "mountain lake",
        "boost": 0.9
      }
    }
  },
  "knn": [ {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "boost": 0.1
  },
  {
    "field": "title-vector",
    "query_vector": [1, 20, -52, 23, 10],
    "k": 10,
    "num_candidates": 10,
    "boost": 0.5
  }],
  "size": 10
}

此搜索查找 image-vector 的全局前 k = 5 个向量匹配项和 title-vector 的全局前 k = 10 个向量匹配项。 然后,这些最高值与 match 查询的匹配项组合,并返回前 10 个文档。 多个 knn 条目和 query 匹配项通过析取组合,就像在它们之间进行布尔 *或* 运算一样。 前 k 个向量结果代表跨所有索引分片的全局最近邻。

使用上述配置的 boost 的文档的评分将为

score = 0.9 * match_score + 0.1 * knn_score_image-vector + 0.5 * knn_score_title-vector

虽然 kNN 是一个强大的工具,但它总是尝试返回 k 个最近邻。 因此,当将 knnfilter 一起使用时,您可能会过滤掉所有相关的文档,而只剩下不相关的文档进行搜索。 在这种情况下,knn 仍然会尽最大努力返回 k 个最近邻,即使这些邻居在向量空间中可能相距甚远。

为了缓解这种担忧,knn 子句中提供了一个 similarity 参数。 此值是向量被认为是匹配项所需的最小相似度。 具有此参数的 knn 搜索流程如下

  • 应用任何用户提供的 filter 查询
  • 探索向量空间以获得 k 个向量
  • 不要返回任何距离超过配置的 similarity 的向量
注意

similarity 是真实的 similarity,在它被转换为 _score 并应用 boost 之前。

对于每个配置的 similarity,这是相应的反转 _score 函数。 如果您想从 _score 的角度进行过滤,您可以执行此微小转换以正确拒绝不相关的结果。

  • l2_norm: sqrt((1 / _score) - 1)

  • cosine: (2 * _score) - 1

  • dot_product: (2 * _score) - 1

  • max_inner_product:

    • _score < 1: 1 - (1 / _score)
    • _score >= 1: _score - 1

这是一个例子。 在此示例中,我们搜索给定 query_vectork 个最近邻。 然而,应用了 filter 并要求找到的向量之间至少具有提供的 similarity

 POST image-index/_search {
  "knn": {
    "field": "image-vector",
    "query_vector": [1, 5, -20],
    "k": 5,
    "num_candidates": 50,
    "similarity": 36,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  },
  "fields": ["title"],
  "_source": false
}

在我们的数据集中,文件类型为 png 的唯一文档具有向量 [42, 8, -15][42, 8, -15][1, 5, -20] 之间的 l2_norm 距离为 41.412,大于配置的相似度 36。 这意味着,此搜索将不返回任何命中。

文本通常会超过特定模型的令牌限制,因此需要在为各个块构建嵌入之前进行分块。 当将 nesteddense_vector 一起使用时,您可以实现最近邻段落检索,而无需复制顶级文档元数据。 请注意,嵌套 kNN 查询仅支持 score_mode=max

这是一个简单的段落向量索引,它存储向量和一些用于过滤的顶级元数据。

 PUT passage_vectors {
    "mappings": {
        "properties": {
            "full_text": {
                "type": "text"
            },
            "creation_time": {
                "type": "date"
            },
            "paragraph": {
                "type": "nested",
                "properties": {
                    "vector": {
                        "type": "dense_vector",
                        "dims": 2,
                        "index_options": {
                            "type": "hnsw"
                        }
                    },
                    "text": {
                        "type": "text",
                        "index": false
                    }
                }
            }
        }
    }
}

使用上面的映射,我们可以索引多个段落向量以及存储各个段落文本。

 POST passage_vectors/_bulk?refresh=true { "index": { "_id": "1" } }
{ "full_text": "first paragraph another paragraph", "creation_time": "2019-05-04", "paragraph": [ { "vector": [ 0.45, 45 ], "text": "first paragraph", "paragraph_id": "1" }, { "vector": [ 0.8, 0.6 ], "text": "another paragraph", "paragraph_id": "2" } ] }
{ "index": { "_id": "2" } }
{ "full_text": "number one paragraph number two paragraph", "creation_time": "2020-05-04", "paragraph": [ { "vector": [ 1.2, 4.5 ], "text": "number one paragraph", "paragraph_id": "1" }, { "vector": [ -1, 42 ], "text": "number two paragraph", "paragraph_id": "2" } ] }

该查询看起来与典型的 kNN 搜索非常相似

 POST passage_vectors/_search {
    "fields": ["full_text", "creation_time"],
    "_source": false,
    "knn": {
        "query_vector": [
            0.45,
            45
        ],
        "field": "paragraph.vector",
        "k": 2,
        "num_candidates": 2
    }
}

请注意,即使我们总共有 4 个向量,我们仍然返回两个文档。 嵌套 dense_vectors 上的 kNN 搜索将始终使顶级文档上的顶级结果多样化。 这意味着,将返回 "k" 个顶级文档,并按其最近的段落向量(例如 "paragraph.vector")对其进行评分。

{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "passage_vectors",
                "_id": "1",
                "_score": 1.0,
                "fields": {
                    "creation_time": [
                        "2019-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "first paragraph another paragraph"
                    ]
                }
            },
            {
                "_index": "passage_vectors",
                "_id": "2",
                "_score": 0.9997144,
                "fields": {
                    "creation_time": [
                        "2020-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "number one paragraph number two paragraph"
                    ]
                }
            }
        ]
    }
}

如果您想按一些顶级文档元数据进行过滤怎么办? 您可以通过将 filter 添加到您的 knn 子句来实现此目的。

注意

filter 将始终位于顶级文档元数据之上。 这意味着您无法根据 nested 字段元数据进行过滤。

 POST passage_vectors/_search {
    "fields": [
        "creation_time",
        "full_text"
    ],
    "_source": false,
    "knn": {
        "query_vector": [
            0.45,
            45
        ],
        "field": "paragraph.vector",
        "k": 2,
        "num_candidates": 2,
        "filter": {
            "bool": {
                "filter": [
                    {
                        "range": {
                            "creation_time": {
                                "gte": "2019-05-01",
                                "lte": "2019-05-05"
                            }
                        }
                    }
                ]
            }
        }
    }
}

现在我们已经基于顶层 "creation_time" 进行了过滤,只有一个文档符合该范围。

{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "passage_vectors",
                "_id": "1",
                "_score": 1.0,
                "fields": {
                    "creation_time": [
                        "2019-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "first paragraph another paragraph"
                    ]
                }
            }
        ]
    }
}

此外,如果您想提取匹配文档的最近段落,您可以向 knn 子句提供 inner_hits

注意

当使用 inner_hits 和多个 knn 子句时,请务必指定 inner_hits.name 字段。 否则,可能会发生命名冲突并导致搜索请求失败。

 POST passage_vectors/_search {
    "fields": [
        "creation_time",
        "full_text"
    ],
    "_source": false,
    "knn": {
        "query_vector": [
            0.45,
            45
        ],
        "field": "paragraph.vector",
        "k": 2,
        "num_candidates": 2,
        "inner_hits": {
            "_source": false,
            "fields": [
                "paragraph.text"
            ],
            "size": 1
        }
    }
}

现在,搜索结果将包含找到的最近段落。

{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "passage_vectors",
                "_id": "1",
                "_score": 1.0,
                "fields": {
                    "creation_time": [
                        "2019-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "first paragraph another paragraph"
                    ]
                },
                "inner_hits": {
                    "paragraph": {
                        "hits": {
                            "total": {
                                "value": 2,
                                "relation": "eq"
                            },
                            "max_score": 1.0,
                            "hits": [
                                {
                                    "_index": "passage_vectors",
                                    "_id": "1",
                                    "_nested": {
                                        "field": "paragraph",
                                        "offset": 0
                                    },
                                    "_score": 1.0,
                                    "fields": {
                                        "paragraph": [
                                            {
                                                "text": [
                                                    "first paragraph"
                                                ]
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                }
            },
            {
                "_index": "passage_vectors",
                "_id": "2",
                "_score": 0.9997144,
                "fields": {
                    "creation_time": [
                        "2020-05-04T00:00:00.000Z"
                    ],
                    "full_text": [
                        "number one paragraph number two paragraph"
                    ]
                },
                "inner_hits": {
                    "paragraph": {
                        "hits": {
                            "total": {
                                "value": 2,
                                "relation": "eq"
                            },
                            "max_score": 0.9997144,
                            "hits": [
                                {
                                    "_index": "passage_vectors",
                                    "_id": "2",
                                    "_nested": {
                                        "field": "paragraph",
                                        "offset": 1
                                    },
                                    "_score": 0.9997144,
                                    "fields": {
                                        "paragraph": [
                                            {
                                                "text": [
                                                    "number two paragraph"
                                                ]
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                }
            }
        ]
    }
}
  • 跨集群搜索 中使用 kNN 搜索时,不支持 ccs_minimize_roundtrips 选项。
  • Elasticsearch 使用 HNSW 算法来支持高效的 kNN 搜索。 与大多数 kNN 算法一样,HNSW 是一种近似方法,它牺牲结果准确性来提高搜索速度。 这意味着返回的结果并不总是真正的 k 个最近邻居。
注意

近似 kNN 搜索始终使用 dfs_query_then_fetch 搜索类型,以便收集跨分片的全局前 k 个匹配项。 运行 kNN 搜索时,您不能显式设置 search_type

当对 kNN 搜索使用 量化向量 时,您可以选择重新评分结果以平衡性能和准确性,通过执行以下操作:

  • 过采样:检索每个分片更多的候选对象。
  • 重评分:使用原始向量值重新计算过采样候选对象的得分。

由于非量化的原始向量用于计算顶部结果的最终得分,因此重评分结合了:

  • 使用量化向量进行近似检索以检索顶部候选对象的性能和内存增益。
  • 使用原始向量对顶部候选对象进行重评分的准确性。

所有形式的量化都会导致一定的精度损失,并且随着量化级别的增加,精度损失也会增加。一般来说,我们发现:

  • int8 需要极少的重评分,甚至不需要。
  • int4 需要一些重评分以获得更高的准确性和更大的召回场景。 通常,过采样 1.5 倍-2 倍可以弥补大部分精度损失。
  • bbq 需要重评分,除非在非常大的索引或专门为量化设计的模型上。 我们发现 3 倍-5 倍的过采样通常就足够了。 但是对于较少的维度或不能很好地量化的向量,可能需要更高的过采样。

您可以使用 rescore_vector [预览] 选项来自动执行重新排序。 当指定了重评分 oversample 参数时,近似 kNN 搜索将:

  • 检索每个分片 num_candidates 个候选对象。
  • 从这些候选对象中,每个分片的前 k * oversample 个候选对象将使用原始向量重新评分。
  • 将返回前 k 个重新评分的候选对象。

这是一个使用带有 oversample 参数的 rescore_vector 选项的示例:

 POST image-index/_search {
  "knn": {
    "field": "image-vector",
    "query_vector": [-5, 9, -12],
    "k": 10,
    "num_candidates": 100,
    "rescore_vector": {
      "oversample": 2.0
    }
  },
  "fields": [ "title", "file-type" ]
}

此示例将:

  • 使用近似 kNN 搜索前 100 个候选对象。
  • 使用原始的非量化向量重新评分每个分片的前 20 个候选对象 (oversample * k)。
  • 返回前 10 个 (k) 重新评分的候选对象。
  • 合并来自所有分片的重新评分的候选对象,并返回前 10 个 (k) 结果。

以下部分提供了其他重评分方式

如果您不想在每个分片上重新评分,而只想在来自所有分片的顶部结果上重新评分,则可以使用此选项。

使用 _search 请求中的 rescore section 对 kNN 搜索的顶部结果进行重新评分。

这是一个示例,使用带有过采样的顶层 knn 搜索,并使用 rescore 对结果进行重新排序

 POST /my-index/_search {
  "size": 10,
  "knn": {
    "query_vector": [0.04283529, 0.85670587, -0.51402352, 0],
    "field": "my_int4_vector",
    "k": 20,
    "num_candidates": 50
  },
  "rescore": {
    "window_size": 20,
    "query": {
      "rescore_query": {
        "script_score": {
          "query": {
            "match_all": {}
          },
          "script": {
            "source": "(dotProduct(params.queryVector, 'my_int4_vector') + 1.0)",
            "params": {
              "queryVector": [0.04283529, 0.85670587, -0.51402352, 0]
            }
          }
        }
      },
      "query_weight": 0,
      "rescore_query_weight": 1
    }
  }
}
  1. 要返回的结果数,请注意它只有 10 个,我们将过采样 2 倍,收集 20 个最近邻居。
  2. 从 KNN 搜索返回的结果数。 这将使用每个 HNSW 图 50 个候选对象的近似 KNN 搜索,并使用量化向量,返回根据量化得分最相似的 20 个向量。 此外,由于这是顶层 knn 对象,因此将在重新评分之前收集来自所有分片的全局前 20 个结果。 与 rescore 结合使用,这意味着过采样 2 倍,这意味着根据量化评分收集 20 个最近邻居,并使用更高保真度的浮点向量进行重新评分。
  3. 要重新评分的结果数,如果要重新评分所有结果,请将此值设置为与 k 相同的值
  4. 重新评分结果的脚本。 脚本得分将直接与最初提供的 float32 向量交互。
  5. 原始查询的权重,这里我们简单地丢弃原始得分
  6. 重评分查询的权重,这里我们只使用重评分查询

如果您想在每个分片上重新评分,并且想要比 rescore_vector 选项提供的更精细的重评分控制,则可以使用此选项。

使用 knn queryscript_score query 按分片重新评分。 一般来说,这意味着每个分片将有更多的重评分,但这样做可以提高整体召回率,但会增加计算成本。

 POST /my-index/_search {
  "size": 10,
  "query": {
    "script_score": {
      "query": {
        "knn": {
          "query_vector": [0.04283529, 0.85670587, -0.51402352, 0],
          "field": "my_int4_vector",
          "num_candidates": 20
        }
      },
      "script": {
        "source": "(dotProduct(params.queryVector, 'my_int4_vector') + 1.0)",
        "params": {
          "queryVector": [0.04283529, 0.85670587, -0.51402352, 0]
        }
      }
    }
  }
}
  1. 要返回的结果数
  2. 执行初始搜索的 knn 查询,这是按分片执行的
  3. 用于初始近似 knn 搜索的候选对象数。 这将使用量化向量进行搜索,并返回每个分片的前 20 个候选对象,然后对其进行评分
  4. 对结果进行评分的脚本。 脚本得分将直接与最初提供的 float32 向量交互。

要运行精确的 kNN 搜索,请使用带有向量函数的 script_score 查询。

  1. 显式映射一个或多个 dense_vector 字段。 如果您不打算将该字段用于近似 kNN,请将 index 映射选项设置为 false。 这可以显着提高索引速度。

     PUT product-index {
      "mappings": {
        "properties": {
          "product-vector": {
            "type": "dense_vector",
            "dims": 5,
            "index": false
          },
          "price": {
            "type": "long"
          }
        }
      }
    }
    
  2. 索引您的数据。

     POST product-index/_bulk?refresh=true { "index": { "_id": "1" } }
    { "product-vector": [230.0, 300.33, -34.8988, 15.555, -200.0], "price": 1599 }
    { "index": { "_id": "2" } }
    { "product-vector": [-0.5, 100.0, -13.0, 14.8, -156.0], "price": 799 }
    { "index": { "_id": "3" } }
    { "product-vector": [0.5, 111.3, -13.0, 14.8, -156.0], "price": 1099 }
    ...
    
  3. 使用 search API 运行包含 vector functionscript_score 查询。

    提示

    为了限制传递给向量函数的匹配文档的数量,我们建议您在 script_score.query 参数中指定一个过滤器查询。 如果需要,您可以使用 match_all query 在此参数中匹配所有文档。 但是,匹配所有文档会显着增加搜索延迟。

     POST product-index/_search {
      "query": {
        "script_score": {
          "query" : {
            "bool" : {
              "filter" : {
                "range" : {
                  "price" : {
                    "gte": 1000
                  }
                }
              }
            }
          },
          "script": {
            "source": "cosineSimilarity(params.queryVector, 'product-vector') + 1.0",
            "params": {
              "queryVector": [-0.5, 90.0, -10, 14.8, -156.0]
            }
          }
        }
      }
    }
    

k 近邻 (kNN) 搜索根据相似性指标查找与查询向量最接近的 k 个向量。

kNN 的常见用例包括

  • 基于自然语言处理 (NLP) 算法的相关性排名
  • 产品推荐和推荐引擎
  • 图像或视频的相似性搜索
提示

查看我们的 动手教程,了解如何将密集向量嵌入数据导入到 Elasticsearch 中。

© . All rights reserved.