“First
点击上方蓝字关注我们
OpenAI最近释放了重磅炸弹GPT4-V,即多模态视觉版本的GPT4。未来的LLM将不仅仅能够理解人类的语言文字,也能够理解各种图像照片,这无疑将极大的拓展大模型的应用场景,想象一下,交给他一张图表,他给你提建议;交给他软件界面,他给你写代码;交给他医疗影像,他给你做诊断...。实际上,我们目前在创建LLM应用时,已经面临多模态的处理问题,比如,我们在创建AI Agent时,为了解决“幻觉”问题常用的RAG(基于私有知识的检索增强生成)方案中,常常会需要对私有知识做拆分与嵌入(Embedding),形成Agent的长期记忆。这些文档中常常会有一些半结构化/非结构化的信息,比如一个PDF文档中的带有重要信息的表格与图片,那么如何对这类信息进行一系列处理(拆分/嵌入/检索)呢?
总体思路
回顾一下基于私有知识的RAG方案通常如下:
对于普通的文本内容(Text类型)来说,要实现这样的处理流程,不管你是否借助于LangChain框架,其处理过程都并不复杂:借助于一些开源的分词、嵌入模型、向量库可以很方便的实现。 但是如果文档中包含有表格信息(简单粗暴的拆分会造成信息不完整)或者图片(需要获取与表达图片的语义)如何处理?
这里我们尝试着参考Langchain官方的Cookbook中的相关内容来研究下这种可能的解决方案,并尽量用更加通用(不依赖于Langchain)的方式来描述其实现过程。 整体的概念方案如下图:
我们把这里方案的重点总结成三点:
-
对原始文档(比如PDF格式)的多模态信息的解析和提取,这里借助于一个开源非结构化数据预处理工具: Unstructured
-
解析出来的图片通过多模态模型来生成语义摘要,即把文档里的图片信息用文字进行细节描述,并用于后续的嵌入与检索,这里借助了一个开源的多模态模型: LlaVa
-
使用了“ 多向量存储与检索 ”方案来关联存储文本/表格/图片的原始数据与摘要信息,以同时获得语义检索的精准性与上下文的完整性。
我们这三个方面做详细阐述。
非结构化信息提取与解析
这是比较容易理解的一部分,简单的说,就是 借助工具来解析PDF文件中的信息,并把其中的信息拆分成文字、表格与图片三大类分别存储成结构化的形式,并用于后续处理 。这里涉及的工具是 unstructured ,具体使用可以参考其开发文档。
unstructured是一个开源的用于处理非结构化文档的库,可以很方便的用来对非结构化的文档进行处理与提取信息(PDF、Office、HTML等),并把提取的结果用结构化的形式输出。在实际测试中,要注意unstructured根据你所处理的文件类型,会依赖于一些底层的库,比如处理pdf需要poppler-utils(pdf工具库)与tesseract-ocr(开源ocr文字识别库)。
在给定的参数下,比如PDF文件名、输出普通文本块的大小限制、是否识别表格信息、怎么处理图片、图片输出目录等,unstructured会把文档的处理结果输出到一个元素数组中,每个元素会有类型与内容。这里我们会把PDF文件中的图片输出成多个文件,而文字与表格则存储到数组中。
这样,我们就提取了PDF中所有的信息,包括了表格与图片,并用便于后续处理形式进行了组织。
图片的嵌入(Embedding)与检索
对于上一阶段形成的以文本形式存在的Text与Table数据处理相对简单,可以很方便的通过嵌入模型进行向量化存储,后续通过语义召回相关内容,然后交给大模型来输出。但是对于图片来说,则有一些区别,比如下面这张图片,如果我们询问“ 苹果的服务收入在最近几年的增长率怎么样?” 这 样的问题时,如何能够让你的LLM应用:
1.根据问题的语义精确的检索到这张图片?
2.检索到图片以后,如何把图片作为参考上下文交给LLM?
基于LangChain的框架实现,我们整理出一个更通用型的方案:
其整体思想: 借助于多模态的LLM来生成图片的信息摘要,并基于这些摘要做语义检索,同时又能关联原始的图片 。这个方案在最后的处理上,有简单的方法1(不依赖于多模态的LLM)和复杂的方法2(依赖于多模态的LLM)。我们做详细的描述:
-
借助于开源的多模态LLM生成图片的摘要信息,将图片信息文本化。 这个主要是为了方便做嵌入(embedding)以及后续的语义搜索。这里可以借助于 LlaVa 这个开源模型对上面的图片进行处理(下图仅为展示效果,实际中需要借助llaVa的工具来批量生成图片摘要):
可以看到,在合适的提示信息下,LlaVa能够把图片包含的重要信息比较详细的进行摘要总结。
当然,这里我们完全可以期待即将发布的GPT4-V的API
-
对提取的图片摘要进行嵌入,存储到向量数据库中,并关联到原始图片。 这里,我们通过设置向量的元数据中的图片ID(生成的唯一ID)与真实的图片进行关联,而真实图片可以存放在一个Memory Store中(如果不考虑性能,存放在磁盘也无不可)。
-
在用户输入问题以后,处理流程可以有两种方式,一种相对简单,一种需要借助于多模态的LLM。
方法一 :通过问题question进行语义检索,如果命中相关的图片摘要,则直接将对应的内容作为参考上下文,交给普通LLM回答即可。
方法二 :通过问题question进行语义检索,如果命中相关的图片摘要,根据元数据中的图片ID关联获取到原始的图片数据(raw image),然后把图片数据交给 多模态的LLM(比如LlaVa或者GPT4-V) 回答。
两种方法的区别是:
第一种方法在把图片转换成文本摘要以后,处理过程就和普通文本完全一致
第二种方法则依赖于向量与原始图片的关联,并且在取出原始图片后,需要借助于多模态的LLM来处理输入问题;
还有一种可能的方法是对摘要文本与原始图片都进行embedding后做向量化存储,后续在做语义检索时一起搜索出来,这种方法依赖于图嵌入技术,暂不做讨论。
从这个过程可以看到,即使不采用LangChain,你也可以借助各种工具的API接口来实现这个方案。当然如果你借助langchain,这个过程会更加简单,这里做简单演示:
# vectorstore:向量存储,用来存储文本摘要,用于语义搜索
vectorstore = Chroma(
collection\_name="summaries",
embedding\_function=OpenAIEmbeddings())
# memorystore:存储父文档,比如更大的文本块;或者原图片
store = InMemoryStore()
#声明后续用来检索的检索器
retriever = (
vectorstore=vectorstore,
docstore=store,id\_key="doc\_id")
#....省略生成图片摘要到cleaned\_img\_summary的过程....
img\_ids = [str(uuid.uuid4()) for \_ in cleaned\_img\_summary]
summary\_img = [Document(page\_content=s,metadata={doc\_id: img\_ids[i]}) for i, s in enumerate(cleaned\_img\_summary)]
#把图片摘要嵌入后添加到vectorestore
retriever.vectorstore.add\_documents(summary\_img)
#把图片放到memorystore,通过img\_id关联
retriever.docstore.mset(list(zip(img\_ids, ### image ### )))
#...后续通过retriever搜索关联内容发送给llm...
retriever.get\_relevant\_documents("### question ###")
多向量存储与检索
在上文中,我们用到了一个叫做“多向量检索器“的组件来实现关联存储一个原始图片和其向量化的摘要信息。这虽然是一个在LangChain中内置的组件,但其实我们在开发LLM应用时, 即使没有使用LangChain,也可以参考其中的思想来更好的实现基于RAG方案的Agent应用,这不仅适用于图片,更适用于普通文本信息。
那么多向量存储与检索是为了解决什么问题呢?
我们知道基于私有知识库的RAG基本流程中有一个关键环节是 对私有知识进行切片(分块)嵌入。 在后续用户输入问题后,可以通过语义检索出问题相关的知识块,并把他们交给LLM用来参考。
那么这里带来的问题就是:
这个切片的大小如何控制? 是500还是1000?如果切片过大,就会导致包含的信息过多,从而在需要精确的向量检索时难以命中;如果切片过小,虽然检索时可以更精确的命中,但是由于携带的内容太少,不利于LLM输出(给的参考答案太少,LLM会自己乱编),即不管是“大块”,还是“小块”,都有缺陷。
多向量存储与检索 就是为了兼顾这两种方案: 通过“大块”来提高携带的上下文的完整性;通过“小块”来做嵌入,以尽可能的捕获语义并提高检索的命中率和相关性。
整个过程大致如下:
-
对原始文档进行较大的切片,这部分不需要进行嵌入
-
对较大的切片进行更小的切片,通过嵌入生成多向量用于后续检索,同时通过ID与原始较大的切片进行关联
-
检索时通过向量库进行检索,召回相关的小块;并通过关联的ID检索出携带更多信息的上下文“大块”,然后交给大模型去参考
这里的“小块”不仅可以是简单的大块化小,也可以是针对大块提取的摘要信息,或者是针对大块的假设性提问。总之,能够增加语义检索的关联与命中即可。 在上面的图片处理案例中,大块就是原始的图片,小块就是生成的图片信息。
在实际的代码实现上,你可以自行通过方法对文档切片拆分,生成小块内容做嵌入,然后在检索时进行关联查询即可。当然,如果你使用Langchain,则有很多现成的组件封装,可以参考上面代码中的MultiVectorRetriver的使用,且针对“大块”与“小块”可能的不同关系,Langchain有不同的封装实现。可以参考:
https://python.langchain.com/docs/modules/data\_connection/retrievers/multi\_vector
结束语
以上是我们总结的在创建私有知识库的LLM应用时,可能会面临的非结构化文档处理的方案。当然,其中需要用到多模态的大模型来协助,相信不久类似OpenAI公司的GPT4-V的多模态API开放后,这部分的处理会更加水到渠成,让我们一起期待。
END
点击关注,探讨LLM深度应用
点击使用AI小助手
