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

📅 2026/7/8 5:24:26
多模态 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-ragcdmultimodal-rag pipinstall-rrequirements.txtrequirements.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 VisionAPI 可用 GPT-4o、Gemini 1.5 Pro新手建议先从 API 方案入手——省去本地加载大模型的麻烦先跑通流程再说。2.4 验证安装跑一小段代码确认环境正常importfitz# PyMuPDFprint(fitz.__doc__)# 能打印说明安装成功三、数据预处理图片 OCR 与表格结构化提取这是整个流程里最脏最累的活但做好了后面的检索就顺了。3.1 文档解析把 PDF 拆成三样东西用 PyMuPDF 解析 PDF把内容分成三类文本块、图片、表格。importfitzdefparse_pdf(pdf_path):docfitz.open(pdf_path)result{texts:[],images:[],tables:[]}forpage_numinrange(len(doc)):pagedoc[page_num]# 提取文本textpage.get_text()iftext.strip():result[texts].append({page:page_num,content:text})# 提取图片imagespage.get_images()forimginimages:result[images].append({page:page_num,data:img# 实际需要解压图片数据})# 提取表格PyMuPDF 的表格检测能力有限后面会加强tablespage.find_tables()fortableintables:result[tables].append({page:page_num,data:table.extract()})returnresult一个容易被忽略的细节解析的时候要保留内容之间的关联关系。比如图片的标题、表格的编号、周围的文字说明——这些上下文信息在检索时非常关键。3.2 图片处理OCR 语义描述图片不能直接存进向量库得先转化成可检索的形式。有两种主流做法方案一OCR 提取文字适合扫描件、合同、带文字的截图。用 PaddleOCR 或 TesseractfrompaddleocrimportPaddleOCR ocrPaddleOCR(use_angle_clsTrue,langch)resultocr.ocr(image_path,clsTrue)text .join([line[1][0]forlineinresult[0]])方案二VLM 生成语义描述适合图表、流程图、照片这类“信息在视觉结构里”的内容。用视觉语言模型如 Qwen-VL、GPT-4V生成一段描述文字# 伪代码示例descriptionvlm.describe(image_path,prompt请描述这张图片的主要内容包括关键数据、趋势或结构信息)实际项目中往往是两者结合OCR 提取文字 VLM 描述视觉语义然后合并成一条富文本记录。3.3 表格处理保留结构信息表格最忌讳的做法是把所有单元格的文字拼成一段话。那样“第二行第三列”这种查询就废了。正确的做法是保留结构defstructure_table(raw_table):# raw_table 是二维数组ifnotraw_table:returnNoneheaderraw_table[0]ifraw_tableelse[]rowsraw_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# 加载嵌入模型modelSentenceTransformer(BAAI/bge-large-zh-v1.5)defbuild_index(contents):vectors[]metadatas[]foritemincontents:# item 可能是文本、图片描述、表格语义单元textitem[text]vectormodel.encode(text)vectors.append(vector)metadatas.append({type:item[type],# text / image / tablepage:item[page],source:item.get(source,)})returnvectors,metadatas存进向量数据库Qdrant、Chroma 或 Milvus 都行importchromadb clientchromadb.PersistentClient(path./db)collectionclient.get_or_create_collection(multimodal_rag)collection.add(embeddingsvectors,metadatasmetadatas,ids[fid_{i}foriinrange(len(vectors))])五、检索流程实现从用户提问到精准定位索引建好了现在来实现检索。5.1 基本检索用户输入一个问题系统把问题转成向量然后在向量库里找最相似的内容defretrieve(query,top_k5):query_vectormodel.encode(query)resultscollection.query(query_embeddings[query_vector],n_resultstop_k)returnresults就这么简单对核心检索就这几行。但真正好用的系统会在几个细节上下功夫。5.2 混合检索纯向量检索有个问题它只看“语义相似度”不看“关键词匹配”。用户搜“E22 故障码”向量检索可能召回一堆关于“故障”的内容但精准匹配“E22”的能力反而不如传统关键词搜索。解决方案混合检索——向量检索 关键词检索BM25然后合并结果。defhybrid_retrieve(query,top_k5):# 向量检索vector_resultscollection.query(query_embeddings[model.encode(query)],n_resultstop_k)# 关键词检索用 whoosh 或 elasticsearchkeyword_resultskeyword_search(query,top_k)# 合并去重 重排序combinedmerge_and_rerank(vector_results,keyword_results)returncombined[:top_k]5.3 分模态召回如果你的索引是按模态分开建的检索时就要并行查defmulti_modal_retrieve(query):text_resultstext_collection.query(query_vector)image_resultsimage_collection.query(query_vector)table_resultstable_collection.query(query_vector)# 合并所有结果按相似度排序all_resultstext_resultsimage_resultstable_resultsreturnsorted(all_results,keylambdax:x[score],reverseTrue)六、答案生成融合视觉信息的回复合成检索只是拿到了一堆相关内容怎么把这些内容变成用户能看懂的答案6.1 构建上下文把检索到的内容组装成 promptdefbuild_prompt(query,retrieved_items):contextforiteminretrieved_items:ifitem[type]text:contextf[文本]{item[content]}\nelifitem[type]image:contextf[图片]{item[description]}\nelifitem[type]table:contextf[表格]{item[semantic]}\npromptf 请根据以下参考信息回答用户的问题。 参考信息{context}用户问题{query}请基于参考信息给出准确回答如果参考信息不足以回答请诚实说明。 returnprompt6.2 调用生成模型把 prompt 发给大模型defgenerate_answer(query,retrieved_items):promptbuild_prompt(query,retrieved_items)# 如果用 OpenAI APIresponseopenai.ChatCompletion.create(modelgpt-4,messages[{role:user,content:prompt}])returnresponse.choices[0].message.content关键点如果检索到的内容包含图片并且你用的是支持多模态的生成模型如 GPT-4o、Qwen-VL可以把图片也作为输入传进去。这样模型不仅能“读到”图片描述还能“看到”图片本身回答会更准确。七、完整案例演示医疗报告或财务报表查询把以上所有步骤串起来跑一个完整流程。案例胸部 CT 报告查询医疗场景假设知识库里有一批胸部 CT 报告PDF 格式每份报告包含文字描述“右肺上叶见磨玻璃密度结节”CT 影像图片检查指标表格结节大小、密度、位置等用户提问“最近半年内右肺上叶结节增大的病例有哪些”系统处理流程解析把所有 PDF 拆成文本、图片、表格图片处理用 VLM 生成 CT 影像的语义描述“右肺上叶磨玻璃结节直径 8mm”表格处理结构化提取结节测量数据建索引全部向量化存入数据库检索query “右肺上叶 结节 增大” → 召回相关报告生成模型基于召回的图文信息列出符合条件的病例及影像某三甲医院的实际部署数据显示这类多模态检索系统能让医生的诊断效率提升 47%。案例财务报表分析金融场景知识库里有季报 PDF包含文字分析、营收图表、财务数据表格。用户提问“2023 年 Q3 营收同比变化多少”系统会从表格中精准定位“营收”行和“2023 Q3”“2022 Q3”列计算同比变化如果检索到相关图表一并返回可视化佐证八、常见报错排查格式错误与显存溢出解决新手最容易卡在以下几个地方8.1 显存溢出OOM现象跑着跑着报CUDA out of memory。解决方案降低 batch size一次少处理几页 PDF启用 CPU 卸载把部分计算放到内存模型量化FP16 转 INT8显存占用能降一半以上设置环境变量针对某些框架PYTORCH_NPU_ALLOC_CONFexpandable_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自托管完全不依赖外部 API10.3 页面级检索不拆内容还有一种思路不拆分图片和表格直接把整个 PDF 页面当成一张图片处理用 ColPali 这类模型直接编码整页。优点是不用折腾 OCR 和表格解析缺点是检索粒度粗只能定位到页面不能定位到具体段落或单元格。两种思路没有绝对的好坏看你的场景需求。写在最后从空白文件夹到一个能处理图片和表格的 RAG 系统我们走完了完整路径。说实话多模态 RAG 没有想象中那么神秘。核心就三件事把非文本内容转成可检索的形式OCR、VLM 描述、结构化解析全部塞进向量库统一向量空间或分库检索 生成和传统 RAG 一样最难的部分其实是第一步——数据预处理。把这个搞定了后面都是常规操作。建议你先用一个小规模的数据集跑通整个流程再逐步扩展。过程中遇到问题不要慌大部分坑前面的人都踩过了搜一搜基本上都有解决方案。祝你的 RAG 系统早日“开眼看世界”。