ElasticSearch 8.0 新特性之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值:这个值决定了如何计算查询向量和文档向量的相似度
PUT my-approx-knn-index
{
  "mappings": {
    "properties": {
      "my-image-vector": {
        "type": "dense_vector",
        "dims": 5,
        "index": true,
        "similarity": "l2_norm"
      },
      "my-tag": {
        "type": "keyword"
      }
    }
  }
}

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

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

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

GET my-approx-knn-index/_knn_search
{
  "knn": {
    "field": "my-image-vector",
    "query_vector": [-0.5, 90.0, -10, 14.8, -156.0],
    "k": 10,
    "num_candidates": 100
  },
  "fields": [
    "my-image-vector",
    "my-tag"
  ]
}

备注:近似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。这将会明显改善索引速度。

PUT my-exact-knn-index
{
  "mappings": {
    "properties": {
      "my-product-vector": {
        "type": "dense_vector",
        "dims": 5,
        "index": false
      },
      "my-price": {
        "type": "long"
      }
    }
  }
}

2、索引我们的数据

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值