自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

基于谷歌Gemini多模態(tài)模型實(shí)現(xiàn)PDF文檔自動(dòng)化處理 原創(chuàng)

發(fā)布于 2024-12-30 08:29
瀏覽
0收藏

本文將提出一種新的PDF文檔自動(dòng)化處理方案,從而成功處理其中的表格、圖像、圖形或方程式等對(duì)象。

引言

近年來(lái),自動(dòng)化文檔處理成為ChatGPT革命的最大贏家之一,因?yàn)長(zhǎng)LM能夠在零樣本設(shè)置中處理廣泛的主題和任務(wù),這意味著無(wú)需域內(nèi)標(biāo)記的訓(xùn)練數(shù)據(jù)。這使得構(gòu)建AI驅(qū)動(dòng)的應(yīng)用程序來(lái)處理、解析和自動(dòng)理解任意文檔變得更加容易。雖然使用LLM的簡(jiǎn)單方法仍然受到非文本上下文(例如圖形、圖像和表格)的阻礙,但是這正是我們將在本文中嘗試解決的問(wèn)題,而且我們特別關(guān)注PDF文件格式。

從根本上講,PDF只是字符、圖像和線條及其精確坐標(biāo)的集合。它們沒(méi)有固有的“文本”結(jié)構(gòu),也不是為作為文本處理而構(gòu)建的,而只是按這些內(nèi)容原樣進(jìn)行查看。這也正是使它們變得困難的原因,因?yàn)榧兾谋痉椒o(wú)法捕獲這些類型的文檔中的所有布局和視覺(jué)元素,從而導(dǎo)致上下文和信息的大量丟失。

繞過(guò)這種“純文本”限制的一種方法是,在將文檔輸入LLM之前,通過(guò)檢測(cè)表格、圖像和布局對(duì)文檔進(jìn)行大量預(yù)處理。表格可以解析為Markdown或JSON格式,圖像和圖形可以用其標(biāo)題表示,文本可以按原樣輸入。但是,這種方法需要自定義模型,并且仍會(huì)導(dǎo)致一些信息丟失。那么,我們能做得更好一些嗎?

多模態(tài)LLM

現(xiàn)在,大多數(shù)最新的大型模型都是多模態(tài)的;這意味著,它們可以處理文本、代碼和圖像等多種模態(tài)形式的數(shù)據(jù)。這為我們的問(wèn)題提供了一種更簡(jiǎn)單的解決方案,即一個(gè)模型可以同時(shí)完成所有工作。因此,我們不必為圖像添加標(biāo)題和解析表格,而是可以將頁(yè)面作為圖像輸入并按原樣處理。我們?cè)诒疚闹刑岢龅墓艿婪桨笇⒛軌蚣虞dPDF,將每個(gè)頁(yè)面提取為圖像,將其拆分為塊(使用LLM),并索引每個(gè)塊。如果檢索到塊,則將整個(gè)頁(yè)面包含在LLM上下文中以執(zhí)行任務(wù)。

接下來(lái),我們將詳細(xì)介紹如何在實(shí)踐中實(shí)現(xiàn)這一方案。

管道方案

概括來(lái)看,我們正在實(shí)施的管道是一個(gè)兩步的過(guò)程。首先,我們將每個(gè)頁(yè)面分割成重要的塊并總結(jié)每個(gè)塊。其次,我們對(duì)塊進(jìn)行一次索引,然后在每次收到請(qǐng)求時(shí)搜索這些塊,并在LLM上下文中包含每個(gè)檢索到的塊的完整上下文信息。

第1步:頁(yè)面分割和摘要

我們將頁(yè)面提取為圖像,并將它們中的每一個(gè)傳遞給多模態(tài)LLM進(jìn)行分割。像Gemini這樣的模型可以輕松理解和處理頁(yè)面布局:

  • 表格被識(shí)別為一個(gè)塊。
  • 圖形形成另一個(gè)塊。
  • 文本塊被分割成單獨(dú)的塊。

對(duì)于每個(gè)元素,LLM都會(huì)生成一個(gè)摘要,可以將其嵌入并索引到向量數(shù)據(jù)庫(kù)中。

第2步:嵌入和上下文檢索

在本文中,我們將僅使用文本嵌入以簡(jiǎn)化操作,但一個(gè)改進(jìn)是直接使用視覺(jué)嵌入。

數(shù)據(jù)庫(kù)中的每個(gè)條目包括:

  • 塊的摘要。
  • 找到它的頁(yè)碼。
  • 指向完整頁(yè)面圖像表示的鏈接,用于添加上下文。

此架構(gòu)允許在本地級(jí)別搜索(在塊級(jí)別)的同時(shí)跟蹤上下文(通過(guò)鏈接返回到完整頁(yè)面)。例如,如果搜索查詢檢索到某個(gè)項(xiàng)目,則代理可以包含整個(gè)頁(yè)面圖像,以便向LLM提供完整布局和額外上下文,從而最大限度地提高響應(yīng)質(zhì)量。

通過(guò)提供完整圖像,所有視覺(jué)提示和重要布局信息(如圖像、標(biāo)題、項(xiàng)目符號(hào)……)和相鄰項(xiàng)目(表格、段落……)在生成響應(yīng)時(shí)都可供LLM使用。

代理

我們將把每個(gè)步驟實(shí)現(xiàn)為單獨(dú)的可重復(fù)使用的代理:

  • 第一個(gè)代理用于解析、分塊和摘要。這涉及將文檔分割成重要的塊,然后為每個(gè)塊生成摘要。此代理只需對(duì)每個(gè)PDF運(yùn)行一次即可對(duì)文檔進(jìn)行預(yù)處理。
  • 第二個(gè)代理管理索引、搜索和檢索。這包括將塊的嵌入插入到向量數(shù)據(jù)庫(kù)中以實(shí)現(xiàn)高效搜索。每個(gè)文檔執(zhí)行一次索引,而搜索可以根據(jù)不同查詢的需要重復(fù)多次。

對(duì)于這兩個(gè)代理,我們都使用谷歌開(kāi)發(fā)的開(kāi)源模型Gemini,這是一種具有強(qiáng)大視覺(jué)理解能力的多模態(tài)LLM。

解析和分塊代理

第一個(gè)代理負(fù)責(zé)將每個(gè)頁(yè)面分割成有意義的塊并總結(jié)每個(gè)塊,步驟如下:

第1步:將PDF頁(yè)面提取為圖像

在本文中,我們使用pdf2image庫(kù)。然后以Base64格式對(duì)圖像進(jìn)行編碼,以簡(jiǎn)化將其添加到LLM請(qǐng)求的過(guò)程。

以下給出關(guān)鍵實(shí)現(xiàn)代碼:

from document_ai_agents.document_utils import extract_images_from_pdf
from document_ai_agents.image_utils import pil_image_to_base64_jpeg
from pathlib import Path

class DocumentParsingAgent:
    @classmethod
    def get_images(cls, state):
        """
        提取一個(gè)PDF的頁(yè)面為Base64編碼的JPEG圖像。
        """
        assert Path(state.document_path).is_file(), "File does not exist"
        # 從PDF中提取圖像
        images = extract_images_from_pdf(state.document_path)
        assert images, "No images extracted"
        # 轉(zhuǎn)換圖像到Base64編碼的JPEG
        pages_as_base64_jpeg_images = [pil_image_to_base64_jpeg(x) for x in images]
        return {"pages_as_base64_jpeg_images": pages_as_base64_jpeg_images}
extract_images_from_pdf:將PDF的每一頁(yè)提取為PIL圖像。

pil_image_to_base64_jpeg:將圖像轉(zhuǎn)換為Base64編碼的JPEG格式。

第2步:分塊和匯總

然后,將每幅圖像發(fā)送到LLM進(jìn)行分割和匯總。我們使用結(jié)構(gòu)化輸出來(lái)確保我們以預(yù)期的格式獲得預(yù)測(cè):

from pydantic import BaseModel, Field
from typing import Literal
import json
import google.generativeai as genai
from langchain_core.documents import Document

class DetectedLayoutItem(BaseModel):
    """
    針對(duì)頁(yè)面上檢測(cè)到的每個(gè)布局元素的架構(gòu)。
    """
    element_type: Literal["Table", "Figure", "Image", "Text-block"] = Field(
        ..., 
        description="Type of detected item. Examples: Table, Figure, Image, Text-block."
    )
    summary: str = Field(..., description="A detailed description of the layout item.")

class LayoutElements(BaseModel):
    """
    針對(duì)頁(yè)面上的布局元素列表的架構(gòu)。
    """
    layout_items: list[DetectedLayoutItem] = []

class FindLayoutItemsInput(BaseModel):
    """
    用于處理單個(gè)頁(yè)面的輸入模式。
    """
    document_path: str
    base64_jpeg: str
    page_number: int

class DocumentParsingAgent:
    def __init__(self, model_name="gemini-1.5-flash-002"):
        """
        使用適當(dāng)?shù)哪J匠跏蓟疞LM。
        """
        layout_elements_schema = prepare_schema_for_gemini(LayoutElements)
        self.model_name = model_name
        self.model = genai.GenerativeModel(
            self.model_name,
            generation_config={
                "response_mime_type": "application/json",
                "response_schema": layout_elements_schema,
            },
        )
    def find_layout_items(self, state: FindLayoutItemsInput):
        """
        Send a page image to the LLM for segmentation and summarization.
        """
        messages = [
            f"Find and summarize all the relevant layout elements in this PDF page in the following format: "
            f"{LayoutElements.schema_json()}. "
            f"Tables should have at least two columns and at least two rows. "
            f"The coordinates should overlap with each layout item.",
            {"mime_type": "image/jpeg", "data": state.base64_jpeg},
        ]
        # 向LLM發(fā)送提示信息
        result = self.model.generate_content(messages)
        data = json.loads(result.text)

        # 將JSON輸出轉(zhuǎn)換為文檔
        documents = [
            Document(
                page_content=item["summary"],
                metadata={
                    "page_number": state.page_number,
                    "element_type": item["element_type"],
                    "document_path": state.document_path,
                },
            )
            for item in data["layout_items"]
        ]
        return {"documents": documents}

上面代碼中,LayoutElements架構(gòu)定義了輸出的結(jié)構(gòu),包括每個(gè)布局項(xiàng)類型(表格、圖形等)及其摘要。

第3步:頁(yè)面的并行處理

為了提高速度,頁(yè)面是并行處理的。由于處理是io綁定的,因此以下方法會(huì)創(chuàng)建一個(gè)任務(wù)列表來(lái)一次性處理所有頁(yè)面圖像:

from langgraph.types import Send

class DocumentParsingAgent:
    @classmethod
    def continue_to_find_layout_items(cls, state):
        """
        生成任務(wù)以并行處理每個(gè)頁(yè)面。
        """
        return [
            Send(
                "find_layout_items",
                FindLayoutItemsInput(
                    base64_jpeg=base64_jpeg,
                    page_number=i,
                    document_path=state.document_path,
                ),
            )
            for i, base64_jpeg in enumerate(state.pages_as_base64_jpeg_images)
        ]

每個(gè)頁(yè)面都作為獨(dú)立任務(wù)發(fā)送到find_layout_items函數(shù)。

完整的工作流程

代理的工作流程使用StateGraph構(gòu)建,將圖像提取和布局檢測(cè)步驟鏈接到統(tǒng)一的管道中:

from langgraph.graph import StateGraph, START, END

class DocumentParsingAgent:
    def build_agent(self):
        """
        使用狀態(tài)圖構(gòu)建代理工作流。
        """
        builder = StateGraph(DocumentLayoutParsingState)

        # 添加節(jié)點(diǎn),用于圖像提取和布局項(xiàng)檢測(cè)
        builder.add_node("get_images", self.get_images)
        builder.add_node("find_layout_items", self.find_layout_items)
        #定義圖形的流程
        builder.add_edge(START, "get_images")
        builder.add_conditional_edges("get_images", self.continue_to_find_layout_items)
        builder.add_edge("find_layout_items", END)

        self.graph = builder.compile()

為了在示例PDF上運(yùn)行代理,我們執(zhí)行以下操作:

if __name__ == "__main__":
    _state = DocumentLayoutParsingState(
        document_path="path/to/document.pdf"
    )
    agent = DocumentParsingAgent()

    # 步驟1:從PDF中提取圖像
    result_images = agent.get_images(_state)
    _state.pages_as_base64_jpeg_images = result_images["pages_as_base64_jpeg_images"]

    #步驟2:處理第一頁(yè)(作為一個(gè)示例)
    result_layout = agent.find_layout_items(
        FindLayoutItemsInput(
            base64_jpeg=_state.pages_as_base64_jpeg_images[0],
            page_number=0,
            document_path=_state.document_path,
        )
    )
    # 顯示處理結(jié)果
    for item in result_layout["documents"]:
        print(item.page_content)
        print(item.metadata["element_type"])

上述代碼將生成PDF的解析、分段和匯總表示,這是我們接下來(lái)要構(gòu)建的第二個(gè)代理的輸入。

RAG代理

第二個(gè)代理負(fù)責(zé)處理索引和檢索部分。它將前一個(gè)代理的文檔保存到向量數(shù)據(jù)庫(kù)中,并使用其結(jié)果進(jìn)行檢索。這可以分為兩個(gè)獨(dú)立的步驟,即索引和檢索。

第1步:索引拆分文檔

使用生成的摘要,我們將其向量化并保存在ChromaDB數(shù)據(jù)庫(kù)中:

class DocumentRAGAgent:
    def index_documents(self, state: DocumentRAGState):
        """
        將解析后的文檔索引到向量存儲(chǔ)區(qū)中。
        """
        assert state.documents, "Documents should have at least one element"
        # 檢查該文檔是否已被編入索引
        if self.vector_store.get(where={"document_path": state.document_path})["ids"]:
            logger.info(
                "Documents for this file are already indexed, exiting this node"
            )
            return  #如果已經(jīng)完成,跳過(guò)索引
        # 將解析后的文檔添加到向量存儲(chǔ)區(qū)中
        self.vector_store.add_documents(state.documents)
        logger.info(f"Indexed {len(state.documents)} documents for {state.document_path}")

上述代碼中,index_documents方法將塊摘要嵌入到向量存儲(chǔ)中。我們保留文檔路徑和頁(yè)碼等元數(shù)據(jù)以供日后使用。

第2步:處理問(wèn)題

當(dāng)用戶提出問(wèn)題時(shí),代理會(huì)在向量存儲(chǔ)中搜索最相關(guān)的塊。它會(huì)檢索摘要和相應(yīng)的頁(yè)面圖像以進(jìn)行上下文理解。

class DocumentRAGAgent:
    def answer_question(self, state: DocumentRAGState):
        """
        檢索相關(guān)的數(shù)據(jù)塊,并生成針對(duì)用戶問(wèn)題的響應(yīng)。
        """
        # 根據(jù)查詢檢索前k個(gè)相關(guān)文檔
        relevant_documents: list[Document] = self.retriever.invoke(state.question)

        # 檢索相應(yīng)的頁(yè)面圖像(避免重復(fù))
        images = list(
            set(
                [
                    state.pages_as_base64_jpeg_images[doc.metadata["page_number"]]
                    for doc in relevant_documents
                ]
            )
        )
        logger.info(f"Responding to question: {state.question}")
        #構(gòu)建提示:結(jié)合圖像、相關(guān)總結(jié)和問(wèn)題
        messages = (
            [{"mime_type": "image/jpeg", "data": base64_jpeg} for base64_jpeg in images]
            + [doc.page_content for doc in relevant_documents]
            + [
                f"Answer this question using the context images and text elements only: {state.question}",
            ]
        )
        #使用LLM生成響應(yīng)
        response = self.model.generate_content(messages)
        return {"response": response.text, "relevant_documents": relevant_documents}

在上述代碼中,檢索器查詢向量存儲(chǔ)以找到與用戶問(wèn)題最相關(guān)的塊。然后,我們?yōu)長(zhǎng)LM(Gemini)構(gòu)建上下文,它將文本塊和圖像結(jié)合起來(lái)以生成響應(yīng)。

完整的代理工作流程

綜合來(lái)看,代理工作流程共有兩個(gè)階段,一個(gè)索引階段和一個(gè)問(wèn)答階段:

class DocumentRAGAgent:
    def build_agent(self):
        """
        構(gòu)建RAG代理的工作流。
        """
        builder = StateGraph(DocumentRAGState)
        # 添加用于編制索引和回答問(wèn)題的節(jié)點(diǎn)
        builder.add_node("index_documents", self.index_documents)
        builder.add_node("answer_question", self.answer_question)
        # 定義工作流
        builder.add_edge(START, "index_documents")
        builder.add_edge("index_documents", "answer_question")
        builder.add_edge("answer_question", END)
        self.graph = builder.compile()

運(yùn)行示例

if __name__ == "__main__":
    from pathlib import Path

  # 導(dǎo)入要解析文檔的第一個(gè)代理
    from document_ai_agents.document_parsing_agent import (
        DocumentLayoutParsingState,
        DocumentParsingAgent,
    )
    # 步驟1:使用第一個(gè)代理來(lái)解析文檔
    state1 = DocumentLayoutParsingState(
        document_path=str(Path(__file__).parents[1] / "data" / "docs.pdf")
    )
    agent1 = DocumentParsingAgent()
    result1 = agent1.graph.invoke(state1)
    #步驟2:設(shè)置第二個(gè)代理進(jìn)行檢索和應(yīng)答
    state2 = DocumentRAGState(
        question="Who was acknowledged in this paper?",
        document_path=str(Path(__file__).parents[1] / "data" / "docs.pdf"),
        pages_as_base64_jpeg_images=result1["pages_as_base64_jpeg_images"],
        documents=result1["documents"],
    )
    agent2 = DocumentRAGAgent()
    # 索引文檔
    agent2.graph.invoke(state2)
    # 回答第一個(gè)問(wèn)題
    result2 = agent2.graph.invoke(state2)
    print(result2["response"])
    # 回答第二個(gè)問(wèn)題
    state3 = DocumentRAGState(
        question="What is the macro average when fine-tuning on PubLayNet using M-RCNN?",
        document_path=str(Path(__file__).parents[1] / "data" / "docs.pdf"),
        pages_as_base64_jpeg_images=result1["pages_as_base64_jpeg_images"],
        documents=result1["documents"],
    )
    result3 = agent2.graph.invoke(state3)
    print(result3["response"])

通過(guò)上面的實(shí)現(xiàn),文檔處理、檢索和問(wèn)答的管道已完成。

完整實(shí)例

現(xiàn)在,讓我們使用本文前面提出的文檔AI管道方案并通過(guò)一個(gè)實(shí)際示例來(lái)解析一個(gè)示例文檔??LLM&Adaptation.pdf??,這是一組包含文本、方程式和圖形的39張幻燈片(CC BY 4.0)。

第1步:解析和摘要文檔(代理1)

  • 執(zhí)行時(shí)間:解析39頁(yè)的文檔需要29秒。
  • 結(jié)果:代理1生成一個(gè)索引文檔,其中包含每個(gè)頁(yè)面的塊摘要和Base64編碼的JPEG圖像。

第2步:詢問(wèn)文檔(代理2)

我們提出以下問(wèn)題:“(Explain LoRA, give the relevant equations)解釋LoRA,給出相關(guān)方程式”

結(jié)果:

檢索到的頁(yè)面如下:

基于谷歌Gemini多模態(tài)模型實(shí)現(xiàn)PDF文檔自動(dòng)化處理-AI.x社區(qū)

來(lái)源:LLM&Adaptation.pdf(CC-BY許可)

LLM的回復(fù)

基于谷歌Gemini多模態(tài)模型實(shí)現(xiàn)PDF文檔自動(dòng)化處理-AI.x社區(qū)

很明顯,LLM能夠利用視覺(jué)上下文根據(jù)文檔生成連貫且正確的響應(yīng),從而將方程式和圖形納入其響應(yīng)中。

結(jié)論

在本文中,我們了解了如何利用最新的LLM多模態(tài)性并使用每個(gè)文檔中可用的完整視覺(jué)上下文信息將文檔AI處理管道繼續(xù)推進(jìn)一步。我非常希望這一思想能夠提高你從信息提取或RAG管道中獲得的輸出質(zhì)量。

具體地說(shuō),我們構(gòu)建了一個(gè)更強(qiáng)大的文檔分割步驟,能夠檢測(cè)段落、表格和圖形等重要項(xiàng)目并對(duì)其進(jìn)行總結(jié);然后,我們使用第一步的結(jié)果查詢項(xiàng)目和頁(yè)面的集合,以使用Gemini模型給出相關(guān)且準(zhǔn)確的答案。接下來(lái),你可以在自己的具體場(chǎng)景的文檔上嘗試這一方案,嘗試使用可擴(kuò)展的向量數(shù)據(jù)庫(kù),并將這些代理部署為AI應(yīng)用程序的一部分。

最后,本文示例工程完整的代碼可從鏈接??https://github.com/CVxTz/document_ai_agents處獲得??。

譯者介紹

朱先忠,51CTO社區(qū)編輯,51CTO專家博客、講師,濰坊一所高校計(jì)算機(jī)教師,自由編程界老兵一枚。

原文標(biāo)題:??Build a Document AI Pipeline for Any Type of PDF with Gemini??,作者:Youness Mansar

?著作權(quán)歸作者所有,如需轉(zhuǎn)載,請(qǐng)注明出處,否則將追究法律責(zé)任
標(biāo)簽
已于2024-12-30 08:34:32修改
收藏
回復(fù)
舉報(bào)
回復(fù)
相關(guān)推薦