老周是家族档案馆的管家,馆里堆着几十年的老照片、旧账本和一摞摞书信。孙女小满总爱来“考古”:“那年生日合照在哪?”“奶奶生日蛋糕花了多少钱?”老周最怕这两个问题——照片上没写字,账本上格子密密麻麻,光靠翻文字根本找不到。这正是多模态 RAG 里最硬的骨头:图片和表格不像普通文字,不能直接塞进搜索引擎。幸运的是,老周后来摸索出一套“笨办法”:给照片写“内容小抄”、给照片按“数字指纹”、把账本拆骨、再念成流水账、建双料目录卡、最后让候选材料进“决赛圈”打分。这下,再刁钻的问题也能在几分钟内翻出答案。本文就跟着老周的档案馆,把图片表格检索的来龙去脉一次讲透。

岗位关联

图片与表格的结构化检索,是 RAG 工程师从“只会检索文字”迈向“服务财报、电商、技术文档等真实业务”的关键一跃,也是多模态 RAG 落地绕不开的硬能力。

目录
  1. 可视化演示
  2. 图片描述生成
  3. 视觉特征编码
  4. 表格结构抽取
  5. 表格序列化
  6. 混合索引构建
  7. 混合检索排序
  8. 应用与评估
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示图片表格检索的核心机制,通过输入/处理/存储/输出四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

图片表格检索:图片描述 图片描述自动生成与视觉特征编码 表格结构化抽取与序列化表示方法 核心机制可视化 图片表格检索整体架构与关键组件

图片描述生成

老周的档案馆里,最难伺候的是爷爷留下的那几铁盒老照片。照片上只有画面没有字,小满来问“去年的全家福在哪”,老周翻遍盒子也只能干瞪眼。后来他想了个土办法:每收一张照片,就在背面用铅笔写一行小字——“2022年6月,奶奶生日,全家客厅合影”。有了这行“小抄”,再找照片就有线索了:先翻小抄,再抽照片。

这个“在照片背面写小抄”的动作,放到 AI 里就叫图片描述生成(Image Captioning)。多模态 RAG 在处理图片时,不会直接把原图塞进文字检索引擎,而是先让视觉语言模型(VLM)“看一眼”整张图,再把看到的内容写成一两句话。与此同时,图片里如果本身印着字——比如路牌、发票、PPT 截图——还需要一条专门的 OCR 通道,把图里的印刷文字原样抠出来。前者写“画面里有什么”,后者写“图面上印着什么”,两行“小抄”合在一起,图片就有了可被文字检索的线索。

图片描述生成本质上是一个“看图说话”的过程:模型把图片变成一组视觉特征,然后像写作文一样一个词一个词往外蹦,每蹦一个词都要回头看看已经写了什么、再看看图片。最终整句话的质量,就是每一步选词概率的乘积:

有了“小抄”,老周的检索从“大海捞针”变成了“先翻索引卡”。但小抄是人写的,难免漏掉细节——照片里角落的那盏灯、那条小船,都可能没被写进去。所以老周明白,光有小抄还不够,他还要练就一双“认脸”的眼睛。

P(w1,w2,…,wT∣I)=∏t=1TP(wt∣w1,…,wt−1,I)\displaystyle P(w_1, w_2, \ldots, w_T \mid I) = \prod_{t=1}^{T} P(w_t \mid w_1, \ldots, w_{t-1}, I)
这个公式说明:图片描述不是一次性抄下来的,而是模型“边看图边造句”,每写一个词都同时结合图片内容和前面写过的词。
  1. 图片条件 $I$$I$ 表示整张图片的视觉信息,相当于模型“看”到的画面,是生成描述的起点。
    I=整张图片的视觉特征I = \text{整张图片的视觉特征}
  2. 描述单词 $w_t$$w_1$ 到 $w_T$ 是最终生成的一句描述里的 T 个词,比如“奶奶”“生日”“合影”。
    wt=描述中的第 t 个词w_t = \text{描述中的第 } t \text{ 个词}
  3. 条件概率 $P(w_t \mid w_1, \ldots, w_{t-1}, I)$表示在已经写了前 t-1 个词、并且看着图片 $I$ 的情况下,下一个词是 $w_t$ 的概率。
    P(wt∣w<t,I)=边看图边接词的概率P(w_t \mid w_{<t}, I) = \text{边看图边接词的概率}
  4. 连乘 $\prod_{t=1}^{T}$把每一步选词的概率乘起来,得到整句话出现的概率;模型挑概率最大的那串词作为最终描述。
    ∏t=1TP(wt∣w<t,I)=整句描述的出现概率\prod_{t=1}^{T} P(w_t \mid w_{<t}, I) = \text{整句描述的出现概率}

视觉特征编码

档案馆新来的年轻人阿凯不看照片背面的小抄,也能一眼认出“这是去年生日那天的合影”。问他怎么认出来的,他说“就是感觉像”——照片里有人脸、有蛋糕、有客厅的灯光,这些“长相”特征一配就对上号了。老周恍然大悟:认东西不光靠文字,还可以靠“长相”。

在 AI 里,“靠长相认东西”就是视觉特征编码。模型不写字、不描述,而是把图片像素直接压缩成一个高维向量,也就是“数字指纹”;两张图长得像,它们的指纹向量在空间里就离得近。更进一步,CLIP 这类“双塔模型”把图片编码器和文本编码器放在一起训练,让一段文字(比如“海边日落”)和对应的图片在同一个向量空间里互相靠近。这样一来,用户输入“海边日落”这句话,也能在图片堆里找到长相最接近的照片。

CLIP 的训练目标是让“正确配对的图文”相似度尽量高、让“乱配对的图文”相似度尽量低,这种思路叫对比学习:

有了“小抄”加“指纹”,老周查照片的效率一下子翻倍。可一旦遇到爷爷的旧账本,他又犯了难:账本上全是密密麻麻的数字和栏目,既不能靠“小抄”描述,也不像照片那样有一张“脸”。账本的问题,得换一套拆法。

L=−log⁡exp⁡(sim(Ii,Ti)/τ)∑j=1Nexp⁡(sim(Ii,Tj)/τ)\displaystyle \mathcal{L} = -\log \frac{\exp(\mathrm{sim}(I_i, T_i) / \tau)}{\sum_{j=1}^{N} \exp(\mathrm{sim}(I_i, T_j) / \tau)}
可以把它理解成一场“认亲测试”:给一张图,让它在一群文本候选人里找出真正和它一对的那句话,找得越准,损失越小。
  1. 余弦相似度 $\mathrm{sim}(I_i, T_i)$计算第 i 张图片与它对应文本之间的相似度,取值在 0 到 1 之间,越大代表越匹配。
    sim(Ii,Ti)=图片与文本的余弦相似度\mathrm{sim}(I_i, T_i) = \text{图片与文本的余弦相似度}
  2. 温度系数 $\tau$一个缩放常数,用来把相似度“拉开”或“收紧”,让模型更自信地区分正确配对与错误配对。
    τ=控制相似度分布陡缓的温度参数\tau = \text{控制相似度分布陡缓的温度参数}
  3. 分母求和 $\sum_{j=1}^{N}$把这张图片与批次里所有 N 个文本的相似度都算出来并求和,用来做归一化,相当于“所有候选人的总热度”。
    ∑j=1Nexp⁡(sim(Ii,Tj)/τ)=所有配对候选的指数和\sum_{j=1}^{N} \exp(\mathrm{sim}(I_i, T_j) / \tau) = \text{所有配对候选的指数和}
  4. 负对数 $-\log$括号内是“正确配对占全体候选的比例”,比例越接近 1,损失越小,模型学得越好。
    −log⁡(正确配对占比)=对比学习损失-\log(\text{正确配对占比}) = \text{对比学习损失}

表格结构抽取

爷爷留下的账本是老周的心病。一页页全是格子,日期、项目、金额搅在一起。小满问“前年六月的西瓜收入是多少”,老周光是找到“六月那一页”就要半天,更别提还要分清哪一格是收入、哪一格是支出。他意识到:账本不能囫囵吞枣地念,得先“拆骨”,拆清楚行和列。

表格结构抽取就是给账本“拆骨”。第一步是表格检测,在扫描件或网页里找到“哪里有一张表”;第二步是行列与单元格解析,把表格还原成“第几行第几列”的网格,标出哪些格子是表头、哪些是普通数据、哪些跨行跨列;第三步是识别表头与表体的关系,搞明白“金额这一列到底属于哪个项目”。三步做完,一张糊在一起的表格就变成了一张带坐标的网格。

单元格解析在算法上是一个分类问题:模型对每个格子预测它属于哪一类角色(表头、数据、合并格等)。对第 r 行第 c 列的格子,模型先给它打各类别的原始分,再用 softmax 变成概率:

账本被拆成网格、每格贴上标签后,老周终于看清它的骨架。但问题又来了:仓库里的文字检索工具根本不认识“网格”,它只认一行一行的字。老周只好把账本的骨架“念”出来,让它变成文字检索工具听得懂的流水账。

p(cr,c=k∣X)=ezr,c,k∑k′=1Kezr,c,k′\displaystyle p(c_{r,c} = k \mid X) = \frac{\displaystyle e^{z_{r,c,k}}}{\displaystyle \sum_{k'=1}^{K} e^{z_{r,c,k'}}}
这个公式把一个格子的“原始打分”换算成“它属于各类别的概率”,概率最大的类别就是最终标签,相当于给每个格子贴上一张“身份标签”。
  1. 单元格坐标 $(r,c)$表格被看作网格,$r$ 是行号、$c$ 是列号,每个格子就是一个单元格。
    (r,c)=第 r 行第 c 列的单元格(r, c) = \text{第 } r \text{ 行第 } c \text{ 列的单元格}
  2. 类别 $k$单元格的角色类别,例如 0 表示表头、1 表示普通数据、2 表示合并格。
    k=单元格的角色类别k = \text{单元格的角色类别}
  3. 模型打分 $z_{r,c,k}$模型对“第 r 行第 c 列的格子属于类别 k”给出的原始分数,越大说明越像这一类。
    zr,c,k=模型对该格子属于类别 k 的打分z_{r,c,k} = \text{模型对该格子属于类别 } k \text{ 的打分}
  4. softmax 归一化分母把该格子所有类别的指数分加起来,分子除以分母后得到 0 到 1 之间的概率 $p$,所有类别的概率之和为 1。
    ∑k′=1Kezr,c,k′=归一化分母,保证概率和为 1\sum_{k'=1}^{K} e^{z_{r,c,k'}} = \text{归一化分母,保证概率和为 1}

表格序列化

老周的仓库里有一位只会“听字”的老伙计,你给他一句话他能记一辈子,但给他一张表格他就犯晕。老周想了个办法:把账本一行一行念给他听,每念一个数字都先报出坐标——“第一行第一列,日期,六月一日;第一行第二列,项目,卖西瓜;第一行第三列,金额,288元”。老伙计把话串起来,账本就被“翻译”成了他能记住的流水账。

这在 AI 里叫表格序列化(Table Serialization)。表格被结构抽取还原出行列之后,还需要被改写成纯文本,才能进入常规的文字索引和语言模型。最常用的写法有三种:扁平文本化,用特殊符号把行列分隔开;转成 Markdown 表格,保留管道符和分隔行;转成 HTML 的 <table> 结构。无论哪种写法,核心原则只有一个:每个单元格的值前面,都要带上它的“行列身份标记”。

用公式表示序列化过程,就是把表格按行优先的顺序,把每个格子改写成“标记 + 值”再首尾拼接:

被“念”出来的账本终于和照片的小抄、照片的指纹一样,可以被检索了。但老周的档案柜现在越来越乱:照片有小抄文本,有指纹向量;账本有流水文本,也有其他特征。要同时管好这么多类型的线索,他需要一套新的归档方法。

S=⟨R1C1⟩ v1,1  ⊕  ⟨R1C2⟩ v1,2  ⊕  ⋯  ⊕  ⟨RRCC⟩ vR,C\displaystyle S = \langle R_{1}C_{1} \rangle\, v_{1,1} \; \oplus \; \langle R_{1}C_{2} \rangle\, v_{1,2} \; \oplus \; \cdots \; \oplus \; \langle R_{R}C_{C} \rangle\, v_{R,C}
公式里的 $\langle R_r C_c \rangle$ 是“我来自第 r 行第 c 列”的身份牌,$v$ 是格子里的文字。整个表格最终变成一大段带标记的文本,文字检索工具终于“听得懂”账本了。
  1. 单元格文本 $v_{r,c}$第 r 行第 c 列里的具体内容,比如“288 元”“蛋糕”。
    vr,c=第 r 行第 c 列的单元格内容v_{r,c} = \text{第 } r \text{ 行第 } c \text{ 列的单元格内容}
  2. 行列标记 $\langle R_r C_c \rangle$一个特殊记号,告诉模型“接下来这个值在第 r 行第 c 列”,保住表格的结构信息。
    ⟨RrCc⟩=单元格的行列身份标记\langle R_r C_c \rangle = \text{单元格的行列身份标记}
  3. 拼接符号 $\oplus$按从左到右、从上到下的顺序,把所有“标记 + 值”串成一大段文字。
    ⊕=字符串首尾拼接操作\oplus = \text{字符串首尾拼接操作}
  4. 整句 $S$序列化后的纯文本,可以被普通文字检索模型当作普通句子处理。
    S=序列化后的表格文本S = \text{序列化后的表格文本}

混合索引构建

归档那天,老周把每份档案做成两张卡片:一张“标签卡”记录所有文字线索,包括照片小抄、OCR 结果、账本流水账;另一张“指纹卡”记录视觉特征向量。他给卡片编好号,再把同一份档案的两张卡用夹子夹在一起。这样无论是“按字找”还是“按长相找”,他都能快速定位到同一份档案。

在系统里,这对应图文混合索引的三种构建思路。文本倒排索引:把描述、OCR 文本、序列化表格切词建倒排表,适合精确匹配关键词;视觉向量索引:把图片编码成向量后建 ANN(近似最近邻)索引,适合按“长相”召回;跨模态统一索引:再训练一个投影层,把文本向量和视觉向量映射到同一个向量空间,让文字查询也能直接和图片向量比距离。

跨模态统一投影的构建可以这样表示:每个文档 d 的文本嵌入和视觉嵌入先拼接,再通过一个可学习的投影层压到统一维度:

索引建好,老周的工作轻松多了。可新的烦恼也随之而来:按文字线索捞上来一摞,按指纹又捞上来一摞,两摞材料有重合也有差异。到底先给谁?老周决定,让这些候选材料进“决赛圈”,按第二轮的标准统一排名。

vd=Projθ(concat(etext(d),  evisual(d)))\displaystyle \mathbf{v}_{d} = \mathrm{Proj}_{\theta}\big( \mathrm{concat}( \mathbf{e}_{\text{text}}^{(d)},\; \mathbf{e}_{\text{visual}}^{(d)} ) \big)
这个公式展示了多模态索引里的核心动作:把来自不同模态的向量先拼起来,再统一“画风”,让它们落进同一个坐标系里,方便统一比较。
  1. 文本嵌入 $\mathbf{e}_{\text{text}}^{(d)}$由文字编码器把文档 d 的文字部分(含描述、OCR、序列化表格)变成向量。
    etext(d)=文档 d 的文本向量\mathbf{e}_{\text{text}}^{(d)} = \text{文档 } d \text{ 的文本向量}
  2. 视觉嵌入 $\mathbf{e}_{\text{visual}}^{(d)}$由视觉编码器把文档 d 的图片部分变成向量。
    evisual(d)=文档 d 的图片向量\mathbf{e}_{\text{visual}}^{(d)} = \text{文档 } d \text{ 的图片向量}
  3. 拼接 $\mathrm{concat}$把两个向量首尾相连,合并成一个更长的向量。
    concat(etext,evisual)=两个向量的首尾拼接\mathrm{concat}(\mathbf{e}_{\text{text}}, \mathbf{e}_{\text{visual}}) = \text{两个向量的首尾拼接}
  4. 投影层 $\mathrm{Proj}_{\theta}$一个可学习线性层,把拼接后的长向量压缩成统一维度,让图文落在同一向量空间并写入索引库。
    Projθ(⋅)=可学习的统一投影变换\mathrm{Proj}_{\theta}(\cdot) = \text{可学习的统一投影变换}

混合检索排序

决赛圈的规矩是老周定的:第一轮海选,文字通道和视觉通道各自捞回一批候选;第二轮不看各通道给出的“原始分”,只看每份材料在各自通道里排第几。比如某份材料在文字通道排第 3、在视觉通道排第 5,另一份排第 10、第 1,老周按名次综合算总分,名次越靠前贡献越大。

这就是混合检索排序。第一步是多路召回融合,常用 RRF(Reciprocal Rank Fusion,倒数排名融合)——它用“名次的倒数”做分数,天然免疫各通道分数尺度不同的问题;第二步是重排序(Rerank),把融合后排进前几十的候选交给一个更强大的交叉编码模型逐条精读,再排出最终名次;第三步是设定阈值与置信度,得分太低的候选直接不返回,避免硬凑答案。

RRF 的分数公式非常简单:

有了决赛圈规则,老周终于能自信地回答小满的问题了。一年下来,档案馆被家里人交口称赞。但小满在夸完他之后问了一句:“周爷爷,你怎么知道自己找得够不够全?”老周愣了一下——对啊,服务好不好,不能光靠感觉,得有一套衡量办法。

RRF(q,d)=∑r∈R1 k+rankr(d) \displaystyle \mathrm{RRF}(q, d) = \sum_{r \in \mathcal{R}} \frac{1}{\,k + \mathrm{rank}_{r}(d)\,}
分数只与“名次”有关,与原始相似度无关。所以即使文字通道打 0.9 分、视觉通道打 0.4 分,也不会因为尺度不同而偏袒某一侧。
  1. 通道集合 $\mathcal{R}$包括文字检索路、视觉检索路等多个召回通道,一份文档可能出现在多条通道的结果里。
    R=所有召回通道的集合\mathcal{R} = \text{所有召回通道的集合}
  2. 名次 $\mathrm{rank}_{r}(d)$文档 $d$ 在第 $r$ 条通道的结果列表里排第几名,名次从 1 开始。
    rankr(d)=文档在通道 r 中的排名\mathrm{rank}_{r}(d) = \text{文档在通道 } r \text{ 中的排名}
  3. 平滑常数 $k$通常取 60,防止名次极端时分数爆炸,同时弱化第一名与第二名的差距。
    k=平滑常数,一般取 60k = \text{平滑常数,一般取 60}
  4. 求和 $\sum_{r \in \mathcal{R}}$把该文档在每条通道的名次分累加起来,总分越高,最终排名越靠前。
    ∑r∈R1k+rankr(d)=该文档的 RRF 总分\sum_{r \in \mathcal{R}} \frac{1}{k + \mathrm{rank}_r(d)} = \text{该文档的 RRF 总分}

应用与评估

为了证明自己的能力,老周开始记录每一次“检索任务”:小满要的是什么,他交出了什么。如果小满要的 3 份材料,老周只交出了 2 份,那这次“找得全”就打了折扣。他把这些记录做成一张考核表,每季度复盘一次。

现实中,图片表格检索已经进入到大量真实业务。财报表格问答:分析师问“去年三季度研发费用”,系统从几百页年报里抽出表格、序列化、检索出正确那张表给大模型作答;电商图片问答:用户描述“白色圆领短袖”,系统靠图片描述加视觉向量找出最接近的商品图;技术规格表查询:客服在手册里查“某型号支持哪些协议”,靠表格结构抽取加重排精排在密密麻麻的规格表里定位答案。

考核检索质量最常用的指标是 Recall@K(前 K 条召回率):看系统返回的前 K 个结果里,有多少比例是真正相关的:

老周拿着考核表,乐呵呵地贴在了档案馆门口。从给照片写小抄,到给照片按指纹,再到拆账本、念流水账、建双料目录、决赛圈排名,最后用考核表量化服务质量——这一整套流程,就是多模态 RAG 里图片与表格检索从“硬骨头”变成“拿手菜”的全过程。

Recall@K=∣{相关文档}∩{前K个检索结果}∣∣{相关文档}∣\displaystyle \mathrm{Recall@K} = \frac{\big| \{\text{相关文档}\} \cap \{\text{前K个检索结果}\} \big|}{\big| \{\text{相关文档}\} \big|}
这个指标回答的正是老周的问题:“该找到的,你找回来几成?”它是衡量多模态 RAG 检索环节的通用标尺。
  1. 相关文档集合人工预先标好“这组问题到底该找到哪几份文档”,是评估时的标准答案。
    {相关文档}=标准答案文档集合\{\text{相关文档}\} = \text{标准答案文档集合}
  2. 前 K 个结果系统按得分从高到低返回的前 K 份文档,K 一般取 5、10 或 20。
    {前K个检索结果}=返回列表的前 K 份文档\{\text{前K个检索结果}\} = \text{返回列表的前 K 份文档}
  3. 交集既是相关文档、又恰好被排进前 K 的数量。
    ∣{相关文档}∩{前K个结果}∣=命中数量|\{\text{相关文档}\} \cap \{\text{前K个结果}\}| = \text{命中数量}
  4. 除法命中数量除以相关文档总数,占比越接近 1,说明检索越“全”。
    命中数量相关文档总数=前 K 条召回率\frac{\text{命中数量}}{\text{相关文档总数}} = \text{前 K 条召回率}

AI 应用场景

财报表格问答

阶段:表格结构抽取 + 表格序列化 + 混合检索

解决的问题:传统方式需要人工在几百页 PDF 里翻找数字,慢且容易看错行;AI 先把财报里的表格检测出来、拆行拆列、序列化成带标记的文本,再结合查询做混合检索,最后让大模型基于检索到的表格作答,把“翻报表”从小时级压到秒级。

具体案例:某券商投研平台接入年报 PDF 后,分析师问“2023 年 Q3 研发费用是多少”,系统抽取出该季度利润表与费用明细表,RRF 融合后命中正确的单元格并在回答中附上出处页码。

电商图片问答

阶段:图片描述生成 + 视觉特征编码 + 向量索引检索

解决的问题:用户用自然语言描述“白色圆领短袖”,传统纯文本搜索只能命中标题含关键词的商品,漏掉标题没写清但图片符合的商品;AI 给商品图生成描述并编码视觉向量,让文字查询也能按“长相”召回商品。

具体案例:某电商导购助手用 CLIP 双塔对千万级商品图建索引,用户描述“ins 风原木色床头柜”,系统在 0.3 秒内返回图片风格最接近的前 20 件商品,点击率比纯文本搜索提升约 35%。

技术规格表查询

阶段:表格结构抽取 + 混合检索排序 + 重排序

解决的问题:客服在几十页产品手册里查“某型号支不支持 Wi-Fi 6”,传统 Ctrl+F 只能查关键词,查不到行列交叉处的结论;AI 抽取规格表的行列关系后,用 Rerank 精排定位到“型号 X 与协议 Wi-Fi 6”交叉的那个单元格。

具体案例:网络设备厂商的客服知识库接入多模态 RAG 后,客服提问“AX3000 是否支持 Mesh”,系统自动检索出规格表对应行,并给出“支持”的 92% 置信度,平均解决时长缩短一半。

Python 演示

python56 行
# 多模态检索小样:图片描述 + 表格序列化 + 混合排序import math # 1) 图片描述生成:模拟视觉语言模型给图片写"小抄"def vlm_caption(img):    captions = {        "birthday_photo": "去年奶奶生日,全家围坐在客厅吃蛋糕的合影",        "beach_photo": "海边日落风景照,画面里没有人",    }    return captions.get(img, "暂无该图片的描述") # 2) 表格序列化:把结构化表格改写成带行列标记的文本def serialize_table(headers, rows):    parts = []    for r, row in enumerate([headers] + rows, start=1):        for c, cell in enumerate(row, start=1):            parts.append("<R%dC%d>%s" % (r, c, cell))    return " ".join(parts) # 3) 简易文字打分:查询里的词命中了多少def text_score(doc, query):    qs = [w for w in query.split() if w]    if not qs:        return 0.0    hits = sum(1 for w in qs if w in doc)    return hits / len(qs) # 4) 混合排序:RRF 只关心名次,不关心原始分def rrf_fuse(channels, k=60):    scores = {}    for channel in channels:        for rank, doc in enumerate(channel, start=1):            scores[doc] = scores.get(doc, 0) + 1.0 / (k + rank)    return sorted(scores.items(), key=lambda kv: -kv[1]) def main():    corpus = {        "photo_birthday": vlm_caption("birthday_photo"),        "photo_beach": vlm_caption("beach_photo"),        "table_spend": serialize_table(            ["日期", "项目", "金额"],            [["六月一日", "卖西瓜", "288元"],             ["六月二日", "买化肥", "120元"]]),    }    query = "去年 奶奶生日 蛋糕 合影"    text_rank = sorted(        ((doc, text_score(desc, query)) for doc, desc in corpus.items()),        key=lambda x: -x[1])    print("文字通道:", [(d, round(s, 2)) for d, s in text_rank])    visual_rank = ["photo_birthday", "photo_beach", "table_spend"]    print("视觉通道:", visual_rank)    final = rrf_fuse([[d for d, _ in text_rank], visual_rank])    print("RRF最终排序:", [d for d, _ in final]) if __name__ == "__main__":    main()
目的:用一个小型可运行程序演示多模态检索的核心链路——图片生成描述、表格序列化为带行列标记的文本、双通道召回后用 RRF 融合排序。
  1. def vlm_caption(img):模拟视觉语言模型给图片生成“小抄”,这是图片描述生成的最小实现。
  2. def serialize_table(headers, rows):表格序列化函数,把二维表格变成一维文本。
  3. parts.append("<R%dC%d>%s" % (r, c, cell))给每个单元格拼上“第几行第几列”的身份标记,结构信息不丢失。
  4. def text_score(doc, query):简易文字打分:查询词在文档里命中越多,得分越高。
  5. def rrf_fuse(channels, k=60):RRF 融合函数,只看名次不看原始分。
  6. scores[doc] = scores.get(doc, 0) + 1.0 / (k + rank)名次越靠前,累加的分数贡献越大。
  7. corpus = {...}构建混合语料库:图片描述文本 + 序列化表格文本。
  8. query = "去年 奶奶生日 蛋糕 合影"模拟用户查询,带真实业务语义。
  9. visual_rank = ["photo_birthday", ...]模拟视觉通道召回结果。
  10. final = rrf_fuse([[d for d, _ in text_rank], visual_rank])把文字与视觉两条路的结果送进决赛圈,得到最终排序。

常见误区

误区我以为给图片配上一段文字描述,再让模型把图里的文字用 OCR 提取出来,图片检索就大功告成了,不需要再做别的处理。

正解文字描述容易有遗漏和歧义(比如一张“日落”照片里有条船没被描述到),OCR 也只能拿到图里印着的字,拿不到“画面像什么”。真正的图片检索还要靠视觉编码器,直接把图片像素压成特征向量,用“长相”来匹配。文字线索和视觉指纹互为补丁,缺一不可。

为何常见:因为人脑天然习惯“用语言描述事物”,容易忽略视觉特征本身的价值,以为“转成文字”就万事大吉。

误区我以为表格就是带格式的文字,用现成的文本解析把格子里的字全部倒出来,塞进文本索引就能检索。

正解直接把格子里的字倒出来会丢掉“第几行第几列”的结构。比如“收入 288 支出 520”连成一句,模型分不清哪个是收入、哪个是支出。必须先做结构抽取(检测表格、解析行列、识别表头),再序列化成带标记的文本,检索时才分得清结构。

为何常见:因为人眼一眼就能看懂表格结构,以为机器也“一眼就懂”,忽略了机器看到的只是一堆没有坐标的字符。

误区我以为把图片描述、表格文本、普通文档全部塞进同一个向量索引,就能同时处理所有类型的检索。

正解不同模态的相似度尺度不一样:文本文档之间相似度普遍偏高,图片向量之间相似度可能普遍偏低。混在一个索引里直接比分会偏袒文字。混合索引通常走“多路”:文本走倒排或文本向量,图片走视觉向量,最后在融合排序层统一排名。

为何常见:因为“一个搜索框搜所有东西”的交互体验很顺,让人误以为后端也只需要一套索引。

误区我以为既然大模型能看图,直接把用户的问题和所有文档原图都塞给它,问题就解决了。

正解大模型“看图”有上下文窗口限制,一次塞不下几十张图,而且会凭空编造细节。RAG 的价值恰恰是先检索出最相关的几张图、几个表,再让大模型基于这些证据作答,既节省窗口又减少幻觉。

为何常见:因为 demo 里大模型“看图说话”太惊艳,让人误以为它自带无限检索能力,忽略了真实场景的规模与事实性要求。

误区我理解成把文字检索得分和视觉检索得分做个加权平均,当成最终分数排序。

正解RRF 不看原始得分,只看文档在每条通道里的“名次”,用 $1/(k+\text{名次})$ 累积分数。因为不同通道的得分尺度可能完全不同(一个 0.9、一个 5.2),直接平均会被尺度大的通道带偏;名次是相对的,融合更稳。

为何常见:因为“算平均分”是最自然的组合直觉,看不到名次融合在跨尺度场景下的优势,只有亲自对比过才会体会。

面试题

Q1. 多模态 RAG 处理图片时,第一步最常做什么?

单选题 · easy

  • A.压缩图片以节省存储空间
  • B.生成描述或提取视觉特征← 正确答案
  • C.把图片转成音频文件
  • D.直接删除模糊的图片

答案:B。图片不能直接进文字检索,所以第一步要么生成“内容小抄”(描述/OCR),要么提取“数字指纹”(视觉特征),相当于给照片背面写小抄或按指纹。压缩、转音频、删图都不产生可检索的线索。

Q2. CLIP 双塔模型中“双塔”指的是?

单选题 · mid

  • A.两个完全相同的卷积层
  • B.图像编码器与文本编码器← 正确答案
  • C.两个并行的损失函数
  • D.两种不同的训练数据集

答案:B。CLIP 由图像编码器和文本编码器两个分支组成,各自把输入映射到同一个向量空间,所以叫“双塔”。它学的是图文对齐,而不是做卷积、算损失或切换数据集。

Q3. 表格序列化最主要目的是什么?

单选题 · mid

  • A.让表格在画面上更美观
  • B.让文字检索工具能处理它← 正确答案
  • C.显著减少数据存储空间
  • D.把多行表格合并成一行

答案:B。表格序列化是把结构化表格改写成带行列标记的纯文本,目的是让文字检索引擎和语言模型能“读懂”表格。美观、压缩、合并都不是核心目的。

Q4. 表格结构抽取包含哪些典型步骤?

多选题 · mid

  • A.检测表格在页面中的位置← 正确答案
  • B.解析行列与单元格归属← 正确答案
  • C.识别表头与表体的关系← 正确答案
  • D.给表格统一增加背景色

答案:ABC。结构抽取分三步走:先找到“哪里有表”,再拆成“行列网格”,最后理清“表头表体关系”。加背景色是渲染美化,与结构理解无关。

Q5. RRF 融合排序依赖什么指标?

单选题 · hard

  • A.各通道输出的原始相似度
  • B.文档在各通道的排名名次← 正确答案
  • C.文档字符数量的多少
  • D.文档发布的时间早晚

答案:B。RRF 的核心是只看“名次”:$1/(k+\text{名次})$ 累加。原始相似度尺度不一不能直接比,字符数、时间与相关性无关。就像决赛圈看排名,不看海选时的具体分数。

Q6. 跨模态统一向量空间的好处是?

单选题 · easy

  • A.文本向量和图片向量可比较← 正确答案
  • B.所有文档长度完全相同
  • C.图片文件可以完全删除
  • D.训练时间能够直接归零

答案:A。统一向量空间让文字描述和图片“落在同一个坐标系”,文字查询可以直接和图片向量比距离,实现“用话说也能找图”。它不改变文档长度、不能删图片,更不能让训练瞬间完成。

Q7. 混合检索排序常用的策略有哪些?

多选题 · hard

  • A.加权分数直接线性融合← 正确答案
  • B.基于名次的 RRF 融合← 正确答案
  • C.用重排序模型精排候选← 正确答案
  • D.按文档大小随机抽取

答案:ABC。加权分数融合和 RRF 都是“多路结果合并”的手段,Rerank 则是在融合后用更强模型精排。随机抽取不带相关性信号,不可能提升检索质量。

Q8. 图片 OCR 识别主要解决什么问题?

单选题 · mid

  • A.识别图片中的物体类别
  • B.提取图片里印刷的文字← 正确答案
  • C.压缩图片减少内存占用
  • D.给图片自动加上水印

答案:B。OCR 专门负责把图片里“印着的字”抠出来,比如发票上的金额、路牌上的地名。它解决的是“图面上有什么字”,而不是“画面里有什么物体”。

Q9. RRF 公式里常数 k 的作用是什么?

单选题 · extreme

  • A.控制召回通道的总数量
  • B.平滑名次差距并防除法异常← 正确答案
  • C.决定重排模型的参数量
  • D.调整文档索引的向量维度

答案:B。k(通常取 60)加在分母上,避免名次极端时分数爆炸,同时让第一名与第二名的分数差距不至于过大。它不控制通道数、参数量,也不影响索引维度。

Q10. 图片描述生成的常见途径有哪些?

多选题 · easy

  • A.视觉语言模型生成自然语句← 正确答案
  • B.OCR 提取图中印制文字← 正确答案
  • C.视觉编码器输出特征向量← 正确答案
  • D.直接删除图片仅保留文件名

答案:ABC。VLM 看图写话、OCR 抠图中文字、视觉编码器输出指纹向量,是三条互补的“小抄”途径。删图保留文件名等于丢弃了所有视觉信息,与描述生成背道而驰。