目录

详细教程如何使用elasticsearch-8.x进行向量搜索

详细教程:如何使用elasticsearch 8.x进行向量搜索

目录


大模型的热度使得向量数据库和embedding也成了ai领域的热门话题,有别于从头开始训练一个大模型或基于基础模型进行微调的方式,embedding检索相关上下文是对大模型进行定制的各种方法中成本最低、技术实现最便捷的方式。

https://i-blog.csdnimg.cn/direct/dfa07e19f9c344e4ad60a39085af1b79.png

从技术实现的角度, embedding检索相关上下文 是预先将要检索的文本内容使用Embedding转换成向量数组,然后保存在向量数据库,检索的时候,将检索的内容也使用Embedding转换成向量数组,然后去向量数据库做相似度检索,找出最相关的前若干条内容,再和问题一起提供给大语言模型,以保证大语言模型能获取到正确的信息。

向量搜索领域,现在有很多专业的向量数据库可供选择,但在预研阶段,elasticsearch8.x提供的向量搜索功能已足够我们进行试验探索。接下来将对 如何基于es 8.x进行向量搜索进行介绍。

前置知识

elasticsearch早在7.2.0版本就引入了dense_vector字段类型,支持存储高维向量数据,如词嵌入或文档嵌入,可以利用向量函数使用script_score查询来执行精确knn。

从8.0版本开始,新增了KNN向量近邻检索可以使用近似knn搜索,此外通过python Eland库,支持用户自定义上传机器学习模型至es,使用pipeline自动生成指定字段向量(白金版功能,免费版不可用)。

dense_vector:用于存储浮点类型的密集向量,其最大维度为2048。该字段不支持聚合和排序

knn搜索:一种分类和回归方法,是监督学习方法里的一种常用方法。k近邻算法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其k个最近邻的训练实例类别,通过多数表决等方式进行预测。k近邻法三要素:距离度量、k值的选择和分类决策规则。其中,k值和距离度量在es参数中可设置调整。

操作

生成向量

根据需要,可以借助BERT模型、GloVe 、Word2vec或者chatgpt embedding api等将文本转为向量,网络上很多相关文章,这里就不赘述了

建立索引

PUT test-index
{
  "mappings": {
    "properties": {
      "title-vector": {
        "type": "dense_vector",
        "dims": 3,
        "index": true,
        "similarity": "l2_norm"
      },
      "title": {
        "type": "text"
      },
      "file-type": {
        "type": "keyword"
      }
    }
  }
}

注意:1.使用近似knn,dense_vector字段必须开启索引,即 index要设置为true;

2.similarity用来定义文档间的相似度算法,l2_norm为欧式距离,其他可选项可参见官方文档;

3.dims是向量维度大小,当index为true时,不能超过1024,当index为false,不能超过2048。且该值必须与后续写入的向量维度一致!!需根据事先确定生成好的向量维度来定义dims

查询

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

文档的分数由查询和文档的向量决定,具体怎么计算,参考similarity参数。

knn api 会先去每个分片找num_candidates个最近邻候选者,然后每个分片计算最优的k个。最后把每个分片的结果合并,在计算出k个全局最优。

每个分片要考虑的最近邻候选者的数量,不能超过 10000。num_candidates往往可以提高最后k个结果的精确度,但是更好的结果会带来更多的消耗。k值必须比num_candidates要小

过滤后knn搜索

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

knn搜索和query混合使用

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

注意!!!

knn搜索和DSL语法混合使用,采用的是多路归并的思路,即分别检索标签和向量再进行结果合并。虽可以解决部分问题,但多数情况下结果不甚理想。主要原因在于,向量检索无范围性,其目标是尽可能保证 TOPK 的准确性,TOPK 很大时,准确性容易下降,造成归并结果的不准确甚至为空的情况。

https://i-blog.csdnimg.cn/direct/f11c49a2fab54e9f8bcf0cc01220cc07.png

以列出的混合查询语句为例,这个查询是先得到全局5个最相邻结果,然后将他们与匹配查询匹配的结果组合,选出得分最高的前10个返回。分数的计算采取:

score = 0.9 * match_score + 0.1 * knn_score

近似knn还可以搭配聚合,它聚合的是top k个邻近文档的结果。如果还有query,那么聚合的是混合查询的结果。

其他注意点

knn搜索api的变动

在8.4版本前,es短暂提供过knn搜索api

POST test-index/_knn_search

但在8.4版本后,统一将knn搜索调整为搜索api _search 的参数

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

所以如果在网络文章上看到该api但在搜索集群上无法使用请不要惊讶,搜索集群是8.7.0的,之前的_knn_search用法已经被取消了

script_score精确查询

在上述的操作过程中,我都默认使用了近似knn搜索而没有提及基于script_score的精确knn,这是因为精确knn本质上是使用脚本来重新定义score的计算方式,在分数中引入向量匹配的部分,script_score查询将扫描每个匹配的文档来计算向量函数。也自然会有使用脚本的最普遍问题:极易导致慢查询,搜索性能变差。

有效的改善方式是使用query来限制传递给向量函数的匹配文档集。但在实际业务应用中很难把性能提高到满意的程度,因此在大多数情况下不建议使用精确knn。

POST test-index/_search
{
  "query": {
    "script_score": {
      "query" : {
        "bool" : {
          "filter" : {
            "term": {
               "file-type": "article"
            }
          }
        }
      },
      "script": {
        "source": "cosineSimilarity(params.queryVector, 'title-vector') + 1.0",
        "params": {
          "queryVector": [90.0, -10, 14.8]
        }
      }
    }
  }
}
应用瓶颈

将Embedding和 LLM 结合来解决上下文长度限制、私有数据安全等问题的应用有很多,例如ChatPDF应用、langchain开源框架、bloop.ai产品、Supabase技术文档等,大模型出现后很多ai产品都采用这套原理。

但正如网络上很多声量所讨论的那样,embedding或者说向量数据库的引入更多是一种工程层面的辅助,是迫于当前大模型的瓶颈(比如输入token长度的限制)而进行的“打补丁”操作。

embedding这一技术本身并不是新鲜事物,所以在使用时,会面临embedding固有的一些问题,最根本的就是 搜不准。embedding检索是基于语义的检索,与传统搜索基于关键词的检索相当不同。

举一个例子是:

假设数据库里有一段文字 “老鼠在寻找食物”。用户输入了"‘奶酪’”的查询。文本搜索根本无法识别这段话,它不包含任何重叠部分。

但是通过Embedding,这两段文字都变成了向量,然后可以对这段文字进行相似性搜索。因为“老鼠”和“奶酪”在某种程度上是相关的,所以尽管缺乏匹配的词,用户还是能够得到该段落的结果。

它的局限性在于,如果你在一段不相干的文字中突然记了一句“老鼠在寻找食物”,用向量很可能输入“老鼠”也搜不出来,而关键词又能找出来。

在embedding的具体使用中,文本转向量模型选择的不同和相似度搜索时nn选择的不同都会与向量搜索出的结果产生很大影响,表现在搜索结果上,就是在人看来风马牛不相及的内容会出现在结果集里。或许,将embedding搜索与传统搜索互补结合,能在一定程度上弥补这一问题,这也需要进一步探索了。

最后,尽管本文是介绍在es中使用向量搜索,但当前开源生态中最热门的向量数据存储工具应该是 PostgreSQL 的拓展模块 pg-vector ,如果你想了解更多关于向量数据库的内容,推荐阅读:[向量数据库 ]

参考文章

[Search API | Elasticsearch Guide [8.7] | Elastic]

[ES-KNN搜索-CSDN博客]