1. 先搞清楚一个问题:Visual Relationship Detection到底在做什么
1.1 从"检测出物体"到"理解物体之间的关系"
去年我被一个项目逼着去做图像中的关系推理,最开始以为这就是"目标检测+一个分类头"的事:把两个框的特征拼在一起,过个全连接层,输出谓词类别,完事。结果在真实验证集上被按在地上摩擦——尤其是那些视觉上几乎无法区分的谓词,模型输出完全是在碰运气。后来翻到这篇Visual Relationship Detection with Language Priors,才意识到问题不出在特征提取上,而是在于我把这个任务理解得太窄了。
Visual Relationship Detection(视觉关系检测)要解决的事情很直白:给定一张图片,输出形如<主语,谓词,宾语>的三元组,比如<人,骑,自行车>、<杯子,放在,桌子上>。它和纯目标检测的本质区别在于:检测只回答"哪里有什么",关系检测还要回答"这些东西之间是什么关系"。这里的主语和宾语通常是图像中的物体,谓词则可以是空间关系(on、above、under)、比较关系(taller than)、动作关系(riding、eating)等。
别看描述很简单,这个任务有一个让纯视觉方法非常难受的特点:组合爆炸。假设物体类别有100种,谓词类别有70种,那么理论上需要判别的关系组合就是 100 × 100 × 70 = 70万种。而训练数据里能覆盖到的组合,通常只有几千种,剩下的全是零样本组合。这还不算最棘手的——即使某个组合在训练集里出现了,如果只出现了三五次,视觉模型也根本学不出稳定模式。
1.2 三个子任务与评测口径
要读这篇论文,第一步是搞清楚它评估的到底是什么。论文把任务拆成了三个由易到难的子任务:
- Predicate Prediction:给定真实的物体框和物体类别,只预测两个物体之间的谓词。这是最"纯净"的评估,用来衡量模型对关系本身的建模能力。
- Phrase Detection:同时输出关系短语的边界框(覆盖主语、宾语及其空间上下文)和短语类别,类似将关系描述当作一种带属性的区域检测。
- Relationship Detection:从零开始检测物体,再输出所有物体对之间的关系,即"检测+关系"联合任务。
对应的评测指标主要是 Recall@50 和 Recall@100。意思是对每张图的预测三元组,按置信度排序后取前50或前100个,看能召回多少标注的真实关系。它不要求预测框和真实框完全重合,只要求IoU超过阈值(通常是0.5),同时三元组完全匹配。
还有一个容易被忽略的评测变体:零样本关系检测(zero-shot relationship detection)。测试集中会故意安排一些"主语-宾语对在训练集里没见过"的组合,比如训练集里从未出现过"人-斑马"的图片,但测试集要求模型预测<人,骑,斑马>或者<人,喂,斑马>。这个设置直接考验模型的组合泛化能力,也是本文语言先验最能发力的地方。
1.3 纯视觉模型为什么在这里天然吃亏
导师当年跟我说过一句话:"关系检测的难点不在于视觉特征提取,而在于先验建模。"我一开始没太当回事,直到我在一个包含70个谓词的评估集上做错误分析才看懂。
视觉模型本质是条件分布建模:p(predicate | visual_features),它希望从图像像素里找到能区分不同谓词的线索。问题是,大量谓词在像素层面几乎不提供判别信息。比如<马,看见,人>和<马,靠近,人>,单看一张静态图,视觉特征差异极小,甚至人类标记者都需要借助常识或者视频上下文才能判断。再比如on和above在很多角度下视觉几乎一样,模型只能靠猜。
更糟糕的是数据分布。VRD数据集的谓词分布极度不均衡,on、has、with这类高频谓词占了绝大部分样本,而eating、chasing、carrying这类动作相关谓词数量很少。标准的交叉熵训练会让模型倾向于把所有关系都预测成高频类,因为这样损失最小。我在复现时看到的现象就是:纯视觉模型在on、has上的准度还行,但长尾谓词几乎没有输出。
这个困境指向一个关键洞察——关系三元组的联合分布并不是完全由视觉决定的。主语类别"人"和宾语类别"苹果"一旦确定,人们能想到的合理谓词范围一下就缩小了:吃、拿、看、放……而不太可能是飞。这种知识不需要看图片也能获得,它藏在语言统计规律里。Language Priors 的思路正是把这个语言层面的先验显式建模出来,去辅助视觉模型。
2. Language Priors 的两个实现分支:统计频率与词向量映射
2.1 频率先验:从训练集里直接统计谓词分布
论文里第一种语言先验实现方式非常朴素,但效果立竿见影。它的逻辑是这样的:既然主语或宾语的类别已知,那谓词的先验分布可以直接从训练三元组的统计中得到。
具体做法是统计两组条件概率。第一组是主语条件分布p(predicate | subject),含义是"当主语是'人'时,谓词出现的历史概率分布"——人更常与"骑"、"吃"、"坐"搭配,几乎不与"飞"搭配。第二组是宾语条件分布p(predicate | object),含义是"当宾语是'苹果'时,谓词的概率分布"——苹果更常与"吃"、"拿"、"切"搭配。然后对两者做组合(论文里是对数域相加,等价于假设主语和宾语条件独立),得到给定主语-宾语对时的谓词先验分数。
实现时有个小细节值得注意:直接统计会有零概率问题。比如训练集里"人"和"自行车"同时出现时恰好没有"骑"这个标注,那p(骑|人, 自行车)直接为0,这对后续融合是非常不利的。所以必须做平滑处理,最常用的是拉普拉斯平滑(Add-1平滑),给每个谓词至少一个单位的计数。别小看这个操作,它对低频率类的鲁棒性影响很大,平滑强度都不需要太大,因为统计分布的峰值出现在高频类上,主要目的是把尾部概率抬起来一点。
这个分支的优点在于零训练成本和可解释性:没有引入任何额外参数,直接把标注库变成了知识库。缺点也很明显:它完全无法泛化到统计中不存在的组合,一旦测试时出现"人-斑马"这种训练里没见过的组合,它只能给出接近均匀的先验,帮助有限。
2.2 词向量先验:用词嵌入补上"没见过"的组合
纯统计方法解决不了组合泛化,这是词向量分支存在的意义。核心出发点非常漂亮:语义相近的词在向量空间里距离也近;"人-马"和"人-斑马"在语义空间里应该很接近,那么"人骑马"这个关系的谓词向量,对"人骑斑马"也是有指导意义的。
具体做法大致分为三步。第一步,用预训练好的 word2vec 把主语、谓语、宾语都映射到向量空间。第二步,学习一个映射函数f,输入主语和宾语的词向量(论文里是把两者拼接后作为输入),输出一个预测的谓语向量。这个映射函数用带隐层的全连接网络实现,非线性映射能力足够。第三步,预测出的谓语向量与候选谓词词表中的每个谓词向量做余弦相似度,得到语言先验的打分分布。
训练这个映射函数时,输入是训练集中所有出现过的三元组里的(subject, object),监督信号是真实谓词对应的 word2vec 向量。损失函数用余弦相似度/均方误差这一类距离度量,让预测向量尽量接近真实谓词向量。
我复现时觉得,这个方法本质上是在做"从名词对到动词向量的回归"。它真正厉害的地方在于:即使测试时遇到训练集里没有的(subject, object)组合,只要它们的词向量和某个训练过的组合语义相近,映射函数输出的谓语向量就会倾向于落在合理的区域。这恰恰是频率先验做不到的。
2.3 两种先验的适用边界
| 维度 | 频率先验 | 词向量先验 |
|---|---|---|
| 信息来源 | 训练集三元组统计 | 大规模语料预训练词向量 |
| 建模对象 | 离散条件概率表 | 名词对到谓词向量的连续映射 |
| 对高频关系 | 非常准确 | 取决于词向量质量和网络容量 |
| 对未见组合 | 基本失效 | 有一定泛化能力 |
| 计算成本 | 几乎为零 | 需要训练一个MLP,成本低 |
| 可解释性 | 强,可直接查看概率表 | 弱,只能观察向量近邻 |
两种先验并不是竞争关系,论文的用法是让它们各自贡献分数。频率先验负责"把握全局分布形状",词向量先验负责"填补组合空洞",两者相加通常比单独使用任何一个都稳。我后面会细说融合的具体公式。
3. 视觉分支与语言先验的融合打分,到底是怎么组合的
3.1 视觉侧怎么提取关系特征
视觉分支相对常规,但有几个实现选择值得讲。输入是一对物体框(subject_box, object_box)以及它们的类别信息(子任务不同,类别来源不同,在 Predicate Prediction 里直接用真实类别,在 Relationship Detection 里用检测结果)。特征来源是预训练检测网络的 RoI 特征,比如 Fast R-CNN 的 fc7 层特征。
除了两个框各自的视觉特征,论文还利用了位置/空间信息。这在关系检测里非常重要,因为很多谓词(on、above、under、next to)本质上是由两个框的相对几何关系决定的。常见的做法是把两个框的坐标差、面积比、交叠面积等几何特征拼进特征向量,或者直接用一个单独的几何分支建模。视觉特征和几何特征拼接后,经过两层全连接网络,最后输出70维的谓词分数。
一个在复现时容易踩的坑是:训练视觉分支时如果只用正例三元组,模型会缺少"这两个物体之间没有任何标注关系"的负例概念。但在关系检测的评估里,你需要对所有物体对打分排序,负例的控制直接影响Recall@k的质量。实际做法是引入一个background类,把所有"出现过但未标注关系"的物体对作为背景负例;或者干脆忽略负例,只对正例对打分——后者在零样本评测里更常用,因为评测集合通常已经限定在标注过关系的物体对范围内。
3.2 融合公式与权重λ的调参逻辑
语言先验分支输出的是谓词分布分数,视觉分支输出的也是谓词分数。融合方式论文里用的是加权组合,大致可以写成:
score_final(predicate) = score_visual(predicate) + λ * score_language(predicate)
或者写成概率域的乘法形式:
p(predicate | image, subject, object) ∝ p_visual(predicate) * p_language(predicate)^λ
两种形式本质等价,λ控制的是语言先验对最终决策的干预强度。λ越大,模型越倾向于输出"符合语言常识"的关系;λ越小,模型越相信"视觉证据"。
λ的取值直接决定成败,这个必须调。我复现时的经验是:先固定视觉分支,单独把语言分支跑通,观察它的分数量级;再在一个小的验证集上网格搜索λ。如果λ太大,模型会把所有"人"都预测成"骑"或"吃",因为这是语言先验里概率最高的选项,视觉证据被完全淹没;如果λ太小,语言先验等于白加,长尾谓词还是出不来。论文里给的效果范围我印象中是λ取1~2之间时增益最大,但具体值依赖你的视觉特征规模和数据分布,应以验证集为准。
3.3 为什么不能只靠语言先验做推理
一个很自然的问题:既然语言先验这么强,为什么还要视觉分支?直接用p(predicate | subject, object)不就行了?
答案是:语言先验描述的是"一般规律",而不是"这张图的规律"。一个反例是,如果图里是一只狗和一辆汽车,语言先验可能会强推<狗,追,汽车>,但视觉证据表明狗只是站在车旁边;如果你只有一个先验分布,你完全无法感知这个反例。更常见的情况是:同一对物体可以存在多个合理关系,比如"人在公园长椅上"和"人在长椅旁边"在语言上都是合理的,但视觉证据决定了到底是on还是next to。
所以语言先验的角色应该是"缩小搜索空间、提高长尾类别的排序",而不是"替视觉做决定"。融合设计成可调节的加权形式,就是这个考虑:让视觉分支在主位,语言先验在旁边扶一把。这个观念后来在做多模态模型时也一直影响着我:先验是锚点,不是答案。
4. 实验复盘:语言先验到底在哪些地方把效果拉起来了
4.1 指标上的整体趋势
论文的实验在主推的组合模型下,相比纯视觉基线的提升是显著的。我当时看到这个结果的第一反应是:语言先验的效果居然比很多精心设计的视觉特征还大。这和我们平时"特征为王"的直觉是相反的,但它恰恰点出了关系检测任务的一个结构性特征——这个任务的难度,很多不在视觉侧,而在语义组合侧。
具体数字我不建议你们照抄论文摘要里的单个值,因为不同版本报告的口径有差异。我这里只说趋势:在 predicate prediction 子任务上,纯视觉模型和纯语言模型单独拎出来都不算强,但两者融合后,Recall@100 和 Recall@50 都有明显跳升,融合效果大于各部分之和。这说明视觉特征和语言先验的信息有很好的互补性——视觉分支擅长识别动作类关系的细微差异,语言先验擅长把高频谓词的排序拉对。
4.2 从谓词类别维度看涨跌:不是所有关系都受益
把结果按谓词类别拆开,能发现一个很有意思的分化:语言先验的增益主要集中在两类谓词上。
一类是高频但视觉线索弱的谓词。比如on、has、with,它们的视觉特征高度相似,模型经常搞混。语言先验在这里起作用的方式很直接:当主语是"桌子"、宾语是"书"时,先验强烈偏向on,直接把视觉分支犹豫不决的分数拉开。
另一类是中等频率的动作类谓词。比如eating、riding,这些词在视觉上其实有线索,但训练样本太少,视觉分支学不扎实。语言先验提供了"人+食物 → 吃"、"人+马/自行车 → 骑"这样的语义惯性,把排序往上提。
反过来,语言先验对高度依赖视觉细节的关系帮助有限。比如<人,注视,猫>与<人,靠近,猫>,主语宾语完全相同,区别全在人的姿态和朝向里。此时语言先验给两者的分数几乎一样,最终还是得靠视觉分支来定。这也解释了为什么融合而不是替代是正解。
| 谓词类型 | 代表例子 | 纯视觉表现 | 加入语言先验后 | 主要原因 |
|---|---|---|---|---|
| 高频通用 | on、has、with | 尚可但容易混 | 显著提升 | 先验能区分高频类之间的倾向 |
| 中频动作 | riding、eating | 学习不充分 | 明显提升 | 语义惯性补充训练样本不足 |
| 高频空间 | above、below | 依赖几何特征 | 提升有限 | 视觉几何信息本身较强 |
| 细微动作 | looking at、pointing at | 差 | 帮助有限 | 区分需要视觉细节,语言无法提供 |
4.3 零样本设置下语言先验的真正价值
零样本关系检测是这篇论文最能展示语言先验实力的赛场。这里测试的三元组组合在训练中完全没出现过,视觉分支面对的是彻底的分布外输入。纯视觉模型在这种设置下几乎只能依赖物体类别特征猜测,Recall值非常低。
频率先验在零样本组合下也基本失效,因为它的概率表里根本没有这个组合的条目。真正起作用的是词向量先验:即使"人-斑马"没有出现在训练集里,但由于"斑马"与"马"的词向量接近,映射函数可以把训练中"人-马 → 骑"的知识迁移过去,给"人骑斑马"一个合理的先验分数。
我在复现时专门跑了这一组实验,结论和论文一致:zero-shot 场景下加入词向量先验后,指标提升幅度比全监督场景更大。这也给所有做组合泛化的研究者一个启发:如果希望模型对没见过的概念组合有基本判断力,必须在特征空间里保证"语义相近的东西距离近",这是语言预训练向量带来的免费午餐。
4.4 定性结果:模型输出到底能不能看
论文里做了个挺有意思的人类评估实验:让被试者判断一段关系描述是机器生成的还是人类标注的,结果很多人区分不出来。这类"图灵测试式"的评估虽然有主观性,但至少说明了加入语言先验后,模型输出的关系三元组在"语言合理性"上有了本质提升。
不过,我复现时也发现定性结果里翻车的情况不少。最常见的错误是:语言先验太强时,模型会输出"常识上可能但图像中不存在"的关系。比如一张"两个人并排走"的图,模型可能会输出<人,推,人>,因为先验里"人+人+推"并不罕见。这类错误在指标上不一定暴露(可能因为某个真实关系被排序在后导致recall变化不明显),但在实际应用中是致命伤——这再次印证,语言先验只能作为辅助,不能成为主导。
5. 复现这篇论文时最容易踩的坑
5.1 数据集预处理:一个定义不清就会毁掉实验
VRD 数据集的原始格式是 JSON 标注,包括图像 id、物体框、物体名词、关系三元组。第一步要把这些解析成统一的样本索引,看起来简单,但要处理几个容易出错的点。
第一,主语和宾语的多词名称问题。物体类别"bike"和"bicycle"可能同指一个东西,但会被当成两个不同类别;关系标注里的主语宾语有时候是短语而不是单词。做词向量先验前,必须统一词表,把多词短语映射到核心名词,否则词向量查不到会产生大量噪声。我在复现时选择直接以数据集官方给的物体类别名称为准做映射,效果比较稳。
第二,训练/测试划分中的信息泄漏。语言先验的统计是全局统计,如果在统计p(predicate | subject)时混入了测试集三元组,零样本评测就失效了。处理方式是严格只用训练集划分的标注来统计先验和训练词向量映射函数。
第三,负例的处理口径。很多复现版本会忽略负例,只在有标注关系的物体对里做排序。这在实际使用时会有问题:真实图片里大部分物体对之间没有显式关系,推理时会给你输出一堆毫无意义的关系。建议在训练视觉分支时加入 background 类,让模型学会不和物体对之间随便建立关系。
5.2 词向量选择与映射网络训练的细节
论文里的词向量即便是用预训练模型,word2vec 本身也有版本和语料差异。我试过对比两个不同语料训练的 word2vec,对最终指标的影响大概是1到2个点。如果你的实验对最终结果敏感,建议在论文基础上固定一个公开可复现的词向量版本,并说明清楚。
映射网络训练时容易过拟合。输入是(subject_vec, object_vec),输出是谓语向量,监督是真实谓词的 word2vec 向量。训练样本量不大(数千到几万条三元组),网络如果太大,很容易在训练集上 loss 很低,但预测的谓语向量落在语义上不合理的区域。我的做法是:隐藏层用一层就够了(比如256维),加 dropout,训练中监控验证集上的余弦相似度而不是训练 loss。此外,对输出向量做 L2 归一化也有帮助,因为最终比较用的是余弦相似度,让输出落在单位球面上更稳定。
5.3 训练和推理阶段的不一致问题
视觉分支训练时用的是真实物体框,但是在 Relationship Detection 评测里,测试输入是检测器输出的预测框。检测框和真实框之间存在位置偏移,特征分布会漂移。这在论文的 Predicate Prediction 子任务里不明显,因为用的是真实框,但放到联合检测时就非常突出。
处理方式通常两种:一是端到端微调检测器和关系分支;二是把检测结果作为候选框,在验证集上校准视觉特征(例如对检测框特征做简单的scale归一)。前者效果更好但工程复杂,后者实现快但上限有限。论文本身更偏重方法验证,所以复现时建议先跑通 Predicate Prediction 子任务,再处理联合检测的工程问题。
还有一个一致性问题是语言先验的"全局高频偏置"。统计先验天然偏向高频谓词,如果不加控制,融合后会让模型的预测分布整体向高频类收缩。我在调参时发现,把语言分支的分数先做一次 z-score 归一化(或log变换),再与视觉分数相加,比直接加原始分数更稳。这个经验论文里没写,但对复现效果影响不小。
5.4 零样本评测的计算规范
零样本关系检测的评测集定义是:测试三元组中的(subject, object)组合不出现在训练集标注里。这里有两个细节容易搞错。
第一,组合是否出现是按(subject, object)对来判断,还是按<subject, predicate, object>完整三元组判断?论文里用的前者。如果你的复现代码用了后者,零样本集合会变大,指标也会不同,和别人对比时就会出现对不上数的情况。第二,计算 Recall@k 时,排序列表里要不要过滤语法不合理的组合?论文的做法是在候选里排除不满足语法规则的组合,这也会影响分母,必须在代码里保持一致。
我当时因为没注意这两点,跑了将近一周的实验结果和论文对不上,后来逐行查评测代码才发现是排序候选集过滤逻辑的问题。建议复现时先拿论文公开的评测代码核对,再自己写。
6. 从这篇论文看语言先验思路的后续演化
6.1 语言先验如何一路走向视觉-语言预训练
这篇论文发表于2016年,放在当时的背景里看,它属于较早把"语言统计规律"作为一等公民引入视觉推理的工作。沿着这条思路往后的演化脉络其实非常清晰。
2017年的 VTransE 用翻译嵌入(translation embedding)直接建模<subject, predicate, object>的向量关系,思想是subj_vec + pred_vec ≈ obj_vec,本质上是对语言先验做更紧致的结构化建模。2018年的 Neural Motif Networks 则指出了另一个方向的先验:视觉关系数据集中存在很强的"上下文偏置"(比如某些物体的出现会带动周围关系模式),这也可以视为一种更高阶的语言/常识先验。到了 Oscar、VinVL 这类视觉-语言预训练模型,object tags 被显式用作视觉和文本之间的锚点,说白了就是用语言标签来帮助视觉理解,只是"先验"变成了可学习的跨模态对齐。
还有一个不能再明显的继承者是 CLIP 这类大规模图文对比模型。它们做的是在海量图文对上学p(text | image)的分布,这不就是一种从数据中涌现的语言先验?区别只在于:2016年这篇论文是先验来自小规模标注统计,今天的模型先验来自上亿图文对。底层逻辑一脉相承:视觉理解任务里的很多不确定性,本质上要靠语言的分布规律来消解。
6.2 现在复现这篇论文还有什么现实价值
坦率地说,今天再拿这个方法去打 SOTA 已经不现实了。大规模预训练模型的 zero-shot 和组合泛化能力已经远超这种手工设计先验。但我觉得它仍然值得复现和学习,原因有三。
第一,它是理解 Scene Graph Generation(场景图生成)的最佳入口。今天所有场景图相关的论文,基本都绕不开关系检测这个基础任务,而这篇是少数把"为什么关系检测难、语言先验为什么有用"讲得特别清楚的方法之一。
第二,它提供了一个"先验建模"的范式课。做多模态任务时,"先验从哪来、怎么和视觉融合、融合强度怎么控制"这三个问题绕不开,这篇论文用最简单的方式演示了一遍完整流程。遇到新任务时,我习惯先问自己能不能用这篇论文的思路搭一个 baseline,往往比一上来就堆模型有效得多。
第三,它对长尾和组合泛化问题的剖析,在今天仍然不过时。现在多模态大模型在常识性的组合上很强,但在细粒度、罕见组合上依然会犯低级错误。理解"用语言先验兜底"这个思想,对设计评测集、诊断模型失效模式依然有直接参考价值。
我个人的体会是:复现经典论文的价值,不在于再用它做实验产出,而在于借它的简单设计看清一个问题的本质结构。这篇论文用很朴素的语言先验讲明白了视觉关系检测的核心矛盾,这个收获比任何指标提升都值。