微信扫码
与创始人交个朋友
我要投稿
大语言模型兴起之前的很长时间里,在信息检索领域,用的比较多的其实是TF-IDF、BM25这类检索方法,这些方法也经历住了时间的考验。在大模型时代,将BM25这类稀疏检索与向量检索相结合,通常能取长补短,大幅提升检索效果。
之所以将BM25称为稀疏检索算法,是因为文档和Query是使用稀疏向量表示的,向量维度通常等于所有文档中唯一词(词表)的数量,其中大部分值都是0,所以称为稀疏向量,与之相对的是稠密向量,也就是深度学习火了之后,Word Embedding以及向量模型所产出的向量,它的维度通常只有几百维,多的也只不过几千,其中几乎所有维度都有非0值,跟TF-IDF、BM25这种动辄上万维的向量相比算是维度很低了,这也是为什么在最开始Word Embedding概念出现时,被称为低维向量。
值得注意的是,在中文中,使用Langchain默认的BM25检索器参数,效果非常差,本人踩过的坑是,在一次项目中没有单独检查稀疏检索的效果,直接进行混合检索,通过调整两者配比最终效果比纯向量检索略好就结束了,以为语义检索效果比稀疏检索会有压倒性地优势,二者混合只略微涨点是合理的,但事后分析才发现,在中文中,使用默认参数设置,BM25检索效果不可能好。本文在核心代码的部分也会解释为什么会这样。
这部分公式比较多,不喜欢的朋友可以跳过直接看代码,也不影响使用。
注意:下面说到的文档,对应到我们的场景,就是知识片段,语料库对应的是所有文档片段
BM25是一个给定query来计算文档相关性的重要的函数,BM指的是best matching。
BM25的评分函数可以用以下公式表示:
其中:
是文档 对查询的评分
是问题中的词数
是问题中的第个词
是词 的逆文档频率(Inverse Document Frequency),计算公式为:
其中:
是文档总数
是包含词的文档数量
是词在文档中的词频(Term Frequency),不要被“频”这个词误解,这是个次数,不是占比
是文档的长度(词数)
是语料库中文档的平均长度(平均词数)
和是参数,通常在1.2 到 2.0 之间,在 0.5 到 0.75 之间
这里顺带介绍一下TF-IDF的计算公式,因为BM25是对TF-IDF的改进:
其中 是问题Q中的第个词,跟BM25中的不同,它代表词在文档中的占比,例如在文档中共20个词,出现了30次,则为3/20。
的计算公式为:
表示文档总数,表示包含的文档的数量。
简单解释一下这个公式在干嘛,其实衡量的是一个词在某个文档中的重要性,聪明的读者应该能一眼看出来,如果只考虑,那分数高的,肯定是“的”、“了”这类没什么用的词,因此引入用来平衡这种高频词的影响,这样两者一综合,反而会使得“的”、“了”这类词的TF-IDF得分不高,而真正在一个文档中独有(说明这个词不是类似“的”这种每篇文档都在用的通用词)却高频(说明这篇文档重点讲这个词相关的话题)的那些词凸显出来。
相比TF-IDF,BM25的改进主要在以下3点:
词频(TF)调整
BM25考虑了词频的饱和效应,即随着词频的增加,增加的相关性会逐渐减少。这是通过使用一个非线性函数来调整词频的影响。主要体现在下面这部分:
当词频比较小时,主导这个公式值的,是除之外的部分,那可以认为,这部分公式,会随着词频的增长而增长,而当词频增大到一定程度时,主导这个公式值的,就变成词频了,综合分子分母可以发现,越到后面,分子上涨的部分,被分母抵消掉了。
为了便于大家理解,我做这样一个实验,假设= 2.0,= 0.75,为20,文档中一共5个词,出现了1词,之后每次向中增加一个与相同的词,则整个增长过程如下所示,其中TF就是,TF_adj是上面这个公式,大家可以观察diff,它表示随着TF的增长,TF_adj增量:
TF | TF_adj | diff |
---|---|---|
1 | 1.600000 | |
2 | 2.033898 | 0.433898 |
3 | 2.236025 | 0.202127 |
4 | 2.352941 | 0.116916 |
5 | 2.429150 | 0.076209 |
6 | 2.482759 | 0.053609 |
7 | 2.522523 | 0.039764 |
8 | 2.553191 | 0.030669 |
9 | 2.577566 | 0.024374 |
10 | 2.597403 | 0.019837 |
可以看到,随着TF的增大,diff越来越小,越到后面,越“涨不动”了,也就说明了词频是有饱和效应的
文档长度标准化
TF-IDF未考虑文档长度的影响,而BM25通过引入文档长度标准化因子,长文档通常会包含更多的词,因此需要对词频进行标准化,以避免长文档得到不成比例的高分。这个标准化是通过参数控制的。主要体现在下面这部分:
因为在BM25中,词频表示的词在文档中出现的次数,那对于越长的文档,相对来说它里面的词的就会越大,因此引入文档长度标准化:
当文档比较长时,较大,从而使分母较大,降低词频的影响。
当文档较短时,较小,从而使分母较小,扩大词频的影响。
引入额外的调节参数
BM25中引入的参数和提供了额外的调节手段,使得模型可以更好地适应不同的应用场景,而TF-IDF则没有这样的灵活性。
混合检索的流程如下图,会分两路分别使用稠密向量和稀疏检索(BM25)方式检索知识片段,然后将检索结果使用使用RAG技术构建企业级文档问答系统:检索优化(3)RAG Fusion中介绍的RRF算法进行排序,截断Top N的知识片段送入大模型,然后由大模型结合用户问题和知识片组生成答案。
从下表可以看出,使用BM25检索和混合检索(Embedding微调+BM25)的方法,检索的命中率超过了之前的所有方法,混合检索的全流程问答准确率也达到了目前最好成绩。
核心代码
本文对应的代码已开源,地址在:https://github.com/Steven-Luo/MasteringRAG/blob/main/retrieval/04_bm25_hybrid.ipynb
使用Langchain实现非常简单,传入切分好的知识库列表splitted_docs即可构建好检索器
但这种方式,效果会非常差,对于本系列构建的测试集,Top1~8的命中率如下:
retriever | top_k | hit_rate |
---|---|---|
vanilla_bm25 | 1 | 0.000000 |
vanilla_bm25 | 2 | 0.000000 |
vanilla_bm25 | 3 | 0.032258 |
vanilla_bm25 | 4 | 0.064516 |
vanilla_bm25 | 5 | 0.064516 |
vanilla_bm25 | 6 | 0.064516 |
vanilla_bm25 | 7 | 0.064516 |
vanilla_bm25 | 8 | 0.064516 |
为什么会出现这种情况?查看Langchain源代码,https://github.com/langchain-ai/langchain/blob/master/libs/community/langchain_community/retrievers/bm25.py会发现,在创建BM25检索器时,会使用preprocess_func
处理文档列表,而preprocess_func
参数如果不指定,默认使用的是default_preprocessing_func
,也就是按空格切分,这对中文来说,肯定是没法正常工作的。
下面对这段代码进行修改,增加中文分词器:
从下面的Top1~8的召回命中率来看,结果正常多了
retriever | top_k | hit_rate |
---|---|---|
jieba_cut_bm25 | 1 | 0.666667 |
jieba_cut_bm25 | 2 | 0.784946 |
jieba_cut_bm25 | 3 | 0.838710 |
jieba_cut_bm25 | 4 | 0.870968 |
jieba_cut_bm25 | 5 | 0.870968 |
jieba_cut_bm25 | 6 | 0.870968 |
jieba_cut_bm25 | 7 | 0.870968 |
jieba_cut_bm25 | 8 | 0.870968 |
可以使用如下方式创建混合检索器:
在这里有一个超参数:稀疏检索和向量检索的权重,针对这个系列的数据,本文对不同权重配比进行了实验,不同权重的检索性能对比如下图:
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-11-25
RAG搭建中,如何选择最合适的向量索引?
2024-11-25
RAG的2024—随需而变,从狂热到理性(下)
2024-11-25
RAG的2024—随需而变,从狂热到理性(下)
2024-11-25
糟糕!LLM输出半截Json的答案,还有救吗!
2024-11-24
解读GraphRAG
2024-11-24
RAGChecker:显著超越RAGAS,一个精细化评估和诊断 RAG 系统的创新框架
2024-11-23
FastRAG半结构化RAG实现思路及OpenAI O1-long COT蒸馏路线思考
2024-11-23
检索增强生成(RAG):解密AI如何融合记忆与搜索
2024-07-18
2024-05-05
2024-07-09
2024-05-19
2024-07-09
2024-06-20
2024-07-07
2024-07-07
2024-07-08
2024-07-09
2024-11-25
2024-11-06
2024-11-06
2024-11-05
2024-11-04
2024-10-27
2024-10-25
2024-10-21