Elasticsearch 和 Go 中使用向量搜索寻找地鼠

news2025/1/16 15:50:37

作者:CARLY RICHMOND,LAURENT SAINT-FÉLIX

就像动物和编程语言一样,搜索也经历了不同实践的演变,很难在其中做出选择。 加入我们的第二部分,通过 Elasticsearch 中的矢量搜索在 Go 中狩猎地鼠(gophers)。

在 Elasticsearch 和 Go 中使用向量搜索进行 gopher 搜索

使用任何编程语言(包括 Go)构建软件都需要一生的学习。 在她的大学和工作生涯中,Carly 涉足了许多编程语言和技术,包括向量搜索的最新和最好的实现。 但这还不够! 最近 Carly 也开始玩 Go。

就像动物、编程语言和友好的作者一样,搜索也经历了不同实践的演变,你可能很难在自己的搜索用例中做出选择。 在本博客中,我们将分享向量搜索的概述以及使用 Elasticsearch 和 Elasticsearch Go 客户端的每种方法的示例。

先决条件

要遵循此示例,请确保满足以下先决条件:

  • 安装 Go 版本 1.13 或更高版本
  • 使用以下命令创建你自己的 Go 存储库
  • 创建您自己的 Elasticsearch 集群,其中填充了一组基于啮齿动物的页面,包括来自维基百科的我们友好的 Gopher:

连接到 Elasticsearch

在我们的示例中,我们将使用 Go 客户端提供的 Typed API。 为任何查询建立安全连接需要使用以下任一配置客户端:

  • 云 ID 和 API 密钥(如果使用 Elastic Cloud)。
  • 集群 URL、用户名、密码和证书。

连接到位于 Elastic Cloud 上的集群如下所示:

func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}

然后,客户端连接可用于向量搜索,如后续部分所示。

如果你是使用自己部署的 Elasticsearch 集群,你可以参考文章 “Elasticsearch:运用 Go 语言实现 Elasticsearch 搜索 - 8.x”。

向量搜索

向量搜索试图通过将搜索问题转换为使用向量的数学比较来解决这个问题。 文档嵌入过程还有一个额外的阶段,即使用模型将文档转换为密集向量表示或简单的数字流。 这种方法的优点是,你可以通过将非文本文档(例如图像和音频)与查询一起转换为向量来搜索它们。

简单来说,向量搜索是一组向量距离计算。 在下图中,我们的查询 “Go Gopher” 的向量表示与向量空间中的文档进行比较,并返回最接近的结果(由常数 k 表示):

根据为文档生成嵌入的方法,有两种不同的方法可以找出地鼠吃什么。

方法一:自带模型

凭借白金许可证,可以通过上传模型并使用推理 API 在 Elasticsearch 中生成嵌入。 建立模型涉及六个步骤:

1)选择要从模型存储库上传的 PyTorch 模型。 在此示例中,我们使用 Hugging Face 中的 Sentence-transformers/msmarco-MiniLM-L-12-v3 来生成嵌入。

2)使用适用于 Python 的 Eland 机器学习客户端,使用 Elasticsearch 集群的 credentials 和任务类型 text_embeddings 将模型加载到 Elastic 中:

eland_import_hub_model
--cloud-id $ELASTIC_CLOUD_ID \
--es-api-key $ELASTIC_API_KEY \
--hub-model-id sentence-transformers/msmarco-MiniLM-L-12-v3 \
--task-type text_embedding \ 
--start

如果你是自管型的 Elasticsearch 集群,请参考文章 “Elasticsearch:如何部署 NLP:文本嵌入和向量搜索” 来完成模型的上传。

3)上传后,使用示例文档快速测试 sentence-transformers__msmarco-minilm-l-12-v3,以确保按预期生成嵌入:

4)创建包含推理处理器的摄取管道。 这将允许使用上传的模型生成向量表示:

PUT _ingest/pipeline/search-rodents-vector-embedding-pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "sentence-transformers__msmarco-minilm-l-12-v3",
        "target_field": "text_embedding",
        "field_map": {
          "body_content": "text_field"
        }
      }
    }
  ]
}

5)创建一个包含密集向量类型的字段 text_embedding.predicted_value 的新索引,以存储为每个文档生成的向量嵌入:

PUT vector-search-rodents
{
  "mappings": {
    "properties": {
      "text_embedding.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true,
        "similarity": "cosine"
      },
      "text": {
        "type": "text"
      }
    }
  }
}

6)使用新创建的摄取管道重新索引文档,以生成文本嵌入作为每个文档上的附加字段 text_embedding.predicted_value :

POST _reindex
{
  "source": {
    "index": "search-rodents"
  },
  "dest": {
    "index": "vector-search-rodents",
    "pipeline": "search-rodents-vector-embedding-pipeline"
  }
}

现在,我们可以使用新索引 vector-search-rodents 在同一搜索 API 上使用 Knn 选项,如下例所示:

func VectorSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnQuery{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             10,
      # Number of candidates to evaluate in comparison
			NumCandidates: 10,
      # Generate query vector using the same model used in the inference processor
			QueryVectorBuilder: &types.QueryVectorBuilder{
				TextEmbedding: &types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).Do(context.Background())

	if err != nil {
		return nil, fmt.Errorf("error in rodents vector search: %w", err)
	}

	return getRodents(res.Hits.Hits)
}

通过解组转换 JSON 结果对象的方式与关键字搜索示例完全相同。 常量 K 和 NumCandidates 允许我们配置要返回的邻居文档的数量以及每个分片要考虑的候选者的数量。 请注意,增加候选数量会提高结果的准确性,但随着执行的比较次数增多,会导致查询运行时间更长。

当使用查询 What do Gophers eat? 执行代码时,返回的结果类似于以下内容,突出显示 Gopher 文章包含所请求的信息,这与之前的关键字搜索不同:

[
  {ID:64f74ecd4acb3df024d91112 Title:Gopher - Wikipedia Url:https://en.wikipedia.org/wiki/Gopher} 
  {ID:64f74ed34acb3d71aed91fcd Title:Squirrel - Wikipedia Url:https://en.wikipedia.org/wiki/Squirrel} 
  //Other results omitted
]

方法二:Huggingface 推理 API

另一种选择是在 Elasticsearch 之外生成这些相同的嵌入,并将它们作为文档的一部分引入。 由于此选项不使用 Elasticsearch 机器学习节点,因此可以在免费层上完成。

Hugging Face 公开了一个免费使用、速率受限的推理 API,通过帐户和 API token,可以使用该 API 手动生成相同的嵌入以进行实验和原型设计,以帮助你入门。 不建议用于生产用途。 也可以使用类似的方法在本地调用你自己的模型来生成嵌入或使用付费 API。

在下面的 GetTextEmbeddingForQuery 函数中,我们针对查询字符串使用推理 API 来生成从 POST 请求返回到端点的向量:

// HuggingFace text embedding helper
func GetTextEmbeddingForQuery(term string) []float32 {
    // HTTP endpoint
    model := "sentence-transformers/msmarco-minilm-l-12-v3"
    posturl := fmt.Sprintf("https://api-inference.huggingface.co/pipeline/feature-extraction/%s", model)

    // JSON body
    body := []byte(fmt.Sprintf(`{
        "inputs": "%s",
        "options": {"wait_for_model":True}
    }`, term))

    // Create a HTTP post request
    r, err := http.NewRequest("POST", posturl, bytes.NewBuffer(body))

    if err != nil {
        log.Fatal(err)
        return nil
    }

    token := os.Getenv("HUGGING_FACE_TOKEN")
    r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))

    client := &http.Client{}
    res, err := client.Do(r)
    if err != nil {
        panic(err)
    }

    defer res.Body.Close()

    var post []float32
    derr := json.NewDecoder(res.Body).Decode(&post)

    if derr != nil {
        log.Fatal(derr)
        return nil
    }

    return post
}

然后,[]float32 类型的结果向量作为 QueryVector 传递,而不是使用 QueryVectorBuilder 选项来利用之前上传到 Elastic 的模型。

func VectorSearchWithGeneratedQueryVector(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	vector, err := GetTextEmbeddingForQuery(term)
	if err != nil {
		return nil, err
	}

	if vector == nil {
		return nil, fmt.Errorf("unable to generate vector: %w", err)
	}

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnQuery{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             10,
      # Number of candidates to evaluate in comparison
			NumCandidates: 10,
      # Query vector returned from Hugging Face inference API
			QueryVector:   vector,
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}

请注意,无论这两个选项如何,K 和 NumCandidates 选项都保持相同,并且当我们使用相同的模型生成嵌入时会生成相同的结果。

结论

在这里,我们讨论了如何使用 Elasticsearch Go 客户端在 Elasticsearch 中执行向量搜索。 查看 GitHub 存储库以获取本系列中的所有代码。 继续阅读第 3 部分,了解如何将向量搜索与Go 中第 1 部分
中介绍的关键字搜索功能相结合。

到那时,快乐地鼠狩猎!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1197508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

沁恒微WCH592程序烧录问题

在使用wch592蓝牙芯片时,使用WCHISPStudio_V3.60工具烧录hex固件时,识别设备OK, 擦除flash OK,就是在烧录时一直报错,错误如下: 原因是:代码和数据保护模式没有启用。 改为如下:

Git可视化界面的操作,SSH协议的以及IDEA集成Git

目录 一. Git可视化界面的操作 二. gitee的ssh key 2.1 SSH协议 2.2 ssh key 三. IDEA集成Git 3.1 分享项目 3.2 下载项目 一. Git可视化界面的操作 上一篇博客只用到了git的命令窗口,现在就来看看可视化窗口要怎么操作。 点击Git GUI Here GUI界面 在g…

由于找不到 d3dx9_43.dll,无法继续执行代码。重新安装程序可能会解决此问题

电脑出现d3dx9_43.dll缺失的问题,通常是由于DirectX组件未安装或损坏导致的。为了解决这个问题,我为您提供了以下四个解决方法: d3dx9_43.dll解决方法1. 使用dll修复程序修复 首先,使用系统文件程序dll进行修复操作非常简单&…

ZYNQ_project:IP_ram_pll_test

例化MMCM ip核,产生100Mhz,100Mhz并相位偏移180,50Mhz,25Mhz的时钟信号。 例化单口ram,并编写读写控制器,实现32个数据的写入与读出。 模块框图: 代码: module ip_top(input …

人工智能与养老:技术助力银色产业的崛起

人工智能与养老:技术助力银色产业的崛起 随着人口老龄化的加速推进,养老问题成为了全球关注的热点。人工智能(AI)技术的迅猛发展,为养老领域注入了新的活力。本文将探讨人工智能在养老领域的应用、关键挑战以及前景展望…

计算机毕业设计:水果识别检测系统 python 深度学习 YOLOv5

[毕业设计]2023-2024年最新最全计算机专业毕设选题推荐汇总 感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人 。 1、项目介绍 本文介绍了一种基于深度学习的水果检测与识别系统…

【解决方案】pytion 运行时提示 import psutil ModuleNotFoundError: No module named ‘psutil‘

报错原因分析 import psutil ModuleNotFoundError: No module named psutil报错原因分析 当前环境pytion中缺少了psutil包,使用pip命令进行安装 解决方案 pip install psutil

十八数藏的新时代探索:数字创新助推文化保护

在这个数字化的新时代,传统文化和数字创新的结合呈现出令人振奋的新面貌。十八数藏,作为文化数字创新的佼佼者,正以数字化的手段助推文化的保护与传承。 十八数藏通过数字技术,将传统非物质文化遗产以数字形式呈现,使其…

【代码随想录】算法训练计划18

1、513. 找树左下角的值 题目: 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 思路: 递归,规则,基本可以自己写出来 var maxDepth int var res int fun…

基于安卓android微信小程序的四六级助手系统

项目介绍 随着我国教育需求不断增加,高校教育资源有限,教育经费相对不足的情况下,利用现代信息技术发展高等教育,不仅充分利用了优秀的教育资源,而且为更多的人提供接受高等教育的机会,同时这也是极大促进…

【开源】基于Vue.js的智能停车场管理系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容A. 车主端功能B. 停车工作人员功能C. 系统管理员功能1. 停车位模块2. 车辆模块3. 停车记录模块4. IC卡模块5. IC卡挂失模块 三、界面展示3.1 登录注册3.2 车辆模块3.3 停车位模块3.4 停车数据模块3.5 IC卡档案模块3.6 IC卡挂…

Transforme原理--全局解读

文章目录 作用全局解读 作用 Transformer最初设计用于处理序列数据,特别在NLP(自然语言处理)领域取得了巨大成功 全局解读 Transformer来源于谷歌的一篇经典论文Attention is All you Need 在此使用Transformer在机器翻译中的运用来讲解Transformer。 其中Tran…

SplayTree高分测试用例

测试用例结果展示 覆盖率 变异得分 测试注意点 从SplayTree测起,然后再测SubSplayTree,因为前者调用后者。SplaySubTree的remove方法大部分内容需要通过反射才能测到。value和index在SplayTree当中都不是唯一的。一个index可能对应多个value。 不足之…

力扣100题——子串

560.和为k的子数组 这道题目不是滑动窗口的类型,因为长度并不是固定的。(好的,我在说废话) 注意题目要求是子数组,且是连贯的。那这里的话,解法有很多,最简单的就是暴力解法,但在这…

ChatGPT 如何改变科研之路

《Nature》全球博士后调查[1]中约有三分之一的受访者正在使用人工智能聊天机器人来帮助完善文本、生成或编辑代码、整理其领域的文献等等。 来自巴西的 Rafael Bretas 在日本生活了十多年,日语说得很好。书面日语的各个方面,例如严格的礼貌等级制度&…

CCNA课程实验-12-NAT

目录 实验条件网络拓朴需求 配置实现基础配置R1PC1ISPR2HTTP_ServerDNS_Server 配置在R2上配置静态NAT,将内网DNS Server关联到公网IP:202.1.1.2测试结果 配置在R2上配置静态NAT,将内网HTTP Server的TCP 80端口关联到公网IP:202.1…

力扣138:随机链表的复制

力扣138:随机链表的复制 题目描述: 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff…

TiPro7000 Smart Tool V1.1无法打开解决办法

长江存储官网下载的TiPro7000 Smart Tool V1.1在win10运行时无法打开,转圈圈之后就没有反应了。官网下载的压缩包解压之后内容如下图。 解决办法:将.exe文件名的“致钛”二字删掉即可。文件名不能有中文。 打开后软件界面如下。 吐槽一下这软件做得挺简…

【Python大数据笔记_day06_Hive】

hive内外表操作 建表语法 create [external] table [if not exists] 表名(字段名 字段类型 , 字段名 字段类型 , ... ) [partitioned by (分区字段名 分区字段类型)] # 分区表固定格式 [clustered by (分桶字段名) into 桶个数 buckets] # 分桶表固定格式 注意: 可以排序[so…

Java,多线程,线程安全的懒汉式、死锁、ReentrantLock的使用以及一些知识点补充

关于线程安全地懒汉式有以下几种方式: /*** 实现线程安全的懒汉式*/ public class BankTest {Bank b1 null;Bank b2 null;public static void main(String[] args){BankTest bb new BankTest();Thread t1 new Thread(){Overridepublic void run(){bb.b1 Bank.…