CVPR2020 | 最新Scene Graph Generation開源框架與SGG的一些碎碎念
- 2020 年 3 月 13 日
- 筆記
前言:
2019上半年跌跌撞撞地搞了很多亂七八糟的東西但都沒work,尤其讓我酸的是我上半年沒做work的一個VQA的idea居然在同年ICCV看到一篇極其相似的文章,雖然對方取巧用了BERT硬是提了一點才中的,但真的沒產出的時候看著別人發paper太酸了。話雖如此,取巧用idea以外的trick發paper還是不值得學習的。同年下半年,受含老師的影響(要求),我去看了很久的《The Book of Why》來尋找靈感,最後到了臨近CVPR deadline,還是回歸了自己的老本行場景圖生成,投稿了一篇《Unbiased Scene Graph Generation from Biased Training》,並幸運的以(SA,SA,WA)的分數中了今年的CVPR 2020。結合我之前對SGG領域的了解,我認為目前SGG領域內關於不同message passing模型設計帶來的提升已經趨於飽和,且這個研究方向目前來看已經愈發沒有意義,因為由於自然存在以及數據標註中的bias和長尾效應(long-tail effect), 所謂的模型優化已經漸漸變成了更好的擬合數據集的bias,而非提取真正有意義的relationships。在此基礎上,我在該工作中主要做了如下兩件事:1)延續我去年在VCTree(CVPR 2019)中提出的mean Recall@K,設計了一個unbias的inference演算法(注意不是training方法哦~),並希望讓更多的人關注真正有意義的SGG,而不是去擬合數據集刷指標;2)由於之前通用的SGG框架neural-motifs已經落後於時代,我設計了個新的程式碼框架(已於Github開源)。不僅結合了最新的maskrnn-benchmark用於底層物體檢測,同時集成了目前最全的metrics包括Recall,Mean Recall,No Graph Constraint Recall, Zero Shot Recall等,同時程式碼中為各種指標的evaluation統一了介面,希望後續有更多的研究者們可以設計出更有價值的metrics,從而使SGG領域不至於再只關注一個biased指標Recall而淪為灌水聖地。
另外:感謝牛玉磊同學對我最新paper里一些核心idea的貢獻,還有史佳欣同學參與的部分程式碼實現。
框架說明:
如果在過去兩年做過SGG的同學應該都或多或少知道neural-motifs程式碼框架,雖然Rowan Zellers本身是我非常崇拜的一位大神,但是他neural-motifs程式碼里的各種炫技操作和神乎其神的trick,經常讓我非常頭大,更不要說糟糕的文檔和說明了。當然,真正讓我下定決心要寫個新的Codebase的主要原因還是因為neural-motifs的底層Faster R-CNN已經過於落後,畢竟SGG里物體的檢測和識別也是非常重要的,甚至有時候比預測relationship更加重要。為了便於大多數有物體檢測背景的同學follow,我挑選了去年最當紅,哦不,最為可靠的facebookresearch/maskrcnn-benchmark框架作為基礎,在其基礎上搭建了我的Scene-Graph-Benchmark.pytorch。該程式碼不僅兼容了maskrcnn-benchmark所支援的所有detector模型,且得益於facebookresearch優秀的程式碼功底,更大大增加了SGG部分的可讀性和可操作性(BoxList類的設計簡直是人類工程智慧的結晶,崇拜,從早年的faster-rcnn框架過來的我熱淚盈眶)。目前我們框架提供的各種baseline模型,有著當之無愧的State-of-The-Art SGCls和SGGen結果(如下圖,PredCls我還需要花時間調一下)。由於復現版本的VCTree為了簡便省略了原文的Hybrid Learning,同時SGGen/SGCls/PredCls超參也做了統一,而非各自最優,所以此處的PredCls低於VCTree原文。
傳送門:
https://github.com/KaihuaTang/Scene-Graph-Benchmark.pytorch

Recall@K for Iterative Message Passing(IMP), Neural Motifs, Transformer,VCTree
Faster R-CNN預訓練
該項目的Faster R-CNN預訓練部分基本完全採用maskrcnn-benchmark的源程式碼(雖然我們又增加了attribute_head的實現,但還沒開始正式使用),僅就數據集做了更換,換成SGG的VisualGenome數據集。因此後續研究者可以完全參考maskrcnn-benchmark的程式碼設計新的detector,並運用於SGG中。下面是一個訓練命令行樣例:
CUDA_VISIBLE_DEVICES=0,1,2,3 python -m torch.distributed.launch --master_port 10001 --nproc_per_node=4 tools/detector_pretrain_net.py --config-file "configs/e2e_relation_detector_X_101_32_8_FPN_1x.yaml" SOLVER.IMS_PER_BATCH 8 TEST.IMS_PER_BATCH 4 DTYPE "float16" SOLVER.MAX_ITER 50000 SOLVER.STEPS "(30000, 45000)" SOLVER.VAL_PERIOD 2000 SOLVER.CHECKPOINT_PERIOD 2000 MODEL.RELATION_ON False OUTPUT_DIR /home/kaihua/checkpoints/pretrained_faster_rcnn SOLVER.PRE_VAL False
主要參數文件可以參考"configs/e2e_relation_detector_X_101_32_8_FPN_1x.yaml"。注意所有config文件的default設置在"maskrcnn_benchmark/config/defaults.py"中,而優先順序如下,具體每個參數的意義可以參考我Github項目中的解釋。
命令行參數 ===》(覆蓋)===》xxx.yaml參數 ===》(覆蓋)===》defaults.py參數
當然因為detector的訓練費時費力費卡,而且考慮到公平起見以往大部分SGG的工作都會follow一個固定的預訓練模型,即neural-motifs項目所給的預訓練detector模型。我這裡也release了一個我訓練好的ResNeXt-101-32×8模型,鏈接詳見原知乎原文
值得一提的是,我們的Faster R-CNN其實還支援了attribute_head,即物體的屬性,但為了公平比較,我目前還沒有在發表的文章中加入對應實驗,歡迎大家完善這部分工作。
SGG as RoI_Head
關於最主要的SGG部分,我將其設計為了一個roi_head,參考其他roi_heads,如box_head的設計,我將主要程式碼集中於"maskrcnn_benchmark/ modeling/ roi_heads/ relation_head"目錄下,結構如下:

relation_head程式碼結構
所以,如果想要設計自己的新模型,大部分情況下,只需要修改 "roi_relation_predictor.py" 以及增加相應的"model_xxx.py"和"utils_xxx.py"足矣。目前我們框架所提供的模型有:
- Neural-MOTIFS: arxiv.org/abs/1711.0664
- Iterative-Message-Passing: arxiv.org/abs/1701.0242
- VCTree: arxiv.org/abs/1812.0188
- Transformer (由史佳欣實現,沒有發表單獨的paper)
- Causal-TDE:arxiv.org/abs/2002.1194
- MOTIFS: arxiv.org/abs/1711.0664
- VCTree: arxiv.org/abs/1812.0188
- VTransE: arxiv.org/abs/1702.0831
他們可以通過"MODEL.ROI_RELATION_HEAD.PREDICTOR"參數進行選擇。因為最後一個Causal-TDE是我今年CVPR2020提出的一個特殊的Inference方式,而非具體的模型,所以它測試了三種模型。我們會在下文無偏見的場景圖生成中具體的介紹這個方法。
所有指標簡介
作為基礎知識,我先簡單介紹下SGG的三種設定: 1) Predicate Classification (PredCls): 給定所有ground-truth的物體類別和bounding boxes,求這張圖的場景圖。2)Scene Graph Classification (SGCls):給定所有ground-truth物體的bounding boxes,求場景圖(需要預測物體類別)3) Scene Graph Detection/Generation (SGDet/SGGen) :只給圖片,自己跑detection檢測物體,最後預測場景圖。
然後關於指標,之前的SGG最大的問題之一,就是過度依賴單一指標Recall@K,而這個指標因為不區分各個類別的貢獻加上VisualGenome本身的長尾效應,很容易被過擬合。這導致了近年大多數SGG的文章,只是在過擬合的道路上越走越遠,而非真正生成了更有意義的場景圖。於是我在該框架中整合了已知的所有指標,也希望後續的工作可以report更多有意義的指標來分析演算法有優缺。下圖是我程式碼的一個結果輸出樣例。

項目在測試/驗證時的輸出格式
所有本框架支援的指標有:
- Recall@K (R@K): 這是最早的也是最廣為接受的指標,由盧老師在arxiv.org/abs/1608.0018中提出。因為VisualGenome數據集的ground-truth中對relationship的標註並不完整,所以簡單的正確率並不能很好的反映SGG的效果。盧老師用了檢索的指標Recall,把SGG看成檢索問題,不僅要求識別準確,也要求能更好的剔除無關係的物體對。
- No Graph Constraint Recall@K (ngR@K):這個指標最早由Pixel2Graph使用,由Neural-MOTIFS命名。這個指標的目的在於,傳統的Recall計算里,一對物體只能有一個relation參與最終的排序,但ngR@K允許一對物體的所有relation都能參與排序。這也非常有道理,比如 human(0.9) – riding (0.6) – horse (0.9):total score=0.9×0.6×0.9,可能這對物體還有另一個relation:human(0.9) – on (0.3) – horse (0.9):total score=0.9×0.3×0.9。後者雖然分數比riding低,但也是一種可能的情況。ngR@K的結果往往大大高於單純的R@K。
- Mean Recall@K (mR@K):這個指標由我的VCTree和另外一個同學的KERN在2019年的CVPR中同時提出,不過我並沒有作為VCTree的主要貢獻,只在補充材料中完整展示了結果表。由於VisualGenome數據集的長尾效應,傳統Recall往往只要學會幾個主要的relation類比如on,near等,即便完全忽視大部分類別也可以取得很好的結果。這當然不是我們想看到的,所以mean Recall做了一件很簡單的事,把所有謂語類別的Recall單獨計算,然後求均值,這樣所有類別就一樣重要了。模型的驅動也從學會儘可能多個relation(有大量簡單relation的重複)變成學會儘可能多種類的relation。
- Zero Shot Recall@K (zR@K):在早期的視覺關係識別中,人們也使用了Zero Shot Recall指標,但在SGG中又漸漸被人忽視了,我們在這又重新增加了這個指標,因為它可以很好的展示SGG的拓展能力。Zero Shot Recall指的並不是從來沒見過的relation,而只是在training中沒見過的主語-謂語-賓語的三元組組合,所有單獨的object和relation類別還是都見過的,不然就沒法學了。
- Top@K Accuracy (A@K):這個指標來自於某個之前研究者對PredCls和SGCls的誤解,並不建議大家report到文章中,這裡列出來是希望大家以後別犯這個錯。該同學在PredCls和SGCls中不僅給了所有object的bounding box,還給了主語-賓語所有pair的組合,所以這就完全不是一個Recall的檢索了,而是給定兩個物體,來判斷他們relation的正確率。
- Sentence-to-Graph Retrieval (S2G):最後是我在Causal-TDE中提出的ground-truth caption到SG檢索,它可以看成一個理想的下游任務,可以看作一個VQA:問指定圖片的SG符不符合給定描述。他的意義在於,他完全摒棄了visual feature,只使用符號化的SG。他可以測試檢測出的SG是否可以用來完整地豐富地表示原圖(潛台詞:從而支援符號化的推理)。由於這需要額外的訓練過程,所以並不能直接在SGG的val/test里輸出。
當然現有的metrics也許仍然不夠完善,如果有更好的能體現SG效果或有助於分析SGG的指標,我的程式碼框架也支援自定義指標,本項目的所有指標都通過如下類實現,可以添加至"maskrcnn_benchmark/data/datasets/evaluation/vg/sgg_eval.py"並在同目錄下的"vg_eval.py"中調用。
class SceneGraphEvaluation(ABC): def __init__(self, result_dict): super().__init__() self.result_dict = result_dict @abstractmethod def register_container(self, mode): print("Register Result Container") pass @abstractmethod def generate_print_string(self, mode): print("Generate Print String") pass # Don't forget the calculation function # def calculate_recall() # return
常見誤區
在從事SGG研究的這兩年里,我從看的paper,程式碼,和自己當reviewer審的paper里,發現兩個常見的誤區,這會導致結果異常的高。我假設這些作者是出於無心,所以在此澄清。
- 不加區分No Graph Constraint Recall@K (ngR@K)和Recall@K (R@K)。除了早期最開始提出ngR的Pixel2Graph外,我還看到過一些人,直接將自己ngR的結果和別人單純Recall的結果作比較,聲稱自己是state-of-the-art的結果。這是非常不公平的。他們常見的癥狀有:1)沒有明顯理由的情況下PredCls的Recall@100大於75%,2)文中沒有明確提及ngR和R的區別。目前比較合理的推測是,在VisualGenome數據集中,傳統Recall@100在Predcls上的上限應當在70%左右。因為數據集的不完整性,其實很多預測都是正確的,只是數據集里沒標,所以這造成了傳統Recall永遠無法達到理論的100%。那麼如果自稱PredCls的Recall@100大於75%的,多半其實是用了ngRecall。
- 在PredCls和SGCls中,使用Top@K Accuracy (A@K)來報告成Recall,這又是另一個誤區。因為PredCls和SGCls中給定的只是所有object的bounding box,而非具體的主語-賓語的pair資訊。一旦給定了pair資訊,那麼其實就沒有recall的ranking了,只是純粹的accuracy。這個誤區最初發現於contrastive loss中。我花了小半個月才發現為什麼他的PredCls和SGCls結果這麼好。它的癥狀也很簡單,PredCls和SGCls中的Recall@50, Recall@100結果一摸一樣,只有Recall@20稍低。因為沒有了ranking,top50和100也就沒區別了,沒有圖片有多於50個的ground-truth relationships。
- 額外的話
- 對於一些模型,打開或關閉 "MODEL.ROI_RELATION_HEAD.POOLING_ALL_LEVELS" 這個設置會很大地影響relation的預測,比如如果在VCTree的Predcls中關閉這個設置,就可以提升結果,但在對應的SGCls和SGGen中卻不行。上文的VCTree結果,我為了統一都打開了這個設定。
- 對於一些模型(不是全部),使用Learning to Count Object中提出的一種fusion方法,可以顯著地提升結果。這種fusion演算法的公式為:
使用方法是,在 "roi_relation_predictors.py" 中,將主語賓語特徵的混合方式改為這個公式,目前項目中使用的為簡單的torch.cat((head_rep, tail_rep), dim=-1)。
- 另外更不要說一些hidden_dim的參數了。我想表達的是,因為時間的限制(還有卡的),我其實沒有對這個項目做過多的調參,我寫的一些超參我也沒有完整測試。如果有打比賽經驗的同學,通過修改參數達到更好的結果我一定不會驚訝。
無偏見的場景圖生成(和一些不成熟的想法)
其實在我接觸SGG以來最大的困惑不是SGG做的不好,而是現在的SGG如果單看Recall明明已經非常好了,但為什麼仍沒有得到廣泛的應用。換句話說,SGG畫的餅這麼香為什麼沒人吃,雖然也有一些下游任務中有人利用SGG發了paper,但都沒有成為主流。我的想法是relationship是一個非常主觀且很依賴語境的標籤,這不同於簡單的物體分類和識別,在後者中對就是對錯就是錯。而我的理解里單純的deep learning的本質是memorize所有數據集,並做一些簡單的歸納匯總(一定程度的generalize),這是一個非常passive的過程,所以在簡單的分類識別等passive的任務上效果最為顯著。比作人類的話,這可以看作人下意識情況下做的一些工作(我們從來不知道自己是怎麼識別出蘋果的,識別的時候純粹是下意識反應,也不會去思考為什麼是蘋果)。最新的NLP中的研究也發現,進入Transformer時代後,很多簡單的問答,機器已經可以做的非常擬人了。但如果稍微在問題中加入一個簡單的1+1之類的需要思考的過程,模型瞬間給出很多滑稽的結果。所以現在的deep learning學出來的模型,更像一個被動的只靠直覺驅使的人。而SGG和任何需要Reasoning的任務比如VQA,都更加主動和主觀。比如SGG中到底是(human, on, horse)還是(human, riding, horse),取決於語境是關注「what's on horse?(空間關係)」還是「what is the man doing?(具體動作)」,不能簡單地說誰對誰做(如果直接歸為多類別問題的話,又會因為同義詞太多而無法得到足夠好的標註)。我不認為現在的大部分推理(Reasoning)的文章,真正做到了推理(雖然我們組就是做這個的),加了很多attention也只是更好的擬合了數據集而已。因為給定同一份輸入,一個確定的網路(假設不包含隨機sample等操作)永遠只會得到一個結果,是一個passive的回饋。而我們最新的CVPR 2020的文章「Unbiased Scene Graph Generation from Biased Training」就嘗試通過對因果圖的干預,使同一份輸入,同一個網路,得到不同的輸出,用於不同的目的。類似於人對於同一個問題,可以在腦海里思考各種可能選擇,做出權衡。我覺得這可能是未來真正解決Reasoning的一個可能方向,用一個deep model擬合對世界的認知(知識),但真正的推理在於對這些知識的應用和分析,即對模型中各個節點進行不同的干預後的觀察。當然這種干預用什麼去驅使,RL?Sample?還有待考慮(這怕不是要靠意識本身)。
扯遠了,回到SGG中。雖然我們還是無法解決用什麼標準去評價一個更主觀更有意義的SGG,但肯定不是目前最主流的Recall@K,在這種指標下即便模型只學出了on/near/wear/has等少數幾種relationship,模型的Recall@K依然非常高,這種場景圖對下游reasoning任務的幫助非常有限,簡單的來說沒有引入足夠多的資訊量,那麼一個最簡單的辦法,就是讓relationship儘可能地diverse,學到儘可能多種類的relationship就有更多資訊量了。所以去年CVPR 2019,我在VCTree這篇文章里提了個mean Recall的指標用於評價一個無偏見的場景圖,上文也作了介紹。
KERN與VCTree(CVPR 2019)
其實同年,有兩篇工作同時提出了一摸一樣的mean Recall的指標,名字都一樣。除了我們的VCTree還有一篇叫KERN的工作。這兩篇工作首次開始關注,怎麼衡量和解決無偏見的場景圖。且這兩篇給出的solutions都和網路的message-passing的結構有關。傳統的方法往往通過attention來體現這種結構,這樣也就將結構和feature一起end-to-end地訓練了,就非常容易造成網路的過擬合,即產生結果完全biased到一些大類中:on/near。那麼我們來看看VCTree和KERN給的兩種解決方案。
1)KERN使用了一個基於統計的message-passing structure,這個結構是一個hard structure無法學習(基於他們的程式碼,我發現這個結構matrix被設為requires_grad = False)。既然結構都不學了,所以也就不存在結構上的過擬合。
2)VCTree則用一個獨立的分支來學習structure,並確保feature learning的分支和structure learning的分支沒有/中斷反向傳播。這也就保證了,結構和feature不會共同過擬合到一起。下圖是我對VCTree核心idea的一個概覽圖。

Causal TDE Inference (CVPR 2020)
到了今年的CVPR,我們做了一個更大膽的構想。我們將常見的MOTIFS和VCTree等模型歸納為了如下的因果圖。因果圖中每個節點為一些關鍵變數,而其中的有向邊就是對各種網路forward運算的簡化,僅體現了一種因果上的決定關係。

然後,我們分析了這些模型中偏見到底來自於哪裡。我們認為,SGG中的偏見主要來源於下圖右下角的那種場景,即不看具體的兩個物體的狀態(feature),單純通過兩個物體的label和一個union box的環境資訊,就盲猜這兩個物體是什麼relationship。因為VisualGenome數據集的bias和長尾效應,偏偏這種盲猜不僅更容易學習還大部分情況下都是對的。這就導致了模型不再關注物體具體的狀態(feature)而直接take了盲猜的biased shortcut(畢竟deep learning永遠優先收斂到各種shortcut上)。導致的結果就是,具體的visual feature不再重要,也就預測不出真正有意義的finegrain的relationships了。因為更finegrain的relation出現太少,而且很容易錯,所以乾脆把所有複雜的sitting on/standing on/riding全預測成on。

然後就產生了下圖C子圖中的非常沒有意義的scene graph,偏偏Recall還挺好。因為標註員本身就傾向於標這些簡單的relationships。(標註員標註時候的狀態基本就像我上文說的passive回饋狀態,所以標出來的relationship都趨同,沒有現實中真正結合語境時的那種靈活。一些Reasoning的任務,比如VQA,的標註也有類似問題。這些思維上比較active的任務,如果要求標註員自己列舉各種語境下不同的結果(SGG)或標註的同時給出腦海里思考的過程(VQA),又顯得代價太高昂而不現實)。

於是為了迫使模型預測更有意義的relationship,我們用了一個causal inference中的概念,即Total Direct Effect(TDE)來取代單純的網路log-likelihood。Effect的概念可以理解為醫學上為了確定一個葯真正的療效,我們除了觀測患者服藥後的表現(傳統模型的預測),還要減去這個葯的安慰劑效應(通過控制變數/反事實幹預得到的bias)。這就體現在了下圖中。我們的Unbiased TDE是一個除去了盲猜帶來的「安慰劑效應」後的真正的relationship可能性。

雖然簡單,這樣的方法卻取得了非常有效的效果,從下圖的各個predicate的單獨Recall結果就可以看出。注意!有的人或許會問,為什麼不一開始就刪除模型中biased shortcut分支,只用feature預測relationship。這就好比去擬合Y=aX+b,想要得到一個無bias:b的模型,我們不能單純的就用Y=aX去擬合數據,而是要先構建出Y=aX+b擬合完了再刪去b。我們實驗部分也證明了單純去掉biased shortcut分支,並不會直接得到最好的unbiased SGG。另外,還有一些額外的使用TDE的trick,可以去我的Github上看對應的Tips and Tricks。

總結和展望
最後我想在總結里再嘮叨嘮叨場景圖的應用,我一直覺得場景圖是visual reasoning的基礎,但同時現在的SGG還遠不足以支撐visual reasoning。即便我提出的Unbiased SGG也還有很長的路要走。而具體怎麼使用SG又是另一個問題了。
我們腦子裡想事情的時候,往往都是先構建事物之間的相互關係,然後思考某些事物處於特定狀態下對其他相關聯事物的影響,然後關聯事物的關聯事物的影響。。。。這裡我指的既可以是Unbiased SGG中提到的因果圖,也可以是將SG運用於Visual Reasoning任務中後的SG。但重要的一點是,我覺得真正的Reasoning肯定不再是簡單的用模型跑個one-time feedforwad。而是類似於:(1):干預1->觀察1->Output1, 干預2->觀察2->Output2, …. (2):{Output1, Output2,…..}->比較分析。
絮絮叨叨地說了一堆,最後都說的有點亂了。很多東西我也只是粗淺的不成熟的想法,有待更多的實驗和研究去驗證。畢竟不知道對不對,不知道work不work,不知道會發生什麼,不知道結果為什麼是這樣,才是科研的常態。希望大家多多嘗試些更有意思的想法,在state-of-the-art每天更新的時代,提升零點幾個點也許遠沒有嘗試些新的可能性,或者對一些現象做出有參考價值的分析來得有意義,哪怕performance不是最高也不要氣餒。說起來我的Causal TDE還降低了傳統指標Recall幾個點呢。
如果大家覺得我的Paper和開源的程式碼對大家的研究有任何啟發或者幫助的話,記得來引用我們的paper啊~
@inproceedings{tang2018learning, title={Learning to Compose Dynamic Tree Structures for Visual Contexts}, author={Tang, Kaihua and Zhang, Hanwang and Wu, Baoyuan and Luo, Wenhan and Liu, Wei}, booktitle= "Conference on Computer Vision and Pattern Recognition", year={2019} } @article{tang2020unbiased, title={Unbiased Scene Graph Generation from Biased Training}, author={Tang, Kaihua and Niu, Yulei and Huang, Jianqiang and Shi, Jiaxin and Zhang, Hanwang}, journal={arXiv preprint arXiv:2002.11949}, year={2020} }
———- 2020.02.29


