OpenCV之DNN模塊,實現深度學習網絡的推理加速

OpenCV是計算機視覺領域使用最爲廣泛的開源庫,以功能全面使用方便著稱。自3.3版本開始,OpenCV加入了對深度神經網絡(DNN)推理運算的支持。在LiveVideoStack線上交流分享中英特爾亞太研發有限公司開源技術中心軟件工程師 吳至文詳細介紹了OpenCV DNN模塊的現狀,架構,以及加速技術。

直播回放

https://www2.tutormeetplus.com/v2/render/playback?mode=playback&token=846e0ba66b954f43834086dec24ae492

注:文中的ppt是作者在Embedded Linux Conference 2018上的演講“Deep Learning in OpenCV”的ppt

大家好,我是吳至文,目前就職於英特爾開源技術中心,主要從事圖形、圖像深度學習算法方面的開發和優化工作。很高興有機會和大家分享一下關於OpenCV深度學習模塊的內容,同時,也會介紹一下我們團隊在OpenCV深度學習方面所做的一些工作。

本次分享的主要內容包含以下幾個方面:

首先,我會介紹一下OpenCV和深度學習的背景知識;然後,介紹今天的主題——OpenCV深度學習模塊;接下來,會簡單介紹我們團隊在OpenCL加速方面所做的工作,以及開發的一個Vulkan後端;最後,會以一個例子的形式來展示如何使用DNN模塊開發深度神經網絡的應用。

一, OpenCV背景介紹

首先,什麼是OpenCV呢?我相信做過圖形圖像、計算機視覺應用開發的同學可能對OpenCV都不會陌生。OpenCV是一個包含了2500多個經過優化的計算機視覺和機器學習算法的開源計算機視覺庫。換句話說,目前主流的、比較知名的計算機視覺算法和論文在OpenVC裏都能找到相應的實現。OpenCV不僅僅是一個很好用的開發工具集,它同時對有志於學習計算機視覺開發的學生也是一個寶庫。OpenVC支持C、C++和Python語言,但是從OpenCV 4.0開始,C語言的API就逐漸被清除出去了,現在比較常用的API是C++和Python語言的。此外,OpenCV也是一個很活躍的開源項目,到目前爲止它在Github上有兩萬多個Forks。

2018年11月份,OpenCV發佈了4.0的版本。在這個版本有了比較大的變化,大概有以下這幾點:首先,它使用了C++11標準編譯器,並且移除了大多數的C 語言的API接口;另外,它不再對之前的版本有二進制的兼容,同時它使用了大量AVX2的指令集優化,從而大大提高了一些算法在CPU上的運行效率;再者就是,它具有更小的內存佔用以及支持OpenVINO作爲DNN模塊的後端。OpenVINO對於有的同學可能比較陌生,它是英特爾發佈的一個針對深度學習視覺應用的SDK。OpenVINO支持各種設備上的加速,包括CPU、GPU和VPU上面的加速,我們在後面還會提及這個內容。

二, 深度神經網絡的關鍵概念

接下來,我將介紹一些深度神經網絡的關鍵概念。

深度神經網絡最基本的組成單元是神經元,我們在文獻中一般稱作Node、 Neuron或Perceptron。一個神經元會對多個輸入進行加權和的運算,然後經過一個激活函數,最後輸出一個響應結果。多個神經元就組成了網絡的層,我們將神經網絡的第一層稱爲輸入層,一般用來加載輸入數據,如一幅圖像。我們將神經網絡的最後一層稱爲輸出層,根據具體網絡結構的不同,輸出層的含義也會不同。以分類網絡爲例,輸出層的每個節點表示屬於某個類別的概率大小。我們將在輸入層和輸出層之間的層稱爲隱層,所謂的深度神經網絡就是隱層數大於1的神經網絡。

接下來是網絡訓練。我們可以把神經網絡看成一個複雜的函數,在這個函數裏有許多參數是未知的,因此我們需要通過訓練來確定這些參數。爲了方便理解,我把訓練大體分爲四個步驟:第一步,選定訓練參數,如學習比例、批次大小、損失函數類型,初始化網絡權重;第二步、設置輸入數據,然後進行前向的網絡運算;第三步、比較運算結果和真實結果的差異;第四步、進行反向傳播運算,然後修改網絡參數,再回到第二步直到差異足夠小,或者人爲終止訓練過程。雖然整個訓練過程看起來比較複雜,但是深度學習框架會幫我們把這些事完成的,深度學習框架有Tensorflow、Caffe和Torch等。因此,我們只需要設計好網絡結構、選定訓練參數,剩下的事就可以交給框架去做。

在通過足夠的訓練之後,我們就可以確定所有的網絡參數,那麼這個複雜的函數就可以確定了。然後,我們輸入數據來通過深度學習庫計算函數結果的過程就叫推理。與訓練相比,推理過程簡單的多。上圖羅列了幾個使用了深度神經網絡的計算機視覺應用場景,如人臉識別、對象語義分割以及目標檢測的應用。

三, OpenCV深度學習模塊

從OpenCV 3.3版本開始,OpenCV加入了深度學習模塊,但這個模塊它只提供推理功能,而不涉及訓練,與此同時它支持多種深度學習框架,比如Tensorflow,Caffe,Torch和Darknet。

聽到這裏,可能有的同學會問:“既然我們已經有了Tensorflow、Caffe、Torch這些深度學習框架,爲什麼還要在OpenCV中再實現一個呢?這是不是在重複造輪子呢?”其實不是的,有下面幾個理由:第一、輕量,由於DNN模塊只實現了推理功能,它的代碼量、編譯運行開銷與其他深度學習框架比起來會少很多;第二、方便使用,DNN模塊提供了內建的CPU和GPU加速且無須依賴第三方庫,如果在之前項目使用了OpenCV,那麼通過DNN模塊可以很方便的無縫的爲原項目添加神經網絡推理能力;第三、通用性,DNN模塊支持多種網絡模型格式,因此用戶無須額外進行網絡模型的轉換就可以直接使用,同時它還支持多種運算設備和操作系統,比如CPU、GPU、VPU等,操作系統包括Linux、Windows、安卓和MacOS。

目前,OpenCV的DNN模塊支持40多種層的類型,基本涵蓋了常見的網絡運算需求,而且新的類型也在不斷的加入當中。

如上圖所示,這裏列出的網絡架構都是經過了很好的測試。它們在OpenCV中能很好支持的,基本涵蓋了常用的對象檢測和語義分割的類別,我們可以直接拿來使用。

接下來給大家介紹DNN模塊的架構。如上圖所示,從而往下,第一層是語言綁定,它支持Python和Java,其中Python用的比較多,因爲開發起來會比較方便。此外,在第一層中還包括準確度測試、性能測試以及一些示例程序。第二層是C++的API層,這屬於是原生的API,它的功能包括加載網絡模型、推理運算以及獲取網絡輸出。第三層是實現層,它包括模型轉換器、DNN引擎、層實現等。模型轉換器負責將各種網絡模型格式轉換成DNN模塊內部的表示,DNN引擎負責內部網絡的組織和優化,層實現是各種層運算的具體實現過程。第四層是加速層,它包括CPU加速、GPU加速、Halide加速和新加入的Intel推理引擎加速。前三個均是DNN模塊的內建實現,無須外部依賴就直接可以使用。CPU加速用到了SSE和AVX指令以及大量的多線程元語,而OpenCL加速是針對GPU進行並行運算的加速,這也是我們團隊工作的主要內容。Halide是一個實驗性的實現,並且性能一般,因此不建議使用。Intel推理引擎加速需要安裝OpenVINO庫,它可以實現在CPU、GPU和VPU上的加速,在GPU上內部會調用clDNN庫來做GPU上的加速,在CPU上內部會調用MKL-DNN來做CPU加速,而Movidius主要是在VPU上使用的專用庫來進行加速。

DNN模塊採用Backend和Target來管理各種加速方法。Backend分爲三種類型:第一種是OpenCV Backend,這是OpenCV默認的Backend;第二種是Halide Backend,第三種是推理引擎Backend。Target指的是最終的運算設備,它包括四種類型,分別是CPU設備、OpenCL設備、OpenCL_FP16設備以及MYRIAD設備。強調一下,OpenCL和OpenCL_FP16實際上都是GPU設備,OpenCL_FP16設備指的是權重值的數據格式爲16位浮點數,OpenCL設備指的是權重值的數據格式爲32位浮點數。MYRIAD設備是Movidius公司提供的VPU設備。我們通過Backend和Target的不同組合可以來決定具體的加速方法。舉個例子,如果你有Movidius的運算棒,則可以通過SetPreferobleBackend API將Backend設置成Inference-NEGINE,通過SetPreferobleTarget API將Target設置成MYRIAD,然後你的網絡運算將會在MYRIAD設備上進行,而不再用任何的CPU資源。

除了上述的加速後端外,DNN模塊還做了一些網絡層面的優化。由於在內部使用了統一的網絡表示,網絡層級的優化對DNN支持的所有格式的網絡模型都有好處。下面介紹兩種網絡層級的優化方法:

一)層融合

第一種優化方法是層融合的優化。它是通過對網絡結構的分析,把多個層合併到一起,從而降低網絡複雜度和減少運算量。下面舉幾個具體的例子:

如上圖所示,在本例中黃色方框代表的是最終被融合掉的網絡層,在這種情況下,卷積層後面的BatchNorm層、Scale層和RelU層都被合併到了卷積層當中。這樣一來,四個層運算最終變成了一個層運算,這種結構多出現在ResNet50的網絡架構當中。

如上圖所示,在本例中,網絡結構將卷積層1和Eltwise Layer和RelU Layer合併成一個卷積層,將卷積層2作爲第一個卷積層新增的一個輸入。這樣一來,原先的四個網絡層變成了兩個網絡層運算,這種結構也多出現於ResNet50的網絡架構當中。

如上圖所示,在本例中,這種網絡結構是把三個層的輸出通過連接層連接之後輸入到後續層,這種情況可以把中間的連接層直接去掉,將三個網絡層輸出直接接到第四層的輸入上面,這種網絡結構多出現SSD類型的網絡架構當中。

二)內存複用

第二種優化方式是內存複用的優化。深度神經網絡運算過程當中會佔用非常大量的內存資源,一部分是用來存儲權重值,另一部分是用來存儲中間層的運算結果。我們考慮到網絡運算是一層一層按順序進行的,因此後面的層可以複用前面的層分配的內存。

上圖是一個沒有經過優化的內存重用的運行時的存儲結構,紅色塊代表的是分配出來的內存,綠色塊代表的是一個引用內存,藍色箭頭代表的是引用方向。數據流是自下而上流動的,層的計算順序也是自下而上進行運算。每一層都會分配自己的輸出內存,這個輸出被後續層引用爲輸入。對內存複用也有兩種方法:

第一種內存複用的方法是輸入內存複用。如上圖所示,如果我們的層運算是一個in-place模式,那麼我們無須爲輸出分配內存,直接把輸出結果寫到輸入的內存當中即可。in-place模式指的是運算結果可以直接寫回到輸入而不影響其他位置的運算,如每個像素點做一次Scale的運算。類似於in-place模式的情況,就可以使用輸入內存複用的方式。

第二種內存複用的方法是後續層複用前面層的輸出。如上圖所示,在這個例子中,Layer3在運算時,Layer1和Layer2已經完成了運算。此時,Layer1的輸出內存已經空閒下來,因此,Layer3不需要再分配自己的內存,直接引用Layer1的輸出內存即可。由於深度神經網絡的層數可以非常多,這種複用情景會大量的出現,使用這種複用方式之後,網絡運算的內存佔用量會下降30%~70%。

接下來,我會爲大家介紹一下我們團隊在深度學習模塊中做的一些工作。

四, OpenCL加速

OpenCL的加速是一個內建的加速實現,它是可以直接使用而不依賴與外部加速庫的,只需安裝有OpenCL的運行時環境即可。此外,它還支持32位浮點數據格式和16位浮點數據格式。如果我們想要使用OpenCL加速,只需要把Backend設置成OpenCV,把Target設置成OpenCL或者OpenCL_FP16即可。

在OpenCL的加速方案中,我們提供了一組經過高度優化的卷積運算和auto-tuning方案,來爲特定的GPU和卷積運算找到最佳的卷積核。簡單地說,auto-tuning方案針對每個卷積任務,會選擇不同的子塊大小進行運算,然後選出用時最短的子塊大小來作爲卷積和的配置。DNN模塊中內置了一些已經設好的卷積和配置,用戶也可以爲自己的網絡和GPU重新運行一次auto-tuning,從而找到最佳的卷積核。如果想要設置auto-tuning,則需要設置環境變量OpenCV_OCL4DNN_CONFIG_PATH,讓它指向一個可寫的目錄。這樣一來,DNN模塊就會把最佳的卷積核配置存儲在這個目錄下。注意,如果打開了auto-tuing,那麼第一次運行某個網絡模型的時間就會比較長。

對於OpenCL的驅動,我們建議使用Neo。Neo是開源Intel GPU的OpenCL驅動,它支持Gen 8以及Gen 8之後的英特爾GPU。我們建議儘量使用最新的版本,根據我們的調試經驗,越新的版本性能越好。

最後,上圖是一個CPU和GPU加速的對比圖,其中一列是OpenCL的加速,其中另一列是C++的加速。CPU是i7-6770、8核、2.6G,GPU是Iris Pro Graphics 580的,這種CPU和GPU都算是比較強勁的配置。 我們可以看到,OpenCL加速之後的運算時間比CPU會短很多,但也不是所有的情況都是這樣的。對於不同的CPU,這個數據有所不同,大家可以通過上面的網站鏈接查看到在其他CPU配置下的CPU和GPU運算時間的對比。

五, Vulkan後端

Vulkan後端是由我開發的一個基於Vulkan Computer Shade的 DNN加速方案,目前已經合併到OpenCV的主分支,OpenCV 4.0裏就包含有Vulkan backend,感興趣的同學可以通過上圖的鏈接瞭解一下技術細節。

如果要使用Vulkan backend,將backend類型設置成VKCOM,將target設置成Vulkan即可。Vulkan後端可以讓DNN模塊在更多的平臺上使用到GPU的加速。例如,安卓系統中是不支持OpenCL的,但是它支持Vulkan,這種情況就可以通過Vulkan backend來加速。

六, 應用實例

最後一部分,這是一個通過DNN開發的用於對象檢測的端到端的應用,下面我會分部分來詳細講解這些代碼段。

在這裏使用的是Python的接口,採用Python語言來開發,模型使用的是MobileNetSSD模型。首先,引入OpenCV的Python包,代碼第2行、第4行、第5行則是指定MobileNetSSD的模型以及它的Graph描述文件。然後,設置輸入Image的大小爲300*300,置信度閾值設置爲0.5,第9行的均值是用來做圖像域處理的一個數值。第10行是可分類的類別,說明我們的MobileNETSSD是一個可以對20個類別進行分類的模型,我們也可以有97或者1000個類別的模型,但是那樣的模型會比較大。第16行則是打開一個Camera設備採集圖像。

從第19行到第26行就是所有的DNN相關的代碼段,可以看到使用起來是非常簡單的。第19行是加載網絡模型,並返回一個網絡對象。從第20行開始進入一個while循環,逐幀處理攝像頭讀入的數據。第22行是讀入的數據,第23行是對這個讀入的Image做Resize,讓它符合網絡模型對輸入數據的大小要求。第24行是調用DNN模塊的BlobFromImage API對輸入的Image做預處理,這裏主要是對輸入數據做規則化處理,即先減均值,再乘以一個Scale。這些都是MobileNETSSD網絡在訓練中引入的均值和Scale,在推理中也需要把它用作輸入Image的預處理,我們將處理好的數據稱爲blob。在第25行把這個blob設置爲網絡的輸入,第26行來調用網絡的Forward做推理預算,然後得到最終的輸出結果Detections,Detections記錄了在這一幀圖像中檢測出來的所有對象,並且每個對象會以一個Vector的形式來描述。

接下來,在這個循環中對每一個對象進行可視化處理,也就是把檢測出來的對象描繪在原圖像上。在第47行是取出對象的置信值與之前設置的閾值進行比較,如果超過了閾值,我們就判定它是一個可信的對象,將其繪製到原圖上面。接下來的代碼段就是繪製對象的代碼段以及繪製對象類別的代碼段,最後是將繪製好對象方框的原圖顯示出來,隨後整個程序結束。在OpenCV的代碼庫當中有許多基於DNN的示例程序,包括C++、Python,大家感興趣則可以在上面的鏈接中去看一下。

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