ES8 向量搜索(knn-search)java-api 实践

本文介绍了如何在Elasticsearch中实现kNN搜索,用于处理基于向量的相似度查询,如NLP相关性排序、产品推荐和图像相似性搜索。通过创建向量索引、插入数据并使用kNN选项进行搜索,可以高效地找到最接近的向量结果。此外,还展示了JavaAPI的使用方法。

简介

官方文档-knn-search

kNN搜索

k-nearest neighbor(kNN)搜索找到与查询向量最近的k个向量,如通过相似度计算。

kNN的常见用例包括:

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

产品推荐和推荐引擎

图像或视频的相似性搜索

要运行kNN搜索,您必须能够将数据转换为有意义的向量值。您可以在Elasticsearch之外创建这些向量,并将它们作为dense_vvector字段值添加到文档中。查询表示为具有相同维度的向量。

根据相似性度设计向量,使文档的向量与查询向量越接近匹配度越高。

kNN方法: 

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

1、近似kNN:使用kNN搜索选项

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

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

暴力搜索必须扫描每个匹配的文档节点来计算向量函数,这将导致搜索非常慢。我们可以通过限制传给向量函数的文档数量来降低延迟。比如我们通过其他查询条件能筛选出一个很小的文档子集,那么我们就可以通过这个方法获取较好的性能。
 

近似kNN搜索

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

 1) 显式映射一个或多个密集向量字段。近似kNN搜索需要以下映射选项:

  • index字段设置为true。
  • similarity 字段,该值确定用于基于查询和文档向量之间的相似性对文档进行评分的相似性度量方式,如l2_norm,欧氏距离等。

创建向量索引

在创建索引时需要选择dense_vector类型并指定index为true,并定义索引时文档间相似度量算法,此时es在索引文档时会为knn搜索生成新的索引文件,新的索引文件采用的是树加链表的存储结构,并根据similarity指定的相似算法计算文档间距离,特征相近的数据会存储在临近的分支里。

PUT image-index
{
  "mappings": {
    "properties": {
      "image-vector": {
        "type": "dense_vector",
        "dims": 3,
        "index": true,
        "similarity": "l2_norm"
      },
      "title-vector": {
        "type": "dense_vector",
        "dims": 5,
        "index": true,
        "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选项搜索

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

 搜索结果中的_score为查询向量和文档向量之间的相似度,计算方式即为索引中指定的similarity字段。

num_candidates是检索分片时获得的候选集数量。类似于milvus的n_list。

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

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

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

kNN+filter搜索

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
}

java api

依赖:

		<!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-client -->
		<dependency>
			<groupId>org.elasticsearch.client</groupId>
			<artifactId>elasticsearch-rest-client</artifactId>
			<version>${elasticsearch.version}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/co.elastic.clients/elasticsearch-java -->
		<dependency>
			<groupId>co.elastic.clients</groupId>
			<artifactId>elasticsearch-java</artifactId>
			<version>${elasticsearch.version}</version>
		</dependency>

		<dependency>
			<groupId>jakarta.json</groupId>
			<artifactId>jakarta.json-api</artifactId>
			<version>2.1.1</version>
		</dependency>
ElasticSearchConfig
import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
 
import javax.annotation.PostConstruct;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
impor
<think>我们使用Elasticsearch8和JDK17。在Elasticsearch中,创建向量索引通常涉及到设置一个字段为dense_vector类型,并指定向量的维度。以下是一个使用ElasticsearchJava APIClient(适用于Elasticsearch8)创建向量索引的示例代码。注意:Elasticsearch8Java客户端是全新的,基于构建器模式,并且是强类型的。步骤:1.添加Maven依赖(如果使用Maven):在pom.xml中添加以下依赖(请检查最新版本):```xml<dependencies><dependency><groupId>co.elastic.clients</groupId><artifactId>elasticsearch-java</artifactId><version>8.12.2</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.2</version></dependency></dependencies>```2.创建Elasticsearch客户端。3.使用客户端创建索引,指定映射,其中包含一个dense_vector字段。假设我们要创建一个索引来存储文档,每个文档有一个“text”字段和一个“embedding”字段(向量字段)。向量的维度为768。代码示例:</think>以下是在 JDK 17 和 Elasticsearch 8 中创建向量索引的完整解决方案: ```java import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; import co.elastic.clients.elasticsearch.indices.CreateIndexResponse; import co.elastic.clients.elasticsearch.indices.IndexSettings; import co.elastic.clients.elasticsearch.indices.IndexSettingsAnalysis; import co.elastic.clients.elasticsearch.indices.PutIndicesSettingsRequest; import co.elastic.clients.elasticsearch.indices.put_settings.IndexSettingsBody; import co.elastic.clients.json.JsonData; import co.elastic.clients.json.jackson.JacksonJsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.rest_client.RestClientTransport; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import java.io.IOException; public class VectorIndexCreator { public static void main(String[] args) throws IOException { // 1. 创建ES客户端 RestClient restClient = RestClient.builder( new HttpHost("localhost", 9200, "http") ).build(); ElasticsearchTransport transport = new RestClientTransport( restClient, new JacksonJsonpMapper() ); ElasticsearchClient client = new ElasticsearchClient(transport); // 2. 定义索引名称 String indexName = "vector-index"; // 3. 创建索引请求 CreateIndexRequest request = CreateIndexRequest.of(builder -> builder .index(indexName) .settings(settings -> settings .numberOfShards(1) .numberOfReplicas(0) .analysis(IndexSettingsAnalysis.of(a -> a .analyzer("english_analyzer", analyzer -> analyzer .custom(custom -> custom .tokenizer("standard") .filter(List.of("lowercase", "english_stop")) ) ) )) ) .mappings(mappings -> mappings .properties("title", prop -> prop.text(text -> text .analyzer("english_analyzer") .fields("keyword", f -> f.keyword(keyword -> keyword.ignoreAbove(256))) )) .properties("content", prop -> prop.text(text -> text .analyzer("english_analyzer") )) .properties("embedding", prop -> prop.denseVector(dv -> dv .dims(768) // 向量维度 (BERT模型常用维度) .index(true) // 启用向量索引 .similarity("cosine") // 相似度算法 )) .properties("timestamp", prop -> prop.date(date -> date)) ) ); // 4. 执行创建索引请求 CreateIndexResponse response = client.indices().create(request); if (response.acknowledged()) { System.out.println("索引创建成功: " + indexName); // 5. (可选) 设置索引刷新间隔 PutIndicesSettingsRequest refreshRequest = PutIndicesSettingsRequest.of(b -> b .index(indexName) .settings(IndexSettingsBody.of(s -> s .index(IndexSettings.of(i -> i .refreshInterval(JsonData.of("30s")) )) )) ); client.indices().putSettings(refreshRequest); } else { System.err.println("索引创建失败"); } // 关闭资源 transport.close(); restClient.close(); } } ``` ### 代码详细解释: 1. **客户端初始化**: - 使用 `RestClient` 和 `ElasticsearchTransport` 创建 Java 客户端 - 默认连接本地 ES 实例 (`localhost:9200`) 2. **索引设置**: - `numberOfShards(1)`:单个分片(适合测试环境) - `numberOfReplicas(0)`:无副本(测试环境) - 自定义分析器 `english_analyzer` 用于文本字段处理 3. **向量字段映射**: - `denseVector` 类型定义向量字段 - `dims(768)`:指定向量维度(BERT 等模型常用 768 维) - `index(true)`:启用向量索引(HNSW 算法) - `similarity("cosine")`:使用余弦相似度计算 4. **其他字段映射**: - `title`:文本字段带英文分析器 - `content`:普通文本字段 - `timestamp`:日期类型字段 5. **优化设置**: - 设置刷新间隔为 30 秒 (`refreshInterval("30s")`) 减少写入开销 ### 关键配置说明: | 配置项 | 值 | 说明 | |--------|----|------| | dims | 768 | 向量维度(需匹配模型输出维度) | | similarity | cosine | 相似度算法(可选 l2_norm/dot_product) | | index | true | 启用 HNSW 索引(默认开启) | | analyzer | english_analyzer | 自定义文本分析器 | | HNSW 参数 | 默认 | M=16, ef_construction=100(ES 默认值) | ### 部署注意事项: 1. **向量维度匹配**:确保 `dims` 值与实际使用的嵌入模型输出维度一致 2. **相似度选择**: - `cosine`:通用推荐(范围 [-1, 1]) - `l2_norm`:欧氏距离(需归一化向量- `dot_product`:点积相似度(需归一化向量) 3. **性能调优**: - 生产环境建议增加副本和分片 - 大数据量时调整 HNSW 参数: ```java .denseVector(dv -> dv .dims(768) .index(true) .similarity("cosine") .indexOptions(io -> io .type("hnsw") .m(24) // 更高的连接数(默认16) .efConstruction(200) // 更高的构建精度(默认100) ) ) ``` ### 查询示例(KNN 搜索): ```java client.search(s -> s .index(indexName) .knn(knn -> knn .field("embedding") .queryVector(queryVector) // float[] 数组 .k(10) .numCandidates(100) ), Product.class ); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值