开头部分:
我把这套多模态模型从预训练一路搭到推理能出结果的时候,遇到一个非常尴尬的局面:输入一张图,问模型“这是什么”,它能把“猫”“狗”“桌子”这样的名词说对,但让它描述画面里的位置关系、颜色细节、动作逻辑,它就明显开始胡编。不是模型不会看,而是它“看得懂、说不清”。这个系列讲到这里,前几节我们把视觉编码、对比学习、模态对齐都拆过一遍,这节就专门来聊怎么解决“说得清”的问题——视觉表达SFT(Visual Expression SFT)。
先说结论:视觉表达SFT本质上是在预训练多模态模型基础上,用高质量图文指令数据做一次有监督微调,让模型从“感知图像特征”进一步学会“把感知结果组织成自然语言表达”。它解决的不是“模型看不看得懂图像”,而是“模型怎么把看懂的东西有条理地讲出来”。这篇文章我会直接从预训练模型的局限讲起,拆解数据构造、训练配置、评测方法,中间穿插几个我自己踩过的坑,希望能给正在做多模态微调的读者一些实际参考。
1. 为什么预训练之后还得再来一轮SFT:从“看得出”到“说得出”
1.1 预训练模型的两种能力状态
我们假设你已经有了一个多模态底座:视觉编码器把图像映射成向量序列,语言模型部分负责解码生成文本。预训练阶段通常做过对比学习、图文匹配、图像文本联合建模这些任务,目标函数设计得也很合理。但这时候你把整个模型直接拿来对话或者做详细图像描述,结果往往是——名词对、动词勉强,一到长句子就翻车。
原因不复杂。预训练阶段的训练目标主要围绕特征对齐和粗粒度语义匹配,模型有很强的图像表征能力,但文本生成侧并没有专门学过“针对一张图说出一段结构完整、细粒度的描述文字”。换句话说,模型知道图像中有一个“人、狗、草地”,但它不知道人类在描述一张图时,习惯先说场景主体、再说位置关系、接着补充动作和环境线索。语言表达这件事有其自身规律,预训练目标不负责教这个。
这一阶段我习惯把它叫作“感知饱和、表达欠缺”。感知饱和体现在:图像分类、检索这类任务上,模型已经能跑出不错的指标;表达欠缺体现在:开放生成任务里,句子短、词汇基础、缺少修饰、空间关系混乱。
1.2 视觉表达SFT究竟改变的是什么
SFT全称是Supervised Fine-Tuning,放在多模态大模型的语境下,它和纯文本SFT的核心差异在于:输入从文本变成了图文对,而目标输出仍然是文本。视觉表达SFT就是要用一批人工精心标注的图文问答数据,把模型在预训练阶段学到的那种“图像特征空间”和新增加的自然语言表达空间连通起来。
我这里用一个直白的类比:预训练阶段相当于给模型看了一万张照片,它知道“哦,这是山、这是树、这是天空”,但你要让它把照片里“山在远处、树在左侧、天空云层很厚”这样的关系组织成一段像人写的描述,它没有样本可以参考。SFT就是给模型喂大量这种“看图说话”的范本,让它在监督信号下调整语言生成的风格和结构。
值得注意的一点是:视觉表达SFT不是重新训练模型学图像特征,大多数情况下它是在微调语言模型的输出侧,同时以较低学习率维持视觉编码器的已有表征。这么设计就是为了防止灾难性遗忘——图像特征本来已经学得很好了,不要为了学会说话把看图的底层能力又搞坏。
1.3 SFT在整套多模态流程里的位置
如果你对整个多模态训练范式有了解,可能会知道常见的流程是:预训练(对比学习、特征对齐)→ 指令微调(SFT,让模型听话)→ 偏好对齐(RLHF/DPO,让模型说得符合人类偏好)。视觉表达SFT处于中间位置,它既不是最笨重的预训练阶段,也不是最后的打磨阶段,但它的成败直接影响后面所有环节的上限。
前面预训练做得好,只是给了你一个“会看图”的模型;SFT做得好,模型才会“看图说话”;后面的偏好优化做得再好,也只能在SFT建立的表达空间里做微调。如果SFT阶段生成的语言都是干巴巴的短语,偏好优化也变不出长句。所以我把这个阶段看成是“跨越感知与认知鸿沟”的关键工程节点,值得单独花一整节来拆解。
2. 视觉表达SFT的数据建构:一句话讲明白,数据就是天花板
2.1 三类核心数据:描述型、推理型、交互型
很多拿到视觉表达SFT的代码仓库就直接用现成数据集开始跑了,我也不反对,但要说清楚一件事:数据质量决定了SFT上限。我把做这类微调常用到的数据分成三类,实际项目中要组合着用。
描述型数据是最基础的一类,样本形态是“一张图 + 一段整体描述”,要求模型做到:主体是什么、背景是什么、主体在做什么、颜色/纹理/光影怎么交代。这类数据负责教模型最基本的表达框架。数量占比可以高一些,我一般放到60%左右。
推理型数据是进阶,样本形态是“一张图 + 一个需要多步推断的问题”,比如“图里的人正在做什么?从他的穿着推断可能是什么天气”。这类数据迫使模型在视觉信息基础上组织因果逻辑链,对激活语言模型的推理能力很有帮助。占比我一般控制在20%~30%。
交互型数据指多轮对话,前一轮模型说过的话可以作为后一轮的上下文。它更接近真实使用场景,比如用户先问“这张图里有什么”,再追问“右下角那个红色物体是什么”。如果没有这类数据,模型很容易在两轮对话中“失忆”。
2.2 数据配比、去重和难度梯度
数据不是越多越好,配比不对反而会把模型带偏。我在一次实验中做过对比:纯描述型数据占比90%的模型,描述能力很强,但问它“图中有几个人分别穿什么颜色”这种需要细粒度抽取的问题,它就开始含糊其辞。原因是分母里就没有足够多需要细粒度定位的样本。
建议先定一个大原则:通用描述:细粒度问答:推断逻辑 = 5:3:2。这个比例适合大多数通用视觉表达需求。如果场景偏向文档理解、图表理解,推理型数据可以继续上调;如果场景偏向具身智能里的场景描述,描述型比例还要再高。
去重这步很多人会漏掉。多模态SFT数据经常来自多个来源,同一张图的不同描述可能高度相似,如果不做清洗,模型会反复强化同一种句式,导致生成多样性下降。我的做法是算一下图像哈希和图文的embedding相似度,超过阈值的样本只留一条。
难度梯度这个点更重要。我见过一上来就给模型刷超高难度数据的,比如要求回答“画面中三个物体的遮挡关系”,如果模型连基本主体描述都不稳,这种数据训完只会让生成变乱。合理的策略是:简单描述样本在前,细粒度问答在后,多步推理放在最后。训练过程中模型先建立基础表达能力,再逐步接触复杂指令。
2.3 数据格式与模板设计
现在很多开源底座都采用统一的对话格式,视觉表达SFT的数据也基本围绕这个思路组织。下面是一个典型的样本结构,我简化过:
{ "image_path": "sample_0423.jpg", "conversations": [ { "role": "user", "content": "请描述这张图片的主要内容。" }, { "role": "assistant", "content": "画面拍摄于傍晚的街道,主体是一位骑自行车的中年人,身穿灰色外套,车后座上绑着白色纸箱。背景有若干店铺招牌和两棵行道树,地面湿漉漉的,说明刚下过小雨。整体氛围偏冷色调,但店铺灯光提供了暖色点缀。" } ] }这个格式本身不难,但里面有两处细节容易被忽略。第一是问题模板的多样性。如果你全部用“请描述这张图片”作为唯一提问,SFT会过拟合到这种问法上,用户换说“图里有什么”“帮我讲讲这张照片”就失效。我一般会给同样一张图配上多种提问方式,比如“详细说说”“图里发生了什么”“用一段话描述画面”。
第二是回答的结构一致性。同一批次的数据里,回答风格要尽量稳定,比如都遵循“整体场景→主体细节→背景环境→氛围总结”的顺序。很多人以为模型会自己总结风格,实际不是,只有当数据中呈现出高度一致的表达范式时,模型才学得会这种叙事结构。
需要注意:不要图省事把回答写得太长。回答长度和图像内容要匹配。一张简单的图配两百字长描述,模型根本学不会从图中抽这么多信息,最后只能靠语言模型的幻觉硬编。我的一般经验是:简单图60~100字,复杂图150~250字。
3. 训练配置与收敛控制:我最常用的一组参数和判断依据
3.1 超参配置与硬件约束
视觉表达SFT的训练和纯文本SFT在超参上有些差异。主要原因是输入侧多了图像,batch size不能盲目加大,梯度更新也要更小心。我给一组自己实测比较稳的起始参数,具体还得根据显存和数据量微调:
| 超参数 | 推荐值 | 说明 |
|---|---|---|
| 学习率 | 1e-5 ~ 3e-5 | 低于纯文本SFT,为了保住视觉编码器已有表征 |
| batch size | 64 ~ 128 | 按图像分辨率调整,分辨率高就调小 |
| 训练轮数 | 2 ~ 4 | 不宜过多,SFT阶段很容易过拟合 |
| 图像分辨率 | 224 ~ 336 | 细粒度描述任务建议往高分辨率走 |
| 优化器 | AdamW | 和预训练保持一致更稳妥 |
| 调度器 | cosine decay | 最后阶段warmup比例控制在3%以内 |
| LoRA rank | 32 ~ 64 | 纯LoRA微调时推荐,全参微调则不需要 |
需要特别说明学习率的问题。很多第一次做多模态SFT的读者会把纯文本微调的习惯带过来,学习率设个5e-5甚至1e-4。在纯文本大模型上这可能没什么问题,但多模态底座里视觉编码器在预训练阶段用了大量的对比学习信号,这部分特征的脆弱性远超你的预期。高学习率微调语言侧时,视觉编码器也会被反向传播波及,很容易出现“句子变流畅了,但看图的精度掉了”的情况。我通常的做法是,如果做全参微调,学习率压到1e-5;如果做LoRA,视觉编码器完全不更新,只在语言模型部分挂LoRA,学习率可以稍微放宽到2e-5。
3.2 选择全参微调还是LoRA
全参微调的上限更高,语言表达的自然度更好,但它有三个问题:显存占用大、训练时间长、灾难性遗忘风险高。LoRA的方案则是冻结原来的权重,只训练分解出来的低秩矩阵,显存占用和过拟合风险都会明显降低。
我的建议是看数据量决定。如果你手上高质量表达样本不足5万条,优先考虑LoRA。数据量不足时全参微调非常容易把已经训练好的多模态对齐直接冲掉。数据量超过10万条、且你有充足显存做全参,可以尝试解冻语言模型部分做全参微调,视觉塔继续冻结。这种做法在细粒度描述任务里效果很显著,生成句子的信息密度会明显高于纯LoRA方案。
另外一个很多人忽视的配置是是否解冻视觉编码器。我见过一部分工作会在SFT阶段让视觉编码器跟着训练,理由是让图像特征更适配语言输出。理论上说得通,实际用下来风险很高,特别是CLIP类的对比学习表征在SFT阶段被更新后,模型容易开始“丢细节”,明明图像里有三个物体,它只愿意描述最大的那个。如果没有充足的实验验证,我建议默认冻结视觉编码器。
3.3 loss曲线到底应该怎么读
训练过程中,最常看到的loss变化是前几百步快速下降,然后进入平台期。视觉表达SFT的起始loss通常比纯文本SFT高不少,因为图像特征经过视觉塔映射后,语言模型一开始很难流畅组织这些新输入。看到一个高起始loss不用慌,重点是看下降趋势是不是平滑。
我自己判断收敛不完全看loss绝对值,而是配合生成样例一起看。每500步挑20条没训练过的样本做推理,直接看输出文本质量。loss在降但生成文本开始“发飘”——描述内容开始出现图片里根本没有的对象——这基本可以判断已经过拟合了,此时即便loss还在降,也应该提前停止。反过来,如果loss下降缓慢但生成文本一直在变好,说明模型正在稳定提取视觉特征,可以继续训下去。
这里还有一个容易踩的盲区:训练loss下降有时不是因为模型学会了看图,而是因为它学会了“绕开图像”。比如一个数据集的回答里60%的句子都以“图片展示”开头,模型会本能地生成先验句式,甚至不看图也能蒙对一部分。判断模型是不是真的在“看”图,需要专门构造混淆样本——图像内容和回答语义不匹配的样本,如果模型还能答对,才说明它真的在用图像特征。
4. 三个真实的训练事故:排查链路完整复盘
4.1 事故一:loss一直在降,生成却一本正经地胡说八道
这个事故是我在早期做拼接式多模态底座时遇到的。训练到第3个epoch,loss从1.8降到1.3,看起来很健康。但我拿验证集的图去测,输入一只柯基犬的侧面照,模型回答的是“画面中是一只金毛犬,毛发呈金黄色,趴在草地上”。柯基和金毛的差异那么明显,模型为什么输出了空气里的答案?
排查过程我按三步走。第一步检查是不是数据格式错了:我扒了训练样本,发现验证的时候我用的图片没有走视觉编码器的预处理通道,直接以原始像素分辨率喂了进去,而训练时图像过了resize和归一化。特征分布不一致,模型等于在拿训练分布里的模糊记忆作答。第二步检查loss是不是被语言模型的先验项主导了:我尝试把视觉token整体mask掉,只让模型用文本token预测,发现loss仍然能降到1.5左右。这个实验非常关键,它说明模型很大程度在靠文本的先验语序做预测,图像信息并没有真正进入生成路径。第三步回到模型结构上检查视觉塔输出的特征有没有被实际拼接到语言模型的输入embedding里。检查之后发现拼接位置确实加了,但视觉特征在进入语言模型前没有做线性映射,维度对不上导致大部分特征在计算注意力时被压成了一个弱信号。
这个事故给我的教训很直接:loss好看什么都说明不了。如果你发现loss正常但生成内容明显与输入图像不符,优先做这两个小实验——冻结视觉特征看loss下降幅度、构造图文负样本看模型是否依赖视觉信息。这套排查链路我现在每次训练都会跑一遍。
4.2 事故二:细粒度描述灾难性遗忘,模型突然不会数数了
第二次事故出现在我上一轮数据配比调整之后。当时我把推理型数据的占比提到了40%,训练后模型的逻辑推断能力确实有提升——至少在某个专门构造的图表推理测试集上是这样的。但一个问题出现了:让模型回答“图中有几个人”“几个红球”这样的计数问题时,它给出的数字和自己的描述对不上。训练前这部分能力虽然不算强,但至少稳定,训练完反而崩了。
追根溯源,我发现新加入的推理型数据中,大量样本的答案是“推断出一条结论”而不是“数出精确数量”。语言模型在学习的时候会去抓数据中的统计规律,如果大多数样本都不需要精确计数,它在生成的时候就会把计数一类token的概率压低。这不是模型学会了错误的知识,而是学会了“忽略”这类视觉细粒度信息。
修复方案不是把推理型数据砍掉,而是引入了足够多的计数类样本,并且在回答模板上做了统一处理:凡是涉及数量信息的问题,回答里必须先用一句“图中一共有X个……”开头。这种模板强约束让模型重新学会了在生成流程中优先提取数量特征。从那以后,我的配比里始终保留5%~10%的计数细粒度样本,宁可牺牲一点推理泛化,也不能丢掉基础的数量感知。
4.3 事故三:直接复用了纯文本SFT数据流程的教训
多模态SFT和纯文本SFT的代码流程看起来很像,但有一个细节非常容易翻车——Text Token的padding策略。纯文本SFT中,一个batch里的不同样本长度不同,通常会在较短序列的右侧做padding,让它们对齐成统一长度。但多模态数据里,图像特征序列和文本token序列是拼接在一起的,右侧padding会导致批量训练时视觉特征的起始位置、序列长度在不同样本之间不一致,注意力机制会学到一种非常奇怪的“位置偏移偏好”。
这个问题的排查难度很高,因为loss曲线看起来一切正常,你甚至看不出哪里有bug。我是怎么发现的?有一次我单独把某个batch的attention矩阵抽出来可视化,发现模型对图像区域token的attention权重极低,而对padding位置的attention很高。这显然不对,但它又没有完全击垮训练结果,只是让模型对图像信息的利用效率一直上不去。
解决方案很简单:不要把padding放在右侧,而是改用左侧padding。这样在拼接图像特征和文本特征时,所有样本中图像特征的起始位置是一致的,语言模型在计算注意力时不会被padding位置干扰。这个问题我想重点提醒读者,因为网上很多开源的训练脚本默认继承纯文本代码的padding策略,一不注意就会带进来。
5. 评测警察的日常:怎么判断SFT到底训没训好
5.1 传统指标为什么会失真
视觉表达SFT做完之后最麻烦的事情不是训练,而是评估。CIDEr、BLEU、ROUGE这类指标在机器翻译和图像描述领域用了很多年,但到了开放生成的多模态大模型上,它们会严重失真。我用一个例子就能说明白:两个回答,一个是“一只狗在草地上奔跑”,另一个是“一只棕色短毛犬在开阔的草地上高速奔跑,四条腿离地瞬间被拍下”,从信息量上看后者远优于前者,但BLEU算出来的分数两者可能差不多,甚至因为前者的n-gram撞上了参考句而拿到更高分。
所以在SFT阶段,我基本不依赖自动指标做最终决策,而是把它们当成“感冒时的体温计”——看个大概趋势而已。真正的评测要靠人工或者更强模型的agent式评价。
5.2 我自己用的三层评测法
我的评估体系分成三层:第一层是要素充分度,第二层是结构合理性,第三层是事实一致性。三层分开打分,每层5分制。
要素充分度看的是回答里是否覆盖了图像里最核心的信息项。我会先给自己看图像,列出5个必须出现的核心要素——主体、动作、位置关系、颜色、背景,然后看模型回答覆盖了几个。覆盖4个及以上算合格,低于3个说明模型没在认真看图。
结构合理性看语言组织有没有叙事逻辑。好的视觉表达应该遵循一个稳定的信息递进顺序,比如主体先行、细节补充、背景收尾。如果回答一会儿说背景、一会儿跳回主体、一会儿又扯出个不存在的物体,说明语言组织能力不行。
事实一致性是最严格的一关,我会标注出回答里有哪些图像中不存在或矛盾的细节。经常出现的情况是模型为了描述更丰富而自己“脑补”新对象,比如图里根本没有树,它会在回答里写“树下蹲着一只猫”。事实一致性得分低的模型,哪怕要素充分度再高也不能用,它会严重影响产品体验。
5.3 错误分类比总分更重要
我后来意识到,只给三项打分不够,还要把错误类型分门别类记录下来。常见的错误类型包括:对象错位(图像里的物体被挪到另一个位置)、属性错置(把颜色、材质等属性套到错误对象上)、数量错误、关系颠倒、超图幻觉(编造不存在的对象)。分类统计做完之后,你往往能反过来推断训练数据的缺陷。
比如我发现一个阶段的模型频繁出现“属性错置”,回头查数据发现很多标注员在写“红衣服的男人”时,并不代表图像里真的有一件红衣服,有时只是背景里有面红旗。这种标注噪声在训练里被模型捕获并放大了,于是生成时它也照着错。数据侧修完,错误比例立刻下来不少。
6. 视觉表达SFT做完之后,还能再往前推一步
6.1 和偏好优化的衔接方法
SFT做完不等于模型最终可用,我提到过后面通常还有偏好优化。视觉表达SFT阶段的模型经常会出现一种表现:描述很完整、事实也对,但用户就是觉得“有点死板”“像在背课文”。这不是能力问题,是表达风格不够顺滑,适合用偏好优化来调。
衔接的核心是把SFT模型当成偏好优化阶段的初始化。如果你在做DPO这类从离线样本学习的方法,负样本应该从SFT模型自己生成,而不是拿手工写的错误文本来凑。这样采样的负例更接近模型真实出错的分布,偏好学习效率会高很多。
6.2 一个常见争议:要不要在SFT阶段引入LLM辅助打分
有个经常被讨论的问题:能不能用更强的语言模型来给视觉表达样本打分,然后把这个分值当成训练信号。我的观点是,可以辅助筛选数据,但不建议把它当成硬性标注。强模型打分有时倾向于语言流畅性而非视觉事实,如果图像里有个很冷门但客观存在的物体,强模型可能没认出来反而判定模型描述错误,引入噪声。我更喜欢把LLM打分放在人工标注之前做粗筛,人工再做关键样本的二审。
6.3 从“表达”走向“细粒度理解”
这节的标题是视觉表达SFT,但表达的深层还是理解。当模型的描述能做到要素充分、结构合理、事实一致之后,下一步可以做的是迈向细粒度多轮交互——用户指向图上某个区域问“这里是什么”,模型需要结合区域坐标和全局语境回答。这个方向对数据的要求又不同,更强调视觉定位和文本生成的协同。不过核心方法论和视觉表达SFT是一致的:数据质量优先,训练配置稳扎稳打,评测永远多看错误类型而不是总分。
大概就是这些。最后再分享一个我一直在做的小习惯:每次SFT训练结束后,我会保留最后一轮的checkpoint之外,再额外存一份训练中段表现最好的模型权重,同时在训练日志里记录下当时生成样例的完整文本。这个习惯帮我解决过不少后续评测阶段的定位问题——因为SFT的loss和效果并不总是同向变化,手上有多个候选版本,对比评测时就不会被单一checkpoint的表现误导。如果你准备开始做视觉表达SFT,我的建议是别急着调模型结构,先把数据、模板、评测这三件事理清楚,再动手训练,你会少走很多弯路。