【搜索核心技术】经典搜索核心算法:BM25及其变种

news2024/11/15 21:51:55

随着基于检索增强的生成(Retrieval-Augmented Generation—RAG)逐渐成为当前大模型落地方案的主流选择,搜索技术在这一过程中扮演着至关重要的角色。然而,仅依赖向量相似性检索往往无法达到理想的效果。因此,为了进一步提升搜索性能,我们需要将基于字面相似的传统搜索算法与向量相似性检索相结合,实现混合搜索。今天,将为大家详细介绍一种非常流行的信息检索算法—BM25。

上篇介绍了TF-IDF算法和它的四个变种,相对于TF-IDF而言,在信息检索和文本挖掘领域,BM25算法则更具理论基础,而且是工程实践中当仁不让的重要基线(Baseline)算法 。BM25在20世纪70年代到80年代被提出,到目前为止已经过去二三十年了,但是这个算法依然在很多信息检索的任务中表现优异,是很多工程师首选的算法之一。

BM25(Best Matching 25)是一种用于信息检索(Information Retrieval)和文本挖掘的算法,它被广泛应用于搜索引擎和相关领域。BM25 基于 TF-IDF(Term Frequency-Inverse Document Frequency)的思想,但对其进行了改进以考虑文档的长度等因素。

Okapi BM25算法(默认的BM25算法)是在Elasticsearch和Lucene中默认使用的算法。它有很多不同的变体,但是核心的算法仍然是TF-IDF模式。

在人们研究词频和相关性之间的关系时发现,这二者并非是线性相关,即当词频达到一定程度后相关性不再上升。因此提出了TF-IDF的改进算法BM25。

BM25 算法是一种常用的信息检索算法,具有以下重要价值:

  1. 提供客观评估标准:能够定量地评估文档与查询的相关性,帮助确定哪些文档与用户的搜索最相关。以客观的方式评估文档的相关性,避免了人为因素的影响。
  2. 提高检索效果:通过对文档进行排序,使得最相关的文档排在前面,提高检索系统的效果和用户满意度。
  3. 可扩展性好:易于与其他算法和技术结合使用,以进一步提高检索性能。可用于多种应用场景,如搜索引擎、知识库、文档管理系统等。而且适用于多种语言的文本检索。
  4. 能够处理大量数据:在大规模数据集上仍然能够高效地运行。
  5. 帮助优化索引:指导索引的构建和优化,提高系统的效率。

本篇将谈谈BM25算法的历史、算法本身的核心概念以及BM25的一些重要变种,快速掌握这个信息检索和文本挖掘的利器。

BM25的历史

BM25,有时候全称是Okapi BM25,是由英国一批信息检索领域的计算机科学家开发的排序算法。这里的“BM”是“最佳匹配”(Best Match)的简称。

BM25背后有两位著名的英国计算机科学家。第一位叫斯蒂芬·罗伯逊(Stephen Robertson)。斯蒂芬最早从剑桥大学数学系本科毕业,然后从城市大学(City University)获得硕士学位,之后从伦敦大学学院(University College London)获得博士学位。斯蒂芬从1978年到1998年之间在城市大学任教。1998年到2013年间在微软研究院剑桥实验室工作。我们之前提到过,美国计算机协会ACM现在每三年颁发一次“杰拉德·索尔顿奖”,用于表彰对信息检索技术有突出贡献的研究人员。2000年这个奖项颁给斯蒂芬,奖励他在理论方面对信息检索的贡献。BM25可谓斯蒂芬一生中最重要的成果。

另外一位重要的计算机科学家就是英国的卡伦·琼斯(Karen Spärck Jones)。在TF-IDF的文章中讲过。卡伦也是剑桥大学博士毕业,并且毕生致力于信息检索技术的研究。卡伦的最大贡献是发现IDF以及对TF-IDF的总结。卡伦在1988年获得了第二届“杰拉德·索尔顿奖”。

BM25算法详解

现代BM25算法是用来计算某一个目标文档(Document)相对于一个查询关键字(Query)的“相关性”(Relevance)的流程。通常情况下,BM25是“非监督学习”排序算法中的一个典型代表。

顾名思义,这里的“非监督”是指所有的文档相对于某一个查询关键字是否相关,这个信息是算法不知道的。也就是说,算法本身无法简单地从数据中学习到相关性,而是根据某种经验法则来“猜测”相关的文档都有什么特质。

那么BM25是怎么定义的呢?我们先来看传统的BM25的定义。一般来说,经典的BM25分为三个部分

  1. 单词和目标文档的相关性
  2. 单词和查询关键词的相关性
  3. 单词的权重部分

这三个部分的乘积组成某一个单词的分数。然后,整个文档相对于某个查询关键字的分数,就是所有查询关键字里所有单词分数的总和。

先从第一部分说起,即单词和目标文档的相关性。这里相关性的基本思想依然是“词频”,也就是TF-IDF里面TF的部分。词频就是单词在目标文档中出现的次数。如果出现的次数比较多,一般就认为更相关。和TF-IDF不同,BM25最大的贡献之一就是挖掘出了词频和相关性之间的关系是非线性的,这是一个初看有违常理但细想又很有道理的洞察。

具体来说,每一个词对于文档相关性的分数不会超过一个特定的阈值。这个阈值当然是动态的,根据文档本身会有调整。这个特征就把BM25里的词频计算和一般的TF区分开了。也就是说,词频本身需要“标准化”(Normalization),要达到的效果是,某一个单词对最后分数的贡献不会随着词频的增加而无限增加。

那BM25里词频的标准化是怎么做的呢?就是某一个词的词频,除以这个词的词频加上一个权重。这个权重包含两个超参数(Hyper-parameter),这些超参数后期是可以根据情况手动调整的。这个做法在非监督的排序算法中很普遍。同时,这个权重还包括两个重要信息:第一,当前文档的长度;第二,整个数据集所有文档的平均长度。

这几个因素混合在一起,就得到了一个新的词频公式,既保证单词相对于文档的相关度和这个单词的词频呈现某种正向关系,又根据文档的相对长度,也就是原始长度和所有文档长度的一个比值关系,外加一些超参数,对词频进行了限制。

有了单词相对于文档的相关度计算公式作为基础,单词相对于查询关键字的相关度可以说是异曲同工。首先,我们需要计算单词在查询关键字中的词频。然后,对这个词频进行类似的标准化过程。

和文档的标准化过程唯一的区别,这里没有采用文档的长度。当然,对于查询关键字来说,如果需要使用长度,也应该是使用查询关键字的长度和平均长度。但是,根据BM25经典公式来说,这一部分并没有使用长度信息进行重新标准化。

接着来谈谈最后一个部分,单词权重的细节,通常有两种选择。

第一种选择就是直接采用某种变形的IDF来对单词加权。一般来说,IDF就是利用对数函数(Log函数)对“文档频率”,也就是有多少文档包含某个单词信息进行变换。这里回顾一下周一讲的内容,IDF是“文档频率”的倒数,并且通过对数函数进行转换。如果在这里使用IDF的话,那么整个BM25就可以看作是一个某种意义下的TF-IDF,只不过TF的部分是一个复杂的基于文档和查询关键字、有两个部分的词频函数。

第二种单词的权重选择叫作“罗伯逊-斯巴克-琼斯”权重(Robertson-Spärck-Jones),简称RSJ值,是由计算机科学家斯蒂芬·罗伯逊和卡伦·琼斯合作发现。我们刚才讲过,这两位都是重要的信息检索学术权威。这个权重其实就是一个更加复杂版本的IDF。一个关键的区别是RSJ值需要一个监督信息,就是要看文档对于某个查询关键字是否相关,而IDF并不需要。

对比以上两种思路,在很多情况下,利用IDF来直接进行单词权重的版本更加普遍。如果在有监督信息的情况下,RSJ值也不失为一个很好的选择。

通过这里简单的介绍,我们可以很容易地发现,BM25其实是一个经验公式。这里面的每一个成分都是经过很多研究者的迭代而逐步发现的。很多研究在理论上对BM25进行了建模,从“概率相关模型”(Probabilistic Relevance Model)入手,推导出BM25其实是对某一类概率相关模型的逼近。需要你记住的是,BM25虽然是经验公式,但是在实际使用中经常表现出惊人的好效果。因此,很有必要对这一类文档检索算法有所了解。

BM25 算法的基本思想:

  1. TF-IDF 的改进: BM25 通过对文档中的每个词项引入饱和函数(saturation function)和文档长度因子,改进了 TF-IDF 的计算。
  2. 饱和函数: 在 BM25 中,对于词项的出现次数(TF),引入了一个饱和函数来调整其权重。这是为了防止某个词项在文档中出现次数过多导致权重过大。
  3. 文档长度因子: BM25 考虑了文档的长度,引入了文档长度因子,使得文档长度对权重的影响不是线性的。这样可以更好地适应不同长度的文档。

其中:

  • $\text{score}(D,Q)$:需要查询的句子Q和文章D的相似得分,分解后其实就是每个字和D的相关性得分的加权和
  • 通常W_i使用特殊IDF表示,R使用特殊TF表示
IDF

对于left部分和TF-IDF的IDF实际使用一样,都是利用smooth方法避免除以0的情况出现:

\text{IDF}(q_i) = \log(\frac{N-n(q_i) + 0.5}{n(q_i) + 0.5} + 1)

其中,

  • n表示包含q_i的文档数量
  • N表示总共有多少个文档
  • \frac{N-n(q_i)}{n(q_i)}表示不包含q_i的文档数和包含q_i的文档数的比值
  • 0.5是平滑值,确保不会出现除以0的情况
  • 1用于确保log的自变量不会是0,因为\frac{N-n}{n} = \frac{N}{n}-1\in(-1, \inf)

为了解释为什么IDF是\log\frac{N-n(q_i)}{n(q_i)},需要从BIM开始利用贝叶斯公式进行公式推导。具体推导过程:

http://singhal.info/ieee2001.pdficon-default.png?t=N7T8http://singhal.info/ieee2001.pdf

TF文档词频

对于right部分是BM25的重点改进部分,它核心改进目标是当TF非常大时趋向于一个固定值,而不至于削弱IDF的影响:

\text{TF}(q_i, D) =\frac{f(q_i,D)\cdot(k_1+1)}{f(q_i,D) + k_1\cdot(1-b+b\cdot\frac{|D|}{avgdl})}

其中:

  • f(q_i, D):表示q_i 的在文档中出现的次数
  • |D|:文章D的字数
  • avgdl:平均文章字数
  • k_1,这个参数控制着词频结果在词频饱和度中的上升速度。默认值为1.2。值越小饱和度变化越快,值越大饱和度变化越慢。设置为0.0时,表示屏蔽词频影响
  • b,这个参数控制着字段长归一值所起的作用,0.0会禁用归一化,1.0会启用完全归一化。默认值为0.75

对于TF部分,当f足够大时,可以忽略分子和分母的其余部分,所以 TF → 1.0

TF查询词频

\text{TF}(q_i, Q)=\frac{f(q_i, Q)\cdot(k_2+1)}{f(q_i,Q)+k_2}

其中,

  • f(q_i, Q):表示q_i 在query中出现的次数,意思是如果出现次数越大,它的比重越高
  • k_2:表示调节因子,值越大,query中的词频越重要。如果设定为0,那么查询词频会被禁用

 BM25与TF-IDF的区别

Best Matching 25 其中25的含义是此算法经过 25 次迭代调整之后得到的,这也是这个匹配算法经久不衰的原因。

BM25算法变种

由于BM25的情况,一方面是经验公式,另一方面是某种理论模型的逼近,这样就出现了各式各样的BM25变种。

BM25的变体总体不会脱离TF-IDF的框架,不是在原版BM25基础上对TF部分更新,就是对IDF部分更新。

一个重要的扩展是BM25F,也就是在BM25的基础上再多个“域”(Field)文档上的计算。这里“域”的概念可以理解成一个文档的多个方面。比如,对于很多文档来说,文档包括标题、摘要和正文。这些组成部分都可以认为是不同的“域”。那么,如何结合不同的“域”,让文档的相关性能够统一到一个分数上就是BM25F的核心内容。

具体来说,BM25F对于BM25的扩展很直观。那就是每一个单词对于文档的相关性是把各个域当做一个“小文档”的加权平均。也就是说,我们先把每个域当做单独的文档,计算词频,进行标准化。然后集合每个域的值,进行加权平均,再乘以词的权重(我们上面提到了,用IDF或者是RSJ值)。

另外一个重要的扩展就是把BM25和其他文档信息(非文字)结合起来。这个想法是在“学习排序”(Learning To Rank)这一思路出现以前的一种普遍的做法,往往就是用线性加权的形式直接把各种信息相结合。例如,在21世纪初期比较流行的做法是用BM25和PageRank的线性结合来确定网页的相关度。这里面,BM25是和某个查询关键字有联系的信息,而PageRank则是一个网页的总体权重。

Python示例

以下是一个简单的 Python 实现 BM25 算法的例子。请注意,实际应用中可能需要进行更复杂的文本预处理,例如去除停用词、词干化等。

import math
from collections import Counter

class BM25:
    def __init__(self, corpus, k1=1.5, b=0.75):
        self.k1 = k1
        self.b = b
        self.corpus = corpus
        self.doc_lengths = [len(doc) for doc in corpus]
        self.avg_doc_length = sum(self.doc_lengths) / len(self.doc_lengths)
        self.doc_count = len(corpus)
        self.doc_term_freqs = [Counter(doc) for doc in corpus]
        self.inverted_index = self.build_inverted_index()

    def build_inverted_index(self):
        inverted_index = {}
        for doc_id, doc_term_freq in enumerate(self.doc_term_freqs):
            for term, freq in doc_term_freq.items():
                if term not in inverted_index:
                    inverted_index[term] = []
                inverted_index[term].append((doc_id, freq))
        return inverted_index

    def idf(self, term):
        doc_freq = len(self.inverted_index.get(term, []))
        if doc_freq == 0:
            return 0
        return math.log((self.doc_count - doc_freq + 0.5) / (doc_freq + 0.5) + 1.0)

    def bm25_score(self, query_terms, doc_id):
        score = 0
        doc_length = self.doc_lengths[doc_id]
        for term in query_terms:
            tf = self.doc_term_freqs[doc_id].get(term, 0)
            idf = self.idf(term)
            numerator = tf * (self.k1 + 1)
            denominator = tf + self.k1 * (1 - self.b + self.b * (doc_length / self.avg_doc_length))
            score += idf * (numerator / denominator)
        return score

    def rank_documents(self, query):
        query_terms = query.split()
        scores = [(doc_id, self.bm25_score(query_terms, doc_id)) for doc_id in range(self.doc_count)]
        sorted_scores = sorted(scores, key=lambda x: x[1], reverse=True)
        return sorted_scores

# Example usage
corpus = [
    "The quick brown fox jumps over the lazy dog",
    "A quick brown dog outpaces a swift fox",
    "The dog is lazy but the fox is swift",
    "Lazy dogs and swift foxes"
]

bm25 = BM25(corpus)
query = "quick brown dog"
result = bm25.rank_documents(query)

print("BM25 Scores for the query '{}':".format(query))
for doc_id, score in result:
    print("Document {}: {}".format(doc_id, score))

小结

第一,简要介绍了BM25的历史。第二,详细介绍了BM25算法的三个主要组成部分。第三,简要地介绍了BM25的一些变种 。

今天讲了文档检索领域或者说搜索领域里最基本的一个技术:BM25。BM25由三个核心的概念组成,包括词在文档中相关度、词在查询关键字中的相关度以及词的权重。BM25是一个长期积累的经验公式,也有很深的理论支持,是一个强有力的非监督学习方法的文本排序算法。

参考文献

[1] BM25(Best Matching 25)算法基本思想

[2] BM25技术分析

[3] BM25算法详解 - 简书

[4] 经典搜索核心算法:BM25及其变种

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络之http状态码和https

目录 HTTP协议 TCP/IP协议 TCP/IP的分层管理 各个协议和HTTP之间的关系 了解并区分URI和URL 返回结果的HTTP状态码 2XX 成功 2.1 200 ok 2.2 204 No Content 2.3 206 Partial Content 3xx表示重定向 3.1 301 Moved Permanently 3.2 302 Found 3.3 303 See …

探索全光网技术 | 全光网络技术方案选型建议二 (宿舍场景)

目录 一、场景设计需求二、宿舍场景拓扑三、部署方式四、产品相关规格说明五、方案优势 注:本文章参考资料为:华三官方资料 - “新华三全光网络3.0解决方案(教育)”与 锐捷官方资料 - “【锐捷】高校极简以太全光3.X方案设计(V1.3…

pinecone向量库的介绍和基本使用(增删改查)

本文来自于【向量库】pinecone向量库的介绍和基本使用(增删改查) Pinecone是一个实时、高性能的向量数据库,专为大规模向量集的高效索引和检索而设计。它提供亚秒级的查询响应时间,确保用户可以迅速获取所需信息。Pinecone采用高…

SAP与九恒星资金系统集成案例(医药行业)

一、项目环境 江西某药业有限公司是一家以医药产业为主营、资本经营为平台的大型民营企业集团。公司成立迄今,企业经营一直呈现稳健、快速发展的态势集团总销售额超40亿元。 为了帮助企业更好的进行资金流、结算、资金调度和运作管理、风险控制,济民…

计算机毕业设计选题推荐-篮球馆会员信息管理系统-Java/Python项目实战

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

前端初期知识点回顾

1.跳转:其中target“_blank”意思是跳转会新标签页打开 2.锚点定位:点击文字跳转到对应页面 3:表单 单元格合并 4.input属性变化 前期vue样式 5.画原神: 6.画学生管理系统: 购物车升序降序 累加函数 保留两位小数点 删…

2023年亚太杯A题:果园采摘机器人的图像识别,一二题(题目代码及结果)

问题一:基于附件1中提供的可收获苹果的图像数据集,提取图像特征,建立数学模型,计算每幅图像中的苹果的数量,并绘制附件1中所有苹果的分布直方图。 对于自动采摘机器人,首要的能力就是识别出苹果对象&#…

K3 BOS单据获取制单人工号

新建BOS单据,打印时有时不想在单据上体现制单人姓名,只要打印出工号就行了 新建时,在单据头增加一个“制单人工号”的字段,字段名一定要设置成"FBillerno", 然后在插件中增加资源中的DLL 效果 套打单据中的…

爬虫:jsonpath模块及腾讯招聘数据获取

目录 jsonpath模块 腾讯招聘数据获取 jsonpath模块 # pip install jsonpath -i https://pypi.tuna.tsinghua.edu.cn/simple import jsonpathdata {"store": {"book":[{"category": "reference","author": "Nigel Ree…

电影票竞价系统:揭开神秘代码的面纱

电影票的购买方式也在不断革新。全民邀约电影票竞价系统就是这样一个创新的购票模式。今天,就让我们一起探索这个系统背后的代码秘密,看看竞价购票的代码是如何编写的。 电影票竞价系统的魅力 电影票竞价系统为广大影迷提供了一种全新的购票体验。通过该…

【计算机方向】中科院一区TOP顶刊对比!国人发文友好,“又好又水”!

今天小编带来计算机方向期刊对比,到底那本期刊才是你的“心尖宠”? 速看!为你的心上刊打Call! 这两本期刊,每个学者主观评价不同。 两本期刊含金量到底如何呢? 今天就跟着小编一起来了解下吧!…

FB03 界面新增自定义字段(在业务范围gsber之后)

两个界面都是 一个screen 是1002 一个是1007 两个都要改 代码控制显示 输入 MODULE FRM_MDF_SCREEN OUTPUT.DATA:lmblnr TYPE matdoc-MBLNR.DATA:lmjahr TYPE matdoc-MJAHR.FIELD-SYMBOLS:<FS_MBLNR> TYPE matdoc-MBLNR,<FS_MJAHR> TYPE ANY,<FS_action>…

华为od机试真题:求幸存数之和(Python)

2024华为OD机试&#xff08;C卷D卷&#xff09;最新题库【超值优惠】Java/Python/C合集 题目描述 给一个正整数列nums&#xff0c;一个跳数jump&#xff0c;及幸存数量left。运算过程为:从索引为0的位置开始向后跳&#xff0c;中间跳过 J 个数字&#xff0c;命中索引为 J1的数…

private DiscoveryClient discoveryClient 爆红

private DiscoveryClient discoveryClient 爆红 大部分情况是因为导错了包&#xff0c;导成了下面的包 import com.netflix.discovery.DiscoveryClient; 但有个特殊情况导包没有导错&#xff0c;discoveryClient依然爆红 import org.springframework.cloud.client.discovery…

软考系统架构师--第2章 操作系统-2.1 操作系统的类型与结构

目录 第2章 操作系统 2.1 操作系统的类型与结构 2.1.1 操作系统的定义 2.1.2 操作系统分类 第2章 操作系统 本章主要介绍操作系统的基本概念及其形成、发展历史和主要类型&#xff0c;并指出操作系统的 5 大管理功能。掌握操作系统原理的关键在于深入理解“一个观点、两条…

宏定义和内联函数的区别?

在C编程中&#xff0c;宏定义和内联函数都是常用的代码优化方法。它们可以在编译时将代码进行优化&#xff0c;提高程序的执行效率。但是&#xff0c;它们的实现方式和使用方法有所不同。下面将详细介绍宏定义和内联函数的区别。 一、宏定义 宏定义是一种简单的代码替换方式。…

SpringCloud+Vue3一个字段多个存储以及回显

♥️作者&#xff1a;小宋1021 &#x1f935;‍♂️个人主页&#xff1a;小宋1021主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

uni-app便携式蓝牙打印机esc指令改成vue3中使用

1、前言&#xff1a; 之前小程序是用vue2写的&#xff0c;现在要改成vue3&#xff0c;由于不能使用require导入js文件&#xff0c;所以vue2打印中使用到的文件需要改成inport 2、需要改的文件目录 主要是修改这三个文件&#xff0c;把require导入改成import 3、esc.js文件…

《Milvus Cloud向量数据库指南》——图像数据:ResNet50与图像及视频搜索的深度解析

图像数据:ResNet50与图像及视频搜索的深度解析 在当今信息爆炸的时代,图像和视频作为最直观、最富表现力的媒体形式之一,其搜索与检索技术显得尤为重要。无论是科研探索、艺术创作还是日常娱乐,人们越来越依赖于高效的图像和视频搜索工具来快速定位所需内容。其中,ResNet…

SpringCloud03

一、网关 网关&#xff1a;就是网络的关口&#xff0c;负责请求的路由&#xff0c;转发&#xff0c;身份校验。 在SpringCloud中网关的实现&#xff1a; SpringCloud Gateway 由SpringCloud官方出品 基于WebFlux响应式编程 无需调优即可获得优异性能 &#xff08;1&…