VQA模型高分却未看图?ReKey稀疏微调让模型真正用上视觉证据
2026/9/20 3:27:19 网站建设 项目流程

VQA 模型在榜单上拿高分,不一定说明模型真的在“看图”。VQA(Visual Question Answering,视觉问答)要同时理解图像和问题,输出一个答案。但很多模型会在训练集里学到答案频率,只要问题中出现“有没有”“什么颜色”等词,就输出高频答案,图像只被当成可有可无的装饰。这种“记答案”的行为会让分数好看,却不能泛化到真实场景。ReKey 是一种试图改变这种局面的技术思路:不重新训练整个模型,而是定位真正决定答案的那一小块证据,只更新与这一小块相关的参数,从而把模型的注意力拉回图像本身。接下来的内容会从 VQA 为什么会出现“高分但没在看图”的现象讲起,拆解 ReKey 的核心机制,再给出一个可以自己动手的最小实验流程,最后补充验证指标、常见问题和生产落地建议。

1. VQA 的高分为什么不可信:先区分“看图”和“记答案”

1.1 VQA 任务的定义,以及它为什么容易走捷径

VQA 的输入既有图像又有文本,输出是答案。这个任务天然有两条解题路径:一条是真正理解图像中的对象、关系、颜色、动作,再回答问题;另一条是仅从问题文本中推断答案,甚至依赖训练集的分布统计。

例如,VQA 数据集中“这个物体是什么颜色?”这类问题,训练样本里“红色”出现的频率可能远高于其他颜色。模型如果把“颜色=红色”作为一个强先验,就能在不少样本上得到正确答案,并且不需要真正区分红色物体。这不只是训练技巧问题,而是数据偏差与模型归纳偏向共同作用的结果。

VQA 的评测通常只看整体准确率,一个模型只要在大部分样本上猜中高频答案,分数就会很高。于是出现了一个尴尬的现象:模型在榜单上排名靠前,但换一组分布不同的图片后,性能立刻崩塌。这也是为什么“高分”和“真正在看图”不能直接画等号。

1.2 一个微型例子:反事实样本立刻暴露“记答案”

假设训练集中大量出现这类样本:

  • 问题:图里的香蕉是什么颜色?
  • 答案:黄色。

模型很容易学会一个捷径:看到“香蕉”和“颜色”,就输出“黄色”。如果给出一张已经腐烂发黑的香蕉图片,正确答案是“黑色”或“棕色”,依赖记忆的模型仍然可能输出“黄色”。

这里要判断模型是在看图还是记答案,不需要特别复杂的指标,只要对图像做反事实修改,看答案是否跟着变化。如果答案不变,说明模型并没有真正使用图像证据。这个小例子说明,准确率只能衡量“在当前数据集上是否答对”,不能衡量“是否在正确推理”。

1.3 只看准确率的盲区:为什么需要归因和定位

为了判断模型是否在记忆,常见做法是看注意力图或归因分数:

  • 注意力图反映模型在回答问题时看的是图像哪个区域。
  • 归因分数反映每个输入 token 或像素对最终答案的贡献。
  • 中间层激活的梯度可以反映哪些通道参与了最终决策。

只靠准确率无法判断模型是否“看对地方”。这也是 ReKey 这一类方法的前提:如果想让模型真正看图,先要知道模型当前把注意力放在了哪里,然后只修改决定答案的关键通道。

2. 模型为什么更容易“记答案”:语言先验、注意力塌缩与长尾分布

2.1 语言先验:问题文本本身就是一条捷径

VQA 模型可以从问题文本中学到很强的先验。例如:

问题模板训练集可能的高频答案风险
图里有没有__?没有忽略小目标
这是什么颜色?红色 / 黄色与具体对象无关
图里有几只__?2忽视数量变化
这个人在这做什么?跑步依赖动作模板

当问题里出现常见的动词、名词、疑问词组合时,模型可以不用认真编码图像,只凭文本模式就估计一个合理答案。如果这个估计在训练集上频繁命中,损失函数不会强制模型去看图像,于是语言先验被保留下来。

2.2 注意力塌缩:视觉证据没有被真正使用

在 Transformer 架构里,跨模态注意力会计算问题 token 与图像 token 的相似度。理想情况下,问题中的“香蕉”应该和图像中的香蕉区域形成高注意力权重。但训练过程中,如果文本先验已经足够降损失,模型就没有动力去优化图像注意力权重。

时间一长,图像 token 的注意力权重可能呈现两种异常:

  • 均匀分布,所有图像区域都被低权重扫过;
  • 集中于背景、文字说明或高频对象的区域,而不是问题真正指向的区域。

这就是“注意力塌缩”。视觉编码器可能已经提取了丰富特征,但后续层根本不使用它们,模型本质上退化成一个文本分类器。

2.3 长尾答案分布:低频正确答案被训练损失压制

VQA 数据集的答案分布并不均匀。常见答案在训练样本里占比高,模型初期就会优先学到这些答案。交叉熵损失对每个样本一视同仁,但梯度更新会被高频类别主导。

低频答案往往需要更细的视觉证据才能判断。例如区分“跑车”和“轿车”需要看轮廓和细节,而“有没有人”这种问题只需要检测到人形区域。由于高频答案梯度占优,低频正确答案所依赖的网络子结构得不到充分更新。最终模型变成一个“高频答案分类器”。

2.4 三个“作弊”通道叠加后的表现

语言先验、注意力塌缩、长尾分布不是互斥的,它们会叠加出现:

  • 问题文本上有强先验,模型“感觉知道”答案;
  • 图像区域被弱注意,模型“随便看看”;
  • 高频答案主导梯度,模型“只学常见项”。

这三个现象共同造成 VQA 模型的高分失真。ReKey 要治理的是这些通道交汇的位置,也就是“到底哪一部分中间层参与编造了答案”。

3. ReKey 的核心思想:定位“决定答案的那一小块”

3.1 从“全量微调”到“定点更新”

传统微调会调整所有层的参数,包括那些与记忆偏差无关、甚至负责通用视觉感知的层。全量微调有两个问题:

  1. 参数量大,训练和存储成本高;
  2. 容易在目标任务上过拟合,破坏预训练模型已经学到的通用视觉能力。

ReKey 的策略不同:先通过归因手段识别哪些中间表示对当前答案的预测贡献最大,然后只更新这些表示背后的参数。这样参数数量少、过拟合风险低,同时保留模型已经具备的视觉理解能力。

3.2 “Key”在 ReKey 中的含义

Transformer 里有 Q(Query)、K(Key)、V(Value)。在视觉问答场景中,问题可以理解为 Query,它要去图像中检索相关信息;图像特征作为 Key 和 Value。

如果某个视觉 token 与当前问题语义相关,它的 Key 就应该和 Query 高度匹配。VQA 模型如果记忆答案,通常意味着匹配关系出了问题:模型总是匹配到高频答案区域,而不是问题指向的目标。

ReKey 的核心动作就是重新调整这些关键的 Key 向量,让 Query 更准确地检索到真实证据,因此叫 ReKey。这里的“一小块”可以是一个视觉 token、一个注意力头,也可以是一个低秩子空间,关键是范围远小于全量参数。

3.3 ReKey 的三步流程

ReKey 的落地可以拆成三个步骤:

步骤目标常见方法
1. 影响定位找到哪些中间表示对最终答案贡献最大梯度范数、注意力分数、积分梯度
2. 子集筛选选出 top-k 个关键块按影响分数排序,组合成参数掩码
3. 稀疏微调只更新关键块对应参数,其余冻结requires_grad、优化器参数过滤

影响定位是 ReKey 最重要的环节。定位不准,后续筛选和微调都会失效。因此实际项目中通常不会只使用单个样本的梯度,而是从一个验证 batch 或多个训练 batch 中统计影响分数,再做平均。

3.4 为什么只更新“关键块”能缓解“记答案”

“记答案”的决策根据并不均匀分布在整个网络里。实践中的观察是,只有少数注意力头和少数视觉 token 对错误答案有主导作用。如果把它们找出来并重新标定,就能破坏语言先验的捷径。

ReKey 相当于做了一次外科手术:定位到病灶,只处理病灶。模型在保留原有视觉语义的同时,被迫改变对问题模板和图像证据的匹配方式,从而减少对高频答案的依赖。这也是它和全量微调、LoRA 等参数高效微调方法最大的区别:ReKey 不只是“减少参数”,而是“定向干预”。

4. 从概念到工程:准备环境并搭一个 VQA 基线

4.1 环境依赖

要复现 ReKey 的最小实验,需要准备以下环境:

  • Python 3.8 以上;
  • PyTorch 2.x;
  • Transformers 库,用于加载文本编码器和部分多模态模型;
  • Pillow,用于图像读取与预处理;
  • datasets,用于管理小规模训练集;
  • tqdm,用于展示训练进度。

可以创建一个虚拟环境并安装依赖:

python -m venv rekey_env source rekey_env/bin/activate pip install torch transformers datasets pillow tqdm

版本号建议在安装时确认,不要直接锁定一个不可验证的旧版本。如果使用 GPU,还需要额外确认 CUDA 版本与 PyTorch 匹配。

4.2 数据准备:带反事实标注的小规模训练集

ReKey 的验证需要“反事实测试集”,也就是同一类问题、同一类对象,但图像内容发生改变。为了让实验快速跑通,可以自己构造一个小型数据集。

数据格式可以是 JSON:

{ "train": [ { "image_id": "train_0001", "image_path": "images/train_0001.jpg", "question": "What color is the banana?", "answer": "yellow", "counterfactual_answer": "black" } ], "val": [ { "image_id": "val_0001", "image_path": "images/val_0001.jpg", "question": "What color is the apple?", "answer": "red", "counterfactual_answer": "green" } ] }

counterfactual_answer字段是关键。它表示同一问题在图像内容被修改后,应该输出的正确答案。这个字段将在后续评估中使用,用来判断模型是否真的根据图像改变答案。实际项目中,反事实样本可以由人工标注,也可以通过图像编辑工具自动生成。

4.3 最小 VQA 模型结构

为了说明 ReKey 的机制,先搭一个最小的 VQA 基线模型。这个模型由三部分组成:

  • 视觉编码器,把图片编码成向量;
  • 文本编码器,把问题编码成向量;
  • 融合层和分类头,输出答案分布。

以下是简化版 PyTorch 代码:

import torch import torch.nn as nn from transformers import BertModel, ResNetModel class MiniVQA(nn.Module): def __init__(self, num_answers=256): super().__init__() self.vision = ResNetModel.from_pretrained("microsoft/resnet-18") self.text = BertModel.from_pretrained("bert-base-uncased") self.proj = nn.Linear(512 + 768, 512) self.classifier = nn.Linear(512, num_answers) def forward(self, images, input_ids, attention_mask): image_feat = self.vision(pixel_values=images).pooler_output text_feat = self.text(input_ids=input_ids, attention_mask=attention_mask).pooler_output fused = torch.cat([image_feat, text_feat], dim=-1) fused = torch.relu(self.proj(fused)) logits = self.classifier(fused) return logits

这里只是示意。实际项目中要替换为更完整的视觉编码器、更大的答案词表,以及处理“自由文本答案”的解码器。如果使用 Qwen3-VL 这类多模态大模型,可以将这个最小结构替换成对应的跨模态注意力和语言模型头,但 ReKey 的定位逻辑仍然适用。

4.4 基线评估:先确认模型确实出现“记答案”行为

在应用 ReKey 之前,先跑一次基线评估。评估不能只看整体准确率,需要按问题类型拆分:

def evaluate_basic(model, dataloader): model.eval() total = 0 correct = 0 stats = {} with torch.no_grad(): for batch in dataloader: logits = model(**batch) pred = logits.argmax(dim=-1) match = pred.eq(batch["answer_ids"]) for i, qtype in enumerate(batch["question_type"]): stats.setdefault(qtype, [0, 0]) stats[qtype][0] += match[i].item() stats[qtype][1] += 1 total += len(match) correct += match.sum().item() print("整体准确率:", correct / total) for qtype, (correct_count, count) in stats.items(): print(qtype, correct_count / count)

如果“颜色类”问题准确率很高,但后续反事实测试准确率很低,说明模型很可能在记高频颜色答案,而不是在看图中的具体颜色。这个结论就是启动 ReKey 的依据。

5. 实现 ReKey:只更新决定答案的那一小块参数

5.1 基于梯度的影响定位

ReKey 的第一步是定位关键参数。这里用梯度范数作为影响分数,简单且容易实现。

思路:对一批样本前向传播,计算损失,反向传播得到每个参数的梯度,然后用梯度范数衡量该参数对当前答案的影响程度。

import torch import torch.nn.functional as F def compute_parameter_importance(model, dataloader, num_batches=4): model.zero_grad() importance = {} for i, batch in enumerate(dataloader): if i >= num_batches: break logits = model(**batch) loss = F.cross_entropy(logits, batch["answer_ids"]) loss.backward() for name, param in model.named_parameters(): if param.grad is not None: norm = param.grad.detach().norm().item() importance[name] = importance.get(name, 0.0) + norm model.zero_grad() for name in importance: importance[name] /= num_batches return importance

这里指定num_batches是为了降低单个 batch 的随机性。影响分数会按参数名记录。如果两个 batch 的定位结果差异过大,需要增加采样 batch 的数量。

5.2 筛选 top-k 关键块并生成参数掩码

拿到每个参数的影响分数后,按分数从高到低排序,选出前 k 个参数名作为“关键块”。

def select_topk_params(importance_dict, k=20): sorted_items = sorted(importance_dict.items(), key=lambda x: x[1], reverse=True) selected = [name for name, _ in sorted_items[:k]] return selected

这里k指的是参数名数量,不是参数量。实际工程中更合理的做法是控制“可训练参数量占总参数量”的比例,例如选择 top 5% 或 top 10%。

筛选之后,生成掩码的方式通常有两种:

  1. 对选中的参数设置requires_grad=True,其余设为False
  2. 在优化器的参数列表里只传入选中的参数。

第二种方式更干净,因为它只影响优化器,不会改变模型内部的 forward 路径。推荐使用第二种方式。

5.3 冻结无关参数,只更新关键子集

下面给出冻结参数并构建优化器的示例:

def prepare_rekey_optimization(model, selected_param_names, lr=1e-4): trainable_params = [] for name, param in model.named_parameters(): if name in selected_param_names: trainable_params.append(param) else: param.requires_grad = False optimizer = torch.optim.Adam(trainable_params, lr=lr) return optimizer

这样只有选中的参数拥有优化器状态和梯度更新。其余参数虽然在前向中仍参与计算,但不会更新。

需要注意:很多模型里存在 LayerNorm 的 gamma/beta 和各类 bias,这些参数虽然名字不在影响力 top-k 中,但在训练中承担偏移校准作用。如果完全不更新它们,模型可能难以稳定训练。实践中可以强制把 bias 和 LayerNorm 参数也加入可训练集合。

5.4 训练循环与验证

ReKey 的训练循环和普通微调没有本质区别,但验证逻辑要调整。下面是一个简化训练循环:

def train_rekey(model, optimizer, train_loader, val_loader, epochs=3): for epoch in range(epochs): model.train() for batch in train_loader: logits = model(**batch) loss = F.cross_entropy(logits, batch["answer_ids"]) optimizer.zero_grad() loss.backward() optimizer.step() val_acc = evaluate_counterfactual(model, val_loader) print(f"epoch {epoch}, val counterfactual acc: {val_acc:.4f}")

这里的evaluate_counterfactual会在下一章解释。ReKey 的核心关注点是反事实准确率能否提升,而不是简单的训练集准确率。

6. 验证效果:怎么判断模型真的在“看图”而不是“记答案”

6.1 验证维度

ReKey 是否有效,不能只看整体准确率。建议从四个维度验证:

维度指标方法
整体准确率Acc常规验证集
反事实能力Counterfactual Acc修改图像后答案是否正确
语言先验依赖Question-only Acc只输入问题不进图像
注意力正确性Attention Overlap注意力区域与标注目标区域的重叠度

只输入问题不进图像,也就是 question-only baseline,能直接反映模型对文本先验的依赖程度。ReKey 后的模型,question-only 准确率应该下降,说明它不再单靠问题文本也能答对,而是依赖图像。

6.2 反事实测试脚本

反事实测试的核心逻辑是:对同一问题构造两张不同的图,模型应该给出不同且正确的答案。

def evaluate_counterfactual(model, dataloader): model.eval() correct = 0 total = 0 with torch.no_grad(): for batch in dataloader: # batch 中包含原始图和反事实图 original_logits = model(images=batch["images"], input_ids=batch["input_ids"]) cf_logits = model(images=batch["cf_images"], input_ids=batch["input_ids"]) original_pred = original_logits.argmax(dim=-1) cf_pred = cf_logits.argmax(dim=-1) original_correct = original_pred.eq(batch["answer_ids"]) cf_correct = cf_pred.eq(batch["cf_answer_ids"]) correct += (original_correct & cf_correct).sum().item() total += len(batch["answer_ids"]) return correct / total

这个指标同时要求模型在原始图上答对,在反事实图上也要答对。如果模型只是记忆了训练集中的高频答案,它难以同时满足两个条件。

6.3 语言先验测试

语言先验测试可以这样设计:

  • 把“What color is the banana?” 改为 “What color is the thing?”;
  • 保持图像不变;
  • 看模型答案是否因为问题缺少“banana”而剧烈变化。

如果模型对“thing”这种无明确指向的问题,也能基于图像颜色输出正确答案,说明它开始依赖视觉证据。ReKey 的目标之一就是让模型在文本信息不足时仍然能看图像。

6.4 注意力可视化与归因确认

最后,用视觉归因方法确认注意力区域是否变化。最简单的做法是取跨模态注意力权重,平均后缩放到原图大小,再和目标区域框计算 IoU。

如果 ReKey 前的注意力集中在背景或高频区域,ReKey 后应当更集中于问题所指对象的区域。这一步虽然不贡献数值指标,但对排查“模型到底在看哪里”非常有效。

7. 常见问题与排查路径

7.1 冻结参数后 BatchNorm 仍在更新

现象:模型训练不稳定,或验证集表现异常。

原因:BatchNorm 的 running statistics 不会因为requires_grad=False而停止更新。即使所有参数都被冻结,BatchNorm 在前向传播时仍会根据当前 batch 更新均值方差。

处理方式:

  1. 在冻结大部分参数时,确认需要更新统计信息的 BatchNorm 是否保留;
  2. 如果不想更新,将模型切到eval()模式;
  3. 或者在构建模型时设置track_running_stats=False

检查方式:打印 BatchNorm 层的running_mean,训练前后对比是否变化。

7.2 梯度定位不稳定,关键块每次都不一样

现象:同一数据和超参数,两次定位得到不同的 top-k 参数集合。

可能原因:

  • 梯度范数本身方差大;
  • 采样 batch 数量太少;
  • 模型没有收敛到稳定区域。

检查方式:固定随机种子,重复两次影响定位,计算所选参数集合的重叠比例。

处理方式:增加采样 batch 数量,或者改用积分梯度、注意力时间序列平均等更稳定的归因方法。如果重叠比例仍然很低,应该先稳定基线模型,再应用 ReKey。

7.3 top-k 选得太大或太小,效果不升反降

这是 ReKey 最需要调的超参数。给出一个参考表现:

关键块比例可能表现常见问题
过小,例如 0.1%模型几乎不更新参数被冻结过多,loss 不下降
适中,例如 5%-10%loss 下降,反事实测试提升需要消融确定具体数值
过大,例如 50% 以上接近全量微调过拟合风险高,失去 ReKey 意义

推荐做法是画一条“反事实准确率随关键块比例变化”的曲线,选择峰值附近较小的 k 值。

7.4 训练后整体准确率没有下降,但反事实准确率也没提升

这可能是验证集设计问题。如果验证集和训练集同分布,ReKey 不一定能体现优势。需要专门构建分布外验证集,例如:

  • 用不同数据集;
  • 用图像编辑生成的新对象组合;
  • 用只含文本先验的困难样本。

另外,还要检查训练损失是否下降。如果损失没有下降,说明关键块定位过窄,或者冻结参数过多。此时可以适当扩大 top-k,或强制加入 LayerNorm 和 bias。

7.5 显存不足

ReKey 冻结了大量参数,可以降低优化器状态和梯度存储,但前向传播和反向传播的中间激活仍然占用显存。

处理方式:

# 启用 gradient checkpointing model.gradient_checkpointing_enable()

同时可以减小 batch size、使用混合精度训练:

pip install torch.cuda.amp

或者使用显存优化工具,例如 DeepSpeed ZeRO。对于显存不是瓶颈的服务器,直接使用单卡 A100 或 4090 也能跑通小规模验证。

8. 最佳实践与扩展方向

8.1 什么时候适合用 ReKey,什么时候不适合

适合 ReKey 的场景:

  • 已经有一个不错的预训练 VQA 模型,但希望缓解记忆偏差;
  • 微调预算有限,无法承受全量参数更新;
  • 需要可解释性,想知道模型修改了哪部分;
  • 有少量反事实标注样本,能够验证干预效果。

不适合的场景:

  • 从头训练一个大型多模态模型,ReKey 不是训练范式;
  • 没有归因工具,也没有留存梯度信息的内部代码;
  • 数据量太少,无法稳定定位关键块。

8.2 ReKey 与 LoRA、Prefix Tuning 的对比

方法更新范围可解释性显存开销适用场景
全量微调所有参数数据充足,算力充足
LoRA低秩矩阵通用参数高效微调
Prefix Tuning前缀 token 参数语言模型生成任务
ReKey影响分数最高的关键块可调定位记忆偏差并干预

ReKey 和 LoRA 不是互斥的。可以先使用 ReKey 定位关键注意力头,再在这个子集上使用 LoRA 进行适配。这样既能减少参数,又能保留可解释性。

8.3 与多模态大模型结合

当前多模态大模型,例如 Qwen3-VL 等,同样存在“答案记忆”风险。大规模预训练后,模型会记住大量常见视觉知识和语言关联。在特定 VQA 任务上,如果只做全量指令微调,仍然可能强化高频答案。

ReKey 可以尝试作为大模型微调的后处理过程:先通过梯度或注意力归因找出哪些跨模态注意力头对错误答案贡献最大,然后只在这些头上做低秩微调。这样既能改变模型对特定问题模板的响应,又不破坏大模型已经具备的开放世界知识。

8.4 可复用清单

在把 ReKey 应用于生产或研究时,建议逐项检查:

  • 是否准备了反事实验证集,而不是只看整体准确率;
  • 影响定位是否在多个 batch 上稳定,而不是单次采样;
  • 参数掩码是否覆盖应该更新的 LayerNorm 和 bias;
  • 是否对 top-k 比例做了消融实验;
  • 是否同时计算 question-only 准确率,确认语言先验下降;
  • 是否保存了冻结参数列表和训练日志,方便复现;
  • 是否保留全量微调或 LoRA 基线,用于对比;
  • 是否检查了 BatchNorm running statistics 的更新行为;
  • 是否记录注意力可视化结果,确认模型真的在看目标区域;
  • 是否在分布外数据上做了测试,而不是只依赖原始验证集。

这份清单可以直接用于代码评审或发布前的质量检查。

8.5 下一步学习建议

如果第一次接触 ReKey,建议先不要直接套用大模型。可以构造一个几千样本的小型 VQA 数据集,故意让训练集中某一类答案占多数,训练一个小模型复现“记答案”现象。然后实现基于梯度的 ReKey,比较全量微调、LoRA 和 ReKey 在反事实测试上的差异。这个过程能帮助你理解定位、筛选、冻结这三步的实际影响。

之后再迁移到 Qwen3-VL 之类的多模态模型上,观察它的跨模态注意力头分布,用 ReKey 的思路做定向干预。VQA 的高分是否可信,最终取决于验证方式是否足够严格。ReKey 给出的不是万能答案,而是一种更接近“外科手术”的干预路径:找到决定答案的那一小块,只更新那一小块,然后通过反事实测试证明模型真的在看图。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询