BM25检索算法 python

1.简介

BM25(Best Matching 25)是一种经典的信息检索算法,是基于 TF-IDF算法的改进版本,旨在解决、TF-IDF算法的一些不足之处。其被广泛应用于信息检索领域的排名函数,用于估计文档D与用户查询Q之间的相关性。它是一种基于概率检索框架的改进,特别是在处理长文档和短查询时表现出色。BM25的核心思想是基于词频(TF)和逆文档频率(IDF)来,同时还引入了文档的长度信息来计算文档D和查询Q之间的相关性。目前被广泛运用的搜索引擎ES就内置了BM25算法进行全文检索。

BM25算法的基本公式

在这里插入图片描述

  • Score(D,Q) 是文档 D 与查询 Q 的相关性得分。
  • qi 是查询中的第 i 个词。
  • f(qi, D)是词 qi 在文档 D 中的频率。
  • IDF(qi) 是词qi 的逆文档频率。
  • |D| 是文档 D的长度。
  • avgdl是所有文档的平均长度。
  • k1 和 b 是可调的参数,通常 k1 在1.2到2之间, b通常设为0.75。

IDF计算方法

在这里插入图片描述

  • N 是文档集合中的文档总数
  • n(q1)是包含词q1的文档数量

  • 词频 (f(qi, D)): 这是查询中的词 q_i在文档 D 中出现的频率。词频是衡量一个词在文档中重要性的基本指标。词频越高,这个词在文档中的重要性通常越大。
  • 逆文档频率 (IDF(qi)): 逆文档频率是衡量一个词对于整个文档集合的独特性或信息量的指标。它是由整个文档集合中包含该词的文档数量决定的。一个词在很多文档中出现,其IDF值就会低,反之则高。这意味着罕见的词通常有更高的IDF值,从而在相关性评分中拥有更大的权重。
  • 文档长度 (|D|): 这是文档D 中的词汇数量。文档长度用于调整词频的影响,因为较长的文档可能仅因为它们的长度就有更高的词频。
  • 平均文档长度 (avgdl): 这是整个文档集合中所有文档长度的平均值。它用于标准化不同文档的长度,以便可以公平比较不同长度的文档。
  • 可调参数 (k1 和 b):
    • k1 是一个正系数,用于控制词频的饱和度。较高的 k1 值意味着词频对评分的影响更大。
    • b 是用于控制文档长度对评分的影响的参数,取值在0到1之间。当 b=1 时,文档长度的影响最大;当b = 0 时,文档长度不影响评分。

2. 主要流程

1 数据预处理
  • 首先需要将文档进行数据预处理,包括分词、去除停用词、词干提取和标准化等步骤。
2 计算文档和查询条件中各个项的得分函数
  • 该步骤计算每个文档和查询条件中各个项的得分函数,并将其存储在倒排索引中。
3 计算文档与查询条件之间的匹配程度
  • 计算文档与查询条件之间的匹配程度得分。该步骤会计算所有匹配的文档的得分值,并按照得分值的大小对文档进行排序。
4 返回最匹配的文档
  • 返回最匹配的文档。

3. python 简单实现

import math
from collections import Counterclass BM25:def __init__(self, docs, k1=1.5, b=0.75):"""BM25算法的构造器:param docs: 分词后的文档列表,每个文档是一个包含词汇的列表:param k1: BM25算法中的调节参数k1:param b: BM25算法中的调节参数b"""self.docs = docsself.k1 = k1self.b = bself.doc_len = [len(doc) for doc in docs]  # 计算每个文档的长度self.avgdl = sum(self.doc_len) / len(docs)  # 计算所有文档的平均长度self.doc_freqs = []  # 存储每个文档的词频self.idf = {}  # 存储每个词的逆文档频率self.initialize()def initialize(self):"""初始化方法,计算所有词的逆文档频率"""df = {}  # 用于存储每个词在多少不同文档中出现for doc in self.docs:# 为每个文档创建一个词频统计self.doc_freqs.append(Counter(doc))# 更新df值for word in set(doc):df[word] = df.get(word, 0) + 1# 计算每个词的IDF值for word, freq in df.items():self.idf[word] = math.log((len(self.docs) - freq + 0.5) / (freq + 0.5) + 1)def score(self, doc, query):"""计算文档与查询的BM25得分:param doc: 文档的索引:param query: 查询词列表:return: 该文档与查询的相关性得分"""score = 0.0for word in query:if word in self.doc_freqs[doc]:freq = self.doc_freqs[doc][word]  # 词在文档中的频率# 应用BM25计算公式score += (self.idf[word] * freq * (self.k1 + 1)) / (freq + self.k1 * (1 - self.b + self.b * self.doc_len[doc] / self.avgdl))return score# 示例文档集和查询
docs = [["the", "quick", "brown", "fox"],["the", "lazy", "dog"],["the", "quick", "dog"],["the", "quick", "brown", "brown", "fox"]]
query = ["quick", "brown"]# 初始化BM25模型并计算得分
bm25 = BM25(docs)
scores = [bm25.score(i, query) for i in range(len(docs))]## query和文档的相关性得分:
## sores = [1.0192447810666774, 0.0, 0.3919504878447609, 1.2045355839511414]

在这个例子中,我们使用了四个文档和一个查询来计算相关性得分。查询是 [“quick”, “brown”]。得分如下:

  • 文档 1 (“the quick brown fox”): 得分约为 1.02
  • 文档 2 (“the lazy dog”): 得分为 0.0(因为它不包含查询中的任何单词)
  • 文档 3 (“the quick dog”): 得分约为 0.39
  • 文档 4 (“the quick brown brown fox”): 得分约为 1.20

这些得分反映了每个文档与查询之间的相关性。得分越高,表示文档与查询的相关性越强。在这个例子中,文档 4 与查询的相关性最高,其次是文档 1,文档 3 的相关性较低,而文档 2 与查询没有相关性。

4. 调用gensim实现

一般流程(对于中文)
  1. 构建corpus
    1.1 构建停用词词表(可加入部分高频词)
    1.2 分词
    1.3 去除停用词

2 训练BM25模型
3. 使用模型计算相似性

from gensim.summarization import bm25def test_gensim_bm25():corpus = [['来', '问', '几', '个', '问题', '第1', '个', '就', '是', '60', '岁', '60', '岁', '的', '时候', '退休', '是', '时间', '到', '了', '一定', '要', '退休', '还是', '觉得', '应该', '差', '不', '多'], ['第1', '个', '是', '应该', '第2', '个', '是'], ['不', '对', '应该', '就是', '差', '不', '多'], ['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']]bm25Model = bm25.BM25(corpus)test_strs = [['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁'],['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁', '问题', '第1', '个'],['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁', '问题', '第1', '个','来', '问', '几', '个', '问题'],['应该', '差', '不', '多', '一定', '要', '退', '60', '岁'],['差', '不', '多', '一定', '要', '退'],['一定', '要', '差', '不', '多', '退'],['一定', '要', '退'],['一定', '差', '不', '多'],]for test_str in test_strs:scores = bm25Model.get_scores(test_str)print('测试句子:', test_str)for i, j in zip(scores, corpus):print('分值:{},原句:{}'.format(i, j))print('\n')if __name__ == '__main__':test_gensim_bm25()

运行结果:

测试句子: ['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']
分值:0.2828807225045471,原句:['来', '问', '几', '个', '问题', '第1', '个', '就', '是', '60', '岁', '60', '岁', '的', '时候', '退休', '是', '时间', '到', '了', '一定', '要', '退休', '还是', '觉得', '应该', '差', '不', '多']
分值:0.226504790662966,原句:['第1', '个', '是', '应该', '第2', '个', '是']
分值:0.42164043562468434,原句:['不', '对', '应该', '就是', '差', '不', '多']
分值:2.2007072441488233,原句:['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']测试句子: ['应该', '差', '不', '多', '一定', '要', '退', '60', '岁']
分值:0.202827468444139,原句:['来', '问', '几', '个', '问题', '第1', '个', '就', '是', '60', '岁', '60', '岁', '的', '时候', '退休', '是', '时间', '到', '了', '一定', '要', '退休', '还是', '觉得', '应该', '差', '不', '多']
分值:0.09756782248085916,原句:['第1', '个', '是', '应该', '第2', '个', '是']
分值:0.42164043562468434,原句:['不', '对', '应该', '就是', '差', '不', '多']
分值:1.2213019690359779,原句:['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']测试句子: ['差', '不', '多', '一定', '要', '退']
分值:0.15212060133310423,原句:['来', '问', '几', '个', '问题', '第1', '个', '就', '是', '60', '岁', '60', '岁', '的', '时候', '退休', '是', '时间', '到', '了', '一定', '要', '退休', '还是', '觉得', '应该', '差', '不', '多']
分值:0,原句:['第1', '个', '是', '应该', '第2', '个', '是']
分值:0.3240726131438252,原句:['不', '对', '应该', '就是', '差', '不', '多']
分值:1.1406697377282669,原句:['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']测试句子: ['一定', '要', '差', '不', '多', '退']
分值:0.15212060133310423,原句:['来', '问', '几', '个', '问题', '第1', '个', '就', '是', '60', '岁', '60', '岁', '的', '时候', '退休', '是', '时间', '到', '了', '一定', '要', '退休', '还是', '觉得', '应该', '差', '不', '多']
分值:0,原句:['第1', '个', '是', '应该', '第2', '个', '是']
分值:0.3240726131438252,原句:['不', '对', '应该', '就是', '差', '不', '多']
分值:1.1406697377282669,原句:['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']测试句子: ['一定', '要', '退']
分值:0.0,原句:['来', '问', '几', '个', '问题', '第1', '个', '就', '是', '60', '岁', '60', '岁', '的', '时候', '退休', '是', '时间', '到', '了', '一定', '要', '退休', '还是', '觉得', '应该', '差', '不', '多']
分值:0,原句:['第1', '个', '是', '应该', '第2', '个', '是']
分值:0,原句:['不', '对', '应该', '就是', '差', '不', '多']
分值:0.898773043805134,原句:['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']测试句子: ['一定', '差', '不', '多']
分值:0.15212060133310423,原句:['来', '问', '几', '个', '问题', '第1', '个', '就', '是', '60', '岁', '60', '岁', '的', '时候', '退休', '是', '时间', '到', '了', '一定', '要', '退休', '还是', '觉得', '应该', '差', '不', '多']
分值:0,原句:['第1', '个', '是', '应该', '第2', '个', '是']
分值:0.3240726131438252,原句:['不', '对', '应该', '就是', '差', '不', '多']
分值:0.24189669392313295,原句:['所以', '是', '应该', '差', '不', '多', '还是', '一定', '要', '退', '60', '岁']

5. rank-bm25 (一个双线搜索引擎,用于查询一组文档并返回与查询最相关的文档)

安装

pip install rank_bm25
初始化

首先要做的是创建BM25类的一个实例,该实例读取文本语料库并对其进行一些索引:

from rank_bm25 import BM25Okapicorpus = ["Hello there good man!","It is quite windy in London","How is the weather today?"
]tokenized_corpus = [doc.split(" ") for doc in corpus]bm25 = BM25Okapi(tokenized_corpus)
# <rank_bm25.BM25Okapi at 0x1047881d0>

此包不进行任何文本预处理。如果你想做一些事情,比如降低词尾、删除词尾、词干等,你需要自己做。唯一的要求是类接收字符串列表,这些字符串是文档标记。

文档排名

我们已经创建了文档索引,我们可以向它提供查询,并查看哪些文档最相关:

query = "windy London"
tokenized_query = query.split(" ")doc_scores = bm25.get_scores(tokenized_query)
# array([0.        , 0.93729472, 0.        ])

除了获取文档分数,你也可以用来检索最佳文档:

bm25.get_top_n(tokenized_query, corpus, n=1)
# ['It is quite windy in London']

参考

心法利器[13] | 任务方案思考:句子相似度和匹配
ChatGLM 金融大模型决赛方案总结
rank-bm25 0.2.2
python根据BM25实现文本检索
相关性算法BM25的python实现
python借助elasticsearch实现精准查询与bm25查询
python实现内容检索子系统(BM25算法)
BM25,超全解释
史上最小白之BM25详解与实现
RAG提效利器——BM25检索算法原理和Python实现

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

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

相关文章

HarmonyOS开发实例:【图片编辑应用】

介绍 本篇Codelab通过动态设置元素样式的方式&#xff0c;实现几种常见的图片操作&#xff0c;包括裁剪、旋转、缩放和镜像。效果如图所示&#xff1a; 相关概念 [image组件]&#xff1a;图片组件&#xff0c;用来渲染展示图片。[div组件]&#xff1a;基础容器组件&#xff0…

学习Rust的第16天:泛型类型

泛型类型是减少代码重复的好方法&#xff0c;因此它们对性能有巨大的影响&#xff0c;通过利用Rust中的泛型类型&#xff0c;开发人员可以编写更通用和可重用的代码&#xff0c;同时保持类型安全和性能。这种方法不仅减少了冗余&#xff0c;还增强了代码的可维护性和可扩展性&a…

AI大模型探索之路-实战篇2:基于CVP架构-企业级知识库实战落地

目录 前言 一、概述 二、本地知识库需求分析 1. 知识库场景分析 2. 知识库应用特点 3. 知识库核心功能 三、本地知识库架构设计 1. RAG架构分析 2. 大模型方案选型 3. 应用技术架构选型 4. 向量数据库选型 5. 模型选型 三、本地知识库RAG评估 四、本地知识库代码落地 1. 文件…

服务器基础知识(1)

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;服务器❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 1、什么是服务器 服务器是计算机的一种&#xff0c;它比普通计算机运行更快、负载更高、价格更贵。服务…

WIFI/BT中蓝牙的硬件资源是如何调度的 UART和PCM接口传输的是什么信号

安卓或IOS手机中&#xff0c;wifi/bt中的蓝牙是如何调度硬件资源的&#xff0c;尤其是UART和PCM是如何分配的。M.2 wifi/bt模块或其他形式的模块中&#xff0c;蓝牙是如何调度硬件资源的&#xff0c;尤其是UART和PCM是如何分配的。今天我们就图文并茂的解决这个问题。 蓝牙文件…

MATLAB使用速成 第三章(MATLAB绘图)

一、二维平面作图 1、简单的x-y坐标图 x、y是长度相同的向量&#xff0c;以x的分量为横坐标&#xff0c;y的分量为纵坐标&#xff0c;作平面曲线&#xff0c;使用命令plot(x,y)。&#xff08;可以省略参数x&#xff0c;这样将会以y的分量下标为横坐标&#xff0c;y的分量为纵坐…

如何在极狐GitLab 中用 docker in docker 的方式使用 docker?

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab &#xff1a;https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署…

C语言游戏实现——贪吃蛇

思路讲解 ** 贪吃蛇游戏是要求我们要操控一条蛇&#xff0c;在游戏规定的空间之内&#xff0c;进行吃食物&#xff0c;吃到一个就增加蛇身的长度&#xff0c;并且游戏得分加1&#xff0c;如果吃到自己&#xff0c;和碰到墙就算死亡&#xff0c;同时可以增加蛇的速度和减慢蛇的…

Python开源项目周排行 2024年第8周

#2024年第8周2024年4月12日1llama3当知无愧AI LLM领域当红炸子鸡&#xff01;Llama 3 是由 Meta AI 开发的大型语言模型 (LLM)&#xff0c;于 2024 年 4 月发布。它基于 Megatron-Turing NLG 模型架构&#xff0c;并在超过 15 万亿个标记的公开可用数据上进行了预训练&#xff…

算法训练营day15

一、层序遍历 参考链接7.2 二叉树遍历 - Hello 算法 (hello-algo.com) 层序遍历本质上属于广度优先遍历&#xff0c;也称广度优先搜索&#xff0c; BFS通常借助队列的先入先出的特性实现 参考链接102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 像这种较为…

百度GL地图实现选点获取经纬度并且地址逆解析

index.html引入 <script src"https://api.map.baidu.com/api?typewebgl&v1.0&ak你的ak"></script>组件使用 <el-input:disabled"[详情].includes(title)"v-model"formData.site"placeholder""><templat…

【行为型模型】迭代器模式

一、迭代器模式概述 迭代器模式定义&#xff1a;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露其内部的表示。把游走的任务放在送代器上&#xff0c;而不是聚合上。这样简化了聚含的接口和实现,也让责任各得其所。(对象行为型) 迭代器模式的优缺点&…

virtualbox 网络设置实现主机和虚拟机互相访问

前言 一般来说&#xff0c;virtualbox 虚拟机的上网模式是 NAT。这样虚拟机可以上网并访问宿主机&#xff0c;但宿主机无法访问虚拟机&#xff0c;也无法 ping 通。下面介绍双网卡模式&#xff0c;实现虚拟机和宿主机能够互相访问 ping 通。 双网卡模式 进入虚拟机的网络设置…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别 一、简单介绍 二、简单人脸识别实现原理 三、简单人脸识别案例实现简单步…

C语言—深度剖析函数指针,函数指针数组

我们先来看一段代码 #include <stdio.h> void test() {printf("hehe\n"); } int main() {printf("%p\n", test);printf("%p\n", &test);return 0; }输出的是两个地址&#xff0c;这两个地址是 test 函数的地址。 那我们的函数的地址…

杰理695的UI模式LED灯控制

UI模式LED灯修改每个模式对应的LED灯闪烁修改在ui_normal_status_deal(u8 *status, u8 *power_status, u8 ui_mg_para)

关系型数据库中primary key和foreign key、索引的作用

文章目录 一、关系型数据库中主键(primary key)和外键(foreign key)的概念。二、MySQL索引的作用(索引的优缺点)一、关系型数据库中主键(primary key)和外键(foreign key)的概念。 二、MySQL索引的作用(索引的优缺点) MySQL索引是一种数据结构,它可以提高查询性能…

MATLAB初学者入门(13)—— 遗传算法

遗传算法是一种受自然选择和遗传学启发的搜索启发式算法&#xff0c;用于解决优化和搜索问题。它模拟了自然界中生物的进化过程&#xff0c;包括基因的选择、交叉&#xff08;杂交&#xff09;和变异。 MATLAB 提供了一个方便的工具箱&#xff0c;即全局优化工具箱&#xff0c;…

网卡技术解密:理解网卡背后的原理

✍✍在这个信息爆炸的时代&#xff0c;网卡承载着无数数据的流动&#xff0c;是我们日常生活和工作不可或缺的一部分。但是&#xff0c;您是否曾经好奇过&#xff0c;这些小小的硬件是如何在瞬息万变的网络世界中稳定地发挥作用的呢&#xff1f; 想象一下&#xff0c;每当我们…

计算机缺少msvcp120.dll如何解决,7种详细的修复方法分享

msvcr120.dll文件是微软Visual C运行时库的一部分&#xff0c;版本号为12.0。这个DLL文件包含了许多用于支持在Windows上运行的应用程序的重要函数和组件。它是确保某些程序能够正确执行的关键组成部分&#xff0c;特别是那些使用C编写或依赖于某些Microsoft库的程序。 当用户…