LLM App构建指南
为什么我们需要LLM
语言的进化使我们人类至今难以置信地走得很远。它使我们能够以我们今天所知道的形式有效地分享知识和协作。因此,我们的大部分集体知识继续通过无组织的书面文本保存和传播。
过去二十年来为数字化信息和流程而采取的举措通常侧重于在关系数据库中积累越来越多的数据。这种方法使传统的分析机器学习算法能够处理和理解我们的数据。
然而,尽管我们付出了广泛的努力,以结构化的方式存储越来越多的数据,但我们仍然无法捕获和处理我们的全部知识。
公司中大约 80% 的数据是非结构化的,例如工作描述、简历、电子邮件、文本文档、Power Point 幻灯片、录音、视频和社交媒体
GPT3.5 的发展和进步标志着一个重要的里程碑,因为它使我们能够有效地解释和分析各种数据集,无论其结构如何。如今,我们有可以理解和生成各种形式的内容的模型,包括文本、图像和音频文件。
那么,我们如何利用它们的能力来满足我们的需求和数据呢?
微调与上下文注入
一般来说,我们有两种根本不同的方法可以使大型语言模型回答LLM无法知道的问题:模型微调和上下文注入。
微调
微调是指使用其他数据训练现有语言模型,以针对特定任务对其进行优化。
不是从头开始训练语言模型,而是使用预先训练的模型,如BERT或LLama,然后通过添加特定于用例的训练数据来适应特定任务的需求。
斯坦福大学的一个团队使用LLM Llama,并使用50,000个用户/模型交互示例对其进行了微调。结果是一个与用户交互并回答查询的聊天机器人。此微调步骤改变了模型与最终用户交互的方式。
→ 对微调的误解
PLLM(预训练语言模型)的微调是一种针对特定任务调整模型的方法,但它并不真正允许您将自己的领域知识注入到模型中。这是因为模型已经在大量通用语言数据上进行了训练,而您的特定领域数据通常不足以覆盖模型已经学习的内容。
因此,当您微调模型时,它可能偶尔会提供正确的答案,但它经常会失败,因为它严重依赖于它在预训练期间学到的信息,这些信息可能不准确或与您的特定任务无关。换句话说,微调有助于模型适应它的通信方式,但不一定适应它所传达的内容。 (保时捷股份公司,2023 年)
这就是上下文注入发挥作用的地方。
上下文学习/上下文注入
使用上下文注入时,我们不是在修改LLM,而是专注于提示本身并将相关上下文注入提示中。
因此,我们需要考虑如何为提示提供正确的信息。在下图中,您可以示意图地看到整个事情是如何工作的。我们需要一个能够识别最相关数据的过程。为此,我们需要使计算机能够相互比较文本片段。
Similarity search in our unstructured data — Image by the author
这可以通过嵌入来完成。通过嵌入,我们将文本转换为向量,使我们能够在多维嵌入空间中表示文本。在空间中彼此靠近的点通常在同一上下文中使用。为了防止这种相似性搜索永远持续下去,我们将向量存储在向量数据库中并对其进行索引。
Microsoft向我们展示了如何与必应聊天配合使用。必应将LLM理解语言和上下文的能力与传统网络搜索的效率相结合。
本文的目的是演示创建简单解决方案的过程,该解决方案允许我们分析自己的文本和文档,然后将从中获得的见解合并到我们的解决方案返回给用户的答案中。我将介绍实现端到端解决方案所需的所有步骤和组件。
那么我们如何利用LLM的功能来满足我们的需求呢?让我们一步一步地完成它。
分步教程 — 您的第一个 LLM 应用程序
在下文中,我们希望利用LLM来回应有关我们个人数据的查询。为此,我首先将我们的个人数据内容传输到矢量数据库中。这一步至关重要,因为它使我们能够有效地搜索文本中的相关部分。我们将使用来自我们的数据和LLM功能的信息来解释文本以回答用户的问题。
我们还可以指导聊天机器人根据我们提供的数据专门回答问题。通过这种方式,我们可以确保聊天机器人始终专注于手头的数据,并提供准确且相关的响应。
为了实现我们的用例,我们将严重依赖 LangChain。
什么是LangChain?
“LangChain是一个用于开发由语言模型驱动的应用程序的框架。(朗链,2023)
因此,LangChain是一个Python框架,旨在支持创建各种LLM应用程序,例如聊天机器人,摘要工具,以及基本上您想要创建的任何工具来利用LLM的强大功能。该库结合了我们需要的各种组件。我们可以将这些组件连接在所谓的链中。
Langchain最重要的模块是(Langchain,2023):
- 模型:与各种型号类型的接口
- 提示: 提示管理、提示优化和提示序列化
- 指标: 文档加载器、文本拆分器、矢量存储 — 实现更快、更高效的数据访问
- 链: 链不仅仅是单个LLM调用,它们允许我们设置调用序列
在下图中,您可以看到这些组件在哪里发挥作用。我们使用索引模块中的文档加载器和文本拆分器加载和处理我们自己的非结构化数据。提示模块允许我们将找到的内容注入到提示模板中,最后,我们使用模型的模块将提示发送到我们的模型。
5.代理:代理是使用LLM来选择采取哪些行动的实体。执行操作后,他们观察该操作的结果并重复该过程,直到任务完成。
我们在第一步中使用Langchain来加载文档,分析它们并使它们有效地可搜索。在我们对文本编制索引后,识别与回答用户问题相关的文本片段应该会变得更加高效。
我们的简单应用程序需要的当然是LLM。我们将通过OpenAI API使用GPT3.5。然后我们需要一个向量存储,允许我们用自己的数据为 LLM 提供。如果我们想对不同的查询执行不同的操作,我们需要一个代理来决定每个查询应该发生什么。
让我们从头开始。我们首先需要导入我们自己的文档。
以下部分描述了 LangChain 的加载器模块中包含哪些模块,以加载来自不同来源的不同类型的文档。
1. 使用语言链加载文档
LangChain能够从各种来源加载许多文档。您可以在 LangChain 文档中找到可能的文档加载器列表。其中包括HTML页面,S3存储桶,PDF,Notion,Google云端硬盘等的加载器。
对于我们的简单示例,我们使用可能未包含在 GPT3.5 训练数据中的数据。我使用维基百科上关于 GPT4 的文章,因为我假设 GPT3.5 对 GPT4 的了解有限。
对于这个最小的例子,我没有使用任何LangChain加载器,我只是使用BeautifulSoup直接从维基百科[许可证:CC BY-SA 3.0]中抓取文本。
请注意,抓取网站只能根据网站的使用条款以及您希望使用的文本和数据的版权/许可状态进行。
import requests
from bs4 import BeautifulSoup
url = "https://en.wikipedia.org/wiki/GPT-4"
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# find the content div
content_div = soup.find('div', {'class': 'mw-parser-output'})
# remove unwanted elements from div
unwanted_tags = ['sup', 'span', 'table', 'ul', 'ol']
for tag in unwanted_tags:
for match in content_div.findAll(tag):
match.extract()
print(content_div.get_text())
2.将文档拆分为文本片段
接下来,我们必须将文本分成称为文本块的较小部分。每个文本块表示嵌入空间中的一个数据点,允许计算机确定这些块之间的相似性。
以下文本片段利用了 langchain 中的文本拆分器模块。在这种特殊情况下,我们指定块大小为 100,块重叠为 20。使用较大的文本块是很常见的,但您可以进行一些实验,以找到适合您的用例的最佳大小。您只需要记住,每个LLM都有一个令牌限制(GPT 4000 为 3.5 个 tokes)。由于我们将文本块插入到提示中,因此我们需要确保整个提示不大于 4000 个标记。
from langchain.text_splitter import RecursiveCharacterTextSplitter
article_text = content_div.get_text()
text_splitter = RecursiveCharacterTextSplitter(
# Set a really small chunk size, just to show.
chunk_size = 100,
chunk_overlap = 20,
length_function = len,
)
texts = text_splitter.create_documents([article_text])
print(texts[0])
print(texts[1])
这将我们的整个文本拆分如下:
3. 从文本块到嵌入
现在我们需要使文本组件易于理解并与我们的算法进行比较。我们必须找到一种方法,将人类语言转换为数字形式,由比特和字节表示。
该图像提供了一个简单的示例,对大多数人来说似乎很明显。但是,我们需要找到一种方法,让计算机理解“查尔斯”这个名字与男人而不是女人有关,如果查尔斯是男人,他就是国王而不是王后。
在过去的几年里,出现了新的方法和模型来做到这一点。我们想要的是一种能够将单词的含义转换为n维空间的方法,因此我们能够将文本块相互比较,甚至计算出它们相似性的度量。
嵌入模型试图通过分析通常使用单词的上下文来准确学习这一点。由于茶,咖啡和早餐经常在同一上下文中使用,因此它们在n维空间中比茶和豌豆更接近。茶和豌豆听起来很相似,但很少一起使用。(组装人工智能,2022 年)
嵌入分析使用单词的上下文,而不是单词本身 - 作者图片
嵌入模型为我们提供了嵌入空间中每个单词的向量。最后,通过使用向量表示它们,我们能够执行数学计算,例如计算单词之间的相似性作为数据点之间的距离。
二维嵌入空间中的随机英语单词 - 作者图片
要将文本转换为嵌入,有几种方法,例如Word2Vec,GloVe,fastText或ELMo。
嵌入模型
为了捕获嵌入中单词之间的相似性,Word2Vec使用了一个简单的神经网络。我们使用大量文本数据训练此模型,并希望创建一个模型,该模型能够在n维嵌入空间中为每个单词分配一个点,从而以向量的形式描述其含义。
对于训练,我们将输入层中的一个神经元分配给数据集中的每个唯一单词。在下图中,您可以看到一个简单的示例。在这种情况下,隐藏层仅包含两个神经元。第二,因为我们想在二维嵌入空间中映射单词。(现有的模型实际上要大得多,因此代表了更高维空间中的单词——例如,OpenAI的Ada嵌入模型使用的是1536维)在训练过程之后,各个权重描述嵌入空间中的位置。
在这个例子中,我们的数据集由一个句子组成:“谷歌是一家科技公司。句子中的每个单词都充当神经网络 (NN) 的输入。因此,我们的网络有五个输入神经元,每个单词一个。
在训练过程中,我们专注于预测每个输入单词的下一个单词。当我们从句子的开头开始时,对应于单词“Google”的输入神经元的值为 1,而其余神经元的值为 0。我们的目标是训练网络在这个特定场景中预测单词“is”。
实际上,有多种方法可以学习嵌入模型,每种方法都有自己独特的方法来预测训练过程中的输出。两种常用的方法是CBOW(连续词袋)和Skip-gram。
在CBOW中,我们将周围的单词作为输入,旨在预测中间单词。相反,在 Skip-gram 中,我们将中间单词作为输入,并尝试预测其左侧和右侧出现的单词。但是,我不会深入研究这些方法的复杂性。假设这些方法为我们提供了嵌入,这些嵌入是通过分析大量文本数据的上下文来捕获单词之间关系的表示。
CBOW vs. Skip-gram — 图片来源:作者
如果您想了解有关嵌入的更多信息_,互联网上有大量可用的信息。但是,如果您更喜欢视觉和分步指南,您可能会发现观看Josh _Starmer的StatQuest关于单词嵌入和Word2Vec会有所帮助_。_
返回嵌入模型
我刚刚试图在二维嵌入空间中使用一个简单的示例来解释的内容也适用于较大的模型。例如,标准的Word2Vec向量有2个维度,而OpenAI的Ada模型有300个维度。这些预训练的向量使我们能够精确地捕获单词与其含义之间的关系,以便我们可以对它们进行计算。例如,使用这些向量,我们可以发现法国+柏林-德国=巴黎,以及更快+温暖-快速=温暖。(塔齐曼,N.D)
使用嵌入计算 — 作者图片
在下文中,我们希望使用OpenAI API不仅使用OpenAI的LLM,而且还利用其嵌入模型。
注意:嵌入模型和LLM之间的区别在于,嵌入模型专注于创建单词或短语的矢量表示以捕获其含义和关系,而LLM是经过训练的通用模型,可根据提供的提示或查询生成连贯且上下文相关的文本。
开放人工智能嵌入模型
与OpenAI的各种LLM类似,您也可以在各种嵌入模型之间进行选择,例如Ada,Davinci,Curie和Babbage。其中,Ada-002是目前速度最快、性价比最高的机型,而达芬奇通常提供最高的精度和性能。但是,您需要自己尝试它们并为您的用例找到最佳模型。如果你有兴趣详细了解 OpenAI 嵌入,可以参考 OpenAI 文档。
我们对嵌入模型的目标是将文本块转换为向量。在第二代 Ada 的情况下,这些向量具有 1536 个输出维度,这意味着它们表示 1536 维空间中的特定位置或方向。
OpenAI 在其文档中对这些嵌入向量进行了如下描述:
“在数字上相似的嵌入在语义上也是相似的。例如,“犬类同伴说”的嵌入向量将更类似于“woof”的嵌入向量,而不是“meow”的嵌入向量。(开放人工智能,2022 年)
让我们试一试。我们使用OpenAI的API将我们的文本片段转换为嵌入,如下所示:
import openai
print(texts[0])
embedding = openai.Embedding.create(
input=texts[0].page_content, model="text-embedding-ada-002"
)["data"][0]["embedding"]
len(embedding)
我们将文本(例如包含“2023 文本生成语言模型”的第一个文本块)转换为具有 1536 维的向量。通过对每个文本块执行此操作,我们可以在 1536 维空间中观察哪些文本块彼此更近、更相似。
让我们试一试。我们的目标是通过为问题生成嵌入,然后将其与空间中的其他数据点进行比较,将用户的问题与文本块进行比较。
哪个文本段在语义上更接近用户的问题?——图片来源:作者
当我们将文本块和用户的问题表示为向量时,我们就获得了探索各种数学可能性的能力。为了确定两个数据点之间的相似性,我们需要计算它们在多维空间中的接近度,这是使用距离指标实现的。有几种方法可用于计算点之间的距离。Maarten Grootendorst在他的一篇Medium帖子中总结了其中的九个。
常用的距离度量是余弦相似性。因此,让我们尝试计算问题和文本块之间的余弦相似性:
import numpy as np
from numpy.linalg import norm
# calculate the embeddings for the user's question
users_question = "What is GPT-4?"
question_embedding = get_embedding(text=users_question, model="text-embedding-ada-002")
# create a list to store the calculated cosine similarity
cos_sim = []
for index, row in df.iterrows():
A = row.ada_embedding
B = question_embedding
# calculate the cosine similarity
cosine = np.dot(A,B)/(norm(A)*norm(B))
cos_sim.append(cosine)
df["cos_sim"] = cos_sim
df.sort_values(by=["cos_sim"], ascending=False)
现在我们可以选择要提供给LLM的文本块数量,以便回答问题。
下一步是确定我们想使用哪个LLM。
4. 定义要使用的模型
Langchain提供了各种模型和集成,包括OpenAI的GPT和Huggingface等。如果我们决定使用 OpenAI 的 GPT 作为我们的大语言模型,第一步就是定义我们的 API 密钥。目前,OpenAI提供了一些免费使用能力,但是一旦我们每月超过一定数量的代币,我们将需要切换到付费帐户。
如果我们使用 GPT 来回答类似于如何使用 Google 的简短问题,成本仍然相对较低。但是,如果我们使用 GPT 来回答需要提供大量上下文(例如个人数据)的问题,则查询可以快速积累数千个令牌。这大大增加了成本。但别担心,您可以设置成本限制。
什么是代币?
简单来说,令牌基本上是一个单词或一组单词。但是,在英语中,单词可以有不同的形式,例如动词时态、复数或复合词。为了解决这个问题,我们可以使用子词标记化,它将一个词分解成更小的部分,如它的根、前缀、后缀和其他语言元素。例如,“烦人”一词可以分为“轮胎”和“一些”,而“累”可以分为“轮胎”和“d”。通过这样做,我们可以认识到“烦人”和“疲惫”共享相同的词根并具有相似的派生。(王,2023)
OpenAI在其网站上提供了一个令牌器,以了解令牌是什么。根据OpenAI的说法,一个令牌通常对应于普通英语文本的~4个字符的文本。这相当于大约一个单词的 100/75(所以 <> 个标记 ~= <> 个单词)。您可以在OpenAI的网站上找到一个Tokenizer应用程序,该应用程序可让您了解实际算作令牌的内容。
设置使用限制
如果您担心成本,可以在 OpenAI 用户门户中找到一个选项来限制每月成本。
您可以在 OpenAI 的用户帐户中找到 API 密钥。最简单的方法是在谷歌中搜索“OpenAI API密钥”。这会将您直接带到设置页面,以创建新密钥。
要在 Python 中使用,您必须将密钥保存为名为“OPENAI\_API\_KEY”的新环境变量:
import os
os.environment["OPENAI_API_KEY"] = "testapikey213412"
定义模型时,可以设置一些首选项。OpenAI Playground 让你可以在决定要使用的设置之前,先使用不同的参数:
在Playground WebUI的右侧,您会发现OpenAI提供的几个参数,这些参数使我们能够影响LLM的输出。值得探索的两个参数是模型选择和温度。
您可以选择各种不同的型号。Text-davinci-003模型是目前最大,最强大的模型。另一方面,像 Text-ada-001 这样的型号更小、更快、更具成本效益。
下面,您可以看到OpenAI定价列表的摘要。与最强大的模型Davinci相比,Ada更便宜。因此,如果 Ada 的性能满足我们的需求,我们不仅可以节省资金,还可以缩短响应时间。
你可以从使用达芬奇开始,然后评估我们是否也能用Ada取得足够好的结果。
因此,让我们在Jupyter Notebook中尝试一下。我们正在使用langchain连接到GPT。
from langchain.llms import OpenAI
llm = OpenAI(temperature=0.7)
如果要查看包含所有属性的列表,请使用\_\_dict\_\_:
llm.__dict__
如果我们不指定特定模型,langchain 连接器默认使用 “text-davinci-003”。
现在,我们可以直接在 Python 中调用模型。只需调用 llm 函数并提供提示作为输入。
您现在可以向 GPT 询问有关人类常识的任何信息。
GPT 只能提供有关其训练数据中未包含的主题的有限信息。这包括未公开的特定详细信息或上次更新训练数据后发生的事件。
那么,我们如何确保模型能够回答有关当前事件的问题呢?
如前所述,有一种方法可以做到这一点。我们需要在提示中为模型提供必要的信息。
为了回答有关现任英国首相的问题,我向提示提供了维基百科文章“英国首相”中的信息。总结这个过程,我们是:
- 加载文章
- 将文本拆分为文本块
- 计算文本块的嵌入
计算所有文本块与用户问题之间的相似性
import requests
from bs4 import BeautifulSoup
from langchain.text_splitter import RecursiveCharacterTextSplitter
import numpy as np
from numpy.linalg import normload documents
URL of the Wikipedia page to scrape
url = 'https://en.wikipedia.org/wiki/Prime_Minister_of_the_United_Kingdom'
Send a GET request to the URL
response = requests.get(url)
Parse the HTML content using BeautifulSoup
soup = BeautifulSoup(response.content, 'html.parser')
Find all the text on the page
text = soup.get_text()
split text
text_splitter = RecursiveCharacterTextSplitter(
# Set a really small chunk size, just to show. chunk_size = 100, chunk_overlap = 20, length_function = len,
)
texts = text_splitter.create_documents([text])
for text in texts:
text_chunks.append(text.page_content)
calculate embeddings
df = pd.DataFrame({'text_chunks': text_chunks})
create new list with all text chunks
text_chunks=[]
for text in texts:
text_chunks.append(text.page_content)
get embeddings from text-embedding-ada model
def get_embedding(text, model="text-embedding-ada-002"):
text = text.replace("\n", " ")
return openai.Embedding.create(input = [text], model=model)'data'['embedding']df['ada_embedding'] = df.text_chunks.apply(lambda x: get_embedding(x, model='text-embedding-ada-002'))
calculate similarities to the user's question
calcuate the embeddings for the user's question
users_question = "Who is the current Prime Minister of the UK?"
question_embedding = get_embedding(text=users_question, model="text-embedding-ada-002")
现在我们尝试找到与用户问题相似度最高的文本块:
from langchain import PromptTemplate
from langchain.llms import OpenAI
# calcuate the embeddings for the user's question
users_question = "Who is the current Prime Minister of the UK?"
question_embedding = get_embedding(text=users_question, model="text-embedding-ada-002")
# create a list to store the calculated cosine similarity
cos_sim = []
for index, row in df.iterrows():
A = row.ada_embedding
B = question_embedding
# calculate the cosine similiarity
cosine = np.dot(A,B)/(norm(A)*norm(B))
cos_sim.append(cosine)
df["cos_sim"] = cos_sim
df.sort_values(by=["cos_sim"], ascending=False)
文本块看起来很混乱,但让我们试一试,看看 GPT 是否足够聪明来处理它。
现在我们已经确定了可能包含相关信息的文本段,我们可以测试我们的模型是否能够回答这个问题。为了实现这一点,我们必须以一种清楚地将我们想要的任务传达给模型的方式构建我们的提示。
5. 定义我们的提示模板
现在我们有了包含我们正在寻找的信息的文本片段,我们需要构建一个提示。在提示中,我们还指定模型回答问题所需的模式。当我们定义模式时,我们正在指定我们希望LLM生成答案的所需行为风格。
LLM可用于各种任务,以下是各种可能性的一些示例:
- 综述:“将以下案文总结为3段,供高管使用:[案文]
- 知识提取:“根据这篇文章:[TEXT],人们在购买房屋之前应该考虑什么?
- 编写内容(例如邮件、消息、代码):给 Jane 写一封电子邮件,要求更新我们项目的文档。使用非正式、友好的语气。
- 语法和风格改进:“将其更正为标准英语,并将语气更改为更友好的语气:[文本]
- 分类:“将每条消息分类为一种支持票证:[TEXT]”
对于我们的示例,我们希望实现一个解决方案,该解决方案从维基百科中提取数据并像聊天机器人一样与用户交互。我们希望它像积极主动、乐于助人的帮助台专家一样回答问题。
为了引导LLM朝着正确的方向前进,我在提示中添加了以下说明:
“你是一个喜欢帮助别人的聊天机器人!仅使用提供的上下文回答以下问题。如果您不确定并且答案没有明确在上下文中,请说“对不起,我不知道如何帮助您。
通过这样做,我设置了一个限制,仅允许 GPT 利用存储在我们数据库中的信息。这种限制使我们能够提供聊天机器人生成响应所依赖的来源,这对于可追溯性和建立信任至关重要。此外,它还帮助我们解决产生不可靠信息的问题,并使我们能够提供可在公司环境中用于决策目的的答案。
作为上下文,我只是使用与问题相似度最高的前 50 个文本块。文本块的大小可能会更好,因为我们通常可以用一两个文本段落回答大多数问题。但是我会留给你找出适合您的用例的最佳尺寸。
from langchain import PromptTemplate
from langchain.llms import OpenAI
# define the LLM you want to use
llm = OpenAI(temperature=1)
# calcuate the embeddings for the user's question
users_question = "Who is the current Prime Minster of the UK?"
question_embedding = get_embedding(text=users_question, model="text-embedding-ada-002")
# create a list to store the calculated cosine similarity
cos_sim = []
for index, row in df.iterrows():
A = row.ada_embedding
B = question_embedding
# calculate the cosine similiarity
cosine = np.dot(A,B)/(norm(A)*norm(B))
cos_sim.append(cosine)
df["cos_sim"] = cos_sim
df.sort_values(by=["cos_sim"], ascending=False)
# define the context for the prompt by joining the most relevant text chunks
context = ""
for index, row in df[0:50].iterrows():
context = context + " " + row.text_chunks
# define the prompt template
template = """
You are a chat bot who loves to help people! Given the following context sections, answer the
question using only the given context. If you are unsure and the answer is not
explicitly writting in the documentation, say "Sorry, I don't know how to help with that."
Context sections:
{context}
Question:
{users_question}
Answer:
"""
prompt = PromptTemplate(template=template, input_variables=["context", "users_question"])
# fill the prompt template
prompt_text = prompt.format(context = context, question=users_question)
llm(prompt_text)
通过使用该特定模板,我将上下文和用户的问题合并到我们的提示中。生成的响应如下所示:
令人惊讶的是,即使是这个简单的实现似乎也产生了一些令人满意的结果。让我们继续向系统提出一些关于英国首相的问题。我将保持一切不变,仅替换用户的问题:
users_question = "Who was the first Prime Minister of the UK?"
它似乎在某种程度上起作用。然而,我们现在的目标是将这一缓慢的进程转变为一个强大而有效的进程。为了实现这一点,我们引入了一个索引步骤,我们将嵌入和索引存储在向量存储中。这将提高整体性能并减少响应时间。
6. 创建向量存储(向量数据库)
矢量存储是一种数据存储类型,它针对存储和检索可表示为矢量的大量数据进行了优化。这些类型的数据库允许根据各种条件(如相似性度量或其他数学运算)有效地查询和检索数据子集。
将我们的文本数据转换为向量是第一步,但这还不足以满足我们的需求。如果我们将向量存储在数据框中,并在每次获得查询时逐步搜索单词之间的相似性,整个过程将非常缓慢。
为了有效地搜索我们的嵌入,我们需要对它们进行索引。索引是矢量数据库的第二个重要组成部分。索引提供了一种将查询映射到向量存储中最相关的文档或项的方法,而无需计算每个查询和每个文档之间的相似性。
近年来,已经发布了一些载体商店。特别是在LLM领域,围绕载体存储的关注已经爆炸式增长:
过去几年的发布矢量存储 — 作者图片来源
现在让我们选择一个并针对我们的用例进行尝试。与我们在前面部分中所做的类似,我们再次计算嵌入并将它们存储在向量存储中。为此,我们使用来自LangChain和色度的合适模块作为矢量存储。
- 收集我们想要用于回答用户问题的数据:
import requests
from bs4 import BeautifulSoup
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.document_loaders import TextLoader
# URL of the Wikipedia page to scrape
url = 'https://en.wikipedia.org/wiki/Prime_Minister_of_the_United_Kingdom'
# Send a GET request to the URL
response = requests.get(url)
# Parse the HTML content using BeautifulSoup
soup = BeautifulSoup(response.content, 'html.parser')
# Find all the text on the page
text = soup.get_text()
text = text.replace('\n', '')
# Open a new file called 'output.txt' in write mode and store the file object in a variable
with open('output.txt', 'w', encoding='utf-8') as file:
# Write the string to the file
file.write(text)
2. 加载数据并定义如何将数据拆分为文本块
from langchain.text_splitter import RecursiveCharacterTextSplitter
# load the document
with open('./output.txt', encoding='utf-8') as f:
text = f.read()
# define the text splitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 500,
chunk_overlap = 100,
length_function = len,
)
texts = text_splitter.create_documents([text])
3. 定义要用于计算文本块嵌入的嵌入模型,并将其存储在向量存储中(此处:色度)
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# define the embeddings model
embeddings = OpenAIEmbeddings()
# use the text chunks and the embeddings model to fill our vector store
db = Chroma.from_documents(texts, embeddings)
4. 计算用户问题的嵌入,在我们的向量存储中找到类似的文本块,并使用它们来构建我们的提示
from langchain.llms import OpenAI
from langchain import PromptTemplate
users_question = "Who is the current Prime Minister of the UK?"
# use our vector store to find similar text chunks
results = db.similarity_search(
query=user_question,
n_results=5
)
# define the prompt template
template = """
You are a chat bot who loves to help people! Given the following context sections, answer the
question using only the given context. If you are unsure and the answer is not
explicitly writting in the documentation, say "Sorry, I don't know how to help with that."
Context sections:
{context}
Question:
{users_question}
Answer:
"""
prompt = PromptTemplate(template=template, input_variables=["context", "users_question"])
# fill the prompt template
prompt_text = prompt.format(context = results, users_question = users_question)
# ask the defined LLM
llm(prompt_text)
总结
为了使我们的LLM能够分析和回答有关我们数据的问题,我们通常不会微调模型。相反,在微调过程中,目标是提高模型有效响应特定任务的能力,而不是教它新信息。
在Alpaca 7B的案例中,LLM(LLaMA)被微调为像聊天机器人一样的行为和交互。重点是完善模型的响应,而不是教它全新的信息。
因此,为了能够回答有关我们自己数据的问题,我们使用上下文注入方法。使用上下文注入创建LLM应用程序是一个相对简单的过程。主要挑战在于组织和格式化要存储在矢量数据库中的数据。此步骤对于有效检索上下文相似的信息和确保可靠的结果至关重要。
本文的目的是演示一种使用嵌入模型、向量存储和 LLM 来处理用户查询的极简方法。它展示了这些技术如何协同工作,以提供相关和准确的答案,即使是不断变化的事实。