语义检索效果差?深度学习rerank VS 统计rerank选哪个

eb00a2fb34c7bc1bcf732b1762abf4b2.png

646f62e2d61ce7f81c7aaeb2c99824da.png

前段时间我开发了一个用白话文搜索语义相近的古诗词的应用(详见:《朋友圈装腔指南:如何用向量数据库把大白话变成古诗词》),但是有时候搜索结果却不让人满意,排名靠前的结果和查询的语义没啥关系,靠后的结果反而和查询更相似。比如,我用白话文“今天的雨好大”搜索,前三个结果是:

今日云景好,水绿秋山明。

今日风日好,明日恐不如。

雨落不上天,水覆难再收。

前两个都和雨没有关系,第三个勉强沾边。

为啥语义更相近的句子,反而排名靠后呢?主要有两个原因,一个是“不理解”,另一个是“难精确”。

“不理解”和嵌入模型有关。我使用的嵌入模型可能训练语料中古诗词较少,导致它不能很好地“理解”古诗词的语义。

“难精确”指的是不论你的度量方法使用的是余弦相似度(Cosine),还是欧几里得距离(L2),都不能保证语义最相似的结果一定排在第一。这些方法都是简化的模型,句子的语义内涵很难只用中学数学知识就能准确计算,只能说在整体趋势上,得分越高的结果语义和查询越接近。这就好像深圳入冬后,我们预测温度在10-20°C之间,这样的预测整体来说是正确的,但是具体到每一天的温度就不一定准确了,可能有那么一两天,温度升到了25°C。

这样的预测相当于语义搜索中的初步搜索,叫做“粗排”。想要优化搜索结果,重新排名,还需要“重排”,也就是 rerank。

除此之外,还有一种情况下也需要重排,那就是混合搜索。我在 《外行如何速成专家?Embedding之BM25、splade稀疏向量解读》 这篇文章中介绍了稀疏向量,稠密向量和稀疏向量各有优势,怎么各取所长呢?可以先分别搜索(也就是混合搜索),再用搜索结果综合起来,而重排就是一种综合多种搜索结果的方法。

这两种重排有所区别,第一种是基于深度学习的重排,第二种是基于统计的重排。第二种原理更简单,我们先来了解第二种。[^1]

01.

基于统计的重排

基于统计的重排用于混合搜索,它可以把多种搜索结果综合起来,重新排序。除了前面介绍的稠密向量和稀疏向量,还可以综合文本向量和图片向量。

怎么综合呢?有两种方法,一种是 WeightedRanker ——分数加权平均算法,通过设置权重计算得分,后面简称权重策略。另一种是 RRFRanker(Reciprocal Rank Fusion)——逆序排名融合算法,通过排名的倒数来计算得分,后面简称 RRF 策略。

1.1 权重策略

权重策略就是设置权重。权重值范围从0到1,数值越大表示重要性越大。计算方法很简单,初始得分乘以权重,就是最终得分。[^2]

81581c47c0b06e1aa55c5603008628f1.jpeg

打个比方,假设某班级考了语文和数学两门课,统计出学生每门科目的分数和排名。学生就相当于向量数据库中的文档,学生这两门课的分数,就相当于文档在不同搜索结果中的得分。

假设学生的成绩如下表所示:

147b3d209e059bb2a1577f85ca2cbf4c.png

在权重策略下,综合得分公式为:

数学成绩语文成绩

根据公式计算出学生们的综合分数,排名如下:

27ad422810c488d3d5a2a826091161c2.png

1.2 RRF 策略

RRF 策略的计算方式稍微复杂一点:

e4fff66869b3790c39894d6f9a832edf.jpeg

公式中的 rank 是初始分数的排名,k 是平滑参数。从公式中可以看出,排名越靠前,rank 的值越小,综合得分越高。同时, k 的值越大,排名对分数的影响越小。

我们使用 RRF 策略重新计算分数和排名。参数 k 一般为60,为方便演示,这里设为 10,公式变成:

6976b32baf8dd2036c0c82958d857a62.jpeg

RRF 策略根据排名计算分数,所以我们先列出数学和语文的排名。

数学成绩排名:

57bad0e5ded50d6ebf6b12b84380e3c8.png

语文成绩排名:

1cb02a90b3edc6ae60721faed9b010f3.png

接下来使用 RRF 策略计算综合得分,重新排名:

14d75023e434ce5817670894b57ec705.png

比较两个排名可以发现,在权重策略下,数学的权重较大,偏科学生 S1虽然语文只有50分,也能因为数学100分而排在第一名。而 RRF 策略注重的是各科的排名,而不是分数,所以 S1的数学虽然排名第一,但是语文排名第10,综合排名下降到第三。

学生 S7 正好相反,在权重策略下,即使他语文得了85的高分,但是权重只占30%,而高权重的数学只得了70分,所以综合排名靠后,排在第六名。在 RRF 策略下,他的数学和语文排名分别是第六名和第二名,语文的高排名拉高了综合排名,上升到了第一名。

通过比较两种策略的排名结果,我们发现了这样的规律,如果你更看重搜索结果的得分,就使用权重策略,你还可以通过调整权重来调整得分;如果你更看重搜索结果的排名,就使用RRF策略。

02.

基于深度学习的重排

和基于统计的重排相比,基于深度学习的重排更加复杂,通常被称为 Cross-encoder Reranker,交叉编码重排,后面简称“重排模型”。

粗排和重排模型有什么区别呢?粗排搜索速度更快,重排模型准确性更高。

为什么粗排搜索速快?粗排使用的是双塔模型(Dual-Encoder),“双塔”指的是它有两个独立的编码器,分别把查询和文档向量化,然后通过计算向量之间的相似度(比如余弦相似度Cosine)搜索结果并且排序。双塔模型的优势在于搜索效率高,因为可以提前计算文档向量,搜索时只需要向量化查询即可。而重排模型则是在搜索时现场编码。就好比两个饭店,一个使用预制菜,一个现场热炒,上菜速度肯定不一样。

重排模型的优势则是准确性高。它把查询和文档组成数据对后输入给编码器编码,然后给它们的相似程度打分,针对性强。这就相当于公司招聘人才,粗排是根据专业、学历和工作年限等几个指标快速筛选简历,挑选出多位候选者。重排则是通过面试详细了解候选者做过什么项目,遇到了什么挑战,解决了什么难题,然后判断他有多适合应聘的岗位(文档与查询有多相似)。

9c78bb10457dcc6a5b51f32ed89c43fb.png

图片来源:自制

所以,重排模型适合那些对回答准确性要求高的场景,比如专业知识库或者客服系统。不适合追求高响应速度和低成本的场景,比如网页搜索、电商,这种场景建议使用基于统计的重排。

你还可以把粗排和重排模型结合起来。比如,先通过粗排筛选出10个候选结果,再用重排模型重新排名。既可以提高搜索速度,也能保证准确度。

03.

代码实践

版本说明:

  • Milvus 版本:2.5.0

  • pymilvus:2.5.0

接下来我们通过代码实践一下,看看这些重排方法实际效果到底如何。

我们会使用“敏捷的狐狸跳过懒惰的狗。”作为查询,从下面10个句子中搜索出语义相似的句子。你可以先猜一猜,粗排、基于统计的重排以及基于深度学习的重排,哪个效果最好。

文档:

[
        {"content": "灵活的狐跳过了懒散的犬。"},
        {"content": "一只敏捷的狐在公园里跳过了那只懒犬。"},
        {"content": "那只懈怠的犬正在大树下睡觉。"},
        {"content": "在公园里,那只棕色的狐狸正在跳。"},
        {"content": "犬跃过了狐。"},
        {"content": "树下有一个小池塘。"},
        {"content": "动物如狗和狐狸生活在公园里。"},
        {"content": "池塘靠近公园里的大树。"},
        {"content": "懒狗跳过了狐狸。"},
        {"content": "那只灵巧的狐狸轻松地跨过了那只懒散的狗。"},
        {"content": "狐迅速地跳过了那只不活跃的犬。"}
]

首先创建集合。我们为集合设置稠密向量“dense_vector”和稀疏向量“sparse_vector”两个字段,分别储存稠密向量和稀疏向量,用于混合搜索。

from pymilvus import MilvusClient, DataType
import time

def create_collection(collection_name):
    # 检查同名集合是否存在,如果存在则删除
    if milvus_client.has_collection(collection_name):
        print(f"集合 {collection_name} 已经存在")
        try:
            # 删除同名集合
            milvus_client.drop_collection(collection_name)
            print(f"删除集合:{collection_name}")
        except Exception as e:
            print(f"删除集合时出现错误: {e}")
    # 创建集合模式
    schema = milvus_client.create_schema(
        auto_id=True,
        enable_dynamic_field=True,
        num_partitions=16,
        description=""
    )
    # 添加字段到schema
    schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, max_length=256)
    schema.add_field(field_name="content", datatype=DataType.VARCHAR, max_length=256)
    # 添加稠密向量字段
    schema.add_field(field_name="dense_vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
    # 添加稀疏向量字段
    schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
    # 创建集合
    try:
        milvus_client.create_collection(
            collection_name=collection_name,
            schema=schema,
            shards_num=2
        )
        print(f"创建集合:{collection_name}")
    except Exception as e:
        print(f"创建集合的过程中出现了错误: {e}")
    # 等待集合创建成功
    while not milvus_client.has_collection(collection_name):
        # 获取集合的详细信息
        time.sleep(1)
    if milvus_client.has_collection(collection_name):
        print(f"集合 {collection_name} 创建成功")

然后,定义把文档向量化的函数。我们使用 bge_m3 生成稠密向量和稀疏向量。

from tqdm import tqdm
import torch
from pymilvus.model.hybrid import BGEM3EmbeddingFunction

# 定义全局变量
bge_m3_ef = None

# 定义初始化嵌入模型实例的函数
def initialize_embedding_model() -> None:
    global bge_m3_ef
    # 检查是否有可用的CUDA设备
    device = "cuda:0" if torch.cuda.is_available() else "cpu"
    # 根据设备选择是否使用fp16
    use_fp16 = device.startswith("cuda")
    # 创建嵌入模型实例
    bge_m3_ef = BGEM3EmbeddingFunction(
        model_name="BAAI/bge-m3",
        device=device,
        use_fp16=use_fp16
    )

# 向量化查询
def vectorize_query(query: list) -> dict:
    global bge_m3_ef
    if bge_m3_ef is None:
        raise ValueError("嵌入模型未初始化,请先调用 initialize_embedding_model 函数。")
    # 把输入的文本向量化
    vectors = bge_m3_ef.encode_queries(query)
    return vectors

# 向量化文档
def vectorize_docs(docs: list) -> dict:
    global bge_m3_ef
    if bge_m3_ef is None:
        raise ValueError("嵌入模型未初始化,请先调用 initialize_embedding_model 函数。")
    # 把输入的文本向量化
    return bge_m3_ef.encode_documents(docs)

# 初始化嵌入模型实例
initialize_embedding_model()

接下来,生成向量并且导入向量数据库。

import json

def vectorize_and_import_data(
    input_file_path: str, 
    field_name: str, 
    batch_size: int = 1000) -> None:
    # 读取 json 文件,把指定字段的值向量化
    with open(input_file_path, 'r', encoding='utf-8') as file:
        data_list = json.load(file)
        # 提取该json文件中的所有指定字段的值
        docs = [data[field_name] for data in data_list]
    # 向量化docs,获取稠密向量和稀疏向量
    dense_vectors = vectorize_docs(docs)['dense']
    sparse_vectors = vectorize_docs(docs)['sparse']

    for data, dense_vector, sparse_vector in zip(data_list, dense_vectors, sparse_vectors):
        data['dense_vector'] = dense_vector.tolist()
        csr_matrix = sparse_vector.tocsr()
        sparse_dict = {int(idx): float(val) for idx, val in zip(csr_matrix.indices, csr_matrix.data)}
        data['sparse_vector'] = sparse_dict
    print(f"正在将数据插入集合:{collection_name}")
    total_count = len(data_list)
    with tqdm(total=total_count, desc="插入数据") as pbar:
        # 每次插入 batch_size 条数据
        for i in range(0, total_count, batch_size):  
            batch_data = data_list[i:i + batch_size]
            res = milvus_client.insert(
                collection_name=collection_name,
                data=batch_data
            )
            pbar.update(len(batch_data))

input_file_path = "docs_rank.json"
field_name = "content"
vectorize_and_import_data(input_file_path, field_name, embed_model)

数据入库后,为它们创建索引。因为数据库中同时包含了两个向量,所以使用混合搜索,需要分别创建稠密向量和稀疏向量的索引。

index_params = milvus_client.prepare_index_params()

# 创建密集向量索引参数
index_params.add_index(
    index_name="IVF_FLAT",
    field_name="dense_vector",
    index_type="IVF_FLAT",
    metric_type="COSINE",
    params={"nlist": 128}
)

# 创建稀疏向量索引参数
index_params.add_index(
    index_name="sparse",
    field_name="sparse_vector",
    index_type="SPARSE_INVERTED_INDEX",
    # 目前仅支持IP
    metric_type="IP",
    params={"drop_ratio_build": 0.2}
)

# 创建索引
milvus_client.create_index(
    collection_name=collection_name,  
    index_params=index_params   
)

# 查看创建的索引
print(milvus_client.list_indexes(collection_name))

加载集合。

print(f"正在加载集合:{collection_name}")
milvus_client.load_collection(collection_name=collection_name)

# 验证加载状态
print(milvus_client.get_load_state(collection_name=collection_name))

为了实现混合搜索,还需要定义混合搜索函数。

# 混合搜索
from pymilvus import AnnSearchRequest, WeightedRanker, RRFRanker

def perform_hybrid_search(
    collection_name: str, 
    query: list, 
    ranker,
    output_fields: list,
    limit_dense: int = 20,
    limit_sparse: int = 20,
    limit_hybrid: int = 10
    ) -> dict:
    # 获取查询的稠密向量和稀疏向量
    query_vector = vectorize_query(query)
    query_dense_vector = [query_vector['dense'][0].tolist()]
    query_sparse_vector = [query_vector['sparse'][[0]].tocsr()]
    # 创建稠密向量的搜索参数
    dense_search_params = {
        # 查询向量
        "data": query_dense_vector,  
        "anns_field": "dense_vector",
        "param": {
            "metric_type": "COSINE",
            "params": {
                "nprobe": 16,
                "radius": 0.1,
                "range_filter": 1
            }
        },
        "limit": limit_dense
    }
    # 创建稠密向量的搜索请求
    dense_req = AnnSearchRequest(**dense_search_params)
    
    # 创建稀疏向量的搜索参数
    sparse_search_params = {
        "data": query_sparse_vector,
        "anns_field": "sparse_vector",
        "param": {
            "metric_type": "IP",
            "params": {"drop_ratio_search": 0.2}
        },
        "limit": limit_sparse
    }
    # 创建稀疏向量的搜索请求
    sparse_req = AnnSearchRequest(**sparse_search_params)
    # 执行混合搜索
    start_time = time.time()
    res = milvus_client.hybrid_search(
        collection_name=collection_name,
        reqs=[dense_req, sparse_req],
        ranker = ranker,
        limit=limit_hybrid,
        output_fields=output_fields
    )
    end_time = time.time()
    total_time = end_time - start_time
    print(f"搜索时间:{total_time:.3f}")
    return res

最后再定义一个打印函数,方便查看搜索结果。

def print_vector_results(res):   
    for hits in res:
        for hit in hits:
            entity = hit.get("entity")
            print(f"content: {entity['content']}")
            print(f"distance: {hit['distance']:.4f}")
            print("-"*50)
        print(f"数量:{len(hits)}")

04.

对比搜索结果

准备工作就绪,先分别看下稠密向量和稀疏向量的搜索结果。在混合搜索的权重策略下,调整权重,一个设置1,另一个设置为0,就可以只查看一种搜索结果。

query = ["敏捷的狐狸跳过懒惰的狗。"]
ranker=WeightedRanker(1, 0)
output_fields = ["content"]
limit_dense = 10
limit_sparse = 10
limit_hybrid = 10

res_dense = perform_hybrid_search(collection_name, query, ranker, output_fields, limit_dense, limit_sparse, limit_hybrid)
print_vector_results(res_dense)

稠密向量的搜索结果勉强及格,正确答案分别排在第一、第三、第四和第五。让人不满意的是,语义和查询完全相反的句子,却排在了第二和第六,而且前6个搜索结果的得分相差很小,区别不明显。

另外,留意一下搜索时间是0.012秒,后面要和基于深度学习的重排做比较。

搜索时间:0.012
content: 灵活的狐跳过了懒散的犬。
distance: 0.9552
--------------------------------------------------
content: 懒狗跳过了狐狸。
distance: 0.9444
--------------------------------------------------
content: 一只敏捷的狐在公园里跳过了那只懒犬。
distance: 0.9373
--------------------------------------------------
content: 那只灵巧的狐狸轻松地跨过了那只懒散的狗。
distance: 0.9366
--------------------------------------------------
content: 狐迅速地跳过了那只不活跃的犬。
distance: 0.9194
--------------------------------------------------
content: 犬跃过了狐。
distance: 0.9025
--------------------------------------------------
content: 在公园里,那只棕色的狐狸正在跳。
distance: 0.8456
--------------------------------------------------
content: 动物如狗和狐狸生活在公园里。
distance: 0.8303
--------------------------------------------------
content: 那只懈怠的犬正在大树下睡觉。
distance: 0.7702
--------------------------------------------------
content: 树下有一个小池塘。
distance: 0.7174
--------------------------------------------------
数量:10

调整权重,再来看看稀疏向量的结果。

ranker=WeightedRanker(0, 1)
res_sparse = perform_hybrid_search(collection_name, query, ranker, output_fields, limit_dense, limit_sparse, limit_hybrid)
print_vector_results(res_sparse)

稀疏向量的搜索结果就更差了,正确答案分别排在第二、第三、第六和第七。这是因为我特意用语义相近但是文本不同的词做了替换,比如用“犬”代替“狗”,“懈怠”代替“懒”,导致它们较难命中查询中的词,得分较低。如果你想了解稀疏向量是如何参与搜索并且计算得分的,可以看看 《外行如何速成专家?Embedding之BM25、splade稀疏向量解读》 这篇文章。

搜索时间是0.014秒,和稠密向量相当。

搜索时间:0.014
content: 懒狗跳过了狐狸。
distance: 0.5801
--------------------------------------------------
content: 那只灵巧的狐狸轻松地跨过了那只懒散的狗。
distance: 0.5586
--------------------------------------------------
content: 一只敏捷的狐在公园里跳过了那只懒犬。
distance: 0.5553
--------------------------------------------------
content: 在公园里,那只棕色的狐狸正在跳。
distance: 0.5502
--------------------------------------------------
content: 动物如狗和狐狸生活在公园里。
distance: 0.5476
--------------------------------------------------
content: 灵活的狐跳过了懒散的犬。
distance: 0.5441
--------------------------------------------------
content: 狐迅速地跳过了那只不活跃的犬。
distance: 0.5336
--------------------------------------------------
content: 犬跃过了狐。
distance: 0.5192
--------------------------------------------------
content: 那只懈怠的犬正在大树下睡觉。
distance: 0.5006
--------------------------------------------------
content: 树下有一个小池塘。
distance: 0.0000
--------------------------------------------------
数量:10

接下来是重点了,我们分别使用权重策略和 RRF 策略,看看重排后的结果如何。

先来看看权重策略中,权重是如何影响综合得分的。我们给稠密向量设置更高的权重——0.8,稀疏向量的权重则设置为0.2。

ranker=WeightedRanker(0.8, 0.2)
res_Weighted = perform_hybrid_search(collection_name, query, ranker, output_fields, limit_dense, limit_sparse, limit_hybrid)
print_vector_results(res_Weighted)

综合排名第一的结果“灵活的狐跳过了懒散的犬。”,在稠密向量中的得分是0.9552,排名也是第一,与第二名相差0.108。

它在稀疏向量中的得分是0.5441,排名第六。虽然排名低,但是得分与第一名只差0.036分,而且权重只占0.2,对综合得分仍然是第一。因为稠密向量的权重高,综合排名基本和稠密向量的排名一致。

搜索时间是0.022秒。

搜索时间:0.022
content: 灵活的狐跳过了懒散的犬。
distance: 0.8730
--------------------------------------------------
content: 懒狗跳过了狐狸。
distance: 0.8716
--------------------------------------------------
content: 那只灵巧的狐狸轻松地跨过了那只懒散的狗。
distance: 0.8610
--------------------------------------------------
content: 一只敏捷的狐在公园里跳过了那只懒犬。
distance: 0.8609
--------------------------------------------------
content: 狐迅速地跳过了那只不活跃的犬。
distance: 0.8423
--------------------------------------------------
content: 犬跃过了狐。
distance: 0.8259
--------------------------------------------------
content: 在公园里,那只棕色的狐狸正在跳。
distance: 0.7865
--------------------------------------------------
content: 动物如狗和狐狸生活在公园里。
distance: 0.7738
--------------------------------------------------
content: 那只懈怠的犬正在大树下睡觉。
distance: 0.7163
--------------------------------------------------
content: 树下有一个小池塘。
distance: 0.5739
--------------------------------------------------
数量:10

接下来,我们来看看权重策略下的第一名,在 RRF 策略中表现如何。

ranker = RRFRanker(k=10)
res_rrf = perform_hybrid_search(collection_name, query, ranker, output_fields, limit_dense, limit_sparse, limit_hybrid)
print_vector_results(res_rrf)

“灵活的狐跳过了懒散的犬。”在 RRF 策略中的排名从第一下滑到了第四。因为这次注重的是排名,它在稠密向量中虽然排名第一,但是在稀疏向量中的排名只有第六,拉低了综合排名。

排名第一是“懒狗跳过了狐狸。”,因为它在两个搜索结果中的排名都很高,分别是第二和第一。

搜索时间是0.022秒,和权重策略的搜索时间差不多。

搜索时间:0.022
content: 懒狗跳过了狐狸。
distance: 0.1742
--------------------------------------------------
content: 那只灵巧的狐狸轻松地跨过了那只懒散的狗。
distance: 0.1548
--------------------------------------------------
content: 一只敏捷的狐在公园里跳过了那只懒犬。
distance: 0.1538
--------------------------------------------------
content: 灵活的狐跳过了懒散的犬。
distance: 0.1534
--------------------------------------------------
content: 在公园里,那只棕色的狐狸正在跳。
distance: 0.1303
--------------------------------------------------
content: 狐迅速地跳过了那只不活跃的犬。
distance: 0.1255
--------------------------------------------------
content: 动物如狗和狐狸生活在公园里。
distance: 0.1222
--------------------------------------------------
content: 犬跃过了狐。
distance: 0.1181
--------------------------------------------------
content: 那只懈怠的犬正在大树下睡觉。
distance: 0.1053
--------------------------------------------------
content: 树下有一个小池塘。
distance: 0.0500
--------------------------------------------------
数量:10

终于,轮到我们最期待的重排模型上场了。其实,因为返回的搜索结果数量和文档中的句子数量相同,对任何一个搜索结果重排,或者直接对文档重排,效果都是一样的。不过,为了和实际应用中的粗排、重排流程一致,我们还是对粗排结果重排,比如稀疏向量的搜索结果。

首先,我们要以字符串列表的形式,获取稀疏向量的搜索结果,以满足重排模型的输入要求。

# 获取稀疏向量的搜索结果
def get_init_res_list(res, field_name):
    res_list = []
    for hits in res:
        for hit in hits:
            entity = hit.get("entity")
            res_list.append(entity[field_name])
    return res_list

# 为了显示重排的效果,我们对搜索结果最差的稀疏向量做重排
init_res_list = get_init_res_list(res_sparse, field_name)

接下来,定义重排模型。这里使用的是 bge_m3的重排模型。

from pymilvus.model.reranker import BGERerankFunction

# 定义重排函数
bge_rf = BGERerankFunction(
    model_name="BAAI/bge-reranker-v2-m3",
    device="cpu"
)

def perform_reranking(query: str, documents: list, top_k: int = 10) -> list:
    # 获取重排结果
    start_time = time.time()
    rerank_res = bge_rf(
        # query参数是字符串
        query=query[0],
        # documents参数是字符串列表
        documents=documents,
        top_k=top_k,
    )
    end_time = time.time()
    total_time = end_time - start_time
    print(f"搜索时间:{total_time:.3f}")
    
    return rerank_res

top_k = 10
rerank_res = perform_reranking(query, init_res_list, top_k)

前面我提到过重排模型会花更多的时间,我们先对比下时间。第一次使用重排模型花了3.2秒,后面再使用一般用时0.4秒,这可能是因为第一次需要加载重排模型到内存中,花的时间较多。所以我们按照用时0.4秒计算。

基于统计的重排用时在0.014-0.022秒之间,按照最慢的0.022秒计算。两者时间相差18倍。

重排模型多花了这么多时间,效果怎么样呢?打印搜索结果看看吧。

for hit in rerank_res:
    print(f"content: {hit.text}")
    print(f"score: {hit.score:.4f}")
    print("-"*50)

我对重排结果还是比较满意的。四个正确答案排在前四名,而且得分非常接近满分1分。而且,它们和其他搜索结果在得分上终于拉开了较大的差距。

content: 灵活的狐跳过了懒散的犬。
score: 0.9998
--------------------------------------------------
content: 狐迅速地跳过了那只不活跃的犬。
score: 0.9997
--------------------------------------------------
content: 那只灵巧的狐狸轻松地跨过了那只懒散的狗。
score: 0.9987
--------------------------------------------------
content: 一只敏捷的狐在公园里跳过了那只懒犬。
score: 0.9980
--------------------------------------------------
content: 犬跃过了狐。
score: 0.3730
--------------------------------------------------
content: 懒狗跳过了狐狸。
score: 0.2702
--------------------------------------------------
content: 在公园里,那只棕色的狐狸正在跳。
score: 0.1924
--------------------------------------------------
content: 动物如狗和狐狸生活在公园里。
score: 0.0972
--------------------------------------------------
content: 那只懈怠的犬正在大树下睡觉。
score: 0.0059
--------------------------------------------------
content: 树下有一个小池塘。
score: 0.0000
--------------------------------------------------

05.

总结

通过对比我们发现,基于统计的重排速度快,准确性一般,适合追求高响应速度和低成本的场景,比如网页搜索、电商。

它有权重和 RRF 两个策略。如果你更看重某种类型的搜索结果,建议使用权重策略。如果你没有明显的偏好,希望在不同搜索结果中,排名都靠前的结果能够胜出,建议使用 RRF 策略。

基于深度学习的重排速度慢,但是准确性高,适合对回答准确性要求高的场景,比如专业知识库或者客服系统。

藏宝图

如果你还想了解更多重排的知识,可以参考下面的文章:

  • 一文玩转 Milvus 新特性之 Hybrid Search 

  • 提高 RAG 应用准确度,时下流行的 Reranker 了解一下?(https://zilliz.com.cn/blog/rag-reranker-therole-and-tradeoffs)

  • Rerankers Overview (https://milvus.io/docs/rerankers-overview.md)

  • BGE重排模型在Milvus中的使用 (https://milvus.io/docs/rerankers-bge.md)

  • Cross Encoder 重排模型在 Milvus 中的使用 (https://milvus.io/docs/rerankers-cross-encoder.md)

  • BGE 重排模型-github (https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/llm_reranker#model-list)

参考

[^1]: 一文玩转 Milvus 新特性之 Hybrid Search (https://zilliz.com.cn/blog/Hybrid-Search)

[^2]: Reranking (https://milvus.io/docs/reranking.md#Weighted-Scoring-WeightedRanker)

代码获取链接: https://pan.baidu.com/s/10dppfvaRPCX9mJmTa6QGHg?pwd=1234 提取码: 1234

作者介绍

588660d787f46a5c80839217e6cfcfe1.jpeg

Zilliz 黄金写手:江浩

推荐阅读

887322780aa0a85fac2f0df077e11f2e.png

6a3abe1aa87dd0ffdf39bcd4bf99792e.png

f05136e6fed0e4696c4ba795eb47faab.png

1ca437a24a34f031f4679b7d9b8c2b8e.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/954816.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux 常用命令 - chmod 【改变文件或目录权限】

简介 “chmod” 这个命令来自于 “change mode” 的缩写,用于更改文件或目录的访问权限。这个命令允许用户设定谁可以读取、写入或执行一个文件。在 Linux 和其他类 Unix 系统中,文件权限对系统安全和用户隐私至关重要。 Linux/Unix 的文件调用权限分为…

基于机器学习的电信用户流失预测与数据分析可视化

完整源码项目包获取→点击文章末尾名片! 背景描述 根据IBM商业社区分享团队描述,该数据集为某电信公司在加利福尼亚为7000余位用户(个人/家庭)提供电话和互联网服务的相关记录。描述用户基本情况,包括每位用户已注册的…

【Sql递归查询】Mysql、Oracle、SQL Server、PostgreSQL 实现递归查询的区别与案例(详解)

文章目录 Mysql 5.7 递归查询Mysql 8 实现递归查询Oracle递归示例SQL Server 递归查询示例PostgreSQL 递归查询示例 更多相关内容可查看 Mysql 5.7 递归查询 MySQL 5.7 本身不直接支持标准 SQL 中的递归查询语法(如 WITH RECURSIVE 这种常见的递归查询方式&#xf…

接上篇基于Alertmanager 配置钉钉告警

Alertmanager 是一个用于处理和管理 Prometheus 警报的开源工具。它负责接收来自 Prometheus 服务器的警报,进行去重、分组、静默、抑制等操作,并通过电子邮件、PagerDuty、Slack 等多种渠道发送通知。 主要功能 去重:合并相同或相似的警报&…

Qt应用之MDI(多文档设计)

qt creator 版本6.8.0 MinGW 64bit 由此模块可以扩展成设计一个qt文本编辑器。 界面如下 部分功能展示如下 新建文件 打开文件 mdi模式、级联模式和平铺模式 界面和程序构建过程。 1.如图所需.cpp和.h文件 2.mainwindow.ui和tformdoc.ui界面布局如下 不懂什么是Action如何…

软件授权管理中的软件激活向导示例

软件激活向导示例 在软件许可中,提供许可应该是简单和安全的。这适用于想要在中央许可证服务器上创建新许可证的软件开发人员,也适用于需要在其设备上获得许可证的最终用户。如果所讨论的系统有互联网连接,或是暂时的连接,就可以…

GB44495-2024 汽车整车信息安全技术要求 - V2X部分前置要求

背景 GB 44495-2024《汽车整车信息安全技术要求》中关于V2X(车与外界通信)的部分,主要关注于通信安全要求,旨在确保车辆在与外部设备进行数据交互时的信息安全。其测试大致可分为消息层(数据无异常)、应用…

phpstudy靶场搭建问题

前言: 靶场搭建遇到的问题,记录一下,可能是基础不牢吧,老是遇到奇奇怪怪的问题 思路: 跟着网上的搭建走一遍 内容: 目录 搭建pikachu遇到的问题 搭建pikachu遇到的问题 其实并不是第一次搭建&#x…

【Excel】【VBA】双列排序:坐标从Y从大到小排列之后相同Y坐标的行再对X从小到大排列

Excel VBA 双列排序 功能概述 这段VBA代码实现了Excel中的双列排序功能,具体是: 跳过前3行表头先按C列数据从大到小排序在C列值相同的情况下,按B列从大到小排序排序时保持整行数据的完整性 流程图 #mermaid-svg-XJERemQluZlM4K8l {font-fa…

Hive SQL必刷练习题:留存率问题

首次登录算作当天新增,第二天也登录了算作一日留存。可以理解为,在10月1号登陆了。在10月2号也登陆了,那这个人就可以算是在1号留存 今日留存率 (今日登录且明天也登录的用户数) / 今日登录的总用户数 * 100% 解决思…

C++基础入门(二)

目录 前言 一、重载 1.函数重载 2.运算符重载 二、构造函数 1.什么是构造函数 2.带参数的构造函数 3.使用初始化列表 4.this关键字 5.new关键字 三、析构函数 1.什么是析构函数 四、静态成员变量 1.静态成员的定义 2.静态成员变量的作用 五、继承 1.继承基本概…

Redis 中 TTL 的基本知识与禁用缓存键的实现策略(Java)

目录 前言1. 基本知识2. Java代码 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 单纯学习Redis可以看我前言的Java基本知识路线!! 对于Java的基本知识推荐阅读: java框架…

基于unity的多人家装应用的设计与实现

摘要 本课题根据主流家装应用存在的问题和结合了Unity3D引擎所具有的优势,在主流家装应用的基础上弥补了常见的缺憾,实现了一种新型的交互更强的家装展示系统。 本系统主要通过将家具模型资源和材质等资源导入Unity3D平台中,通过C#代码开发&a…

Three.js+Vue3+Vite应用lil-GUI调试开发3D效果(三)

前期文章中我们完成了创建第一个场景、添加轨道控制器的功能,接下来我们继续阐述其他的功能,本篇文章中主要讲述如何应用lil-GUI调试开发3D效果,在开始具体流程和步骤之前,请先查看之前的内容,因为该功能必须在前期内容…

采用海豚调度器+Doris开发数仓保姆级教程(满满是踩坑干货细节,持续更新)

一、采用海豚调度器Doris开发平替CDH Hdfs Yarn Hive Oozie的理由。 海豚调度器Doris离线数仓方案与CDH Hive在多个方面存在显著差异,以下是对这两种方案的对比分析: 1. 架构复杂性 CDH Hive:基于Hadoop生态,组件众多&#…

50.【8】BUUCTF WEB HardSql

进入靶场 随便输输 上order by ????????,被过滤了,继续找其他也被过滤的关键字 #,-- -,-- 都不行,尝试其他特殊字符后发现and,union,select,空格,都被过滤了 如下 我就不知…

Redis 3.2.1在Win10系统上的安装教程

诸神缄默不语-个人CSDN博文目录 这个文件可以跟我要,也可以从官网下载:https://github.com/MicrosoftArchive/redis/releases 这个是微软以前维护的Windows版Redis安装包,如果想要比较新的版本可以从别人维护的项目里下(https://…

mac配置 iTerm2 使用lrzsz与服务器传输文件

mac配置 1. 安装支持rz和sz命令的lrzsz brew install lrzsz2. 下载iterm2-send-zmodem.sh和iterm2-recv-zmodem.sh两个脚本 # 克隆仓库 git clone https://github.com/aikuyun/iterm2-zmodem ~/iterm2-zmodem# 进入到仓库目录 cd ~/iterm2-zmodem# 设置脚本文件可执行权限 c…

9.7 visual studio 搭建yolov10的onnx的预测(c++)

1.环境配置 在进行onnx预测前,需要搭建的环境如下: 1.opencv环境的配置,可参考博客:9.2 c搭建opencv环境-CSDN博客 2.libtorch环境的配置,可参考博客:9.4 visualStudio 2022 配置 cuda 和 torch (c)-CSDN博客 3.cuda环境的配置…

YOLOv8从菜鸟到精通(二):YOLOv8数据标注以及模型训练

数据标注 前期准备 先打开Anaconda Navigator,点击Environment,再点击new(new是我下载anaconda的文件夹名称),然后点击创建 点击绿色按钮,并点击Open Terminal 输入labelimg便可打开它,labelimg是图像标注工具,在上篇…