衡量和提升语义搜索中的检索质量

时间: 30 分钟级别: 中级

语义搜索管道的效果取决于它们使用的嵌入。如果你的模型不能正确表示输入数据,相似的对象可能在向量空间中相距甚远。在这种情况下,搜索结果不佳也就不足为奇了。然而,还有一个可能降低搜索结果质量的组件,那就是ANN算法本身。

在本教程中,我们将展示如何衡量语义检索的质量,以及如何调整Qdrant中使用的HNSW(近似最近邻算法)的参数,以获得最佳结果。

嵌入质量

嵌入的质量是一个单独教程的主题。简而言之,它通常通过基准测试来衡量和比较,例如 大规模文本嵌入基准测试 (MTEB)。评估过程本身相当 直接,基于人类构建的真实数据集。我们有一组查询和一组我们期望 为每个查询接收的文档。在评估过程中,我们取一个查询,在向量空间中找到最相似的文档并比较 它们与真实数据。在这种设置中,找到最相似的文档是通过完整的kNN搜索实现的,没有任何近似。 因此,我们可以测量嵌入本身的质量,而不受ANN算法的影响。

检索质量

嵌入质量确实是语义搜索质量中最重要的因素。然而,像Qdrant这样的向量搜索引擎并不执行纯kNN搜索。相反,它们使用近似最近邻(ANN)算法,这些算法比精确搜索快得多,但可能会返回次优结果。我们还可以测量这种近似的检索质量,这也有助于整体搜索质量。

质量指标

有多种方法可以量化语义搜索的质量。其中一些方法,如精度@k,基于前k个搜索结果中相关文档的数量。其他方法,如均值倒数排名 (MRR),则考虑了搜索结果中第一个相关文档的位置。DCG和NDCG指标则是基于文档的相关性评分。

如果我们将搜索管道视为一个整体,我们可以使用所有这些方法。对于嵌入质量评估也是如此。然而,对于ANN算法本身,任何基于相关性评分或排名的内容都不适用。向量搜索中的排名依赖于查询和文档在向量空间中的距离,然而由于近似,距离不会改变,因为函数仍然是相同的。

因此,仅通过前k个搜索结果中的相关文档数量来衡量ANN算法的质量是有意义的,例如precision@k。它的计算方法是前k个搜索结果中的相关文档数量除以k。在仅测试ANN算法的情况下,我们可以使用精确的kNN搜索作为基准,其中k是固定的。这将是一个衡量ANN算法如何近似精确搜索的指标。

衡量搜索结果的质量

让我们在Qdrant中构建一个ANN算法的质量评估。首先,我们将以标准方式调用搜索端点以获取近似搜索结果。然后,我们将调用精确搜索端点以获取精确匹配,最后在精度方面比较这两个结果。

在我们开始之前,让我们创建一个集合,填充一些数据,然后开始我们的评估。我们将使用与从Hugging Face hub加载数据集教程中相同的数据集,Qdrant/arxiv-titles-instructorxl-embeddings来自Hugging Face hub。我们将以流模式下载它,因为我们只会使用其中的一部分。

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

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=16ef_construct=100。让我们尝试将它们增加到 m=32ef_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

这个页面有用吗?

感谢您的反馈!🙏

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