天天看點

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

因為這幾個月飯店生意恢複,加上研究 Faster-RCNN 用掉了很多時間,就沒有更新部落格了🐶。這篇開始會介紹對象識别的模型與實作方法,首先會介紹最簡單的 RCNN 與 Fast-RCNN 模型,下一篇會介紹 Faster-RCNN 模型,再下一篇會介紹 YOLO 模型。

在前面的文章中我們看到了如何使用 CNN 模型識别圖檔裡面的物體是什麼類型,或者識别圖檔中固定的文字 (即驗證碼),因為模型會把整個圖檔當作輸入并輸出固定的結果,是以圖檔中隻能有一個主要的物體或者固定數量的文字。

如果圖檔包含了多個物體,我們想識别有哪些物體,各個物體在什麼位置,那麼隻用 CNN 模型是無法實作的。我們需要可以找出圖檔哪些區域包含物體并且判斷每個區域包含什麼物體的模型,這樣的模型稱為對象識别模型 (Object Detection Model),最早期的對象識别模型是 RCNN 模型,後來又發展出 Fast-RCNN (SPPnet),Faster-RCNN ,和 YOLO 等模型。因為對象識别需要處理的資料量多,速度會比較慢 (例如 RCNN 檢測單張圖檔包含的物體可能需要幾十秒),而對象識别通常又要求實時性 (例如來源是攝像頭提供的視訊),是以如何提升對象識别的速度是一個主要的命題,後面發展出的 Faster-RCNN 與 YOLO 都可以在一秒鐘檢測幾十張圖檔。

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

對象識别的應用範圍比較廣,例如人臉識别,車牌識别,自動駕駛等等都用到了對象識别的技術。對象識别是當今機器學習領域的一個前沿,2017 年研發出來的 Mask-RCNN 模型還可以檢測對象的輪廓。

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

因為看上去越神奇的東西實作起來越難,對象識别模型相對于之前介紹的模型難度會高很多,請做好心理準備😱。

在介紹具體的模型之前,我們首先看看對象識别模型需要什麼樣的訓練資料:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

對象識别模型需要給每個圖檔标記有哪些區域,與每個區域對應的标簽,也就是訓練資料需要是清單形式的。區域的格式通常有兩種,(x, y, w, h) => 左上角的坐标與長寬,與 (x1, y1, x2, y2) => 左上角與右下角的坐标,這兩種格式可以互相轉換,處理的時候隻需要注意是哪種格式即可。标簽除了需要識别的各個分類之外,還需要有一個特殊的非對象 (背景) 标簽,表示這個區域不包含任何可以識别的對象,因為非對象區域通常可以自動生成,是以訓練資料不需要包含非對象區域與标簽。

RCNN (Region Based Convolutional Neural Network) 是最早期的對象識别模型,實作比較簡單,可以分為以下步驟:

用某種算法在圖檔中選取 2000 個可能出現對象的區域

截取這 2000 個區域到 2000 個子圖檔,然後縮放它們到一個固定的大小

用普通的 CNN 模型分别識别這 2000 個子圖檔,得出它們的分類

排除标記為 "非對象" 分類的區域

把剩餘的區域作為輸出結果

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

你可能已經從步驟裡看出,RCNN 有幾個大問題😠:

結果的精度很大程度取決于選取區域使用的算法

選取區域使用的算法是固定的,不參與學習,如果算法沒有選出某個包含對象區域那麼怎麼學習都無法識别這個區域出來

慢,賊慢🐢,識别 1 張圖檔實際等于識别 2000 張圖檔

後面介紹模型結果會解決這些問題,但首先我們需要了解最簡單的 RCNN 模型,接下來我們細看一下 RCNN 實作中幾個重要的部分吧。

選取可能出現對象的區域的算法有很多種,例如滑動視窗法 (Sliding Window) 和選擇性搜尋法 (Selective Search)。滑動視窗法非常簡單,決定一個固定大小的區域,然後按一定距離滑動得出下一個區域即可。滑動視窗法實作簡單但選取出來的區域數量非常龐大并且精度很低,是以通常不會使用這種方法,除非物體大小固定并且出現的位置有一定規律。

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

選擇性搜尋法則比較進階,以下是簡單的說明,摘自 opencv 的文章:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

你還可以參考 這篇文章 或 原始論文 了解具體的計算方法。

如果你覺得難以了解可以跳過,因為接下來我們會直接使用 opencv 類庫中提供的選擇搜尋函數。而且選擇搜尋法精度也不高,後面介紹的模型将會使用更好的方法。

使用算法選取出來的區域與實際區域通常不會完全重疊,隻會重疊一部分,在學習的過程中我們需要根據手頭上的真實區域預先判斷選取出來的區域是否包含對象,再告訴模型預測結果是否正确。判斷選取區域是否包含對象會依據重疊率 (IOU - Intersection Over Union),所謂重疊率就是兩個區域重疊的面積占兩個區域合并的面積的比率,如下圖所示。

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

我們可以規定重疊率大于 70% 的候選區域包含對象,重疊率小于 30% 的區域不包含對象,而重疊率介于 30% ~ 70% 的區域不應該參與學習,這是為了給模型提供比較明确的資料,使得學習效果更好。

計算重疊率的代碼如下,如果兩個區域沒有重疊則重疊率會為 0:

如果你想看 RCNN 的原始論文可以到以下的位址:

https://arxiv.org/pdf/1311.2524.pdf

好了,到這裡你應該大緻了解 RCNN 的實作原理,接下來我們試着用 RCNN 學習識别一些圖檔。

因為收集圖檔和标記圖檔非常累人🤕,為了偷懶這篇我還是使用現成的資料集。以下是包含人臉圖檔的資料集,并且帶了各個人臉所在的區域的标記,格式是 (x1, y1, x2, y2)。下載下傳需要注冊帳号,但不需要交錢🤒。

https://www.kaggle.com/vin1234/count-the-number-of-faces-present-in-an-image

下載下傳解壓後可以看到圖檔在 train/image_data 下,标記在 bbox_train.csv 中。

例如以下的圖檔:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

對應 csv 中的以下标記:

資料的意義如下:

Name: 檔案名

width: 圖檔整體寬度

height: 圖檔整體高度

xmin: 人臉區域的左上角的 x 坐标

ymin: 人臉區域的左上角的 y 坐标

xmax: 人臉區域的右下角的 x 坐标

ymax: 人臉區域的右下角的 y 坐标

使用 RCNN 學習與識别這些圖檔中的人臉區域的代碼如下:

和之前文章給出的代碼例子一樣,這份代碼也分為了 prepare, train, eval 三個部分,其中 prepare 部分負責選取區域,提取正樣本 (包含人臉的區域) 和負樣本 (不包含人臉的區域) 的子圖檔;train 使用普通的 resnet 模型學習子圖檔;eval 針對給出的圖檔選取區域并識别所有區域中是否包含人臉。

除了選取區域和提取子圖檔的處理以外,基本上和之前介紹的 CNN 模型一樣吧🥳。

執行以下指令以後:

的最終輸出如下:

訓練集和驗證集的正确率變化如下:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

正确率看起來很高,但這隻是針對選取後的區域判斷的正确率,因為選取算法效果比較一般并且樣本數量比較少,是以最終效果不能說令人滿意😕。

執行以下指令,再輸入圖檔路徑可以使用學習好的模型識别圖檔:

以下是部分識别結果:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN
寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

精度一般😕。

RCNN 慢的原因主要是因為識别幾千個子圖檔的計算量非常龐大,特别是這幾千個子圖檔的範圍很多是重合的,導緻了很多重複的計算。Fast-RCNN 着重改善了這一部分,首先會針對整張圖檔生成一個與圖檔長寬相同 (或者等比例縮放) 的特征資料,然後再根據可能包含對象的區域截取特征資料,然後再根據截取後的子特征資料識别分類。RCNN 與 Fast-RCNN 的差別如下圖所示:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

遺憾的是 Fast-RCNN 隻是改善了速度,并不會改善正确率。但下面介紹的例子會引入一個比較重要的處理,即調整區域範圍,它可以讓模型給出的區域更接近實際的區域。

以下是 Fast-RCNN 模型中的一些處理細節。

在 RCNN 中,傳給 CNN 模型的圖檔是經過縮放的子圖檔,而在 Fast-RCNN 中我們需要傳原圖檔給 CNN 模型,那麼原圖檔也需要進行縮放。縮放使用的方法是填充法,如下圖所示:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

縮放圖檔使用的代碼如下 (opencv 版):

縮放圖檔後區域的坐标也需要轉換,轉換的代碼如下 (都是枯燥的代碼🤒):

在前面的文章中我們已經了解過,CNN 模型可以分為卷積層,池化層和全連接配接層,卷積層,池化層用于抽取圖檔中各個區域的特征,全連接配接層用于把特征扁平化并交給線性模型處理。在 Fast-RCNN 中,我們不需要使用整張圖檔的特征,隻需要使用部分區域的特征,是以 Fast-RCNN 使用的 CNN 模型隻需要卷積層和池化層 (部分模型池化層可以省略),卷積層輸出的通道數量通常會比圖檔原有的通道數量多,并且長寬會按原來圖檔的長寬等比例縮小,例如原圖的大小是 3,256,256 的時候,經過處理可能會輸出 512,32,32,代表每個 8x8 像素的區域都對應 512 個特征。

這篇給出的 Fast-RCN 代碼為了易于了解,會讓 CNN 模型輸出和原圖一模一樣的大小,這樣抽取區域特征的時候隻需要使用 <code>[]</code> 操作符即可。

Fast-RCNN 根據整張圖檔生成特征以後,下一步就是抽取區域特征 (Region of interest Pooling) 了,抽取區域特征簡單的來說就是根據區域在圖檔中的位置,截區域中該位置的資料,然後再縮放到相同大小,如下圖所示:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

抽取區域特征的層又稱為 ROI 層。

如果特征的長寬和圖檔的長寬相同,那麼截取特征隻需要簡單的 <code>[]</code> 操作,但如果特征的長寬比圖檔的長寬要小,那麼就需要使用近鄰插值法 (Nearest Neighbor Interpolation) 或者雙線插值法 (Bilinear Interpolation) 進行截取,使用雙線插值法進行截取的 ROI 層又稱作 ROI Align。截取以後的縮放可以使用 MaxPool,近鄰插值法或雙線插值法等算法。

想更好的了解 ROI Align 與雙線插值法可以參考這篇文章。

在前面已經提到過,使用選擇搜尋法等算法選取出來的區域與對象實際所在的區域可能有一定偏差,這個偏差是可以通過模型來調整的。舉個簡單的例子,如果區域内有臉的左半部分,那麼模型在經過學習後應該可以判斷出區域應該向右擴充一些。

區域調整可以分為四個參數:

對左上角 x 坐标的調整

對左上角 y 坐标的調整

對長度的調整

對寬度的調整

因為坐标和長寬的值大小不一定,例如同樣是臉的左半部分,出現在圖檔的左上角和圖檔的右下角就會讓 x y 坐标不一樣,如果遠近不同那麼長寬也會不一樣,我們需要把調整量作标準化,标準化的公式如下:

x1, y1, w1, h1 = 候選區域

x2, y2, w2, h2 = 真實區域

x 偏移 = (x2 - x1) / w1

y 偏移 = (y2 - y1) / h1

w 偏移 = log(w2 / w1)

h 偏移 = log(h2 / h1)

經過标準化後,偏移的值就會作為比例而不是絕對值,不會受具體坐标和長寬的影響。此外,公式中使用 log 是為了減少偏移的增幅,使得偏移比較大的時候模型仍然可以達到比較好的學習效果。

計算區域調整偏移和根據偏移調整區域的代碼如下:

Fast-RCNN 模型會針對各個區域輸出兩個結果,第一個是區域對應的标簽 (人臉,非人臉),第二個是上面提到的區域偏移,調整參數的時候也需要同時根據這兩個結果調整。實作同時調整多個結果可以把損失相加起來再計算各個參數的導函數值:

有一個需要注意的地方是,在這個例子裡計算标簽損失需要分别根據正負樣本計算,否則模型在經過調整以後隻會輸出負結果。這是因為線性模型計算抽取出來的特征時有可能輸出正 (人臉),也有可能輸出負 (非人臉),而 ROI 層抽取的特征很多是重合的,也就是來源相同,當負樣本比正樣本要多的時候,結果的方向就會更偏向于負,這樣每次調整參數的時候都會向輸出負的方向調整。如果把損失分開計算,那麼不重合的特征可以分别向輸出正負的方向調整,進而達到學習的效果。

此外,偏移損失隻應該根據正樣本計算,負樣本沒有必要學習偏移。

最終的損失計算處理如下:

因為選取區域的算法本來就會傳回很多重合的區域,可能會有有好幾個區域同時和真實區域重疊率大于一定值 (70%),導緻這幾個區域都會被認為是包含對象的區域:

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

模型經過學習後,針對圖檔預測得出結果時也有可能傳回這樣的重合區域,合并這樣的區域有幾種方法:

使用最左,最右,最上,或者最下的區域

使用第一個區域 (區域選取算法會按出現對象的可能性排序)

結合所有重合的區域 (如果區域調整效果不行,則可能出現結果區域比真實區域大很多的問題)

上面給出的 RCNN 代碼例子已經使用第二個方法合并結果區域,下面給出的例子也會使用同樣的方法。但下一篇文章的 Faster-RCNN 則會使用第三個方法,因為 Faster-RCNN 的區域調整效果相對比較好。

如果你想看 Fast-RCNN 的原始論文可以到以下的位址:

https://arxiv.org/pdf/1504.08083.pdf

代碼時間到了😱,這份代碼會使用 Fast-RCNN 模型來圖檔中的人臉,使用的資料集和前面的例子一樣。

在 31 輪訓練以後的輸出如下 (因為訓練時間實在長,這裡偷懶了🥺):

調整區域前

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

調整區域後

寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN
寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN
寫給程式員的機器學習入門 (九) - 對象識别 RCNN 與 Fast-RCNN

精度和 RCNN 差不多,甚至有些降低了 (為了支援 4G 顯存縮放圖檔了)。不過識别速度有很大的提升,在同一個環境下,Fast-RCNN 處理單張圖檔隻需要 0.4~0.5 秒,而 RCNN 則需要 2 秒左右。

這篇介紹的 RCNN 與 Fast-RCNN 隻是用于入門對象識别的,實用價值并不大 (速度慢,識别精度低)。下一篇介紹的 Faster-RCNN 則是可以用于生産的模型,但複雜程度也會高一個等級🤒。

此外,這篇文章和下一篇文章的代碼實作和論文中的實作、網上的其他實作不完全一樣,這是因為我的機器顯存較低,并且我想用盡量少的代碼來實作相同的原理,使得代碼更容易了解 (網上很多實作都是分一堆檔案,甚至把部分邏輯使用 c/c++ 擴充實作,性能上有好處但是初學者看了會頭大)。