赞
踩
词向量(Embeddings)是一种将非结构化数据,如单词、句子或者整个文档,转化为实数向量的技术。
嵌入背后的主要想法是,相似或相关的对象在嵌入空间中的距离应该很近。
举个例子,我们可以使用词嵌入(word embeddings)来表示文本数据。在词嵌入中,每个单词被转换为一个向量,这个向量捕获了这个单词的语义信息。例如,“king” 和 “queen” 这两个单词在嵌入空间中的位置将会非常接近,因为它们的含义相似。而 “apple” 和 “orange” 也会很接近,因为它们都是水果。而 “king” 和 “apple” 这两个单词在嵌入空间中的距离就会比较远,因为它们的含义不同。
在RAG(Retrieval Augmented Generation,检索增强生成)方面词向量的优势主要有两点:
在搭建 RAG 系统时,我们往往可以通过使用嵌入模型来构建词向量,我们可以选择:
使用各个公司的 Embedding API;
在本地使用嵌入模型将数据构建为词向量。
向量数据库是用于高效计算和管理大量向量数据的解决方案。
向量数据库是一种专门用于存储和检索向量数据(embedding)的数据库系统。它与传统的基于关系模型的数据库不同,它主要关注的是向量数据的特性和相似性。
在向量数据库中,数据被表示为向量形式,每个向量代表一个数据项。这些向量可以是数字、文本、图像或其他类型的数据。向量数据库使用高效的索引和查询算法来加速向量数据的存储和检索过程。
向量数据库中的数据以向量作为基本单位,对向量进行存储、处理及检索。向量数据库通过计算与目标向量的余弦距离、点积等获取与目标向量的相似度。当处理大量甚至海量的向量数据时,向量数据库索引和查询算法的效率明显高于传统数据库。
Chroma:是一个轻量级向量数据库,拥有丰富的功能和简单的 API,具有简单、易用、轻量的优点,但功能相对简单且不支持GPU加速,适合初学者使用。
Weaviate:是一个开源向量数据库。除了支持相似度搜索和最大边际相关性(MMR,Maximal Marginal Relevance)搜索外还可以支持结合多种搜索算法(基于词法搜索、向量搜索)的混合搜索,从而搜索提高结果的相关性和准确性。
Qdrant:Qdrant使用 Rust 语言开发,有极高的检索效率和RPS(Requests Per Second),支持本地运行、部署在本地服务器及Qdrant云三种部署模式。且可以通过为页面内容和元数据制定不同的键来复用数据。
目前GPT embedding mode有三种,性能如下所示:
从以上三个embedding model我们可以看出text-embedding-3-large有最好的性能和最贵的价格,当我们搭建的应用需要更好的表现且成本充足的情况下可以使用;text-embedding-3-small有着较好的性能跟价格,当我们预算有限时可以选择该模型;而text-embedding-ada-002是OpenAI上一代的模型,无论在性能还是价格都不如及前两者,因此不推荐使用。
import os from openai import OpenAI from dotenv import load_dotenv, find_dotenv # 读取本地/项目的环境变量。 # find_dotenv()寻找并定位.env文件的路径 # load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中 # 如果你设置的是全局的环境变量,这行代码则没有任何作用。 _ = load_dotenv(find_dotenv()) # 如果你需要通过代理端口访问,你需要如下配置 os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890' os.environ["HTTP_PROXY"] = 'http://127.0.0.1:7890' def openai_embedding(text: str, model: str=None): # 获取环境变量 OPENAI_API_KEY api_key=os.environ['OPENAI_API_KEY'] client = OpenAI(api_key=api_key) # embedding model:'text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002' if model == None: model="text-embedding-3-small" response = client.embeddings.create( input=text, model=model ) return response response = openai_embedding(text='要生成 embedding 的输入文本,字符串形式。')
API返回的数据为json格式,除object向量类型外还有存放数据的data、embedding model 型号model以及本次 token 使用情况usage等数据,具体如下所示:
{ "object": "list", "data": [ { "object": "embedding", "index": 0, "embedding": [ -0.006929283495992422, ... (省略) -4.547132266452536e-05, ], } ], "model": "text-embedding-3-small", "usage": { "prompt_tokens": 5, "total_tokens": 5 } }
我们可以调用response的object来获取embedding的类型。
print(f'返回的embedding类型为:{response.object}')
返回的embedding类型为:list
embedding存放在data中,我们可以查看embedding的长度及生成的embedding。
print(f'embedding长度为:{len(response.data[0].embedding)}')
print(f'embedding(前10)为:{response.data[0].embedding[:10]}')
embedding长度为:1536
embedding(前10)为:[0.03884002938866615, 0.013516489416360855, -0.0024250170681625605, -0.01655769906938076, 0.024130908772349358, -0.017382603138685226, 0.04206013306975365, 0.011498954147100449, -0.028245486319065094, -0.00674333656206727]
我们也可以查看此次embedding的模型及token使用情况。
print(f'本次embedding model为:{response.model}')
print(f'本次token使用情况为:{response.usage}')
本次embedding model为:text-embedding-3-small
本次token使用情况为:Usage(prompt_tokens=12, total_tokens=12)
Embedding-V1是基于百度文心大模型技术的文本表示模型,Access token为调用接口的凭证,使用Embedding-V1时应先凭API Key、Secret Key获取Access token,再通过Access token调用接口来embedding text。同时千帆大模型平台还支持bge-large-zh等embedding model。
import requests import json def wenxin_embedding(text: str): # 获取环境变量 wenxin_api_key、wenxin_secret_key api_key = os.environ['QIANFAN_AK'] secret_key = os.environ['QIANFAN_SK'] # 使用API Key、Secret Key向https://aip.baidubce.com/oauth/2.0/token 获取Access token url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={0}&client_secret={1}".format(api_key, secret_key) payload = json.dumps("") headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' } response = requests.request("POST", url, headers=headers, data=payload) # 通过获取的Access token 来embedding text url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/embeddings/embedding-v1?access_token=" + str(response.json().get("access_token")) input = [] input.append(text) payload = json.dumps({ "input": input }) headers = { 'Content-Type': 'application/json' } response = requests.request("POST", url, headers=headers, data=payload) return json.loads(response.text) # text应为List(string) text = "要生成 embedding 的输入文本,字符串形式。" response = wenxin_embedding(text=text)
Embedding-V1每次embedding除了有单独的id外,还有时间戳记录embedding的时间。
print('本次embedding id为:{}'.format(response['id']))
print('本次embedding产生时间戳为:{}'.format(response['created']))
本次embedding id为:as-hvbgfuk29u
本次embedding产生时间戳为:1711435238
同样的我们也可以从response中获取embedding的类型和embedding。
print('返回的embedding类型为:{}'.format(response['object']))
print('embedding长度为:{}'.format(len(response['data'][0]['embedding'])))
print('embedding(前10)为:{}'.format(response['data'][0]['embedding'][:10]))
返回的embedding类型为:embedding_list
embedding长度为:384
embedding(前10)为:[0.060567744076251984, 0.020958080887794495, 0.053234219551086426, 0.02243831567466259, -0.024505289271473885, -0.09820500761270523, 0.04375714063644409, -0.009092536754906178, -0.020122773945331573, 0.015808865427970886]
尚未开放
智谱有封装好的SDK,我们调用即可。
from zhipuai import ZhipuAI
def zhipu_embedding(text: str):
api_key = os.environ['ZHIPUAI_API_KEY']
client = ZhipuAI(api_key=api_key)
response = client.embeddings.create(
model="embedding-2",
input=text,
)
return response
text = '要生成 embedding 的输入文本,字符串形式。'
response = zhipu_embedding(text=text)
response为zhipuai.types.embeddings.EmbeddingsResponded类型,我们可以调用object、data、model、usage来查看response的embedding类型、embedding、embedding model及使用情况。
print(f'response类型为:{type(response)}')
print(f'embedding类型为:{response.object}')
print(f'生成embedding的model为:{response.model}')
print(f'生成的embedding长度为:{len(response.data[0].embedding)}')
print(f'embedding(前10)为: {response.data[0].embedding[:10]}')
response类型为:<class 'zhipuai.types.embeddings.EmbeddingsResponded'>
embedding类型为:list
生成embedding的model为:embedding-2
生成的embedding长度为:1024
embedding(前10)为: [0.017892399802803993, 0.0644201710820198, -0.009342825971543789, 0.02707476168870926, 0.004067837726324797, -0.05597858875989914, -0.04223804175853729, -0.03003198653459549, -0.016357755288481712, 0.06777040660381317]
为构建我们的本地知识库,我们需要对以多种类型存储的本地文档进行处理,读取本地文档并通过前文描述的 Embedding 方法将本地文档的内容转化为词向量来构建向量数据库。在本节中,我们以一些实际示例入手,来讲解如何对本地文档进行处理。
选用 Datawhale 一些经典开源课程作为示例,具体包括:
《机器学习公式详解》PDF版本
《面向开发者的LLM入门教程、第一部分Prompt Engineering》md版本
我们将知识库源数据放置在…/data_base/knowledge_db 目录下。
我们可以使用 LangChain 的 PyMuPDFLoader 来读取知识库的 PDF 文件。PyMuPDFLoader 是 PDF 解析器中速度最快的一种,结果会包含 PDF 及其页面的详细元数据,并且每页返回一个文档。
from langchain.document_loaders.pdf import PyMuPDFLoader
# 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
loader = PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf")
# 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
pdf_pages = loader.load()
文档加载后储存在 pages 变量中:
print(f"载入后的变量类型为:{type(pdf_pages)},", f"该 PDF 一共包含 {len(pdf_pages)} 页")
载入后的变量类型为:<class 'list'>, 该 PDF 一共包含 196 页
page 中的每一元素为一个文档,变量类型为 langchain_core.documents.base.Document, 文档变量类型包含两个属性
pdf_page = pdf_pages[1] print(f"每一个元素的类型:{type(pdf_page)}.", f"该文档的描述性数据:{pdf_page.metadata}", f"查看该文档的内容:\n{pdf_page.page_content}", sep="\n------\n") #输出内容 每一个元素的类型:<class 'langchain_core.documents.base.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/releases 编委会 主编:Sm1les、archwalker、jbb0523 编委:juxiao、Majingmin、MrBigFan、shanry、Ye980226 封面设计:构思-Sm1les、创作-林王茂盛 致谢 特别感谢awyd234、 feijuan、 Ggmatch、 Heitao5200、 huaqing89、 LongJH、 LilRachel、 LeoLRH、 Nono17、 spareribs、sunchaothu、StevenLzq 在最早期的时候对南瓜书所做的贡献。 扫描下方二维码,然后回复关键词“南瓜书” ,即可加入“南瓜书读者交流群” 版权声明 本作品采用知识共享署名-非商业性使用-相同方式共享4.0 国际许可协议进行许可。
我们可以以几乎完全一致的方式读入 markdown 文档:
from langchain.document_loaders.markdown import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader("../../data_base/knowledge_db/prompt_engineering/1. 简介 Introduction.md")
md_pages = loader.load()
读取的对象和 PDF 文档读取出来是完全一致的:
#打印1 print(f"载入后的变量类型为:{type(md_pages)},", f"该 Markdown 一共包含 {len(md_pages)} 页") #输出1 载入后的变量类型为:<class 'list'>, 该 Markdown 一共包含 1 页 #打印2 md_page = md_pages[0] print(f"每一个元素的类型:{type(md_page)}.", f"该文档的描述性数据:{md_page.metadata}", f"查看该文档的内容:\n{md_page.page_content[0:][:200]}", sep="\n------\n") #输出2 每一个元素的类型:<class 'langchain_core.documents.base.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 (Larg
我们期望知识库的数据尽量是有序的、优质的、精简的,因此我们要删除低质量的、甚至影响理解的文本数据。
可以看到上文中读取的pdf文件不仅将一句话按照原文的分行添加了换行符\n,也在原本两个符号中间插入了\n,我们可以使用正则表达式匹配并删除掉\n。
import re
pattern = re.compile(r'[^\u4e00-\u9fff](\n)[^\u4e00-\u9fff]', re.DOTALL)
pdf_page.page_content = re.sub(pattern, lambda match: match.group(0).replace('\n', ''), pdf_page.page_content)
print(pdf_page.page_content)
前言 “周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读 者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推 导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充 具体的推导细节。” 读到这里,大家可能会疑问为啥前面这段话加了引号,因为这只是我们最初的遐想,后来我们了解到,周 老师之所以省去这些推导细节的真实原因是,他本尊认为“理工科数学基础扎实点的大二下学生应该对西瓜书 中的推导细节无困难吧,要点在书里都有了,略去的细节应能脑补或做练习”。所以...... 本南瓜书只能算是我 等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二 下学生”。 使用说明 • 南瓜书的所有内容都是以西瓜书的内容为前置知识进行表述的,所以南瓜书的最佳使用方法是以西瓜书 为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;• 对于初学机器学习的小白,西瓜书第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/releases 编委会 主编:Sm1les、archwalker、jbb0523 编委:juxiao、Majingmin、MrBigFan、shanry、Ye980226 封面设计:构思-Sm1les、创作-林王茂盛 致谢 特别感谢awyd234、feijuan、Ggmatch、Heitao5200、huaqing89、LongJH、LilRachel、LeoLRH、Nono17、spareribs、sunchaothu、StevenLzq 在最早期的时候对南瓜书所做的贡献。 扫描下方二维码,然后回复关键词“南瓜书”,即可加入“南瓜书读者交流群” 版权声明 本作品采用知识共享署名-非商业性使用-相同方式共享4.0 国际许可协议进行许可。
进一步分析数据,我们发现数据中还有不少的•和空格,我们的简单实用replace方法即可。
pdf_page.page_content = pdf_page.page_content.replace('•', '')
pdf_page.page_content = pdf_page.page_content.replace(' ', '')
print(pdf_page.page_content)
前言 “周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读 者通过西瓜书对机器学习有所了解,所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推 导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充 具体的推导细节。” 读到这里,大家可能会疑问为啥前面这段话加了引号,因为这只是我们最初的遐想,后来我们了解到,周 老师之所以省去这些推导细节的真实原因是,他本尊认为“理工科数学基础扎实点的大二下学生应该对西瓜书 中的推导细节无困难吧,要点在书里都有了,略去的细节应能脑补或做练习”。所以......本南瓜书只能算是我 等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二 下学生”。 使用说明 南瓜书的所有内容都是以西瓜书的内容为前置知识进行表述的,所以南瓜书的最佳使用方法是以西瓜书 为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;对于初学机器学习的小白,西瓜书第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/releases 编委会 主编:Sm1les、archwalker、jbb0523 编委:juxiao、Majingmin、MrBigFan、shanry、Ye980226 封面设计:构思-Sm1les、创作-林王茂盛 致谢 特别感谢awyd234、feijuan、Ggmatch、Heitao5200、huaqing89、LongJH、LilRachel、LeoLRH、Nono17、spareribs、sunchaothu、StevenLzq在最早期的时候对南瓜书所做的贡献。 扫描下方二维码,然后回复关键词“南瓜书”,即可加入“南瓜书读者交流群” 版权声明 本作品采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可。
上文中读取的md文件每一段中间隔了一个换行符,我们同样可以使用replace方法去除。
md_page.page_content = md_page.page_content.replace('\n\n', '\n')
print(md_page.page_content)
第一章 简介
欢迎来到面向开发者的提示工程部分,本部分内容基于吴恩达老师的《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 视为一名新毕业的大学生,要求他完成这个任务,你甚至可以提前指定他们应该阅读哪些文本片段来写关于阿兰·图灵的文本,这样能够帮助这位新毕业的大学生更好地完成这项任务。本书的下一章将详细阐释提示词设计的两个关键原则:清晰明确和给予充足思考时间。
由于单个文档的长度往往会超过模型支持的上下文,导致检索得到的知识太长超出模型的处理能力,因此,在构建向量知识库的过程中,我们往往需要对文档进行分割,将单个文档按长度或者按固定的规则分割成若干个 chunk,然后将每个 chunk 转化为词向量,存储到向量数据库中。
在检索时,我们会以 chunk 作为检索的元单位,也就是每一次检索到 k 个 chunk 作为模型可以参考来回答用户问题的知识,这个 k 是我们可以自由设定的。
Langchain 中文本分割器都根据 **chunk_size (块大小)和 chunk_overlap (块与块之间的重叠大小)**进行分割。
chunk_size 指每个块包含的字符或 Token (如单词、句子等)的数量
chunk_overlap 指两个块之间共享的字符数量,用于保持上下文的连贯性,避免分割丢失上下文信息
Langchain 提供多种文档分割方式,区别在怎么确定块与块之间的边界、块由哪些字符/token组成、以及如何测量块大小
'''
* 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
# 使用递归字符文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=OVERLAP_SIZE
)
text_splitter.split_text(pdf_page.page_content[0:1000])
['前言\n“周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读\n者通过西瓜书对机器学习有所了解,所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推\n导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充\n具体的推导细节。”\n读到这里,大家可能会疑问为啥前面这段话加了引号,因为这只是我们最初的遐想,后来我们了解到,周\n老师之所以省去这些推导细节的真实原因是,他本尊认为“理工科数学基础扎实点的大二下学生应该对西瓜书\n中的推导细节无困难吧,要点在书里都有了,略去的细节应能脑补或做练习”。所以......本南瓜书只能算是我\n等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二\n下学生”。\n使用说明\n南瓜书的所有内容都是以西瓜书的内容为前置知识进行表述的,所以南瓜书的最佳使用方法是以西瓜书\n为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;对于初学机器学习的小白,西瓜书第1章和第2章的公式强烈不建议深究,简单过一下即可,等你学得',
'有点飘的时候再回来啃都来得及;每个公式的解析和推导我们都力(zhi)争(neng)以本科数学基础的视角进行讲解,所以超纲的数学知识\n我们通常都会以附录和参考文献的形式给出,感兴趣的同学可以继续沿着我们给的资料进行深入学习;若南瓜书里没有你想要查阅的公式,\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/releases\n编委会',
'编委会\n主编:Sm1les、archwalk']
split_docs = text_splitter.split_documents(pdf_pages)
print(f"切分后的文件数量:{len(split_docs)}")
#输出
切分后的文件数量:720
print(f"切分后的字符数(可以用来大致评估 token 数):{sum([len(doc.page_content) for doc in split_docs])}")
#输出
切分后的字符数(可以用来大致评估 token 数):308931
如何对文档进行分割,其实是数据处理中最核心的一步,其往往决定了检索系统的下限。但是,如何选择分割方式,往往具有很强的业务相关性——针对不同的业务、不同的源数据,往往需要设定个性化的文档分割方式。因此,在本章,我们仅简单根据 chunk_size 对文档进行分割。
本节重点为搭建并使用向量数据库,因此读取数据后我们省去数据处理的环节直入主题,数据清洗等步骤可以参考第三节
import os from dotenv import load_dotenv, find_dotenv # 读取本地/项目的环境变量。 # find_dotenv()寻找并定位.env文件的路径 # load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中 # 如果你设置的是全局的环境变量,这行代码则没有任何作用。 _ = load_dotenv(find_dotenv()) # 如果你需要通过代理端口访问,你需要如下配置 os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890' os.environ["HTTP_PROXY"] = 'http://127.0.0.1:7890' # 获取folder_path下所有文件路径,储存在file_paths里 file_paths = [] folder_path = '../../data_base/knowledge_db' for root, dirs, files in os.walk(folder_path): for file in files: file_path = os.path.join(root, file) file_paths.append(file_path) print(file_paths[:3])
['./data_base/knowledge_db/.DS_Store', './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', './data_base/knowledge_db/prompt_engineering/2. 提示原则 Guidelines.md']
from langchain.document_loaders.pdf import PyMuPDFLoader
from langchain.document_loaders.markdown import UnstructuredMarkdownLoader
# 遍历文件路径并把实例化的loader存放在loaders里
loaders = []
for file_path in file_paths:
file_type = file_path.split('.')[-1]
if file_type == 'pdf':
loaders.append(PyMuPDFLoader(file_path))
elif file_type == 'md':
loaders.append(UnstructuredMarkdownLoader(file_path))
# 下载文件并存储到text
texts = []
for loader in loaders: texts.extend(loader.load())
载入后的变量类型为langchain_core.documents.base.Document, 文档变量类型同样包含两个属性
text = texts[1]
print(f"每一个元素的类型:{type(text)}.",
f"该文档的描述性数据:{text.metadata}",
f"查看该文档的内容:\n{text.page_content[0:]}",
sep="\n------\n")
每一个元素的类型:<class 'langchain_core.documents.base.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/releases 编委会 主编:Sm1les、archwalker、jbb0523 编委:juxiao、Majingmin、MrBigFan、shanry、Ye980226 封面设计:构思-Sm1les、创作-林王茂盛 致谢 特别感谢awyd234、 feijuan、 Ggmatch、 Heitao5200、 huaqing89、 LongJH、 LilRachel、 LeoLRH、 Nono17、 spareribs、sunchaothu、StevenLzq 在最早期的时候对南瓜书所做的贡献。 扫描下方二维码,然后回复关键词“南瓜书” ,即可加入“南瓜书读者交流群” 版权声明 本作品采用知识共享署名-非商业性使用-相同方式共享4.0 国际许可协议进行许可。
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 切分文档
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50)
split_docs = text_splitter.split_documents(texts)
Langchain 集成了超过 30 个不同的向量存储库。我们选择 Chroma 是因为它轻量级且数据存储在内存中,这使得它非常容易启动和开始使用。
LangChain 可以直接使用 OpenAI 和百度千帆的 Embedding,同时,我们也可以针对其不支持的 Embedding API 进行自定义,例如,我们可以基于 LangChain 提供的接口,封装一个 zhupuai_embedding,来将智谱的 Embedding API 接入到 LangChain 中。在本章的附LangChain自定义Embedding封装讲解中,以智谱 Embedding API 为例,介绍了如何将其他 Embedding API 封装到 LangChain 中,欢迎感兴趣的读者阅读。
注:如果你使用智谱 API,你可以参考讲解内容实现封装代码,也可以直接使用我们已经封装好的代码zhipuai_embedding.py,将该代码同样下载到本 Notebook 的同级目录,就可以直接导入我们封装的函数。在下面的代码 Cell 中,我们默认使用了智谱的 Embedding,将其他两种 Embedding 使用代码以注释的方法呈现,如果你使用的是百度 API 或者 OpenAI API,可以根据情况来使用下方 Cell 中的代码。
# 使用 OpenAI Embedding
# from langchain.embeddings.openai import OpenAIEmbeddings
# 使用百度千帆 Embedding
# from langchain.embeddings.baidu_qianfan_endpoint import QianfanEmbeddingsEndpoint
# 使用我们自己封装的智谱 Embedding,需要将封装代码下载到本地使用
from zhipuai_embedding import ZhipuAIEmbeddings
# 定义 Embeddings
# embedding = OpenAIEmbeddings()
embedding = ZhipuAIEmbeddings()
# embedding = QianfanEmbeddingsEndpoint()
# 定义持久化路径
persist_directory = '../../data_base/vector_db/chroma'
!rm -rf '../../data_base/vector_db/chroma' # 删除旧的数据库文件(如果文件夹中有文件的话),windows电脑请手动删除
from langchain.vectorstores.chroma import Chroma
vectordb = Chroma.from_documents(
documents=split_docs[:20], # 为了速度,只选择前 20 个切分的 doc 进行生成;使用千帆时因QPS限制,建议选择前 5 个doc
embedding=embedding,
persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
)
在此之后,我们要确保通过运行 vectordb.persist 来持久化向量数据库,以便我们在未来的课程中使用。
让我们保存它,以便以后使用!
vectordb.persist()
print(f"向量库中存储的数量:{vectordb._collection.count()}")
#输出
向量库中存储的数量:20
Chroma的相似度搜索使用的是余弦距离,即:
当你需要数据库返回严谨的按余弦相似度排序的结果时可以使用similarity_search函数。
question="什么是大语言模型"
sim_docs = vectordb.similarity_search(question,k=3)
print(f"检索到的内容数:{len(sim_docs)}")
#输出
检索到的内容数:3
for i, sim_doc in enumerate(sim_docs): print(f"检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n") #输出 检索到的第0个内容: 开发大模型相关应用时请务必铭记: 虚假知识:模型偶尔会生成一些看似真实实则编造的知识 在开发与应用语言模型时,需要注意它们可能生成虚假信息的风险。尽管模型经过大规模预训练,掌握了丰富知识,但它实际上并没有完全记住所见的信息,难以准确判断自己的知识边界,可能做出错误推断。若让语言模型描述一个不存在的产品,它可能会自行构造出似是而非的细节。这被称为“幻觉”(Hallucination),是语言模型 -------------- 检索到的第1个内容: 例如,在以下的样例中,我们先给了一个祖孙对话样例,然后要求模型用同样的隐喻风格回答关于“韧性”的问题。这就是一个少样本样例,它能帮助模型快速抓住我们要的语调和风格。 利用少样本样例,我们可以轻松“预热”语言模型,让它为新的任务做好准备。这是一个让模型快速上手新任务的有效策略。 ```python prompt = f""" 您的任务是以一致的风格回答问题。 <孩子>: 请教我何为耐心。 < -------------- 检索到的第2个内容: 第二章 提示原则 如何去使用 Prompt,以充分发挥 LLM 的性能?首先我们需要知道设计 Prompt 的原则,它们是每一个开发者设计 Prompt 所必须知道的基础概念。本章讨论了设计高效 Prompt 的两个关键原则:编写清晰、具体的指令和给予模型充足思考时间。掌握这两点,对创建可靠的语言模型交互尤为重要。 首先,Prompt 需要清晰明确地表达需求,提供充足上下文,使语言模型准确理解 --------------
如果只考虑检索出内容的相关性会导致内容过于单一,可能丢失重要信息。
最大边际相关性 (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个内容: 开发大模型相关应用时请务必铭记: 虚假知识:模型偶尔会生成一些看似真实实则编造的知识 在开发与应用语言模型时,需要注意它们可能生成虚假信息的风险。尽管模型经过大规模预训练,掌握了丰富知识,但它实际上并没有完全记住所见的信息,难以准确判断自己的知识边界,可能做出错误推断。若让语言模型描述一个不存在的产品,它可能会自行构造出似是而非的细节。这被称为“幻觉”(Hallucination),是语言模型 -------------- MMR 检索到的第1个内容: 相反,我们应通过 Prompt 指引语言模型进行深入思考。可以要求其先列出对问题的各种看法,说明推理依据,然后再得出最终结论。在 Prompt 中添加逐步推理的要求,能让语言模型投入更多时间逻辑思维,输出结果也将更可靠准确。 综上所述,给予语言模型充足的推理时间,是 Prompt Engineering 中一个非常重要的设计原则。这将大大提高语言模型处理复杂问题的效果,也是构建高质量 Promp -------------- MMR 检索到的第2个内容: ```python text_1 = f""" Making a cup of tea is easy! First, you need to get some \ water boiling. While that's happening, \ grab a cup and put a tea bag in it. Once the water is \ hot enough, just --------------
LangChain 并没有内置所有大模型,它通过允许用户自定义 Embeddings 类型,来提供强大的可扩展性。
在本部分,我们以智谱 AI 为例,讲述如何基于 LangChain 自定义 Embeddings。
本部分涉及相对更多 LangChain、大模型调用的技术细节
要实现自定义 Embeddings,需要定义一个自定义类继承自 LangChain 的 Embeddings 基类,然后定义三个函数:
首先我们导入所需的第三方库:
from __future__ import annotations
import logging
from typing import Dict, List, Any
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel, root_validator
logger = logging.getLogger(__name__)
这里我们定义一个继承自 Embeddings 类的自定义 Embeddings 类:
class ZhipuAIEmbeddings(BaseModel, Embeddings):
"""`Zhipuai Embeddings` embedding models."""
client: Any
"""`zhipuai.ZhipuAI"""
在 Python 中,root_validator 是 Pydantic 模块中一个用于自定义数据校验的装饰器函数。root_validator 用于在校验整个数据模型之前对整个数据模型进行自定义校验,以确保所有的数据都符合所期望的数据结构。
root_validator 接收一个函数作为参数,该函数包含需要校验的逻辑。函数应该返回一个字典,其中包含经过校验的数据。如果校验失败,则抛出一个 ValueError 异常。
这里我们只需将.env文件中ZHIPUAI_API_KEY配置好即可,zhipuai.ZhipuAI会自动获取ZHIPUAI_API_KEY。
@root_validator()
def validate_environment(cls, values: Dict) -> Dict:
"""
实例化ZhipuAI为values["client"]
Args:
values (Dict): 包含配置信息的字典,必须包含 client 的字段.
Returns:
values (Dict): 包含配置信息的字典。如果环境中有zhipuai库,则将返回实例化的ZhipuAI类;否则将报错 'ModuleNotFoundError: No module named 'zhipuai''.
"""
from zhipuai import ZhipuAI
values["client"] = ZhipuAI()
return values
接下来我们重写 _embed 方法(在本章第二节有定义及演示),调用远程 API 并解析 embedding 结果。
def _embed(self, texts: str) -> List[float]:
embeddings = self.client.embeddings.create(
model="embedding-2",
input=texts
)
return embeddings.data[0].embedding
重写 embed_documents 方法,因为这里 _embed 已经定义好了,可以直接传入文本并返回结果即可。
def embed_documents(self, texts: List[str]) -> List[List[float]]:
"""
生成输入文本列表的 embedding.
Args:
texts (List[str]): 要生成 embedding 的文本列表.
Returns:
List[List[float]]: 输入列表中每个文档的 embedding 列表。每个 embedding 都表示为一个浮点值列表。
"""
return [self._embed(text) for text in texts]
embed_query 是对单个文本计算 embedding 的方法,因为我们已经定义好对文档列表计算 embedding 的方法embed_documents 了,这里可以直接将单个文本组装成 list 的形式传给 embed_documents。
def embed_query(self, text: str) -> List[float]:
"""
生成输入文本的 embedding.
Args:
texts (str): 要生成 embedding 的文本.
Return:
embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
"""
resp = self.embed_documents([text])
return resp[0]
可能大家会好奇,为什么要先定义embed_documents再用 embed_query 调用呢,不返过来呢,其实也是可以的,embed_query 单独请求也是可以的。
对于 embed_documents 可以加入一些内容处理后再请求 embedding,比如如果文档特别长,我们可以考虑对文档分段,防止超过最大 token 限制,这些都是可以的,靠大家发挥自己的主观能动性完善啦,这里只是给出一个简单的 demo。
通过上述步骤,我们就可以基于 LangChain 与 智谱 AI 定义 embedding 的调用方式了。我们将此代码封装在 zhipuai_embedding.py 文件中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。