相关性算法BM25的python实现

本文深入解析BM25算法的计算原理,包括词频、逆文档频率及TF值变换,并通过gensim库的BM25模型实现文本相似度计算。展示了中文分词、构建语料库、训练模型及计算相似性的完整流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

计算原理

在这里插入图片描述

  • 第一项c(w,q)就是搜索q中词w的词频
  • 第三项是词w的逆文档频率,M是所有文本的个数,df(w)是出现词w的文本个数
  • 中间的第二项是关键,实质是词w的TF值的变换,c(w,d)是词w在文本d中的词频。首先是一个TF Transformation,目的是防止某个词的词频过大,经过下图中公式的约束,词频的上限为k+1,不会无限制的增长。例如,一个词在文本中的词频无论是50还是100,都说明文本与这个词有关,但相关度不可能是两倍关系。

优点

开源实现

snownlp
gensim_bm25
rank_bm25

实践

一般流程(对于中文)

  1. 构建corpus
    1.1. 构建停用词词表(可加入部分高频词)
    1.2. 分词
    1.3. 去除停用词
  2. 训练BM25模型
  3. 使用模型计算相似性

gensim的使用

from gensim.summarization import bm25


def 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', '岁']

更多关于gensim BM25

想取得生成模型后的逆文档频率,只需访问其属性idf

TODO

  • 对开源实现的深度优劣分析

参考:
python根据BM25实现文本检索

### BM25算法Python中的实现 BM25是一种基于概率模型的信息检索排名函数,广泛应用于搜索引擎和其他文本匹配场景。以下是几种常见的BM25算法Python中的实现方式。 #### 使用Rank_bm25库 `rank-bm25` 是一个专门为BM25设计的高效Python库。它支持向量化操作并能快速处理大规模数据集[^4]。 安装该库可以通过pip完成: ```bash pip install rank-bm25 ``` 下面是一个简单的例子展示如何使用 `rank-bm25` 进行文档相似度计算: ```python from rank_bm25 import BM25Okapi corpus = [ "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) query = "windy London" tokenized_query = query.split(" ") doc_scores = bm25.get_scores(tokenized_query) print(doc_scores) # 输出每篇文档的相关性分数 ``` 此代码片段展示了如何通过分词后的语料库创建BM25对象,并利用输入查询获取相关性得分[^4]。 #### 自定义BM25实现 如果不想依赖第三方库,则可以手动编写BM25的核心逻辑。以下提供了一个简化版的手动实现方案: ```python import math from collections import Counter class SimpleBM25: def __init__(self, documents, k1=1.5, b=0.75): self.documents = documents self.k1 = k1 self.b = b self.avgdl = sum(len(doc.split()) for doc in documents) / len(documents) self.idf = self._calculate_idf() def _calculate_idf(self): df = {} N = len(self.documents) for document in self.documents: words = set(document.split()) for word in words: if word not in df: df[word] = 0 df[word] += 1 idf = {word: math.log((N - freq + 0.5) / (freq + 0.5)) for word, freq in df.items()} return idf def get_score(self, query): scores = [] query_words = query.split() for idx, document in enumerate(self.documents): score = 0 doc_len = len(document.split()) tf = Counter(document.split()) for term in query_words: if term in self.idf and term in tf: numerator = self.idf[term] * ((tf[term] * (self.k1 + 1))) denominator = tf[term] + self.k1 * (1 - self.b + self.b * (doc_len / self.avgdl)) score += numerator / denominator scores.append(score) return scores documents = ["Hello there good man", "It is quite windy in London", "How is the weather today?"] bm25_model = SimpleBM25(documents) scores = bm25_model.get_score("windy London") print(scores) # 手动计算的结果与实际应用可能略有差异 ``` 这段代码实现了基本的BM25功能,包括逆文档频率(IDF)以及TF-IDF权重调整机制[^5]。 ### 性能优化建议 当面对更大规模的数据时,考虑引入更高效的工具如Annoy、Faiss或者Milvus来替代传统的Elasticsearch SVS方法可能会带来性能上的提升,尽管这会牺牲部分功能性[^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值