如何通过为每个对象存储多个向量来优化向量存储
在实际场景中,单个对象可能以多种不同方式描述。如果您经营电子商务业务,您的商品通常会有名称、较长的文本描述和大量照片。在烹饪时,您可能关心配料表、口味描述,也关心食谱和您制作的餐点会是什么样子。直到现在,如果您想为每个对象使用多个向量启用语义搜索,Qdrant 会要求您为每种向量类型创建单独的集合,即使它们可以在 payload 中共享一些其他属性。然而,自 Qdrant 0.10 版本起,您能够将所有这些向量一起存储在同一集合中,并共享一份 payload 副本!
运行新版 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"])
该数据集包含两列: TEXT 和 URL。因此,每个数据样本都由两个独立的信息片段描述,并且每个信息片段必须使用不同的模型进行编码。
使用预训练模型处理数据
多亏了 embetter,我们可以重用一些现有的预训练模型并使用方便的 scikit-learn API,包括 pipeline。这个库还提供了一些加载图像的实用程序,但仅支持本地文件系统,因此我们需要创建自己的类,该类将根据 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
现在我们准备定义 pipeline,以便分别使用 all-MiniLM-L6-v2 和 vit_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,
),
},
)
定义 pipeline
由于我们已经准备好所有拼图,我们可以开始处理,将原始数据转换为我们需要的嵌入。预训练模型派上了用场。
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 上调用每个 pipeline,并将创建的向量放入 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”,因为我们最初就是这样配置的。
搜索结果:图像 vs 文本搜索
由于我们有两个不同的向量描述每个对象,我们可以使用其中任何一个来执行搜索查询。因此,根据所选的嵌入方法,结果会有所不同也就不足为奇了。下面的图片展示了 Qdrant 为左侧的图像/文本返回的结果。
图像搜索
如果我们使用图像嵌入查询系统,它将返回以下结果

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

用于创建神经编码的方法在搜索过程及其质量中起着重要作用,这不足为奇。如果您的数据点可以使用多个向量描述,那么最新版本的 Qdrant 让您有机会将它们一起存储并重用 payload,而不是创建多个集合并分别查询它们。
总结
- Qdrant 0.10 引入了高效的向量存储优化,允许在单个集合中无缝管理每个对象的多个向量。
- 本次更新通过消除每种向量类型都需要单独集合的需求,简化了语义搜索功能,提高了搜索准确性和性能。
- 借助 Qdrant 的新功能,用户可以轻松配置每种向量类型的向量参数,包括大小和距离函数,从而优化搜索结果和用户体验。
如果您想查看其他示例,请查看我们的完整 notebook,其中展示了搜索结果和整个 pipeline 实现。