第一章、知识库文档处理
本项目是一个个人知识库助手项目,旨在帮助用户根据个人知识库内容,回答用户问题。个人知识库应当可以或许支持各种范例的数据,支持用户便捷地导入导出、进行管理。在我们的项目中,我们以 Datawhale 的一些经典开源课程作为示例,设计了多种文件范例,介绍每一种文件范例的处理方式,从而支持用户无难度地构建本身的知识库。
一、知识库设计
我们的知识库选用 Datawhale 一些经典开源课程、视频(部分)作为示例,具体包括:
· 《呆板学习公式详解》PDF版本
· 《面向开辟者的 LLM 入门教程`第一部分 Prompt Engineering》md版本
· 《强化学习入门指南》MP4版本
我们会将知识库源数据放置在 ../../data_base/knowledge_db 目录下。
二、 文档加载
1. PDF 文档
我们使用 PyMuPDFLoader 来读取知识库的 PDF 文件。PyMuPDFLoader 是 PDF 分析器中速度最快的一种,效果会包含 PDF 及其页面的具体元数据,并且每页返回一个文档。- ## 安装必要的库
- # !pip install rapidocr_onnxruntime -i https://pypi.tuna.tsinghua.edu.cn/simple
- # !pip install "unstructured[all-docs]" -i https://pypi.tuna.tsinghua.edu.cn/simple
- # !pip install pyMuPDF -i https://pypi.tuna.tsinghua.edu.cn/simple
复制代码- from langchain.document_loaders import PyMuPDFLoader
- # 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
- loader = PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf")
- # 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
- pages = loader.load()
复制代码 1.1 探索加载的数据
文档加载后储存在 pages 变量中:
- page 的变量范例为 List
- 打印 pages 的长度可以看到 pdf 一共包含多少页
- print(f"载入后的变量类型为:{type(pages)},", f"该 PDF 一共包含 {len(pages)} 页")
复制代码- 载入后的变量类型为:<class 'list'>, 该 PDF 一共包含 196 页
复制代码 page 中的每一元素为一个文档,变量范例为 langchain.schema.document.Document, 文档变量范例包含两个属性
- page_content 包含该文档的内容。
- meta_data 为文档相关的描述性数据。
- page = pages[1]
- print(f"每一个元素的类型:{type(page)}.",
- f"该文档的描述性数据:{page.metadata}",
- f"查看该文档的内容:\n{page.page_content[0:1000]}",
- sep="\n------\n")
复制代码- 每一个元素的类型:<class 'langchain.schema.document.Document'>.
- ------
- 该文档的描述性数据:{'source': '../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'file_path': '../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'page': 1, 'total_pages': 196, 'format': 'PDF 1.5', 'title': '', 'author': '', 'subject': '', 'keywords': '', 'creator': 'LaTeX with hyperref', 'producer': 'xdvipdfmx (20200315)', 'creationDate': "D:20230303170709-00'00'", 'modDate': '', 'trapped': ''}
- ------
- 查看该文档的内容:
- 前言
- “周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读
- 者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推
- 导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充
- 具体的推导细节。”
- 读到这里,大家可能会疑问为啥前面这段话加了引号,因为这只是我们最初的遐想,后来我们了解到,周
- 老师之所以省去这些推导细节的真实原因是,他本尊认为“理工科数学基础扎实点的大二下学生应该对西瓜书
- 中的推导细节无困难吧,要点在书里都有了,略去的细节应能脑补或做练习”。所以...... 本南瓜书只能算是我
- 等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二
- 下学生”。
- 使用说明
- • 南瓜书的所有内容都是以西瓜书的内容为前置知识进行表述的,所以南瓜书的最佳使用方法是以西瓜书
- 为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;
- • 对于初学机器学习的小白,西瓜书第 1 章和第 2 章的公式强烈不建议深究,简单过一下即可,等你学得
- 有点飘的时候再回来啃都来得及;
- • 每个公式的解析和推导我们都力 (zhi) 争 (neng) 以本科数学基础的视角进行讲解,所以超纲的数学知识
- 我们通常都会以附录和参考文献的形式给出,感兴趣的同学可以继续沿着我们给的资料进行深入学习;
- • 若南瓜书里没有你想要查阅的公式,或者你发现南瓜书哪个地方有错误,请毫不犹豫地去我们 GitHub 的
- Issues(地址:https://github.com/datawhalechina/pumpkin-book/issues)进行反馈,在对应版块
- 提交你希望补充的公式编号或者勘误信息,我们通常会在 24 小时以内给您回复,超过 24 小时未回复的
- 话可以微信联系我们(微信号:at-Sm1les);
- 配套视频教程:https://www.bilibili.com/video/BV1Mh411e7VU
- 在线阅读地址:https://datawhalechina.github.io/pumpkin-book(仅供第 1 版)
- 最新版 PDF 获取地址:https://github.com/datawhalechina/pumpkin-book/re
复制代码 2. MD 文档
我们可以以险些完全一致的方式读入 markdown 文档:- from langchain.document_loaders import UnstructuredMarkdownLoader
- loader = UnstructuredMarkdownLoader("../../data_base/knowledge_db/prompt_engineering/1. 简介 Introduction.md")
- pages = loader.load()
复制代码 读取的对象和 PDF 文档读取出来是完全一致的:- print(f"载入后的变量类型为:{type(pages)},", f"该 Markdown 一共包含 {len(pages)} 页")
复制代码- 载入后的变量类型为:<class 'list'>, 该 Markdown 一共包含 1 页
复制代码- page = pages[0]
- print(f"每一个元素的类型:{type(page)}.",
- f"该文档的描述性数据:{page.metadata}",
- f"查看该文档的内容:\n{page.page_content[0:]}",
- sep="\n------\n")
复制代码- 每一个元素的类型:<class 'langchain.schema.document.Document'>.
- ------
- 该文档的描述性数据:{'source': '../../data_base/knowledge_db/prompt_engineering/1. 简介 Introduction.md'}
- ------
- 查看该文档的内容:
- 第一章 简介
- 欢迎来到面向开发者的提示工程部分,本部分内容基于吴恩达老师的《Prompt Engineering for Developer》课程进行编写。《Prompt Engineering for Developer》课程是由吴恩达老师与 OpenAI 技术团队成员 Isa Fulford 老师合作授课,Isa 老师曾开发过受欢迎的 ChatGPT 检索插件,并且在教授 LLM (Large Language Model, 大语言模型)技术在产品中的应用方面做出了很大贡献。她还参与编写了教授人们使用 Prompt 的 OpenAI cookbook。我们希望通过本模块的学习,与大家分享使用提示词开发 LLM 应用的最佳实践和技巧。
- 网络上有许多关于提示词(Prompt, 本教程中将保留该术语)设计的材料,例如《30 prompts everyone has to know》之类的文章,这些文章主要集中在 ChatGPT 的 Web 界面上,许多人在使用它执行特定的、通常是一次性的任务。但我们认为,对于开发人员,大语言模型(LLM) 的更强大功能是能通过 API 接口调用,从而快速构建软件应用程序。实际上,我们了解到 DeepLearning.AI 的姊妹公司 AI Fund 的团队一直在与许多初创公司合作,将这些技术应用于诸多应用程序上。很兴奋能看到 LLM API 能够让开发人员非常快速地构建应用程序。
- 在本模块,我们将与读者分享提升大语言模型应用效果的各种技巧和最佳实践。书中内容涵盖广泛,包括软件开发提示词设计、文本总结、推理、转换、扩展以及构建聊天机器人等语言模型典型应用场景。我们衷心希望该课程能激发读者的想象力,开发出更出色的语言模型应用。
- 随着 LLM 的发展,其大致可以分为两种类型,后续称为基础 LLM 和指令微调(Instruction Tuned)LLM。基础LLM是基于文本训练数据,训练出预测下一个单词能力的模型。其通常通过在互联网和其他来源的大量数据上训练,来确定紧接着出现的最可能的词。例如,如果你以“从前,有一只独角兽”作为 Prompt ,基础 LLM 可能会继续预测“她与独角兽朋友共同生活在一片神奇森林中”。但是,如果你以“法国的首都是什么”为 Prompt ,则基础 LLM 可能会根据互联网上的文章,将回答预测为“法国最大的城市是什么?法国的人口是多少?”,因为互联网上的文章很可能是有关法国国家的问答题目列表。
- 与基础语言模型不同,指令微调 LLM 通过专门的训练,可以更好地理解并遵循指令。举个例子,当询问“法国的首都是什么?”时,这类模型很可能直接回答“法国的首都是巴黎”。指令微调 LLM 的训练通常基于预训练语言模型,先在大规模文本数据上进行预训练,掌握语言的基本规律。在此基础上进行进一步的训练与微调(finetune),输入是指令,输出是对这些指令的正确回复。有时还会采用RLHF(reinforcement learning from human feedback,人类反馈强化学习)技术,根据人类对模型输出的反馈进一步增强模型遵循指令的能力。通过这种受控的训练过程。指令微调 LLM 可以生成对指令高度敏感、更安全可靠的输出,较少无关和损害性内容。因此。许多实际应用已经转向使用这类大语言模型。
- 因此,本课程将重点介绍针对指令微调 LLM 的最佳实践,我们也建议您将其用于大多数使用场景。当您使用指令微调 LLM 时,您可以类比为向另一个人提供指令(假设他很聪明但不知道您任务的具体细节)。因此,当 LLM 无法正常工作时,有时是因为指令不够清晰。例如,如果您想问“请为我写一些关于阿兰·图灵( Alan Turing )的东西”,在此基础上清楚表明您希望文本专注于他的科学工作、个人生活、历史角色或其他方面可能会更有帮助。另外您还可以指定回答的语调, 来更加满足您的需求,可选项包括专业记者写作,或者向朋友写的随笔等。
- 如果你将 LLM 视为一名新毕业的大学生,要求他完成这个任务,你甚至可以提前指定他们应该阅读哪些文本片段来写关于阿兰·图灵的文本,这样能够帮助这位新毕业的大学生更好地完成这项任务。本书的下一章将详细阐释提示词设计的两个关键原则:清晰明确和给予充足思考时间。
复制代码 3. MP4 视频
LangChain 提供了对 Youtube 视频进行爬取并转写的处理接口,但是如果我们想直接对我们的本地 MP4 视频进行处理,必要起首经过转录加载成文本格式,在加载到 LangChain 中。
我们使用 Whisper 实现视频的转写,Whisper 的安装方式此处不再赘述,详见教程:知乎|开源免费离线语音识别神器whisper怎样安装
此处我们直接使用 Whisper 在原目录下输出转写效果:- whisper ../../data_base/knowledge_db/easy_rl/强化学习入门指南.mp4 --model large --model_dir whisper-large --language zh --output_dir ../../data_base/knowledge_db/easy_rl
复制代码 注意,此处 model_dir 参数应是你下载到本地的 large-whisper 参数路径。
转化完后,会在原目录下天生 强化学习入门指南.txt 文件,我们直接加载该 txt 文件即可:- from langchain.document_loaders import UnstructuredFileLoader
- loader = UnstructuredFileLoader("../../data_base/knowledge_db/easy_rl/强化学习入门指南.txt")
- pages = loader.load()
复制代码 加载出来的数据属性同上文一致:- page = pages[0]
- print(f"每一个元素的类型:{type(page)}.",
- f"该文档的描述性数据:{page.metadata}",
- f"查看该文档的内容:\n{page.page_content[0:1000]}",
- sep="\n------\n")
复制代码 三、文档分割
Langchain 中文天职割器都根据 chunk_size (块大小)和 chunk_overlap (块与块之间的重叠大小)进行分割。
- chunk_size 指每个块包含的字符或 Token (如单词、句子等)的数量
- chunk_overlap 指两个块之间共享的字符数量,用于保持上下文的连贯性,制止分割丢失上下文信息
Langchain 提供多种文档分割方式,区别在怎么确定块与块之间的边界、块由哪些字符/token组成、以及怎样测量块大小
- RecursiveCharacterTextSplitter(): 按字符串分割文本,递归地尝试按不同的分隔符进行分割文本。
- CharacterTextSplitter(): 按字符来分割文本。
- MarkdownHeaderTextSplitter(): 基于指定的标题来分割markdown 文件。
- TokenTextSplitter(): 按token来分割文本。
- SentenceTransformersTokenTextSplitter(): 按token来分割文本
- Language(): 用于 CPP、Python、Ruby、Markdown 等。
- NLTKTextSplitter(): 使用 NLTK(自然语言工具包)按句子分割文本。
- SpacyTextSplitter(): 使用 Spacy按句子的切割文本。
- '''
- * RecursiveCharacterTextSplitter 递归字符文本分割
- RecursiveCharacterTextSplitter 将按不同的字符递归地分割(按照这个优先级["\n\n", "\n", " ", ""]),
- 这样就能尽量把所有和语义相关的内容尽可能长时间地保留在同一位置
- RecursiveCharacterTextSplitter需要关注的是4个参数:
- * separators - 分隔符字符串数组
- * chunk_size - 每个文档的字符数量限制
- * chunk_overlap - 两份文档重叠区域的长度
- * length_function - 长度计算函数
- '''
- #导入文本分割器
- from langchain.text_splitter import RecursiveCharacterTextSplitter
复制代码- # 知识库中单段文本长度
- CHUNK_SIZE = 500
- # 知识库中相邻文本重合长度
- OVERLAP_SIZE = 50
复制代码- # 此处我们使用 PDF 文件作为示例from langchain.document_loaders import PyMuPDFLoader
- # 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
- loader = PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf")
- # 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
- pages = loader.load()page = pages[1]# 使用递归字符文天职割器from langchain.text_splitter import TokenTextSplittertext_splitter = RecursiveCharacterTextSplitter( chunk_size=CHUNK_SIZE, chunk_overlap=OVERLAP_SIZE)text_splitter.split_text(page.page_content[0:1000])
复制代码- ['前言\n“周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读\n者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推\n导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充\n具体的推导细节。”\n读到这里,大家可能会疑问为啥前面这段话加了引号,因为这只是我们最初的遐想,后来我们了解到,周\n老师之所以省去这些推导细节的真实原因是,他本尊认为“理工科数学基础扎实点的大二下学生应该对西瓜书\n中的推导细节无困难吧,要点在书里都有了,略去的细节应能脑补或做练习”。所以...... 本南瓜书只能算是我\n等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二\n下学生”。\n使用说明\n• 南瓜书的所有内容都是以西瓜书的内容为前置知识进行表述的,所以南瓜书的最佳使用方法是以西瓜书\n为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;\n• 对于初学机器学习的小白,西瓜书第 1 章和第 2 章的公式强烈不建议深究,简单过一下即可,等你学得',
- '有点飘的时候再回来啃都来得及;\n• 每个公式的解析和推导我们都力 (zhi) 争 (neng) 以本科数学基础的视角进行讲解,所以超纲的数学知识\n我们通常都会以附录和参考文献的形式给出,感兴趣的同学可以继续沿着我们给的资料进行深入学习;\n• 若南瓜书里没有你想要查阅的公式,或者你发现南瓜书哪个地方有错误,请毫不犹豫地去我们 GitHub 的\nIssues(地址:https://github.com/datawhalechina/pumpkin-book/issues)进行反馈,在对应版块\n提交你希望补充的公式编号或者勘误信息,我们通常会在 24 小时以内给您回复,超过 24 小时未回复的\n话可以微信联系我们(微信号:at-Sm1les);\n配套视频教程:https://www.bilibili.com/video/BV1Mh411e7VU\n在线阅读地址:https://datawhalechina.github.io/pumpkin-book(仅供第 1 版)\n最新版 PDF 获取地址:https://github.com/datawhalechina/pumpkin-book/re']
复制代码- split_docs = text_splitter.split_documents(pages)
- print(f"切分后的文件数量:{len(split_docs)}")
复制代码- print(f"切分后的字符数(可以用来大致评估 token 数):{sum([len(doc.page_content) for doc in split_docs])}")
复制代码- 切分后的字符数(可以用来大致评估 token 数):314712
复制代码 四、文档词向量化
在呆板学习和自然语言处理(NLP)中,Embeddings(嵌入)是一种将类别数据,如单词、句子大概整个文档,转化为实数向量的技术。这些实数向量可以被盘算机更好地明白和处理。嵌入背后的紧张想法是,相似或相关的对象在嵌入空间中的间隔应该很近。
举个例子,我们可以使用词嵌入(word embeddings)来表示文本数据。在词嵌入中,每个单词被转换为一个向量,这个向量捕获了这个单词的语义信息。比方,"king" 和 "queen" 这两个单词在嵌入空间中的位置将会非常接近,由于它们的寄义相似。而 "apple" 和 "orange" 也会很接近,由于它们都是水果。而 "king" 和 "apple" 这两个单词在嵌入空间中的间隔就会比较远,由于它们的寄义不同。
让我们取出我们的切分部分并对它们进行 Embedding 处理。
这里提供三种方式进行,一种是直接使用 openai 的模子去天生 embedding,另一种是使用 HuggingFace 上的模子去天生 embedding。
- openAI 的模子必要消耗 api,对于大量的token 来说成本会比较高,但黑白常方便。
- HuggingFace 的模子可以本地部署,可自界说合适的模子,可玩性较高,但对本地的资源有部分要求。
- 接纳其他平台的 api。对于获取 openAI key 不方便的同学可以接纳这种方法。
对于只想体验一下的同学来说,可以尝试直接用天生好的 embedding,大概在本地部署小模子进行尝试。
HuggingFace 是一个优秀的开源库,我们只必要输入模子的名字,就会自动帮我们分析对应的能力。- # 使用前配置自己的 api 到环境变量中如
- import os
- import openai
- import zhipuai
- import sys
- sys.path.append('../..')
- from dotenv import load_dotenv, find_dotenv
- _ = load_dotenv(find_dotenv()) # read local .env fileopenai.api_key = os.environ['OPENAI_API_KEY']
- openai.api_key = os.environ['OPENAI_API_KEY']
- zhihuai.api_key = os.environ['ZHIPUAI_API_KEY']
复制代码- from langchain.embeddings.openai import OpenAIEmbeddings
- from langchain.embeddings.huggingface import HuggingFaceEmbeddings
- from zhipuai_embedding import ZhipuAIEmbeddings
- # embedding = OpenAIEmbeddings()
- # embedding = HuggingFaceEmbeddings(model_name="moka-ai/m3e-base")
- embedding = ZhipuAIEmbeddings()
复制代码- import numpy as np
- from sklearn.metrics.pairwise import cosine_similarity
复制代码- query1 = "机器学习"
- query2 = "强化学习"
- query3 = "大语言模型"
- # 通过对应的 embedding 类生成 query 的 embedding。
- emb1 = embedding.embed_query(query1)
- emb2 = embedding.embed_query(query2)
- emb3 = embedding.embed_query(query3)
- # 将返回结果转成 numpy 的格式,便于后续计算
- emb1 = np.array(emb1)
- emb2 = np.array(emb2)
- emb3 = np.array(emb3)
复制代码 可以直接查看 embedding 的具体信息,embedding 的维度通常取决于所使用的模子。- print(f"{query1} 生成的为长度 {len(emb1)} 的 embedding , 其前 30 个值为: {emb1[:30]}")
复制代码- 机器学习 生成的为长度 1024 的 embedding , 其前 30 个值为: [-0.02768379 0.07836673 0.1429528 -0.1584693 0.08204 -0.15819356
- -0.01282174 0.18076552 0.20916627 0.21330206 -0.1205181 -0.06666514
- -0.16731478 0.31798768 0.0680017 -0.13807729 -0.03469152 0.15737721
- 0.02108428 -0.29145902 -0.10099868 0.20487919 -0.03603597 -0.09646764
- 0.12923686 -0.20558454 0.17238656 0.03429411 0.1497675 -0.25297147]
复制代码 我们已经天生了对应的向量,我们怎样度量文档和问题的相关性呢?
这里提供两种常用的方法:
- 盘算两个向量之间的点积。
- 盘算两个向量之间的余弦相似度
点积是将两个向量对应位置的元素相乘后求和得到的标量值。点积相似度越大,表示两个向量越相似。
这里直接使用 numpy 的函数进行盘算- print(f"{query1} 和 {query2} 向量之间的点积为:{np.dot(emb1, emb2)}")
- print(f"{query1} 和 {query3} 向量之间的点积为:{np.dot(emb1, emb3)}")
- print(f"{query2} 和 {query3} 向量之间的点积为:{np.dot(emb2, emb3)}")
复制代码- 机器学习 和 强化学习 向量之间的点积为:17.218882120572722
- 机器学习 和 大语言模型 向量之间的点积为:16.522186236712727
- 强化学习 和 大语言模型 向量之间的点积为:11.368461841901752
复制代码 点积:盘算简朴,快速,不必要进行额外的归一化步骤,但丢失了方向信息。
余弦相似度:可以同时比较向量的方向和数量级大小。
余弦相似度将两个向量的点积除以它们的模长的乘积。其基本的盘算公式为 $cos(θ)=∑i=1n(xi×yi)∑i=1n(xi)2×∑i=1n(yi)2cos(θ)=∑i=1n(xi)2×∑i=1n(yi)2∑i=1n(xi×yi)$余弦函数的值域在-1到1之间,即两个向量余弦相似度的范围是[-1, 1]。当两个向量夹角为0°时,即两个向量重合时,相似度为1;当夹角为180°时,即两个向量方向相反时,相似度为-1。即越接近于 1 越相似,越接近 0 越不相似。- print(f"{query1} 和 {query2} 向量之间的余弦相似度为:{cosine_similarity(emb1.reshape(1, -1) , emb2.reshape(1, -1) )}")
- print(f"{query1} 和 {query3} 向量之间的余弦相似度为:{cosine_similarity(emb1.reshape(1, -1) , emb3.reshape(1, -1) )}")
- print(f"{query2} 和 {query3} 向量之间的余弦相似度为:{cosine_similarity(emb2.reshape(1, -1) , emb3.reshape(1, -1) )}")
复制代码- 机器学习 和 强化学习 向量之间的余弦相似度为:[[0.68814796]]
- 机器学习 和 大语言模型 向量之间的余弦相似度为:[[0.63382724]]
- 强化学习 和 大语言模型 向量之间的余弦相似度为:[[0.43555894]]
复制代码 可以看出,模子认为呆板学习和强化学习更相关一点,强化学习和大语言模子之间的相关性更差。(这部分跟训练语料的时间相关,embedding 的模子应该没有大语言模子相关的语料。)
目前,我们已经学习了文档的基本处理,但是怎样管理我们天生的 embedding 并寻找和 query 最相关的内容呢?难道要每次遍历所有文档么?向量数据库可以帮我们快速的管理和盘算这些内容。
第二章、向量数据库的介绍及使用
一、向量数据库简介
向量数据库是用于高效盘算和管理大量向量数据的解决方案。向量数据库是一种专门用于存储和检索向量数据(embedding)的数据库体系。它与传统的基于关系模子的数据库不同,它紧张关注的是向量数据的特性和相似性。
在向量数据库中,数据被表示为向量形式,每个向量代表一个数据项。这些向量可以是数字、文本、图像或其他范例的数据。向量数据库使用高效的索引和查询算法来加快向量数据的存储和检索过程。
Langchain 集成了凌驾 30 个不同的向量存储库。我们选择 Chroma 是由于它轻量级且数据存储在内存中,这使得它非常容易启动和开始使用。- from langchain.vectorstores import Chroma
- from langchain.document_loaders import PyMuPDFLoader
- from langchain.text_splitter import RecursiveCharacterTextSplitter
- from langchain.embeddings.openai import OpenAIEmbeddings
- from langchain.embeddings.huggingface import HuggingFaceEmbeddings
- from zhipuai_embedding import ZhipuAIEmbeddings
- from langchain.llms import OpenAI
- from langchain.llms import HuggingFacePipeline
- from zhipuai_llm import ZhipuAILLM
复制代码- # 使用前配置自己的 api 到环境变量中如
- import os
- import openai
- import zhipuai
- import sys
- sys.path.append('../..')
- from dotenv import load_dotenv, find_dotenv
- _ = load_dotenv(find_dotenv()) # read local .env fileopenai.api_key = os.environ['OPENAI_API_KEY']
- openai.api_key = os.environ['OPENAI_API_KEY']
- zhipuai.api_key = os.environ['ZHIPUAI_API_KEY']
- os.environ["HTTP_PROXY"] = "http://127.0.0.1:7890"
- os.environ["HTTPS_PROXY"] = "http://127.0.0.1:7890"
复制代码- # 加载 PDF
- loaders_chinese = [
- PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf") # 南瓜书
- # 大家可以自行加入其他文件
- ]
- docs = []
- for loader in loaders_chinese:
- docs.extend(loader.load())
- # 切分文档
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150)
- split_docs = text_splitter.split_documents(docs)
- # 定义 Embeddings
- embedding = OpenAIEmbeddings()
- # embedding = HuggingFaceEmbeddings(model_name=model_name, model_kwargs=model_kwargs)
- # embedding = ZhipuAIEmbeddings()
复制代码- persist_directory = '../../data_base/vector_db/chroma'
复制代码- !rm -rf '../../data_base/vector_db/chroma' # 删除旧的数据库文件(如果文件夹中有文件的话),window电脑请手动删除
复制代码 二、构建 Chroma 向量库
- vectordb = Chroma.from_documents(
- documents=split_docs[:100], # 为了速度,只选择了前 100 个切分的 doc 进行生成。
- embedding=embedding,
- persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
- )
复制代码 在此之后,我们要确保通过运行 vectordb.persist 来持久化向量数据库,以便我们在将来的课程中使用。
让我们保存它,以便以后使用!大家也可以直接载入已经构建好的向量库- vectordb = Chroma(
- persist_directory=persist_directory,
- embedding_function=embedding
- )
复制代码- print(f"向量库中存储的数量:{vectordb._collection.count()}")
复制代码 三、通过向量数据库检索
3.1 相似度检索
- sim_docs = vectordb.similarity_search(question,k=3)
- print(f"检索到的内容数:{len(sim_docs)}")
复制代码- for i, sim_doc in enumerate(sim_docs):
- print(f"检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
复制代码- 检索到的第0个内容:
- 导,同时也能体会到这三门数学课在机器学习上碰撞产生的“数学之美”。
- 1.1
- 引言
- 本节以概念理解为主,在此对“算法”和“模型”作补充说明。“算法”是指从数据中学得“模型”的具
- 体方法,例如后续章节中将会讲述的线性回归、对数几率回归、决策树等。“算法”产出的结果称为“模型”,
- 通常是具体的函数或者可抽象地看作为函数,例如一元线性回归算法产出的模型即为形如 f(x) = wx + b
- 的一元一次函数。
- --------------
- 检索到的第1个内容:
- 模型:机器学习的一般流程如下:首先收集若干样本(假设此时有 100 个),然后将其分为训练样本
- (80 个)和测试样本(20 个),其中 80 个训练样本构成的集合称为“训练集”,20 个测试样本构成的集合
- 称为“测试集”,接着选用某个机器学习算法,让其在训练集上进行“学习”(或称为“训练”),然后产出
- 得到“模型”(或称为“学习器”),最后用测试集来测试模型的效果。执行以上流程时,表示我们已经默
- --------------
- 检索到的第2个内容:
- →_→
- 欢迎去各大电商平台选购纸质版南瓜书《机器学习公式详解》
- ←_←
- 第 1 章
- 绪论
- 本章作为“西瓜书”的开篇,主要讲解什么是机器学习以及机器学习的相关数学符号,为后续内容作
- 铺垫,并未涉及复杂的算法理论,因此阅读本章时只需耐心梳理清楚所有概念和数学符号即可。此外,在
- 阅读本章前建议先阅读西瓜书目录前页的《主要符号表》,它能解答在阅读“西瓜书”过程中产生的大部
- 分对数学符号的疑惑。
- 本章也作为
- --------------
复制代码 3.2 MMR 检索
如果只考虑检索出内容的相关性会导致内容过于单一,可能丢失紧张信息。
最大边际相关性 (MMR, Maximum marginal relevance) 可以帮助我们在保持相关性的同时,增加内容的丰富度。
焦点思想是在已经选择了一个相关性高的文档之后,再选择一个与已选文档相关性较低但是信息丰富的文档。这样可以在保持相关性的同时,增加内容的多样性,制止过于单一的效果。- mmr_docs = vectordb.max_marginal_relevance_search(question,k=3)
复制代码- for i, sim_doc in enumerate(mmr_docs):
- print(f"MMR 检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
复制代码- MMR 检索到的第0个内容:
- 导,同时也能体会到这三门数学课在机器学习上碰撞产生的“数学之美”。
- 1.1
- 引言
- 本节以概念理解为主,在此对“算法”和“模型”作补充说明。“算法”是指从数据中学得“模型”的具
- 体方法,例如后续章节中将会讲述的线性回归、对数几率回归、决策树等。“算法”产出的结果称为“模型”,
- 通常是具体的函数或者可抽象地看作为函数,例如一元线性回归算法产出的模型即为形如 f(x) = wx + b
- 的一元一次函数。
- --------------
- MMR 检索到的第1个内容:
- 而人工智能的基本挑战是
- 学习在不确定的情况下做出好的决策
- 这边我举个例子
- 比如你想让一个小孩学会走路
- 他就需要通过不断尝试来发现
- 怎么走比较好
- 怎么走比较快
- 强化学习的交互过程可以通过这张图来表示
- 强化学习由智能体和环境两部分组成
- 在强化学习过程中
- 智能体与环境一直在交互
- 智能体在环境中获取某个状态后
- 它会利用刚刚的状态输出一个动作
- 这个动作也被称为决策
- 然后这个动作会
- --------------
- MMR 检索到的第2个内容:
- 与基础语言模型不同,指令微调 LLM 通过专门的训练,可以更好地理解并遵循指令。举个例子,当询问“法国的首都是什么?”时,这类模型很可能直接回答“法国的首都是巴黎”。指令微调 LLM 的训练通常基于预训练语言模型,先在大规模文本数据上进行预训练,掌握语言的基本规律。在此基础上进行进一步的训练与微调(finetune),输入是指令,输出是对这些指令的正确回复。有时还会采用RLHF(reinforce
- --------------
复制代码 可以看到内容有了更多的差别。
四、构造检索式问答连
我们已经可以通过向量数据库找到最相关的内容了,接下来我们可以让 LLM 来用这些相关的内容回答我们的问题。
4.1 直接询问 LLM
基于 LangChain,我们可以构造一个使用 LLM 进行问答的检索式问答链,这是一种通过检索步骤进行问答的方法。我们可以通过传入一个语言模子和一个向量数据库来创建它作为检索器。然后,我们可以用问题作为查询调用它,得到一个答案。- # 导入检索式问答链
- from langchain.chains import RetrievalQA
复制代码- llm = OpenAI(temperature=0)
复制代码- # 可以使用 HuggingFacePipeline 本地搭建大语言模型
- model_id = 'THUDM/chatglm2-6b-int4' # 采用 int 量化后的模型可以节省硬盘占用以及实时量化所需的运算资源
- tokenizer = AutoTokenizer.from_pretrained(model_id)
- model = AutoModel.from_pretrained(model_id, trust_remote_code=True).half().quantize(4).cuda()
- model = model.eval()
- pipe = pipeline(
- "text2text-generation",
- model=model,
- tokenizer=tokenizer,
- max_length=100
- )
- llm = HuggingFacePipeline(pipeline=pipe)
复制代码- llm = ZhipuAILLM(model="chatglm_std", temperature=0)
复制代码- # 声明一个检索式问答链
- qa_chain = RetrievalQA.from_chain_type(
- llm,
- retriever=vectordb.as_retriever()
- )
复制代码- # 可以以该方式进行检索问答
- question = "本知识库主要包含什么内容"
- result = qa_chain({"query": question})
- print(f"大语言模型的回答为:{result['result']}")
复制代码- 大语言模型的回答为: 这个知识库主要包含了一些关于强化学习的基础知识,传统的强化学习算法,适用强化学习算法的解决方法,简单生动的例子,专业的公式推导和分析,注解,观念词,习题和面试题,代码实战,以及如何高效学习的指导。
复制代码 4.2 结合 prompt 提问
对于 LLM 来说,prompt 可以让更好的发挥大模子的能力。
我们起首界说了一个提示模板。它包含一些关于怎样使用下面的上下文片段的说明,然后有一个上下文变量的占位符。- from langchain.prompts import PromptTemplate
- # Build prompt
- template = """使用以下上下文片段来回答最后的问题。如果你不知道答案,只需说不知道,不要试图编造答案。答案最多使用三个句子。尽量简明扼要地回答。在回答的最后一定要说"感谢您的提问!"
- {context}
- 问题:{question}
- 有用的回答:"""
- QA_CHAIN_PROMPT = PromptTemplate.from_template(template)
复制代码- # Run chain
- qa_chain = RetrievalQA.from_chain_type(
- llm,
- retriever=vectordb.as_retriever(),
- return_source_documents=True,
- chain_type_kwargs={"prompt": QA_CHAIN_PROMPT}
- )
复制代码- question = " 2025 年大语言模型效果最好的是哪个模型"
复制代码- result = qa_chain({"query": question})
- print(f"LLM 对问题的回答:{result['result']}")
复制代码- LLM 对问题的回答:2025年,大语言模型的效果将取决于各种技术的发展情况。目前,OpenAI 的 GPT-3 模型是最先进的大语言模型,它的效果非常出色。但是,随着技术的发展,2025 年可能会有更先进的模型出现,效果更好。感谢您的提问!
复制代码 这里由于没有对应的信息,以是大语言模子只能回答不知道。您可以将知识库的内容调整为大语言模子综述的内容重新进行尝试。- print(f"向量数据库检索到的最相关的文档:{result['source_documents'][0]}")
复制代码- 向量数据库检索到的最相关的文档:page_content='在本模块,我们将与读者分享提升大语言模型应用效果的各种技巧和最佳实践。书中内容涵盖广泛,包括软件开发提示词设计、文本总结、推理、转换、扩展以及构建聊天机器人等语言模型典型应用场景。我们衷心希望该课程能激发读者的想象力,开发出更出色的语言模型应用。\n\n随着 LLM 的发展,其大致可以分为两种类型,后续称为基础 LLM 和指令微调(Instruction Tuned)LLM。基础LLM是基于文本训练数据,训练出预测下一个单词能力的模型。其通常通过在互联网和其他来源的大量数据上训练,来确定紧接着出现的最可能的词。例如,如果你以“从前,有一只独角兽”作为 Prompt ,基础 LLM 可能会继续预测“她与独角兽朋友共同生活在一片神奇森林中”。但是,如果你以“法国的首都是什么”为 Prompt ,则基础 LLM 可能会根据互联网上的文章,将回答预测为“法国最大的城市是什么?法国的人口是多少?”,因为互联网上的文章很可能是有关法国国家的问答题目列表。' metadata={'source': '../knowledge_base/prompt_engineering/1. 简介 Introduction.md'}
复制代码 这种方法非常好,由于它只涉及对语言模子的一次调用。然而,它也有范围性,即如果文档太多,可能无法将它们全部适配到上下文窗口中。
langchain 提供了几种不同的处理文档的方法:
范例界说/区别优点缺点Stuff将整个文本内容一次性输入给大模子进行处理。- 只调用大模子一次,节省盘算资源和时间。- 上下文信息完整,有助于明白团体语义。- 实用于处理较短的文本内容。- 不实用于处理较长的文本内容,可能导致模子过载。Refine通过多次调用大模子渐渐改进文本质量,进行多次迭代优化。- 可以在多次迭代中渐渐改进文本质量。- 实用于必要进行多次迭代优化的场景。- 增加了盘算资源和时间的消耗。- 可能必要多轮迭代才能达到期望的文本质量。- 不实用于实时性要求较高的场景。Map reduce将大模子应用于每个文档,并将输出作为新文档传递给另一个模子,最终得到单个输出。- 可以对多个文档进行并行处理,提高处理服从。- 可以通过多次迭代处理实现优化。- 实用于必要对多个文档进行处理和归并的场景。- 增加了盘算资源和时间的消耗。- 可能必要多轮迭代才能达到期望的效果。- 不实用于处理单个文档的场景。Map re-rank在每个文档上运行初始提示,为答案给出一个分数,返回得分最高的响应。- 可以根据置信度对文档进行排序和选择,提高效果的准确性。- 可以提供更可靠的答案。- 实用于必要根据置信度对文档进行排序和选择的场景。- 增加了盘算资源和时间的消耗。- 可能必要对多个文档进行评分和排序。- 不实用于不必要对文档进行排序和选择的场景。我们可以根据必要设置 chain_type 的参数,选择对应的处理方式。如:- RetrievalQA.from_chain_type(
- llm,
- retriever=vectordb.as_retriever(),
- chain_type="map_reduce"
- )
复制代码 第三章 构建项目数据库
在前面两章我们具体介绍了我们的知识库选择,并介绍了怎样加载、处理数据并构建、使用向量数据库。在本章中,我们结合之前解说的内容,具体构建出本项目使用的数据库,之后我们的 Prompt Engineeering 及迭代优化、验证评估均会在该数据库基础上进行。
在这里,我们使用 OpenAI 的 Embedding 模子实现向量化,大家也可以根据前面的解说选用不同的 Embedding 模子。
由于此处加载向量数据库必要一定时间运行,我们也提供了 py 脚本供大家使用。- # 首先实现基本配置
- from langchain.vectorstores import Chroma
- from langchain.document_loaders import PyMuPDFLoader
- from langchain.text_splitter import RecursiveCharacterTextSplitter
- from langchain.document_loaders import UnstructuredMarkdownLoader
- from langchain.document_loaders import UnstructuredFileLoader
- from langchain.embeddings.openai import OpenAIEmbeddings
- from langchain.embeddings.huggingface import HuggingFaceEmbeddings
- from langchain.llms import OpenAI
- from langchain.llms import HuggingFacePipeline
- # 使用前配置自己的 api 到环境变量中如
- import os
- import openai
- import sys
- from dotenv import load_dotenv, find_dotenv
- _ = load_dotenv(find_dotenv()) # read local .env fileopenai.api_key = os.environ['OPENAI_API_KEY']
- openai.api_key = os.environ['OPENAI_API_KEY']
复制代码 接下来逐个加载知识库里的文档:- #pdf
- # 加载 PDF
- loaders = [
- PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf") # 机器学习,
- ]
- docs = []
- for loader in loaders:
- docs.extend(loader.load())
复制代码- #md
- folder_path = "../../data_base/knowledge_db/prompt_engineering/"
- files = os.listdir(folder_path)
- loaders = []
- for one_file in files:
- loader = UnstructuredMarkdownLoader(os.path.join(folder_path, one_file))
- loaders.append(loader)
- for loader in loaders:
- docs.extend(loader.load())
复制代码- #mp4-txt
- loaders = [
- UnstructuredFileLoader("../../data_base/knowledge_db/easy_rl/强化学习入门指南.txt") # 机器学习,
- ]
- for loader in loaders:
- docs.extend(loader.load())
复制代码 然后对加载好的文档进行切片并向量化后存储到向量数据库中:- # 切分文档
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=150)
- split_docs = text_splitter.split_documents(docs)
- # 定义 Embeddings
- embedding = OpenAIEmbeddings()
- # 定义持久化路径
- persist_directory = '../../data_base/vector_db/chroma'
- # 加载数据库
- vectordb = Chroma.from_documents(
- documents=split_docs,
- embedding=embedding,
- persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
- )
复制代码- Using embedded DuckDB with persistence: data will be stored in: ../../data_base/vector_db/chroma
复制代码 最后将加载好的向量数据库持久化即可:免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |