Pinecone向量数据库使用方法

1 建立索引

  1. 通过OpenAI 的 Embedding API 接口生成文本内容的embeddings
  2. 上传生成的embeddings道Pinecone

2 查询索引

  1. 通过OpenAI的Embedding API接口生成查询文字的embedding。
  2. 获取返回的向量结果,并将其作为查询条件发给Pinecone
  3. 查询语义相似的文档,即使他们不与查询的内容有相同的关键字

image.png

3 选择索引类型和大小

介绍

在规划您的 Pinecone 部署时,了解您的载体的大致存储要求以选择合适的【 pod 类型】和【数量】非常重要。此页面将提供有关尺码的指导,以帮助您做出相应的计划。

就像所有的指南一样,这些考虑因素是一般性的,可能不适用于您具体的使用情况。我们提醒您始终测试您的部署,并确保您使用的索引配置符合您的要求。

【集合】(Collections)使得创建不同的 pod 类型和大小的【索引】的新版本变得非常容易,我们鼓励您利用这个功能来测试不同的配置。本指南仅是一个关于大小考虑因素的概述,不应被视为最终指南。

标准版、企业版和企业专用版的用户可以联系支持人员获取有关大小和测试的进一步帮助。

概述

当决定如何配置 Pinecone 索引时,有五个主要考虑因素:

  • 向量数量
  • 向量的维度
  • 每个向量上的【元数据】的大小
  • 【QPS】 吞吐量
  • 索引元数据的【基数】
    每个考虑因素都有相应的对于【索引大小】、【pod 类型】和【复制策略】的要求。

向量数(Number of vectors)

在确定大小时,最重要的考虑因素是您计划处理的向量数量。一个经验法则是,单个 p1 pod 可以存储约 1M 个向量,而 s1 pod 可以存储 5M 个向量。但是,其他因素,如下文所述的向量维度和元数据,可能会影响这一点。

向量的维数(Dimensionality of vectors)

上述关于每个 pod 可以存储多少个向量的经验法则假设每个向量具有典型的 768 个【维度】。由于您的个人使用情况将决定您的向量的维度,因此存储它们所需的空间可能必要更大或更小。

每个向量的每个维度使用 4 个字节的内存和存储空间,因此,如果您预计有每个具有 768 个维度的向量的 1M 个向量,则没有考虑元数据或其他开销,大约需要 3GB 的存储空间。利用这个参考值,我们可以估计给定索引所需的典型 pod 大小和数量。下表 1 给出了一些示例。

表 1:按维度估算的每 1M 向量的 pod 数量

Pod type[pod类型] Dimensions[维度] Estimated max vectors per pod【每个pod的向量最大值】
p1 512 1,250,000
p1 768 1,000,000
p1 1024 675,000
p2 512 1,250,000
p2 768 1,100,000
p2 1024 1,000,000
s1 512 8,000,000
s1 768 5,000,000
s1 1024 4,000,000

Pinecone 不支持部署分数 pod,因此在选择 pod 时,始终四舍五入到最近的整数

Queries per second (QPS)

QPS 速度受索引的 pod 类型、【副本数量】和查询的【 top_k 值】的组合控制。Pod 类型是驱动 QPS 的主要因素,不同的 pod 类型针对不同的方法进行了优化。

p1 pods 是性能优化的 pods,提供非常低的查询延迟,但每个 pod 的向量数量比 s1 pods 少。它们非常适合具有低延迟要求(<100ms)的应用程序。s1 pods 针对存储进行了优化,提供大型存储容量和较低的总成本,但查询延迟略高于 p1 pods。它们非常适合具有中等或较宽松延迟要求的非常大的索引。

p2 pod 类型具有更高的查询吞吐量和更低的延迟。它们的每个副本支持 200 QPS 并在不到 10ms 的时间内返回查询结果。这意味着查询吞吐量和延迟比 s1 和 p1 更好,特别是对于低维向量(<512D)。

通常,具有每个具有 768 个维度的 1M 个向量且没有副本的单个 p1 pod 可以处理约 20 QPS。根据元数据的大小、向量的数量、向量的维度和搜索的 top_k 值,可能会获得更快或更慢的速度。请参见下表 2 以获取更多示例。

表 2:按 pod 类型和 top_k 值划分的 QPS*

Pod type top_k 10 top_k 250 top_k 1000
p1 30 25 20
p2 150 50 20
s1 10 10 10

表2中的QPS值表示1M向量和768维度的基准QPS。

增加副本是增加QPS最简单的方法。每个副本增加的吞吐量潜力大致相同,因此如果使用p1 pod以实现150 QPS,则需要使用主要pod和5个副本。在应用中使用线程或多进程也很重要,因为实现单个查询序列仍然会受到底层延迟的影响。 Pinecone的gRPC客户端也可以用于增加upserts的吞吐量。

元数据基数和大小(Metadata cardinality and size)

规划索引时的最后一点考虑因素是元数据的基数和大小。当涉及到几百万个向量时,增量可能很小,但在向量增长到数以亿计时,它们可以产生真正的影响。

具有非常高基数的索引,比如在每个向量上存储唯一的用户ID的索引,可能具有相当大的内存需求,导致每个pod中适合的向量较少。此外,如果每个向量的【元数据】大小较大,则索引需要更多存储空间。使用【选择性元数据索引】限制索引哪些元数据字段可以帮助降低内存使用。

Pod sizes

你也可以从其中一个较大的pod尺寸开始,比如p1.x2。每增加一步的pod尺寸都会使你的向量空间加倍。我们建议从x1 pod开始,随着业务的增长再逐步扩展。这样,你就不会从过大的pod尺寸开始,却无法再进行扩展,这意味着你必须在准备好之前迁移到新的索引。

Example applications

以下的例子将展示如何使用上述大小指南来选择适合你的索引的类型、大小和pod数量。

例子1:新闻文章的语义搜索

在我们的第一个例子中,我们将使用我们文档中语义搜索的演示应用程序。在这种情况下,我们仅使用了204,135个向量。每个向量使用300个维度,远低于一般度量的768个维度。根据上述经验法则,每个p1 pod最多可以使用1M向量,我们可以轻松地使用单个p1.x1 pod运行此应用程序。

示例 2:面部识别

针对这个例子,假设您正在构建一个应用程序,通过面部识别来识别客户,以保证银行应用程序的安全性。面部识别可以仅使用128个维度就能完成,但在这种情况下,由于该应用程序将被用于获取财务信息,我们要确保使用它的人是正确的。我们计划服务1亿名客户,并将每个向量使用2048个维度。

100M / 1M = 100 base p1 pods

2048 / 768 = 2.667 vector ratio

2.667 * 100 = 267 rounding up

因此,我们需要267个p1.x1 pod。我们可以通过改为s1 pod来减少数量,以牺牲延迟时间来提高存储可用性。它们的存储容量是p1.x1的五倍,因此数学计算非常简单:

267 / 5 = 54 rounding up

因此,我们估计需要54个s1.x1 pod来存储每位银行客户的高维度面部数据。

附:

在本指南中,我们将看到如何使用Pinecone进行语义搜索。首先,我们必须安装所需的先决条件库:

1
!pip install -qU pinecone-client[grpc] datasets sentence-transformers

数据预处理

数据集准备过程需要几个步骤:

  1. 从Hugging Face Datasets下载Quora数据集。
  2. 将数据集的文本内容嵌入向量中。
  3. 重格式化为(id、vector、metadata)结构,以添加到Pinecone中。

在本节中,我们将看到如何完成步骤1、2和3,但我们不会在整个数据集中实现步骤2和3,直到我们到达upsert循环,因为我们将迭代地执行这两个步骤。

无论哪种情况,这可能需要一些时间。如果您宁愿跳过数据准备步骤,并直接进行upsert并测试语义搜索功能,则应参考快速笔记本。

创建索引

现在数据已经准备好了,我们可以设置我们的索引来存储它。 我们首先初始化与 Pinecone 的连接。为此,我们需要一个免费的 API 密钥。

1
2
3
4
5
6
7
8
9
10
11
12
import os
import pinecone

# get api key from app.pinecone.io
PINECONE_API_KEY = os.environ.get('PINECONE_API_KEY') or 'PINECONE_API_KEY'
# find your environment next to the api key in pinecone console
PINECONE_ENV = os.environ.get('PINECONE_ENVIRONMENT') or 'PINECONE_ENVIRONMENT'

pinecone.init(
api_key=PINECONE_API_KEY,
environment=PINECONE_ENV
)

现在我们创建一个名为语义搜索的新索引。重要的是,我们要使索引维度和指标参数与 MiniLM-L6 模型所需的参数保持一致。

1
2
3
4
5
6
7
8
9
10
11
12
index_name = 'semantic-search-fast'

# only create index if it doesn't exist
if index_name not in pinecone.list_indexes():
pinecone.create_index(
name=index_name,
dimension=model.get_sentence_embedding_dimension(),
metric='cosine'
)

# now connect to the index
index = pinecone.GRPCIndex(index_name)

现在我们插入数据,我们将以 128 为一组进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from tqdm.auto import tqdm

batch_size = 128

for i in tqdm(range(0, len(questions), batch_size)):
# find end of batch
i_end = min(i+batch_size, len(questions))
# create IDs batch
ids = [str(x) for x in range(i, i_end)]
# create metadata batch
metadatas = [{'text': text} for text in questions[i:i_end]]
# create embeddings
xc = model.encode(questions[i:i_end])
# create records list for upsert
records = zip(ids, xc, metadatas)
# upsert to Pinecone
index.upsert(vectors=records)

# check number of records in the index
index.describe_index_stats()

进行查询

现在我们的索引已填充,我们可以开始进行查询。我们正在对类似问题进行语义搜索,因此我们应该嵌入并搜索另一个问题。让我们开始。

1
2
3
4
5
6
7
query = "which city has the highest population in the world?"

# create the query vector
xq = model.encode(query).tolist()

# now query
xc = index.query(xq, top_k=3, include_metadata=True)

这些结果显然非常相关。所有这些问题都与我们的问题具有完全相同的含义,或者是相关的。如果我们使用更复杂的语言,这会让问题变得更加困难,但只要我们的查询背后的“含义”保持相同,我们应该会看到类似的结果。

1
2
3
4
5
6
7
8
9
query = "which urban locations have the highest concentration of homo sapiens?"

# create the query vector
xq = model.encode(query).tolist()

# now query
xc = index.query(xq, top_k=3, include_metadata=True)
for result in xc['matches']:
print(f"{round(result['score'], 2)}: {result['metadata']['text']}")

0.64: What are the most populated cities in the world?

0.62: Which is the most populated city in the world.?

0.62: What is the most populated city in the world?

在这里,我们在查询中使用了完全不同的术语,并且与返回的文档有很大差异。我们用“城市”替换了“城市位置”,用“人口稠密”替换了“人类集中度”。

尽管有这些非常不同的术语,并且查询和返回的文档之间没有术语重叠——我们得到了高度相关的结果——这就是语义搜索的力量。

您可以继续在上面提出更多问题。完成后,删除索引以节省资源。

1
pinecone.delete_index(index_name)