DETR精讀筆記
論文:End-to-End Object Detection with Transformers (發表于 ECCV-2020)
代碼:https://github.com/facebookresearch/detr
解讀視頻:DETR 論文精讀【論文精讀】 本筆記主要基于 Yi Zhu 老師的解讀
引言
2020年5月出現在 arXiv 上的 DETR(DEtection TRansformer)可以說是近年來目標檢測領域的一個里程碑式的工作,兩年間引用量就已經超過 2000,并且有一系列基于它的改進工作,如 Deformable-DETR, Omni-DETR, up-DETR, PnP-DETR, SMAC-DETR, DAB-DETR, SAM-DETR, DN-DETR, OW-DETR, OV-DETR, …。其他有這么多直接加前綴的改進工作的論文如 ResNets、RCNNs、YOLOs,都已經成為了 CV 領域大家耳熟能詳的工作。
從論文題目 End-to-End Object Detection with Transformers 中不難看出,本文的關鍵創新點有兩個:end-to-end(端到端)和 Transformer。眾所周知,不同于簡單的圖像分類任務,目標檢測任務的定義更為復雜,模型需要同時預測圖像中物體的種類和位置,并且通常每張圖片的物體個數都是不一致的。因此,現有的目標檢測方法都有著復雜的流程。現有的主流目標檢測方法大致可分為 proposal-based,anchor-based,anchor-free 幾類方法,它們都需要借助先驗知識,先為模型提供一些候選的區域或者錨框,并且在后處理時, NMS(Non Maximum Suppression)操作基本是必不可少的。這樣看來,現有的目標檢測方法都不能稱為嚴格意義上的 “端到端”。實際上,這種復雜的架構可以看作是一種間接的、 “曲線救國” 的方式,因為在之前,直接端到端地做目標檢測任務,無法取得很好的結果,只能通過設計 anchor、proposal,NMS 等前后處理方式,來提升性能。
本文回歸到了目標檢測任務的本質,將其視作集合預測的問題,即給定一張圖像,預測圖像中感興趣物體的集合。DETR 不需要先驗地設置 anchor 和 proposal,并且通過借助 Transformer 的全局建模能力和 learned object query,DETR 輸出的結果中不會含有對于同一個物體的多個冗余的框,其輸出結果就是最終的預測結果,因此也不需要后處理 NMS 的操作,從而實現了一個端到端的目標檢測框架。具體來說,DETR 設計的點主要有兩個:
- 提出了一種新的目標檢測的損失函數。通過二分圖匹配的方式,強制模型輸出一個獨特的預測,對圖像中的每個物體只輸出一個預測框;
- 使用了 Transformer Encoder-Decoder 的架構。相比于原始的 Transformer,為了適配目標檢測任務,DETR 的 Decoder 還需要輸入一個可學習的目標查詢(learned object query),并且 DETR 的 Decoder 可以對每個類別進行并行預測。
本文最大的貢獻是提出了一個簡單的、端到端的、基于 Transformer 的新型目標檢測框架。實際上,DETR 的目標檢測性能與當時最強的檢測器相比還有不小的差距。但是作者指出,DETR 使用的模型本身只是一個最簡單的模型,還有很多目標檢測的網絡設計(如多尺度、可形變等)沒有進行探索。其他的檢測方法(如 RCNNs、YOLOs)也都是經過多年的發展才有了如今的性能,歡迎研究者們繼續基于 DETR 進行改進。后來在 DETR 提出的兩年時間里,出現了前面提到的大量的對于它的改進工作。甚至,不只是對于目標檢測問題,作者希望 DETR 成為一個更通用的視覺任務的框架,并且在原文已經驗證了 DETR 在全景分割任務上的有效性。在之后,也有一系列工作將 DETR 應用在了目標追蹤、視頻姿態預測、視頻目標分割等多種視覺任務上。因此,DETR 可以說是近年來目標檢測領域甚至整個 CV 領域一個里程碑式的工作。
我們首先通過圖1來大致看一下 DETR 的流程,具體的方法會在后面詳細介紹
DETR 的流程可以分為四個步驟:
-
通過 CNN 提取輸入圖像的特征;
-
將 CNN 特征拉直,送入到 Transformer Encoder 中進一步通過自注意力學習全局的特征;
關于 Encoder 的作用,一個直觀的理解是:Transformer Encoder 學習到的全局特征有利用移除對于同一個物體的多個冗余的框。具體來說,Transformer 中的自注意力機制,使得特征圖中的每個位置都能 attend 到圖中所有的其他特征,這樣模型就能大致知道哪一個區域是一個物體,哪塊區域又是另一個物體,從而能夠盡量保證每個物體只出一個預測框;
-
通過 Transformer Decoder 生成輸出框;
注意輸入到 Decoder 的,除了 Encoder 提取的特征之外,還有 learned object query(圖1中沒有畫出,后面會講)。圖像特征與 learned object query 在 Decoder 中通過自注意力機制進行交互,輸出預測框。輸出的框的個數是由 learned object query 決定的,是一個固定的值 NNN,原文中設置的是 100;
-
計算生成的 NNN 個輸出框與 Ground Truth 的損失函數;
關鍵問題是輸出的框是固定 NNN 個,但是實際數據集中的 GT 框一般只有幾個且個數不定,那么怎么計算 loss 呢?這里就是本文最關鍵的一個貢獻,通過二分圖匹配來選出 NNN 個輸出框中與 MMM 個 GT 框最匹配的 MMM 個,然后再像常規目標檢測方法一樣計算它們之間的損失,即分類損失和邊框回歸損失。
在推理時,直接對輸出的 NNN 個框卡一個閾值,作為預測結果即可。
可以看到,在整個 DETR 的流程中,確實不需要 anchor、proposal 的設置,也不需要后處理 NMS,因此說 DETR 是一個端到端的目標檢測框架。
方法
下面我們詳細來看一下 DETR 的具體方法。在原文的方法章節,作者分了兩個部分,第一部分介紹將目標檢測看做集合預測問題所用的損失函數,這部分是 DETR 能夠做成一個端到端的方法(無需前后處理)的關鍵;第二部分介紹了整個 DETR 的架構,就是更為詳細、完整地介紹了我們上面圖1中的流程,這一部分的關鍵在于 Transformer 網絡結構怎么用在整個 DETR 的框架中,還有怎樣設置 learned object query。接下來依次介紹這兩部分。
目標檢測的集合預測損失
之前提到,DETR 模型每次輸出的框的個數是固定的 N=100N=100N=100 ,自然地,這個 NNN 應該設置為遠大于數據集中單張圖片的最大物體數。但是,對于每一張圖片來說,GT 框的個數都是不一致的,并且通常只有幾個,那么怎么知道哪個預測框對應哪個 GT 框呢,怎么來計算損失呢?作者將這個問題轉化成了一個二分圖匹配的問題(optimal bipartite matching)。
我們先來介紹一下二分圖匹配到底是一個什么問題。假設現在有 3 個工人和 4 個任務,由于每個工人的特長不一樣,他們完成不同任務的時間(成本)也是不一樣的,現在的問題就是怎樣分配任務,能夠使得總成本最低。最直接的,用暴力遍歷的方法也能找出各種排列組合的最小成本值,但這樣的復雜度無疑是很高的。匈牙利算法是解決該問題的一個知名算法,能夠以較優的復雜度得到答案。
在 scipy 庫中,已經封裝好了匈牙利算法,只需要將成本矩陣輸入進去就能夠得到最優的匹配。在 DETR 的官方代碼中,也是調用的這個函數進行匹配。
from scipy.optimize import linear_sum_assignment
實際上,我們目前面對的 “從 NNN 個預測框中選出 MMM 個與 GT 對應的框” 的問題也可以視作二分圖匹配的問題。而這里所謂的 “成本”,就是每個框與 GT 框之間的損失。對于目標檢測問題,損失就是分類損失和邊框損失組成。即:?1{ci≠?}p^σ(i)+1{co≠?}Lbox(bi,b^σ(i))-\mathbb{1}_{\{c_i\ne\empty\}}\hat{p}_{\sigma(i)}+\mathbb{1}_{\{c_o\ne\empty\}}\mathcal{L}_{box}(b_i,\hat{b}_{\sigma(i)})?1{ci?=?}?p^?σ(i)?+1{co?=?}?Lbox?(bi?,b^σ(i)?) 。
其實這里與之前匹配 proposal 的 anchor 的方式類似,但是這里要的是 “一對一” 的對應關系,即強制要對每個物體只出一個框,因而不需要 NMS 的后處理。
在確定了預測框與 GT 框的對應關系之后,再按照常規的目標檢測方式去計算損失函數即可:
LHungarian(y,y^)=∑i=1N[?log?p^σ^(i)(ci)+1ci≠?Lbox(bi,b^σ^(i))]\mathcal{L}_{Hungarian}(y,\hat{y})=\sum_{i=1}^N[-\log \hat{p}_{\hat{\sigma}(i)}(c_i)+\mathbb{1}_{c_i\ne\empty}\mathcal{L}_{box}(b_i,\hat{b}_{\hat{\sigma}(i))}] LHungarian?(y,y^?)=i=1∑N?[?logp^?σ^(i)?(ci?)+1ci?=??Lbox?(bi?,b^σ^(i))?]
對于損失函數,DETR 還有兩點小改動:
- 一是對于分類損失,即上式前一項,通常目標檢測方法計算損失時是需要加 log?\loglog 的,但是 DETR 中為了保證兩部分損失的數值區間接近,便于優化,選擇了去掉 log?\loglog ;
- 二是對于邊框回歸損失,即上式后一項,通常方法只計算一個 L1L_1L1? 損失,但是 DETR 中用 Transformer 提取的全局特征對大物體比較友好,經常出一些大框,而大框的 L1L_1L1? 損失會很大,不利于優化,因此作者這里還添加了一個 Generalized IoU 損失,與 L1L_1L1? 一起,組成邊框回歸損失。
這都屬于小的實現細節了。
DETR 框架
DETR 的整體框架其實在引言的部分已經大致介紹過了,在原文方法部分的圖中,作者將更為詳細的流程畫了出來。下圖在原文的基礎上將一個典型的 3×800×10663\times 800\times 10663×800×1066 尺寸的圖像輸入在整個 DETR 框架后,整個數據流中各個張量的尺寸畫了出來,方便大家理解。
- backbone 部分就是用 CNN 去提取圖像特征,得到 2048×25×342048\times 25\times342048×25×34 大小的特征圖,然后經過一個 1x1 Conv 降低一下通道數,得到特征尺寸為 256×25×34256\times25\times34256×25×34
- 然后要將 CNN 特征送入 Transformer,而 Transformer 本身是沒有位置信息的,因此需要加上一個同尺寸的位置編碼(positional encoding)。然后將帶有位置編碼的特征圖拉直為 850×256850\times256850×256 ,送入 Transformer Encoder,Transformer Block 的輸入輸出特征尺寸是不變的,經過幾層之后,得到原尺寸 850×256850\times256850×256 的特征;
- 然后將得到的全局圖像特征送入 Transformer Decoder,這里還要同時輸入的是 learned object query,它的尺寸是 100×256100\times 256100×256 ,256 是為了對應特征尺寸,100 則是要出框的個數 N=100N=100N=100 。learned object query 是可學習的,與模型參數一起根據梯度進行更新。在 Decoder 中,圖像全局特征與 learned object query 做交叉注意力 (cross attention),最終得到的特征尺寸的為 100×256100\times 256100×256 ;
- 最后,就要拿 100×256100\times 256100×256 特征通過檢測頭輸出最終的預測,這里的檢測頭是比較常規的,就是通過一些全連接層輸出分類結果和邊框坐標。
再然后,就是按照之前介紹的二分圖匹配的方式找到匹配的框,然后計算損失,梯度反傳,更新參數即可。
為了說明端到端的 DETR 框架的簡潔性,作者在論文末尾給出了 DETR 模型定義、推理的 “偽代碼”,總共不到 50 行。之所以這里的偽代碼要加引號,是因為其實這已經不算是偽代碼了,而是直接可運行的 PyTorch 代碼。當然這個版本缺少了一些細節,但也完全能夠展現出 DETR 的流程了。該版本直接用來訓練,最終也能達到 40 的 AP。讀者可以對應偽代碼再過一遍剛才介紹的 DETR 完成流程,體會一下一個端到端的目標檢測框架有多么簡潔。
import torch
from torch import nn
from torchvision.models import resnet50class DETR(nn.Module):def __init__(self, num_classes, hidden_dim, nheads,num_encoder_layers, num_decoder_layers):super().__init__()# We take only convolutional layers from ResNet-50 modelself.backbone = nn.Sequential(*list(resnet50(pretrained=True).children())[:-2])self.conv = nn.Conv2d(2048, hidden_dim, 1)self.transformer = nn.Transformer(hidden_dim, nheads, num_encoder_layers, num_decoder_layers)self.linear_class = nn.Linear(hidden_dim, num_classes + 1)self.linear_bbox = nn.Linear(hidden_dim, 4)self.query_pos = nn.Parameter(torch.rand(100, hidden_dim))self.row_embed = nn.Parameter(torch.rand(50, hidden_dim // 2))self.col_embed = nn.Parameter(torch.rand(50, hidden_dim // 2))def forward(self, inputs):x = self.backbone(inputs)h = self.conv(x)H, W = h.shape[-2:]pos = torch.cat([self.col_embed[:W].unsqueeze(0).repeat(H, 1, 1),self.row_embed[:H].unsqueeze(1).repeat(1, W, 1),], dim=-1).flatten(0, 1).unsqueeze(1)h = self.transformer(pos + h.flatten(2).permute(2, 0, 1),self.query_pos.unsqueeze(1))return self.linear_class(h), self.linear_bbox(h).sigmoid()detr = DETR(num_classes=91, hidden_dim=256, nheads=8, num_encoder_layers=6, num_decoder_layers=6)
detr.eval()
inputs = torch.randn(1, 3, 800, 1200)
logits, bboxes = detr(inputs)
實驗
定量性能對比
下面的表格給出了 DETR 與基線 Faster RCNN 的定量性能對比。最上面一部分的 Faster RCNN 的性能結果是 Detectron2 的實現,之所以將 Faster RCNN 分成兩部分,是因為 DETR 中使用了近年來很多新的訓練 trick,如 GIoU loss、更強的數據增強策略、更長的訓練時間,因此作者團隊添加這些策略重新訓練了 Faster RCNN,以作公平的對比。
通過觀察實驗結果表格,有以下結論:
-
近年來的新的訓練策略對于目標檢測模型的提升非常明顯。
對比表格的第一、第二部分,完全相同的模型,只是用了更優的訓練策略,基本能穩定漲兩個點。
-
在同樣的訓練策略、網絡規模大小的情況下,DETR 比 Faster RCNN 高 1-2 個點。
對比表格的后兩部分可以觀察到這一點,DETR 對比基線的 Faster RCNN 還是還是有提升的。
-
DETR 在大物體的檢測上遠超 Faster RCNN,但是在小物體的檢測上卻也低了不少。
表格的后三列分別是小、中、大物體的檢測性能,可以觀察到 DETR 在大物體的檢測上更出色,但是對于小物體的檢測甚至遠不如 Faster RCNN。大物體檢測性能的提升得益于 Transformer 結構的全局建模能力,且沒有預置的固定 anchor 的限制,因此預測框想多大就多大。而 DETR 在小物體上表現不佳,是因為本文中 DETR 的模型還是一個比較簡單的模型,沒有做很多針對目標檢測的優化設計,比如針對小物體、多尺度的 FPN 設計。DETR 的網絡結構還有待后續工作來改進。
-
參數量、計算量和推理速度之間并沒有必然的關系。
#params、GFLOPS、FPS 分別表示了模型了參數量、計算量和推理速度,從表中可以觀察到,有的模型參數量更小、計算量也更小,但是推理速度卻慢很多,這種差異在跨結構之間的對比時格外明顯(Transformers v.s. CNNs)。可能是由于硬件對于不同結構的優化程度有差異。目前來看, CNN 在同樣網絡規模甚至更大網絡規模下,推理速度比 Transformer 更快。
可視化
編碼器自注意力圖可視化
首先我們來看對于 Encoder 的可視化,下圖展示了對于一組參考點(圖中紅點)的 Encoder 注意力熱力圖的可視化,即參考點對于圖像中所有其他點自注意力值的大小。
可以觀察到,Transformer Encoder 基本已經能夠非常清晰地區分開各個物體了,甚至熱力圖已經有一點實例分割的 mask 圖的意思了。在有一定遮擋的情況下(左側兩頭牛),也能夠清楚地分開哪個是哪個。這種效果正是 Transformer Encoder 的全局建模能力所帶來的,每個位置能夠感知到圖像中所有的其他位置。因此能夠區分出圖像中的不同物體,從而對于一個物體,盡量只出一個預測框。
解碼器注意力圖可視化
既然 Encoder 和 Decoder 都是 Transformer 結構,為什么要分成兩部分呢?Decoder 和 Encoder 真的有在學習不同的信息嗎?
通過前面的可視化,我們已經看到,Encoder 學習了一個全局的特征,基本已經能夠區分開圖中不同的物體。但是對于目標檢測來說,大致地區分開不同的物體是不夠的,我們還需要精確的物體的邊界框坐標,這部分就由 Decoder 來做。
下圖在 Decoder 特征中對每個不同的物體做了注意力的可視化,比如左圖中的兩頭大象分別由藍色和橙色表示。可以觀察到,Decoder 網絡中對于每個物體的注意力都集中在物體的邊界位置,如大象的鼻子、尾巴、象腿等處。作者認為這是 Decoder 在區分不同物體邊界的極值點(extremities),在 Encoder 能夠區分開不同的物體之后,Decoder 再來關注不同物體邊界的具體位置,最終精準地預測出不同物體的邊框位置。因此,Encoder-Decoder 的結構是必要的,它們各司其職,一個都不能少。
learned object query 的可視化
learned object query 也是 DETR 與其他方法做法上一個很大的區別。
下圖展示了 COCO2017 驗證集中所有的預測框關于 learned object query 的可視化。20 張圖分別表示 N=100N=100N=100 個 learned object query 中的一個,每個點表示的是一個預測框的歸一化中心坐標。其中綠色的點表示小框、紅色的點表示大的橫向的框、藍色的點表示大的縱向的框。
可以看到,每個 learned object query 其實是學習了一種 “查詢” 物體的模式。比如第一個 query,就是負責查詢圖中左下角有沒有一個小物體,中間有沒有一個大的橫向的物體;第二個 query 負責查詢中間偏下有沒有小物體,以此類推。從這個可視化實驗可以看出,其實 learned object query 做的事情與 anchor 是類似的,就是去看某個位置有沒有某種物體。但是 anchor 需要先驗地手動設置,DETR 中的 learned object query 則是可以與網絡一起端到端地進行學習。
總結
總結下來,DETR 用 learned object query 取代了 anchor 的設置,用二分圖匹配的方式取代了 NMS 后處理,將之前不可學習的步驟都替換為可以學習的內容,從而實現了一個端到端的目標檢測網絡。并且借助 Transformer 的全局特征交互能力,使得直接對每個物體“一對一”地輸出一個可靠的預測結果成為了可能。雖然 DETR 本身檢測性能并不突出,但是由于它切實解決了目標檢測領域的一些痛點,提出了一個新的端到端的檢測框架,隨后就有一系列跟進工作把它的性能提了上來。DETR 完全稱得上是目標檢測領域,乃至整個視覺領域一篇里程碑式的工作。