基于传统信息检索算法 BM25 的检索器 和 基于大语言模型生成的文本嵌入(dense embeddings)来计算相似度的检索器 两者的区别和联系

news/2025/2/24 8:37:09

首先给出示例代码:

python">#### INDEXING ####

# Load blog
import bs4
from langchain_community.document_loaders import WebBaseLoader

# 设置一个常见的浏览器 User-Agent 字符串
os.environ["USER_AGENT"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"

loader = WebBaseLoader(
    web_paths=("https://www.yixue.com/%E6%80%8E%E6%A0%B7%E7%9C%8B%E8%A1%80%E5%B8%B8%E8%A7%84%E5%8C%96%E9%AA%8C%E5%8D%95",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("mw-body-content")
        )
    ),
)

blog_docs = loader.load()

# Split
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=300, 
    chunk_overlap=50)

# Make splits
splits = text_splitter.split_documents(blog_docs)
# print(splits)

# Index
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

model_name = "BAAI/bge-small-zh-v1.5"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}
hf_embeddings = HuggingFaceBgeEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)

from langchain_community.vectorstores import Chroma
vectorstore = Chroma.from_documents(documents=splits, 
                                    embedding=hf_embeddings)


retriever = vectorstore.as_retriever()

dense_retriever = retriever

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

bm25_retriever = BM25Retriever.from_documents(splits)

ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, dense_retriever], weights=[0.5, 0.5]
)

question = "血红蛋白(Hb)测定人体正常范围是多少?"
docs = ensemble_retriever.invoke(question)
docs

下面我们一步步解释这段代码的作用和含义。


1. 加载网页内容

python">import bs4
from langchain_community.document_loaders import WebBaseLoader

os.environ["USER_AGENT"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"

loader = WebBaseLoader(
    web_paths=("https://www.yixue.com/%E6%80%8E%E6%A0%B7%E7%9C%8B%E8%A1%80%E5%B8%B8%E8%A7%84%E5%8C%96%E9%AA%8C%E5%8D%95",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            class_=("mw-body-content")
        )
    ),
)

blog_docs = loader.load()

解释:

  • 设置 User-Agent: 为了防止网站屏蔽爬虫,这里设置了一个常见浏览器的 User-Agent 字符串。
  • WebBaseLoader: 利用 WebBaseLoader 加载指定 URL(这里是一个医学科普博客页面),并只解析 HTML 中 CSS 类名为 mw-body-content 的部分(通常是页面的主要内容),这样可以过滤掉导航栏、广告等无关信息。
  • 加载文档: loader.load() 将网页内容提取出来,存储在变量 blog_docs 中。

举例说明:
假如你想获取一本在线教程中的正文内容而不包含广告和侧边栏,通过这种方法就能只提取核心内容。


2. 文本分块

python">from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=300, 
    chunk_overlap=50)

splits = text_splitter.split_documents(blog_docs)

解释:

  • 文本切分器: 这里使用了 RecursiveCharacterTextSplitter 将加载的长文本分割成更小的块,每块最多 300 个字符,且相邻块有 50 个字符的重叠。
  • 目的: 切分后的文本块更容易生成向量表示,方便后续的检索和匹配,同时重叠部分可以避免信息断裂,保证上下文连续性。

举例说明:
类似于将一本书拆成多个小段,每段之间有部分重叠,这样在搜索时,即使某一段只有部分信息,也能通过重叠部分关联到完整内容。


3. 构建向量索引

python">from langchain_community.embeddings import HuggingFaceBgeEmbeddings

model_name = "BAAI/bge-small-zh-v1.5"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}
hf_embeddings = HuggingFaceBgeEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)

解释:

  • 生成嵌入: 使用 HuggingFace 上的中文模型 “BAAI/bge-small-zh-v1.5” 将每个文本块转换为数值向量(嵌入)。
  • 归一化: 归一化后的向量在计算相似度时更稳定,适合后续比较。

举例说明:
就像给每一段文字都制作一个数字指纹,这样当你输入查询问题时,可以计算数字指纹之间的相似度,找到最匹配的文本段。


4. 构建向量库并设置检索器

python">from langchain_community.vectorstores import Chroma
vectorstore = Chroma.from_documents(documents=splits, 
                                    embedding=hf_embeddings)

retriever = vectorstore.as_retriever()

dense_retriever = retriever

解释:

  • 向量库: 使用 Chroma 将所有文本块及其嵌入向量存入一个向量数据库。
  • 检索器: 通过 as_retriever() 方法,将向量库包装成一个检索器,可以根据查询计算相似度并返回最相关的文本块。
  • dense_retriever: 这里将基于向量相似度的检索器赋值给变量 dense_retriever,表示这是一种基于“密集向量”(dense vector)的检索方法。

举例说明:
类似于在图书馆中给每本书都贴上了一个数字标签,当你搜索某个主题时,可以快速比对这些数字标签,找出最相关的书。


5. 构造 BM25 检索器及集成检索器(Ensemble Retriever)

python">from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

bm25_retriever = BM25Retriever.from_documents(splits)

ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, dense_retriever], weights=[0.5, 0.5]
)

解释:

  • BM25Retriever: 这是一个基于 BM25 算法的传统检索器,它利用关键词、词频等统计信息来衡量查询与文档之间的匹配。
  • 集成检索器 EnsembleRetriever: 将两种检索方法组合起来,通过设置相同的权重(各 50%)对查询结果进行综合排序。
  • 目的: 利用传统关键词匹配(BM25)和基于语义的向量匹配(dense_retriever)的优势,互补不足,从而得到更全面、更准确的检索结果。

举例说明:
假设你搜索“血红蛋白正常值”,传统 BM25 检索器会根据关键词“血红蛋白”、“正常值”来查找完全匹配的段落,而 dense_retriever 则能发现语义上相关但词汇不同的内容。将两者融合后,你既不会错过精确匹配的内容,也能捕捉到隐含语义相似的答案。


6. 检索问答示例

python">question = "血红蛋白(Hb)测定人体正常范围是多少?"
docs = ensemble_retriever.invoke(question)
docs

在这里插入图片描述

解释:

  • 输入问题: 设置查询问题:“血红蛋白(Hb)测定人体正常范围是多少?”
  • 调用检索器: 通过 ensemble_retriever.invoke(question) 进行查询,综合 BM25 和 dense 检索器的结果,返回与问题最相关的文本块。
  • 输出结果: 最终返回的 docs 变量中包含了经过排序后认为最能回答这个问题的文本内容。

举例说明:
假设页面中有一段文字写道:“一般来说,男性血红蛋白正常值在13.5至17.5 g/dL之间,女性在12.0至15.5 g/dL之间。”经过检索器处理后,这段内容就可能被排在最前面,作为回答问题的依据。


总结

整段代码的整体流程如下:

  1. 加载网页内容 —— 从指定博客页面中提取核心内容;
  2. 文本分块 —— 将长文档切分成小块,保证后续处理更加高效;
  3. 生成向量嵌入 —— 利用预训练模型将每个文本块转换为数值向量;
  4. 构建向量库 —— 存储所有嵌入并包装成检索器;
  5. 构造两个检索器并集成 —— 使用传统关键词匹配(BM25)和语义匹配(dense 检索器)各自查找相关文档,然后综合排序;
  6. 执行检索问答 —— 输入查询问题,得到与问题最相关的文本块作为答案依据。

通过这种方法,即使面对不同表达方式的查询,也能综合多种检索策略,给出更加准确和全面的答案。

这段代码中用到两个检索器是为了利用各自的优势,从而提高检索结果的全面性和准确性。

  • BM25Retriever:
    这是一个基于传统信息检索算法 BM25 的检索器。BM25 算法利用文档中的关键词、词频和逆文档频率(IDF)等统计信息,来衡量查询和文档之间的匹配程度。它在捕捉精确词汇匹配方面表现很好,适合那些查询中包含明确关键词的场景。

  • dense_retriever:
    这是基于大语言模型生成的文本嵌入(dense embeddings)来计算相似度的检索器。它通过将查询和文档都转换为向量,然后计算它们之间的相似性(例如使用余弦相似度),可以捕捉到更深层次的语义信息。这样,即使查询和文档之间没有完全相同的关键词,也能发现它们在语义上的相似性。

  • 两者的区别和联系:

    • 区别: BM25Retriever 依赖于传统的关键词统计和匹配,注重词汇层面的精确匹配;而 dense_retriever 则依赖于预训练语言模型的语义理解能力,更擅长捕捉隐含语义信息。
    • 联系: 两者都旨在评估查询和文档之间的相关性,但侧重点不同。通过组合它们(例如各占 50% 权重),可以使得检索系统既能利用关键词匹配的精确性,又能发挥语义匹配的广泛覆盖能力,从而提升整体检索效果。

这种集成方法通常能弥补单一方法的不足,使得最终的检索结果既精准又全面。


http://www.niftyadmin.cn/n/5864120.html

相关文章

ZLMediaKi集群设置

要在集群环境中部署 ZLMediaKit,您可以按照以下步骤进行操作。ZLMediaKit 是一个高性能的流媒体服务器,支持 RTMP、RTSP、HLS 等协议。以下是一个详细的集群部署方案: ### 1. 环境准备 - **服务器**:准备多台服务器,…

使用 Power Automate 转换 HTML to PDF

前言 今天,我们看一下如何使用Adobe PDF服务,转换HTML到PDF。 正文 1.先在Flow的最上面,声明两个参数,如下图: 2.通过搜索找到Adobe PDF Services,如下图: 3.找到convert-static-html-to-pdf&am…

架构对比分析

您提到的两种架构描述本质上遵循相同的分层设计理念,但存在差异的原因在于 视角不同 和 硬件平台特性。以下是详细解析: 一、架构对比分析 1. 逻辑分层(通用软件设计视角) 应用层(UI/用户交互)↓ 业务逻辑…

Python selenium 库

Selenium 是一个用于自动化 Web 浏览器操作的强大工具,广泛应用于 Web 应用程序测试、网页数据抓取和任务自动化等场景。 Selenium 为各种编程语言提供了 API,用作测试。 目前的官方 API 文档有 C#、JavaScript、Java、Python、Ruby。 安装 Selenium 和…

第一届网谷杯

统计四场的所有题目(共计12题,四场比赛一共上了21题【包括换题】) 随便记记,以免老题复用(已经复用了) Web 文件包含 1 伪协议 http://120.202.175.143:8011/?cphp://filter/convert.base64-encode/reso…

【练习】【类似于子集问题】力扣491. 非递减子序列/递增子序列

题目 非递减子序列 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况…

WPF框架学习

WPF 可以想winfrom 那样在cs文件修改 属性数据; 为了前后端分离 而解耦合,有了M-V-VM模式 常见框架有 MVVMlight / Prism 等 ------------------------------------------------------------------------------------- 一、前提:有一定基…

Xcode如何高效的一键重命名某个关键字

1.选中某个需要修改的关键字; 2.右击,选择Refactor->Rename… 然后就会出现如下界面: 此时就可以一键重命名了。 还可以设置快捷键。 1.打开Settings 2.找到Key Bindings 3.搜索rename 4.出现三个,点击一个地方设置后其…