前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何为kNN 搜索选择最佳的 k 和 num_candidates?

如何为kNN 搜索选择最佳的 k 和 num_candidates?

原创
作者头像
点火三周
修改2024-05-28 15:18:53
1950
修改2024-05-28 15:18:53
举报
文章被收录于专栏:Elastic Stack专栏Elastic Stack专栏

如何选择最佳的 k 和 num_candidates 用于 kNN 搜索

如何选择最佳的 knum_candidates

在当前生成式 AI/ML 领域,向量搜索成为了一种变革性的技术。它使我们能够基于语义意义而不仅仅是精确的关键词匹配来查找相似的项目。

Elasticsearch 的 k-最近邻(kNN)算法是用于分类和回归任务的基础 ML 技术。随着向量搜索功能的引入,它在 Elasticsearch 生态系统中占据了重要位置。在 Elasticsearch 8.5 中引入的基于 kNN 的向量搜索使用户能够在密集向量字段上执行高速相似性搜索。

用户可以利用 kNN 算法,通过指定的距离度量(如欧氏距离或余弦相似度),找到索引中与给定向量“最接近”的文档。此功能标志着一个重要的进步,特别适用于需要语义搜索、推荐和其他用例(如异常检测)的应用程序。

引入密集向量字段和 k-最近邻(kNN)搜索功能,开辟了实现超越传统文本搜索的复杂搜索功能的新天地。

本文深入探讨了选择 knum_candidates 参数的最佳值的策略,并通过使用 Kibana 的实际示例进行了说明。

kNN 搜索查询

Elasticsearch 提供了一种用于最近邻搜索的 kNN 搜索选项,如下所示:

代码语言:javascript
复制
POST movies/_search
{
  "knn": {
    "field": "title_vector.predicted_value",
    "query_vector_builder": {
      "text_embedding": {
        "model_id": ".multilingual-e5-small",
        "model_text": "Good Ugly"
      }
    },
    "k": 3,
    "num_candidates": 100
  },
  "_source": [
    "id",
    "title"
  ]
}

如片段所示,knn 查询使用向量搜索获取查询(电影标题为“Good Ugly”)的相关结果。搜索在多维空间中进行,生成与给定查询向量最接近的向量。

从上述查询中,可以注意到两个属性:num_candidates 是考虑的初始候选池,k 是最近邻的数量。

kNN 关键参数 - k 和 num_candidates

要有效利用 kNN 功能,需要深入了解两个关键参数:k - 要检索的全局最近邻的数量,以及 num_candidates - 搜索期间每个分片考虑的候选邻居数量。

选择 knum_candidates 的最佳值需要在精度、召回率和性能之间取得平衡。这些参数在有效处理机器学习应用中常见的高维向量空间时起着至关重要的作用。

k 的最佳值很大程度上取决于具体的用例。例如,如果您正在构建推荐系统,较小的 k(例如 10-20)可能足以提供相关的推荐。相反,对于需要聚类或异常检测功能的用例,您可能需要较大的 k

需要注意的是,较高的 k 值可能显著增加计算和内存使用,尤其是在大数据集的情况下。重要的是测试不同的 k 值,以在结果相关性和系统资源使用之间找到平衡。

k:揭示最接近的邻居

我们可以根据需求选择 k 值。有时,设置较低的 k 值可以更或多或少地得到您想要的结果,只是少数结果可能不会出现在最终输出中。然而,设置较高的 k 值可能会扩大搜索结果的数量,但可能会有时收到多样化的结果。

设置较低的 K

较低的 K 设置优先考虑极端精确度,这意味着我们将获得最类似于查询向量的少量书籍。这确保了与我们特定兴趣高度相关的结果。如果您正在寻找具有特定主题或写作风格的书籍,这可能是理想的选择。

设置较高的 K

使用较大的 K 值,我们将获得更广泛的探索结果集。请注意,结果可能不如您的确切查询那样紧密聚焦。然而,您将遇到更多潜在有趣的书籍。此方法对于多样化您的阅读清单并发现意外的珍品可能非常有价值。

当我们说 k 的较高或较低值时,我们指的是实际值取决于多个因素,例如数据集的大小、可用计算能力等。在某些情况下,k=10 可能很大,但在其他情况下可能很小。因此,请注意此参数预计运行的环境。

num_candidates 属性:幕后工作

虽然 k 决定了您看到的最终书籍数量,但 num_candidates 在幕后起着关键作用。它本质上定义了每个分片的搜索空间——分片中初始的候选书籍池,从中识别出最相关的 K 个邻居。当我们发出查询时,我们期望提示 Elasticsearch 在每个分片的“x”数量的候选者中运行查询。

例如,假设我们的书籍索引包含 5000 本书,均匀分布在五个主要分片中(即每个分片约 1000 本书)。当我们执行搜索时,显然选择每个分片的所有 1000 个文档既不可行也不正确。相反,我们将从 1000 个文档中选择最多 25 个文档(即我们的 num_candidates)。这相当于 125 个文档作为我们的总搜索空间(5 个分片乘以每个分片 25 个文档)。

我们将让 kNN 查询知道从每个分片中选择 25 个文档,这个数字是 num_candidates 参数。当 kNN 搜索执行时,“协调器”节点将请求查询发送到所有相关的分片。每个分片的 num_candidates 文档将构成搜索空间,并从该空间中提取前 k 个文档。假设 k 是 3,前 3 个文档从每个分片的 25 个候选文档中选出并返回给协调器节点。即,协调器节点将从所有相关节点接收 15 个文档。这些顶级 15 个文档随后进行排名,以提取全局前 3(k==3)个文档。

这个过程如下图所示:

num_candidates
num_candidates

设置较低的 num_candidates

这种方法可能会限制搜索空间,可能会错过初始探索集之外的一些相关书籍。可以将其视为对图书馆书架的一小部分进行调查。

设置较高的 num_candidates

较高的 num_candidates 值增加了在我们选择的 K 内找到真正最近邻居的可能性。它扩展了搜索空间——即考虑更多的候选者——因此导致搜索时间略有增加。因此,较高的值通常会提高准确性(因为遗漏相关向量的可能性减少),但代价是性能。

平衡精度和性能

knum_candidates 的最佳值取决于几个因素和具体需求。如果我们优先考虑极高的精度和高度相关的结果,那么较低的 k 和中等的 num_candidates 可能是理想的。相反,如果探索和发现意外书籍是您的目标,较高的 K 和较大的 num_candidates 可能更合适。

虽然没有明确的“较低”或“较高”数字来定义 num_candidates,您需要根据数据集、计算能力和预期精度来决定此数字。

实验是关键

通过实验不同的 K 和 num_candidates 组合,监控搜索结果和性能,您可以微调搜索以在精度、探索和速度之间实现完美平衡。请记住,没有一刀切的解决方案——最佳方法取决于您的独特目标和数据特征。

使用 kNN 进行电影推荐

让我们以电影为例,创建一个手动的“简单”框架来理解 k 和 num_candidates 属性在搜索电影时的影响。

手动框架

让我们了解如何开发一个自制框架,以调整 kNN 搜索中的 knum_candidates 属性。

框架的机制如下:

  • 创建一个具有多个 dense_vector 字段的电影索引以保存我们的向量化数据。
  • 创建一个嵌入管道,使每部电影的标题和概要字段都通过 multilingual-e5-small 模型嵌入以存储向量。
  • 执行索引操作,通过上述嵌入管道进行。相应的字段将被向量化。
  • 使用 kNN 功能创建搜索查询。
  • 根据需要调整 knum_candidates 选项。

让我们深入了解一下。

创建推理管道

我们需要通过 Kibana 索引数据——虽然不是理想的方法,但它对于理解手动框架足够了。然而,每部被索引的电影必须对标题和概要字段进行向量化,以便对我们的数据进行语义搜索。我们可以通过优雅地创建一个推理管道处理器并将其附加到我们的批量索引操作中来实现这一点。

让我们创建一个推理管道:

代码语言:javascript
复制
PUT _ingest/pipeline/movie_embedding_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": ".multilingual-e5-small",
        "target_field": "title_vector",
        "field_map": { "title": "text_field" }
      }
    },
    {
      "inference": {
        "model_id": ".multilingual-e5-small",
        "target_field": "synopsis_vector",
        "field_map": { "synopsis": "text_field" }
      }
    }
  ]
}

上述的推理管道 movie_embedding_pipeline 创建了文本嵌入字段用于标题和概要字段。它使用内置的 multilingual-e5-small 模型创建文本嵌入。

创建索引映射

我们需要创建一个包含几个 dense_vector 字段的映射。以下代码片段完成了这项工作:

代码语言:javascript
复制
PUT movies
{
  "mappings": { 
    "properties": { 
      "title": {
        "type": "text",
        "fields": { 
          "original": {
            "type": "keyword"
          }
        }
      },
      "title_vector.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true
      },
      "synopsis": {
        "type": "text"
      },
      "synopsis_vector.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true
      },
      "actors": {
        "type": "text"
      },
      "director": {
        "type": "text"
      },
      "rating": {
        "type": "half_float"
      },
      "release_date": {
        "type": "date",
        "format": "dd-MM-yyyy"
      },
      "certificate": {
        "type": "keyword"
      },
      "genre": {
        "type": "text"
      }
    }
  }
}

一旦执行上述命令,我们就有了一个包含适当密集向量字段的新的电影索引,包括 title_vector.predicted_valuesynopsis_vector.predicted_value 字段,用于存储相应的向量。

index 映射参数在 8.10 之前默认设置为 false。在 8.11 版本中,该参数默认设置为 true,因此无需指定它。

下一步是数据摄取。

索引电影

我们可以使用 _bulk 操作来索引一组电影——我正在重用我的《Elasticsearch in Action》第二版书籍创建的数据集——可以在 这里 找到:

为完整性考虑,这里提供了使用 _bulk 操作进行摄取的片段:

代码语言:javascript
复制
POST _bulk?pipeline=movie_embedding_pipeline
{"index":{"_index":"movies","_id":"1"}}
{"title": "The Shawshank Redemption","synopsis": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.","actors": ["Tim Robbins", "Morgan Freeman", "Bob Gunton", "William Sadler"] ,"director":" Frank Darabont ","rating":"9.3","certificate":"R","genre": "Drama "}
{"index":{"_index":"movies","_id":"2"}}
{"title": "The Godfather","synopsis": "An organized crime dynasty's aging patriarch transfers control of his clandestine empire to his reluctant son.","actors": ["Marlon Brando", "Al Pacino", "James Caan", "Diane Keaton"] ,"director":" Francis Ford Coppola ","rating":"9.2","certificate":"R","genre": ["Crime", "Drama"] }
{"index":{"_index":"movies","_id":"3"}}
{"title": "The Dark Knight","synopsis": "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, Batman must accept one of the greatest psychological and physical tests of his ability to fight injustice.","actors": ["Christian Bale", "Heath Ledger", "Aaron Eckhart", "Michael Caine"] ,"director":" Christopher Nolan ","rating":"9.0","certificate":"PG-13","genre": ["Action", "Crime", "Drama"] }

确保替换脚本以使用完整的数据集。

请注意,_bulk 操作后缀有管道(?pipeline=movie_embedding_pipeline),因此每部电影都通过此管道,从而生成向量。

当我们用向量嵌入初始化我们的 movies 索引后,就可以开始实验调优 knum_candidates 属性了。

kNN 搜索

由于我们在电影索引中有向量数据,我们将使用近似 k-最近邻(kNN)搜索。例如,要推荐具有父子情感的类似电影(“Father and son” 作为搜索查询),我们将使用 kNN 搜索找到最近邻:

代码语言:javascript
复制
POST movies/_search
{
  "_source": ["title"], 
  "knn": {
    "field": "title_vector.predicted_value",
    "query_vector_builder": {
      "text_embedding": {
        "model_id": ".multilingual-e5-small",
        "model_text": "Father and son"
      }
    },
    "k": 5,
    "num_candidates": 10
  }
}

在上述示例中,查询利用顶级 kNN 搜索选项参数,直接专注于查找最接近给定查询向量的文档。与在顶级查询中使用 knn 查询不同的关键区别在于,前者情况下,查询向量将由机器学习模型实时生成。

这段加粗部分在技术上不正确。实时向量生成仅通过使用 query_vector_builder 而不是 query_vector 实现,后者需要传入预先计算的向量,但无论是在顶级 knn 搜索选项中还是在 knn 搜索查询中都提供了此功能。

脚本根据我们的搜索查询(使用 query_vector_builder 块构建)获取相关结果。我们使用随机的 knum_candidates 值,分别设置为 5 和 10。

kNN 查询属性

上述查询有一组属性组成 kNN 查询。以下信息关于这些属性将帮助您更好地理解查询:

field 属性指定索引中包含文档向量表示的字段。在这种情况下,title_vector.predicted_value 是存储文档向量的字段。

query_vector_builder 属性是此示例显著偏离简单 kNN 查询的地方。与提供静态查询向量不同,此配置使用文本嵌入模型动态生成查询向量。该模型将一段文本(示例中的“Father and son”)转换为表示其语义含义的向量。

text_embedding 表明将使用文本嵌入模型生成查询向量。

model_id 是要使用的预训练机器学习模型的标识符,在此示例中为 .multilingual-e5-small 模型。

model_text 属性是将由指定模型转换为向量的文本输入。在此处,它是单词“Father and son”,模型将对其进行语义解释以找到类似的电影标题。

k 是要检索的最近邻的数量,即确定根据查询向量返回的最相似文档的数量。

num_candidates 属性是每个分片中作为潜在匹配的更广泛候选文档集,以确保最终结果尽可能准确。

kNN 结果

执行 kNN 基本搜索脚本应获取前 5 个结果——为了简洁起见,我只提供电影列表。

代码语言:javascript
复制
# 结果应获取一组 5 部电影,如下列表所示:

"title": "The Godfather"
"title": "The Godfather: Part II"
"title": "Pulp Fiction"
"title": "12 Angry Men"
"title": "Life Is Beautiful"

正如您所期望的那样,《教父》(两部)是父子情感的一部分,而《低俗小说》不应该是结果的一部分(尽管查询要求“情感联系”——《低俗小说》完全是关于几个人之间的联系)。

现在我们有了一个基本框架,可以适当调整参数并推导出近似设置。在调整设置之前,让我们了解 k 属性的最佳设置。

选择最佳 K 值

在 k-最近邻(kNN)算法中选择最佳的 k 值对于以最小错误率获得数据集上的最佳性能至关重要。然而,没有一刀切的答案,因为最佳的 k 值可能取决于数据的具体情况以及我们试图预测的内容。

要选择最佳的 `k

` 值,必须创建一个包含多种策略和考虑因素的自定义框架。

  • k = 1:尝试首先运行 k=1 的搜索查询。确保更改每次运行的输入查询。查询应返回不可靠的结果,因为更改输入查询会随着时间推移返回不正确的结果。这导致了一个称为“过拟合”的机器学习模式,其中模型过于依赖于直接邻域中的特定数据点。因此,模型在泛化到未见示例时会遇到困难。
  • k = 5:运行 k=5 的搜索查询并检查预测。查询的稳定性应理想地改善,您应获得足够可靠的预测。

您可以逐步增加 k 值,例如以 5 或 x 的步长增加,直到找到结果准确性与错误数量较少的甜蜜点。

您还可以选择极值 k,例如选择较高的 k=50 值,如下所述:

  • k = 50:将 k 值增加到 50 并检查搜索结果。错误结果很可能会比实际/预期预测更显著。这时您会知道自己达到了 k 值的硬边界。较大的 k 值导致机器学习特征称为“欠拟合”——在 KNN 中,当模型过于简单且未能捕捉数据中的底层模式时会发生欠拟合。

选择最佳 num_candidates

num_candidates 参数在找到搜索准确性和性能之间的最佳平衡方面起着至关重要的作用。与 k 直接影响返回的搜索结果数量不同,num_candidates 决定了选择最终 k 最近邻的初始候选集的大小。如前所述,num_candidates 参数定义了在每个分片上选择多少最近邻居。

调整此参数对于确保搜索过程既高效又能产生高质量结果至关重要。

  • num_candidates = 小值(例如 10):首先使用较低值(“低值探索”)开始 num_candidates。目的是在此阶段建立性能基线。由于候选集只是少数候选者,搜索将很快,但可能会错过相关结果,从而导致准确性差。这种情况有助于了解搜索质量明显下降的最低阈值。
  • num_candidates = 中等值(例如 25?):将 num_candidates 增加到中等值(“中值探索”)并观察搜索质量和执行时间的变化。适度数量的候选者可能通过考虑更广泛的潜在邻居池来提高结果的准确性。随着候选者数量的增加,将会有资源成本,需要密切监控性能指标。然而,随着搜索准确性的提高,计算成本的增加可能是合理的。
  • num_candidates = 步进增加:继续逐步增加 num_candidates(增量增加探索),可能以 20 或 50 的步长增加(取决于数据集的大小)。评估每次增量时额外的候选者是否有助于显著提高搜索准确性。将会有一个收益递减点,此时进一步增加 num_candidates 几乎不会对结果质量产生影响。与此同时,您可能已经注意到,这会加剧我们的资源负担并显著影响性能。
  • num_candidates = 高值(例如 1000, 5000):尝试使用较高的 num_candidates 值,以了解选择较高设置的上限影响。存在搜索准确性稳定或因包含不相关候选者而略有下降的可能性。这可能导致最终 k 结果的精度降低。正如我们所讨论的,高值的 num_candidates 始终会增加计算负荷,从而导致查询时间更长和潜在的资源限制。

寻找最佳平衡

现在我们知道如何调整 knum_candidates 属性,并了解不同设置如何改变搜索准确性结果。

目标是找到一个甜蜜点,使搜索结果始终准确且处理大型候选集的性能开销较低。

当然,最佳值将根据数据的具体情况、向量的维度以及其他性能要求而有所不同。

总结

最佳 K 值在于通过实验和试验找到甜蜜点。您希望使用足够的邻居(较低的 K 值)来捕获基本模式,但不要太多(较高的 K 值)以至于模型受到噪声或不相关细节的过度影响。您还希望调整候选者,以确保在给定的 k 值下搜索结果的准确性。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何选择最佳的 k 和 num_candidates 用于 kNN 搜索
    • 如何选择最佳的 k 和 num_candidates?
      • kNN 搜索查询
        • kNN 关键参数 - k 和 num_candidates
          • k:揭示最接近的邻居
            • 设置较低的 K
            • 设置较高的 K
          • num_candidates 属性:幕后工作
            • 设置较低的 num_candidates
            • 设置较高的 num_candidates
          • 平衡精度和性能
            • 实验是关键
              • 使用 kNN 进行电影推荐
                • 手动框架
                  • 创建推理管道
                    • 创建索引映射
                      • 索引电影
                        • kNN 搜索
                          • kNN 查询属性
                            • kNN 结果
                              • 选择最佳 K 值
                                • 选择最佳 num_candidates 值
                                  • 寻找最佳平衡
                                    • 总结
                                    相关产品与服务
                                    Elasticsearch Service
                                    腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
                                    领券
                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档