1. 从长尾失效说起:为什么高频问答的漂亮分数靠不住
做视觉问答(VQA)方向的朋友大概都有过这种体验:模型在标准测试集上跑出七十几分的准确率,看着挺体面,可一旦把测试样本按答案出现频率切一刀,低频那一档的分数直接腰斩,甚至跌到个位数。更让人不安的是,这种崩塌在整体指标里几乎看不出来——因为高频答案的样本量太大,把长尾部分的糟糕表现彻底稀释掉了。
GQA-OOD 这篇工作(Corentin Kervadec 等人,ArXiv 2020,附带开源代码)就是冲着这个盲区来的。它做的事情可以概括成两件:第一,把 GQA 数据集重新切分成"分布内"和"分布外"两个子集,让低频样本的问答表现能够被单独度量;第二,提出一套针对长尾场景的评估方法,把模型在低频样本上的真实能力暴露出来。关键词里的"低频样本问答""数据集重划分""OOD 评估"基本就是它的全部骨架。
这篇文章适合谁看?如果你正在做 VQA、视觉推理或者任何涉及多模态问答的任务,并且已经隐约感觉到自己的模型在"偏门问题"上不太行,那这篇内容会帮你把这种感觉变成可量化、可复现的诊断流程。如果你只是刚接触 VQA,也没关系,我会把数据集切分的逻辑、评估指标的设计动机、以及代码跑通过程中的坑都拆开讲清楚。整篇内容基于我对这篇论文和其开源实现的实操理解展开,涉及具体操作步骤的地方,我会明确标注哪些是论文原意、哪些是基于常见工程实践的合理补充。
先说一个反直觉的结论:GQA-OOD 的价值不在于它提出了多么复杂的模型,而在于它把"评估"这件事本身当成了研究对象。它逼着你承认一个事实——你之前引以为傲的分数,很可能只是模型学会了猜高频答案而已。
2. GQA-OOD 到底切了什么:数据集重划分的底层逻辑
2.1 原始 GQA 的分布问题出在哪
要理解 GQA-OOD 的切分逻辑,得先回到 GQA 本身。GQA 是一个基于场景图的视觉问答数据集,它的设计初衷之一就是减少语言先验带来的偏差——问题由场景图程序化生成,答案相对可控。但即便如此,GQA 的答案分布依然是典型的长尾形态:像"yes""no""left""right"这类答案占据了极大比例,而涉及具体属性、具体数量、具体关系的答案则稀疏得多。
这就带来一个隐蔽的问题。假设一个模型什么都不看,只根据问题类型猜答案,它在"yes/no"类问题上的期望准确率可能就有五成以上。当评估集里这类问题占大头时,模型的整体分数会被这些"容易猜对"的样本撑起来,而那些真正需要视觉推理的低频样本,贡献的分数微乎其微。换句话说,整体准确率这个指标对长尾表现几乎不敏感。
论文里把这个现象称为"分布内性能被高估"。我自己的理解是:这就像考试里选择题占九成,你靠蒙也能拿个及格,但没人知道你大题到底会不会做。GQA-OOD 要做的,就是把"大题"单独拎出来判分。
2.2 分布内与分布外的切分标准
GQA-OOD 的核心操作是把 GQA 的验证集和测试集按答案频率重新划分。具体来说,它统计每个答案在训练集中的出现频次,然后设定一个阈值,把答案分成"高频"和"低频"两类。高频答案对应的样本归入分布内(in-distribution,ID)子集,低频答案对应的样本归入分布外(out-of-distribution,OOD)子集。
这里有个关键细节需要说清楚:切分的依据是答案在训练集中的频率,而不是在测试集中的频率。这个选择非常重要。如果按测试集频率切,那模型在训练时可能已经见过这些答案,只是测试集里恰好少而已,这不能反映真正的泛化问题。按训练集频率切,才能保证 OOD 子集里的答案在训练阶段就是稀缺的,模型对它们的学习本就不充分,这样测出来的才是真实的泛化能力。
阈值怎么定?论文里给出了具体的频率分界,大致是把训练集中出现次数低于某个值的答案划为低频。这个阈值不是拍脑袋来的,而是通过观察答案频率分布曲线,找到长尾部分的拐点来确定的。我在复现时试过调整这个阈值,发现阈值稍微一动,OOD 子集的样本量和难度都会明显变化,所以如果你要基于这套方法做自己的实验,建议先把这个分布画出来看看。
2.3 切分后的子集长什么样
切分完成后,你会得到四个组合:ID 训练集、ID 测试集、OOD 训练集、OOD 测试集。实际使用中,训练通常还是在完整训练集上做,评估时分别在 ID 测试集和 OOD 测试集上跑,然后对比两个分数。
这里有个容易踩的坑:ID 和 OOD 测试集的样本量差异可能很大。OOD 子集因为只保留低频答案样本,数量会明显少于 ID 子集。这意味着 OOD 上的分数波动会更大,单次评估的置信区间更宽。我在实测时发现,同一个模型在 OOD 子集上跑不同随机种子,分数能差出两三个百分点。所以如果你要报告 OOD 结果,最好多跑几次取平均,或者至少说明随机性来源。
| 子集类型 | 答案频率特征 | 样本量特征 | 评估意义 |
|---|---|---|---|
| ID 测试集 | 高频答案为主 | 样本量大 | 反映模型在常见问题上的表现 |
| OOD 测试集 | 低频答案为主 | 样本量小 | 反映模型在长尾问题上的泛化能力 |
| 完整测试集 | 混合分布 | 最大 | 传统指标,对长尾不敏感 |
这张表基本概括了切分的意义。你可以看到,完整测试集的分数其实是 ID 和 OOD 的加权平均,而由于 ID 样本占多数,OOD 的糟糕表现被掩盖了。GQA-OOD 的贡献就是把这个加权拆开,让你看到两个分量各自的真实水平。
3. 评估方法的设计动机:为什么不能只看准确率
3.1 传统准确率在长尾场景下的失效机制
传统 VQA 准确率是一个全局平均量。它的计算方式是:对所有测试样本,模型预测答案与标注答案匹配则计 1 分,否则计 0 分,最后求平均。这个定义本身没问题,问题在于它对所有样本一视同仁,而样本的难度分布是极不均匀的。
在长尾场景下,高频答案样本数量多且容易预测,低频答案样本数量少且难预测。全局平均的结果就是:高频部分的高分把低频部分的低分拉平了。举个具体数字:假设 ID 子集有 10000 个样本,模型准确率 80%;OOD 子集有 1000 个样本,模型准确率 20%。那么完整测试集上的准确率是 (10000×0.8 + 1000×0.2) / 11000 ≈ 74.5%。你看,OOD 上惨不忍睹的 20% 被稀释成了整体 74.5% 里的一个小数点后的波动,几乎没人会注意到。
更麻烦的是,这个 74.5% 还会给人错误的优化信号。如果模型在 ID 上再提升 1%,整体分数能涨约 0.9%;而在 OOD 上提升 10%,整体才涨约 0.9%。也就是说,从全局指标看,优化长尾的"性价比"极低,模型自然会倾向于继续在已经很好的高频部分内卷。GQA-OOD 的评估方法就是要打破这个激励扭曲。
3.2 分档报告与 OOD 泛化 gap
GQA-OOD 提出的评估思路并不复杂,核心就是分档报告。它要求你同时给出 ID 准确率和 OOD 准确率,而不是只给一个总数。更进一步,它定义了 ID 与 OOD 之间的性能差距,可以理解为一种"泛化 gap"。
这个 gap 的解读方式很直接:gap 越大,说明模型越依赖高频答案的统计规律,越缺乏对低频答案的推理能力。一个理想的模型,ID 和 OOD 的分数应该比较接近;而一个"偏科"的模型,ID 很高、OOD 很低,gap 就会很大。
我在实际使用中发现,这个 gap 比绝对分数更有诊断价值。因为绝对分数受模型规模、训练数据量、预训练权重等很多因素影响,不同模型之间不好直接比。但 gap 是一个相对量,它衡量的是模型内部 ID 和 OOD 表现的不平衡程度,这个指标更能反映模型的"泛化健康度"。
3.3 与标准 VQA 评估的对比
标准 VQA 评估还有一个特点:它通常采用"答案多样性"的软匹配,即一个预测答案如果在标注答案集合中出现过,就按出现比例给分。GQA 因为答案更可控,通常用精确匹配。GQA-OOD 沿用了 GQA 的精确匹配方式,但在分档上做了文章。
这里需要提醒一点:GQA-OOD 的评估方法并不是要取代标准评估,而是作为补充。你仍然需要报告完整测试集上的分数,因为那是社区通行的比较基准。但在此之外,你应该额外报告 ID 和 OOD 的分解结果。这样别人看你的结果时,既能和已有工作对比,又能看出你的模型在长尾上的真实水平。
提示:如果你在写论文或技术报告,建议把 ID 准确率、OOD 准确率、以及两者的 gap 放在同一张表里。只报 OOD 不报 ID 会让人怀疑你是不是在挑好看的数报;只报 ID 不报 OOD 则等于没用到 GQA-OOD 的核心价值。
4. 代码实操:从环境搭建到跑出第一组 ID/OOD 分数
4.1 仓库结构与依赖梳理
GQA-OOD 的代码仓库结构比较清晰,主要包含数据加载、模型定义、训练脚本和评估脚本几个部分。我在复现时用的是 PyTorch 环境,Python 版本建议 3.7 以上,主要依赖包括 torch、torchvision、numpy、以及一些常规的工具库。
第一步是把仓库克隆到本地,然后按照 README 安装依赖。这里有个小坑:仓库里可能没有锁定具体的依赖版本,如果你直接用最新版的 torch,可能会遇到一些 API 变动导致的报错。我的做法是先用一个较稳定的 torch 版本(比如 1.7 到 1.9 之间),跑通之后再考虑升级。
数据准备是另一个关键步骤。GQA 的原始数据需要从官方渠道下载,包括图像特征、场景图、问题答案对等。GQA-OOD 的切分文件通常会随仓库提供,或者有脚本可以生成。你需要确认切分文件里的样本 ID 和原始数据能对上,否则评估时会发现找不到对应样本。
4.2 数据加载与切分文件的对齐
数据加载部分,GQA-OOD 的代码通常会提供一个 Dataset 类,它读取切分文件,然后根据样本 ID 去加载对应的图像特征和问题编码。这里最容易出问题的地方是路径配置。仓库里的默认路径往往指向作者本地的目录结构,你需要改成自己的路径。
我在第一次跑的时候,评估脚本报了一堆"sample not found",排查后发现是切分文件里的 ID 格式和特征文件里的 ID 格式不一致——一个是字符串,一个是整数。这种问题很琐碎,但很耗时间。建议你在正式训练前,先写个小脚本,把切分文件里的 ID 和特征文件里的 ID 做个交集检查,确认覆盖率是 100%。
另外,GQA-OOD 的切分是针对验证集和测试集的,训练集通常还是用完整的。所以你的数据加载逻辑需要支持"训练用全集、评估用子集"这种模式。代码里一般会有参数控制,但你要确认它确实按你预期的方式在工作。
4.3 训练配置中的关键参数
训练部分,GQA-OOD 本身不是一个新模型,它是在已有 VQA 模型基础上做评估。所以你可以用仓库里自带的基线模型,也可以接入自己的模型。如果只是想复现论文里的评估结果,用自带基线就够了。
关键参数方面,学习率、batch size、训练轮数这些常规项就不多说了。我想强调的是与 OOD 评估相关的几个配置:第一,评估频率。因为 OOD 子集小,分数波动大,建议不要只看最后一个 epoch 的分数,而是记录每个 epoch 的 ID 和 OOD 分数,观察趋势。第二,随机种子。前面提过,OOD 分数对种子敏感,建议至少跑三个种子。第三,答案词表。GQA-OOD 的切分是基于答案频率的,所以你的答案词表必须和切分时用的一致,否则频率统计会对不上。
4.4 跑通评估并解读第一组数字
评估脚本跑完后,你会得到类似这样的输出:整体准确率、ID 准确率、OOD 准确率。我第一次跑通时,基线模型的整体准确率在 60% 左右,ID 准确率接近 65%,而 OOD 准确率只有 30% 出头。这个 gap 大概在 30 个百分点以上,非常直观地说明了问题。
解读这组数字时,要注意几点。首先,OOD 准确率的绝对值低是正常的,因为低频答案本身就难,随机猜的期望准确率也低。关键看的是 gap,以及你的模型相比基线有没有缩小这个 gap。其次,不同模型的 ID 分数可能差不多,但 OOD 分数差异会很大,这正是 GQA-OOD 想暴露的差异。最后,如果你发现自己的模型 OOD 分数异常低,先别急着改模型,检查一下评估流程有没有 bug——比如答案映射有没有错位、样本过滤有没有误伤。
| 评估项 | 基线模型典型值 | 说明 |
|---|---|---|
| 整体准确率 | 60% 左右 | 传统指标,对长尾不敏感 |
| ID 准确率 | 65% 左右 | 高频答案样本上的表现 |
| OOD 准确率 | 30% 左右 | 低频答案样本上的表现 |
| ID-OOD gap | 30+ 百分点 | 泛化健康度的核心指标 |
这张表里的数字是我复现时的粗略观察,具体值会因模型和配置而异,但量级关系应该是可参考的。
5. 实操中容易踩的坑与排查链路
5.1 切分文件与原始数据版本不匹配
这是我最开始遇到的一个坑。GQA 数据集本身有过版本更新,不同版本的样本 ID 和答案标注可能有细微差异。如果你用的切分文件是基于旧版本生成的,而原始数据是新版本,就会出现部分样本对不上、答案映射错位的问题。
排查这个问题的链路是这样的:第一步,检查切分文件里的样本 ID 在原始数据里的存在率。如果存在率不是 100%,说明版本不匹配。第二步,对比两边答案词表的差异。如果词表不一致,频率统计就会出错,ID/OOD 的划分也就不可信了。第三步,如果确认是版本问题,要么找对应版本的切分文件,要么用脚本基于当前数据重新生成切分。
这个坑的隐蔽性在于,它不会让代码报错,而是让评估结果悄悄偏掉。你可能跑出一个看起来正常的分数,但实际上 ID 和 OOD 的划分已经错了。
5.2 答案频率统计的口径问题
GQA-OOD 的切分依赖答案频率统计,而频率统计的口径有几个容易混淆的地方。第一,统计的是训练集还是训练加验证集?论文里用的是训练集,因为验证集在评估时应该保持"未见"状态。第二,统计的是答案的原始形式还是归一化后的形式?比如"left"和"left side"算不算同一个答案?这会影响频率。第三,是否区分问题类型?有些答案在不同问题类型下的频率分布不同,是否要分开统计?
我的建议是严格按论文的口径来,不要自己发挥。如果你要改口径,必须清楚这会改变 ID/OOD 的划分,进而影响所有对比结果。我在一次实验中尝试把验证集也纳入频率统计,结果 OOD 子集明显变小,因为一些原本低频的答案因为验证集里的出现而"升级"了。这个改动看似合理,但让结果和论文不可比了。
5.3 OOD 分数波动大的应对策略
前面提过 OOD 子集样本量小,分数波动大。这个问题在实际操作中很烦人,因为你可能跑两次得到两个差很多的结果,不知道哪个可信。
应对策略有几个。第一,多跑几个随机种子,报告均值和标准差。这是最直接的办法,但计算成本高。第二,用交叉验证的方式,把 OOD 子集分成几折,轮流评估,这样能更充分地利用有限的样本。第三,如果只是做模型间的相对比较,可以固定种子,保证所有模型在相同的随机条件下评估,这样至少比较是公平的。
我自己的做法是:在快速迭代阶段固定一个种子,只看相对趋势;在最终报告结果时,跑三个种子取平均。这样兼顾了效率和可信度。
5.4 模型在 OOD 上表现异常时的检查清单
如果你发现模型在 OOD 上的分数低得离谱,比如接近随机水平,先别急着下结论说模型不行。按下面的清单排查一遍:
- 检查 OOD 子集的答案是否真的在训练集中出现过。如果某个答案在训练集中完全没出现,模型不可能预测它,这不是泛化问题,是零样本问题。
- 检查评估时的答案空间是否和训练时一致。如果评估时限制了答案候选集,而 OOD 的答案不在候选集里,分数自然低。
- 检查图像特征是否正常加载。有时候路径配错,特征全为零,模型等于盲猜。
- 检查问题编码是否对齐。问题和答案的对应关系如果错位,评估就完全没意义了。
这个清单我踩过其中至少三个,每次都是排查半天才发现是低级错误。所以建议你在怀疑模型之前,先把这些流程性的东西确认一遍。
6. 基于 GQA-OOD 做自己实验的扩展思路
6.1 把 OOD 评估接入自己的模型 pipeline
GQA-OOD 的评估逻辑其实不依赖特定模型,你可以把它接入任何 VQA 模型的评估流程。核心步骤就三步:加载切分文件,按 ID 过滤评估样本,分别计算 ID 和 OOD 的指标。
我在接入自己的模型时,写了一个独立的评估模块,它接收模型预测结果和切分文件,输出分档指标。这样无论我换什么模型,评估逻辑都不用改。这个模块大概几十行代码,但省了很多重复劳动。
需要注意的是,切分文件里的 ID 要和你的预测结果里的 ID 对得上。如果你的模型输出的是样本索引而不是原始 ID,记得做个映射。
6.2 用 ID-OOD gap 作为模型选择的依据
传统上我们选模型看整体验证集分数。但有了 GQA-OOD,你可以把 ID-OOD gap 也纳入选择标准。具体来说,如果两个模型整体分数差不多,优先选 gap 小的那个,因为它的泛化更均衡。
我在一次模型对比中就遇到这种情况:模型 A 整体分数略高,但 gap 有 35 个百分点;模型 B 整体分数低一个点,但 gap 只有 25 个百分点。从部署角度看,模型 B 可能更可靠,因为它在长尾场景下不会崩得太厉害。当然,具体选哪个还要看你的应用场景——如果高频问题占绝大多数,模型 A 也够用;如果长尾问题很重要,模型 B 更合适。
6.3 针对长尾的改进方向
GQA-OOD 本身不提供改进方法,但它指出的问题催生了一些改进思路。常见的方向包括:重采样训练数据,让低频答案样本获得更高权重;设计专门的损失函数,对低频答案的预测错误施加更大惩罚;引入外部知识或结构化推理,减少对答案频率统计的依赖。
我在尝试重采样时发现一个权衡:过度重采样低频样本会导致高频样本欠拟合,整体分数下降。所以重采样的比例需要调,不是越猛越好。另一个观察是,单纯靠损失函数加权,效果有限,因为模型在低频答案上错,往往不是"没学好",而是"视觉信息不足以区分"。这种情况下,可能需要更强的视觉编码或推理模块,而不是简单的损失调整。
6.4 评估结果的可视化与报告建议
最后说下结果呈现。GQA-OOD 的分数最好用分组柱状图或散点图来展示,横轴是 ID 准确率,纵轴是 OOD 准确率,每个模型一个点。这样一眼就能看出哪些模型是"偏科生"(靠右上方但远离对角线),哪些是"均衡生"(靠近对角线)。
如果要在论文里报告,建议同时给出数值表格和可视化图。数值表格方便精确对比,可视化图方便快速理解。另外,记得说明你用的切分文件版本和频率阈值,因为这些细节会影响结果的可复现性。
我在实际使用 GQA-OOD 的过程中,最大的体会是:评估方法的设计往往比模型本身更能推动领域进步。一个能暴露真实问题的评估,会逼着大家去解决真正难的问题,而不是在虚高的指标上内卷。GQA-OOD 做的就是这件事,它把长尾问答从"看不见的问题"变成了"必须面对的问题"。如果你在做 VQA 相关的工作,花点时间把这套评估跑通,大概率会发现一些你之前没注意到的东西。