偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器! 原創(chuàng)

發(fā)布于 2025-8-14 07:58
瀏覽
0收藏

本文介紹ColPali與DocLayNet結合的多模態(tài)RAG系統(tǒng),通過視覺語言建模理解文檔中的表格、圖表等布局信息,顯著提升復雜文檔問答的準確性和上下文感知能力。

簡介

檢索增強生成(RAG)已成為構建開放領域和特定領域問答系統(tǒng)的標準范例。傳統(tǒng)意義上,RAG流程嚴重依賴于基于文本的檢索器,這些檢索器使用密集或稀疏嵌入來索引和檢索段落。雖然這些方法對于純文本內容有效,但在處理視覺復雜的文檔(例如科學論文、財務報告或掃描的PDF)時,往往會遇到困難,因為這些文檔中的關鍵信息嵌入在表格、圖形或結構化布局中,而這些布局無法很好地轉換為純文本。

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

論文插圖:與傳統(tǒng)方法相比,ColPali簡化了文檔檢索流程,同時提供了更高的性能和更低的延遲(來源:??arxiv??)?

為了突破這些限制,Manuel Faysse等人近期的研究成果提出了ColPALI(ICLR 2025),這是一個視覺語言檢索框架,它使用類似ColBERT的視覺嵌入后期交互,基于圖像理解來檢索文檔內容。與此同時,Pfitzmann等人提出了Yolo DocLayNet(CVPR 2025),這是一個快速且布局感知的對象檢測模型,專門用于以高精度和高效率提取文檔組件,例如表格、圖表和章節(jié)標題。

在本文中,我將指導你完成混合RAG管道的實際實現,該管道結合了ColPALI和DocLayout-YOLO,以實現對閱讀和查看的文檔的問答。

RAG系統(tǒng)中的視覺盲點

盡管在處理文本查詢方面取得了成功,但大多數RAG系統(tǒng)都忽略了一個關鍵問題。它們幾乎忽略了表格、圖表和圖形等視覺元素,而這些元素在許多實際文檔中都承載著至關重要的意義。讓我們來看看下圖。

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

圖片來自:SLB 2023年年度報告的摘錄(資料來源:??報告??)?

通過應用常見的OCR工具從表格中提取文本,我們可以得到以下結果。

Option Awards Stock Awards
 Name
 Option/ 
PSU/RSU 
...
...
... (truncated)
Shares, Units, or 
Other Rights That 
Have Not Vested 
($)(1)
 D. Ralston 1/20/2021 67,220(2) 3,498,129
 1/20/2021 33,610(3) 1,749,064
 2/3/2021 29,390(4) 1,529,456
 1/19/2022 64,587(5) 3,361,107
 1/19/2022 22,321(6) 1,161,585
 1/18/2023 41,668(7) 2,168,403
 1/18/2023 14,427(8) 750,781

顯然,OCR結果無法捕捉多層級標題結構和列分組,導致文本呈現扁平的線性,數值與其對應指標之間的關聯(lián)性缺失。這使得數據所屬類別(例如授予日期與股票獎勵)難以識別,從而降低了提取數據對下游分析的實用性。

用戶查詢:What is the market value of unearned shares, units, or other rights that have not vested for the 1/20/2021 grant date?(2021年1月20日授予日尚未歸屬的未賺取股份、單位或其他權利的市場價值是多少?)

RAG回應:The market value of unearned shares, units, or other rights that have not vested for the 1/20/2021 grant date is $1,749,064.(2021年1月20日授予日尚未歸屬的未賺取股份、單位或其他權利的市場價值為1,749,064美元。)

由于提取的信息缺乏結構和上下文,因此產生的RAG響應不準確,因為它無法可靠地將值與原始表中的預期含義關聯(lián)起來。

讓我們探討另一個例子來進一步說明傳統(tǒng)RAG系統(tǒng)的局限性。

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

圖片來源:SLB 2023年年度報告中的數據圖表片段(來源:??報告??)?

這是從上圖中提取的OCR結果。

Total Shareholder Return
 Indexed ($100)
 $50.0 \n$40.0 \n$30.0 \n$20.0 \n$10.0 \n$0.0-$10.0-$20.0
 2020 2021 2023 2022
 CEO CAP Avg. NEO CAP SLB TSR-$10.6
 $2.2 \n$32.4 \n$13.1 \n$39.4 \n$26.2 \n$17.3 \n$9.3 
 ...
 ...
 ... (truncated)
 $40.00 \n$20.00 \n$0.00
 OSX TSR
 CAP vs. Total Shareholder Return
 (SLB and OSX)
 (in millions of US dollars)
 Compensation Actually Paid

易知,該圖表的OCR輸出也缺乏結構一致性,未能捕捉數據點之間的關系,例如哪些條形或標簽對應特定年份或股東回報率線。此外,它還未能將數值與其視覺元素進行對齊,導致難以區(qū)分每年的CEO CAP、NEO CAP和TSR值。

問題:What was the SLB Total Shareholder Return (TSR) in 2022?(2022年SLB總股東回報率(TSR)是多少?)

RAG回應:The SLB Total Shareholder Return (TSR) in 2022 was $134.09.(SLB2022年的總股東回報(TSR)為134.09美元。)

與前面的示例一樣,由于OCR提取的數據中結構和上下文的丟失,此處的RAG響應也不準確。

多模態(tài)RAG架構

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

作者插圖:我們實驗中的多模態(tài)RAG架構

該架構由兩個主要組件組成:索引管道和聊天推理管道。在索引階段,文檔語料庫通過兩條并行路徑進行處理。第一條路徑使用YOLO-DocLayNet檢測器識別表格和圖形等視覺元素,然后使用ColPALI圖像編碼器將其嵌入并存儲在圖像向量集合中。第二條路徑使用PyTesseractOCR從文檔中提取原始文本,然后使用Mxbai-Embed模型對其進行編碼,并保存在同一向量數據庫中的文本向量集合中。

在聊天推理過程中,用戶查詢會同時由用于Mxbai-Embed文本檢索的編碼器和用于視覺語言檢索的ColPALI編碼器進行編碼。然后,系統(tǒng)會針對各自的向量集合執(zhí)行雙重檢索(文本到文本和文本到圖像)。檢索到的文本和圖像區(qū)域會被轉發(fā)到多模態(tài)LLM(LLaMA-4),該模型會綜合兩種模態(tài),生成上下文感知且準確的響應。這種設計將文本理解與細粒度的視覺推理相結合,從而實現強大的文檔質量保證(QA)。

我的測試設置

A. 環(huán)境設置

為了高效運行完整的多模式RAG管道,我使用單個NVIDIA RTX A6000 GPU和48GB的VRAM,這為運行ColPALI、YOLO模型和句子嵌入模型提供了足夠的內存。

對于軟件環(huán)境,我建議使用Miniconda來隔離你的依賴關系并確保可重復性。

1.創(chuàng)建Conda環(huán)境

conda create -n multimodal_rag pythnotallow=3.11 
conda activate multimodal_rag

2. 準備requirements.txt

ultralytics 
git+https://github.com/illuin-tech/colpali 
groq 
pill 
pymilvus 
sentence-transformers 
uvicorn 
fastapi 
opencv-python 
pytesseract 
PyMuPDF 
pydantic 
chainlit 
pybase64 
huggingface_hub[hf_transfer]

3.安裝Python依賴項

pip install -r requirements.txt

B.預訓練模型設置

要復現此實驗,你需要下載檢索和布局提取流程中使用的三個預訓練模型。所有模型都將存儲在該pretrained_models/目錄下,以保持一致性并更易于加載。

下面是使用Hugging Face CLI下載它們的命令hf transfer,該命令針對更快的下載速度進行了優(yōu)化:

# 下載YOLO DocLayNet用于布局檢測
HF_HUB_ENABLE_HF_TRANSFER=1 hf download hantian/yolo-doclaynet --local-dir pretrained_models/yolo-doclaynet

# 下載 ColQwen 2.5(用于 ColPALI)用于基于圖像的檢索
HF_HUB_ENABLE_HF_TRANSFER=1 hf download vidore/colqwen2.5-v0.2 --local-dir pretrained_models/colqwen2.5-v0.2

# 下載 Mixedbread Embed Large 模型用于基于文本的檢索
HF_HUB_ENABLE_HF_TRANSFER=1 hf download mixedbread-ai/mxbai-embed-large-v1 --local-dir pretrained_models/mxbai-embed-large-v1

C. 代碼設置和初始化

import torch 
from Typing import Cast 
from Ultralytics import YOLO 
from transforms.utils.import_utils import is_flash_attn_2_available 
from colpali_engine.models.paligemma.colpali.processing_colpali import ColPaliProcessor 
from colpali_engine.models import ColQwen2_5, ColQwen2_5_Processor 
from sentence_transformers import SentenceTransformer 

# 定義設備
device = "cuda" if torch.cuda.is_available() else "cpu" 

# 定義知識庫源和目標圖像目錄
document_source_dir = "document_sources"
 img_dir = "image_database"
 os.makedirs(img_dir, exist_ok= True ) # 確保目錄存在

# YOLO-12L-Doclaynet
 yolo_model = YOLO( "pretrained_models/yolo-doclaynet/yolov12l-doclaynet.pt" ) 
yolo_model = yolo_model.to(device) 

# ColQwen2.5-Colpali
 colpali_model = ColQwen2_5.from_pretrained( 
 "pretrained_models/colqwen2.5-v0.2" , 
 torch_dtype=torch.bfloat16, 
 device_map=device, # 或 "mps" 如果在 Apple Silicon 上
 attn_implementatinotallow= "flash_attention_2" 如果is_flash_attn_2_available() else None , 
 )。eval () 
colpali_processor = ColQwen2_5_Processor.from_pretrained( "pretrained_models/colqwen2.5-v0.2" ) 
processor = cast( 
 ColPaliProcessor, 
 colpali_processor) 

# Mxbai-embed-large-v1
 embed_model = SentenceTransformer( "pretrained_models/mxbai-embed-large-v1" ,device=device) 

# 定義實體顏色
ENTITIES_COLORS = { 
 "Picture" : ( 255 , 72 , 88 ), 
 "Table" : ( 128 , 0 , 128 ) 
} 

print ( "FINISH SETUP..." )

上述代碼初始化了多模態(tài)檢索系統(tǒng)的核心組件。它設置了設備(GPU或CPU),確保圖像輸出目錄存在,并加載了三個預訓練模型:YOLOv12L-DocLayNet模型(用于檢測表格和圖形等布局元素)、ColQwen2.5模型(其ColPALI處理器用于對裁剪圖像區(qū)域進行視覺語言嵌入)以及mxbai-embed-large-v1模型(使用SentenceTransformers嵌入文本)。此外,它還定義了檢測到的實體類型的顏色映射,以支持預處理過程中的可視化。

索引管道

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

作者插圖:索引管道

索引管道負責將原始文檔轉換為結構化的、可搜索的文本和視覺表示形式。在本節(jié)中,我們將逐步講解實際實現過程,展示如何使用代碼處理、編碼和存儲文檔內容。

A. 準備

在繼續(xù)開發(fā)之前,讓我們準備一些對分析有用的處理函數。

import matplotlib.pyplot as plt 
import cv2 

def display_img_array ( img_arr ): 
 image_rgb = cv2.cvtColor(img_arr, cv2.COLOR_BGR2RGB) 
 plt.figure(figsize=( 30 , 30 )) 
 plt.imshow(image_rgb) 
 plt.axis( 'off' ) 
 plt.show() 

def show_layout_detection ( detection_results, img_arr ): 
 for result indetection_results : 
 boxes = result.boxes # 獲取檢測框
 for box in boxes: 
 x, y, w, h = box.xywh[ 0 ] # 框坐標(中心 x, y, 寬度, 高度)
 x, y, w, h = int (x), int (y), int (w), int (h) # 轉換為整數
 conf = box.conf.item() # 置信度得分
 cls = int (box.cls.item()) # 類 ID
 label = f" {yolo_model.model.names[cls]} {conf: .2 f} "
 color = ENTITIES_COLORS[yolo_model.model.names[cls]] # 獲取此類的顏色
 top_left = (x - w // 2 , y - h // 2 ) 
 bottom_right = (x + w // 2 , y + h // 2 ) # 特定類的彩色框
 cv2.rectangle(img_arr, top_left, bottom_right, color, 2 ) 
 cv2.putText(img_arr, label, (top_left[ 0 ], top_left[ 1 ] - 10 ), cv2.FONT_HERSHEY_SIMPLEX, 0.9 , color, 2 ) # 匹配文本顏色
 display_img_array(img_arr)

上面的兩個輔助函數將用于可視化布局檢測。其中,display_img_array將BGR圖像轉換為RGB并使用matplotlib顯示它;同時,show_layout_detection使用YOLO檢測結果和特定于類的顏色在檢測到的布局元素(例如表格、圖形)上疊加邊界框和標簽。

接下來,讓我們準備要加載的PDF中的特定頁面,如下所示。

import fitz # PyMuPDF
import numpy as np
import cv2
import os

# 定義文件名和頁面
page_id = 38
 filename = "SLB-2023-Annual-Report.pdf" 

# 閱讀文檔
doc = fitz. open (os.path.join(document_source_dir,filename)) 
page = doc.load_page(page_id) 
pix = page.get_pixmap(dpi= 300 ) 
img_rgb = np.frombuffer(pix.samples, dtype=np.uint8).reshape((pix.height, pix.width, pix.n)) 
img_page = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)

此代碼使用PyMuPDF從PDF文件加載特定頁面,以高分辨率呈現,并將其轉換為適合OpenCV處理的BGR圖像數組。

B.布局檢測

現在,讓我們編寫如下布局檢測代碼。

def layout_detection(img_doc):
 return yolo_model.predict(
 source=img_doc, 
 classes=[6,8],
 cnotallow=0.25, 
 iou=0.45)

layout_results = layout_detection(img_page)
show_layout_detection(layout_results, img_page)

在此步驟中,我們專門過濾檢測到的布局元素,使其僅包含與嵌入的相關視覺區(qū)域相對應的類標簽6(表格)和8(圖形)。

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

圖片來源:SLB 2023年年度報告第37頁(來源:??報告??)?

在這里,我們成功定位了相關的視覺對象,例如圓形圖和表格,這些將用于下游的視覺嵌入。

C. 提取并保存

接下來,讓我們從圖像中檢索本地化的表格和圖片區(qū)域,并用白色遮罩它們以將它們從原始頁面視圖中刪除。

def extract_and_masking_images(img_doc, layout_results):
 height, width, _ = img_doc.shape
 extracted_imgs = []
 for box in layout_results[0].boxes:
 x, y, w, h = map(int,box.xywh[0]) #矩形坐標(中心x、y、寬度、高度)
 # 計算左上角(x_min, y_min)
 x_min = x - w // 2
 y_min = y - h // 2
 x_max = x_min + w
 y_max = y_min + h
 # 將坐標夾緊到圖像邊界
 x_start = max(0, x_min)
 y_start = max(0, y_min)
 x_end = min(width, x_max)
 y_end = min(height, y_max)
 # 如果區(qū)域無效,則跳過
 if x_start >= x_end or y_start >= y_end:
 continue
 # 將圖像提取到extracted_imgs數組中
 extracted_imgs.append(img_doc[y_start:y_end, x_start:x_end].copy())
 #將區(qū)域設置為白色
 img_doc[y_start:y_end, x_start:x_end] = [255, 255, 255]
 return extracted_imgs, img_doc

extracted_imgs, img_page = extract_and_masking_images(img_page, layout_results)
display_img_array(img_page)

此函數從文檔圖像中提取檢測到的表格和圖片區(qū)域,并用白色遮罩這些區(qū)域,返回裁剪后的視覺效果和更新后的圖像。更新后的頁面圖像如下所示。

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

圖片來源:SLB 2023年年度報告第37頁(來源:??報告??)?

接下來,讓我們使用如下代碼保存提取的圖形。

import os
import cv2

def save_img_files(extracted_imgs, filename, page_id):
 #目標路徑
 save_path = os.path.join(img_dir, filename, f"page_{page_id}/")
 # 確保目錄存在
 os.makedirs(os.path.dirname(save_path), exist_ok=True)
 # 保存圖像
 for i in range(len(extracted_imgs)):
 cv2.imwrite(save_path+f"fig_{i}.jpg", extracted_imgs[i])

save_img_files(extracted_imgs, filename, page_id)

此代碼將提取的圖形和表格圖像保存到指定的本地目標目錄中img_dir。

D.文本OCR

在此步驟中,讓我們使用帶有pytesseract的普通OCR從屏蔽文檔圖像中提取剩余的文本信息。

import pytesseract

text = pytesseract.image_to_string(img_page)
print(text)

結果如下:

Short-Term Cash Incentive Awards

We pay performance-based short-term (annual) cash incentives to
our executives to foster a results-driven, pay-for-performance culture,
and to align executives’ interests with those of our shareholders. STI
awards are earned according to the achievement of quantitative
Company financial and non-financial objectives, as well as strategic
objectives. Our Compensation Committee selects performance
measures that it believes support our strategy and strike a balance
between motivating our executives to increase near-term financial and
operating results and driving profitable long-term Company growth
and value for shareholders.

2022 STI Opportunity Mix

Compensation Discussion and Analysis

For 2023, 70% of our NEOs’ target STI opportunity was based on
achieving quantitative Company financial objectives, 10% was based
on achieving quantitative Company non-financial objectives, and 20%
was based on strategic personal objectives. The financial portion of
the target plan was evenly split between adjusted EBITDA and free
cash flow performance goals. The total maximum STI payout for 2023
was 200% of target—consistent with 2022—and the weighted payout
range for each metric as a percentage of target is reflected by the outer
bars in the 2023 STI Opportunity Mix chart below.

2023 STI Opportunity Mix

?>

?>

In January 2023, our Compensation Committee determined to leave the target STI opportunity for all NEOs unchanged from 2022, following
a review of market data indicating that our NEOs’ target STI opportunity (as a percentage of base salary) was competitively positioned. As a
result, the 2023 target STI opportunity for our CEO was 150% of his base salary and for our other NEOs it was 100% of base salary.

The following table reflects our NEOs’ full-year 2023 STI results, together with relevant weightings of the different components and payouts

under each component.

(1) Equals the sum of the financial, non-financial, and personal portions of the STI achieved, shown as a percentage of base salary.
(2) In January 2024, due to factors not contemplated in the 2023 forecast, our Compensation Committee applied a discretionary downward
adjustment to reduce all executive payouts by 5% under our 2023 STI plan.

2024 Proxy Statement

E. 使用Milvus DB Client建立索引

在此步驟中,我們將使用Milvus數據庫進行向量存儲和檢索;我們選擇使用文件milvus_file.db的簡單本地實例來進行此實驗,而不是使用可擴展的生產級設置。

1.Retriever類

讓我們定義兩個檢索器:用于細粒度基于圖像的檢索的ColBERT樣式檢索器和用于基于文本的檢索的基本密集檢索器。

from pymilvus import MilvusClient, DataType
import numpy as np
import concurrent.futures
import os
import base64

class MilvusColbertRetriever:
 def __init__(self, milvus_client, collection_name, img_dir, dim=128):
 #使用Milvus客戶端、集合名稱和向量嵌入的維度初始化檢索器。
 # If the collection exists, load it.
 self.collection_name = collection_name
 self.client = milvus_client
 if self.client.has_collection(collection_name=self.collection_name):
 self.client.load_collection(collection_name)
 self.dim = dim
 self.img_dir = img_dir

 def create_collection(self):
 # 在Milvus中創(chuàng)建一個新的集合來存儲嵌入。
 #如果現有集合已存在,請刪除該集合,并為該集合定義架構。
 if self.client.has_collection(collection_name=self.collection_name):
 self.client.drop_collection(collection_name=self.collection_name)
 schema = self.client.create_schema(auto_id=True, enable_dynamic_field=True)
 schema.add_field(field_name="pk", datatype=DataType.INT64, is_primary=True)
 schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=self.dim)
 schema.add_field(field_name="seq_id", datatype=DataType.INT16)
 schema.add_field(field_name="doc_id", datatype=DataType.INT64)
 schema.add_field(field_name="doc", datatype=DataType.VARCHAR, max_length=65535)
 self.client.create_collection(collection_name=self.collection_name, schema=schema)

 def create_index(self):
 # 在向量字段上創(chuàng)建索引,以實現快速相似性搜索。
 # 在使用指定參數創(chuàng)建新索引之前,釋放并刪除任何現有索引。
 self.client.release_collection(collection_name=self.collection_name)
 self.client.drop_index(collection_name=self.collection_name, index_name="vector")
 index_params = self.client.prepare_index_params()
 index_params.add_index(
 field_name="vector",
 index_name="vector_index",
 index_type="IVF_FLAT", 
 metric_type="IP", 
 )
 self.client.create_index(collection_name=self.collection_name, index_params=index_params, sync=True)

 def search(self, data, topk):
 # 對集合執(zhí)行向量搜索,以找到前k個最相似的文檔。
 search_params = {"metric_type": "IP", "params": {}}
 results = self.client.search(
 self.collection_name,
 data,
 limit=int(50),
 output_fields=["vector", "seq_id", "doc_id","$meta"],
 search_params=search_params,
 )
 doc_meta = {}
 for r_id in range(len(results)):
 for r in range(len(results[r_id])):
 entity = results[r_id][r]["entity"]
 doc_id = entity["doc_id"]
 if doc_id not in doc_meta:
 doc_meta[doc_id] = {
 "page_id": entity["page_id"],
 "fig_id": entity["fig_id"],
 "filename": entity["filename"],
 }
 scores = []

 def rerank_single_doc(doc_id, data, client, collection_name):
 #通過檢索單個文檔的嵌入并計算其與查詢的相似度來對其重新排序。
 doc_colbert_vecs = client.query(
 collection_name=collection_name,
 filter=f"doc_id in [{doc_id}]",
 output_fields=["seq_id", "vector", "doc"],
 limit=1000,
 )
 doc_vecs = np.vstack(
 [doc_colbert_vecs[i]["vector"] for i in range(len(doc_colbert_vecs))]
 )
 score = np.dot(data, doc_vecs.T).max(1).sum()
 return (score, doc_id)

 with concurrent.futures.ThreadPoolExecutor(max_workers=300) as executor:
 futures = {
 executor.submit(
 rerank_single_doc, doc_id, data, self.client, self.collection_name
 ): doc_id
 for doc_id in doc_meta.keys()
 }
 for future in concurrent.futures.as_completed(futures):
 score, doc_id = future.result()
 meta = doc_meta[doc_id]
 img_path = os.path.join(self.img_dir, meta["filename"], f"page_{meta['page_id']}", f"fig_{meta['fig_id']}.jpg")
 with open(img_path, "rb") as f:
 img_base64 = base64.b64encode(f.read()).decode('utf-8')
 scores.append({
 "score":float(score), 
 "page_id": meta["page_id"],
 "fig_id": meta["fig_id"],
 "filename": meta["filename"],
 "content": img_base64})

 scores.sort(key=lambda x: x["score"], reverse=True)
 if len(scores) >= topk:
 return scores[:topk]
 else:
 return scores

 def insert(self, data):
 # 將文檔的ColBERT嵌入和元數據插入集合中。
 #將數據作為多個向量(每個序列一個)與相應的元數據一起插入。
 colbert_vecs = [vec for vec in data["colbert_vecs"]]
 seq_length = len(colbert_vecs)
 self.client.insert(
 self.collection_name,
 [
 {
 "vector": colbert_vecs[i],
 "seq_id": i,
 "doc_id": data["doc_id"] ,
 "doc": "",
 "page_id": data["page_id"],
 "fig_id": data["fig_id"],
 "filename": data["filename"],
 }
 for i in range(seq_length)
 ],
 )

class MilvusBasicRetriever:
 def __init__(self, milvus_client, collection_name, dim=1024):
 # 使用Milvus客戶端、集合名稱和向量嵌入的維度初始化檢索器。
 #如果集合存在,則加載之。
 self.collection_name = collection_name
 self.client = milvus_client
 if self.client.has_collection(collection_name=self.collection_name):
 self.client.load_collection(collection_name)
 self.dim = dim

 def normalize(self, vec):
 #把向量規(guī)范化
 norm = np.linalg.norm(vec)
 if norm == 0:
 return vec
 return vec / norm

 def create_collection(self):
 # 在Milvus中創(chuàng)建一個新的集合來存儲嵌入。
 #如果現有集合已存在,請刪除該集合,并為該集合定義架構。
 if self.client.has_collection(collection_name=self.collection_name):
 self.client.drop_collection(collection_name=self.collection_name)
 schema = self.client.create_schema(auto_id=True, enable_dynamic_field=True)
 schema.add_field(field_name="pk", datatype=DataType.INT64, is_primary=True)
 schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=self.dim)
 schema.add_field(field_name="content", datatype=DataType.VARCHAR, max_length=65535)
 self.client.create_collection(collection_name=self.collection_name, schema=schema)

 def create_index(self):
 #在向量字段上創(chuàng)建索引,以實現快速相似性搜索。
 # 在使用指定參數創(chuàng)建新索引之前,釋放并刪除任何現有索引。
 self.client.release_collection(collection_name=self.collection_name)
 self.client.drop_index(collection_name=self.collection_name, index_name="vector")
 index_params = self.client.prepare_index_params()
 index_params.add_index(
 field_name="vector",
 index_name="vector_index",
 index_type="IVF_FLAT", # or any other index type you want
 metric_type="IP", # or the appropriate metric type
 )
 self.client.create_index(collection_name=self.collection_name, index_params=index_params, sync=True)

 def search(self, data, topk):
 #對集合執(zhí)行向量搜索,以找到前k個最相似的文檔。
 normalized_data = self.normalize(data)
 search_params = {"metric_type": "IP", "params": {}}
 results = self.client.search(
 self.collection_name,
 [normalized_data],
 limit=topk,
 output_fields=["vector", "content","$meta"],
 search_params=search_params,
 )
 return_arr = []
 for hit in results[0]:
 return_arr.append({
 "score":hit.distance,
 "page_id":hit["entity"]["page_id"],
 "filename":hit["entity"]["filename"],
 "content":hit["entity"]["content"]
 })
 return return_arr

 def insert(self, data):
 data["vector"] = self.normalize(np.array(data["vector"])).tolist()
 self.client.insert(
 self.collection_name,
 [data]
 )

這段代碼定義了兩個與Milvus交互的檢索器類,以支持混合檢索:

  • MilvusColbertRetriever專為使用ColBERT樣式的多向量嵌入進行基于圖像的檢索而設計。它支持插入來自ColPALI的塊級視覺嵌入,使用后期交互(MaxSim)對結果進行重新排序,并返回最匹配的圖像區(qū)域及其元數據和base64編碼的內容。?
  • MilvusBasicRetriever用于基于文本的檢索。它存儲并搜索來自句子嵌入的單個密集向量,對余弦相似度進行歸一化(通過內積),并檢索最相關的文本塊及其源元數據。?

兩種檢索器均可處理集合創(chuàng)建、索引和插入,從而實現對視覺和文本文檔內容的靈活的多模式檢索。

2. Retriever設置

使用上面定義的檢索器類,讓我們初始化ColBERT風格的圖像檢索器和基本文本檢索器,并將它們安裝在由milvus_file.db支持的本地Milvus實例上,以進行存儲和檢索。

client = MilvusClient("milvus_file.db")
colbert_retriever = MilvusColbertRetriever(collection_name="colbert", milvus_client=client,img_dir=img_dir)
basic_retriever = MilvusBasicRetriever(collection_name="basic", milvus_client=client)

對于初始化步驟,我們必須為兩個檢索器創(chuàng)建集合和索引,如下所示。

colbert_retriever.create_collection() 
colbert_retriever.create_index() 
basic_retriever.create_collection() 
basic_retriever.create_index()

3.圖像數據加載器

讓我們將使用ColPALI模型的圖像嵌入過程包裝到數據加載函數中。

from colpali_engine.utils.torch_utils import ListDataset
from torch.utils.data import DataLoader
from typing import List
from tqdm import tqdm
from PIL import Image

def create_image_embedding_loader(extracted_imgs):
 images = [Image.fromarray(img_arr) for img_arr in extracted_imgs]
 dataloader_images = DataLoader(
 dataset=ListDataset[str](images),
 batch_size=1,
 shuffle=False,
 collate_fn=lambda x: processor.process_images(x),
 )
 ds: List[torch.Tensor] = []
 for batch_doc in tqdm(dataloader_images):
 with torch.no_grad():
 batch_doc = {k: v.to(colpali_model.device) for k, v in batch_doc.items()}
 embeddings_doc = colpali_model(**batch_doc)
 ds.extend(list(torch.unbind(embeddings_doc.to("cpu"))))
 return ds

embedding_loader = create_image_embedding_loader(extracted_imgs)
embedding_loader

此函數將提取的圖像區(qū)域列表包裝到DataLoader中,并使用ColPALI模型對其進行處理,并返回每個圖像的多向量嵌入列表。返回列表的結果如下。

[tensor([[-0.0055, 0.0991, -0.0903, ..., -0.0474, -0.0042, -0.1138],
 [-0.0067, 0.1064, -0.0488, ..., -0.0723, 0.0535, -0.0986],
 [-0.0200, 0.1113, -0.1084, ..., -0.0747, 0.0447, -0.0786],
 ...,
 [-0.0027, 0.0811, -0.1602, ..., 0.0354, -0.0112, -0.1670],
 [-0.0557, -0.1099, 0.0128, ..., 0.0203, -0.0728, -0.0688],
 [ 0.1025, 0.0145, -0.0420, ..., 0.0894, -0.0413, 0.1650]], dtype=torch.bfloat16),
 tensor([[-0.0055, 0.0991, -0.0903, ..., -0.0474, -0.0042, -0.1138],
 [-0.0067, 0.1064, -0.0488, ..., -0.0723, 0.0535, -0.0986],
 [-0.0200, 0.1113, -0.1084, ..., -0.0747, 0.0447, -0.0786],
 ...,
 [-0.0141, 0.0645, -0.1377, ..., 0.0430, -0.0061, -0.1338],
 [-0.0835, -0.1094, 0.0049, ..., 0.0211, -0.0608, -0.0645],
 [ 0.1396, 0.0549, -0.0669, ..., 0.0942, 0.0038, 0.1514]], dtype=torch.bfloat16),
 tensor([[-0.0053, 0.0996, -0.0894, ..., -0.0471, -0.0042, -0.1128],
 [-0.0068, 0.1060, -0.0491, ..., -0.0713, 0.0532, -0.0986],
 [-0.0204, 0.1118, -0.1089, ..., -0.0752, 0.0444, -0.0791],
 ...,
 [ 0.0330, 0.0398, -0.0505, ..., 0.0586, 0.0250, -0.1099],
 [-0.0508, -0.0981, -0.0126, ..., 0.0183, -0.0791, -0.0713],
 [ 0.1387, 0.0698, -0.0330, ..., 0.0238, 0.0923, 0.0337]], dtype=torch.bfloat16)]

4. 圖像和文本索引

在此步驟中,讓我們將頁面圖像中提取的數據組件(包括圖像和文本)索引到數據庫集合中。

import random

for i in range(len(extracted_imgs)):
 data = {
 "colbert_vecs": embedding_loader[i].float().numpy(),
 "doc_id": random.getrandbits(63),
 "page_id": page_id,
 "fig_id": i,
 "filename": filename,
 }
 colbert_retriever.insert(data)

此代碼將每個圖像嵌入到ColBERT樣式檢索器中,并附帶相關元數據,分配唯一的doc_id并存儲頁面和圖形索引引用。

data = {
 "vector": embed_model.encode(text),
 "content": text,
 "page_id": page_id,
 "filename": filename
}
basic_retriever.insert(data)

此代碼將文本嵌入及其元數據(包括內容、頁面ID和文件名)插入到基本文本檢索器中進行索引。

5. Retriever測試

最后,讓我們測試一下上面設置的檢索器。

query = "O.Le Peuch Payout Results in percentage according to SLB Financial Objectives"
batch_query = colpali_processor.process_queries([query]).to(device)
embeddings_query = torch.unbind(colpali_model(**batch_query).to("cpu"))[0].float().numpy()
colbert_retriever_result = colbert_retriever.search(embeddings_query, topk=3)
colbert_retriever_result

此代碼使用ColPALI模型嵌入用戶查詢,并從ColBERT風格的檢索器中檢索出最相關的前3個圖像區(qū)域。結果如下。

[{ 'score' : 20.13466208751197, 
 'page_id' : 38, 
 'fig_id' : 1, 
 'filename' : 'SLB-2023-Annual-Report.pdf' , 
 'content' : '/9j/4AAQSkZJRgABAQAAAQABAAD/...' }, 
{ 'score' : 20.13466208751197, 
 'page_id' : 38, 
 'fig_id' : 1, 
 'filename' : 'SLB-2023-Annual-Report.pdf' , 
 'content' : '/9j/4AAQSkZJRgABAQAAAQABAAD/...' }, 
{ 'score' : 15.088707166083623,
 'page_id':41,
 'fig_id':1,
 'filename':'SLB-2023-Annual-Report.pdf',
 'content':'/9j/4AAQSkZJRgABAQAAAQABAAD/...' }]

接下來,讓我們測試一下基本的檢索器。

query = "Potential Payout as a % of Target Opportunity"
basic_retriever_result = basic_retriever.search(embed_model.encode(query), topk=3)
basic_retriever_result

此代碼使用文本嵌入模型對查詢進行編碼,并從基本檢索器中檢索出最相關的前3個文本條目。結果如下。

[{'score': 0.6565427184104919,
 'page_id': 38,
 'filename': 'SLB-2023-Annual-Report.pdf',
 'content': 'Short-Term Cash Incentive Awards\n\nWe pay performance-based short-term (annual) cash incentives to\nour ... (truncated)'},
 {'score': 0.6533020734786987,
 'page_id': 40,
 'filename': 'SLB-2023-Annual-Report.pdf',
 'content': "Free Cash Flow Targets and Results\n\nIn January 2023, our Compensation Committee considered SLB's\n2022 ... (truncated)"},
 {'score': 0.6505128145217896,
 'page_id': 59,
 'filename': 'SLB-2023-Annual-Report.pdf',
 'content': "Executive Compensation Tables\n\nChange in Control\n\nUnder our omnibus incentive plans, in the event of ... (truncated)"}]

在這里,得分的差異是由于檢索方法造成的:ColBERT風格的檢索器使用內積(IP),從而產生更大的分數值,而基本檢索器反映余弦相似度,通常會產生在-1和之間較小范圍內的分數+1。

聊天推理管道

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

作者插圖:聊天推理管道

聊天推理管道通過從文本和圖像嵌入中檢索相關內容來處理用戶查詢,從而生成準確且情境感知的響應。在本節(jié)中,我們將實現查詢編碼、檢索和多模態(tài)步驟,以完成端到端問答工作流程。

A. 準備

對于聊天補全模型,我們使用Meta開發(fā)的一款功能強大的多模態(tài)LLM Llama-4。我們將使用GROQ API Key來使用此Llama模型。代碼如下。

import os
from groq import Groq

# Groq API-Llama4
os.environ["GROQ_API_KEY"] = "<your-api-key>"
client_groq = Groq()

接下來,讓我們準備一些處理函數,如下所示。

def url_conversion(img_base64):
 return f"data:image/jpeg;base64,{img_base64}"

def llama4_inference(messages, token=1024):
 completion = client_groq.chat.completions.create(
 model="meta-llama/llama-4-maverick-17b-128e-instruct",
 messages=messages,
 temperature=0.1,
 max_completion_tokens=token,
 top_p=1,
 stream=True,
 stop=None,
 )
 inference_result = ""
 for chunk in completion:
 chunk_inference = chunk.choices[0].delta.content or ""
 inference_result += chunk_inference
 text = inference_result
 return text

此代碼定義了一個函數,用于將base64編碼的圖像轉換為可顯示的URL,以及一個llama推理函數,用于通過Groq的API使用LLaMA 4 Maverick模型執(zhí)行流推理。

B. 用戶查詢和相關上下文

現在,讓我們定義用戶查詢并檢索相關上下文,如下所示。

user_query = "I want to know the payout"
batch_query = colpali_processor.process_queries([user_query]).to(device)
embeddings_query = torch.unbind(colpali_model(**batch_query).to("cpu"))[0].float().numpy()
colbert_retriever_result = colbert_retriever.search(embeddings_query, topk=3)
basic_retriever_result = basic_retriever.search(embed_model.encode(user_query), topk=3)

此代碼使用ColPALI模型執(zhí)行文本到圖像檢索的查詢嵌入,并使用句子嵌入模型執(zhí)行文本到文本檢索,遵循與上一個檢索步驟相同的方法。

C.系統(tǒng)指令

接下來,讓我們?yōu)槲覀兊膌lama模型定義系統(tǒng)指令提示,如下所示。

system_instruction = """
You are a helpful assistant designed to answer user queries based on document-related content.

You will be provided with two types of context:
1. Text-based context — extracted textual content from documents.
2. Image-based context — visual content (e.g., figures, tables, or screenshots) extracted from documents.

Your tasks are:
- Analyze the user query and determine the appropriate response using the available context.
- Decide whether the answer requires information from the image-based context.

If the image context is necessary to answer the query:
- Set "need_image" to True.
- Set "image_index" to the appropriate index of the image used (e.g., 0 for the first image, 1 for the second, and so on).
- Include a clear explanation or reasoning in the response.

If the image context is **not** needed:
- Set "need_image" to False.
- Set "image_index" to -1.

All responses **must be returned in strict JSON format**:
{"response": <string>, "need_image": <true|false>, "image_index": <int>}

If you are unsure or cannot answer based on the given context, clearly state that you do not know.

Examples:
{"response": "The chart in image 1 shows the revenue trend.", "need_image": true, "image_index": 1}
{"response": "The policy details are outlined in the text section.", "need_image": false, "image_index": -1}
"""

該系統(tǒng)指令定義了助手應如何基于兩種類型的文檔上下文(基于文本和基于圖像)回答用戶查詢。它指導模型判斷是否需要圖像來回答查詢,并以嚴格的JSON格式構建響應,并包含一個標志(need_image)和一個image_indexif applicable標記。該指令確保文檔理解任務的響應一致、可解釋且支持多模式感知。

D. 消息有效載荷

接下來,讓我們創(chuàng)建將傳遞給Llama模型API的消息有效負載,如下所示。

#定義有效載荷內容
payload_content = [{
 "type": "text",
 "text": f"User Query: {user_query}"
 }]

# 構造正在檢索的圖像URL
for i in range(len(colbert_retriever_result)):
 img_payload = {
 "type": "image_url",
 "image_url": {"url":url_conversion(colbert_retriever_result[i]["content"])}
 }
 payload_content.append(img_payload)

# 構建基于文本的上下文
for i in range(len(basic_retriever_result)):
 txt_payload = {
 "type": "text",
 "text": f"Text-based Context #{i+1}:\n{basic_retriever_result[i]['content']}"
 }
 payload_content.append(txt_payload)

#創(chuàng)建最終消息形式
messages = [
 {
 "role": "system",
 "content": system_instruction
 },
 {
 "role": "user",
 "content": payload_content
 }
]

此代碼通過將用戶查詢、檢索到的圖像(以base64 URL的形式)和基于文本的上下文組合成結構化消息格式,構建LLM的輸入負載。然后,它將這些內容與系統(tǒng)指令一起包裝,形成最終的messages推理輸入。

構造messages如下。

[{'role': 'system',
 'content': '\nYou are a helpful assistant designed to answer user queries based on document-related content.\n\nYou will be provided with two types of context:\n1. Text-based ... (truncated)'},
 {'role': 'user',
 'content': [{'type': 'text',
 'text': 'User Query: I want to know the payout'},
 {'type': 'image_url',
 'image_url': {'url': 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...'}},
 {'type': 'image_url',
 'image_url': {'url': 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...'}},
 {'type': 'image_url',
 'image_url': {'url': 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/...'}},
 {'type': 'text',
 'text': 'Text-based Context #1:\nExecutive Compensation Tables\n\nSummary Compensation Table\n\nThe following table sets forth information regarding the total compensation ... (truncated)'},
 {'type': 'text',
 'text': 'Text-based Context #2:\nExecutive Compensation Tables\n\nGrants of Plan-Based Awards in 2023\n\nThe following table provides additional information regarding cash ... (truncated)'},
 {'type': 'text',
 'text': 'Text-based Context #3:\nPay vs. Performance Comparison\n\nPay vs. Performance Comparison\n\nAs discussed in the CD&A above, our Compensation Committee has implemented ... (truncated)'}]}

E.模型推理

現在,讓我們使用Llama模型來預測響應,如下所示。

import json
import re 

chat_result = llama4_inference(messages) 
chat_result = re.findall( r'\{[^{}]+\}' , chat_result) 
chat_result = json.loads(chat_result[- 1 ]) 
chat_result

此代碼運行LLM推理,使用正則表達式從輸出中提取最后的JSON格式的響應,并將其解析為Python字典以供進一步使用。

由此推論可得出如下結果。

{'response': 'The payout varies based on the performance metric. For Relative TSR Percentile Rank, Delta ROCE, and FCF Margin, the payouts are illustrated in the provided graphs. For example, at a Relative TSR Percentile Rank of 60%, the payout is 60%; at a Delta ROCE of 0 bps, the payout is 100%; and at an FCF Margin of 10%, the payout is 100%.',
 'need_image': True,
 'image_index': 0}

F. 輸出響應

下一步是按如下方式構建輸出響應。

if chat_result[ "need_image" ]: 
 img_content = colbert_retriever_result[chat_result[ 'image_index' ]][ 'content' ] 
else : 
 img_content = ""

 output_response = { 
 "response" :chat_result[ "response" ], 
 "need_image" :chat_result[ "need_image" ], 
 "img_base64" :img_content 
 } 
output_response

此代碼檢查LLM響應是否需要圖像;如果需要,則從ColBERT檢索器結果中檢索相應的base64圖像內容。然后,它會構建一個最終響應字典,其中包含答案文本、圖像標志以及圖像內容(如果適用)。

最終構建的響應如下。

{'response': 'The payout varies based on the performance metric. For Relative TSR Percentile Rank, Delta ROCE, and FCF Margin, the payouts are illustrated in the provided graphs. For example, at a Relative TSR Percentile Rank of 60%, the payout is 60%; at a Delta ROCE of 0 bps, the payout is 100%; and at an FCF Margin of 10%, the payout is 100%.',
 'need_image': True,
 'img_base64': '/9j/4AAQSkZJRgABAQAAAQABAAD/...'}

評估

在本節(jié)中,我們將討論我們提出的多模態(tài)RAG管道和標準純文本RAG管道之間的定性比較,重點介紹檢索相關性和答案質量方面的關鍵差異,特別是對于基于視覺的查詢。

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

作者插圖:我們的管道與常見的RAG管道的比較

我們的定性比較表明,多模態(tài)RAG流程比標準的純文本RAG系統(tǒng)能夠提供更準確的答案,尤其是在涉及表格、圖形和圖表等結構化視覺內容的查詢時。標準RAG流程依賴OCR將文檔視覺內容轉換為純文本,這通常會導致空間結構的丟失和關鍵信息的誤解。

相比之下,我們的系統(tǒng)結合了基于ColPALI的圖像檢索、用于布局檢測的YOLO DocLayNet以及標準文本嵌入,從而同時保留視覺和語義上下文。這使得它能夠準確地檢索和推理基于OCR的流程通常會遺漏的內容,凸顯了真正多模態(tài)方法的有效性。

進一步的演示

我開發(fā)了一個簡單的基于Chainlit的應用程序來總結我們的實驗。以下是該Web應用程序的前端概覽。

ColPali聯(lián)手DocLayNet:打造能“看懂”文檔布局的視覺問答神器!-AI.x社區(qū)

作者插圖:多模式RAG應用程序的前端

通過此應用程序,聊天機器人能夠檢索文本和圖像信息來回答用戶查詢。當用戶查詢相關時,它還可以顯示相關圖像,以增強理解并提供更清晰的背景信息。

為了復制此Web應用程序及其對應的后端服務器,我創(chuàng)建了一個GitHub存儲庫,你可以在??此處??訪問。此存儲庫完全復制了我們的實驗,包括完整的知識庫索引管道以及端到端部署所需的所有組件。?

結論

在本文中,我們構建了一個多模態(tài)RAG系統(tǒng),該系統(tǒng)結合了用于基于圖像的檢索的ColPALI和用于視覺區(qū)域檢測的YOLO-DocLayNet,從而突破了傳統(tǒng)純文本檢索的局限性。通過實際結果演示,我們展示了如何將文本和視覺上下文相結合,在文檔問答任務中提供更準確、更具有上下文感知的答案。

參考文獻

  1. Faysse, M.,Sibille, H.,Wu, T.,Omrani, B.,Viaud, G.,Hudelot, C.和Colombo, P.(2024)。ColPali:基于視覺語言模型的高效文檔檢索。arXiv預印本arXiv:2407.01449。地址:??https ://arxiv.org/abs/2407.01449??。?
  2. Pfitzmann, B.,Auer, C.,Dolfi, M.,Nassar, AS和Staar, P.(2022)。DocLayNet:用于文檔布局分割的大型人工注釋數據集。載于第28屆ACM SIGKDD知識發(fā)現與數據挖掘會議論文集(第3743-3751頁)。?
  3. Reimers, N.和Gurevych, I.(2020)。利用知識蒸餾將單語句子嵌入多語言化。載于2020年自然語言處理實證方法會議(EMNLP)論文集。計算語言學協(xié)會。地址:??https ://arxiv.org/abs/2004.09813??。?

譯者介紹

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

原文標題:??ColPALI Meets DocLayNet: A Vision-Aware Multimodal RAG for Document-QA??,作者:Abu Hanif Muhammad Syarubany

?著作權歸作者所有,如需轉載,請注明出處,否則將追究法律責任
已于2025-8-14 09:41:34修改
收藏
回復
舉報
回復
相關推薦