衡量和改进语义搜索中的检索质量
时间:30 分钟 | 级别:中级 |
---|
语义搜索管道的质量取决于它们使用的嵌入。如果你的模型无法正确表示输入数据,那么相似的对象在向量空间中可能会相距很远。在这种情况下,搜索结果会很差,这并不奇怪。然而,过程中还有一个组件也会降低搜索结果的质量,那就是 ANN 算法本身。
在本教程中,我们将展示如何衡量语义检索的质量,以及如何调整 Qdrant 中使用的 ANN 算法 HNSW 的参数以获得最佳结果。
嵌入质量
嵌入的质量是一个单独教程的主题。简而言之,它通常通过基准测试来衡量和比较,例如 大规模文本嵌入基准测试 (MTEB)。评估过程本身非常简单,并且基于人工构建的真实数据集。我们有一组查询和一组我们期望为每个查询接收到的文档。在评估过程中,我们接收一个查询,在向量空间中找到最相似的文档,并将其与真实数据进行比较。在这种设置下,查找最相似的文档是作为完整的 kNN 搜索实现的,没有任何近似。因此,我们可以衡量嵌入本身的质量,而不受 ANN 算法的影响。
检索质量
嵌入质量确实是语义搜索质量中最关键的因素。然而,像 Qdrant 这样的向量搜索引擎并不执行纯粹的 kNN 搜索。相反,它们使用近似最近邻 (ANN) 算法,这种算法比精确搜索快得多,但可能返回次优结果。我们也可以衡量该近似方法的检索质量,这也对整体搜索质量有所贡献。
质量指标
有多种方法可以量化语义搜索的质量。其中一些,例如 Precision@k,基于在前 k 个搜索结果中相关文档的数量。其他的,例如 平均倒数排名 (MRR),则考虑了搜索结果中第一个相关文档的位置。而 DCG 和 NDCG 指标则基于文档的相关性得分。
如果我们把搜索管道作为一个整体来看待,我们可以使用所有这些指标。嵌入质量评估也是如此。然而,对于 ANN 算法本身来说,任何基于相关性得分或排名的指标都不适用。向量搜索中的排名依赖于查询和文档在向量空间中的距离,但由于近似,距离不会改变,因为函数仍然相同。
因此,通过在前 k 个搜索结果中相关文档的数量来衡量 ANN 算法的质量才有意义,例如 precision@k
。它的计算方法是将前 k 个搜索结果中相关文档的数量除以 k
。在仅测试 ANN 算法的情况下,我们可以使用精确的 kNN 搜索作为真实数据,其中 k
是固定的。这将衡量 ANN 算法对精确搜索的近似程度。
衡量搜索结果的质量
让我们构建一个对 Qdrant 中 ANN 算法的质量评估。首先,我们将以标准方式调用搜索端点以获得近似搜索结果。然后,我们将调用精确搜索端点以获得精确匹配,最后比较两者的精度结果。
在开始之前,让我们创建一个集合,填充一些数据,然后开始我们的评估。我们将使用与从 Hugging Face hub 加载数据集教程中相同的数据集,来自Hugging Face hub的 Qdrant/arxiv-titles-instructorxl-embeddings
。我们将以流模式下载它,因为我们只打算使用其中的一部分。
from datasets import load_dataset
dataset = load_dataset(
"Qdrant/arxiv-titles-instructorxl-embeddings", split="train", streaming=True
)
我们需要一些数据用于索引,另一组数据用于测试。让我们获取前 50000 个项目用于训练,接下来的 1000 个用于测试。
dataset_iterator = iter(dataset)
train_dataset = [next(dataset_iterator) for _ in range(60000)]
test_dataset = [next(dataset_iterator) for _ in range(1000)]
现在,让我们创建一个集合并索引训练数据。这个集合将使用默认配置创建。请注意,它可能与你的集合设置不同,并且始终重要的是测试你在生产中将要使用的完全相同的配置。
from qdrant_client import QdrantClient, models
client = QdrantClient("http://localhost:6333")
client.create_collection(
collection_name="arxiv-titles-instructorxl-embeddings",
vectors_config=models.VectorParams(
size=768, # Size of the embeddings generated by InstructorXL model
distance=models.Distance.COSINE,
),
)
我们现在准备好索引训练数据。上传记录将触发索引过程,该过程将构建 HNSW 图。索引过程可能需要一些时间,具体取决于数据集的大小,但你的数据在收到 upsert
端点的响应后即可立即用于搜索。只要索引尚未完成,并且 HNSW 未构建,Qdrant 将执行精确搜索。我们必须等到索引完成后,才能确定执行的是近似搜索。
client.upload_points( # upload_points is available as of qdrant-client v1.7.1
collection_name="arxiv-titles-instructorxl-embeddings",
points=[
models.PointStruct(
id=item["id"],
vector=item["vector"],
payload=item,
)
for item in train_dataset
]
)
while True:
collection_info = client.get_collection(collection_name="arxiv-titles-instructorxl-embeddings")
if collection_info.status == models.CollectionStatus.GREEN:
# Collection status is green, which means the indexing is finished
break
标准模式 vs 精确搜索
Qdrant 内置了一个精确搜索模式,可用于衡量搜索结果的质量。在此模式下,Qdrant 对每个查询执行完整的 kNN 搜索,没有任何近似。它不适用于高负载的生产环境,但非常适合评估 ANN 算法及其参数。可以通过在搜索请求中将 exact
参数设置为 True
来触发此模式。我们将简单地使用测试数据集中的所有示例作为查询,并比较近似搜索结果与精确搜索结果。让我们创建一个辅助函数,其中 k
是一个参数,这样我们就可以计算不同 k
值的 precision@k
。
def avg_precision_at_k(k: int):
precisions = []
for item in test_dataset:
ann_result = client.query_points(
collection_name="arxiv-titles-instructorxl-embeddings",
query=item["vector"],
limit=k,
).points
knn_result = client.query_points(
collection_name="arxiv-titles-instructorxl-embeddings",
query=item["vector"],
limit=k,
search_params=models.SearchParams(
exact=True, # Turns on the exact search mode
),
).points
# We can calculate the precision@k by comparing the ids of the search results
ann_ids = set(item.id for item in ann_result)
knn_ids = set(item.id for item in knn_result)
precision = len(ann_ids.intersection(knn_ids)) / k
precisions.append(precision)
return sum(precisions) / len(precisions)
计算 precision@5
就像调用带有相应参数的函数一样简单
print(f"avg(precision@5) = {avg_precision_at_k(k=5)}")
响应
avg(precision@5) = 0.9935999999999995
正如我们所见,近似搜索与精确搜索的精度相当高。然而,在某些场景下,我们需要更高的精度,并且可以接受更高的延迟。HNSW 是高度可调的,我们可以通过更改其参数来提高精度。
调整 HNSW 参数
HNSW 是一种分层图,其中每个节点都有一组指向其他节点的链接。每个节点的边数称为 m
参数。它的值越大,搜索精度越高,但需要更多空间。ef_construct
参数是在构建索引期间考虑的邻居数量。同样,值越大,精度越高,但索引时间越长。这些参数的默认值为 m=16
和 ef_construct=100
。让我们尝试将它们增加到 m=32
和 ef_construct=200
,看看它如何影响精度。当然,我们需要等到索引完成后才能执行搜索。
client.update_collection(
collection_name="arxiv-titles-instructorxl-embeddings",
hnsw_config=models.HnswConfigDiff(
m=32, # Increase the number of edges per node from the default 16 to 32
ef_construct=200, # Increase the number of neighbours from the default 100 to 200
)
)
while True:
collection_info = client.get_collection(collection_name="arxiv-titles-instructorxl-embeddings")
if collection_info.status == models.CollectionStatus.GREEN:
# Collection status is green, which means the indexing is finished
break
可以使用相同的函数计算平均 precision@5
print(f"avg(precision@5) = {avg_precision_at_k(k=5)}")
响应
avg(precision@5) = 0.9969999999999998
精度显然提高了,我们也知道如何控制它。然而,精度与搜索延迟和内存需求之间存在权衡。在某些特定情况下,我们可能希望尽可能提高精度,现在我们知道如何做到这一点了。
总结
评估检索质量是评估语义搜索性能的关键方面。在追求最佳搜索结果质量时,衡量检索质量至关重要。Qdrant 提供了一个内置的精确搜索模式,可用于衡量 ANN 算法本身的质量,甚至可以作为 CI/CD 管道的一部分进行自动化。
再次强调,嵌入的质量是最重要的因素。HNSW 在精度方面表现相当出色,并且在需要时可以参数化和调整。还有一些其他可用的 ANN 算法,例如 IVF*,但它们在质量和性能方面通常不如 HNSW。