使用 Qdrant 评估检索质量

时间:30分钟难度:中等

语义搜索管道的质量取决于其所使用的向量嵌入。如果你的模型无法正确表示输入数据,相似对象在向量空间中可能相距甚远。在这种情况下,搜索结果不尽如人意也就在意料之中了。然而,该过程中还有另一个组件也会降低搜索结果的质量,那就是 ANN(近似最近邻)算法本身。

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

嵌入质量

嵌入质量是一个单独的教程主题。简而言之,它通常通过基准测试来衡量和比较,例如 海量文本嵌入基准测试 (MTEB)。评估过程本身非常直接,基于人工构建的基础事实(ground truth)数据集。我们有一组查询以及预期对于每个查询应检索到的文档集。在评估过程中,我们取一个查询,在向量空间中找到最相似的文档,并将它们与基础事实进行比较。在这种设置下,寻找最相似文档的操作实现为全量 kNN 搜索,而不进行任何近似处理。因此,我们可以在不受 ANN 算法影响的情况下衡量嵌入本身的质量。

检索质量

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

质量指标

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

如果我们把搜索管道看作一个整体,我们可以使用上述所有指标。这对于嵌入质量评估同样适用。然而,对于 ANN 算法本身,任何基于相关性评分或排名的指标都不适用。向量搜索中的排序依赖于向量空间中查询与文档之间的距离,但由于函数保持不变,距离不会因为近似而改变。

因此,衡量 ANN 算法质量唯一合理的方式是查看 Top-k 搜索结果中的相关文档数量,例如 precision@k。它的计算方法是用 Top-k 搜索结果中相关文档的数量除以 k。在测试 ANN 算法时,我们可以将精确的 kNN 搜索作为基础事实,并将 k 固定。这将衡量 ANN 算法对精确搜索的近似程度

衡量搜索结果的质量

让我们在 Qdrant 中对 ANN 算法进行质量评估。首先,我们将以标准方式调用搜索接口以获得近似搜索结果。然后,我们将调用精确搜索接口以获得精确匹配结果,最后比较两者的准确率。

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

from datasets import load_dataset

dataset = load_dataset(
    "Qdrant/arxiv-titles-instructorxl-embeddings", split="train", streaming=True
)

我们需要一些数据用于索引,另一些数据用于测试。让我们获取前 50,000 条数据作为训练集,接下来的 1,000 条作为测试集。

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("https://: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 提供了内置的精确搜索模式,即使作为 CI/CD 管道的一部分,也可以自动用于衡量 ANN 算法本身的质量。

再次强调,嵌入质量是最重要的因素。HNSW 在精度方面表现良好,并且在需要时是可参数化和可调的。市面上还有其他一些 ANN 算法,例如 IVF*,但它们通常在质量和性能方面表现不如 HNSW

此页面有用吗?

感谢您的反馈!🙏

很遗憾听到这个消息。😔 你可以在 GitHub 上编辑此页面,或创建一个 GitHub 问题。