• 文章
  • 通过管理多个向量优化语义搜索
返回向量搜索手册

通过管理多个向量优化语义搜索

Kacper Łukawski

·

2022年10月5日

Optimizing Semantic Search by Managing Multiple Vectors

如何通过每个对象存储多个向量来优化向量存储

在实际应用场景中,单个对象可能会以多种不同方式进行描述。如果您经营电子商务业务,那么您的商品通常会有一个名称、较长的文本描述以及一系列照片。在烹饪时,您可能关心食材清单、口味描述,还包括食谱和菜肴的外观。到目前为止,如果您想为每个对象启用带有多个向量的语义搜索,Qdrant会要求您为每种向量类型创建单独的集合,即使它们可以在有效载荷中共享一些其他属性。然而,自Qdrant 0.10版本起,您能够将所有这些向量存储在同一个集合中,并共享一个有效载荷的副本!

运行新版本的Qdrant一如既往地简单。通过运行以下命令,您可以设置一个单个实例,该实例也将暴露HTTP API

docker run -p 6333:6333 qdrant/qdrant:v0.10.1

创建一个集合

添加新功能通常需要对接口进行一些更改,因此我们不得不这样做以支持多个向量也就不足为奇了。目前,如果您想创建一个集合,您需要定义要为每个对象存储的所有向量的配置。每种向量类型都有其自己的名称以及用于衡量点之间距离的距离函数。

from qdrant_client import QdrantClient
from qdrant_client.http.models import VectorParams, Distance

client = QdrantClient()
client.create_collection(
   collection_name="multiple_vectors",
   vectors_config={
       "title": VectorParams(
           size=100,
           distance=Distance.EUCLID,
       ),
       "image": VectorParams(
           size=786,
           distance=Distance.COSINE,
       ),
   }
)

如果您想为每个集合保留一个向量,您仍然可以在不指定名称的情况下进行操作。

client.create_collection(
   collection_name="single_vector",
   vectors_config=VectorParams(
       size=100,
       distance=Distance.COSINE,
   )
)

所有与搜索相关的操作的接口也略有更改,因此您可以在特定请求中选择要使用的向量。然而,通过一个真实世界示例的端到端Qdrant使用过程来查看所有更改可能会更容易。

构建具有多个嵌入的服务

构建搜索引擎的一种常见方法是将语义文本功能与图像搜索结合起来。为此,我们需要一个包含图像及其文本描述的数据集。有几个数据集可用,其中MS_COCO_2017_URL_TEXT可能是最简单的。由于它在HuggingFace上可用,我们可以轻松地使用他们的datasets库。

from datasets import load_dataset

dataset = load_dataset("ChristophSchuhmann/MS_COCO_2017_URL_TEXT")

现在,我们有一个数据集,其结构包含图像URL和其英文文本描述。为简单起见,我们可以将其转换为DataFrame,因为这种结构可能非常方便未来的处理。

import pandas as pd

dataset_df = pd.DataFrame(dataset["train"])

该数据集由两列组成:TEXTURL。因此,每个数据样本由两部分独立的信息描述,并且每一部分都必须用不同的模型进行编码。

使用预训练模型处理数据

得益于embetter,我们可以重用一些现有的预训练模型,并使用方便的scikit-learn API,包括管道。这个库还提供了一些加载图像的实用工具,但只支持本地文件系统,所以我们需要创建自己的类,根据URL下载文件。

from pathlib import Path
from urllib.request import urlretrieve
from embetter.base import EmbetterBase

class DownloadFile(EmbetterBase):

   def __init__(self, out_dir: Path):
       self.out_dir = out_dir

   def transform(self, X, y=None):
       output_paths = []
       for x in X:
           output_file = self.out_dir / Path(x).name
           urlretrieve(x, output_file)
           output_paths.append(str(output_file))
       return output_paths

现在我们准备定义管道,分别使用all-MiniLM-L6-v2vit_base_patch16_224模型处理我们的图像和文本。首先,让我们从Qdrant配置开始。

创建Qdrant集合

我们将为每个对象放置两个向量(一个用于图像,另一个用于文本),因此我们需要创建一个允许我们这样做的集合配置。

from qdrant_client import QdrantClient
from qdrant_client.http.models import VectorParams, Distance

client = QdrantClient(timeout=None)
client.create_collection(
   collection_name="ms-coco-2017",
   vectors_config={
       "text": VectorParams(
           size=384,
           distance=Distance.EUCLID,
       ),
       "image": VectorParams(
           size=1000,
           distance=Distance.COSINE,
       ),
   },
)

定义管道

既然所有的拼图都已到位,我们就可以开始处理,将原始数据转换为我们需要的嵌入。预训练模型非常方便。

from sklearn.pipeline import make_pipeline
from embetter.grab import ColumnGrabber
from embetter.vision import ImageLoader, TimmEncoder
from embetter.text import SentenceEncoder

output_directory = Path("./images")

image_pipeline = make_pipeline(
   ColumnGrabber("URL"),
   DownloadFile(output_directory),
   ImageLoader(),
   TimmEncoder("vit_base_patch16_224"),
)

text_pipeline = make_pipeline(
   ColumnGrabber("TEXT"),
   SentenceEncoder("all-MiniLM-L6-v2"),
)

多亏了scikit-learn API,我们可以简单地在创建的DataFrame上调用每个管道,并将创建的向量放入Qdrant,以实现快速向量搜索。为方便起见,我们将把向量作为其他列放入我们的DataFrame中。

sample_df = dataset_df.sample(n=2000, random_state=643)
image_vectors = image_pipeline.transform(sample_df)
text_vectors = text_pipeline.transform(sample_df)
sample_df["image_vector"] = image_vectors.tolist()
sample_df["text_vector"] = text_vectors.tolist()

创建的向量可以很容易地放入Qdrant中。为简单起见,我们将跳过此步骤,但如果您对详细信息感兴趣,请查看Jupyter Notebook,其中包含逐步说明。

使用多个向量进行搜索

如果您决定用几个神经嵌入来描述每个对象,那么在每次搜索操作中,您都需要提供向量名称以及向量嵌入,这样引擎就知道使用哪个。搜索操作的接口非常直观,需要一个NamedVector实例。

from qdrant_client.http.models import NamedVector

text_results = client.search(
   collection_name="ms-coco-2017",
   query_vector=NamedVector(
       name="text",
       vector=row["text_vector"],
   ),
   limit=5,
   with_vectors=False,
   with_payload=True,
)

另一方面,如果我们决定使用图像嵌入进行搜索,那么我们只需提供在创建集合时选择的向量名称,因此我们将提供“image”而不是“text”,因为这是我们在一开始配置的方式。

由于我们有两个不同的向量描述每个对象,我们可以使用其中任何一个执行搜索查询。因此,结果会因所选的嵌入方法而异也就不足为奇了。下面的图片显示了Qdrant对左侧图像/文本返回的结果。

如果我们使用图像嵌入查询系统,那么它会返回以下结果

图片搜索结果

然而,如果我们使用文本描述嵌入,那么结果会略有不同

文本搜索 然而,如果我们使用文本描述嵌入,那么结果会略有不同

用于创建神经编码的方法在搜索过程及其质量中扮演重要角色,这一点不足为奇。如果您的数据点可以用多个向量描述,那么Qdrant的最新版本为您提供了将它们一起存储并重用有效载荷的机会,而不是创建多个集合并分别查询它们。

总结

  • Qdrant 0.10引入了高效的向量存储优化,允许在单个集合中无缝管理每个对象的多个向量。
  • 此更新通过消除为每种向量类型创建单独集合的需要,简化了语义搜索功能,提高了搜索准确性和性能。
  • 借助Qdrant的新功能,用户可以轻松配置每种向量类型的向量参数,包括大小和距离函数,从而优化搜索结果和用户体验。

如果您想查看其他示例,请查看我们的完整笔记本,其中展示了搜索结果和整个管道实现。

此页面有用吗?

感谢您的反馈!🙏

听到这个消息我们很抱歉。😔 您可以在 GitHub 上编辑此页面,或创建一个 GitHub 问题。