向您展示如何将自己的数据集成到 ChatGPT 中。本教程需要一个 OpenAI API 密钥。如果没有,您可以像那样按照教程进行操作。 如何学习提示,索引,记忆和链,嵌入等概念 - 理论和实践。
嗨,在这个文章中,我将向您展示如何使用 LangChain 来增强像 gpt4 这样的大型语言模型。例如,使用 LangChain,您可以将 GPT 连接到自己的知识库中,而这些知识库并未经过模型训练。
理论可行性
首先,我将解释为什么要这样做以及如何在理论上实现。然后,我们将通过实际的代码来更容易地理解。
让我们以一个虚构的例子为例。假设我们有 100 个文本的知识库,我们希望将其提供给其他人,并通过 gpt4 生成类似人类的自然语言信息,而不仅仅是从这些文本文件中查询事实。
然而,问题是,您给机器人的提示(Prompts)或消息只能有一定数量的单词或标记,因此您必须预先过滤大型数据集。这通常使用语义过滤来完成,而向量数据库就派上了用场。它们会检查问题与数据库中的数据的相似性,然后将相似的数据作为输出返回。然后,将这个减少的输出再次作为提示(Prompts)发送。
现在,让我们从开发者的角度来看。
作为开发者,我们最初只有这些文本文件,但当然我们也可以有其他的知识来源,比如 PDF 或 CSV 文件。对于文本文件,提取数据相对容易。但对于 PDF 文件,情况就比较困难了。一旦我们提取了数据,我们不能直接将其馈送到数据库中,而是需要将其拆分成小块,即所谓的“块”。例如,如果我们有 10,000 个字,我们将它们分成 100 个字的一组,最后得到 100 个块。
现在我们有了很多块,这些块必须转换为所谓的“嵌入(embedding)”。
什么是嵌入(embedding)?
嵌入(embedding)模型会将输入文本转换为一个向量,您可以看到这个向量只是一组数字,或者说是一个数字数组或数字列表。 这些向量存储在数据库中,您可以在这里看到这些向量具有意义。
例如,与动物相关的任何内容都存储在一个向量空间中,与运动员相关的任何内容都存储在另一个空间中。然后,您发出一个查询,例如“什么是大象”,大象也会被转换为一个向量,然后从数据库中检索类似的向量。这就是语义过滤。
现在,假设您从数据库中获得了前四个向量,并将这些向量转换回文本。然后,您提出一个问题,并将从向量数据库中检索到的文本作为提示 (Prompts) 输入到 GPT 中进行检查。
好了,这就是理论部分。
现在我们将进入代码部分,我将为您介绍代码,您可以了解如何创建一个向量数据库以及如何在其中运行查询。
代码实现
安装包
首先,我要安装 LangChain,这是一个包,可以让我们很容易地使用向量数据库以及 openAI 来运行查询。 这是非常好的和轻量级的包,我将在这里展示基本用法。您还需要安装 openAI 并拥有一个 openAI 的 API 密钥,这是唯一的先决条件,否则将无法工作,因为我使用 openAI 作为我的嵌入(embedding)模型。我还需要安装 pickle,这是用于反序列化和序列化我们的向量数据库的。我们将使用由 Facebook 构建的文件。我们还将安装 python-dotenv,因为我将我的 API 密钥存储在这个.env 文件中,并且我不希望向所有人展示。在初始化或安装了这些包之后,我会加载我的 API 密钥到变量 API key 中。
!pip install langchain !pip install openAI !pip install pickle !pip install python-dotenv
加载 LangChain 的 document_loaders
好的,在加载了 API 密钥之后,我们现在可以看一下 LangChain 的第一个概念,那就是 document_loaders
。 document_loaders
是一个类,允许您使用数据库或文本来源,并将其转换或放入数据库中。
from langchain.document_loaders import DirectoryLoader, TextLoaderloader = DirectoryLoader('./FAQ', glob="**/*.txt", loader_cls=TextLoader, show_progress=True)docs = loader.load()
有很多不同的 loader,比如 TextLoader
、PDFLoader
以及这里的 DirectoryLoader
,这里数据中包含了一些关于动物的有效信息。 有兔子和狗,这样做是为了确保我对数据库发出的查询不是由 GPT 生成的,而是从我的数据库中检索到的。
因此,要加载数据,我们必须先导入:import DirectoryLoader, TextLoader
。 我使用 DirectoryLoader
,因为 FAQ 文本文件中的所有内容都是文本,我可以直接使用此路径,然后使用 DirectoryLoader
和 TextLoader
来加载它,并显示我的进度,然后将其加载到内存中。
原始数据切块
加载数据到内存中后,我们现在必须将其拆分成所谓的块。 LangChain 有非常好的文本拆分器,用于递归字符文本拆分。
from langchain.text_splitter import RecursiveCharacterTextSplittertext_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=100,)documents = text_splitter.split_documents(docs)# documents[0]
这里我将指定一个块大小为 500 个单词或标记,并且我还需要一个 100 个单词或标记的重叠。因为我不想丢失块的上下文。
在初始化或实例化了这个 RecursiveCharacterTextSplitter
类之后,我现在将拆分存储在这个 docs
变量中的文档(docs 变量保存了全部 txt 文件的数据)。
当运行这段代码时,您现在可以看到从文本文件中提取出来的内容,并且其块大小为 500。
向量存储
现在我们加载了我们的块,我们现在需要使用我们的嵌入(embedding)。从 LangChain 中,您可以使用不同的嵌入(embedding)类,我将使用 openAI 的嵌入(openAIEmbeddings)。
from langchain.embeddings import openAIEmbeddings embeddings = openAIEmbeddings(openAI_api_key=API_KEY)
我将在这里导入它,并使用 openAI 的 API 密钥实例化它,这个值是我从我的.env 文件中加载的 API 密钥。所以我有了一个实例化的类,然后我们需要将块加载到一个向量数据库中。
在 LangChain 中,有不同类型的向量数据库可以使用,我将在这里使用 FAISS,并导入这个文件类。
from langchain.vectorstores.faiss import FAISS import pickle vectorstore = FAISS.from_documents(documents, embeddings) with open("vectorstore.pkl", "wb") as f: pickle.dump(vectorstore, f)
现在,从 FAISS 中,我有一个 from_documents
的类方法,我将使用这些 documents
和嵌入(embedding)实例 embeddings
,这将创建一个向量存储,这个向量存储可以被 pickle 或 dump,现在我在文件系统上有了一个向量存储。
现在,我有了一个向量存储,可以在其上运行查询和操作。现在我们只需要再次加载我们的向量存储,并将其放入内存中。
with open("vectorstore.pkl", "rb") as f: vectorstore = pickle.load(f)
输入提示(Prompts)
好了,现在我们可以向 GPT 发送一个问题,但您不能只是提出一个问题,您需要提供更多的内容,这个内容实际上是上下文,所以在问题中的上下文被称为所谓的提示(Prompts)。
您给机器人或 GPT 模型一个身份,比如说您是一名兽医,您帮助用户处理他们的动物问题,然后您在这里放置您的回答,所有这些放在一起被称为所谓的提示 (Prompts) 。
您可以从 LangChain 获得一个不错的提示 (Prompts) 模板,也就是代码中的 PromptTemplate
, 并使用上下文变量和问题进行实例化。
from langchain.prompts import PromptTemplateprompt_template = """你是一位兽医,帮助用户处理他们的宠物.{context}Question: {question}Answer is:"""PROMPT = PromptTemplate( template=prompt_template, input_variables=["context", "question"]
这是整个模板,这部分是固定的,这部分是变量,您将其放在这里,现在我们有了提示(Prompts)模板。 所以这个提示(Prompts)模板现在可以在“链(Chains)”中使用,您可以为不同的用例获得不同的链(Chains),比如聊天或检索 QA。
链(Chains)
检索式问答链 RetrievalQA
将被用于我们的查询和数据库,然后将该输出放入模型中,这在检索式问答链 RetrievalQA
中得到了很好的实现。
from langchain.llms import openAIfrom langchain.chains import RetrievalQAchain_type_kwargs = {"prompt": PROMPT}llm = openAI(openAI_api_key=API_KEY)qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever(), chain_type_kwargs=chain_type_kwargs)query = "一只鸡可以活多久?"qa.run(query)
我们首先需要初始化或实例化我们的模型,这里我将使用 openAI。在这里放入 openAI API 密钥,现在我们有了我们的 LLM(大型语言模型),将其作为变量放入链(Chains)中。
现在您还需要在这里放入一个检索器(retriever),将 向量存储转换为检索器 retriever=vectorstore.as_retriever()
。
如果您现在运行整个链(Chains),您将得到将要使用的 LLM。
记忆内存(Memory)
您还将在这里使用数据存储,因此我将运行 :
query = "一只鸡可以活多久?" qa.run(query)
了解动物的寿命,GPT 将为我回答。
首先,我当然必须运行模板,然后运行链(Chains)。
但是现在我们有一个小问题,或者至少有时候我们有一个小问题,那就是如果我们运行多个查询,我们总是必须在这里给出正确的问题,但是整个对话的上下文丢失了。这就是记忆内存(Memory)非常有用的地方,记忆内存(Memory)了整个对话并将其用于 Prompts 链 (Chains) 中的输入。
Langchain 提供了多个记忆类,例如对话缓冲区记忆 ConversationBufferMemory
,您可以给它一个记忆键 memory_key='chat_history'
,如聊天记录和一个输出键 output_key
。您还可以指定您希望从记忆中返回的消息。
from langchain.memory import ConversationBufferMemorymemory = ConversationBufferMemory(memory_key='chat_history', return_messages=True, output_key='answer')
上面创建这个对话缓冲区记忆的实例,并且我们使用不同的通道,因为检索式问答链(Chains)无法使用记忆,但是对话检索链 ConversationalRetrievalChain
可以使用它。
所以我们做同样的事情,使用 text-davinci-003
模型来实例化它,这是一个非常适合文本生成的模型,并给温度调节为 0.7,表示模型的动态程度以及记忆作为输入参数。
from langchain.chains import ConversationalRetrievalChainqa = ConversationalRetrievalChain.from_llm( llm=OpenAI(model_name="text-davinci-003", temperature=0.7, openai_api_key=API_KEY), memory=memory, retriever=vectorstore.as_retriever(), combine_docs_chain_kwargs={'prompt': PROMPT})query = "鸡能活多久?"qa({"question": query})qa({"question": "鸡每天吃多少?"})
如果您运行多个查询,例如这里的“动物寿命有多长”和“它吃多少”。
在这种情况下,它从记忆内存(Memory)中检索到或从记忆内存(Memory)中解释出来,否则 GPT 将不知道你的真实意图,也就是会出现“胡说八道”,这样背离了我们需要 GPT 从我们自己的知识库中回答问题的初衷,因此它必须有记忆。
好了,就是这样,正如您所看到的,Langchain 非常易于使用且功能强大。
本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.5amiao.com/baike/2440.html