数据截至 (上游 commit 8c51b8dc5408)
ColPali:把整页当图片检索
本章讲什么: 为什么一页要编码成上千个向量而不是一个、MaxSim 到底在算什么、Morphik 怎么把模型跑起来。这是全项目最核心的一个机制。
1. 它要解决的小问题
传统文本嵌入把一整块文字压成一个向量。这个压缩比太狠了:6000 字符压进 768 个浮点数,细节必然丢失。对一页有图有表的文档,先 OCR 再压缩,丢两次。
有没有办法既不丢版面,又不做那么狠的压缩?
2. 思路:不压缩,改成"多对多打分"
ColPali 的两条思路缺一不可:
第一条:输入端不抽文本,直接吃图。 一页 PDF 渲染成 PNG,交给一个视觉语言模型。图表、版式、手写全部原样进入模型。
第二条:输出端不做池化。 视觉模型内部会把图片切成很多个 patch(视觉 token),每个 patch 出一个向量。常规做法是把这些向量平均成一个;ColPali 全部保留。一页就是一个 (N_patch, 128) 的矩阵。查询侧同理,一句话是 (N_token, 128)。
打分时不再是两个向量点积,而是:
对查询里的每一个 token:
在这一页的所有 patch 里,找出和它最像的那个,记下相似度
把所有 token 的最高相似度加起来 = 这一页的得分
这个打分函数叫 MaxSim(逐查询词取最大值再求和),也叫 late interaction(晚交互)——查询和文档的交互推迟到打分那一刻,而不是提前压缩掉。
图示:一次 MaxSim
查询 "扭矩 规格" 页面第 37 页
| |
| q1 = [0.2, -0.5, ...] p1 p2 p3 ... p1024
| q2 = [0.9, 0.1, ...] (每个 patch 一个 128 维向量)
v v
q1 --对 p1..p1024 逐个算相似度--> 最大值 0.83 (落在图里的"扭矩"文字上)
q2 --对 p1..p1024 逐个算相似度--> 最大值 0.71 (落在表头上)
--------
这一页的得分 = 1.54
为什么这样更强: 查询里每个词都能独立"指"到页面上的某个位置。词多的长查询自然拿到更高的分,而单向量方案里长查询会被平均稀释。