关于人工智能:可视化FAISS矢量空间并调整RAG参数提高结果精度

4次阅读

共计 7794 个字符,预计需要花费 20 分钟才能阅读完成。

随着开源大型语言模型的性能一直进步,编写和剖析代码、举荐、文本摘要和问答 (QA) 对的性能都有了很大的进步。然而当波及到 QA 时,LLM 通常会在未训练数据的相干的问题上有所欠缺,很多外部文件都保留在公司外部,以确保合规性、商业秘密或隐衷。当查问这些文件时,会使得 LLM 产生幻觉,产生不相干、捏造或不统一的内容。

为了解决这一挑战的一种可用技术是检索加强生成 (retrieve – augmented Generation, RAG)。它波及通过在响应生成之前援用其训练数据源之外的权威知识库来加强响应的过程。RAG 应用程序包含一个检索系统,用于从语料库中获取相干文档片段,以及一个 LLM,用于应用检索到的片段作为上下文生成响应,所以语料库的品质及其在向量空间中的示意(称为嵌入) 在 RAG 的准确性中施展重要作用。

在本文中,咱们将应用可视化库 renumics-spotlight 在 2 - D 中可视化 FAISS 向量空间的多维嵌入,并通过扭转某些要害的矢量化参数来寻找进步 RAG 响应精度的可能性。对于咱们抉择的 LLM,将采纳 TinyLlama 1.1B Chat,这是一个紧凑的模型,与 Llama 2 雷同的架构。它的长处是具备更小的资源占用和更快的运行工夫,但其准确性没有成比例的降落,这使它成为疾速试验的现实抉择。

零碎设计

QA 零碎有两个模块,如图所示。

LoadFVectorize 模块加载 pdf 或 web 文档。对于最后的测试和可视化。第二个模块加载 LLM 并实例化 FAISS 检索,而后创立蕴含 LLM、检索器和自定义查问提醒的检索链。最初咱们对它的向量空间进行可视化。

代码实现

1、装置必要的库

renumics-spotlight 库应用相似 umap 的可视化,将高维嵌入缩小到更易于治理的 2D 可视化,同时保留要害属性。咱们在以前的文章中也介绍过 umap 的应用,然而只是功能性的简略介绍,这次咱们作为残缺的零碎设计,将他整合到一个真正可用的理论我的项目中。首先是装置必要的库:

 pip install langchain faiss-cpu sentence-transformers flask-sqlalchemy psutil unstructured pdf2image unstructured_inference pillow_heif opencv-python pikepdf pypdf
 pip install renumics-spotlight
 CMAKE_ARGS="-DLLAMA_METAL=on" FORCE_CMAKE=1 pip install --upgrade --force-reinstall llama-cpp-python --no-cache-dir

下面的最初一行是装置带有 Metal 反对的 llama- pcp -python 库,该库将用于在 M1 处理器上加载带有硬件加速的 TinyLlama。

2、LoadFVectorize 模块

模块包含 3 个性能:

load_doc 解决在线 pdf 文档的加载,每个块宰割 512 个字符,重叠 100 个字符,返回文档列表。

vectorize 调用下面的函数 load_doc 来获取文档的块列表,创立嵌入并保留到本地目录 opdf_index,同时返回 FAISS 实例。

load_db 查看 FAISS 库是否在目录 opdf_index 中的磁盘上并尝试加载,最终返回一个 FAISS 对象。

该模块代码的残缺代码如下:

 # LoadFVectorize.py
 
 from langchain_community.embeddings import HuggingFaceEmbeddings
 from langchain_community.document_loaders import OnlinePDFLoader
 from langchain.text_splitter import RecursiveCharacterTextSplitter
 from langchain_community.vectorstores import FAISS
 
 # access an online pdf
 def load_doc() -> 'List[Document]':
     loader = OnlinePDFLoader("https://support.riverbed.com/bin/support/download?did=7q6behe7hotvnpqd9a03h1dji&version=9.15.0")
     documents = loader.load()
     text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=100)
     docs = text_splitter.split_documents(documents)
     return docs
 
 # vectorize and commit to disk
 def vectorize(embeddings_model) -> 'FAISS':
     docs = load_doc()
     db = FAISS.from_documents(docs, embeddings_model)
     db.save_local("./opdf_index")
    return db
 
 # attempts to load vectorstore from disk
 def load_db() -> 'FAISS':
     embeddings_model = HuggingFaceEmbeddings()
     try:
         db = FAISS.load_local("./opdf_index", embeddings_model)
    except Exception as e:
         print(f'Exception: {e}\nNo index on disk, creating new...')
         db = vectorize(embeddings_model)
     return db

3、主模块

主模块最后定义了以下模板的 TinyLlama 提示符模板:

<|system|>{context}</s><|user|>{question}</s><|assistant|>

另外采纳来自 TheBloke 的量化版本的 TinyLlama 能够极大的缩小内存,咱们抉择以 GGUF 格局加载量化 LLM。

而后应用 LoadFVectorize 模块返回的 FAISS 对象,创立一个 FAISS 检索器,实例化 RetrievalQA,并将其用于查问。

 # main.py
 from langchain.chains import RetrievalQA
 from langchain.prompts import PromptTemplate
 from langchain_community.llms import LlamaCpp
 from langchain_community.embeddings import HuggingFaceEmbeddings
 import LoadFVectorize
 from renumics import spotlight
 import pandas as pd
 import numpy as np
 
 # Prompt template 
 qa_template = """<|system|>
 You are a friendly chatbot who always responds in a precise manner. If answer is 
 unknown to you, you will politely say so.
 Use the following context to answer the question below:
 {context}</s>
 <|user|>
 {question}</s>
 <|assistant|>
 """
 
 # Create a prompt instance 
 QA_PROMPT = PromptTemplate.from_template(qa_template)
 # load LLM
 llm = LlamaCpp(
     model_path="./models/tinyllama_gguf/tinyllama-1.1b-chat-v1.0.Q5_K_M.gguf",
     temperature=0.01,
     max_tokens=2000,
     top_p=1,
     verbose=False,
     n_ctx=2048
 )
 # vectorize and create a retriever
 db = LoadFVectorize.load_db()
 faiss_retriever = db.as_retriever(search_type="mmr", search_kwargs={'fetch_k': 3}, max_tokens_limit=1000)
 # Define a QA chain 
 qa_chain = RetrievalQA.from_chain_type(
     llm,
     retriever=faiss_retriever,
     chain_type_kwargs={"prompt": QA_PROMPT}
 )
 
 query = 'What versions of TLS supported by Client Accelerator 6.3.0?'
 
 result = qa_chain({"query": query})
 print(f'--------------\nQ: {query}\nA: {result["result"]}')
 
 visualize_distance(db,query,result["result"])

向量空间可视化自身是由下面代码中的最初一行 visualize_distance 解决的:

visualize_distance 拜访 FAISS 对象的属性__dict__,index_to_docstore_id 自身是值 docstore-ids 的要害索引字典, 用于向量化的总文档计数由索引对象的属性 ntotal 示意。

     vs = db.__dict__.get("docstore")
     index_list = db.__dict__.get("index_to_docstore_id").values()
     doc_cnt = db.index.ntotal

调用对象索引的办法 reconstruct_n, 能够实现向量空间的近似重建

    embeddings_vec = db.index.reconstruct_n()

有了 docstore-id 列表作为 index_list,就能够找到相干的文档对象,并应用它来创立一个包含 docstore-id、文档元数据、文档内容以及它在所有 id 的向量空间中的嵌入的列表:

    doc_list = list() 
    for i,doc-id in enumerate(index_list):
        a_doc = vs.search(doc-id)
        doc_list.append([doc-id,a_doc.metadata.get("source"),a_doc.page_content,embeddings_vec[i]])

而后应用列表创立一个蕴含列题目的 DF,咱们最初应用这个 DF 进行可视化

     df = pd.DataFrame(doc_list,columns=['id','metadata','document','embedding'])

在持续进行可视化之前,还须要将问题和答案联合起来,咱们创立一个独自的问题以及答案的 DF,而后与下面的 df 进行合并,这样可能显示问题和答案呈现的中央,在可视化时咱们能够高亮显示:

     # add rows for question and answer
     embeddings_model = HuggingFaceEmbeddings()
     question_df = pd.DataFrame(
         {
             "id": "question",
             "question": question,
             "embedding": [embeddings_model.embed_query(question)],
         })
     answer_df = pd.DataFrame(
         {
             "id": "answer",
             "answer": answer,
             "embedding": [embeddings_model.embed_query(answer)],
         })
     df = pd.concat([question_df, answer_df, df])

这里应用应用 np.linalg.norm 在文件和问题嵌入上的进行间隔大小的计算:

     question_embedding = embeddings_model.embed_query(question)
     # add column for vector distance
     df["dist"] = df.apply(                                                                                                                                                                         
         lambda row: np.linalg.norm(np.array(row["embedding"]) - question_embedding
         ),axis=1,)

因为 spotlight 能够对 df 进行可视化,所有咱们间接调用即可

 spotlight.show(df)

这一步将在浏览器窗口中启动 spotlight。

运行测试

1、根本测试

上面是咱们抉择的样本问题:

What versions of TLS supported by Client Accelerator 6.3.0?

正确答案是:

Client Accelerator 6.3.0 supports TLS 1.1 or 1.2.

以下额定信息可能蕴含在响应中。

You must enable this capability using the following CLI command on the Client Accelerator:(config) # policy id <id-number> ssl backend client-tls-1.2Even though TLS 1.1 is not mentioned in the syntax of this command, using this command automatically enables support for both TLS 1.1 and 1.2.

而后咱们看看 TinyLlama 对上述问题的答复:

Client Accelerator 6.3.0 supports TLS 1.1 or 1.2 as the default supported TLS versions with Client Accelerator. You must enable this capability using the following CLI command on the Client Accelerator:(config) # policy id <id-number> ssl backend client-tlss1.2Note that this command only applies to TLS 1.1 or TLS 1.2. If you need to support older TLS versions, you can use the

ssl backend

command with the

client-tlss1.0

or

client-tlss1.1

option instead.

看起来与理论答案十分类似,但它并不完全正确,因为这些 TLS 版本不是其默认值。

那么咱们来看看他是从哪些段落中找到的答案呢?

在能够 spotlight 中应用visible 按钮来管制显示的列。按“dist”对表格进行排序,在顶部显示问题、答案和最相干的文档片段。查看咱们文档的嵌入,它将简直所有文档块形容为单个簇。这是正当的,因为咱们原始 pdf 是针对特定产品的部署指南,所以被认为是一个簇是没有问题的。

单击 Similarity Map 选项卡中的过滤器图标,它只突出显示所选的文档列表,该列表是严密汇集的,其余的显示为灰色,如图下所示。

2、测试块大小和重叠参数

因为检索器是影响 RAG 性能的关键因素,让咱们来看看影响嵌入空间的几个参数。TextSplitter 的块大小 chunk size(1000,2000) 和 / 或重叠 overlap (100,200) 参数在文档宰割期间是不同的。

对所有组合的对于输入仿佛类似,然而如果咱们认真比拟正确答案和每个答复,精确答案是(1000,200)。其余答复中不正确的细节曾经用红色突出显示。咱们来尝试应用可视化嵌入来解释这一行为:

从左到右察看,随着块大小的减少,咱们能够察看到向量空间变得稠密且块更小。从底部到顶部,重叠逐步增多,向量空间特色没有显著变动。在所有这些映射中整个汇合依然或多或少地出现为一个繁多的簇,并只有几个异样值存在。这种状况在生成的响应中是能够看到的因为生成的响应都十分类似。

如果查问位于簇核心等地位时因为最近邻可能不同,在这些参数发生变化时响应很可能会产生显著变动。如果 RAG 应用程序无奈提供预期答案给某些问题,则能够通过生成相似上述可视化图表并联合这些问题进行剖析,可能找到最佳划分语料库以进步整体性能方面优化办法。

为了进一步阐明,咱们将两个来自不相干畛域(Grammy Awards 和 JWST telescope)的维基百科文档的向量空间进行可视化展现。

 def load_doc():
     loader = WebBaseLoader(['https://en.wikipedia.org/wiki/66th_Annual_Grammy_Awards','https://en.wikipedia.org/wiki/James_Webb_Space_Telescope'])
     documents = loader.load()
     ...

只批改了下面代码其余的代码放弃不变。运行批改后的代码,咱们失去下图所示的向量空间可视化。

这里有两个不同的不重叠的簇。如果咱们要在任何一个簇之外提出一个问题,那么从检索器取得上下文不仅不会对 LLM 有帮忙,而且还很可能是无害的。提出之前提出的同样的问题,看看咱们 LLM 产生什么样的“幻觉”

Client Accelerator 6.3.0 supports the following versions of Transport Layer Security (TLS):

  1. TLS 1.2\2. TLS 1.3\3. TLS 1.2 with Extended Validation (EV) certificates\4. TLS 1.3 with EV certificates\5. TLS 1.3 with SHA-256 and SHA-384 hash algorithms

这里咱们应用 FAISS 用于向量存储。如果你正在应用 ChromaDB 并想晓得如何执行相似的可视化,renumics-spotlight 也是反对的。

总结

检索加强生成 (RAG) 容许咱们利用大型语言模型的能力,即便 LLM 没有对外部文档进行训练也能失去很好的后果。RAG 波及从矢量库中检索许多相干文档块,而后 LLM 将其用作生成的上下文。因而嵌入的品质将在 RAG 性能中施展重要作用。

在本文中,咱们演示并可视化了几个要害矢量化参数对 LLM 整体性能的影响。并应用 renumics-spotlight,展现了如何示意整个 FAISS 向量空间,而后将嵌入可视化。Spotlight 直观的用户界面能够帮忙咱们依据问题摸索向量空间,从而更好地了解 LLM 的反馈。通过调整某些矢量化参数,咱们可能影响其生成行为以进步精度。

https://avoid.overfit.cn/post/30168a23de744b3f91ec22da1725eb14

作者:Kennedy Selvadurai, PhD

正文完
 0