從RAG到多模態搜尋看OpenAI的技術演進

2023.12.24

本文探討了資訊檢索與文字產生領域的最新進展,特別關注了OpenAI的RAG模型及其在文字內容搜尋上的應用。文章詳細介紹了gpt-4-vision-preview模型,這一模型標誌著從結構化搜尋到非結構化搜尋的重大轉變,能夠有效處理和解釋多模態訊息,如圖片、表格和文字。透過實際案例分析,文章展示瞭如何利用這些技術進行企業文件管理、學術研究和媒體內容分析,為讀者提供了關於如何運用這些先進技術進行多模態資料處理的深入見解。

開篇

在人工智慧的領域內,資訊檢索與文字產生一直是兩個重要的研究方向。OpenAI的RAG(Retrieval-Augmented Generation)模型,作為這一領域的突破性成果,成功地將神經網路的文本生成能力與大規模資料集的檢索功能結合。這項創新不僅提升了文本生成模型的準確性和資訊豐富度,也解決了傳統模型在應對複雜查詢時的限制。

RAG模型的主流應用體現在文字內容的搜尋上,例如企業知識庫的檢索。透過文字載入、切割、嵌入、索引等方法建立輸入與目標的相關性,最終將搜尋結果呈現給使用者。然而,隨著資訊類型的多樣化,傳統的文本搜尋已經無法滿足所有的需求。

此時,OpenAI推出了gpt-4-vision-preview模型,不僅是技術上的一大躍進,更標誌著從結構化搜尋走向非結構化搜尋的重要轉變。該模型具備處理和解釋多模態資訊的能力,無論是圖片、表格或文本,都能夠被有效地摘要和搜尋。這項進步大大擴展了RAG功能的外延,為多模態資料處理開闢了新的道路。

例如,在企業文檔管理方面,gpt-4-vision-preview可以分析含有圖表和文字的PDF格式文檔,如合約和報告,提供精準的摘要和關鍵資訊擷取。在學術研究領域,這項技術能自動整理分析學術論文中的資料和影像,大幅提升研究效率。而在媒體內容分析上,新聞報導中的圖片與文字內容可以整合分析,為媒體從業人員提供更深入的洞察。

今天我會透過這篇文章,手把手帶大家如何實現一個多模態PDF的分析和搜尋。

場景分析

那麼今天的主角就要登場了,如下圖所示,這個PDF文件是一個典型的財經市場分析報告,包含了豐富的文字、圖表和數據。

財經市場分析報告財經市場分析報告

報告不僅包含了詳細的文字描述,如市場趨勢的分析和預測,還包括了大量的圖表和數據,如股票市場的指數、固定收益產品的收益率和關鍵利率等。這些圖表和數據在理解整個市場狀況中起著至​​關重要的作用。

然而,人工處理這類多模態的PDF檔案常常是耗時且勞力密集的。分析師需要仔細閱讀文本,解讀圖表和數據,並將這些資訊綜合起來以形成完整的市場觀點。這個過程不僅耗時,還容易出錯,特別是在處理大量複雜資料時。

傳統的RAG模型在處理這類多模態PDF檔案時顯得力不從心。儘管RAG在處理和生成基於文字的資訊方面表現出色,但它主要針對文字內容進行搜尋和生成,對於非文字元素,如圖表和數據,其處理能力有限。這意味著,在使用RAG模型進行資訊檢索時,對於包含非文字元素的複雜PDF文件,它可能無法充分理解和利用文件中的所有資訊。

因此,針對這種業務場景就需要使用OpenAI推出的多模態的處理方式,利用GPT -4-Vision-Preview模型處理文字、圖表和數據,以提供更全面和準確的分析。從而提高分析效率,也能減少因人工處理的錯誤所造成的風險。

技術分析

在多模態PDF文件處理中,首要挑戰是識別文件中的非結構化訊息,如圖片、表格和文字。這裡我們需要使用unstructured函式庫,它提供了用於攝取和預處理影像和文字文件的開源元件,如PDF、HTML、Word文件等。它的主要用途是簡化和優化大型語言模型(LLMs)的資料處理工作流程。unstructured的模組化功能和連接器形成了一個簡化資料攝取和預處理的一致系統,使其適應不同平台,有效地將非結構化資料轉換為結構化輸出。

除此之外,為了更好地處理PDF文檔,我們還引入了poppler-utils工具, 它被用來提取圖片和文字。特別是其中的pdfimages和pdftotext工具,分別用於從PDF中提取嵌入的圖像和全部文本,這對於多模態PDF的分析至關重要。

備註:Poppler是一個PDF文件渲染庫,可以使用兩種後端來繪製,分別是Cairo和Splash。這兩種後端的特性有所不同,Poppler的功能也可能依賴它所使用的後端。此外,還有一個基於Qt4的繪圖框架「Arthur」的後端,但這個後端不完整且已停止開發。Poppler為Glib和Qt5提供綁定,這些綁定提供了對Poppler後端的接口,儘管Qt5綁定只支援Splash和Arthur後端。Cairo後端支援向量圖形的抗鋸齒和透明對象,但不支援平滑點陣圖影像如掃描文檔,且不依賴X Window系統,因此Poppler可以在Wayland、Windows或macOS等平台上運行。Splash後端支援點陣圖的縮小過濾。Poppler還附帶一個文字渲染後端,可透過命令列工具pdftotext調用,用於在命令列中搜尋PDF中的字串,例如使用grep工具。

在解決了圖片、表格和問題的提取問題之後,在將圖片和表格內容轉換為可分析的文字格式方面,Tesseract-OCR工具發揮了關鍵作用。儘管Tesseract不能直接處理PDF文件,但它可以將轉換為.tiff格式的PDF文件中的圖像轉換為文字。這個過程是處理多模態PDF的關鍵環節,使得原本以圖像或表格形式存在的資訊轉化為可供進一步分析的文字形式。

當然除了上述的辨識和分離PDF元素的技術之外,例如:向量儲存、向量索引器等技術也會用到,由於在傳統的RAG中常用到,這裡就不贅述了。

程式碼實現

經過場景和技術分析之後,我們了解到如果需要對PDF進行多模態的分析與查詢,首先是要對其中的圖片、表格、文字進行辨識。這裡我們會引進unstructured 、Poppler函式庫以及Tesseract-OCR工具。有了這些工具的祝福讓我們的編碼過程更加如虎添翼。

安裝庫和工具

!pip install langchain unstructured[all-docs] pydantic lxml openai chromadb tiktoken -q -U
!apt-get install poppler-utils tesseract-ocr
  • 1.
  • 2.

上面的庫有幾個之前沒有提到的,這裡做一下說明。

  • pydantic:用於資料驗證和設定管理。
  • lxml: XML和HTML處理庫。
  • openai:與OpenAI的API進行互動。
  • chromadb:用於向量資料庫管理或資料儲存。

擷取PDF資訊

既然工具都準備好了,接下來就是處理PDF的內容了,程式碼如下:

from typing import Any

from pydantic import BaseModel
from unstructured.partition.pdf import partition_pdf

# 设置存放图像的路径
images_path = "./images"

# 调用 partition_pdf 函数处理PDF文件,提取其中的元素
raw_pdf_elements = partition_pdf(
    filename="weekly-market-recap.pdf",           # 指定要处理的PDF文件名
    extract_images_in_pdf=True,                   # 设置为True以从PDF中提取图像
    infer_table_structure=True,                   # 设置为True以推断PDF中的表格结构
    chunking_strategy="by_title",                 # 设置文档切分策略为按标题切分
    max_characters=4000,                          # 设置每个块的最大字符数为4000
    new_after_n_chars=3800,                       # 在达到3800字符后开始新的块
    combine_text_under_n_chars=2000,              # 将少于2000字符的文本组合在一起
    image_output_dir_path=images_path,            # 指定输出图像的目录路径
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

這段Python程式碼使用了pydantic和unstructured函式庫來處理PDF檔。下面是程式碼的逐行解釋:

images_path = "./images":設定一個變數images_path,用於存放從PDF中提取的影像。

接下來呼叫partition_pdf函數,這個函數用於處理PDF檔案並提取其中的元素:

  • filename="weekly-market-recap.pdf":指定要處理的PDF檔名。
  • extract_images_in_pdf=True:設定為True以從PDF中擷取影像。
  • infer_table_structure=True:設定為True以推斷PDF中的表格結構。
  • chunking_strategy="by_title":設定文件切分策略為按標題切分。
  • max_characters=4000:設定每個區塊的最大字元數為4000。
  • new_after_n_chars=3800:在達到3800字元後開始新的區塊。
  • combine_text_under_n_chars=2000:將少於2000字元的文字組合在一起。
  • image_output_dir_path=images_path:指定輸出影像的目錄路徑。

可以透過指令取得PDF圖片的信息,由於透過上面程式碼將圖片儲存到images目錄下面了,所以透過以下程式碼展示圖片。

from IPython.display import Image
Image('images/figure-1-1.jpg')
  • 1.
  • 2.

從PDF 中抽取的圖片訊息從PDF 中抽取的圖片訊息

為圖片資訊產生摘要

下面這段程式碼定義了一個名為ImageSummarizer的類,用於產生圖像內容的摘要資訊。

# 引入所需的库
import base64
import os

# 从langchain包导入ChatOpenAI类和HumanMessage模块
from langchain.chat_models import ChatOpenAI
from langchain.schema.messages import HumanMessage

# 定义图像摘要类
class ImageSummarizer:

    # 初始化函数,设置图像路径
    def __init__(self, image_path) -> None:
        self.image_path = image_path
        self.prompt = """你的任务是将图像内容生成摘要信息,以便检索。
        这些摘要将被嵌入并用于检索原始图像。请给出一个简洁的图像摘要,以便于检索优化。
"""

    # 定义函数将图像转换为base64编码
    def base64_encode_image(self):
        with open(self.image_path, "rb") as image_file:
            return base64.b64encode(image_file.read()).decode("utf-8")

    # 定义摘要函数
    def summarize(self, prompt = None):
        # 获取图像的base64编码数据
        base64_image_data = self.base64_encode_image()
        # 创建ChatOpenAI对象,使用gpt-4-vision预览模型,最大token数为1000
        chat = ChatOpenAI(model="gpt-4-vision-preview", max_tokens=1000)

        # 调用chat对象的invoke方法,发送包含文本和图像的消息
        response = chat.invoke(
            [
                #人类提示语的输入
                HumanMessage(
                    content=[
                        {
                            "type": "text",
                            "text": prompt if prompt else self.prompt
                        },
                        {
                            "type": "image_url",
                            "image_url": {"url": f"data:image/jpeg;base64,{base64_image_data}"},
                        },
                    ]
                )
            ]
        )
        # 返回base64编码的图像数据和响应内容
        return base64_image_data, response.content
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.

以下是程式碼的詳細解釋:

1.導​​入庫

導入base64和os庫,用於影像編碼和作業系統相關功能。

從Langchain套件中匯入ChatOpenAI類別和HumanMessage模組,這些用於與OpenAI的聊天模型互動。

2.定義影像摘要類別ImageSummarizer

__init__方法初始化影像路徑和摘要產生的提示語。

base64_encode_image方法將影像檔案讀取並轉換為base64編碼,此編碼格式適用於在網路上傳輸影像,便於圖片資訊與GPT模型互動。

3. 定義摘要函數summarize

使用base64_encode_image方法取得影像的base64編碼資料。

建立ChatOpenAI對象,指定使用gpt-4-vision-preview模型,最大token數設為1000,用於處理影像摘要任務。

使用chat.invoke方法,向模型發送包含文字提示和圖像資料的訊息。文字提示用於指導模型生成圖像的摘要。

創建summarize函數的目的就是為了給圖片產生摘要,接下來就是呼叫該函數。下面這段程式碼建立影像資料和影像摘要的列表,並遍歷指定路徑下的所有檔案以產生這些資訊。

# 创建图像数据和图像摘要的列表
image_data_list = []
image_summary_list = []

# 遍历指定路径下的所有文件
for img_file in sorted(os.listdir(images_path)):
    # 检查文件扩展名是否为.jpg
    if img_file.endswith(".jpg"):
        # 创建ImageSummarizer对象
        summarizer = ImageSummarizer(os.path.join(images_path, img_file))
        # 调用summarize方法生成摘要
        data, summary = summarizer.summarize()
        # 将base64编码的图像数据添加到列表
        image_data_list.append(data)
        # 将图像摘要添加到列表
        image_summary_list.append(summary)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

代碼解釋:

1. 初始化列表

image_data_list和image_summary_list兩個清單被建立用於儲存影像的base64編碼資料和對應的影像摘要。

2.遍歷指定路徑下的所有文件

使用os.listdir(images_path)列出指定路徑(images_path)下的所有文件,並使用sorted函數對文件名稱進行排序。

3. 檢查和處理每個文件

透過if img_file.endswith(".jpg")檢查檔案副檔名是否為.jpg,以確保只處理圖片檔案。

對於每個.jpg文件,建立ImageSummarizer對象,傳入圖像的完整路徑。

呼叫summarize方法產生該圖像的摘要。

將傳回的base64編碼圖像資料加入image_data_list列表。

將傳回的圖片摘要新增至image_summary_list清單。

最終會產生兩個清單:一個包含了影像檔案的base64編碼數據,另一個包含了相應的影像摘要,這些資訊可以用於影像檢索或其他處理。

我們透過如下程式碼來查看圖片的摘要信息

image_summary_list
  • 1.

輸出結果如下:

['這個圖像包含了三個不同的圖表,提供了有關假日銷售成長、各行業一周和年迄今(YTD)的表現對比的數據。\n\n第一張圖表是一個柱狀圖,標題為“Holiday sales growth is set to return to its pre-pandemic pace”,展示了2010年至2023年間11月和12月銷售同比增長的實際數據和預測數據。藍色柱子代表實際數據,條紋柱子代表預測數據,藍色虛線代表2010-2019年平均成長率,綠色虛線代表2020-2022年平均成長率。\n\n第二個圖表是一個色塊圖,分為兩部分,展示了“V”, “B”, 和“G”在不同的尺度(大、中、小)下的1周和年迄今(YTD)的表現。數值在色塊內以白色文字顯示。\n\n第三個圖表是兩個並列的長條圖,分別顯示了不同產業在1週和年迄今(YTD)的表現。左側的藍色長條圖展示了一周的變化,右側的綠色長條圖展示了年迄今的變化。圖表的Y軸代表了百分比變化,X軸列出了不同的行業。\n\n摘要:三個圖表顯示假日銷售成長預計將恢復至疫情前水平,不同市場尺度(大、中、小)的1週和年迄今表現,以及不同行業的1周和年迄今表現數據。']

從結果可以看出GPT -4- Vision - Preview模型對圖片是能夠識別,並且對一些細節也能夠把控。

識別表格和文字訊息

圖片、表格和文字資訊組成了PDF的完整內容, 在辨識了圖片資訊之後,就需要對表格和文字資訊下手了。

下面這段程式碼定義了處理PDF文檔元素的過程。

# 定义一个基本模型Element,用于存储文档元素的类型和文本内容
class Element(BaseModel):
    type: str
    text: Any

# 创建表格元素和文本元素的空列表
#表格信息
table_elements = []
#文本信息
text_elements = []

# 遍历从PDF中分割出的所有元素
for element in raw_pdf_elements:
    # 判断元素是否为表格:非结构化-文档-元素-表格
    if "unstructured.documents.elements.Table" in str(type(element)):
        # 如果是表格,将其添加到表格元素列表
        table_elements.append(Element(type="table", text=str(element)))
    # 判断元素是否为复合元素
    elif "unstructured.documents.elements.CompositeElement" in str(type(element)):
        # 如果是复合元素,将其添加到文本元素列表
        text_elements.append(Element(type="text", text=str(element)))
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

程式碼解釋如下:

1.定義Element基本模型

使用pydantic的BaseModel定義了一個名為Element的類,用於儲存文件元素的類型(如表格或文字)和文字內容。這個類別使用型別註解str和Any定義了​​兩個屬性:type(元素類型)和text(元素內容)。

2. 建立空白列表以儲存表格和文字元素

table_elements和text_elements分別用於儲存表格資訊和文字資訊。

3. 遍歷PDF中的元素

對於從PDF檔案中分割出的每個元素(在raw_pdf_elements中),程式碼透過判斷元素的類型來分類處理:

如果元素是表格(unstructured.documents.elements.Table),則建立Element對象,並將其類型設為"table",文字內容設為元素的字串表示,然後將此物件新增至table_elements清單。

如果元素是複合元素(unstructured.documents.elements.CompositeElement),則以相似的方式處理,將其類型設為"text",並新增至text_elements清單。

在對PDF文件中的不同元素進行分類和存儲,接下來就需要對其進行處理,對這些表格和文字進行總結。程式碼如下:

# 导入所需模块
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser

# 定义用于生成总结的提示文本
prompt_text = """
  您负责简洁地总结表格或文本块,中文输出。:
  {element}
"""
# 从模板创建提示
prompt = ChatPromptTemplate.from_template(prompt_text)

# 创建总结链,结合提示和GPT-3.5模型
summarize_chain = {"element": lambda x: x} | prompt | ChatOpenAI(temperature=0, model="gpt-3.5-turbo") | StrOutputParser()

# 从表格元素中提取文本
tables = [i.text for i in table_elements]
# 使用总结链批量处理表格文本,设置最大并发数为5
table_summaries = summarize_chain.batch(tables, {"max_concurrency": 5})

# 从文本元素中提取文本
texts = [i.text for i in text_elements]
# 使用总结链批量处理文本,设置最大并发数为5
text_summaries = summarize_chain.batch(texts, {"max_concurrency": 5})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

這段程式碼用於產生表格和文字區塊的總結,利用LangChain和GPT-3.5模型:

1.導​​入模組

導入LangChain的ChatOpenAI、ChatPromptTemplate和StrOutputParser模組。

2.定義產生總結的提示文本

定義了一個用於指導模型總結表格或文字區塊的提示文字範本。

3.創建提示和總結鏈

使用ChatPromptTemplate.from_template根據定義的提示文字建立提示。

建立一個名為summarize_chain的總結鏈,結合了提示、GPT-3.5模型(由於產生總結的能力不需要用到GPT -4 ,為了節省Token使用GPT -3.5 ),和字串輸出解析器(StrOutputParser )。

4.處理表格和文字元素

從table_elements中提取表格文本,使用summarize_chain.batch方法批次處理這些文本,最大並發數設為5。

從text_elements中提取文本塊文本,同樣使用summarize_chain.batch批次處理,最大並發數設為5。

我們將總結的內容列印如下:

表格和文字摘要訊息表格和文字摘要訊息

建立索引器

好了,目前為止我們將PDF中的圖片、表格和文本信息都逐一抽取出來了,並針對這些信息生成了摘要,其目的是方便後續搜索,在做好這一系列準備之後就需要安排上索引器了。索引器顧名思義就是將被搜尋資訊加上索引,方便搜尋之用。

下面這段程式碼建立了一個多向量檢索器,用於儲存和檢索文字、表格和圖像資料:

import uuid  # 导入uuid库,用于生成唯一标识符

# 导入所需的langchain模块
from langchain.embeddings import OpenAIEmbeddings
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.schema.document import Document
from langchain.storage import InMemoryStore
from langchain.vectorstores import Chroma

id_key = "doc_id"  # 设置文档的唯一标识键

# 初始化多向量检索器,初始为空,文字,图片,表格
retriever = MultiVectorRetriever(
    vectorstore=Chroma(collection_name="summaries", embedding_function=OpenAIEmbeddings()),  # 使用OpenAI的嵌入方法
    docstore=InMemoryStore(),  # 使用内存存储文档
    id_key=id_key,  # 设置文档的唯一标识键
)

# 为文本添加唯一标识
doc_ids = [str(uuid.uuid4()) for _ in texts]  # 为每个文本生成一个唯一的UUID
# 创建文本的文档对象并添加到检索器
summary_texts = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})  # 将文本封装为文档对象
    for i, s in enumerate(text_summaries)
]
retriever.vectorstore.add_documents(summary_texts)  # 将文本文档添加到向量存储中
retriever.docstore.mset(list(zip(doc_ids, texts)))  # 将文本文档的ID和内容存储在内存存储中

# 为表格添加唯一标识
table_ids = [str(uuid.uuid4()) for _ in tables]  # 为每个表格生成一个唯一的UUID
# 创建表格的文档对象并添加到检索器
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)))  # 将表格文档的ID和内容存储在内存存储中

# 为图像添加唯一标识
doc_ids = [str(uuid.uuid4()) for _ in image_data_list]  # 为每个图像生成一个唯一的UUID
# 创建图像的文档对象并添加到检索器
summary_images = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})  # 将图像封装为文档对象
    for i, s in enumerate(image_summary_list)
]
retriever.vectorstore.add_documents(summary_images)  # 将图像文档添加到向量存储中
retriever.docstore.mset(list(zip(doc_ids, image_data_list)))  # 将图像文档的ID和内容存储在内存存储中
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.

代碼解釋:

1. 初始化多向量檢索器

使用Chroma作為向量存儲,它利用OpenAI嵌入方法將文件轉換為向量形式。

使用InMemoryStore作為文檔存儲,這是一種將文檔存儲在內存中的方法。

設定id_key為文件的唯一識別鍵。

2. 處理文字數據

為每個文字建立唯一識別碼(UUID)。

將文字總結封裝為Document對象,並加入到向量儲存和文件儲存中。

3. 處理表格數據

類似地,為每個表格建立UUID。

將表格總結封裝為Document對象,並新增至向量儲存和文件儲存。

4. 處理影像數據

為每個影像建立UUID。

將影像總結封裝為Document對象,並新增至向量儲存和文件儲存。

這段程式碼建立了一個系統,可以儲存和檢索多種類型的文檔數據,包括文字、表格和圖像,利用唯一識別碼來管理每個文件。這對於處理和組織大量複雜資料非常有用。

產生多模態查詢提示語

下面這段程式碼包含多個函數,用來處理和展示圖像與文字資料。

from PIL import Image
from IPython.display import HTML, display
import io
import re

# 显示基于base64编码的图片
def plt_img_base64(img_base64):
    display(HTML(f''))

# 检查base64数据是否为图片
def is_image_data(b64data):
    """
    通过查看数据开头来检查base64数据是否为图片
    """
    image_signatures = {
        b"\xFF\xD8\xFF": "jpg",
        b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A": "png",
        b"\x47\x49\x46\x38": "gif",
        b"\x52\x49\x46\x46": "webp",
    }
    try:
        header = base64.b64decode(b64data)[:8]  # 解码并获取前8个字节
        for sig, format in image_signatures.items():
            if header.startswith(sig):
                return True
        return False
    except Exception:
        return False

# 分离base64编码的图片和文本
def split_image_text_types(docs):
    """
    分离base64编码的图片和文本
    """
    b64_images = []
    texts = []
    for doc in docs:
        # 检查文档是否为Document类型并提取page_content
        if isinstance(doc, Document):
            doc = doc.page_content
        #是图片
        if is_image_data(doc):
            b64_images.append(doc)
        else:
            texts.append(doc)
    return {"images": b64_images, "texts": texts}

# 根据数据生成提示信息
def img_prompt_func(data_dict):
    messages = []

    # 如果存在图片,则添加到消息中
    #图片的信息单独拿出来, 需要和提示语一起传给大模型的。
    if data_dict["context"]["images"]:
        for image in data_dict["context"]["images"]:
            image_message = {
                "type": "image_url",
                "image_url": {"url": f"data:image/jpeg;base64,{image}"},
            }
            messages.append(image_message)

    # 添加文本到消息
    formatted_texts = "\n".join(data_dict["context"]["texts"])
    text_message = {
        "type": "text",
        "text": (
            "You are financial analyst.\n"
            "You will be given a mixed of text, tables, and image(s) usually of charts or graphs.\n"
            "Use this information to answer the user question in the finance. \n"
            f"Question: {data_dict['question']}\n\n"
            "Text and / or tables:\n"
            f"{formatted_texts}"
        ),
    }
    messages.append(text_message)
    print(messages)
    return [HumanMessage(content=messages)]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.

代碼解釋:

1. 顯示基於base64編碼的圖片(plt_img_base64)

這個函數的目的是顯示base64編碼的圖片。

2. 檢查base64資料是否為圖片(is_image_data)

透過檢查base64編碼資料的開頭是否符合常見影像格式(如JPEG, PNG, GIF, WEBP)的簽章來判斷資料是否為圖片。

3. 分離base64編碼的圖片和文字(split_image_text_types)

此函數遍歷文件集合,使用is_image_data函數來區分哪些是圖像數據,哪些是文本,然後將它們分別儲存在兩個列表中。

4. 根據資料產生提示訊息(img_prompt_func)

此函數產生用於向LangChain的大模型傳遞的訊息。對於每個圖像,建立包含圖像URL的訊息;對於文字數據,建立包含提示文字和問題的訊息。

需要對"text"進行特別說明,它定義訊息的具體文字內容。

首先說明了使用者角色:"You are financial analyst."(你是財務分析師)。

緊接著說明任務類型和資料格式:"You will be given a mixed of text, tables, and image(s) usually of charts or graphs."(你將會得到包含文字、表格和圖像(通常是圖表或圖形)的混合數據)。

提示使用這些資訊回答財務問題:"Use this information to answer the user question in the finance."(使用這些資訊來回答財務方面的使用者問題)。

{data_dict['question']}:這裡{}內的部分是Python字串格式化的佔位符,用於插入變數data_dict中的'question'鍵所對應的值,即使用者提出的問題。

"Text and / or tables:\n":提示文本,說明接下來的內容是文字和/或表格。

f"{formatted_texts}":再次使用字串格式化將變數formatted_texts(格式化後的文字資料)插入到訊息中。

實現金融分析查詢

萬事俱備只欠東風,現在開始利用GPT -4- Vision - Preview模型進行推理了, 我們嘗試對索引器提問,看看結果如何。

下面這段程式碼實作了一個基於RAG(Retrieval Augmented Generation)模型的查詢處理管​​道。

from langchain.schema.runnable import RunnableLambda, RunnablePassthrough
# 创建ChatOpenAI模型实例
model = ChatOpenAI(temperature=0, model="gpt-4-vision-preview", max_tokens=1024)

# 构建RAG(Retrieval Augmented Generation)管道
chain = (
    {
        "context": retriever | RunnableLambda(split_image_text_types), # 使用检索器获取内容并分离图片和文本
        "question": RunnablePassthrough(), # 传递问题
    }
    | RunnableLambda(img_prompt_func) # 根据图片和文本生成提示信息
    | model # 使用ChatOpenAI模型生成回答
    | StrOutputParser() # 解析模型的字符串输出
)

# 定义查询问题
query = "Which year had the highest holiday sales growth?"

# 调用chain的invoke方法执行查询
chain.invoke(query)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

代碼解釋:

1.建立ChatOpenAI模型實例

使用ChatOpenAI類別建立模型實例,配置了溫度參數為0、使用的模型為gpt-4-vision-preview,最大token數設為1024。

2.建構RAG管道(chain)

retriever | RunnableLambda(split_image_text_types):先使用retriever檢索內容,然後透過RunnableLambda呼叫split_image_text_types函數分離圖片和文字。

RunnablePassthrough():用於直接傳遞查詢問題。

RunnableLambda(img_prompt_func):根據檢索到的圖片和文字產生提示資訊。

model:使用配置好的ChatOpenAI模型根據產生的提示資訊產生答案。

StrOutputParser():解析模型輸出的字串。

3. 定義查詢問題(query)

定義了一個字串query作為查詢問題,例如:「Which year had the highest holiday sales growth?」(哪一年的假期銷售成長最高?)。

4. 執行查詢(chain.invoke(query))

呼叫chain的invoke方法執行查詢,根據問題和檢索到的信息產生答案。

總的來說,這個程式碼片段建立了一個複雜的查詢處理流程,結合了內容檢索、資料分離、提示產生和模型回答,用於處理和回答基於文字和圖像資料的複雜問題。

來查看輸出的結果:

根據提供的圖表,假期銷售成長最高的年份是 2021 年,成長率為 12.7%。這由條形圖中「實際」部分下最高的長條表示,它代表 11 月和 12 月銷售額的實際同比增長率。

我們將其翻譯成中文如下:

根據提供的圖表,2021年的假期銷售成長率最高,為12.7%。這一點從「實心」部分的長條圖中最高的柱子可以看出,該柱子代表了11月和12月的年度同比增長率。

PDF中圖表部分與大模型回應對比PDF中圖表部分與大模型回應對比

透過大模型的回應,再對比PDF中描述假日銷售成長率的長條圖,可以明顯發現2 021年的成長率是最高的,為1 2.7% ,對於柱子「實心」的描述也是正確的。看來OpenAI提供的多模態功能起到了作用。

總結

文章透過分析和實例演示,清楚地展示了OpenAI的最新技術在多模態資料處理方面的潛力和應用前景。透過對unstructured函式庫、Poppler工具和Tesseract-OCR工具的細緻介紹和程式碼實現的解讀,文章不僅為讀者提供了理論知識,也指導如何實際操作和應用這些工具。文章的結論強調了gpt-4-vision-preview模型在提高分析效率和減少人工錯誤方面的價值。