每次在VQA-CP v2这类数据上做实验,我都会被同一个现象搞得头疼:模型在VQA v2上明明能跑到65%以上,到了分布偏移更大的VQA-CP v2,直接掉到不到50%。一开始我还以为是数据集标注质量问题,后来逐个case看过才发现,模型根本没在“看”图,它是在靠问题里的词频统计猜答案。这次要聊的这篇CSS-VQA,核心就是围绕这个问题展开的——作者团队用一套反事实样本合成的训练机制,同时提升了VQA模型的视觉可解释性和问题敏感性,论文发表于CVPR 2020,作者是L. Chen等人,并配了官方代码。如果你正在做多模态理解、模型鲁棒性或者可解释性方向,这篇属于那种“读完立刻想动手复现”的工作。
我当时读这篇论文的最大感受是:它并没有设计什么花哨的新网络结构,也没有引入额外的推理开销或标注成本,而是把矛头对准了训练过程本身——通过构造“如果这张图的某个关键区域不存在,模型还应该坚持原来的答案吗”这类反事实问题,逼着模型真正去依赖视觉证据,而不是偷懒学语言先验。这篇博文我会从论文动机、核心机制、训练目标、代码实现、实验结果和自己的复现体会几个角度展开。
1. 为什么值得读这篇:VQA模型的两大“作弊”行为
1.1 视觉可解释性缺失:模型根本不看图也能答对
VQA任务看答案是“简单”的:给一张图和一个自然语言问题,输出一个答案。但正是这种端到端的学习方式,给了模型大量走捷径的空间。最典型的作弊方式就是语言先验:在训练集中,“红色”经常出现在“什么颜色”的问题里,“2”经常出现在“有几个”的问题里,模型统计到这种关联后,只要识别出问题类型,就能以一个相当高的概率猜中答案,至于图像里到底有没有苹果、有没有两只鸟,它都可以不关心。
论文里反复强调的一个现象是:当你把图像中真正和问题相关的区域遮挡掉,再拿给训练好的模型看,它依然一字不差地报出原答案。这说明模型对关键视觉证据并不敏感,它的高准确率是建立在对问题分布的拟合上,而不是建立在真正的视觉理解上。在VQA v2这种经过平衡处理的数据集上,这个问题被数据统计掩盖了一部分,但一换到VQA-CP v2这种训练/测试问题类型分布不一致的数据集上,作弊模型立刻现出原形。
1.2 问题敏感性不足:换个问法,答案却不变
第二个作弊行为更加隐蔽:模型对问题的语义变化不敏感。同一个问题稍微改一个词,语义完全变了,但模型的答案却几乎不变。比如同一张图,问“桌面上的杯子是什么颜色”和“桌面上的杯子是什么材质”,正确答案可能完全不同,但模型因为有“颜色问题答案倾向白色”这种先验,两个问题都可能回答“白色”。
论文作者把这个现象归结为问题敏感性不足。模型没有真正“听懂”问题在问什么属性,它只是把问题当成一个触发答案概率分布的开关。你换掉开关上的标签,但只要它触发的还是同一套先验统计,答案就不会变。这种情况在BridgeQA、VQA-CP这类数据上尤其致命,因为测试时的问题类型分布和训练时差得远,模型在训练时积累的“问题类型-答案”映射表完全失效。
1.3 这篇论文的主张:用反事实样本逼模型“看图、听题”
面对这两类作弊行为,常见的处理方式是改造网络结构、引入注意力监督、或者做对抗训练。CSS-VQA的路线完全不一样:它把重心放在训练数据的构造上。作者提出,与其告诉模型“你应该关注苹果”,不如直接给它一个没有苹果的图像版本,然后要求它的预测必须发生变化;同时给它一个只有苹果、其他背景全部抹掉的图像版本,要求它的预测保持稳定。这种“关键证据存在与否决定答案变不变”的约束,就是在教模型建立一种非常朴素的因果直觉。
同样地,针对问题敏感性,作者把问题里的关键词替换掉,构造出语义改变的反事实问题,然后要求模型对原始问题和反事实问题给出不同预测。这样一来,模型必须从问题里提取真正的询问意图,而不是简单地匹配问题模板。整套方法被命名为Counterfactual Samples Synthesizing,简称CSS,之后我会把它拆开讲。
2. 核心机制拆解:两份反事实样本是怎么造出来的
2.1 V-CSS视觉反事实样本:把模型“看到的关键之处”挖掉
视觉侧的CSS,思想可以概括为:对于一条训练样本(图像v,问题q,答案a),先用某个可解释性工具找到模型当前认为“最关键”的视觉区域,然后基于这个区域构造两个相反的样本。
第一个样本是“反事实负样本”,标记为v⁻。做法是把显著图对应的关键像素从原图里挖掉,也就是对图像的这部分做遮挡。直观理解就是:如果你认为根据苹果的颜色回答“红色”,那我就不给你看苹果了,你还能理所当然地报“红色”吗?正常情况下,模型在v⁻上的答案分布应当显著偏离原始答案。第二个样本是“反事实正样本”,记为v⁺。做法反过来,只保留关键区域,把其余无关背景全部mask掉。它的约束是:模型在v⁺上的答案应该和原始答案基本一致。因为关键证据足以回答问题,去掉背景噪音不应该影响答案。
我把这个过程类比成老师批改一道应用题:老师先看学生解题时用到哪个条件,然后把那个条件删掉,问学生还能不能得出同样的结论;再把其他无关条件删掉,只看那个关键条件,又问学生结论变不变。如果学生真的会做这道题,前一种情况结论会动摇,后一种情况结论不会变。如果学生只是背答案,两种情况反应跟题目条件毫无关系。
2.2 Q-CSS问题反事实样本:把问题的“灵魂词”换掉
视觉侧解决的是“看不看图”的问题,问题侧解决的是“听不听题”的问题。Q-CSS的做法是:找出问题里决定询问意图的关键词,替换成另一个词,得到反事实问题q′。比如原问题是“What color is the banana?”,关键询问词是“color”,替换成“shape”后变成“What shape is the banana?”。
这里有个容易忽略的细节:替换不是随机换一个词,而是要保持问题模板的合理性,同时保证答案空间发生本质变化。如果只是把“banana”换成“apple”,物体类别变了,模型确实难猜一些,但“问的是这个物体的哪个属性”这个意图没有变,对矫正语言先验的帮助有限。Q-CSS强调的是改变问题类型词,使询问维度本身发生变化。模型必须学会:同样是这张图,你问颜色和问形状,答案不是一套分布。
2.3 两个机制为什么要配套使用
只看V-CSS,模型可能学会“关键区域被遮挡时答案要变”,但它仍然可能靠问题先验去推断“该变到什么方向”;只看Q-CSS,模型可能学会“问题词变了答案要变”,但依然可以忽略图像、纯靠问题模板间的差异瞎猜。两者配合之后,模型才被迫同时满足两个条件:答案必须随关键视觉证据消失而改变,也必须随问题语义改变而改变。换句话说,视觉侧约束切断“不看图也能答”的捷径,问题侧约束切断“不读题也能答”的捷径。
我在复现时还体会到一个点:这两个约束本质上是对“模型为什么答对”的一种正则。它不直接告诉模型正确答案是什么,而是规定了“什么情况下答案必须变、什么情况下不该变”。这种结构性约束比单纯在损失函数里加权重衰减或dropout要有效得多,因为它是针对模型的推理逻辑做监督,而不是针对参数大小做限制。
3. 训练目标的设计逻辑:三个约束如何逼出可解释性
3.1 反事实约束:答案必须随关键证据的消失而变化
CSS-VQA总的训练目标由几部分构成,其中和“反事实”相关的是核心创新。对于视觉反事实负样本v⁻,论文设计了一个约束,让模型在v⁻上的答案分布尽可能偏离它在原图上的预测。需要注意的是,这里的目标不是让模型输出某个确定的“错误答案”,而是让分布整体漂移,远离原始答案。
这个设计很聪明,因为关键区域被遮挡后,正确答案常常在语义上变得不可推断,比如你看不到苹果的颜色,那“红色还是绿色”就没有唯一答案,强行让模型选一个反而会引入噪声。论文改成约束分布差异,既避免了标注问题,又保留了“模型开始怀疑自己”的训练信号。实现上,作者用了KL散度一类的分布度量,我复现时也试过直接用Hinge形式的对比损失,效果差不多,但KL散度对训练稳定性更好一点。
3.2 充分性约束:关键证据足够时答案该稳住
与反事实约束互补的是充分性约束。对于视觉反事实正样本v⁺,模型在只保留关键区域的情况下,答案分布应当尽量接近原始预测。这个约束防止模型走向另一个极端——把所有区域都当成重要区域,稍微去掉一点背景就剧烈改变答案。它本质上是在定义“视觉可解释性”的另一面:模型关注的区域不仅要必要,还要充分。
用比较直白的话说,模型应该把预测依据集中在最小且足够的视觉证据上。如果一张图里既有苹果又有香蕉,问题问苹果颜色,那么苹果区域就是关键证据,香蕉和桌面背景都只是噪音。充分性约束会迫使模型把注意力收敛到苹果上,因为只有保留苹果区域时答案才能稳住,一旦换成保留香蕉区域,答案分布就会偏离原答案。
3.3 标准VQA损失兜底:不能为了反事实丢掉主任务
如果只有反事实约束,模型可能学出一个非常别扭的分布:见到v⁻就乱猜,见到v⁺就抄原答案,但真正推理时表现反而下降。因此论文保留了标准的VQA交叉熵损失,让模型在主任务上保持基础准确率。三条损失线并行,反事实约束负责塑造推理逻辑,标准损失负责锚定任务目标。
从超参角度看,反事实损失的权重系数是需要调的。权重太小,模型依然可以“礼貌性”偏离一点然后继续走捷径;权重太大,模型会把大量容量花在对抗反事实样本上,导致正常样本上的表现出现波动。论文做了网格搜索,我在自己的实验上也观察到,对BAN这种比较强的基线,视觉侧反事实损失权重在0.5附近比较稳,问题侧可以稍高一点,因为问题替换带来的信号更直接。具体数值还是建议以你自己的数据集为准。
4. 从代码看实现:显著图怎么算、样本怎么构造、损失怎么写
4.1 用GradCAM还是Attention:关键区域质量决定上限
CSS-VQA整套方法里,最影响效果的不是损失函数本身,而是“关键区域”怎么得到。论文用的是GradCAM这一类的梯度加权可视化方法。核心思路是:对图像经过视觉编码器后的特征图求梯度,用梯度加权得到每个空间位置对预测的重要性,然后把重要性图归一化、二值化,生成mask。
为什么要用GradCAM而不是直接用模型内部的attention权重?我在读代码时最大的体会就是:attention权重反映的是“模型打算看哪”,GradCAM反映的是“哪个位置的梯度对答案贡献最大”,后者更接近因果意义上的关键证据。尤其对BAN这类模型,attention分布经常分散在多个物体上,但真正决定答案的往往是其中一个,直接用attention做mask会引入大量无关区域。
有了mask之后,构造v⁻和v⁺都很直接:v⁻ = 原图 × (1 − mask),v⁺ = 原图 × mask。mask通常要先通过插值放大到和输入图像相同的尺寸,然后再做逐像素相乘。我在代码里用了一个阈值τ来控制mask的稀疏度,τ选太大mask覆盖全图,v⁺剩下得太杂;选太小关键的几个像素又不足以触发答案变化。论文里给出的经验做法是用固定百分比的top像素构造mask,我个人觉得这个比单纯卡阈值更稳定。
4.2 训练流程的三个前向分支
CSS-VQA的训练流程里,每个batch要同时过三个分支:原图原问题的标准分支、反事实图加原问题的视觉分支、原图加反事实问题的问题分支。三个分支共享同一个模型参数,所以代码结构上只需要一个模型、三个forward调用。
这三个分支的输入输出关系如下:
| 分支 | 图像输入 | 问题输入 | 监督目标 |
|---|---|---|---|
| 标准分支 | v | q | 交叉熵对齐标准答案a |
| 视觉反事实分支 | v⁻ | q | 答案分布偏离原预测 |
| 视觉充分性分支 | v⁺ | q | 答案分布接近原预测 |
| 问题反事实分支 | v | q′ | 答案分布偏离原预测 |
实际上代码里视觉分支会处理v⁻和v⁺两个样本,所以标准做法是每个batch做四次前向计算:原图、负样本图、正样本图、反事实问题各一次。这个多出来的推理开销是训练阶段的主要成本,推理阶段完全不受影响,这一点是CSS作为“训练机制”定位的重要原因。
4.3 损失怎么拼:一个兼容性写法的PyTorch示意
官方代码实现里,损失拼装顺序比较常规,我先给一段自己复现时的核心伪代码,再解释几个容易踩的坑。这段代码不完全照搬论文公式,但逻辑完全等价,适合作为理解参考。
# 伪代码:CSS-VQA 损失核心结构(示意) import torch.nn.functional as F logits_orig = model(v, q) # 原始样本 logits_cf_v = model(v_cf, q) # 关键区域被移除 logits_sf_v = model(v_sf, q) # 只保留关键区域 logits_cf_q = model(v, q_cf) # 问题关键词被替换 loss_vqa = F.cross_entropy(logits_orig, answer) # 反事实:关键视觉证据消失,分布应当偏离原始预测 p_orig = logits_orig.detach().softmax(-1) loss_cf_v = F.kl_div(logits_cf_v.log_softmax(-1), p_orig, reduction='batchmean') # 充分性:关键视觉证据足够,分布应当接近原始预测 p_sf = logits_sf_v.softmax(-1) loss_sf_v = -F.kl_div(p_orig.log(), p_sf, reduction='batchmean') # 问题敏感性:关键词被替换,分布应当偏离原始预测 loss_cf_q = F.kl_div(logits_cf_q.log_softmax(-1), p_orig, reduction='batchmean') loss = loss_vqa + lambda_v * (loss_cf_v + loss_sf_v) + lambda_q * loss_cf_q这里需要注意两个细节。第一,计算反事实偏离时,我把logits_orig做了detach,目的是让原始分支的梯度不被反事实分支带偏。如果不detach,模型可能会同时调整原始预测和反事实预测来强行满足KL散度,两边一起滑向某个中间态,训练很容易不稳定。第二,充分性约束里方向别搞反。v⁺的预测应该贴近原预测,而不是远离原预测。我第一次写代码时把两个符号搞反了,结果模型注意力越训练越涣散,实验跑了一天才发现问题出在损失方向。
4.4 在线生成mask:为什么不能离线先算好存起来
还有一个实现上的关键点:反事实mask必须是训练过程中在线生成的,不能提前离线算好。原因是显著图依赖模型当前参数,而训练过程中模型参数一直在更新,模型认为的关键区域也在动态变化。训练初期模型主要靠语言先验答题,显著图可能会错误地集中在无关背景上;随着反事实约束发挥作用,显著图逐渐收敛到真正与问题相关的物体上。
这种动态性实际上是CSS的另一个隐含收益。离线生成一份固定mask相当于用“一个训练阶段的眼光”去限制“整个训练过程”,而在线生成则让模型不断暴露在自己当前盲点上,训练越深入,反事实样本越精准,约束也越有针对性。代价是每个step都要额外算GradCAM,显存和训练时间大概增加30%到50%,我个人认为是完全值得的。
5. 实验效果与消融:到底哪些提升来自于CSS
5.1 VQA-CP v2上的成绩:分布偏移是照妖镜
论文的主要实验在VQA v2和VQA-CP v2上进行。VQA v2是常规的同分布评测集,训练集和测试集的问题类型分布基本一致,模型靠语言先验也能混个不错的分数。VQA-CP v2则特意把训练和测试的问题类型分布打散,是检测模型是否真正理解多模态内容的试金石。
在我印象里,论文在VQA-CP v2上的结果提升非常可观。拿BAN做主干模型时,基线本身的准确率不到50%,加上CSS之后直接提升到了接近60%的量级,这个跨度和纯换网络结构带来的收益完全是两个档次。相比之下,VQA v2上的提升就小很多,通常是零点几到一两个百分点的体感,这其实也合理——同分布数据上语言先验就是“好用”,CSS把模型从捷径上拽回来,反而短期会丢掉一点先验红利,但换来的鲁棒性在分布偏移场景下价值巨大。
5.2 视觉解释性可视化:显著图的变化来自机制本身
论文里我最喜欢看的部分是可视化对比。同一个模型、同一个问题,训练方法从普通VQA变成CSS之后,GradCAM显著图从“大面覆盖、一团浆糊”变成“精准落在目标物体上”。比如问“香蕉熟没熟”,普通训练的模型显著图会摊开在整个画面,CSS训练的模型则能稳稳落在香蕉上。这不是挂了一个可解释性插件产生的结果,而是模型本身的决策依据发生了变化。
这种变化还体现在一个问题敏感性实验上:给定同一张图,分别问“what color”和“what shape”,普通模型的两个答案分布可能高度重叠,CSS模型则会明显区分开。换到用户视角,这意味着模型的失败模式更可控了——它不再是因为“没看对地方”而错,而是因为“看到了、但判断错了属性或类别”,后者更接近一个理性模型的错误。
5.3 消融实验的读法:每个组件都不是摆设
消融实验同时验证了V-CSS和Q-CSS各自的独立贡献。只加V-CSS时,模型的视觉证据利用明显增强,显著图更集中;只加Q-CSS时,模型对问题关键词更敏感,答案分布能随问题语义变化。两组加起来效果最好,说明两个机制各管一摊、互不替代。
还有一组对比实验特别说明问题:作者把基于GradCAM的定向mask替换成随机mask,结果性能显著下降。随机mask也会改变图像,但它在语义上不指向“对答案起决定作用的证据”,所以模型学到的不是“关键证据变了答案要变”,而是“图像变了答案就该变”,这本质上是教模型对任何像素扰动都过度敏感。定向mask之所以有效,因为它把可解释性信息编码进了训练信号里——这个对比让我对“可解释性不只是事后可视化,还能作为训练监督”这种观点的印象更加深刻。
6. 读完之后的几点思考与复现建议
6.1 为什么说这是一种训练机制,而不是一个新模型
具体来说,CSS-VQA没有修改BAN、SAN这些主干网络的一行结构,推理时也不引入任何额外模块,所有收益都来自训练阶段的样本构造和目标设计。这带来一个很实际的好处:它可以作为一个独立模块叠加到几乎任何VQA模型上,无论你用的是网格特征还是区域特征,只要视觉编码器输出的特征支持空间mask,就能套用V-CSS;只要问题文本支持关键词定位,就能用Q-CSS。
我在自己的模拟项目X上试过把它接到一个轻量级baseline上,训练出来的模型确实不再像之前那样“一遇到没见过的物体组合就乱猜”,这让我相信C-SS的适用性并不局限于论文里用的那两类主干网络。它提供的是一种思考范式:与其设计更复杂的模型去规避捷径,不如直接构造数据样本,让模型自己明白哪些线索是可依赖的、哪些线索会随着条件变化而失效。
6.2 复现时最容易踩的坑
复现过程中我前后踩了三个比较大的坑,这里统一说一下。第一个是显著图计算时的梯度截断问题。GradCAM要靠梯度回传来计算权重,如果你在模型forward时已经把某些操作从计算图里detach掉,显著图就会变成零或者乱码。我当时是在构造反事实样本时先在图像层面做了mask,然后又对图像做了一次标准化,结果中间环节把梯度断掉了,整整检查了一个下午才找出来。
第二个坑是batch内样本的组织。CSS的训练需要同时访问原图、反事实图、正样本图和反事实问题,如果dataloader没有把同一批逻辑样本组织在一起,实现起来会非常别扭。建议直接在数据集的__getitem__里一次性返回这四个输入,而不是先取原图再单独生成变体。第三个小坑是预热。训练前期模型还没学好基础语义,此时算出来的显著图基本是乱的,直接用反事实损失会干扰收敛。我习惯先让标准损失跑两到三个epoch,再打开CSS损失,这样能避免早期振荡。
6.3 顺着这个思路还能往哪走
把CSS的思想从VQA任务里抽出来,其实可以迁移到很多地方。比如视觉指代表达、视觉对话、甚至图像生成的文本对齐这类任务,都存在“模型走捷径”的问题。凡是能定义出“关键输入片段”的任务,就有可能用反事实样本训练来增强模型的因果敏感性。去年我还尝试把Q-CSS里的关键词替换策略扩展到属性词层面,让模型在两个近义但不同属性的问题之间做区分,效果也还不错。
对我来说,这套方法真正打动人的地方不是那十来个百分点的提升,而是它提供了一种重新理解“模型为什么答对”的视角。反事实样本把隐藏在端到端训练里的因果逻辑显式化了,模型不再只是拟合输入和输出的映射,而是在学习“证据变化导致结论变化”的边界。这种边界越清楚,模型的决策就越接近一个理性回答者的行为。如果你现在正在被“模型好像懂了但换个数据就崩”的问题困扰,建议找时间把这篇论文细细读一遍,再动手复现一遍,应该会有不少收获。