RAG 进阶 半结构化数据

我在LlamaIndex和LangChain框架学习中,都有玩过RAG。最近准备开个RAG进阶系统,一起学习AI, 一起成长。欢迎点赞,留言交流。

前言

RAG是一种自然语言处理技术,它将检索(向量数据库)和生成式人工智能模型的能力,有效提高信息检索质量,我们称之为检索增强生成技术。

ChatGPT是聊天机器人,那么基于大模型的文档聊天机器人,就是RAG应用了。

Naive RAG

Naive RAG 指最本的检索生成,包括文档分块、嵌入(Embedding)、并基于用户提出的问题进行语义相似性搜索来生成检索内容。本文我们将在Naive RAG的基础上,升华我们对RAG的认识和能力。

Naive RAG的优点是简单,缺点是性能比较差,质量也不高,本系列让我们一起来学习Advanced RAG。

Semi-Structured Data

Semi Structured Data半结构化数据的RAG是我们Advanced RAG学习的第一篇。那么什么是半结构化数据呢?这个应该是相当结构化数据来说的,我们先来理下这些概念。

  • 结构化数据

信息有预定义的结构格式。举子例子,在Mysql中,数据表的行与列分别对数据进行了预定义,这就是典型的结构化数据。它的优点是非常易于搜索与分析。

  • 非结构化数据

没有特定的格式和结构,主要由文字、图片、多媒体等构成。非结构化数据不太好统一处理,但是这些数据又是RAG需要检索的重点内容,十分具有挑战性。

  • 半结构化数据

介于结构化和非结构化之间,它由两种格式的数据混合在一起。那么我们怎么来处理呢?结构化数据我们可以用SQL等DSL语言快速解决问题,非结构化数据我们可以拆分,再embedding检索。但是如果我们的数据是结构化和非结构化混合的半结构化数据。文档的拆分就会破坏表结构, 同时表格和图片要做向量化,然后做语义查询。

PDF文档就是半结构化数据的例子。它里面包含文字、表格、图片等。等下,我们就来挑战一下怎么基于半结构化数据构建RAG。主要会用到以下几个组件:unstructured包,帮助我们自定义管道或流来处理文字、图表、图片这些元素。还有就是LangChain,我们用它来搭建整个RAG应用。向量数据库我们用的是chromadb。

Nvidia 股权变量声明

等下demo里处理的半结构化数据来自Nvida的一份股权变更声明。大家可以从下面的截图看到它的内容,比较小,方便展示结构化的图表和非结构化的文字,我们打理过后的效果。

实战

  • 安装依赖包
css 复制代码
!pip install langchain unstructured[all-docs] pydantic lxml openai chromadb tiktoken -q -U

langchain是RAG应用开发框架、unstructured支持半结构或非结构化数据处理、pydantic可以做数据验证和解析转换、lxml做xml解析、ooenai是大模型、chromadb是向量数据库、tiktoken可以统计token数量。

  • 下载 PDF文件,命名为statement_of_changes.pdf
arduino 复制代码
!wget -o statement_of_changes.pdf https://d18rn0p25nwr6d.cloudfront.net/CIK-0001045810/381953f9-934e-4cc8-b099-144910676bad.pdf
  • 安装poppler-utils和tesseract-ocr

这两个包是系统包,用于PDF文件内容的抽取以及字符的识别,安装命令会因系统不一样有所区别(mac/windows/linux)

arduino 复制代码
!apt-get install poppler-utils tesseract-ocr
  • 准备LLM ,这里我们使用gpt4
lua 复制代码
import os
os.environ["OPENAI_API_KEY"] = ""
  • 编码

首先,让我们使用unstructured库提供的partition_pdf将PDF文档中的内容分成不同类型的元素。

ini 复制代码
from typing import Any
from pydantic import BaseModel
from unstructured.partition.pdf import partition_pdf

raw_pdf_elements = partition_pdf(
    filename = "statement_of_changes.pdf",
    extract_images_in_pdf=False,
    infer_table_structure=True,
    # 基于标题来划分chunk
    chunking_strategy = "by_title",
    max_characters=4000,
    new_after_n_chars=3000,
    combine_text_under_n_chars=2000,
    image_output_dir_path="."
)

我们来聊下partition_pdf函数里的几个参数的意义。extract_images_in_pdf 表示是否要抽取pdf里的图片,这里不处理,因为当前pdf里面没有图片。infer_table_structure 表示是否来抽取表格数据,这里是处理。从代码运行看,它会触发一些模型文件并加载。从下图可以看到,使用的是microsoft/table-transformer-struct-recognition模型,需要使用到GPU资源,否则就非常慢。

  • 将元素分类
ini 复制代码
category_counts = {}
for element in raw_pdf_elements:
    category = str(type(element))
    if category in category_counts:
        category_counts[category] += 1
    else:
        category_counts[category] = 1
unique_categories = set(category_counts.keys())
category_counts

通过遍历raw_pdf_elements,我们得到每个elment的类型。set 帮助我们去重,拿到了所有的类别,category_counts字典包含了每个类别的数量信息。

从上图可以看到,CompositionElement有5个,Table有4个。接下来,我们可以根据这些类型,将不同的内容放到不同的处理容器中,完成分拣操作。

python 复制代码
class Element(BaseModel):
    type: str
    text: Any

table_elements = []
text_elements = []
for element in raw_pdf_elements:
    if "unstructured.documents.elemnts.Table" in str(type(element)):
        table_elements.append(Element(type="table", text=str(element)))
     elif "unstructured.documents.elments.CompositeElement" in str(type(element)):
     text_elements.append(Element(type="text", text=str(element)))
     print(len(table_elements))
     print(len(text_elements))

打印是4和5,我们再来打印下结构化的table。

从打印结果,可以看出所对应的表格,识别的很靠谱。table_elements[0]对应的是下图的这块,我们了解了unstructured是如何解析table的。

Chain

LangChain构建一条Chain来处理数据了。

ini 复制代码
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser
# 对文本和表格做摘要
prompt_text = """
You are responsible for concisely summarizing table or text chunk.
{element}
"""
prompt = ChatPromptTemplate.from_template(prompt_text)
model = ChatOpenAI(temperature=0,model="gpt-4")
summarize_chain={"element": lambda x: x} | prompt | model | StrOutputParser
ini 复制代码
# 对每个element都做, 并发是5
# 给表格做摘要, 表格也是文本
tables = [i.text for i in table_elements]
table_summarizes = summarize_chain.batch(tables, {"max_concurrency": 5})
# 给文本做摘要
texts = [i.text for i in text_elments]
text_summarizes = summarize_chain.batch(texts, {"max_concurrency": 5})

接下来,我们再使用MultiVectorRetriever构建检索链,它会将摘要信息和原始文本信息以父子关系一对一关联起来。这样即可以使用到原始文本,也可以使用到摘要信息,帮助我们提高RAG的质量。

ini 复制代码
# 生成唯一id
import uuid
# 嵌入,文本数据转向量数据
from langchain.embeddings import OpenAIEmbeddings
# Document 文档
from langchain.schema.document import Document
# 内存存储
from langchain.storage import InMemoryStore
# Chroma向量数据库
from langchain.vectorstores import Chroma
# 声明向量数据库实例
vectorstore = Chroma(collection_name="summaries", embedding_function=OpenAIEmbeddings())
store = InMemoryStore()
# 通过key 将父子文档关联起来
id_key="doc_id"
# 检索器
retriever = MultiVectorRetriever(
    vectorstore = vectorstore,
    docstore = store,
    id_key=id_key
)
# 对每个文本生成文本ID
doc_ids = [str(uuid.uuid4()) for _ in texts]
# s是摘要,metadata是原数据,里面包含id_key
summary_texts = [
    Document(page_content=s, metadata={id_key:doc_ids[i]})
    for i, s in enumerate(text_summaries)
]
# 将摘要放进向量数据库,会做Embedding
retriever.vectorstore.add_documents(summary_texts)
# 将原文放入内存存储
retriever.docstore.mset(list(doc_ids, texts))
# 表格也来做一下
table_ids = [str(uuid.uuid4()) for _ in tables]
summary_tables = [
    Document(page_content=s, metadata={id_key:table_ids[i]})
    for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)
retriever.docstore.mset(list(zip(table_ids, tables)))

集成

ini 复制代码
from langchain.schema.runnable import RunnablePassthrough

template = """Answer the question based only on the following context, which can include text and tables:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)

## LLM
model = ChatOpenAI(temperature = 0, model="gpt-4")

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

执行

我们基于文档中的表格数据进行提问,在某个时刻对某只股票做了交易,或变更,最后还有受益人。

arduino 复制代码
chain.invoke("How many stocks were disposed?Who is the beneficial owner?")

注意,我们这里使用的是gpt4, 大家可以切换成gpt-3.5-turbo, 你会发现就不那么work了。

总结

  • MultiVectorRetriever
  • unstructured
  • chromadb 和 InMemoryStore

参考资料

相关推荐
dwjf3218 分钟前
机器学习(四)-回归模型评估指标
人工智能·机器学习·线性回归
吕小明么13 分钟前
OpenAI o3 “震撼” 发布后回归技术本身的审视与进一步思考
人工智能·深度学习·算法·aigc·agi
算力魔方AIPC1 小时前
Meta重磅发布Llama 3.3 70B:开源AI模型的新里程碑
人工智能·llama
CSBLOG1 小时前
深度学习试题及答案解析(一)
人工智能·深度学习
四口鲸鱼爱吃盐1 小时前
Pytorch | 利用VMI-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python
四口鲸鱼爱吃盐1 小时前
Pytorch | 利用PI-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python
边缘计算社区2 小时前
吉快科技荣膺“金边奖·最佳大模型一体机”,引领AI边缘新时代
人工智能·科技
新智元2 小时前
LeCun 八年前神预言,大模型路线再颠覆?OpenAI 宣告:强化学习取得稳定性突破
人工智能·openai
电子海鸥2 小时前
迁移学习--fasttext概述
人工智能·机器学习·迁移学习
因_果_律2 小时前
亚马逊云科技 re:Invent 2024重磅发布!Amazon Bedrock Data Automation 预览版震撼登场
大数据·人工智能·科技·亚马逊云科技·re invent