日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人工智能 > 目标检测 >内容正文

目标检测

人工智能目标检测总结(五)——深入理解one-stage目标检测模型

發布時間:2023/12/14 目标检测 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 人工智能目标检测总结(五)——深入理解one-stage目标检测模型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文翻譯自One-shot object detection,原作者保留版權。

作為計算機視覺領域的一項重要任務,目標檢測是要找到一張圖片里的感興趣物體:

這比圖像分類任務更高級,因為分類只需要告訴圖像中主要物體是什么,然而目標檢測要找到多個物體,不僅要分類,而且要定位出它們在圖像中的位置。目標檢測模型不僅要預測出各個物體的邊界框(bounding boxes),還要給出每個物體的分類概率。通常情況下目標檢測要預測許多邊界框。每個邊界框還需要一個置信度(confidence score),代表其包含物體的可能性大小。在后處理中,通過設定置信度閾值來過濾那些置信度較低的邊界框。目標檢測相比分類任務更復雜,其面臨的一個主要問題在于一張圖片中可能存在許多位置不定的物體,而且模型會輸出很多預測結果,要計算損失函數就需要匹配真實框(ground-truth bounding box)與預測框。

這里我們主要關注one-stage目標檢測算法(也稱one-shot object detectors),其特點是一步到位,速度相對較快。另外一類目標檢測算法是two-stage的,如Faster R-CNN算法先生成候選框(region proposals,可能包含物體的區域),然后再對每個候選框進行分類(也會修正位置)。這類算法相對就慢,因為它需要多次運行檢測和分類流程。而one-stage檢測方法,僅僅需要送入網絡一次就可以預測出所有的邊界框,因而速度較快,非常適合移動端。最典型的one-stage檢測算法包括YOLO,SSD,SqueezeDet以及DetectNet。

盡管這些方法都已經公布paper和源碼,但是大部分paper對技術細節并沒有完全給出來。因而,這篇博文將詳細講述one-shot檢測算法(以YOLO和SSD作為典例)的原理,以及它是如何訓練和預測的。

為什么目標檢測問題更難

圖像分類是生成單個輸出,即類別概率分布。但是這只能給出圖像整體內容的摘要,當圖像有多個感興趣的物體時,它就不行了。在下面的圖像中,分類器可能會識別出圖像即包含貓,也包含狗,這是它最擅長的。

而目標檢測模型將通過預測每個物體的邊界框來給出各個物體的位置:

因為可以專注于對邊界框內的物體進行分類并忽略外部背景,因此模型能夠為各個物體提供更加準確的預測。如果數據集帶有邊界框標注,則可以非常輕松地在模型添加一個定位預測:只需預測額外4個數字,分別用于邊界框的每個角落。

現在該模型有兩部分輸出:類別概率分布和邊界框回歸。模型的損失函數只是將邊界框的回歸損失與分類的交叉熵損失相加,通常使用均方誤差(MSE):

outputs = model.forward_pass(image) class_pred = outputs[0] bbox_pred = outputs[1] class_loss = cross_entropy_loss(class_pred, class_true) bbox_loss = mse_loss(bbox_pred, bbox_true) loss = class_loss + bbox_loss optimize(loss)

然后采用SGD方法對模型優化訓練,這是一個預測實例:

模型已正確對圖像中物體(狗)分類,并給出它在圖像中的位置。紅色框是真實框,而青色框是預測框,雖然有偏差,但非常接近。為了評估預測框與真實框的匹配程度,我們可以計算兩個邊界框之間的IOU(intersection-over-union,也稱為Jaccard index)。IOU在0到1之間,越大越好。理想情況下,預測框和真框的IOU為100%,但實際上任何超過50%的預測通常都被認為是正確的。對于上面的示例,IOU為74.9%,因而預測框比較精確。

使用回歸方法預測單個邊界框可以獲得較好的結果。然而,當圖像中存在多個感興趣的物體時,就會出現問題:

由于模型只能預測一個邊界框,因而它必須要選擇一個物體,這會最終落在中間位置。實際上這很容易理解:圖像里有兩個物體,但是模型只能給出一個邊界框,因而選擇了折中,預測框位于兩者中間,也許大小也是介于兩個物體大小之間。

注意:也許你可能認為模型應該給出一個包含兩個物體的邊界框,但是這不太會發生,因為訓練不是這樣的,真實框都是各個物體分開標注的,而不是一組物體進行標注。

你也許認為,上述問題很好解決,對于模型的回歸部分增加更多的邊界框預測不就好了。畢竟,如果模型可以預測N個邊界框,那么就應該可以正確定位N個物體。聽起來不錯,但是并沒有效。就算模型有多個檢測器(這里一個邊界框回歸稱為一個檢測器),我們得到的邊界框依然會落在圖像中間:

為什么會這樣?問題是模型不知道應該將哪個邊界框分配給哪個物體,為了安全起見,它將它們放在中間的某個位置。該模型無法決定:“我可以在左邊的馬周圍放置邊界框1,并在右邊的馬周圍放置框2。”相反,每檢測器仍然試圖預測所有物體,而不是一個檢測器預測一個物體。盡管該模型具有N個檢測器,但它們無法協同工作。具有多個邊界框檢測器的模型的效果與僅預測一個邊界框的模型完全相同。

我們需要的是使邊界框檢測器更專一化的一些方法,以便每個檢測器將嘗試僅預測單個物體,并且不同的探測器將找到不同的物體。在不專一的模型中,每個檢測器應該能夠處理圖像中任何可能位置的各類物體。這太簡單了,模型學會的是預測位于圖像中心的方框,因為這樣整個訓練集實際上會最小化損失函數。從SGD的角度來看,這樣做平均得到了相當不錯的結果,但在實踐中它卻不是真正有用的結果,所以我們需要更加有效地訓練模型。

通過將每個邊界框檢測器分配到圖像中的特定位置,one-stage目標檢測算法(例如YOLO,SSD和DetectNet)都是這樣來解決這個問題。因為,檢測器學會專注于某些位置的物體。為了獲得更好的效果,我們還可以讓檢測器專注于物體的形狀和大小。

使用網格

使用固定網格上的檢測器是one-stage目標檢測算法的主要思想,也是它們與基于候選框的目標檢測方法(如R-CNN)的區別所在(實際上Faster R-CNN中RPN網絡也采用網格檢測)。

讓我們考慮這類模型最簡單的架構。它首先有一個充當特征提取器的基礎網絡。像大多數特征提取器一樣,它通常在ImageNet上訓練。對于YOLO,特征提取器以416×416圖像作為輸入。SSD通常使用300×300大小的圖像。它們比用于分類的圖像(如224×224)更大,因為我們不希望丟失細節。

作為特征提取器的基礎網絡可以是任何CNN網絡,如Inception,ResNet以及YOLO中DarkNet,對于移動端可以采用輕量級的網路,如SqueezeNet以及MobileNet等。在特征提取器的后面是幾個額外的卷積層。這是模型的目標檢測部分,這些都經過訓練后學習如何預測這些邊界框以及框內物體的分類概率。

有許多用于訓練目標檢測算法的通用數據集。這里,我們將使用Pascal VOC數據集,該數據集有20個類。因此神經網絡的第一部分在ImageNet上進行訓練,而檢測部分是在VOC數據集上訓練。

YOLO或SSD的實際架構要比這個簡單的示例網絡稍微復雜一點,比如有殘差結構,但我們稍后會介紹。目前,上述模型已經可用于構建快速且相當精確的目標檢測模型。

最后一層的輸出是一個特征圖(上圖中的綠色圖)。對于我們的示例模型,這是一個包含125個通道的13×13大小的特征圖。

注意:此處的13x13是因為輸入圖片大小為416x416,而此處使用的特定基礎網絡具有五個池化層(或具有stride=2的卷積層),這樣按比例縮小32倍:416/32 = 13。如果你想要一個更精細的網格,例如19×19,那么輸入圖像應該是19×32 =608像素寬和高(或者你可以使用一個較小步幅的網絡)。

我們將此特征圖解釋為13x13個單元格的網格。該數字是奇數,因此中心有一個單元格。網格中的每個單元格都有5個獨立的物體檢測器,每個檢測器都預測一個邊界框。

這里的關鍵是檢測器的位置是固定的:它只能檢測位于該單元附近的物體(實際上,物體的中心必須位于網格單元內)。這可以避免上一部分所說的問題,當時的檢測器有太多的自由度。使用此網格,圖像左側的檢測器將永遠不會預測位于右側的物體。

每個物體檢測器產生25個值:

  • 表征類別概率的20個值
  • 4個邊界框坐標(中心x,中心y,寬度w,高度h)
  • 1個值表示置信度

由于每個單元有5個檢測器,5×25 = 125,這就是我們有125個輸出通道的原因。

與常規分類器一樣,類別概率可以采用softmax獲得。我們可以通過查看最高概率值確定類別。 (雖然通常情況下將其視為多標簽分類,但是這里20個類是獨立的,我們使用sigmoid而不是softmax)

置信度介于0和1(或100%)之間,用于表征模型認為此預測邊界框包含真實物體的可能性。請注意,這個分數只說明了這是否是一個物體,但沒有說明這是什么類型的物體,后面需要分類概率才能確定。

該模型總是預測固定數量的邊界框:13×13個單元乘以5個檢測器給出845個預測。顯然,絕大多數這些預測都不會有用,畢竟大多數圖像最多只包含少量物體,而不是超過800。置信度告訴我們哪些預測框可以忽略。通常情況下,我們最終會得到模型認為很好的十幾個預測。其中一些將重疊,這是因為相鄰的單元可能都對同一個物體進行預測,有時單個單元會進行多次預測(盡管在訓練中這是被抑制的)。

具有許多大部分重疊的預測框在目標檢測中比較常見。標準后處理方法是應用非最大抑制(NMS)來去除重疊框。簡而言之,NMS保留擁有最高的置信度的預測框,并刪除任何與之重疊超過一定閾值的預測框(例如60%)。通常我們只保留10個左右的最佳預測并丟棄其他預測。理想情況下,我們希望圖像中的每個物體只有一個邊界框。

好吧,上面描述了使用網格進行物體檢測的基本思路。但為什么它有效呢?

約束讓模型更容易學習

前面已經提到將每個邊界框檢測器分配到圖像中的固定位置是one-stage目標檢測算法的技巧。我們使用13×13網格作為空間約束,使模型更容易學習如何預測對象。使用這種(架構)約束是神經網絡特別有用的技巧。事實上,卷積本身也是一個約束:卷積層實際上只是一個全連接(FC)層的更受限制的版本。(這就是為什么你可以使用FC層實現卷積,反之亦然,它們基本上是相同的)

如果我們只使用普通的FC層,那么神經網絡模型要學習圖像要困難得多。對卷積層施加的約束:它一次只看幾個像素,并且連接共享相同的權重。我們使用這些約束來消除自由度并引導模型學習我們想要學習的內容。

同樣,網格結構強制讓模型學習專門針對特定位置的物體檢測器。左上角單元格中的檢測器僅預測位于左上角單元格附近的物體,而不會預測距離較遠的物體(對模型進行訓練,使得給定網格單元中的檢測器僅負責檢測其中心落入該網格單元內的物體)。

前面所說的模型沒有這樣的約束,因此它的回歸層永遠不會限制在特定位置。

先驗框(Anchors,錨)

網格是一種有用的約束,它限制了檢測器可以在圖像中找到物體的位置。我們還可以添加另一個約束來幫助模型做出更好的預測,就是物體形狀的約束。

我們的示例模型具有13×13個網格單元,每個單元具有5個檢測器,因此總共有845個檢測器。但為什么每個網格單元有5個檢測器而不是一個?好吧,就像檢測器很難學會如何預測可以位于任何地方的物體一樣,檢測器也很難學會預測任何形狀或大小的物體。

我們使用網格來專門限制檢測器僅查看某個特定空間位置。相應地,為每個網格單元設置幾個不同的檢測器,我們可以使這些物體檢測器中的每一個都專注于某種物體形狀。

我們在5種特定形狀上訓練檢測器:

紅色框是訓練集中五個最典型的物體形狀。青色框分別是訓練集中最小和最大的物體。請注意,這些邊界框顯示在輸入為416×416中。 (該圖還以淺灰色顯示網格,因此可以看到這五個形狀網格單元相關。每個網格單元在輸入圖像中覆蓋32×32像素)

這五種形狀稱為先驗框或者錨。先驗框只是一個寬度和高度列表:

anchors = [1.19, 1.99, # width, height for anchor 12.79, 4.60, # width, height for anchor 24.54, 8.93, # etc.8.06, 5.29,10.33, 10.65]

先驗框描述數據集中5個最常見(平均)的物體形狀。這里的“形狀”指的是它們的寬度和高度,因為在目標檢測種總是使用基本的矩形。有5個先驗框并非偶然。網格單元中的每個檢測器都有一個先驗框。就像網格對檢測器施加位置約束一樣,先驗框迫使檢測器專門處理特定的物體形狀。

單元中的第一個檢測器負責檢測與第一個先驗框相似的物體,第二個檢測器負責與第二個先驗框相似的物體,依此類推。因為我們每個單元有5個檢測器,所以我們也有5個先驗框。因此,檢測器1將拾取小物體,檢測器2拾取稍大的物體,檢測器3拾取長而扁平的物體,檢測器4拾取高而薄的物體,而檢測器5拾取大物體。

注意:上面代碼片段中先驗框的寬度和高度用網格的13×13坐標系表示,因此第一個先驗框略寬于1個網格單元格,高度接近2個網格單元格。最后一個先驗框覆蓋了幾乎整個網格,超過10×10個單元格。這就是YOLO設置先驗框的方式。然而,SSD具有幾個不同尺寸的不同網格,因此使用先驗框的標準化坐標(在0和1之間),它們與網格的大小無關。任何一種方法都可以。

重要的是要了解這些先驗框是事先選擇的。它們是恒定的,在訓練期間不會改變。

由于先驗框只是一個寬度和高度集合,而且它們是事先選擇的,所以YOLO紙也稱它們為“dimension priors”(Darknet,官方的YOLO源代碼,稱它們為“biases”,也是比較合理 - 檢測器偏向于預測某個形狀的物體,但是這個術語令人困惑)。

YOLO通過在所有訓練圖像的所有邊界框上進行k-means聚類來選擇先驗框(k=5,因此它找到五個最常見的物體形狀)。因此,YOLO的先驗框適合當前訓練(和測試)的數據集。k-means算法中數據點是數據集中所有真實邊界框的寬度和高度值。如果我們在Pascal VOC數據集的框中運行k-means,將得到5個簇:

這些簇表示此數據集中存在的不同物體形狀的五個“平均值”。你可以看到k-means在藍色簇中將非常小的物體組合在一起,在紅色簇中將較大的物體組合在一起,將非常大的物體分組為綠色。它決定將中間物體分成兩組:一組邊界框(黃色)寬大于高,另一組高于寬(紫色)。

但5個簇是最佳選擇嗎?我們可以設定不同的k進行k-means,并計算真實框與它們最接近的先驗框之間的平均IOU。當然,使用更多的質心(更大的k值)會產生更高的平均IOU,但這也意味著我們需要在每個網格單元中使用更多的檢測器,這會使模型運行得更慢。對于YOLO v2,他們選擇5個聚類中心,這是在準確度和模型復雜性之間的折衷。

SSD不使用k-means來確定先驗框。相反,它使用數學公式來計算先驗框尺寸,因此SSD的先驗框與數據集無關(SSD稱它們為“default boxes”)。SSD和YOLO的先驗框設置,到底哪種方式比較好,并沒有定論。另一個小差異是:YOLO的先驗框只是寬度和高度,但SSD的先驗框也有x,y位置。其實,YOLO也包含位置,只是默認先驗框位置始終位于網格單元格的中心(對于SSD,先驗框也是在網格中心)。

由于先驗框,檢測器僅預測相比邊界框的偏移值,這使得訓練更容易,因為預測值全為零時等價于輸出先驗框,平均上更接近真實物體。如果沒有先驗框,每個檢測器都必須從頭開始學習不同的邊界框形狀,這相當困難。

注意:本文所說的YOLO是指YOLOv2或v3,這與YOLOv1是非常不同的。YOLOv1具有較小的網格(7×7,每個單元僅有2個檢測器),使用全連接層而不是卷積層來預測網格,并且不使用先驗框。那個版本的YOLO現在已經過時了。相比之下,v2和v3之間的差異要小得多。而YOLOv3在很多方面與SSD非常相似。

模型到底預測了什么

讓我們更加細致地看一下模型的輸出是什么,由于模型僅僅是一個卷積神經網絡,所以前向過程是這樣的:

你輸入一個416x416的RGB圖像,經過卷積層之后是一個13x13x125的特征圖。由于是回歸預測,最后的層沒有激活函數。最后的輸出包含21,125個數,我們要將它們轉化為邊界框。

我們采用4個數來表示一個邊界框的坐標。通常有兩種方式:或者用xmin, ymin, xmax, ymax表示邊界框左上和右下點坐標,或者使用center x, center y, width, height。兩個方式都可以(兩者可以互相轉換),但是這里采用后者,因為知道框中心更加容易將邊界框與網格單元進行匹配。

模型預測的不是邊界框在圖像中的絕對坐標,而是4個偏移值(delta or offset):

  • delta_x, delta_y:邊界框中心點在網格單元內部的坐標;
  • delta_w, delta_h: 邊界框的寬和高相對先驗框的縮放值。

每個檢測器預測的邊界框都是相對一個先驗框,這個先驗框已經很好地匹配了物體的實際大小(這也是為什么要使用先驗框),但是它不完全準確。因而,我們需要預測一個縮放因子來知道預測的框是比先驗框大還是小,同樣地,也要知道預測框的中心相對于網格中心的偏移量。

要得到邊界框在圖像中的真實寬與高,需要這樣計算:

box_w[i, j, b] = anchor_w[b] * exp(delta_w[i, j, b]) * 32 box_h[i, j, b] = anchor_h[b] * exp(delta_h[i, j, b]) * 32

這里的i和j是網格(0-12)的行與列,而b是檢測器(0-4)的索引。預測框比原始圖片寬或者高都是正常的,但是寬和高不應該會是負值,因此這里取預測值的指數。如果delta_w<0,那么exp(delta_w)介于0~1,此時預測框比先驗框小。若delta_w>0,那么exp(delta_w)大于1,此時預測框比先驗框大。特別地,delta_w=0,那么exp(delta_w)為1,此時預測框與先驗框一樣大小。順便說一下,這里之所以要乘以32是因為先驗框坐標是13x13網格上的,網格上各個像素點等價于原始416x416圖像上的32個像素點。

注意:在損失函數中,我們使用上述公式的逆運算:不是將預測的偏移值通過exp轉換到實際坐標,而是將真實框通過log運算轉換為偏移值。后面會詳細介紹。

利用下面公式可以得到預測框中心的像素坐標:

box_x[i, j, b] = (i + sigmoid(delta_x[i, j, b])) * 32 box_y[i, j, b] = (j + sigmoid(delta_y[i, j, b])) * 32

YOLO模型的一個重要特點是僅當某個物體的中心點落在一個檢測器的網格單元中心時,這個檢測器才負責預測它。這樣避免沖突,即相鄰的單元格不會用來預測同一個物體。這樣,delta_x和delta_y必須限制在0~1之間,代表預測框在網格單元的相對位置,這里用sigmod函數限制值的范圍。然后我們加上單元格的坐標i和j(0~12之間),乘以每個單元格所包含的像素數(32)。這樣box_x和box_y就表示預測框中心在原始416×416圖像中的坐標。

然后,SSD卻有點不同:

box_x[i, j, b] = (anchor_x[b] + delta_x[i, j, b]*anchor_w[b]) * image_w box_y[i, j, b] = (anchor_y[b] + delta_y[i, j, b]*anchor_h[b]) * image_h

這里預測的delta值實際上是先驗框寬或高的倍數,所以沒有sigmoid函數。這也意味著在SSD中物體的中心可落在單元格的外面。需要注意的是SSD預測的坐標是相對于先驗框中心而不是單元格中心。實際上一般情況下兩個中心是重合的,但是SSD和YOLO采用的坐標系不一樣。SSD的先驗框坐標是歸一化到[0,1],這樣它們獨立于網格大小(SSD之所以這樣是采用了不同大小的網格)。

可以看出,即使YOLO和SSD大致原理是相同的,但是具體的實現細節是有差異的。

除了坐標之外,模型每個邊界框預測了一個置信度,一般采用sigmoid函數得到0~1的概率值:

confidence[i, j, b] = sigmoid(predicted_confidence[i, j, b])

實際上我們的模型預測了845個邊界框,但是一張圖片中的物體一般是很少的。在訓練過程中,由于我們僅讓一個檢測器預測一個真實框,因此只有很少的預測框的置信度較高,那些不應該找到物體的檢測器應該得到較小的置信度。

最后,我們預測分類概率,對于Pasval VOC數據每個邊界框有20個值,采用softmax得到概率分布:

classes[i, j, b] = softmax(predicted_classes[i, j, b])

除了采用softmax,還可以采用sigmoid,這將變成一個多標簽分類器,每個預測框實際上可以同時有多個類別,SSD和YOLOv3是這樣。

但是SSD沒有預測置信度,它給分類器增加了一個特殊的類:背景。如果分類結果是背景,那么意味著檢測器沒有找到物體,這實際上等價于YOLO給出一個較低的置信度。

由于很多預測是不需要的,所以對于置信度較低的預測我們將舍棄。在YOLO中,結合置信度和最大類別概率來過濾預測值(條件概率):

confidence_in_class[i, j, b] = classes[i, j, b].max() * confidence[i, j, b]

置信度低表示模型很不確定預測框是否含有物體,而低分類概率表示模型不確定哪種物體在預測框內,只有兩者都高時,我們才采納這個預測值。

由于大部分的框不含物體,我們可以忽略那些置信度低于某個閾值(比如0.3)的預測框,然后對余下的框執行NMS(non-maximum suppression),去除重疊框,這樣處理后一般得到1~10個預測框。

這是卷積預測

卷積神經網絡天然適合這樣的網格檢測器,13×13的網格是卷積層得到的. 卷積實際上是在整個輸入圖像上以較小的窗口(卷積核)進行滑動,卷積核在每個位置是共享的。我們的樣例模型最后的卷積層包含125個卷積核。

為什么是125?因為有5個檢測器,每個檢測器有25個卷積核。這25個卷積核中的每一個都預測邊界框的屬性值:坐標x和y,寬度,高度,置信度,20類概率。

注意:一般來說,如果數據集有k類,而模型有b個檢測器,那么網格需要有b×(4+1+k)個輸出通道。

這125個卷積核在13×13特征圖中的每個位置上滑動,并在每個位置上進行預測。然后,我們將這125個數字解釋為5個預測邊界框。最初,在每個網格位置預測到的125個數字將是完全隨機和無意義的,但是隨著訓練,損失函數將引導模型學習做出更有意義的預測。

盡管每個網格單元中有5個檢測器,但對于845個檢測器來說,這個模型實際上只學習了總共5個檢測器,而不是每個網格單元都學習5個唯一的檢測器。這是因為卷積層的權重在每個位置都相同,因此在網格單元之間共享。但模型為每個先驗框學習一個檢測器,它們在圖像上滑動,以獲得845個預測值,網格上每個位置有5個。因此,盡管我們總共只有5個獨特的檢測器,但由于卷積的緣故,這些檢測器與它們在圖像中的位置無關,因此無論它們位于何處,都可以檢測到物體。每個位置的輸入像素與檢測器所學習到的權重,決定了該位置的最終邊界框預測。

這也解釋了為什么模型總是預測邊界框相對于網格單元中心的位置。由于該模型具有卷積性質,無法預測絕對坐標,而卷積核在圖像上滑動,因此它們的預測總是相對于在特征圖中的當前位置。

YOLO vs SSD

上面關于one-stage的目標檢測模型的原理的描述幾乎適用于所有的檢測器。但是在解釋輸出的方式上可能存在細微的差異(例如,分類概率采用sigmoid而不是softmax),但一般是相同的。

然而,不同版本的YOLO和SSD之間存在一些結構上的差異。以下是YOLOv2&v3和SSD模型的架構示意圖:

可以看到,YOLOv3和SSD非常相似,盡管它們是通過不同的方法得到網格(YOLO使用升序采樣,SSD使用降序采樣)。而YOLOv2(我們的示例模型)只有一個13×13的輸出網格,而SSD有幾個不同大小的網格。Mobilenet+SSD版本有6個網格,大小分別為19×19、10×10、5×5、3×3、2×2和1×1。因此,SSD網格的范圍從非常細到非常粗。這樣做是為了在更廣泛的物體尺度上獲得更精確的預測。比較精細的19×19網格,其網格單元非常靠近,負責預測最小的物體。而最后一層生成的1×1網格負責預測基本上占據整個圖像的較大物體,其他層的網格適應比較寬范圍的物體。YOLOv3更像SSD,因為它使用3個不同大小的網格預測邊界框。

和YOLO一樣,每個SSD網格單元都會進行多個預測。每個網格單元的檢測數量各不相同:在更大、更細粒度的網格上,SSD每個網格單元有3或4個檢測器,在較小的網格上,每個網格單元有6個檢測器。而YOLOv3的每個網格單元均使用3個檢測器。

坐標預測是相對于先驗框的,在SSD文件中被稱為“default boxes”,但有一個區別是,SSD的預測中心坐標可以超出其網格單元。先驗框位于單元的中心,但SSD不會對預測的X,Y偏移應用sigmoid。所以理論上,模型右下角的一個檢測器可以預測一個中心在圖像左上角的邊界框(但這在實踐中可能不會發生)。

與YOLO不同的是,SSD沒有置信度。每個預測只包含4個邊界框坐標和類概率。YOLO使用置信度來表示這個預測包含實際物體的可能性。SSD通過一個特殊的“背景”類來解決這個問題:如果類預測是背景類,那么它意味著沒有為這個檢測器找到物體。這實際上和YOLO中置信度較低等同。

SSD在先驗框上與YOLO稍有不同。因為YOLO必須從單個網格進行所有預測,所以它使用的先驗框的范圍從小(大約單個網格單元的大小)到大(大約整個圖像的大小)。SSD更為保守。19×19網格上的先驗框比10×10網格上的要小,比5×5網格上的更小,以此類推。與YOLO不同的是,SSD不使用先驗框來使檢測器專注于物體大小,而是使用不同的網格來實現這一點。SSD的先驗框主要用于使檢測器處理物體形狀(長寬比)的變化,而不是它們的大小。如前所述,SSD的先驗框是使用公式計算的,而YOLO的先驗框是通過對訓練數據運行k-means聚類來找到的。

因為SSD使用3到6個先驗框,它有6個網格而不是1個,所以實際上它總共使用了32個獨特的檢測器(這與具體網絡結構有關)。因為SSD有更多的網格和檢測器,它將輸出更多的預測。YOLO進行了845次預測,而MobileNet-SSD則包含1917次預測。而SSD512甚至輸出24564個預測!其優點是可以更容易找到圖像中的所有物體。缺點是,你最終不得不做更多的后處理來找出你想要保留的預測。

由于這些差異,將真實框與檢測器的匹配方式在SSD和YOLO之間稍有不同,損失函數也略有不同。后面講解訓練過程時會討論這個問題。

現在關于這些模型如何做出預測已經講解的非常清楚了。現在讓我們來看看訓練目標檢測模型所需的數據類型。

數據

有很多常用的目標檢測訓練數據集,如Pascal VOC, COCO, KITTI。這里我們關注Pascal VOC,因為它是最常用的,并且YOLO使用了它。

VOC數據集包含圖像和不同任務的標注,這里我們僅關注目標檢測的標注,共有20個類別:

aeroplane bicycle bird boat bottle bus car cat chair cow diningtable dog horse motorbike person pottedplant sheep sofa train tvmonitor

VOC數據集附帶一個建議的訓練/驗證集分割,大約為50/50。由于數據集不太大,因此將50%的數據用于驗證似乎有點浪費。因此,通常將訓練集和驗證集組合成一個大的訓練集“trainval”(總共16551張圖像),然后隨機選取10%左右的圖像用于驗證。可以在2007測試集上測試模型,因為label已經給出。還有一個2012年的測試集,但label是不公開的(也有習慣于將2007年測試集包括在訓練數據中,數據越多越好)。

2007+2012組合訓練集有8218個帶物體框標注的圖像,驗證集有8333個圖像,2007測試集有4952個圖像。這比ImageNet的130萬張圖片要少得多,所以最好使用遷移學習,而不是從頭開始訓練模型。這就是為什么我們從一個已經在ImageNet上預訓練過的特征提取器開始。

標注

標注描述了圖像中的內容。簡而言之,標注提供了我們訓練所需的目標。標注采用XML格式,每個訓練圖像一個。標注文件包含一個或多個帶有類名稱的部分:用xmin、xmax、ymin、ymax描述的邊界框以及每個object的一些其他屬性。如果一個物體被標為difficult,我們將忽略它,這些通常是非常小的物體,它們也被VOC競賽的官方評估指標忽略。以下是標注文件示例,voc2007/annotations/003585.xml:

<annotation>
<folder>VOC2007</folder>
<filename>003585.jpg</filename>
<source>
<database>The VOC2007 Database</database>
<annotation>PASCAL VOC2007</annotation>
<image>flickr</image>
<flickrid>304100796</flickrid>
</source>
<owner>
<flickrid>Huw Lambert</flickrid>
<name>huw lambert</name>
</owner>
<size>
<width>333</width>
<height>500</height>
<depth>3</depth>
</size>
<object>
<name>person</name>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>138</xmin>
<ymin>183</ymin>
<xmax>259</xmax>
<ymax>411</ymax>
</bndbox>
</object>
<object>
<name>motorbike</name>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>89</xmin>
<ymin>244</ymin>
<xmax>291</xmax>
<ymax>425</ymax>
</bndbox>
</object>
</annotation>

這個圖片大小為333×500,包含兩個物體:人和摩托車。沒有被標注為difficult或者truncated (部分在圖像外).

注意:Pascal VOC數據集坐標從1開始,而不是0,也許是采用MATLAB的格式。 我們可以畫出這個圖像的各個物體的邊界框:


VOC2007和2012共包含如下的圖像:

dataset images objects
------------------------------
train 8218 19910
val 8333 20148
test 4952 12032 (2007 only)


約有一半的圖像僅有一個物體,其它的包含1個以上,下面是訓練集統計的直方圖:

一張圖片中最大物體數為39,驗證集和測試集的直方圖大致類似。同樣地,我們給出訓練集中所有物體區域大小的直方圖(長和寬歸一化到[0,1]):


可以看到許多物體相對較小。峰值為1.0是因為有相當多的物體大于圖像(例如,只有部分可見的人),因此邊界框填充整個圖像。這里還有另一種方法來查看這些數據:邊界框寬度與高度的關系圖。圖中的“坡度”顯示框的高寬比。


數據擴增

由于數據集相當小,在訓練時經常使用大量的數據擴增,如隨機翻轉、隨機裁剪、顏色抖動等。值得注意的是,對圖像所做的任何操作都必須對邊界框同樣執行!比如,如果翻轉圖像,還必須對應地翻轉真值框的坐標。
YOLO的數據擴增流程如下:

  • 加載原始圖像;
  • 通過隨機增加/減去原始大小的20%來選擇新的寬度和高度;
  • 按照新大小裁剪圖像,如果新圖像在一個或多個邊上大于原始圖像,則用零填充
  • 將圖像resize到416×416,使其成為正方形
  • 隨機水平翻轉圖像(50%的概率)
  • 隨機改變圖像的色調、飽和度和曝光(亮度)
  • 對應地,要通過移動和縮放邊界框坐標來調整邊界框,以適應前面所做的裁剪和調整大小,以及水平翻轉等操作

旋轉也是一種常見的數據擴增技術,但這會比較麻煩,因為我們還需要同時旋轉邊界框,所以通常不會這樣做。而SSD采用的數據擴增方式包括:

  • 隨機裁剪一個圖像區域,使該區域中物體的最小IOU(與原始圖像中物體)為0.1、0.3、0.5、0.7或0.9,IOU越小,模型就越難檢測到物體
  • 使用“縮小”增強,將圖像變小,從而構建包含小物體的訓練數據,這對于模型更好地處理小物體很有用。

隨機裁剪可能會導致物體的部分(或全部)落在裁剪圖像之外。因此,我們只希望保留中心位于該裁剪區域某個位置的邊界框,而不希望保留中心位于裁剪區域之外的框。


注意高寬比

我們的預測是在13x13的正方形網格上,輸入圖像也是正方形的(416x416)。但是訓練數據集中圖像通常不是正方形的,而且測試圖像一般也不是。而且,所有的圖像大小可能并不相同。下圖是VOC數據集中所有圖像的高寬比的可視化:

紅色框是寬大于高,而青色框恰恰相反。 雖然存在一些奇怪的高寬比,但是大部分是1.333 (4:3), 1.5 (3:2), 0.75 (3:4)。有些圖片甚至很寬,這里是一個極端例子:

由于網絡的輸入是416×416大小的正方形圖像,因此我們必須將訓練圖像放在該正方形中。下面是幾種方法:

  • 直接將圖像resize到416×416,這可能會擠壓圖像;
  • 將最小邊調整為416,然后從圖像中裁剪出416×416區域;
  • 將最大邊調整為416,用零填充另外的短邊;

上述方法都是有效的,但每個方法都有其副作用。我們直接將其高寬比更改為1:1,可能會擠壓圖像。如果原始圖像寬大于高,則所有物體都比平常窄。如果原來的物體高大于寬,那么所有的物體都會變平。通過裁剪,雖然高寬比保持不變,但我們可能切掉圖像的重要部分,使模型更難看到真實的物體,這樣模型可能需要預測部分位于圖像外部的邊界框。而對于方法3,它可能會使物體太小而無法檢測,尤其是在高寬比極端的情況下。

為什么這很重要?訓練前,我們將邊界框的xmin和xmax除以圖像寬度,ymin和ymax除以圖像高度,以歸一化坐標,使它們介于0和1之間。這樣做是為了使訓練獨立于每個圖像的實際像素大小。但是輸入的圖像通常不是正方形的,所以x坐標除以一個與y坐標不同的數字。根據圖像的尺寸和高寬比,每個圖像的除數可能不同,這會影響我們如何處理邊界框坐標和先驗框。
方法1是最簡單粗暴,盡管它會暫時破壞圖像的高寬比。如果所有的圖像都有相似的高寬比(在VOC中沒有),或者高寬比不太極端,那么神經網絡仍然可以正常工作。CNN網絡似乎對于物體的“厚度”變化相當健壯(意思是物體擠壓時CNN依然有效)。

對于方法2和3,在歸一化邊界框坐標時,我們應該記住高寬比。現在有可能邊界框比輸入圖像大,因為我們只是對裁剪部分進行預測。而由于物體可能部分落在圖像之外,邊界框也可能部分落在圖像之外。裁剪的缺點是我們可能會丟失圖像的重要部分,這可能比稍微擠壓物體更糟糕。擠壓還是裁剪也會影響如何從數據集中計算先驗框。使用先驗框的重要因素,這些先驗框的形狀類似于數據集中最常見的物體形狀。這在裁剪時仍然是正確的。一些先驗框現在可能部分落在圖像之外,但至少它們的高寬比真正代表了訓練數據中的物體。對于擠壓,計算出的先驗框并不能真正代表真正的框,不同的高寬比會被忽略,因為每個訓練圖像的擠壓方式略有不同。現在,先驗框更像是在不同的扭曲圖像求平均結果。

數據擴增也會有副作用。通過隨機截取圖像,然后將大小調整為416×416,這也會擾亂高寬比(更像故意的)。

總結來看,直接對原始圖像進行resize,而忽略邊界框的高寬比,這是最簡單有效的。這也是Yolo和SSD所采用的方式,這種方式可以看成讓模型學會自適應高寬比。如果我們在處理固定大小的輸入圖像,例如1280×720,那么使用裁剪可能更合適。


模型是如何訓練的


前面都是預備項,接下來我們將來介紹這類目標檢測模型是如何訓練的。該模型使用卷積神經網絡直接進行預測,然后把這些預測數字轉換成邊界框。數據集包含真實框,表示訓練圖像中實際存在哪些物體,因此要訓練這種模型,我們需要設計一個損失函數,將預測框與真實框進行比較。

問題是,不同圖像之間的真實框數量可能會有所不同,從零到幾十個不等。這些框可能圖像的不同位置,而且有些會重疊。在訓練期間,我們必須將每個檢測器與這些真實框中的一個相匹配,以便我們可以計算每個預測框的回歸損失。

如果我們直接簡單地進行匹配,例如總是將第一個真實框分配給第一個檢測器,將第二個物體分配給第二個檢測器,以此類推,或者通過將物體隨機分配給檢測器,那么每個檢測器都將被訓練來預測各種各樣的物體:一些較大的物體,一些是極小的物體,有的會在圖像的一角,有的會在相反的一角,有的會在中間,等等。這就出現前面所提到的問題:為什么僅僅在模型中添加一組回歸輸出就難有效。解決方案是使用帶有固定大小網格的檢測器,其中每個檢測器只負責檢測位于圖像該部分的物體,并且只負責特定大小的物體。
現在,損失函數需要知道哪個物體歸屬于哪個檢測器,或者說在哪個網格單元中,相反地,哪些檢測器沒有與它們相關聯的真實框。這就是我們所說的“匹配”。


將真實框與檢測器匹配

匹配的方法是各種各樣的,在YOLO中,圖像中的每個物體僅由一個檢測器負責來預測。由于我們要找到邊界框中心落在哪個網格單元中,那個單元與整個物體關聯,而其它的網格單元如果預測了這個物體將被損失函數所懲罰。
VOC數據集給出的邊界框標注為xmin, ymin, xmax, ymax。由于我們需要知道邊界框中心,所以需要將邊界框坐標轉為center x, center y, width, and height。我們一般會先將邊界框坐標歸一化到[0, 1],這樣它們獨立于輸入圖像的大小(因為訓練圖像的大小并不一致)。

由于需要匹配,我們在采用一些數據擴增如隨機翻轉,要同時應用在圖像和邊界框上。

注意:對于一些數據擴增如隨機裁剪和翻轉,我們在每個epoch需要重新對真實框與檢測器進行匹配。這個過程無法提前完成,并緩存下來,因為數據擴增是隨機的,一般會改變匹配結果。

僅僅為每個物體選擇網格單元是不夠的。每個網格單元都有多個檢測器,我們只需要其中一個檢測器來查找物體,我們需要選擇其先驗框與物體的真實框最匹配的檢測器。這通常采用IOU來衡量匹配度。這樣,最小的物體被分配給檢測器1(有最小的先驗框),非常大的物體使用檢測器5(有最大的先驗框),以此類推。所以,只有那個單元中的特定檢測器才可以預測這個物體。此規則使得不同的檢測器更專注于處理形狀和大小與先驗框相似的物體(記住,物體的大小不必與先驗框的大小完全相同,因為模型預測會預測相對于先驗框的位置和大小偏移,先驗框只是一個參考)。

因此,對于一個給定的訓練圖像,一些檢測器將有一個與之相關的物體,而其他檢測器將不會。如果訓練圖像中有3個物體,即有3個真實框,那么845個檢測器中只有3個應該進行預測,而其他842個檢測器則應該預測“無物體”(就我們的模型輸出而言,得到的是置信度很低的邊界框,理想情況下為0)。

從現在開始,我們用正例指代一個匹配到物體的檢測器,而對于一個沒有關聯物體的檢測器來說,則是負例,也可以說是“無對象”或背景。

由于模型的輸出是13×13×125張量,因此損失函數所使用的目標張量也將是13×13×125。這個數字125來自:5個檢測器,每個檢測器預測類別的20個概率值+4個邊界框坐標+1個置信度得分。在目標張量中,對于正例,我們會給出物體的邊界框坐標和onehot編碼的類別向量,而置信度為1.0(因為我們100%確定這是一個真實的物體)。對于負例,目標張量的所有值為0,邊界框坐標和類向量在這里并不重要,因為它們將被損失函數所忽略,并且置信度得分為0,因為我們100%確定這里沒有物體。

因此,訓練的每個迭代過程,需要的是一個batch×416×416×3的圖像張量和一個batch×13×13×125的目標張量,這個目標張量中元素的大多都是0,因為大多數檢測器不負責預測一個物體。

匹配時還需要考慮一些其他細節。例如,當有多個物體的中心恰好落在同一個單元中時,該怎么處理?雖然實際上這可能不是一個大問題,特別是如果網格足夠大時,但是我們仍然需要一種方法來處理這種情況。理論上,物體基于最佳IOU來匹配檢測器,例如,物體A的邊界框與檢測器2的IOU最大,物體B的邊界框與檢測器4的IOU最大,那么我們可以將這物體與該單元中的不同檢測器匹配。然而,這并不能避免有兩個物體需要相同檢測器這個問題。

YOLO的解決方案比較粗暴:每次隨機打亂真實框,每個單元只選擇第一個進入它中心的物體。因此,如果一個新的真實框與一個已經負責另一個物體的單元相匹配,那么我們就只能忽略它了。這意味著在YOLO中,每個單元至多有一個檢測器被匹配到物體,而其他檢測器不應該檢測到任何東西(如果檢測到了,就會受到懲罰)。

這只是YOLO的策略,SSD的匹配策略卻不相同。SSD可以將同一個真實框與多個檢測器匹配:首先選擇具有最佳IOU值的檢測器,然后選擇那些與之IOU超過0.5的但是未被匹配過的檢測器(注意檢測器和先驗框是綁定的,一一對應,所以說IOU指的是檢測器的先驗框與物體的邊界框之間的重疊)。這應該使模型更容易學習,因為它不必在哪個檢測器應該預測這個對象之間進行唯一選擇,畢竟多個檢測器可以預測這個對象。

注意:兩者設計似乎是矛盾的。YOLO將一個物體只分配給一個檢測器(而該單元的其他檢測器則是無物體),以幫助檢測器更專注。但是SSD說多個檢測器可以預測同一個物體。兩者實際上都可以。對于SSD,檢測器專注于形狀而不是大小。


損失函數

損失函數實際上是告訴模型它應該學習什么。對于目標檢測,我們需要損失函數它能夠使模型預測出正確的邊界框,并對這些框正確分類,另一方面,模型不應該預測不存在的物體。這實際上是多任務學習。因此,損失函數由幾個不同的部分組成,其中一部分是回歸以預測邊界框位置,另一部分用于分類。

對于任何一個檢測器,有兩種可能的情況:

  • 這個檢測器沒有與之相關的真實框,這是負例,它不應該檢測到任何物體(即它應該預測一個置信度為0的邊界框)。
  • 這個檢測器匹配到了一個真實框,即正例,它負責檢測到物體。

對于不應該檢測到物體的檢測器,當它們預測出置信度大于0的邊界框時要懲罰它們。因為它們給出的檢測是假陽性,圖像中的這個位置上并沒有真實物體。過多的誤檢會降低模型的效果。相反,如果檢測器是正例,當出現下面的情況時,我們希望懲罰它:

  • 當坐標錯誤
  • 當置信度太低時
  • 分類錯誤

理想情況下,檢測器應該預測一個與真實框完全重疊的框,類別也應該一致,并且具有較高的置信度。當置信度得分過低時,預測結果將被視為假陰性(false negative),這也意味著模型沒有找到真正的物體。但是,如果置信度得分高,但坐標不準確或分類錯誤,則預測將被視為假陽性(false positive)。盡管模型檢測出一個物體,但它是錯誤的。

這意味著相同的預測可以被判定為假陰性(會減低模型的召回),也可能是假陽性(降低模型的準確度)。只有當所有三個方面——坐標、置信度、類別都正確時,預測才算真陽性(true positive)。因為任何一個方面都可能出錯,損失函數由幾個部分組成,分別來衡量模型給出的預測的不同類型“錯誤性”,將這些部分相加,得到整體損失函數。

SSD、YOLO、Squezedet、Detectnet和其他one-stage目標檢測模型的損失函數可能有差異,但是它們往往由相同的部分組成。

(1)沒有被匹配的檢測器(負例)

對于負例,損失函數僅包含置信度部分,因為沒有真實框,所以沒有任何坐標或類別標簽來計算損失。如果這樣的檢測器確實找到了一個物體,它應該受到懲罰。置信度分數表示檢測器是否認為有一個物體的中心在這個網格單元中。對于這樣的檢測器,目標張量中的真實置信度得分被設置為0,因為這里沒有物體。預測得分也應該是0,或接近它。損失函數要降低預測值與目標值之間的誤差。在YOLO中,這樣計算:

no_object_loss[i, j, b] = no_object_scale * (0 - sigmoid(pred_conf[i, j, b]))**2

這里pred_conf[i, j, b]是網格單元 i, j上的檢測器b預測的置信度. 這里使用sigmoid來將置信度的取值限制在[0,1]。可以看到,上面的loss僅僅是計算預測值與目標值之差的平方。而no_object_scale是一個超參數,一般取0.5, 這樣這部分loss占整體比重不大. 由于圖像中只有少量物體,所以845個檢測器中的大部分僅計算這類“no object”損失。由于我們不想讓模型僅僅學習到“no objects”,這部分loss不應該比那些匹配到物體的檢測器的loss重要。

上述公式僅是計算一個網格單元中一個檢測器的loss,實際上要將所有網格中的負例檢測器的loss求和才是最終的loss。對于那些正例檢測器,這項loss總是0。SqueezeDet求的是各個檢測器loss的平均值(總loss除以負例檢測器數量),而在YOLO中直接取loss和。

實際上,YOLO還有一個特別處理之處。如果一個檢測器的預測框與所有真實框的IOU最大值大于一個閾值(比如0.6),那么忽略這個檢測器的no_object_loss。換句話說,如果一個檢測器被認為不應該預測一個物體,但是實際上卻預測了一個不錯的結果,那么最好是忽略它(或者鼓勵它預測物體,也許我們應該讓這個檢測器與這個物體匹配)。這個trick到底會起多大作用,并無法評估(深度學習很多這樣無法講明白的trick)。

SSD沒有這項loss,因為它將背景類看成一個特殊類進行處理。如果預測的是背景類,那個檢測器被認為沒有檢測到物體。

注意:YOLO采用平方和誤差(sum-squared error,SSE),而不是常見的用于回歸的均方差(mean-squared error,MSE),或是用于分類的交叉熵。一個可能原因是每張圖片物體數量并不同,如果取平均,那么包含10個物體的圖片與包含1個物體的圖片的loss的重要性一樣,而采用求和,前者的重要性約是后者的10倍,這可能更公平。

(2)被匹配的檢測器(正例)

前面所說的是不負責檢測物體的負例檢測器,接下來講另一類檢測器:它們應該檢測到物體。當這類檢測器沒有檢測到物體,或者給物體錯誤分類時,它們就被判定出錯,有三部分loss來評估錯誤。

(a)置信度

首先是置信度loss:

object_loss[i, j, b] = object_scale * (1 - sigmoid(pred_conf[i, j, b]))**2

這與前面的no_object_loss很類似,只是這里的目標值是1,因為我們100%確定存在一個物體。 實際上,YOLO的處理方式更微妙:

object_loss[i, j, b] = object_scale * (IOU(truth_coords, pred_coords) - sigmoid(pred_conf[i, j, b]))**2

預測的置信度pred_conf[i, j, b]應該能夠表示預測框和真實框的IOU值,理想狀態下這是1。YOLO在計算loss時不采用理想值,而是使用兩個框的實際IOU值。這也講得通:當IOU值低時,置信度會低,反之IOU值高,置信度也會高。對于no-object loss,我們一直希望預測的置信度為0,而這里我們并不是想要模型的置信度一直是100%。相反,模型應該能夠學習評估預測的邊界框的實際好壞,而IOU恰好可以反映這一點。前面已經提到, SSD不預測置信度,所以這項loss也不計算。

(b)類別概率

每個檢測器都會預測物體的類別,這與邊界框坐標是分開的。本質上,我們為不同大小的物體訓練了5個獨立的分類器(同一個網格中的每個檢測器的分類器是不同的)。YOLOv1和v2按如下方式計算分類的loss:

class_loss[i, j, b] = class_scale * (true_class - softmax(pred_class))**2

這里true_class是onehot編碼的目標向量(對于VOC數據集,大小為20) ,而pred_class是預測的logits向量。注意這里我們雖然使用了softmax,但是并沒有計算交叉熵,反而是誤差平方和loss,或者這是為了與其它loss保持一致。實際上,甚至可以不應用softmax也是可以的。

而YOLOv3和SSD采用不同的方式,它們將這個問題看成多標簽分類問題。所以不采用softmax(它導致各個類別互斥),反而使用sigmoid,這樣允許預測多個標簽。進一步,它們采用標準的二元交叉熵計算loss。

由于SSD不預測置信度,所以它增加了一個背景類。如果檢測器預測是背景,那么此檢測器沒有檢測到物體,即忽略這個預測。實際上SSD的no-object loss就是背景類的分類loss。

(c)邊界框坐標

最后一項loss是邊界框坐標,也稱為定位損失,其實就是簡單地計算邊界框的4個坐標的回歸損失:

coord_loss[i, j, b] = coord_scale * ((true_x[i, j, b] - pred_x[i, j, b])**2+ (true_y[i, j, b] - pred_y[i, j, b])**2+ (true_w[i, j, b] - pred_w[i, j, b])**2+ (true_h[i, j, b] - pred_h[i, j, b])**2)

其中縮放因子coord_scale是設置定位損失的權重,這個超參一般設置為5,這樣該項損失相比其它更重要。這項損失是極其簡單的,但是有必要知道公式中true_*和pred_*到底指什么。 在之前的部分,我們已經給出了如何得到真實的邊界框坐標:

box_x[i, j, b] = (i + sigmoid(pred_x[i, j, b])) * 32 box_y[i, j, b] = (j + sigmoid(pred_y[i, j, b])) * 32 box_w[i, j, b] = anchor_w[b] * exp(pred_w[i, j, b]) * 32 box_h[i, j, b] = anchor_h[b] * exp(pred_h[i, j, b]) * 32

我們需要進行對模型的預測做一定的后處理才能得到有效的坐標值。由于模型實際上不是直接預測有效的邊界框坐標,所以損失函數中的真實框也要與之對應,即我們要先將真實框的實際坐標進行逆向轉換:

true_x[i, j, b] = ground_truth.center_x - grid[i, j].center_x true_y[i, j, b] = ground_truth.center_y - grid[i, j].center_y true_w[i, j, b] = log(ground_truth.width / anchor_w[b]) true_h[i, j, b] = log(ground_truth.height / anchor_h[b])

注意true_x和true_y是相對于網格單元格的,而true_w和true_h是相對于先驗框的縮放因子。因此,在填充目標張量時,一定要先進行上述的逆向轉換,否則損失函數將計算的是兩個不同量的誤差。

在SSD中,計算定位損失有稍微的不同,它采用的是“Smooth L1”損失:

difference = abs(true_x[i, j, b] - pred_x[i, j, b]) if difference < 1:coord_loss_x[i, j, b] = 0.5 * difference**2 else:coord_loss_x[i, j, b] = difference - 0.5

對于其它項坐標也是如此,這項loss對邊界值更不敏感(曲線更平穩)。

開始訓練

接下來,我們可以給出一個完整的模型訓練過程,首先我們需要:

  • 一個包含圖片以及邊界框標注的數據集(如Pascal VOC);
  • 一個可以擁有網格檢測器的模型,并采用一個匹配策略將真實框轉化為目標張量;
  • 一個計算預測值與目標值的損失函數。

然后就可以采用SGD對模型進行訓練,由于檢測器對正例和負例的loss計算方式不同,需要一定的循環才可以計算出整個loss,簡單的偽代碼如下:

for i in 0 to 12:for j in 0 to 12:for b in 0 to 4:gt = target[i, j, b] # ground-truthpred = grid[i, j, b] # prediction from model# is this detector responsible for an object?if gt.conf == 1:iou = IOU(gt.coords, pred.coords)object_loss[i, j, b] = (iou - sigmoid(pred.conf[i, j, b]))**2coord_loss[i, j, b] = sum((gt.coords - pred.coords)**2)class_loss[i, j, b] = cross_entropy(gt.class, pred.class)else:no_object_loss[i, j, b] = (0 - sigmoid(pred.conf[i, j, b]))**2

最終的loss是各項loss的加權和:

loss = no_object_scale * sum(no_object_loss) + object_scale * sum(object_loss) + coord_scale * sum(coord_loss) + class_scale * sum(class_loss)

但是實際上可以將上述循環過程向量化以可以在GPU上加速運算,主要思路是采用一個mask屏蔽那些不需要計算的部分:

# the mask is 1 for detectors that have an object, 0 otherwise mask = (target.conf == 1) # compute IOUs between each detector's predicted box and # the corresponding ground-truth box from the target tensor ious = IOU(target.coords, grid.coords) # compute the loss terms for the entire grid at once: object_loss = sum(mask * (ious - sigmoid(grid.conf))**2) coord_loss = sum(mask * (target.coords - grid.coords)**2) class_loss = sum(mask * (target.class - softmax(grid.class))**2) no_object_loss = sum((1 - mask) * (0 - sigmoid(grid.conf))**2)

即使看起來目標檢測的損失函數比圖像分類更復雜,但是一旦你理解了每個部分的含義就比較簡單了。由于YOLO,SSD以及其它的one-stage目標檢測模型在計算loss時有稍微的不同,因而你有很多可選擇的余地進行設計。

另外有一些值得注意的小技巧來訓練模型:

  • 多尺度訓練。一般情況下,目標檢測模型用于不同大小的圖片,因而也包含不同尺度的物體。一個可以讓模型可以對不同大小的輸入泛化的方法是每一定的迭代過程中隨機選擇不同的輸入尺寸。比如隨機從320×320到608×608之間選擇的輸入,而不是恒定在416x416。
  • 熱身訓練(Warm-up training)。 YOLO在早期訓練階段為每個單元中心增加一個假的真實框(先驗框),采用這個額外的坐標損失來鼓勵模型的預測可以匹配到檢測器的先驗框。
  • 難例挖掘(Hard negative mining)。前面已經說過大部分檢測器是不負責檢測任何物體的。這意味著正例數量要遠少于負例。YOLO采用一個超參數no_object_scale 來處理這種情況,但是SSD采用難例挖掘:它不是計算所有負例的損失,而是只計算那些預測結果最錯的部分損失(即置信度較高的負例)。

即使一旦訓練后模型就能很好地工作,但是你有時候需要這些技巧讓模型快速學習。

如何評價模型

為了評估一個分類模型,你可以簡單的計算在測試集上預測正確的數量,并除以測試圖片的總數,從而得到分類準確度。然而對于目標檢測模型,你需要評估以下幾個部分:

  • 每個檢測物體的分類準確度;
  • 預測框與真實框的重合度(IOU)
  • 模型是否找到圖片中的所有物體(召回,recall)。

僅采用任何一個指標是不夠的。比如,如果設定IOU的閾值為50%,當一個預測框與一個真實框的IOU值大于該閾值時,被判定為真陽(TP),反之被判定為假陽(FP)。但是這并不足以評估模型的好壞,因為我們無法知道模型是否漏檢了一些物體,比如存在某些模型沒有預測出的真實框(假陰,FN)。

注意:在目標檢測中沒有真陰(true negatives),一個真陰是本來物體也不存在,正好模型也沒有給出預測。大部分情況下我們更關心包含物體的地方,而不關心背景部分,實際上真陰也無法計算。

為了將以上幾種不同因素轉化為一個單一指標,通常我們計算mAP(mean average precision)。mAP值越高,模型越好。計算mAP的方法隨數據集略有差異。

計算mAP

對于Pascal VOC數據集,首先我們要單獨計算各個類別的AP(average precision),然后取平均值得到最終的mAP,所以mAP是平均的平均。對于precision,它是真陽數除以檢測的總數:

precision = TP / (TP + FP)

在這個場景中,假陽值是檢測器預測了一個在圖像中并不存在的物體。這一般發生在預測框與圖像中的真實框差異很大(IOU值低于閾值),或者預測的類別是錯誤的。

注意:這里我們并關心到底是哪個檢測器給出的預測。在評估模型時我們并不會像訓練過程那樣將特定的檢測器分配給某個物體,而是僅僅將預測框與真實框進行比較,以確定到底檢測出了多少物體。

另外一個與 precision一起計算的指標是recall(true positive rate or the sensitivity):

recall = TP / (TP + FN)

recall和precision的唯一區別是分母不同,前者是真陽數加假陰數,即所有真實物體的總數。對于假陰,就是檢測器沒有找到一個真實的物體,或者給出的置信度較低。

舉例來說,precision衡量的在預測為貓的物體中,到底有多少是真的貓,這里FP就是那些預測為貓但實際上卻不是貓的數量。而recall衡量的是模型找到了圖像中所有真實貓的多少個,FN是指的遺漏檢測的貓的數量。比如,模型預測出了3只貓,但是實際上其中一個是狗,另外一個不存在物體,那么precision就等于1/3=0.33(三個預測中僅有一個是對的)。如果圖像中存在4只貓,那么recall就是1/4=0.25,因為僅檢測出了一只貓。如果圖像中存在一只狗,那么對于狗這類,precision和recall都是0,因為狗的TP為0。這里是計算TP和FP的偽代碼:

sort the predictions by confidence score (high to low) for each prediction:true_boxes = get the annotations with same class as the predictionand that are not marked as "difficult"find IOUs between true_boxes and predictionchoose ground-truth box with biggest IOU overlapif biggest IOU > threshold (which is 0.5 for Pascal VOC):if we do not already have a detection for this ground-truth box:TP += 1else:FP += 1else:FP += 1

如果某個預測框的分類正確,且與真實框的IOU值大于50%,那么就認為是TP,反之則是FP。如果存在兩個及以上的預測與某個真實框的IOU大于50%,那么我們必須選擇其中的一個認為是正確的預測,其它的將被當做FP。我們希望模型僅對每個物體預測一個框,這里我們通常會選擇那個置信度最高的預測框。

由于對同個物體進行多次預測是受到懲罰的,所以最好先進行NMS以盡可能地去除重復的預測。最好也要扔掉那些置信度較低的預測(如低于0.3),否則它們會被當成FP。YOLO模型給出845個預測,而SSD給出1917個預測,這遠遠多于真實物體,因為大部分圖像只含有1到3個物體。

目前為止,我們并沒有計算FN,實際上并不需要。因為計算recall公式的分母是TP+FN,這實際上等于圖像中真實物體的數量(我們所關注的特定類)。

現在我們計算出了precision和recall,但是單個precision和recall無法說明模型的效果。所以我們將計算一系列的precision和recall對,然后畫出precision-recall曲線。對每個類,我們都會做出這樣的曲線。而某個類的AP值就是曲線下的面積。

precision-recall曲線

如下是狗這個類的precision-recall曲線:

其中x坐標是recall,從0(沒有檢測到物體)到1(發現了所有物體),而y坐標是precision。這里precision看成是recall的函數,所以曲線的面積實際上就是這類物體的平均precision,因此叫做 “mean average precision”:我們想知道不同recall下的precision的平均值。

如何解釋這條曲線?precision-recall曲線通常是通過設定不同的閾值來計算precision和recall對。對于一個二分類器來說,高于閾值就被判定為正例。在目標檢測領域,我們會不斷改變閾值(對應預測框的置信度)來得到不同的precision和recall。首先,我們計算第一個預測值(最大閾值)的precision和recall,然后計算第一個和第二個預測值(稍微降低閾值)的precision和recall,接著是前三個預測值的precision和recall(閾值更低),直到我們計算所有預測值下(閾值最低)的precision和recall。每對precision和recall對應就是曲線的一個點,x值為recall而y值是precision。在較大閾值處,recall是較低的,因為只包含很少預測結果,所以會有非常多的FN。你可以看到在曲線的最左側,precision為100%,因為我們只包含了非常肯定的預測框。但是recall是極低的,因為漏掉了很多物體。隨著閾值降低,將包含更多物體,recall增加。但precision上下波動,但由于FP會越來越多,它往往會變得更低。在最低閾值處,recall是最大的,因為現在包含了模型的所有預測。

可以看到,模型預測值的FP和FN之間始終存在折中。使用precision-recall曲線可以衡量這種折中,并找到一個較好的置信度閾值。選擇高閾值意味著我們保留較少的預測,因此將減少FP(減少了錯誤),但我們也會有更多的FN(錯過了更多的物體)。閾值越低,包含的預測越多,但它們通常質量較低。

理想情況下,各個recall下的precision都很高。計算出所有recall下的precision的平均值,可以給出模型在檢測此特定類物體的總體效果。一旦我們獲得了所有不同閾值下的precision和recall,就可以通過計算該曲線下的面積來得到AP。對于Pascal VOC數據集,實際上有兩種不同的方法:2007版本使用近似方法; 2012版本更精確(使用積分)但分數一般更低。最終的mAP僅僅是20個類的AP平均值。當然,mAP越高越好。但這并不意味著mAP就是最重要的, YOLO的mAP一般低于其它模型,但速度卻更快,特別是在移動設備上使用時,我們希望使用在速度和準確度之間具有較好折衷的模型。

總結

以上是生活随笔為你收集整理的人工智能目标检测总结(五)——深入理解one-stage目标检测模型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。