LLM - 使用 RAG (检索增强生成) 多路召回 实现 精准知识问答 教程

欢迎关注我的CSDN:https://spike.blog.csdn.net/
本文地址:https://spike.blog.csdn.net/article/details/142629289

免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。


RAG

RAG (Retrieval-Augmented Generation,检索增强生成) 的多路召回,包括向量召回和本文召回,可用于精准知识问答,减轻大模型的幻觉问题,即:

  1. 并行:同时使用文本召回和向量召回,合计获得 TopN 个样本,再使用重排序的方式,获得 TopK 个样本,作为最终的召回文本。
  2. 串行:优先使用文本召回,召回 TopN 个样本,再使用向量排序,获得 TopK 个样本,作为最终的召回样本。

启动 Ollama 服务:

# 配置 HOST
export OLLAMA_HOST="0.0.0.0:11434"
# 配置 模型路径
export OLLAMA_MODELS="ollama_models"nohup ollama serve > nohup.ollama.out &

RAG 使用 LangChain 框架,参考:LangChain - Quickstart

LangChain 的相关依赖包,即:

pip install langchain
pip install beautifulsoup4
pip install faiss-cpu
pip install jiebapip install langchain-community
pip install langchain-huggingface
pip install rank_bm25
pip install langchain_openai

准备编码模型 BGE,即:

# https://huggingface.co/BAAI/bge-large-zh-v1.5
modelscope download --model BAAI/bge-large-zh-v1.5 --local_dir BAAI/bge-large-zh-v1.5

导入 LangChain 的相关 Python 包:

from typing import List
import jiebafrom langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.retrievers import BM25Retriever

使用 LangChain 读取外部文档 medical_data.txt,即:

loader = TextLoader('medical_data.txt')
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size = 500,chunk_overlap  = 0,length_function = len,separators=['\n']
)
docs = text_splitter.split_documents(documents)

其中 medical_data.txt (4999 条) 格式如下,已经组织成 questionanswer 的内容:

# ...
{'question': '曲匹地尔片的用法用量', 'answer': '注意:同种药品可由于不同的包装规格有不同的用法或用量。本文只供参考。如果不确定,请参看药品随带的说明书或向医生询问。口服。一次50~100mg(1-2片),3次/日,或遵医嘱。'}
# ...

Docs 是 list 格式,单项如下:

  • metadata 信息源
  • page_content 信息内容

即:

Document(metadata={'source': 'medical_data.txt'}, page_content="{'question': '曲匹地尔片的用法用量', 'answer': '注意:同种药品可由于不同的包装规格有不同的用法或用量。本文只供参考。如果不确定,请参看药品随带的说明书或向医生询问。口服。一次50~100mg(1-2片),3次/日,或遵医嘱。'}")

Query 是文档中已经问题,即:

query = "请问锁骨骨折多久能干活?"

使用 BM25Retriever 构建检索器,选择 TopK=10 个文档,因为是中文,预处理使用 Jieba 分词,即:

def preprocessing_func(text: str) -> List[str]:return list(jieba.cut(text))
retriever = BM25Retriever.from_documents(docs, preprocess_func=preprocessing_func, k=10)
bm25_res = retriever.invoke(query)

BM25 算法的核心,在于利用 词频(Term Frequency, TF) 和 逆文档频率(Inverse Document Frequency, IDF) 衡量文档与查询之间的相关性,同时引入文档长度信息,来调整相关性的计算。

构建向量 Embeddings 库:

embeddings = HuggingFaceEmbeddings(model_name='llm/BAAI/bge-large-zh-v1.5', model_kwargs = {'device': 'cuda:1'})
db = FAISS.from_documents(docs, embeddings)

其中 5000 条向量,构建 embeddings 需要 1min 15s,CPU 执行。

获取向量召回:

vector_res = db.similarity_search(query, k=10)

使用 RRF 算法,进行多路召回合并,10+10=20 选取最优的 10 个召回,即:

def rrf(vector_results: List[str], text_results: List[str], k: int=10, m: int=60):"""使用 RRF 算法对两组检索结果进行重排序params:vector_results (list): 向量召回的结果列表, 每个元素是专利IDtext_results (list): 文本召回的结果列表, 每个元素是专利IDk(int): 排序后返回前k个m (int): 超参数return:重排序后的结果列表,每个元素是(文档ID, 融合分数)"""doc_scores = {}# 遍历两组结果,计算每个文档的融合分数for rank, doc_id in enumerate(vector_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank+m)for rank, doc_id in enumerate(text_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank+m)# 将结果按融合分数排序sorted_results = [d for d, _ in sorted(doc_scores.items(), key=lambda x: x[1], reverse=True)[:k]]return sorted_resultsvector_results = [i.page_content for i in vector_res]
text_results = [i.page_content for i in bm25_res]
rrf_res = rrf(vector_results, text_results)

RRF (Reciprocal Rank Fusion, 倒数排名融合) 算法将多个检索结果合并一个聚合列表,通过每个列表中每个项目的排名取倒数,即 1 除以排名,将倒数排名在所有列表中相加,得到每个项目的最终得分。

提示词工程:

prompt = '''
任务目标:根据检索出的文档回答用户问题
任务要求:1、不得脱离检索出的文档回答问题2、若检索出的文档不包含用户问题的答案,请回答我不知道用户问题:
{}检索出的文档:
{}
'''

使用 Ollama 服务进行大模型推理,注意需要使用长 Token 模型,即:

from langchain_community.llms import Ollama
model = Ollama(model="qwen-2_5-32b-max-context:latest")
print(f"[Info] rrf_res: {len(rrf_res)}")
full_prompt = prompt.format(query, ''.join(rrf_res))
# print(f"[Info] prompt: {full_prompt}")
res = model.invoke(full_prompt)  # RAG
print(f"[Info] response: {res}")res = model.invoke(query)  # 非 RAG
print(f"[Info] response: {res}")

RAG 的输出,与文档高度一致,即:

锁骨骨折的恢复时间一般在3个月左右。虽然骨折刚刚愈合时可以进行轻微的工作,但若涉及重体力劳动,则通常需要大约半年的时间才能重新开始,最少也需要4-5个月。过早地从事重体力工作有可能导致骨折处再次受伤。因此,在这期间避免过度负重活动是十分重要的,以确保锁骨能完全恢复并维持愈合效果。

非 RAG 的输出:

锁骨骨折的恢复时间取决于骨折的严重程度以及治疗方法。一般来说,轻微到中度的锁骨骨折可能需要大约6-8周的时间来初步愈合,在这段时间内,患者可能会被建议限制肩部和上肢的活动以促进骨折部位的稳定与修复。
但是,能否重新开始工作还依赖于具体工作的性质。如果工作不需要使用受伤的手臂或肩膀进行高强度劳动,则在几周后可能就可以慢慢恢复工作。然而,如果是需要手臂大力操作的工作,则可能需要等待3个月甚至更长时间才能安全地返回工作岗位,并且最好等到医生确认骨折完全愈合为止。
因此,在考虑重返岗位之前,应该咨询主治医师的意见,确保不会对康复过程造成负面影响或导致二次伤害。

参考:https://github.com/wyf3/llm_related

全部源码:

from typing import Listimport jieba
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.llms import Ollama
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddingsclass RagRetriever(object):"""RAG retriever"""def __init__(self):loader = TextLoader(db_path)documents = loader.load()text_splitter = RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=0,length_function=len,separators=['\n'])docs = text_splitter.split_documents(documents)def preprocessing_func(text: str) -> List[str]:return list(jieba.cut(text))self.doc_retriever = BM25Retriever.from_documents(docs, preprocess_func=preprocessing_func, k=10)print("[Info] init doc done!")embeddings = HuggingFaceEmbeddings(model_name=bge_path,model_kwargs={'device': 'cuda:1'})self.db = FAISS.from_documents(docs, embeddings)print("[Info] init db done!")self.prompt = '''任务目标:根据检索出的文档回答用户问题任务要求:1、不得脱离检索出的文档回答问题2、若检索出的文档不包含用户问题的答案,请回答我不知道用户问题:{}检索出的文档:{}'''print("[Info] init all done!")@staticmethoddef rrf(vector_results: List[str], text_results: List[str], k: int = 10, m: int = 60):"""使用 RRF 算法对两组检索结果进行重排序params:vector_results (list): 向量召回的结果列表, 每个元素是专利IDtext_results (list): 文本召回的结果列表, 每个元素是专利IDk(int): 排序后返回前k个m (int): 超参数return:重排序后的结果列表,每个元素是(文档ID, 融合分数)"""doc_scores = {}# 遍历两组结果,计算每个文档的融合分数for rank, doc_id in enumerate(vector_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank + m)for rank, doc_id in enumerate(text_results):doc_scores[doc_id] = doc_scores.get(doc_id, 0) + 1 / (rank + m)# 将结果按融合分数排序sorted_results = [d for d, _ in sorted(doc_scores.items(), key=lambda x: x[1], reverse=True)[:k]]return sorted_resultsdef retrieve(self, query):bm25_res = self.doc_retriever.invoke(query)vector_res = self.db.similarity_search(query, k=10)vector_results = [i.page_content for i in vector_res]text_results = [i.page_content for i in bm25_res]rrf_res = self.rrf(vector_results, text_results)model = Ollama(model="qwen-2_5-32b-max-context:latest")print(f"[Info] rrf_res: {len(rrf_res)}")full_prompt = self.prompt.format(query, ''.join(rrf_res))# print(f"[Info] prompt: {full_prompt}")res1 = model.invoke(full_prompt)print(f"[Info] rag response: {res1}")res2 = model.invoke(query)print(f"[Info] n-rag response: {res2}")return res1, res2def main():query = "请问锁骨骨折多久能干活?"rr = RagRetriever()rr.retrieve(query)if __name__ == '__main__':main()

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

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

相关文章

想要编辑 PDF 文件?使用这 10 种最佳 PDF 编辑工具

您是否遇到过利益干系人要求您对 PDF 文件进行细微更改的情况? 通常我们会这样做! 但是,对你来说,做出要求的改变有多难呢? 好吧,当您没有用于创建 PDF 文件的源文件时,问题就来了。是的&…

求5X5的次小值/次大值

我们知道,求最大值和最小值是比较容易的,就是通过分组判断,然后再次比较即可求出,那么求出次小值/次大值怎么实现呢,本文提供一个设计的思路。 以5x5为例,求出次小值, 第一步,先分…

export command in Limux

export command in Linux with Examples The ‘export’ command is one of the essential built-in commands in the Bash shell, allowing users to manage environment variables effectively. It is defined in POSIX standards, which state that the shell will assign …

Elastic 在 IDC MarketScape:2024 年全球企业 SIEM 报告中被评为领导者

作者:来自 Elastic Mark Settle Elastic 在 IDC MarketScape 2024 年企业 SIEM 供应商评估中被评为领导者。 Elastic Security 利用 AI 驱动的安全分析(SIEM 的未来)实现威胁检测、调查和响应的现代化。它是 SOC 团队的首选工具,因…

如何修复变砖的手机并恢复丢失的数据

您可能之前听说过“变砖”,但您知道什么是变砖手机吗?正如许多论坛中经常提出的问题一样,我如何知道我的手机是否变砖了?好吧,手机变砖主要有两种类型,即软件变砖和硬变砖。软变砖手机意味着重启后您仍然可…

如何选购适合自己的内衣洗衣机?五款热门卓越型号测评推荐

相信不少小伙伴都跟我一样,一方面,认为内裤、袜子与大件的上衣、裤子放一块清洗,会感觉很不卫生,而且穿在身上也不安心。但是另一方面,本身又很懒惰,也不想自己用手洗,不但经常会遗漏一些污渍&a…

什么是 JWT?它是如何工作的?

松哥最近辅导了几个小伙伴秋招,有小伙伴在面小红书时遇到这个问题,这个问题想回答全面还是有些挑战,松哥结合之前的一篇旧文和大伙一起来聊聊。 一 无状态登录 1.1 什么是有状态 有状态服务,即服务端需要记录每次会话的客户端信…

排序(交换排序:快排)

快速排序: 写快排的注意事项 1.单趟排序hoare 2.不写优化只说优化就行 理想的情况下:每次排序都是二分,直到二分到最后,那就相当于递归高度次(logN),每一层单趟排都是O(N),时间复杂度O(NlogN) 空间复杂度就…

Wed前端--HTML基础

目录 一、开发工具 二、HTML文档结构 2.1头部head 2.1.1title标记 2.1.2元信息meta标记 具体实例 ​编辑 一、开发工具 最基础的开发工具是:HBuilder 二、HTML文档结构 HTML文档由头部head和主体body组成 头部head标记中可以定义标题样式,头部信…

【流计算】流计算概论

前言 作者在之前写过一个大数据的专栏,包含GFS、BigTable、MapReduce、HDFS、Hadoop、LSM树、HBase、Spark,专栏地址: https://blog.csdn.net/joker_zjn/category_12631789.html?fromshareblogcolumn&sharetypeblogcolumn&sharerI…

常用激活函数总结

文章目录 什么是激活函数激活函数的作用常用激活函数1.Sigmoid函数2.Softmax函数3.Tanh函数4.Relu函数5.LeakyRelu函数6.PRelu函数7.ELU函数8.SELU函数 什么是激活函数 激活函数,通俗讲,就是一个函数,针对某个神经元,就是将输入经…

智慧园区与智能城市融合发展的关键举措

随着科技的快速发展和城市化进程的加速,智慧园区和智能城市作为未来城市发展的重要方向备受关注。智慧园区注重企业创新与产业升级,而智能城市着眼于城市管理与居民生活的便利。它们之间的融合将促进城市发展的智能化、可持续化和协同化。以下是实现智慧…

【web开发网页制作】Html+Css郑州大学保卫处学校网页制作(5页面附源码)

HTMLCSS学校主题网页目录 🍔涉及知识🥤写在前面🍧一、网页主题🌳二、网页效果Page1、首页Page2、通知公告Page3、公告详情页Page4、新闻动态页Page5、服务专区 🐋三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说…

华为OD机试 - 匿名信(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试真题(Python/JS/C/C)》。 刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,…

【网络安全 白帽子】用技术合法挖漏洞,一个月能拿多少钱

现在很多白帽子(网安工程师/渗透测试工程师)都会在下班之后利用业余时间去补天之类的漏洞赏金平台挖漏洞,用技术合法给企业找出威胁存在,拿到漏洞赏金。 那么现在,一般人用网安技术挖漏洞一个月能拿多少钱&#xff1f…

C++进阶:AVL树保持平衡的秘诀

✨✨所属专栏:C✨✨ ✨✨作者主页:嶔某✨✨ AVL树的底层实现代码:BSTree.h 钦某/Clearning 什么是AVL树? • AVL树是最先发明的⾃平衡⼆叉查找树,AVL是⼀颗空树,或者具备下列性质的⼆叉搜索树&#xff1…

【Linux基础IO】Linux IO编程入门:揭秘动态库与静态库的秘密

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:Linux “ 登神长阶 ” 🤡往期回顾🤡:Linux Shell 🌹🌹期待您的关注 🌹🌹 ❀Linux基础IO 📒1. …

在编写vue3项目过程中,不希望ts校验怎么设置

问: 现在我查询了很多设置 可以看到,设置了很多关闭校验,但是仍然有报错提示,提示当前类型不正确,应该怎么办? 回答: 从其他人的博客中获取灵感,因为vscode的自己的设置了类型检查…

DL_语义分割(学习笔记)

文章目录 图像分割1 常见分类1.1 语义分割1.2 实例分割1.3 全景分割 2 语义分割2.1 模型评价指标2.2 常用数据集2.3 转置卷积2.4 膨胀卷积2.5 感受野2.6 双线性插值2.7 FCN 图像分割 1 常见分类 1.1 语义分割 定义:【只判断类别,无法区分个体】 语义分…

Tableau 瀑布图应用示例

通过探索 10 个示例,将瀑布图的应用拓展到更深层次的业务分析! 作为一种直观展示数据变化的图表,瀑布图被广泛应用在业务分析中。同时,借助 Tableau 2024.2 中的 Viz Extensions,如今我们可以快速在 Tableau 中实现瀑布…