混合搜索 Qdrant 集合

问题描述 投票:0回答:1

我只是想确保我正确遵循混合搜索的想法,从而遵循 qdrant 实现。

矢量化数据库上的混合搜索:对密集嵌入执行搜索和关键字匹配搜索,最后一项可以在稀疏向量上完成。

完成简单的定义后,我们可以在 Qdrant 中创建一个向量化数据库,并且由于 qdrant 不支持子字符串搜索,或者没有对此进行优化,因此我们创建一个包含密集和稀疏向量的集合,例如:

baseURL = "http://localhost:6333"
client = QdrantClient(url=baseURL)

collection_name = "example_collection"
client.create_collection(
    collection_name=collection_name,
    vectors_config={
        "text-dense": models.VectorParams(
            size=1536,  # OpenAI Embeddings
            distance=models.Distance.COSINE,
        )
    },
    sparse_vectors_config={
        "text-sparse": models.SparseVectorParams(
            index=models.SparseIndexParams(
                on_disk=False,
            )
        )
    },
)

因此,假设我们用以下内容更新了一些点:

points = [
    PointStruct(
        id=1,
        vector={
            "text-dense": [0.1] * 1536,  
            "text-sparse": models.SparseVector(indices=[6, 7], values=[1.0, 2.0]),
        },
        payload={"name": f"Example_{1}"},  
    )
]

client.upsert(
    collection_name=collection_name,
    wait=True,
    points=points,
)

我们现在可以执行混合搜索,这可以通过一种非常基本的方式来实现,例如对数据库进行两个独立的查询,一个用于密集嵌入,另一个用于稀疏嵌入,例如:

# Query example once treated with specific models.
user_query_dense_vector = [0.099] * 1536
user_query_sparse_vector = {6: 2.0, 7: 3.0}

# Dense search
dense_results = client.search(
    collection_name=collection_name,
    query_vector=("text-dense", user_query_dense_vector),
    with_vectors=True,
)

# Sparse search
sparse_results = client.search(
    collection_name=collection_name,
    query_vector=models.NamedSparseVector(
        name="text-sparse",
        vector=models.SparseVector(
            indices=list(user_query_sparse_vector.keys()),
            values=list(user_query_sparse_vector.values()),
        ),
    ),
    with_vectors=True,
)

假设 user_query_dense_vector 和 user_query_sparse_vector 是用户查询经过模型处理后的结果向量,我们可以执行融合方法,例如倒数排名融合(RRF):1 / (k +rank)。我们基本上迭代密集和稀疏的结果,并计算检索到的每个块的分数。

combined_scores = {}

for rank, point in enumerate(dense_results, start=1):
    if point.id not in combined_scores:
        combined_scores[point.id] = 0
    combined_scores[point.id] += 1 / (k + rank)

for rank, point in enumerate(sparse_results, start=1):
    if point.id not in combined_scores:
        combined_scores[point.id] = 0
    combined_scores[point.id] += 1 / (k + rank)

fused_results = sorted(combined_scores.items(), key=lambda x: x[1], reverse=True)

结果在哪里:

[(1, 0.03278688524590164)]

这基本上只返回第一个点,因为只有一个点。

现在的问题是,如果我们遵循 Qdrant 文档,他们使用预取方法来实现混合搜索,如果我们省略 Matryoshka 分支、第一个整数搜索(为了更快的检索)和最后一个后期交互重新排名,我们应该基本上得到与上面代码相同的结果,我们分别搜索然后融合它们。

HybridPipline

sparse_dense_rrf_prefetch = models.Prefetch(
    prefetch=[
        models.Prefetch(
            query=user_query_dense_vector,
            using="text-dense",
            limit=25,
        ),
        models.Prefetch(
            query=models.SparseVector(
                indices=list(user_query_sparse_vector.keys()),
                values=list(user_query_sparse_vector.values()),
            ),
            using="text-sparse",
            limit=25,
        ),
    ],
    # RRF fusion
    query=models.FusionQuery(
        fusion=models.Fusion.RRF,
    ),
)

client.query_points(
    collection_name=collection_name,
    prefetch=[sparse_dense_rrf_prefetch],
    query=user_query_dense_vector,
    using="text-dense", 
    with_payload=True,
    limit=10,  
)

显然,它返回数据库中的唯一点。

QueryResponse(points=[ScoredPoint(id=1, version=0, score=0.9999997, payload={'name': 'point_1'}, vector=None, shard_key=None, order_value=None)])

我想我期待的分数与上面从 RRF 实现的分数类似,而不是查询的稀疏向量与稀疏向量之间的余弦相似度,这正是我通过稀疏搜索实现的结果 (sparse_results) .

不知道为什么,但我觉得有点不对劲。是否有人实现了类似的东西并且可以证实预取确实正确实现了?

提前谢谢您!

python database rag qdrant
1个回答
0
投票

您提供的代码有两个问题。

首先,应该改变k值。您没有共享定义 k 的代码部分,但您似乎正在使用 k = 60。应改为 k = 2

其次,您正在将 rank 初始化为 1,而它应该在 0 初始化。

要进行这些更改,请使用以下代码:

combined_scores = {}
k = ?????
for rank, point in enumerate(dense_results, start=1):
    if point.id not in combined_scores:
        combined_scores[point.id] = 0
    combined_scores[point.id] += 1 / (k + rank)

for rank, point in enumerate(sparse_results, start=1):
    if point.id not in combined_scores:
        combined_scores[point.id] = 0
    combined_scores[point.id] += 1 / (k + rank)

fused_results = sorted(combined_scores.items(), key=lambda x: x[1], reverse=True)

并将其更改为:

combined_scores = {}
k = 2
for rank, point in enumerate(dense_results):
    if point.id not in combined_scores:
        combined_scores[point.id] = 0
    combined_scores[point.id] += 1 / (k + rank)

for rank, point in enumerate(sparse_results):
    if point.id not in combined_scores:
        combined_scores[point.id] = 0
    combined_scores[point.id] += 1 / (k + rank)
© www.soinside.com 2019 - 2024. All rights reserved.