Elastic 中的模型管理与向量考虑

1,482 阅读31分钟

在本章中,我们将提供 Hugging Face 生态系统、Elasticsearch 的 Eland Python 库以及在 Elasticsearch 中使用嵌入模型的实用策略概览。我们将首先探索 Hugging Face 平台,讨论如何开始使用、选择合适的模型,并利用其庞大的数据集合集。我们还将深入了解 Hugging Face 的 Spaces 功能及其有效使用方式。 接下来,我们将介绍由 Elastic 创建的 Eland Python 库,并通过一个 Jupyter Notebook 示例展示其使用方法。 本章将涵盖的主题如下:

  • 由 Elastic 创建的 Eland Python 库
  • 索引映射
  • 机器学习(ML)节点
  • 将 ML 模型集成到 Elasticsearch 中
  • 规划集群容量的关键方面
  • 可帮助优化 Elasticsearch 集群性能和资源利用的存储效率策略

技术要求

为了实现本章涵盖的概念,您将需要以下内容:

Hugging Face

如引言中简要讨论的,Hugging Face 的主要目标是民主化对最先进自然语言处理(NLP)技术的访问,并促进其在各种行业和应用中的采用。通过提供一个庞大的预训练模型库(编写本文时超过120,000个模型),用户友好的API,以及一个用于模型共享和微调的协作环境,Hugging Face 使开发人员和研究人员能够轻松创建先进的语言处理应用程序。

在这一基础上,Hugging Face 不仅仅提供一个广泛的库;它还确保了顺畅的访问和有效的应用管理。为此,一个突出的功能是模型中心(Model Hub)。

模型中心

Hugging Face 提供专注于研究人员和企业需求的资源和服务。这些服务包括模型中心,它是一个集中存储预训练模型的库,包括允许轻松访问这些模型的推理API。

模型可以按名称或任务进行搜索,并可以通过任务类型、架构等轻松过滤,以找到适合您用例的模型。在图3.1中,您可以看到我们搜索术语“bert”时可用的模型列表:

image.png

选择一个模型将为您提供有关该模型的信息。在顶部,您将看到任务类型、可用的各种架构、使用的库、语言、许可证等信息。在图3.2中,我们看到了关于 bert-base-uncased 模型的信息: image.png

大多数模型,尤其是更受欢迎的模型,都有一个模型卡片(Model Card)。模型卡片包含了开发者提供的信息,如模型的训练方式、详细描述、模型变体、相关模型、预期用途和限制、运行的代码示例、已知偏见,以及关于其训练方式的信息。在图3.3中,我们看到了关于BERT模型训练方式的信息:

image.png

在图3.4中,我们可以看到上个月有多少次下载,并且经常是最有用的,一个指向托管推理API的实时界面。托管推理API允许通过编程方式访问在 Hugging Face 共享基础设施上运行的模型,该模型将接受输入,通过模型处理,并返回输出。这个API界面将允许您通过提供输入并查看输出来测试模型。

image.png

讨论了模型中心及其各种功能的相关性后,同样重要的是阐明支持这些模型的底层数据。这就引入了 Hugging Face 数据集,它是自然语言处理(NLP)和机器学习(ML)数据工具领域的基石。

数据集

Hugging Face 数据集是一个旨在简化下载、预处理和使用各种 NLP 和 ML 任务数据集的过程的库。该库提供访问大量数据集,涵盖广泛的主题和语言,适用于多种用例,如情感分析、翻译和摘要。这些数据集来自研究论文、网络抓取或用户贡献,并以标准化格式提供给社区。Hugging Face 数据集的一个关键特性是其与 Transformers 库的直接集成,使用户能够轻松地将这些数据集与 NLP 模型结合使用,进行训练、评估和部署。

使用 Hugging Face 数据集很简单。要开始使用,您需要使用 pip 安装库:pip install datasets。安装库后,您可以通过简单地导入库并使用 load_dataset() 函数来加载数据集。例如,如果您想加载 IMDb 电影评论数据集进行情感分析,可以使用以下代码:

from datasets import load_dataset
imdb_dataset = load_dataset("imdb")

Hugging Face 数据集还提供了用于数据集预处理和操作的有用功能,包括过滤、映射和洗牌。这些功能使用户能够轻松准备数据以用于其模型。在这个示例中,我们将使用 Transformers 库中的预训练分词器对 IMDb 数据集进行分词:

from datasets import load_dataset
from transformers import AutoTokenizer
# 加载 IMDB 数据集的一小部分(100个样本)
imdb_dataset = load_dataset("imdb", split="train[:100]")
# 初始化分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# 对 IMDB 数据集进行分词,包括截断和填充
tokenized_imdb_dataset = imdb_dataset.map(
    lambda x: tokenizer(x["text"], truncation=True, padding="max_length")
)
print(tokenized_imdb_dataset)
# 这将打印出:
Dataset({
    features: ['text', 'label', 'input_ids', 'token_type_ids', 'attention_mask'],
    num_rows: 100
})
# 获取第一行的令牌
first_row_tokens = tokenized_imdb_dataset[0]["input_ids"]
# 打印前10个令牌及其对应的词语
for token in first_row_tokens[:10]:
    print(f"Token: {token}, Word: {tokenizer.decode([token])}")

这将打印出以下内容:

Token: 101, Word: [CLS] 
Token: 1045, Word: i 
Token: 12524, Word: rented 
Token: 1045, Word: i 
Token: 2572, Word: am 
Token: 8025, Word: curious 
Token: 1011, Word: - 
Token: 3756, Word: yellow 
Token: 2013, Word: from 
Token: 2026, Word: my

在输出中,我们可以看到当我们运行 imdb_dataset['text'][0] 时,我们从 IMDb 输入的第一行获取了前10个令牌及其对应的词语。

空间

Hugging Face Spaces 是一个旨在促进 ML 模型的部署、分享和协作的平台,特别是那些专注于 NLP 的模型。通过 Spaces,用户可以轻松地将他们的模型部署为交互式Web应用程序,由 Gradio 或 Streamlit 提供支持,无需额外的基础设施或复杂配置。Spaces 允许用户展示他们的工作,收集反馈,并与社区中的其他人合作,这对于 ML 和 NLP 领域的经验丰富的从业者和初学者都是无价的工具。

Hugging Face Spaces 的一个关键特性是其与 Hugging Face 生态系统的无缝集成,包括 Transformers 库和 Hugging Face Hub。这种集成允许用户快速部署他们的预训练或微调模型作为交互式应用程序,使其他人可以测试模型并了解其功能。要开始使用 Spaces,用户需要在 Hugging Face 网站上创建一个账户并遵循部署新空间的指导方针。过程通常涉及编写一个 Python 脚本,该脚本利用 Gradio 或 Streamlit 框架,从 Hugging Face Hub 导入所需的模型,并定义与模型交互的用户界面组件。

让我们来看看将允许您设置一个简单的 Gradio 界面的代码:

import gradio as gr
from transformers import pipeline
sentiment_pipeline = pipeline("sentiment-analysis")
def sentiment_analysis(text):
    result = sentiment_pipeline(text)
    return result[0]["label"]
iface = gr.Interface(fn=sentiment_analysis, inputs="text", outputs="text")
iface.launch()

在这个示例中,使用 Hugging Face Transformers 库创建了一个情感分析管道,并定义了一个 Gradio 界面,以接受文本输入并显示情感标签作为输出。一旦代码上传到 Hugging Face Space,用户可以通过网络浏览器与应用程序互动,使用他们自己的文本输入测试情感分析模型。Spaces 提供了一种简单而强大的方式来展示 ML 模型并促进社区内的协作。

虽然 Hugging Face Spaces 为 NLP 应用提供了一个卓越的平台,但 Hugging Face 并不是我们唯一可以使用的资源。让我们通过将焦点转移到 Eland 及其在 Elasticsearch 和传统数据处理框架(如 pandas)之间架起桥梁的角色,来多样化我们的工具包。

Eland

Eland 是由 Elastic 开发的一个 Python 库,允许用户无缝地与 Elasticsearch 进行数据操作和分析接口。该库建立在官方 Elasticsearch Python 客户端之上,并将 pandas API 扩展到 Elasticsearch。这使用户能够使用熟悉的 pandas 式语法和约定与 Elasticsearch 数据进行交互,从而更容易将 Elasticsearch 与现有的数据分析工作流程和工具集成。

Eland 特别适用于处理无法在内存中容纳且需要分布式处理的大型数据集。Elasticsearch 可以通过在集群中跨多个节点分布数据来水平扩展。这允许用户高效地处理远大于笔记本电脑可能的数据集。让我们来看看 Eland 的一些功能:

Eland 的一个关键用例是查询存储在 Elasticsearch 中的数据。用户可以编写类似于 pandas 语法的 Python 代码来过滤、排序和聚合数据,而不是编写原始的 Elasticsearch 查询。例如,您可以按以下方式从 Elasticsearch 索引中提取数据:

import eland as ed
df = ed.DataFrame("http://localhost:9200", "my_index")
filtered_df = df[df['some_field'] > 100]

除了数据查询和转换外,Eland 还支持聚合,使用户能够生成摘要统计或对 Elasticsearch 数据进行复杂的聚合。这对于了解大型数据集的分布和特征很有用。这是计算特定字段平均值的一个示例:

average_value = df['some_field'].mean()

Eland 还可用于数据可视化,因为它与流行的 Python 可视化库(如 Matplotlib 和 Seaborn)无缝集成。一旦您使用 Eland 获取和操作数据,就可以轻松将结果转换为 pandas DataFrame 并创建可视化。这是一个示例:

import seaborn as sns
import pandas as pd
filtered_df = df[df['some_field'] > 100]
pandas_df = filtered_df.to_pandas()
sns.boxplot(x='category', y='value', data=pandas_df)

最近,Eland 扩展了将 NLP 变换器模型导入 Elasticsearch 的功能。Elasticsearch 目前支持一组已在 PyTorch 中训练并符合标准 BERT 模型接口的架构。这使 Elasticsearch 能够原生运行 NLP 任务,如命名实体识别、零样本分类、问答等。当前支持的任务列表可以在文档中查看:www.elastic.co/guide/en/ma…

与本书最相关的是对文本嵌入模型的原生支持。Eland 用于加载本地存储的模型,或更常见的是,直接从 Hugging Face 加载模型到 Elasticsearch。模型被下载并分块,以便可以加载到 Elasticsearch 中。

现在,我们将通过一个 Jupyter 笔记本进行演示,其中我们将配置一个 Elasticsearch 连接,从 Hugging Face 加载模型,部署(启动)模型,并生成一个测试向量。

完整代码可以在书的 GitHub 仓库的第3章文件夹中的 Jupyter 笔记本中查看:github.com/PacktPublis…

有了对 Eland 如何促进 Hugging Face 模型与 Elasticsearch 集成的基础了解,让我们更深入地探讨使用代码的实际方面。接下来,我们将介绍直接从 Hugging Face 将 Sentence Transformer 加载到 Elasticsearch 的步骤,为那些希望利用这些平台的联合力量的人阐明过程。

从 Hugging Face 将 Sentence Transformer 加载到 Elasticsearch

此代码将向您展示如何将 Hugging Face 支持的嵌入模型加载到 Elastic Cloud 中的 Elasticsearch 集群:cloud.elastic.co/。

这里,我们将安装并导入所需的 Python 库:

  • eland
  • elasticsearch
  • transformers
  • sentence_transformers
  • torch==1.11

Elastic 使用 Eland 库从 Hugging Face Hub 下载模型并将其加载到 Elasticsearch:

pip -q install eland elasticsearch transformers sentence_transformers torch==1.13
from pathlib import Path
from eland.ml.pytorch import PyTorchModel
from eland.ml.pytorch.transformers import TransformerModel
from elasticsearch import Elasticsearch
from elasticsearch.client import MlClient

配置 Elasticsearch 身份验证

推荐的身份验证方法使用 Elastic Cloud ID(www.elastic.co/guide/en/cl… API 密钥(www.elastic.co/guide/en/ki…

您可以使用任何方法设置所需的凭证。在此示例中,我们使用 getpass 提示凭证以避免在 GitHub 中存储它们:

import getpass
es_cloud_id = getpass.getpass('Enter Elastic Cloud ID:  ')
es_api_id = getpass.getpass('Enter cluster API key ID:  ')
es_api_key = getpass.getpass('Enter cluster API key:  ')
# Connect to Elastic Cloud
es = Elasticsearch(cloud_id=es_cloud_id, api_key=(es_api_id, es_api_key))
es.info() # should return cluster info

从 Hugging Face Hub 加载模型

Elastic Stack ML 功能支持符合标准 BERT 模型接口并使用 WordPiece 分词算法的变换器模型。

对于 WordPiece 分词的深入解释,请查看 Hugging Face NLP 课程的此部分:huggingface.co/learn/nlp-c…

从 Hugging Face 下载嵌入模型,使用如图 3.6 所示的复制链接。

对于此示例,我们将使用 sentence-transformers/msmarco-MiniLM-L-12-v3 模型:huggingface.co/sentence-tr…

下载模型

在这里,我们指定 Hugging Face 的模型 ID。获取此 ID 的最简单方法是点击模型页面上名称旁边的图标以复制模型名称。

image.png

当调用 TransformerModel 时,您需要指定 Hugging Face 模型的 ID 和任务类型。您可以尝试指定 auto,Eland 将尝试从模型配置中确定正确的类型。这并不总是可能的,所以可以设置特定的 task_type 列表:

hf_model_id='sentence-transformers/msmarco-MiniLM-L-12-v3'
tm = TransformerModel(model_id=hf_model_id, task_type="text_embedding")

接下来,我们设置并确认模型 ID。为了使名称与 Elasticsearch 兼容,将 / 替换为 __(双下划线):

es_model_id = tm.elasticsearch_model_id()

将模型以 TorchScrpt 表示形式导出,Elasticsearch 将使用此形式:

tmp_path = "models"
Path(tmp_path).mkdir(parents=True, exist_ok=True)
model_path, config, vocab_path = tm.save(tmp_path)

将模型加载到 Elasticsearch

这里,模型将被序列化并加载到 Elasticsearch,然后可以在 ML 节点上启动:

ptm = PyTorchModel(es, es_model_id)
ptm.import_model(model_path=model_path, config_path=None, vocab_path=vocab_path, config=config)

启动模型

这一步将为我们提供模型的信息,如名称和状态,作为输出:

# 在 Elasticsearch 中列出模型
m = MlClient.get_trained_models(es, model_id=es_model_id)

部署模型

以下代码将在 ML 节点上加载模型并启动使其可用于 NLP 任务的过程:

s = MlClient.start_trained_model_deployment(es, model_id=es_model_id)

接下来,我们将验证模型启动是否无问题:

stats = MlClient.get_trained_models_stats(es, model_id=es_model_id)
stats.body['trained_model_stats'][0]['deployment_stats']['nodes'][0]['routing_state']

您应该看到以下输出,让您知道模型已部署到 ML 节点:

{'routing_state': 'started'}

为查询生成向量

在我们可以运行 kNN 查询之前,我们需要将查询字符串转换为向量。 接下来,我们将创建一个示例查询句子,以验证一切是否已正确设置:

docs =  [
    {
      "text_field": "Last week I upgraded my iOS version and ever since then my phone has been overheating whenever I use your app."
    }
  ]

我们调用 _infer 端点,提供 model_id 和我们想要向量化的文档:

z = MlClient.infer_trained_model(es, model_id=es_model_id, docs=docs, )

响应字典中可以访问第一个文档的向量,如下所示:

doc_0_vector = z['inference_results'][0]['predicted_value']
doc_0_vector

有了加载并成功测试的嵌入模型,我们现在可以在生产环境中使用它。

在 Elasticsearch 中生成向量

向量可以在文档被索引(写入)到 Elasticsearch 之前,在摄取管道中生成。摄取管道中的每个处理器执行不同的任务,例如丰富、修改或移除数据。该管道中的关键处理器是推断处理器,它将文本传递给嵌入模型,接收向量表示,然后将向量添加到原始文档负载中,再将其存储到索引中。这确保了文档的向量表示在摄取后立即可用于索引和搜索。

接下来是一个使用推断处理器的 Elasticsearch 摄取管道配置示例,该处理器与我们之前加载的 sentence-transformers/msmarco-MiniLM-L-12-v3 模型一起使用。该管道从输入文档中获取名为 summary 的字段,使用嵌入模型处理它以生成嵌入,并将结果向量存储在名为 vector 的字段中。 在这里,我们使用 HTTP 调用而不是 Python 代码来演示 Python 之外的另一种方法。请注意,模型已假定已在集群中加载,因为我们在之前的示例中已经做到了:

PUT _ingest/pipeline/embedding_book_pipeline
{
  "description": "Ingest pipeline using bert-base-uncased model",
  "processors": [
    {
      "inference": {
          "target_field": "vector",
          "model_id": "sentence-transformers__msmarco-minilm-l-12-v3",
          "field_map": {
           "summary": "text_field"
          }
      }
    }
  ]
}

在索引文档时使用此摄取管道,包括在索引请求中的 pipeline 参数:

PUT my_index/_doc/1?pipeline=embedding_book_pipeline
{
  "summary": "This is an example text for the embedding model."
}

此请求将索引一个文档,其中包含提供的文本字段和一个包含由 bert-base-uncased 模型生成的嵌入的额外向量字段。 _infer 端点也可以直接调用以在查询时(见下一节)或出于任何其他临时原因生成向量:

POST _ml/trained_models/sentence-transformers__msmarco-minilm-l-12-v3/_infer
{
     "docs": [
{"text_field": "How do I load an NLP model from Hugging Face"}
]
}

这将返回一个 200 响应代码和提交文本的向量表示:

{
  "inference_results": [
    {
      "predicted_value": [
        0.012806695885956287,
        -0.045506563037633896,
        0.014663844369351864,
 ...
        0.002125605707988143,
        0.06371472030878067
      ]
    }
  ]
}

在搜索时,可以调用 _infer 端点从输入文本生成向量,然后可以将其用作 kNN 查询的一部分:

GET search-elastic-docs-vectorized/_search
{
  "knn": {
    "field": "body_content-vector",
    "k": 1,
    "num_candidates": 20,
    "query_vector": [
        0.012806695885956287,
        -0.045506563037633896,
        0.014663844369351864,
 ...
        0.002125605707988143,
        0.06371472030878067
      ]
  },
  "fields": [ "body_content", "headings" ],
  "_source": false
}

或者,在 Elasticsearch 8.7 之后的 _knn 查询中可以指定一个嵌入模型,Elasticsearch 将在运行其余的 _knn 查询之前处理生成查询向量。这允许更流畅地整合向量搜索功能并简化 kNN 搜索过程:

GET search-elastic-docs-vectorized/_search
{
  "knn": {
    "field": "dense-vector-field",
    "k": 10,
    "num_candidates": 100,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "my-text-embedding-model",
        "model_text": "The opposite of blue"
      }
    }
  }
}

让我们看看如何在生产环境中规划集群容量和资源。

规划集群容量和资源

为任何生产环境规划足够的集群容量和资源是至关重要的,尤其是在实现大规模向量搜索用例时。为确保最佳性能和效率,必须进行仔细的考虑、规划和测试。

在接下来的章节中,我们将深入探讨负载测试,这是微调和优化 Elasticsearch 部署的一个重要部分。但在此之前,我们将探讨在 Elasticsearch 的 ML 节点上运行嵌入模型所需的因素,概述为实现性能与资源利用之间的正确平衡需要考虑的关键因素。在本节中,我们将讨论 CPU、RAM 和磁盘需求的关键方面,为 Elasticsearch 中的资源管理提供全面的理解。

CPU 和内存需求

Elasticsearch 中向量搜索的 CPU 需求与传统搜索的需求并无太大不同。主要区别在于近似 kNN(我们在此简称 kNN)查询期间如何访问向量数据。

在传统搜索和暴力/精确匹配向量搜索中,随着更多文档的添加,磁盘空间会扩展,CPU 也会升级以适应增加的吞吐量。

在 Elasticsearch 的 kNN 搜索中同样如此;然而,为了最快的响应时间,我们希望有足够的堆外 RAM 可用于将所有向量嵌入加载到内存中。当 Elasticsearch JVM 外部没有足够的 RAM 可以容纳嵌入时,Elasticsearch 将不得不从磁盘读取,频繁这样做会导致响应时间变慢。这种数据访问方式的转变可以提高性能,因为观察到当向量必须从磁盘而不是直接从内存读取时,热线程更有可能发生。通过直接从堆外 RAM 访问数据,向量搜索减少了这些热线程的影响,提供了更高效的查询过程。

注意 hot_threads 是一个诊断 API,提供了当前在 Elasticsearch 节点中运行的最繁忙的内部任务的洞察。它通过突出显示消耗异常高的 CPU 资源的线程来帮助识别性能瓶颈。

考虑到页缓存内存需求对 kNN 搜索特别重要,用户需要注意一个节点可用的页缓存 RAM 中可以放入多少向量。假设使用分层导航小世界(HNSW)的默认配置,所需字节的粗略计算如下:

num_vectors * 4 * (num_dimensions + 12) = total RAM in bytes

例如,如果您使用的模型输出 512 维向量,并且您期望将 2000 万向量加载到 Elasticsearch 中,公式将如下:

20,000,000 * 4 * (512 + 12) = 41,920,000,000 bytes
41,920,000,000 / 102439 GB RAM

2000 万嵌入字段大致需要约 39 GB 的堆外 RAM,以使 kNN 搜索达到最佳性能。如果您考虑 Elasticsearch 的一个全尺寸节点为 64 GB——一半用于 Elasticsearch JVM,几 GB 用于操作系统和其他进程——您将得到一个起始估计,需要 2 个数据节点来处理这些向量。此外,这还没有考虑副本。

这是一个起始估计,也是您开始测试的好地方。它并不意味着确切的 RAM 需求。稍后在本章中介绍的测试将在转向生产之前为您提供最佳数字。

在设置了 RAM 使用的要求和影响后,我们将注意力转向生态系统中另一个关键资源:存储。

磁盘需求

正如我们所见,性能良好的 kNN 搜索的主要考虑因素之一是确保有足够的堆外 RAM 可用于存储向量。然而,重要的是要注意,这些向量仍然是在磁盘上生成和存储的。因此,用户必须确保有足够的存储空间可用。然而,由于磁盘的大小与 RAM 的大小相比,磁盘容量通常不是规划集群容量时的限制因素。

前一节中提到的所需 RAM 的估算公式也是向量数据磁盘容量的计算公式,但您还必须考虑与向量一起存储的任何其他字段,如向量的文本、其他文本字段和元数据。

磁盘容量的粗略起始估计如下:

size of a single document * number of documents * number of replicas * 1.2

这里,1.2 是 JSON 扩展和磁盘压缩之间的净乘数。请注意,这是一个非常粗略的估计。

然而,了解您的数据在 Elasticsearch 中的表现的唯一真正方式是加载一个样本数据集。您可以轻松使用 Elastic Cloud 的免费试用来启动一个小型集群并加载一个小型代表性数据集。启动试用很快,只需要一个电子邮件地址:cloud.elastic.co/registratio…

您将获得一个小型、功能齐全的 Elasticsearch 集群,包括一个 ML 节点和所有企业功能。www.elastic.co/getting-sta… 的入门指南可以帮助您快速启动并运行。

一旦您加载了数据,您将能够看到您的数据将扩展到的确切大小,这是为生产需求做计划的唯一真正方式。即使在免费试用期过后,如果需要,您也可以慢慢扩展您的集群,通过简单的按月付款方式。

您可以使用下一节中介绍的磁盘使用 API 查看磁盘使用情况,我们将在下一章中讨论集群调优、负载测试和全面扩展测试。

现在我们已经讨论了了解数据大小和扩展影响的重要性,拥有提供这些资源真实世界利用率的精确洞察工具至关重要。Elasticsearch 套件中的一个关键工具是分析磁盘使用 API。让我们探索它的功能以及它如何帮助了解磁盘空间消耗。

分析索引磁盘使用 API

一旦向量(和任何其他数据)被加载到 Elasticsearch 中,您可以使用分析索引磁盘使用 API 来计算占用的磁盘空间有多少。有关分析磁盘使用 API 的更多信息,请访问文档页面:www.elastic.co/guide/en/el…

search-elastic-docs 索引有多个矢量字段,这些字段在数据大小上相对较小。其中之一是 title-vector:

POST /search-elastic-docs/_disk_usage?run_expensive_tasks=true&pretty

这给出了以下响应(API 调用的完整响应非常长,因此我们仅显示包含相关部分的截断输出):

{
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "search-elastic-docs": {
    "store_size": "989.5mb",
    "store_size_in_bytes": 1037592858,
    "all_fields": {
      "total": "987.6mb",
      "total_in_bytes": 1035596641,
      "inverted_index": {
        "total": "69.8mb",
        "total_in_bytes": 73269048
      },
...
      "knn_vectors": "83.4mb",
      "knn_vectors_in_bytes": 87462952
    },
    "fields": {
...
      "title-vector": {
        "total": "40mb",
        "total_in_bytes": 42046916,
        "inverted_index": {
          "total": "0b",
          "total_in_bytes": 0
        },
...
        "knn_vectors": "40mb",
        "knn_vectors_in_bytes": 42046916
      },
...
}

我们可以看到向量占用了 83.4 MB。分析了磁盘使用情况后,我们可以将注意力转向 ML 节点容量规划。

ML 节点容量

对于在 Elasticsearch 中的 ML 节点上运行的嵌入模型,您需要规划以确保您的节点在推理时有足够的容量运行模型。Elastic Cloud 允许根据 CPU 需求自动扩展 ML 节点,这使得它们在需要更多计算资源时能够扩展,需求减少时能够缩减。

我们将在下一章更详细地讨论调优 ML 节点进行推理,但至少,您需要一个 ML 节点,该节点有足够的 RAM 至少加载一个嵌入模型的实例。随着您的性能要求增加,您可以增加单个模型的分配数量以及每次分配分配的线程数。

要检查模型的大小和加载模型所需的内存(RAM)量,您可以运行获取训练模型统计信息 API(有关此 API 的更多信息,请访问文档页面 www.elastic.co/guide/en/el…

GET _ml/trained_models/dslim__bert-base-ner-uncased/_stats?human

这将返回各种统计数据,包括模型大小和内存需求:

{
      "model_id": "dslim__bert-base-ner-uncased",
      "model_size_stats": {
        "model_size": "415.4mb",
        "model_size_bytes": 435663717,
        "required_native_memory": "1gb",
        "required_native_memory_bytes": 1122985674
      },

当模型正在运行时,您还可以通过进入 Kibana UI 的 Machine Learning > Model Management > Trained Models 并展开模型 ID 来获取关于它的统计信息。

在规划 ML 节点容量时,需要考虑两种不同的情况:

  1. 索引时:通常,有大量文档初次批量加载到 Elasticsearch 索引中,这需要生成向量。这将对您的 ML 节点提出迅速运行推理的大需求。这是轻松扩展 ML 节点并动态增加模型分配数量非常有用的地方。您可以在初次批量加载运行时扩展模型分配,然后在加载完成后减少分配。
  2. 搜索时推理:当用户发送搜索查询时,您需要使用生成文档向量时使用的相同嵌入模型来生成查询向量。跟上您的平均查询负载将允许您规划正常运行的大小。如果您预计查询负载将有所增加,例如在电子商务中的繁忙购物周末,您可以主动增加模型分配的数量,进而扩展 ML 节点。

测试这两种条件的唯一真正方式是在您实际数据上进行负载测试和性能调优,我们将在下一章更深入地涵盖这些内容。

存储效率策略

随着您的向量搜索生产数据集的增长,存储这些向量并及时搜索它们所需的资源也在增加。在本节中,我们将讨论用户可以采取的几种策略来减少这些资源的使用。每种策略都有其权衡,应在投入生产前仔细考虑并彻底测试。

降低维度

降低维度是指将高维数据转换为低维表示的过程。这种做法通常用于缓解处理高维数据时遇到的挑战,如维数灾难(en.wikipedia.org/wiki/Curse_… t 分布随机邻居嵌入(t-SNE),可以帮助提高 kNN 向量搜索的效率和有效性。然而,降低维度也有优点和缺点。

降低维度的一个重要优点是减少存储、内存和计算需求。高维向量数据可能会消耗大量的 RAM 和 CPU 资源,使其难以在 Elasticsearch 中处理。通过将数据转换为低维表示,可以减少所需的堆外 RAM 和计算量,可能提高 kNN 向量搜索算法的效率,并使其在资源受限的环境中应用。

另一个降低维度的优点是它可以帮助缓解维数灾难,这是一种当维度增加且数据点变得越来越稀疏时出现的现象。这种稀疏性可能导致基于距离的算法性能不佳,因为高维空间中数据点之间的距离变得不再有意义。降低维度可以通过有效地在低维空间捕捉最相关的信息来缓解这个问题,使基于距离的搜索算法更加有效。

然而,降低维度也有一些缺点。主要的缺点之一是潜在的信息丢失。维度降低技术通常涉及某种程度的近似或压缩,这可能导致数据中微妙的关系或细微差别的丢失。这种信息的丢失可能会影响 kNN 向量搜索的准确性和有效性,因为降维后的表示可能无法完全捕捉原始高维空间中数据点之间的关系。

另一个缺点是降维过程中增加的复杂性和计算开销。应用维度降低技术通常需要额外的计算和资源,这可能会抵消降低数据维度所带来的一些好处。此外,选择适当的维度降低技术并调整其参数可能具有挑战性,因为最佳选择通常取决于数据的特定特性和问题域。

以下是使用 Iris 数据集的一个示例,该数据集有四个维度,并使用 PCA 将其维度降低到两个:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.decomposition import PCA
# 加载 Iris 数据集
iris = datasets.load_iris()
X = iris.data
y = iris.target
# 应用 PCA 进行维度降低
pca = PCA(n_components=2)
X_reduced = pca.fit_transform(X)
# 可视化原始数据
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Set1, edgecolor='k')
plt.xlabel('Sepal length')
plt.ylabel('Sepal width')
plt.title('Original Iris dataset')
plt.show()
# 可视化降维后的数据
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y, cmap=plt.cm.Set1, edgecolor='k')
plt.xlabel('First Principal Component')
plt.ylabel('Second Principal Component')
plt.title('Iris dataset after PCA')
plt.show()

图 3.7 中的散点图显示了原始数据和降维后数据的分布。降低维度导致了一些信息丢失,但它也可以使数据更易于管理,并提高基于距离的算法(如 kNN 搜索)的性能。

image.png

另一个缺点是降维过程中增加的复杂性和计算开销。应用维度降低技术通常需要额外的计算和资源,这可能会抵消降低数据维度所带来的一些好处。此外,选择适当的维度降低技术并调整其参数可能具有挑战性,因为最佳选择通常取决于数据的特定特性和问题域。

量化

量化是在向量搜索中用于减小高维数据的大小和复杂性的一种技术,同时保持执行有意义的搜索操作的能力。量化过程涉及将通常表示为浮点数的连续向量值转换为更小、更紧凑的表示形式,如整数。这种转换减少了与高维数据相关的存储和计算需求,使搜索和检索操作更加高效。

将浮点表示转换为8位向量可能会显著影响数据的大小和存储需求。浮点数通常需要32或64位来表示单个值,而8字节向量仅使用每个值8位。这种大小的减少可以导致存储和内存需求的大幅节省,使大规模向量搜索应用的高效处理成为可能。然而,这种压缩可能以减少精度和准确性为代价,因为量化过程涉及用较少的离散级别近似原始浮点值。这种精度的损失可能会影响搜索结果的相关性,因为量化向量之间的距离可能无法完美反映原始高维空间中数据点之间的关系。

在考虑量化时,用户应在量化前后测试搜索结果的相关性,以查看降低是否可接受,然后再投入生产。降低维度和量化是用于优化嵌入模型和向量表示的两种技术,但它们服务于不同的目的并解决数据的不同方面。降低维度针对的是向量表示中的维数,而量化专注于减少向量中每个元素的精度。这两种技术都可以提高效率和减少内存需求,但它们也可能引入一些信息或精度损失,这可能影响搜索结果的准确性和质量。

在讨论了优化向量表示的细节之后,重要的是要注意,系统的效率不仅仅关于数据本身,还关于数据的存储和检索方式。在 Elasticsearch 和向量搜索中,_source 字段发挥了关键作用,尤其是在处理高维数据时。让我们讨论 _source 字段中 dense_vector 的含义及其对搜索性能的潜在影响。

从 _source 中排除 dense_vector

Elasticsearch 在索引时存储传递的原始 JSON 文档在 _source 字段中。默认情况下,搜索结果中的每个匹配项包含完整文档。然而,当文档包含高维 dense_vector 字段时,_source 字段可能变得相当大,导致加载数据的显著开销。结果,由于处理这些较大的 _source 字段所需的额外时间,kNN 搜索的速度可能会显著降低。

为了缓解这个问题,您可以使用排除映射参数禁用在 _source 中存储 dense_vector 字段。这种方法防止在搜索操作期间加载和返回大型向量,减少处理开销和索引大小。尽管从 _source 中省略了向量,但仍可以在 kNN 搜索中使用这些向量,因为搜索过程依赖于单独的数据结构来有效地执行搜索。

在应用排除参数之前,了解从 _source 中省略字段的潜在缺点很重要。这样做可能限制访问文档或执行需要访问原始字段值的某些更新操作的能力。

以下是映射中此设置的一个示例:

PUT my_index
{
  "mappings": {
    "_source": {
      "excludes": [
        "vector_field"
      ]
    },
    "properties": {
      "vector_field": {
        "type": "dense_vector",
        "dims": 384,
        "index" : True,
        "similarity" : "dot_product"
      },
      "text_field": {
        "type": "text"
      }
    }
  }
}

除非您计划在某个时点重新索引该字段,否则用户应当为任何包含 dense_vector 字段的索引设置此参数,因为它将节省空间并增加 kNN 搜索速度。从 Elasticsearch 的 _source 中排除 dense_vector 字段可以导致高效的 kNN 搜索速度和减小的索引大小。然而,用户应该权衡这些好处与潜在的重新索引需求,确保为他们特定用例优化和有效的存储方法。

总结

在本章中,我们深入探讨了 Hugging Face 生态系统的复杂性和 Elasticsearch 的 Eland Python 库的功能,提供了在 Elasticsearch 中使用嵌入模型的实用示例。我们探索了 Hugging Face 平台,强调了其数据集、模型选择和其空间的潜力。此外,我们提供了 Eland 库的实用方法,展示了其功能并讨论了诸如映射、ML 节点和模型集成等关键考虑因素。我们还触及了集群容量规划的细节,强调了 RAM、磁盘大小和 CPU 考虑因素。最后,我们强调了几种存储效率策略,重点是维度降低、量化和映射设置,以确保您的 Elasticsearch 集群的最佳性能和资源保护。

在下一章中,我们将深入探讨与数据工作的操作阶段,并学习如何调整性能以进行干扰和查询。