Skip to content

约 9 分钟阅读

18. 图文模型与多模态大模型:从 CLIP 到读懂表单截图 ​

上一篇的分类器只回答一个问题:这是哪一类票据。员工真正想问的往往更具体:“我这张请假单哪里填错了?”“这张打车票的金额和日期是多少?”回答这些问题,模型既要看懂图片,又要用自然语言作答。这一篇先讲 CLIP 怎样用对比学习把图片和文字放进同一个向量空间,再讲 LLaVA 这类视觉语言模型怎样把视觉编码器接到大语言模型上、分两个阶段训练,然后讨论文字密集的截图为什么需要更高分辨率,以及怎样为资料助手的表单截图做微调、怎样发现模型在描述图里根本没有的东西。读完应能说清多模态模型的每个部件在训练时冻结还是更新,并为一个截图问答任务设计数据和评估。

CLIP:从 4 亿对图文里学“对得上” ​

网上的图片大多附带一段文字:网页里的说明、图片的替代文本、新闻配图的标题。这些文字不是精心标注的类别,但它们和图片“说的是同一件事”。CLIP(2021)收集了 4 亿对这样的图文,训练的任务只有一个:给一批图片和一批文字,认出谁和谁是一对。

模型由两个编码器组成。图像编码器把一张图变成一个向量,文本编码器把一段文字变成同样长度的向量。一个批次有 N 对图文,两边各编码出 N 个向量,两两点积就得到一个 N×N 的相似度矩阵:

CLIP 训练时一批 N 对图文两两计算相似度,对角线上的真实配对被拉高,其余错配被压低;训练好后把类别名写成句子即可做零样本分类

对角线上的 N 个格子是真实配对,其余 N² − N 个都是错配。损失对每一行做一次交叉熵(图片 i 要在 N 段文字里选中自己的那段),对每一列再做一次(文字 j 要在 N 张图里选中自己的那张),两者平均。这类“把正例拉近、把负例推远”的训练叫对比学习,第 20 篇训练检索模型时会再次用到同一个结构。

text
相似度 s(i, j) = 图像向量 I_i 与文本向量 T_j 的余弦相似度 ÷ 温度 τ
第 i 行的损失 = −log( exp(s(i, i)) ÷ Σ_j exp(s(i, j)) )
CLIP:N = 32,768,每行 1 个正例、32,767 个负例;τ 可学习,初值 0.07

批次大小在这里直接决定任务难度:N = 4 时,从 4 段文字里选对一段很容易;N = 32,768 时,同一批里很可能有好几段都在讲“一张收据”,模型必须学到更细的差别才能选对。这也是 CLIP 训练需要大规模并行的原因之一,一个批次的相似度矩阵就有 10 亿多个格子。

零样本分类:把类别写成句子 ​

训练好的 CLIP 可以不经任何微调直接做分类。做法是把每个类别名写成一句话,比如“一张发票的照片”“一张打车票的照片”“一张餐饮小票的照片”,用文本编码器编成向量;用户上传的图片编成向量后,和这几句话分别算相似度,最高的那句就是预测类别。新增一个类别只需要多写一句话,不需要重新训练,这叫零样本分类。

CLIP 论文里,最大的模型这样在 ImageNet 上达到 76.2% 的准确率,与当年完整训练过的 ResNet-50 相当,而它一张 ImageNet 训练图都没用。句子怎么写有影响:只写类别名,不如写成“A photo of a {label}.”,后者提高了 1.3 个百分点;把 80 种不同的说法平均起来又提高约 3.5 个百分点。

所以在上一篇的报销单分类任务上,CLIP 零样本是一个几乎零成本的基线,值得先跑。它也有明显的短板,论文自己列了出来:手写数字 MNIST 只有 88%,比直接在像素上做逻辑回归还差;数图里有几个物体这类任务很弱;花卉品种、飞机型号这种细粒度分类,比在 ResNet-50 特征上训练的线性分类器低 10 个百分点以上。CLIP 学的是“图文整体对得上”,不是逐字阅读。如果发票和小票只差抬头上的几个字,CLIP 零样本多半分不清,这时在 CLIP 图像特征上做线性探针,或者回到上一篇的微调,才是正路。

LLaVA:给大语言模型装一双眼睛 ​

CLIP 只能打分,不能回答“金额是多少”。要让模型用语言回答关于图片的问题,主流做法是把一个视觉编码器接到一个现成的大语言模型上,组成视觉语言模型(VLM)。LLaVA(2023)是这种结构最常被引用的开源实现,结构由三部分组成:

LLaVA 式视觉语言模型由 CLIP 视觉编码器、投影层和大语言模型组成;第一阶段只训练投影层,第二阶段训练投影层和大语言模型,资料助手的表单微调冻结视觉编码器并在大语言模型上加 LoRA

视觉编码器沿用 CLIP 的图像编码器。LLaVA-1.5 用的是 CLIP ViT-L/14,输入 336×336,按 14×14 的 patch 切分:

text
336 / 14 = 24,24 × 24 = 576 个 patch,每个输出一个 1024 维特征

投影层把这 576 个 1024 维特征映射到大语言模型的词向量维度。LLaVA 最初用一个线性层,LLaVA-1.5 换成两层 MLP。映射之后,576 个向量在大语言模型看来就像 576 个特殊的“词”,排在问题文字前面一起输入。

大语言模型读这些图像 token 和问题,像平时一样逐个生成回答。训练损失和第 09 篇的指令微调完全一样:只对回答部分算下一个 token 的预测损失。

LLaVA 系列分两个阶段训练,每个阶段只动一部分参数。

第一阶段:特征对齐。投影层是新加的、随机初始化的,它输出的向量对大语言模型来说是乱码。这一阶段冻结视觉编码器和大语言模型,只训练投影层,数据是图片配简短描述(LLaVA-1.5 用了 55.8 万对),任务是根据图像 token 生成描述。投影层参数很少,这一步很快,目的只是让图像特征“翻译”成大语言模型能理解的输入。如果这时就放开大语言模型,乱码般的图像 token 产生的大梯度会损坏它的语言能力。

第二阶段:视觉指令微调。视觉编码器仍然冻结,投影层和大语言模型一起训练,数据换成多轮问答、细节描述、推理、视觉问答等混合指令数据(LLaVA-1.5 共 66.5 万条)。这一阶段教会模型按问题回答,而不是只会描述图片。LLaVA-1.5 的 13B 版本两阶段合计用约 120 万条公开数据,在一台 8 卡 A100 服务器上约一天训完。

视觉编码器为什么一直冻结?它已经在 4 亿对图文上学好了,用几十万条数据去改它,收益有限还容易破坏通用性。一些后来的模型会在最后阶段解冻视觉编码器,那通常需要更多、更多样的数据。

文档和表单:分辨率决定能不能看清字 ​

336×336 对“图里有几只猫”足够,对一张 A4 表单截图就太小了。一张 1240×1754 的截图缩到 336 像素宽,原来 12 像素高的字只剩约 3 像素,任何模型都读不出来。模型读不清时并不会说“看不清”,而是按语言习惯编一个看起来合理的数字。

常见的解决办法是切图(tiling):把大图切成若干块,每块单独以编码器的原生分辨率编码,再加一张缩小的全图提供整体布局。LLaVA-1.5 的高分辨率版本(LLaVA-1.5-HD)就是把图切成 224×224 的网格,最多 6 格(最大 672×448),每格单独编码后拼起来,再接上一张缩小全图。后来的许多模型改成按原图长宽比动态决定切多少块,或者让视觉编码器直接接受任意分辨率。

代价是 token 数。按 224 像素、14 像素 patch 算:

text
每格 (224 / 14)² = 16 × 16 = 256 个图像 token
6 格 + 1 张全图 = 7 × 256 = 1792 个图像 token
对比 336 单图的 576 个,约 3 倍

图像 token 越多,训练时的显存和推理时的预填充时间都按比例增加,长文档截图很快就能用掉几千个 token。训练和推理必须用同一套切图设置,否则模型在训练中学到的“一格对应多大一块区域”在推理时就对不上了。推理侧的成本见语音与多模态推理。

为资料助手的表单截图微调 ​

通用的开源视觉语言模型能读懂大多数截图,但对公司内部表单有三个典型问题:不认识内部字段名(“成本中心”“审批节点”),把相邻栏位的值张冠李戴,以及字段为空时编一个值出来。这些问题可以通过微调改善。

数据的基本单位是“图片 + 问题 + 回答”。表单截图来自内部系统,系统数据库里正好存着每张表单的真实字段值,这让构造数据变得容易:对一张截图,可以用模板生成多种问题,回答直接取数据库的值。

json
{
  "image": "leave_form_0421.png",
  "conversations": [
    {"role": "user", "content": "<image>\n这张请假单的请假类型和天数是什么?"},
    {"role": "assistant", "content": "请假类型:年假;天数:1.5 天。"},
    {"role": "user", "content": "审批人填了吗?"},
    {"role": "assistant", "content": "审批人一栏为空,尚未填写。"}
  ]
}

几个要点。一是要有“图里没有”的样本,像上面第二轮那样,字段为空或问到表单里不存在的内容时,标准回答是“没有”或“为空”;如果训练集里每个问题都有答案,模型会学到“永远给出一个值”。二是覆盖真实的截图来源:不同分辨率、手机截图和电脑截图、带聊天窗口边框的截图。三是截图里有员工姓名、工号等个人信息,训练前要脱敏或取得授权,训练数据和日志按公司数据规范保管。

训练配置上,视觉编码器冻结;大语言模型部分用 LoRA,可训练参数少、显存省、还能和其他适配器并存;投影层数据量少时冻结,数据量较大(数万条)且字段识别仍有系统性错误时再放开。损失只对回答算,做法和第 09 篇一样。几千条高质量样本通常就能让字段名和格式稳定下来;这个量级是起点估计,以留出集的字段准确率为准。

不适用的情况:如果表单本来就来自内部系统,员工截图提问时,资料助手更可靠的做法是让员工给出表单编号,通过工具直接读取结构化数据,而不是让模型从截图里认字。微调后的模型仍会认错数字,凡是金额、日期这类要进入后续流程的值,都要和系统数据或 OCR 结果交叉核对。

幻觉:说出图里没有的东西 ​

视觉语言模型最典型的失败是幻觉:回答里出现图片中根本不存在的内容。比如问“这张单子谁审批的”,审批栏明明是空的,模型回答“由部门经理张某审批”;或者把 1,280 元读成 1,820 元。原因有几层:大语言模型的语言习惯很强,“审批单”后面接“审批人”是高频模式;图像 token 里的信息不够清楚(分辨率不足、字太小)时,模型更依赖语言习惯;训练数据里“有问必有答”又强化了这种倾向。

评估时要把这类错误单独测出来,而不是只看回答读起来是否通顺:

评估项怎样测发现什么问题
字段准确率逐字段与数据库真实值精确比对,按字段分别统计哪些栏位常读错、哪类数字常颠倒
空字段与不存在字段专门构造“问的东西图里没有”的题,看模型是否回答“没有”编造倾向
存在性追问对图里有和没有的元素分别问“图中有没有印章”,比较两类的正确率是否倾向于一律回答“有”
分辨率分组按截图原始尺寸、字号分组统计小字截图上是否明显变差
通用能力回归微调前后在一组通用图文问答上对比是否学坏了通用描述能力

评测集要按表单类型和时间留出,不和训练截图重复,评测细节见第 21 篇。上线后,模型在字段准确率低的表单类型上应主动提示“请核对”,而不是直接给出确定答案。

CLIP 用 N×N 对比学习把图片和文字对齐,带来零样本分类,也留下了不擅长读字和计数的短板;LLaVA 式模型在 CLIP 编码器和大语言模型之间接一个投影层,先只训投影层对齐,再连同大语言模型做指令微调;文字密集的截图靠切图提高分辨率,代价是成倍的图像 token;为资料助手微调时冻结视觉编码器、在大语言模型上加 LoRA,数据里必须有“图里没有”的样本,评估要单独测编造。到这里都是在“看懂”图片,下一篇反过来,看模型怎样从噪声里生成一张图:19. 扩散模型。

别急,先让缓存热一下。