Faster R-CNN實現細節

本篇博客記錄Fatser R-CNN的實現過程。Ref部分給的鏈接的代碼是python+c+CUDA混合編程的,本博客不會涉及CUDA的並行,完全基於Python+Pytorch+Numpy。PS:都做人工智能了,CUDA的並行也得學習,大牛的代碼都是會考慮性能的,不能還是初級階段實現功能就行。

算法思想

包括SSD在內的這些OB算法,都是用卷積提特徵,基於特徵使用卷積層或者全連接計算類別和偏移量,非線性的激活函數應用到神經元裏面,這樣的網絡結構能夠擬合任意函數.相比於SSD多特徵層分別應用錨框,Faster R-CNN更簡單.

整體架構

在這裏插入圖片描述
這一部分適合在閱讀完整篇文章後或者已經對Faster R-CNN瞭解後看.隨着訓練的進行,RPN的前向輸出會達到一定的準確率,但是在訓練前期,因爲參數不是很優,RPN的前向結果不是很好,如果直接使用RPN的結果進行RoIPooling以及之後的處理的話,那麼全連接層及其之後的層的學習效果就不是很好,所以在訓練時,我們需要在RPN網絡的輸出中加入真實框,可以幫助訓練全連接層及其之後的層.最後我們在測試時直接使用RPN的輸出,進行RoIPooling,進而傳入全連接層及其之後的層. 在實現的角度來說,我們在train模式加入真實框,在eval模式不加入真實框,直接使用RPN的輸出,這一部分輸出並不是最終的在圖片上顯示的anchors,就如應用部分所講,得到這一部分anchors後,我們還要基於它們的分數和邊界框偏移量預測值,進行進一步裁剪、移動和刪減.正如所描述的,在測試階段Faster R-CNN會進行兩次anchors的移動,而在訓練時因爲不需要最後的輸出我們只用進行RPN中的anchors移動(RPN的結果是相對於輸入圖片的座標).

NMS(Non-Maximum Suppression)非極大值抑制

Faster R-CNN網絡採用了anchor思想,所以anchors很多,實質就是窮舉anchors,所以anchors間很多重複區域,我們希望最小化anchors數量,在那些IoU很大的anchors之間保留前景(非背景)分數較高的anchors。在Faster R-CNN中,它用於RPN中,與SSD中有點不一樣,因爲它將所有前景類別看做一個類別即前景,它的步驟是直接取所有anchors的前景預測分數,然後從大到小排序,首先取最大的分數的anchor,以它爲基準,遍歷分數比他低的所有其他anchor,如果這個anchor與它的IoU大於某個閥值,那麼就把這個anchor移出,遍歷完後,選擇次大的且未被移出的anchor,作爲基準,重複這樣的過程,直到沒有這樣的基準,剩餘的anchor就是NMS的結果。

特徵層和區域提議網絡(RPN)

此部分介紹整體架構中的卷積神經網絡和RPN。

卷積神經網絡

其中卷積神經網絡部分可以使用Resnet、VGG-16,VGG-16使用conv-5_3,即最後一個特徵層,最後輸出的特徵的尺度爲原始圖片尺度的1/16。在下文中,爲了便於區分,我們稱這一部分爲特徵層。

RPN

在RPN中的卷積層是一個輸入輸出channels均爲512,核大小爲3*3,填充爲1的卷積層,再接上一個ReLU,它的輸入是特徵層的輸出。在通過這一層得到了一個更加綜合的特徵後,對於二元類別預測部分,它是一個卷積層,輸入channels爲512,因爲我們是二元預測,即背景和前景兩個類別,所以輸出channels爲anchors數目*2,核大小爲1,沒有ReLU和填充,它的作用就是得到分別以特徵圖上所有點爲中心,在一組anchors上的預測類別。卷積核爲1的卷積層有綜合特徵的作用,綜合各個channel的特徵得到最後的預測分數。同理邊界框預測部分依然是一個卷積層,它和二元類別預測部分的唯一不同是輸出的channels,這裏的channels的數量應該爲anchors數目*4邊界框預測部分的輸出是計算anchors與真實框的偏移量,即一個迴歸問題。我們在得到了二元預測結果後需要通過softmax層得到概率(這裏涉及將卷積輸出的類別預測結果變換一下形狀)。
然後我們需要利用類別預測信息和邊界框預測信息生成anchors。首先我們使用scale=[8, 16, 32](PS:scale應根據input圖片本身的大小而定,論文中進行縮放將最小邊轉爲600,scales採用128*128、256*256、512*512,因爲這是相對原圖的anchor尺寸,我們需要縮放到feature map上即除以16,剛好爲8*8,16*16,32*32),ratios=[0.5, 1, 2]參數產生9個anchors,首先使用ratios保持面積得對長寬進行放縮,得到三個anchors,然後對這三個anchors使用scale進行放縮,得到最終的9個anchors。anchors的數量是一個超參,這個超參決定了二元類別預測部分和邊界框預測部分的網絡結構。我們假定類別預測部分的輸出的前anchors數目通道爲對背景的預測,後anchors數目通道是對前景的預測。我們暫時只需要對前景的預測,所以我們取後anchors數目的通道的所有anchors的前景預測。
因爲之前得到的9個anchors是相對座標,我們需要將anchors的座標放回原圖的座標中去,前面已經提到了最後的特徵圖的尺寸爲原圖的1/16,所以只需要將特徵圖上的座標乘上16即可還原,然後我們加上相對座標,就得到了anchors在原圖中對應的座標,最後的形狀爲(H*W*A,4),特徵圖上的每一個點對應了A個anchors。
我們將邊界框偏移量預測的結果變換一下形狀,將其應用到我們的anchors上,變換一下位置,這樣就得到了提議區域
因爲得到的提議區域很多,且可能提議區域座標已超過原圖大小,或者爲負數,我們需要進行裁剪,將座標變爲原圖範圍內。我們設定了一個anchor的最小尺寸,我們需要移出那些很小的anchors,同時從上面得到的所有anchors的前景預測結果中去掉這些小的anchors。此時因爲anchors還是很多,在進行NMS(Non-Maximum Suppression)前我們需要去除一部分前景得分低的anchors,這一部分的設置爲取12000個anchors。然後我們進行NMS,設置Thresh爲0.7。我們在實現NMS時把分高的先加入進最終的集合。這樣的anchors集合同樣很大,我們在進行完NMS後,取前景預測分數最高的2000個anchors。這個就是RPN的輸出了。這個輸出anchors是加上了RPN預測的偏移量的。因爲特徵層很簡單,且後續的興趣區域池化層,需要先計算RPN,我們將它合併到RPN中,所以最終RPN的前向輸出就有特徵層的輸出和anchors信息(形狀爲batch_size,x1,x2,x3,x4)。anchors的座標是相對於原圖的.
在測試和訓練時,RPN網絡的前向輸出除了前向輸出的anchors數量不同外其他沒什麼區別.訓練時輸出更多的anchors,測試時輸出較少的anchors.

RoI pooling

這一層就是興趣區域池化層,這個池化層與我們平時使用的池化層有點區別.PyTorch中有torch.nn.Module和torch.autograd.Function的概念,Module一般是一層或者幾層的網絡,它不需要backward函數,它的求導由自動機制完成,而Function一般用於某個自定義函數的父類,它是計算圖中的邊,他會爲每一個操作創建一個函數對象,使用計算圖保存數據和計算曆史,需要我們自己定義它的子類的backward函數進行反向傳播.
這裏我們首先創建一個繼承自Module類的RoIPool類,在這個類的forward函數中定義一個繼承自Function的RoIPoolFunction類,直接使用對象+()的形式就可以調用RoIPoolFunction的forward函數.下面我們關鍵說明RoIPoolFunction的實現.(對於RoIPool類我認爲是可以省略的,它僅僅是一個打包類)
因爲各個RoI的大小是不一樣的,我們通過前向傳播時我們希望將這些RoI的特徵變成一個相同尺寸,對於一張圖片,RoI Pooling的輸出應該是ncwh(n表示anchors(RoIs)數量,c分別表示特徵層的通道數,w h表示我們希望的特徵尺度,是一個超參,我們取7),它的輸入是特徵層的輸出和RoIs區域(這個區域在訓練和測試時計算方法不一樣).因爲這些RoIs區域的座標是相對於原圖的,所以我們需要乘上1/16.我們將RoI在特徵層輸出上的區域均分爲77的區域,對這49個區域我們進行最大池化操作,因爲每一個區域的計算都是獨立的,我們能夠進行並行化,作者的代碼是將輸出拉成一維,然後對於輸出的每一個元素進行並行化.除了得到結果,我們需要記錄額外的信息,便於後面進行反向傳播,我們需要記錄輸出的每一個元素對應於特徵層的輸出的位置,你也可以把特徵層拉成一維時的座標作爲那個位置,只要可以還原即可.對於某一個anchor來說,它的通道與特徵層的輸出的c是對應的,即相同的c只能在特徵層的相同c中進行RoIPooling.
接下來就是反向傳播了,對於Function的backward函數的輸入是對於其輸出的梯度,根據鏈式法則,我們只需要進行乘上這一層的局部梯度得到其輸入的梯度,返回即可.所以最終的梯度的維度與其輸入的維度一樣,即特徵層的輸出的維度.對於後層傳來的梯度,如果特徵層的輸出作爲最大值參加了計算,那麼需要累加這些位置的梯度,因爲是最大值運算,所以局部梯度爲1.同樣的道理,這裏最終每一個梯度值的計算都是獨立的,可以進行並行.算法步驟是對於每一個最終的梯度值位置我們遍歷所有的RoIs,接着判斷這個位置是否已經超過了當前anchor的範圍,如果沒有超過,說明這個位置可能參加了前向計算,接着計算這個位置如果參加前向大概在前向結果的哪些位置(實際上這個位置只可能參與某一個值的計算,但是因爲我們在前向的過程中有取整的計算,所以我們最好遍歷更多的位置,在估計這些位置時,儘可能最大化),接着我們就要使用之前前向傳播存儲的最大數位置座標了,遍歷這些位置的最大值位置,如果就等於當前位置,那麼加上這些位置的梯度.爲了計算這一層後向傳播的梯度,除了存儲最大數位置座標,我們還需要存儲RoIs 特徵層輸出的維度 興趣區域池化層的輸出尺寸 縮放比例(原圖尺寸到特徵層輸出尺寸的比例).

全連接層、類別預測和邊界框預測

全連接部分是由兩個全連接層組成的,結構分別爲(51277,4096),(4096,4096).我們在得到了RoIPooling的輸出後,將每個anchor的特徵(c,h,w)拉成一維的,這樣RoIPooling的結果變爲了(n,51277),n是anchors的數目,相當於batch_size.我們在每一個全連接層做完ReLU運算後,使用一個Dropout.
類別預測我們使用一個全連接層,它的結構是(4096,類別數),邊界框預測的結構是(4096,類別數乘以4),他們均不使用ReLU,直接輸出線性運算結果.他們的輸入爲上一層的全連接層輸出.最後在測試時整個Faster R-CNN網絡的輸出爲類別預測結果的softmax、邊界框預測輸出和RPN網絡的前向輸出的anchors.

訓練

作者在論文中提到了,聯合在一起訓練可能難以收斂,而訓練主要解決的是share feature maps,作者提出了4步交替訓練法——RPN訓練、基於訓練好的RPN訓練FAST-RCNN、固定feature maps爲FAST-RCNN結果僅僅微調RPN網絡、固定RPN和feature maps微調FAST-RCNN全聯接部分。

RPN訓練

我們需要真實的邊界框、困難的邊界框(是真實邊界框的子集)、不關心的邊界框(包含一些小目標,我們不用這個)。同樣的方法,我們首先爲特徵圖上每一點生成一組anchors,anchors的座標都是相對於原圖,然後直接去除那些座標超過原圖邊界或者爲負的anchors。利用剩餘的anchors,然後我們根據真實的邊界框計算每一個anchors和每一個真實框的IoU,然後我們得到每個anchors與哪一個真實框overlap最大,我們設置背景的overlap爲0.3,那些最大overlap都小於0.3的我們標註爲背景,然後我們計算每一個真實框與哪一個anchor有最大的overlap,我們將這些anchors標註爲前景類別,最後我們將那些最大overlap大於等於0.7的anchors標註爲前景類別。然後我們處理不關心的邊界框,我們計算anchors與不關心邊界框相互之間的相交面積除以anchors的面積,然後對於一個anchor我們相加這個面積,將面積和大於0.5的anchors的標籤設爲-1。接着我們處理困難的邊界框,我們依然求anchors和困難邊界框之間的IoU,對於每一個anchors來說,我們求它的最大overlap,如果overlap大於等於0.7,那麼設置這個anchor的標籤爲-1,然後計算每一個困難邊界框對應哪一個anchor有最大overlap,將這些anchors的標籤設爲-1。我們設置標籤爲1的anchors的數量最高爲128,如果超過了這個數量,那麼我們隨機採樣,把採樣得到的anchors標籤設爲-1。然後我們計算類別爲背景(0)的anchors的最大個數爲256-目前標籤爲1的anchors數目,如果超過這個數目,那麼我們依然採樣,將採樣的anchors設爲-1。
接下來我們需要標註anchors的偏移量,依然是上述的anchors,我們不管他們的標籤是什麼,我們用他們和他們overlap最大值對應的真實邊界框計算偏移量,偏移量的計算和移動anchors的計算剛好相反。
接着我們計算mask,這個mask是一個(H*W*A,4)形狀,mask中類別爲1的anchors的值爲四個1,其他均爲0。(PS:還有一個bbox的外部權值,它的值和mask一樣,但是不知道幹嘛的,應該都是用於偏移量的權值。)
因爲之前我們使用的anchors都是在圖片內部的anchors,不是最開始的完整的anchors,我們需要變換回去,以-1和0進行填充。爲了計算損失值,我們對將label變爲了(1, A * height, width,1);我們將偏移量標籤、mask、bbox的外部權值變爲(1, A * 4, height, width)。
接着我們就要建立損失,我們需要兩種損失,一個是針對類別的,另一個是針對anchors偏移量的,對於類別損失我們只關心背景類別和前景類別,對於標籤爲-1的我們不關心,我們使用交叉熵損失函數,顯然我們需要直接使用卷積層的輸出,而不能經過softmax,這裏我們是對二元類別預測部分的卷積和邊界框預測部分的卷積的結果計算損失,而與RPN的前向傳播結果沒有太大關係.從上面的標籤可見,偏移量標籤維度與卷積的輸出維度一樣,我們只關心預測爲1的anchors,所以我們先用mask按element-wise分別乘上偏移量預測和偏移量標籤,然後將結果使用平滑L1範數損失,PyTorch中對這個損失函數的定義如下
在這裏插入圖片描述
n爲總元素.我們不希望它直接除以n,因爲我們的矩陣中有很多0,這樣會減小損失值,我們需要設定size_average=False,然後除以前景數量,爲了確保分母不爲0,我們加上一個epsilon=1e-4.最終我們加上這兩個損失值,其中偏移量損失我們乘上10,交叉熵損失不變.

Faster R-CNN輸出部分

可以想象如果在訓練的時候我們使用RPN的輸出錨框,那麼訓練將非常困難,因爲訓練前期RPN錨框的輸出是隨機的,讓最後的輸出部分基於這樣的錨框進行學習效果會很差.所以我們需要在RPN預測的錨框基礎上加入真實的錨框.
先我們需要從真實框中去除困難框,我們基於這些簡單框還要生成相對真實框抖動的框,這個抖動框和其相對應的真實框的寬度和長度一樣,只是位置有所偏移(PS:這一部分爲什麼需要抖動,作者說爲了避免將真實框加入到RoIs中將造成0損失,不太懂).接着我們將簡單框和抖動框合併到RPN預測的RoIs中.
首先我們對所有anchors進行打標籤,他們的標籤是IoU最大的那個真實邊界框的標籤.接下來需要對這一部分RoIs進行篩選了,首先我們去除與困難邊界框最大IoU大於等於0.5的anchors.接着去除與所有不關心邊界框交互面積佔當前anchor面積比例之和大於0.5的anchors.我們先找到那些最大IoU大於等於0.5的anchors,從這裏面去除剛纔找到的需要刪除的anchors,這樣我們就得到了我們認爲是前景的anchors,這個前景anchors列表和前面的標籤列表不一樣,前面的標籤列表中一些背景anchors也被設置了前景標籤.接着我們設置最大的前景anchors列表的size爲32,如果我們當前的列表的大小大於32,那麼進行隨機採樣.接着我們用同樣的方式找背景框,這些框的最大IoU在[0.1,0.5)之間的anchors,依然去除那些需要刪除的anchors.我們用128減去當前前景anchors的個數得到背景框的最大個數,如果背景框列表的個數大於它,依然採樣.我們從最開始生成的標籤列表中找出這些有效的anchors,並將背景部分設爲0.然後從所有的anchors中取出這些有效的anchors.然後我們生成這些anchors的邊界框標籤,因爲我們要與Faster R-CNN最後的輸出形狀相匹配,所以標籤爲一維(n,),anchors爲(n,5)(第二維的0維是圖片的batch_size),邊界框標籤結果爲(n,4類別數目),mask爲(n,4類別數目).同樣對於邊界框標籤和mask我們依然只關心標籤爲前景的anchors,即除了前景的anchors邊界框標籤其他均爲0.
這個輸出的anchors就是我們在訓練網絡最後輸出部分所使用的anchors,注意在測試時直接使用RPN的輸出anchors.損失的計算和RPN差不多,有一點不同在於在進行交叉熵計算時使用了權值,0類的權值爲標籤爲前景的anchors數量除以標籤爲背景的anchors的數量,其他爲1.根據作者代碼的參數設置,訓練Faster R-CNN最後一部分時使用的anchors的數量會包含RPN的結果,至少會包含背景anchors.
這裏的偏移量損失一樣使用平滑L1範數損失,然後它的係數依然爲10.最終的損失值是這兩個損失的加權和,這與RPN一樣.

完整的訓練過程以及參數設定

我們從特徵層部分中的conv-3開始訓練,因爲前幾層的特徵層都是提取一些邊緣信息,不同數據集差別不大.我們使用動量隨機梯度下降更新算法,設定起始學習率爲0.001,正則化係數爲0.0005,動量係數爲0.9,對於訓練的epoch,這個還得跟數據集有關吧,也是一種超參.
我們將一張圖片傳入我們構造的Faster R-CNN中,根據我們的實現我們前向傳播的過程中就構造了兩個損失值,最終我們需要優化的損失值是RPN網絡的損失加上Faster R-CNN最後部分的損失,接着將梯度置0,然後使用最終的損失值反向傳播,這裏防止梯度過大,我們需要進行裁剪,我們得到所有需要更新的參數的梯度,求這些梯度的平方和再開根號,我們設置這個值的最大值爲10,如果超過了就讓所有需要更新參數的梯度乘上10除以我們得到的值,這樣新的梯度的平方和開根號就等於10.裁剪完以後我們就需要根據新的梯度更新參數.注意一個比較好的習慣是訓練到一定輪數後要保存參數,不然程序崩了就又得重頭開始訓練,親身經歷,這種情況很難受.對於PyTorch來說需要我們自己手動進行學習率衰減,很簡單就是用新的學習率重新申請一個新的優化器,其他你認爲在訓練時需要的log信息可以打印出來,包括一段時間內的損失值信息.到此訓練部分就講完了.

測試

測試部分與訓練部分差別就是將網絡改爲eval模式,然後不再更新參數,測試比訓練更加簡單,不再多說.

應用

對於一張隨意的圖片我們也要能夠處理,並輸出邊界框.其實因爲我們使用了RoIPooling操作,所以我們對原圖實際上不用處理爲標準格式.但作者在他的demo程序中進行了縮放處理.我們首先需要對圖像進行縮放處理,我們有目標範圍爲600,最大尺寸爲1000,我們先用600除以圖片寬高中的最小值,得到縮放比例,用這個係數乘上圖片寬高的最大值,如果超過了最大尺寸1000,那麼我們就用1000除以最大值得到縮放比例,否則我們就用已得到的縮放比例,這樣都在1000以內.其實可以發現,寬高不同的圖片最終結果也可能不同,驗證了網絡確實可以處理不同尺寸的圖片.然後我們獲得圖片信息包括圖片的尺度和相對原圖的變換比例,其他的參數如真實邊界框等我們就不需要了,這些只在我們計算損失即前向傳播時有用.我們將圖片和圖片信息傳入Faster R-CNN得到預測分數、邊界框預測和邊界框偏移量預測.者如前面所說這個anchors的數目非常大且還包括背景,我們需要裁剪.
我們從預測分數結果中找到anchors的預測類別和對應得分,我們從這個結果裏面得到預測爲前景的anchors和其得分大於等於0.7的anchors.然後我們得到這些anchors的邊界框偏移量預測,因爲我們預測的結果的第二維是4*類別數目,我們裁剪它,我們只需要它對應類別的邊界框偏移量預測值,得到的第二維的大小爲4.然後我們基於這個偏移量移動anchors,移動的方法和我們計算偏移量標籤時的方法相反,最後因爲我們對原圖進行了縮放,我們需要還原回去,所以我們將最終的anchors座標除以我們的縮放比例.在作者的代碼中,先進行縮放然後移動anchors,我覺得這樣不合理,我們得到的anchors的偏移量應該是基於輸入圖的而不是原圖(PS:個人覺得原圖的縮放就沒有多大意義).我們最後爲了確保anchors在圖片內,我們需要對框進行裁剪,使得框在圖片範圍內.這樣處理後anchors的數量還是很多,且anchors之間的重疊區域可能很大,我們需要進行NMS.我們使用前面提到的NMS方法取出最終的anchors,其中閥值設爲0.3.對於這裏可以直接輸出最終的anchors框、分數和類別名詞.最後我們可以使用OpenCV庫在原圖上畫出邊界框、分數和類別.

Ref

[1]https://github.com/longcw/faster_rcnn_pytorch
[2]https://github.com/d2l-ai/d2l-zh

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章