首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【Elasticsearch】一文读懂ES向量搜索:原理剖析与技术全景

  • 25-03-08 01:03
  • 4802
  • 10250
blog.csdn.net

注:本文若未说明ES版本则为7.10,其他版本会特别标记,由于ES版本不同,部分差异较大,具体请以官方文档为准

一、向量搜索的核心原理

1.1 向量化表示的本质

现代AI技术将文本、图像等非结构化数据转化为高维向量(通常128-1024维),这些向量在数学空间中携带语义特征。如:

  • 文本嵌入(Embedding):BERT等模型生成768维向量
  • 图像特征:ResNet提取2048维特征向量

1.2 向量搜索简介

向量搜索,简单来说,就是在向量空间中进行的搜索操作。在传统的文本搜索中,我们通常基于关键词进行匹配,这种方式对于精确匹配的场景效果较好,但在处理语义理解、模糊匹配等复杂场景时显得力不从心。而向量搜索则将文本、图像、音频等各种数据转化为向量表示,通过计算向量之间的相似度来进行搜索。这种方式能够更好地捕捉数据的语义信息,从而实现更精准、更智能的搜索。

例如,对于文本数据,我们可以使用词嵌入(Word Embedding)技术将每个单词映射为一个向量,然后将整个文本的向量表示为其包含单词向量的某种聚合(如平均、求和等)。在搜索时,将查询文本也转化为向量,通过计算查询向量与文档向量之间的相似度,找出与查询最相关的文档。

二、Elasticsearch两种核心向量搜索方式

Elasticsearch为了满足向量搜索的需求,在7.0版本新增了两个字段类型dense_vector 和 sparse_vector,分别是密集向量和稀疏向量,其中 sparse_vector在7.6中被弃用,在8.0-8.10版本中是没有该字段类型的。

2.1 近似kNN(HNSW算法)

在7.10版本中,没有该方式,因此按照2025/3/1最新版本8.17版本介绍.

k-nearest neighbor(kNN)搜索通过相似性度量所测量等算法查询向量最近的k个向量。

前置条件

要使用KNN搜索,首先必须要有dense_vector或 sparse_vector字段,这些字段的维度一定要和查询的维度相同,所以一定要确定好维度。关于向量,可以在Elasticsearch中使用自然语言处理(NLP)模型创建这些向量,或者在Elasticsearch外部生成它们,比如使用阿里云的向量模型API获取文本向量后赋值到搜索参数中。

  1. 首先需要显式映射一个或多个dense_vector字段。

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

PUT image-index
{
  "mappings": {
    "properties": {
      "image-vector": {
        "type": "dense_vector",
        "dims": 3,       //向量维度
        "index": true,   //8.0之前使用KNN搜索
        "similarity": "l2_norm"  //获取knn相似度的函数,不设置改值默认为cosine(余弦),且只能在index设置为true时指定
      },
      "title-vector": {
        "type": "dense_vector",
        "dims": 5,
        "similarity": "l2_norm"
      },
      "title": {
        "type": "text"
      },
      "file-type": {
        "type": "keyword"
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

similarity的选择有(详情可看密集向量字段的参数):

  • l2_norm
  • dot_product
  • cosine
  • max_inner_product
  1. 创建数据
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" }
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 使用knn选项或knn查询
POST image-index/_search
{
  "knn": {
    "field": "image-vector",
    "query_vector": [-5, 9, -12],
    "k": 10, //查询的数量
    "num_candidates": 100
  },
  "fields": [ "title", "file-type" ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 原理

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

可以增加num_candidates以获得更准确的结果,但代价是搜索速度变慢。num_candidates值比较较高的搜索 会从每个分片中考虑更多的候选者。会花费更多的时间,但也会提高搜索真正的k个最接近的向量的概率。

2.2 精确暴力搜索

计算查询向量与所有过滤后的文档向量的余弦/欧式距离,保证100%召回率但计算成本高。使用精确KNN搜索的话,需要使用带有vector函数的script_score查询。

  1. 首先需要显式映射一个或多个dense_vector字段。如果不打算将该字段使用近似kNN搜索,可以将索引映射选项设置为false。这可以显著提高索引速度。
PUT product-index
{
  "mappings": {
    "properties": {
      "product-vector": {
        "type": "dense_vector",
        "dims": 5,
        "index": false
      },
      "price": {
        "type": "long"
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  1. 创建数据
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 }
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 使用search API运行包含向量函数的script_score查询。
    向量函数的类型有:
    1. cosineSimilarity-计算余弦相似度
    2. dotProduct-计算点积
    3. l1 norm-计算L1距离
    4. Hamming-计算Hamming距离
    5. l2 norm-计算L2距离
    6. doc[].vectorValue-以浮点数组的形式返回向量的值
    7. doc[].magnitude-返回向量的大小
      接下来以cosineSimilarity为例,这是一个通过计算两个向量的余弦大小来判断向量相似度的函数。我们通过传入的参数向量queryVector,这个名称是自定义的,但是需要与source的脚本中params.queryVector一致,下面方法将会查询所有文档
POST product-index/_search
{
  "query": {
    "script_score": {
      "query" : {
        "match_all" : {}
       },
      "script": {
        "source": "cosineSimilarity(params.queryVector, 'product-vector') + 1.0",
        "params": {
          "queryVector": [-0.5, 90.0, -10, 14.8, -156.0]
        }
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

但是使用match_all查询来全量匹配所有文档会显著增加搜索延迟,为了限制传递给vector函数的匹配文档的数量,可以在script_score.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]
        }
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

2.3 两种搜索的对比

原理差异

  • 精确暴力搜索:在进行搜索时,精确暴力搜索会遍历数据集中的每一个向量,逐一计算查询向量与它们之间的相似度(如欧几里得距离、余弦相似度等),然后根据相似度的大小对所有向量进行排序,最后选取最相似的k个向量作为结果返回。这种方法简单直接,能够保证搜索结果的精确性,但在数据量较大时,计算量会非常庞大。
  • 近似 k - NN 搜索:近似 k - NN 搜索则采用了一些优化策略来减少计算量。例如,利用上述提到的 KD - Tree、HNSW 等索引结构,通过构建数据的层次化表示或图结构,在搜索时能够快速定位到可能包含相似向量的区域,而无需遍历整个数据集。以 HNSW 为例,它通过多层图结构,在高层图中快速跳过大量不相关的向量,仅在底层图中对局部区域进行更精确的搜索,从而在保证一定搜索精度的前提下,大大提高了搜索效率。

性能表现

  • 搜索时间:精确暴力搜索的搜索时间与数据集的大小成正比,数据量越大,搜索时间越长。在大规模数据集上,搜索可能需要很长时间才能完成。而近似 k - NN 搜索借助索引结构,能够显著减少搜索过程中需要计算相似度的向量数量,搜索时间相对较短,尤其在处理海量数据时,优势更为明显。
  • 内存使用:精确暴力搜索不需要额外的内存来存储索引结构,仅需存储数据集本身。然而,近似 k - NN 搜索需要构建和维护索引结构,这会占用额外的内存空间。例如,HNSW 索引在构建过程中会生成多层图结构,每个节点都需要存储一定的连接信息,因此会消耗更多的内存。

适用场景

  • 精确暴力搜索:适用于数据集较小、对搜索结果的精确性要求极高且对搜索时间没有严格限制的场景。例如,在一些数据量有限的科研项目中,研究人员可能更关注搜索结果的准确性,而不太在意搜索时间,此时精确暴力搜索可以满足需求。
  • 近似 k - NN 搜索:在实际应用中,尤其是面对大规模数据集时,近似 k - NN 搜索更为适用。例如,在图像搜索引擎中,可能需要处理数百万甚至数十亿张图像的向量数据,如果使用精确暴力搜索,搜索效率将极低。而近似 k - NN 搜索能够在较短的时间内返回近似最优的结果,满足用户对实时性的需求
  1. 用精确搜索的情况
  • 公司只有200份合同,需要100%确保找到所有相关文件
  • 医疗诊断系统,不能容忍任何漏诊可能
  1. 用近似搜索的情况
  • 淘宝有10亿商品图片,用户想快速找到相似款衣服
  • 抖音推荐视频,允许少量误差但要求毫秒级响应

精确的强力kNN保证了准确的结果,但无法很好地扩展大型数据集。如果必须要使用精确暴力搜索,您可以通过使用查询参数来限制传递给函数的匹配文档的数量。如果过滤数据到一个小的文档子集,就可以得到很好的搜索结果。

三、Elasticsearch支持的其他语义搜索方式

Elasticsearch使用自然语言处理(NLP)和向量搜索提供各种语义搜索功能。使用NLP模型使您能够从文本中提取文本嵌入。嵌入是提供文本的数字表示的向量。具有相似含义的内容片段具有相似的表示。

Elasticsearch中的semantic_text字段也支持语义文本搜索。在使用时只需要将文档的字段设置为semantic_text, 不需要指定如何为数据生成嵌入,或者如何对其进行索引。推理端点自动确定要使用的嵌入生成,索引和查询。
在这里插入图片描述

翻译 警告:此功能处于测试阶段,可能会发生变化。设计和代码不如官方GA功能成熟,并且按原样提供,没有任何保证。Beta版功能不受正式GA功能的支持SLA的限制。使用时需要注意风险

使用

  1. 创建Index Mapping
    默认是使用预配置的.elser-2-elasticsearch端点,使用以下API请求设置semantic_text:
PUT semantic-embeddings
{
  "mappings": {
    "properties": {
      "content": {
        "type": "semantic_text"
        //"inference_id": "my-elser-endpoint", 默认是使用预配置的``.elser-2-elasticsearch``端点,也可以自己设置推理端点
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  1. 加载数据
    将文档添加到索引时,Elasticsearch 会自动使用指定的推理端点计算嵌入,并将其存储在 semantic_text 字段中。您无需手动生成嵌入向量。以下是索引文档的示例:
PUT semantic-embeddings/_doc/1
{
  "content": "Elasticsearch 8.17 introduces semantic search capabilities."
}

  • 1
  • 2
  • 3
  • 4
  • 5
  1. 语义搜索
GET semantic-embeddings/_search
{
  "query": {
    "semantic": {
      "field": "content",
      "query": "如何在跑步时避免肌肉酸痛?"
    }
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

使用嵌入丰富数据集之后,可以使用语义搜索查询数据。在语义查询类型中提供semantic_text字段名称和查询文本。用于为semantic_text字段生成嵌入的推理端点将用于处理查询文本。

通过上述步骤,您可以在 Elasticsearch 8.17 中利用 semantic_text 字段实现高效的语义搜索功能。该功能使搜索更加智能,能够理解查询的意图和上下文,从而提供更相关的搜索结果。

向量搜索典型应用场景

  • 基于自然语言处理(NLP)算法的相关性排序
  • 产品推荐和推荐引擎
  • 图像或视频的相似性搜索
程序员大任
微信公众号
程序员大任,分享知识~
注:本文转载自blog.csdn.net的程序员大任的文章"https://blog.csdn.net/qq2632246528/article/details/145972016"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

132
搜索
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top