使用 Hugging Face Zephyr 和 LangChain 的 GitHub Issues 简单 RAG
此笔记本演示了如何使用 HuggingFaceH4/zephyr-7b-beta
模型和 LangChain 快速构建项目的 GitHub Issues 的 RAG(检索增强生成)。
什么是 RAG?
RAG 是一种流行的方法,用于解决强大的大语言模型由于其训练数据中没有特定内容而无法识别特定内容,或者即使在之前见过该内容的情况下也会产生幻觉的问题。此类特定内容可能是专有的、敏感的,或者,如本例所示,是最近的并且经常更新的。
如果您的数据是静态的并且不会定期更改,您可以考虑对大型模型进行微调。但是,在许多情况下,微调可能成本高昂,并且当重复进行时(例如,为了解决数据漂移),会导致“模型偏移”。这是指模型的行为以不可取的方式发生变化。
**RAG(检索增强生成)**不需要模型微调。相反,RAG 通过为大语言模型提供从相关数据中检索到的额外上下文来工作,以便它可以生成更明智的响应。
这是一个快速示例
外部数据使用单独的嵌入模型转换为嵌入向量,并且向量存储在数据库中。嵌入模型通常很小,因此定期更新嵌入向量比微调模型更快、更便宜且更容易。
同时,由于不需要微调,您可以自由地在可用时将您的 LLM 替换为更强大的 LLM,或者在需要更快推理时切换到较小的蒸馏版本。
让我们演示一下如何使用开源 LLM、嵌入模型和 LangChain 构建 RAG。
首先,安装所需的依赖项
!pip install -q torch transformers accelerate bitsandbytes transformers sentence-transformers faiss-gpu
# If running in Google Colab, you may need to run this cell to make sure you're using UTF-8 locale to install LangChain
import locale
locale.getpreferredencoding = lambda: "UTF-8"
!pip install -q langchain langchain-community
准备数据
在本例中,我们将加载 PEFT 库的仓库 中的所有 Issues(已打开和已关闭)。
首先,您需要获取 GitHub 个人访问令牌 以访问 GitHub API。
from getpass import getpass
ACCESS_TOKEN = getpass("YOUR_GITHUB_PERSONAL_TOKEN")
接下来,我们将加载 huggingface/peft 仓库中的所有问题。
- 默认情况下,拉取请求也被视为问题,这里我们选择通过设置
include_prs=False
将其从数据中排除。 - 设置
state = "all"
表示我们将加载打开和关闭的问题。
from langchain.document_loaders import GitHubIssuesLoader
loader = GitHubIssuesLoader(repo="huggingface/peft", access_token=ACCESS_TOKEN, include_prs=False, state="all")
docs = loader.load()
单个 GitHub 问题的內容可能比嵌入模型可以作为输入的内容更长。如果我们想嵌入所有可用的内容,我们需要将文档分成适当大小的部分。
最常见和最直接的分割方法是定义一个固定的块大小,以及块之间是否应该有任何重叠。在块之间保留一些重叠可以让我们保留块之间的语义上下文。对于通用文本,推荐的分割器是 RecursiveCharacterTextSplitter,我们将在本文中使用它。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=30)
chunked_docs = splitter.split_documents(docs)
创建嵌入和检索器
现在文档都已达到适当的大小,我们可以创建一个包含其嵌入的数据库。
要创建文档块嵌入,我们将使用 HuggingFaceEmbeddings
和 BAAI/bge-base-en-v1.5
嵌入模型。Hub 上还有许多其他嵌入模型可用,您可以通过查看 大型文本嵌入基准测试 (MTEB) 排行榜 来关注性能最佳的模型。
要创建向量数据库,我们将使用 FAISS
,这是 Facebook AI 开发的一个库。该库提供了密集向量的有效相似性搜索和聚类,这正是我们这里需要的。FAISS 目前是大型数据集上 NN 搜索最常用的库之一。
我们将通过 LangChain API 访问嵌入模型和 FAISS。
from langchain.vectorstores import FAISS
from langchain.embeddings import HuggingFaceEmbeddings
db = FAISS.from_documents(chunked_docs, HuggingFaceEmbeddings(model_name="BAAI/bge-base-en-v1.5"))
我们需要一种方法来根据非结构化查询返回(检索)文档。为此,我们将使用 as_retriever
方法,使用 db
作为基础。
search_type="similarity"
表示我们希望在查询和文档之间执行相似性搜索。search_kwargs={'k': 4}
指示检索器返回前 4 个结果。
retriever = db.as_retriever(search_type="similarity", search_kwargs={"k": 4})
向量数据库和检索器现已设置完毕,接下来我们需要设置链的下一部分——模型。
加载量化模型
对于此示例,我们选择了 HuggingFaceH4/zephyr-7b-beta
,这是一个小巧但功能强大的模型。
由于每周都会发布许多模型,您可能希望将此模型替换为最新最好的模型。跟踪开源 LLM 的最佳方法是查看 开源 LLM 排行榜。
为了使推理速度更快,我们将加载模型的量化版本。
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
model_name = "HuggingFaceH4/zephyr-7b-beta"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16
)
model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config)
tokenizer = AutoTokenizer.from_pretrained(model_name)
设置 LLM 链
最后,我们拥有了设置 LLM 链所需的所有部分。
首先,使用加载的模型及其分词器创建一个文本生成管道。
接下来,创建一个提示模板——这应该遵循模型的格式,因此,如果您替换模型检查点,请确保使用适当的格式。
from langchain.llms import HuggingFacePipeline
from langchain.prompts import PromptTemplate
from transformers import pipeline
from langchain_core.output_parsers import StrOutputParser
text_generation_pipeline = pipeline(
model=model,
tokenizer=tokenizer,
task="text-generation",
temperature=0.2,
do_sample=True,
repetition_penalty=1.1,
return_full_text=True,
max_new_tokens=400,
)
llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
prompt_template = """
<|system|>
Answer the question based on your knowledge. Use the following context to help:
{context}
</s>
<|user|>
{question}
</s>
<|assistant|>
"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=prompt_template,
)
llm_chain = prompt | llm | StrOutputParser()
注意:您还可以使用 tokenizer.apply_chat_template
将消息列表(作为字典:{'role': 'user', 'content': '(...)'}
)转换为具有适当聊天格式的字符串。
最后,我们需要将 llm_chain
与检索器结合起来以创建一个 RAG 链。我们将原始问题以及检索到的上下文文档传递到最终的生成步骤。
from langchain_core.runnables import RunnablePassthrough
retriever = db.as_retriever()
rag_chain = {"context": retriever, "question": RunnablePassthrough()} | llm_chain
比较结果
让我们看看 RAG 在生成针对库特定问题的答案方面有什么区别。
question = "How do you combine multiple adapters?"
首先,让我们看看仅使用模型本身(不添加上下文)可以获得什么样的答案。
llm_chain.invoke({"context": "", "question": question})
如您所见,模型将问题解释为关于物理计算机适配器的问题,而在 PEFT 的上下文中,“适配器”指的是 LoRA 适配器。让我们看看添加 GitHub 问题的上下文是否可以帮助模型给出更相关的答案。
rag_chain.invoke(question)
如我们所见,添加的上下文确实帮助了完全相同的模型,为库特定问题提供了更相关和更准确的答案。
值得注意的是,库中已添加了组合多个适配器进行推理的功能,并且可以在文档中找到此信息,因此对于此 RAG 的下一次迭代,可能值得包含文档嵌入。
< > 更新 于 GitHub