🦜️ LangChain +Streamlit🔥+ Llama 🦙:将对话式人工智能引入您的本地设备🤯
将开源LLMs和LangChain集成以进行免费生成式问答(不需要API密钥)
在过去的几个月中,**大型语言模型(LLMs)**得到了广泛的关注,吸引了全球开发者的兴趣。这些模型为正在开发聊天机器人、个人助理和内容创作的开发者带来了令人兴奋的前景。LLMs带来的可能性在开发者|人工智能|自然语言处理社区引发了热潮。
什么是LLMs?
大型语言模型(LLM)是指能够产生与人类语言密切相似并以自然方式理解提示的机器学习模型。这些模型使用包括书籍、文章、网站和其他来源的大量数据集进行训练。通过分析数据中的统计模式,LLM预测给定输入后最可能出现的词语或短语。
最近几年LLM的时间线: 《大型语言模型综述》[1]
图片链接:
通过利用大型语言模型(LLM),我们可以有效地利用领域特定数据来解决问题。当处理模型在初始训练期间无法访问的信息时,例如公司的内部文件或知识库,这将特别有优势。
为此目的使用的架构称为"检索增强生成"或者更常见的"生成式问答"。
LangChain是什么🦜🔗?
LangChain是一个令人印象深刻并且免费提供的框架,精心设计用于让开发者利用语言模型,尤其是大型语言模型(LLM)来创建应用程序。
LangChain革命性地改变了各种应用程序(包括聊天机器人、生成式问答和摘要)的开发过程。通过无缝地将来自多个模块的组件链接在一起,LangChain使得能够根据LLMs的威力创建出出色的应用程序。
了解更多: 官方文档
https://python.langchain.com/v0.1/docs/get_started/introduction.html
动机?
在本文中,我将演示从零开始创建自己的文档助手的过程,利用LLaMA 7b和LangChain,这是一个专门为与LLMs无缝集成而开发的开源库。
以下是博客结构的概述,列出了将对流程进行详细分解的特定部分:
1.设置虚拟环境和创建文件结构
2.将LLM安装到本地机器
3.将LLM与LangChain集成并自定义PromptTemplate
4.文档检索和答案生成
5.使用Streamlit构建应用程序
第一部分:设置虚拟环境和创建文件结构
设置虚拟环境为运行应用程序提供了一个受控且隔离的环境,确保其依赖与其他系统范围的软件包分离。这种方法简化了依赖管理,并帮助在不同环境之间保持一致性。
为了设置这个应用程序的虚拟环境,我会在我的GitHub存储库中提供pip文件。首先,我们根据图中所示,创建必要的文件结构。或者,您可以直接克隆存储库以获取所需的文件。
在models文件夹内,我们将存储我们将要下载的LLMs,而pip文件将位于根目录中。要在虚拟环境中创建并安装所有依赖项,可以在相同的目录下使用 pipenv install
命令,或者简单地运行 ⚙️setup_env.bat
批处理文件。它将从 pipfile
中安装所有依赖项。这将确保所有必要的软件包和库都已安装在虚拟环境中。一旦成功安装了依赖项,我们可以继续下一步,即下载所需的模型。这是存储库链接👇:
GitHub - afaqueumer/DocQA: Question Answering with Custom FIles using LLMs
第二部分:在本地计算机上获取LLaMA
什么是LLaMA?
LLaMA是由Meta AI设计的一种新的大型语言模型,Meta AI是Facebook的母公司。LLaMA拥有从70亿到650亿参数的多样化模型集合,是目前最综合的语言模型之一。于2023年2月24日,Meta将LLaMA模型发布给公众,以展示他们对开放科学的承诺。
图片来源: LLaMA[4]
考虑到LLaMA的显著能力,我们决定利用这个强大的语言模型来实现我们的目标。具体而言,我们将使用LLaMA的最小版本,即LLaMA 7B。即使在这个较小的规模下,LLaMA 7B也具备显著的语言处理能力,使我们能够高效有效地实现我们的预期结果。
官方研究论文:LLaMA: 开放且高效的基础语言模型[5]`
要在本地CPU上执行LLM,我们需要一个GGML格式的本地模型。有几种方法可以实现这一点,但最简单的方法是直接从Hugging Face模型库🤗[6]下载bin文件。在我们的情况下,我们将下载Llama 7B模型。这些模型是开源的,可供免费下载。
如果你想节省时间和精力,不用担心-我已经帮你解决了。这是你下载模型的直接链接⏬[7]。只需下载任何一个版本,然后将文件移动到我们根目录中的models目录中。这样,你就可以方便地访问模型以供使用。
GGML是什么?为什么使用GGML?如何使用GGML?LLaMA CPP是什么?
GGML是一个用于机器学习的张量库,它是一个允许您在仅使用CPU或CPU + GPU上运行LLM的C++库。它定义了一种用于分发大型语言模型(LLMs)的二进制格式。GGML利用一种称为量化的技术,使得大型语言模型能够在消费者硬件上运行。
那么什么是量化?
LLM的权重是浮点数(小数)。就像需要更多的空间来表示一个大整数(例如1000)比表示一个小整数(例如1)需要更多的空间一样,将浮点数的高精度(例如0.0001)与低精度浮点数(例如0.1)相比,需要更多的空间。量化一个大型语言模型的过程涉及减少权重的表示精度,以减少使用模型所需的资源。GGML支持多种不同的量化策略(例如4位、5位和8位量化),每种策略在效率和性能之间提供不同的权衡。
Llama的量化大小
为了有效使用这些模型,考虑到内存和磁盘的需求是至关重要的。由于目前模型完全加载到内存中,您需要足够的磁盘空间来存储它们,以及足够的RAM来在执行时加载它们。对于65B模型,即使经过量化处理后,建议至少有40 GB的可用RAM。值得注意的是,内存和磁盘需求目前是等价的。
量化在管理这些资源需求中起着至关重要的作用。除非您有特殊的计算资源访问🤑🤑🤑
通过减少模型参数的精度并优化内存使用,量化使得模型能够在更一般的硬件配置上运行。这确保了模型的运行能在更广泛的设备上实现可行和高效。
要在Python中使用C++库,应该如何操作?
如何在Python中使用C++库?
这就是Python绑定发挥作用的地方。绑定是指在我们的Python和C++之间创建桥梁或接口的过程。我们将使用llama-cpp-python ,这是llama.cpp 的Python绑定,它作为LLaMA模型在纯C/C++中的推理。llama.cpp的主要目标是使用4位整数量化运行LLaMA模型。这种集成使我们能够有效地利用LLaMA模型,充分发挥C/C++实现的优势和4位整数量化的益处🚀
llama.cpp支持的模型: 源代码[8]
通过已准备好的GGML模型和我们的所有依赖项(感谢pipfile),现在是时候开始我们的LangChain之旅了。但在深入探索令人兴奋的LangChain世界之前,让我们用传统的"Hello World"仪式开始一切------毕竟,LLM也是一种语言模型 😄。
Voilà!我们已成功在CPU上执行了我们的第一个LLM,完全离线和完全随机的方式(您可以调整超参数temperature进行玩耍)。
有了这个令人激动的里程碑🎯,我们现在准备迎接我们的主要目标:使用LangChain框架进行自定义文本的问答。
第三节:开始使用LLM和LangChain整合🤝
在上一节中,我们使用llama cpp初始化了LLM。现在,让我们利用LangChain框架来开发使用LLM的应用程序。您可以通过文本与它们进行交互的主要接口是文本。简单来说,很多模型都是⬇️以文本为输入,输出文本⬆️的。因此,LangChain中的许多接口都围绕文本展开。
提示工程的兴起📈
在不断发展的编程领域中出现了一个引人注目的范例:提示。提示涉及提供特定输入给语言模型,以诱导出所期望的回应。这种创新方法使我们能够根据我们提供的输入来塑造模型的输出。
我们用提示的措辞方式的微妙差别对模型的回应性质和内容产生了显著的影响,这令人惊讶。结果可能会在语言上有根本性的不同,凸显出在构思提示时的仔细考虑的重要性。
为了为LLMs提供无缝的互动,LangChain提供了几个类和功能,以便使用提示模板轻松构建和处理提示。这是一种可复制的方法来生成提示。它包含一个文本字符串"模板",可以接受来自最终用户的一组参数并生成一个提示。让我们看几个例子。
我希望之前的解释能更清楚地说明提示的概念。现在,让我们来提示LLM。
这个效果非常完美,但并不是LangChain的最佳利用方式。到目前为止,我们已经使用了单个组件。我们取出了提示模板进行格式化,然后取出了LLM,再将这些参数传递给LLM以生成答案。在简单的应用中,孤立使用LLM是可以的,但在更复杂的应用中,需要将LLM链接起来 - 要么彼此链接,要么与其他组件链接。
LangChain为此类链接🔗应用程序提供了Chain接口。我们通常将链定义为对组件的调用序列,其中可以包括其他链。通过链,我们可以将多个组件组合在一起,创建一个单一的、连贯的应用程序。例如,我们可以创建一个链,将用户输入与提示模板一起格式化,然后将格式化后的响应传递给LLM。通过将多个链组合在一起或将链与其他组件组合,我们还可以构建更复杂的链。
为了理解一个链,让我们创建一个非常简单的链接🔗,它将接受用户输入,使用它来格式化提示,然后使用上述已经创建的各个组件将其发送到LLM。
当处理多个变量时,您可以选择使用字典将它们集体输入。这就结束了本节内容。现在,让我们深入研究主要部分,我们将把外部文本作为问题回答目的的检索器进行整合。
第4节:生成用于问题回答的嵌入和向量存储
在许多LLM应用中,需要用户特定的数据,而这些数据不包含在模型的训练集中。LangChain为您提供了加载、转换、存储和查询数据的基本组件。
LangChain中的数据连接
五个阶段是:
1.**文档加载器:**用于将数据加载为文档。
2.**文档转换器:**将文档分割成较小的块。
3.**嵌入:**将块转换为矢量表示,即嵌入。
4.**向量存储:**用于将上述块向量存储在向量数据库中。
5.**检索器:**用于检索与查询中的矢量相似的一组/多组矢量,这些矢量以同一潜在空间中的嵌入形式存在。
文档检索/问答循环
现在,我们将逐步介绍执行与查询最相似的文档块检索的五个步骤。在此之后,我们可以根据检索到的向量块生成答案,如所提供的图示所示。
在继续之前,我们需要准备一个文本来执行上述任务。为了进行这个虚构的测试,我已经从维基百科上复制了一段关于一些流行的DC超级英雄的文本。以下是该文本:
加载和转换文档
首先,让我们创建一个文档对象。在这个例子中,我们将使用文本加载器。但是,语言链支持多个文档,所以根据您的具体文档,您可以使用不同的加载器。接下来,我们将使用**load**
方法从预配置的来源检索数据并将其加载为文档。
一旦文档加载完毕,我们可以通过将其分成较小的块来进行转换过程。为了实现这一点,我们将使用TextSplitter。默认情况下,拆分器会在"\n\n"分隔符处将文档分开。然而,如果您将分隔符设为null并定义特定的块大小,每个块将具有指定的长度。因此,结果列表的长度将等于文档长度除以块大小。总之,它将类似于这样:列表长度 = 文档长度 / 块大小。让我们践行这番话语。
旅程的一部分就是embedding!!!
这是最重要的步骤。嵌入生成文本内容的向量化描绘。这具有实际意义,因为它允许我们在一个向量空间中概念化文本。
Word嵌入只是一个包含实数的单词的向量表示。由于语言通常包含至少数万个单词,简单的二进制词向量由于维度过高可能变得不实用。Word嵌入通过在低维向量空间中提供密集表示来解决这个问题。
当我们谈论检索时,我们是指检索与嵌入在相同潜在空间中的向量形式的查询最相似的一组向量。
LangChain中的基本嵌入类暴露了两种方法:一种用于嵌入文档,一种用于嵌入查询。前者以多个文本作为输入,而后者以单个文本作为输入。
为了全面理解嵌入,我高度推荐深入研究其基础知识,因为它们构成了神经网络处理文本数据的核心。我在其中一篇利用TensorFlow的博客中广泛涵盖了这个主题。这是链接👇
词嵌入 - 神经网络的文本表示: 嵌入是一种可以学习的自然语言表示,其中具有相似含义的单词具有相似的...[9]
创建向量存储并检索文档
向量存储有效地管理嵌入式数据的存储,并为您提供向量搜索功能。嵌入和存储嵌入向量是存储和搜索非结构化数据的普遍方法。在查询时,非结构化查询也被嵌入,检索与嵌入查询最相似的嵌入向量。这种方法能够有效地从向量存储中检索相关信息。
在这里,我们将利用Chroma,一种嵌入式数据库和向量存储,专门用于简化包含嵌入的AI应用程序的开发。它提供了一套全面的内置工具和功能,以便方便地通过执行简单的 pip install chromadb
命令在本地机器上安装。
到目前为止,我们已经见证了嵌入和向量存储在从大量文件集合中检索相关片段方面的显著能力。现在,是时候将这个检索到的片段作为上下文与我们的查询一起呈现给LLM了。用它神奇的魔杖一挥,我们将恳求LLM基于我们提供的信息生成答案。重要的部分是提示结构。
然而,强调一个良好结构的提示的重要性是至关重要的。通过制定一个精心设计的提示,我们可以减轻LLM在面对不确定性时可能发生的幻觉,即它可能会编造事实。
不再拖延等待时间,我们现在继续进行最后阶段,并发现我们的LLM是否能够产生一个引人入胜的答案。时机已经到来,我们要见证我们努力的顶点,揭示结果。我们去吧 ⚡
(与医生的问答)
这就是我们一直等待的时刻!我们做到了!👏👏我们刚刚构建了我们自己的问题回答机器人🤖,利用本地运行的LLM。⚡⚡
第五部分:使用Streamlit🔥链接所有内容
本节完全可选,因为它不是Streamlit的全面指南。我不会深入研究这部分内容,而是提供一个基本应用程序,允许用户上传任何文本文档。然后,他们将有机会通过文本输入来提出问题。在幕后,功能将与我们在前一节中涵盖的内容保持一致。
然而,在Streamlit中上传文件有一个注意事项。为了防止潜在的内存错误,特别是考虑到LLMs的内存密集型特性,我将简单地读取文档并将其写入我们文件结构中的临时文件夹中,命名为raw.txt
。这样,无论文档的原始名称是什么,Textloader都将在将来无缝处理它。
目前,该应用程序设计用于文本文件,但您可以将其适应为PDF、CSV或其他格式。基本概念保持不变,因为LLMs主要设计用于文本输入和输出。此外,您可以尝试使用Llama C++绑定支持的不同LLMs。
不深入研究复杂的细节,我提供这个应用的代码。请随意根据您特定的使用情况进行自定义。
python
# Bring in deps
import streamlit as st
from langchain.llms import LlamaCpp
from langchain.embeddings import LlamaCppEmbeddings
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
# Customize the layout
st.set_page_config(page_title="DOCAI", page_icon="🤖", layout="wide", )
st.markdown(f"""
<style>
.stApp {{background-image: url("https://images.unsplash.com/photo-1509537257950-20f875b03669?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1469&q=80");
background-attachment: fixed;
background-size: cover}}
</style>
""", unsafe_allow_html=True)
# function for writing uploaded file in temp
def write_text_file(content, file_path):
try:
with open(file_path, 'w') as file:
file.write(content)
return True
except Exception as e:
print(f"Error occurred while writing the file: {e}")
return False
# set prompt template
prompt_template = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
{context}
Question: {question}
Answer:"""
prompt = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
# initialize hte LLM & Embeddings
llm = LlamaCpp(model_path="./models/llama-7b.ggmlv3.q4_0.bin")
embeddings = LlamaCppEmbeddings(model_path="models/llama-7b.ggmlv3.q4_0.bin")
llm_chain = LLMChain(llm=llm, prompt=prompt)
st.title("📄 Document Conversation 🤖")
uploaded_file = st.file_uploader("Upload an article", type="txt")
if uploaded_file is not None:
content = uploaded_file.read().decode('utf-8')
# st.write(content)
file_path = "temp/file.txt"
write_text_file(content, file_path)
loader = TextLoader(file_path)
docs = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
texts = text_splitter.split_documents(docs)
db = Chroma.from_documents(texts, embeddings)
st.success("File Loaded Successfully!!")
# Query through LLM
question = st.text_input("Ask something from the file", placeholder="Find something similar to: ....this.... in the text?", disabled=not uploaded_file,)
if question:
similar_doc = db.similarity_search(question, k=1)
context = similar_doc[0].page_content
query_llm = LLMChain(llm=llm, prompt=prompt)
response = query_llm.run({"context": context, "question": question})
st.write(response)
以下是streamlit应用程序的样子🔥。
这次我提供了从维基中复制的《蝙蝠侠:黑暗骑士》的情节,并询问"谁的脸被严重烧伤了?"然后LLM回答说------"哈维·登特"。
好了,好了,好了!就这样,我们完成了这篇博客。
我希望您喜欢这篇文章!并且觉得它有用且有趣。您可以通过Afaque Umer[10]来关注我,了解更多类似的文章。
我将努力提出更多机器学习/数据科学概念,并尝试将那些听起来高级的术语和概念解释得更简单。
我是查老师,我们下期见!!!
声明
本文原作者为査老师,
持续更新更多的AI相关的内容,感兴趣的请点赞、关注、收藏。
References
[1]
《大型语言模型综述》: https://arxiv.org/abs/2303.18223
[2]
官方文档 : https://python.langchain.com/docs/get_started/introduction.html
[3]
GitHub - afaqueumer/DocQA: Question Answering with Custom FIles using LLMs: https://github.com/afaqueumer/DocQA
[4]
LLaMA: https://research.facebook.com/publications/llama-open-and-efficient-foundation-language-models/
[5]
LLaMA: 开放且高效的基础语言模型: https://research.facebook.com/publications/llama-open-and-efficient-foundation-language-models/
[6]
Hugging Face模型库🤗: https://huggingface.co/models
[7]
下载地址: https://huggingface.co/TheBloke/LLaMa-7B-GGML
[8]
源代码 : https://github.com/ggerganov/llama.cpp
[9]
词嵌入 - 神经网络的文本表示: 嵌入是一种可以学习的自然语言表示,其中具有相似含义的单词具有相似的...: https://medium.com/codex/word-embeddings-text-representation-for-neural-networks-65fd934d1fa2
[10]
Afaque Umer: https://medium.com/u/430bc504f9d9