当前位置:   article > 正文

ElasticSearch 8.0 新特性之kNN搜索_elasticsearch knn

elasticsearch knn

kNN搜索是通过相似度判断来根据查询向量查找K个邻近的向量

使用kNN的场景有:

1、基于自然语言处理(NLP)算法的相关性排序

2、产品推荐和推荐引擎

3、图片或视频的相似搜索

前置条件:

1、为了运行kNN搜索,我们需要把我们的数据转换成有意义的向量值。然后把向量值添加到文档的dense_vector类型的字段里面。查询向量也需要有相同的维度

2、基于相似度来设计我们的向量,使与查询向量越接近的文档的向量其匹配越好

kNN方法:

ES支持两种kNN搜索的方法:

1、近似kNN:使用kNN搜索API

2、精确,暴力kNN:利用向量函数使用script_score查询

多数情况下,我们只需要使用近似kNN。近似kNN提供低延迟是以较慢的索引速度和不完美的准确性为代价。

精确的暴力kNN保证精确结果但是不能很好地适应大型数据集。通过这种方式,script_score查询必须扫描每个匹配的文档节点来计算向量函数,这将导师搜索非常慢。然而我们可以通过限制传给向量函数的文档数量来降低延迟。如果我们能筛选出一个很小的文档子集,那么我们就可以通过这个方法获取较好的性能。

近似kNN:

这个功能是技术预研功能,未来可能后续会改变或者移除,使用的时候请注意。

为了使用近似kNN搜索,需要使用kNN搜索api来搜索已经被索引的dense_vector字段

1、明确映射一个或多个dense_vector字段,近似kNN搜索要求下列mapping选项需要设置:

  • index属性设置为true
  • similarity值:这个值决定了如何计算查询向量和文档向量的相似度
  1. PUT my-approx-knn-index
  2. {
  3. "mappings": {
  4. "properties": {
  5. "my-image-vector": {
  6. "type": "dense_vector",
  7. "dims": 5,
  8. "index": true,
  9. "similarity": "l2_norm"
  10. },
  11. "my-tag": {
  12. "type": "keyword"
  13. }
  14. }
  15. }
  16. }

2、添加文档数据,进行索引

  1. POST my-approx-knn-index/_bulk?refresh=true
  2. { "index": { "_id": "1" } }
  3. { "my-image-vector": [230.0, 300.33, -34.8988, 15.555, -200.0], "my-tag": "cow.jpg" }
  4. { "index": { "_id": "2" } }
  5. { "my-image-vector": [-0.5, 100.0, -13.0, 14.8, -156.0], "my-tag": "moose.jpg" }
  6. { "index": { "_id": "3" } }
  7. { "my-image-vector": [0.5, 111.3, -13.0, 14.8, -156.0], "my-tag": "rabbit.jpg" }

3、通过kNN搜索api执行搜索

  1. GET my-approx-knn-index/_knn_search
  2. {
  3. "knn": {
  4. "field": "my-image-vector",
  5. "query_vector": [-0.5, 90.0, -10, 14.8, -156.0],
  6. "k": 10,
  7. "num_candidates": 100
  8. },
  9. "fields": [
  10. "my-image-vector",
  11. "my-tag"
  12. ]
  13. }

备注:近似kNN搜索是在8.0版本新增的,在此之前,dense_vector字段不支持在mapping里面设置index=true。如果我们在8.0版本之前创建的索引包含dense_vector字段,为了支持近似kNN搜索,需要重建索引并且设置新字段的mapping中index=true。

调整近似kNN的速度或精确度

为了收集结果,kNN搜索api在每个分片上查找一定数量(num_candidates )的近似最近邻候选对象。搜索计算候选向量与查询向量的相似度,从每个分片选择k个最相似的结果。搜索最终拿到每个分片返回的结果然后找到最终的k个邻近结果。

我们可以通过调大num_candidates的值来更精确的获取结果,代价是搜索的速度会变慢。如果使用一个比较大的num_candidates值会从每个分片获取更多的候选者。这就要花费更多的时间,也就有更大的可能找到真正的k个邻近结果。

类似的,我们可以降低num_candidates的值来获取更快的搜索,同时要接受潜在不太精确的结果。

索引考虑因素

ES的每个分片都是由segment组成,索引数据存储在segment里面。为了实现近似搜索,ES在segment里面把密集向量的值当做HNSW图来存储。索引近似kNN搜索的向量之所以花费大量的时间是因为构建这个图的成本很高。我们需要增加客户端的索引请求超时时间并使用bulk请求。

强制索引segment合并为一个可以改善kNN索引的延迟,当索引只有一个segment的时候,搜索只需要检测一个包括所有的HNSW图。当存在多个segment时,kNN搜索每一个segment来检测每一个小的HNSW图。我们应该在索引没有不再写入数据的时候强制合并segment。

近似kNN搜索的限制

  • 我们不能在过滤的别名上面运行近似kNN搜索
  • 不能在nested mapping里面的dense_vector字段执行近似kNN搜索
  • 不能使用查询DSL来过滤文档以进行近似kNN搜索。如果需要过滤文档,请考虑使用精确的kNN
  • ES使用HNSW算法来实现有效的kNN搜索,和其他的kNN算法一样,HNSW是一个近似的算法以牺牲精确度来改善搜索速度。这意味着搜索的结果可能不是真正的最邻近的K个结果。

精确kNN

为了使用精确kNN搜索,我们需要使用带向量函数的script_score查询

1、明确的定义一个或多个dense_vector字段,如果我们不打算使用近似kNN搜索,可以忽略index属性的设置或者设置为false。这将会明显改善索引速度。

  1. PUT my-exact-knn-index
  2. {
  3. "mappings": {
  4. "properties": {
  5. "my-product-vector": {
  6. "type": "dense_vector",
  7. "dims": 5,
  8. "index": false
  9. },
  10. "my-price": {
  11. "type": "long"
  12. }
  13. }
  14. }
  15. }

2、索引我们的数据

  1. POST my-exact-knn-index/_bulk?refresh=true
  2. { "index": { "_id": "1" } }
  3. { "my-product-vector": [230.0, 300.33, -34.8988, 15.555, -200.0], "my-price": 1599 }
  4. { "index": { "_id": "2" } }
  5. { "my-product-vector": [-0.5, 100.0, -13.0, 14.8, -156.0], "my-price": 799 }
  6. { "index": { "_id": "3" } }
  7. { "my-product-vector": [0.5, 111.3, -13.0, 14.8, -156.0], "my-price": 1099 }
  8. ...

3、使用搜索api来运行带有向量函数的的script_score查询

  1. GET my-exact-knn-index/_search
  2. {
  3. "query": {
  4. "script_score": {
  5. "query" : {
  6. "bool" : {
  7. "filter" : {
  8. "range" : {
  9. "my-price" : {
  10. "gte": 1000
  11. }
  12. }
  13. }
  14. }
  15. },
  16. "script": {
  17. "source": "cosineSimilarity(params.queryVector, 'my-product-vector') + 1.0",
  18. "params": {
  19. "queryVector": [-0.5, 90.0, -10, 14.8, -156.0]
  20. }
  21. }
  22. }
  23. }
  24. }

备注:为了限制传递给向量函数的匹配到的文档数量,最好在script_score.query参数增加一个filter查询。如果需要,我们可以在参数里面使用match_all query来匹配所有的文档。然而,匹配所有文档会显著增加搜索延迟

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号