RRF算法原理与RAG中的应用
倒数排名融合算法(Reciprocal Rank Fusion,简称RRF)是一种在元搜索或混合搜索中常用的相关性评分算法。元搜索或混合搜索是指从多个搜索引擎或搜索源获取结果,并将这些结果融合到一个结果集中的过程。在RAG中,这种算法能够提高你的文档召回率,准率的文档召回才能使得RAG系统出色的完成了第一步。
RRF算法原理
RRF的基本思想是,每个搜索源返回的结果都有其自身的排名。这些排名可能基于各种因素,如 relevancy score、page rank、click-through rate 等。RRF算法将这些排名转化为倒数,然后将这些倒数相加,从而得到一个融合的排名分数。
RRF的具体公式如下 (i一般作为经验值取60,根据实际情况调整):
$$ RRF(k) = \frac{1}{k + i} $$
其中 k 表示一个搜索结果在其原始搜索源中的排名。这个公式的意义在于,排名越高(即 k 越小),其倒数越大,因此其在融合结果中的排名也应该越高。
RRF的优点在于其简单易用,且不需要知道原始搜索源的排名算法的具体细节。只需要知道每个搜索结果在其原始搜索源中的排名,就可以计算出其在融合结果中的排名。
然而,RRF也有其局限性。它假设所有的搜索源都同样重要,但实际上,不同的搜索源可能有不同的权重。此外,它也没有考虑到搜索结果的相关性评分,只考虑了排名。因此,如果两个搜索结果的排名相同,但一个的相关性评分远高于另一个,RRF无法区分这两个结果。
现在以大白话来解释RRF算法,保证你一看就懂:
假设有两个搜索引擎A和B,用来搜索用户提出的关于水果的问题,它们分别返回了以下两个搜索结果列表:
搜索引擎A排名列表:[apple, banana, cherry, date]
搜索引擎B排名列表:[banana, cherry, apple, date]
现在我们想要使用倒数排名融合(RRF)来融合这两个排名列表。首先,我们计算每个项目在各个排名列表中的倒数排名:
对于搜索引擎A:
- apple在A中的倒数排名为1
- banana在A中的倒数排名为2
- cherry在A中的倒数排名为3
- date在A中的倒数排名为4
对于搜索引擎B:
- banana在B中的倒数排名为1
- cherry在B中的倒数排名为2
- apple在B中的倒数排名为3
- date在B中的倒数排名为4
然后,我们将这些倒数排名进行加权平均,得到新的融合排名列表:
- apple: (1/1 + 1/3) / 2 = 0.67
- banana: (1/2 + 1/1) / 2 = 0.75
- cherry: (1/3 + 1/2) / 2 = 0.42
- date: (1/4 + 1/4) / 2 = 0.25
最终的融合排名列表为:[banana, apple, cherry, date],这就是通过倒数排名融合得到的综合排名结果。
RRF算法实战
Elasticsearch在8.12版本中支持了Vector Search。如果你的Embedding后的向量是浮点类型的向量,要么采用类似Milvus的向量数据库进行检索,现在也可以采用Elasticsearch进行检索了;如果你的Embedding后是二进制类型目前Elasticsearch也是支持的。在8.12版本中支持了HNSW算法,自动将 float32 值量化为 int8 字节值。虽然这会使磁盘使用量增加,但是HNSW 搜索所需的内存减少了 75%,大幅减少密集向量搜索所需的资源开销,在这一点上Milvus和Elasticsearch同时支持。
关于Elasticsearch 8.12的环境参考:
-
ElasticSearch 8.12.2 elasticsearch-8.12.2-linux-x86_64.tar.gz
-
Kibana 8.12.2 kibana-8.12.2-linux-x86_64.tar.gz
-
es-analysis 分词器 elasticsearch-analysis-ik-8.12.2.zip
Elasticsearch 的 kNN 搜索功能可以找到与查询向量最相似的 k 个向量。这种搜索技术在自然语言处理、产品推荐、图像或视频的相似性搜索等领域有广泛应用。
要使用 kNN 搜索,首先需要将数据转换为有意义的向量值,并将它们作为 dense_vector
字段值添加到文档中。查询也以同样维度的向量形式表示。设计时需确保文档向量与查询向量越接近,其匹配度就越高。
Elasticsearch 支持两种 kNN 方法:一是使用 knn 搜索选项或 knn 查询进行近似 kNN 搜索;二是通过带有 vector 函数的 script_score 查询进行精确但暴力计算的 kNN 搜索。通常情况下,建议使用近似方法,因为它提供了较低延迟和可接受准确性之间平衡,尽管索引速度会慢些。而精确方法虽能保证结果准确无误,但对于大型数据集来说并不易扩展。
运行一个近似 kNN 搜索时需要特别注意资源配置,并且所有矢量数据必须适合节点页面缓存以保证效率。此外,在映射中明确定义至少一个启用索引功能的 dense_vector
字段,并设置相应参数如 similarity 值(默认为余弦相似性)。还可以调整 num_candidates 参数来权衡搜索速度和结果准确性:增加该值可获得更精准结果但降低搜索速度;反之则可能牺牲一定准确性以换取更快响应时间。
现在以QA问答式数据为例,来演示如何使用RRF算法:
elasticsearch==8.12.1
openai==1.12.1
插入使用BM25算法的文本匹配表数据:
from datetime import datetime
from elasticsearch import Elasticsearch
import json
es = Elasticsearch(
['http://demo.com:9200'],
basic_auth=('username', 'password'),
)
with open('pc_qa_library.json', 'r', encoding='utf-8') as f:
data = json.load(f)
for item in data:
res = es.index(index="qa-pc-doc", id=item['id'], body={
'qa': item['qa'],
'timestamp': datetime.now()
})
测试一下检索:
res = es.search(index="qa-pc-doc", body={
"size": 5, # 限定最终的Top-k的数量
"query": {
"match": {
'qa': 'Linux支持吗?'
}
}
})
# print(res)
print("总共%d条结果:" % res['hits']['total']['value'])
for hit in res['hits']['hits']:
print(hit['_score'], "---------- %(qa)s" % hit["_source"])
OK,插入和查询数据都验证没问题!
插入使用向量检索的数据:
from elasticsearch import Elasticsearch
es = Elasticsearch(
['http://demo.com:9200'],
basic_auth=('elastic', 'password'),
)
# 创建索引
create_index_body = {
"mappings": {
"properties": {
"embedding": {
"type": "dense_vector",
"dims": 1536, # 这个维度目前是OpenAI text-embedding-3-small 的维度
# "similarity": "l2_norm" # sqrt((1 / _score) - 1)
"similarity": "cosine" # (2 * _score) - 1
},
"context": {
"type": "keyword"
}
}
}
}
es.indices.create(index="qa-knn", body=create_index_body)
openai.api_key = 'sk-xxx'
# 读取JSON文件
with open('pc_qa_library.json', 'r', encoding='utf-8') as f:
data = json.load(f)
count = 0
for item in data:
resp = openai.embeddings.create(input=[item['qa']], model="text-embedding-3-small")
res = es.index(index="qa-pc-knn", id=item['id'], body={
'embedding': resp.data[0].embedding,
'qa': item['qa'],
'timestamp': datetime.now()
})
count += 1
print("Inserted %d documents into Elasticsearch." % count)
向量检索的方式查询试试:
question = 'Linux支持吗?'
q_vector = openai.embeddings.create(input=[question], model="text-embedding-3-small").data[0].embedding
print(q_vector)
search_body = {
"knn": {
"field": "embedding",
"query_vector": q_vector,
"k": 5, # 最终的Top-k的数量
"num_candidates": 350
},
"fields": ["qa"]
}
res = es.search(index="qa-pc-knn", body=search_body)
print("总共%d条结果:" % res['hits']['total']['value'])
for hit in res['hits']['hits']:
print(hit['_score'], "---------- %(qa)s" % hit["_source"])
现在将两种搜索方式的结果结合起来,然后采用RRF算法的到最终的结果:
import openai
from elasticsearch import Elasticsearch
es = Elasticsearch(
['http://nas.zouchanglin.cn:9200'],
basic_auth=('elastic', 'lhl123456an'),
)
openai.api_key = 'sk-xxx'
question = '你这里的电脑Linux能跑吗?'
top_k = 5
q_vector = openai.embeddings.create(input=[question], model="text-embedding-3-small").data[0].embedding
search_body = {
"size": top_k, # 限定最终的Top-k的数量
"knn": {
"field": "embedding",
"query_vector": q_vector,
"k": top_k, # 最终的Top-k的数量
"num_candidates": 512
},
"fields": ["context"]
}
res = es.search(index="qa-pc-knn", body=search_body)
group_knn = []
print("总共%d条结果:" % res['hits']['total']['value'])
for hit in res['hits']['hits']:
# print(hit['_id'], '---------', hit['_score'], "---------- %(context)s" % hit["_source"])
print(hit['_id'], '---------', hit['_score'])
group_knn.append(hit['_id'])
res = es.search(index="qa-pc-doc", body={
"size": top_k, # 限定最终的Top-k的数量
"query": {
"match": {
'qa': question
}
}
})
group_bm25 = []
print("总共%d条结果:" % res['hits']['total']['value'])
for hit in res['hits']['hits']:
# print(hit['_id'], '---------', hit['_score'], "---------- %(question)s: %(answer)s" % hit["_source"])
print(hit['_id'], '---------', hit['_score'])
group_bm25.append(hit['_id'])
# 倒数排序融合(Reciprocal Rank Fusion,RRF)
# 1/(k+rank) k取60
group_knn_rank = []
for i in range(1, len(group_knn)+1):
group_knn_rank.append({
group_knn[i-1]: 1/(60+i)
})
print(group_knn_rank)
group_bm25_rank = []
for i in range(1, len(group_bm25)+1):
group_bm25_rank.append({
group_bm25[i-1]: 1/(60+i)
})
print(group_bm25_rank)
# fusion
group_fusion = {}
for i in range(len(group_knn_rank)):
for k, v in group_knn_rank[i].items():
if k in group_fusion:
group_fusion[k] += v
else:
group_fusion[k] = v
for i in range(len(group_bm25_rank)):
for k, v in group_bm25_rank[i].items():
if k in group_fusion:
group_fusion[k] += v
else:
group_fusion[k] = v
# rank
group_fusion = sorted(group_fusion.items(), key=lambda x: x[1], reverse=True)
print('---------------最终结果-------------------')
for k, v in group_fusion:
# print(k, v)
res = es.get(index="qa-pc-doc", id=k)
content: str = res['_source']['qa']
content = content.replace('\n', '')
print(f'id={k}, score={v}, content={content}')
print('---------------------------------')
可以看到排序结果还是非常令人满意的:
相似问题优化
RAG中常用的一个的优化方式:有时候存在用户提问是一个非常模糊的问题,此时通常情况下我们会生成3-5个类似问题,同时进行检索:
client = OpenAI(
api_key=os.environ.get("OPENAI_API_KEY")
)
completion = client.chat.completions.create(
model="gpt-4-0125-preview",
messages=[
{
"role": "system",
"content": f'''Generate 3 similar questions based on existing questions to ensure \
that the semantics remain unchanged and must be in Chinese.
## Problem
{question}
## Return JSON Format
["", "", ""]
'''
}
],
temperature=0.2,
)
questions = json.loads(completion.choices[0].message.content.strip())
print(questions)
最后贴出50条QA Demo数据 pc_qa_library.json:
[
{
"id": 0,
"qa": "问:我应该选择固态硬盘还是机械硬盘? 答:如果您追求更快的速度和更好的性能,建议选择固态硬盘。"
},
{
"id": 1,
"qa": "问:购买电脑时,哪些因素需要考虑? 答:您需要考虑处理器性能、内存容量、硬盘类型、显卡性能等因素。"
},
{
"id": 2,
"qa": "问:我可以在购买电脑时升级内存吗? 答:通常可以,在购买时选择较低配置,后期再单独购买内存进行升级。"
},
{
"id": 3,
"qa": "问:购买电脑时有哪些推荐的品牌? 答:常见的电脑品牌有惠普、戴尔、华硕、联想等,选择时可以根据个人喜好和需求来决定。"
},
{
"id": 4,
"qa": "问:购买电脑时有什么保修服务? 答:大多数电脑品牌会提供一定时期的免费保修服务,可以根据实际情况选择延长保修期。"
},
{
"id": 5,
"qa": "问:如何选择合适的显示器? 答:选择显示器时需要考虑分辨率、屏幕大小、刷新率等因素,根据自己的需求来选择。"
},
{
"id": 6,
"qa": "问:购买电脑时可以选择哪些支付方式? 答:通常支持支付宝、微信支付、银行转账等多种支付方式,具体以商家支持的方式为准。"
},
{
"id": 7,
"qa": "问:购买电脑后配送时间需要多久? 答:配送时间会根据您所在地区和配送方式而有所不同,一般在1-7个工作日内送达。"
},
{
"id": 8,
"qa": "问:我可以在电脑上安装Linux系统吗? 答:可以,在购买时选择兼容Linux系统的硬件配置,安装Linux系统通常不会有太大问题。"
},
{
"id": 9,
"qa": "问:购买电脑需要注意什么? 答:购买电脑时需要注意配置是否满足自己的需求、售后服务是否完善、价格是否合理等方面。"
},
{
"id": 10,
"qa": "问:如何选择合适的处理器? 答:选择处理器时可以考虑性能、核心数量、功耗等因素,根据自己的需求来选择合适的处理器。"
},
{
"id": 11,
"qa": "问:购买电脑时可以选择哪些配送方式? 答:通常支持快递配送、自提等多种配送方式,具体以商家提供的配送方式为准。"
},
{
"id": 12,
"qa": "问:购买电脑时有哪些性能指标需要关注? 答:您可以关注处理器型号、内存容量、显卡性能、硬盘类型等性能指标。"
},
{
"id": 13,
"qa": "问:购买电脑时可以选择哪些保修方案? 答:通常有标准保修、延长保修、意外保修等多种保修方案可供选择。"
},
{
"id": 14,
"qa": "问:如何选择合适的显卡? 答:选择显卡时可以考虑显存大小、核心频率、功耗等因素,根据自己的需求来选择合适的显卡。"
},
{
"id": 15,
"qa": "问:购买电脑时有哪些常见的配件? 答:常见的配件有键盘、鼠标、显示器、音箱等,可以根据实际需求选择购买。"
},
{
"id": 16,
"qa": "问:购买电脑时可以选择哪些操作系统? 答:常见的操作系统有Windows、Mac OS、Linux等,可以根据个人喜好选择。"
},
{
"id": 17,
"qa": "问:如何选择合适的内存? 答:选择内存时可以考虑容量、频率、延迟等因素,根据主板支持的规格来选择合适的内存。"
},
{
"id": 18,
"qa": "问:购买电脑时有哪些网络连接方式? 答:通常支持有线连接、无线连接等多种网络连接方式,可以根据需求选择。"
},
{
"id": 19,
"qa": "问:购买电脑时如何选择合适的机箱? 答:选择机箱时需要考虑散热性能、扩展性、外观设计等因素,根据自己的需求来选择。"
},
{
"id": 20,
"qa": "问:购买电脑时有哪些常见的接口? 答:常见的接口有USB接口、HDMI接口、网口、音频接口等,可以根据外设需求选择。"
},
{
"id": 21,
"qa": "问:购买电脑时可以选择哪些储存设备? 答:常见的储存设备有固态硬盘、机械硬盘、光驱等,可以根据需求选择。"
},
{
"id": 22,
"qa": "问:如何选择合适的键盘? 答:选择键盘时可以考虑键盘类型、按键手感、背光等因素,根据个人习惯来选择合适的键盘。"
},
{
"id": 23,
"qa": "问:购买电脑时可以选择哪些音频设备? 答:常见的音频设备有耳机、音箱、麦克风等,可以根据需求选择适合的音频设备。"
},
{
"id": 24,
"qa": "问:购买电脑时如何选择合适的散热系统? 答:选择散热系统时可以考虑散热效率、噪音、尺寸等因素,根据电脑配置来选择合适的散热系统。"
},
{
"id": 25,
"qa": "问:购买电脑时可以选择哪些外设? 答:常见的外设有打印机、扫描仪、摄像头等,可以根据实际需求选择适合的外设。"
},
{
"id": 26,
"qa": "问:如何选择合适的电源? 答:选择电源时可以考虑功率、效率、稳定性等因素,根据电脑配置来选择合适的电源。"
},
{
"id": 27,
"qa": "问:购买电脑时可以选择哪些电池? 答:如果是笔记本电脑,可以选择标准电池、大容量电池等不同容量的电池。"
},
{
"id": 28,
"qa": "问:购买电脑时如何选择合适的摄像头? 答:选择摄像头时可以考虑像素、拍摄效果、适配性等因素,根据需求选择合适的摄像头。"
},
{
"id": 29,
"qa": "问:如何选择合适的无线网络设备? 答:选择无线网络设备时可以考虑信号覆盖范围、传输速度、稳定性等因素,根据需求选择合适的设备。"
},
{
"id": 30,
"qa": "问:购买电脑时可以选择哪些耗材? 答:常见的耗材有墨盒、硒鼓、纸张等,可以根据打印需求选择适合的耗材。"
},
{
"id": 31,
"qa": "问:购买电脑时如何选择合适的投影仪? 答:选择投影仪时可以考虑分辨率、亮度、投影距离等因素,根据使用场景选择合适的投影仪。"
},
{
"id": 32,
"qa": "问:如何选择合适的键鼠套装? 答:选择键鼠套装时可以考虑连接方式、手感、耐用性等因素,根据个人习惯选择合适的键鼠套装。"
},
{
"id": 33,
"qa": "问:购买电脑时可以选择哪些办公软件? 答:常见的办公软件有Office套件、金山WPS等,可以根据需求选择适合的办公软件。"
},
{
"id": 34,
"qa": "问:购买电脑时如何选择合适的打印机? 答:选择打印机时可以考虑打印速度、打印质量、耗材成本等因素,根据需求选择合适的打印机。"
},
{
"id": 35,
"qa": "问:如何选择合适的扫描仪? 答:选择扫描仪时可以考虑扫描速度、扫描精度、连接方式等因素,根据需求选择合适的扫描仪。"
},
{
"id": 36,
"qa": "问:购买电脑时可以选择哪些办公设备? 答:常见的办公设备有复印机、传真机、装订机等,可以根据需求选择适合的办公设备。"
},
{
"id": 37,
"qa": "问:购买电脑时如何选择合适的耳机? 答:选择耳机时可以考虑音质、佩戴舒适度、阻抗等因素,根据需求选择合适的耳机。"
},
{
"id": 38,
"qa": "问:如何选择合适的音箱? 答:选择音箱时可以考虑音质、功率、连接方式等因素,根据需求选择合适的音箱。"
},
{
"id": 39,
"qa": "问:购买电脑时可以选择哪些网络设备? 答:常见的网络设备有路由器、交换机、网卡等,可以根据需求选择适合的网络设备。"
},
{
"id": 40,
"qa": "问:购买电脑时如何选择合适的显示器支架? 答:选择显示器支架时可以考虑承重能力、调节功能、稳定性等因素,根据显示器尺寸选择合适的支架。"
},
{
"id": 41,
"qa": "问:如何选择合适的鼠标垫? 答:选择鼠标垫时可以考虑材质、尺寸、防滑性等因素,根据鼠标类型选择合适的鼠标垫。"
},
{
"id": 42,
"qa": "问:购买电脑时可以选择哪些电脑桌椅? 答:常见的电脑桌椅有电脑椅、电脑桌、桌面升降架等,可以根据使用习惯选择合适的电脑桌椅。"
},
{
"id": 43,
"qa": "问:购买电脑时如何选择合适的电脑包? 答:选择电脑包时可以考虑尺寸、质地、舒适度等因素,根据电脑尺寸选择合适的电脑包。"
},
{
"id": 44,
"qa": "问:如何选择合适的电脑桌面壁纸? 答:选择桌面壁纸时可以根据个人喜好选择风格、颜色等,也可以自定义壁纸。"
},
{
"id": 45,
"qa": "问:购买电脑时可以选择哪些办公软件? 答:常见的办公软件有Office套件、金山WPS等,可以根据需求选择适合的办公软件。"
},
{
"id": 46,
"qa": "问:购买电脑时如何选择合适的打印机? 答:选择打印机时可以考虑打印速度、打印质量、耗材成本等因素,根据需求选择合适的打印机。"
},
{
"id": 47,
"qa": "问:如何选择合适的扫描仪? 答:选择扫描仪时可以考虑扫描速度、扫描精度、连接方式等因素,根据需求选择合适的扫描仪。"
},
{
"id": 48,
"qa": "问:购买电脑时可以选择哪些办公设备? 答:常见的办公设备有复印机、传真机、装订机等,可以根据需求选择适合的办公设备。"
},
{
"id": 49,
"qa": "问:购买电脑时如何选择合适的耳机? 答:选择耳机时可以考虑音质、佩戴舒适度、阻抗等因素,根据需求选择合适的耳机。"
}
]