news 2026/7/8 5:24:18

多模态 RAG 实战:图片与表格检索从零搭建

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多模态 RAG 实战:图片与表格检索从零搭建

多模态 RAG 实战:图片与表格检索从零搭建

你好,欢迎来到这篇实战教程。
WEB项目地址:演示地址
安卓APP下载地址:演示地址

你是否有过这样的经历——做了一个 RAG 系统,用户问“这张图表里第三季度的数据是多少”,系统一脸茫然?或者用户上传了一份带表格的 PDF,问“表格里利润率最高的是哪一行”,结果系统只返回了“表格包含利润率数据”这种毫无营养的废话?

如果被说中了,那这篇文章就是为你准备的。

我们将从一个空文件夹开始,逐行代码、逐个命令地搭建一个能同时处理文本、图片和表格的多模态 RAG 系统。不绕弯子,不堆砌术语,每一步都告诉你为什么要这么做、不这么做会怎样。

一、核心概念解析:为何传统 RAG 搞不定图表

先看一个真实场景。

假设你有一份《智能咖啡机产品手册》,里面有这么一段:

故障代码 E22:传感器通讯故障。解决方案:检查传感器连接线,参考图 3-1

图 3-1 是一张传感器位置示意图。

传统 RAG 怎么处理?它会把所有文字切碎、向量化、存进数据库。用户问“E22 怎么办”,系统检索到“E22,传感器通讯故障,检查传感器连接线,参考图 3-1”。

然后呢?用户看到“参考图 3-1”,但系统根本没把图 3-1 当成有效信息返回。用户还得自己翻手册找图。

问题出在哪?

传统 RAG 只有一条“文本通道”。对于图片,它要么直接丢掉,要么只保留图片的 alt 文字或文件名。表格稍微好一点——至少能把表格里的文字抠出来,但行与列之间的关系、表头与数据的对应关系全丢了。

某金融科技公司的测试数据显示,在包含复杂图表的文档检索中,没处理非文本信息的 RAG 系统准确率比完整处理方案低了 42%。这不是小差距。

我们要解决的就是这个问题:让 RAG 系统不仅能“读”文字,还能“看”图片、“理”表格。

二、环境极速部署:依赖安装与模型加载

动手之前先把环境搞定。

2.1 硬件最低要求

别被“多模态”三个字吓到。目前有方案最低仅需6GB 显存就能跑起来。如果你的显卡是 8GB 以上的消费级卡(比如 RTX 3060/3070),完全够用。实在不行,CPU 模式也能跑,只是慢一些。

2.2 安装依赖

创建一个项目目录,然后安装核心依赖:

mkdirmultimodal-rag&&cdmultimodal-rag pipinstall-rrequirements.txt

requirements.txt里面至少要有这些:

pymupdf # PDF 解析,提取文本、图片、表格 paddleocr # OCR 识别,处理扫描件和图片中的文字 sentence-transformers # 文本嵌入模型 pillow # 图片处理 qdrant-client # 向量数据库客户端(也可以用 chromadb) transformers # 加载多模态模型 torch # PyTorch 后端

如果不想自己写 requirements,可以直接参考开源项目的配置。

2.3 模型选择

多模态 RAG 通常需要两类模型:

嵌入模型(把内容变成向量):

  • 纯文本场景用BAAI/bge-large-zh-v1.5(中文)或all-MiniLM-L6-v2(英文)
  • 需要图文混合检索,可以用多模态嵌入模型,比如 Nomic Embed Vision或 Qwen3-VL 系列

生成模型(根据检索结果回答问题):

  • 开源可选 Qwen2-VL、Llama 3.2 Vision
  • API 可用 GPT-4o、Gemini 1.5 Pro

新手建议先从 API 方案入手——省去本地加载大模型的麻烦,先跑通流程再说。

2.4 验证安装

跑一小段代码确认环境正常:

importfitz# PyMuPDFprint(fitz.__doc__)# 能打印说明安装成功

三、数据预处理:图片 OCR 与表格结构化提取

这是整个流程里最脏最累的活,但做好了,后面的检索就顺了。

3.1 文档解析:把 PDF 拆成三样东西

用 PyMuPDF 解析 PDF,把内容分成三类:文本块、图片、表格

importfitzdefparse_pdf(pdf_path):doc=fitz.open(pdf_path)result={"texts":[],"images":[],"tables":[]}forpage_numinrange(len(doc)):page=doc[page_num]# 提取文本text=page.get_text()iftext.strip():result["texts"].append({"page":page_num,"content":text})# 提取图片images=page.get_images()forimginimages:result["images"].append({"page":page_num,"data":img# 实际需要解压图片数据})# 提取表格(PyMuPDF 的表格检测能力有限,后面会加强)tables=page.find_tables()fortableintables:result["tables"].append({"page":page_num,"data":table.extract()})returnresult

一个容易被忽略的细节:解析的时候要保留内容之间的关联关系。比如图片的标题、表格的编号、周围的文字说明——这些上下文信息在检索时非常关键。

3.2 图片处理:OCR + 语义描述

图片不能直接存进向量库,得先转化成可检索的形式。有两种主流做法:

方案一:OCR 提取文字

适合扫描件、合同、带文字的截图。用 PaddleOCR 或 Tesseract:

frompaddleocrimportPaddleOCR ocr=PaddleOCR(use_angle_cls=True,lang='ch')result=ocr.ocr(image_path,cls=True)text=' '.join([line[1][0]forlineinresult[0]])

方案二:VLM 生成语义描述

适合图表、流程图、照片这类“信息在视觉结构里”的内容。用视觉语言模型(如 Qwen-VL、GPT-4V)生成一段描述文字:

# 伪代码示例description=vlm.describe(image_path,prompt="请描述这张图片的主要内容,包括关键数据、趋势或结构信息")

实际项目中往往是两者结合:OCR 提取文字 + VLM 描述视觉语义,然后合并成一条富文本记录。

3.3 表格处理:保留结构信息

表格最忌讳的做法是把所有单元格的文字拼成一段话。那样“第二行第三列”这种查询就废了。

正确的做法是保留结构:

defstructure_table(raw_table):# raw_table 是二维数组ifnotraw_table:returnNoneheader=raw_table[0]ifraw_tableelse[]rows=raw_table[1:]iflen(raw_table)>1else[]# 生成语义单元:每一行 + 表头组合成一句话semantic_units=[]forrowinrows:unit=",".join([f"{header[i]}{row[i]}"foriinrange(min(len(header),len(row)))])semantic_units.append(unit)return{"header":header,"rows":rows,"semantic_units":semantic_units}

这样处理后,表格既保留了结构(可以精确检索某行某列),又生成了可用于向量检索的语义单元。

一个关键提醒:经过结构化处理的表格数据,在向量检索中的相似度匹配准确率能提升 65%。这个提升非常可观,值得花时间把表格处理做好。

四、向量化策略:构建图文混合索引库

预处理完了,接下来要把不同类型的内容统统变成向量,存进同一个向量数据库。

4.1 分库还是合库?

这里有两种路线:

路线一:统一向量空间

把所有内容(文本、图片描述、表格语义单元)都用同一个嵌入模型转成向量,存进一个集合。优点是检索简单,一次查询全搞定;缺点是不同模态的语义可能不在同一个空间里。

路线二:分模态建库

文本一个集合、图片一个集合、表格一个集合,各自用最适合的嵌入模型。查询时并行检索三个库,然后合并结果。优点是各模态处理得更精准;缺点是检索逻辑复杂一些。

新手推荐路线一起步——简单、够用。等跑通了再考虑升级到分库方案。

4.2 构建索引

fromsentence_transformersimportSentenceTransformer# 加载嵌入模型model=SentenceTransformer('BAAI/bge-large-zh-v1.5')defbuild_index(contents):vectors=[]metadatas=[]foritemincontents:# item 可能是文本、图片描述、表格语义单元text=item["text"]vector=model.encode(text)vectors.append(vector)metadatas.append({"type":item["type"],# text / image / table"page":item["page"],"source":item.get("source","")})returnvectors,metadatas

存进向量数据库(Qdrant、Chroma 或 Milvus 都行):

importchromadb client=chromadb.PersistentClient(path="./db")collection=client.get_or_create_collection("multimodal_rag")collection.add(embeddings=vectors,metadatas=metadatas,ids=[f"id_{i}"foriinrange(len(vectors))])

五、检索流程实现:从用户提问到精准定位

索引建好了,现在来实现检索。

5.1 基本检索

用户输入一个问题,系统把问题转成向量,然后在向量库里找最相似的内容:

defretrieve(query,top_k=5):query_vector=model.encode(query)results=collection.query(query_embeddings=[query_vector],n_results=top_k)returnresults

就这么简单?对,核心检索就这几行。但真正好用的系统会在几个细节上下功夫。

5.2 混合检索

纯向量检索有个问题:它只看“语义相似度”,不看“关键词匹配”。用户搜“E22 故障码”,向量检索可能召回一堆关于“故障”的内容,但精准匹配“E22”的能力反而不如传统关键词搜索。

解决方案:混合检索——向量检索 + 关键词检索(BM25),然后合并结果。

defhybrid_retrieve(query,top_k=5):# 向量检索vector_results=collection.query(query_embeddings=[model.encode(query)],n_results=top_k)# 关键词检索(用 whoosh 或 elasticsearch)keyword_results=keyword_search(query,top_k)# 合并去重 + 重排序combined=merge_and_rerank(vector_results,keyword_results)returncombined[:top_k]

5.3 分模态召回

如果你的索引是按模态分开建的,检索时就要并行查:

defmulti_modal_retrieve(query):text_results=text_collection.query(query_vector)image_results=image_collection.query(query_vector)table_results=table_collection.query(query_vector)# 合并所有结果,按相似度排序all_results=text_results+image_results+table_resultsreturnsorted(all_results,key=lambdax:x["score"],reverse=True)

六、答案生成:融合视觉信息的回复合成

检索只是拿到了一堆相关内容,怎么把这些内容变成用户能看懂的答案?

6.1 构建上下文

把检索到的内容组装成 prompt:

defbuild_prompt(query,retrieved_items):context=""foriteminretrieved_items:ifitem["type"]=="text":context+=f"[文本]{item['content']}\n"elifitem["type"]=="image":context+=f"[图片]{item['description']}\n"elifitem["type"]=="table":context+=f"[表格]{item['semantic']}\n"prompt=f""" 请根据以下参考信息回答用户的问题。 参考信息:{context}用户问题:{query}请基于参考信息给出准确回答,如果参考信息不足以回答,请诚实说明。 """returnprompt

6.2 调用生成模型

把 prompt 发给大模型:

defgenerate_answer(query,retrieved_items):prompt=build_prompt(query,retrieved_items)# 如果用 OpenAI APIresponse=openai.ChatCompletion.create(model="gpt-4",messages=[{"role":"user","content":prompt}])returnresponse.choices[0].message.content

关键点:如果检索到的内容包含图片,并且你用的是支持多模态的生成模型(如 GPT-4o、Qwen-VL),可以把图片也作为输入传进去。这样模型不仅能“读到”图片描述,还能“看到”图片本身,回答会更准确。

七、完整案例演示:医疗报告或财务报表查询

把以上所有步骤串起来,跑一个完整流程。

案例:胸部 CT 报告查询(医疗场景)

假设知识库里有一批胸部 CT 报告(PDF 格式),每份报告包含:

  • 文字描述(“右肺上叶见磨玻璃密度结节”)
  • CT 影像图片
  • 检查指标表格(结节大小、密度、位置等)

用户提问:“最近半年内,右肺上叶结节增大的病例有哪些?”

系统处理流程

  1. 解析:把所有 PDF 拆成文本、图片、表格
  2. 图片处理:用 VLM 生成 CT 影像的语义描述(“右肺上叶,磨玻璃结节,直径 8mm”)
  3. 表格处理:结构化提取结节测量数据
  4. 建索引:全部向量化存入数据库
  5. 检索:query “右肺上叶 结节 增大” → 召回相关报告
  6. 生成:模型基于召回的图文信息,列出符合条件的病例及影像

某三甲医院的实际部署数据显示,这类多模态检索系统能让医生的诊断效率提升 47%。

案例:财务报表分析(金融场景)

知识库里有季报 PDF,包含文字分析、营收图表、财务数据表格。

用户提问:“2023 年 Q3 营收同比变化多少?”

系统会:

  1. 从表格中精准定位“营收”行和“2023 Q3”“2022 Q3”列
  2. 计算同比变化
  3. 如果检索到相关图表,一并返回可视化佐证

八、常见报错排查:格式错误与显存溢出解决

新手最容易卡在以下几个地方:

8.1 显存溢出(OOM)

现象:跑着跑着报CUDA out of memory

解决方案

  • 降低 batch size:一次少处理几页 PDF
  • 启用 CPU 卸载:把部分计算放到内存
  • 模型量化:FP16 转 INT8,显存占用能降一半以上
  • 设置环境变量(针对某些框架):PYTORCH_NPU_ALLOC_CONF=expandable_segments:True

如果显存只有 6-8GB,优先考虑用 API 方案而不是本地加载大模型。

8.2 OCR 识别率低

现象:扫描件里的文字识别出一堆乱码。

解决方案

  • 针对特定领域(财报、医疗报告)做 OCR 后处理优化
  • 尝试不同的 OCR 引擎(PaddleOCR 对中文效果较好)
  • 图片预处理:提高对比度、去噪

8.3 表格解析失败

现象find_tables()找不到表格,或者提取出来是乱的。

解决方案

  • PyMuPDF 的表格检测能力有限,复杂表格可以考虑用 Table Transformer 等专用模型
  • 或者把表格区域截图,用 OCR + 后处理还原结构

8.4 检索结果不相关

现象:用户问 A,召回的内容全是 B。

排查方向

  • 检查嵌入模型是否适合你的语言(中文场景别用英文模型)
  • 检查分块策略——块太大或太小都会影响检索质量
  • 试试混合检索(向量 + 关键词)

九、效果优化技巧:提升复杂图表理解准确率

系统跑通了只是第一步,要想真正好用,这几个优化值得做:

9.1 重排序(Rerank)

向量检索召回 Top 20,然后用一个更精细的模型重新打分,只取 Top 5。

重排序模型可以是:

  • 专门的 cross-encoder(如 BGE-reranker)
  • 多模态重排序模型(如 Lychee Rerank MM)

效果提升很明显——Top 10 里可能有 3 个不相关,重排后 Top 5 基本都相关。

9.2 分块策略优化

不要机械地按字数切分。应该按语义单元切分

  • 一个段落是一个块
  • 一张图片 + 它的标题 + 周围的说明文字合并成一个块
  • 一个表格 + 它的编号 + 上下的文字说明合并成一个块

这样每个块都是“自包含”的语义单元,检索时不会缺上下文。

9.3 图表专项优化

对于柱状图、折线图、饼图这类视觉信息密集的内容:

  • 用 VLM 生成详细的结构化描述(“X 轴是季度,Y 轴是营收,Q3 最高”)
  • 不只描述“是什么”,还要描述“趋势”和“对比”
  • 某方案在 FinRAGBench 数据集上通过专项优化,准确率从 69.6% 提升到了 90.7%

十、进阶扩展方向

这个系统可以往很多方向扩展,说几个最实用的:

10.1 支持更多文件格式

不只是 PDF,还可以支持:

  • Word(.docx):用 python-docx 提取
  • Excel(.xlsx):用 openpyxl 直接读表格
  • 图片(.png/.jpg):直接进入图片处理流程
  • PPT(.pptx):用 python-pptx 提取

目前已有开源方案支持 15+ 种多模态文件格式。

10.2 私有化部署

如果对数据安全有要求,可以全部部署在本地:

  • 用 Ollama 或 vLLM 跑本地模型
  • 向量数据库用 Chroma(本地文件)或 Qdrant(自托管)
  • 完全不依赖外部 API

10.3 页面级检索(不拆内容)

还有一种思路:不拆分图片和表格,直接把整个 PDF 页面当成一张图片处理,用 ColPali 这类模型直接编码整页。

优点是不用折腾 OCR 和表格解析,缺点是检索粒度粗(只能定位到页面,不能定位到具体段落或单元格)。

两种思路没有绝对的好坏,看你的场景需求。

写在最后

从空白文件夹到一个能处理图片和表格的 RAG 系统,我们走完了完整路径。

说实话,多模态 RAG 没有想象中那么神秘。核心就三件事:

  1. 把非文本内容转成可检索的形式(OCR、VLM 描述、结构化解析)
  2. 全部塞进向量库(统一向量空间或分库)
  3. 检索 + 生成(和传统 RAG 一样)

最难的部分其实是第一步——数据预处理。把这个搞定了,后面都是常规操作。

建议你先用一个小规模的数据集跑通整个流程,再逐步扩展。过程中遇到问题不要慌,大部分坑前面的人都踩过了,搜一搜基本上都有解决方案。

祝你的 RAG 系统早日“开眼看世界”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/8 5:22:39

LLaMA-2 7B 中文词表扩充实战:新增 17953 个 Token 后 PPL 降低 15%

LLaMA-2 7B 中文词表扩充实战:新增 17953 个 Token 后 PPL 降低 15%1. 为什么需要中文词表扩充当我们将英文基座大模型(如LLaMA-2)适配到中文场景时,原始的词表(Vocabulary)设计往往成为第一个需要突破的瓶…

作者头像 李华
网站建设 2026/7/8 5:22:35

电商智能选品:用数据驱动替代经验决策

选品是电商运营的核心命脉——选对了,事半功倍;选错了,库存积压、资金套牢。传统的选品方式依赖"老板直觉"或"跟风爆款",而现代电商正在用一套数据化、系统化、自动化的方法论,让选品从"拍脑…

作者头像 李华
网站建设 2026/7/8 5:20:45

手把手教你怎么安装EditPlus(EditPlus v6)editplus下载安装教程

文章目录前言editplus 安装前准备EditPlus 下载地址EditPlus v6 安装教程(超详细)EditPlus v6 怎么设置中文界面?汉化包安装步骤详解前言 EditPlus 出自韩国 Sangil Kim 公司之手,是一款在 Windows 平台上广受好评的轻量级文本编…

作者头像 李华
网站建设 2026/7/8 5:16:04

5分钟掌握Luyten:Java反编译工具的最佳图形化解决方案

5分钟掌握Luyten:Java反编译工具的最佳图形化解决方案 【免费下载链接】Luyten An Open Source Java Decompiler Gui for Procyon 项目地址: https://gitcode.com/gh_mirrors/lu/Luyten 你是否曾经遇到过需要分析Java字节码文件却无从下手的困境?…

作者头像 李华