BM25稀疏检索算法笔记

张开发
2026/5/6 8:07:33 15 分钟阅读

分享文章

BM25稀疏检索算法笔记
文章目录bm25有什么用?bm25bm25-手动实现示例bm25-jieba分词示例bm25-idf示例bm25有什么用?BM25 在 RAG/AI 里的价值就 4 点1、和向量检索互补构成最强混合检索2、精准匹配关键词、术语、编号解决向量漂移3、轻量、快、无GPU、中文友好4、可解释适合合规、专业文档没有 BM25 的 RAG在专业文档、中文场景、精准查询上效果通常会明显弱一截。bm25bm25-手动实现示例代码importmathfromcollectionsimportCounterclassSimpleBM25:def__init__(self,documents,k11.5,b0.75):self.k1k1 self.bb self.documentsdocuments self.Nlen(documents)# 1. 预处理分词 (这里简单按空格切分中文需先用 jieba)self.tokenized_docs[doc.split()fordocindocuments]# 2. 计算文档长度和平均长度self.doc_lengths[len(doc)fordocinself.tokenized_docs]self.avg_dlsum(self.doc_lengths)/self.N# 3. 计算每个词在所有文档中出现的次数 (用于算 IDF)self.freq_map{}# {word: count_of_docs_containing_word}fordocinself.tokenized_docs:unique_wordsset(doc)forwordinunique_words:self.freq_map[word]self.freq_map.get(word,0)1# 4. 预计算 IDFself.idf{}forword,countinself.freq_map.items():# IDF 公式变种ln((N - n 0.5) / (n 0.5) 1)self.idf[word]math.log((self.N-count0.5)/(count0.5)1)defget_score(self,query):query_tokensquery.split()scores[]fori,docinenumerate(self.tokenized_docs):score0.0doc_lenself.doc_lengths[i]counterCounter(doc)# 当前文档的词频统计fortokeninquery_tokens:iftokennotincounter:continuetfcounter[token]# 词频 f(q, D)idfself.idf.get(token,0)# BM25 核心公式部分numeratortf*(self.k11)denominatortfself.k1*(1-self.bself.b*(doc_len/self.avg_dl))scoreidf*(numerator/denominator)scores.append(score)returnscores# --- 测试数据 ---docs[自然语言处理 是 人工智能 的 核心,人工智能 和 机器学习 都 很 重要,深度学习 是 机器学习 的 一种,自然语言处理 需要 大量 数据]# 初始化bm25SimpleBM25(docs)# 查询query自然语言处理 人工智能scoresbm25.get_score(query)print(f查询: {query})print(-*30)fori,scoreinenumerate(scores):print(f文档{i1}:{docs[i]})print(f得分:{score:.4f})print(-*30)# 排序结果ranked_indicessorted(range(len(scores)),keylambdai:scores[i],reverseTrue)print(排序后的最佳匹配:)foridxinranked_indices:print(f[{scores[idx]:.4f}]{docs[idx]})输出结果查询:自然语言处理 人工智能------------------------------ 文档1: 自然语言处理 是 人工智能 的 核心 得分:1.3863文档2: 人工智能 和 机器学习 都 很 重要 得分:0.6359文档3: 深度学习 是 机器学习 的 一种 得分:0.0000文档4: 自然语言处理 需要 大量 数据 得分:0.7617------------------------------ 排序后的最佳匹配:[1.3863]自然语言处理 是 人工智能 的 核心[0.7617]自然语言处理 需要 大量 数据[0.6359]人工智能 和 机器学习 都 很 重要[0.0000]深度学习 是 机器学习 的 一种bm25-jieba分词示例fromrank_bm25importBM25Okapiimportjieba# 1. 准备中文文档数据documents[自然语言处理是人工智能的核心领域广泛应用于搜索引擎。,机器学习和深度学习是人工智能的重要分支。,搜索引擎需要使用自然语言处理技术来理解用户查询。,今天天气不错适合出去打球。,# 无关文档自然语言处理技术在医疗领域的应用也在不断增加。]# 2. 中文分词 (关键步骤)# 使用 jieba 进行分词返回列表的列表tokenized_docs[list(jieba.cut(doc))fordocindocuments]# 3. 初始化 BM25 模型bm25BM25Okapi(tokenized_docs)# 4. 处理查询query自然语言处理 搜索引擎# 查询也必须分词tokenized_querylist(jieba.cut(query))# 5. 获取得分 (返回一个包含所有文档分数的列表)scoresbm25.get_scores(tokenized_query)# 6. 【修正点】手动排序并提取 Top Ndefget_top_n_documents(scores,documents,n3):# 将 (分数, 原始索引, 文档内容) 打包# enumerate(scores) 生成 (索引, 分数)ranked_resultssorted([(score,idx,doc)foridx,(score,doc)inenumerate(zip(scores,documents))],keylambdax:x[0],# 按分数排序reverseTrue# 降序)# 取前 N 个returnranked_results[:n]top_n3top_resultsget_top_n_documents(scores,documents,ntop_n)# 7. 展示结果print(f查询: {query})print(f分词后查询:{tokenized_query})print(-*50)forrank,(score,idx,doc)inenumerate(top_results,1):ifscore0:print(fRank{rank}(得分:{score:.4f}):)print(f 原文:{doc})print(f 分词预览:{/.join(list(jieba.cut(doc))[:5])}...)print()else:# 如果前 N 名里有得分为 0 的说明相关文档不足 N 个print(fRank{rank}: 无相关文档 (得分 0))# 如果你只需要索引列表 (模拟 top_n 的功能)top_indices[idxfor_,idx,_intop_results]print(fTop{top_n}文档索引列表:{top_indices})bm25-idf示例期待特别高频出现的词如的它的得分反而低。而量子力学这样的词得分反而高。代码fromrank_bm25importBM25Okapiimportmath# 构造极端数据# 文档 1-99: 都是废话 的 的 的# 文档 100: 包含稀有词 量子力学documents[[的]*10]*99[[量子力学]]# 注意这里为了演示我们没有过滤的看看会发生什么tokenized_docsdocuments bm25BM25Okapi(tokenized_docs)# 情况 A: 搜索常见词 的query_common[的]scores_commonbm25.get_scores(query_common)print(f搜索 的 (出现在 99 篇文档):)print(f 前 5 篇得分:{scores_common[:5]})# 应该都很低print(f 最后一篇(无的)得分:{scores_common[-1]})# 应该是 0# 情况 B: 搜索稀有词 量子力学query_rare[量子力学]scores_rarebm25.get_scores(query_rare)print(f\n搜索 量子力学 (只出现在 1 篇文档):)print(f 前 99 篇得分:{scores_rare[:5]})# 应该都是 0print(f 最后一篇得分:{scores_rare[-1]:.4f})# 应该非常高# 对比ifscores_rare[-1]scores_common[0]:print(\n✅ 验证成功稀有词的得分远高于常见词这就是 IDF 的作用)

更多文章