最近大半年时间都在做RAG的工作,分享一点个人探索的方向。和提升的方案。文章中会分享是如何做的,以及对应的效果。
核心问题
如何提升RAG的效果?
如何提升召回的准确率。
写在前边:已验证的方案
方案 | 优化方向 | 效果 | 备注 |
3.1 优化分词器 | 英文去除停顿词,单复数标准化 | 解决了badcase | |
3.2 分词器 | 使用IK分词器 | BM召回率提升了1% | |
3.3 向量检索召回 | 替换embedding模型 | 向量召回率提升了15%左右 | |
3.4 丰富数据 | 定义有层级的数据结构 | 可以很有效的解决一些和标题相关的问题的case。 | |
3.5.1 意图识别 | 判别是否走知识库检索 | 解决非检索问题 | |
3.5.2 为问题生成假设性答案(HyDE) | 假设性问答,解决场景问题 | 可以很好的解决 长尾的query整体召回率提升了3% | |
3.5.3 query 蒸馏 | 去除query中的不相关的关键词 | 可以很好的提升召回排名 | |
3.5.4 query问题拆解 CoT思维链 | 解决复杂问题的问答 | 复杂难问题拆解后回答效果好 | |
3.7 排序策略rerank | 提高相关文档的排名 | 效果明显好。 | |
3.8 ELSER稀疏向量模型 | 使用稀疏向量模型(es官方提供的),提升召回 | 在英文场景下,稀疏向量的召回率相比较BM25,提升明显!提升到了100%,提升了14% topK召回率有很大提升,top1提升了31%,top5提升了28.5%,top10提升了22% | 收费纯英文环境。不支持中文。 |
3.9 同义词库 | 加入通用的同义词库 | BM25召回效果,下降50% | 问题分析1、长尾query,同义词库效果不好。需要对query进行更多处理,再配合上同义词会有好的效果。2、针对领域的专业词库,效果会更好一些。 |
3.10 评分排序优化 | item weight | BM25召回率提提升了 1.23% top1召回率提升了 7.6%,top5提升了 5%,top10提升了3.3%,top20提升了2% |
1.基础的召回
使用es的标准分词器
相关性召回,mb25 + 向量(embedding模型使用m3e OR BGE-m3)
2.测试数据集构建
这里是第一版,有点粗糙。最近在做RAG的benchmark工作,后续把构建测试数据的经验分享出来。如果可以的话,把测试数据集也贡献出来。
2.1 借助模型来构建QA和原文定位的测试数据
在有了测试数据以后,就可以验证召回的效果和RAG最终的效果了。
2.2 测试索引结构定义
2.2.1 召回率测试集
测试索引:retrieval_test_20240118
只放问题和内容
唯一键(_id): doc_id + content_id
doc_id: 文章id
content :文本块内容
content_id:文本块id
question:问题
answer: 回答
basis:依据的句子
qid: 问题id(生成规则数字递增)
d_id: 文档id
2.2.2 召回完整度测试集
用来测试句子,测试召回的内容的完整度
需要把测试集中的问题和答案都存起来。
唯一键(_id): doc_id + q_id
doc_id: 文章id
content :文本块内容
content_id:文本块id
question:问题
answer: 回答
basis:依据的句子
qid: 问题id(生成规则数字递增)
d_id: 文档id
2.3 英文测试数据集
2.3 测试数据集合写入
这里写入es。
2.4 测试数据集标注
模型生成的问题还是会有问题。有的问题很乏。
eg:
2.4.1 人工标注规则
先把生成的qa入库。然后做一遍检索召回测试。重点关注向量和BM都没有召回的问题。这种问题是可能有问题的。再关注有召回,但是排名靠后的case。然后人工标注一次。
无法使用的问题,会标记为橙色,可用标识为0。弱相关的数据标识为绿色,可用标识为0。 相关但是无法被召回或者召回后排名靠后的标记为黄色,可用标识为1。后续提升重点关注可用标识为1的case,验证提升方案,对这些case的提升效果。
- 优化过程
最终目标是提升召回率和提升召回的rank质量。
可以考虑以解决badcase为出发点。总结badcase的共性,寻找通用的解决方法。
其中 向量无法召回,BM25召回的badcase,作为向量的提升方向。
其中 向量召回,但是BM25无召回的badcase,作为BM25的提升方向。
如何划分和定义搜索的最小单元。词?字?句子?短句?
TODO 待解决问题
- 召回策略制定:问题究竟要在什么粒度的数据中做检索。句子?还是段落?还是摘要?还是整篇文章?
此问题,目前只有在片段中做检索。
- 层级数据结构:文档结构定义
3.1 优化分词器 —— 停顿词和单复数
解决中文和英文的问题,选择使用标准分词器。
需要添加额外的策略:英文去除停顿词,单复数标准化。
此策略,可以很好的解决单复数的问题。
3.1.1 badcase
问题
How should I respond to public demonstrations and protests?
期望召回
Article 27 Hong Kong residents shall have freedom of speech, of the press and of publication; freedom of association, of assembly, of procession and of demonstration; and the right and freedom to form and join trade unions, and to strike.
3.1.2 效果
处理前无召回
经过处理后 top3即可召回
3.2 分词器测试
测试中英文数据分开存储,然后使用各自不同的分词器的效果。
3.2.2 测试IK分词器
案例一:切换IK中文分词器,只影响BM25的召回率,可以看到整体的召回率降低了3%
案例二:使用另外一个测试集对比,BM召回率提升了1%。
在以上的测试中,案例一的QA对是基于全文的,而案例二的QA是基于片段的。且案例二的测试用例比案例一更多。
结论:可以考虑,使用中文分词器做替换。
3.3 向量检索召回—— 测试可替代m3e的模型
embedding模型的效果可能比较相近了。不过确实有一些排名更好的文本embedding模型。可以验证一下召回效果。以解决更多badcase为目标导向。
测试了xiaobu,bge,gte模型,将模型导入到es中。其中xiaobu无法成功导入,bge,gte导入后无法成功应用。
算法同事测试了不同的embedding模型的效果
基于段落的召回效果测试
基于全篇内容的召回测试
英文段落内容的召回测试
3.4 丰富数据
TODO :将以下需要丰富的内容,定义到mapping中。整理最新的mapping结构。
3.4.1 生成句子向量,单独一路召回。
句子向量,需要测试一下效果。可能之前测过,觉得不好。 可以拿一些badcase试试看,看看能不能解决badcase。将命中的句子对应的段落召回
3.4.2 为标题单独生成向量
为标题单独生成向量。召回的时候作为单独一路召回。如果相似度到达一个阈值以后。则考虑将标题下的内容给模型。测试效果。
3.4.3 为内容生成命题
为内容生成命题(使用模型生活,生成原子问题)。 这里和生成假设性问题思路类似。作为一路召回,测试对召回率的影响。
3.4.5 为句子,提取重要的关键词
在数据写入阶段,根据词性,以及词在句子中的重要性,去提取重要的关键词。目前来看可能不是太重要。
3.5 丰富Query 与 rewrite Query
3.5.1 意图识别
先弄清楚,请求是要做什么的。是简单召回请求,还是分析类型请求(需要做问题拆解),还是问答类的请求。
对于知识库来说,最应该清楚一件事情,是否应该走知识库检索?
实际上,想要判断是否能够从知识库中检索,是一件很难的事情。无法穷举知识库的特征,所以就无法做分类。但是换个角度,让模型判断query是否是模型应该回答的,是较为容易得事情。所以这里可以先判断模型是否能够回答。如果能够回答,则不走知识库搜索。
3.5.2 为问题生成假设性答案(HyDE)
为问题生成假设性答案,然后转向量。作为召回的一路,去测试召回提升率。或者去测试badcase,看看能不能解决。HyDE的核心思想是接收用户提问后,先让LLM在没有外部知识的情况下生成一个假设性的回复。然后,将这个假设性回复和原始查询一起用于向量检索。假设回复可能包含虚假信息,但蕴含着LLM认为相关的信息和文档模式,有助于在知识库中寻找类似的文档。
HyDE在解决法律的案例场景问答问题,有着出色的效果。 精准解决了90%的问题。
并且经过测试,在已有的测试数据集上,HyDE召回了提升了3%。
3.5.2.1 prompt
这里我限制了模型生成的内容的长度为256个token以内。
write a passage that answer zhe given query . Please note that the answer is controlled within 256 tokens. query:"What is the offence of using a copy of a fake document?"
3.5.2.2 拼接规则
新的query = 原始query + LLM 生成的答案
3.5.2.3 效果
对比了chatGPT 和yayi
测试数据说明。选用HK 人工整理的高质量测试数据集和高质量QA验证对。全部测试QA数据集是198个。
top5的召回率为92%。其中共有15个未成功召回的badcase。通过使用HpDE + Bm25 + GPT的方式,在top10以内召回的数量增加了5个,召回率为95%。整体提升3%。yayi和chatGPT的query改写能力,基本持平。
3.5.3 query 蒸馏
在数据查询阶段,根据词性,以及词在query中的重要性,去提取重要的关键词。排除非必须的关键词。然后用提炼后的词去做bm25的召回。以通过排除干扰词来提高相关性。
eg.
Q: 今年春节以来,博爱县采取了哪些措施来促进消费市场的回暖?
如果能提取出来:博爱县 采取 措施 促进 消费市场 回暖
+ 同义词
你是一位专业的 prompt engineer,用户将给你提供一个问题。 你需要对问题进行重写。
要求,先理解问题。根据词性等信息,提取出来重要的内容,去掉一些不重要的内容。
对一些有必要的词扩展关键词。
例如:"今年春节以来,博爱县采取了哪些措施来促进消费市场的回暖?"
经过理解后,可以知道博爱县是主题词,提取出来,博爱县 采取 措施 促进 消费市场 回暖。并添加一些关键词的同义词。
eg:文章中提到了哪些特色产业领域?
实际上有意义的,只有特色领域。应该有一个步骤,将这些问题的干扰项去掉。(文章中提到了哪些特色产业领域)
3.5.4 query问题拆解 CoT思维链
query问题拆解。将复杂问题进行拆解成N个子问题。使用LLM生成多个搜索查询,特别适用于一个问题可能需要依赖多个子问题的情况。
3.5.5 上下文会话信息改写query
使用滑动窗口,例如基于前10轮对话,来改写当前的query。
3.6 丰富召回策略
更多的召回与Q相关的content
BM25能召回的:关键词匹配命中的
向量检索:内容有一定的相关度。
需要挖掘的:有些内容需要阅读全文才能获取知识。这里能不能抽取一些政策和关键词出来。这里需要知道什么时候应该把全文召回。
eg:兴隆村在推动乡村振兴方面采取了哪些措施,以及这些措施带来了哪些具体的产业变化?
3.7 排序策略与相关性蒸馏 rerank
将更相关的数据排序在前。对召回的全部内容进行蒸馏,将不相关的杂质去除。
3.8 ELSER稀疏向量模型——英文场景 与 BM25对比测试
在英文场景下,稀疏向量的召回率相比较BM25,提升明显!提升到了100%,提升了14%
topK召回率有很大提升,top1提升了31%,top5提升了28.5%,top10提升了22%
注意!这里在做测试的时候,有发现中文数据一定不能掺杂到英文数据中来,否则会严重影响召回率。
3.9 同义词库
中文同义词库的对比测试
其中3是未添加同义词库前,6为添加同义词库后。
明显看到。在添加了同义词库后,BM25整体的召回效果降低了很多!降低了将近50%
结论:这里暂时不考虑使用同义词库。同义词库作为未来的探索方向,是不是词库不好?
3.10 评分排序优化-1
对于有一些问题case,是可以考虑抽取实体的。通过实体匹配来加分,提高排序的效果。
eg:文章中提到了哪些促进区域协调发展的措施?
通过词性分析,提取一些重要的词来做加分的查询操作。使用should语法
整理了一定的规则。
NT : 时间名词,连续的NT词必须是一个整体;
VV: 去除单字;保留2个字及以上;
NN:前后独立的NN去除;单字的NN需和前后NN组成整体词;
OD:中文序数词,如“第一、第二、十九”,需与前后组成整体词;
M:数量词;需与前后组成整体词;
在通过加分的优化下:
BM25召回率提提升了 1.23% 。原来有63个没有被召回。修改后只有34个没有召回。实际上效果已经提升了将近50%。
top1召回率提升了 7.6%,top5提升了 5%,top10提升了3.3%,top20提升了2%
在34个没有召回的case里边。其中有24条,是有问题的问题(问题太泛了)。
剩余10条没有召回case里,看到加分策略的副作用。这里举一个例子:如何理解“十个行动”?
显然这里会拿着"理解" + "行动"去加分,结果会变差。
3.11 评分排序优化-2
在数据写入的阶段抽取实体在检索的过程中,对query也抽取实体,然后利用实体去加分。
还可以考虑使用模型来规划query(TODO 调试prompt)
- 测试方案
4.1 测试项
4.1.1 优化分词器 —— 停顿词和单复数
4.2 整体性测试
召回率,top值
5. yayi提升记录
针对HK问题,最初的测试结果
人工进行PDF文档切分后
重新训练模型后
错误原因分析:
24-02-03版评测中20条错误答案归因:
正确回答 2条
未召回 9条 (最终确认,未召回,是指未出现在Top5中,需要优化rerank过程。)
主体内容正确,模型延伸出现幻觉 2条
主体内容正确,判断出错 3 条
内容正确,但对问题回复太过笼统,未回答出核心 2条
引用内容正确,数理逻辑判断错误 1条
被截断 1条
- 文档拆分问题
文档排重
原始文档中的数据,要做一个排重。相同的文档,理应只存储一份,否则在多文档检索中,会严重的影响召回率!
文档分类
应该按照什么样的策略,来对文档数据进行分类?
首先同类型的数据一定是要放在同一类中。例如论文、书籍、法律法规、财务报表、等等。不同类型的文档又有不同的知识密度。在检索的时候,分开检索,可以有效减少之间的相互影响。
如何解析转义文档?
如何原值原味的保留文档的原始结构? 通常文档的结构都有特定的含义,例如不同级别的标题。
文档转行 markdown格式。是否能转?是否能切分?(langchain)
分块大小的问题,每个块要分多大,以什么规则分?
大块数据和小块数据各自有各自的优势。处理长篇文章或书籍时,较大的分块有助于保留更多的上下文和主题连贯性;而对于社交媒体帖子,较小的分块可能更适合捕捉每个帖子的精确语义。如果用户的查询通常是简短和具体的,较小的分块可能更为合适。
不同的嵌入模型有其最佳输入大小。比如Openai的text-embedding-ada-002的模型在256 或 512大小的块上效果更好。实际场景中,我们可能还是需要不断实验调整,在一些测试中,128大小的分块往往是最佳选择,在无从下手时,可以从这个大小作为起点进行测试。
所以根据文档,应该有不同的切分规则。有句子构成的句向量有必要,由段落构成的块儿向量也有必要。通常句子向量中蕴含的信息较少,在回答一个问题的时候,通常具有信息确实的可能性。一般情况下,句子向量应该是作为标记点,使用窗口的方式,把对应的段落,或者是上下文召回。
7. 关于query改写的理解
query改写其实理解起来很简单,就是把原始的query经历一系列的操作,然后变成另外一个query,从而达到提升召回率和准确率的效果。
query改写的过程中,这一系列的操作,其实是围绕两个方面展开的。第一是在原始query中添加一些有用的内容(可以理解为query扩展),把本该召回却没有召回的内容给召回,提高topK的召回率。第二是在原始query中去掉一些杂质内容(可以理解为蒸馏),主要操作是对召回的数据,进行一个反馈。
围绕这两个方面,特别是在LLM越来越火热的情况下,学术界有了越来越多的研究。
针对扩展这个方向,要看具体的相关度匹配方法。例如以BM25为代表的稀疏检索。query扩展是会引入更多的词,扩大了召回的可能性,但是前提是引入正向的词,假如引入的是噪音数据,那么反而会降低召回的效果。
在PRF中,主要的思想是想用query在候选集中做一次召回。然后从召回的结果集中挖掘有意义的扩展信息,重写query。在多篇文章中,提到了召回的数量一般设置为10,通常会截取召回的数据,有的是128个token,有的是256个token。