<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[向量数据库 - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[向量数据库 - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/cn/search-labs/blog/category/vector-database</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/blog/category/vector-database</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/category/vector-database.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Wed, 23 Sep 2026 06:46:00 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch 向量数据库：数分钟内即可完成部署，能以超高性价比扩充至千亿规模]]></title>
    <description><![CDATA[混合检索的难点部分已经解决，配备优化的默认设置、第三方和原生 Jina AI 模型以及开箱即用的托管型 GPU 推理功能。您可以构建快速、可扩展的 AI 应用，无需构建基础架构。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch 是全球部署最广泛的向量工作负载平台之一，为 GitHub、Docusign、Seismic 等众多公司的语义搜索、检索增强生成 (RAG) 以及推荐功能提供支持。今天，我们正式发布 Elasticsearch 向量数据库，这是一款专为基于向量的应用进行优化的全新无服务器产品。您只需提供文档和查询指令，我们来负责处理嵌入、索引调优以及基础架构。此外，该产品还兼具低成本和可扩展的优势。 </p><p>对于新用户而言，这是运行高质量向量搜索的最快方式。如果您已在使用 Elasticsearch，这款新产品能够在您的数据所在平台上实现向量搜索，您无需引入任何新系统。Elasticsearch 向量数据库支持多种应用场景，例如为大型语言模型 (LLM) 提供事实依据，为 AI 智能体赋予检索与记忆能力，以及处理数千亿个向量等。<a href="https://cloud.elastic.co/registration?onboarding_token=vector">立即创建新项目</a>，只需几分钟即可开始使用。</p><h2>一个引擎，满足所有向量应用场景</h2><p>Elasticsearch 向量数据库专为使用向量构建应用程序的用户而设计：</p><ul><li><p><strong>RAG：</strong>通过密集和稀疏向量检索为您的 LLM 检索适当的上下文，或者采用结合向量检索和词汇检索的混合搜索。您的生成质量会随着检索质量的提升而提高。</p></li><li><p><strong>AI 智能体：</strong>为智能体提供针对文档和对话记忆的快速筛选检索，满足多步智能体循环所需的低延迟要求。</p></li><li><p><strong>语义搜索：</strong>根据含义而非关键字进行匹配，只需一种字段类型且无需任何管道代码。</p></li><li><p><strong>推荐和相似度：</strong>针对产品、图像或任何内容大规模查找最近邻。</p></li></ul><h2>您的向量工作负载所需的一切，开箱即用且经过优化</h2><p>构建基于向量的应用程序意味着将多个独立部分连接起来：设置并托管嵌入模型，通过这些模型为您的文档编制索引，高效存储向量，将嵌入模型应用于每个查询，与向量存储进行匹配，以及最后检索匹配项背后的文档。Elasticsearch 向量数据库可为您处理所有这些操作，无需进行额外配置或设置。</p><h3>使用 vectordb_document 索引模式进行向量索引</h3><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-vectordb-document-mode">vectordb_document</a> 索引模式是一种专为向量优先型工作负载构建的新索引配置，默认启用，因此您可以获得专家会选择的设置。以下是它会启用的功能：</p><ul><li><p><strong>默认使用 bfloat16：</strong>向量的存储大小仅为 float32 的一半，对召回率的影响可忽略不计，甚至在考虑量化之前，就能将您的磁盘占用空间大致减半。</p></li><li><p><strong>排除源向量：</strong>在 Elasticsearch 中，您的嵌入向量已存在于用于搜索的索引结构中；在 _source 中保留第二份原始副本只会增加存储占用并降低获取结果的速度。我们排除了重复项，以便更快获得响应并减少存储空间。</p></li><li><p><strong>将正确的文件预加载到缓存中：</strong>向量查询最先访问的数据结构会提前预热到内存中，从而确保无论是第一次查询还是第一千次查询，速度都快如闪电。</p></li><li><p><strong>并行合并：</strong>合并可将分段整合为组织更有序的向量结构，从而同时提升召回率并改善延迟，而以多线程方式运行这些合并则可以更快达成这一目标。</p></li></ul><h3>向量存储、压缩和自动调优</h3><ul><li><p>您的向量会自动压缩。<a href="https://www.elastic.co/cn/search-labs/blog/better-binary-quantization-lucene-elasticsearch">更好的二进制量化 (BBQ)</a> 可在保持召回率的同时将向量的内存占用最多减少 32 倍，而 DiskBBQ 则能针对大规模工作负载进一步降低内存需求。<a href="https://www.elastic.co/cn/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> </a></p></li><li><p>选择启用<a href="https://www.elastic.co/cn/search-labs/blog/vector-quantization-auto-calibration-diskbbq">自动校准</a>，该功能会根据您的数据调整每个分段的量化参数，并在数据发生漂移时在每次合并时重新调整。在 18 个数据集上的测试结果显示，每秒查询数 (QPS) 平均提升了 16.7%，且大多数数据集的召回率也有所提高。</p></li></ul><h3>托管 GPU 推理上的嵌入</h3><ul><li><p>通过原生 <a href="https://www.elastic.co/cn/jina-search-models">Jina AI 嵌入和重排序模型</a>生成嵌入向量，或引入第三方模型；所有模型均在 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a> 提供的托管 GPU 上运行，无需运维模型服务器。如果您愿意，您也可以选择自行托管。</p></li><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><strong>semantic_text</strong></a> 字段类型可自动处理分块和嵌入以及查询，这是市面上实现语义搜索的最简单途径。 </p></li></ul><h3>混合搜索和过滤向量搜索</h3><ul><li><p><a href="https://www.elastic.co/cn/elasticsearch/hybrid-search">混合搜索</a>是内置功能，可在单个查询中结合全文检索和向量检索。您可以使用倒数排序融合 (RRF) 或您所需的任何其他融合机制来融合结果。在混合搜索中，向量搜索部分的配置往往最具挑战性；而借助 Elasticsearch 向量数据库，这一难题迎刃而解，您的整个混合技术栈也会变得更加出色。 </p></li><li><p>借助<a href="https://www.elastic.co/cn/search-labs/blog/filtered-hnsw-knn-search">带过滤功能的向量搜索</a>，将元数据过滤作为向量检索过程本身的一部分来实施，而不是将其作为事后补救措施，从而避免损害召回率。</p></li></ul><h3>从第一天起即面向企业</h3><p>您还可以获得基于角色的访问控制 (RBAC)、审计日志，以及纯向量数据库通常缺乏的合规性认证。</p><h2>扩展时经济实惠且具有可预测性</h2><p>Elasticsearch 向量数据库旨在让您随着业务增长仍可负担：BBQ 和 DiskBBQ 压缩可使存储线性增长并保持较低内存占用，这意味着即使扩展到数千亿个向量，费用也不会大幅攀升。您实际支付的费用由您已知的数字决定：存储的数据量、索引的数据量，以及所需的搜索容量。估算您的文档数量、向量维度和查询负载，您便可在创建项目之前算出所需费用。您还可以在月底逐项了解账单明细。没有不透明的计算单元，也不会因后台操作产生意外费用。</p><h2>如何开始使用 Elasticsearch 向量数据库</h2><h3>创建无服务器向量数据库项目</h3><p>创建新的 <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud 无服务器向量数据库项目</a>。将数据指向终端，即可开始编入索引。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt556cbdfba551f248/6aa10b4332b53038406d321a/image1.png" alt="Elastic Cloud Serverless project types: Elasticsearch, Vector Database, Observability and Security" /><h3>使用 semantic_text 创建索引</h3><p>向量索引模式负责处理向量配置。使用 semantic_text 意味着系统会在托管 GPU 推理上为您管理嵌入和分块设置以及索引设置，无需构建嵌入管道。</p>PUT my-vectors
{
"mappings": {
"properties": {
"description": { "type": "semantic_text" }
    }
  }
}<h3>采集文档</h3><p>索引文本，系统便会为您生成嵌入。</p>POST /my-vectors/_doc
{
  "id": "park_rocky-mountain",
  "title": "Rocky Mountain",
  "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra."
}<h3>运行语义搜索查询</h3><p>查询您刚刚创建的相同语义字段：</p>GET /my-vectors/_search
{
  "query": {
    "semantic": {
      "field": "description",
      "query": "a mountain range in the middle of north america"
    }
  }
}<p>然后您会获得返回的结果：</p>{
  "took": 80,
  "hits": {
    "max_score": 0.7792325,
    "hits": [
      {
        "_index": "my-vectors",
        "_score": 0.7792325,
        "_source": {
          "id": "park_rocky-mountain",
          "title": "Rocky Mountain",
          "description": "Bisected north to south by the Continental Divide, ..."
        }
      }
    ]
  }
}<p>语义搜索只是开始。运行纯文本查询，或将两者结合为混合查询。您甚至可以构建自己的向量查询，实现完全控制。请参阅文档中的<a href="https://www.elastic.co/docs/solutions/vector-database/vector-full-text-search">语义搜索快速入门</a>，获取完整说明。</p><h2>Elasticsearch 中向量搜索的未来展望</h2><p>我们已经在着手进行后续改进：</p><ul><li><p><strong>更出色的多租户处理：</strong>如果您的数据需要按租户保持隔离，我们将为您提供一种速度更快、代码量更少的方法来实现这一目标。</p></li><li><p><strong>自动索引优化：</strong>从“全新索引”到“完全优化”，无需过多人工干预。</p></li><li><p><strong>持续的基础架构改进：</strong>对向量数据库的设置和基础架构进行持续调优，确保您始终获得最佳吞吐量和最快响应。</p></li></ul><h2>在 Elastic Cloud Serverless 上试用 Elasticsearch 向量数据库</h2><p>仅需数分钟，您即可从零开始构建支持混合检索与过滤功能的向量查询应用，并利用生产级默认配置自动完成性能调优。您可以构建快速、可扩展的 AI 应用，无需构建基础架构。</p><p>开始使用 <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud Serverless</a>，或深入了解<a href="https://www.elastic.co/docs/solutions/vector-database">完整文档</a>和 <a href="https://www.elastic.co/docs/api/doc/elastic-cloud-serverless/group/endpoint-vectordb-projects">API 参考。</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Dustin Coates]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4def84aae6aff861/6aa10ab1ee57e53d9b05253c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[如何衡量和提升 Elasticsearch 搜索召回率：通过混合搜索将召回率从 0.43 提升至 0.75]]></title>
    <description><![CDATA[了解如何通过将 BM25 词汇搜索与 Jina AI 向量嵌入相结合来测量和提高 Elasticsearch 中的搜索召回率，并使用 rank_eval API 以实际数据验证改进效果。]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/full-text">词汇搜索</a>使用 <a href="https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch">BM25 排序算法</a>，对于各种查询来说成本低、速度快且非常有效。但它有一个盲点：无法处理与文档没有共同标记的查询。在本文中，您将准确衡量 BM25 的不足之处。我们将使用 Elasticsearch 的<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">排名评估 API</a> (<code>rank_eval</code>)，并通过添加 <a href="https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-elastic-inference-service">Jina AI 嵌入</a>，通过 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic 推理服务</a> (EIS) 来缩小这一差距。您会看到召回分数从 <code>0.43</code> 提升到 <code>0.75</code>，并理解其原因。</p><h2>什么是召回？</h2><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">召回率</a> 以 <code>0</code> 到 <code>1</code> 的范围来衡量用户真正想要的文档有多少出现在搜索结果中。如果某个查询应显示三个产品，而您的搜索结果仅有两个进入前 10 名，则该查询的得分为 <code>recall@10 = 0.67</code>。这是一个基于集合的指标：它并不关心相关文档在这 <em>k</em> 个结果中的位置。位置 10 的相关文档与位置 1 的相关文档具有同等效力。高召回率意味着您不会丢失相关结果。</p><p>
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ffd147b13705680/6a170a6fe8fbce11a539fc22/b13af2a5d0ca055535d8bfe3dfe4b3d1093ee6da-1457x796.png" alt="维恩图展示了如何计算 Recall@10，通过显示所有相关文档与 BM25 检索出的前 10 个结果的重叠情况，得出 Recall@10 得分为 0.40。" /><p>该图表显示了两组文档：所有相关文档（左侧）和 BM25 实际检索到的文档（前 10 个，右侧）。只有交集部分才计入召回率，找到了 <code>prod_1</code> 和 <code>prod_2</code>，而 <code>prod_3</code>、<code>prod_4</code> 和 <code>prod_6</code> 则完全遗漏。结果：<code>Recall@10 = 2/5 = </code><strong><code>0.40</code></strong>。</p><h2>准备工作</h2><p>让我们言归正传，更好地了解召回的工作原理。本演示使用 Python。您可以在配套笔记本 (<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/notebook.ipynb">notebook.ipynb</a>) 中跟着操作，其中每个代码块都是一个可直接运行的单元。</p><p>提供的代码使用以下内容：</p><ul><li><p>Elasticsearch 9.3+</p></li><li><p>Python 3.10+</p></li></ul>pip install elasticsearch pandas plotly python-dotenv<ul><li><p>包含 Elasticsearch 凭据的 <code>.env</code> 文件</p></li></ul>ELASTICSEARCH_URL=https://your-cluster-url
ELASTICSEARCH_API_KEY=your-api-key<h2>该数据集</h2><p>我们将使用包含 1,000 种产品的产品目录，涵盖鞋类、电子产品、工具等多个类别。</p><p>每份文档有四个字段：</p><p>字段</p><p>类型</p><p>“标题”</p><p>文本</p><p>“描述”</p><p>文本</p><p>“品牌”</p><p>关键字</p><p>`类别`</p><p>关键字</p><p>该数据集加载自 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/dataset.csv"><code>dataset.csv</code></a>。</p><h2>词汇搜索的支持和局限性</h2><p>BM25 是 Elasticsearch 和大多数搜索引擎的默认排名算法。它根据查询词在文档中的出现频率对其进行评分，并根据文档长度和这些词在整个索引中的出现频率进行调整。在此基础上，您还可以获得<a href="https://www.elastic.co/docs/reference/text-analysis/analyzer-reference">分析器</a>：小写规范化、词干提取和停用词消除。查询“跑步鞋”将匹配“跑步鞋”，也可能匹配“跑步”。</p><p>这对很多查询都很有效：</p><ul><li><p>“跑鞋”会立即匹配标题中包含这些确切标记的产品。</p></li><li><p>“蓝牙扬声器”会显示便携式音频产品，因为这些词语是逐字匹配的。</p></li></ul><p>搜索结果具有确定性和可解释性：文档排名靠前，是因为查询词出现在其中。调试相关性很简单。</p><h3>出现问题的地方</h3><p>现在，让我们针对同一目录尝试这些查询：</p><ul><li><p><strong>“护肤流程”：</strong>在任何产品标题中都没有出现“流程”这个词。BM25 能够部分匹配“护肤”这一词，但面部精华液、身体精油和保湿霜等产品是用“维生素 C”、“视黄醇”或“提亮”等术语来描述的，这些术语与查询词都没有重叠。构成完整护肤流程的产品分散在索引中，没有任何共同的令牌将其关联起来。</p></li></ul>ID: B06XX6DS3P, Score: 9.0552, Title: Replenix Retinol Smooth + Tighten Body Lotion - Collagen-Boosting, Regenerating Anti-Aging Body Cream, Reduces Appearance of Stretch Marks, 6.7 oz.

  ID: B08XMPKJ1L, Score: 5.2699, Title: Bio-Oil Skincare Body Oil (Natural) Serum for Scars and Stretchmarks, Face and Body Moisturizer Hydrates Skin, with Organic Jojoba Oil and Vitamin E, For All Skin Types, 6.7 oz

  ID: B01CY764KQ, Score: 5.0057, Title: Nike Up Or Down Men Deodorant - Pack of 2 | Long-Lasting Fragrance, Body Spray Combo for Men | Deodorant for Active Living | Nike Men's Deo Set | Ultimate Odor Protection | Grooming Essentials | Signature Nike Scent | High-Performance Men's Deodorant<ul><li><p><strong>“宠物旅行配件”：</strong>这是一个用例分组，而非产品类别。宠物狗背带、宠物汽车座椅和旅行笼都与此相关，但它们的描述侧重于便携性、安全性和舒适性，而非“旅行配件”。BM25 与“宠物”大致匹配，但无法区分旅行专用产品与宠物目录中的其他产品。</p></li></ul>ID: B0BVV7BKTW, Score: 7.4371, Title: Large Foldable Travel Duffel Bag with Shoes Compartment

ID: B07TNPHYNV, Score: 6.6455, Title: 40 Pieces Christmas Bronze Jingle Bells Craft Small Bells

ID: B08R8FRW53, Score: 6.6335, Title: CUBY Dog and Cat Sling Carrier
ID: B08QMCQYGM, Score: 6.5259, Title: YTFGGY Whiteboard Pinstripe Tape 6 Rolls 1/8"
ID: B0CP3LQSWM, Score: 6.2994, Title: Portable Dog Water Bottle 32 Oz<p>这是一个<strong>召回问题</strong>。相关文档已存在于您的索引中。BM25 无法找到它们，因为用户的用词和文档中的词语匹配度不够高。</p><p>添加同义词有助于处理已知情况。但您无法枚举用户表达某种意图的所有方式。这就是向量发挥作用的地方。</p><h2>为何要测量召回率</h2><p>在解决问题之前，需要先对问题进行量化。</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall"><strong>Recall@k</strong></a> 衡量有多少用户真正想要的文档出现在搜索结果中。正式来说：</p>Recall@k = (relevant documents found in top k) / (total relevant documents)<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision"><strong>Precision@k</strong></a> 衡量前 k 个结果，以及其中有多少是实际相关的：</p>Precision@k = (relevant documents in top k) / k<p>高精度意味着您返回的结果质量较高。在电子商务领域，缺少相关产品（召回率低）通常比显示稍有瑕疵的结果（精度较低）更糟糕，因为隐藏的产品意味着销售损失。</p><p>Elasticsearch 的 <code>rank_eval</code> API 允许您系统地测量两者。您提供一系列查询，每个查询都有一组已评分的文档，Elasticsearch 会为您计算所有查询的指标。</p><h2>设置评估</h2><p><code>rank_eval</code> API 需要一个<strong>评级数据集</strong>：查询与每个查询相关的文档之间的映射，以及相关性等级（0＝不相关，1＝相关，2＝高度相关）。</p><p>在笔记本中，这是<a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr#learning-to-rank-judgement-list">判断列表</a>：</p>judgments = [
    # Query 1: "running shoes" BM25 handles well (tokens appear in product titles) 
    {"query_id": "q1", "doc_id": "B09NQJFRW6", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08JMD4LMM", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08VRJ6F2Q", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07S8NRRWR", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01HD620I8", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07DX86321", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B0968YVLQ8", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B093QJ39ZS", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B096FGSC39", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01GVQWVV2", "grade": 1, "query": "running shoes"},

    # Query 2: "skincare routine" intent-based, "routine" never appears in product titles
    {"query_id": "q2", "doc_id": "B08XMPKJ1L", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BN3WQB92", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BT7B7P5T", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00NPA2WEY", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B06XX6DS3P", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B07PDRD1KT", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B074J7869B", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B08JV31QW4", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00K3TVJMQ", "grade": 1, "query": "skincare routine"},

    # Query 3: "study desk setup" intent-based, products are desks/stands/organizers
    {"query_id": "q3", "doc_id": "B08CS35J2T", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B09B3LFDXJ", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B07W58LMND", "grade": 1, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B0CHYDX91L", "grade": 1, "query": "study desk setup"},

    # Query 4: "pet travel accessories" use-case grouping, products are carriers/crates/seats
    {"query_id": "q4", "doc_id": "B08R8FRW53", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B01MYUYX33", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B003C5RKE4", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B09GF8GBF6", "grade": 1, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B0CP3LQSWM", "grade": 1, "query": "pet travel accessories"},
]<p>这种混合是有意为之：<code>q1</code> 是 BM25 可以很好处理的查询（产品标题中的精确标记），而 <code>q2</code>、<code>q3</code> 和 <code>q4</code> 是基于意图的查询，用户的意图是以概念而非具体产品关键词来表达的。</p><h2>测量 BM25 基线召回率</h2><p>首先，设置 Elasticsearch 客户端，并对原始文本数据建立索引：</p>import os
import json
import pandas as pd
import plotly.graph_objects as go
from elasticsearch import Elasticsearch, helpers
from dotenv import load_dotenv

load_dotenv()

es = Elasticsearch(
    os.getenv("ELASTICSEARCH_URL"),
    api_key=os.getenv("ELASTICSEARCH_API_KEY")
)

INDEX_NAME = "ecommerce-products"<p>现在为 BM25 构建 <code>rank_eval</code> 请求。列表中的每个请求都将会查询及其评分结合起来：</p>judgments_df = pd.DataFrame(judgments)

bm25_requests = []
for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    bm25_requests.append({
        "id": query_id,
        "request": {
            "query": {
                "multi_match": {
                    "query": query_text,
                    "fields": ["title", "description"]
                }
            }
        },
        "ratings": ratings,
    })

bm25_eval = {
    "requests": bm25_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

bm25_result = es.rank_eval(index=INDEX_NAME, body=bm25_eval)
print("BM25 Recall@10:", bm25_result.body["metric_score"])<p>结果：</p>BM25 Recall@10: 0.43<p><code>0.43</code> 这意味着在所有四个查询中，BM25 只找到了它应该找到的文档的 43%。这种不足集中体现在基于意图的查询中：“护肤流程”漏掉了面部精华液和身体精油，因为“流程”一词从未出现在产品标题中；而“宠物旅行配件”则检索出了一些不相关的宠物产品，却遗漏了那些以便携性和安全性而非“旅行配件”来描述的宠物笼和宠物箱。</p><p>这就是我们的基准。现在我们有了一个要超越的数字。</p><h2>使用 Jina 嵌入添加向量搜索</h2><p><a href="https://www.elastic.co/docs/solutions/search/vector"><code>Vector search</code></a> 将文档和查询编码为高维向量，这是一种由数百甚至数千个数值组成的向量，每个数值都对它所代表的数据的特定特征进行编码。意义相似的文档最终会在向量空间中靠近，即使它们没有共同的词汇。“健身器材”和“哑铃套装”会放在一起，因为这两个概念是相关的。我选择 Elasticsearch 作为我的向量数据库，是因为它支持混合搜索，让我既能理解语义，又能精确查找关键字。</p><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a> 包括通过其<a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">推理 API</a> 嵌入模型的开箱即用支持。</p><h3>步骤 1：使用 Jina 嵌入 v5 作为推理终端</h3>INFERENCE_ENDPOINT_ID = ".jina-embeddings-v5-text-small"<p>如果您的集群具有 GPU 资源（在 Elastic Cloud 和 Elasticsearch 9.3+ 中可用），嵌入将在 GPU 上生成，这比 CPU 推理快得多，并消除了历史上使向量在扩展时变得昂贵的性能权衡。</p><p>为什么要特别选用 Jina 嵌入？<a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text</a> 是一种多语言模型（支持 119 种以上语言），具有 32,000 个标记的上下文窗口，并支持特定任务的<a href="https://arxiv.org/abs/2106.09685">低秩自适应 (LoRA) 适配器</a>。它适用于开箱即用的简短产品描述。<a href="https://huggingface.co/jinaai/jina-embeddings-v5-text-small">点击此处</a>了解有关 <code>jina-embeddings-v5-text</code> 模型的更多信息。</p><h3>步骤 2：创建具有语义字段的索引</h3>index_mappings = {
    "mappings": {
        "properties": {
            "title": {"type": "text", "copy_to": "semantic_field"},
            "description": {"type": "text", "copy_to": "semantic_field"},
            "brand": {"type": "keyword"},
            "category": {"type": "keyword"},
            "semantic_field": {
                "type": "semantic_text",
                "inference_id": INFERENCE_ENDPOINT_ID,
            },
        }
    }
}

if not es.indices.exists(index=INDEX_NAME):
    es.indices.create(index=INDEX_NAME, body=index_mappings)
    print(f"Created index: {INDEX_NAME}")<p>这里的关键在于 <a href="https://www.elastic.co/docs/solutions/search/semantic-search/semantic-search-semantic-text"><code>semantic_text</code></a> 字段类型。这是对 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a> 的更高级别的抽象：您将其指向一个推理终端，Elasticsearch 会自动生成嵌入。</p><p><code>title</code> 和<code>description</code> 上的 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> 属性意味着这两个字段的内容都会流入 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_field</code></a> 进行嵌入，因此单个向量就能捕获完整的产品表示。</p><h3>步骤 3：为产品编制索引</h3>def bulk_index(products, index_name):
    actions = []
    for product in products:
        doc_id = product.get("_id")
        source = {k: v for k, v in product.items() if k != "_id"}
        action = {"_index": index_name, "_source": source}
        if doc_id:
            action["_id"] = doc_id
        actions.append(action)

    success, failed = helpers.bulk(es, actions, raise_on_error=False)
    if failed:
        for error in failed:
            print(f"Error: {error}")
    else:
        print(f"Successfully indexed {success} documents")

bulk_index(products, INDEX_NAME)<p>索引时，Elasticsearch 会调用每个文档的推理端点，并将生成的嵌入存储在 <code>semantic_field</code> 中。您无需编写任何额外代码。</p><h2>混合搜索：将 BM25 与向量结合并采用 RRF</h2><p>添加向量可以提高召回率，但仅使用向量可能会在精确匹配查询中失去精度；“跑鞋”仍应将逐字匹配的结果排在首位。混合搜索则保留词汇成分，以保持这种精确性。</p><p>使用<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">倒数排序融合</a> (RRF) 的混合搜索可以保持两者的优点：</p><ul><li><p>BM25 可以高精度处理精确和近似精确的查询。</p></li><li><p>语义搜索能以高召回率处理基于意图和多语言的查询。</p></li><li><p>RRF 将两份排名表合并为一份排名表。</p></li></ul><p>RRF 公式根据每个文档在每个结果列表中的排名，为每个文档分配分数：</p>score = sum(1 / (rank_constant + rank))<p>在两个列表中均排名靠前的文档将获得更高的综合得分。<code>rank_constant</code>用于控制排名较低的文档获得的权重大小。</p>hybrid_requests = []

for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    hybrid_requests.append({
        "id": query_id,
        "request": {
            "retriever": {
                "rrf": {
                    "retrievers": [
                        {
                            "standard": {
                                "query": {
                                    "multi_match": {
                                        "query": query_text,
                                        "fields": ["title", "description"],
                                    }
                                }
                            }
                        },
                        {
                            "standard": {
                                "query": {
                                    "match": {
                                        "semantic_field": {"query": query_text}
                                    }
                                }
                            }
                        },
                    ],
                    "rank_window_size": 50,
                    "rank_constant": 5,
                }
            }
        },
        "ratings": ratings,
    })

hybrid_eval = {
    "requests": hybrid_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

hybrid_result = es.rank_eval(index=INDEX_NAME, body=hybrid_eval)
print("Hybrid Recall@10:", hybrid_result.body["metric_score"])<p>结果：</p>Hybrid Recall@10: 0.75<p>混合搜索在 BM25 (<code>0.43</code>) 的基础上有了显著提升，并为“跑鞋”等精确匹配查询保留了精确度。</p><h2>结果：前后结果对比</h2><p>以下是所有三种方法的完整对比：</p>methods = {
    "BM25 (Lexical)": bm25_requests,
    "Hybrid (BM25 + Vectors)": hybrid_requests,
}

recall_metric = {"recall": {"k": 10, "relevant_rating_threshold": 1}}

comparison_data = []
for method_name, requests in methods.items():
    result = es.rank_eval(
        index=INDEX_NAME,
        body={"requests": requests, "metric": recall_metric}
    )
    comparison_data.append({
        "method": method_name,
        "recall@10": result.body["metric_score"]
    })

comparison_df = pd.DataFrame(comparison_data)
print(comparison_df.to_string(index=False))<p>结果：</p><p>方法</p><p>Recall@10</p><p>BM25（词法）</p><p>0.43</p><p>混合型（BM25 + 向量）</p><p>0.75</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5a1d72b57056fe64/6a170a71c1e8a56c58f882ab/e49f6c10516b0a48a0ad75962c6590ee07311407-700x500.png" alt="条形图比较了 BM25 词汇搜索和 BM25 与向量相结合的混合搜索的 Recall@10，结果显示混合搜索的召回率明显更高。" /><p>按查询细分：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt871347f754c866d0/6a170a73839dfa40abdcfeb4/40e36dcb7b34cbf4649c512bcb60cef60f1778a6-700x500.png" alt="分组条形图比较了四个产品查询中 BM25 词法搜索和混合搜索的 Recall@10，显示混合搜索在每个查询中始终优于词法搜索。" /><h2>结论</h2><p>在这篇文章中，我们看到，当用户键入精确的查询时，BM25 词汇搜索是可靠的，但当他们根据意图而非关键词进行搜索时，其召回率就会下降。借助 <code>rank_eval</code>，我们建立了一个可重复的基线，用真实数据来衡量这一差距。在此基础上，我们添加了一个由 Jina 嵌入提供支持的 <code>semantic_text</code> 字段，并再次运行了评估。结果：混合搜索将召回率从 <code>0.43</code> 提高到 <code>0.75</code>，同时保留了精确匹配查询的精确度，但实际幅度取决于您的查询组合。</p><p>该模式可扩展至本示例之外：从用户的实际查询中收集判断，以 <code>rank_eval</code> 作为基准运行，添加 <code>semantic_text</code>，然后再次进行测量。您将确切了解改进了哪些方面以及改进了多少。</p><h2>后续步骤</h2><ul><li><p>深入了解召回与向量搜索：《<a href="https://www.elastic.co/search-labs/blog/recall-vector-search-quantization">召回与向量搜索量化</a>》，作者：Jeff Vestal</p></li><li><p>添加重排序功能，以进一步提升前几条结果的精准度</p></li><li><p>探索 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Elasticsearch 混合搜索文档</a></p></li><li><p>阅读有关 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html"><code>rank_eval</code></a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">API</a> 的更多信息</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</guid>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37c9d2971b5a2db3/6a170a75cf4f254223b2d149/492c9b5432a2b9e40cebb3b60f0df019a8c7bf6d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[我们如何构建 Elasticsearch simdvec，使其成为世界上速度最快的向量搜索之一]]></title>
    <description><![CDATA[我们如何打造 Elasticsearch simdvec——这是 Elasticsearch 中每一次向量搜索查询背后的手动调优 SIMD 内核库。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch simdvec 是 Elasticsearch 中每一次向量距离计算的核心引擎。它为 Elasticsearch 支持的每一种向量类型提供手动调优的 AVX-512 和 NEON 内核。其批量评分架构通过 x86 上的显式预取和 ARM 上的交错加载来隐藏内存延迟；当数据规模超出 CPU 缓存容量时，性能最高可比 FAISS 和 jvector 等库快 4 倍。在本文中，我们将介绍为何要打造它、其内部构成，以及它如何让 Elasticsearch 向量搜索跻身全球最快之列。</p><h2>我们如何构建 Elasticsearch simdvec</h2><p>Elasticsearch 中的每一次向量搜索查询，无论是<a href="https://arxiv.org/abs/1603.09320">分层导航小世界 (HNSW)</a> 遍历、倒排文件 (IVF) 扫描，还是重排序阶段，最终都会归结为同一个问题：在一次查询中，数百万次计算向量之间的距离。Elasticsearch 支持广泛的数据类型和量化策略，从 float32 到 int8、bfloat16、binary 以及 Better Binary Quantization (BBQ)。每种类型都在内存、吞吐量和召回率之间形成不同取舍。而这一切背后都有一个统一的引擎：simdvec。</p><p>我们打造 simdvec，是为了让每一次距离计算都尽可能逼近硬件允许的性能上限。在本文中，我们将介绍为何要打造它、它的内部构成，以及它在哪些场景中影响最大。</p><h3>设计得如同一辆赛车</h3><p>作为一级方程式赛车 (F1) 爱好者（我们其中一人曾效力于法拉利 F1 车队），我们看到了一个清晰的相似之处。F1 赛车的设计只有一个目标：取得最佳单圈成绩。发动机功率、空气动力学和底盘设计之所以重要，是因为它们都服务于这一结果。向量数据库也是如此：索引吞吐量、查询延迟和召回率决定了成功与否。</p><p>最终结果固然重要，但要达到最高性能水平，就需要每个组件都做到极致。它不能只是<em>“足够好”</em>，而必须是同类中的<em>“最佳”</em>。simdvec 正是以这种思路打造的，聚焦于系统中的一个关键部分：引擎。它是一个专门构建、针对<a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">单指令多数据</a> (SIMD) 优化的内核库，提供手动调优的原生 C++ 距离函数，并通过 <a href="https://openjdk.org/projects/panama/">Panama</a> 外部函数接口 (FFI) 从 Java 调用这些函数。它支持批量评分、缓存行预取，以及 Elasticsearch 中使用的所有向量类型和布局。</p><p>这就是每个查询背后的引擎。</p><h3>为什么我们要自研</h3><p>我们在 2023 年从 Apache Lucene 中的 Panama Vector API 起步。它在处理 float32 点积时表现良好，但 Elasticsearch 的需求很快就超出了它的能力范围。Elasticsearch 需要支持一系列量化向量类型：int8、int4、bfloat16、单比特以及非对称 BBQ。每种类型都有不同的 SIMD 策略、打包布局和累加器要求。除了类型覆盖范围之外，Elasticsearch 的评分路径还需要的不只是单对向量吞吐量：HNSW 需要在一次过程中为多个图邻居评分，IVF 需要对数千个候选项进行带预取的批量评分，而基于磁盘的评分需要直接在 mmap 映射内存上工作，实现零拷贝。我们考察了现有方案，发现没有一个能完全满足这些要求。</p><p>于是，我们打造了 simdvec：通过 FFI 从 Java 调用手动调优的原生 C++ 内核，具备批量评分和预取能力，并支持 Elasticsearch 使用的每一种向量类型。通过自有这个库，我们可以控制完整技术栈。当我们添加 BBQ 这样的新量化类型时，它会获得经过调优的 SIMD 内核，并完整接入整个系统。我们无需等待上游库支持它，也无需在任何类型的性能上做出妥协。Elasticsearch 中的每一次向量查询——无论是 HNSW、IVF、重排序还是混合检索——都运行在这个引擎之上。这个引擎正是围绕我们实际使用的操作和类型量身打造的。</p><p>simdvec 针对 x86 和 ARM 分别提供原生库，每种库都有多个指令集架构 (ISA) 层级，并在启动时选择。通过 FFI 从 Java 调用的开销非常低，仅为<a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#ffm-downcall-overhead-measurements">个位数纳秒级</a>。</p><h3>技术格局</h3><p>我们并不是唯一在打造 SIMD 优化向量距离内核的团队。这个生态系统非常丰富，我们希望了解 simdvec 的表现。这并非为了给项目排名，而是为了提供上下文，说明 Elasticsearch 的引擎处在什么位置。我们选择了三个项目作为参考点，每个代表一种不同的技术路径：</p><ul><li><p><strong>jvector：</strong>一个 Java 近似最近邻 (ANN) 库，使用 Panama Vector API 进行向量化距离计算，并在 x86 上提供可选的原生 C 加速。</p></li><li><p><strong>FAISS：</strong>一个广泛部署的开源矢量搜索框架，带有手动调整的 AVX2/AVX-512 内核。</p></li><li><p><strong>NumKong</strong>（原 SimSIMD）：一个包含 2,000 多个手动调优 SIMD 内核的综合库，覆盖距离函数、矩阵运算和地理空间计算。</p></li></ul><p>每个项目服务于不同的目标，有着不同的取舍。我们引用它们的参考数据，是为了给 simdvec 在 Elasticsearch 所需特定操作上的性能提供参照。</p><h3>我们如何衡量</h3><p>simdvec 和 <a href="https://github.com/ChrisHegarty/jvector-kernel-benchmarks">jvector 基准测试</a>使用 Java 编写，并采用 JMH（标准 JVM 微基准测试框架），测试中包含 FFI 开销。对于 <a href="https://github.com/ldematte/simsimd-benchmarks">NumKong 基准测试</a>和 <a href="https://github.com/ChrisHegarty/faiss-kernel-benchmarks">FAISS 基准测试</a>，我们使用 Google Benchmark（标准 C++ 微基准测试框架）编写了小型 C/C++ 测试程序。两个框架都会在预热和迭代校准后报告每次操作所需的纳秒数。我们通过硬件性能计数器验证了所有库在两个平台上都确实使用了 SIMD。所有基准测试代码均已公开在链接的 GitHub 存储库中；对于 simdvec，代码位于 <a href="https://github.com/elastic/elasticsearch">elasticsearch</a> 存储库中。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90a7d66553be3c9d/6a1701d166c4f942caf8bea5/aee116772df161cf86b7668f575ac34c733a23c5-1580x238.png" alt="表格列出两个平台：x86（AMD EPYC Turin Zen 5，AVX2 和 AVX-512，AWS c8a.4xlarge）；ARM（Graviton 4 Neoverse V2，NEON 和 SVE2，AWS c8g.4xlarge）。" /><p><strong>软件：</strong>JDK 25.0.2、JMH 1.37、GCC 14、Google Benchmark（最新版）。</p><h2>一次处理一个向量</h2><p>向量搜索中最基础的操作是计算两个向量之间的距离。每一次 HNSW 邻居评估、每一次 IVF 候选项评分、每一次重排序比较，都会归结为这个内层循环。</p><p>我们在两个平台上测量了 1024 维下的单对向量吞吐量，首先从 float32 开始。这是基准类型，也是生态系统中竞争最激烈的类型。我们将 simdvec 与 FAISS 和 jvector 进行了对比；我们排除了 NumKong，因为它在 float32 上使用 float64 累加器，速度慢 3.2 到 5.3 倍（取决于平台），这是以吞吐量换取数值精度。为了保持同类对比，我们改为在 int8 上测试 NumKong，因为此时它采用的累加器策略与 simdvec 相同。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3bc2ecaf3ad2b61/6a1701d2ab7f08039ddb9d0f/352cfa0fc18f123140f746d843404f80127fb1b7-1500x675.png" alt="横向条形图，标题为“float32 点积 — AMD Turin”，比较了五个实现：FAISS AVX-512，23.2 ns/op；ES simdvec AVX-512，28.3 ns/op；FAISS AVX2，36.4 ns/op；ES simdvec AVX2，38.9 ns/op；以及 jvector，43.9 ns/op。" /><p>在 x86 上，FAISS AVX-512 是最快的单对内核，耗时 23 ns。simdvec AVX-512 紧随其后，为 28 ns，这一差距反映了 FFI 调用开销。两者都使用 512 位 FMA，并采用多累加器展开。在 AVX2 层级，两者更接近，分别为 36 ns 和 39 ns，都受限于 256 位寄存器和内存加载宽度。jvector 使用 Java Panama Vector API，耗时 44 ns。Panama 能生成良好的 SIMD 代码，但手动调优的 C++ 内部函数仍然具有优势。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbbbe442631a575d/6a1701d42b835f81bcf4b083/95e44d9767f21ec4bccaed835e3c99aa180431ee-1500x495.png" alt="题为“float32 点积－Graviton 4 (ARM)”的横条形图显示 ES simdvec 为 70.2 ns/op，jvector 为 110.0 ns/op，FAISS 为 155.6 ns/op。" /><p>在 ARM 上，simdvec 以 70 ns 领先，明显快于 110 ns 的 jvector 和 156 ns 的 FAISS。simdvec 针对 aarch64 提供手动调优的 NEON 内核。jvector 没有 ARM 原生代码，依赖 Panama。FAISS 依赖编译器自动向量化，而非显式的 NEON 内部函数，这也解释了更大的性能差距。这体现了拥有自有内核库的一个实际优势：当 Elasticsearch 扩展到 Graviton 时，我们添加了专门构建的 NEON 内核。而 jvector 和 FAISS 尚未以同等程度优先投入 ARM 原生代码。</p><p>但 Elasticsearch 评分的远不止 float32。<strong>int8</strong> 量化可将内存占用降至原来的四分之一，bfloat16 降至原来的一半，BBQ 降至原来的三十二分之一。每种类型都需要自己的 SIMD 策略，而 simdvec 为所有这些类型都提供手动调优的原生内核。</p><p>在我们比较的库中，只有 NumKong 拥有可用于 int8 对比的内核。我们测量了 1024 维度下的 int8 点积、平方欧几里得距离和余弦计算。</p><p><strong>Int8 单对评分（1024 维，ns/vec op – 越低越好）</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ac255d158e93504/6a1701d5a292997e48d00eb1/a0b852fd5f51d57bd2488886600472ee65ab64da-1594x378.png" alt="表格对比了 x86 和 ARM 上点积、平方欧几里得距离和余弦运算的性能，并列出两种架构下每项操作对应的 ES、NumKong 和 diff 值。" /><p>在两种架构上，NumKong 在中小维度下持平或更快，差异主要来自更低的调用开销（直接 C 调用 vs Java FFI）。在更高维度下，simdvec 迎头赶上，因为更高效的内核实现（使用级联展开）摊薄了调用成本：随着维度增加，<a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#single-pair-i8-nsop-2">这一差距会缩小并最终反转</a>。交叉点出现在 768 到 1536 维之间，具体取决于函数和架构。</p><p>尽管 Java FFI 存在略高的开销，simdvec 的表现仍足以媲美高度优化的 C/C++ 库。它不仅是唯一一个同时为 float32 <em>和</em> int8 提供优化内核的库，而且在 ARM 上保持领先，在 x86 上的 float32 方面也仅略逊于 FAISS，在 int8 上与 NumKong 在两个架构上都非常接近。对于 bfloat16、int4、binary 和 BBQ，虽然存在其他替代方案，但 simdvec 的优势在于，它能够针对每种类型的数据布局进行手动 SIMD 调优。</p><p>然而，生产环境下的搜索引擎不会一次只为一个向量评分，而是会在每次查询中为数千个向量评分。接下来的问题是：在如此规模下性能表现如何？</p><h3>一次处理数千个向量</h3><p>单对向量性能只是整体图景的一部分。在实践中，真正重要的是系统在负载下的行为。一次 HNSW 查询可能会为数百个图邻居评分。一次 IVF 扫描可能会为数千个倒排列表条目评分。一次重排序阶段可能会为数万个候选项评分。单对吞吐量固然重要，但更关键的是评分大量向量时的速度，以及当工作集超出 CPU 缓存时性能下降是否平缓。</p><p>simdvec 为每一种数据类型都提供了批量评分功能。这绝非简单的单对内核循环，而是使用了多累加器内层循环：在每个维度步长 (stride) 中仅加载一次查询向量，并让多个文档向量共享该向量，同时针对下一批次执行显式的缓存行预取。在本文撰写之时，jvector 和 FAISS 都没有提供等效功能。jvector 没有批量 API，调用者只能在循环中逐对评分。FAISS 暴露了 <code>fvec_inner_products_ny</code>，但在撰写本文时，其实现方式仍是循环调用单对向量距离函数，没有查询向量摊销，也没有预取。</p><p><strong>Float32。</strong>为了在内核层面衡量影响，我们使用随机访问模式来模拟类似 HNSW 的分散式图邻居查找，并让单个查询对数量不断增加的 1024 维 float32 文档向量进行评分。我们选择了 32、625 和 32,500 个向量这三种数据集规模，使工作集分别超出 L1、L2 和 L3 缓存。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df64ec77a1921ae/6a1701d714b270393ae3c4de/1d90267be1c63ac82b8ba588617ebedb0be0d1b6-1334x558.png" alt="两张条形图对比了 Elasticsearch simdvec、FAISS 和 jvector 在 AMD Turin（x86，AVX-512）和 Graviton 4（ARM，NEON）上，针对 32 个、625 个和 32,500 个向量的批量 float32 点积评分耗时。" /><p>当数据能放入缓存时，simdvec 在两个平台上都是最快的，但优势不大，因为此时内核算术运算占主导。真正的差距出现在工作集超出 L3 缓存之后。在 x86 上，simdvec 每个向量 95 ns，而 FAISS 需要 165 ns，jvector 需要 412 ns。在 ARM 上，模式相同：simdvec 保持在 162 ns，而 FAISS 攀升到 347 ns，jvector 到 476 ns。simdvec 中的预取和查询向量摊销能够以简单循环调用单对向量内核无法匹敌的方式掩盖内存延迟；而在真实搜索工作负载所处的大量访问主内存的场景中，这种优势会进一步扩大。</p><p><strong>Int8。</strong>同样的模式在量化类型上也成立。我们测量了 1024 维 int8 点积的批量评分，数据集大小同样选择为超出 L1、L2、L3 缓存边界，将 simdvec 的批量评分与 NumKong 循环调用的单对评分进行了对比。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda85e10007188d0/6a1701d9cf4f25d722b2cff7/9ee97d98b40d13b19370b76b33e67ea66bfb3250-1580x338.png" alt=" 标题为“x86 — 批量评分，int8 点积（ns/op，越低越好）”的表格，对比了 ES simdvec 和 NumKong 在三种向量规模（128、2,500 和 130,000）下的表现，并列出了相应的 ns/op 数值和加速比。" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt972c311b7d08ba73/6a1701dadc55dec02ee00c87/601f50a03fa3e6263cbac9700281dfe3e511de60-1580x338.png" alt="标题为“ARM — 批量评分，int8 点积（ns/op，越低越好）”的表格，对比了 ES simdvec 和 NumKong 在三种向量规模（128、2,500 和 130,000）下的表现，并列出了相应的 ns/op 数值和加速比" /><p>在 x86 上，simdvec 快 1.2 倍到 1.9 倍，这得益于显式预取和批处理的结合。在 ARM 上，simdvec 再次胜出，在所有数据集大小下快 1.7 倍到 1.9 倍。优势来自每次批处理四个向量，通过交错访问模式提供内存级并行性。在这两种情况下，最引人注目的结果都出现在最大数据集规模上，而这也正是最关键的场景。</p><p>平方距离和余弦计算的结果也呈现类似模式：ARM 上加速 1.4 倍到 1.8 倍，x86 上加速 1.3 倍到 3.0 倍（详见<a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md">此处</a>）。</p><h3>当内存成为瓶颈</h3><p>生产环境中的向量索引通常无法放入 CPU 缓存。一个包含 1,000 万个 1024 维 int8 向量的索引大小为 10 GB。为候选项评分意味着需要从 DRAM 流式读取数据，而这正是批量评分架构发挥作用的地方。</p><p>我们使用硬件性能计数器来测量批量评分过程中 CPU 内部的实际运行情况，结果发现，隐藏内存延迟需要两种截然不同的策略，每种架构各对应一种。</p><p><strong>在 x86 上，显式预取大幅减少了缓存未命中。</strong>批量内核会按顺序处理向量，先完整计算一个向量，再处理下一个，同时为下一批数据发出预取指令。未来所需的数据在 CPU 需要之前就被拉入 L1 缓存。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt368f29b5143d0f5a/6a1701dcc1e8a51780f8817b/a39548f8060c2a4a5154521a4047dd92d8cd77be-1580x309.png" alt="标题为“x86 (AMD Turin) — 每次 int8 操作的硬件计数器”的表格，对比了单对模式与批量模式下的 L1 缓存未命中、IPC 和 dTLB 未命中，并列出相应的改进倍数。" /><p>在 ARM 上，即使使用预取，顺序处理方法也表现不佳。取而代之的是，<strong>批量内核采用交错加载策略</strong>：在每个步幅位置交错加载四个向量的数据，为乱序执行引擎提供四个独立的内存流。CPU 并没有加快取数速度，而是在内存请求在途时，通过始终保持有计算任务可做来减少等待时间。详细分析可参阅<a href="https://github.com/elastic/elasticsearch/issues/145412">此 GitHub issue</a>。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8751ac5b20b75508/6a1701deab7f08db83db9d13/832de3bc0d556a493b7bf3f250196018acfc1585-1580x238.png" alt="标题为“ARM (Graviton 4) — 每次 int8 操作的硬件计数器”的表格，对比了单对模式与批量模式下的 L1 缓存未命中和后端停顿，并列出相应的改进说明" /><p>这些数字讲述了两个不同的故事：</p><ol><li><p>在 x86 上，预取将缓存未命中从 13.9 万次降低到 1.9 万次，每周期指令数 (IPC) 提升了一倍以上。批量处理的优势会随着数据集规模增长而扩大：当工作集位于 L2 时为 1.2 倍，超出 L3 后则达到 2.8 倍，因为预取能够掩盖越来越高的 DRAM 往返访问开销。</p></li><li><p>在 ARM 上，缓存未命中几乎没有变化。真正变化的是利用率：后端停顿减少 40%，因为交错访问模式让流水线持续有任务可执行。这一优势在不同数据集规模下稳定保持在 1.8 倍，因为内存级并行性无论数据来自缓存还是 DRAM 都同样适用。</p></li></ol><p>两种架构，两种策略，一个结果：在生产规模下，即使向量散落在主内存各处，simdvec 也能让 CPU 流水线保持忙碌。</p><h2>这对 Elasticsearch 用户意味着什么</h2><p>这些内核层面的能力会不断叠加。一次向量搜索查询可能会执行数百万次距离操作：HNSW 图遍历、候选项评分、重排序。在数千个并发查询下，每次操作的纳秒级差异都会直接影响查询延迟和集群吞吐量。无论您使用 float32、int8、bfloat16 还是 BBQ，无论您的索引在内存中还是磁盘上，simdvec 都是底层的引擎，而每一次操作都运行在这个引擎上，并经过了哪怕一纳秒都不放过的极致精细调优。</p><p>关键结论是，在生产规模下，向量搜索性能并不主要由原始 SIMD 吞吐量决定，而是取决于系统能否在持续处理数百万次小型操作的同时，高效掩盖内存延迟。</p><p>simdvec 内核几乎会随每个 Elasticsearch 版本持续改进。当新的量化类型和硬件平台出现时，它们从第一天起就能获得经过调优的内核。而现有类型也会随着我们不断优化已发布的实现而持续变快。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Lorenzo Dematte,Simon Cooper]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18088409621369b/6a1701dfdc55de7297e00c8b/df9646091bafbbf0a6dfd212ff8a6bd1e8589708-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[基于 Elasticsearch + Jina 嵌入的无监督文档集群]]></title>
    <description><![CDATA[一种使用 Elasticsearch 和 Jina 嵌入进行无监督文档集群的实用、可复现方法。]]></description>
    <content:encoded><![CDATA[<p>向量搜索从查询开始，但如果您没有查询呢？</p><p>组织往往会积累大量文档，例如支持工单、法律文件、新闻资讯和研究论文；在提出正确的问题之前，首先需要了解这些文档里都包含哪些内容。没有标签或训练数据，手动审查数千份文档是不切实际的。当您不知道要搜索什么时，传统搜索无济于事。</p><p>本文将介绍一种 Elasticsearch 原生方法，用于无监督文档集群和时序故事追踪，帮助解决这一发现难题。读完本文后，您就可以像这样跨天追踪故事脉络：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="贯穿 2025 年 2 月的时间故事链：每条彩色路径都表示一个跨天延续的故事，连线宽度表示 kNN 重叠强度。" /><p><strong>您将发现：</strong></p><ul><li><p>为什么当您希望在没有查询的情况下进行主题发现时，<strong>集群嵌入</strong>（而非检索嵌入）至关重要。</p></li><li><p>如何借助 Elasticsearch 的 k 近邻 (kNN) 和批量 <code>msearch</code>，通过密度探测质心分类按主题对文档进行分组。</p></li><li><p><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><code>significant_text</code></a> 如何自动为集群添加标签，让主题在无需训练模型的情况下也能清晰呈现。</p></li><li><p>时间故事链如何将每日集群联系起来，展示主题如何逐日演变。</p></li></ul><p>该管道以来自 BBC News 和 The Guardian 的约 8,500 篇 2025 年 2 月文章作为测试语料库。新闻之所以适合作为示例，是因为它具有清晰的时间演化特征；而在任何文档发现至关重要的场景中，这种模式同样适用，例如法律审查、合规监控、研究整合和客户支持分流。</p><p><strong>技术栈：</strong></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text"><strong>Jina v5</strong></a> <strong>集群嵌入：</strong>用于主题分组的任务专用低秩自适应 (LoRA) 适配器。<a href="https://www.elastic.co/blog/elastic-jina-ai">Jina 已加入 Elastic</a>，其模型可通过 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a> 原生调用。</p></li><li><p><strong>Elasticsearch：</strong>可扩展的 <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a>、<code>significant_text</code> 标签生成和向量存储。</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><strong>DiskBBQ：</strong></a>一种基于磁盘的向量索引格式，结合了 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq">Better Binary Quantization (BBQ)</a> 与分层 k-means 分区，以加速近似最近邻 (ANN)。这种索引分区是向量搜索的内部机制，与本文使用的密度探测集群算法相互独立。与 <code>bbq_hnsw</code> 相比，<code>bbq_disk</code> 将量化向量存储在磁盘上，并仅在堆内存中保留分区元数据，在保持高召回率的同时，大幅降低了资源需求。</p></li><li><p><strong>全局集群 + 每日时间链接：</strong>发现与故事演变。</p></li></ul><p><strong>您需要：</strong></p><ul><li><p>Elasticsearch 部署（Elastic Cloud、Elasticsearch Serverless 或 Elastic 自托管 8.18+/9.0+）：<code>bbq_disk</code> 需要 8.18 或更高版本。可选的 diversify retriever 部分需要 9.3+ 或 serverless。</p></li><li><p><a href="https://jina.ai/embeddings/">Jina API 密钥</a>：免费层级包含 1,000 万个 token，足以覆盖核心集群管道的需求（约 425 万个 token）。可选的 retrieval-versus-clustering 对比需要进行第二轮嵌入计算。</p></li><li><p><a href="https://bonobo.capi.gutools.co.uk/register/developer">Guardian API 密钥</a>（免费）。</p></li></ul><h2>设置</h2><p>安装所需软件包：</p>pip install elasticsearch pandas numpy plotly umap-learn python-dotenv pydantic-settings datasets requests<p>可选（仅当您从此仓库运行抓取帮助程序时）：</p>pip install beautifulsoup4<p>然后在项目根目录的 <code>.env</code> 文件中配置 API 密钥：</p>ELASTIC_CLOUD_ID=your-cloud-id        # or ELASTIC_HOST=https://...
ELASTIC_API_KEY=your-api-key
JINA_API_KEY=your-jina-key
GUARDIAN_API_KEY=your-guardian-key<p>此笔记本调用 <code>load_dotenv(override=True)</code>，因此本地 <code>.env</code> 值优先。</p>Connected to Elasticsearch<h2>第 1 部分：发现式集群 —— 为什么要使用集群嵌入？</h2><p>大多数向量搜索都会使用经过训练的<strong>检索嵌入</strong>来将<em>查询</em>与相关<em>文档</em>进行匹配。这对于搜索非常合适，但并不适合用于发现。当您希望在没有任何查询的情况下找到语料库中的主题时，您需要使用能将相似文档组合在一起的嵌入。</p><p>Jina v5 通过<strong>面向特定任务的低秩适配 (LoRA) 适配器</strong>解决了这个问题。LoRA 在保持大部分基础模型权重冻结的同时，对目标内部层添加小幅低秩更新，使模型行为转向特定任务，而无需完全重新训练。同一基模型根据 <code>task</code> 参数产生不同的嵌入：</p><p>任务</p><p>训练用于</p><p>用例</p><p>检索.段落</p><p>查询-文档匹配</p><p>搜索，检索增强生成 (RAG)</p><p>聚类</p><p>主题分组（针对紧密集群进行优化）</p><p>发现与分类</p><p>集群适配器经过训练，使相同主题的文档在嵌入空间中<em>更接近</em>，而不同主题的文档则<em>更远离</em>。下面的可视化对比会更直观地呈现这种差异。</p><h3>检索与集群：可视化对比</h3><p>为了展示这种差异，我们分别使用两种任务类型对文档样本进行嵌入。集群在原始 1024 维嵌入空间中执行；Uniform Manifold Approximation and Projection (UMAP) 仅用于将这些嵌入投影到 2D 进行可视化。UMAP 保留局部邻域结构，因此可用于比较集群的分离程度。</p><p>下图展示了同一组 480 篇文档样本分别采用两种任务类型进行嵌入后，再通过 UMAP 投影到 2D 的结果。请观察集群面板中那些更紧密、彼此分离更明显的颜色分组。</p>    Full dataset: 8,495 articles
    Sources: guardian: 5749, bbc: 2746
    Date range: 2025-02-01 to 2025-02-28


    Sample: 480 docs across 8 sections
    section
    Film              60
    World news        60
    Australia news    60
    Opinion           60
    Football          60
    US news           60
    Sport             60
    Business          60


    Clustering embeddings: 480
    Retrieval embeddings:  480


    UMAP projection complete<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b3733dccad212b6/6a1709407d8d67aaeb70e6a4/9bcf7a744900560c1c6c63a2dc3af2f9bfd33e11-1100x500.png" alt="UMAP 检索与集群嵌入对比" /><p><em>检索嵌入（左）会更分散地铺开各个主题；集群嵌入（右）则会基于相同文档形成更紧密、彼此分离更明显的分组。</em></p><p>集群嵌入能够形成更紧密、视觉上也更清晰的分组。检索嵌入会更均匀地分布各个主题，因此非常适合搜索（细粒度相似度）；但对发现来说，更关键的是紧密的主题集群。</p><p>这就是为什么在本演练的其余部分中使用 <code>task="clustering"</code> 的原因。</p><h3>加载数据集</h3><p>该语料库结合了 2025 年 2 月的两个新闻来源：</p><ul><li><p><strong>BBC News</strong>通过<a href="https://huggingface.co/datasets/RealTimeData/bbc_news_alltime">RealTimeData/bbc_news_alltime</a>HuggingFace 数据集。</p></li><li><p><strong>The Guardian</strong> 通过 <a href="https://open-platform.theguardian.com/">Guardian Open Platform API</a>。</p></li></ul><p>纳入多个来源，有助于验证集群识别出的是真正的<em>主题</em>，而不是<em>某个来源特有的写作风格</em>。</p>    Total articles:  8,495
    
    Source breakdown:
    source
    guardian    5749
    bbc         2746
    
    Date range: 2025-02-01 → 2025-02-28
    Days covered: 28
    
    Sample article:
      Source:  guardian
      Title:   Carbon monoxide poisoning ruled out in death of Gene Hackman and wife, police sa
      Section: Film
      Text:    Authorities have ruled out that Gene Hackman and his wife, Betsy Arakawa, died from carbon monoxide poisoning earlier this week in their home in Santa Fe, New Mexico. The Santa Fe county sheriff, Adan...<h3>使用集群任务进行嵌入</h3><p>在调用 Jina v5 API 处理所有文档时均会传入 <code>task="clustering"</code>。嵌入会缓存到磁盘，因此后续运行会完全跳过 API。</p><p>API 调用很简单。<code>task</code> 参数是与典型嵌入使用的关键区别：</p>payload = {
    "model": "jina-embeddings-v5-text-small",
    "input": texts,
    "task": "clustering",  # ← This selects the clustering LoRA adapter
}<p>以下时间反映的是缓存命中情况。第一次对 API 运行时间更长，具体取决于语料库大小。</p>    Embeddings ready: 8,495 vectors of dimension 1024
    Time: 0.6s<h3>索引到单个 Elasticsearch 索引</h3><p>对于发现式集群，整个月的数据都会写入同一个索引 (<code>docs-clustering-all</code>)。每日分区会在后续阶段进行，用于实现时间故事链接。</p><p>索引映射对向量字段使用 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_disk</code></a>：</p>{
  "embedding": {
    "type": "dense_vector",
    "dims": 1024,
    "index": true,
    "similarity": "cosine",
    "index_options": {
      "type": "bbq_disk"        // hierarchical k-means partitioning for ANN index lookup; separate from this post's clustering algorithm
    }
  }
}<p>1024 维 float32 向量大小为 4 KB。 <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><code>bbq_disk</code></a> 使用分层 k-means 将向量划分为小集群，对其进行二进制量化，并将全精度向量存储在磁盘上以便进行二次评分。只有分区元数据保留在堆内存中，因此即使面对大型语料库，内存需求仍然较低。对于能够承受更多堆内存的工作负载，<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_hnsw</code></a> 构建分层可导航小世界 (HNSW) 图，以实现更快的查找，但资源消耗更高。</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a> 字段类型支持多种量化策略：<code>bbq_disk</code> 和 <code>bbq_hnsw</code> 最适合高维嵌入，例如此处使用的 1,024 维向量。</p>    Indexed 8,495 documents into docs-clustering-all
    Time: 57.5s<h3>集群：基于密度探测的质心分类</h3><p>传统的集群算法（如 HDBSCAN）假设您可以将完整的 N×d 向量矩阵保存在内存中，并运行重复的完整遍历更新。对于 8,495 篇 1024 维文档而言，这一规模尚可管理（约 35 MB）；但如果没有额外基础设施，这种方法就无法扩展到数百万篇文档。</p><p>从概念上看，该算法类似于采用 Voronoi 分配和噪声底限的 KMeans++ 初始化；但它将 Elasticsearch <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN 搜索</a>作为计算原语，因此几乎所有工作都在服务器端完成。</p><ol><li><p><strong>抽取 5% 的文件</strong> 作为密度探针（随机抽样，至少 50 个）。</p></li><li><p><strong>通过批量</strong> <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><strong><code>msearch</code></strong></a> <strong>kNN</strong> 查询探测密度。每个探针发出 kNN 查询，并记录其邻居的平均相似度。高平均相似度 = 嵌入空间中的稠密区域。<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><code>msearch</code></a> 在单个 HTTP 调用中发送多个搜索请求，这一点至关重要：密度探测生成数百个 kNN 查询，批量处理可避免每个请求的开销。</p></li><li><p><strong>通过多样化策略选择高密度种子</strong>：将密度高于中位数的候选种子按密度从高到低排序，只有当它与每个现有种子的余弦相似度都低于分离阈值时，才按贪婪策略予以接受。这是唯一的客户端计算（8k 文档约 0.01 秒）。</p></li><li><p><strong>通过</strong> <strong><code>msearch</code></strong> <strong>kNN</strong> 按质心对所有文档进行分类：每个种子都作为一个质心，kNN 搜索会检索相似度高于阈值的邻近文档。每个文档都会被分配给返回该文档且得分最高的质心。小集群被归为噪声。</p></li></ol><p>Elasticsearch 负责处理核心计算：使用 <code>msearch</code> 进行密度探测和分类，并使用 <code>significant_text</code> 生成标签。对于该语料库（8,495 个文档），5% 的密度探针样本会发起 425 个 kNN 探针查询，<code>msearch</code> 会将其批量处理为 9 次 HTTP 调用（批大小为 50），从而避免每个探针单独发起一次请求的开销。再结合 <code>bbq_disk</code> ANN 查找，整个集群阶段便能兼顾速度与可扩展性。在集群过程中，kNN 查询会使用尽可能小的 <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/approximate-knn-search"><code>num_candidates</code></a> 值来提升速度；而在生产环境的搜索查询中，则应使用更高的 <code>num_candidates</code> 值，以牺牲一定延迟为代价换取更高的召回率。</p><p>集群的自然大小由每个质心周围的嵌入空间密度决定，而非硬性的 <code>k</code> 上限。主题越密集的区域，形成的集群就越大；而越小众的主题，则会形成更小的集群。</p><h4>为什么不选择 KMeans 或 HDBSCAN？</h4><p>KMeans 假设集群为球形，并需要将完整的 N×d 矩阵加载到内存中。对于适合内存的语料库，<a href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.HDBSCAN.html">HDBSCAN</a> 是一个强有力的替代方案。它既可以处理任意形状的集群，也具备更易理解的密度语义。</p><p>密度探测质心方法面向的是另一类场景：您希望在同一系统中完成存储、检索和集群，或者数据规模已经大到使客户端矩阵运算变得不切实际。它使用 Elasticsearch kNN 作为计算原语，处理任意大小的集群，并将几乎所有计算保留在服务器端。</p>    Clustered global index in 31.6s
      Total clusters: 82
      Total noise:    2420 (28.5%)
      Density probes: 425 kNN queries via 9 _msearch HTTP calls<h4>理解噪声率</h4><p>约 28% 的噪声率是有意为之，并不意味着系统出现了故障。在配置的 <code>similarity_threshold</code> 下，不属于任何密集集群的文档将保持未分配状态，而不是被强制匹配到不合适的集群中。这相当于一道质量门槛：评论专栏、短文和一次性报道往往难以形成集群，因为它们缺乏构成连贯分组所需的主题密度。</p><p>阈值可调：降低 <code>similarity_threshold</code> 会产生更激进的集群（分配更多文档，但集群更松散），提高则会收紧集群并增加噪声比例。对于这种包含混合新闻内容的语料库，约 30% 的噪声比例是一个合理的平衡点。生产部署应根据特定领域的质量标准调整阈值。</p><h3>使用 significant_text 自动添加标签</h3><p>现在，每个集群都需要一个便于人工理解的标签。Elasticsearch 的 <code>significant_text</code> 聚合会找出在前景集（集群）中出现异常频繁、而在背景集（完整语料库）中不常见的词项。</p><p>其底层采用统计启发式方法（默认为 JLH 分数），平衡了绝对频率与相对频率的变化，无需机器学习，也无需调用大语言模型 (LLM)。例如，一个关于英国政治的集群，可能会浮现出 <code>starmer</code>、<code>labour</code>、<code>downing</code> 等词项，因为与整体新闻语料库相比，这些词项在该集群中出现得异常频繁。</p><p>在这一全局处理阶段，标签直接基于 <code>docs-clustering-all</code> 计算，因此前景集和背景集都取自整个月的数据。在第 2 部分中，标签会使用每日索引模式 (<code>docs-clustering-*</code>)。这是一个通配符，可让查询同时覆盖所有匹配的索引，从而为 significant_text 提供更广泛的背景，以获得更好的对比效果。</p><p>一个最小查询形状如下所示：</p>{
  "size": 0,
  "query": { "term": { "cluster_id": "72" } },
  "aggs": {
    "label_terms": {
      "significant_text": {
        "field": "text",
        "size": 5,
        "filter_duplicate_text": true
      }
    }
  }
}<p><code>significant_text</code> significant_text 也可作为一道质量门槛：未产生任何显著词项的集群，说明其缺乏可区分的词汇特征。这类分组本身并不连贯，因此应归为噪声，而不应赋予带有误导性的标签。</p><p>一个轻量级的确定性清理步骤会移除噪声较大的标签词项（如数字 token 和通用词），并在必要时回退到代表性标题。这样既保留了 Elasticsearch 原生标签的特点，也提升了可读性。</p>    Sample cluster labels:
      cluster   3  (200 docs)  arsenal | mikel | villa
      cluster   1  (198 docs)  volodymyr | ukrainian | kyiv
      cluster   0  (196 docs)  hostages | hamas | israeli
      cluster   4  (187 docs)  scrum | rugby | borthwick
      cluster  52  (185 docs)  fossil | renewable | renewables
      cluster  10  (156 docs)  labour | gwynne | mps
      cluster  40  (151 docs)  novel | novels | literary
      cluster  11  (149 docs)  mewis | sarina | wiegman
      cluster  44  (143 docs)  flooding | rainfall | rain
      cluster  13  (131 docs)  doge | musk | elon
      cluster  12  (128 docs)  murder | insp | knockholt
      cluster   5  (124 docs)  putin | backstop | starmer


    Reassigned 35 docs from incoherent clusters to noise
    Total docs: 8,495
    Clustered:  6,040 (71.1%)
    Noise:      2,455 (28.9%)<h3>集群可视化</h3><p>下方的可视化结果展示了全局集群阶段的发现，包括按日期划分的集群文档与噪声文档分布、整个月的 UMAP 投影，以及用于验证集群反映的是主题而非来源的来源构成图。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ed087d8b6dac2a0/6a17094260084b44543c4501/99099f5adaa945ae4097c50b0d7151c7dd28872e-1000x400.png" alt="集群文档与噪声文档的每日分布" /><p>2025 年 2 月期间，集群文档与噪声文档的每日分布情况。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5ca320bc91131ab/6a17094366c4f95828f8bfbf/477c6c7177942955a942f85f5c881da50e517915-1100x700.png" alt="全月 UMAP 投影（含所有文档）" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f554a6bc2bc367b/6a170945a929cfa7a1ae0947/4f4302556c8974c416842452cf33bca06e90b966-1100x700.png" alt="仅显示集群文档的 UMAP 投影" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8e40c26f89ce5523/6a17094747d49c147d2d8974/327f96a79e382ef30614cb0570aa7fccd822b8f8-1100x700.png" alt="[突出显示单个集群的 UMAP 投影" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1dd2c19ae628f1d/6a1709481949f7a630e7a9a3/acfb1524a10e24d6ff2412e7c3ec0f2b3ac75193-900x600.png" alt="每个集群的来源分布，显示基于主题的分组" /><p>UMAP 中的每个彩色岛屿都代表一个集群：一组关于同一主题的文章，纯粹是通过嵌入相似性而发现的。灰色噪声点则是未能明确归入任何集群的文章（通常是短篇文章、观点文章或一次性报道）。</p><p>来源细分图表确认，集群中的文章<strong>同时</strong>来自 BBC News 和 The Guardian。集群找到的是<em>主题</em>，而非<em>来源</em>，这正是无监督发现应该产生的结果。</p><h3>使用 diversify retriever 探索集群的广度</h3><p>普通 kNN 返回与集群质心（密集核心）最相似的文档。但真实的集群往往还会涵盖多个子主题。<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/diversify-retriever"><strong>diversify retriever</strong></a> 使用最大边际相关性 (MMR)，呈现既与质心相关、<em>彼此之间又有所差异</em>的文档。</p><p>关键参数是<strong>λ（lambda）</strong>：</p><ul><li><p>λ = 1.0 → 纯相关性（与普通 kNN 相同）。</p></li><li><p>λ = 0.0 → 纯多样性（结果最大程度分散）。</p></li><li><p>λ = 0.5 → 均衡：既与主题保持相关，又能覆盖不同角度。</p></li></ul><p>最简 retriever 请求结构如下：</p>{
  "size": 8,
  "retriever": {
    "diversify": {
      "type": "mmr",
      "field": "embedding",
      "lambda": 0.5,
      "query_vector": "&lt;cluster-centroid-vector&gt;",
      "retriever": {
        "knn": {
          "field": "embedding",
          "query_vector": "&lt;cluster-centroid-vector&gt;",
          "k": 50,
          "num_candidates": 100
        }
      }
    }
  }
}<p>在 diversify 层级，<code>type</code>、<code>field</code> 和 <code>query_vector</code> 参数均为必需：<code>field</code> 用于告知 MMR 应使用哪个 dense_vector 字段来计算结果之间的相似度，而 <code>query_vector</code> 则提供相关性评分的参考向量。</p><p>这可以让您回答：“这个集群到底涵盖了什么？”而不仅仅是“它的中心是什么？”</p>    Exploring cluster 52 (185 docs)
    Label: fossil | renewable | renewables
    Centroid computed (dim=1024)


    ========================================================================
    Plain kNN (closest to centroid)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9710] Thirteen more oil and gas licences could be cancelled as ministers decide new guidance for fossil fuel extraction after a landmark court...
      3. [0.9699] Experts have accused the fossil fuel industry of seeking special treatment after lobbyists argued greenhouse gas emissions from oilfields...
      4. [0.9681] Burning wood is a terrible way of producing electricity . Chopping down trees destroys habitats for wildlife, and growing new trees cannot...
      5. [0.9649] Keir Starmer will do huge damage to the global fight against climate change if he gives in to political pressure and allows the development...
      6. [0.9641] Labour will next week be confronted with stark policy choices that threaten to expose the fault lines between the Treasury and the...
      7. [0.9638] The Drax power station near Selby in north Yorkshire burns imported wood pellets  The government has agreed a new funding arrangement with...
      8. [0.9581] If you care about the world we are handing on to future generations, the news on Thursday morning was dramatic. This January was the...
    
    ========================================================================
    Diversify retriever (MMR, lambda=0.5)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9434] Oil and gas interests have waged a coordinated campaign to kill pro-electrification policies that ban gas connections in new buildings ,...
      3. [0.9303] It was interesting to read that new licences for oil and gas production in the North Sea are being delayed by legal action ( Thirteen more...
      4. [0.9139] The US energy secretary, Chris Wright, has said he “would love to see Australia get in the game of supplying uranium and maybe going down...
      5. [0.9077] Rachel Reeves was facing criticism on Saturday night as it was confirmed that a report she cited as evidence that a third ­runway at...
      6. [0.8996] When Margaret Thatcher opened the Hadley Centre for Climate Change in 1990 journalists suggested she was attempting to appear to be doing...
      7. [0.8993] The vast majority of governments are likely to miss a looming deadline to file vital plans that will determine whether or not the world has...
      8. [0.8987] European imports of seaborne gas shipments fell by a fifth last year to their lowest level since the pandemic, according to a new report,...
    
    Overlap: 1/8 documents appear in both result sets
    
    Avg pairwise similarity (lower = more diverse):
      Plain kNN:          0.9057
      Diversify retriever: 0.6965<p>普通 kNN 的结果往往集中在主题的某一个侧面，也就是那些与质心最相似、彼此之间也最相似的文档。diversify retriever 则会展示同一集群的不同侧面，包括子主题、不同来源和多样化视角。</p><p>多样性指标定量证实了这一点：diversify retriever 结果的平均两两相似度较低，意味着返回的文档覆盖范围更广。</p><p>这适用于：</p><ul><li><p><strong>理解一个集群实际涵盖的范围</strong>，不仅要关注其中心，还要关注其边缘。</p></li><li><p><strong>生成摘要</strong>。多样化且有代表性的文档为 LLM 提供了更好的素材。</p></li><li><p><strong>寻找代表性示例</strong>，用于人工审核或下游标签生成。</p></li><li><p><strong>质量检查</strong>。如果多样化结果看起来不够连贯，就说明这个集群可能需要进一步拆分。</p></li></ul><h2>第 2 部分：时间故事链</h2><h3>跨天追踪故事</h3><p>第 1 部分对整个月的数据进行了全局集群，以发现其中的主题。为了呈现时间演化，同样的密度探测质心分类会按天在<strong>每日索引</strong>上独立运行，再将相邻日期的集群连接起来。请注意，每日集群与第 1 部分中的全局集群相互独立；每天都会生成自己的集群分配和标签，并根据当天的内容进行调整。</p><h4><strong>链接方法：采样与查询</strong></h4><p>对于第 A 天的每个集群：</p><ol><li><p>采样几个代表性文档。</p></li><li><p>对 B 天的索引运行 kNN。</p></li><li><p>统计落入 B 天每个集群的命中数量。</p></li><li><p>如果命中比例超过阈值（kNN 比例 ≥ 0.4），则记录一条链接。</p></li></ol><p>这速度很快（每个集群只查询少量文档，不是全部），并且使用 Elasticsearch 的原生 kNN，无需外部工具。</p>Preparing daily indices for temporal linkage...


Indexed 8,495 docs into 28 daily indices


Temporal links found: 808 in 145.4s

Strongest links:
  2025.02.01 'league | arsenal | premier' -&gt; 2025.02.02 'league | season | striker'  (100%)
  2025.02.03 'league | striker | loan' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.03 'score | operator | gedling' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.12 'playoff | leg | bayern' -&gt; 2025.02.13 'league | players | injury'  (100%)
  2025.02.14 'league | injury | football' -&gt; 2025.02.15 'league | premier | football'  (100%)
  2025.02.18 'russia | ukraine | talks' -&gt; 2025.02.19 'saudi | russia | arabia'  (100%)
  2025.02.18 'football | league | bayern' -&gt; 2025.02.19 'league | manchester | players'  (100%)
  2025.02.21 'league | premier | manchester' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.21 'rugby | calcutta | brilliant' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.26 'metals | kyiv | ukrainian' -&gt; 2025.02.27 'ukraine | russia | talks'  (100%)<p>kNN 比例达到 100% 表示源集群中的所有采样文档都落入同一个目标集群，也就是强度最高的跨日关 流水以上大多数关联都与足球相关，这很合理：英超联赛的报道每天都有，且主题一致性很高。</p><p><code>score | operator | gedling</code> → <code>league | striker | season</code> 链接是一个小众本地足球集群（Gedling 是一家非联赛俱乐部）在第二天被吸收到更广泛的英超联赛集群中的一个例子，这是每日以不同粒度重新集群的自然效果。</p><h3>构建故事链</h3><p>故事链是由连续多天的关联集群组成的序列。</p><p>单个配对链接可以显示周一与周二“英国政治”集群之间的关联。故事链则能揭示完整的发展脉络：一个故事从周一开始，在一周内持续发展，并在周五逐渐淡出。</p><p>链通过贪婪策略构建，所依据的是 kNN 比例 ≥ 0.4 的关联；这意味着源集群中至少有 40% 的采样文档会落入同一个目标集群。算法从最早出现的集群开始，并始终沿着最强的出向关联继续延伸。
</p>    Strong links (kNN fraction &gt;= 0.4): 244
    Story chains spanning 3+ days: 18
      Chain 1: 'ukrainian | kyiv | eastern' (19 days: Feb 3 → Feb 21)
      Chain 2: 'playing | opposition' (19 days: Feb 10 → Feb 28)
      Chain 3: 'tadhg | maro | cadan' (10 days: Feb 1 → Feb 10)
      Chain 4: 'invade | china | putin' (8 days: Feb 21 → Feb 28)
      Chain 5: 'elected | labour | leader' (7 days: Feb 12 → Feb 18)
      Chain 6: 'film | swift | awards' (6 days: Feb 2 → Feb 7)
      Chain 7: 'amendment | termination | reporting' (6 days: Feb 12 → Feb 17)
      Chain 8: 'officers | scene | police' (5 days: Feb 1 → Feb 5)<p>最长的链条连续 19 天追踪乌克兰–俄罗斯相关报道。考虑到 2025 年 2 月持续紧张的地缘政治局势，这并不令人意外。其次是贯穿当月 19 天的英超足球报道。更短的链条则对应于颁奖季（电影/颁奖，6 天）、六国橄榄球赛（10 天）以及英国政治领导层相关报道（7 天）。每条链都代表一条故事轨迹，这些轨迹完全是基于每日索引之间的嵌入相似性自动发现的。</p><h3>Sankey：可视化故事流</h3><p>Sankey 图是一种流向可视化图表，其中连线宽度表示连接强度。在这里，每个垂直条带代表一天，每个节点代表一个每日集群（大小由文档数量决定），每条彩色路径则描绘出一条跨时间延展的故事链。链接宽度表示 kNN 重叠强度：更粗的链接意味着更多采样文档落入目标集群。每条链都使用统一颜色，因此从左到右的一条同色路径就代表一个故事的发展过程。</p><p>例如，乌克兰－俄罗斯链（作为较长路径之一清晰可见）从 2 月初一直延续到第三周；其链接始终较粗，表明该主题在不同日期之间具有很强的连续性。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="贯穿 2025 年 2 月的时间故事链" /><p><em>时间故事链贯穿 2025 年 2 月。每条彩色路径都代表一个跨天延续的故事；连线宽度表示 kNN 重叠强度。</em></p><h2>这种方法的成果</h2><p>本文完整介绍了基于 Elasticsearch 构建的无监督文档集群管道：</p><ol><li><p><strong>集群嵌入</strong>：Jina v5 的任务专用适配器可生成针对主题分组优化的嵌入，而不仅仅是用于查询-文档匹配。</p></li><li><p><strong>全局发现式集群</strong>：在一个索引中对整个月的数据进行集群，可最大限度地发掘跨日主题。</p></li><li><p><strong>密度探测质心分类</strong>：取样 5%，通过 <code>msearch</code> kNN 探测密度，选择不同的高密度种子，再根据这些质心对所有文档进行分类。Elasticsearch 负责处理大部分计算任务；客户端仅负责耗时极短（约 0.01 秒）的种子选择工作。</p></li><li><p><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><strong><code>significant_text</code></strong></a><strong>标签生成</strong>：无需借助 ML 模型或人工标注，显著性检验就能生成有意义的集群标签。无法产生任何显著词项的集群，说明其内部缺乏连贯性，因此会被降为噪声——这也是一种内置的质量控制机制。</p></li><li><p><strong>时间故事链接</strong>：借助每日索引以及跨索引的采样与查询 kNN，追踪故事如何随时间演变。</p></li></ol><p><strong>关键要点：</strong></p><ul><li><p>嵌入任务类型至关重要：集群嵌入能够形成明显更紧密的主题分组。</p></li><li><p>借助 <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN 搜索</a>，Elasticsearch 既可以充当存储层，也可以充当集群引擎。</p></li><li><p>密度探测质心分类几乎将所有计算保留在服务器端，并生成由嵌入空间密度决定的自然大小的集群。</p></li><li><p><code>significant_text</code> 该方法速度快、可解释性强，在自动标注和质量门控方面同样十分有效。</p></li></ul><p><strong>这种方法适用的场景：</strong></p><ul><li><p>您拥有带有时间戳的文本，且希望在无标注训练数据的情况下进行主题发现。</p></li><li><p>您希望使用同一套技术栈完成存储、向量搜索、标注和时间关联。</p></li></ul><p><strong>还可以进一步探索的扩展方向：</strong></p><ul><li><p>多周期集群（如按周、按月汇总）</p></li><li><p>通过增量集群分配进行实时摄取。</p></li><li><p>以 significant_text 词项为种子生成 LLM 集群摘要。</p></li><li><p>在更大规模下，采样得到的 KMeans 质心可以作为基于密度的集群算法的热启动种子，从而降低探测阶段的成本。</p></li></ul><h2>亲自试用</h2><p>您可以将其替换为自己的带时间戳文档语料库；任何包含日期信息的文本集合都适用于这一管道。完整的笔记本和支持代码可在 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/unsupervised-document-clustering-elasticsearch-jina-embeddings">配套仓库</a>中找到。</p><ul><li><p><a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs"><strong>开始免费试用 Elastic Cloud</strong></a>：几分钟内即可启动一个支持 <code>bbq_disk</code> 的托管集群。</p></li><li><p><a href="https://www.elastic.co/elasticsearch/serverless"><strong>试用 Elasticsearch Serverless</strong></a>：无需管理集群，可自动扩展，并支持本演练涵盖的全部内容。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[ML 研究]]></category>
    <category><![CDATA[Jina AI]]></category>
    <dc:creator><![CDATA[Matthew Adams]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4bd7dd10a7cd6dc8/6a17094a14b270581de3c5b6/662c00694c3e0c2fb2128098bdb6813df9e86a72-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[当 TSDS 遇到 ILM：设计不会拒绝延迟数据的时序数据流]]></title>
    <description><![CDATA[TSDS 时间边界如何与 ILM 阶段交互；以及如何设计能够容忍延迟到达指标的策略。]]></description>
    <content:encoded><![CDATA[<p>最近，我将客户的指标集群从“所有内容都在热层”迁移到了热层/冷层/冻结层架构。这是我以前已执行过几十次的更改。几分钟内，Logstash 就完全停止了数据传输。</p><p>Elasticsearch 拒绝了延迟到达的指标。这些拒绝导致管道滞后，导致更多的延迟数据，从而引发了更多的拒绝。最终，该管道彻底停滞了。</p><p>我们不得不从快照中恢复数据，重新索引数据，并重新设计摄取管道以恢复数据。</p><p>根本原因并非索引生命周期管理 (ILM) 本身。而是时序数据流 (TSDS) 以及它们如何执行有时间限制的后备索引。</p><p>TSDS 可以将指标的存储需求减少 40——70%，但使 TSDS 高效的架构更改也改变了索引随时间推移的行为方式。这些变化在设计 ILM 策略或数据摄取管道可能会产生延迟到达的数据时非常重要。</p><h2>简要说明</h2><p>使用 TSDS 时：</p><ul><li><p>后备索引仅接受特定时间窗口内的文档。</p></li><li><p>如果在索引移动到冷冻或冻结状态后有延迟的数据到达，Elasticsearch 将拒绝接受这些文档，或将其路由到故障存储（如果已配置）。</p></li></ul><p>设计规则：</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<h2>什么是时序数据流？</h2><p><em>时序数据流</em> (TSDS) 是针对指标数据进行了优化的专用数据流。对数据进行路由，使相关文档位于同一分片内，从而优化它们以进行查询和检索。下面介绍 Elasticsearch 如何实现这一操作：</p><p>每个文档包含：</p><ul><li><p>时间戳。</p></li><li><p>用于识别时间序列的维度字段。</p></li><li><p>表示测量值的度量字段。</p></li></ul><p>示例包括：</p><ul><li><p>每台主机的 CPU 使用率。</p></li><li><p>每项服务的请求延迟。</p></li><li><p>每个传感器的温度读数。</p></li></ul><p><em>维度 </em>确定了我们要测量的内容，而<em>度量 </em>则代表了随时间变化的值。</p><h3>尺寸</h3><p>维度描述被测量的实体。</p><p>示例:</p>host.name
service.name
container.id<p>我们在映射中按以下方式定义它们：</p>time_series_dimension: true<h3>指标</h3><p>指标代表数值，并使用以下方式定义：</p>time_series_metric<p>常用指标类型：</p><ul><li><p>计量：数值会上升和下降。</p></li><li><p>计数器：数值不断增加，直至重置。</p></li></ul><p>Elastic Agent 主要收集指标和日志数据，因此，即使您没有手动启用任何 TSDS 索引，集群中仍可能包含这些索引。</p><h3>_tsid 字段</h3><p>Elasticsearch 内部会根据维度字段生成 <code>_tsid</code> 值。这样，具有相同尺寸的文档就可以路由到相同的分区，从而改进：</p><ul><li><p>压缩。</p></li><li><p>查询位置。</p></li><li><p>聚合性能。</p></li></ul><h2>关键区别：有时间限制的后备索引</h2><p>传统数据流始终写入最新的支持索引，称为 <em>写索引</em>，但 TSDS 的行为有所不同。</p><p>每个 TSDS 后备索引都有一个定义的时间窗口，并且仅接受 <code>@timestamp</code> 值在该窗口内的文档：</p>GET _data_stream/my-metrics-data-stream


     "index_mode": "time_series",
     "time_series": {
       "temporal_ranges": [
         {
           "start": "2026-01-15T14:35:50.000Z",
           "end": "2026-03-16T11:34:40.000Z"
         }
       ]
     }<p>为文档编制索引时，Elasticsearch 会将其路由到负责该时间戳的后备索引，这意味着与传统索引不同，TSDS 可以同时写入多个后备索引。</p><p>例如：</p><ul><li><p>实时数据 → 最新索引。</p></li><li><p>较晚的数据 → 覆盖该时间范围的较早索引。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b001853af30d5f8/6a17dc2cfaa9137a7d93c751/31af2bb3b3dc24db8342e791e1db77a44659ba7a-1589x502.png" alt="时间线显示迟到的文档如何被路由到较旧的索引，而当前的文档则被路由到最新的索引。" /><h2>为延迟到达的数据进行设计</h2><p>真正的摄取管道很少能完美地按时提供指标。指标可能会由于网络中断、传输过程中的积压、批量摄取以及边缘设备的丢失而延迟，这些设备重新连接后会开始追赶进度。</p><p>传统索引会悄然吸收这些延迟。TSDS 不会。</p><p>如果文档的时间戳超出了可写后备索引的范围，Elasticsearch 将拒绝该文档，这意味着您的 ILM 策略必须考虑延迟数据。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e8eae1b2ddad142/6a17dc2e1d1b8335a793e35c/32a103b95b20e31615c214271e27811a7ee315ae-1999x691.png" alt="索引生命周期时间线" /><h2>关键制约因素</h2><p>后备索引必须保持足够长的可写时间，以接受延迟数据。</p><p>实际上：</p>time_until_readonly &gt; maximum_expected_lateness<p>由于 ILM 衡量的是从滚动更新开始算起的年限，因此操作规则变为：</p>warm_or_cold_min_age &gt; rollover_max_age + maximum_expected_lateness<p></p><p>例如，如果指标最多可能延迟六小时到达，则索引在滚动更新后必须保持至少六小时的可写状态。</p><p></p><p>正是由于没有考虑到这一限制，才导致了前面所述的摄取失败。延迟到达的数据被定向到一个早期索引，该索引已经处于冷层并因此被写入阻塞。</p><p></p><h2>处理被拒绝的文档</h2><p>当 TSDS 拒绝文档时，Elasticsearch 返回一个错误，表明时间戳不在可写索引的范围内。您的摄取管道如何处理该错误，决定了是丢失数据还是停止摄取。</p><p>处理被拒绝文档的主要机制是故障存储。</p><h3>故障存储（在 Elasticsearch 9.1+ 中推荐）</h3><p>Elasticsearch 9.1 引入了失败存储，它能自动捕获被拒绝的文档。Elasticsearch 不会将错误返回给客户端，而是将失败的文档写入数据流中的专用失败索引。</p><p>您可以使用以下方法检查故障：</p>GET metrics-myapp::failures/_search<p>使用故障存储可防止摄取管道因拒绝错误而阻塞，同时保留失败的数据以供分析或<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/reindex-tsds">重新索引</a>。</p><h2>监测拒绝问题</h2><p>延迟到达问题通常首先表现为摄取异常。您可能会先注意到它们：</p><ul><li><p>索引速率突然下降。</p></li><li><p>拒绝的文档激增。</p></li><li><p>越来越多的故障存储条目。</p></li><li><p>管道输入和输出计数不匹配。</p></li></ul><p>通过对这些信号发出警报，操作人员可以在管道停滞之前检测问题。工作流、机器学习作业和其他机制可用于自动检测和通知。</p><h2>TSDS + ILM 迁移检查清单</h2><p>如果要将指标集群迁移到 TSDS、引入 ILM 分层，或升级到指标默认为 TSDS 的 Elasticsearch 版本，请先查看这些项目。</p><h3><strong>1. 测量摄取延迟</strong></h3><p>在更改 ILM 策略之前，请确定：</p><ul><li><p>正常摄取延迟。</p></li><li><p>事件期间的最坏延迟情况。</p></li><li><p>批量管道造成的延迟。</p></li></ul><p>您的 ILM 设计必须适应最大实际延迟。</p><h3><strong>2. 验证索引时间窗口</strong></h3><p>检查您的 TSDS 支持索引：</p>GET _data_stream/&lt;your-stream&gt;<p>寻找：</p><ul><li><p><code>time_series.start_time</code></p></li><li><p><code>time_series.end_time</code></p></li></ul><p>这些界限决定了哪些索引可以接受文档。了解这些时间窗口有助于您确定数据最多可以延迟多久才不会被拒绝。</p><h3><strong>3. 为延迟到达的数据调整热层的大小</strong></h3><p>确保后备索引保持可写状态的时间足够长，以便写入延迟到达的数据。</p><p>操作规则：</p><ul><li><p><code>warm_min_age &gt; rollover_max_age + maximum_expected_lateness</code></p></li></ul><p>请记住，如果指标可能晚到六个小时，那么索引必须至少在六个小时内保持可写状态。</p><h3><strong>4. 决定如何处理被拒绝的文档</strong></h3><p>在启用 TSDS 之前选择策略：</p><ul><li><p>故障存储（在 Elasticsearch 9.1+ 中推荐）。</p></li><li><p>Logstash 死信队列。</p></li><li><p>为延迟到达的数据提供后备索引。</p></li><li><p>接受有限的数据丢失。</p></li></ul><h3><strong>5. 监测摄取健康状况</strong></h3><p>为以下内容添加警报：</p><ul><li><p>索引速率下降。</p></li><li><p>已拒绝的文档。</p></li><li><p>故障存储增长。</p></li><li><p>管道输入/输出不匹配。</p></li></ul><p>数据延迟问题通常首先表现为摄取异常。</p><h2>总结</h2><p>时序数据流为指标工作负载提供重大的存储和性能改进，但它们引入了重要的架构变更：后备索引是时间绑定的，这影响了 ILM 的行为。</p><p>使用 TSDS 时：</p><ul><li><p>索引必须保持足够长的可写时间，以接受延迟数据。</p></li><li><p>摄取管道应安全处理被拒绝的文档。</p></li></ul><p>要记住的关键规则是：</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<p>如果围绕这一约束条件设计 ILM 策略，TSDS 就能很好地处理指标工作负载。</p><p>但若忽视这一限制，您的摄取管道可能会很难发现这些时间界限。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</guid>
    <category><![CDATA[索引数据]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Bret Wortman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcacf154aeacb29d8/6a17dc30dbb4ffc7ddfb557f/e4c46e4a6f746d9c845857e80de036f5d51cd4e7-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LINQ to Elasticsearch ES|QL：编写 C# 代码，查询 Elasticsearch]]></title>
    <description><![CDATA[探索 Elasticsearch .NET 客户端中全新的 LINQ to Elasticsearch ES|QL 提供程序。借助该程序，您可以编写会自动转换为 ES|QL 查询的 C# 代码。]]></description>
    <content:encoded><![CDATA[<p>从 <strong>v9.3.4</strong> 和 <strong>v8.19.18</strong> 开始，Elasticsearch .NET 客户端包含一个<a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">语言集成查询 (LINQ) </a>提供程序，可在运行时将 C# LINQ 表达式转换为 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">Elasticsearch 查询语言 (ES|QL)</a> 查询。您可以使用<code>Where</code>、<code>Select</code>、<code>OrderBy</code>、<code>GroupBy</code> 和其他标准操作符来编写查询，而无需手工编写 ES|QL 字符串。提供程序负责转换、参数化和结果反序列化，包括按行流式传输，无论结果集大小如何，都能保持稳定的内存使用量。</p><h2>您的第一个查询</h2><p>首先定义一个映射到 Elasticsearch 索引的普通旧 CLR 对象 (POCO)。属性名称通过标准<code>System.Text.Json</code> 属性（如<code>[JsonPropertyName]</code>）或配置的<code>JsonNamingPolicy</code> 解析为 ES|QL 列名。适用于客户端其他部分的<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">源序列化</a>规则在这里也同样适用。</p>using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Brand { get; set; }

    [JsonPropertyName("price_usd")]
    public double Price { get; set; }

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}<p>类型设置完成后，查询语句如下所示：</p>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync&lt;Product&gt;(q =&gt; q
    .From("products")
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}<p>该提供程序将此转换为以下 ES|QL：</p><p>需要注意的一些细节：</p><ul><li><p><strong>属性名称解析：</strong>由于 <code>[JsonPropertyName]</code> 属性，<code>p.Price</code> 变成了 <code>price_usd</code>，根据默认 camelCase 命名策略，<code>p.Brand</code> 变成 <code>brand</code>。</p></li><li><p><strong>参数捕获：</strong>C# 变量 <code>minPrice</code> 和 <code>brand</code> 被捕获为命名参数 (<code>?minPrice</code>，<code>?brand</code>)。它们与 JSON 有效负载中的查询字符串分开发送，这样可以防止注入，并实现服务器端查询计划缓存。</p></li><li><p><strong>流式传输：</strong><code>QueryAsync&lt;T&gt;</code> 返回 <code>IAsyncEnumerable&lt;T&gt;</code>。从 Elasticsearch 返回数据时，数据会逐行具体化。</p></li></ul><p>您还可以在不执行的情况下检查生成的查询及其参数：</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE (in_stock == true AND price_usd &gt;= 100) | SORT price_usd DESC | LIMIT 10

Console.WriteLine(query.ToEsqlString(inlineParameters: false));
// FROM products | WHERE (in_stock == true AND price_usd &gt;= ?minPrice AND brand == ?brand) | SORT price_usd DESC | LIMIT 10

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }<h2>这如何运作？快速回顾一下 LINQ</h2><p>使 LINQ 提供程序成为可能的机制是 <code>IEnumerable&lt;T&gt;</code> 和 <code>IQueryable&lt;T&gt;</code> 之间的区别。</p><p>在 <code>IEnumerable&lt;T&gt;</code> 上调用 <code>.Where(p =&gt; p.Price &gt; 100)</code> 时，lambda 会编译为 <code>Func&lt;Product, bool&gt;</code>，即一个由运行时在进程内执行的常规委托。这就是 LINQ-to-Objects。</p><p>当您在<code>IQueryable&lt;T&gt;</code> 上调用相同的方法时，C# 编译器会将 lambda 封装在<code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> 中。这是一种数据结构，表示代码的<em>结构</em>，而不是代码的可执行形式。在运行时，该表达式树可被检查、分析，并转换为另一种语言。</p>// IEnumerable: the lambda is a compiled delegate
IEnumerable&lt;Product&gt; local = products.Where(p =&gt; p.Price &gt; 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable&lt;Product&gt; remote = queryable.Where(p =&gt; p.Price &gt; 100);<p><code>IQueryProvider</code> 接口是扩展点。任何提供程序均可通过实现 <code>CreateQuery&lt;T&gt;</code> 和 <code>Execute&lt;T&gt;</code>，将这些表达式树转换为目标语言。实体框架就是利用此机制生成 SQL 语句。LINQ to ES|QL 提供程序使用它来生成 ES|QL 查询。</p><p>上述查询的表达式树如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="示例查询的表达式树。" /><p><em>示例查询的表达式树。</em></p><p>此表达式树由内而外嵌套：<code>Take</code> 包裹着 <code>OrderByDescending</code>，它又包裹着 <code>Where</code>，而后者再包裹着 <code>From</code>，而最内层是根节点 <code>EsqlQueryable&lt;Product&gt;</code> 常量。对于 <code>&amp;&amp;</code>、<code>&gt;=</code> 和 <code>==</code> 这几种操作符而言，<code>Where</code> 谓词本身是一个由 <code>BinaryExpression</code> 个节点构成的子树，其中包含 <code>MemberExpression</code> 个叶子节点，这些叶子节点用于属性访问，以及对 <code>minPrice</code> 和 <code>brand</code> 变量的闭包捕获。提供程序会遍历这一数据结构，从而生成最终的 ES|QL 查询。</p><h2>深入了解：转换管道</h2><p>从 LINQ 表达式到查询结果的路径遵循六阶段管道：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="转换管道概述。" /><p><em>转换管道概述。</em></p><h3>1. 表达式树捕获</h3><p>当在一个 <code>IQueryable&lt;T&gt;</code> 对象上串联使用 <code>.Where()</code>、<code>.OrderBy()</code>、<code>.Take()</code> 及其他操作符时，标准的 LINQ 基础架构会构建一个表达式树。<code>EsqlQueryable&lt;T&gt;</code> 实现了 <code>IQueryable&lt;T&gt;</code> 接口，并将处理委托给 <code>EsqlQueryProvider</code>。</p><h3>2. 翻译</h3><p>当查询被执行 (通过枚举、调用 <code>ToList()</code>，或使用 <code>await foreach)</code> 时)，<code>EsqlExpressionVisitor</code>自内而外遍历表达式树。它会将每个 LINQ 方法调用分派给一个专门的访问器进行处理：</p><p>访客</p><p>翻译</p><p>进入</p><p>WhereClauseVisitor</p><p>.Where(predicate)</p><p>WHERE 条件</p><p>SelectProjectionVisitor</p><p>.Select(selector)</p><p>评估 + 保留 + 重命名</p><p>GroupByVisitor</p><p>.GroupBy().Select()</p><p>统计信息 ... 依据</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>SORT 字段 [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*、Math.*、字符串方法</p><p>80+ ES|QL 函数</p><p>在翻译过程中，表达式中引用的 C# 变量被捕获为命名参数。</p><h3>3. 查询模型</h3><p>访问器不会直接生成字符串。相反，它们会产生 <code>QueryCommand</code> 对象，一个不可变的中间表征。一个 <code>FromCommand</code>、一个 <code>WhereCommand</code>、一个 <code>SortCommand</code> 和一个 <code>LimitCommand</code>，各代表一条 ES|QL 处理命令。这些数据被收集到<code>EsqlQuery</code> 模型中。</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="查询模型和命令模式。" /><p><em>查询模型和命令模式。</em></p><p>该中间模型与表达式树和输出格式均解耦。它可以被检查、拦截（通过 <code>IEsqlQueryInterceptor</code>）或在格式化前进行修改。</p><h3>4. 格式化</h3><p><code>EsqlFormatter</code> 依次访问每个<code>QueryCommand</code> ，并生成最终的 ES|QL 字符串。每条命令占一行，通过 ES|QL 中用于串联处理命令的管道 (|) 运算符分隔。若标识符包含特殊字符，系统会自动用反引号进行转义处理。</p><h3>5. 执行</h3><p>格式化后的 ES|QL 查询字符串及捕获的参数会以 JSON 数据载荷的形式发送至 Elasticsearch 的 <code>/_query</code> 终端。而 <code>IEsqlQueryExecutor</code> 接口则对传输层进行了抽象封装，这正是分层包架构发挥作用的关键环节。</p><h3>6. 实现</h3><p><code>EsqlResponseReader</code> 流式传输JSON响应，但不会将整个结果集缓冲到内存中。以流式方式传输 JSON 响应数据，无需将整个结果集缓存至内存。针对每次查询预先计算生成的 <code>ColumnLayout</code> 树结构，会将扁平化的 ES|QL 列名（如 <code>address.street</code>、<code>address.city</code>）映射到嵌套的 POCO 属性。每行数据会被组装为 <code>T</code> 实例，并通过 <code>IEnumerable&lt;T&gt;</code> 或 <code>IAsyncEnumerable&lt;T&gt;</code> 逐个返回。</p><h2>分层架构</h2><p>LINQ to ES|QL 功能分为三个软件包：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="软件包架构。" /><p><em>软件包架构。</em><a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> 是纯转换引擎。该组件完全不依赖 HTTP 协议栈，集成了表达式访问器、查询模型、格式化器及响应解析器等核心模块。您可独立使用它来构建和检查 ES|QL 查询（无需连接 Elasticsearch），这在测试验证、查询日志记录或自定义执行层开发等场景中极具实用价值。翻译要点解析：</p>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable&lt;Product&gt;(provider)
    .From("products")
    .Where(p =&gt; p.InStock)
    .OrderByDescending(p =&gt; p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC<p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> 是一款轻量级的独立 ES|QL 客户端。该组件通过 <code>Elastic.Transport</code> 在 <code>Elastic.Esql</code> 之上扩展了 HTTP 协议执行能力。如果您的应用程序仅需使用 ES|QL 而无需其他 Elasticsearch API，此方案可实现最小化依赖集成。</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> 是完整的 Elasticsearch.NET 客户端。它还建立在<code>Elastic.Esql</code> 的基础上，并通过<code>client.Esql</code> 命名空间公开 LINQ 提供程序接口。这是大多数应用程序的推荐入口点。</p><p>两个执行层组件包均提供了针对 <code>IEsqlQueryExecutor</code> 接口的独立实现。该策略接口作为转换与传输层的桥梁。</p><p>当与源码生成的 <code>JsonSerializerContext</code> 配合使用时，这三个组件包均支持原生 AOT 编译。如需完整客户端集成方案，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">原生 AOT 文档</a>。</p><h2>不只使用基础功能</h2><p>上面的例子涵盖了筛选、排序和分页。该提供程序支持更广泛的操作范围。</p><h3>聚合</h3><p><code>GroupBy</code>结合 <code>Select</code> 中的聚合函数，转换为 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>：</p>var stats = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .GroupBy(p =&gt; p.Brand)
    .Select(g =&gt; new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p =&gt; p.Price),
        MaxPrice = g.Max(p =&gt; p.Price)
    }));

// -&gt; FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand<h3>投影</h3><p><code>Select</code>，使用匿名类型生成 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a> 和 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a> 命令：</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Select(p =&gt; new { ProductName = p.Name, p.Price, p.InStock });

// -&gt; FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName<h3>丰富的函数库</h3><p>通过 <code>EsqlFunctions</code> 类，可以使用超过 80 个 ES|QL 函数，涵盖日期/时间、字符串、数学、IP、模式匹配和评分。标准 <code>Math.*</code> 和 <code>string.*</code> 方法也已转换：</p>.Where(p =&gt; p.Name.Contains("Pro"))       // -&gt; WHERE name LIKE "*Pro*"
.Where(p =&gt; EsqlFunctions.CidrMatch(      // -&gt; WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))<h3>查找连接</h3><p>跨索引查找转换为 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>：</p>var enriched = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .LookupJoin&lt;Product, CategoryLookup, string, object&gt;(
        "category-lookup-index",
        product =&gt; product.Id,
        category =&gt; category.CategoryId,
        (product, category) =&gt; new { product.Name, category!.CategoryLabel }));<h3>原生 ES|QL 直通接口</h3><p>对于 LINQ 提供程序尚未涵盖的 ES|QL 功能，您可以添加原始片段：</p>var results = client.Esql.Query&lt;Product&gt;(q =&gt; q
    .Where(p =&gt; p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));<h3>服务器端异步查询</h3><p>对于长时间运行的查询，可将其提交给服务器进行后台处理：</p>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync&lt;Product&gt;(
    q =&gt; q.Where(p =&gt; p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);<p>服务器端异步查询对于长时间运行的分析型查询/大规模数据集处理尤其有用，这类操作可能会超出常规的超时阈值；在存在负载均衡器、API 网关或代理（这些组件会强制执行严格的 HTTP 超时设置）的超时敏感环境中，异步查询同样优势显著。异步查询通过将查询提交与结果获取解耦，避免了连接中断的情况。</p><h2>开始使用</h2><p>LINQ to ES|QL 自以下版本起可用：</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong>（9.x 分支）</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong>（8.x 分支）</p></li></ul><p>从 NuGet 安装：</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>入口点位于<code>client.Esql</code>上：</p><p>方法</p><p>返回值</p><p>用例</p><p>查询&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>同步执行</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>异步流式处理</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>高级结构分析和检测</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>长时间运行的服务器端查询</p><p>有关完整的功能参考，包括查询选项、多字段访问、嵌套对象和多值字段处理，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">LINQ to ES|QL 文档</a>。</p><h2>结论</h2><p>LINQ 转 ES|QL 将 C# LINQ 的强大表达能力引入到 Elasticsearch 的 ES|QL 查询语言中，让您无需手工编写查询字符串，就能生成强类型、可组合的查询。它具备自动参数捕获、流式物化功能，还拥有分层式的软件包架构，既能满足独立转换需求，也能适配完整的 Elasticsearch 客户端，可自然融入任意规模的 .NET 应用程序。安装最新客户端，将 LINQ 表达式指向索引，剩下的就交给该提供程序来处理。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Florian Bernd,Martijn Laarman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa35fbcbbf4959f/6a1705b9dc55de19a4e00d07/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[快速与准确：测量量化向量搜索的召回率]]></title>
    <description><![CDATA[说明如何在 Elasticsearch 中以最少的设置来测量向量搜索的召回率。]]></description>
    <content:encoded><![CDATA[<p>所有人都希望向量搜索能即时响应，但是高维向量占用的资源很大。一个 1024 维的 float-32 向量会占用大量内存，将其与数百万个其他向量进行比较的计算成本非常高昂。</p><p>为了解决这个问题，像 Elasticsearch 这样的搜索引擎使用了两种主要的优化策略：</p><ol><li><p><strong>近似搜索（分层可导航小世界 [HNSW]）：</strong>我们不需要扫描每一份文档，而是建立一个导航图，以便快速跳转到答案的可能邻域。</p></li><li><p><strong>量化：</strong>我们对向量进行压缩（例如，从 32 位浮点数压缩为 8 位整数，甚至 1 位二进制值），以减少内存使用量并加快计算速度。</p></li></ol><p>但优化往往会使<strong>准确性</strong>下降。</p><p>这种担忧是有道理的：“如果我在搜索过程中压缩数据并使用快捷方式，我会错过最佳结果吗？”“这种优化是否会降低搜索引擎的相关性？”</p><p>为了证明 Elastic 的量化不会降低结果，我们使用<a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"><strong>DBPedia-14</strong></a><a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"> 数据集</a>构建了一个可重复的测试工具，以精确计算在使用 Elasticsearch 的默认优化时，准确率下降了多少（特别是<strong>召回率）</strong>才能提高速度。</p><p>总结：可能比您想象的要少得多。点击<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">此处查看笔记本</a>，亲自试试</p><h2><strong>定义（面向非专业人士）</strong></h2><p>在了解代码之前，让我们先明确一些术语。</p><ul><li><p><strong>相关性与召回率：</strong><strong>相关性</strong>具有主观性（我找到的是优质内容吗？），而<strong>召回率</strong>则是基于数学计算。如果数据库中有 10 份文档与查询在数学层面<em>完美</em>匹配，而搜索引擎找到了其中 9 份，那么召回率就是 90%（或 0.9）。</p></li><li><p><strong>精确搜索（扁平式）：</strong>有时也被称为“暴力搜索”法。搜索引擎会扫描索引中的每一份文档并计算距离。</p><ul><li><p><em>优点：</em>召回率达到 100%。</p></li><li><p><em>缺点：</em>计算量大且大规模扩展缓慢。</p></li></ul></li><li><p><strong>近似搜索 (HNSW)：</strong>“捷径”方法。搜索引擎生成 <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">HNSW</a> 图表。它遍历图表以找到最近邻。</p><ul><li><p><em>优点：</em>速度极快且可扩展。</p></li><li><p><em>缺点：</em>如果图表遍历过早停止，可能会错过近邻。</p></li></ul></li></ul><h2><strong>实验：精准与近似</strong></h2><p>为了测试召回率，我们使用了 <strong>DBPedia-14</strong> 数据集，这是一个包含 14 个本体类别的大型标题和摘要数据集，通常用于训练和评估文本分类模型。具体而言，我们将重点关注“电影”类别。我们希望将优化的生产设置与数学上完美的基准真值进行比较。</p><p>在此次实验中，我们采用 <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text-small</a> 模型。这是一款处于行业领先水平的多语言模型，在文本表征方面树立了行业基准。我们选择该模型，是因为它确立了当下高性能嵌入的标杆标准。通过将 Jina v5 卓越的精准度与Elasticsearch原生量化技术相结合，我们能够展示一种既具备高效计算能力，又在检索质量上毫不妥协的搜索架构。</p><p>我们设置了一个具有双重映射的索引。我们同时将相同的文本导入两个不同的字段：</p><ol><li><p><strong><code>content.raw</code></strong>类型：<code>flat</code>。这会使 Elasticsearch 对全部 Float32 向量执行暴力扫描。通过这种扫描，系统会返回完全匹配的结果，并将用于我们的基线。</p></li><li><p><strong><code>content</code></strong>类型为<code>semantic_text</code>。默认情况下使用 HNSW + 更好的二进制量化 (BBQ)。这是用于近似匹配的标准、优化生产设置。</p></li></ol><h3><strong>Recall@10 测试</strong></h3><p>在我们的评估指标中，我们使用了 Recall@10。</p><p>我们随机挑选了 50 部电影，并对这两个字段运行了相同的查询。</p><ul><li><p>如果<strong>精确（扁平式）</strong>搜索显示前 10 个近邻是 ID [1, 2, 3……10]，</p></li><li><p>而<strong>近似 (HNSW)</strong> 搜索结果显示的是 ID [1，2，3... 9，99]。</p></li><li><p>我们正确地找到了前 10 个中的 9 个。得分为 <strong>0.9</strong>。</p></li></ul><p>这是我们使用的映射：</p># The "Control Group": Forces exact brute-force scan
"raw": {
    "type": "semantic_text",
    "inference_id": ".jina-embeddings-v5-text-small",
    "index_options": {
        "dense_vector": {
            "type": "flat"
        }
    }
}<p><strong>结果：成功的“平直线”</strong></p><p>我们进行了一次规模测试，重新加载了整个数据集，并对 1,000 到 40,000 个文档的索引规模进行了测试。</p><p>召回率得分情况如下：</p><p>文档</p><p>Recall@10得分</p><p>1,000</p><p>1.000 (100%)</p><p>5,000</p><p>0.998 (100%)</p><p>10,000</p><p>0.992 (99.4%)</p><p>20,000</p><p>0.999 (99.0%)</p><p>40,000</p><p>0.992 (98.8%)</p><p>结果非常稳定。<strong>即使我们扩大了搜索范围，近似搜索也能</strong>在 99% 的情况下与暴力精确搜索相匹配。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8168a0a4946bade7/6a170e154a531b61b536a9eb/a4bfacb1d0cce6fdf6df0e1a9d4fc5d4007a66da-1999x1209.png" alt="向量搜索稳定性：召回率与索引规模" /><h2><strong>为什么它如此有效？</strong></h2><p>您可能会认为将向量压缩成二进制值会对准确性的影响更大。不这样做的原因在于 Elasticsearch 处理检索的方式。</p><p>目前大多数嵌入模型输出的是 Float32 向量，这些向量很大。为了提高搜索效率，Elasticsearch 对高维向量使用量化技术。具体来说，自 9.2 版起，它默认使用<a href="https://www.elastic.co/search-labs/blog/elasticsearch-9-1-bbq-acorn-vector-search">BBQ</a>。</p><p>BBQ 采用<strong>重新打分</strong>机制：</p><ol><li><p><strong>遍历：</strong>搜索引擎使用压缩（量化）向量来快速遍历 HNSW 图表。由于向量较小，它可以高效地进行过度采样，收集更大的候选文档列表（例如，前 100 个大致相似的文档），而不会影响性能。</p></li><li><p><strong>重新评分：</strong>一旦有了这些候选文件，它就会只检索这几份文件的全精度值，以计算出最终的精确排名。</p></li></ol><p>这样就能两全其美，既能以量化的速度完成繁重的工作，又能以浮点运算的精度完成最终排序。</p><h2><strong>我们能做得更好吗？</strong></h2><p>值得注意的是，我们在这里看到的结果是使用默认设置和随机抽样数据得出的。可以将其视为高性能的起点。尽管 Jina v5 性能卓越，但这些召回率分数并非适用于所有数据集的“万能保障”。每个数据集都有其独特之处，虽然您肯定可以进一步调整优化以挖掘出更多性能潜力，但您始终应基于自身特定数据进行基准测试，以明确性能上限所在。</p><h2><strong>结论</strong></h2><p>这是一次规模非常小的测试。不过，本次测试的重点并非专门评估嵌入模型或 BBQ 的性能，而是要展示如何通过极简的设置轻松衡量数据集的召回率。</p><p>如果您想用自己的数据运行此测试，可以点击<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">此处查看笔记本</a>，亲自试试。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt198c7085db96aa04/6a170e17cdacbfe88c7d2a86/09f03b9239d66c36763cdab3fafcdac207ff6d83-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 中 HNSW 的自适应提前终止]]></title>
    <description><![CDATA[为 Elasticsearch 中的 HNSW 引入一种新的自适应提前终止策略。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch 使用<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">分层可导航小世界</a> (HNSW) 算法对邻近图进行矢量搜索。众所周知，HNSW 算法在 k 近邻 (KNN) 搜索结果的质量与相关计算成本之间实现了良好的平衡。</p><p>在 HNSW 中，搜索过程是通过在图中迭代扩展候选节点来推进的，同时维护一个迄今为止已发现的、规模受限的最近邻节点集合。每次扩展都会带来一定的影响（包括向量运算、磁盘随机寻址等操作），并且随着搜索的推进，这种影响所带来的边际效益往往会逐渐降低。</p><p>优化 HNSW 图遍历的一种方法是，当发现新真实邻近节点的边际概率不再提升时，立即停止搜索。因此，在 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">Elasticsearch 9.2</a> 中，我们引入了新<a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">提前终止机制</a>。当连续访问图节点的次数达到固定数量，但仍无法提供足够的新最近邻节点时，搜索过程就会停止。</p><p>本文将指导您了解我们如何改进 HNSW 中提到的提前终止机制，使其更适合不同的数据集和数据分布。</p><h2><strong>HNSW 中的提前终止</strong></h2><p>在 HNSW 中，搜索过程是通过在近邻图中迭代扩展候选节点来推进的，同时持续维护一个迄今已发现、规模受限的最近邻节点集合，直至搜索遍历完整个图，或者满足某些提前终止条件为止。</p><p>因此，提前终止不一定总是性能优化，它本身就是<strong>搜索算法不可或缺的组成部分</strong>。决定终止搜索的时机，直接决定了效率与召回率之间的权衡关系。在 Elasticsearch 中，针对 HNSW 图的查询已内置多种提前终止机制：</p><ul><li><p>访问节点的最大数量固定不变。</p></li><li><p>已达到固定的超时时间。</p></li></ul><p>这些规则虽然简单且可预测，但在很大程度上<strong>与搜索的实际操作无关</strong>。此外，它们主要用于确保最终用户在合理的时间内完成查询。</p><p>在<a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">上一篇博文</a>中，我们介绍了 HNSW 冗余的概念。简而言之，当 HNSW 持续评估那些无法带来更多最近邻节点的新候选节点时，就会产生冗余计算。</p><h2><strong>耐心：衡量进展而非过程</strong></h2><p><em>“耐心”</em>这一概念将提前终止的判定标准重新定义为<strong>“衡量进展而非过程”</strong>。</p><p>而不是问：</p><p>“我们走了多少步？”</p><p>新的问题变成了：</p><p>"在我们彻底丧失希望之前，我们能够接受浪费多少计算量？"</p><p>在 HNSW 搜索过程中，早期探索阶段通常能显著提升前 k 个候选结果集的质量。在 HNSW 图探索的初始阶段，随着算法不断发现与查询向量距离更近的邻近节点，邻近节点集会持续更新。随着搜索逐步收敛，这类质量提升会逐渐减少。<a href="https://cs.uwaterloo.ca/~jimmylin/publications/Teofili_Lin_ECIR2025.pdf">基于“耐心”机制的终止</a>策略会监测这一变化模式，当持续一段时间内未再出现显著改进时，即终止搜索过程。</p><p>在实际操作中，我们在遍历 HNSW 图的过程中，每跳转到一个候选节点时，都会计算队列饱和度。该指标用于衡量在访问最近一个图节点期间，未发生变化的最近邻节点所占的百分比（或者说，是上一轮迭代中引入的新邻节点数量的倒数）。若连续多次迭代中，这一比率持续过高，我们便会停止对图的遍历。</p><p>从概念层面来看，“耐心”机制将HNSW搜索视为一个<strong>收益递减的过程</strong>。当搜索收益趋于平稳时，继续遍历图结构所带来的增益将微乎其微。</p><p>这种框架之所以强大，是因为它将终止与<em>可观察到的结果</em>直接联系起来，而不是与任意的固定限制联系起来。</p><p>采用这种智能提前终止技术的优势在于，HNSW 图探索过程在保持近乎完美的相对召回率的同时，往往会访问更少数量的图节点。</p><p>为了直观地说明这一点，我们可以在几个数据集（FinancialQA 和 Quora）和模型（JinaV3 和 E5-small）上绘制基于耐心的提前终止（标注为 <em><code>et=static</code></em>）与默认 HNSW 行为（标注为 <em><code>et=no</code></em>）的对比图。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd0d692b9beb476a/6a170ef4dc55debf0be00e97/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" alt="HNSW 的自适应提前终止 " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93509c251a1b641e/6a170ef6dc55dea2b3e00e9b/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" alt="HNSW 的自适应提前终止" /><h2><strong>静态阈值和 HNSW 动态</strong></h2><p>实际上，Elasticsearch 使用<strong>静态阈值</strong>来实现这一点。其中一个阈值指的是<strong>饱和阈值</strong>，即我们认为次优的饱和度比率。另一个阈值指的是，在队列达到次优饱和度的情况下，我们允许访问的连续图节点数，即<strong>耐心阈值</strong>。</p><p>当我们在 Elasticsearch 9.2 中引入这种提前终止策略时，我们决定选择保守的默认设置，以便在延迟和内存消耗方面仍能达到效果的同时，尽可能多地让系统召回。因此，我们将饱和阈值设为 100%，耐心阈值设为 KNN 查询中 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-top-level-parameters:~:text=search%20request%20size.-,num_candidates,-(Optional%2C%20integer)%20The"><em><code>num_candidates</code></em></a> 的（有界）30%。</p><p>在很多情况中，这些设置能取得不错的效果；然而，对于请求相同数量邻节点的两个查询而言，它们的收敛行为可能存在极大差异。有些查询会遇到密集的局部邻域，能迅速达到饱和状态；而有些查询则必须遍历漫长且稀疏的路径，才能找到具有竞争力的候选节点。事实证明，后一种情况最难以有效处理。</p><p>因此，我们有时会发现：</p><ul><li><p>简单查询的过度探索。</p></li><li><p>复杂查询的过早终止。</p></li></ul><p>因此，我们认为固定阈值编码了关于收敛的全局假设，而我们可以使 HNSW 更好地适应不同的动态。</p><h2><strong>实现 HNSW 的提前终止自适应</strong></h2><p>自适应提前终止从另一个角度解决了这个问题。该算法不是强制执行预定义的停止阈值，而是<strong>从搜索动态本身推断何时停止</strong>。</p><p>因此，我们不再比较连续两个候选节点间的队列饱和度比率，而是决定引入即时平滑发现率 （即最近一次访问 <em>i</em> 中，针对查询 <em>q</em> 新发现的邻近节点数量），同时结合图遍历过程中该发现率的滑动均值 和标准差（采用<a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm">韦尔福德算法</a>计算）。这些关于发现率的统计量按每个查询独立计算，从而可根据不同查询的特性动态调整其“耐心”阈值。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbfb1e123f1b026d/6a170ef7cf4f25d9bab2d216/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" alt="" /><p>先前静态设定的阈值将根据发现率统计数据实现自适应调整：饱和阈值调整为滚动均值加上标准差；同时，我们将耐心值设为与标准差呈反比变化的动态参数。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d4d91f464a9dc9d/6a170ef8d7c0223420de656a/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" alt="" /><p>提前退出的规则保持不变；当瞬时发现率低于自适应饱和阈值时，即判定达到饱和状态。如果在连续访问的候选节点数量超过自适应耐心值所设定的次数后，饱和状态仍持续存在，则停止对图的遍历。</p><p>如此一来，我们实现了搜索行为不再依赖于 KNN 查询中的 <em><code>num_candidates</code></em> 参数（该参数可能始终被设定为固定值或保留默认值，而与提前终止机制无关），同时能够根据每个查询和向量分布进行动态适配。</p><p>在 FinancialQA 和 Quora 数据集上，采用自适应策略（标记为 <em><code>et=adaptive</code></em>）时，每个访问节点的召回率相较于静态策略（ <em><code>et=static</code></em>）和默认 HNSW 行为（<em><code>et=no</code></em>）均有显著提升。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteab7ba53ae14da0e/6a170ef9961e69e072c4cfd5/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" alt=" 自适应策略和默认 HNSW 行为" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6fb9e672d3698200/6a170efb67045b7b2b45c2ab/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" alt="" /><p>在 Elasticsearch 9.3 中，HNSW 密集向量字段的自适应提前终止默认处于启用状态（最终可以通过<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">相同的索引级别设置</a>将其关闭）。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27b746cc1995e6b7/6a170efda29299de8ad010c6/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 向量搜索速度比 OpenSearch 快 8 倍]]></title>
    <description><![CDATA[探索 OpenSearch 与 Elasticsearch 的过滤向量搜索基准测试，以及为何向量搜索性能对上下文工程系统至关重要。]]></description>
    <content:encoded><![CDATA[<h2>为什么搜索速度对 AI 智能体和上下文工程很重要</h2><p>我们在 2000 万文档语料库上进行的基准测试显示，Elasticsearch 在过滤向量搜索方面的吞吐量比 OpenSearch 高达 8 倍，同时在我们测试的配置中也实现了更高的 Recall@100。上下文工程不仅仅依赖快速的向量检索。随着工作流的迭代，团队还需要强大的相关性控制（如混合搜索和过滤）、操作简便性和可预测的性能。但是，由于智能体通常会在每个请求中多次运行检索、推理、检索循环，因此检索延迟会成倍增加，所以这方面的改进会直接转化为更好的端到端响应能力和更低的成本。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9daec868eed84658/6a170bef6234e0c322db1a19/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" alt="OpenSearch 对比 Elasticsearch：过滤向量搜索基准测试吞吐量" /><p>对于上下文工程来说，检索不是一次性的步骤。智能体和应用程序会反复运行循环，例如检索→推理→检索，以完善查询、验证事实、组合基础上下文并完成任务。这种模式在智能体工作流和迭代检索增强生成 (RAG) 中很常见。由于每个用户请求可能会多次调用检索，这会增加响应延迟和/或增加基础设施成本。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt718c72fb858e4c98/6a170bf00e2e496e7241a132/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" alt="上下文工程将庞大的上下文池转换为有限的 LLM 上下文窗口。" /><h2>为什么向量搜索性能至关重要？</h2><p></p><p>想象一个购物助手回答以下问题：“我需要一个价格在 60 美元以下的随身背包，它可以容纳一台 15 英寸的笔记本电脑、防水，并且可以在周五之前送达。”</p><p>在生产环境中，助手很少只发出一个向量查询然后停止。它会运行一个检索循环以构建正确的上下文，并且每一步通常会受到过滤条件的限制，如可用性、地区、发货承诺、品牌规则和政策资格。</p><p><strong>第 1 步：解读意图，并转化为约束条件。</strong></p><p>智能体可将请求转化为结构化的过滤条件和语义查询，例如：</p><ul><li><p>过滤条件：有现货，可配送至用户邮编，可在周五前送达，价格低于 60 美元，有效上架</p></li><li><p>向量查询：“随身背包15英寸笔记本电脑防水”</p></li></ul><p><strong>步骤 2：检索候选对象，然后进行细化。</strong></p><p>它通常会重复检索，但会有所变化，以避免遗漏好的匹配结果：</p><ul><li><p>“旅行背包随身便携笔记本电脑保护套”</p></li><li><p>“15 英寸防水通勤背包”</p></li><li><p>“轻型机舱背包”</p></li></ul><p>每个查询都使用相同的资格过滤条件，因为检索无关或不可用的项目会造成上下文的浪费。</p><p><strong>步骤 3：展开以确认详细信息并降低风险。</strong></p><p>代理随后再次检索以验证影响最终答案的关键属性：</p><ul><li><p>材料与防水性表述</p></li><li><p>尺寸和笔记本隔层都适合</p></li><li><p>退货政策或保修限制</p></li><li><p>库存不足时的替代方案</p></li></ul><p>这就是多步上下文工程：检索、推理、检索、组合。</p><h2>延迟与召回为何对上下文工程至关重要</h2><p>这些交互可能涉及每个用户会话中数十次过滤的检索调用。这使得每次调用的延迟成为端到端响应时间的直接倍增因素，而低召回率则迫使进行额外的重试或导致智能体错过符合条件的项目，导致答案质量下降。</p><p>要点：在上下文工程系统中，过滤近似最近邻 (ANN) 并非一次单一查找。由于这是在约束条件下的重复操作，因此即使大型语言模型 (LLM) 是最明显的组件，向量搜索性能也会立即体现在延迟、吞吐量和成本上。</p><h2>基准测试</h2><h3>成果度</h3><p>在图表 2 中，每个点代表一个测试配置。最佳结果出现在左上方，这意味着更高的召回率和更低的延迟。Elasticsearch 的结果始终比 OpenSearch 更接近左上角，表明在相同的工作负载配置下，Elasticsearch 具有更好的速度和准确率。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb562b30a600cc8e1/6a170bf2cf4f253582b2d1b2/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" alt=" 图表 2：召回率与平均延迟（重评分 1）。" /><h4>一些关键见解</h4><ul><li><p><code>s_n_r_value</code>: <code>size_numCandidates_rescoreOversample</code> 的简写（在这些测试中，k 和 numCandidates 设置为等于 numCandidates），例如，<code>100_500_1</code> 表示 size=100、numCandidates=500 和 k=500，重新评分过采样=1。</p></li><li><p>召回率：该配置的测量召回率@100</p></li><li><p>平均延迟（毫秒）：每次查询的平均端到端延迟</p></li><li><p>吞吐量：每秒查询次数</p></li><li><p>召回率 (%)：Elasticsearch 相较于 OpenSearch 的相对召回率提升 (Elasticsearch - OpenSearch) / OpenSearch</p></li><li><p>延迟 Xs：OpenSearch 平均延迟除以 Elasticsearch 平均延迟</p></li><li><p>吞吐量 Xs：Elasticsearch 吞吐量除以 OpenSearch 吞吐量</p></li></ul><p>引擎</p><p>`s_n_r_value`</p><p>召回</p><p>平均延迟（ms）</p><p>吞吐量</p><p>召回率%</p><p>延迟 Xs</p><p>吞吐量 Xs</p><p>Elasticsearch</p><p>100_250_1</p><p>0.7704</p><p>25</p><p>534.75</p><p>9.70%</p><p>2.28</p><p>1.91</p><p>OpenSearch</p><p>100_250_1</p><p>0.7023</p><p>57.08</p><p>279.58</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_500_1</p><p>0.8577</p><p>25.42</p><p>524.14</p><p>7.20%</p><p>2.4</p><p>2</p><p>OpenSearch</p><p>100_500_1</p><p>0.8001</p><p>60.9</p><p>262.12</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_750_1</p><p>0.8947</p><p>29.67</p><p>528.09</p><p>5.72%</p><p>2.25</p><p>2.21</p><p>OpenSearch</p><p>100_750_1</p><p>0.8463</p><p>66.76</p><p>239.11</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1000_1</p><p>0.9156</p><p>29.65</p><p>534.5</p><p>4.66%</p><p>2.46</p><p>2.44</p><p>OpenSearch</p><p>100_1000_1</p><p>0.8748</p><p>72.88</p><p>219.01</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1500_1</p><p>0.9386</p><p>31.84</p><p>497.3</p><p>3.38%</p><p>2.71</p><p>2.68</p><p>OpenSearch</p><p>100_1500_1</p><p>0.9079</p><p>86.16</p><p>185.4</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2000_1</p><p>0.9507</p><p>34.69</p><p>457.2</p><p>2.57%</p><p>2.98</p><p>2.96</p><p>OpenSearch</p><p>100_2000_1</p><p>0.9269</p><p>103.36</p><p>154.55</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2500_1</p><p>0.9582</p><p>37.9</p><p>418.43</p><p>1.99%</p><p>3.28</p><p>3.26</p><p>OpenSearch</p><p>100_2500_1</p><p>0.9395</p><p>124.29</p><p>128.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_3000_1</p><p>0.9636</p><p>41.86</p><p>379.4</p><p>1.62%</p><p>3.46</p><p>3.44</p><p>OpenSearch</p><p>100_3000_1</p><p>0.9482</p><p>144.67</p><p>110.34</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_4000_1</p><p>0.9705</p><p>50.28</p><p>316.21</p><p>1.06%</p><p>3.87</p><p>3.85</p><p>OpenSearch</p><p>100_4000_1</p><p>0.9603</p><p>194.36</p><p>82.22</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_5000_1</p><p>0.9749</p><p>58.77</p><p>270.91</p><p>0.73%</p><p>4.43</p><p>4.41</p><p>OpenSearch</p><p>100_5000_1</p><p>0.9678</p><p>260.33</p><p>61.38</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_6000_1</p><p>0.9781</p><p>66.75</p><p>238.59</p><p>0.52%</p><p>4.91</p><p>4.89</p><p>OpenSearch</p><p>100_6000_1</p><p>0.973</p><p>327.44</p><p>48.81</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_7000_1</p><p>0.9804</p><p>74.64</p><p>213.49</p><p>0.38%</p><p>5.28</p><p>5.27</p><p>OpenSearch</p><p>100_7000_1</p><p>0.9767</p><p>394.24</p><p>40.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_8000_1</p><p>0.9823</p><p>82.28</p><p>193.59</p><p>0.27%</p><p>6.86</p><p>6.83</p><p>OpenSearch</p><p>100_8000_1</p><p>0.9797</p><p>564.14</p><p>28.33</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_9000_1</p><p>0.9837</p><p>90.08</p><p>176.96</p><p>0.16%</p><p>7.63</p><p>7.61</p><p>OpenSearch</p><p>100_9000_1</p><p>0.9821</p><p>687.25</p><p>23.25</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_10000_1</p><p>0.9848</p><p>97.64</p><p>163.31</p><p>0.08%</p><p>8.38</p><p>8.36</p><p>OpenSearch</p><p>100_10000_1</p><p>0.984</p><p>818.64</p><p>19.53</p><p></p><p></p><p></p><p>例如，在 <code>100_9000_1</code> 处，OpenSearch 每次检索平均为 687 毫秒， Elasticsearch 为 90 毫秒，而在 10 步检索循环中，等待时间约为 10 x (687 - 90) = 6 秒。 </p><p>查看<a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search/jingra/results/20260220">完整结果</a>。</p><h3>方法</h3><p>我们使用 Python 发送查询并跟踪响应时间及其他统计数据，向引擎发送了以下查询。请记住，任何向量搜索引擎的性能取决于您如何调整其核心参数：考虑多少个候选项，重新评分的程度，以及返回多少上下文。这些设置直接影响召回率（找到正确答案的可能性）和延迟（获得结果的速度）。</p><p>在我们的基准测试中，我们使用了通常在智能体检索循环中调整的候选对象、重新评分和结果大小设置，并测量了 Elasticsearch 在该工作负载下的表现。然后我们以相同的设置运行了 OpenSearch 作为参考。</p><p>OpenSearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;": {
        "vector": [...],
        "k": &lt;NUMBER_OF_CANDIDATES&gt;,
        "method_parameters": {
          "ef_search": &lt;NUMBER_OF_CANDIDATES&gt;
        },
        "rescore": {
          "oversample_factor": &lt;OVERSAMPLE&gt;
        },
        "filter": {
          &lt;SOME_FILTER&gt;
        }
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>：返回给客户端的命中次数。在这个基准测试中，计算 Recall@100 的结果大小为 100。</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>：最近邻候选对象的数量。</p></li><li><p><code>"ef_search": &lt;NUMBER_OF_CANDIDATES&gt;</code>：要检查的向量数量。</p></li><li><p><code>"oversample_factor": &lt;OVERSAMPLE&gt;</code>：在重新评分之前检索了多少个候选向量。</p></li></ul><p>Elasticsearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "field": "&lt;DENSE_VECTOR_FIELD_NAME&gt;",
      "query_vector": [...],
      "k": &lt;NUMBER_OF_CANDIDATES&gt;,
      "num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;,
      "rescore_vector": {
        "oversample": &lt;OVERSAMPLE&gt;
      },
      "filter": {
        &lt;SOME_FILTER&gt;
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>：返回给客户端的命中次数。在这个基准测试中，计算 Recall@100 的结果大小为 100。</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>：从每个分片返回的最近邻数量。</p></li><li><p><code>"num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;</code>：进行 <code>knn</code> 搜索时每个分片要考虑的最近邻候选数目。</p></li><li><p><code>"oversample": &lt;OVERSAMPLE&gt;</code>：在重新评分之前检索了多少个候选向量。</p></li></ul><p>示例</p><p><code>Knn</code> 查询, (<code>100_500_1</code>)，将如下所示：</p><p>OpenSearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "search_catalog_embedding": {
        "vector": [...],
        "k": 500,
        "method_parameters": {
          "ef_search": 500
        },
        "rescore": {
          "oversample_factor": 1
        },
        "filter": {
          "term": {
            "valid": true
          }
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>Elasticsearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "field": "search_catalog_embedding",
      "query_vector": [...],
      "k": 500,
      "num_candidates": 500,
      "rescore_vector": {
        "oversample": 1
      },
      "filter": {
        "term": {
          "valid": true
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>完整配置、Terraform 脚本、Kubernetes 清单和基准测试代码均可在此<a href="https://github.com/elastic/competitive-benchmarking-studies">存储库</a>的 <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search">es-9.3-vs-os-3.5-vector-search</a> 文件夹中找到。</p><h3>集群设置</h3><p>我们在六台 e2-standard-16 云服务器上运行了测试，每台服务器配备 16 个 vCPU 和 64 GB 内存。在每台服务器上，我们为每个运行搜索引擎节点的 Kubernetes pod 分配了 15 个 vCPU 和 56 GB RAM，其中 28 GB 保留给 JVM 堆。</p><p>这些集群运行 Elasticsearch 9.3.0 和 OpenSearch 3.5.0 (Lucene 10.3.2)。由于在此基准测试中两个系统使用相同的 Lucene 版本，我们观察到的吞吐量和延迟差异不能单独归因于 Lucene，而是反映了每个引擎如何集成和执行过滤后的 k 最近邻 (kNN) 检索和重新评分的差异。我们使用了一个单一索引，包含三个主分片和一个副本（因此总共 6 个分片，每个节点 1 个分片）。</p><p>我们还在同一区域使用了一台独立服务器运行基准客户端，并收集时序统计数据。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c7bdd567395d5b7/6a170bf3c1e8a56ee2f882f6/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" alt="Elasticsearch 和 OpenSearch 基准测试的集群设置" /><h3>该数据集</h3><p></p><p>对于这个基准测试，我们使用了一个大规模电子商务风格的目录嵌入数据集，包含 2000 万份文档，旨在反映实际的大规模筛选后向量检索的扩展能力。</p><p></p><p>每份文件代表一个目录项目，包括：</p><p></p><ul><li><p>一种用于近似 kNN 检索的 128 维稠密向量嵌入。</p></li><li><p>结构化元数据字段用于筛选（例如，项目有效性和可用性，以及其他目录限制条件），从而支持常见的生产环境模式，即仅在符合条件的子集内检索最近邻。</p></li></ul><p></p><p>我们之所以选择这个数据集，是因为它捕捉到了我们在生产中看到的智能体和 RAG 型系统所面临的核心性能挑战：仅有矢量相似性是不够的，检索经常受到筛选条件的限制，系统必须在这些限制条件下保持较高的召回率和较低的延迟。与较小的 QA 风格数据集相比，2000 万文档的语料库更能反映筛选后 ANN 系统在实践中面临的扩展和候选压力。</p><h2>结论</h2><p>在现代 AI 架构中，尤其是在那些围绕上下文工程构建的架构中，向量搜索速度并非一个微不足道的实现细节。它是一个倍增因素。当智能体和工作流迭代检索→推理→检索时，检索性能直接影响端到端延迟、吞吐量以及输入到模型中的上下文质量。</p><p>在我们的基准测试中，与 OpenSearch 相比，当 Elasticsearch 在正确性取决于检索正确文档而不仅仅是相似向量的情况下，始终能以更低的延迟提供更高的召回率。在受控数据集上，差异是显而易见的，而在生产中，这些收益会在大量检索调用中累积，从而提高响应速度、增加容量裕度并降低基础设施成本。</p><h3>延展阅读</h3><ol><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">什么是上下文工程？</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">混合搜索和上下文工程的演进</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">相关性在 AI 智能体的上下文工程中的影响</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b38e5114bbf098c/6a170bf560084b3a6c3c459d/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 NVIDIA cuVS 将 Elasticsearch 中的向量索引速度提升高达 12 倍：GPU 加速（第二章）]]></title>
    <description><![CDATA[了解 Elasticsearch 如何借助 GPU 加速的向量索引和 NVIDIA cuVS，实现近 12 倍的索引吞吐量提升。]]></description>
    <content:encoded><![CDATA[<p>今年早些时候，Elastic 宣布与 NVIDIA <a href="https://ir.elastic.co/news/news-details/2025/Elastic-Brings-Enterprise-Data-to-NVIDIA-AI-Factories/default.aspx">合作</a>，为 Elasticsearch 引入 GPU 加速功能，并与 <a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a> 集成 — 相关详情可参阅 <a href="https://www.nvidia.com/en-us/on-demand/session/gtc25-S71286/">NVIDIA GTC 大会</a>的相关会议以及多篇<a href="https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia">博文</a>。本文主要介绍我们与 NVIDIA 向量搜索团队在联合工程方面的最新进展。</p><h2>回顾</h2><p>先简单回顾一下最新动态。Elasticsearch 现已确立其作为强大向量数据库的地位，在大规模相似性搜索方面提供了丰富的功能和强劲的性能。凭借标量量化、Better Binary Quantization (<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>)、<a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">SIMD</a> 向量运算以及 <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a> 等在磁盘利用方面更高效的算法，Elasticsearch 已经为管理向量工作负载提供了高效而灵活的多种选项。</p><p>通过将 NVIDIA cuVS 集成为可调用的向量搜索模块，我们希望大幅提升向量索引的性能和效率，从而更好地支撑大规模向量工作负载。</p><h2>挑战</h2><p>构建高性能向量数据库的最大挑战之一，就是构建向量索引，即 <a href="https://arxiv.org/abs/1603.09320">HNSW</a> 图。随着每个向量都要与大量其他向量进行比对，索引构建很快就会被数以百万乃至数十亿次的算术运算所主导。此外，压缩、合并等索引生命周期操作还会进一步增加索引的整体计算开销。随着数据量和相关向量嵌入呈指数级增长，专为大规模并行和高吞吐量数值运算而设计的加速计算 GPU 非常适合处理这些工作负载。</p><h2>进入 Elasticsearch-GPU 插件</h2><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>是一个开源的 CUDA-X 库，用于 GPU 加速的向量搜索和数据集群，能够为 AI 和推荐工作负载快速构建索引和嵌入检索。</p><p>Elasticsearch 通过 <a href="https://mvnrepository.com/artifact/com.nvidia.cuvs/cuvs-java">cuvs-java</a> 使用 cuVS，这是一个由社区开发并由 NVIDIA 维护的开源库。cuvs-java 库十分轻量，基于 <a href="https://docs.rapids.ai/api/cuvs/nightly/c_api/">cuVS C API</a> 构建，并借助 <a href="https://openjdk.org/projects/panama/">Panama</a> 外部函数接口，以符合 Java 习惯用法的方式暴露 cuVS 功能，同时兼具现代性和高性能。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fd7361099da05a/6a17e920be608670af00477f/5f6daa1eb07f704a6707d9e6b7ccb81d0abaa8c9-566x419.png" alt="Elasticsearch 如何与 NVIDIA cuVS 协同工作，并支持 CPU 与 GPU 两种索引方式" /><p>cuvs-java 库被集成到<a href="https://github.com/elastic/elasticsearch/pull/135545">一个新的 Elasticsearch 插件</a>中；因此，GPU 上的向量索引可在同一 Elasticsearch 节点和同一进程内完成，无需部署任何外部组件或额外硬件。在索引构建过程中，如果已安装 cuVS 库且存在已正确配置的 GPU，Elasticsearch 会利用 GPU 加速向量索引过程。向量会被传递给 GPU，由 GPU 构建 <a href="https://arxiv.org/abs/2308.15136">CAGRA</a> 图。随后将该图转换为 HNSW 格式，使其能够立即在 CPU 上用于向量搜索。构建完成的图，其最终格式与在 CPU 上构建的图完全一致；这使得在底层硬件支持的情况下，Elasticsearch 可以利用 GPU 实现高吞吐量的向量索引，同时释放 CPU 算力，用于并发搜索、数据处理等其他任务。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt485f55f29d6df5c4/6a17e922be6086dcf3004785/3ea255bd9bfd7983f78143c5eba999d2149d72be-671x356.png" alt="" /><h2>索引构建加速</h2><p>作为将 GPU 加速集成到 Elasticsearch 的一部分，对 cuvs-java 进行了多项增强，重点是高效的数据输入/输出和函数调用。一项关键增强是使用 <a href="https://github.com/rapidsai/cuvs/blob/2cf5fa7666d703dccbe655f8214656b0952bb69b/java/cuvs-java/src/main/java/com/nvidia/cuvs/CuVSMatrix.java">cuVSMatrix</a> 对向量进行透明建模，无论它们位于 Java 堆中、堆外还是 GPU 内存中。这使数据可以在内存与 GPU 之间高效传输，避免对可能多达数十亿个向量进行不必要的复制。</p><p>由于这种底层的零拷贝抽象，数据传输到 GPU 内存以及从中检索图时都可以直接完成。在索引过程中，向量首先缓存在 Java 堆内存中，然后发送到 GPU，以构建 CAGRA 图。随后，从 GPU 中取回该图，将其转换为 HNSW 格式，并持久化到磁盘。</p><p>在合并时，向量已经存储在磁盘上，完全绕过了 Java 堆。索引文件采用内存映射，数据直接传输到 GPU 内存中。该设计还能轻松适应不同的位宽，如 float32 或 int8，并自然扩展到其他量化方案。</p><h2>话不多说，那它的实际表现如何呢？</h2><p>在我们探讨数字之前，了解一些背景信息会有所帮助。在索引期间，Elasticsearch 中的分段合并通常在后台自动运行，这会导致在隔离环境中进行基准测试变得十分困难。为了获得可重复的结果，我们使用了强制合并来在受控实验中明确触发分段合并。由于强制合并与后台合并执行相同的底层合并操作，因此其性能可作为预期改进的有用指标，尽管在实际索引工作负载中，具体收获可能会有所不同。</p><p>现在让我们来探讨数字。</p><p>我们的初步基准测试结果非常令人鼓舞。我们在 AWS <code>g6.4xlarge</code> 实例上运行了基准测试，该实例具有本地连接的 NVMe 存储。我们将单个 Elasticsearch 节点配置为使用默认的最佳索引线程数（8 个，每个物理核心一个），并关闭<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge">合并限速功能</a>（在使用快速 NVMe 磁盘时，这一功能的适用性较低）。</p><p>对于数据集，我们使用了 <a href="https://github.com/elastic/rally-tracks/blob/master/openai_vector/README.md">OpenAI Rally 向量赛道</a>中的 260 万个、每个具有 1,536 维的向量，将其编码为 <a href="https://github.com/elastic/elasticsearch/pull/137072">base64 字符串</a>，并以 float32 <em>hnsw</em> 结构进行索引。在所有场景中，构建的图都能达到最高约 95% 的召回率。以下是我们的发现：</p><ul><li><p><strong>索引吞吐量：</strong>通过在内存缓冲区刷新期间将图构建移交给 GPU 处理，我们将吞吐量提高了约 12 倍。</p></li><li><p><strong>强制合并：</strong>索引完成后，GPU 继续加速分段合并，将强制合并阶段加快约 7 倍。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfea4ee13b5a3b10d/6a17e923e9ea879c6aa9c616/f60ea9ee5996e456f393ffd195ee7eada6e5a7c2-948x387.png" alt="" /><ul><li><p><strong>CPU 使用率：</strong>将图构建任务分流到 GPU，可显著降低 CPU 的平均和峰值利用率。以下图表展示了索引和合并期间的 CPU 使用情况，凸显了在 GPU 上运行这些操作时 CPU 使用率的显著降低。GPU 索引期间降低 CPU 使用率，可释放 CPU 周期并重新分配，从而提升搜索性能。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80ff9c53f9b6884a/6a17e925445de9ee4b4d0187/5e680a5fc41700a877f3d8b2e5ce18ebd3f37a0b-1600x562.png" alt="" /><ul><li><p><strong>召回率：</strong>在 CPU 与 GPU 的运行结果中，准确性几乎一致，而 GPU 所构建的图在召回率方面略胜一筹。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5cbe084eca27b8e4/6a17e926faa913317093c8b7/48a2b7758606bd321712b7d8378cd2640e652a4e-1384x544.png" alt="" /><h2>再从价格这个维度来进行比较</h2><p>前面的对比特意选用了相同的硬件配置，唯一的区别只是索引时是否启用 GPU。这种设置有助于单独考察计算性能的影响，不过也可以从成本角度来进行对比。</p><p>在与 GPU 加速配置大致相同的按小时费用下，可以搭建一套仅使用 CPU 的环境，其 CPU 和内存资源大约是前者的两倍：32 个 vCPU（AMD EPYC）和 64 GB RAM，因而可将索引线程数量增加到 16</p><p>为了保持比较的公平和一致性，我们在 AWS g6.8xlarge 实例上运行了这个仅 CPU 的实验，并且明确禁用了 GPU。这使我们能够在评估 GPU 加速与仅 CPU 索引的成本-性能权衡时，保持所有其他硬件特性不变。</p><p>正如您所预期的那样，更强大的 CPU 实例与上述部分的基准测试相比，性能确实有所提高。然而，将这一性能更强的 CPU 实例与最初的 GPU 加速结果进行对比后可以看到，GPU 依然带来显著性能提升：索引吞吐量提高<strong>约 5 倍</strong>，强制合并阶段加速<strong>约 6 倍</strong>，同时构建的图其召回率最高可达 <strong>95%</strong>。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94b5eb6f95ba307d/6a17e928abe0f255d4dfea35/8ffa58cae3ad175ef2932a351aeef4c34a1407b9-948x394.png" alt="" /><h2>结论</h2><p>在端到端场景中，使用 NVIDIA cuVS 进行的 GPU 加速使索引吞吐量提高了近 12 倍，将强制合并延迟降低到原来的 1/7，同时显著降低了 CPU 利用率。这表明向量索引和合并工作负载从 GPU 加速中受益显著。在成本调整后的对比中，GPU 加速依然带来显著的性能提升：索引吞吐量约提升 5 倍，强制合并操作的速度提升约 6 倍。</p><p>GPU 加速的向量索引目前计划在 Elasticsearch 9.3 的技术预览版中推出，该版本计划于 2026 年初发布。</p><p>敬请关注更多内容。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik,Corey Nolet,Manas Singh,Mithun Radhakrishnan,Mayya Sharipova,Lorenzo Dematte,Ben Frederickson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1248d51633bd75d9/6a17e92ae9ea8714b3a9c61a/08f7469a4daaf67b7c5999585aae179b6680c78d-896x746.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用 Elasticsearch 和 SigLIP-2 对山峰进行多模式搜索 ]]></title>
    <description><![CDATA[了解如何使用 SigLIP-2 嵌入和 Elasticsearch kNN 向量搜索实现文本到图像和图像到图像的多模态搜索。项目重点：寻找珠峰徒步旅行中拍摄的阿玛达布拉姆峰照片。]]></description>
    <content:encoded><![CDATA[<p>您是否曾想过按含义搜索相册？试着询问 "给我看我穿着蓝色夹克坐在长椅上的照片"、"给我看珠穆朗玛峰的照片 "或 "清酒和寿司"。喝杯咖啡（或您最喜欢的饮料），继续阅读。在本博客中，我们将向您展示如何构建多模态混合搜索应用程序。多模态是指应用程序可以理解和搜索不同类型的输入（文本、图像和音频），而不仅仅是文字。混合式意味着它结合了关键词匹配、kNN 向量搜索和地理围栏等技术，以提供更清晰的结果。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="来自珠穆朗玛峰徒步旅行的不同山峰照片库。" /><p>为此，我们使用谷歌的 SigLIP-2 为图像和文本生成矢量嵌入，并将其存储在 Elasticsearch 矢量数据库中。在查询时，我们将搜索输入（文本或图像）转换为嵌入，并运行快速的 kNN 向量搜索来检索结果。这种设置可实现高效的文本到图像和图像到图像搜索。Streamlit 用户界面为我们提供了一个前端，不仅可以进行基于文本的搜索，从相册中查找并查看匹配的照片，还可以从上传的图片中识别山峰，并查看相册中该山峰的其他照片，从而使该项目栩栩如生。我们还介绍了为提高搜索准确性而采取的措施，以及实用技巧和窍门。为便于进一步探索，我们提供了<a href="https://github.com/navneet83/multimodal-mountain-peak-search">GitHub 存储库</a>和<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">Colab 笔记本</a>。</p><h2>如何开始</h2><p>这篇博文的灵感来自于一个 10 岁的孩子，他让我给他们看我在珠峰大本营徒步旅行时拍摄的阿玛达布拉姆山的所有照片。在翻阅相册时，我还被要求辨认其他几座山峰，其中一些我还叫不出名字。</p><p>这让我想到，这可以成为一个有趣的计算机视觉项目。我们的目标</p><ul><li><p>按名称查找山峰图片</p></li><li><p>从图片中猜测山峰名称，并在相册中找到类似的山峰</p></li><li><p>让概念查询发挥作用<em>（人</em>、<em>河流</em>、<em>祈祷旗</em> <em>等）</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="阿玛-达布拉姆山 " /><h2>组建梦之队：SigLIP-2、Elasticsearch&amp; Streamlit</h2><p>很快我们就发现，要想实现这一目标，我们需要将文字（"阿玛达布拉姆"）和图像（我相册中的照片）都转化为可以进行有意义比较的矢量，即在同一个矢量空间中。一旦我们做到了这一点，搜索就只是 "寻找最近的邻居"。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2、Elasticsearch&amp; Streamlit--梦之队。" /><p>为了生成图像嵌入，我们使用了多语言<a href="https://huggingface.co/blog/vlms-2025"> 视觉语言编码器</a>，因此山峰的照片和 "Ama Dablam "这样的短语会出现在同一个向量空间中。</p><p>谷歌最近发布的<a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a> 在这方面非常适合。它可以在没有特定任务训练的情况下生成嵌入式（<strong>零镜头</strong>设置），并能很好地适用于我们的使用案例：未标记的照片和具有不同名称和语言的山峰。由于它是针对文本与图像匹配进行训练的，因此即使查询语言或拼写不同，徒步旅行中的山峰图片和简短的文字提示最终也能接近嵌入。</p><p>SigLIP-2 在质量与速度之间实现了很好的平衡，支持多种输入分辨率，并可在 CPU 和 GPU 上运行。SigLIP-2 在设计上比以前的型号（如最初的 CLIP）更适合户外拍摄。在我们的测试中，SigLIP-2 始终能生成可靠的结果。此外，它还得到了很好的支持，因此是本项目的不二之选。</p><p>接下来，我们需要一个向量数据库来存储嵌入和强力搜索。它不仅应支持对图像嵌入进行余弦 kNN 搜索，还应在单个查询中应用地理围栏和文本过滤器。Elasticsearch 在这方面非常适合：它能很好地处理向量（在 dense_vector 字段上使用 HNSW kNN），支持结合文本、向量和地理查询的混合搜索，并提供开箱即用的过滤和排序功能。它还可以横向扩展，因此很容易从少量照片扩展到数千张照片。<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python"></a>最后，我们需要一个轻量级前端，以便输入搜索查询并查看结果。对于基于 Python 的快速演示，Streamlit 非常适合。它提供了我们所需的基本功能--文件上传、响应式图像网格以及用于排序和地理围栏的下拉菜单。它很容易克隆并在本地运行，也可以在 Colab 笔记本中使用。</p><h2>实施</h2><h3>Elasticsearch 索引设计和索引策略</h3><p>我们将在这个项目中使用两个索引：<code>peaks_catalog</code> 和<code>photos</code> 。</p><h4>峰值_目录索引</h4><p>该索引是珠峰大本营徒步旅行期间可看到的著名山峰的简明目录。该索引中的每份文件都对应一座山峰，如珠穆朗玛峰。对于每个山峰文档，我们都会存储名称/别名、可选的经纬度坐标以及由 SigLIP-2 文本提示（+ 可选的参考图片）混合而成的单一原型向量。</p><p><strong>索引映射：</strong></p><p>现场</p><p>类型</p><p>示例</p><p>目的/说明</p><p>矢量/索引</p><p>本我</p><p>关键词</p><p>阿玛-达布拉姆</p><p>稳定的弹头/ID</p><p>-</p><p>姓名</p><p>文本 + 关键字子字段</p><p>["Ama Dablam","Amadablam"]</p><p>别名/多语言名称；names.raw 用于精确筛选</p><p>-</p><p>纬纶</p><p>地理点</p><p>{"lat":27.8617,"lon":86.8614}</p><p>以经纬度组合形式显示的山顶 GPS 坐标（可选）</p><p>-</p><p>海拔_m</p><p>整数</p><p>6812</p><p>海拔（可选）</p><p>-</p><p>嵌入文本</p><p>dense_vector</p><p>768</p><p>该山峰的混合原型（提示和可选的 1-3 幅参考图片</p><p>index:true, similarity:"cosine", index_options：{type:"hnsw", m:16, ef_construction:128}</p><p>该索引主要用于图像到图像的搜索，例如从图像中识别山峰。我们还使用该索引来增强文本到图片的搜索结果。</p><p>总之，<code>peaks_catalog</code> 将问题""这是什么山？" "转化为一个重点突出的 "最近邻问题"，有效地将概念理解与图像数据的复杂性分离开来。</p><p><strong>peaks_catalog 索引的索引策略： </strong>首先，我们创建了一份在 EBC 徒步旅行中可见的最突出山峰的列表。对于每个山峰，我们都会在<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">yaml 文件</a>中存储其地理位置、名称、同义词和海拔高度。下一步是<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351"> 生成</a> 每个峰值的 嵌入 值，并将其存储在<code>text_embed</code> 字段中。为了生成稳健的嵌入，我们使用了以下技术：</p><ul><li><p>创建文本原型：</p><ul><li><p>山峰名称</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">提示组合</a>（使用多个不同的提示来尝试回答同一个问题），例如</p><ul><li><p>"尼泊尔喜马拉雅山脉山峰的自然照片{name} "</p></li><li><p>"{name} 昆布地区的地标性山峰，高山景观"</p></li><li><p>"{name} 山顶，积雪，岩石山脊线"</p></li></ul></li><li><p>可选的<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">反概念</a>（告诉 SigLIP-2 什么不能匹配）：为 "绘画、插图、海报、地图、徽标 "减去一个小矢量，这样我们就偏向于真实照片。</p></li></ul></li><li><p>如果提供了峰值的参考图像，可选择<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">创建图像原型</a>。</p></li></ul><p>然后，我们<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">混合文本和图像原型</a>，生成最终的嵌入。最后，文件将被<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">索引到</a>所有必填字段：</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (&lt;= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found &gt; max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p><code>peaks_catalog</code> 索引中的文件样本：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="来自 Elasticsearch 中 peaks_catalog 索引的示例文档。" /><h4>照片索引</h4><p>该主索引存储相册中所有照片的详细信息。每份文档代表一张照片，包含以下信息：</p><ul><li><p>相册中照片的相对路径。可用于查看匹配图像或在搜索用户界面中加载图像。</p></li><li><p>图片的 GPS 和时间信息。</p></li><li><p>SigLIP-2 生成的图像编码密集矢量。</p></li><li><p><code>predicted_peaks</code> 可让我们根据峰名进行筛选。<strong>索引映射</strong></p></li></ul><p>现场</p><p>类型</p><p>示例</p><p>目的/说明</p><p>矢量/索引</p><p>路径</p><p>关键词</p><p>data/images/IMG_1234.HEIC</p><p>用户界面如何打开缩略图/全图</p><p>-</p><p>剪贴图片</p><p>dense_vector</p><p>768</p><p>SigLIP-2 图像嵌入</p><p>index:true, similarity:"cosine", index_options：{type:"hnsw", m:16, ef_construction:128}</p><p>预测峰值</p><p>关键词</p><p>["ama-dablam","pumori"]</p><p>索引时的 Top-K 猜想（廉价用户体验过滤器/面）</p><p>-</p><p>全球定位系统</p><p>地理点</p><p>{"lat":27.96,"lon":86.83}</p><p>启用地理筛选器</p><p>-</p><p>拍摄时间</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>捕捉时间：排序/过滤</p><p>-</p><p><strong>照片索引的索引策略： </strong>对于相册中的每张照片，我们会采取以下措施：
<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">从图像元数据中</a>提取图像<code>shot_time</code> 和<code>gps</code> 信息。</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">SigLIP-2 图像嵌入</a>：通过模型传递图像并对向量进行 L2 归一化。将嵌入内容存储在<code>clip_image</code> 字段中。</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">预测峰值</a>并将其存储在<code>predicted_peaks</code> 字段中。为此，我们首先获取上一步生成的照片图像向量，然后针对<code>peaks_catalog</code> 索引中的 text_embed 字段快速运行 kNN 搜索。我们保留顶部的 3-4 个山峰，忽略其余的。</p></li><li><p>我们通过对图片名称和路径进行<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">散列</a>计算<code>_id</code> 字段。这可以确保我们在多次运行后不会出现重复。</p></li></ul><p>一旦我们确定了照片的所有字段，就会使用<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530"> 批量 索引对照片文件进行</a> 批量 索引：</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) &gt;= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")<p>照片索引中的样本文件：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="来自 Elasticsearch 照片索引的样本文件。" /><p>总之，照片索引是相册中所有照片的快速、可过滤、kNN 就绪存储。它的映射结构非常简单，只需足够的结构就能快速检索、清晰显示，并按空间和时间对结果进行切分。该索引可同时满足这两种搜索用途。创建这两个索引的 Python 脚本可在<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">此处</a>找到。</p><p>下面的 Kibana 地图可视化将相册中的文档显示为绿色圆点，将<code>peaks_catalog</code> 索引中的山峰显示为红色三角形，其中绿色圆点与珠峰大本营徒步路线非常吻合。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Kibana 地图可视化显示相册中的文件为绿色圆点，peaks_catalog 索引中的山峰为红色三角形，其中绿色圆点与珠峰大本营徒步路线非常吻合。" /><h2>搜索用例</h2><p><strong>按名称搜索（文本到图像）：</strong>该功能可让用户使用文本查询查找山峰照片（甚至是 "祈祷旗 "等抽象概念）。为此，使用 SigLIP-2 将文本输入<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">转换为文本向量</a>。为了生成稳健的文本向量，我们采用了与在<code>peaks_catalog</code> 索引中创建文本嵌入相同的策略：<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> 将</a> 文本输入与小型 提示集合<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100"> 相结合</a> ，减去次要的<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> 反概念向量</a> ，并应用<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> L2 归一化 生成最终的查询向量。</a>然后在<code>photos.clip_image</code> 字段上执行 kNN<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">查询</a>，根据余弦相似度检索匹配度最高的峰值，从而找到最接近的图像。作为查询的一部分，还可选择应用地理和日期<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">筛选器</a>和/或<code>photos.predicted_peaks</code> 术语筛选器来提高搜索结果的相关性（见下文查询示例）。这有助于排除在徒步过程中看不到的相似山峰。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="如何在 Elasticsearch 中通过名称（文本到图像）进行多模式搜索。" /><p><strong>带有地理过滤器的 Elasticsearch 查询：</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>按图像搜索（图像到图像）：</strong>通过该功能，我们可以识别照片中的某座山，并在相册中查找该座山的其他图像。图像上传后，将由 SigLIP-2 图像编码器处理，生成<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">图像矢量</a>。然后在<code>peaks_catalog.text_embed</code> 字段上进行<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">kNN 搜索</a>，以确定最匹配的峰值名称。随后，根据这些匹配的山峰名称<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257"> 生成</a> 一个 文本向量 ，并在照片索引中进行另一次<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263"> kNN 搜索</a> ，以找到相应的照片。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Elasticsearch 如何通过图像进行多模式搜索（图像到图像）。" /><p><strong>Elasticsearch 查询：</strong></p><p>第 1 步：找到匹配的山峰名称</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>第 2 步：在<code>photos</code> 索引上进行搜索，找到匹配的图片（与文本到图片搜索用例中的查询相同）：</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>流光 UI</h2><p>为了将所有功能整合在一起，我们创建了一个简单的 Streamlit 用户界面，让我们可以同时执行两种搜索用例。左侧栏显示可滚动的峰值列表（从<code>photos.predicted_peaks</code> 中汇总），并带有复选框和小地图/地理过滤器。顶部有一个<strong>按姓名搜索</strong>框和一个<strong>从照片</strong>上传识别按钮。中心窗格采用响应式缩略图网格，显示 kNN 分数、预测峰值徽章和捕获时间。每张图片都有一个<strong>查看图片</strong>按钮，用于全分辨率预览。</p><p><strong>通过上传图片进行搜索：</strong>我们会预测峰值，并从相册中找到匹配的峰值。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="这是一个简单的流光式用户界面，可通过文本到图像和图像到图像的多模态搜索方式搜索阿玛达布拉姆山峰。" /><p><strong>文本搜索</strong>从文本中查找相册中匹配的峰值</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="如何在山峰库中通过文本搜索珠穆朗玛峰。" /><h2>结论</h2><p><em>我们能看看 </em><em><strong>阿玛-达布拉姆</strong></em><em> 的照片吗？</em>变成了一个可运行的小型<strong>多模态搜索</strong>系统。我们采集了原始的徒步旅行照片，将其转化为<strong>SigLIP-2 嵌入</strong>，并使用<strong>Elasticsearch</strong>对向量进行快速的<strong>kNN</strong>处理，再加上简单的地理/时间过滤器，根据<em>意义</em>浮现出正确的图像。在此过程中，我们将两个索引的关注点分开：一个是混合原型的小<code>peaks_catalog</code> （用于识别），另一个是图像向量和 EXIF 的可扩展<code>photos</code> 索引（用于检索）。它实用、可复制、易扩展。</p><p>如果您想对其进行调整，有几项设置可供使用：</p><ul><li><p><strong>查询时间设置：</strong> <code>k</code> （您希望返回多少个邻居）和<code>num_candidates</code> （最终评分前的搜索范围）。这些设置将在<a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">此处的</a>博客中讨论。</p></li><li><p><strong>索引时间设置：</strong> <code>m</code> （图形连接性）和<code>ef_construction</code> （构建时间精度与内存）。对于查询，也可以尝试使用<code>ef_search</code> --更高通常意味着更高的召回率，但需要权衡一定的延迟。有关这些设置的更多详情，请参阅<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">本博客</a>。</p></li></ul><p>展望未来，用于<strong>多模态</strong>和<strong>多语言</strong>搜索的本地模型/路由器即将登陆<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> Elastic</a>生态系统，这将使图像/文本检索和混合排名功能更加强大。</p><p>如果你想亲自尝试一下：</p><ul><li><p><strong>GitHub 代码库</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>： https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Colab 快速入门</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">：https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>我们的旅程就此结束，是时候飞回去了。希望这对你有帮助，如果你改动（或改进）了它，我很乐意听听你的改动。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用混合搜索重排提高多语言嵌入模型的相关性]]></title>
    <description><![CDATA[了解如何在 Elasticsearch 中使用 Cohere 的 reranker 和混合搜索提高 E5 多语言嵌入模型搜索结果的相关性。]]></description>
    <content:encoded><![CDATA[<h2>引言</h2><p>在<a href="https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch">本系列的最后一部分</a>，我们介绍了如何部署 Elastic 预训练的 E5 模型（以及 Hugging Face 提供的其他多语言文本嵌入模型），并深入探讨了如何使用 Elasticsearch 和 Kibana 从文本数据中生成密集向量嵌入。在本博客中，我们将研究这些嵌入的结果，并强调利用多语言模型的显著优势。</p><p>现在我们已经有了索引<code>coco_multilingual</code> ，执行搜索时将会得到多种语言的文档，其中的 "en "字段可供我们参考：</p># GET coco_multilingual/_search
    {
       "_index": "coco_multilingual",
       "_id": "WAiXQJYBgf6odR9bLohZ",
       "_score": 1,
       "_source": {
         "description": "Ein Parkmeßgerät auf einer Straße mit Autos",
         "en": "A row of parked cars sitting next to parking meters.",
         "language": "de",
         "vector_description": {...}
       }
     },
     . . .<h2>用英语进行搜索</h2><p>让我们尝试用英语进行搜索，看看效果如何：</p>GET coco_multi/_search
{
"size": 10,
"_source": [
  "description", "language", "en"
],
"knn": {
  "field": "vector_description.predicted_value",
  "k": 10,
  "num_candidates": 100,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": ".multilingual-e5-small_linux-x86_64_search",
      "model_text": "query: kitty"
    }
  }
}
}{
       "_index": "coco_multi",
       "_id": "JQiXQJYBgf6odR9b6Yz0",
       "_score": 0.9334303,
       "_source": {
         "description": "Eine Katze, die in einem kleinen, gepackten Koffer sitzt.",
         "en": "A brown and white cat is in a suitcase.",
         "language": "de"
       }
     },
      {
       "_index": "coco_multi",
       "_id": "3AiXQJYBgf6odR9bFod6",
       "_score": 0.9281012,
       "_source": {
         "description": "Una bambina che tiene un gattino vicino a una recinzione blu.",
         "en": "A little girl holding a kitten next to a blue fence.",
         "language": "it"
       }
     },
     . . .<p>在这里，尽管查询看起来非常简单，但我们要搜索的是 "kitty "一词在所有语言的所有文档中的数字嵌入。由于我们执行的是矢量搜索，因此可以从语义上搜索所有可能与 "kitty "相关的词：猫"、"小猫"、"猫科动物"、"gatto"（意大利语）、"mèo"（越南语）、고양이（韩语）、猫（汉语）等。因此，即使我的查询是以英语进行的，我们也可以搜索到所有其他语言的内容。例如，搜索 kitty l<code>ying on something</code> ，也会出现意大利语、荷兰语或越南语的文档。谈效率</p><h2>搜索其他语言的内容</h2>GET coco_multi/_search
{  
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: kitty lying on something"
     }
   }
 }
}{
 "description": "A black kitten lays on her side beside remote controls.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "en"
},
{
 "description": "un gattino sdraiato su un letto accanto ad alcuni telefoni ",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "it"
},
{
 "description": "eine Katze legt sich auf ein ausgestopftes Tier",
 "en": "a cat lays down on a stuffed animal",
 "language": "de"
},
{
 "description": "Một chú mèo con màu đen nằm nghiêng bên cạnh điều khiển từ xa.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "vi"
}
. . .<p>同样，用韩语（"고양이"）搜索关键词 "猫 "也会得到有意义的结果。令人惊叹的是，在这份索引中，我们甚至没有任何韩文文件！</p>GET coco_multi/_search
{
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: 고양이"
     }
   }
 }
} {
       {
         "description": "eine Katze legt sich auf ein ausgestopftes Tier",
         "en": "a cat lays down on a stuffed animal",
         "language": "de"
       }
     },
     {
       {
         "description": "Một con chó và con mèo đang ngủ với nhau trên một chiếc ghế dài màu cam.",
         "en": "A dog and cat lying  together on an orange couch. ",
         "language": "vi"
       }
     },<p>这是因为嵌入模型在共享语义空间中表示意义，即使查询语言与索引标题不同，也能检索到相关图像。</p><h2>利用混合搜索和重新排序增加相关搜索结果</h2><p>我们很高兴相关结果如期出现。但是，在现实世界中，比如在电子商务或 RAG 应用程序中，需要缩小到前 5-10 个最适用的结果，我们可以使用 rerank 模型来优先处理最相关的结果。</p><p>在这里，用越南语查询 "猫是什么颜色的？"会得到很多结果，但前 1 或前 2 个结果可能不是最相关的。</p>GET coco_multi/_search
{
 "size": 20,
 "_source": [
   "description",
   "language",
   "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 20,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: con mèo màu gì?"
     }
   }
 }
}<p>结果都提到了猫或某种颜色：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt979f5944b1708042/6a17ef76420229ff6829f6aa/33e1e887dbbdd1066cfedc7375f5e3b46538529e-859x847.png" alt="" /><p>因此，让我们来改进这一点！让我们结合<a href="https://cohere.com/blog/rerank-3pt5">Cohere</a>的多语言 rerank 模型来改进与我们的问题相对应的推理。</p>PUT _inference/rerank/cohere_rerank
{
 "service": "cohere",
 "service_settings": {
   "api_key": "your_api_key",
   "model_id": "rerank-v3.5"
 },
 "task_settings": {
   "top_n": 10,
   "return_documents": true
 }
}


GET coco_multi/_search
{
"size": 10,
"_source": [
  "description",
  "language",
  "en"
],
"retriever": {
  "text_similarity_reranker": {
    "retriever": {
      "rrf": {
        "retrievers": [
          {
            "knn": {
              "field": "vector_description.predicted_value",
              "k": 50,
              "num_candidates": 100,
              "query_vector_builder": {
                "text_embedding": {
                  "model_id": ".multilingual-e5-small_linux-x86_64_search",
                  "model_text": "query: con mèo màu gì?" // English: What color is the cat?
                }
              }
            }
          }
        ],
        "rank_window_size": 100,
        "rank_constant": 0
      }
    },
    "field": "description",
    "inference_id": "cohere_rerank",
    "inference_text": "con mèo màu gì?"
  }
}
} {
       "_index": "coco_multi",
       "_id": "rQiYQJYBgf6odR9bBYyH",
       "_score": 1.5501487,
       "_source": {
         "description": "Hai cái điện thoại được đặt trên một cái chăn cạnh một con mèo con màu đen.",
         "en": "A black kitten lays on her side beside remote controls.",
         "language": "vi"
       }
     },
     {
       "_index": "coco_multi",
       "_id": "swiXQJYBgf6odR9b04uf",
       "_score": 1.5427427,
       "_source": {
         "description": "Một con mèo sọc nâu nhìn vào máy quay.", // Real translation: A brown striped cat looks at the camera 
         "en": "This cat is sitting on a porch near a tire.",
         "language": "vi"
       }
     },<p>现在，有了最重要的结果，我们的应用程序就可以肯定地回答，这只小猫的颜色是黑色或带条纹的棕色。更有趣的是，我们的矢量搜索实际上发现了原始数据集中英文标题的遗漏。尽管参考英译本忽略了这一细节，但它还是能找到那只棕色条纹的猫。这就是矢量搜索的威力。</p><h2>结论</h2><p>在这篇博客中，我们介绍了多语言嵌入模型的实用性，以及如何利用 Elasticsearch 来集成模型以生成嵌入，并通过混合搜索和重新分词器来有效提高相关性和准确性。您可以<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs"> 创建一个 自己的 云集群</a> ，<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5"> 使用我们开箱即用的 E5 模型</a> 在您选择的语言和数据集上尝试 多语言语义搜索 。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[运维]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf625e3f63fcd9f54/6a17ef7796142a61f8eb1bcd/d341b04acecc8eeec321f5404e1643447ecc8526-720x420.png" length="0" type="image/png"/>
    <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[在 Elasticsearch 中部署多语言嵌入模型]]></title>
    <description><![CDATA[了解如何在 Elasticsearch 中为向量搜索和跨语言检索部署 e5 多语言嵌入模型。]]></description>
    <content:encoded><![CDATA[<h2>引言</h2><p>在用户遍布全球的世界里，跨语言信息检索（CLIR）至关重要。CLIR 可让您以<em>任何</em>语言查找信息，而不是将搜索局限于单一语言，从而增强用户体验并简化操作。想象一下，在全球市场上，电子商务客户可以用自己的语言搜索商品，正确的结果就会出现，而无需提前对数据进行本地化。或者，学术研究人员可以用自己的母语搜索论文，即使资料来源是另一种语言，也会有细微差别和复杂性。</p><p>多语言文本嵌入模型让我们能够做到这一点。嵌入是一种用数字向量表示文本含义的方法。设计这些向量的目的是让含义相似的文本在高维空间中彼此靠近。多语言文本嵌入模型专门用于将不同语言中具有相同含义的单词和短语映射到相似的向量空间中。</p><p>开源的多语言 E5 等模型是在海量文本数据的基础上进行训练的，通常使用对比学习等技术。在这种方法中，模型学会区分意义相似的文本对（正对）和意义不同的文本对（负对）。对模型进行训练，以调整其产生的向量，从而使正向配对之间的相似性最大化，反向配对之间的相似性最小化。对于多语言模型，这些训练数据包括不同语言的文本对，这些文本对互为翻译，从而使模型能够学习多种语言的共享表示空间。由此产生的嵌入结果可用于各种 NLP 任务，包括跨语言搜索，在跨语言搜索中，文本嵌入之间的相似性可用于查找相关文档，而不受查询语言的限制。</p><h2>多语言矢量搜索的优势</h2><ul><li><p><strong>细致入微</strong>：矢量搜索擅长捕捉语义，超越关键词匹配。这对于需要理解语境和语言微妙之处的任务至关重要。</p></li><li><p><strong>跨语言理解</strong>：即使查询和文档使用不同的词汇，也能跨语言进行有效的信息检索。</p></li><li><p><strong>相关性</strong>：通过关注查询和文档之间的概念相似性，提供更相关的结果。</p></li></ul><p>例如，一位学术研究人员正在研究"社交媒体对不同国家政治话语的影响" 。通过矢量搜索，他们可以输入"l'impatto dei social media sul discorso politico" (意大利文) 或"ảnh hưởng của mạng xã hội đối với diễn ngôn chính trị" (越南文) 等查询，并找到相关的英文论文、西班牙语或任何其他索引语言的相关论文。这是因为矢量搜索可以识别讨论社交媒体对政治影响这一<em>概念</em>的论文，而不仅仅是包含确切关键词的论文。这大大提高了他们研究的广度和深度。</p><h2>开始使用</h2><p>下面介绍如何使用 Elasticsearch（开箱即用的 E5 模型）设置 CLIR。我们将使用<a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">开源的多语言 COCO 数据集</a>（其中包含多种语言的图片说明）来帮助我们可视化两种类型的搜索：</p><ol><li><p>一个英语数据集上的其他语言查询和搜索词，以及</p></li><li><p>在包含多语言文档的数据集上进行多语言查询。</p></li></ol><p>然后，我们将利用混合搜索和重新排序的功能，进一步改进搜索结果。</p><h2>准备工作</h2><ul><li><p>Python 3.6+</p></li><li><p>Elasticsearch 8+</p></li><li><p>Elasticsearch Python 客户端： pip install elasticsearch</p></li></ul><h2>数据集</h2><p><a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">COCO 数据集是</a>一个大型字幕数据集。数据集中的每张图片都有多种不同语言的标题，每种语言都有几种翻译。为便于演示，我们将把每份译文作为单独的文件进行索引，并附上第一份可用的英文译文以供参考。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc7e508e9a7dffe8/6a17f3e2b1e113249579f394/d4f0632529c71a22fbdecf21c9f4f0bb64b8e69c-1600x567.png" alt="" /><h3>步骤 1：下载多语种 COCO 数据集</h3><p>为了简化博客并方便阅读，我们在这里通过简单的 API 调用，将 restval 的前 100 行加载到本地 JSON 文件中。或者，您也可以使用 HuggingFace 的库数据集来加载整个数据集或数据集的子集。</p>import requests
import json
import os
### Download multilingual coco dataset into a json file (for easy viewing)
### Here we are retrieving first 100 rows for this example
### Alternatively, you can use `datasets` library from Hugging Face
url = "https://datasets-server.huggingface.co/rows?dataset=romrawinjp%2Fmultilingual-coco&amp;config=default&amp;split=restval&amp;offset=0&amp;length=100"
response = requests.get(url)


if response.status_code == 200:
   data = response.json()
   output_file = "multilingual_coco_sample.json" 
   ### Loading the downloaded content into a json file locally
   with open(output_file, "w", encoding="utf-8") as f:
       json.dump(data, f, indent=4, ensure_ascii=False)
   print(f"Data successfully downloaded and saved to {output_file}")
else:
   print(f"Failed to download data: {response.status_code}")
   print(response.text)<p>如果数据成功加载到 JSON 文件中，你应该会看到类似下面的内容：</p><p><code>Data successfully downloaded and saved to multilingual_coco_sample.json</code></p><h3>第 2 步：（启动 Elasticsearch）并在 Elasticsearch 中编制数据索引</h3><p>a) 启动本地 Elasticsearch 服务器。</p><p>b) 启动 Elasticsearch 客户端。</p>from elasticsearch import Elasticsearch
from getpass import getpass


# Initialize Elasticsearch client
es = Elasticsearch(getpass("Host: "), api_key=getpass("API Key: "))


index_name = "coco"


# Create the index if it doesn't exist
if not es.indices.exists(index=index_name):
   es.indices.create(index=index_name, body=mapping)<p>c) 指数数据</p># Load the JSON data
with open('./multilingual_coco_sample.json', 'r') as f:
   data = json.load(f)


rows = data["rows"]
# List of languages to process
languages = ["en", "es", "de", "it", "vi", "th"]


# For each image, we will process each individual caption as its own document
bulk_data = []
for data in rows:
   row = data["row"]
   image = row.get("image")
   image_url = image["src"]


   # Process each language
   for lang in languages:
       # Skip if language not present in this row
       if lang not in row:
           continue


       # Get all descriptions for this language
 # along with first available English caption for reference
       descriptions = row[lang]
       first_eng_caption = row["en"][0]


       # Prepare bulk indexing data
       for description in descriptions:
           if description == "":
               continue
           # Add index operation
           bulk_data.append(
               {"index": {"_index": index_name}}
           )
           # Add document
           bulk_data.append({
               "language": lang,
               "description": description,
               "en": first_eng_caption,
               "image_url": image_url,
           })


# Perform bulk indexing
if bulk_data:
   try:
       response = es.bulk(operations=bulk_data)
       if response["errors"]:
           print("Some documents failed to index")
       else:
           print(f"Successfully bulk indexed {len(bulk_data)} documents")
   except Exception as e:
       print(f"Error during bulk indexing: {str(e)}")


print("Indexing complete!")<p>数据编入索引后，您应该会看到类似下面的内容：</p><p><code>Successfully bulk indexed 4840 documents</code></p><p><code>Indexing complete!</code></p><h3>步骤 3：部署 E5 训练模型</h3><p>在 Kibana 中，导航到 "堆栈管理&gt; <strong>训练模型</strong>"页面，然后单击 "<strong>部署</strong>.multilingual-e5-small_linux-x86_64选择。这款 E5 机型是为 linux-x86_64 优化的小型多语言机型，开箱即可使用。单击 "部署 "将显示一个屏幕，您可以在此调整部署设置或 vCPU 配置。为简单起见，我们将使用默认选项，并选择自适应资源，它将根据使用情况自动调整部署规模。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbc09867063a8e6f/6a17f3e3148009a295b4889d/95cd8f352425d1db2d04b00c3c88d1e71d1ef19a-1600x440.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt264a2016341e9b6f/6a17f3f0e8fbce18de3a1aa7/1599d99949dda8267acc58f400a403a3af5373ef-1600x655.png" alt="" /><p>如果您想使用其他文本嵌入模型，也可以选择使用。例如，要使用 BGE-M3，可以使用<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning#ml-nlp-pytorch">Elastic 的 Eland Python 客户端</a>从 HuggingFace 导入模型。</p>export MODEL_ID="bge-m3"
export HUB_MODEL_ID="BAAI/bge-m3"
export CLOUD_ID={{CLOUD_ID}}
export ES_API_KEY={{API_KEY}}
docker run -it --rm docker.elastic.co/eland/eland \
eland_import_hub_model --cloud-id $CLOUD_ID --es-api-key $ES_API_KEY --hub-model-id $HUB_MODEL_ID --es-model-id $MODEL_ID --task-type text_embedding --start<p>然后，导航到 "训练有素的模型 "页面，使用所需的配置部署导入的模型。</p><h3>第 4 步：利用已部署的模型对原始数据进行矢量化或创建嵌入模型</h3><p>要创建嵌入模型，我们首先需要创建一个摄取管道，使我们能够获取文本并通过推理文本嵌入模型运行。您可以在 Kibana 的用户界面或通过 Elasticsearch 的应用程序接口进行此操作。</p><p><strong>要通过 Kibana 界面完成此操作</strong>，在部署训练模型后，单击<strong>测试 </strong>按钮。这将使您能够测试和预览生成的嵌入式内容。为<code>coco</code> 索引创建一个新的数据视图，将数据视图设置为新创建的 coco 数据视图，并将字段设置为<code>description</code> ，因为这是我们要生成嵌入的字段。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24a2b9a9a5111bbc/6a17f3f13e9e452c7fba15d5/cfe189e13dc118d325e7fb90bdace0c912e29f51-1088x1600.png" alt="" /><p>效果很好！现在，我们可以继续创建摄取管道，并重新为原始文档建立索引，将它们通过管道，然后用嵌入创建一个新索引。单击 "<strong>创建管道</strong>"即可实现这一功能，它将引导您完成管道创建过程，并自动填充所需的处理器，帮助您创建嵌入。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte39c5ad52702103d/6a17f3f3e9ea87dd05a9c734/1e043c1c3279b66fbdf19c06b41e76e613043998-1600x1126.png" alt="" /><p>向导还可以自动填充所需的处理器，以便在摄取和处理数据时处理故障。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63e002a79d177cf4/6a17f3f596142a3e91eb1c48/8804d31b4f869078e3b2245040bbb0ab1720a94a-1600x1084.png" alt="" /><p>现在让我们创建摄取管道。我将管道命名为<code>coco_e5</code> 。管道创建成功后，可以立即使用管道生成嵌入，方法是将原始索引数据重新索引到向导中的新索引。单击 "<strong>重新索引 </strong>"启动该过程。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39243d9ad1779fdf/6a17f3f696142a13eaeb1c4c/e34b1b18f5b24420d4581fe4d657c569926c2023-1600x1126.png" alt="" /><h2>对于更复杂的配置，我们可以使用 Elasticsearch API。</h2><p>对于某些模型，由于训练模型的方式不同，我们可能需要在生成嵌入之前在实际输入中预置或附加某些文本；否则，我们将看到性能下降。</p><p>例如，在使用 e5 时，模型希望输入文本跟随 "passage：{content of passage}".让我们利用摄取管道来实现这一目标：我们将创建一个新的摄取管道<strong>vectorize_descriptions</strong>。在此流程中，我们将创建一个新的临时<code>temp_desc</code> 字段，在 "passage：" 添加到<code>description</code> 文本中，通过模型运行<code>temp_desc</code> 以生成文本嵌入，然后删除<code>temp_desc</code> 。</p>PUT _ingest/pipeline/vectorize_descriptions
{
"description": "Pipeline to run the descriptions text_field through our inference text embedding model",
"processors": [
 {
   "set": {
     "field": "temp_desc",
     "value": "passage: {{description}}"
   }
 },
 {
   "inference": {     
"field_map": {
       "temp_desc": "text_field"
     },
     "model_id": ".multilingual-e5-small_linux-x86_64_search",
     "target_field": "vector_description"
   }
 },
 {
   "remove": {
     "field": "temp_desc"
   }
 }
]
}<p>此外，我们可能还想为生成的向量指定<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-quantization">量化类型</a>。默认情况下，Elasticsearch 使用<code>int8_hnsw</code> ，但在这里，我需要<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">更好的二进制量化</a>（或<code>bqq_hnsw</code> ），它将每个维度降低到一位精度。这样，内存占用减少了 96% （或 32 倍），但精度却提高了。我之所以选择这种量化类型，是因为我知道稍后会使用重新anker 来改善精度损失。</p><p>为此，我们将创建一个名为<strong>coco_multi 的</strong>新索引，并指定映射。这里的奥妙在于 vector_description 字段，我们在其中指定<strong>index_options</strong>的类型为<strong>bbq_hnsw</strong>。</p>PUT coco_multi
{
 "mappings": {
   "properties": {
     "description": {
       "type": "text"
     },
     "en": {
       "type": "text"
     },
     "image_url": {
       "type": "keyword"
     },
     "language": {
       "type": "keyword"
     },
     "vector_description.predicted_value": {
       "type": "dense_vector",
       "dims": 384,
       "index": "true",
       "similarity": "cosine",
       "index_options": {
         "type": "bbq_hnsw" 
       }
     }
   }
 }
}<p>现在，我们可以将原始文档重新索引到新的索引中，我们的摄取管道将对描述字段进行 "矢量化 "或创建嵌入。</p>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "coco"
 },
 "dest": {
   "index": "coco_multilingual",
   "pipeline": "vectorize_descriptions"
 }
}<p>就是这样！我们已经成功地利用 Elasticsearch 和 Kibana 部署了一个多语言模型，并逐步了解了如何通过 Kibana 用户界面或 Elasticsearch API 利用 Elastic 数据创建向量嵌入。在本系列的第二部分，我们将探讨使用多语言模型的结果和细微差别。同时，您可以<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs"> 创建一个 自己的 云集群</a> ，在您选择的语言和数据集上<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5"> 使用我们开箱即用的 E5 模型</a> 尝试 多语言语义搜索 。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[运维]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt59254226694f93a6/6a17f3f81480098988b488a1/8f2aa7bebb6b2f701e274ba7282273f9ab4abed6-720x432.png" length="0" type="image/png"/>
    <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[矢量搜索过滤：保持相关性]]></title>
    <description><![CDATA[仅靠矢量搜索来查找与查询最相似的结果是不够的。要缩小搜索结果的范围，通常需要进行筛选。本文介绍了在 Elasticsearch 和 Apache Lucene 中如何对矢量搜索进行过滤。]]></description>
    <content:encoded><![CDATA[<p>矢量搜索不足以找到相关结果。使用过滤标准非常常见，这有助于缩小搜索结果的范围并过滤掉不相关的结果。</p><p>了解筛选在矢量搜索中是如何工作的，将有助于你平衡性能和召回率之间的权衡，并发现一些优化方法，使矢量搜索在使用筛选时性能更佳。</p><h2>为什么要过滤？</h2><p>矢量搜索彻底改变了我们在大型数据集中查找相关信息的方式，使我们能够发现与查询语义相似的项目。</p><p>然而，仅仅找到相似的物品是不够的。我们经常需要根据特定的标准或属性来缩小搜索结果的范围。</p><p>想象一下，您正在一家电子商务商店中搜索产品。纯矢量搜索可能会显示视觉上相似的商品，但您可能还想根据价格范围、品牌、可用性或客户评价进行筛选。如果不进行筛选，您就会看到大量类似的产品，很难准确找到您要找的产品。</p><p>过滤功能可对搜索结果进行精确控制，确保检索到的项目不仅在语义上一致，而且符合所有必要的要求。这将带来更加准确、高效和用户友好的搜索体验。</p><p>这正是 Elasticsearch 和 Apache Lucene 的优势所在--对各种数据类型进行有效过滤是它们与其他矢量数据库的主要区别之一。</p><h2>精确矢量搜索的筛选</h2><p>进行精确矢量搜索主要有两种方法：</p><ul><li><p>为 dense_vector 字段使用<code>flat</code> 索引类型。这使得<code>knn</code> 搜索使用精确搜索而不是近似搜索。</p></li><li><p>使用<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions"> script_score 查询</a> ，该 查询 使用向量函数计算分数。这可用于任何索引类型。</p></li></ul><p>在执行精确向量搜索时，所有向量都会与查询进行比较。在这种情况下，过滤将有助于提高性能，因为只需要比较通过过滤的向量。</p><p>这不会影响结果质量，因为所有向量都会被考虑在内。我们只是提前过滤掉不感兴趣的结果，从而减少操作次数。</p><p>这一点非常重要，因为当应用筛选器得到的文档数量很少时，执行精确搜索比近似搜索更有效。</p><p>经验法则是，当通过过滤器的文件少于 10k 时，应使用精确搜索。<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>索引的比较速度更快，因此当基于索引的数据少于 100k 时，使用精确搜索是合理的。详情请查看<a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">本博文</a>。</p><p>如果您的筛选器总是限制性很强，您可以考虑使用<code>flat</code> 索引类型而不是基于 HNSW 的索引类型，将索引重点放在精确搜索而不是近似搜索上。更多详情，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">index_options 的属性</a>。</p><h2>近似矢量搜索的筛选</h2><p>在执行近似向量搜索时，我们需要用结果的准确性来换取性能。像 HNSW 这样的矢量搜索数据结构可在数百万个矢量上高效搜索近似近邻。它们的重点是通过进行最少的向量比较来检索最相似的向量，而向量比较的计算成本很高。</p><p>这意味着其他过滤属性不属于矢量数据的一部分。不同的数据类型有自己的索引结构，如术语字典、发布列表和 doc 值等，可以有效地查找和过滤这些数据。</p><p>既然这些数据结构与矢量搜索机制是分开的，那么我们如何将过滤功能应用于矢量搜索呢？有两种选择：在矢量搜索后应用过滤器（后过滤）或在矢量搜索前应用过滤器（预过滤）。</p><p>每种方案都各有利弊。让我们深入了解它们！</p><h3>后过滤</h3><p>后过滤在矢量搜索完成后应用过滤器。这意味着，在找到前 k 个最相似的向量结果后，才会应用筛选器。</p><p>显然，在对结果进行筛选后，我们可能会得到少于 k 个结果。当然，我们可以从矢量搜索中获取更多的结果（k 值更高），但我们无法确定在应用过滤器后是否会得到 k 或更多的结果。</p><p>后过滤的优势在于它不会改变矢量搜索的运行时行为--矢量搜索不知道过滤的存在。但是，它确实会改变检索结果的最终数量。</p><p>下面是使用<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">knn 查询</a>进行后过滤的示例。检查过滤子句是否与 knn 查询分开：</p>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<p>使用后置<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">过滤器</a>还可对 knn 搜索进行后置过滤：</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}<p>请记住，您需要在 knn 搜索中使用明确的后置过滤器部分。如果不使用后置过滤器，knn 搜索<a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features"> 会将最近邻</a> 搜索 结果 与其他查询或过滤器 结合起来 ，而不是进行后置过滤器。</p><h3>预过滤</h3><p>在矢量搜索前应用筛选器将首先检索出满足筛选条件的文档，然后将这些信息传递给矢量搜索。</p><p>Lucene 使用<a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a>高效地存储满足筛选条件的文档。然后，矢量搜索会遍历 HNSW 图，并将满足条件的文档考虑在内。在将候选文件添加到结果中之前，它会检查该候选文件是否包含在有效文件的 BitSet 中。</p><p>不过，即使候选文件不是有效文件，也必须对其进行探索并与查询进行比较。HNSW 的有效性取决于图中向量之间的联系--如果我们停止探索某个候选向量，就意味着我们可能也会跳过它的邻近向量。</p><p>就像开车去加油站一样。如果放弃任何一条没有加油站的道路，您就不可能到达目的地。其他道路可能不是你所需要的，但它们将你<em>连接</em>到目的地。HNSW 图形上的向量也是如此！</p><p>因此，应用预过滤比不应用过滤的性能要低。我们需要对搜索中访问的<em>所有</em>向量进行处理，并丢弃不符合筛选条件的向量。我们正在做更多的工作，花更多的时间来获得最高 K 值的结果。</p><p>下面是在 Elasticsearch 查询 DSL 中进行预过滤的示例。检查过滤子句是否已成为 knn 部分的一部分：</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}<p><a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">knn 搜索</a>和<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">knn 查询</a>均可使用预过滤功能：</p>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<h4>预过滤优化</h4><p>我们可以进行一些优化，以确保预过滤的性能。</p><p>如果筛选条件非常严格，我们可以切换到精确搜索。当需要比较的向量很少时，对满足筛选条件的少数文档进行精确搜索会更快。</p><p>这是<a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a>和 Elasticsearch 自动应用的优化。</p><p>另一种优化方法是忽略不符合筛选条件的向量。相反，该方法会检查滤波向量的邻近向量是否通过滤波。这种方法不考虑过滤后的向量，而是继续探索与当前路径相连的向量，从而有效减少了比较次数。</p><p>这种算法就是 ACORN-1，<a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">本篇博文</a>将详细介绍其过程。</p><h2>使用文档级安全过滤</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">文档级别安全（DLS）</a>是 Elasticsearch 的一项功能，可指定用户角色可检索的文档。</p><p>DLS 通过查询来执行。一个角色可以有一个与索引相关联的查询，这实际上限制了属于该角色的用户可以从索引中检索的文档。</p><p>角色查询用作过滤器，用于<a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">检索与之匹配的文档</a>，并作为 BitSet 缓存。然后，这个 BitSet 会被用来封装底层的 Lucene 阅读器，因此只有从查询返回的文档才会被认为是<em>实时的</em>，也就是说，它们存在于索引中，并且没有被删除。</p><p>由于要<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">从阅读器获取</a>实时文档来执行 knn 查询，因此只考虑用户可用的文档。如果有预检器，DLS 文件将被<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204"> 添加到 预检器 中</a> 。</p><p>这意味着，DLS 过滤可以作为近似矢量搜索的预过滤，具有相同的性能影响和优化效果。</p><p>使用精确搜索的 DLS 与应用任何过滤器的好处相同--从 DLS 检索的文档越少，精确搜索的性能就越高。还要考虑 DLS 返回的文档数量--如果 DLS 的作用非常有限，可以考虑使用精确搜索而不是近似搜索。</p><h2>基准</h2><p>在 Elasticsearch，我们希望确保矢量搜索过滤的效率。我们有<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">一个专门的向量过滤基准</a>，通过不同的过滤执行近似向量搜索，以确保向量搜索尽可能快地检索到相关结果。</p><p>查看 ACORN-1 推出时的<a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:'2025-05-28T01:27:58.456Z',to:'2025-06-30T13:53:26.430Z'))&amp;_a=()">改进</a>情况。在只有 2% 个向量通过过滤器的测试中，查询延迟时间缩短到原来的 55% ：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt820cb0b715cf291c/6a17e227dbb4ff8d49fb5615/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" alt="" /><h2>结论</h2><p>过滤是搜索不可或缺的一部分。确保过滤在矢量搜索中的性能，并了解权衡和优化，是高效和准确搜索的关键所在。</p><p>过滤会影响向量搜索的性能：</p><ul><li><p>使用过滤功能时，精确搜索速度更快。如果过滤条件足够严格，应考虑使用精确搜索而不是近似搜索。这是 Elasticsearch 的自动优化功能。</p></li><li><p>使用预过滤时，近似搜索速度较慢。通过预过滤，我们可以得到与过滤器匹配的前 k 个结果，但搜索速度会减慢。</p></li><li><p>后过滤并不一定能检索到前 k 个结果，因为在应用过滤器时，这些结果可能已被过滤器过滤。</p></li></ul><p>快乐过滤</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-filtering</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-filtering</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39ede1736e0f1456/6a17e2282f4a5c5031fa8843/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[将嵌入映射到 Elasticsearch 字段类型：semantic_text、dense_vector、sparse_vector]]></title>
    <description><![CDATA[讨论如何以及何时使用 semantic_text、dense_vector 或 sparse_vector，以及它们与嵌入生成的关系。]]></description>
    <content:encoded><![CDATA[<p>多年来，利用嵌入式技术提高信息检索相关性和准确性的做法有了长足的发展。Elasticsearch 等工具已经通过密集向量、稀疏向量和语义文本等专门字段类型支持这类数据。不过，要取得良好的效果，必须了解如何将嵌入正确映射到可用的 Elasticsearch 字段类型：<code>semantic_text</code>、<code>dense_vector</code> 和<code>sparse_vector</code> 。</p><p>在本文中，我们将讨论这些字段类型、每种类型的使用时间，以及它们与嵌入生成和使用策略的关系，包括在索引和查询过程中的关系。</p><h2>密集矢量类型</h2><p>Elasticsearch 中的<code>dense_vector</code> 字段类型用于存储密集向量，密集向量是文本、图像和音频等数据的数字表示，其中几乎所有维度都是相关的。这些向量是使用 OpenAI、Cohere 或 Hugging Face 等平台提供的嵌入模型生成的，旨在捕捉数据的整体语义，即使数据与其他文档不共享确切术语。</p><p>在 Elasticsearch 中，稠密向量的维度最多可达 4096，具体取决于所使用的模型。例如，all-MiniLM-L6-v2 模型生成的向量有 384 个维度，而 OpenAI 的 text-embedding-ada-002 模型生成的向量有 1536 个维度。</p><p><code>dense_vector</code> 字段通常被用作存储这类嵌入的默认类型，当需要更多控制时，例如使用预生成向量、应用自定义相似性函数或与外部模型集成。</p><h3>何时以及为何使用 dense_vector 类型？</h3><p>密集向量非常适合捕捉句子、段落或整个文档之间的语义相似性。当目标是比较文本的整体含义时，即使它们不共享相同的术语，它们也能很好地发挥作用。</p><p>密集矢量字段非常适合已经拥有外部嵌入生成管道，使用 OpenAI、Cohere 或 Hugging Face 等平台提供的模型，并且只想手动存储和查询这些矢量的情况。这种类型的字段与嵌入模型具有很高的兼容性，在生成和查询方面具有充分的灵活性，允许您控制矢量的生成、索引和搜索使用方式。</p><p>此外，它还支持不同形式的语义搜索，在需要调整排名逻辑的情况下，可使用 k-NN 或 script_score 等查询。这些可能性使密集矢量成为 RAG（检索增强生成）、推荐系统和基于相似性的个性化搜索等应用的理想选择。</p><p>最后，该字段允许您自定义相关性逻辑，使用<code>cosineSimilarity</code> 、<code>dotProduct</code> 或<code>l2norm</code> 等函数，根据使用情况的需要调整排名。 </p><p>对于需要灵活性、定制化和与上述高级用例兼容的用户来说，密集矢量仍然是最佳选择。</p><h3>如何使用密集矢量类型查询？</h3><p>对定义为<strong><code>dense_vector</code></strong> 的字段的搜索使用 k 近邻查询。该查询负责查找密集向量与查询向量最接近的文档。下面举例说明如何将 k-NN 查询应用于密集向量场：</p>{
  "knn": {
    "field": "my_dense_vector",
    "k": 10,
    "num_candidates": 50,
    "query_vector": [/* vector generated by model */]
  }
}<p>除 k-NN 查询外，如果需要自定义文档评分，也可以使用 script_score 查询，将其与<strong>余弦相似度、点积或 l2norm 等</strong>向量比较函数相结合，以更可控的方式计算相关性。请看示例：</p>{
"script_score": {
    "query": { "match_all": {} },
    "script": {
      "source": "cosineSimilarity(params.query_vector,
'my_dense_vector') + 1.0",
      "params": {
        "query_vector": [/* vector */]
      }
    }
  }
}<p>如果您想深入了解，我建议您阅读《<a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">如何在 Elasticsearch 中设置向量搜索</a>》一文。</p><p></p><h2>稀疏矢量类型</h2><p><strong><code>sparse_vector</code></strong> 字段类型用于存储稀疏矢量，稀疏矢量是一种数值表示，其中大部分值为零，只有少数项具有重要权重。这种类型的向量在基于术语的模型中很常见，如 SPLADE 或 ELSER（弹性学习稀疏 EncodeR）。</p><h3>何时以及为何使用稀疏向量类型？</h3><p>当你需要更精确的词汇搜索而又不牺牲语义智能时，稀疏向量是理想的选择。它们将文本表示为标记/值对，只突出显示最相关的术语和相关权重，从而提供清晰度、控制和效率。</p><p>这类字段在根据术语生成向量时特别有用，例如在 ELSER 或 SPLADE 模型中，这些模型会根据每个标记在文本中的相对重要性为其分配不同的权重。</p><p>如果您想控制查询中特定词语的影响，稀疏向量类型允许您手动调整词语的权重，以优化结果的排名。</p><p>它的主要优点包括：搜索透明，因为可以清楚地了解为什么某个文件被认为是相关的；存储高效，因为只保存非零值的标记，而不像密集向量那样存储所有维度。</p><p>此外，稀疏向量是混合搜索策略的理想补充，甚至可以与密集向量相结合，将词汇精确性与语义理解相结合。</p><h3>如何使用稀疏向量类型查询？</h3><p><strong><code>sparse_vector</code></strong> 查询可让您根据标记/值格式的查询向量搜索文档。请看下面的查询示例：</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "query_vector": {
        "token1": 0.6,
        "token2": 0.2,
        "token3": 0.9
      }
    }
  }
}<p>如果希望使用训练有素的模型，可以使用推理端点自动将查询文本转换为稀疏向量：</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "inference_id": "the inference ID to produce the token/weights",
      "query": "search text"
    }
  }
}<p>要进一步探讨这一主题，我建议阅读《<a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">用训练有素的 ML 模型理解稀疏向量嵌入</a>》。</p><h2>语义文本类型</h2><p><strong><code>semantic_text</code></strong> 字段类型是在 Elasticsearch 中使用语义搜索的最简单、最直接的方法。它通过一个推理端点，在索引和查询时自动处理嵌入生成。这意味着你不必担心手动生成或存储矢量的问题。</p><h3>何时以及为何使用语义文本？</h3><p><code>semantic_text</code> 字段非常适合那些希望以最少的技术投入、无需手动处理矢量即可开始工作的用户。该字段可自动执行嵌入生成和矢量搜索映射等步骤，使设置更快更方便。</p><p>如果您重视<strong>简单性和抽象性</strong>，就应该考虑使用<code>semantic_text</code> ，因为它<strong>消除了手动配置映射、嵌入生成和摄取管道的复杂性</strong>。只需选择推理模型，其余的就交给 Elasticsearch 处理。</p><p>其主要优势包括在索引和查询过程中<strong>自动生成嵌入</strong>，以及<strong>可随时使用的映射</strong>，该映射经过预先配置，可支持选定的推理模型。</p><p>此外，该领域还提供<strong>对自动分割长文本（文本分块）的本地支持</strong>，可将大文本分割成较小的段落，每个段落都有自己的嵌入，从而提高搜索精度。这极大地提高了工作效率，尤其是对于那些希望在不处理语义搜索底层工程的情况下快速实现价值的团队而言。</p><p>不过，虽然<code>semantic_text</code> 提供了速度和简便性，但这种方法也有一些局限性。它允许使用市场标准模型，只要这些模型可以作为 Elasticsearch 中的推理端点。但<strong>它不支持外部生成的嵌入</strong>，而<code>dense_vector</code> 字段则可以做到这一点。</p><p>如果您需要对向量的生成方式进行更多控制，希望使用自己的嵌入，或需要将多个字段结合起来以实现高级策略，<code>dense_vector</code> 和<code>sparse_vector</code> 字段可提供更多自定义或特定领域方案所需的灵活性。</p><h3>如何使用语义文本类型查询</h3><p>在<strong><code>semantic_text</code></strong> 之前，必须根据嵌入类型（密集或稀疏）使用不同的查询。<code>sparse_vector</code> 查询用于稀疏字段，而<code>dense_vector</code> 字段则需要 KNN 查询。</p><p>使用语义文本类型时，搜索是通过<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">语义查询</a>进行的，查询会自动生成查询向量，并与索引文档的嵌入进行比较。<strong><code>semantic_text</code></strong> 类型允许您定义用于嵌入查询的推理端点，但如果未指定任何推理端点，则将对查询应用索引过程中使用的相同端点。</p>{
  "query": {
    "semantic": {
      "field": "semantic_text_field",
      "query": "search text"
    }
  }
}<p>要了解更多信息，我建议您阅读<a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text"> Elasticsearch 新语义_文本映射这 篇文章 ：简化语义搜索</a> 。</p><h2>结论</h2><p>在选择如何在 Elasticsearch 中映射嵌入时，必须了解要如何生成向量以及需要对向量进行何种程度的控制。如果您追求简单，语义文本字段可实现自动和可扩展的语义搜索，使其成为许多初始用例的理想选择。当需要更多控制、微调性能或与自定义模型集成时，密集矢量和稀疏矢量场可提供必要的灵活性。</p><p>理想的字段类型取决于您的使用案例、可用基础设施以及机器学习堆栈的成熟度。最重要的是，Elastic 提供了用于构建现代化和高度适应性搜索系统的工具。</p><h2>参考资料</h2><ul><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">语义文本字段类型</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html">稀疏矢量场类型</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">密集矢量场类型</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">语义查询</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">稀疏向量查询</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">kNN 搜索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch 新语义文本映射：简化语义搜索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">用训练有素的 ML 模型理解稀疏向量嵌入</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[映射]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72cd3c2601b22886/6a17083b0c4857259901a9dc/f98fdff837db55b466780c0bae672aa6f6c3a966-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[如何在使用案例中实施更好的二进制量化 (BBQ)]]></title>
    <description><![CDATA[探讨为什么要在用例中实施更好的二进制量化 (BBQ) 以及如何实施。]]></description>
    <content:encoded><![CDATA[<p>矢量搜索为实现文本的语义搜索或图像、视频或音频的相似性搜索提供了基础。在矢量搜索中，矢量是数据的数学表示，可能非常庞大，有时也会比较迟钝。更好的二进制量化（以下简称 BBQ）是一种矢量压缩方法。它可以让你找到正确的匹配，同时缩小矢量，使搜索和处理速度更快。本文将介绍 BBQ 和 rescore_vector，这是一个仅适用于量化索引的字段，可自动对向量重新评分。</p><p>本文中提到的所有完整查询和输出都可以在我们的<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/how-and-why-bbq">Elasticsearch Labs 代码库中</a>找到。</p><h2>为什么要在使用案例中实施更好的二进制量化 (BBQ)？</h2>注：要深入了解 BBQ 背后的数学原理，请查看下面的<a href="https://www.elastic.co/cn/search-labs/blog/bbq-implementation-into-use-case#further-learning">"进一步学习 "部分</a>。就本博客而言，重点是实施。<p>数学知识固然耐人寻味，但要想完全掌握矢量搜索保持精确的原因，这一点至关重要。归根结底，这一切都与压缩有关，因为事实证明，目前的矢量搜索算法受到数据读取速度的限制。因此，如果能将所有数据都存储到内存中，那么与从存储设备中读取数据相比，速度将得到显著提升 （内存的 读取<a href="https://sre.google/static/pdf/rule-of-thumb-latency-numbers-letter.pdf"> 速度约为固态硬盘的 200 倍</a> ）。</p><p>有几点需要注意：</p><ul><li><p>基于图形的索引，如<a href="https://arxiv.org/pdf/1603.09320">HNSW</a>（层次导航小世界），对于向量检索来说是最快的。</p><ul><li><p>HNSW：一种近似近邻搜索算法，可构建多层图结构，从而实现高效的高维相似性搜索。</p></li></ul></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt760bd95c206bfa8f/6a17e2ad505ac393f7ad8a95/590f3b3c72a76023a38a0436cd9ff90a9f80e936-1964x1262.png" alt="HNSW：一种近似近邻搜索算法，可构建多层图结构，从而实现高效的高维相似性搜索。" /><ul><li><p>从根本上说，HNSW 的速度受限于从内存读取数据的速度，或者在最糟糕的情况下，受限于从存储器读取数据的速度。</p><ul><li><p>理想情况下，您希望能够将所有存储的向量加载到内存中。</p></li></ul></li><li><p>嵌入模型通常以 float32 的精度生成向量，每个浮点数 4 个字节。</p></li><li><p>最后，根据向量和/或维数的多少，内存很快就会不够存放所有向量。</p></li></ul><p>如果把这看作是理所当然的，那么一旦你开始摄入数百万甚至数十亿的向量，每个向量都可能有数百甚至数千个维度，你就会发现问题很快就出现了。题为 "<a href="https://www.elastic.co/cn/search-labs/blog/bbq-implementation-into-use-case#approximate-numbers-on-the-compression-ratios">压缩比近似值</a>"的部分提供了一些粗略的数字。</p><h2>开始需要什么？</h2><p>要开始使用，您需要具备以下条件：</p><ul><li><p>如果使用 Elastic Cloud 或内部部署，则需要高于 8.18 的 Elasticsearch 版本。虽然 BBQ 是在 8.16 中引入的，但在本文中，您将使用<code>vector_rescore</code> ，它是在 8.18 中引入的。</p></li><li><p>此外，您还需要确保集群中有一个<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.18/ml-settings.html">机器学习（ML）节点</a>。(注意：加载模型需要至少 4GB 的 ML 节点，但如果要完成生产工作负载，可能需要更大的节点）。</p></li><li><p>如果使用的是无服务器，则需要选择针对向量进行了优化的实例。</p></li><li><p>您还需要具备矢量数据库方面的基础知识。如果您还不熟悉 Elastic 中的矢量搜索概念，可能需要先查看以下资源：</p><ul><li><p><a href="https://www.elastic.co/cn/search-labs/blog/elastic-vector-database-practical-example">导航弹性矢量数据库</a></p></li><li><p><a href="https://www.elastic.co/cn/blog/retrieval-augmented-generation-explained">检索增强生成背后的重大理念</a></p></li></ul></li></ul><h2>更好的二进制量化 (BBQ) 实现</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18df00df95ff2ca7/6a17e2af414c6411989450df/4d388078495566f0527e931e0c2e38facdce83c6-1503x748.png" alt="Elasticsearch bbq 实现。" /><p>为了使本博客简单明了，您将在可用时使用内置函数。在这种情况下，<a href="https://www.elastic.co/cn/guide/en/machine-learning/8.17/ml-nlp-e5.html"><code>.multilingual-e5-small</code></a> 向量嵌入模型将直接在 Elasticsearch 内部的机器学习节点上运行。请注意，您可以用自己选择的嵌入器<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.18/infer-service-openai.html">（OpenAI</a>、<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.18/infer-service-google-ai-studio.html">Google AI Studio</a>、<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.18/infer-service-cohere.html">Cohere</a>等）替换<code>text_embedding</code> 模型。如果您喜欢的模型尚未集成，您也可以<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.18/bring-your-own-vectors.html">自带密集向量嵌入</a>模型）。</p><p>首先，您需要创建一个推理端点，为给定文本生成向量。您将从 Kibana<a href="https://www.elastic.co/cn/guide/en/kibana/8.18/console-kibana.html">Dev Tools 控制台</a>运行所有这些命令。该命令将下载<code>.multilingual-e5-small</code>.如果端点还不存在，它将为您设置端点；这可能需要一分钟的时间。你可以在 Outputs 文件夹中的<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/01-create-an-inference-endpoint-output.json">01-create-an-inference-endpoint-output.json</a>文件中看到预期输出。 </p>PUT _inference/text_embedding/my_e5_model
{
  "service": "elasticsearch",
  "service_settings": {
    "num_threads": 1,
    "model_id": ".multilingual-e5-small",
    "adaptive_allocations": {
      "enabled": true,
      "min_number_of_allocations": 1
    }
  }
}<p>返回后，模型就设置好了，您可以使用以下命令测试模型是否按预期运行。你可以在 Outputs 文件夹中的<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/02-embed-text-output.json">02-embed-text-output.json</a>文件中看到预期输出。</p>POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}<p>如果遇到训练好的模型没有分配到任何节点的问题，可能需要手动启动模型。</p>POST _ml/trained_models/.multilingual-e5-small/deployment/_start<p>现在，让我们创建一个带有 2 个属性的新映射，一个标准文本字段 (<code>my_field</code>) 和一个 384 维的密集矢量字段 (<code>my_vector</code>) ，以匹配嵌入模型的输出。您还可以覆盖<code>index_options.type to bbq_hnsw</code> 。你可以在 Outputs 文件夹中的文件<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/03-create-byte-qauntized-index-output.json">03-create-byte-qauntized-index-output.json</a>中看到预期输出。</p>PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}<p>要确保 Elasticsearch 生成向量，可以使用<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.18/ingest.html">Ingest Pipeline</a>。该管道需要三样东西：端点 (<code>model_id</code>)、要为其创建向量的<code>input_field</code> 以及用于存储这些向量的<code>output_field</code> 。下面的第一条命令将创建推理摄取管道，该管道使用<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/inference-apis.html">推理服务 </a>，第二条命令将测试管道是否正常工作。你可以在 Outputs 文件夹中的文件<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/04-create-and-simulate-ingest-pipeline-output.json">04-create and-simulate-ingest-pipeline-output.json</a>中看到预期输出。 </p>PUT _ingest/pipeline/my_inference_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "my_e5_model",
        "input_output": [
          {
            "input_field": "my_field",
            "output_field": "my_vector"
          }
        ]
      }
    }
  ]
}

POST _ingest/pipeline/my_inference_pipeline/_simulate
{
  "docs": [
    {
      "_source": {
        "my_field": "my awesome text field"
      }
    }
  ]
}<p>现在，您可以使用下面的前 2 个命令添加一些文档，并使用第 3 个命令测试搜索是否有效。你可以在 Outputs 文件夹中的<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/05-bbq-index-output.json">05-bbq-index-output.json</a>文件中查看预期输出。 </p>PUT bbq-my-byte-quantized-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT bbq-my-byte-quantized-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p>正如<a href="https://www.elastic.co/cn/search-labs/blog/better-binary-quantization-lucene-elasticsearch#lucene-benchmarking">本文章</a>所建议的，当您扩展到非数量级的数据时，建议使用重采样和超采样，因为它们有助于在受益于压缩优势的同时保持较高的召回准确率。从 Elasticsearch 8.18 版开始，您可以使用<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.18/knn-search.html#dense-vector-knn-search-rescoring">rescore_vector</a> 这样做。预期输出在 Outputs 文件夹中的<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/06-bbq-search-8-18-output.json">06-bbq-search-8-18-output.json</a>文件中。</p>GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "rescore_vector": {
              "oversample": 3
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p>这些分数与原始数据的分数相比如何？如果您再次进行上述操作，但使用<code>index_options.type: hnsw</code> ，您会发现得分非常接近。你可以在 Outputs 文件夹中的<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/07-raw-vector-output.json">07-raw-vector-output.json</a>文件中看到预期输出。</p>PUT my-raw-vector-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "hnsw"
        }
      }
    }
  }
}

PUT my-raw-vector-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT my-raw-vector-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET my-raw-vector-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<h2>压缩比的近似值</h2><p>在使用矢量搜索时，存储和内存需求很快就会成为一项重大挑战。下面的细目说明了不同的量化技术如何显著减少矢量数据的内存占用。</p><p>向量 (V)</p><p>尺寸（D）</p><p>未加工（V x D x 4）</p><p>int8 (V x (D x 1 + 4))</p><p>int4 (V x (D x 0.5 + 4))</p><p>bbq (V x (D x 0.125 + 4))</p><p>10,000,000</p><p>384</p><p>14.31GB</p><p>3.61GB</p><p>1.83GB</p><p>0.58GB</p><p>50,000,000</p><p>384</p><p>71.53GB</p><p>18.07GB</p><p>9.13GB</p><p>2.89GB</p><p>100,000,000</p><p>384</p><p>143.05GB</p><p>36.14GB</p><p>18.25GB</p><p>5.77GB</p><h2>结论</h2><p>BBQ 是一种优化方法，可用于压缩矢量数据而不影响精度。它的工作原理是将向量转换为比特，让您能够有效地搜索数据，并使您能够扩展人工智能工作流程，加快搜索速度并优化数据存储。</p><h2>进一步学习</h2><p>如果您想了解有关烧烤的更多信息，请务必查看以下资源：</p><ul><li><p><a href="https://www.elastic.co/cn/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Lucene 和 Elasticsearch 中的二进制量化 (BBQ)</a></p></li><li><p><a href="https://www.elastic.co/cn/search-labs/blog/bit-vectors-elasticsearch-bbq-vs-pq">更好的二进制量化（BBQ）与乘积量化比较</a></p></li><li><p><a href="https://www.elastic.co/cn/search-labs/blog/optimized-scalar-quantization-elasticsearch">优化的标量量化更好的二进制量化</a></p></li><li><p><a href="https://www.youtube.com/watch?v=04NzMt2Nigc">更好的二进制量化 (BBQ)：从字节到烧烤，更好的矢量搜索的秘密》，本-特伦特著</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[基础功能]]></category>
    <dc:creator><![CDATA[Sachin Frayne,Jessica Garson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3dd0495b536b2615/6a17e2b0414c6488459450e3/66842055367cdd795532b01c167f2a4b03dc65e3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch BBQ 与 OpenSearch FAISS：矢量搜索性能比较]]></title>
    <description><![CDATA[Elasticsearch BBQ 和 OpenSearch FAISS 的性能比较。]]></description>
    <content:encoded><![CDATA[<p><strong>二进制量化矢量搜索使用 BBQ 的 Elasticsearch 比使用 FAISS 的 OpenSearch 快 5 倍</strong>。Elastic 收到了来自社区的请求，希望澄清 Elasticsearch 和 OpenSearch 之间的性能差异，尤其是在语义搜索/矢量搜索领域，因此我们进行了这些性能测试，以提供清晰的、数据驱动的比较。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8792b5284e7a553/6a1709b9961e69a084c4cee6/7f4f8d08f7bee188423e4e65f0caefc7e34f0355-1600x681.png" alt="Elasticsearch BBQ 与 OpenSearch FAISS - 速度&amp; 吞吐量 召回率比较" /><h2>二进制量化对决</h2><p>以原始形式存储高维向量会耗费大量内存。量化技术可将这些矢量压缩为紧凑的表示形式，从而大大减少内存占用。然后，搜索在压缩空间内进行，从而降低了计算复杂度，加快了搜索速度，尤其是在大型数据集中。</p><p>Elastic 致力于使 Lucene 成为性能一流的矢量引擎。在 Elasticsearch 8.16 中，我们在 Lucene 的基础上引入了<a href="https://www.elastic.co/cn/search-labs/blog/better-binary-quantization-lucene-elasticsearch">更好的二进制量化</a>(BBQ)，并在 8.18 和 9.0 中进一步发展。BBQ 基于一种新的<a href="https://www.elastic.co/cn/search-labs/blog/optimized-scalar-quantization-elasticsearch">标量量化</a>方法，将 float32 维度减少到比特，在保持高排名质量的同时，减少了 ~95% 内存。</p><p>另一方面，OpenSearch 使用多种矢量引擎：nmslib（现已废弃）、Lucene 和 FAISS。在<a href="https://www.elastic.co/cn/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison"> 上</a> 一篇 博客 中，我们比较了 Elasticsearch 和 OpenSearch 的向量搜索。我们使用了三个不同的数据集，并在两种产品上测试了不同的引擎和配置组合。</p><p>本博客重点介绍这两种产品目前提供的二进制量化算法。我们使用 BBQ 对 Elasticsearch 进行了测试，并使用<a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">FAISS 的二进制量化</a>（<a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a>Rally track）对 OpenSearch 进行了测试。</p><p>主要目的是评估两种解决方案在相同召回率下的性能。<em>召回</em>是什么意思？召回率是衡量搜索系统成功检索到多少相关结果的指标。</p><p>在这项评估中，<em>recall</em>@k 尤为重要，其中k代表所考虑的顶级结果的数量。因此，<strong>Recall@10</strong>、<strong>Recall@50 和 Recall@100</strong>分别用来衡量有多少真正相关的结果出现在检索结果的前 10、50 和 100 项中。召回率以 0 到 1 的范围表示（或 0% 到 100% 精确度）。这一点很重要，因为我们讨论的是近似 KNN (ANN)，而不是精确 KNN，后者的召回率总是 1 (100%).</p><p>对于每个<em>k</em>值，我们还指定了<em>n， </em>即在应用最终排名之前考虑的候选者数量。这意味着，对于 Recall@10、Recall@50 和 Recall@100，系统首先使用二进制量化算法检索<em>n 个</em>候选项，然后对它们进行排序，以确定前<em>k 个</em>结果是否包含预期的相关项。</p><p>通过控制<em>n</em>，我们可以分析效率和精度之间的权衡。较高的<em>n</em>通常会<strong>增加</strong>召回率，因为有更多候选者可供排序，但同时也会<strong>增加</strong>延迟和<strong> 降低 </strong>吞吐量。相反，<em>n</em>越小，检索速度越快，但如果初始集合中包含的相关候选信息太少，则可能会降低召回率。</p><p>在这次比较中，Elasticsearch 在相同的设置下比 OpenSearch 的延迟更低，吞吐量更高。</p><h2>方法</h2><p>完整的配置，以及 Terraform 脚本、Kubernetes 清单和特定的 Rally 轨道，都可在此<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq">仓库</a> <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq/rally-custom/custom_tracks/elasticsearch/openai_vector_bq"><em>openai_vector_bq</em></a> 下找到。</p><p>与之前的基准测试一样，我们使用的 Kubernetes 集群由以下部分组成：</p><ul><li><p>1 个用于 Elasticsearch 9.0 的节点池，包含 3 台<code>e2-standard-32</code> 机器（128GB 内存和 32 个 CPU）</p></li><li><p>1 个用于 OpenSearch 2.19 的节点池，包含 3 台<code>e2-standard-32</code> 机器（128GB 内存和 32 个 CPU）</p></li><li><p>1 个用于 Rally 的节点池，包含 2 台<code>e2-standard-4</code> 机器（16GB 内存和 4 个 CPU）</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt934e988e96a5849f/6a1709bbacf08838f9be9ac1/169bb6033f6eebfd1b177b3446bf916fde4ee5c5-1600x856.png" alt="Elasticsearch BBQ 与 Opensearch FAISS 方法论设置对比" /><p>我们建立了一个版本为 9.0 的 Elasticsearch 集群和一个版本为 2.19 的 OpenSearch 集群。</p><p>Elasticsearch 和 OpenSearch 都使用了完全相同的设置进行测试：我们使用了经过<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/commit/b97d5d95c22c8cf862f2030964524bdd156a5da3"> 一些修改的</a><a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector"> openai_vector</a> Rally track，它使用了来自<a href="https://huggingface.co/datasets/BeIR/nq"> NQ 数据集</a> 的 250 万份文档，并使用 OpenAI 的<a href="https://openai.com/blog/new-and-improved-embedding-model"> text-embedding-ada-002 模型</a> 生成了丰富的嵌入。</p>{
  "source-file": "open_ai_corpus-initial-indexing.json.bz2",
  "document-count": 2580961,
  "compressed-bytes": 32076749416,
  "uncompressed-bytes": 90263571686
}<p>结果报告了在不同召回级别（召回率@10、召回率@50 和召回率@100）下，使用 8 个客户端同时执行搜索操作所测出的延迟和吞吐量。我们只使用一个分片，没有副本。</p><p>我们运行了以下 k-n-rescore 组合，例如10-2000-2000，或<em>k:10</em>、<em>n:2000</em>和<em>rescore:2000</em>将检索 n 个候选者（2000）中的前 k（10），并对 2000 个结果进行重新评分（相当于 "超抽样因子 "1）。每次搜索运行 10.000 次，预热 1000 次：</p><p></p><p><u><strong>召回@10</strong></u></p><ul><li><p>10-40-40</p></li><li><p>10-50-50</p></li><li><p>10-100-100</p></li><li><p>10-200-200</p></li><li><p>10-500-500</p></li><li><p>10-750-750</p></li><li><p>10-1000-1000</p></li><li><p>10-1500-1500</p></li><li><p>10-2000-2000</p></li></ul><p><u><strong>回忆@50</strong></u></p><ul><li><p>50-150-150</p></li><li><p>50-200-200</p></li><li><p>50-250-250</p></li><li><p>50-500-500</p></li><li><p>50-750-750</p></li><li><p>50-1000-1000</p></li><li><p>50-1200-1200</p></li><li><p>50-1500-1500</p></li><li><p>50-2000-2000</p></li></ul><p><u><strong>Recall@100</strong></u></p><ul><li><p>100-200-200</p></li><li><p>100-250-250</p></li><li><p>100-300-300</p></li><li><p>100-500-500</p></li><li><p>100-750-750</p></li><li><p>100-1000-1000</p></li><li><p>100-1200-1200</p></li><li><p>100-1500-1500</p></li><li><p>100-2000-2000</p></li></ul><p>为了复制基准，Rally-elasticsearch 和 rally-opensearch 的 Kubernetes 清单都将所有相关变量外部化到了 ConfigMap 中，可<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-es-bq.yml">在此处</a>（ES）和<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-os-bq.yml">此处</a>（OS）查看。可以自定义<em>search_ops</em>参数，以测试 k、n 和 rescore 的任意组合。</p><h3>OpenSearch Rally 配置</h3><p><code>/k8s/rally-openai_vector-os-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-os
  labels:
    app: rally-opensearch
data:
  user-tags.json: |
    {
      "product": "OpenSearch",
      "product-version": "OpenSearch-2.19.0",
      "product-label": "OpenSearch-2.19-faiss",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "ann_threshold": 0,
      "vector_mode": "on_disk",
      "compression_level": "32x",
      "vector_method_name": "hnsw",
      "vector_method_engine": "faiss",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>Opensearch 索引配置</h3><p>然后，ConfigMap 中的变量将用于索引配置，某些参数则保持不变。OpenSearch 中的 1 位量化是通过<a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization"> 将压缩级别设置为 "32x " 来配置的</a> 。</p><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {% if preload_pagecache %}
    "index.store.preload": [
      "vec", "vex", "vem", "veq", "veqm", "veb", "vebm"
    ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }},
    "index.knn": true,
    "index.knn.advanced.approximate_threshold": {{ ann_threshold | default(15000) }}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "knn_vector",
        "dimension": 1536,
        "space_type": "innerproduct",
        "data_type": "float",
        "mode": {{ vector_mode | default("in_memory") | tojson }},
        "compression_level": {{ compression_level | default("32x") | tojson }},
        "method": {
          "name": {{ vector_method_name | default("hnsw") | tojson }},
          "engine": {{ vector_method_engine | default("faiss") | tojson }},
          "parameters": {
            "ef_construction": 100,
            "m": 16
          }
        }
      }
    }
  }
}<h3>Elasticsearch Rally 配置</h3><p><code>/k8s/rally-openai_vector-es-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-es
  labels:
    app: rally-elasticsearch
data:
  user-tags.json: |
    {
      "product": "Elasticsearch",
      "product-version": "Elasticsearch-9.0.0-ade01164",
      "product-label": "Elasticsearch-9.0-BBQ",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "vector_index_type": "bbq_hnsw",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>Elasticsearch 索引配置</h3><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {# non-serverless-index-settings-marker-start #}
    {%- if build_flavor != "serverless" or serverless_operator == true -%}
    {% if preload_pagecache %}
    "index.store.preload": [ "vec", "vex", "vem", "veq", "veqm", "veb", "vebm" ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }}
    {%- endif -%}
    {# non-serverless-index-settings-marker-end #}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 1536,
        "index": true,
        "similarity": "dot_product",
        "index_options": {
          "type": {{ vector_index_type | default("bbq_hnsw") | tojson }},
          "ef_construction": 100,
          "m": 16
        }
      }
    }
  }
}<h2>实施结果</h2><p>对结果有多种解释方法。对于延迟和吞吐量，我们绘制了每个召回级别的简化图和详细图。如果我们考虑到每项指标 "越高越好"，就很容易发现其中的差异。不过，延迟是负值（实际上越低越好），而吞吐量是正值。在简化图表中，我们使用<strong>（召回率/延迟）*10000 </strong>（简称 "速度"）和<strong> 召回率*吞吐量</strong>，因此这两个指标都意味着速度越快和吞吐量越大越好。让我们开始吧。</p><h3>召回 @ 10 - 简化</h3><p>在这种召回水平下，Elasticsearch BBQ 比 OpenSearch FAISS<strong>快 5 倍 </strong>（平均快 3.9 倍），<strong>吞吐量</strong>平均高出 3.2 倍。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3de6b3460127a8ae/6a1709bcb339d55975769f6f/d580ad53e8974bd3aa75957c413a0136c4e465c5-1600x681.png" alt="在速度和吞吐量方面，Elasticsearch BBQ 比 OpenSearch FAISS 快达 5 倍（平均快 3.9 倍），吞吐量平均高出 3.2 倍 Recall@10" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd47045013cac5e9d/6a1709be0e2e49599a41a088/18edce667fe36ab95033264ef8df6f352dda2425-2044x866.png" alt="与 OpenSearch FAISS 相比，Elasticsearch BBQ 的速度快达 5 倍（平均快 3.9 倍），吞吐量平均高出 3.2 倍。" /><h4>召回 @ 10 - 详细</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ecb02d4a2967dbd/6a1709bf14b270c04fe3c5c6/a7459b87e679f4ad963d0e2f1685499b40f6f050-1600x799.png" alt="Elasticsearch BBQ 与 Opensearch FAISS 的详细召回@10 延迟比较" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt182da309ad6cfd39/6a1709c166c4f99077f8bfd8/c6036f55a13377654d296eb3148c7199e1965475-1600x799.png" alt="Elasticsearch BBQ 与 Opensearch FAISS 的详细召回率@10 吞吐量比较。" /><p></p><p>工作</p><p>latency.mean</p><p>吞吐量平均值</p><p>平均调用次数</p><p>Elasticsearch-9.0-BBQ</p><p>10-100-100</p><p>11.70</p><p>513.58</p><p>0.89</p><p>Elasticsearch-9.0-BBQ</p><p>10-1000-100</p><p>27.33</p><p>250.55</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-1500-1500</p><p>35.93</p><p>197.26</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-200-200</p><p>13.33</p><p>456.16</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>10-2000-2000</p><p>44.27</p><p>161.40</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-40-40</p><p>10.97</p><p>539.94</p><p>0.84</p><p>Elasticsearch-9.0-BBQ</p><p>10-50-50</p><p>11.00</p><p>535.73</p><p>0.85</p><p>Elasticsearch-9.0-BBQ</p><p>10-500-500</p><p>19.52</p><p>341.45</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>10-750-750</p><p>22.94</p><p>295.19</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-100-100</p><p>35.59</p><p>200.61</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-1000-1000</p><p>156.81</p><p>58.30</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-1500-1500</p><p>181.79</p><p>42.97</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-200-200</p><p>47.91</p><p>155.16</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>10-2000-2000</p><p>232.14</p><p>31.84</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-40-40</p><p>27.55</p><p>249.25</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-50-50</p><p>28.78</p><p>245.14</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-500-500</p><p>79.44</p><p>97.06</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-750-750</p><p>104.19</p><p>75.49</p><p>0.96</p><h3>召回 @ 50 - 简化</h3><p>在这种召回水平下，Elasticsearch BBQ 比 OpenSearch FAISS<strong> 快多达 5 倍</strong> （平均快<strong> 4.2 倍），平均 吞吐量多</strong> 3.9 倍 。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt486683f2c7a1d677/6a1709c2a6c2b995bde796a3/3189ffb330948b35854eeea9ae317d4846c14972-1600x681.png" alt="Recal @50 向量性能对比 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba830c6741784682/6a1709c4a292990627d00ff7/607383d674dcf0b8f94bfb1a450063f52fcbeb15-2060x876.png" alt="Elasticsearch BBQ 的速度比 OpenSearch FAISS 快 5 倍（平均快 4.2 倍），吞吐量平均高出 3.9 倍。" /><h4>详细结果 - 召回率 @ 50</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt821775e7f87e5ede/6a1709c6a6c2b90af3e796a7/ebfffe0b776aad31dd03d315cfbf5aa098b41226-1600x789.png" alt="Recall@50 Elasticsearch BBQ 和 Opensearch FAISS 延迟结果" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f47784ed1017c15/6a1709c7e8fbce0fbe39fbf5/ae20cf870a65c400a2112bbad62eb56e244f549a-1600x799.png" alt="Recall@50 Elasticsearch BBQ 和 Opensearch FAISS 吞吐量结果" /><p></p><p>任务</p><p>平均时延</p><p>吞吐量平均值</p><p>平均召回率</p><p>Elasticsearch-9.0-BBQ</p><p>50-1000-1000</p><p>25.71</p><p>246.44</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-1200-1200</p><p>28.81</p><p>227.85</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-150-150</p><p>13.43</p><p>362.90</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>50-1500-1500</p><p>33.38</p><p>202.37</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-200-200</p><p>12.99</p><p>406.30</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>50-2000-2000</p><p>42.63</p><p>163.68</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-250-250</p><p>14.41</p><p>373.21</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>50-500-500</p><p>17.15</p><p>341.04</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>50-750-750</p><p>31.25</p><p>248.60</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>50-1000-1000</p><p>125.35</p><p>62.53</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-1200-1200</p><p>143.87</p><p>54.75</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-150-150</p><p>43.64</p><p>130.01</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>50-1500-1500</p><p>169.45</p><p>46.35</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-200-200</p><p>48.05</p><p>156.07</p><p>0.91</p><p>OpenSearch-2.19-faiss</p><p>50-2000-2000</p><p>216.73</p><p>36.38</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-250-250</p><p>53.52</p><p>142.44</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>50-500-500</p><p>78.98</p><p>97.82</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>50-750-750</p><p>103.20</p><p>75.86</p><p>0.96</p><h3>召回 @ 100</h3><p>在这种召回水平下，Elasticsearch BBQ 比 OpenSearch FAISS<strong>快 5 倍 </strong>（平均快 4.6 倍），<strong>吞吐量 </strong>平均高 3.9 倍。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt380eb9165343a7b3/6a1709c9acf0882669be9ac5/d3f29db64cbde9956de1fa3ae64a75f15141a2bb-1600x681.png" alt="回忆 @100 Elasticsearch BBQ 与 Opensearch FAISS 的结果" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt505ce8aa70898390/6a1709cb1949f73a37e7a9bb/10aff7f8c61fdac895b9ba9c5342baf239ba3ffc-2072x864.png" alt="Elasticsearch BBQ 和 Opensearch FAISS 延迟和吞吐量性能比较" /><h4>详细结果 - Recall @ 100</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2a73e29940e9d6b/6a1709cccf4f257615b2d138/8790fdf9512b850447f6875fb69969f6f1d4da5f-1600x799.png" alt="详细的延迟结果 - Recall @ 100 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2c91ca21da03d96/6a1709ce0c4857d3fc01aa39/d47032fac6c288cd4eedd9f25001e417b2fa9d65-1600x787.png" alt="详细的吞吐量结果 - Recall @ 100 Elasticsearch BBQ vs Opensearch FAISS。" /><p></p><p>工作</p><p>latency.mean</p><p>吞吐量平均值</p><p>平均调用次数</p><p>Elasticsearch-9.0-BBQ</p><p>100-1000-1000</p><p>27.82</p><p>243.22</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1200-1200</p><p>31.14</p><p>224.04</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1500-1500</p><p>35.98</p><p>193.99</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-200-200</p><p>14.18</p><p>403.86</p><p>0.88</p><p>Elasticsearch-9.0-BBQ</p><p>100-2000-2000</p><p>45.36</p><p>159.88</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-250-250</p><p>14.77</p><p>433.06</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>100-300-300</p><p>14.61</p><p>375.54</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>100-500-500</p><p>18.88</p><p>340.37</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>100-750-750</p><p>23.59</p><p>285.79</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>100-1000-1000</p><p>142.90</p><p>58.48</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1200-1200</p><p>153.03</p><p>51.04</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1500-1500</p><p>181.79</p><p>43.20</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-200-200</p><p>50.94</p><p>131.62</p><p>0.83</p><p>OpenSearch-2.19-faiss</p><p>100-2000-2000</p><p>232.53</p><p>33.67</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-250-250</p><p>57.08</p><p>131.23</p><p>0.87</p><p>OpenSearch-2.19-faiss</p><p>100-300-300</p><p>62.76</p><p>120.10</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>100-500-500</p><p>84.36</p><p>91.54</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>100-750-750</p><p>111.33</p><p>69.95</p><p>0.94</p><h2>烧烤炉的改进</h2><p>自首次发布以来，BBQ 已经取得了长足的进步。在 Elasticsearch 8.16 上，为了便于比较，我们将 8.16 的基准运行与当前的基准运行放在一起，我们可以看到召回率和延迟从那时起有了怎样的改善。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f1f7ae1bb425a3a/6a1709cf67045bde8e45c192/45a0acfe5985bff28ccada76ec4eca190fe65f72-1600x799.png" alt="以 Elasticsearch 8.16 BBQ 为基准测试 Elasticsearch 9.0 BBQ 延迟召回改进情况" /><p>在 Elasticsearch 8.18 和 9.0 中，我们重写了量化向量的核心算法。因此，8.16 版的 BBQ 已经很不错了，而最新版本则更加出色。您可以<a href="https://www.elastic.co/cn/search-labs/blog/optimized-scalar-quantization-elasticsearch">在这里</a>和<a href="https://www.elastic.co/cn/search-labs/blog/scalar-quantization-optimization">这里</a>了解相关信息。简而言之，每个矢量都通过优化的标量量化进行了单独量化。因此，用户可以在不影响性能的情况下获得更高的向量搜索准确性，使 Elasticsearch 的向量检索功能更加强大。</p><h2>结论</h2><p>在 Elasticsearch BBQ 和 OpenSearch FAISS 的性能比较中，Elasticsearch 在矢量搜索方面明显优于 OpenSearch，在各种召回级别中，Elasticsearch 的查询速度平均提高了 5 倍，吞吐量提高了 3.9 倍。</p><p>主要发现包括</p><ul><li><p><strong>Recall@10</strong>：与 OpenSearch FAISS 相比，Elasticsearch BBQ 的速度快达 5 倍（平均快 3.9 倍），吞吐量平均高出 3.2 倍。</p></li><li><p><strong>Recall@50</strong>：与 OpenSearch FAISS 相比，Elasticsearch BBQ 的速度快达 5 倍（平均快 4.2 倍），吞吐量平均高出 3.9 倍。</p></li><li><p><strong>Recall@100</strong>：与 OpenSearch FAISS 相比，Elasticsearch BBQ 的速度快达 5 倍（平均快 4.6 倍），吞吐量平均高出 3.9 倍。</p></li></ul><p>这些结果凸显了 Elasticsearch BBQ 的效率和性能优势，尤其是在高维向量搜索场景中。Elasticsearch 8.16 中引入的更好的二进制量化（BBQ）技术在保持较高排序质量的同时，大幅减少了内存（~95% ），是大规模矢量搜索应用的上佳选择。</p><p>在 Elastic，我们坚持不懈地创新，改进 Apache Lucene 和 Elasticsearch，为搜索和检索用例（包括 RAG（检索增强生成））提供最佳的向量数据库。在 Lucene 10 的基础上，我们<a href="https://www.elastic.co/cn/search-labs/blog/optimized-scalar-quantization-elasticsearch">最近取得的进步</a>大大提高了性能，使矢量搜索比以前更快、更节省空间。本博客就是这种创新的又一例证。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd76ada1eebfe05cd/6a1709d1a29299ed29d00ffb/796de4829e29566f1f3efa2482f5c3e54b31b1d6-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[用于谷歌云 Vertex AI 平台原生落地的 Elasticsearch 向量数据库]]></title>
    <description><![CDATA[探索 Elasticsearch 作为 Google Cloud's Vertex AI 的首个第三方原生落地引擎，如何让您通过将 Gemini 模型落地到企业数据中来构建定制的 GenAI 体验。]]></description>
    <content:encoded><![CDATA[<p>Elastic非常高兴地宣布，Elasticsearch矢量数据库现已集成到谷歌云的Vertex人工智能平台，成为一个本地支持的信息检索引擎，使用户能够利用谷歌Gemini模型的多模态优势和Elasticsearch先进的人工智能驱动的语义和混合搜索功能。</p><p>开发人员现在可以在一个统一的旅程中创建他们的 RAG 应用程序，以一种低代码、灵活的方式将他们的聊天体验建立在他们的私人数据之上。无论您是为客户和内部员工构建人工智能代理，还是在软件中利用 LLMs 生成，Vertex 人工智能平台都能让 Elasticsearch 的相关性触手可及，只需最少的配置。这种集成使 Gemini 模型更容易、更快速地应用于生产用例，推动 GenAI 从 PoCs 进入实际生活场景。</p><p>在本博客中，我们将指导您将 Elasticsearch 与谷歌云的 Vertex AI 平台集成，以实现无缝数据落地，并构建完全可定制的 GenAI 应用程序。让我们一起来探索吧。</p><h2>借助 Elasticsearch，Google 云的 Vertex AI 和 Gemini 模型可在您的数据中落地生根</h2><p>利用 Vertex AI 服务和工具创建 GenAI 应用程序的用户现在可以访问新的 "接地 "选项，将其私人数据自动引入对话交互。Elasticsearch 现在是该功能的一部分，可以通过这两种方式使用：</p><ul><li><p>Vertex AI<a href="https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference">LLM API</a>，可在生成时直接丰富 Google 的双子座模型（首选）；</p></li><li><p><a href="https://cloud.google.com/generative-ai-app-builder/docs/grounded-gen">地基生成应用程序接口（Grounded Generation API</a> ），在顶点人工智能代理 生成器 （Vertex AI Agent Builder）生态系统中用于构建代理体验。</p></li></ul><p>通过这种集成，下载和部署最多的<a href="https://www.elastic.co/cn/elasticsearch/vector-database">向量数据库</a>Elasticsearch 将把您的相关企业数据带到您的内部终端客户聊天中所需的任何地方，这对 GenAI 在业务流程中的实际应用至关重要。</p><p>上述应用程序接口将允许开发人员在其代码中采用这项新的合作伙伴功能。不过，及时的工程设计和测试仍然是应用程序开发的关键步骤，也是最初的探索过程。为了支持这一点，Elasticsearch 的设计便于用户在 Vertex AI Studio 控制台工具中进行评估。</p><p>只需几个简单的步骤，在用户界面的 "自定义接地 "选项卡中用所需的参数（要搜索的索引、要检索的文档数量和所需的搜索模板）配置 Elastic 端点，如下图所示（注意，要使其正常工作，必须在用户界面和下面的代码示例中键入带有"ApiKey" 字样的 API 密钥）。现在，您已经准备好用您的私人知识进行生成了！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d313968acca1fea/6a17e624b1e113278079f250/69b3d979d18fa90742d9397f57975c586edf5d9f-1003x710.gif" alt="Google 云的 Vertex AI 和 Gemini 模型通过 Elasticsearch 在您的数据上接地气" /><h2>轻松实现可投入生产的 GenAI 应用程序</h2><p>Elastic 和 Google Cloud 致力于提供开发人员优先、全面和愉快的体验。在 Vertex AI 上构建 GAI 应用程序时，通过 LLM 和接地生成 API 原生连接到 Elastic 可降低复杂性和开销，避免不必要的额外 API 和数据协调，同时只需一次统一调用即可接地。</p><p>让我们看看在这两种情况下是如何工作的。</p><p>第一个示例使用 LLM API 执行：</p>curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \https://us-central1-aiplatform.googleapis.com/v1beta1/projects/&lt;PROJECT_ID&gt;/locations/us-central1/publishers/google/models/gemini-2.0-flash-001:generateContent \
  -d '
{
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "What's my company car policy?"
        }
      ]
    }
  ],
  "tools": [{
    "retrieval": {
      "externalApi": {
        "api_spec": "ELASTIC_SEARCH",
    "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
    "apiAuth": {
      "apiKeyConfig": {
            "apiKeyString": "ApiKey &lt;API_KEY&gt;"
      }
    },
    "elasticSearchParams": {
      "index": "&lt;my-index&gt;",
      "searchTemplate": "&lt;my-search-template&gt;"
    }
      }
    }
  }]
}<p>在上例中，通过请求将内容生成到 Gemini 2.0 Flash 的 API 的<code>retrieval</code> 字段，我们可以根据上下文设置请求的检索引擎。将<code>api_spec</code> 设置为 "ELASTIC_SEARCH "后，就可以使用其他配置参数，如 API 密钥和集群端点（需要将请求路由到 Elastic 集群）、检索数据的索引以及用于搜索逻辑的搜索模板。</p><p>同样，通过接地生成 API，设置<code>groundingSpec</code> 参数，也可以获得相同的结果：</p>curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" https://us-discoveryengine.googleapis.com/v1alpha/projects/&lt;PROJECT_ID&gt;/locations/global:generateGroundedContent -d '
{
  "contents": [{
    "role": "user",
    "parts": [{
      "text": "What do I need to patch a hole in my drywall?"
    }]
  }],
  "groundingSpec": {
    "groundingSources": [{
      "elasticSource": {
        "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
        "index": "&lt;my-index&gt;",
        "searchTemplate": "&lt;my-search-template",
        "apiKey": "projects/&lt;PROJECT_ID&gt;/secrets/api-key/versions/latest"
      }
    }]
  }
}
'<p>使用这两种方法，响应都将提供一个包含 Elasticsearch 中最相关的私有文档的答案，以及相关的连接数据源，以支持您的查询。</p><p>但是，不能把简单与缺乏个性化以满足您的特定需求和使用情况混为一谈。考虑到这一点，我们在设计时允许您根据自己的情况对搜索配置进行完美调整。</p><h2>指尖上的完全自定义搜索：搜索模板</h2><p>为了最大限度地定制您的搜索方案，我们与 Google Cloud 合作，在我们著名的<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/search-template.html">搜索模板</a>基础上打造了这一体验。Elasticsearch 搜索模板是创建动态、可重用和可维护搜索查询的绝佳工具。它们允许你预定义和重复使用查询结构。在使用不同参数执行类似查询时，它们尤其有用，因为它们可以节省开发时间并减少出错的机会。模板可包括变量占位符，使查询动态化，并适应不同的搜索要求。</p><p>在使用 Vertex AI API 和 Elasticsearch 作为基础时，您必须引用所需的搜索模板（如上面的代码片段所示），在该模板中实现搜索逻辑并将其推送到 Elasticsearch。对于 Vertex AI 用户、网络应用程序开发人员或 AI 工程师来说，他们只需在基础 API 中指定模板名称，就能以完全透明的方式异步管理、配置和更新搜索方法，并根据特定的索引、模型和数据进行定制。</p><p>这种设计允许完全定制，将广泛的 Elasticsearch 检索功能置于 Google Cloud AI 环境中供您使用，同时确保模块化、透明性和易用性，以适应不同的开发人员，甚至是不熟悉 Elastic 的开发人员。</p><p>无论何时您需要 BM25 检索、语义检索或两者的混合方法（您是否已经探索过<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/retrievers-overview.html">检索器</a>？在单个搜索 API 调用中使用可组合检索技术），您可以在搜索模板中定义自定义逻辑，Vertex AI 可以自动利用该模板。</p><p>这也适用于您选择用来管理向量和结果的嵌入和重排模型。根据您的使用情况，您可能希望在 Elastic 的 ML 节点上托管模型，通过推理 API 使用第三方服务端点，或者在本地运行本地模型。这可以通过搜索模板来实现，我们将在下一节了解其工作原理。</p><h2>从参考模板开始，然后创建自己的模板</h2><p>为了帮助您快速上手，我们提供了一套兼容的搜索模板示例，供您作为初始参考，然后您可以修改并建立自己的自定义模板：</p><ul><li><p>使用 ELSER 模型（稀疏向量和分块）进行语义搜索</p></li><li><p>使用 e5 多语言模型（密集向量和分块）进行语义搜索</p></li><li><p>使用顶点人工智能文本嵌入模型进行混合搜索</p></li></ul><p>您可以在此<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/Cloud-Vertex-AI/search-templates">GitHub 仓库</a>中找到它们。</p><p>让我们来看一个例子：使用谷歌云的顶点人工智能应用程序接口在产品目录上创建嵌入。首先，我们需要在 Elasticsearch 中创建搜索模板，如下图所示：</p>PUT _scripts/google-template-knn
{
  "script": {
    "lang": "mustache",
    "source": {
      "_source": {
        "excludes": [ "title_embedding", "description_embedding", "images" ]
      },
        "size": "{{num_hits}}",
          "knn" : [
          { 
            "field": "description_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
              }
            },
            "boost": 0.4
          },
          {
            "field": "title_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
            }
          },
          "boost": 0.6
          }
          ]
    }  
  }
}<p>在本例中，我们将在一次搜索中对两个字段执行 KNN 搜索： <code>title_embedding</code> - 包含产品名称的矢量字段 - 以及<code>description_embedding</code> - 包含产品描述的矢量字段。</p><p>您可以利用<code>excludes</code> 语法，避免向 LLM 返回不必要的字段，以免在处理过程中产生噪音，影响最终答案的质量。在我们的示例中，我们排除了包含向量和图片 url 的字段。</p><p>矢量是在查询时通过顶点人工智能嵌入式应用程序接口<code>googlevertexai_embeddings_004</code> 的推理端点在提交的输入上即时创建的，其定义如下：</p>PUT /_inference/text_embedding/googlevertexai_embeddings_004
{
    "service": "googlevertexai",
    "service_settings": {
        "service_account_json": "&lt;your_service_account_key&gt;",
        "model_id": "text-embedding-004",
        "location": "us-central1",
        "project_id": "&lt;your_gcp_project&gt;"
    }
}<p>有关如何使用 Elastic Inference API 的更多信息，<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/inference-apis.html">请点击此处</a>。</p><p>现在我们可以测试模板搜索了：</p>GET product-catalog-with-embeddings/_search/template
{
  "id": "google-template-knn",
  "params": {
    "query": "What do I need to patch a hole in my drywall?",
    "index_name": "product-catalog-with-embeddings",
    "num_hits": 3
  }
}<p><code>params</code> 字段将替换我们在模板脚本中设置的变量，这些变量位于双卷曲括号中。目前，Vertex AI LLM 和 Grounded Generation API 可以向 Elastic 发送以下输入变量：</p><ul><li><p>"query" - 要搜索的用户查询</p></li><li><p>"index_name" - 要搜索的索引名称</p></li><li><p>"num_hits" - 我们希望在最终输出中检索多少文件</p></li></ul><p>下面是输出示例：</p>{
        "_index": "product-catalog-with-embeddings",
        "_id": "9ZQCm5IBcrGI1ivqV-f_",
        "_score": 0.4925191,
        "_ignored": [
          "description.keyword",
          "images.keyword"
        ],
        "_source": {
          "description": "DAP Eclipse Rapid Wall Repair Patch is a new, revolutionary product solution for repairing drywall damage. No more waiting for spackling to dry or messy sanding. DAP Eclipse allows you to patch drywall damage and paint immediately, allowing you to finish your project faster. This all-in-1, mess free solution not only provides a permanent, long-lasting repair but also superior impact resistance for areas that may see reoccurring impact, such as behind a door.",
          "availability": "InStock",
          "model_id": "googlevertexai_embeddings_004",
          "title": "4 in. Eclipse Wall Repair Patch (2-Pack)",
          "url": "https://www.myDIYwebsite.com/p/DAP-4-in-Eclipse-Wall-Repair-Patch-2-Pack-7079809164/317967195",
          "price": 23.96,
          "product_id": 317967195,
          "currency": "USD",
          "brand": "DAP"
        }<p>上述查询正是 Google Cloud 的 Vertex AI 在引用之前创建的搜索模板时在 Elasticsearch 上运行的幕后查询。Gemini 模型将使用输出文档作为其答案的基础：当您问 "我需要什么来修补干墙？"时，聊天代理将为您提供具体的产品，而不是通用的建议！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte88a0c34242df6fa/6a17e6263e03d704504f2c2e/8c8b1374da7e9b2c17df8758acb448eed5b74e2d-1473x913.png" alt="在谷歌顶点人工智能平台上创建提示语" /><h2>借助 Elastic 和谷歌云实现端到端 GenAI 之旅</h2><p>Elastic 与谷歌云（Google Cloud）合作，创建生产就绪的端到端 GenAI 体验和解决方案。正如我们刚刚看到的那样，Elastic 是第一个直接集成到顶点人工智能平台的用户界面和 SDK 中的 ISV，可以利用我们的矢量搜索功能实现无缝、接地气的双子座模型提示和代理。此外，Elastic 还与<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/infer-service-google-vertex-ai.html"> Vertex AI</a> 和<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/infer-service-google-ai-studio.html"> Google AI Studio</a> 的嵌入、重排序和完成模型集成，无需离开 Google 云环境即可创建矢量并对 其 进行排序，确保 遵循负责任的人工智能<a href="https://cloud.google.com/responsible-ai?hl=en"> </a>原则。通过支持多模式方法，我们共同促进了各种数据格式的应用。</p><p>您可以通过我们的<a href="https://www.elastic.co/cn/search-labs/blog/vertex-ai-elasticsearch-playground-fast-rag-apps">Playground</a> 调整、测试和导出 GenAI 搜索代码。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a978835a5418eb9/6a17e628fbc5f8abff491a6a/b1fc48dba1713cd01c4d7f3296587d0c4e6e7e0c-862x651.png" alt="借助 Elastic 和谷歌云实现端到端 GenAI 之旅 " /><p>但它不仅仅是构建搜索应用程序：Elastic 利用 Gemini 模型增强 IT 运营能力，如<a href="https://www.elastic.co/cn/blog/elastic-google-vertex-ai-integration">Elastic AI 助手、攻击发现和自动导入功能</a>，减少安全分析师和 SRE 每天在低价值任务上的疲劳，使他们能够专注于改进业务。Elastic 还能全面<a href="https://www.elastic.co/cn/guide/en/integrations/current/gcp_vertexai.html">监控 Vertex AI 的使用情况</a>，跟踪响应时间、令牌和资源等指标和日志，以确保最佳性能。我们共同管理从数据摄取、嵌入生成到混合搜索落地的整个 GenAI 生命周期，同时利用 LLM 驱动的行动确保 GenAI 工具的稳健可观测性和安全性。</p><h2>更多探索和尝试！</h2><p>您有兴趣试一试吗？该功能目前在您的 Google Cloud 项目中使用！</p><p>如果您还没有开始使用 Elastic Search AI Platform 并探索我们的功能，最简单的方法之一就是<a href="https://cloud.elastic.co/registration">免费试用 Elastic Cloud</a>或通过<a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace</a> 订阅。</p><p><em>本文章中描述的任何特性或功能的发布和时间均由 Elastic 自行决定。目前尚未提供的任何特性或功能可能无法按时交付或根本无法交付。Elastic、Elasticsearch 和相关标记是 Elasticsearch N.V. 在美国和其他国家的商标、徽标或注册商标。所有其他公司和产品名称均为其各自所有者的商标、徽标或注册商标。</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Valerio Arvizzigno]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt973959b32f2070d6/6a17e62a6317304ec1585a5e/d1f1c8860f1f0b989ad698a882f869de7284ab78-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[加速合并 HNSW 图表]]></title>
    <description><![CDATA[探索我们为降低构建多个 HNSW 图形的开销所做的工作，尤其是降低合并图形的成本。]]></description>
    <content:encoded><![CDATA[<p>过去，<a href="https://www.elastic.co/cn/search-labs/blog/multi-graph-vector-search">我们讨论过</a>搜索多个<a href="https://www.elastic.co/cn/search-labs/blog/hnsw-graph">HNSW 图表所</a>面临的一些挑战，以及我们是如何缓解这些挑战的。当时，我们提到了我们计划进行的一些进一步改进。这篇文章就是这项工作的结晶。</p><p>你可能会问，为什么要使用多图表呢？这是 Lucene 架构选择的副作用：不可变的段。与大多数建筑选择一样，有利也有弊。例如，我们最近对无服务器 Elasticsearch 进行了 GA。在这种情况下，我们从不可变分段中获得了非常显著的优势，包括高效的索引复制以及将索引和查询计算解耦并独立自动扩展的能力。对于矢量量化，分段合并让我们有机会更新参数，使其适应数据特征。按照这种思路，我们认为有机会测量数据特征和重新审视索引选择还有其他好处。</p><p>在这篇文章中，我们将讨论我们为大幅降低构建多个 HNSW 图形的开销，尤其是降低合并图形的成本所做的工作。</p><h3>背景</h3><p>为了保持可管理的分段数量，Lucene 会定期检查是否应该合并分段。这相当于检查当前分段数是否超过目标分段数，目标分段数由基本分段大小和合并策略决定。如果超过该计数，Lucene 会合并片段组，同时违反约束条件。这一过程在<a href="https://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html">其他地方有</a>详细描述。</p><p>Lucene 选择合并大小相似的数据段，因为这样可以实现写入放大的对数增长。就向量索引而言，写入放大是指向量插入图形的次数。Lucene 会尝试以大约 10 个为一组合并数据段。因此，向量插入图的次数大约为 {10}\left (\frac{n}{n_0} \right )次，其中是索引向量数，n 是预期的基本段向量数。由于写入量呈对数增长，即使是庞大的指数，写入放大率也只有个位数。不过，合并图形所花费的总时间与写入放大率成线性比例。</p><p>在合并 HNSW 图形时，我们已经进行了小幅优化：保留最大分段的图形，并将其他分段的向量插入其中。这就是上述 9/10 因素的原因。下面，我们将展示如何通过使用我们正在合并的所有图表中的信息来大幅提高性能。</p><h3>HNSW 图表合并</h3><p>此前，我们保留了最大的图形，并从其他图形中插入矢量，但忽略了包含这些矢量的图形。我们在下文中利用的关键见解是，我们丢弃的每个 HNSW 图形都包含了有关其所含向量的重要邻近性信息。我们希望利用这些信息来加快插入至少部分载体的速度。</p><p>我们重点讨论将较小的图插入较大的图 _l=(V _l, E _l)的问题，因为这是一个原子操作，我们可以用它来构建任何合并策略。</p><p>策略是找到的一个顶点子集，将其插入大图中。然后，我们利用这些顶点在小图中的连通性，加速插入剩余的顶点。在下文中，我们用和分别表示小图和大图中顶点的邻居。具体流程如下</p><p><code>MERGE-HNSW</code></p><p><code>Inputs </code><code> and </code></p><p><code>1</code><code>Find </code><code> to insert into </code><code> using COMPUTE-JOIN-SET</code>
<code>2</code><code>Insert each vertex </code><code> into </code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code><code>FAST-SEARCH-LAYER</code>
<code>7</code><code>SELECT-NEIGHBORS-HEURISTIC</code>
<code>8</code></p><p>我们使用下面讨论的程序来计算集合（第 1 行）。然后，我们使用标准的 HNSW 插入程序将中的每个顶点插入大图中（第 2 行）。对于我们尚未插入的每个顶点，我们都要找到已插入的邻接顶点及其在大图中的邻接顶点（第 4 行和第 5 行）。我们使用<code>FAST-SEARCH-LAYER</code> 程序（第 6 行）作为种子程序，从 HNSW<a href="https://arxiv.org/pdf/1603.09320">论文</a>（第 7 行）中找到<code>SELECT-NEIGHBORS-HEURISTIC</code> 的候选者。实际上，我们在<code>INSERT</code> 方法（论文中的算法 1）中替换了<code>SEARCH-LAYER</code> 来查找候选集，其他方面没有变化。最后，我们将刚刚插入的顶点添加到（第 8 行）。</p><p>很明显，要做到这一点，中的每个顶点都必须在至少有一个邻居。事实上，我们要求对于中的每个顶点，|J\capfor some M，即最大层连接性。我们观察到，在真实的 HNSW 图中，顶点度的分布相当广泛。下图显示了 Lucene HNSW 图表底层顶点度的典型累积密度函数。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc815e1a9c3bb0d06/6a17e178ec0f89308a5a6564/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" alt="HNSW 图形：顶点度分布示例" /><p>我们探讨了的固定值以及使其成为顶点度函数的方法。第二种选择会带来更快的速度，而对图形质量的影响却很小，因此采用了以下方案</p><p>请注意，根据定义，|N_s| 等于小图中顶点的度数。下限为 2 意味着我们将插入每个度数小于 2 的顶点。</p><p>一个简单的计数论证表明，如果我们仔细选择 ，我们只需要在 中直接插入大约具体来说，如果我们将一条图边的一个末端顶点恰好插入到，我们就会给这条图边着色。那么我们知道，对于 中 至少有  个 邻居，我们至少需要给 条边着色。此外，我们预计</p><p>这里，_U（left[N_s(U)|\right]）是小图中的平均顶点度。对于每个顶点u\我们最多为 |N_s条边着色。因此，我们期望着色的边的总数最多为 |J|\_U\left[|N_s(U)|\right].我们希望通过仔细选择，使着色的边数接近这一数字，因此，为了覆盖所有顶点，J| 需要满足以下条件</p><p>这意味着 {1}{4}|V_s|=\frac{1}{5} |V_s|。</p><p>如果<code>SEARCH-LAYER</code> 的运行时间占主导地位，这表明我们可以将合并时间最多提高。考虑到写入放大率的对数增长，这意味着即使对于非常大的索引，我们的构建时间通常也只比构建一个图形多一倍。</p><p>这种策略的风险在于会破坏图形质量。我们最初尝试使用无操作程序<code>FAST-SEARCH-LAYER</code> 。我们发现这降低了图表质量，以至于影响了作为延迟函数的召回率，尤其是在合并到单个片段时。然后，我们通过对图形的有限搜索，探索了各种替代方案。最终，最有效的选择是最简单的。使用<code>SEARCH-LAYER</code> ，但<code>ef_construction</code> 要低。通过这种参数设置，我们能够获得质量极佳的图形，同时还能将合并时间平均缩短 30% 多一点。</p><h3>计算连接集</h3><p>寻找一个好的连接集可以表述为一个 HNSW 图覆盖问题。贪婪启发式是一种简单有效的近似最优图覆盖的启发式。我们采用的方法是按增益递减的顺序逐个选取顶点添加到。增益定义如下</p><p>这里，表示向量在中的邻域数，是指示函数。增益包括我们添加到的顶点计数的变化，即 \max，因为我们添加了一个覆盖范围较小的顶点，从而更接近我们的目标。下图展示了中心橙色顶点的增益计算。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a353d56fa09af5f/6a17e17a2f4a5c33f7fa8825/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" alt="加入 HNSW 图中连接集 J 的顶点增益" /><p>我们为每个顶点 维护以下状态</p><ol><li><p>是否陈旧、</p></li><li><p>其增益</p></li><li><p>中相邻顶点的计数，用 表示、</p></li><li><p>范围为 [0,1] 的随机数，用于打破平局。</p></li></ol><p>计算连接集的伪代码如下。</p><p><code>COMPUTE-JOIN-SET</code></p><p><code>Inputs </code></p><p><code>1</code>
<code>2</code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code>
<code>7</code><code>while </code><code> do
8</code><code> maximum gain vertex in </code>
<code>9</code><code>Remove the state for </code><code> from </code>
<code>10</code><code>if </code><code> is not stale then</code>
<code>11</code>
<code>12</code>
<code>13</code><code>for </code><code> do</code>
<code>14</code><code>mark </code><code> as stale if </code>
<code>15</code><code>mark neighbors of </code><code> stale if </code><code>
16</code><code>
17</code><code>else
18</code><code>
19</code><code>if </code><code> then
20</code><code>
21</code><code>return </code></p><p>我们首先在第 1-5 行中初始化状态。</p><p>在主循环的每次迭代中，我们首先提取最大增益顶点（第 8 行），然后随机打破平局。在进行任何更改之前，我们需要检查顶点的增益是否过时。特别是，每次我们将一个顶点添加到，都会影响其他顶点的增益：</p><ol><li><p>由于它的所有邻居在中都多了一个邻居，因此它们的收益会发生变化（第 14 行）</p></li><li><p>如果它的任何一个邻居现在被完全覆盖，其所有邻居的收益都会发生变化（第 14-16 行）</p></li></ol><p>我们以一种懒散的方式重新计算增益，因此只有当我们想要将某个顶点插入，才会重新计算该顶点的增益（第 18-20 行）。由于增益只会减少，我们永远不会错过应该插入的顶点。</p><p>请注意，我们只需跟踪我们添加到的顶点的总增益，就能确定何时退出。此外，当 {exit} 时，至少有一个顶点的增益不为零，因此我们总能取得进展。</p><h3>实施结果</h3><p>我们在四个数据集上进行了实验，这四个数据集涵盖了我们支持的三种距离度量（欧氏、余弦和内积）：</p><ol><li><p>quora-E5-small：522931 个文档，384 个维度，使用余弦相似性、</p></li><li><p>cohere-wikipedia-v2：1M 文档，768 维度，使用余弦相似性、</p></li><li><p>gist：100 万个文档、960 个维度并使用欧氏距离，以及</p></li><li><p>cohere-wikipedia-v3：100 万文档，1024 维度，使用最大内积。</p></li></ol><p>对于每个数据集，我们都会评估两种量化水平：</p><ol><li><p>int8 - 每个维度使用一个 1 字节的整数，而</p></li><li><p>BBQ - 每个维度使用一个比特。</p></li></ol><p>最后，在每个实验中，我们在两个检索深度对搜索质量进行了评估，并在建立索引后和强制合并为单一片段后对搜索质量进行了检查。</p><p>总之，我们在索引和合并方面实现了持续的大幅提速，同时保持了图的质量，因此在所有情况下都能保持搜索性能。</p><h4>实验 1：int8 量化</h4><p>从基线到候选方案（建议的修改）的平均提速为</p><p>索引时间加速：<strong>1.</strong> 次</p><p>强制合并加速：<strong>1.</strong> 次</p><p>运行时间细分如下</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8422be122ef1a9c9/6a17e17bbe6086522e00467d/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" alt="基线和候选合并策略的索引和合并时间" /><p>为完整起见，确切时间为</p><p></p><p>索引</p><p></p><p>合并</p><p></p><p>数据集</p><p>底线</p><p>候选人</p><p>构建</p><p>候选人</p><p>quora-E5-small</p><p>112.41s</p><p>81.55s</p><p>113.81s</p><p>70.87s</p><p>wiki-cohere-v2</p><p>158.1s</p><p>122.95s</p><p>425.20s</p><p>239.28s</p><p>要领</p><p>141.82s</p><p>119.26s</p><p>536.07s</p><p>279.05s</p><p>wiki-cohere-v3</p><p>211.86s</p><p>168.22s</p><p>654.97s</p><p>414.12s</p><p>下面我们展示了候选方案（虚线）与基线在两种检索深度下的召回率与延迟对比图：多段索引的召回率@10 和召回率@100（我们的默认合并策略在索引所有矢量后的最终结果），以及强制合并为单段后的召回率与延迟对比图。曲线越高、越靠左越好，这意味着在较低的延迟条件下有更高的记忆率。</p><p>正如您所看到的，对于 Cohere v3 数据集来说，候选者的多分段指数更好，而对于所有其他数据集来说，候选者的多分段指数稍差，但几乎不相上下。合并为单一网段后，所有情况下的召回曲线几乎相同。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf674622469bf6a90/6a17e17dfbc5f874a84919c9/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" alt="建立索引后的 10 倍和 100 倍召回率与延迟时间对比" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde89f7e94ac823f4/6a17e17fe9ea876429a9c507/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" alt="合并为单一网段后，10 和 100 的召回率与延迟对比" /><h4>实验 2：烧烤量化</h4><p>从基准线到候选方案的平均加速度为</p><p>索引时间加速：<strong>1.</strong> 次</p><p>强制合并加速：<strong>1.</strong> 次</p><p>运行时间细分如下</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b0eb305222fa5c2/6a17e18125daab514f08a18c/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" alt="基线和候选合并策略的索引和合并时间" /><p>为完整起见，确切时间为</p><p></p><p>索引</p><p></p><p>合并</p><p></p><p>数据集</p><p>底线</p><p>候选人</p><p>构建</p><p>候选人</p><p>quora-E5-small</p><p>70.71s</p><p>58.25s</p><p>59.38s</p><p>40.15s</p><p>wiki-cohere-v2</p><p>203.08s</p><p>142.27s</p><p>107.27s</p><p>85.68s</p><p>要领</p><p>110.35s</p><p>105.52s</p><p>323.66s</p><p>202.2s</p><p>wiki-cohere-v3</p><p>313.43s</p><p>190.63s</p><p>165.98s</p><p>159.95s</p><p>对于多分段索引，候选者在几乎所有数据集上都更胜一筹，但 cohere v2 除外，基线略胜一筹。就单段指数而言，所有情况下的召回曲线几乎相同。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb12174abeadbcdd1/6a17e1822f4a5c4cc2fa8829/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" alt="建立索引后的 10 倍和 100 倍召回率与延迟时间对比" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1b88d4f7fec9905/6a17e184414c64a8de9450b4/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" alt="恢复 @10 和 @100 与合并为单一分段后的延迟对比" /><h3>结论</h3><p>本博客中讨论的算法将在即将发布的 Lucene 10.2 以及基于该算法的 Elasticsearch 版本中提供。用户将能利用这些新版本中改进的合并性能和缩短的索引构建时间。这一变更是我们为使 Lucene 和 Elasticsearch 在矢量和混合搜索方面快速高效而不断努力的一部分。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Thomas Veasey,Mayya Sharipova]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fca6a01d0541cfb/6a17e187033c8d46486bb0e1/49a6c880f5dedd0fa502ece5be124824ee218cc0-1792x1024.png" length="0" type="image/png"/>
    <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[扩展 Elasticsearch 中的后期交互模型 — 第 2 部分]]></title>
    <description><![CDATA[本文探讨了为大规模生产工作负载优化后期交互向量的技术，例如降低磁盘占用与提升计算效率。]]></description>
    <content:encoded><![CDATA[<p>在我们<a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">之前关于 ColPali 的博文</a>中，我们探讨了如何使用 Elasticsearch 构建视觉搜索应用。我们主要关注了 ColPali 这类模型带来的应用价值，但相较于使用 E5 等双编码器的向量搜索，它们在性能上存在不足。</p><p>基于<a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">第 1 部分</a>的示例，本文将探讨如何运用多种技术及 Elasticsearch 强大的向量搜索工具集，使后期交互向量能够胜任大规模生产工作负载。</p><p>如需获取完整代码示例，请访问 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/colpali">GitHub</a>。</p><h2>后期交互模型的挑战</h2><p>ColPali 会为我们索引中的每个文档页面生成超过 1000 个向量。</p><p>这导致在处理后期交互向量时面临两大挑战：</p><ol><li><p>磁盘空间：将所有向量存储到磁盘会占用大量存储空间，在大规模使用时成本高昂。</p></li><li><p>计算开销：使用 <code>maxSimDotProduct()</code> 比较函数对文档排序时，需要将每个文档的所有向量与查询的 N 个向量逐一对比。</p></li></ol><p>接下来，我们看看解决这些问题的一些技巧。</p><h2>优化后期交互模型的技术</h2><h3>位向量</h3><p>为了减少磁盘占用，我们可以将向量压缩为二值（比特）向量。我们可以使用一个简单的 Python 函数将多向量转换为二值向量：</p>def to_bit_vectors(embeddings: list) -&gt; list:
    return [
        np.packbits(np.where(np.array(embedding) &gt; 0, 1, 0))
        .astype(np.int8)
        .tobytes()
        .hex()
        for embedding in embeddings
    ]<p>函数的核心思想很简单：大于 0 的值量化为 1，小于 0 的值量化为 0。这样会得到一个由 0 和 1 组成的数组，随后我们将其转换为代表二值向量的十六进制字符串。</p><p>对于我们的索引映射，我们将<code>element_type</code>参数设置为<code>bit</code>：</p>mappings = {
    "mappings": {
        "properties": {
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>将所有新的二值向量写入索引后，我们可以使用以下代码对其进行排序：</p>query = "What do companies use for recruiting?"
query_vector = to_bit_vectors(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimInvHamming(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcaf0c8f999d68701/6a17f46696142a6f46eb1c56/51b989446e4099745971e1eac27d147a78d13e0a-1600x480.png" alt="" /><p>这以牺牲少量精度为代价，使我们能够利用汉明距离 (<code>maxSimInvHamming(...)</code>) 进行比较，而汉明距离可以利用位掩码、SIMD 等技术进行优化。如需了解更多信息，请<a href="https://www.elastic.co/search-labs/blog/bit-vectors-in-elasticsearch">阅读我们这篇关于二值向量与汉明距离的博文</a>。</p><p>或者，我们可以不将查询向量转换为位向量，而是使用全保真度的后期交互向量进行搜索：</p>query = "What do companies use for recruiting?"
query_vector = create_col_pali_query_vectors(query)
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2fa6c89fb451b6e0/6a17f468af47b65da9cde0d9/89f3c795c6dc44b2f7d46801320288316b47e1b2-1600x488.png" alt="使用二值向量优化后期交互模型的效果" /><p>这将使用一种非对称相似度函数来比较我们的向量。</p><p></p><p>让我们来考虑两个比特向量之间的标准汉明距离。假设有一个文档向量 <em>D：</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4b6ece1ec4dedac/6a17f4694b055d248143234e/366a70a23e37d403788327b7aefc873fd4482f5e-1235x86.png" alt="" /><p>和一个查询向量 <em>Q：</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7721df9c8f0d84f1/6a17f46b3e03d77cf54f2dcb/978ec31e0afbc0eaebf548a015b628c0cad84625-1247x84.png" alt="" /><p></p><p>简单的二值量化会将向量 <em>D</em> 转换为 <code>10101101</code>，将 <em>Q</em> 转换为 <code>11111011</code>。计算汉明距离需要直接的位运算，速度极快。本例中，汉明距离为 <code>01010110</code>，其中 1 的个数为 4。因此，评分即为汉明距离的倒数。请记住，更相似的向量汉明距离更小，取倒数后能让更相似的向量得分更高。具体到这里，得分将是 1/4 = <code>0.25</code>。</p><p>然而，请注意我们丢失了每个维度的幅度信息。一个 <code>1</code> 就只是一个 <code>1</code>。因此，对于 <em>Q</em> 向量，<code>0.01</code> 和 <code>0.79</code> 之间的差异就消失了。由于我们只是根据 <code>&gt;0</code> 进行量化，这里可以用一个小技巧：不量化查询向量 Q。虽然这无法利用极快的位运算，但由于文档向量 D 仍是量化的，因此仍能保持较低的存储成本。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted50315518e2d599/6a17f46c414c6463709452d3/508e8498ba969534d2a0131d431c75735fc27cb9-1399x611.png" alt="" /><p>简而言之，这保留了 <em>Q</em> 向量中的信息，从而提高了距离估计的质量，同时保持了低存储开销。</p><p>使用二值向量可以为我们显著节省磁盘空间并降低查询时的计算负载。但我们还能做得更多。</p><h3>平均向量</h3><p>要在数十万甚至更多文档中扩展搜索，仅靠二值向量带来的性能提升可能还不够。为了支撑这类规模的工作负载，我们需要利用 Elasticsearch 为向量搜索设计的 HNSW 索引结构。</p><p>ColPali 为每个文档生成约一千个向量，数量过多，无法全部加入 HNSW 图。因此，我们需要减少向量数量。为此，我们可以对 ColPali 为图像生成的所有文档向量取平均值，从而创建代表文档含义的单一向量表示。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc053425fbfcf8de8/6a17f46f148009faf1b488aa/7c3b4dffb70bd95f67deb35f8c01e73c5286c2ab-1476x1102.png" alt="对全部后期交互向量取平均" /><p>目前，这无法在 Elasticsearch 内部直接完成，我们需要在将数据摄入 Elasticsearch 之前对向量进行预处理。 </p><p>这可以通过 Logstash 或 Ingest 管道实现，但这里我们将使用一个简单的 Python 函数：</p>def to_avg_vector(vectors):
    vectors_array = np.array(vectors)
    
    avg_vector = np.mean(vectors_array, axis=0)
    
    norm = np.linalg.norm(avg_vector)
    if norm &gt; 0:
        normalized_avg_vector = avg_vector / norm
    else:
        normalized_avg_vector = avg_vector

    return normalized_avg_vector.tolist()<p>我们同时对向量进行归一化，以便使用点积相似度。</p><p>将所有 ColPali 向量转换为平均向量后，即可将其索引到 dense_vector 字段中：</p>mappings = {
    "mappings": {
        "properties": {
            "avg_vector": {
                "type": "dense_vector",
                "dims": 128,
                "index": True,
                "similarity": "dot_product"
            },
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>需要注意的是，这可能会增加总磁盘使用量，因为我们在保存后期交互向量之外还存储了额外信息。同时，我们需要额外的内存来存放 HNSW 图，但这使得我们能够在数十亿向量规模上进行搜索。为了降低内存占用，我们可以利用备受欢迎的 <a href="https://www.elastic.co/search-labs/blog/optimized-scalar-quantization-elasticsearch">BBQ 特性</a>。如此一来，我们便能在原本无法处理的海量数据集上获得快速的搜索结果。</p><p>现在，只需使用 kNN 查询即可搜索到最相关的文档。</p>query = "What do companies use for recruiting?"
query_vector = to_avg_vector(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "knn": {
        "field": "avg_vector",
        "query_vector": query_vector,
        "k": 10,
        "num_candidates": 100
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4ac6b7fd444f935/6a17f471a29299d76ad02dc0/a500f9907020f032c3b1c7a48ed8c759dc2a1dd4-1600x498.png" alt="" /><p>遗憾的是，原先的最佳匹配项排名跌落至第 3 位。</p><p>为了解决这个问题，我们可以采用多阶段检索策略。在第一阶段，使用 kNN 查询在数百万文档中为查询筛选出最佳候选集。在第二阶段，仅使用精度更高的 ColPali 后期交互向量对前 k 个（例如：10 个）候选进行重排序。</p>query = "What do companies use for recruiting?"
col_pali_vector = create_col_pali_query_vectors(query)
avg_vector = to_avg_vector(col_pali_vector)
es_query = {
  "_source": False,
  "retriever": {
    "rescorer": {
      "retriever": {
        "knn": {
          "field": "avg_vector",
          "query_vector": avg_vector,
          "k": 10,
          "num_candidates": 100
        }
      },
      "rescore": {
        "window_size": 10,
        "query": {
          "rescore_query": {
            "script_score": {
              "query": {
                "match_all": {}
              },
              "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                  "query_vector": col_pali_vector
                }
              }
            }
          }
        }
      }
    }
  },
  "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt265edd5f37be40b7/6a17f4733e9e45583dba15e6/797f490860af87fcf29f34668a5c4511419c6fd0-1600x501.png" alt="使用平均向量优化后期交互模型的结果" /><p>这里，我们使用 8.18 版本引入的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.18/retriever.html#rescorer-retriever">重打分检索器</a>来对结果进行重排序。重打分后，可以看到最佳匹配项又回到了第一位。 </p><p>注意：在实际生产应用中，可以设置比 10 大得多的 k 值，因为 maxSim 函数相对而言仍有不错的性能。</p><h3>令牌池化</h3><p>令牌池化通过聚合冗余信息（例如白色背景的图像块）来减少多向量嵌入的序列长度。该技术在减少嵌入数量的同时，保留了文档页面的绝大部分信息。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d643e6ac908f640/6a17f4754b055d3783432352/09eae0b768f4b450e555d7e53e57561bd52de2c0-1412x1056.png" alt="令牌池化以优化后期交互模型" /><p>令牌池化的工作原理是，使用聚类算法将文档内相似的令牌嵌入分组到聚类中。然后，计算每个聚类中所有向量的均值，以生成一个聚合的向量表示。这个聚合向量将取代该组中原始的多个令牌向量，从而在不显著损失文档信息的前提下减少向量总数。</p><p>ColPali 论文建议对大多数数据集使用初始池化因子 3，这可以在保持 97.8% 原始性能的同时，将向量总数减少 66.7%。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d78a15c6944a1ea/6a17f477414c64a2929452d9/343cc9eaf54af8c7a125d4115838f3c7d5659de0-1600x1007.png" alt="用于优化后期交互模型的池化因子" /><p>但需要注意：对于包含文本密集、空白极少的“Shift”数据集，随着池化因子增大，其性能会迅速下降。</p><p>要生成池化后的向量，我们可以使用 colpali_engine 库：</p>from colpali_engine.compression.token_pooling import HierarchicalTokenPooler

pooler = HierarchicalTokenPooler(pool_factor=3) # test on your data for a good pool_factor

def pool_vectors(embedding: list) -&gt; list:
    tensor = torch.tensor(embedding).unsqueeze(0)
    pooled = pooler.pool_embeddings(tensor)
    return pooled.squeeze(0).tolist()<p>现在我们得到了一个维度减少约 66.7% 的向量。我们可以照常对其进行索引，并使用 <code>maxSimDotProduct()</code> 函数进行搜索。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc460c14031814ede/6a17f4794202292bf629f72d/2412c5db7d79a590b96d42fe01f140c42f010612-1600x481.png" alt="后期交互模型效果评估" /><p>我们能够获得良好的搜索结果，代价是结果精度有轻微损失。</p><p>提示：使用更高的 pool_factor（如 100-200），可以在平均向量方案和此处讨论的方案之间取得折中。当每个文档仅有 5-10 个向量时，将它们索引到嵌套字段中并利用 HNSW 索引就变得可行。</p><h2>交叉编码器 vs. 后期交互模型 vs. 双编码器</h2><p>根据我们目前的了解，与其他 AI 检索技术相比，如 ColPali 或 ColBERT 这类后期交互模型定位如何？</p><p>虽然 maxSim 函数相比交叉编码器计算成本更低，但仍比使用双编码器的向量搜索需要多得多的比较和计算（后者每个查询-文档对仅比较两个向量）。 </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbfd97f3bba3e5b17/6a17f47be31791b0052d5943/75e1fc9e601aa7a6e88f137565c1919166c7a71c-1480x458.png" alt="交叉编码器 vs. 后期交互模型 vs. 双编码器" /><p>因此，对于后期交互模型，我们通常建议仅将其用于对前 k 个搜索结果进行重排序。这也体现在其字段类型的名称上：rank_vectors。</p><p>那么交叉编码器呢？后期交互模型是否因为查询时执行成本更低而更优？和往常一样，答案是：视情况而定。交叉编码器通常能产生更高质量的搜索结果，但它们需要大量的计算，因为查询-文档对需要完整地通过 Transformer 模型。它们的优势在于无需对向量进行索引，可以无状态运行。这带来以下特点：</p><ul><li><p>使用更少的磁盘空间</p></li><li><p>更简单的系统</p></li><li><p>搜索结果质量更高</p></li><li><p>延迟更高，因此无法进行深层重排序</p></li></ul><p>另一方面，后期交互模型可以将部分计算转移到索引阶段，从而降低查询成本。我们付出的代价是必须索引向量，这使得索引流水线更复杂，并且需要更多磁盘空间来存储这些向量。</p><p>具体到 ColPali，由于图像包含大量数据，从中分析信息的成本非常高。在这种情况下，权衡的天平倾向于使用 ColPali 这类后期交互模型，因为在查询时评估这些信息将过于消耗资源且速度太慢。 </p><p>对于像 ColBERT 这样处理文本数据（与大多数交叉编码器，如 elastic-rerank-v1，类似）的后期交互模型，决策可能更倾向于使用交叉编码器，以受益于其节省磁盘和架构简单的特点。</p><p>我们鼓励您根据自身的使用场景权衡这些利弊，并尝试 Elasticsearch 提供的各种工具，以构建最佳的搜索应用。</p><h2>结论</h2><p>在本博客中，我们探讨了多种优化后期交互模型（如 ColPali）的技术，以支持在 Elasticsearch 中进行大规模向量搜索。虽然后期交互模型在检索效率和排序质量之间提供了良好的平衡，但也带来了存储和计算方面的挑战。</p><p>为了应对这些挑战，我们研究了以下技术：</p><ul><li><p><strong>二值向量</strong>：可显著减少磁盘空间，同时利用汉明距离或非对称最大相似度等高效相似度计算。</p></li><li><p><strong>平均向量</strong>：将多个嵌入压缩为单一的稠密表示，从而实现利用 HNSW 索引的高效检索。</p></li><li><p><strong>令牌池化</strong>：智能合并冗余的嵌入，同时保持语义完整性，降低查询时的计算开销。</p></li></ul><p>Elasticsearch 提供了一套强大的工具集，让您可以根据需求定制和优化搜索应用。无论您优先考虑检索速度、排序质量还是存储效率，这些工具和技术都能帮助您在实际应用中平衡性能与质量。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</guid>
    <category><![CDATA[相关性]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Peter Straßer,Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt97a536033e6b0a56/6a17f47dfbc5f88c86491c0d/c780b78a07573f2df1cfef8b29a7109f839b0ab3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 20 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用英伟达™（NVIDIA®）探索 Elasticsearch 中的 GPU 加速矢量搜索：第一章]]></title>
    <description><![CDATA[这项合作由英伟达™（NVIDIA®）cuVS 提供支持，旨在为开发人员在 Elasticsearch 中进行矢量搜索提供 GPU 加速。]]></description>
    <content:encoded><![CDATA[<p>我们 Elastic Engineering 组织一直在忙于优化矢量数据库的性能。我们的使命：让 Lucene 和 Elasticsearch 成为最好的矢量数据库。通过硬件加速<a href="https://www.elastic.co/cn/blog/accelerating-vector-search-simd-instructions">CPU SIMD 指令</a>，引入新的矢量数据压缩创新技术<a href="https://www.elastic.co/cn/search-labs/blog/better-binary-quantization-lucene-elasticsearch">（更好的二进制量化，又称 BBQ</a>），然后通过更新 BBQ 算法方法以获得更多优势，并<a href="https://www.elastic.co/cn/search-labs/blog/filtered-hnsw-knn-search">使过滤 HNSW 更快</a>，从而超越了预期。我们正在打造一个更快、更好、更高效（呃？）在开发人员解决这些 RAG-gedy 问题时，为他们提供矢量数据库！</p><p>作为 "不遗余力地提高效率 "这一使命的一部分，我们正在利用这些奇特的计算机芯片探索加速机会，你可能听说过英伟达™（NVIDIA®）GPU！(说真的，你没听说过吗？）</p><p>在执着于性能时，我们有几个问题空间需要探索--如何索引指数级增长的数据，如何从中获取洞察力，以及如何在涉及 ML 模型时做到这一点。有了 GPU，你就能把所有的优势都发挥出来。</p><p>在本篇文章中，我们将深入探讨与英伟达™（NVIDIA®）矢量搜索团队的合作，探索 Elasticsearch 中的 GPU 加速矢量搜索。这项工作为开发人员在实际的 Elasticsearch 应用程序中混合使用 GPU 和 CPU 的用例铺平了道路。激动人心的时刻</p><h2>Elasticsearch GPU</h2><p>我们很高兴地与大家分享，Elasticsearch 工程团队正在帮助开发人员构建开源的 cuVS Java API 体验，该体验为矢量搜索算法提供了绑定。这项工作利用了我们以前在巴拿马 FFI 方面的经验。Elasticsearch 和 Apache Lucene 在索引过程中使用英伟达 cuVS API 构建图形。好吧，我们跳到前面，让我们倒退一下。</p><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a> 是一个开源 C++ 库，是此次合作的核心。它旨在通过提供更高的吞吐量、更低的延迟和更快的索引构建时间，为矢量搜索提供 GPU 加速。但 Elasticsearch 和 Apache Lucene 都是用 Java 编写的，这怎么行呢？</p><p>进入<a href="https://github.com/SearchScale/lucene-cuvs">lucene-cuvs</a>和 Elastic-NVIDIA-SearchScale 合作，将其引入 Lucene 生态系统，在 Elasticsearch 中探索 GPU 加速的矢量搜索。在最近发布的英伟达 cuVS 25.02 中，我们为 cuVS 添加了 Java API。新的应用程序接口是试验性的，将继续发展，但目前已经可以使用。也许有人会问：Java 对本地函数的调用不是很慢吗？现在不一样了！我们正在使用新的<a href="https://openjdk.org/projects/panama/">巴拿马 FFI</a>（外来函数接口）进行绑定，它将 Java 与本地向下调用的开销降至最低。</p><p>我们<a href="https://www.elastic.co/cn/search-labs/blog/lucene-and-java-moving-forward-together"> 在 Elasticsearch 和 Lucene 中 使用 巴拿马 FFI</a> 已经有一段时间了。太棒了但是......总是有 "但是 "的，不是吗？FFI 在跨 Java 版本的可用性方面存在挑战。我们将 cuVS 应用程序接口编译到 Java 21，并将实现封装在一个针对 Java 22 的多版本 jar 中，从而克服了这一问题。这样就可以直接在 Lucene 和 Elasticsearch 中使用 cuVS Java。</p><p>好了，既然我们已经有了 cuVS Java API，还需要什么呢？</p><h2>两种 CPU 算法的故事</h2><p>Elasticsearch 支持用于可扩展近似 KNN 搜索的<a href="https://arxiv.org/abs/1603.09320">HNSW 算法</a>。不过，为了最大限度地利用 GPU，我们使用了另一种算法<a href="https://arxiv.org/pdf/2308.15136"> CAGRA [</a><a href="https://arxiv.org/pdf/2308.15136"><strong> CUDA</strong></a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"><em>ANN</em></a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136">GRAph]</a> ，它是专门为 GPU 提供的高并行性而设计的。</p><p>在了解如何添加对 CAGRA 的支持之前，我们先来看看 Elasticsearch 和 Lucene 是如何通过 "编解码器格式 "访问索引数据的。这包括</p><ol><li><p>磁盘表示法、</p></li><li><p>读写数据的接口、</p></li><li><p>以及处理 Lucene 基于段的架构的机制。</p></li></ol><p>我们正在实施一种新的 KNN（k-近邻）<a href="https://lucene.apache.org/core/10_1_0/core/org/apache/lucene/codecs/KnnVectorsFormat.html">向量格式</a>，该格式内部使用 cuVS Java API 在 GPU 上进行索引和搜索。从这里开始，我们通过 Elasticsearch 的映射将此编解码器类型 "剽窃 "到索引中的字段类型。因此，无论后备索引使用的是 CAGRA 还是 HNSW 图形，现有的 KNN 查询都能继续工作。当然，这忽略了许多细节，我们计划在今后的博客中加以介绍。以下是 GPU 加速 Elasticsearch 的高级架构。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6197b631a34f8b6/6a170b0da6c2b9e60ce7970b/be6b7356c03df4dee7230625c2c9af3b019f93be-756x510.png" alt="" /><p>这种新的编解码器格式默认为 CAGRA。不过，它也支持将 CAGRA 图形转换为 HNSW 图形，以便在 CPU 上进行搜索。</p><h2>在 GPU 上进行索引和搜索：做出一些 "核心 "决定</h2><p>Elasticsearch Serverless 的无状态<a href="https://www.elastic.co/cn/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">架构</a>将索引和搜索分离开来，因此现在有了明确的职责划分。我们选择最佳的硬件配置文件来履行这些独立的职责。</p><p>我们预计用户会考虑两种主要的部署策略：</p><ol><li><p>在 GPU 上进行索引和搜索：在索引过程中，建立一个 CAGRA 图，并在搜索过程中使用它--这在需要极低延迟的搜索时非常理想。</p></li><li><p>在 GPU 上索引，在 CPU 上搜索：在索引过程中，构建 CAGRA 图并将其转换为 HNSW 图。HNSW 图形存储在索引中，随后可用于 CPU 的搜索。</p></li></ol><p>这种灵活性提供了不同的部署模式，在成本和性能之间进行了权衡。例如，索引服务可以使用 GPU 及时高效地构建和合并图形，同时使用性能较低的 CPU 进行搜索。</p><h2>以下是在 Elasticsearch 中使用 GPU 加速矢量搜索的计划</h2><p>我们期待为用户带来性能提升和部署策略的灵活性，提供各种旋钮来平衡成本和性能。<a href="https://www.nvidia.com/gtc/session-catalog/?tab.catalogallsessionstab=16566177511100015Kus&amp;search=Lucene#/">下面是 NVIDIA GTC 2025 会议</a>对这项工作的详细介绍。</p><p>我们要感谢 NVIDIA 和 SearchScale 工程团队的出色合作。在下一篇博客中，我们将更深入地探讨实施细节和性能分析。戴上好奇帽🎩 ！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt298e839e708ca11c/6a170b0fb339d560c2769fc2/38bc0377a6adce7eae0099f61902fdbbe644eb4a-1440x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[矢量搜索简介]]></title>
    <description><![CDATA[本文是三篇系列文章中的第一篇，将深入探讨矢量搜索（也称语义搜索）的复杂性以及如何在 Elasticsearch 中实现。]]></description>
    <content:encoded><![CDATA[<p>本文是三篇系列文章中的第一篇，将深入探讨矢量搜索（也称语义搜索）的复杂性以及如何在 Elasticsearch 中实现。</p><p>第一部分重点介绍嵌入向量的基础知识以及向量搜索的工作原理。</p><p>有了在第一篇文章中学到的所有知识，<a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">第二部分</a>将指导你如何在 Elasticsearch 中设置矢量搜索。</p><p>在<a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">第三部分</a>中，我们将利用在前两部分中学到的知识，在此基础上深入研究如何在 Elasticsearch 中创建强大的混合搜索查询。</p><p>在进入本文的正题之前，让我们回顾一下向量的历史，向量是语义搜索的一个关键概念。</p><h2>病媒并不新鲜</h2><p>相信每个人都会同意，自 2022 年 11 月 ChatGPT 问世以来，没有一天不听到或读到关于 "矢量搜索 "的消息。它无处不在，如此普遍，以至于我们经常会觉得这是一项刚刚问世的新尖端技术，但事实上，这项技术已经存在了六十多年！对这一主题的研究始于 20 世纪 60 年代中期，1978 年，信息检索专家杰拉德-萨尔顿和他在康奈尔大学的同事发表了第一批研究论文。萨尔顿在密集和稀疏向量模型方面的研究成果是现代向量搜索技术的基础。</p><p>在过去的 20 年里，基于他的研究成果，许多不同的<a href="https://db-engines.com/en/ranking/vector+dbms/all">矢量数据库管理系统</a>相继问世并推向市场。其中包括由 Apache Lucene 项目提供支持的 Elasticsearch，该项目于 2019 年开始<a href="https://issues.apache.org/jira/browse/LUCENE-9004">致力于矢量搜索</a>。</p><p>矢量现在无处不在，非常普遍，因此在使用矢量之前，首先要充分掌握其基本理论和内部工作原理。在深入探讨之前，让我们先快速回顾一下词法搜索和矢量搜索之间的区别，以便更好地理解它们的不同之处以及如何相互补充。</p><h2>矢量搜索与词法搜索</h2><p>介绍向量搜索的一个简单方法是将其与你可能已经习惯的更传统的词法搜索进行比较。矢量搜索（通常也称为语义搜索）和词法搜索的工作原理截然不同。词法搜索是我们在 Elasticsearch 中使用多年的一种搜索方式。简而言之，它并不试图理解所索引和查询内容的真正含义，而是努力将用户在查询中键入的单词或其变体（如词干、同义词等）的字面意思与之前已通过相似性算法（如 TF-IDF）索引到数据库中的所有字面意思进行<strong>词法</strong>匹配。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbb8e150865a5ec8/6a17e0c725daab50f408a16e/a4f3eba19599e5330e9b0d29ecdbbeac211cdab0-1600x583.png" alt="词法搜索示例" /><p>我们可以看到，左上方的三份文档已经进行了标记化和分析。然后，将得到的术语编入倒排索引，该索引只是将分析术语映射到包含这些术语的文档 ID。请注意，所有术语都只出现一次，没有任何文件共享这些术语。搜索 "优秀的德国教师 "时，三份文档的匹配得分各不相同，但没有一份真正符合查询的真正含义。</p><p>如下图 2 所示，在处理多义词或同形词（即拼写相同但<strong>含义不同的词</strong>（right、palm、bat、mean 等））时，情况会变得更加棘手。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea0cc829ff2c8029/6a17e0c92f4a5c8107fa8811/ebb9cc1be03475bbf68269a8dcea5f08bcda0b64-1594x754.png" alt="用词法搜索查找同形词" /><p>搜索<em>"我不对"</em>，返回的文档与第一个返回结果的含义完全相反。如果搜索完全相同的术语，但为了产生不同的含义而对它们进行不同的排序，例如<em>"向右转 "</em>和<em>"</em> 右转"，则会得到完全相同的结果（即第三份文档 "向右转"）。当然，我们的查询过于简化，没有使用短语匹配等更高级的查询，但这有助于说明词法搜索并不了解索引内容和搜索内容背后的真正含义。如果还不清楚，也不要着急，我们将在第三篇文章中再次讨论这个例子，看看矢量搜索在这种情况下如何提供帮助。</p><p>对词法搜索来说，当你能够控制如何为<strong>结构化</strong>数据建立索引（如映射、文本分析、摄取管道等）以及如何制作查询（如巧妙制作的 DSL 查询、查询词分析等）时，你就能利用词法搜索引擎创造奇迹，这是毋庸置疑的！Elasticsearch 的词法搜索能力令人惊叹。在过去的几年里，它所取得的成就以及它对词法搜索领域的普及和改进确实令人瞩目。</p><p>但是，如果要为需要提出自由文本问题的用户提供<a href="https://www.elastic.co/what-is/unstructured-data"><strong>非结构化</strong></a><a href="https://www.elastic.co/what-is/unstructured-data"> 数据</a>（如图像、视频、音频、原始文本等）的查询支持，词法搜索就显得力不从心了。此外，有时查询的内容甚至不是文本，而可能是图片，我们很快就会看到。在这种情况下，词法搜索无法胜任的主要原因是，非结构化数据既无法像结构化数据那样被索引，也无法像结构化数据那样被查询。在处理非结构化数据时，<strong>语义学</strong>开始发挥作用。语义学是什么意思？很简单，就是这个意思！</p><p>让我们以简单的图像搜索引擎（如谷歌图像搜索或 Lens）为例。您只需拖放一张图片，谷歌语义搜索引擎就会找到并返回与您查询的图片最相似的图片。在下图 3 中，我们可以看到左侧是一张德国牧羊犬的图片，右侧是所有已检索到的相似图片，第一个结果是与所提供图片相同的图片（即最相似的图片）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e0a0fb3c300537a/6a17e0cbe8fbce69d13a185d/c0dff24d4379141abd1038c9c4b5577fd2dca802-1600x657.png" alt="语义搜索示例：搜索图片" /><p>尽管这对我们人类来说听起来简单且合乎逻辑，但对计算机来说却是另一回事。这正是矢量搜索所能实现和帮助实现的。矢量搜索所释放的能量是巨大的，全世界最近都见证了这一点。现在，让我们揭开引擎盖，看看下面隐藏着什么。</p><h2>嵌入向量</h2><p>正如我们在前面所看到的，通过词法搜索引擎，文本等结构化数据可以很容易地被标记为在搜索时可以匹配的术语，而不管这些术语的真正含义是什么。然而，非结构化数据可以采用不同的形式，例如大型二进制对象（图像、视频、音频等），而且完全不适合采用相同的标记化流程。此外，语义搜索的整个目的就是为数据编制索引，以便根据数据所代表的意义进行搜索。我们如何做到这一点？答案就在两个字里：<strong>机器学习</strong>！或者更准确地说，是深度学习！</p><p><strong>深度学习</strong>是机器学习的一个特定领域，它依赖于基于人工神经网络的多层处理模型，可以逐步提取数据的真实含义。这些神经网络模型的工作方式在很大程度上受到了人脑的启发。下图 4 显示了神经网络的外观，包括输入层、输出层和多个隐藏层：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ca5fd8f0e61d939/6a17e0cd7f6f152a67c09a53/aeea3bab3c29e1c591a9c9082f2e73e7d3990ef1-1600x1116.png" alt="向量搜索中的神经网络层" /><p>神经网络的真正功能在于，它能够将单个非结构化数据转化为浮点数值序列，这些浮点数值被称为<strong>嵌入向量</strong>或简称<strong>嵌入</strong>。作为人类，只要将向量在二维或三维空间中形象化，我们就能很好地理解向量是什么。矢量的每个分量代表二维 x-y 平面或三维 x-y-z 空间中的一个坐标。</p><p>然而，神经网络模型所依赖的嵌入向量可能有几百甚至上千个维度，而且仅仅代表多维空间中的一个点。每个向量维度代表非结构化数据的一个<strong>特征</strong>或特性。让我们用一个将图像转化为 2048 维嵌入向量的深度学习模型来说明这一点。该模型将把我们在图 3 中使用的德国牧羊犬图片转化为下表所示的嵌入向量。请注意，我们只显示了前三个元素和后三个元素，但表格中还有 2,042 个列/维度。</p><p></p><p>is_red</p><p>是狗</p><p>blue_sky</p><p>…</p><p>no_gras</p><p>德国牧羊犬</p><p>is_tree</p><p>德国牧羊犬 嵌入</p><p>0.0121</p><p>0.9572</p><p>0.8735</p><p>…</p><p>0.1198</p><p>0.9712</p><p>0.0512</p><p>每一列是模型的一个维度，代表底层神经网络试图建模的一个特征或特性。根据输入与 2048 个维度中每个维度的相似程度，对模型的每个输入进行特征描述。因此，嵌入向量中每个元素的值表示该输入与特定维度的<strong>相似度</strong>。在这个例子中，我们可以看到模型检测到狗和德国牧羊犬之间的高度相似性，以及一些蓝天的存在。</p><p>与词法搜索相比，矢量搜索能让我们更好地了解非结构化数据与模型支持的每个维度的<em>相似</em>程度。因此，嵌入向量可以作为非结构化数据的绝佳语义表示。</p><h2>秘制酱汁</h2><p>既然我们已经知道深度学习神经网络是如何将非结构化数据切成嵌入向量的，这些嵌入向量可以从多个维度捕捉数据的相似性，那么我们就需要了解这些向量的匹配是如何进行的。事实证明，答案非常简单。相互<strong>接近</strong>的嵌入向量代表<strong>语义相似的</strong>数据片段。因此，当我们查询矢量数据库时，搜索输入（图像、文本等）首先会使用索引所有非结构化数据时使用的相同模型转化为嵌入矢量，最终目标是找到与该查询矢量<strong>最近的相邻矢量</strong>。因此，我们需要做的就是找出如何测量查询向量与数据库中索引的所有现有向量之间的 "距离 "或 "相似性"，仅此而已。</p><h3>距离和相似性</h3><p>幸运的是，由于有了向量算术，测量两个向量之间的距离是一个很容易解决的问题。因此，让我们来看看现代矢量搜索数据库（如 Elasticsearch）支持的最常用的距离和相似性函数。警告，前方有数学题！</p><h4>L1 距离</h4><p>两个向量 x 和 y 的 L1 距离（也称为曼哈顿距离）是通过将它们所有元素的成对绝对差值相加来测量的。显然，距离 d 越小，两个矢量就越接近。计算公式非常简单，如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df2e9bc20883491/6a17e0ce033c8d006a6bb0bf/2b17bcedfbedde61117a3e7970af55bc62318c6c-312x102.png" alt="向量搜索中的 L1 距离公式" /><p>下图 5 可以直观地说明 L1 距离：</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb90a33e6b1cbe00b/6a17e0d0414c64eb76945096/52075441892151536ed216081817a8e852566daa-474x464.png" alt="可视化两个向量之间的 L1 距离" /><p>假设有两个向量 x 和 y，例如 x = (1, 2) 和 y = (4, 3)，那么这两个向量的 L1 距离为 | 1 - 4 | + | 2 - 3 | = 4。</p><h4>L2 距离</h4><p>测量两个向量 x 和 y 的 L2 距离（也称欧几里得距离）的方法是，首先求出它们所有元素成对差值的平方和，然后取其平方根。它基本上是两点之间最短的路径（也称为斜边）。与 L1 类似，距离 d 越小，两个向量越接近：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae9b63fb593ae225/6a17e0d1af47b68379cddea8/b7675aa41f4f381e954c21dacd23a51a2dde6780-384x112.png" alt="向量搜索中的 L2 距离" /><p>L2 距离如下图 6 所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32d913b46e07e79d/6a17e0d27f6f1582f7c09a57/bac99d08d6cf8a3a387a8acdc2e8f67357dad235-448x456.png" alt="可视化两个向量之间的 L2 距离" /><p>让我们重新使用与 L1 距离相同的两个样本向量 x 和 y，现在我们可以计算出 L2 距离为。10 的平方根就是 3.16。</p><p></p><h4>林夫距离</h4><p>两个向量 x 和 y 的 Linf（表示 L 无穷大）距离，也称为切比雪夫距离或棋盘距离，简单地定义为这两个向量的任意两个元素之间的最长距离，或沿其中一个轴/维测量的最长距离。计算公式非常简单，如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863411e15de16fa3/6a17e0d4505ac30939ad8a48/179ea6c6520a59acd97638313a2fb1b105e2ffed-436x82.png" alt="矢量搜索中的林夫距离公式" /><p>林夫距离的表示方法如下图 7 所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863ee7b51fd5fc15/6a17e0d56864a4772db686af/b75540d793cdc0645244d77dc36da9d5734ccbac-548x560.png" alt="两个向量之间的林夫距离" /><p>同样，取相同的两个样本向量 x 和 y，我们可以计算出无穷大距离为 max ( | 1 - 4 | , | 2 - 3 | ) = max (3, 1) = 3。</p><h4>余弦相似性</h4><p>与 L1、L2 和 Linf 不同的是，余弦相似度不是测量两个向量 x 和 y 之间的距离，而是测量它们的相对角度，即它们是否都指向大致相同的方向。相似度 s 越高，两个向量就越 "接近"。计算公式同样非常简单，如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61d55341a6457ab7/6a17e0d7e9ea872b4ea9c4e2/931b6b90f0ee83e63a66496d06d6b4cef3affddd-304x72.png" alt="向量搜索中的余弦相似度公式" /><p>两个向量的余弦相似度的表示方法如下图 8 所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50eb6399510b5380/6a17e0d83e9e45302cba139a/52092e8b5d366178e50a35f8c954ee8eaca76965-582x586.png" alt="两向量的余弦相似度" /><p>此外，由于余弦值始终处于[-1, 1]区间，-1 表示相似度相反（即两个矢量之间的夹角为 180°），0 表示相似度不相关（即夹角为 90°），1 表示相似度相同（即夹角为 0°），如下图 9 所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt785e195c75a5089e/6a17e0da1d1b8355bc93e398/fd2690a845b89b69ff202bd04192893638538aec-1600x456.png" alt="向量搜索中的余弦相似谱" /><p>
让我们再次使用相同的样本向量 x 和 y，用上述公式计算余弦相似度。首先，我们可以计算出两个向量的点积为。然后，我们将两个向量的长度（也称为幅值）相乘：最后，我们用点积除以乘积长度 10 / 11.18034 = 0.894427（即 26°角），这个值非常接近 1，因此可以认为这两个矢量非常相似。</p><h4>点积相似性</h4><p>余弦相似度的一个缺点是，它只考虑两个矢量之间的角度，而不考虑它们的大小（即长度），这意味着如果两个矢量大致指向同一方向，但其中一个矢量比另一个矢量长很多，那么这两个矢量仍然会被认为是相似的。点积相似度（也称为标量或内积）考虑了向量的角度和大小，从而改进了点积相似度，提供了更精确的相似度量。</p><p>点积相似性的计算有两个等价公式。第一种情况与我们之前在余弦相似性的分子中看到的相同：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f1c3369f8628457/6a17e0db1d1b832a1893e39c/52e2723926ab27cd96b688e805fae15f607073c8-482x104.png" alt="向量搜索中的点积相似性公式" /><p>第二个公式只是将两个矢量的长度乘以它们之间夹角的余弦：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt136dd2a749c2398a/6a17e0dc6df73108a80a0e1f/dc9b11fc67dd748d9f1f29b735f4726138cb7d39-452x70.png" alt="简化向量搜索中的点积相似性公式" /><p>下图 10 展示了点积相似性：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2c095e1c1948752/6a17e0dd6df731e30b0a0e23/1bda38cf82a1e1037f44b6e9657602c9efe1c0a6-558x574.png" alt="内积相似度" /><p>最后，我们取样 x 和 y 向量，使用第一个公式计算它们的点积相似度，就像之前计算余弦相似度一样，即 (1) + (2) = 10。</p><p>利用第二个公式，我们将两个矢量的长度相乘：+(4^2 + 3^2)^{1/2} = 11.18034，再乘以两个向量之间 26°夹角的余弦，就得到 11.18034(26°) = 10。</p><p>值得注意的一点是，如果首先对所有向量进行<strong>归一化处理</strong>（即长度为 1），那么点积相似度就会与余弦相似度（因为 |x| |y| = 1）完全相同，即两个向量之间夹角的余弦值。正如我们稍后将看到的，对向量进行归一化处理是一种很好的做法，这样可以使向量的大小变得无关紧要，从而使相似性只集中在角度上。它还能加快索引和查询时的距离计算速度，这在处理数十亿矢量时可能是个大问题。</p><h3>快速回顾</h3><p>哇，到目前为止，我们已经了解了很多信息，让我们稍停片刻，快速回顾一下目前的状况。我们了解到</p><ul><li><p>...语义搜索基于深度学习神经网络模型，擅长将非结构化数据转化为多维嵌入向量。</p></li><li><p>......模型的每个维度都代表了非结构化数据的一个特征或特性。</p></li><li><p>......嵌入向量是一串相似度值（每个维度一个），表示给定的非结构化数据与每个维度的相似程度。</p></li><li><p>......两个向量越 "接近"（即近邻），它们代表的语义概念就越相似。</p></li><li><p>......距离函数（L1、L2、Linf）可以测量两个向量的距离。</p></li><li><p>......相似函数（余弦和点积）使我们能够测量两个向量朝同一方向移动的程度。</p></li></ul><p></p><p>现在，我们需要深入研究的最后一个部分是矢量搜索引擎本身。当收到查询时，首先对查询进行矢量化，然后矢量搜索引擎会找到与查询矢量最近的相邻矢量。测量查询向量与数据库中所有向量之间的距离或相似性的粗暴方法可以适用于小型数据集，但随着向量数量的增加，这种方法很快就会失效。换句话说，我们如何才能索引数百万、数十亿甚至数万亿的向量，并在合理的时间内找到查询向量的近邻？这就需要我们发挥聪明才智，找出索引矢量的最佳方法，以便在不降低精度的情况下，尽可能快地找到最近的邻近矢量。</p><h3>矢量搜索算法和技术</h3><p>多年来，许多不同的研究团队投入了大量精力，开发出了非常聪明的向量搜索算法。在此，我们将简要介绍主要的几种。根据不同的使用情况，有的比有的更适合。</p><h4>线性搜索</h4><p>在前面提到将查询向量与数据库中的所有向量进行比较的粗暴方法时，我们简要地提到了线性搜索或平面索引。虽然它在小型数据集上运行良好，但随着向量和维数的增加，性能会迅速下降（复杂度为 O(n)）。</p><p>幸运的是，有一种更有效的方法被称为<strong>近似近邻</strong>（ANN），在这种方法中，嵌入向量之间的距离是预先计算出来的，相似向量的存储和组织方式可以使它们靠近在一起，例如使用聚类、树、哈希或图。这些方法被称为 "近似 "方法，因为它们通常不能保证 100% 的准确性。最终目标是尽可能快地<strong>缩小搜索范围</strong>，以便只关注最有可能包含相似向量的区域，或者<strong>降低向量的维度</strong>。</p><h4>K 维树</h4><p>K 维树（或 KD 树）是二进制搜索树的一种概括，它在 k 维空间中存储点，并通过将搜索空间连续分成较小的左树和右树来索引向量。搜索时，算法只需访问查询向量（图 11 中的红点）周围的几个树枝，就能找到最近的邻居（图 11 中的绿点）。如果请求的邻居超过 k 个，黄色区域就会扩大，直到算法找到更多邻居。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55e01707cd9fca57/6a17e0dfbe60866a90004653/e21af2d613112279089b6fa2166359233b10019d-829x860.png" alt="向量搜索中的 KD 树算法" /><p>KD 树算法的最大优势在于，它可以让我们迅速只关注一些局部树枝，从而将大部分向量排除在外。不过，这种算法的效率会随着维数的增加而降低，因为需要访问的分支要比低维空间多得多。</p><h4>反转文件索引</h4><p>倒置文件索引（IVF）方法也是一种<strong>空间分区</strong>算法，它将彼此接近的向量分配到它们的共享中心点上。在二维空间中，使用 Voronoi 图最直观，如图 12 所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b7e4fe6deff5ef3/6a17e0e17b54f940eb8b381c/33ddaaa818ab2f2a5fc87982c82c2a34cb849e33-640x640.png" alt="二维空间中倒置文件索引的 Voronoi 表示法 " /><p>我们可以看到，上述二维空间被划分为 20 个聚类，每个聚类的中心点用黑点表示。空间中的所有嵌入向量都被分配到其中心点最接近的聚类中。在搜索时，算法首先通过找到与查询向量最接近的中心点来确定要关注的集群，然后就可以简单地将该区域归零，必要时还可以将周围的区域归零，以便找到最近的邻居。</p><p>这种算法在高维空间中使用时，也存在与 KD 树相同的问题。这就是所谓的 "维度诅咒"（curse of dimensionality），当空间体积大幅增加，以至于所有数据都显得稀疏，而要获得更精确的结果所需的数据量却呈指数级增长时，就会出现这种情况。当数据稀疏时，这些空间划分算法就很难将数据组织成群。幸运的是，还有其他算法和技术可以缓解这一问题，详情如下。</p><h4>量化</h4><p>量化是一种<strong>基于压缩的</strong>方法，通过降低嵌入向量的精度，我们可以减小数据库的总大小。这可以通过将浮点矢量值转换为整数值的<strong>标量量化 (SQ)</strong>来实现。这不仅将数据库的大小减少了 8 倍，还降低了内存消耗，并加快了搜索时向量间距离的计算速度。</p><p>另一种技术称为<strong>乘积量化（PQ），</strong>它首先将空间划分为低维子空间，然后使用聚类算法（类似于 k-means）在每个子空间中对相近的向量进行分组。</p><p>请注意，量化不同于<strong>降维</strong>，后者是减少维数，即简单地缩短向量。</p><h4>分层导航小世界（HNSW）</h4><p>如果光看名字就觉得很复杂，别担心，其实并不复杂！简而言之，"分层可导航小世界 "是一种基于多层图的算法，非常流行且高效。包括 Apache Lucene 在内的许多不同的矢量数据库都在使用它。下文图 13 显示了国家污水处理厂的概念图。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f4f4a8e393c8d53/6a17e0e3e8fbcefa193a1861/189ef9a8bec476e379222c454644ba4c1f952085-1400x840.png" alt="分层导航小世界（HNSW）" /><p>在顶层，我们可以看到一个由极少数向量组成的图，这些向量之间的链接最长，也就是说，这是一个相似性最小的连接向量图。我们越深入下层，发现的向量就越多，图形就越密集，越来越多的向量相互靠近。在最底层，我们可以找到所有矢量，其中最相似的矢量彼此距离最近。</p><p>搜索时，算法从顶层的任意入口点开始，找到最接近查询向量的向量（如灰色点所示）。然后，它再向下移动一层，从它留在上一层的同一个向量开始，重复同样的过程，如此逐层进行，直到到达最底层，并找到与查询向量最近的邻居。</p><h4>位置敏感散列（LSH）</h4><p>与迄今为止介绍的所有其他方法一样，对位置敏感的哈希算法试图大幅缩小搜索空间，以提高检索速度。通过这种技术，嵌入向量被转化为哈希值，同时保留了相似性信息，因此搜索空间最终变成了一个可以查询的简单哈希表，而不是一个需要遍历的图或树。基于哈希值的方法的主要优势在于，包含任意（大量）维度的向量可以映射为固定大小的哈希值，这大大加快了检索时间，同时又不会牺牲太多精度。</p><p>一般来说，散列数据，特别是嵌入向量，有许多不同的方法，但本文不会深入探讨每种方法的细节。传统的散列方法通常会对看似非常相似的数据产生截然不同的散列值。由于嵌入向量是由浮点数值组成的，因此我们取两个在向量运算中被认为非常接近的浮点数值样本（例如 0.73 和 0.74），然后通过几个常见的散列函数对它们进行运算。从下面的结果来看，普通的散列函数显然无法保留输入之间的相似性。</p><p>散列功能</p><p>0.73</p><p>0.74</p><p>MD5</p><p>1342129d04cd2924dd06cead4cf0a3ca</p><p>0aec1b15371bd979cfa66b0a50ebecc5</p><p>SHA1</p><p>49d2c3e0e44bff838e1db571a121be5ea874e8d9</p><p>a534e76482ade9d9fe4bff3035a7f31f2f363d77</p><p>SHA256</p><p>99d03fc3771fe6848d675339fc49eeb1cb8d99a12e6358173336b99a2ec530ea</p><p>5ecbc825ba5c16856edfdaf0abc5c6c41d0d8a9c508e34188239521dc7645663</p><p>传统的散列方法试图<em>尽量减少</em>相似数据之间的散列碰撞，而位置敏感散列的主要目标恰恰相反，即<em>尽量增加散列碰撞</em>，从而使相似数据以很高的概率落在同一个桶中。通过这种方法，多维空间中相距较近的嵌入向量将被哈希到属于同一个桶的固定大小的值。由于 LSH 允许这些散列向量保持其邻近性，因此这种技术在数据聚类和近邻搜索中非常有用。</p><p>所有繁重的工作都是在编制索引时进行的，此时需要计算哈希值，而在搜索时，我们只需要对查询向量进行哈希处理，以便查找包含最接近嵌入向量的桶。找到候选桶后，通常会进行第二轮，以确定与查询向量最近的相邻向量。</p><h2>结束语</h2><p>为了介绍矢量搜索，我们必须在这篇文章中介绍一些内容。在比较了词法搜索和向量搜索的区别之后，我们了解了深度学习神经网络模型是如何捕捉非结构化数据的语义，并将其意义转码为高维嵌入向量的。值得注意的是，向量搜索和词法搜索并不是相互竞争的信息检索技术，而是相辅相成的（我们将在本系列的第三部分深入探讨混合搜索）。</p><p>之后，我们介绍了向量搜索的基本构件，即距离（和相似性）函数，它允许我们测量两个向量的接近程度，并评估它们所代表概念的相似性。</p><p>最后，我们回顾了各种最流行的向量搜索算法和技术，它们可以基于树、图、簇或哈希值，其目标是快速缩小多维空间中特定区域的范围，以便找到最近的邻居，而无需像线性暴力搜索那样访问整个空间。</p><p>如果您喜欢现在阅读的内容，请务必查看本系列的其他部分：</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">第 2 部分：如何在 Elasticsearch 中设置矢量搜索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">第 3 部分：使用 Elasticsearch 进行混合搜索</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/introduction-to-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/introduction-to-vector-search</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70da374b8dc0c490/6a17e0e46864a473aab686b3/63eea8ea95b49e7241e539f65bf5aa3bb8823fff-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[如何使用用于微软语义内核（Microsoft Semantic Kernel）的Elasticsearch矢量存储连接器进行人工智能代理开发]]></title>
    <description><![CDATA[微软语义内核（Microsoft Semantic Kernel）是一款轻量级开源开发工具包，可让您轻松构建人工智能代理，并将最新的人工智能模型集成到您的 C#、Python 或 Java 代码库中。随着Semantic Kernel Elasticsearch向量存储连接器（Elasticsearch Vector Store Connector）的发布，使用Semantic Kernel构建人工智能代理的开发人员现在可以将Elasticsearch作为可扩展的企业级向量存储插件，同时继续使用Semantic Kernel抽象。]]></description>
    <content:encoded><![CDATA[<p>我们与<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/"> 微软语义内核</a> （ Microsoft<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/"> Semantic Kernel ）团队合作，宣布面向 微软语义内核</a> （.NET）用户推出<a href="https://github.com/elastic/semantic-kernel-net/"> Semantic Kernel Elasticsearch矢量存储连接器（Vector Store Connector ）。</a>语义内核（Semantic Kernel）简化了企业级人工智能代理的构建过程，包括利用来自矢量存储库（Vector Store）的更多相关数据驱动响应来增强大型语言模型（LLM）的能力。语义内核（Semantic Kernel）为与Elasticsearch等矢量存储进行交互提供了一个无缝的抽象层，可提供创建、列出和删除记录集合以及上传、检索和删除单条记录等基本功能。</p><p><a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">开箱即用的Semantic Kernel Elasticsearch向量存储连接器（Vector Store Connector</a>）支持Semantic Kernel<a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">向量存储抽象</a>，这使得开发人员在构建人工智能代理时能够非常容易地将Elasticsearch作为向量存储插件。</p><p>Elasticsearch 在开源社区拥有坚实的基础，最近采用了<a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">AGPL 许可证</a>。这些工具与开源的微软语义内核（Microsoft Semantic Kernel）相结合，可提供强大的企业级解决方案。您可以通过运行此命令<code>curl -fsSL https://elastic.co/start-local | sh </code> ，在几分钟内启动 Elasticsearch（详情请参考<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a>），然后在生产人工智能代理的同时，迁移到<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">云托管</a>或<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">自托管</a>版本。</p><p>在本篇博客中，我们将探讨在使用Semantic Kernel（语义内核）时，如何使用<a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch向量存储连接器</a>。该连接器的 Python 版本将在未来推出。</p><h2>高级应用场景：利用 Semantic Kernel&amp; Elasticsearch 构建 RAG 应用程序</h2><p>下面我们将举例说明。在高层次上，我们正在构建一个 RAG（检索增强生成）应用程序，它将用户的问题作为输入，并返回一个答案。我们将使用 Azure OpenAI （ 也可使用<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/"> 本地 LLM</a> ）作为 LLM，Elasticsearch 作为向量存储，Semantic Kernel (.net) 作为将所有组件连接在一起的框架。</p><p>如果您不熟悉 RAG 架构，可以通过以下文章快速了解<a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">： https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag。</a></p><p>答案由 LLM 生成，LLM 从 Elasticsearch 向量存储中获取与问题相关的上下文。答复还包括法律硕士用作背景的资料来源。</p><h3>RAG 示例</h3><p>在这个具体例子中，我们创建了一个应用程序，允许用户就内部酒店数据库中存储的酒店提出问题。例如，用户可以根据不同标准搜索特定酒店，或要求提供酒店列表。</p><p>在示例数据库中，我们生成了一个包含 100 个条目的<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">酒店列表</a>。为了让您尽可能轻松地试用连接器演示，我们特意设置了较小的样本量。在实际应用中，Elasticsearch 连接器将显示出其优于其他选项（如 "InMemory "向量存储实现）的优势，尤其是在处理超大数据量时。</p><p>完整的演示应用程序可在 Elasticsearch 向量存储连接器存储<a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">库中</a>找到。</p><p>让我们先将所需的 NuGet 软件包和指令添加到项目中：</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>现在，我们可以创建我们的数据模型，并为其提供语义内核（Semantic Kernel）的特定属性，以定义存储模型模式和文本搜索的一些提示：</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>存储模型模式属性（`VectorStore*`）与 Elasticsearch 向量存储连接器的实际使用最为相关，即</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> 来标记记录类上的一个属性，作为记录存储在向量存储中的键。</p></li><li><p><code>VectorStoreRecordData</code> 将记录类的一个属性标记为 "数据"。</p></li><li><p><code>VectorStoreRecordVector</code> 将记录类的一个属性标记为矢量。</p></li></ul><p>所有这些属性都接受各种可选参数，可用于进一步定制存储模型。以<code>VectorStoreRecordKey </code> 为例，可以指定不同的距离函数或不同的索引类型。</p><p>文本搜索属性 (<code>TextSearch*</code>) 在本示例的最后一步中非常重要。我们稍后再谈。</p><p>下一步，我们将初始化语义内核引擎，并获取核心服务的引用。在实际应用中，应使用<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">依赖注入</a>而不是直接访问服务集合。同样的道理也适用于硬编码的配置和秘密，它们应该使用<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">配置提供程序</a>来读取：</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p>现在可以使用<code>vectorStoreCollection</code> 服务创建数据集，并摄取一些<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">演示记录</a>：</p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>由此可见，语义内核（Semantic Kernel）是如何将向量存储的使用及其复杂性简化为几个简单的方法调用的。</p><p>在 Elasticsearch 中创建一个新索引，并创建所有必要的属性映射。然后，我们的数据集会完全透明地映射到存储模型中，并最终存储到索引中。下面是映射在 Elasticsearch 中的显示方式。</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p><code>embeddings.GenerateEmbeddingsAsync()</code> 会透明地调用已配置的 Azure AI 嵌入生成服务。</p><p>在这个演示的最后一个步骤中，我们还可以看到更多的神奇之处。</p><p>当用户就数据提问时，只需调用<code>InvokePromptAsync</code> ，就能执行以下所有操作：</p><p>1.为用户的问题生成嵌入代码</p><p>2.在矢量存储器中搜索相关条目</p><p>3.将查询结果插入提示模板</p><p>4.最终提示形式的实际查询将发送到人工智能聊天完成服务</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>还记得我们之前在数据模型上定义的<code>TextSearch*</code> 属性吗？有了这些属性，我们就能在提示模板中使用相应的占位符，这些占位符会根据向量存储中的条目信息自动填充。</p><p>对于我们的问题"，请告诉我所有拥有屋顶酒吧的酒店。" ，最终答复如下：</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>正确答案是指 hotels.csv 中的以下条目</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>这个例子很好地说明了微软语义内核的使用是如何通过其深思熟虑的抽象功能大大降低复杂性，并实现高度灵活性的。例如，只需修改一行代码，就可以更换向量存储或所使用的人工智能服务，而无需重构代码的任何其他部分。</p><p>同时，该框架还提供了大量高级功能，如 "InvokePrompt "函数或模板或搜索插件系统。</p><p>完整的演示应用程序可在 Elasticsearch 向量存储连接器存储库中找到。</p><h2>Elasticsearch 还能做什么</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch 新语义文本映射：简化语义搜索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">利用检索器在 Elasticsearch 中进行语义重排</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">高级 RAG 技术第 1 部分：数据处理</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">高级 RAG 技术第 2 部分：查询和测试</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">使用 Llama 3 开放源代码和 Elastic 构建 RAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">使用 LangGraph、LLaMA3 和 Elasticsearch 向量存储从零开始构建本地代理的教程</a></p></li></ul><h2>Elasticsearch&amp; Semantic Kernel（语义内核）：下一步是什么？</h2><ul><li><p>我们展示了在.NET中构建GenAI应用时，如何将Elasticsearch向量存储轻松插入Semantic Kernel。敬请期待下一步的 Python 集成。</p></li><li><p>由于Semantic Kernel（语义内核）为<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">混合</a>搜索等高级搜索功能建立了抽象，Elasticsearch连接将使.NET开发人员能够在使用Semantic Kernel（语义内核）的同时轻松实现这些功能。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[如何在电子商务产品目录中使用混合搜索]]></title>
    <description><![CDATA[了解如何使用混合搜索建立电子商务产品目录，使用分面、促销、个性化和行为分析。]]></description>
    <content:encoded><![CDATA[<p>在本文中，我们将演示如何实现混合搜索，将全文搜索和矢量搜索的结果结合起来。混合搜索将这两种方法统一起来，充分利用了两种搜索策略的优点，从而提高了搜索结果的广度。</p><p>除了集成混合搜索，我们还将演示如何添加功能，使您的搜索解决方案更加强大。其中包括切面和个性化产品促销。此外，我们还将向您展示如何使用 Elastic 的行为分析工具捕捉用户互动并生成有价值的见解。</p><p>在本实现中，您将看到如何构建允许用户查看搜索结果并与之交互的界面，以及负责返回信息的应用程序接口。要访问包含源代码的资源库，请点击下面的链接：</p><ul><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search</a></p></li><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store</a> </p></li></ul><p>我们将本指南分为几个步骤，从创建索引到实施分面和结果个性化等高级功能。最后，您将拥有一个强大的搜索解决方案，可以在电子商务场景中使用。</p><h2>电子商务混合搜索的环境设置</h2><p>在开始实施之前，我们需要设置环境。您可以选择使用 Elastic Cloud 上的服务或容器化解决方案来管理 Elasticsearch。如果选择容器化，可在此版本库中找到通过 Docker Compose 进行的配置：<a href="https://github.com/andreluiz1987/product-store-search/blob/main/docker/docker-compose.yml">docker-compose.yml</a>。</p><h2>创建索引和录入产品目录</h2><p>索引将根据化妆品目录创建，其中包括名称、描述、照片、类别和标签等字段。用于全文搜索的字段，如"名称" 和"描述，" 将被映射为<code>text</code> ，而用于聚合的字段，如"类别" 和"品牌，" 将被映射为<code>keyword</code> ，以便进行分面搜索。</p><p>"description" 字段将用于矢量搜索，因为它提供了有关产品的更多背景信息。这个字段将被定义为<code>dense_vector,</code> ，存储描述的矢量表示。</p><p>索引映射如下</p>{
   "mappings":{
      "properties":{
         "id":{
            "type":"keyword"
         },
         "brand":{
            "type":"text",
            "fields":{
               "keyword":{
                  "type":"keyword"
               }
            }
         },
         "name":{
            "type":"text"
         },
         "price":{
            "type":"float"
         },
         "price_sign":{
            "type":"keyword"
         },
         "currency":{
            "type":"keyword"
         },
         "image_link":{
            "type":"keyword"
         },
         "description":{
            "type":"text"
         },
         "description_embeddings":{
            "type":"dense_vector",
            "dims":384
         },
         "rating":{
            "type":"keyword"
         },
         "category":{
            "type":"keyword"
         },
         "product_type":{
            "type":"keyword"
         },
         "tag_list":{
            "type":"keyword"
         }
      }
   }
}<p>创建索引的脚本可在<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/infra/create_index.py">此处</a>找到。</p><h2>嵌入式生成</h2><p>为了将产品描述矢量化，我们使用了 all-MiniLM-L6-v2 模型。在这种情况下，应用程序负责在编制索引前生成嵌入。另一种方法是将模型导入 Elasticsearch 集群，但在本地环境中，我们选择直接在应用程序中执行矢量化。</p><p>我们使用<a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">Kaggle</a>上的化妆品数据集来填充索引，为了提高数据摄取的效率，我们使用了批处理方法。在同一摄取阶段，我们将生成"description" 字段的嵌入，并将其索引到新字段"description_embeddings" 中。</p><p>整个数据摄取过程可通过存储库中的<strong>Jupyter Notebook</strong>直接跟踪和执行。该笔记本提供了关于如何读取、处理数据并将其编入 Elasticsearch 索引的分步指南，便于复制和实验。</p><p>您可以通过以下链接访问该笔记本：<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/ingestion/ingestion.ipynb">摄入笔记本。</a></p><h2>混合搜索实施</h2><p>现在，让我们来实现混合搜索。对于基于关键字的搜索，我们使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">multi_match</a>查询，目标字段为"name、" " category、" 和"description。"这样就能确保检索到这些字段中包含搜索词的文档。</p><p>对于向量搜索，我们使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">KNN 查询</a>。在执行查询之前，需要对搜索词进行矢量化，这需要使用对输入词进行矢量化的方法来完成。请注意，摄取时使用的同一模型也用于搜索词。</p><p>这两种搜索的组合是通过<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">互易等级融合（RRF） </a>算法完成的，该算法可合并两种查询的结果，并通过减少噪音来提高搜索精度。RRF 允许基于关键字的搜索和矢量搜索共同发挥作用，从而增强对用户查询的理解。</p>query = {
   "retriever": {
       "rrf": {
           "retrievers": [
               {
                   "standard": {
                       "query": organic_query['query']
                   }
               },
               {
                   "knn": {
                       "field": "description_embeddings",
                       "query_vector": vector,
                       "k": 5,
                       "num_candidates": 20
                   }
               }
           ],
           "rank_window_size": 20,
           "rank_constant": 5
       }
   },
   "_source": organic_query['_source']
}<h3>结果比较：关键词搜索与混合搜索</h3><p>现在，让我们比较一下传统关键词搜索和混合搜索的结果。当使用关键字搜索"干性皮肤粉底" 时，我们会得到以下结果：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcb5405df787bc8f5/6a17023e961e697ca1c4cdb4/52e717aa3c9c1fadfadb639f5fb77cf8e47e3b34-1600x1021.png" alt="比较结果：关键词搜索与混合搜索" /><ol><li><p><strong>Revlon ColorStay Makeup for Normal / Dry SkinDescription</strong>：露华浓持久彩妆（Revlon ColorStay Makeup）质地轻盈，遮瑕持久，不会结块、褪色或脱妆。这款无油、保湿平衡配方采用了 "定时释放技术"，特别适合中性或干性肌肤使用，能够持续为肌肤补充水分：妆感舒适，可持续使用长达 24 小时；中等至完全遮盖；有多种美丽色调可供选择。
</p></li><li><p><strong>美宝莲梦幻柔滑慕斯粉底液描述</strong>：你会爱上它的原因独特的乳霜状粉底提供 100% 婴儿般柔滑的完美肤质。不含油、不含香料，通过皮肤科医生测试，通过过敏测试，不致粉刺，不会堵塞毛孔。适合敏感性皮肤</p></li></ol><p><strong>分析</strong>：在搜索"干性皮肤粉底时，" 搜索结果是通过搜索关键词与产品标题和描述之间的精确匹配获得的。然而，这种匹配并不总是最佳选择。例如，<strong>Revlon ColorStay Makeup for Normal / Dry Skin</strong>就是一个不错的选择，因为它是专为干性皮肤配制的。尽管它不含油分，但其配方设计却能提供持续的保湿效果。相比之下，我们还收到了<strong>美宝莲梦幻柔滑慕斯粉底液</strong>，虽然这款粉底液不含油分，也能补充水分，但一般更推荐油性或混合性皮肤使用，因为无油产品往往侧重于控油，而不是提供干性皮肤所需的额外水分。这凸显了基于关键词搜索的局限性，因为这种搜索可能会返回无法完全满足干性皮肤患者特殊需求的产品。</p><p>现在，使用混合方法进行相同的搜索：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt412e38b327cf6a4f/6a17024066c4f94c52f8beb9/13b562a5120619355ba0861976102e208964a49f-1600x1021.png" alt="使用混合方法进行搜索" /><ol><li><p><strong>CoverGirl Outlast Stay Luminous Foundation Creamy Natural (820):产品简介</strong>：CoverGirl Outlast Stay Luminous 粉底液是打造露光妆效和微妙光泽的完美之选。它不含油分，配方不油腻，能为肌肤带来全天候的自然亮泽！这款全天候粉底液可为肌肤补充水分，同时提供无瑕遮瑕。<strong>分析： </strong>这款产品非常适合干性皮肤的用户，因为它强调补水。"Hydrates skin" 和"dewy finish" 这两个词符合用户寻找干性皮肤粉底的意图。矢量搜索很可能理解了补水的概念，并将其与解决皮肤干燥问题的粉底需求联系起来。
</p></li><li><p><strong>Revlon ColorStay Makeup for Normal / Dry Skin:说明：</strong>Revlon ColorStay Makeup 采用轻盈配方，具有持久遮瑕效果，不会结块、褪色或脱落。这款不含油分的保湿平衡配方采用了 "定时释放技术"，特别适合中性或干性肌肤使用，能持续为肌肤补充水分。<strong>分析： </strong>这款产品直接针对干性皮肤用户的需求，明确指出其配方适用于中性或干性皮肤。"水分平衡配方" 和持续的保湿效果非常适合寻找适合干性皮肤的粉底的人。矢量搜索成功检索到这一结果，不仅是因为关键词匹配，还因为重点关注补水，并特别提到干性皮肤是目标人群。
</p></li><li><p><strong>精华粉底液说明： </strong>精华粉底液质地轻盈，遮瑕度适中，共有 21 种色调可供选择。这些粉底液的遮瑕度适中，看起来很自然，精华液质地非常轻盈。它们的粘度很低，可使用随附的泵或单独购买的玻璃滴管进行分配。<strong>分析：</strong>在这款产品中，说明强调的是一款质地轻盈、妆感自然的精华粉底液，这与干性皮肤人群的需求不谋而合，因为干性皮肤人群通常需要的是温和、保湿、妆感不结块的产品。尽管"干性皮肤" 这个词没有被明确提及，但矢量搜索可能从更广泛的背景中捕捉到了轻盈、自然的遮盖力和类似精华液的质地，这与保湿度和涂抹舒适度有关，使其与干性皮肤相关。</p></li></ol><h2>面的实施</h2><p>面孔对于有效提炼和过滤搜索结果至关重要，可为用户提供更有针对性的导航，尤其是在电子商务等产品种类繁多的情况下。它们允许用户根据类别、品牌或价格等属性调整搜索结果，使搜索更加准确。为了实现这一功能，我们在<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html"> </a><code>category</code>和 字段上使用了 术语聚合<code>brand</code> ，这两个 字段<code>keyword</code> 在索引创建阶段被定义为 。</p>    query = build_query(term, categories, product_types, brands)
    query["aggs"] = {
        "product_types": {"terms": {"field": "product_type"}},
        "categories": {"terms": {"field": "category"}},
        "brands": {"terms": {"field": "brand.keyword"}}
    }<p>实施的完整代码可在<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L144">此处</a>找到。</p><p>下面是搜索"适用于干性皮肤的粉底" 的面结果：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31080652a60d8600/6a1702416234e0af7ddb18e7/e8907af359c021eaa38ec7a6a53f10c325ee8ade-1146x1248.png" alt="搜索&quot;适用于干性皮肤的粉底液的面部结果&quot;" /><h2>自定义结果：固定查询</h2><p>在某些情况下，在搜索结果中推广某些产品可能是有益的。为此，我们使用了 "<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html"><strong>固定查询"</strong></a>，它允许特定产品出现在搜索结果的顶部。下面，我们将在不推销任何产品的情况下搜索"Foundation" ：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31e75c815262993e/6a170243509168ae42e1b95d/9396c4ed358e7a68eed47f17dd6915d0bad492aa-1600x1157.png" alt="在不推销任何产品的情况下搜索&quot;Foundation&quot; " /><p>在我们的例子中，我们可以推广带有"无麸质标签的产品。"通过使用产品 ID，我们可以确保产品在搜索结果中的优先级。具体而言，我们将推广以下产品：<strong>精华粉底液</strong>（编号：1043）、<strong>遮瑕粉底液</strong>（编号：1042）和<strong>Realist 隐形定妆粉</strong>（编号：1039）。</p>{
   "query":{
      "pinned":{
         "ids":[
            "1043",
            "1042",
            "1039"
         ],
         "organic":{
            "bool":{
               "must":[
                  {
                     "multi_match":{
                        "query":"foundation",
                        "fields":[
                           "name",
                           "category",
                           "description"
                        ]
                     }
                  }
               ]
            }
         }
      }
   }
}<p>我们使用特定的产品 ID 来确保它们在查询结果中的优先级。查询结构包括一个产品 ID 列表，该列表应将"钉在" 的顶部（在本例中，ID 为 1043、1042 和 1039），而其余结果则按照搜索的有机流程，使用"name" 、"category" 和"description" 等字段中的文本查询条件组合。这样，就有可能以可控的方式推广项目，确保其可见性，同时保持搜索的其他部分基于通常的相关性。</p><p>下面是查询执行的结果和促销产品：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a6d5cbef227f16/6a1702458b73cb68f0189ef3/8c1a547bfe31360c405eae0891c666635051a51b-1600x1039.png" alt="带促销产品的查询执行结果" /><p>完整的查询代码可在<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L112">此处</a>找到。</p><h2>利用行为分析技术分析搜索行为</h2><p>到目前为止，我们已经增加了一些功能，以提高搜索结果的相关性和产品的可发现性。现在，我们将通过加入一项功能来最终确定搜索解决方案，该功能将帮助我们分析用户的搜索行为，识别有结果或无结果的查询以及搜索结果的点击等模式。为此，我们将使用 Elastic 提供的<strong>行为分析</strong>功能。有了它，只需几个步骤，我们就能监控和分析用户的搜索行为，获得宝贵的见解，从而优化搜索体验。</p><h3>创建行为分析集合</h3><p>我们的第一项操作是创建一个集合，负责接收所有行为分析事件。要创建集合，请访问<strong>Search&gt; Behavioral Analytics</strong> 中的 Kibana 界面。在下面的示例中，我们创建了名为<code>tracking-search</code> 的集合。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b4cb5480ab0bfef/6a1702474a531b189936a7e7/c05e533212a3690c5ea9f2d5226cbcdc901c482a-1600x1009.png" alt="行为分析--为您的收藏命名" /><h3>将行为分析整合到界面中</h3><p>我们的<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">前端</a>应用程序是用 JavaScript 开发的，为了集成行为分析，我们将按照 Elastic 官方文档中描述的步骤安装<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-start.html#behavioral-analytics-start-ui-integration-js-client"><strong>行为分析 JavaScript 跟踪器</strong></a>。</p><h3>实施 JavaScript 跟踪器</h3><p>现在，我们将把跟踪器客户端导入应用程序，并使用<code>trackPageView</code> 、<code>trackSearch</code> 和<code>trackSearchClick</code> 方法来捕捉用户交互。</p><p><strong>免责声明</strong>：虽然我们正在使用一种工具来收集用户交互数据，但这对于确保遵守<strong>GDPR</strong> 至关重要。这意味着要明确告知用户正在收集哪些数据、将如何使用这些数据，并提供选择退出跟踪的选项。此外，我们必须采取强有力的安全措施来保护收集到的信息，并尊重用户的权利，如数据访问和删除，确保所有步骤都符合 GDPR 原则。
</p><p><strong>步骤 1：创建跟踪器实例</strong></p><p>首先，我们将创建用于监控交互的跟踪器实例。在此配置中，我们定义了目标端点、集合名称和 API 密钥：</p>createTracker({
  endpoint: "https://endpoint:443",
  collectionName: "tracking-search",
  apiKey: "api-key"
});<p><strong>步骤 2：获取页面浏览量</strong></p><p>要跟踪页面浏览量，我们可以配置<code>trackPageView</code> 事件：</p>    trackPageView({
      page: {
        title: "home-page"
      },
    });<p>有关<code>trackPageView</code> 事件的详细信息，请参阅本<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-event-reference.html#behavioral-analytics-event-reference-pageview-fields">文档</a>。</p><p><strong>步骤 3：捕捉搜索查询</strong></p><p>为了监控用户的搜索行为，我们将使用<code>trackSearch</code> 方法：</p>      trackSearch({
        search: {
          query: searchTerm,
          results: {
            items: documents,
            total_results: response.data.length,
          },
        },
      });<p>在这里，我们收集搜索词和搜索结果。</p><p><strong>步骤 4：跟踪搜索结果的点击率</strong></p><p>最后，为了捕捉搜索结果的点击，我们将使用<code>trackSearchClick</code> 方法：</p>trackSearchClick({
      document: { id: product.id, index: "products-catalog"},
      search: {
        query: searchTerm,
        page: {
          current: 1,
          size: products.length,
        },
        results: {
          items: documents,
          total_results: products.length,
        },
        search_application: "app-product-store"
      },
    });<p>我们收集被点击文档的 ID 信息以及搜索词和搜索结果。</p><h3>在 Kibana 中分析数据</h3><p>既然已经捕捉到了用户交互事件，我们就可以获得有关搜索操作的宝贵数据。Kibana 使用行为分析工具来可视化和分析这些行为数据。要查看结果，只需导航到<strong>搜索&gt; 行为分析&gt; 我的收藏</strong>，就会显示捕获事件的概览。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdab4f4507c5a4732/6a17024860084b34ca3c4411/e219c4af2e01b0ccc1b9079459a07832835abc75-1600x1155.png" alt="在 Kibana 中分析数据" /><p>在此概览中，我们可以大致了解集成到界面中的每个操作所捕获的事件。从这些信息中，我们可以获得有关用户搜索行为的宝贵见解。不过，如果您想创建个性化的仪表盘，其中包含与您的特定场景更相关的指标，Kibana 提供了用于构建仪表盘的强大工具，允许您创建各种指标可视化。</p><p>下面，我创建了一些可视化和图表来监测，例如，一段时间内搜索次数最多的词语、没有结果的查询、突出显示搜索次数最多的词语的词云，以及最后的地理可视化，以确定搜索访问来自哪里。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt04ced4406436f942/6a17024a5091685116e1b961/84a2c39dab77a3f9366c258a3a45d2cbc7df125e-1600x689.png" alt="可视化和图表监控" /><h2>结论</h2><p>在本文中，我们实施了一种混合搜索解决方案，将关键词和矢量搜索相结合，为用户提供更准确、更相关的搜索结果。我们还探索了如何使用其他功能，如面和钉住查询的个性化结果，以创建更完整、更高效的搜索体验。</p><p>此外，我们还集成了 Elastic 的<strong>行为分析</strong>功能，以捕捉和分析用户与搜索引擎交互过程中的行为。通过使用<code>trackPageView</code> 、<code>trackSearch</code> 和<code>trackSearchClick</code> 等方法，我们能够监控搜索查询、搜索结果点击量和页面浏览量，从而对搜索行为产生有价值的见解。</p><h2>参考资料</h2><p>数据集</p><p><a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset</a></p><p>变压器</p><p><a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2</a></p><p>互惠等级融合</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever">https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever</a></p><p>Knn 查询</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html</a></p><p>固定查询</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html</a></p><p>行为分析应用程序接口</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html</a></p><p>https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-overview.html</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63c711d1bf9b2501/6a17024c66c4f9c2b7f8bebd/05578fc595a12f6b1ebf88a10a2a31e9971b545e-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Blazor 和 Elasticsearch 构建搜索应用]]></title>
    <description><![CDATA[了解如何使用 Blazor 和 Elasticsearch 构建搜索应用程序，以及如何使用 Elasticsearch .NET 客户端进行混合搜索。]]></description>
    <content:encoded><![CDATA[<p>在本文中，您将学习如何利用 C# 技能使用 Blazor 和 Elasticsearch 构建搜索应用程序。我们将使用<a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html">Elasticsearch .NET</a>客户端运行<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html">全文</a>、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">语义</a>和<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">混合</a>搜索查询。</p><p><strong>注意</strong>如果您熟悉旧版本的 Elasticsearch C# 客户端<a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest.html">NEST</a>，请阅读这篇关于 NEST 客户端弃用和新功能的<a href="https://www.elastic.co/search-labs/blog/net-client-evolution">博文</a>。<em>NEST 是上一代的 .NET 客户端，后来被 </em><code>Elastic.Clients.Elasticsearch package</code></p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8d1da68641fac0/6a17f65763173044d4585bfb/18f890286ca122cd97286c09ebf740208b802d0b-650x395.png" alt="使用 blazor 图表构建 blazor 应用程序：ESRE" /><ul><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-blazor?">什么是 Blazor？</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-esre?">什么是 ESRE？</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#configuring-elser">配置 ELSER</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#indexing-data">索引数据</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#building-the-app-with-blazor-&amp;-elasticsearch">建筑应用</a></p></li></ul><h2>什么是 Blazor？</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91278ae422883e28/6a17f6592f4a5c3213fa8a95/622741915d016b68bf94f742332d10d736d60052-707x461.png" alt="Blazor 服务器" /><p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor</a>是微软开发的基于 HTML、CSS 和 C# 的开放源代码网络框架，允许开发人员构建可在客户端或服务器上运行的网络应用程序。Blazor 还允许您制作可重复使用的组件，以更快地构建应用程序；它使开发人员能够在同一个文件中构建 HTML 视图和 C# 操作，这有助于保持代码的可读性和简洁性。此外，有了Blazor Hybrid，您还可以通过.NET代码构建本地移动应用程序，访问本地平台功能。</p><p>Blazor 的部分功能使其成为一个非常适合工作的框架：</p><ul><li><p>服务器端和客户端渲染选项</p></li><li><p>可重复使用的用户界面组件</p></li><li><p>利用 SignalR 实时更新</p></li><li><p>内置状态管理</p></li><li><p>内置路由系统</p></li><li><p>强大的类型和编译时检查</p></li></ul><h3>为什么选择 Blazor？</h3><p>与其他框架和库相比，Blazor 具有以下优势：它允许开发人员在客户端和服务器代码中使用 C#，提供强大的类型和编译时检查功能，从而提高了可靠性。它与 .NET 生态系统无缝集成，实现了 .NET 库和工具的重用，并提供强大的调试支持。</p><h2>什么是 ESRE？</h2><p><a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">Elasticsearch Relevance Engine™ (ESRE)</a>是一套在强大的 Elasticsearch 搜索引擎基础上使用机器学习和人工智能<a href="https://www.elastic.co/guide/en/esre/current/learn.html">构建搜索应用程序的工具</a>。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf313ba986de01b93/6a17d851505ac306fcad8975/4c0f2645ed1c27fe3ef61a1a9126adadfd8d5368-721x421.png" alt="盐" /><p>要了解有关 ESRE 的更多信息，请<a href="https://www.elastic.co/search-labs/blog/introducing-elasticsearch-relevance-engine-esre">点击此处</a>阅读我们的博文。</p><h2>配置 ELSER</h2><p>为了充分利用 Elastic 的<a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">ESRE</a>功能，我们将使用<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a>作为模型提供者。</p><p><em>请注意，要使用 Elasticsearch 的 ELSER 模型，您必须拥有白金级或企业级许可证，并至少拥有一个 4GB 大小的专用机器学习 (ML) 节点。</em><a href="https://www.elastic.co/guide/en/machine-learning/8.15/ml-nlp-elser.html#elser-req"><em> 点击此处 了解更多信息 。</em></a></p><p>首先创建推理端点：</p>PUT _inference/sparse_embedding/my-elser-model
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}<p>如果您是第一次使用 ELSER，在后台加载模型时可能会遇到 502 Bad Gateway 错误。您可以在 Kibana 的<code>Machine Learning &gt; Trained Models</code> 中查看模型的状态。部署完成后，就可以进行下一步。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee295a3516338302/6a17f65b414c640deb94531d/a7ff94b72d337cde892165d744b9f42fba702a87-1440x649.png" alt="检查训练有素的模型" /><h2>索引数据</h2><p>您可以<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor/books.zip">在这里</a>下载数据集，然后使用 Kibana 导入数据。为此，请访问主页并点击"Upload data" 。然后，上传文件并点击<code>Import</code> 。最后，进入<code>Advanced</code> 标签，粘贴以下映射：</p>{
   "properties":{
      "authors":{
         "type":"keyword"
      },
      "categories":{
         "type":"keyword"
      },
      "longDescription":{
         "type":"semantic_text",
         "inference_id":"my-elser-model",
         "model_settings":{
            "task_type":"sparse_embedding"
         }
      },
      "pageCount":{
         "type":"integer"
      },
      "publishedDate":{
         "type":"date"
      },
      "shortDescription":{
         "type":"text"
      },
      "status":{
         "type":"keyword"
      },
      "thumbnailUrl":{
         "type":"keyword"
      },
      "title":{
         "type":"text"
      }
   }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f07300647edd2ca/6a17f65dfaa913172a93ca0c/1aea0b9c51e275f89339f5d463fcaef799fc3943-1235x1083.png" alt="导入数据" /><p>我们将创建一个能够运行语义和全文查询的索引。<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">语义文本字段</a>类型将负责数据分块和嵌入。<em>请注意，我们是将 索引</em><em><code>longDescription</code></em><em> 为</em><em><code>semantic_text</code></em><em> ， 如果要将一个字段 索引 为</em><em><code>semantic_text</code></em><em> 和 text` ， 可以使用</em> copy_to 。</p><h2>使用 Blazor&amp; Elasticsearch 构建应用程序</h2><h3>API 密钥</h3><p>我们需要做的第一件事是创建一个 API 密钥，以验证对 Elasticsearch 的请求。API 密钥应为只读，只允许查询<code>books-blazor</code> 索引。</p>POST /_security/api_key
{
  "name": "books-blazor-key",
  "role_descriptors": {
    "books-blazor-reader": {
      "indices": [
        {
          "names": ["books-blazor"],
          "privileges": ["read"]
        }
      ]
    }
  }
}<p>你会看到类似这样的内容：
</p>{
  "id": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "books-blazor-key",
  "api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "encoded": "XXXXXXXXXXXXXXXXXXXXXXXX=="
}<p>保存<code>encoded</code> 响应字段的值，以备不时之需。如果您在<a href="https://www.elastic.co/cloud/">Elastic Cloud</a> 上运行，还需要您的 Cloud ID。(您可以<a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id">在此处</a>找到）。</p><h4>创建 Blazor 项目</h4><p>首先安装 Blazor，并按照<a href="https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/install">官方说明</a>创建一个示例项目。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c20b896fe1e98e/6a17f65f6317301566585bff/f5720867d960c91fd1b3c4ae9be174e062e6b2fc-975x830.png" alt="Blazor 教程 - 创建 Blazor 项目" /><p>创建项目后，文件夹结构和文件应如下所示：</p>BlazorApp/
|-- BlazorApp.csproj
|-- BlazorApp.sln
|-- Program.cs
|-- appsettings.Development.json
|-- appsettings.json
|-- Properties/
|   `-- launchSettings.json
|-- Components/
|   |-- App.razor
|   |-- Routes.razor
|   |-- _Imports.razor
|   |-- Layout/
|   |   |-- MainLayout.razor
|   |   |-- MainLayout.razor.css
|   |   |-- NavMenu.razor
|   |   `-- NavMenu.razor.css
|   `-- Pages/
|       |-- Counter.razor
|       |-- Error.razor
|       |-- Home.razor
|       `-- Weather.razor
|-- wwwroot/
|-- bin/
`-- obj/ <p>模板应用程序包括<a href="https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/">Bootstrap v5.1.0</a>用于造型。</p><p>安装<a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.0/installation.html">Elasticsearch .NET</a>客户端，完成项目设置：</p>dotnet add package Elastic.Clients.Elasticsearch<p>完成这一步后，您的页面应该是这样的：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b90e394ded7ff56/6a17f660faa913d49f93ca10/ef9d2186c2cf49e78a307c4aa69c51632e84578d-940x529.png" alt="Blazor hello world" /><h3>文件夹结构</h3><p>现在，我们将按如下方式整理文件夹：</p>BlazorApp/
|-- Components/
|   |-- Pages/
|   |   |-- Search.razor
|   |   `-- Search.razor.css
|   `-- Elasticsearch/
|       |-- SearchBar.razor
|       |-- Results.razor
|       `-- Facet.razor
|-- Models/
|   |-- Book.cs
|   `-- Response.cs
`-- Services/
    `-- ElasticsearchService.cs<p>文件解释：</p><ul><li><p>Components/Pages/Search.razor：包含搜索栏、搜索结果和过滤器的主页面。</p></li><li><p>Components/Pages/Search.razor.css：页面样式。</p></li><li><p>Components/Elasticsearch/SearchBar.razor：搜索栏组件。</p></li><li><p>Components/Elasticsearch/Results.razor：结果组件。</p></li><li><p>Components/Elasticsearch/Facet.razor: 过滤器组件。</p></li><li><p>Components/Svg/GlassIcon.razor: 搜索图标。</p></li><li><p>Components/_Imports.razor：这将导入所有组件。</p></li><li><p>Models/Book.cs：这将存储图书字段模式。</p></li><li><p>Models/Response.cs：这将存储响应模式，包括搜索结果、面和总点击数。</p></li><li><p>Services/ElasticsearchService.cs：Elasticsearch 服务。它将处理与 Elasticsearch 的连接和查询。</p></li></ul><h4>初始配置</h4><p>我们先来清理一下。</p><p>删除文件：</p><ul><li><p>Components/Pages/Counter.razor</p></li><li><p>Components/Pages/Weather.razor</p></li><li><p>Components/Pages/Home.razor</p></li><li><p>Components/Layout/NavMenu.razor</p></li><li><p>Components/Layout/NavMenu.razor.css</p></li></ul><p>检查<code>/Components/_Imports.razor</code> 文件。您应该有以下进口：</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components<h4>将 Elastic 集成到项目中</h4><p>现在，让我们导入 Elasticsearch 组件：</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components
@using BlazorApp.Components.Elasticsearch @* &lt;--- Add this line *@<p>我们将从<code>/Components/Layout/MainLayout.razor</code> 文件中删除默认侧边栏，以便为应用程序提供更多空间：</p>@inherits LayoutComponentBase

&lt;div class="page"&gt;
    &lt;main&gt;
        &lt;article class="content"&gt;
            @Body
        &lt;/article&gt;
    &lt;/main&gt;
&lt;/div&gt;

&lt;div id="blazor-error-ui"&gt;
    An unhandled error has occurred.
    &lt;a href="" class="reload"&gt;Reload&lt;/a&gt;
    &lt;a class="dismiss"&gt;🗙&lt;/a&gt;
&lt;/div&gt;<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt441012c5aeac85f8/6a17f661ec0f8949f15a67ac/55bfff607f9167a532f83ad074b479364a346c6e-816x472.png" alt="从布局中移除导航条" /><p>现在让我们输入<a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&amp;tabs=linux#secret-manager">用户机密</a>的 Elasticsearch 凭据：</p>dotnet user-secrets init
dotnet user-secrets set ElasticsearchCloudId "your Cloud ID"
dotnet user-secrets set ElasticsearchApiKey "your API Key"<p>使用这种方法，.Net 8 可将敏感数据存储在项目文件夹之外的单独位置，并可通过<code>IConfiguration</code> 界面进行访问。任何使用相同用户秘密的 .Net 项目都可以使用这些变量。</p><p>然后，让我们修改<code>Program.cs</code> 文件以读取机密并挂载 Elasticsearch 客户端：</p><p>首先，导入必要的库：</p>using BlazorApp.Services;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;<ul><li><p>BlazorApp.Services：包含 Elasticsearch 服务。</p></li><li><p>Elastic.Clients.Elasticsearch：导入 Elasticsearch 客户端 .Net 8 库。</p></li><li><p>Elastic.Transport：导入 Elasticsearch 传输库，它允许我们使用 ApiKey 类来验证请求。</p></li></ul><p>其次，在<code>var app = builder.Build()</code> 行之前插入以下代码：</p>// Initialize the Elasticsearch client.
builder.Services.AddScoped(sp =&gt;
{
    // Getting access to the configuration service to read the Elasticsearch credentials.
    var configuration = sp.GetRequiredService&lt;IConfiguration&gt;();
    var cloudId = configuration["ElasticsearchCloudId"];
    var apiKey = configuration["ElasticsearchApiKey"];

    if (string.IsNullOrEmpty(cloudId) || string.IsNullOrEmpty(apiKey))
    {
        throw new InvalidOperationException(
            "Elasticsearch credentials are missing in configuration."
        );
    }

    var settings = new ElasticsearchClientSettings(cloudId, new ApiKey(apiKey)).EnableDebugMode();
    return new ElasticsearchClient(settings);
});<p>这段代码将从用户机密中读取 Elasticsearch 凭据，并创建一个 Elasticsearch 客户端实例。</p><p>在初始化 ElasticSearch 客户端后，添加以下一行以注册 Elasticsearch 服务：</p>builder.Services.AddScoped&lt;ElasticsearchService&gt;();<p>下一步将在<code>/Services/ElasticsearchService.cs</code> 文件中建立搜索逻辑：</p><p>首先，导入必要的库和模型：</p>using BlazorApp.Models;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;<p>其次，添加类<code>ElasticsearchService</code> 、构造函数和变量：</p>namespace BlazorApp.Services
{
    public class ElasticsearchService
    {
        private readonly ElasticsearchClient _client;

        // The logger is used to log information, warnings and errors about the Elasticsearch service and requests.
        private readonly ILogger&lt;ElasticsearchService&gt; _logger;

        public ElasticsearchService(
            ElasticsearchClient client,
            ILogger&lt;ElasticsearchService&gt; logger
        )
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger;
        }
    }
}<h4>配置搜索</h4><p>现在，让我们构建搜索逻辑：</p>private static Action&lt;RetrieverDescriptor&lt;BookDoc&gt;&gt; BuildHybridQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return retrievers =&gt;
        retrievers.Rrf(rrf =&gt;
            rrf.RankWindowSize(50)
                .RankConstant(20)
                .Retrievers(
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.MultiMatch(mm =&gt;
                                                mm.Query(searchTerm)
                                                    .Fields(
                                                        new[]
                                                        {
                                                            "title",
                                                            "shortDescription",
                                                        }
                                                    )
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        ),
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.Semantic(sem =&gt;
                                                sem.Field("longDescription")
                                                    .Query(searchTerm)
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        )
                )
        );
}

public static List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt; BuildFilters(
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = new List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt;();

    if (selectedFacets != null)
    {
        foreach (var facet in selectedFacets)
        {
            foreach (var value in facet.Value)
            {
                var field = facet.Key.ToLower();
                if (!string.IsNullOrEmpty(field))
                {
                    filters.Add(m =&gt; m.Term(t =&gt; t.Field(new Field(field)).Value(value)));
                }
            }
        }
    }

    return filters;
}<ul><li><p><code>BuildFilters</code> 将使用用户选择的面为搜索查询建立过滤器。</p></li><li><p><code>BuildHybridQuery</code> 将建立一个结合全文和语义搜索的<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">混合</a>搜索查询。</p></li></ul><p>接下来，添加搜索方法：</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");

        // Retrieve the hybrid query with filters applied.
        var retrieverQuery = BuildHybridQuery(searchTerm, selectedFacets);

        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Retriever(retrieverQuery)
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}

public static Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; FormatFacets(
    Elastic.Clients.Elasticsearch.Aggregations.AggregateDictionary aggregations
)
{
    var facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

    foreach (var aggregation in aggregations)
    {
        if (
            aggregation.Value
            is Elastic.Clients.Elasticsearch.Aggregations.StringTermsAggregate termsAggregate
        )
        {
            var facetName = aggregation.Key;
            var facetDictionary = ConvertFacetDictionary(
                termsAggregate.Buckets.ToDictionary(b =&gt; b.Key, b =&gt; b.DocCount)
            );
            facets[facetName] = facetDictionary;
        }
    }

    return facets;
}

private static Dictionary&lt;string, long&gt; ConvertFacetDictionary(
    Dictionary&lt;Elastic.Clients.Elasticsearch.FieldValue, long&gt; original
)
{
    var result = new Dictionary&lt;string, long&gt;();
    foreach (var kvp in original)
    {
        result[kvp.Key.ToString()] = kvp.Value;
    }
    return result;
}<ul><li><p><code>SearchBooksAsync</code>该功能将使用混合查询执行搜索，并返回结果，其中包括用于构建切面的聚合。</p></li><li><p><code>FormatFacets</code>：将聚合响应格式化为字典。</p></li><li><p><code>ConvertFacetDictionary</code>：将面字典转换为更易读的格式。</p></li></ul><p>下一步是创建模型，这些模型将代表 Elasticsearch 查询<code>hits</code> 中返回的数据，这些数据将作为结果打印在搜索页面中。</p><p>我们首先创建文件<code>/Models/Book.cs</code> 并添加以下内容：</p>namespace BlazorApp.Models
{
    public class BookDoc
    {
        public string? Title { get; set; }
        public int? PageCount { get; set; }
        public string? PublishedDate { get; set; }
        public string? ThumbnailUrl { get; set; }
        public string? ShortDescription { get; set; }
        public LongDescription? LongDescription { get; set; }
        public string? Status { get; set; }
        public List&lt;string&gt;? Authors { get; set; }
        public List&lt;string&gt;? Categories { get; set; }
    }

    public class LongDescription
    {
        public string? Text { get; set; }
    }
}<p>然后，在<code>/Models/Response.cs</code> 文件中设置弹性响应，并添加以下内容：</p>namespace BlazorApp.Models
{
    public class ElasticResponse
    {
        public ElasticResponse()
        {
            Documents = new List&lt;BookDoc&gt;();
            Facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
        }

        public long TotalHits { get; set; }
        public List&lt;BookDoc&gt; Documents { get; set; }
        public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; Facets { get; set; }
    }
}<h4>配置基本用户界面</h4><p>接下来，添加 SearchBar 组件。在文件<code>/Components/Elasticsearch/SearchBar.razor</code> 中添加以下内容：</p>@using System.Threading.Tasks

&lt;form @onsubmit="SubmitSearch"&gt;
  &lt;div class="input-group mb-3"&gt;
    &lt;input type="text" @bind-value="searchTerm" class="form-control" placeholder="Enter search term..." /&gt;
    &lt;button type="submit" class="btn btn-primary input-btn"&gt;
      &lt;span class="input-group-svg"&gt;
        Search
      &lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/form&gt;

@code {
  [Parameter]
  public EventCallback&lt;string&gt; OnSearch { get; set; }

  private string searchTerm = "";

  private async Task SubmitSearch()
  {
    await OnSearch.InvokeAsync(searchTerm);
  }
}<p>该组件包含一个搜索栏和一个执行搜索的按钮。</p><p>Blazor 允许在同一文件中使用 C# 代码动态生成 HTML，具有极大的灵活性。</p><p>之后，我们将在<code>/Components/Elasticsearch/Results.razor</code> 文件中构建显示搜索结果的结果组件：</p>@using BlazorApp.Models

@if (SearchResults != null &amp;&amp; SearchResults.Any())
{
  &lt;div class="row"&gt;
  @foreach (var result in SearchResults)
    {
      &lt;div class="col-12 mb-3"&gt;
        &lt;div class="card"&gt;
          &lt;div class="row g-0"&gt;
            &lt;div class="col-md-3 image-container"&gt;
              @if (!string.IsNullOrEmpty(result?.ThumbnailUrl))
              {
                &lt;img src="@result?.ThumbnailUrl" class="img-fluid rounded-start" alt="Thumbnail"&gt;
              }
              else
              {
                &lt;div class="placeholder"&gt;
                  @result?.Title
                &lt;/div&gt;
              }
            &lt;/div&gt;

            &lt;div class="col-md-9"&gt; &lt;!-- Adjusted to use the remaining 75% --&gt;
              &lt;div class="card-body"&gt;
                &lt;h4 class="card-title"&gt;
                  @result?.Title
                &lt;/h4&gt;

                &lt;div class="details-container"&gt;
                  &lt;div class=""&gt;

                    @if (result?.Authors?.Any() == true)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Authors: &lt;small class="text-muted"&gt;@string.Join(", ", result.Authors)&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Categories?.Any() == true)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Categories: &lt;small class="text-muted"&gt;@string.Join(", ", result.Categories)&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                  &lt;div class="numPages-status"&gt;
                    @if (result?.PageCount != null)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Pages: &lt;small class="text-muted"&gt;@result.PageCount&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Status != null)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Status: &lt;small class="text-muted"&gt;@result.Status&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                &lt;/div&gt;

                &lt;div class="long-text-container"&gt;
                  &lt;p class="card-text"&gt;&lt;small class="text-muted"&gt;@result?.LongDescription?.Text&lt;/small&gt;&lt;/p&gt;
                &lt;/div&gt;
                @if (!string.IsNullOrEmpty(result?.PublishedDate))
                {
                  &lt;div class="date-container"&gt;
                    &lt;p class="card-text"&gt;
                      Published Date: &lt;small class="text-muted small-date"&gt;@FormatDate(result.PublishedDate)&lt;/small&gt;
                    &lt;/p&gt;
                  &lt;/div&gt;
                }
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    }
  &lt;/div&gt;
}
else if (SearchResults != null)
{
  &lt;p&gt;No results found.&lt;/p&gt;
}

@code {
  [Parameter]
  public List&lt;BookDoc&gt; SearchResults { get; set; } = new List&lt;BookDoc&gt;();

  private string FormatDate(string? date)
  {
    if (DateTime.TryParse(date, out DateTime parsedDate))
    {
      return parsedDate.ToString("MMMM dd, yyyy");
    }
    return "";
  }
}<p>最后，我们需要创建面来过滤搜索结果。</p><p><em>注：筛选器允许用户根据特定属性或类别（如产品类型、价格范围或品牌）缩小搜索结果的范围。这些筛选器通常以复选框的形式显示为可点击的选项，帮助用户缩小搜索范围，更轻松地找到相关结果。在 Elasticsearch 的上下文中，面是通过</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html"><em>聚合</em></a>创建的<em>。</em></p><p>我们在文件<code>/Components/Elasticsearch/Facet.razor</code> 中输入以下代码来设置切面：</p>@if (Facets != null)
{
  &lt;div class="facets-container"&gt;
  @foreach (var facet in Facets)
    {
      &lt;h3&gt;@facet.Key&lt;/h3&gt;
      @foreach (var option in facet.Value)
      {
        &lt;div&gt;
          &lt;input type="checkbox" checked="@IsFacetSelected(facet.Key, option.Key)"
            @onclick="() =&gt; ToggleFacet(facet.Key, option.Key)" /&gt;
          @option.Key (@option.Value)
        &lt;/div&gt;
      }
    }
  &lt;/div&gt;
}


@code {
  [Parameter]
  public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;? Facets { get; set; }

  [Parameter]
  public EventCallback&lt;Dictionary&lt;string, List&lt;string&gt;&gt;&gt; OnFacetChanged { get; set; }

  private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new();

  private void ToggleFacet(string facetName, string facetValue)
  {
    if (!selectedFacets.TryGetValue(facetName, out var facetValues))
    {
      facetValues = selectedFacets[facetName] = new List&lt;string&gt;();
    }

    if (!facetValues.Remove(facetValue))
    {
      facetValues.Add(facetValue);
    }

    OnFacetChanged.InvokeAsync(selectedFacets);
  }

  private bool IsFacetSelected(string facetName, string facetValue)
  {
    return selectedFacets.ContainsKey(facetName) &amp;&amp; selectedFacets[facetName].Contains(facetValue);
  }
}<p>该组件从<code>terms</code> 中读取<code>author</code> 、<code>categories</code> 和<code>status</code> 字段的聚合，然后生成一个过滤器列表，发送回 Elasticsearch。</p><p>现在，让我们把所有东西放在一起。</p><p>在<code>/Components/Pages/Search.razor</code> 文件中：</p>@page "/"
@rendermode InteractiveServer
@using BlazorApp.Models
@using BlazorApp.Services
@inject ElasticsearchService ElasticsearchService
@inject ILogger&lt;Search&gt; Logger

&lt;PageTitle&gt;Search&lt;/PageTitle&gt;

&lt;div class="top-row px-4 "&gt;

    &lt;div class="searchbar-container"&gt;
        &lt;h4&gt;Semantic Search with Elasticsearch and Blazor&lt;/h4&gt;

        &lt;SearchBar OnSearch="PerformSearch" /&gt;
    &lt;/div&gt;

    &lt;a href="https://www.elastic.co/search-labs/esre-with-blazor" target="_blank"&gt;About&lt;/a&gt;
&lt;/div&gt;

&lt;div class="px-4"&gt;

    &lt;div class="search-details-container"&gt;
        &lt;p role="status"&gt;Current search term: @currentSearchTerm&lt;/p&gt;
        &lt;p role="status"&gt;Total results: @totalResults&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class="results-facet-container"&gt;
        &lt;div class="facets-container"&gt;
            &lt;Facet Facets="facets" OnFacetChanged="OnFacetChanged" /&gt;
        &lt;/div&gt;
        &lt;div class="results-container"&gt;
            &lt;Results SearchResults="searchResults" /&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

@code {
    private string currentSearchTerm = "";
    private long totalResults = 0;
    private List&lt;BookDoc&gt; searchResults = new List&lt;BookDoc&gt;();
    private Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
    private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new Dictionary&lt;string, List&lt;string&gt;&gt;();

    protected override async Task OnInitializedAsync()
    {
        await PerformSearch();
    }

    private async Task PerformSearch(string searchTerm = "")
    {
        try
        {
            currentSearchTerm = searchTerm;

            var response = await ElasticsearchService.SearchBooksAsync(currentSearchTerm, selectedFacets);
            if (response != null)
            {
                searchResults = response.Documents;
                facets = response.Facets;
                totalResults = response.TotalHits;
            }
            else
            {
                Logger.LogWarning("Search response is null.");
            }

            StateHasChanged();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error performing search.");
        }
    }

    private async Task OnFacetChanged(Dictionary&lt;string, List&lt;string&gt;&gt; newSelectedFacets)
    {
        selectedFacets = newSelectedFacets;
        await PerformSearch(currentSearchTerm);
    }
}<p>我们的页面正在运行！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc750e55fff43de4b/6a17f6636864a4ab08b68931/5f0f25cb029a34df7f577a9f307278226ed07c3f-816x473.png" alt="Blazor 页面示例" /><p>如您所见，该页面功能齐全，但缺乏风格。让我们添加一些 CSS，使其看起来更有条理，反应更灵敏。</p><p>让我们开始更换布局样式。在<code>Components/Layout/MainLayout.razor.css</code> 文件中：</p>.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}<p>在<code>Components/Pages/Search.razor.css</code> 文件中添加搜索页面的样式：</p>.input-group .input-group-svg {
  background: transparent;
  border: transparent;
  pointer-events: none;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.search-details-container {
  display: flex;
  justify-content: space-between;
  margin-top: 1rem;
}

.searchbar-container {
  padding-top: 2rem;
  display: flex;
  flex-direction: column; 
  flex-grow: 1;
  height: 100%;
  max-width: 100%; 
}

.searchbar-container h4 {
  margin: 0;
}

.top-row {
  margin-top: -1.1rem;
  position: relative; 
  background-color: hsl(216, 29%, 67%);
  border-bottom: 1px solid #d6d5d5;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 1rem;
}

.top-row a {
  margin-left: auto;
  margin-top: -4rem; 
  color: #000000;
  text-decoration: none;
}

.top-row a:hover {
  text-decoration: underline;
}

@media (max-width: 640.98px) {
  .top-row {
    justify-content: space-between;
  }

  .top-row ::deep a,
  .top-row ::deep .btn-link {
    margin-left: 0;
  }
}

@media (min-width: 641px) {
  .top-row.auth ::deep a:first-child {
    flex: 1;
    text-align: right;
    width: 0;
  }

  .top-row,
  article {
    padding-left: 2rem !important;
    padding-right: 1.5rem !important;
  }
}<p>我们的页面开始变得更好看了：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ecc49f404f18591/6a17f6654b055d959b432384/3dadd7ade2dd9070300a4e0514a4da2ae9cc9fb9-817x473.png" alt="为搜索页面添加样式后的 Blazor 页面" /><p>让我们来做最后的润色：</p><p>创建以下文件</p><ul><li><p>Components/Elasticsearch/Facet.razor.css</p></li><li><p>Components/Elasticsearch/Results.razor.css</p></li></ul><p>并为<code>Facet.razor.css</code> 添加样式：</p>.facets-container {
  font-size: 15px;
  margin-right: 4rem;
  overflow-x: auto;
  white-space: nowrap;
  max-width: 300px;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.results-facet-container &gt; * {
  flex-shrink: 0;
}<p>供<code>Results.razor.css</code> ：</p>.image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 1rem;
  box-sizing: border-box;
}

.image-container img {
  max-width: 100%;
  height: auto;
  border-radius: 0.5rem;
}

.placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  font-size: 0.9rem;
  color: #888;
  text-align: center;
  padding: 1rem;
  border-radius: 0.5rem;
}

.card-body {
  padding: 1rem;
}

.details-container {
  display: flex;
  justify-content: space-between;
  padding: 1.5rem 0;
}

.date-container {
  margin-top: 1rem;
  display: flex;
  justify-content: flex-end;
}

.date-container .small-date {
  font-weight: bold;
}<p>最终结果</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78bbd8e6f9e23988/6a17f6664b055d2ed8432388/3e68ec4a38775fdfbeaa1a990e6a1e11dfb081d8-816x472.png" alt="构建 blazor 应用程序页面的最终结果" /><p>要运行应用程序，可以使用以下命令：</p><p><code>dotnet watch</code></p><p>你做到了现在，您可以使用搜索栏在 Elasticsearch 索引中搜索图书，并按作者、类别和状态过滤结果。</p><h3>进行全文和语义搜索</h3><p>默认情况下，我们的应用程序将使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html"> 全文</a> 和 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">语义搜索</a><a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search"> 执行 混合</a> 搜索 。您可以通过创建两个独立的方法来改变搜索逻辑，一个用于全文搜索，另一个用于语义搜索，然后根据用户的输入选择一种方法来创建查询。</p><p>在<code>/Services/ElasticsearchService.cs</code> 文件中的<code>ElasticsearchService</code> 类中添加以下方法：</p>private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildSemanticQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return query =&gt;
        query.Bool(b =&gt;
            b.Must(m =&gt; m.Semantic(sem =&gt; sem.Field("longDescription").Query(searchTerm)))
                .Filter(filters.ToArray())
        );
}

private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildMultiMatchQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    if (string.IsNullOrEmpty(searchTerm))
    {
        return query =&gt; query.Bool(b =&gt; b.Filter(filters.ToArray()));
    }

    return query =&gt;
        query.Bool(b =&gt;
            b.Should(m =&gt;
                    m.MultiMatch(mm =&gt;
                        mm.Query(searchTerm).Fields(new[] { "title", "shortDescription" })
                    )
                )
                .Filter(filters.ToArray())
        );
}<p>这两种方法的工作原理与<code>BuildHybridQuery</code> 方法类似，但它们只进行全文或语义搜索。</p><p>您可以修改<code>SearchBooksAsync</code> 方法，使用选定的搜索方法：</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");
        
        // Modify the query builder to use the selected search method.
        var multiMatchQuery = BuildMultiMatchQuery(searchTerm, selectedFacets); // For full text search
        var semanticQuery = BuildSemanticQuery(searchTerm, selectedFacets); // For semantic search

        // In this case we will not use retrievers, but you can add them if you want to use them.
        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Query(multiMatchQuery) // Change this line to use different search methods, for example: .Query(semanticQuery) for semantic search
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}<p>您可<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor">在此处</a>找到完整的申请表</p><h2>结论</h2><p>Blazor 是一个有效的框架，可让您使用 C# 构建网络应用程序。Elasticsearch 是一个功能强大的搜索引擎，可让您构建搜索应用程序。将两者结合起来，您就可以轻松构建强大的搜索应用程序，利用 ESRE 的强大功能在短时间内创建<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">语义搜索体验</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7424ac5f0b223b4/6a17f668414c641971945323/7ba0d6bec908bfcae966b7f38626fabd682c6f3d-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LangChain4j 使用 Elasticsearch 作为嵌入存储]]></title>
    <description><![CDATA[LangChain4j（Java 版 LangChain）将 Elasticsearch 作为嵌入式存储。了解如何使用它在普通 Java 中构建 RAG 应用程序。]]></description>
    <content:encoded><![CDATA[<p>
在<a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">上一篇文章</a>中，我们了解了什么是 LangChain4j 以及如何使用：</p><ul><li><p>与法律硕士进行讨论，实施<code>ChatLanguageModel</code> 和 <code>ChatMemory</code></p></li><li><p>在记忆中保留聊天记录，以便回忆起之前与一位法律硕士讨论的背景情况</p></li></ul><p>本博文将介绍如何</p><ul><li><p>根据文本示例创建向量嵌入</p></li><li><p>将向量嵌入存储在 Elasticsearch 嵌入存储中 </p></li><li><p>搜索类似载体</p></li></ul><h2>创建嵌入</h2><p>要创建嵌入式，我们需要定义一个<code>EmbeddingModel</code> 。例如，我们可以使用<a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">上一篇文章</a>中使用过的相同的 mistral 模型。它和奥拉马一起跑：</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>模型能够从文本中生成向量。在这里，我们可以检查模型生成的维数：</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>要从文本中生成向量，我们可以使用</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>或者，如果我们还想提供元数据，以便对文本、价格、发布日期等内容进行筛选，我们可以使用<code>Metadata.from()</code>.NET。例如，我们在这里添加游戏名称作为元数据字段：</p>TextSegment game1 = TextSegment.from("""
    The game starts off with the main character Guybrush Threepwood stating "I want to be a pirate!"
    To do so, he must prove himself to three old pirate captains. During the perilous pirate trials, 
    he meets the beautiful governor Elaine Marley, with whom he falls in love, unaware that the ghost pirate 
    LeChuck also has his eyes on her. When Elaine is kidnapped, Guybrush procures crew and ship to track 
    LeChuck down, defeat him and rescue his love.
""", Metadata.from("gameName", "The Secret of Monkey Island"));
Response&lt;Embedding&gt; response1 = model.embed(game1);
TextSegment game2 = TextSegment.from("""
    Out Run is a pseudo-3D driving video game in which the player controls a Ferrari Testarossa 
    convertible from a third-person rear perspective. The camera is placed near the ground, simulating 
    a Ferrari driver's position and limiting the player's view into the distance. The road curves, 
    crests, and dips, which increases the challenge by obscuring upcoming obstacles such as traffic 
    that the player must avoid. The object of the game is to reach the finish line against a timer.
    The game world is divided into multiple stages that each end in a checkpoint, and reaching the end 
    of a stage provides more time. Near the end of each stage, the track forks to give the player a 
    choice of routes leading to five final destinations. The destinations represent different 
    difficulty levels and each conclude with their own ending scene, among them the Ferrari breaking 
    down or being presented a trophy.
""", Metadata.from("gameName", "Out Run"));
Response&lt;Embedding&gt; response2 = model.embed(game2);<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">Step5EmbedddingsTest.java</a>类。</p><h2>添加 Elasticsearch 来存储向量</h2><p>LangChain4j 提供内存嵌入存储。这对运行简单测试非常有用：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>但是，这显然不能用于更大的数据集，因为该数据存储将所有内容都存储在内存中，而我们的服务器上没有无限的内存。因此，我们可以将嵌入式数据存储到 Elasticsearch 中，根据定义，Elasticsearch 是"elastic" ，可以随着数据的扩展而扩展。为此，让我们在项目中添加 Elasticsearch：</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>正如你所注意到的，我们还在项目中添加了 Elasticsearch TestContainers 模块，这样我们就可以从测试中启动 Elasticsearch 实例：</p>// Create the elasticsearch container
ElasticsearchContainer container =
  new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.15.0")
    .withPassword("changeme");

// Start the container. This step might take some time...
container.start();

// As we don't want to make our TestContainers code more complex than
// needed, we will use login / password for authentication.
// But note that you can also use API keys which is preferred.
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level Rest client which connects to the elasticsearch container.
client = RestClient.builder(HttpHost.create("https://" + container.getHttpHostAddress()))
  .setHttpClientConfigCallback(httpClientBuilder -&gt; {
    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
    httpClientBuilder.setSSLContext(container.createSslContextFromCa());
    return httpClientBuilder;
  })
  .build();

// Check the cluster is running
client.performRequest(new Request("GET", "/"));<p>要将 Elasticsearch 用作嵌入式存储，"，" ，就必须从 LangChain4j 内存数据存储切换到 Elasticsearch 数据存储：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>这将在 Elasticsearch 中以<code>default</code> 索引的形式存储向量。您还可以将索引名称改为更有意义的名称：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>如果您想运行此代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">Step6ElasticsearchEmbedddingsTest.java</a>类。</p><h2>搜索类似载体</h2><p>要搜索相似向量，我们首先需要使用之前使用过的相同模型，将问题转换为向量表示。我们已经做到了，所以再做一次并不难。请注意，在这种情况下我们不需要元数据：</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>我们可以用问题的这种表示法建立一个搜索请求，并要求嵌入式存储空间找出最前面的向量：</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>现在，我们可以遍历结果并打印一些信息，如来自元数据的游戏名称和得分：</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>正如我们所预料的那样，"Out Run" 作为第一击：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="跑出" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">Step7SearchForVectorsTest.java</a>类。 </p><h2>幕后花絮</h2><p>Elasticsearch 嵌入存储的默认配置是在后台使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">近似 kNN 查询</a>。</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>但这可以通过向嵌入存储区提供默认配置 (<code>ElasticsearchConfigurationKnn</code>) 以外的另一种配置 (<code>ElasticsearchConfigurationScript</code>) 来改变：</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p><code>ElasticsearchConfigurationScript</code><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"></a>执行程序使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"><code>cosineSimilarity</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"> 函数 在后台运行</a> 查询 。</p><p>基本上，打电话时</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>现在呼叫</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>在这种情况下，结果并不会因为"order" 而发生变化，只是分数会有所调整，因为<code>cosineSimilarity</code> 调用并不使用任何近似值，而是计算每个匹配向量的余弦值：</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>如果您想运行这段代码，请查看<a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">Step7SearchForVectorsTest.java</a>类。</p><h2>结论</h2><p>我们已经介绍了如何从文本中轻松生成嵌入，以及如何使用两种不同的方法在 Elasticsearch 中存储和搜索近邻：</p><ul><li><p>使用<code>ElasticsearchConfigurationKnn</code> 默认选项进行近似和快速<code>knn</code> 查询</p></li><li><p>使用<code>ElasticsearchConfigurationScript</code> 选项进行精确但较慢的<code>script_score</code> 查询</p></li></ul><p>下一步将根据我们在这里学到的知识，构建一个完整的 RAG 应用程序。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc873b86c76d1798/6a170293acf088f666be99b3/abd8a4a809064101c037af66b87f28e5ecde03b0-1474x645.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[高级 RAG 技术第 2 部分：查询和测试]]></title>
    <description><![CDATA[讨论并实施可提高 RAG 性能的技术。第 2 部分（共 2 部分），重点是查询和测试高级 RAG 管道。]]></description>
    <content:encoded><![CDATA[<p><em>所有代码都可以 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>在 Searchlabs 软件仓库的 advanced-rag-techniques 分支中</em></a>找到 <em>。</em></p><p>欢迎阅读我们关于高级 RAG 技术文章的第二部分！在<a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">本系列的第 1 部分</a>中，我们建立、讨论并实施了高级 RAG 管道的数据处理组件：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="高级 RAG 管道" /><p>在这一部分，我们将继续查询和测试我们的实现。让我们直奔主题！</p><h3>目录</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#searching-and-retrieving,-generating-answers">搜索和检索，生成答案</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#enriching-queries-with-synonyms">用同义词丰富查询</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-hypothetical-document-embedding">HyDE（假设文档嵌入）</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hybrid-search">混合搜索</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#experiments">实验</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#summary-of-results">结果摘要</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-1-who-audits-elastic">测试 1：谁审核了 Elastic？</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-2--total-revenue-2023">测试 2：2023 年总收入</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-1">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-1">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-3-what-product-does-growth-primarily-depend-on-how-much">测试 3：增长主要依赖于什么产品？多少钱？</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-2">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-2">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-4-describe-employee-benefit-plan">测试 4：说明员工福利计划</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-3">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-3">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-5-which-companies-did-elastic-acquire">测试 5：Elastic 收购了哪些公司？</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-4">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-4">SimpleRAG</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#conclusion">结论</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#appendix">附录</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#prompts">提示</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#rag-question-answering-prompt">RAG 问题解答提示</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#elastic-query-generator-prompt">弹性查询生成器提示</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#potential-questions-generator-prompt">潜在问题生成器提示</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-generator-prompt">HyDE 生成器提示</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#sample-hybrid-search-query">混合搜索查询示例</a></p></li></ul></li></ul><h2>搜索和检索，生成答案</h2><p>让我们提出第一个问题，最好是主要在年度报告中找到的一些信息。怎么样？</p>Who audits Elastic?"
<p>现在，让我们运用一些技术来增强查询。</p><h3>用同义词丰富查询</h3><p>首先，让我们增强查询措辞的多样性，并将其转化为可轻松处理成 Elasticsearch 查询的形式。我们将借助 GPT-4o 将查询转换为 OR 子句列表。让我们来写下这个提示：</p>
ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<p>当应用到我们的查询时，GPT-4o 会生成基本查询和相关词汇的同义词。</p>'audits elastic OR 
elasticsearch audits OR 
elastic auditor OR 
elasticsearch auditor OR 
elastic audit firm OR 
elastic audit company OR 
elastic audit organization OR 
elastic audit service'
<p>在<code>ESQueryMaker</code> 类中，我定义了一个分割查询的函数：</p>def parse_or_query(self, query_text: str) -&gt; List[str]:
    # Split the query by 'OR' and strip whitespace from each term
    # This converts a string like "term1 OR term2 OR term3" into a list ["term1", "term2", "term3"]
    return [term.strip() for term in query_text.split(' OR ')]
<p>它的作用是将这串 OR 子句拆分成一个术语列表，使我们能够对关键文档字段进行多重匹配：</p>["original_text", 'keyphrases', 'potential_questions', 'entities']
<p>最后得出了这个疑问：</p> 'query': {
    'bool': {
        'must': [
            {
                'multi_match': {
                'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
                'fields': [
                    'original_text',
                'keyphrases',
                'potential_questions',
                'entities'
                ],
                'type': 'best_fields',
                'operator': 'or'
                }
            }
      ]
<p>这比原始查询涵盖的范围更广，有望降低因忘记同义词而错过搜索结果的风险。但我们可以做得更多。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h3>HyDE（假设文档嵌入）</h3><p>让我们再次利用 GPT-4o 来实现<a href="https://arxiv.org/abs/2212.10496">HyDE</a>。</p><p>HyDE 的基本前提是生成一个假设文档--一种可能包含原始查询答案的文档。文件的真实性或准确性并不重要。有鉴于此，让我们写下下面的提示：</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<p>由于矢量搜索通常是通过余弦矢量相似性进行操作的，因此 HyDE 的前提是，我们可以通过文档与文档的匹配，而不是查询与文档的匹配，来获得更好的结果。</p><p>我们关心的是结构、流程和术语。事实性不强。GPT-4o 可以输出这样的 HyDE 文档：</p>'Elastic N.V., the parent company of Elastic, the organization known for developing Elasticsearch, is subject to audits to ensure financial accuracy, 
regulatory compliance, and the integrity of its financial statements. The auditing of Elastic N.V. is typically conducted by an external, 
independent auditing firm. This is common practice for publicly traded companies to provide stakeholders with assurance regarding the company\'s 
financial position and operations.\n\nThe primary external auditor for Elastic is the audit firm Ernst &amp; Young LLP (EY). Ernst &amp; Young is one of the 
four largest professional services networks in the world, commonly referred to as the "Big Four" audit firms. These firms handle a substantial number 
of audits for major corporations around the globe, ensuring adherence to generally accepted accounting principles (GAAP) and international financial 
reporting standards (IFRS).\n\nThe audit process conducted by EY involves several steps. Initially, the auditors perform a risk assessment to identify 
areas where misstatements due to error or fraud could occur. They then design audit procedures to test the accuracy and completeness of financial statements,
 which include examining financial transactions, assessing internal controls, and reviewing compliance with relevant laws and regulations. Upon completion of 
 the audit, Ernst &amp; Young issues an audit report, which includes the auditor’s opinion on whether the financial statements are free from material misstatement 
 and are presented fairly in accordance with the applicable financial reporting framework.\n\nIn addition to external audits by firms like Ernst &amp; Young, 
 Elastic may also be subject to internal audits. Internal audits are performed by the company’s own internal auditors to evaluate the effectiveness of internal 
 controls, risk management, and governance processes.\n\nOverall, the auditing process plays a crucial role in maintaining the transparency and reliability of 
 Elastic\'s financial information, providing confidence to investors, regulators, and other stakeholders.'
<p>它看起来非常可信，是我们希望索引的文档类型的理想候选者。我们将把它嵌入并用于混合搜索。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h3>混合搜索</h3><p>这是我们搜索逻辑的核心。我们的词法搜索组件将是生成的 OR 子句字符串。我们的密集矢量组件将是嵌入式 HyDE 文档（又称搜索矢量）。我们使用 KNN 来有效识别与搜索向量最接近的几个候选文档。我们将词法搜索组件默认称为<em>TF-IDF 和 BM25 评分</em>。最后，将采用<a href="https://arxiv.org/abs/2407.01219">Wang 等</a>人推荐的 30/70 比例合并词性和密集向量得分。</p>def hybrid_vector_search(self, index_name: str, query_text: str, query_vector: List[float], 
                         text_fields: List[str], vector_field: str, 
                         num_candidates: int = 100, num_results: int = 10) -&gt; Dict:
    """
    Perform a hybrid search combining text-based and vector-based similarity.

    Args:
        index_name (str): The name of the Elasticsearch index to search.
        query_text (str): The text query string, which may contain 'OR' separated terms.
        query_vector (List[float]): The query vector for semantic similarity search.
        text_fields (List[str]): List of text fields to search in the index.
        vector_field (str): The name of the field containing document vectors.
        num_candidates (int): Number of candidates to consider in the initial KNN search.
        num_results (int): Number of final results to return.

    Returns:
        Dict: A tuple containing the Elasticsearch response and the search body used.
    """
    try:
        # Parse the query_text into a list of individual search terms
        # This splits terms separated by 'OR' and removes any leading/trailing whitespace
        query_terms = self.parse_or_query(query_text)

        # Construct the search body for Elasticsearch
        search_body = {
            # KNN search component for vector similarity
            "knn": {
                "field": vector_field,  # The field containing document vectors
                "query_vector": query_vector,  # The query vector to compare against
                "k": num_candidates,  # Number of nearest neighbors to retrieve
                "num_candidates": num_candidates  # Number of candidates to consider in the KNN search
            },
            "query": {
                "bool": {
                    # The 'must' clause ensures that matching documents must satisfy this condition
                    # Documents that don't match this clause are excluded from the results
                    "must": [
                        {
                            # Multi-match query to search across multiple text fields
                            "multi_match": {
                                "query": " ".join(query_terms),  # Join all query terms into a single space-separated string
                                "fields": text_fields,  # List of fields to search in
                                "type": "best_fields",  # Use the best matching field for scoring
                                "operator": "or"  # Match any of the terms (equivalent to the original OR query)
                            }
                        }
                    ],
                    # The 'should' clause boosts relevance but doesn't exclude documents
                    # It's used here to combine vector similarity with text relevance
                    "should": [
                        {
                            # Custom scoring using a script to combine vector and text scores
                            "script_score": {
                                "query": {"match_all": {}},  # Apply this scoring to all documents that matched the 'must' clause
                                "script": {
                                    # Script to combine vector similarity and text relevance
                                    "source": """
                                    # Calculate vector similarity (cosine similarity + 1)
                                    # Adding 1 ensures the score is always positive
                                    double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;
                                    # Get the text-based relevance score from the multi_match query
                                    double text_score = _score;
                                    # Combine scores: 70% vector similarity, 30% text relevance
                                    # This weighting can be adjusted based on the importance of semantic vs keyword matching
                                    return 0.7 * vector_score + 0.3 * text_score;
                                    """,
                                    # Parameters passed to the script
                                    "params": {
                                        "query_vector": query_vector,  # Query vector for similarity calculation
                                        "vector_field": vector_field  # Field containing document vectors
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        }

        # Execute the search request against the Elasticsearch index
        response = self.conn.search(index=index_name, body=search_body, size=num_results)
        # Log the successful execution of the search for monitoring and debugging
        logger.info(f"Hybrid search executed on index: {index_name} with text query: {query_text}")
        # Return both the response and the search body (useful for debugging and result analysis)
        return response, search_body
    except Exception as e:
        # Log any errors that occur during the search process
        logger.error(f"Error executing hybrid search on index: {index_name}. Error: {e}")
        # Re-raise the exception for further handling in the calling code
        raise e
<p>最后，我们可以拼凑出一个 RAG 函数。我们的 RAG（从询问到答复）将遵循这一流程：</p><ol><li><p>将查询转换为 OR 子句。</p></li><li><p>生成 HyDE 文档并嵌入。</p></li><li><p>将二者作为混合搜索的输入。</p></li><li><p>检索前 N 个结果，将它们倒转，使最相关的得分是 LLM 上下文内存中"最近的" （反向打包） 反向打包示例：查询："Elasticsearch 查询优化技术" 检索文档（按相关性排序）：  LLM 上下文的反向顺序：  通过颠倒顺序，最相关的信息(1)会出现在上下文的最后，从而可能在生成答案时受到 LLM 的更多关注。</p><ol><li><p>"使用 bool 查询可有效组合多个搜索条件。"</p></li><li><p>"实施缓存策略，缩短查询响应时间。"</p></li><li><p>"优化索引映射，提高搜索性能。"</p></li><li><p>"优化索引映射，提高搜索性能。"</p></li><li><p>"实施缓存策略，缩短查询响应时间。"</p></li><li><p>"使用 bool 查询可有效组合多个搜索条件。"</p></li></ol></li><li><p>将上下文传递给 LLM 生成。</p></li></ol>def get_context(index_name, 
                match_query, 
                text_query, 
                fields, 
                num_candidates=100, 
                num_results=20, 
                text_fields=["original_text", 'keyphrases', 'potential_questions', 'entities'], 
                embedding_field="primary_embedding"):

    embedding=embedder.get_embeddings_from_text(text_query)

    results, search_body = es_query_maker.hybrid_vector_search(
        index_name=index_name,
        query_text=match_query,
        query_vector=embedding[0][0],
        text_fields=text_fields,
        vector_field=embedding_field,
        num_candidates=num_candidates,
        num_results=num_results
    )

    # Concatenates the text in each 'field' key of the search result objects into a single block of text.
    context_docs=['\n\n'.join([field+":\n\n"+j['_source'][field] for field in fields]) for j in results['hits']['hits']]

    # Reverse Packing to ensure that the highest ranking document is seen first by the LLM.
    context_docs.reverse()
    return context_docs, search_body

def retrieval_augmented_generation(query_text):
    match_query= gpt4o.generate_query(query_text)
    fields=['original_text']

    hyde_document=gpt4o.generate_HyDE(query_text)

    context, search_body=get_context(index_name, match_query, hyde_document, fields)

    answer= gpt4o.basic_qa(query=query_text, context=context)
    return answer, match_query, hyde_document, context, search_body

<p>让我们运行查询并得到答案：</p>According to the context, Elastic N.V. is audited by an independent registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of independent registered public accounting firm," which states:

"We have audited the accompanying consolidated balance sheets of Elastic N.V. [...] / s / pricewaterhouseco."
<p>不错。没错。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h2>实验</h2><p>现在有一个重要问题需要回答。我们在这些实施中投入了如此多的精力和额外的复杂性，究竟得到了什么？</p><p>让我们来做个小小的比较。我们实施的 RAG 管道与基线混合搜索相比，没有任何增强功能。我们将进行一系列小测试，看看是否会发现任何实质性差异。我们将把刚刚实现的 RAG 称为 AdvancedRAG，把基本管道称为 SimpleRAG。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" alt="简单的 RAG 管道" /><h4>结果摘要</h4><p>本表总结了两种 RAG 管道的五次测试结果。我根据答案的细节和质量来判断每种方法的相对优劣，但这完全是主观判断。现将实际答案转载于下表，供您参考。说了这么多，让我们来看看他们的表现如何！</p><p>SimpleRAG 无法回答问题 1&amp; 5。AdvancedRAG 对问题 2、3 和 4 的回答也要详细得多。基于更多的细节，我认为 AdvancedRAG 的答案质量更高。</p><p>测试</p><p>问题</p><p>高级 RAG 性能</p><p>SimpleRAG 性能</p><p>AdvancedRAG 延迟</p><p>SimpleRAG 延迟</p><p>优胜者</p><p>1</p><p>谁审核 Elastic？</p><p>正确确定普华永道为审计员。</p><p>未能确定审计员。</p><p>11.6s</p><p>4.4s</p><p>AdvancedRAG</p><p>2</p><p>2023 年的总收入是多少？</p><p>提供了正确的收入数字。包括往年收入的补充情况。</p><p>提供了正确的收入数字。</p><p>13.3s</p><p>2.8s</p><p>AdvancedRAG</p><p>3</p><p>增长主要依靠什么产品？多少钱？</p><p>正确指出弹性云是关键驱动因素。包括总体收入情况&amp; 。</p><p>正确指出弹性云是关键驱动因素。</p><p>14.1s</p><p>12.8s</p><p>AdvancedRAG</p><p>4</p><p>说明员工福利计划</p><p>全面介绍了退休计划、医疗计划和其他福利。包括不同年份的具体捐款额。</p><p>提供了很好的福利概览，包括薪酬、退休计划、工作环境和 Elastic Cares 计划。</p><p>26.6s</p><p>11.6s</p><p>AdvancedRAG</p><p>5</p><p>Elastic 收购了哪些公司？</p><p>正确列出了报告中提到的近期收购（CmdWatch、Build Security 和 Optimyze）。提供了一些收购日期和收购价格。</p><p>未能从提供的上下文中检索到相关信息。</p><p>11.9s</p><p>2.7s</p><p>AdvancedRAG</p><h4>测试 1：谁审核了 Elastic？</h4><p>AdvancedRAG</p>According to the context, Elastic N.V. is audited by an independent 
registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of 
independent registered public accounting firm," which states:
"We have audited the accompanying consolidated balance sheets 
of Elastic N.V. [...] / s / pricewaterhouseco."
<p>SimpleRAG</p>According to the provided context, no specific auditing firm 
or company is mentioned as the auditor for Elastic. 
The context provided talks about various features, strengths, 
deployment options, and other details related to Elastic and 
its operations but does not specify which auditing firm is 
responsible for auditing Elastic's financial statements or 
internal controls.
<p><strong>摘要</strong>：SimpleRAG 没有将普华永道确定为审计机构</p><p>好吧，这其实挺让人惊讶的。这看起来像是 SimpleRAG 的搜索失败。没有检索到与审计有关的文件。让我们在下一个测试中降低难度。</p><h4>测试 2：2023 年总收入</h4><p>AdvancedRAG</p>### Total Revenue for 2023

According to the provided context, the total revenue for the 
year ended April 30, 2023, was **$1,068,989,000**. This 
information can be found several times in the provided text. 
Specifically, one part states:

"The following table sets forth our results of operations 
for the periods presented in dollars and as a percentage of 
our total revenue. ... year ended April 30, 2023, ... 
total revenue 1,068,989".

Additionally, it is mentioned here as well:

"For the years ended April 30, 2023, 2022, and 2021, 
representing year-over-year growth of 24% for the year 
ended April 30, 2023... total revenue $1,068,989".

Therefore, the total revenue for 2023 is $1.069 billion.
<p>SimpleRAG</p>According to the context provided, the total revenue for the fiscal year ended April 30, 2023, is:

$1,068,989,000 (or approximately $1.1 billion).
<p><strong>摘要</strong>：两个 RAG 都得到了正确答案：2023 年总收入为 1,068,989,000 美元</p><p>他们都在这里。看来，AdvancedRAG 可能获得了更多的文件？当然，答案会更加详细，并包含往年的信息。考虑到我们所做的改进，这是意料之中的，但现在下结论还为时过早。</p><p>让我们提高难度。</p><h4>测试 3：增长主要依赖于什么产品？多少钱？</h4><p>AdvancedRAG</p>### Product Growth Primarily Depends On:
According to the context, "our revenue is derived primarily from 
subscriptions of search, observability and security built into 
the elastic stack." The document mentions various strategies and 
factors contributing to the company’s growth which include investment 
in "elastic cloud," "elastic stack," and the conversion of free 
users to paid subscribers.

### Quantitative Data:
- **Revenue**: The document states, "Our total revenue was $1.1 billion, 
$862.4 million, and $608.5 million for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Subscription Revenue**: "Subscriptions accounted for 92%, 93%, 
and 93% of our total revenue for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Elastic Cloud Contribution**: "For the years ended April 30, 2023, 
2022, and 2021, Elastic Cloud contributed 40%, 35%, and 27% of our 
total revenue, respectively."
- **Customer Growth**: The number of customers increased from 
"over 15,000 as of April 30, 2021," to "approximately 20,200 as of 
April 30, 2023."

### Summary:
Product growth for the company primarily depends on the "Elastic Stack" 
(which includes search, observability, and security solutions) and 
particularly the "Elastic Cloud." The Elastic Cloud's growing contribution 
to revenue highlights its significance, which grew from 27% in 2021 to 40% in 2023.
<p>SimpleRAG</p>According to the provided context, growth primarily depends on **Elastic Cloud**, 
which is described as the company's "family of cloud-based offerings." 
The context also emphasizes the significance of Elastic Cloud in achieving 
the company's long-term growth potential and its role in increasing subscription revenue.

From the context:

- "Increasing adoption of Elastic Cloud... is an important growth opportunity 
for our business. Organizations are increasingly looking for hosted 
deployment alternatives with reduced administrative burdens."
- "We believe that offering Elastic Cloud is important for achieving 
our long-term growth potential, and we expect Elastic Cloud’s contribution 
to our subscription revenue to continue to increase."

In terms of the contribution to total revenue, here are the specific figures provided:

- Elastic Cloud contributed 40% of the total revenue for the year ended April 30, 2023.
- Elastic Cloud contributed 35% of the total revenue for the year ended April 30, 2022.
- Elastic Cloud contributed 27% of the total revenue for the year ended April 30, 2021.

This progression shows a clear upward trend in the importance and 
contribution of Elastic Cloud to the company's overall revenue.
<p><strong>摘要</strong>：两个 RAG 都正确地将弹性云确定为主要增长动力。不过，AdvancedRAG 包含更多细节，将订阅收入和客户增长考虑在内，并明确提及其他 Elastic 产品。</p><h4>测试 4：说明员工福利计划</h4><p>AdvancedRAG</p>### Employee Benefit Plan Overview

The provided context outlines various elements of the employee benefit plan at 
Elastic, often referred to as "Elasticians." Here are the key components 
described in the texts:

1. **401(k) Plan**:
   - **Coverage**: The 401(k) plan is available to substantially all U.S. 
   employees who meet minimum age and service requirements.
   - **Contributions**: Elastic makes contributions to the 401(k) plan up to 
   6% of the participating employee’s W-2 earnings and wages.
   - **Expenses**: For the fiscal years ended April 30, Elastic recorded 
   expenses of $17.9 million (2023), $15.2 million (2022), and $11.4 million (2021) 
   related to the 401(k) plan.
   - **Defined-Contribution Plans in Other Countries**: Elastic has 
   defined-contribution plans in various other countries and recorded respective 
   expenses of $9.4 million (2023), $7.2 million (2022), and $5.1 million (2021).

2. **Stock-Based Compensation**:
   - **Types of Awards**: Stock options, restricted stock units (RSUs), 
   and shares under the Employee Stock Purchase Plan (ESPP).
   - **Fair Value Measurement**: Fair value of these stock awards is 
   measured using models like Black-Scholes.
   - **Employee Stock Purchase Plan (2022 ESPP)**: 
     - Started in 2022, it allows employees to acquire ordinary 
     shares at a discount (85% of the market value at the beginning 
     or end of the offering period).
     - Offering periods are approximately six months long.

3. **Total Rewards Compensation**:
   - **Components**: Includes cash compensation as well as equity awards, 
   reflecting a comprehensive interest in the well-being of the employees.
   - **Programs**: Market competitive medical and dental programs, 
   mental health, and holistic well-being programs.
   - **Paid Time Off**: Features 16 weeks of paid leave for new 
   parents and life-planning benefits.

4. **Retirement and Income Protection Plans**:
   - **401(k) Match**: Elastic provides a dollar-for-dollar 
   match up to 6% of eligible earnings up to a plan-limit 
   maximum for U.S.-based employees.
   - **Similar Plans Outside the U.S.**: Competitive retirement 
   plans available for employees based outside the United States.

5. **Flexible Work Environment**: 
   - **Distributed Workforce**: Elastic operates with a global 
   distributed workforce model, allowing employees to work remotely 
   and providing a broad candidate pool.
   
6. **Learning and Development**:
   - **Programs**: Access to on-demand video-based learning, 
   leadership development programs, and other specific training 
   to develop managers and leaders.

7. **Employee Engagement and Surveys**:
   - **Mechanisms**: Annual employee engagement survey and 
   mid-year pulse survey.
   - **Objectives**: To monitor morale, develop action plans, 
   and maintain high engagement scores.

8. **Onboarding Program**:
   - **X-School**: An extensive new-hire orientation program to 
   help new employees meet peers and learn about the company's 
   products and solutions.

In summary, Elastic's employee benefit plan encompasses a broad 
spectrum of financial, health, well-being, and professional development 
benefits designed to support and enhance the lives of its employees. 
The plan is both competitive and comprehensive, aiming to attract,
retain, and engage its global workforce.
<p>SimpleRAG</p>The employee benefit plan at the Company includes several key 
components aimed at enhancing the overall well-being and 
professional development of employees, referred to as "Elasticians." 

1. **Compensation and Benefits:**
   - The Company provides market-competitive compensation 
   packages which typically include cash compensation and equity awards.
   - Comprehensive medical and dental programs are offered, 
   emphasizing mental health and holistic well-being.
   - Paid time off programs feature:
     - 16 weeks of paid leave for new parents.
     - Life-planning benefits.
     - Travel reimbursements for certain healthcare services.

2. **Retirement and Income Protection Plans:**
   - In the United States, employees are eligible for a 401k 
   plan with a dollar-for-dollar match by the Company up to 6% 
   of eligible earnings, subject to a plan limit.
   - Similar competitive retirement and income protection plans 
   are available for employees outside the United States.

3. **Work Environment:**
   - The Company offers a flexible work environment, allowing most 
   employees to work from anywhere as often as they would like.
   - Physical offices around the world are available for employees 
   who prefer to work on-site or need a space for face-to-face interactions.

4. **Community Involvement:**
   - The Elastic Cares Program supports charitable organizations 
   that matter to employees on both local and global levels.
   - The program includes:
     - Donation matching.
     - Providing the Company's technology for free to certain 
     nonprofit organizations.
     - Volunteer time off initiative.
<p><strong>摘要</strong>：AdvancedRAG 更深入、更详细地介绍了美国员工的 401K 计划，以及美国以外地区的缴费计划。报告还提到了 "健康与福利计划"，但没有提到 SimpleRAG 提到的 "Elastic Cares 计划"。</p><h4>测试 5：Elastic 收购了哪些公司？</h4><p>AdvancedRAG</p>Elastic acquired several companies over the years to enhance its 
technology and market presence. According to the provided context, 
the specific acquisitions include:

1. **CmdWatch Security Inc.**: Acquired on September 17, 2021. 
The total purchase consideration was $77.8 million.
2. **Build Security Ltd.**: Acquired on September 2, 2021. The 
combined purchase price for Build Security Ltd. and another acquisition was $57.2 million.
3. **Optimyze Cloud Inc.**: Acquired on November 1, 2021. Part 
of the combined purchase price mentioned above with Build Security Ltd.
4. **Endgame, Inc.**: Although the context mentions an amended 
and restated stock incentive plan, it indicates that Endgame, Inc. was 
associated with Elastic, suggesting it could have been acquired at some point.

These acquisitions are part of Elastic's strategy to 
enhance the technology underlying its security and 
observability offerings.
<p>SimpleRAG</p>The provided context does not specify any companies that Elastic has acquired. 
Therefore, based on the context, there is no information available about the companies acquired by Elastic.
<p><strong>摘要</strong>：SimpleRAG 无法检索到任何有关收购的相关信息，导致回答失败。AdvancedRAG 正确地列出了 CmdWatch、Build Security 和 Optimyze，它们是报告中列出的主要收购项目。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h2>结论</h2><p>根据我们的测试，我们的先进技术似乎增加了所提供信息的范围和深度，有可能提高 RAG 答案的质量。</p><p>此外，可靠性也可能有所提高，因为 AdvancedRAG 可以正确回答<code>Which companies did Elastic acquire?</code> 和<code>Who audits Elastic</code> 等措辞含糊的问题，而 SimpleRAG 则不能。</p><p>不过，值得注意的是，在 5 个案例中的 3 个案例中，基本的 RAG 管道（包括混合搜索，但不包括其他技术）设法得出了能够捕捉到大部分关键信息的答案。</p><p>我们应该注意到，由于在数据准备和查询阶段加入了 LLM，AdvancedRAG 的延迟一般是 SimpleRAG 的 2-5 倍。这是一笔不小的费用，可能使 AdvancedRAG 只适用于优先考虑应答质量而不是延迟的情况。</p><p>在数据准备阶段，使用 Claude Haiku 或 GPT-4o-mini 等更小巧、更便宜的 LLM，就能减轻巨大的延迟成本。将高级模型留待生成答案时使用。</p><p>这与 Wang 等人的研究结果一致。结果表明，任何改进都是相对渐进的。简而言之，简单的基线 RAG 就能让您获得大部分体面的最终产品，而且成本更低，速度更快。对我来说，这是一个有趣的结论。对于速度和效率至关重要的使用案例，SimpleRAG 是明智的选择。对于需要榨取每一滴性能的使用案例，AdvancedRAG 中包含的技术可能会提供一条出路。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt56b7067a9d41d5a8/6a171119acf0886fb4be9c45/ea811706b6adc4731d90b925a9fefa0ac15901b4-1440x1060.jpg" alt="王家管道" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">返回顶部</a></p><h2>附录</h2><h3>提示</h3><h4>RAG 问题解答提示</h4><p>提示 LLM 根据查询和上下文生成答案。</p>BASIC_RAG_PROMPT = '''
You are an AI assistant tasked with answering questions based primarily on the provided context, while also drawing on your own knowledge when appropriate. Your role is to accurately and comprehensively respond to queries, prioritizing the information given in the context but supplementing it with your own understanding when beneficial. Follow these guidelines:

1. Carefully read and analyze the entire context provided.
2. Primarily focus on the information present in the context to formulate your answer.
3. If the context doesn't contain sufficient information to fully answer the query, state this clearly and then supplement with your own knowledge if possible.
4. Use your own knowledge to provide additional context, explanations, or examples that enhance the answer.
5. Clearly distinguish between information from the provided context and your own knowledge. Use phrases like "According to the context..." or "The provided information states..." for context-based information, and "Based on my knowledge..." or "Drawing from my understanding..." for your own knowledge.
6. Provide comprehensive answers that address the query specifically, balancing conciseness with thoroughness.
7. When using information from the context, cite or quote relevant parts using quotation marks.
8. Maintain objectivity and clearly identify any opinions or interpretations as such.
9. If the context contains conflicting information, acknowledge this and use your knowledge to provide clarity if possible.
10. Make reasonable inferences based on the context and your knowledge, but clearly identify these as inferences.
11. If asked about the source of information, distinguish between the provided context and your own knowledge base.
12. If the query is ambiguous, ask for clarification before attempting to answer.
13. Use your judgment to determine when additional information from your knowledge base would be helpful or necessary to provide a complete and accurate answer.

Remember, your goal is to provide accurate, context-based responses, supplemented by your own knowledge when it adds value to the answer. Always prioritize the provided context, but don't hesitate to enhance it with your broader understanding when appropriate. Clearly differentiate between the two sources of information in your response.

Context:
[The concatenated documents will be inserted here]

Query:
[The user's question will be inserted here]

Please provide your answer based on the above guidelines, the given context, and your own knowledge where appropriate, clearly distinguishing between the two:
'''
<h4>弹性查询生成器提示</h4><p>提示使用同义词丰富查询内容，并将其转换为 OR 格式。</p>ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<h4>潜在问题生成器提示</h4><p>提示生成潜在问题，丰富文件元数据。</p>RAG_QUESTION_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating questions for Retrieval-Augmented Generation (RAG) systems. Your task is to analyze a given document and create 10 diverse questions that would effectively test a RAG system's ability to retrieve and synthesize information from this document.

Guidelines:
1. Thoroughly analyze the entire document.
2. Generate exactly 10 questions that cover various aspects and levels of complexity within the document's content.
3. Create questions that specifically target:
   a. Key facts and information
   b. Main concepts and ideas
   c. Relationships between different parts of the content
   d. Potential applications or implications of the information
   e. Comparisons or contrasts within the document
4. Ensure questions require answers of varying lengths and complexity, from simple retrieval to more complex synthesis.
5. Include questions that might require combining information from different parts of the document.
6. Frame questions to test both literal comprehension and inferential understanding.
7. Avoid yes/no questions; focus on open-ended questions that promote comprehensive answers.
8. Consider including questions that might require additional context or knowledge to fully answer, to test the RAG system's ability to combine retrieved information with broader knowledge.
9. Number the questions from 1 to 10.
10. Output only the ten questions, without any additional text, explanations, or answers.

Document:
[The document content will be inserted here]

Generate 10 questions optimized for testing a RAG system based on this document:
'''
<h4>HyDE 生成器提示</h4><p>使用 HyDE 生成假设文档的提示</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<h3>混合搜索查询示例</h3>{'knn': {'field': 'primary_embedding',
  'query_vector': [0.4265527129173279,
   -0.1712949573993683,
   -0.042020395398139954,
   ...],
  'k': 100,
  'num_candidates': 100},
 'query': {'bool': {'must': [{'multi_match': {'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
      'fields': ['original_text',
       'keyphrases',
       'potential_questions',
       'entities'],
      'type': 'best_fields',
      'operator': 'or'}}],
   'should': [{'script_score': {'query': {'match_all': {}},
      'script': {'source': '\n                                        double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;\n                                        double text_score = _score;\n                                        return 0.7 * vector_score + 0.3 * text_score;\n                                        ',
       'params': {'query_vector': [0.4265527129173279,
         -0.1712949573993683,
         -0.042020395398139954,
        ...],
        'vector_field': 'primary_embedding'}}}}]}},
 'size': 10}
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[高级 RAG 技术第 1 部分：数据处理]]></title>
    <description><![CDATA[讨论并实施可提高 RAG 性能的技术。第 1 部分（共 2 部分），重点介绍高级 RAG 管道的数据处理和摄取部分。]]></description>
    <content:encoded><![CDATA[<p><em>这是我们探索高级 RAG 技术的第一部分。 </em><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2"><em>点击此处查看第二部分！</em></a></p><p>最近发表的论文《<a href="https://arxiv.org/abs/2407.01219">在检索增强生成中寻找最佳实践</a>》对各种 RAG 增强技术的功效进行了实证评估，目的是为 RAG 找到一套最佳实践。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt671704ff06a4011d/6a170b3ea929cf2d19ae09d8/dafa7250e7c4ead4d9b4aed7c407509131929749-1440x572.png" alt="王建议的 RAG 管道" /><p>我们将实施其中一些建议的最佳实践，即旨在提高搜索质量的实践<strong>（句子分块、HyDE、反向打包）</strong>。</p><p>为简洁起见，我们将省略那些侧重于提高效率的技术<strong>（查询分类和摘要）</strong>。</p><p>我们还将实施一些未涉及但我个人认为有用且有趣的技术<strong>（元数据包含、复合多字段嵌入、查询丰富化）</strong>。</p><p>最后，我们将进行一个简短的测试，看看搜索结果和生成答案的质量与基线相比是否有所提高。让我们开始吧！</p><h2>RAG 概览</h2><p>RAG 的目的是通过检索外部知识库中的信息来丰富生成的答案，从而增强 LLM。通过提供特定领域的信息，LLM 可以快速适应训练数据范围之外的用例；比微调成本低得多，也更容易保持更新。</p><p>提高 RAG 质量的措施通常集中在两个方面：</p><ol><li><p>提高知识库的质量和清晰度。</p></li><li><p>提高搜索查询的覆盖面和针对性。</p></li></ol><p>这两项措施将实现提高法律硕士获得相关事实和信息的几率的目标，从而减少产生幻觉或利用自身知识的可能性--这些知识可能已经过时或不相关。</p><p>方法的多样性难以用几句话说清楚。为了更清楚地说明问题，让我们直接进入实施阶段。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="高级 RAG 管道" /><h3>目录</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#overview">概述</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">目录</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#set-up">设置</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#ingesting-processing-and-embedding-documents">摄取、处理和嵌入文件</a>  </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#data-ingestion">数据采集</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#sentence-level-token-wise-chunking">句子级标记分块</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#metadata-inclusion-and-generation">元数据的纳入和生成</a> </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#keyphrases-extracted-by-textrank">通过 TextRank 提取的关键词</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#potential-questions-generated-by-gpt-4o">GPT-4o 提出的潜在问题</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#entities-extracted-by-spacy">Spacy 提取的实体</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#composite-multi-field-embeddings">复合多场嵌入</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#indexing-to-elastic">索引至弹性</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#cat-break">猫休息</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#appendix">附录</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#definitions">定义</a></p></li></ul></li></ul><h2>设置</h2><p><em>所有代码均可 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>在 Searchlabs 软件仓库中</em></a>找到 <em>。</em></p><p>先说第一件事。您需要以下材料</p><ol><li><p>弹性云部署</p></li><li><p>LLM 应用程序接口--我们在本笔记本中使用了 Azure OpenAI 上的 GPT-4o 部署</p></li><li><p>Python 3.12.4 或更高版本</p></li></ol><p>我们将运行<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/main.ipynb"> main.ipynb 笔记本 中的所有代码 。</a></p><p>继续 git 克隆该 repo，导航至 supporting-blog-content/Advanced-rag-techniques，然后运行以下命令：</p># Create a new virtual environment named 'rag_env'
python -m venv rag_env

# Activate the virtual environment (for Unix-based systems)
source rag_env/bin/activate

# (For Windows)
.\rag_env\Scripts\activate

# Install packages listed in requirements.txt
pip install -r requirements.txt
<p>完成后，创建一个<em>.env</em>文件，并填写以下字段（在<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/.env.example"><em>.env.example</em></a> 中引用）。感谢我的合著者 Claude-3.5 提出的有益意见。</p># Elastic Cloud: Found in the 'Deployment' page of your Elastic Cloud 
# console
ELASTIC_CLOUD_ENDPOINT=""
ELASTIC_CLOUD_ID=""

# Elastic Cloud: Created during deployment setup or in 'Security' 
# settings
ELASTIC_USERNAME=""
ELASTIC_PASSWORD=""

# Elastic Cloud: The name of the index you created in Kibana or via API
ELASTIC_INDEX_NAME=""

# Azure AI Studio: Found in 'Keys and Endpoint' section of your Azure 
# OpenAI resource
AZURE_OPENAI_KEY_1=""
AZURE_OPENAI_KEY_2=""
AZURE_OPENAI_REGION=""
AZURE_OPENAI_ENDPOINT=""

# Azure AI Studio: Found in 'Deployments' section of your Azure OpenAI 
# resource
AZURE_OPENAI_DEPLOYMENT_NAME=""

# Using BAAI/bge-small-en-v1.5 because I think it is a good balance of 
# resource efficiency and performance. 
HUGGINGFACE_EMBEDDING_MODEL="BAAI/bge-small-en-v1.5"
<p>接下来，我们将选择要摄取的文档，并将其放在文档文件夹中。在本文中，我们将使用<a href="https://s201.q4cdn.com/217177842/files/doc_downloads/OtherDocuments/2023/AnnualMeeting/Annual-Report-Fiscal-Year-2023.pdf"> Elastic N.V. 的《 2023 年年度报告》</a> 。这是一份相当具有挑战性的密集文件，非常适合对我们的 RAG 技术进行压力测试。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte292dc6030d496cc/6a170b40dc55de9b03e00dfc/e513b9d67adac43da794c25a5969b893127bbbe3-1440x395.jpg" alt="2023 年弹性年度报告" /><p>现在我们都准备好了，开始摄入。打开<em>main.ipynb</em>，执行前两个单元格以导入所有软件包并初始化所有服务。</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h2>摄取、处理和嵌入文件</h2><h3>数据采集</h3><ul><li><p><em>个人感言：LlamaIndex 的便利性令我震惊。在还没有 LLM 和 LlamaIndex 的年代，录入各种格式的文档是一个痛苦的过程，需要从各处收集深奥的软件包。现在只需调用一个函数。狂野</em></p></li></ul><p><code>SimpleDirectoryReader</code> 将加载<code>directory_path.</code> 文件中的每个文档。对于<code>.pdf</code> 文件，它会返回一个文档对象列表，我将其转换为 Python 字典，因为我觉得它们更容易处理。</p># llamaindex_processor.py
from llama_index.core import SimpleDirectoryReader

class LlamaIndexProcessor:
   def __init__(self):
       pass 
   
   def load_documents(self, directory_path):
       ''' 
       Load all documents in directory
       '''
       reader = SimpleDirectoryReader(input_dir=directory_path)
       return reader.load_data()

# main.ipynb
llamaindex_processor=LlamaIndexProcessor()
documents=llamaindex_processor.load_documents('./documents/')
documents=[dict(doc_obj) for doc_obj in documents]
<p>每个字典都包含<code>text</code> 字段中的关键内容。它还包含有用的元数据，如页码、文件名、文件大小和类型。</p>{
  'id_': '5f76f0b3-22d8-49a8-9942-c2bbab14f63f',
  'metadata': {'page_label': '5',
   'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_path': '/Users/han/Desktop/Projects/truckasaurus/documents/Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_type': 'application/pdf',
   'file_size': 3724426,
   'creation_date': '2024-07-27',
   'last_modified_date': '2024-07-27'},
   'text': 'Table of Contents\nPage\nPART I\nItem 1. Business 3\n15 Item 1A. Risk Factors\nItem 1B. Unresolved Staff Comments 48\nItem 2. Properties 48\nItem 3. Legal Proceedings 48\nItem 4. Mine Safety Disclosures 48\nPART II\nItem 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of \nEquity Securities49\nItem 6. [Reserved] 49\nItem 7. Management's Discussion and Analysis of Financial Condition and Results of Operations 50\nItem 7A. Quantitative and Qualitative Disclosures About Market Risk 64\nItem 8. Financial Statements and Supplementary Data 66\nItem 9. Changes in and Disagreements With Accountants on Accounting and Financial Disclosure 100\n100\n101Item 9A. Controls and Procedures\nItem 9B. Other Information\nItem 9C. Disclosure Regarding Foreign Jurisdictions That Prevent Inspections 101\nPART III\n102\n102\n102\n102Item 10. Directors, Executive Officers and Corporate Governance\nItem 11. Executive Compensation\nItem 12. Security Ownership of Certain Beneficial Owners and Management, and Related Stockholder Matters  \nItem 13. Certain Relationships and Related Transactions, and Director Independence\nItem 14. Principal Accountant Fees and Services 102\nPART IV\n103\n105Item 15. Exhibits and Financial Statement Schedules  \nItem 16. Form 10-K Summary\nSignatures 106\ni',
   ...
}
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h3>句子级标记分块</h3><p>首先要做的是将我们的文件缩减成标准长度的块状（以确保一致性和可管理性）。嵌入模型有独特的标记限制（可处理的最大输入尺寸）。标记是模型处理文本的基本单位。为防止信息丢失（内容截断或遗漏），我们应提供不超过这些限制的文本（将较长的文本分割成较小的片段）。</p><p>分块对性能有重大影响。在理想情况下，每个信息块都代表一个独立的信息片段，捕捉有关单个主题的上下文信息。分块方法包括字级分块（按字数分割文档）和语义分块（使用 LLM 识别逻辑断点）。</p><p>单词级的分块处理成本低、速度快、操作简单，但存在拆分句子从而破坏上下文的风险。语义分块的速度越来越慢，成本越来越高，尤其是在处理像116页的《弹性年度报告》这样的文档时。</p><p>让我们选择一种中间路线。句子级分块仍然简单，但比单词级分块能更有效地保留上下文，而且成本更低，速度更快。此外，我们还将采用一个滑动窗口来捕捉周围的一些上下文，并减轻分割段落的影响。</p># chunker.py 

import uuid
import re


class Chunker: 
    def __init__(self, tokenizer):
        self.tokenizer = tokenizer 
    
    def split_into_sentences(self, text):
        """Split text into sentences."""
        return re.split(r'(?&lt;=[.!?])\s+', text)
 
    def sentence_wise_tokenized_chunk_documents(self, documents, chunk_size=512, overlap=20, min_chunk_size=50):
        '''
        1. Split text into sentences.
        2. Tokenize using the provided tokenizer method.
        3. Build chunks up to the chunk_size limit.
        4. Create an overlap based on tokens - to preserve context.
        5. Only keep chunks that meet the minimum token size requirement.
        '''
        chunked_documents = []

        for doc in documents:
            sentences = self.split_into_sentences(doc['text'])
            tokens = []
            sentence_boundaries = [0]

            # Tokenize all sentences and keep track of sentence boundaries
            for sentence in sentences:
                sentence_tokens = self.tokenizer.encode(sentence, add_special_tokens=True)
                tokens.extend(sentence_tokens)
                sentence_boundaries.append(len(tokens))

            # Create chunks
            chunk_start = 0
            while chunk_start &lt; len(tokens):
                chunk_end = chunk_start + chunk_size

                # Find the last complete sentence that fits in the chunk
                sentence_end = next((i for i in sentence_boundaries if i &gt; chunk_end), len(tokens))
                chunk_end = min(chunk_end, sentence_end)

                # Create the chunk
                chunk_tokens = tokens[chunk_start:chunk_end]

                # Check if the chunk meets the minimum size requirement
                if len(chunk_tokens) &gt;= min_chunk_size:
                    # Create a new document object for this chunk
                    chunk_doc = {
                        'id_': str(uuid.uuid4()),
                        'chunk': chunk_tokens,
                        'original_text': self.tokenizer.decode(chunk_tokens),
                        'chunk_index': len(chunked_documents),
                        'parent_id': doc['id_'],
                        'chunk_token_count': len(chunk_tokens)
                    }

                    # Copy all other fields from the original document
                    for key, value in doc.items():
                        if key != 'text' and key not in chunk_doc:
                            chunk_doc[key] = value

                    chunked_documents.append(chunk_doc)

                # Move to the next chunk start, considering overlap
                chunk_start = max(chunk_start + chunk_size - overlap, chunk_end - overlap)

        return chunked_documents

# main.ipynb 
# Initialize Embedding Model
HUGGINGFACE_EMBEDDING_MODEL = os.environ.get('HUGGINGFACE_EMBEDDING_MODEL')
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

# Initialize Chunker
chunker=Chunker(embedder.tokenizer)
<p><code>Chunker</code> 类采用嵌入模型的标记化器对文本进行编码和解码。现在，我们将构建每块 512 个令牌的分块，其中有 20 个令牌重叠。为此，我们会将文本分割成句子，对这些句子进行标记化处理，然后将标记化处理后的句子添加到当前语块中，直到无法在不超出标记限制的情况下添加更多句子为止。</p><p>最后，将句子解码回原始文本进行嵌入，将其存储在名为<code>original_text</code> 的字段中。数据块存储在一个名为<code>chunk</code> 的字段中。为了减少噪音（又称无用文件），我们将丢弃长度小于 50 个 token 的文件。</p><p>让我们在文件上运行一下：</p>chunked_documents=chunker.sentence_wise_tokenized_chunk_documents(documents, chunk_size=512)
<p>然后得到类似这样的文本块：</p>print(chunked_documents[4]['original_text'])

[CLS] the aggregate market value of the ordinary shares held by non - affiliates of the registrant, 
based on the closing price of the shares of ordinary shares on the new york stock exchange on 
october 31, 2022 ( the last business day of the registrant 's second fiscal quarter ), was 
approximately $ 6. 1 billion. [SEP] [CLS] as of may 31, 2023, the registrant had 97, 390, 886 
ordinary shares, par value €0. 01 per share, outstanding. [SEP] [CLS] documents incorporated by 
reference portions of the registrant 's definitive proxy statement relating to the registrant 's 2
023 annual general meeting of shareholders are incorporated by reference into part iii of this annual 
...
...
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h3>元数据的纳入和生成</h3><p>我们已将文件分块。现在是丰富数据的时候了。我想生成或提取额外的元数据。这些附加元数据可用于影响和提高搜索性能。</p><p>我们将定义一个<code>DocumentEnricher</code> 类，它的作用是接收文档列表（Python 字典）和处理器函数列表。这些函数将在文档的<code>original_text</code> 列中运行，并将其输出存储在新字段中。</p><p>首先，我们使用<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/nltk_processor.py">TextRank</a> 提取关键词。TextRank 是一种基于图的算法，它能根据词与词之间的关系对关键短语和句子的重要性进行排序，从而从文本中提取关键短语和句子。</p><p>接下来，我们将<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/llm.py">使用 GPT-4o 生成 potential_questions</a>。</p><p>最后，我们将使用<a href="https://spacy.io/"> Spacy</a> <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/entity_extractor.py">提取实体</a> 。</p><p>由于每项工作的代码都相当冗长和复杂，我就不在此赘述了。如果您感兴趣，这些文件已在下面的代码示例中标出。</p><p>让我们运行数据浓缩：</p># documentenricher.py
from tqdm import tqdm

class DocumentEnricher:

    def __init__(self):
        pass 

    def enrich_document(self, documents, processors, text_col='text'):
        for doc in tqdm(documents, desc="Enriching documents using processors: "+str(processors)): 
            for (processor, field) in processors: 
                metadata=processor(doc[text_col])
                if isinstance(metadata, list):
                    metadata='\n'.join(metadata)
                doc.update({field: metadata})
 
# main.ipynb
# Initialize processor classes 
nltkprocessor=NLTKProcessor() // nltk_processor.py
entity_extractor=EntityExtractor() // entity_extractor.py
gpt4o = LLMProcessor(model='gpt-4o') // llm.py

# Initialize LLM
documentenricher=DocumentEnricher()

# Create new fields in the documents - These are the outputs of the processor functions.
processors=[
    (nltkprocessor.textrank_phrases, "keyphrases"),
    (gpt4o.generate_questions, "potential_questions"),
    (entity_extractor.extract_entities, "entities")
    ]

# .enrich_document() will modify chunked_docs in place. 
# To view the results, we'll print chunked_docs in the next few cells!
documentenricher.enrich_document(chunked_docs, text_col='original_text', processors=processors)
<p>看看结果吧：</p><h4>通过 TextRank 提取的关键词</h4><p>这些关键短语是大块核心主题的替身。如果查询与网络安全有关，这块内容的得分就会提高。</p>print(chunked_documents[25]['keyphrases'])

'elastic agent stop', 'agent stop malware', 
'stop malware ransomware', 'malware ransomware environment', 
'ransomware environment wide', 'environment wide visibility', 
'wide visibility threat', 'visibility threat detection', 
'sep cl key', 'cl key feature'
<h4>GPT-4o 提出的潜在问题</h4><p>这些潜在问题可能与用户查询直接匹配，从而提高得分。我们会提示 GPT-4o 生成一些问题，这些问题可以用当前语块中的信息来回答。</p>print(chunked_documents[25]['potential_questions'])

1. What are the primary functions that Elastic Agent provides in terms of cybersecurity?
2. Describe how Logstash contributes to data management within an IT environment.
3. List and explain any key features of Logstash mentioned in the document.
4. How does Elastic Agent enhance environment-wide visibility in threat detection?
5. What capabilities does Logstash offer for handling data beyond simple collection?
6. In what ways does the document suggest that Elastic Agent stops malware and ransomware?
7. Can you identify any relationships between the functionalities of Elastic Agent and Logstash in an integrated environment?
8. What implications might the advanced threat detection capabilities of Elastic Agent have for organizational security policies?
9. Compare and contrast the roles of Elastic Agent and Logstash based on their described functions.
10. How might the centralized collection ability of Logstash support the threat detection capabilities of Elastic Agent?
<h4>Spacy 提取的实体</h4><p>这些实体的作用与关键词类似，但可以捕捉到组织和个人的名称，而关键词提取可能会遗漏这些名称。</p>print(chunked_documents[29]['entities'])

'appdynamics', 'apm data', 'azure sentinel', 
'microsoft', 'mcafee', 'broadcom', 'cisco', 
'dynatrace', 'coveo', 'lucidworks'
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h3>复合多场嵌入</h3><p>现在，我们已经用更多的元数据丰富了我们的文档，我们可以利用这些信息创建更强大、更能感知上下文的嵌入。</p><p>让我们回顾一下目前的进程。我们在每份文档中都有四个关注领域。</p>{
    "chunk": "...",
    "keyphrases": "...", 
    "potential_questions": "...", 
    "entities": "..." 
}
<p>每个字段都代表了对文件背景的不同看法，可能突出了法律硕士应重点关注的关键领域。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84cb328fce6aae23/6a170b42964cea3e4408bbc4/aea1f513009a0c7c8545a79fad8f072a5bcae24c-1440x1067.jpg" alt="RAG 中的元数据丰富管道" /><p>我们的计划是嵌入每个字段，然后创建嵌入的加权和，即复合嵌入。</p><p>幸运的话，除了引入另一个可调整的超参数来控制搜索行为外，这种复合嵌入还能让系统变得更加了解上下文。</p><p>首先，让我们使用在 main.ipynb 笔记本开头导入的本地定义的嵌入模型，嵌入每个字段并就地更新每个文档。</p># EmbeddingModel defined in embedding_model.py
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

cols_to_embed=['keyphrases', 'potential_questions', 'entities']

embedding_cols=[]
for col in cols_to_embed:
    # Works on text input
    embedding_col=embedder.embed_documents_text_wise(chunked_documents, text_field=col)
    embedding_cols.append(embedding_col)
# Works on token input
embedding_col=embedder.embed_documents_token_wise(chunked_documents, token_field="chunk")
embedding_cols.append(embedding_col)
<p>每个嵌入函数都会返回嵌入的字段，即带有<code>_embedding</code> 后缀的原始输入字段。</p><p>现在我们来定义复合嵌入的权重：</p>embedding_cols=[
                'keyphrases_embedding',
                'potential_questions_embedding',
                'entities_embedding',
                'chunk_embedding']
combination_weights=[
                    0.1,
                    0.15,
                    0.05,
                    0.7
                ]
<p>通过权重，您可以根据用例和数据质量为每个组件分配优先级。直观地说，这些权重的大小取决于每个组件的语义值。由于大块文本本身的内容迄今为止最为丰富，我将其权重定为 70% 。由于实体最小，只是一个组织或个人名称列表，因此我将其权重定为 5% 。这些值的精确设置必须根据具体情况，根据经验来确定。</p><p>最后，让我们编写一个函数来应用权重，并创建我们的复合嵌入。为了节省空间，我们还将删除所有的组件嵌入。</p>from tqdm import tqdm 
def combine_embeddings(objects, embedding_cols, combination_weights, primary_embedding='primary_embedding'):
    # Ensure the number of weights matches the number of embedding columns
    assert len(embedding_cols) == len(combination_weights), "Number of embedding columns must match number of weights"
    
    # Normalize weights to sum to 1
    weights = np.array(combination_weights) / np.sum(combination_weights)
    
    for obj in tqdm(objects, desc="Combining embeddings"):
        # Initialize the combined embedding
        combined = np.zeros_like(obj[embedding_cols[0]])
        
        # Compute the weighted sum
        for col, weight in zip(embedding_cols, weights):
            combined += weight * np.array(obj[col])
        
        # Add the new combined embedding to the object
        obj.update({primary_embedding:combined.tolist()})
        
        # Remove the original embedding columns
        for col in embedding_cols:
            obj.pop(col, None)

combine_embeddings(chunked_documents, embedding_cols, combination_weights)
<p>至此，我们完成了文件处理工作。现在我们有了一个文档对象列表，看起来像这样：</p>{ 'id_': '7fe71686-5cd0-4831-9e79-998c6dbeae0c', 'chunk': [2312, 14613, ...], 'original_text': 'if an emerging growth company, indicate by check mark if the registrant has elected not to use the extended ...', 'chunk_index': 3, 'chunk_token_count': 399, 'metadata': {'page_label': '3', 'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf', ... 'keyphrases': 'sep cl unk\ncheck mark registrant\ncl unk indicate\nunk indicate check\nindicate check mark\nprincipal executive office\naccelerate filer unk\ncompany unk emerge\nunk emerge growth\nemerge growth company', 'potential_questions': '1. What are the different types of registrant statuses mentioned in the document?\n2. Under what section of the Sarbanes-Oxley Act must registrants file a report on the effectiveness of their internal ...', 'entities': 'the effe ctiveness of\nsection 13\nSEP\nUNK\nsection 21e\n1934\n1933\nu. s. c.\nsection 404\nsection 12\nal', 'primary_embedding': [-0.3946287803351879, -0.17586839850991964, ...] }
<h4>索引至弹性</h4><p>让我们将文档批量上传到 Elastic Search。为此，我很早就在<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/elastic_helpers.py"><code>elastic_helpers.py</code></a> 中定义了一组 Elastic Helper 函数。这是一段非常冗长的代码，所以我们还是只看函数调用。</p><p><code>es_bulk_indexer.bulk_upload_documents</code> 利用 Elasticsearch 方便的动态映射，可以处理任何字典对象列表。</p># Initialize Elasticsearch
ELASTIC_CLOUD_ID = os.environ.get('ELASTIC_CLOUD_ID')
ELASTIC_USERNAME = os.environ.get('ELASTIC_USERNAME')
ELASTIC_PASSWORD = os.environ.get('ELASTIC_PASSWORD')
ELASTIC_CLOUD_AUTH = (ELASTIC_USERNAME, ELASTIC_PASSWORD)
es_bulk_indexer = ESBulkIndexer(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)
es_query_maker = ESQueryMaker(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)

# Define Index Name
index_name=os.environ.get('ELASTIC_INDEX_NAME')


# Create index and bulk upload 
index_exists = es_bulk_indexer.check_index_existence(index_name=index_name)
if not index_exists:
    logger.info(f"Creating new index: {index_name}")
    es_bulk_indexer.create_es_index(es_configuration=BASIC_CONFIG, index_name=index_name)

success_count = es_bulk_indexer.bulk_upload_documents(
    index_name=index_name, 
    documents=chunked_documents, 
    id_col='id_',
    batch_size=32
)
<p>前往 Kibana，确认所有文件都已编入索引。应该有 224 个。对于这么大的文件来说，还算不错！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efeface6effe01d/6a170b447d8d67652870e72a/1b3b07f6b98ceb65f6594ce4be83c5b0ed7e7cf9-1440x1380.jpg" alt="Kibana 索引" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p><h2>猫休息</h2><p>我们休息一下吧，文章有点沉重，我知道。看看我的猫</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1db5595f71c12ff/6a170b450e2e49940241a0fe/baca4eb52b801b21ced97352cc55462f0a12d6b0-969x996.jpg" alt="汉族管道" /><p>真可爱帽子不见了，我半信半疑是她偷藏起来的：(</p><p>祝贺你们走到这一步 :)</p><p>请看<a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">第二部分</a>，了解我们对 RAG 管道的测试和评估！</p><h2>附录</h2><h3>定义</h3><p><strong>1.句子分块</strong></p><ul><li><p>RAG 系统中使用的一种预处理技术，用于将文本划分为更小的、有意义的单元。</p></li><li><p><em>过程：</em> </p><ol><li><p>输入：大段文本（如文档、段落）</p></li><li><p>输出：较小的文本片段（通常是句子或小句子组）</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>创建细粒度、针对特定上下文的文本片段</p></li><li><p>允许更精确的索引和检索</p></li><li><p>提高 RAG 系统检索信息的相关性</p></li></ul></li><li><p><em>特点</em> </p><ul><li><p>分段具有语义意义</p></li><li><p>可独立索引和检索</p></li><li><p>通常保留一些上下文，以确保独立的可理解性</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>提高检索精度</p></li><li><p>使 RAG 管道的扩容更有针对性</p></li></ul></li></ul><p><strong>2.HyDE（假设文档嵌入）</strong></p><ul><li><p>在 RAG 系统中使用 LLM 生成用于查询扩展的假设文档的技术。</p></li><li><p><em>过程：</em>  </p><ol><li><p>向 LLM 输入查询</p></li><li><p>LLM 生成回答查询的假设文档</p></li><li><p>嵌入生成的文件</p></li><li><p>使用嵌入进行向量搜索</p></li></ol></li><li><p><em>主要区别</em> </p><ul><li><p>传统 RAG：将查询与文档匹配</p></li><li><p>HyDE：将文档匹配到文档</p></li></ul></li><li><p><em>目的是</em> </p><ul><li><p>提高检索性能，尤其是复杂或模糊查询的检索性能</p></li><li><p>捕捉比简短查询更丰富的语义上下文</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>利用 LLM 的知识扩展查询</p></li><li><p>有可能提高检索文件的相关性</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>需要额外的 LLM 推理，增加了延迟和成本</p></li><li><p>性能取决于生成的假设文件的质量</p></li></ul></li></ul><p><strong>3.反向包装</strong></p><ul><li><p>RAG 系统中使用的一种技术，用于在将搜索结果传递给 LLM 之前对其重新排序。</p></li><li><p><em>过程：</em> </p><ol><li><p>搜索引擎（如 Elasticsearch）按相关性降序返回文档。</p></li><li><p>顺序颠倒，将最相关的文件放在最后。</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>利用 LLM 的新旧偏差，LLM 往往更关注其上下文中的最新信息。</p></li><li><p>确保最相关的信息"最新鲜的" 在 LLM 的上下文窗口中。</p></li></ul></li><li><p><em>举例说明：</em>原始顺序：[最相关、第二最相关、第三最相关、......] 倒序：[......，最重要的第三项，最重要的第二项，最相关的］</p></li></ul><p><strong>4.查询分类</strong></p><ul><li><p>通过确定查询是需要 RAG 还是可以直接由 LLM 回答来优化 RAG 系统效率的技术。</p></li><li><p><em>过程：</em> </p><ol><li><p>针对使用中的 LLM 开发定制数据集</p></li><li><p>训练专门的分类模型</p></li><li><p>使用模型对收到的查询进行分类</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>避免不必要的 RAG 处理，提高系统效率</p></li><li><p>将查询引导至最合适的响应机制</p></li></ul></li><li><p><em>要求：</em> </p><ul><li><p>LLM 专用数据集和模型</p></li><li><p>不断改进以保持准确性</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>减少简单查询的计算开销</p></li><li><p>有可能缩短非 RAG 查询的响应时间</p></li></ul></li></ul><p><strong>5.总结</strong></p><ul><li><p>在 RAG 系统中压缩检索文档的技术。</p></li><li><p><em>过程：</em> </p><ol><li><p>检索相关文件</p></li><li><p>生成每份文件的简明摘要</p></li><li><p>在 RAG 管道中使用摘要而非完整文件</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>关注基本信息，提高 RAG 性能</p></li><li><p>减少不相关内容的噪音和干扰</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>有可能提高 LLM 答复的相关性</p></li><li><p>允许在上下文限制内纳入更多文件</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>总结时有可能丢失重要细节</p></li><li><p>生成摘要的额外计算开销</p></li></ul></li></ul><p><strong>6.元数据的纳入</strong></p><ul><li><p>一种用额外的上下文信息来丰富文档的技术。</p></li><li><p><em>元数据类型：</em>  </p><ul><li><p>关键词</p></li><li><p>标题</p></li><li><p>日期</p></li><li><p>作者详细信息</p></li><li><p>简介</p></li></ul></li><li><p><em>目的是</em> </p><ul><li><p>增加 RAG 系统可用的背景信息</p></li><li><p>让法律硕士更清楚地了解文件内容和相关性</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>有可能提高检索的准确性</p></li><li><p>提高法律硕士评估文件实用性的能力</p></li></ul></li><li><p><em>实施：</em> </p><ul><li><p>可在文件预处理过程中完成</p></li><li><p>可能需要额外的数据提取或生成步骤</p></li></ul></li></ul><p><strong>7.复合多字段嵌入</strong></p><ul><li><p>RAG 系统的高级嵌入技术，可为不同的文档组件创建单独的嵌入。</p></li><li><p><em>过程：</em> </p><ol><li><p>确定相关字段（例如标题、关键词、简介、主要内容）</p></li><li><p>为每个字段生成单独的嵌入</p></li><li><p>合并或存储这些嵌入信息，以用于检索</p></li></ol></li><li><p><em>与标准方法的区别：</em> </p><ul><li><p>传统：对整个文档进行单一嵌入</p></li><li><p>复合：针对不同文档方面的多重嵌入</p></li></ul></li><li><p><em>目的是</em> </p><ul><li><p>创建更细致入微、更能感知上下文的文档表示法</p></li><li><p>在文件中获取更多来源的信息</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>有可能提高模糊或多方面查询的性能</p></li><li><p>允许在检索中更灵活地加权不同的文件内容</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>嵌入存储和检索流程的复杂性增加</p></li><li><p>可能需要更复杂的匹配算法</p></li></ul></li></ul><p><strong>8.丰富查询</strong></p><ul><li><p>一种用相关术语扩展原始查询以提高搜索覆盖率的技术。</p></li><li><p><em>过程：</em> </p><ol><li><p>分析原始查询</p></li><li><p>生成同义词和语义相关的短语</p></li><li><p>用这些附加术语来扩展查询</p></li></ol></li><li><p><em>目的是</em> </p><ul><li><p>增加文件语料库中潜在匹配的范围</p></li><li><p>提高使用特定或技术语言查询的检索性能</p></li></ul></li><li><p><em>优势：</em> </p><ul><li><p>可能检索到与原始查询条件不完全匹配的相关文档</p></li><li><p>有助于克服查询和文档之间的词汇不匹配问题</p></li></ul></li><li><p><em>挑战：</em> </p><ul><li><p>如果不认真执行，则有查询偏移的风险</p></li><li><p>可能会增加检索过程中的计算开销</p></li></ul></li></ul><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">返回顶部</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 14 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 与 OpenSearch：向量搜索性能比较]]></title>
    <description><![CDATA[Elasticsearch 开箱即用，在向量搜索方面比 OpenSearch 快 2 倍至 12 倍]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison#up-to-12x-faster-out-of-the-box">TLDR：Elasticsearch 的速度提高了 12 倍</a> - Elastic 收到了来自社区的大量请求，要求澄清 Elasticsearch 和 OpenSearch 之间的性能差异，特别是在语义搜索/向量搜索领域，因此我们进行了这项性能测试，以提供清晰、数据驱动的对比——没有模棱两可之处，只有直接的事实来为我们的用户提供信息。结果表明，<strong>Elasticsearch 在向量搜索方面比 OpenSearch 快 12 倍</strong>，因此所需的计算资源更少。这反映了 Elastic 致力于将 Lucene 打造为搜索和检索用例的最佳向量数据库。</p><p>向量搜索正在彻底改变我们进行相似性搜索的方式，特别是在 AI 和机器学习等领域。随着向量嵌入模型的日益普及，在数百万个高维向量中进行高效搜索的能力变得至关重要。</p><p>在为向量数据库提供支持时，Elastic 和 OpenSearch 采取了显著不同的方法。Elastic 投入巨资优化 Apache Lucene 和 Elasticsearch，使其成为向量搜索应用程序的顶级选择。相比之下，OpenSearch 扩大了其关注范围，整合了其他向量搜索实现，并探索了超出 Lucene 范围的领域。我们对 Lucene 的关注具有战略意义，这使我们能够在 Elasticsearch 版本中提供高度集成的支持，从而形成一个功能更强大的组合，其中每个组件都能相互补充并增强彼此的功能。</p><p>本博客详细比较了 Elasticsearch 8.14 和 OpenSearch 2.14 的不同配置和向量引擎。在这项性能分析中，Elasticsearch 被证明是向量搜索操作的卓越平台，而即将推出的<a href="https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed">功能</a>将更加<a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">显著地</a>扩大差异。与 OpenSearch 相比，它在每个基准轨道上都表现出色——<strong>平均性能提高了 2 倍到 12 倍</strong>。这涉及使用不同的向量数量和维度的场景，包括 <code>so_vector</code>（2M 向量，768D）、<code>openai_vector</code>（2.5M 向量，1536D）和 <code>dense_vector</code>（10M 向量，96D），所有这些都可在<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance">此存储库</a>中找到，并附有在 Google Cloud 上配置所有所需基础架构的 Terraform 脚本和用于运行测试的 Kubernetes 清单。</p><p>本博客中详述的结果补充了<a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">之前发布并经过第三方验证的研究</a>的结果。该研究表明，在最常见的搜索分析操作中，Elasticsearch 比 OpenSearch 快 40%–140%；此类操作包括文本查询、排序、范围、日期直方图和术语过滤。现在我们可以添加另一个差异化因素：向量搜索。</p><h2>开箱即用，性能提升高达 12 倍</h2><p>我们在四个向量数据集上的基准测试涉及近似 KNN 和精确 KNN 搜索，考虑了不同的大小、维度和配置，总共进行了 <code>40.189.820</code> 次未缓存的搜索请求。结果：<strong>Elasticsearch 在向量搜索中比 OpenSearch 快 12 倍</strong>，因此需要更少的计算资源。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34b83c6eba3bcb6e/6a17d727dbb4ff18d6fb54fb/cdb26e91f085b90e9b12aeb8fee53b04d365ecae-1440x1156.webp" alt="p90 平均值" /><p>图 1：Elasticsearch 和 OpenSearch 中不同组合的 ANN 和精确 KNN 的分组任务。</p><p>像 <code>knn-10-100</code> 这样的组表示 KNN 搜索， 和 。在 HNSW 向量搜索中， 决定了查询向量要检索的最近邻居数量。它指定了要找到多少个相似向量。 设置每个段要检索的候选向量数量。更多的候选者可以提高准确性，但需要更多的计算资源。</p><p>我们还测试了不同的量化技术，并利用特定引擎进行了优化。各个轨道、任务和向量引擎的详细结果如下。</p><h2>精确 KNN 和近似 KNN</h2><p>在处理不同的数据集和用例时，向量搜索的正确方法会有所不同。在本博客中，所有标记为 <code>knn-*</code> 的任务，如 <code>knn-10-100</code>，使用 <strong>近似 KNN</strong>，而 <code>script-score-*</code> 指的是 <strong>精确 KNN</strong>，但它们之间有什么区别，为什么它们很重要？</p><p>本质上，如果您处理的是更大规模的数据集，首选方法是近似 K 最近邻 (ANN)，因为它具有更出色的可扩展性。对于可能需要过滤过程的规模较小的数据集，精确 KNN 方法是理想选择。</p><p>精确 KNN 使用暴力方法，计算数据集中一个向量与其他每个向量之间的距离。然后对这些距离进行排序，找出  个最近的邻居。虽然这种方法能确保精确匹配，但对于大型高维数据集来说，它在可扩展性方面面临挑战。然而，在很多情况下，需要使用精确 KNN：</p><ul><li><p><strong>重新评分</strong>：在涉及词汇或语义搜索后进行基于向量的重新评分时，精确 KNN 至关重要。例如，在产品搜索引擎中，可以根据文本查询（例如，关键字、类别）筛选初始搜索结果，然后使用与筛选出的项目相关的向量进行更准确的相似性评估。</p></li><li><p><strong>个性化</strong>：处理大量用户时，如果每个用户都由相对较少数量（例如 100 万）的不同向量表示，则按用户特定的元数据（例如 user_id）对索引进行排序，并使用向量进行暴力评分会变得高效。这种方法能够根据精准的向量比较，根据用户的个人偏好为用户量身定制个性化推荐或内容交付。</p></li></ul><p>因此，精确 KNN 确保了基于向量相似度的最终排名和推荐精准且符合用户偏好。</p><p>另一方面，近似 KNN（或 ANN）采用的方法使得数据搜索比精确 KNN 更快、更高效，尤其是在大型高维数据集中。ANN 并不采用蛮力方法（即测量查询与所有点之间的精确最近距离，从而带来计算和扩展挑战），而是使用某些技术来有效地重构数据集中可搜索向量的索引和维度。虽然这可能会导致轻微的不准确，但它显著提高了搜索过程的速度，使其成为处理大型数据集的有效替代方案。</p><p>在本博客中，所有表述为 <code>knn-*</code> 的任务，例如 <code>knn-10-100</code>，均使用<strong>近似 KNN</strong>，而 <code>script-score-*</code> 指的是<strong>精确 KNN</strong>。</p><h2>测试方法</h2><p>虽然 Elasticsearch 和 OpenSearch 在 BM25 搜索操作的 API 方面相似，但由于后者是前者的分支，向量搜索并非如此，因为它是在分支之后才引入的。在算法方面，OpenSearch 采取了与 Elasticsearch 不同的方法，除了 <code>lucene</code> 之外，还引入了另外两个引擎——<code>nmslib</code> 和 <code>faiss</code>，每个引擎都有其特定的配置和限制（例如，OpenSearch 中的 <code>nmslib</code> 不支持使用筛选器，而筛选器是许多用例的一项基本功能）。</p><p>这三个引擎都使用分层可导航小世界 (HNSW) 算法，该算法对于近似最近邻搜索非常高效，尤其在处理高维数据时表现出色。需要注意的是，<code>faiss</code> 还支持第二种算法 <code>ivf</code>，但由于它需要对数据集进行预训练，因此我们将仅关注 HNSW。HNSW 的核心理念是将数据组织成多层连接图表，每一层代表数据集的不同粒度。搜索从最顶层的粗略视图开始，逐步深入到越来越精细的层级，直至到达最底层。</p><p>这两个搜索引擎在受控环境中的相同条件下进行了测试，以确保测试的公平性。所采用的方法与<a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap#testing-methodology">之前发布的性能比较</a>类似，为 Elasticsearch、OpenSearch 和 Rally 配备了专用节点池。<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/main/terraform/main.tf">terraform 脚本</a>（与所有源一起）可用于配置具有以下功能的 Kubernetes 集群：</p><ul><li><p>1 个适用于 Elasticsearch 的节点池，包含 3 台 <code>e2-standard-32</code> 计算机（128GB RAM 和 32 个 CPU）</p></li><li><p>1 个 Node 池用于 OpenSearch，配备 3 台 <code>e2-standard-32</code> 机器（128GB RAM 和 32 个 CPU）</p></li><li><p>1 个适用于 Rally 的节点池，包含 2 台 <code>t2a-standard-16</code> 计算机（64GB RAM 和 16 个 CPU）</p></li></ul><p>每个“轨道”（或测试）的每种配置都运行了 10 次，其中包括不同的引擎、不同的配置和不同的向量类型。轨道上的任务会根据轨道的不同重复 1,000 到 10,000 次。如果某个轨道中的某个任务由于网络超时而失败，则所有任务都将被丢弃，因此所有结果都代表了顺利开始并完成的轨道。所有测试结果都经过统计验证，确保改进并非偶然。</p><h2>详细结果</h2><p>为什么要使用第 99 个百分位而不是平均延迟来进行比较？考虑一个假设的例子：某个社区的平均房价。平均价格可能表明一个昂贵的地区，但仔细观察后，可能会发现大多数房屋的价值要低得多，只有少数豪华属性抬高了平均价格。这说明了平均价格如何无法准确代表该地区房屋价值的完整范围。这就好比检查响应时间，平均值可能会掩盖关键问题。</p><h4>任务</h4><ul><li><p>近似 KNN，k:10 n:50</p></li><li><p>使用 k:10 n:100 的近似 KNN</p></li><li><p>近似 KNN，k:100 n:1000</p></li><li><p>使用 k:10 n:50 和关键字筛选器的近似 KNN</p></li><li><p>使用 k:10 n:100 和关键字筛选器的近似 KNN</p></li><li><p>使用 k:100 n:1000 和关键字筛选器的近似 KNN</p></li><li><p>使用 k:10 n:100 结合索引的近似 KNN</p></li><li><p>精确 KNN（脚本分数）</p></li></ul><h4>向量引擎</h4><ul><li><p><code>lucene</code> 在 Elasticsearch 和 OpenSearch 中，版本均为 9.10</p></li><li><p><code>faiss</code> 在 OpenSearch 中</p></li><li><p><code>nmslib</code> 在 OpenSearch 中</p></li></ul><h4>向量类型</h4><ul><li><p><code>hnsw</code> 在 Elasticsearch 和 OpenSearch 中</p></li><li><p><code>int8_hnsw</code> 在 Elasticsearch 中（采用自动 8 位量化的 HNSW：<a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">链接</a>）</p></li><li><p><code>sq_fp16 hnsw </code>在 OpenSearch 中（采用自动 16 位量化的 HNSW：<a href="https://opensearch.org/docs/2.14/search-plugins/knn/knn-vector-quantization#faiss-16-bit-scalar-quantization">链接</a>）</p></li></ul><h4>开箱即用和并行分段搜索</h4><p>您可能知道，Lucene 是一个用 Java 编写的高性能文本搜索引擎库，它是 Elasticsearch、OpenSearch 和 Solr 等许多搜索平台的核心。Lucene 的核心是将数据组织成多个段，这些段本质上是独立的索引，使 Lucene 能够更高效地执行搜索。因此，当您向任何基于 Lucene 的搜索引擎发出搜索请求时，您的搜索最终将在这些段中按顺序或并行执行。</p><p>OpenSearch 将并发分段搜索作为可选标记引入，默认情况下不使用该标记，您必须使用特殊的索引设置 <code>index.search.concurrent_segment_search.enabled</code> 启用该标记，<a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/">此处</a>有详细说明，但有一些<a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/#other-considerations">限制</a>。</p><p>另一方面，Elasticsearch 能以<a href="https://github.com/elastic/elasticsearch/pull/101230">开箱即用的</a>方式并发搜索各网段，因此我们在本博客中进行的比较除了考虑不同的向量引擎和向量类型外，还将考虑不同的配置：</p><ul><li><p>Elasticsearch ootb：开箱即用的 Elasticsearch，支持并行分段搜索；</p></li><li><p>OpenSearch ootb：未启用并行分段搜索；</p></li><li><p>OpenSearch css：启用并发段搜索</p></li></ul><p>现在，让我们深入了解每个已测试的向量数据集的详细结果：</p><h2>250 万个向量，1536 个维度（openai_vector）</h2><p>从最简单的轨道开始，但在维度方面也是最大的，<a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a>——它使用了 <a href="https://huggingface.co/datasets/BeIR/nq">NQ 数据集</a>，并通过使用 OpenAI 的 <a href="https://openai.com/blog/new-and-improved-embedding-model">text-embedding-ada-002 模型</a>生成的嵌入来丰富该数据集。它是最简单的，因为它仅测试近似 KNN，并且只有 5 个任务。它既能独立测试（不进行索引），也能与索引一起测试，并且能使用单个客户端和 8 个同时运行的客户端进行测试。</p><h3>任务</h3><ul><li><p><strong>standalone-search-knn-10-100-multiple-clients</strong>：同时使用 8 个客户端搜索 250 万个向量 (k:10, n:100)</p></li><li><p><strong>standalone-search-knn-100-1000-multiple-clients</strong>：使用 8 个客户端同时搜索 250 万个向量（k: 100，n: 1000）</p></li><li><p><strong>standalone-search-knn-10-100-single-client</strong>：使用单个客户端在 250 万个向量上搜索（k: 10，n: 100）</p></li><li><p><strong>standalone-search-knn-100-1000-single-client</strong>：使用单个客户端在 250 万个向量上搜索（k: 100，n: 1000）</p></li><li><p><strong>parallel-documents-indexing-search-knn-10-100</strong>：在 250 万个向量上搜索，同时索引另外 100,000 个文档 (k:10, n：100)</p></li></ul><p>平均 p99 性能概述如下：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5791848eb0c12fb/6a17d72925daab9cae08a09e/eea0b2b49c690baada3e09d6968e513bfffe51a9-1440x318.webp" alt="openai_vector 表" /><p>在此我们观察到，在执行索引（即读写）的同时进行向量搜索时，Elasticsearch 比 OpenSearch <strong>快 3 到 8 倍</strong>，而在不进行索引的情况下（:10, :100），速度提高了 <strong>2 倍到 3 倍</strong>，其中 k 和 n 相同。对于 :100 和 :1000（<em>standalone-search-knn-100-1000-single-client</em> 和 <em>standalone-search-knn-100-1000-multiple-clients</em>），Elasticsearch 的平均速度比 OpenSearch 快 <strong>2 到 7 倍</strong>。</p><p>详细结果显示了比较的确切案例和向量引擎：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbe41d3187ced7ec/6a17d72a445de951c44cff4c/a7a761ed631d3e6211beb83d9d93d752d10123c9-1440x1728.webp" alt="openai_vector" /><h4>召回</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969485</p><p>0.995138</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.781445</p><p>0.784817</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.96519</p><p>0.995422</p><p>OpenSearch-2.14.0@faiss</p><p>0.984154</p><p>0.98049</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.980012</p><p>0.97721</p><p>OpenSearch-2.14.0@nmslib</p><p>0.982532</p><p>0.99832</p><h2>一千万个向量，96 个维度 (dense_vector)</h2><p>在具有 1000 万个向量和 96 维的 <a href="https://github.com/elastic/rally-tracks/tree/master/dense_vector">dense_vector</a> 中。它基于 <a href="https://big-ann-benchmarks.com/">Yandex DEEP1B</a> 图像数据集。该数据集是从名为 <code>learn.350M.fbin</code> 的“样本数据”文件的前 1000 万个向量创建的。搜索操作使用来自“查询数据”文件查询的向量。<code>public.10K.fbin</code>。</p><p>Elasticsearch 和 OpenSearch 在此数据集上的表现都非常出色，尤其是在<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html">强制合并</a>之后，这通常是在只读索引上进行的，类似于对索引进行碎片整理，使其成为一个单一的“表”以供搜索。</p><h3>任务</h3><p>每个任务预热 100 个请求，然后测量 1000 个请求</p><ul><li><p><strong>knn-search-10-100</strong>：在 1000 万个向量上搜索（k: 10，n: 100）</p></li><li><p><strong>knn-search-100-1000</strong>：在 1000 万个向量上搜索（k：100，n：1000）</p></li><li><p><strong>knn-search-10-100-force-merge</strong>：在强制合并后搜索 1,000 万个向量 (k:10, n:100)</p></li><li><p><strong>knn-search-100-1000-force-merge</strong>：在强制合并后搜索 1000 万个向量 (k: 100, n: 1000)</p></li><li><p><strong>knn-search-100-1000-concurrent-with-indexing</strong>：在 1,000 万个向量上进行搜索，同时更新<a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/challenges/default.json#L76C36-L76C37">数据集的 5%</a> (k:100, n:1000)</p></li><li><p><strong>script-score-query</strong>：对 <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/queries.json">2000 个特定向量</a>进行精确的 KNN 搜索。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4629d06af85eb96c/6a17d72c6864a423e7b685dc/174995e0a2156d86359cdb7aa446dfaae6312ea4-1440x316.webp" alt="dense_vector" /><p>Elasticsearch 和 OpenSearch 在近似 KNN 上表现良好。当索引在 <em>knn-搜索-100-1000-force-merge</em> 和 <em>knn-搜索-10-100-force-merge</em> 中合并（即只有一个段）时，OpenSearch 在使用 <code>nmslib</code> 和 <code>faiss</code> 时表现优于其他，即使它们都在 15 毫秒左右并且都非常接近。</p><p>但是，当索引在 <em>knn-search-10-100</em> 和 <em>knn-search-100-1000</em> 中具有多个分段（这是索引接收其文档更新的典型情况）时，Elasticsearch 将延迟保持在约 7 毫秒和 16 毫秒左右，而所有其他 OpenSearch 引擎则较慢。</p><p>此外，当同时搜索和写入索引时 (<em>knn-search-100-1000-concurrent-with-indexing</em>)，Elasticsearch 将延迟保持在 15 毫秒以下（13.8 毫秒），比开箱即用的 OpenSearch 快近 <strong>4 倍</strong>（49.3 毫秒），并且在启用并发段搜索时仍然更快（17.9 毫秒），但由于过于接近，所以意义不大。</p><p>至于精确 KNN，差距则大得多：Elasticsearch 比 OpenSearch<strong>快 6 倍</strong>（约 260 毫秒对约 1600 毫秒）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt254f43bcaa3dbfc2/6a17d72ddbb4ffc780fb54ff/17aec6be31117440bc4d1f99984aed95df1c4f6b-1440x1728.webp" alt="dense_vector" /><h4>召回</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969843</p><p>0.996577</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.775458</p><p>0.840254</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.971333</p><p>0.996747</p><p>OpenSearch-2.14.0@faiss</p><p>0.9704</p><p>0.914755</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.968025</p><p>0.913862</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><h2>200 万个向量，768 个维度 (so_vector)</h2><p>此<a href="https://github.com/elastic/rally-tracks/tree/master/so_vector">轨道</a> <code>so_vector</code> 源自于 2022 年 4 月 21 日下载的 <a href="https://archive.org/download/stackexchange/stackoverflow.com-Posts.7z">StackOverflow 帖子转储</a>。它仅包含问题文档——所有代表答案的文档都已删除。每个问题的标题都已使用句子转换器模型 <a href="https://huggingface.co/sentence-transformers/multi-qa-mpnet-base-cos-v1">multi-qa-mpnet-base-cos-v1</a> 编码成一个向量。此数据集包含前 200 万个问题。</p><p>与前一个轨道不同，这里的每个文档都包含除向量之外的其他字段，以支持诸如带筛选和混合搜索功能的近似 KNN 等测试功能。此测试中明显缺少适用于 OpenSearch 的 <code>nmslib</code>，<a href="https://opensearch.org/docs/latest/search-plugins/knn/filter-search-knn/#k-nn-search-with-filters">因为它不支持筛选器</a>。</p><h3>任务</h3><p>每个任务会预热 100 个请求，然后测量 100 个请求。请注意，为了简单起见，我们对任务进行了分组，因为测试包含 16 种搜索类型 * 2 个不同的 k 值 * 3 个不同的 n 值。</p><ul><li><p><strong>knn-10-50</strong>：在没有筛选器的情况下搜索 200 万个向量 (k:10, n:50)</p></li><li><p><strong>knn-10-50-filtered</strong>：<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">使用筛选器</a>在 200 万个向量上进行搜索 (k:10, n:50)</p></li><li><p><strong>knn-10-50-after-force-merge</strong>：在强制合并并使用过滤器后，对 200 万个向量进行搜索（k: 10，n: 50）</p></li><li><p><strong>knn-10-100</strong>：在没有过滤器的情况下搜索 200 万个向量（k: 10，n: 100）</p></li><li><p><strong>KNN-10-100-filtered</strong>：在 200 万个向量上<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">使用过滤器</a>进行搜索（k: 10，n: 100）</p></li><li><p><strong>knn-10-100-after-force-merge</strong>：在强制合并后，使用筛选器在 200 万个向量上进行搜索 (k:10, n:100)</p></li><li><p><strong>knn-100-1000</strong>：在没有过滤器的情况下搜索 200 万个向量（k:100，n:1000）</p></li><li><p><strong>knn-100-1000-filtered</strong>：<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">使用筛选器</a>在 200 万个向量上进行搜索 (k:100, n:1000)</p></li><li><p><strong>knn-100-1000-after-force-merge</strong>：在强制合并后使用过滤器在 200 万个向量上搜索（k：100，n：1000）</p></li><li><p><strong>exact-knn</strong>：<a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json#L56">带筛选器和不带筛选器</a>的精确 KNN 搜索。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b44e1306af35877/6a17d72f577262aca11bca3e/d4ed2982d55370cad4b2048b23ce97caa55017c0-1440x316.webp" alt="so_vector 表" /><p>在这项测试中，Elasticsearch <strong>始终比 OpenSearch 的开箱即用速度快</strong>，OpenSearch 仅在两种情况下更快，但差距不大（<em>knn-10-100</em> 和 <em>knn-100-1000</em>）。将<em>knn-10-50</em>、<em>knn-10-100</em> 和 <em>knn-100-1000</em> 与筛选器结合使用的任务显示出高达 <strong>7 倍</strong>的差异（112 毫秒对 803 毫秒）。</p><p>执行“强制合并”操作后，两种解决方案的性能似乎趋于平稳，这是可以理解的，从 <em>knn-10-50-after-force-merge</em>、<em>knn-10-100-after-force-merge</em> 和 <em>knn-100-1000-after-force-merge</em> 中可以明显看出这一点。在这些任务中，<code>faiss</code> 的速度更快。</p><p>在精确 KNN 的性能上差异再次明显，这次 Elasticsearch 比 OpenSearch <strong>快 13 倍</strong>（约 385 毫秒对比 5262 毫秒）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ccc7c2df84559/6a17d7314b055de00d43203f/615cb9228eb05ddd2e9512b3a6a5bc88d4088a1a-1440x1440.webp" alt="so_vector" /><h4>召回</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>knn-recall-10-50</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>1</p><p>0.986667</p><p>1</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><p>0.976394</p><h2>Elasticsearch 和 Lucene 明显胜出</h2><p>在 Elastic，我们坚持不懈地对 Apache Lucene 和 Elasticsearch 进行创新，以确保我们能够为搜索和检索用例（包括 RAG (Retrieval-Augmented Generation)）提供一流的向量数据库。我们最近取得的进展极大地提升了性能，使向量搜索比以前<a href="https://search-labs.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">更快、更节省空间</a>，这是在 Lucene 9.10 所取得的成果基础上实现的。这篇博客介绍了一项研究，该研究表明，在比较最新版本时，Elasticsearch 的速度比 OpenSearch 快多达 12 倍。</p><p>值得注意的是，这两款产品都使用相同版本的 Lucene（<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-8.14.0.html">Elasticsearch 8.14 发行说明</a> 和 <a href="https://github.com/opensearch-project/OpenSearch/blob/2.14/release-notes/opensearch.release-notes-2.14.0.md">OpenSearch 2.14 发行说明</a>）。</p><p>Elastic 的创新步伐不仅将为我们的本地部署和 Elastic Cloud 客户提供更多服务，还将为使用我们的<a href="https://www.elastic.co/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">无状态平台</a>的客户提供更多服务。在提供对<a href="https://www.elastic.co/search-labs/blog/int4-scalar-quantization-in-lucene">标量量化到 int4</a> 等功能的支持时，我们将进行严格的测试，以确保客户能够在不显著减少召回率的情况下使用这些技术，这与<a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">我们对 int8 的测试</a>类似。</p><p>由于 AI 和机器学习应用程序的普及，向量搜索效率正成为现代搜索引擎中不可或缺的功能。对于寻求能够满足大容量、高复杂性向量数据需求的强大搜索引擎的组织来说，Elasticsearch 是最佳选择。</p><p>无论是扩展已建立的平台还是启动新项目，集成 Elasticsearch 以满足向量搜索需求都是一项战略性举措，将带来切实的长期效益。Elasticsearch 凭借其公认的性能优势，已准备好支撑搜索领域的下一波创新浪潮。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d70b25967c2194e/6a17d732b1e11383f879f0ca/13c3c0053e2968fb835ba2f90f34bec3a011b5c0-880x592.webp" length="0" type="image/webp"/>
    <pubDate>Wed, 26 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[向量相似性测量和评分]]></title>
    <description><![CDATA[探索 Elasticsearch 中的向量相似性度量和评分，包括 L1&amp; L2 距离、余弦相似性、点积相似性和最大内积相似性。]]></description>
    <content:encoded><![CDATA[<p>当需要搜索自由文本，而 Ctrl+F / Cmd+F 又无法满足要求时，通常会想到使用词法搜索引擎。词法搜索引擎擅长分析和标记要搜索的文本，将其转化为可在搜索时匹配的术语，但在理解和理解被索引和搜索文本的真正含义方面，它们通常存在不足。</p><p>这正是矢量搜索引擎的优势所在。它们可以为同一文本编制索引，以便根据文本所代表的含义及其与其他具有相似或相关含义的概念之间的关系进行搜索。</p><p>在本博客中，我们将简要介绍矢量是如何传达文本含义的一个重要数学概念。然后，我们将深入探讨 Elasticsearch 在搜索相邻向量（即搜索含义相似的向量）时支持的不同相似性技术，以及如何对它们进行评分。</p><h2>什么是向量嵌入？</h2><p>本文不会深入探讨向量嵌入的复杂性。如果您想进一步了解这一主题，或者在继续学习之前需要一些入门知识，我们建议您查看<a href="https://www.elastic.co/cn/what-is/vector-embedding">以下指南</a>。</p><p>简而言之，矢量嵌入是通过机器学习过程获得的。深度学习神经网络），可将任何类型的非结构化输入数据（如原始文本、图像、视频、声音等）转化为承载其意义和关系的数字数据。不同类型的非结构化数据需要不同类型的机器学习模型，这些模型经过训练，可以"了解" 每种类型的数据。</p><p>每个矢量将特定数据定位为多维空间中的一个点，该位置代表了模型用来描述数据特征的一组特征。维度的数量取决于机器学习模型，但通常从几百到几千不等。例如，<a href="https://platform.openai.com/docs/guides/embeddings">OpenAI Embeddings 模型</a>拥有 1536 个维度，而<a href="https://docs.cohere.com/reference/embed">Cohere Embeddings 模型</a>则有 382 到 4096 个维度。从最新版本开始，Elasticsearch dense_vector 字段类型支持多达 4096 个维度。</p><p>向量嵌入的真正特点是，意义相似的数据点在空间中靠得很近。另一个有趣的方面是，向量嵌入也有助于捕捉数据点之间的关系。</p><h2>如何比较向量？</h2><p>我们知道，非结构化数据会被机器学习模型切割成向量嵌入，以捕捉数据在大量维度上的相似性，现在我们需要了解这些向量的匹配是如何进行的。事实证明，答案非常简单。</p><p>相互<strong>接近</strong>的向量嵌入代表<strong>语义上相似的</strong>数据片段。因此，当我们查询矢量数据库时，搜索输入（图像、文本等）首先会被转化为矢量嵌入，使用的机器学习模型与索引所有非结构化数据时使用的相同，最终目标是找到与该查询矢量<strong>最近的相邻矢量</strong>。因此，我们需要做的就是找出如何测量查询向量与数据库中索引的所有现有向量之间的"距离" 或"相似性" - 就是这么简单。</p><h2>距离、相似性和评分</h2><p>幸运的是，由于有了向量算术，测量两个向量之间的距离或相似性是一个很容易解决的问题。因此，让我们来看看 Elasticsearch 支持的最常用的距离和相似性函数。警告，前方有数学题！</p><p>在开始之前，让我们先来了解一下计分。事实上，Lucene 只允许分数为正。我们即将介绍的所有距离和相似度函数都能衡量两个向量的接近或相似程度，但这些原始数据很少适合用作分数，因为它们可能是负数。因此，需要从距离或相似度值中得出最终分数，以确保分数为正值，分数越大，排名越靠前（即向量越接近）。</p><h3>L1 距离</h3><p>两个向量和的 L1 距离（也称作曼哈顿距离）是通过将它们所有元素的成对绝对差值相加来测量的。显然，距离 越小，两个向量就越接近。L1 距离公式 (1) 非常简单，如下所示：</p><p>下图（红色）可以直观地说明 L1 距离：</p><p>计算以下两个向量的 L1 距离 \vec  </p><p><strong>重要：</strong> 值得注意的是，L1 距离函数仅支持使用<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html#vector-functions-l1"> </a><code>script_score</code>DSL 查询进行<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.11/dense-vector.html#dense-vector-params"> 精确向量搜索</a> （又称暴力搜索），但不支持使用<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"><code>knn</code></a><a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"> 搜索选项</a> 或<a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"><code>knn</code></a><a href="https://www.elastic.co/cn/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"> DSL 查询 进行 近似</a> kNN 搜索 。</p><h3>L2 距离</h3><p>两个向量和的 L2 距离（也称欧几里得距离）的测量方法是：首先求出它们所有元素的成对差值的平方和，然后取其平方根。它基本上是两点之间最短的路径。与 L1 类似，距离 越小，两个向量越接近：</p><p>下图中的 L2 距离用红色表示：</p><p>让我们重新使用与  距离相同的两个样本向量 和 距离为 。</p><p>就评分而言，两个向量之间的距离越小，它们就越接近（即越相似）。因此，为了得出分数，我们需要反转距离度量，使最小的距离产生最高的分数。使用 L2 距离计算分数的方法如下式（3）所示：</p><p>重复使用前面例子中的样本向量，它们的得分是 \frac  0.2352。相距很近的两个向量的得分将接近 1，而相距很远的两个向量的得分将趋于 0。</p><p>在总结 L1 和 L2 距离函数时，一个很好的类比方法是将 A 和 B 视为纽约市曼哈顿的两座大楼。一辆从 A 地开往 B 地的出租车必须沿着 L1 路径（街道和大道）行驶，而一只鸟可能会使用 L2 路径（直线）。</p><h3>余弦相似性</h3><p>与 L1 和 L2 不同的是，余弦相似度不是测量两个向量和 之间的距离，而是测量它们的相对角度，即它们是否都指向大致相同的方向。相似度 越高，两个矢量之间的夹角就越小，因此，"，它们之间的距离就越近，" ，它们所表达的含义就越相似，"，" 。</p><p>为了说明这一点，让我们设想两个人在野外朝不同的方向寻找。在下图中，穿蓝色衣服的人朝矢量的方向看，穿红色衣服的人朝矢量 的方向看。他们的视线越是朝向同一个方向（即他们的矢量越接近），蓝色和红色区域所代表的视野就越是重叠。它们的视场重叠程度就是它们的余弦相似度。但是，请注意，人 B 比人 A 看得更远（即矢量更长）。人 B 可能在看地平线上远处的一座山，而人 A 可能在看附近的一棵树。对于余弦相似度来说，这没有任何作用，因为它只与角度有关。</p><p>现在我们来计算余弦相似度。公式 (4) 非常简单，分子是两个矢量的点乘，分母是两个矢量的大小乘积（即长度）：</p><p>和之间的余弦相似度如下图所示，是它们之间夹角的度量（红色）：</p><p>让我们绕个弯，具体解释一下这些余弦相似度值的含义。从下面描述余弦函数的图片中可以看出，数值总是在区间内摆动。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0e4eb98ebb64cf3/6a17da287b54f983818b3783/e31145284b8c27d0bd9e3d2831f811388134fd83-1188x272.png" alt="余函数" /><p>请记住，要使两个矢量相似，它们之间的夹角必须尽可能锐利，最好接近，即完全相似度为。换句话说，当矢量...</p><ol><li><p>......彼此<strong>接近</strong>，其角度的余弦接近（即接近）。</p></li></ol><ol><li><p>......<strong>无关</strong>，它们的夹角余弦接近（即接近）。</p></li></ol><ol><li><p>......<strong>相反</strong>，它们的夹角余弦接近（即接近）。</p></li></ol><p>既然我们已经知道如何计算两个向量之间的余弦相似度，并对如何解释所得值有了很好的概念，那么我们就可以重新使用相同的样本向量和，并使用我们之前看到的公式 (4) 计算它们的余弦相似度。</p><p>我们得到的余弦相似度为，更接近于而不是，这意味着这两个矢量<strong>有些相似</strong>，即不是完全相似，但也不是完全无关，当然也没有相反的含义。</p><p>为了从任何余弦相似度值中得出正分数，我们需要使用下面的公式 (5)，该公式将在区间内摆动的余弦相似度值转换为区间内的分数：</p><p>因此，样本向量和的得分为\frac  0.8253。</p><h3>点积相似性</h3><p>余弦相似度的一个缺点是，它只考虑两个矢量之间的角度，而不考虑它们的大小，这意味着如果两个矢量大致指向同一个方向，但其中一个矢量比另一个矢量长很多，那么这两个矢量仍然会被认为是相似的。点积相似性（也称为标量或内积相似性）考虑了向量的角度和大小，从而改进了点积相似性，提供了更精确的相似性度量。为了使向量的大小变得无关紧要，点积相似性要求首先对向量进行归一化处理，因此我们最终只比较单位长度为 1 的向量。</p><p>让我们用同样的两个人来再次说明这个问题，但这次我们把他们放在一个圆形房间的中间，这样他们的视线范围就完全相同了（即房间的半径）。与余弦相似度类似，它们转向的方向越一致（即它们的矢量越接近），它们的视场重叠就越多。然而，与余弦相似性相反的是，两个矢量的长度相同，两个区域的表面相同，这意味着两个人看的是位于相同距离的完全相同的图片。这两个区域的重叠程度表示它们的点积相似度。</p><p>在介绍点积相似性公式之前，让我们先快速了解一下如何对向量进行归一化处理。这非常简单，只需两个微不足道的步骤即可完成：</p><ol><li><p>计算向量的大小</p></li><li><p>将每个分量除以 1 中得到的大小。</p></li></ol><p>举例来说，让我们把向量 \vec我们可以计算出它的大小 \Vert\Vert ，就像我们之前在回顾余弦相似性时看到的那样，即 \sqrt 。然后，将矢量的每个分量除以其幅度，我们就得到了下面的归一化矢量 ：</p><p>对第二个向量 \vec  ：</p><p>为了得出点积相似度公式，我们可以使用公式（4）计算归一化向量和之间的余弦相似度，如下图所示：</p><p>由于两个归一化向量的大小现在都是，所以点积相似公式 (6) 就变成了......你猜对了，两个归一化向量的点积：</p><p>在下图中，我们显示了归一化向量和，我们可以用一个向量在另一个向量上的投影（红色）来说明它们的点积相似性。</p><p>使用新公式 (6)，我们可以计算出两个归一化向量的点积相似度，不出意外，这将得到与余弦相似度完全相同的相似度值：</p><p>在利用点积相似性时，得分的计算方法会因向量包含浮点值还是字节值而有所不同。在前一种情况下，得分的计算方法与余弦相似性相同，使用下面的公式（7）：</p><p>但是，当向量由字节值组成时，计算得分的方法就有些不同了，如下式（8）所示，其中是向量的维数：</p><p>此外，为了获得准确的分数，还有一个限制条件，即包括查询向量在内的所有向量必须具有相同的长度，但不一定是 1。</p><h3>最大内积相似度</h3><p>自 8.11 版起，新的相似度函数比点积相似度的限制更少，因为向量不需要进行归一化处理。<a href="https://www.elastic.co/cn/search-labs/blog/lucene-bringing-maximum-inner-product-to-lucene">下面的文章</a>将详细解释其中的主要原因，但简单来说，某些数据集不太适合对其向量进行归一化处理（例如<a href="https://www.elastic.co/cn/search-labs/blog/elasticsearch-cohere-embeddings-support">Cohere 内嵌</a>），这样做可能会导致相关性问题。</p><p>计算最大内积相似度的公式与点积公式 (6) 完全相同。改变的是计算得分的方式，即使用一个片断函数对最大内积相似度进行缩放，该函数的计算公式取决于相似度是正还是负，如下式（9）所示：</p><p>这个分段函数的作用是，在区间内对所有负的最大内积相似度值进行缩放，在区间内对所有正值进行缩放。</p><h2>总之</h2><p>从数学的角度看，这真是一次不寻常的旅程，但这里有一些启示，你可能会觉得有用。</p><p>您可以使用哪种相似度函数，最终取决于您的向量嵌入是否经过归一化处理。如果您的矢量已经归一化，或者您的数据集与矢量归一化无关（即相关性不会受到影响），那么您可以继续对矢量进行归一化，并使用点积相似性，因为它的计算速度比余弦相似性快得多，因为不需要计算每个矢量的长度。在比较数百万个向量时，这些计算量会增加很多。</p><p>如果您的向量没有标准化，那么您有两种选择：</p><ol><li><p>如果无法对向量进行归一化处理，则使用余弦相似度</p></li><li><p>如果您希望矢量的大小对评分有帮助，可以使用新的最大内积相似性，因为它们确实有意义（例如，Cohere 嵌入）。</p></li></ol><p>至此，计算向量嵌入之间的距离或相似性，以及如何得出它们的分数，对你来说应该已经很清楚了。希望这篇文章对您有所帮助。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5a3e9d39849d3ed/6a17da31a2929960a3d02b3f/4d9e89678798b9de68357b5cc06dbbd8b9c6e5e8-1440x823.webp" length="0" type="image/webp"/>
    <pubDate>Mon, 13 May 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[人工智能剽窃利用 Elasticsearch 检测剽窃行为]]></title>
    <description><![CDATA[以下是如何使用 Elasticsearch 检查人工智能剽窃，重点是使用 NLP 模型和矢量搜索的用例。]]></description>
    <content:encoded><![CDATA[<p>剽窃可以是<strong>直接剽窃</strong>，即抄袭部分或全部内容；也可以是<strong>转述剽窃</strong>，即对作者的作品进行改写，改动一些词或短语。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6e139760e56ec98/6a171147dc55de0ad2e00edf/5d7073187fda829438aeec8d3a1194a5bea2ba57-1440x347.png" alt="" /><p>灵感和意译是有区别的。阅读一篇内容，获得启发，然后用自己的语言来探讨这个想法，即使得出的结论相似，也是有可能的。</p><p>虽然剽窃一直是讨论的话题，但内容的加速生产和发布使其具有现实意义，并构成了持续的挑战。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2af1678cb04506b/6a171149cf4f251c2ab2d257/a0b9a98d729db09dae0a79315c001e6763c12704-1400x1016.png" alt="" /><p>这种挑战并不局限于书籍、学术研究或司法文件，在这些领域经常会进行抄袭检查。它还可以延伸到报纸甚至社交媒体。</p><p>随着信息的丰富和出版的便捷，如何在可扩展的层面上有效检查抄袭行为？</p><p>大学、政府机构和公司使用的工具多种多样，虽然直接的<a href="https://www.elastic.co/search-labs/lexical-and-semantic-search-with-elasticsearch">词汇搜索</a>可以有效检测直接抄袭，但主要的挑战在于识别<strong>转述的内容。</strong></p><h2>利用生成式人工智能检测剽窃行为</h2><p>生成式人工智能带来了新的挑战。人工智能生成的内容在复制时是否被视为抄袭？</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a1ed1b6fa3f1a56/6a17114b4a531bc40736aa69/9345b28d6d27c37469bc38e823c41780b4eabfe5-1440x875.png" alt="" /><p>例如，<a href="https://openai.com/">OpenAI</a>的<a href="https://openai.com/policies/terms-of-use">使用条款</a>规定，OpenAI 不会对 API 为用户生成的内容主张版权。在这种情况下，使用生成式人工智能的个人可以随意使用生成的内容，而无需引用。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2e08ab5b808e38/6a17114dab7f086cbadb9f93/e1f415f69a247666f02ddc81468944920c874cd7-968x814.png" alt="" /><p>不过，是否接受使用生成式人工智能来提高效率仍是一个讨论话题。</p><p>为了对剽窃检测做出贡献，OpenAI 开发了一个<a href="https://huggingface.co/roberta-base-openai-detector">检测模型</a>，但后来承认其准确性不够高。</p><p><em>"我们认为，这对于独立检测来说精度还不够高，需要与基于元数据的方法、人工判断和公众教育相结合，才能更加有效。"</em></p><p>然而，随着更多工具的出现，现在有了更多检测抄袭的选择，即使是在转述和人工智能内容的情况下。</p><h2>使用 Elasticsearch 检测剽窃行为</h2><p>有鉴于此，在本博客中，我们将利用自然语言处理（NLP）模型和矢量搜索（plagiarism detection）来探索元数据搜索之外的另一个使用案例。</p><p>我们利用<a href="https://www.sbert.net/"> SentenceTransformers</a> 提供的<a href="https://sbert.net/datasets/emnlp2016-2018.json"> 数据集（</a> 其中包含 NLP 相关文章），通过<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/plagiarism-detection-with-elasticsearch/plagiarism_detection_es.ipynb"> Python 示例 进行了演示。</a>我们通过 "语义文本相似性 "检查摘要是否抄袭，"语义文本相似性 "考虑的是用之前导入 Elasticsearch 的<a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2">文本嵌入模型</a>生成的 "摘要 "嵌入。此外，为了识别人工智能生成的内容--人工智能剽窃，还将 OpenAI 开发的<a href="https://huggingface.co/roberta-base-openai-detector">NLP 模型</a>导入 Elasticsearch。</p><p>下图说明了数据流：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0464f88d3ed12070/6a17114fab7f084905db9f97/1ad89c98a2f42a497548ca3947749bad54ec1172-1440x880.png" alt="" /><p>在使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html"> 推理处理器</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">的摄取管道</a> 中，"抽象 "段落被映射为一个 768 维的向量，即 "abstract_vector.predicted_value"。</p><p>绘图：</p>"abstract_vector.predicted_value": { # Inference results field
"type": "dense_vector", 
"dims": 768, # model embedding_size
"index": "true", 
"similarity": "dot_product" # When indexing vectors for approximate kNN search, you need to specify the similarity function for comparing the vectors.
<p>矢量表示之间的相似性使用矢量相似性度量来衡量，该度量使用 "相似性 "<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">参数</a>来定义。</p><p><a href="https://en.wikipedia.org/wiki/Cosine_similarity">余弦</a>是默认的相似度量，计算公式为"(1 + cosine(query, vector))/ 2'.除非您需要保留原始矢量，并且无法事先对它们进行归一化处理，否则执行余弦相似性的最有效方法就是将所有矢量归一化为单位长度。这有助于避免在搜索过程中执行额外的矢量长度计算，而应使用 "dot_product"。</p><p>在同一管道中，另一个包含<a href="https://huggingface.co/roberta-base-openai-detector">文本分类模型</a>的推理处理器会检测内容是可能由人类撰写的 "真实 "内容，还是可能由人工智能撰写的 "虚假 "内容，并将 "openai-detector.predicted_value "添加到每个文档中。</p><p>摄取管道：</p>client.ingest.put_pipeline( 
    id="plagiarism-checker-pipeline",
    processors = [
    {
      "inference": { #for ml models - to infer against the data that is being ingested in the pipeline
        "model_id": "roberta-base-openai-detector", #text classification model id
        "target_field": "openai-detector", # Target field for the inference results
        "field_map": { #Maps the document field names to the known field names of the model.
        "abstract": "text_field" # Field matching our configured trained model input. 
        }
      }
    },
    {
      "inference": {
        "model_id": "sentence-transformers__all-mpnet-base-v2", #text embedding model id
        "target_field": "abstract_vector", # Target field for the inference results
        "field_map": {
        "abstract": "text_field" # Field matching our configured trained model input. Typically for NLP models, the field name is text_field.
        }
      }
    }
    
  ]
)
<p>在查询时，同样的文本嵌入模型也被用于在 "query_vector_builder "对象中生成查询 "model_text "的向量表示。</p><p>k-nearest neighbor (kNN) 搜索会根据相似度量找到与查询向量最近的 k 个向量。</p><p>每份文档的  分数都来自相似度，确保分数越大，排名越靠前。这意味着文件在语义上更加相似。因此，我们打印了三种可能性：如果得分&gt; 0.9，我们认为是 "高度相似"；如果&lt; 0.7，是 "低度相似"，否则是 "中度相似"。您可以根据自己的使用情况，灵活设置不同的阈值，以确定哪种程度的 _score 是否属于抄袭。</p><p>此外，还进行了文本分类，以检查文本查询中是否有人工智能生成的元素。</p><p>查询：</p>from elasticsearch import Elasticsearch
from elasticsearch.client import MlClient

#duplicated text - direct plagiarism test

model_text = 'Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at http://hucvl.github.io/recipeqa.'

response = client.search(index='plagiarism-checker', size=1,
    knn={
        "field": "abstract_vector.predicted_value",
        "k": 9,
        "num_candidates": 974,
        "query_vector_builder": { #The 'all-mpnet-base-v2' model is also employed to generate the vector representation of the query in a 'query_vector_builder' object.
            "text_embedding": {
                "model_id": "sentence-transformers__all-mpnet-base-v2",
                "model_text": model_text
            }
        }
    }
)

for hit in response['hits']['hits']:
    score = hit['_score']
    title = hit['_source']['title']
    abstract = hit['_source']['abstract']
    openai = hit['_source']['openai-detector']['predicted_value']
    url = hit['_source']['url']

    if score &gt; 0.9:
        print(f"\nHigh similarity detected! This might be plagiarism.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    elif score &lt; 0.7:
        print(f"\nLow similarity detected. This might not be plagiarism.")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    else:
        print(f"\nModerate similarity detected.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

ml_client = MlClient(client)

model_id = 'roberta-base-openai-detector' #open ai text classification model

document = [
    {
        "text_field": model_text
    }
]

ml_response = ml_client.infer_trained_model(model_id=model_id, docs=document)

predicted_value = ml_response['inference_results'][0]['predicted_value']

if predicted_value == 'Fake':
    print("\nNote: The text query you entered may have been generated by AI.\n")
<p>输出：</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:1.0
<p>在本例中，利用数据集中的一个 "摘要 "值作为文本查询 "model_text "后，剽窃行为被识别出来。相似度分数为 1.0，表明相似度很高，即<strong>直接抄袭</strong>。矢量化查询和文档没有被识别为人工智能生成的内容，这在意料之中。</p><p>查询：</p>#similar text - paraphrase plagiarism test 

model_text = 'Comprehending and deducing information from culinary instructions represents a promising avenue for research aimed at empowering artificial intelligence to decipher step-by-step text. In this study, we present CuisineInquiry, a database for the multifaceted understanding of cooking guidelines. It encompasses a substantial number of informative recipes featuring various elements such as headings, explanations, and a matched assortment of visuals. Utilizing an extensive set of automatically crafted question-answer pairings, we formulate a series of tasks focusing on understanding and logic that necessitate a combined interpretation of visuals and written content. This involves capturing the sequential progression of events and extracting meaning from procedural expertise. Our initial findings suggest that CuisineInquiry is poised to function as a demanding experimental platform.'
<p>输出：</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:0.9302529

Note: The text query you entered may have been generated by AI.
<p>通过更新文本查询 "model_text"，使用人工智能生成的文本，在传达相同信息的同时尽量减少相似词语的重复，检测到的相似度仍然很高，但得分为 0.9302529，而不是 1.0，这就是<strong>转述剽窃</strong>。人们还预计，由人工智能生成的这一查询会被检测到。</p><p>最后，考虑到文本查询 "model_text "是关于 Elasticsearch 的文本，而不是这些文档的摘要，检测到的相似度为 0.68991005，表明根据所考虑的阈值，相似度较低。</p><p>查询：</p>#different text - not a plagiarism

model_text = 'Elasticsearch provides near real-time search and analytics for all types of data.'
<p>输出：</p>Low similarity detected. This might not be plagiarism.
<p>虽然在人工智能生成的文本查询中，以及在转述和直接复制内容的情况下，都能准确识别出抄袭行为，但在剽窃检测领域中，还需要认识到各个方面的问题。</p><p>在人工智能生成的内容检测方面，我们探索了一种模式，这种模式做出了宝贵的贡献。不过，必须认识到独立检测的固有局限性，因此需要结合其他方法来提高准确性。</p><p>选择文本嵌入模型带来的可变性是另一个考虑因素。使用不同数据集训练的不同模型会产生不同程度的相似性，这突出了所生成的文本嵌入的重要性。</p><p>最后，在这些例子中，我们使用了文件摘要。然而，剽窃检测通常涉及大量文件，因此必须解决文本长度的难题。文本通常会超过模型的标记限制，这就要求在建立嵌入之前将文本分割成块。处理这种情况<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.11/knn-search.html#nested-knn-search">的实用方法</a>是使用 dense_vector 嵌套结构。</p><h2>结论</h2><p>在这篇博客中，我们讨论了检测抄袭（尤其是在转述和人工智能生成的内容中）所面临的挑战，以及如何利用语义文本相似性和文本分类来实现这一目的。</p><p>结合这些方法，我们提供了一个剽窃检测实例，成功识别了人工智能生成的内容、直接剽窃和转述剽窃。</p><p>我们的主要目标是建立一个过滤系统，简化检测工作，但人工评估对于验证工作仍然至关重要。</p><p>如果您有兴趣了解更多有关语义文本相似性和 NLP 的信息，我们建议您同时查看以下链接：</p><ul><li><p><a href="https://www.elastic.co/what-is/semantic-search">什么是语义搜索？</a></p></li><li><p><a href="https://www.elastic.co/what-is/natural-language-processing">什么是自然语言处理（NLP）？</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch">使用 Elasticsearch 进行词法和语义搜索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">通过 Ingest 管道对大型文档进行分块，再加上嵌套向量，就能轻松进行通道搜索</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a5bc2434a9b03b/6a1711510e2e49a09641a22a/83e05cd4f81799fbb7b7950ed87600e825ec81e9-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 19 Dec 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 和 Go 进行混合搜索来打地鼠]]></title>
    <description><![CDATA[了解如何使用 Elasticsearch 和 Elasticsearch Go 客户端结合关键字和矢量搜索实现混合搜索。]]></description>
    <content:encoded><![CDATA[<p>本系列的前几部分演示了如何使用 Elasticsearch Go 客户端进行<a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">传统的关键字搜索</a>和<a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">矢量搜索</a>。第三部分涉及混合搜索。我们将分享如何使用<a href="https://www.elastic.co/elasticsearch/"> Elasticsearch</a> 和<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch"> </a><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 客户端</a> 结合矢量搜索和关键字搜索的 示例 。</p><h2>准备工作</h2><p>与本系列的第一部分一样，本示例也需要以下前提条件：</p><ol><li><p>安装 Go 1.21 或更高版本</p></li><li><p>使用<a href="https://go.dev/doc/code">Go 文档</a>中推荐的结构和软件包管理创建自己的 Go 仓库</p></li><li><p>创建自己的 Elasticsearch 集群，其中包含一组<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch#sources"> 基于啮齿类动物的页面</a> ，包括维基百科中与我们友好的<a href="https://en.wikipedia.org/wiki/Gopher"> Gopher</a> 的 页面 ：</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="维基百科 Gopher 网页" /><h2>连接到 Elasticsearch</h2><p>提醒一下，在我们的示例中，我们将使用 Go 客户端提供的<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">Typed API</a>。要为任何查询建立安全连接，都需要使用以下两种方法之一配置客户端：</p><ol><li><p>云 ID 和 API 密钥（如果使用弹性云</p></li><li><p>群集 URL、用户名、密码和证书</p></li></ol><p>连接到位于 Elastic Cloud 上的集群的过程如下：</p>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:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

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

	return es, nil
}
<p>然后，<code>client</code> 连接可用于搜索，这将在随后的章节中演示。</p><h2>手动增强混合搜索</h2><p>在组合任何一组搜索算法时，传统的方法是手动配置常量，以提高每种查询类型的效率。具体来说，为每个查询指定一个因子，然后将组合结果集与预期结果集进行比较，以确定查询的召回率。然后，我们对几组因子进行重复计算，选出最接近理想状态的因子。</p><p>例如，在两种查询类型中都指定<code>Boost</code> 字段，就可以将系数为<code>0.8</code> 的单一文本搜索查询与系数为<code>0.2</code> 的较低 knn 查询结合起来，如下例所示：</p>func HybridSearchWithBoost(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10
	var knnBoost float32 = 0.2
	var queryBoost float32 = 0.8

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			Boost:         &amp;knnBoost,
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {
					Query: term,
					Boost: &amp;queryBoost,
				},
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p><code>Boost</code> 选项中为每个查询指定的因子会添加到文档得分中。通过将匹配查询的得分提高一个比 knn 查询更大的系数，关键词查询的结果将获得更高的权重。</p><p>手动提升的挑战在于，它需要进行调整，以找出能产生理想结果集的因素，这对不是搜索专家的人来说尤其如此。这只是一个尝试随机值的例子，看看什么值更接近你想要的结果集。</p><h2>混合搜索中的互惠排名融合&amp; Go 客户端</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">互惠排名融合</a>（RRF）在 Elasticsearch 8.9 混合搜索技术预览版中发布。其目的是降低与调整相关的学习曲线，减少为优化结果集而试验各种因素的时间。</p><p>使用 RRF 时，文档得分通过以下算法混合得分重新计算：</p>score := 0.0
// q is a query in the set of queries (vector and keyword search)
for _, q := range queries {
    // result(q) is the results 
    if document in result(q) {
        // k is a ranking constant (default 60)
        // rank(result(q), d) is the document's rank within result(q) 
        // range from 1 to the window_size (default 100)
        score +=  1.0 / (k + rank(result(q), d))
    }
}

return score
<p>使用 RRF 的好处是，我们可以利用 Elasticsearch 中合理的默认值。排序常数<code>k</code> 默认为<code>60</code> 。在对大型数据集进行搜索时，为了在返回文档的相关性和查询性能之间进行权衡，每个考虑过的查询结果集的大小都限制在<code>window_size</code> 的值范围内，默认值为<code>100</code> ，如<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html#rrf-api">文档</a>中所述。</p><p><code>k</code> 和<code>windows_size</code> 也可以在 Go 客户端<code>Rank</code> 方法中的<code>Rrf</code> 配置中进行配置，如下例所示：</p>func HybridSearchWithRRF(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10

	// Minimum required window size for the default result size of 10
	var windowSize int64 = 10
	var rankConstant int64 = 42

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {Query: term},
			},
		}).
		Rank(&amp;types.RankContainer{
			Rrf: &amp;types.RrfRank{
				WindowSize:   &amp;windowSize,
				RankConstant: &amp;rankConstant,
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<h2>结论</h2><p>在此，我们讨论了如何使用 Elasticsearch<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Go 客户端 在</a> Elasticsearch 中将向量搜索和关键字搜索结合起来。</p><p>查看<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">GitHub 仓库</a>中本系列的所有代码。如果您还没有阅读本系列的所有代码，请查看<a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">第 1 部分</a>和<a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">第 2 部分</a>。</p><p><em>祝您打地鼠愉快</em></p><h2>资源</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Elasticsearch 指南</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 客户端</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">什么是矢量搜索？| 弹性</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">互惠等级融合</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 02 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch Go 客户端在 Elasticsearch 中执行矢量搜索]]></title>
    <description><![CDATA[通过实际示例了解如何使用 Elasticsearch Go 客户端在 Elasticsearch 中执行矢量搜索。]]></description>
    <content:encoded><![CDATA[<p>使用包括 Go 语言在内的任何编程语言构建软件，都需要终身学习。在大学和工作生涯中，卡莉涉猎过多种编程语言和技术，包括最新、最先进的矢量搜索技术。但这还不够！所以最近 Carly 也开始玩围棋了。</p><p>就像动物、编程语言和您友好的作者一样，搜索也经历了不同实践的演变，您可能很难在自己的搜索用例中做出选择。在本博客中，我们将分享矢量搜索的概述，以及使用<a href="https://www.elastic.co/elasticsearch/"> Elasticsearch</a> 和<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Elasticsearch Go 客户端</a> 的每种方法的<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch"> 示例</a> 。这些示例将向您展示如何使用 Elasticsearch 和 Go 中的矢量搜索查找地鼠并确定它们吃什么。</p><h2>准备工作</h2><p>要学习本示例，请确保满足以下前提条件：</p><ol><li><p>安装 Go 1.21 或更高版本</p></li><li><p>使用</p></li><li><p>创建您自己的 Elasticsearch 集群，其中包含一组基于啮齿动物的页面，包括维基百科中对我们友好的<a href="https://en.wikipedia.org/wiki/Gopher">Gopher</a> 的介绍：</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="维基百科 Gopher 网页" /><h2>连接到 Elasticsearch</h2><p>在我们的示例中，我们将使用 Go 客户端提供的<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">类型 API</a>。要为任何查询建立安全连接，都需要使用以下两种方法之一配置客户端：</p><ol><li><p>云 ID 和 API 密钥（如果使用弹性云）。</p></li><li><p>集群 URL、用户名、密码和证书。</p></li></ol><p>连接到位于 Elastic Cloud 上的集群的过程如下：</p>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:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

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

	return es, nil
}
<p>如后续章节所示，<code>client</code> 连接可用于矢量搜索。</p><h2>向量搜索</h2><p>矢量搜索试图通过将搜索问题转换为使用矢量的数学比较来解决这一问题。文件嵌入过程还有一个额外的阶段，即使用模型将文件转换成密集的向量表示，或者简单地转换成数字流。这种方法的优势在于，通过将非文本文档（如图像和音频）翻译成矢量并与查询一起进行搜索。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4b3b9b2ad1e8f46/6a1704d06234e0800fdb1927/b113358093e367358d684f7aaf0a6684ebb2d0dd-1440x653.png" alt="矢量搜索图" /><p>简单地说，矢量搜索是一组矢量距离计算。在下图中，我们将查询<code>Go Gopher</code>的向量表示与向量空间中的文档进行比较，并返回最接近的结果（用常数<code>k</code> 表示）：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c736ecfc49bb038/6a1704d2cf4f25bcb6b2d075/54a17a4b41029644e7c66f33d428e8d01a6f4ce3-1184x743.png" alt="Gopher 矢量空间示例" /><p>根据为文档生成嵌入式的方法，有两种不同的方法可以找出地鼠吃什么。</p><h3>方法 1：自带模型</h3><p>有了白金许可，就可以通过上传模型和使用推理 API 在 Elasticsearch 中生成嵌入。建立模型有六个步骤：</p><ol><li><p>从模型库中选择一个 PyTorch 模型上传。在本例中，我们使用 Hugging Face 的<a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">句子转换器/msmarco-MiniLM-L-12-v3</a>来生成嵌入。</p></li><li><p>使用 Elasticsearch 集群的凭据和任务类型<code>text_embeddings</code> ，使用<a href="https://www.elastic.co/guide/en/elasticsearch/client/eland/current/overview.html">Python 版 Eland 机器学习客户端</a>将模型加载到 Elastic 中。如果没有安装 Eland，可以<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-import-model.html#ml-nlp-import-docker">使用 Docker 运行导入步骤</a>，如下图所示：</p></li></ol>docker run -it --rm --network host \
    docker.elastic.co/eland/eland \
    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
<ol><li><p>上传后，请使用样本文档快速测试模型<code>sentence-transformers__msmarco-minilm-l-12-v3</code> ，以确保生成的嵌入结果符合预期：</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a399e1f50ecfdaf/6a1704d4ab7f08935edb9d78/fbbdde621361a487eb07304bed029c228d0f7aa6-1440x789.png" alt="弹性测试训练模型示例" /><ol><li><p>创建包含推理处理器的摄取管道。这将允许使用上传的模型生成矢量表示：</p></li></ol>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"
        }
      }
    }
  ]
}
<ol><li><p>创建一个新索引，其中包含类型为<code>dense_vector</code> 的字段<code>text_embedding.predicted_value</code> ，用于存储为每个文档生成的向量嵌入：</p></li></ol>PUT vector-search-rodents
{
  "mappings": {
    "properties": {
      "text_embedding.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true,
        "similarity": "cosine"
      },
      "text": {
        "type": "text"
      }
    }
  }
}
<ol><li><p>使用新创建的摄取管道重新索引文档，生成文本嵌入作为每个文档的附加字段<code>text_embedding.predicted_value</code> ：</p></li></ol>POST _reindex
{
  "source": {
    "index": "search-rodents"
  },
  "dest": {
    "index": "vector-search-rodents",
    "pipeline": "search-rodents-vector-embedding-pipeline"
  }
}
<p>现在，我们可以使用新的索引<code>vector-search-rodents</code> ，在同一个搜索 API 中使用<code>Knn</code> 选项，如下例所示：</p>func VectorSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
  var k = 10
	var numCandidates = 10

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             &amp;k,
      # Number of candidates to evaluate in comparison
			NumCandidates: &amp;numCandidates,
      # Generate query vector using the same model used in the inference processor
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;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)
}
<p>通过解分叉转换 JSON 结果对象的方法与关键字搜索示例完全相同。常量<code>K</code> 和<code>NumCandidates</code> 允许我们配置返回的邻接文件数和每个分区要考虑的候选文件数。需要注意的是，增加候选结果的数量会提高结果的准确性，但由于要进行更多的比较，查询的运行时间会更长。</p><p>当使用查询<code>What do Gophers eat?</code> 执行代码时，返回的结果与下图类似，突出显示 Gopher 文章包含所需的信息，与之前的关键字搜索不同：</p>[
  {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
]
<h3>方法 2：拥抱脸推理应用程序接口</h3><p>另一种方法是在 Elasticsearch 之外生成相同的嵌入，并将其作为文档的一部分进行摄取。由于该选项不使用 Elasticsearch 机器学习节点，因此可以在免费层上完成。</p><p>Hugging Face 公开了一个免费使用、有费率限制的<a href="https://huggingface.co/docs/api-inference/index">推理 API</a>，只要有账户和 API 令牌，就可以手动生成相同的嵌入式数据，用于实验和原型开发，帮助你开始工作。不建议用于生产。也可以使用类似的方法在本地调用自己的模型来生成嵌入，或使用付费 API。</p><p>在下面的函数<code>GetTextEmbeddingForQuery</code> 中，我们根据查询字符串使用推理 API 生成从<code>POST</code> 请求端点返回的向量：</p>// 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 := &amp;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(&amp;post)

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

    return post
}
<p>生成的<code>[]float32</code> 类型的向量将作为<code>QueryVector</code> 传递，而不是使用<code>QueryVectorBuilder</code> 选项来利用之前上传到 Elastic 的模型。</p>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)
	}

  var k = 10
	var numCandidates = 10

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

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p>请注意，无论使用哪两个选项，<code>K</code> 和<code>NumCandidates</code> 选项都是一样的，而且生成的结果也是一样的，因为我们使用的是同一个模型来生成嵌入结果</p><h2>结论</h2><p>在此，我们讨论了如何使用 Elasticsearch<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Go 客户端 在</a> Elasticsearch 中执行矢量搜索。查看<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">GitHub 仓库</a>中本系列的所有代码。请继续阅读<a href="https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client">第 3 部分</a>，了解如何将矢量搜索与 Go 语言<a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">第一部分</a>中的关键字搜索功能相结合。</p><p>在此之前，祝您打地鼠愉快！</p><h2>资源</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Elasticsearch 指南</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 客户端</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">什么是矢量搜索？| 弹性</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 01 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 进行词法和语义搜索]]></title>
    <description><![CDATA[在本博客中，我们将探讨使用 Elasticsearch 检索信息的各种方法，重点是词法和语义搜索。]]></description>
    <content:encoded><![CDATA[<p>搜索是根据您的搜索查询或组合查询查找最相关信息的过程，相关搜索结果是与这些查询最匹配的文档。尽管与搜索相关的挑战和方法有多种，但最终目标始终不变，即<strong>尽可能找到问题的最佳答案</strong>。</p><p>考虑到这一目标，在本篇博文中，我们将探讨使用 Elasticsearch 检索信息的不同方法，重点关注文本搜索：<strong>词法搜索和语义搜索。</strong></p><h2>准备工作</h2><p>为此，我们将提供 Python 示例，演示在模拟电子商务产品信息生成的<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/products-ecommerce.json">数据集上</a>进行搜索的各种情况。</p><p>该数据集包含 2,500 多种产品，每种产品都有说明。这些产品分为 76 个不同的产品类别，每个类别包含的产品数量各不相同，如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34415151c00ced2b/6a17d8710b0bed6c9bdd342c/4104466050f3024b6bcaf382da2a702650f62227-1440x708.png" alt="" /><p><em>树状地图可视化 - category.keyword 的前 22 个值（产品类别）</em></p><p>安装时您需要</p><ul><li><p>Python 3.6 或更高版本</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html">弹性 Python 客户端</a></p></li><li><p>Elastic 8.8 或更高版本，配备 8GB 内存的机器学习节点</p></li><li><p><a href="https://www.elastic.co/guide/en/machine-learning/8.9/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a>模型已预装到 Elastic 中，并已在部署中启动</p></li></ul><p>我们将使用 Elastic Cloud，<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">可免费试用</a>。</p><p>除了本博文提供的搜索查询外，<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python 笔记本</a>还能指导您完成以下过程：</p><ul><li><p>使用 Python 客户端与我们的弹性部署建立连接</p></li><li><p>将文本嵌入模型载入 Elasticsearch 集群</p></li><li><p>创建一个索引，其中包含用于索引特征向量和密集向量的映射。</p></li><li><p>创建带推理处理器的摄取管道，用于文本嵌入和文本扩展</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e95406ece8f08d9/6a17d8731d1b83d32e93e2f4/54a9a490a3b0cf1b9c2228bee8eddd3f566bd435-1418x1102.png" alt="" /><h2>词汇检索--稀疏检索</h2><p>Elasticsearch 根据文本查询对文档进行相关性排序的经典方法是使用<a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a>模型的 Lucene 实现，这是一种<strong>用于词法搜索的稀疏模型</strong>。这种方法沿用了传统的文本搜索方法，即寻找完全匹配的术语。</p><p>为了实现这种搜索，Elasticsearch 通过执行文本分析将文本<strong>字段</strong>数据转换为可搜索格式。</p><p><strong>文本分析</strong> 由<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html"> 分析器</a> 执行， 分析器 是一套规则，用于管理提取相关词组以供搜索的过程。一个分析器必须有一个<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> 标记符</a>。标记化器接收字符流，并将其分解为单个标记（通常是单个单词），就像下面的例子一样：</p><h3>用于词法搜索的字符串标记化</h3>#Performs text analysis on a string and returns the resulting tokens.

# Define the text to be analyzed
text = "Comfortable furniture for a large balcony"

# Define the analyze request
request_body = {
  "analyzer": "standard",
  "text": text
}

# Perform the analyze request
response = client.indices.analyze(analyzer=request_body["analyzer"], text=request_body["text"])

# Extract and display the analyzed tokens
tokens = [token["token"] for token in response["tokens"]]
print("Analyzed Tokens:", tokens)
<p>输出</p>Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']
<p>在本例中，我们使用的是默认分析器--<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html">标准</a>分析器，它能提供基于英语语法的标记化，因此能很好地满足大多数使用要求。标记化可实现单个术语的匹配，但每个标记仍按字面进行匹配。</p><p>如果您想个性化搜索体验，可以选择不同的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html"> 内置分析器</a>。例如，通过更新代码以使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-analyzer.html">停顿分析器</a>，就能在任何非字母字符处将文本分解为词块，并支持删除停顿词。</p>...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...
<p>输出</p>Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']
<p>当内置分析器无法满足您的需求时，您可以创建一个<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html"> 自定义分析器</a> ，该 分析器 使用零个或多个<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html"> 字符过滤器</a> 、一个<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> 标记器</a> 和零个或多个<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html"> 标记过滤器的</a> 适当组合。</p>"analyzer":  {

  "my_analyzer": {

    "type": "custom", #For custom analyzers, use a type of custom or omit the type parameter.

    "tokenizer": "standard", #Built-in or customized tokenizer

    "filter": ["lowercase", "synonym"] #Built-in or customized token filters
  }
}
<p>在上述结合了标记符号化器和标记符号过滤器的示例中，文本将先由<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html"> 小写过滤器</a> 进行 小写<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html#:~:text=Elasticsearch%20will%20use%20the%20token,applied%20to%20the%20synonym%20entries."> 处理，然后再由 同义词标记符号过滤器</a> 进行处理。</p><h2>词汇匹配</h2><p><a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables">BM25</a>将根据术语的频率及其重要性来衡量文档与给定搜索查询的相关性。</p><p>下面的代码执行<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html"> 匹配</a> 查询，搜索最多两个文档，考虑到<em> "ecommerce-search</em><em> "</em> 索引中的 <strong>"描述"</strong><em><strong> 字段值和搜索查询 " 大阳台的舒适家具</strong></em><strong> ".</strong></p><p>改进文档的匹配标准可以提高查询的精确度。然而，更具体的结果是以较低的差异容忍度为代价的。</p># BM25

response = client.search(size=2,
index="ecommerce-search",
query= {
  "match": {
    "description" : {  
      "query": "Comfortable furniture for a large balcony",
      "analyzer": "stop"
    }
  }
}
)

hits = response['hits']['hits']

if not hits:
  print("No matches found")

else:
  for hit in hits:
    score = hit['_score']
    product = hit['_source']['product']
    category = hit['_source']['category']
    description = hit['_source']['description']
    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>输出</p>Score: 15.607948
Product: Barbie Dreamhouse
Category: Toys
Description: is a classic Barbie playset with multiple rooms, furniture, a large balcony, a pool, and accessories. It allows kids to create their dream Barbie world.

Score: 9.137739
Product: Comfortable Rocking Chair
Category: Indoor Furniture
Description: enjoy relaxing moments with this comfortable rocking chair. Its smooth motion and cushioned seat make it an ideal piece of furniture for unwinding.
<p>通过分析输出结果，最相关的结果是"<em>芭比娃娃梦幻屋</em>" 产品，属于"<em>玩具</em>" 类别，其描述高度相关，因为其中包括术语"<em>家具</em>" 、"<em>大型"</em>和<em>"阳台</em>" ，这是唯一一个在描述中有 3 个术语符合搜索查询的产品，该产品也是唯一一个在描述中有术语<em>"阳台"</em>的产品。</p><p>排名第二的产品是"<em>舒适摇椅</em>" ，归类为"<em>室内家具</em>" ，其描述包括"<em>舒适</em>" 和"<em>家具</em>" 等术语。数据集中只有 3 种产品至少与该搜索查询的 2 个词匹配，该产品就是其中之一。</p><p><em>"Comfortable"</em>出现在 105 个产品的描述中，<em>"furniture"</em>出现在 4 个产品的描述中，有 4 个不同的类别：<em>玩具</em>、<em>室内家具、室外家具和 "猫狗用品&amp; 玩具"。</em></p><p>正如您所看到的，考虑到该查询，最相关的产品是玩具，第二相关的产品是室内家具。如果你想了解分数计算的详细信息，知道为什么这些文档是匹配的，可以将<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html"><em>explain</em></a>__query 参数设置为 true。</p><p>尽管这两个结果都是最相关的结果，但考虑到该数据集中的文档数量和术语出现率，查询"<em>大型阳台的舒适家具</em>" 背后的意图是搜索实际大型阳台的家具，不包括玩具和室内家具。</p><p>词法搜索相对<strong>简单快捷</strong>，但也有局限性，因为在不了解用户意图和查询的情况下，不可能总是知道所有可能的术语和同义词。自然语言使用中的一个常见现象是<strong>词汇不匹配</strong>。<a href="https://dl.acm.org/doi/abs/10.1145/32206.32212">研究</a>表明，不同的人（同一领域的专家）对同一事物的命名平均有<strong>80% </strong>不同。</p><p>这些局限性促使我们寻找其他包含语义知识的评分模型。基于变换器的模型擅长处理像自然语言这样的连续输入标记，它通过考虑文档和查询的数学表示来捕捉搜索的基本含义。这样就能对文本进行密集的、上下文感知的矢量表示，为<strong>语义搜索</strong>提供动力，这是一种查找相关内容的精细方法。</p><h2>语义搜索 - 密集检索</h2><p>在这种情况下，将数据转换成有意义的向量值后，利用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">k-nearest neighbor（kNN）</a>搜索算法在数据集中找到与查询向量最相似的向量表示。Elasticsearch 支持两种 kNN 搜索方法，即<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn">精确暴力 kNN</a>和<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">近似 kNN</a>（也称为 ANN）。</p><p>粗暴的 kNN 可以保证准确的结果，但在处理大型数据集时并不能很好地扩展。近似 kNN 通过牺牲一些精度来提高性能，从而有效地找到近似近邻。</p><p>借助 Lucene 对 kNN 搜索和密集矢量索引的支持，Elasticsearch 利用了分层导航小世界 (HNSW) 算法，该算法在各种<a href="http://ann-benchmarks.com/">ann-benchmark 数据集上</a>都表现出了强大的搜索性能。可以使用下面的示例代码在 Python 中执行近似 kNN 搜索。</p><h3>使用近似 kNN 进行语义搜索</h3># KNN - approximate kNN

response = client.search(index='ecommerce-search', size=2,
knn={
  "field": "description_vector.predicted_value",
  "k": 50, # Number of nearest neighbors to return as top hits.
#The optimal value of k is dependent on the data. It can vary in different scenarios.

  "num_candidates": 500, # Number of nearest neighbor candidates to consider per shard.

#Increasing num_candidates tends to improve the accuracy of the final k results.

  "query_vector_builder": { # Object indicating how to build a query_vector. kNN search enables you to perform semantic search by using a previously deployed text embedding model, the steps for this process are demonstrated in the Python notebook.
    "text_embedding": { 
      "model_id": "sentence-transformers__all-mpnet-base-v2", # Text embedding model id
      "model_text": "Comfortable furniture for a large balcony" # Query
    }
  }
}
)

for hit in response['hits']['hits']:
        
  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>考虑到产品数据集中 " 描述"字段的嵌入情况，该代码块使用 Elasticsearch 的 kNN 返回最多两个描述与"<em>Comfortable furniture for a large balcony</em> " 的矢量化查询 (query_vector_build) 相似的产品。</p><p>产品嵌入之前是在摄取管道中生成的，摄取管道中的推理处理器包含 <em>"</em><a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2"><em>all-mpnet-base-v2</em></a><em>"</em>文本嵌入模型的推理处理器，根据管道中正在摄取的数据进行推理。</p><p>选择该模型的依据是使用 <em>"</em><a href="https://github.com/UKPLab/sentence-transformers/blob/master/docs/package_reference/sentence_transformer/evaluation.md"><em>句子转换器评估</em></a><em>"</em>在训练过程中，使用不同的类别来评估模型。"all-mpnet-base-v2" 模型在<a href="https://www.sbert.net/docs/pretrained_models.html">Sentence-Transformers</a>排行榜上显示出最佳平均性能，并在<a href="https://huggingface.co/spaces/mteb/leaderboard">大规模文本嵌入基准 (MTEB)</a>排行榜上占据有利位置。该模型预先训练了<a href="https://huggingface.co/microsoft/mpnet-base"> 微软/mpnet-base</a>模型，并在 1B 句子对数据集上进行了微调，将句子映射到 768 维的密集向量空间。</p><p>此外，还有许多其他模型可以使用，特别是那些针对特定领域数据进行微调的模型。</p><p>输出</p>Score: 0.79207325
Product: Patio Sofa Set with Ottoman
Category: Outdoor Furniture
Description: is a versatile and comfortable patio sofa set, including a sofa, ottoman, and coffee table, great for outdoor lounging.

Score: 0.7836937
Product: Patio Sofa Set with Canopy
Category: Outdoor Furniture
Description: is a luxurious and comfortable patio sofa set with a canopy, providing shade and style for outdoor lounging.
<p><em>根据所选模型、</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example"><em>过滤器</em></a> <em>和</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#tune-approximate-knn-for-speed-accuracy"><em>近似 kNN 调整</em></a>，输出结果可能会有所不同<em>。</em></p><p>kNN 搜索结果都属于"<em>Outdoor Furniture</em>" 类别，尽管"<em>outdoor</em>" 这个词并没有作为查询的一部分被明确提及，这凸显了语义理解在上下文中的重要性。</p><p>密集矢量搜索有几个优点：</p><ul><li><p>启用语义搜索</p></li><li><p>处理超大数据集的可扩展性</p></li><li><p>灵活处理各种数据类型</p></li></ul><p>不过，<strong>密集矢量搜索也有其自身的挑战</strong>：</p><ul><li><p>为您的使用案例选择合适的嵌入模式</p></li><li><p>选定模型后，可能需要对模型进行微调，以优化特定领域数据集的性能，这一过程需要领域专家的参与</p></li><li><p>此外，索引高维向量的计算成本很高</p></li></ul><h2>语义搜索--学习型稀疏检索</h2><p>让我们来探索另一种方法：学习稀疏检索，这是执行语义搜索的另一种方式。</p><p>作为一种稀疏模型，它利用 Elasticsearch 基于 Lucene 的倒排索引，该索引得益于数十年的优化。不过，这种方法不仅仅是用 BM25 等词汇评分功能添加同义词那么简单。相反，它利用更深层次的语言知识将学习到的联想融入其中，以优化相关性。</p><p>通过扩展搜索查询以包含原始查询中不存在的相关术语，<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elastic Learned Sparse Encoder</a>可以<strong>改进稀疏向量嵌入</strong>，如下例所示。</p><h3>利用弹性学习稀疏编码器进行稀疏向量搜索</h3># Elastic Learned Sparse Encoder

response = client.search(index='ecommerce-search', size=2,
query={
  "text_expansion": {
    "ml.tokens": {
      "model_id":"elser_model",
      "model_text":"Comfortable furniture for a large balcony"                
    }
  }
}
)

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>输出</p>Score: 14.405318
Product: Garden Lounge Set with Side Table
Category: Garden Furniture
Description: is a comfortable and stylish garden lounge set, including a sofa, chairs, and a side table for outdoor relaxation.

Score: 14.281318
Product: Rattan Patio Conversation Set
Category: Outdoor Furniture
Description: is a stylish and comfortable outdoor furniture set, including a sofa, two chairs, and a coffee table, all made of durable rattan material.
<p>本例中的结果包括"<em>Garden Furniture</em>" 类别，该类别提供的产品与"<em>Outdoor Furniture</em>" 非常相似。</p><p>通过分析"ml.tokens" 、"rank_features" 字段，其中包含学习稀疏检索生成的标记，可以明显看出，在生成的各种标记中，有一些术语虽然不是搜索查询的一部分，但其含义仍然相关，如"<em>relax</em>" （舒适）、"<em>sofa</em>" （家具）和"<em>outdoor</em>" （阳台）。</p><p>下面的图片突出显示了查询中的一些术语，包括术语扩展和未扩展术语。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e869985347b19a7/6a17d875e31791dc2c2d56a1/dd86607fce6137d843a3ec002390eaa988b432f9-1440x502.png" alt="" /><p>正如观察到的那样，该模型提供了一种上下文感知搜索，有助于缓解词汇不匹配问题，同时提供更多可解释的结果。在不进行特定领域再训练的情况下，它甚至可以超越密集向量模型。</p><h2>混合搜索：结合词法和语义搜索获得相关结果</h2><p>在搜索方面，没有放之四海而皆准的解决方案。这些检索方法各有所长，但也面临挑战。根据不同的使用情况，最佳选择可能会有所变化。通常情况下，不同检索方法的最佳结果是互补的。因此，为了提高相关性，我们要把每种方法的优势结合起来。</p><p>实现<strong>混合搜索</strong>的方法有多种，包括线性组合、给每个得分加权和互惠等级融合（RRF），其中互惠等级融合无需指定权重。</p><h3>Elasticsearch：词法和语义搜索的两全之策</h3># BM25 + Elastic Learned Sparse Encoder (Linear Combination)

response = client.search(index='ecommerce-search', size=2,

query= {
  "bool": {
    "should": [
    {
      "match": {
        "description" : {  
          "query": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
    },                   
    {
      "text_expansion": {
        "ml.tokens": {
          "model_id": "elser_model",
          "model_text": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
     }
    ]
  }
}
)

# The boost value is 1 for the text expansion and match query. This means that the relevance score of the results of these queries are not boosted. You can specify a boost value to give a weight to each score in the sum. The scores will be calculated as: score = boost value * match_score + boost value * text_expansion_score

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>在这段代码中，我们使用两个查询值为"<em>大阳台的餐桌和舒适的椅子</em>" 进行了混合搜索。我们没有使用"<em>家具</em>" 作为搜索词，而是指定了我们要查找的内容，并且两个搜索都考虑了相同的字段值，即"描述" 。排名由 BM25 和 ELSER 分数等权重的线性组合确定。</p><p>输出</p>Score: 31.628141
Product: Garden Dining Set with Swivel Rockers
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel rockers for easy movement.

Score: 31.334227
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>在下面的代码中，我们将对查询使用相同的值，但会使用对等排名融合方法合并 BM25（查询参数）和 kNN（knn 参数）的得分，对文档进行合并和排名。</p># BM25 + KNN (RRF)

response = client.search(index='ecommerce-search', size=2,
query={
  "bool": {
    "should": [
    {
      "match": {
        "description": {
        "query": "A dining table and comfortable chairs for a large balcony"
        }
      }
    }
    ]
  }
},
knn={
  "field": "description_vector.predicted_value",
  "k": 50,
  "num_candidates": 500,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": "sentence-transformers__all-mpnet-base-v2",
      "model_text": "A dining table and comfortable chairs for a large balcony"
    }
  }
},
rank={
  "rrf": { # Reciprocal rank fusion
    "window_size": 50, # This value determines the size of the individual result sets per query.
    "rank_constant": 20 # This value determines how much influence documents in individual result sets per query have over the final ranked result set.
  }
}
)

for hit in response['hits']['hits']:
        
  rank = hit['_rank']
  category = hit['_source']['category']
  product = hit['_source']['product']
  description = hit['_source']['description']
  print(f"\nRank: {rank}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p><em>RRF 功能处于技术预览阶段。在全球大会之前，语法可能会有所改变。</em></p><p>输出</p>Rank: 1
Product: Patio Dining Set with Bench
Category: Outdoor Furniture
Description: is a spacious and functional patio dining set, including a dining table, chairs, and a bench for additional seating.

Rank: 2
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>在这里，我们还可以使用不同的字段和值，<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python 笔记本</a>中提供了其中一些示例。</p><p>正如您所看到的，有了 Elasticsearch，您就可以两全其美：传统的词法搜索和矢量搜索（无论是稀疏搜索还是密集搜索），以实现您的目标<strong>并找到问题的最佳答案。</strong></p><p>如果您想继续了解这里提到的方法，这些博客会很有用：</p><ul><li><p><a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">改进弹性堆栈中的信息检索：混合检索</a></p></li><li><p><a href="https://www.elastic.co/blog/vector-search-elasticsearch-rationale">Elasticsearch 中的矢量搜索：设计背后的原理</a></p></li><li><p><a href="https://www.elastic.co/blog/lexical-ai-powered-search-elastic-vector-database">如何利用 Elastic 的向量数据库获得词法搜索和人工智能驱动搜索的最佳效果</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">介绍 Elastic 学习稀疏编码器：Elastic 用于语义搜索的人工智能模型</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">改进 Elastic Stack 中的信息检索：介绍我们的新检索模型 Elastic Learned Sparse Encoder</a></p></li></ul><p>Elasticsearch 提供了矢量数据库以及构建矢量搜索所需的所有工具：</p><ul><li><p>Elasticsearch<a href="https://www.elastic.co/elasticsearch/vector-database">矢量数据库</a></p></li><li><p>使用 Elastic 的<a href="https://www.elastic.co/enterprise-search/vector-search">矢量搜索</a>用例</p></li></ul><h2>结论</h2><p>在这篇博文中，我们探讨了使用 Elasticsearch 检索信息的各种方法，尤其侧重于文本、词法和语义搜索。为了证明这一点，我们提供了 Python 示例，使用包含电子商务产品信息的数据集展示了不同的搜索场景。</p><p>我们回顾了 BM25 的经典词汇搜索，并讨论了其优势和挑战，如词汇不匹配。我们强调了结合语义知识来克服这一问题的重要性。此外，我们还讨论了实现语义搜索的密集矢量搜索，并介绍了与这种检索方法相关的挑战，包括索引高维矢量时的计算成本。</p><p>另一方面，我们提到稀疏向量的压缩效果特别好。因此，我们讨论了 Elastic 的 "学习稀疏编码器"（Learned Sparse Encoder），该编码器可扩展搜索查询，以包含原始查询中不存在的相关术语。</p><p>在搜索方面，没有放之四海而皆准的解决方案。每种检索方法都有其优势和挑战。因此，我们还讨论了混合搜索的概念。</p><p>正如您所看到的，有了 Elasticsearch，您就可以同时拥有传统词法搜索和矢量搜索这两种搜索方式的优点！</p><p>准备好开始了吗？查看可用的<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python 笔记本</a>并开始<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">免费试用 Elastic Cloud</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[查询语言]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd7e961f594005e7/6a17d80f033c8d981f6bb009/d240bfef29e9d432069059b312dd044eb76eec6c-1440x840.png" length="0" type="image/png"/>
    <pubDate>Tue, 03 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用 Elasticsearch 中的 NLP 和向量搜索增强聊天机器人功能]]></title>
    <description><![CDATA[探索矢量搜索和 NLP 如何增强聊天机器人的能力，并了解 Elasticsearch 如何促进这一过程。]]></description>
    <content:encoded><![CDATA[<p>对话式界面已经存在了一段时间，作为协助完成各种任务（如客户服务、信息检索和任务自动化）的一种手段，它正变得越来越流行。这些界面通常通过语音助手或消息应用程序访问，模拟人类对话，以帮助用户更高效地解决疑问。</p><p>随着技术的进步，聊天机器人被用来快速处理更复杂的任务，同时还能为用户提供个性化体验。自然语言处理（NLP）使聊天机器人能够处理用户的语言，识别其信息背后的意图，并从中提取相关信息。例如，"命名实体识别 "通过将文本分类，提取文本中的关键信息。情感分析可确定情感基调，而问题解答则可确定查询的 "答案"。NLP 的目标是使算法能够处理人类语言，并完成历史上只有人类才能完成的任务，如在大量文本中查找相关段落、总结文本以及生成新的原创内容。</p><p>这些先进的 NLP 功能建立在一种称为<a href="https://www.elastic.co/what-is/vector-search">向量搜索的</a>技术之上。Elastic 本身支持向量搜索，可执行精确和近似的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search">k-nearest neighbor (kNN) 搜索</a>，还支持 NLP，可直接在 Elasticsearch 中使用自定义或<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-model-ref.html#ml-nlp-model-ref">第三方模型</a>。</p><p>在这篇博文中，我们将探讨矢量搜索和 NLP 如何增强聊天机器人的能力，并演示 Elasticsearch 如何促进这一过程。首先，让我们简要介绍一下矢量搜索。</p><h2>向量搜索</h2><p>虽然人类可以理解书面语言的含义和上下文，但机器却无法做到这一点。这就是向量的作用所在。通过将文本转换为矢量表示（文本含义的数字表示），机器可以克服这一限制。与传统搜索相比，矢量不依赖于关键词和基于频率的词法搜索，而是使用为数值定义的运算来处理文本数据。</p><p>这样，通过使用"嵌入空间" 中的距离来表示给定查询向量的相似性，向量搜索就能定位具有相似概念或上下文的数据。当数据相似时，相应的向量也会相似。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a615a8ba0ac931/6a17d795fbc5f8b257491910/08542abf8108aace288745b1aca8579b476ddc1b-1440x618.png" alt="" /><p>矢量搜索不仅可用于 NLP 应用，还可用于涉及非结构化数据的其他各种领域，包括图像和视频处理。</p><p>在聊天机器人流程中，用户的查询可能有多种方式，因此，有不同的方法来改进信息检索，以获得更好的用户体验。由于每种方案都有自己的优点和可能的缺点，因此必须考虑到可用的数据和资源，以及训练时间（如适用）和预期的准确性。在下一节中，我们将介绍 NLP 问答模型的这些方面。</p><h2>回答问题</h2><p>问题解答（QA）模型是一种 NLP 模型，旨在回答用自然语言提出的问题。当用户提出的问题需要从多个资源中推断答案，而文档中又没有预先存在的目标答案时，生成式质量保证模型就能派上用场。不过，这些模型的计算成本可能很高，而且需要大量数据来进行与领域相关的训练，因此在某些情况下可能不太实用，尽管这种方法对处理域外问题特别有价值。</p><p>另一方面，当用户就特定主题提出问题，而实际答案就在文档中时，就可以使用提取式质量保证模型。这些模型直接从源文档中提取答案，提供透明、可验证的结果，对于希望提供简单高效的问题解答方式的企业或组织来说，是一种更实用的选择。</p><p>下面的示例演示了如何使用预先训练好的抽取式质量保证模型从给定上下文中抽取答案，该模型<a href="https://huggingface.co/deepset/minilm-uncased-squad2">可在 Hugging Face 上获得</a>，并已部署到 Elasticsearch 中：</p>POST _ml/trained_models/deepset__minilm-uncased-squad2/deployment/_infer
{
    "docs": [{"text_field": "Canvas is a data visualization and presentation application within Kibana. With Canvas, live data can be pulled directly from Elasticsearch and combined with colors, images, text, and other customized options to create dynamic, multi-page displays."}],
    "inference_config": {"question_answering": {"question": "What is Kibana Canvas?"}}
}


{
  "predicted_value": "a data visualization and presentation application",
  "start_offset": 10,
  "end_offset": 59,
  "prediction_probability": 0.28304219431376443
}
<p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-models.html">部署训练有素的模型。</a></p><p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-ner-example.html#ex-ner-ingest">将模型添加到推理摄取管道中。</a></p><p>处理用户查询和检索信息的方法多种多样，在处理非结构化数据时，使用多种语言模型和数据源不失为一种有效的选择。为了说明这一点，我们以一个聊天机器人的数据处理为例，该聊天机器人会根据从选定文档中提取的数据来回答询问。</p><h2>聊天机器人数据处理：NLP 和向量搜索</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta418a9c54bb16cf9/6a17d7975772624b371bca43/c2d1a2f110e937b1d3e5df0d5caac3c906c98fb0-1440x748.png" alt="" /><p>如上图所示，聊天机器人的数据处理可分为三个部分：</p><ul><li><p><strong>矢量处理：</strong>这部分将文档转换为矢量表示。</p></li><li><p><strong>用户输入处理：</strong>这部分从用户查询中提取相关信息，并执行语义搜索和混合检索。</p></li><li><p><strong>优化：</strong>这部分包括监控，对于确保聊天机器人的可靠性、最佳性能和良好的用户体验至关重要。</p></li></ul><h2>矢量处理</h2><p>对于<strong>处理</strong>部分，第一步是确定每个文档的组成部分，然后将每个元素转换为矢量表示法；这些表示法可以为多种数据格式创建。</p><p>有多种方法可用于计算嵌入，包括预训练模型和库。</p><p>值得注意的是，在这些表征上进行搜索和检索的有效性取决于现有数据以及所用方法的质量和相关性。</p><p>在计算向量时，它们会以<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">dense_vector</a>字段类型存储在 Elasticsearch 中。</p>PUT &lt;target&gt;
{
  "mappings": {
    "properties": {
      "doc_part_vector": {
        "type": "dense_vector",
        "dims": 3
      },
      "doc_part" : {
        "type" : "keyword"
      }
    }
  }
}
<h2>聊天机器人用户输入处理</h2><p>对于<strong>用户</strong>来说，在收到问题后，最好先从中提取所有可能的信息，然后再继续提问。这有助于理解用户的意图，在这种情况下，我们使用<a href="https://huggingface.co/dslim/bert-base-NER">命名实体识别模型（NER）</a>来帮助理解用户的意图。NER 是将命名实体识别和分类为预定义实体类别的过程。</p>POST _ml/trained_models/dslim__bert-base-ner/deployment/_infer
{
  "docs": { "text_field": "How many people work for Elastic?"}
}


{
  "predicted_value": "How many people work for [Elastic](ORG&amp;Elastic)?",
  "entities": [
    {
      "entity": "Elastic",
      "class_name": "ORG",
      "class_probability": 0.4993975435876747,
      "start_pos": 25,
      "end_pos": 32
    }
  ]
}
<p>虽然这不是必要步骤，但通过使用结构化数据或上述或其他 NLP 模型结果对用户的查询进行分类，我们可以使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example">过滤器</a>对 kNN 搜索进行限制。这有助于减少需要处理的数据量，从而提高性能和准确性。</p>    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
<h2>语义搜索和混合检索</h2><p>由于提示源于用户询问，而聊天机器人需要处理人类语言的多变性和模糊性，因此<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#semantic-search">语义搜索</a>非常适合。在 Elasticsearch 中，只要将查询字符串和<a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">嵌入模型</a>的 ID 传递到 query_vector_builder 对象中，就能在一个步骤中执行语义搜索。这将对查询进行矢量化，并执行 kNN<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">搜索</a>，以检索与查询含义最接近的前 k 个匹配项：</p>POST /&lt;target&gt;/_search
{
  "knn": {
    "field": "doc_part_vector",
    "k": 5,
    "num_candidates": 20,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "&lt;text-embedding-model-id&gt;",
        "model_text": "&lt;query_string&gt;"
      }
    }
  }
 }
<p><a href="https://www.elastic.co/guide/en/machine-learning/8.7/ml-nlp-text-emb-vector-search-example.html">端到端示例：如何部署文本嵌入模型并将其用于语义搜索。</a>Elasticsearch 使用 Okapi BM25 的 Lucene 实现（一种<strong>稀疏模型</strong>）对文本查询进行相关性排序，而<strong>密集模型</strong>则用于<strong>语义搜索</strong>。为了<strong>综合矢量</strong>匹配和文本查询匹配的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#_combine_approximate_knn_with_other_features"><strong>优势</strong></a> <strong>，</strong>可以执行<strong>混合检索</strong>：</p>POST &lt;target&gt;/_search
{
  "query": {
          "match": {
            "content": {
              "query": "&lt;query_string&gt;"
            }
        }
  },
  "knn": {
    "field": "doc_part_vector",
    "query_vector_builder": {
      "text_embedding": {
    "model_id": "&lt;text-embedding-model-id&gt;",
     "model_text": "&lt;query_string&gt;"
      }
    },
    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
  }
}
<h3>将稀疏模型和密集模型相结合，往往能获得最佳结果</h3><p>稀疏模型通常在简短查询和特定术语方面表现更佳，而密集模型则利用上下文和关联。如果您想进一步了解这些方法如何相互比较和互补，我们在此将 BM25 与两个经过专门检索训练的密集模型进行比较。</p><p>最相关的结果通常可能是给用户的第一个答案，分数是一个用来确定返回文档<strong>相关性的</strong>数字。</p><h2>优化聊天机器人</h2><p>为了帮助改善聊天机器人的用户体验、性能和可靠性，除了应用混合评分外，您还可以采用以下方法：<strong>情感分析：</strong>为了在对话过程中了解用户的评论和反应，您可以采用<a href="https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english">情感分析模型</a>：</p>POST _ml/trained_models/distilbert-base-uncased-finetuned-sst-2-english/deployment/_infer
{
  "docs": { "text_field": "That was not my question!"}
}


{
  "predicted_value": "NEGATIVE",
  "prediction_probability": 0.980080439016437
}
<p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data"><strong>GPT 的功能</strong></a> <strong>：</strong>作为增强整体体验的另一种方法，您可以将 Elasticsearch 的搜索相关性与 OpenAI 的 GPT 问题解答功能相结合，利用<a href="https://platform.openai.com/docs/guides/chat">聊天完成 API</a>返回给用户由模型生成的回复，并将这些前 k 个文档作为上下文。<em>提示："回答此问题&lt;user_question&gt; 仅使用此文档&lt;top_search_result&gt;"</em></p><p><strong>可观察性：</strong>确保任何聊天机器人的性能都至关重要，而监控是实现这一目标的重要组成部分。除了记录聊天机器人的互动日志，跟踪响应时间、延迟和其他相关的聊天机器人指标也很重要。<a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Elastic Observability</a>工具使您能够收集和分析这些信息。</p><h2>总结</h2><p>这篇博文介绍了什么是 NLP 和矢量搜索，并举例说明了聊天机器人如何通过考虑从文档的矢量表示中提取的数据来回答用户的询问。</p><p>如图所示，利用 NLP 和矢量搜索，聊天机器人能够执行结构化、目标数据之外的复杂任务。这包括使用多种数据源和格式作为上下文进行推荐和回答特定产品或业务相关查询，同时提供个性化的用户体验。</p><p>使用案例包括提供客户服务，协助客户进行咨询；帮助开发人员进行查询，提供逐步指导，提出建议，甚至自动执行任务。根据目标和现有数据，还可以利用其他模型和方法来取得更好的效果，改善整体用户体验。</p><p>下面是一些关于该主题的链接，可能会对您有所帮助：</p><ol><li><p><a href="https://www.elastic.co/blog/how-to-deploy-natural-language-processing-nlp-getting-started">如何部署自然语言处理 (NLP)：入门</a></p></li><li><p><a href="https://www.elastic.co/blog/overview-image-similarity-search-in-elastic">Elasticsearch 中的图像相似性搜索概述</a></p></li><li><p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT 和 Elasticsearch：OpenAI 与私人数据的结合</a></p></li><li><p><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">使用 OpenTelemetry 和 Elastic 监测 OpenAI API 和 GPT 模型</a></p></li><li><p><a href="https://www.elastic.co/blog/why-technology-leaders-need-vector-search">IT 领导者需要矢量搜索来改善搜索体验的 5 个原因</a></p></li></ol><p>通过在 Elasticsearch 中集成 NLP 和本地矢量搜索，您可以利用其速度、可扩展性和搜索功能来创建高效、有效的聊天机器人，从而能够处理大量结构化或非结构化数据。</p><p>准备好开始了吗？开始<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">免费试用弹性云</a>。</p><p><em>在本博文中，我们可能会使用或提及第三方生成式人工智能工具，这些工具由其各自所有者拥有和运营。Elastic对第三方工具没有任何控制权，我们对其内容、操作或使用不承担任何责任或义务，对您使用此类工具可能造成的任何损失或损害也不承担任何责任或义务。在使用人工智能工具处理个人、敏感或机密信息时，请务必谨慎。您提交的任何数据都可能用于人工智能培训或其他目的。我们不保证您所提供信息的安全性或保密性。在使用任何人工智能生成工具之前，您都应熟悉其隐私惯例和使用条款。</em></p><p><em>Elastic、Elasticsearch 和相关标记是 Elasticsearch N.V. 在美国和其他国家的商标、徽标或注册商标。所有其他公司和产品名称均为其各自所有者的商标、徽标或注册商标。</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c545fc80b6d79d6/6a170214839dfad776dcfd6f/d968e646240cd3ef7c79b5124d562a5f951d812b-1440x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用向量场进行文本相似性搜索]]></title>
    <description><![CDATA[这篇文章探讨了如何利用文本嵌入和 Elasticsearch 的新 dense_vector 类型来支持相似性搜索。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch 最初是一个<a href="https://www.elastic.co/about/history-of-elasticsearch">菜谱搜索引擎</a>，旨在提供快速、强大的全文搜索功能。有鉴于此，改进文本搜索一直是我们在矢量方面持续开展工作的重要动力。在 Elasticsearch 7.0 中，我们为高维向量引入了实验性字段类型，现在 7.3 版本支持在文档评分中使用这些向量。</p><p>本篇文章主要介绍一种名为文本相似性搜索的特殊技术。在这类搜索中，用户输入一个简短的自由文本查询，然后根据文档与查询的相似度进行排序。文本相似性可用于多种用途：</p><ul><li><p><strong>问题解答：</strong>给定一系列常见问题，找出与用户输入的问题相似的问题。</p></li><li><p><strong>文章搜索：</strong>在研究文章集中，返回标题与用户查询密切相关的文章。</p></li><li><p><strong>图像搜索：</strong>在有标题的图片数据集中，查找标题与用户描述相似的图片。</p></li></ul><p>相似性搜索的一种直接方法是根据文档与查询共享的单词数量进行排序。但是，即使一个文档与查询的共同点很少，它们也可能是相似的--一个更稳健的相似性概念应同时考虑其句法和<a href="https://en.wikipedia.org/wiki/Semantic_similarity">语义</a>内容。</p><p>自然语言处理（NLP）界开发了一种名为文本嵌入的技术，可将单词和句子编码为数字向量。这些向量表示旨在捕捉文本的语言内容，可用于评估查询和文档之间的相似性。</p><p>本文章将探讨如何利用文本嵌入和 Elasticsearch 的 dense_vector 类型来支持相似性搜索。我们将首先概述嵌入技术，然后使用 Elasticsearch 演示一个简单的相似性搜索原型。</p><strong>注：</strong>在搜索中使用文本嵌入是一个复杂且不断发展的领域。本博客并非对特定架构或实施方案的推荐。从这里开始了解如何利用<a href="https://www.elastic.co/what-is/vector-search">矢量搜索</a>的强大功能提升搜索体验。<h2>什么是文本嵌入？</h2><p>让我们仔细看看不同类型的文本嵌入，以及它们与传统搜索方法的比较。</p><h3>词语嵌入</h3><p><a href="https://en.wikipedia.org/wiki/Word_embedding">单词嵌入</a>模型将单词表示为一个密集的数字向量。这些向量旨在捕捉词语的语义属性--向量相近的词语在语义上应该是相似的。在一个好的嵌入中，向量空间中的方向与词义的不同方面相关联。例如，"加拿大" 的向量可能在一个方向上接近"法国" ，在另一个方向上接近"多伦多" 。</p><p>一段时间以来，NLP 和搜索界一直对单词的向量表示法很感兴趣。在过去的几年里，人们对单词嵌入的兴趣再次升温，许多传统的任务都在使用神经网络进行重新研究。一些成功的词嵌入算法被开发出来，包括<a href="https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf">word2vec</a>和<a href="https://nlp.stanford.edu/pubs/glove.pdf">GloVe</a>。这些方法利用大量文本集合，并检查每个单词出现的上下文，以确定其向量表示：</p><ul><li><p>word2vec Skip-gram 模型训练一个神经网络，以预测句子中某个单词周围的上下文单词。网络的内部权重给出了单词嵌入。</p></li><li><p>在 GloVe 中，单词的相似度取决于它们与其他上下文单词出现的频率。该算法根据词的共现计数训练一个简单的线性模型。</p></li></ul><p>许多研究小组分发的模型都是在维基百科或 Common Crawl 等大型文本语料库上预先训练过的，方便下载并用于下游任务。虽然有时会直接使用预训练版本，但调整模型以适应特定的目标数据集和任务可能会有所帮助。这通常是通过在预训练模型上运行 "微调 "步骤来实现的。</p><p>词嵌入已被证明是相当稳健和有效的，在机器翻译和情感分类等 NLP 任务中，使用词嵌入来代替单个词块已成为一种普遍做法。</p><h3>句子嵌入</h3><p>最近，研究人员开始关注不仅能表示单词，还能表示较长文本部分的嵌入技术。目前的大多数方法都基于复杂的神经网络架构，有时还会在训练过程中加入标记数据，以帮助捕捉语义信息。</p><p>训练完成后，这些模型就能处理一个句子，为上下文中的每个单词生成一个向量，并为整个句子生成一个向量。与词嵌入类似，许多模型都有预训练版本，用户可以跳过昂贵的训练过程。虽然训练过程可能非常耗费资源，但调用模型却要轻便得多--句子嵌入模型的速度通常很快，足以作为实时应用的一部分。</p><p>一些常见的句子嵌入技术包括<a href="https://arxiv.org/abs/1705.02364">InferSent</a>、<a href="https://arxiv.org/abs/1803.11175">Universal Sentence Encoder</a>、<a href="https://arxiv.org/abs/1802.05365">ELMo</a> 和<a href="https://arxiv.org/abs/1810.04805">BERT</a>。改进单词和句子嵌入是一个活跃的研究领域，很可能会引入更多强大的模型。</p><h3>与传统搜索方法的比较</h3><p>在传统的信息检索中，将文本表示为数字向量的常见方法是为词汇表中的每个单词分配一个维度。然后，根据词汇中每个术语出现的次数来确定文本的向量。这种表示文本的方式通常被称为"bag of words，" ，因为我们只计算单词出现次数，而不考虑句子结构。</p><p>文本嵌入在某些重要方面有别于传统的向量表示法：</p><ul><li><p>编码矢量密度高，维数相对较低，通常在 100 到 1000 维之间。相比之下，词袋向量比较稀疏，可以包含 50,000 多个维度。作为语义建模的一部分，嵌入算法将文本编码到低维空间中。理想情况下，同义词和短语最终会在新的向量空间中得到相似的表示。</p></li><li><p>在确定向量表示时，句子嵌入可以考虑单词的顺序。例如，"tune in" 与"in tune" 可能会被映射为截然不同的向量。</p></li><li><p>实际上，句子嵌入通常不能很好地概括大段文本。它们通常不用于表示长度超过一小段的文本。</p></li></ul><h2>使用嵌入式进行相似性搜索</h2><p>假设我们有一大堆问题和答案。用户可以提出一个问题，我们希望在问题集中检索出最相似的问题，以帮助他们找到答案。</p><p>我们可以使用文本嵌入来检索类似的问题：</p><ul><li><p>在索引编制过程中，每个问题都会通过句子嵌入模型生成一个数字向量。</p></li><li><p>当用户输入一个查询时，它会通过相同的句子嵌入模型产生一个向量。为了对回复进行排序，我们计算每个问题与查询向量之间的向量相似度。在比较嵌入向量时，通常使用<a href="https://en.wikipedia.org/wiki/Cosine_similarity">余弦相似度</a>。</p></li></ul><p><a href="https://github.com/jtibshirani/text-embeddings">该版本库</a>提供了一个简单的示例，说明如何在 Elasticsearch 中实现这一功能。主脚本从<a href="https://github.com/elastic/rally-tracks/tree/master/so">StackOverflow 数据集中</a>索引约 20,000 个问题，然后允许用户针对数据集输入自由文本查询。</p><p>我们很快就会详细介绍脚本的各个部分，但首先让我们看看一些示例结果。在许多情况下，即使查询和索引问题之间没有很强的词语重叠，该方法也能捕捉到相似性：</p><ul><li><p>"压缩文件" 返回"压缩/解压缩文件夹&amp; 文件"</p></li><li><p>"确定某物是 IP" 返回"如何判断字符串是 IP 还是主机名"</p></li><li><p>"将字节转换为双倍" 返回"在 Python 中将字节转换为浮点数"</p></li></ul><h3>实施细节</h3><p><a href="https://github.com/jtibshirani/text-embeddings/blob/blog/src/main.py">脚本</a>首先在 TensorFlow 中下载并创建嵌入模型。我们选择了谷歌的通用句子编码器，但也可以使用许多其他嵌入方法。脚本按原样使用嵌入模型，无需额外的训练或微调。</p><p>接下来，我们创建 Elasticsearch 索引，其中包括问题标题、标签以及编码为向量的问题标题的映射：</p>"mappings": {
"properties": {
"title": {
"type": "text"
},
"title_vector": {
"type": "dense_vector",
"dims": 512
}
"tags": {
"type": "keyword"
},
...
}
}
<p>在 dense_vector 的映射中，我们需要指定向量的维数。索引 title_vector 字段时，Elasticsearch 将检查该字段的维数是否与映射中指定的相同。</p><p>为编制文档索引，我们通过嵌入模型运行问题标题，以获得一个数字数组。该数组会添加到文档的 title_vector 字段中。</p><p>当用户输入查询时，文本会首先通过相同的嵌入模型运行，并存储在参数 query_vector 中。从 7.3 开始，Elasticsearch 在其本地脚本语言中提供了<a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-script-score-query.html#vector-functions">余弦相似度函数</a>。因此，为了根据问题与用户查询的相似度对问题进行排序，我们使用了 script_score 查询：</p>{
"script_score": {
"query": {"match_all": {}},
"script": {
"source": "cosineSimilarity(params.query_vector, 'title_vector') + 1.0",
"params": {"query_vector": query_vector}
}
}
}
<p>我们确保将查询向量作为脚本参数传递，以<a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/modules-scripting-using.html#prefer-params"> 避免</a> 在每次新查询时 重新编译 script()。由于 Elasticsearch 不允许负分，因此有必要在余弦相似度中加一个负分。</p><p><strong>| 注：</strong>这篇博文最初使用了 Elasticsearch 7.3 中的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/query-dsl-script-score-query.html#vector-functions">另一种矢量函数语法</a>，但在 7.6 中已被弃用。|</p><h3>重要限制</h3><p>script_score 查询旨在封装限制性查询，并修改其返回文档的分数。不过，我们提供了一个 match_all 查询，这意味着脚本将在索引中的所有文档上运行。这是目前 Elasticsearch 中向量相似性的一个限制--向量可用于为文档评分，但不能用于初始检索步骤。基于向量相似性的支持检索是<a href="https://github.com/elastic/elasticsearch/issues/42326">当前工作</a>的一个重要领域。</p><p>为了避免扫描所有文件并保持快速性能，可以用选择性更强的查询来代替 match_all 查询。用于检索的正确查询可能取决于具体的使用情况。</p><p>虽然我们在上面看到了一些令人鼓舞的例子，但重要的是要注意，结果也可能是嘈杂和不直观的。例如，"压缩文件" 也会给"部分 .csproj 分配高分文件" 和"如何避免使用 .pyc" 。而当方法返回令人惊讶的结果时，如何调试问题并不总是很清楚--每个矢量成分的含义往往是不透明的，与可解释的概念并不对应。使用基于词语重叠度的传统评分技术，通常更容易回答"为何该文档排名靠前？"</p><p>如前所述，该原型旨在举例说明如何将嵌入模型与矢量场结合使用，而不是一个可投入生产的解决方案。在制定新的搜索策略时，关键是要测试该方法在自己的数据中的表现，确保与匹配查询等强大的基线进行比较。在取得可靠结果之前，可能有必要对策略进行重大修改，包括针对目标数据集微调嵌入模型，或尝试不同的嵌入方法，如单词级查询扩展。</p><h2>结论</h2><p>嵌入技术是捕捉文本语言内容的有力方法。通过索引嵌入和基于向量距离的评分，我们可以使用超越词级重叠的相似性概念来比较文档。</p><p>我们期待着引入更多基于向量字段类型的功能。使用矢量进行搜索是一个细致入微、不断发展的领域--我们一如既往地希望在<a href="https://github.com/elastic/elasticsearch">Github</a>和<a href="https://discuss.elastic.co/">讨论论坛</a>上听到您的使用案例和经验！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Julie Tibshirani]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91384bd99b05cd28/6a17e7e01d1b835cc593e467/c633ed737add7d22a7d65b3ca5c56480ef3d8b2c-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Oct 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>