讓深度學習進入移動端,蘑菇街在移動端的深度學習優化實踐
原創【51CTO.com原創稿件】深度學習是機器學習中一種基于對數據進行表征學習的方法,與傳統靠手工設計特征的機器學習算法不同,深度學習能根據不同任務自動學習數據的特征。
目前深度學習在語音、圖像、視頻處理上已經取得了令人印象深刻的進步,但是它通常需要功能強大的電腦才可以運行,如果它出現在我們的手機上呢?
2017 年 12 月 1 日-2 日,由 51CTO 主辦的 WOTD 全球軟件開發技術峰會在深圳中州萬豪酒店隆重舉行。
本次峰會以軟件開發為主題,黃文波先生在軟件性能優化專場與來賓分享"深度學習在移動端的優化實踐"的主題演講,為大家詳細闡述深度學習模型在移動端的設計和優化策略等問題。
討論涉及到如下三個方面:
- 為什么做深度學習的優化
- 深度學習在移動端的優化實踐
- 總結
為什么做深度學習的優化?
深度學習近年來雖然特別火,但是由于計算量巨大,其對應的模型動輒就有上百兆。
要想把深度學習放在只能分配出幾十兆空間給單個 App 的手機上,我們就需要從算法層面上極致地縮小其模型。
這兩年來,深度學習的發展趨勢是將大部分應用都放置到云端,而使用的設備一般是 GPU。
但是如果真要實現 AI,單靠云端的算法是遠遠不夠的,因為在一些應用的場景中,計算必須在本地進行。
例如:蘋果的 Face ID,如果僅放在云端,那么一旦手機沒有了信號,用戶豈不是無法使用手機了?
同理,無人駕駛需要及時響應外部環境,包括識別車外的人、交通燈等,那么如果網絡發生了延遲,豈不是會發生交通事故?因此,許多應用都必須將計算放置在本地。
蘑菇街為什么會做深度學習的優化?主要原因有如下幾點:
- 服務器:我們通過減少訓練、預測的時間,來縮小模型。節約 GPU 資源和省電,這對于深度學習來說是非常重要的因素。
例如 Alpha GO 下一盤棋,需要 1920 個 CPU 到 280 多個 GPU,其耗費的電費約為 3000 美元。同樣,蘑菇街就算使用的是 GPU,其電費也有上萬元。
- 移動端:實時響應需求。通過本地化運行處理,我們不需要將圖片傳到服務器上,也不會侵犯用戶的隱私。
CNN(卷積神經網絡)基礎
深度學習對于圖像處理的理念是:經過層層濾波與篩選,最終得到結果。
它的典型流程為:輸入(INPUT)經過卷積層(CONV)和激活層(RELU)的特征提取,再經過池化層(Polling)的壓縮與降維,最后由全連接層(FC)連接所有的特征,并輸出圖像歸屬類型的概率。
如上圖所示左邊的圖片經過了多層處理后,最后得出它屬于 car 的概率最高,因此我們可以認為它是一輛車。
該示例只有 10 層左右,而在實際場景中,層數會更多,甚至能達到 100 多層。
對于上方簡易示圖的識別,實際上它經歷了 CNN 的成百上千萬次計算,才最終得到結果。
深度學習應用挑戰
深度學習領域的發展趨勢是:隨著其網絡日漸加深和大數據驅動所導致的數據激增,訓練結果的準確率也會越來越高。
伴隨著網絡越來越深,會出現一個問題:深度學習模型越來越大,用于計算所需耗費的資源也就越來越多。
與此同時,由于手機不像服務器可以使用性能強大的 GPU,因此手機的計算性能受到了限制。
另外,由于手機上 CPU 和電池容量暫時無法被突破,其功耗也會相應地受到限制。
深度學習在移動端的優化實踐
模型壓縮的兩類方式
將深度學習放置到手機上,可以從兩個方面入手:
- 模型壓縮,現有的模型一般具有 100M~200M,其準確率非常高。因此我們在拿到模型后,需要進行壓縮。
- 設計網絡,將網絡設計得非常小,同時保證網絡具有很強的表達能力。
模型壓縮
在算法層面對模型的壓縮主要采取了三種方式:
- 剪枝(Pruning)
- 量化(Quantization)
- 霍夫曼編碼(Huffman Encoding)
剪枝的方法較為直觀,它的思想是:在訓練神經網絡時,每個神經元會有一個權重,而權重具有大小之分。其中權重小的表示對最終結果的影響力非常小。
因此在 2015 年剛開始研究時,有人提出在不會影響到最終結果的情況下,將這些小的權重砍掉(Remove)了。
前面提到的“砍”權重的做法對于內存是很不友好的。例如卷積里有 3×3 的矩陣,如果僅部分被砍的話,實際計算還要去往被砍處,從而造成了內存的不連續。
因此有人提出將 Filter 一并砍掉,在將整個 3×3 全部砍掉之后再予以訓練,以達到較好的效果。
2017 年,有人提出通過對每個通道添加一個 scale 因子對網絡進行訓練,然后選擇把 scale 值比較小的卷積全部砍掉(如上圖中橙色處所示),以方便對內存進行高效地操作。
除了砍掉網絡,我們還可以通過量化來將其做得更小。如上圖所示的 3×3 權重,在實際存儲時權重都是浮點數(Float),而存儲每個浮點數都需要 32 比特位。
因此量化的思想是把這 9 個浮點值進行聚類,分別聚到四個類中,那么我們在存浮點時就只需要存這四個浮點數即可。
對于這些值的表達,我們可以通過 Index 來實現。由于此處已聚了四個類,我們在 Index 時,只需兩個便可以表達了,即 2 的 2 次方正好是 4,正好表達出了四個數。
想必大家在學習數據結構時都了解過霍夫曼編碼。它的思想是:由于部分權重的出現次數遠高于其他權重,因此對于出現次數較多的權重,我們可以用更少的比特位來編碼。
而對于出現次數較少的權重,則用較大的比特位來表達。那么該方法可以在總體 Index 為已知的角度,直接用固定的位數進行存儲,從而節約了空間。
模型壓縮的流程是:進行 channel 級別的權重剪枝→對權重進行量化→聚類到固定的幾個類中→將量化后的 Index 通過霍夫曼編碼進行進一步壓縮。
設計網絡
另一個壓縮的思路是設計小網絡,它主要有三種方式:
- SqueezeNet
- MobileNet
- ShuffleNet
SqueezeNet 的核心思想是在做下一個 3×3 的卷積時,先進行一個 1×1 的卷積,將以前的 64 維降到 16 維,然后在此 16 維的基礎上再進行 3×3 的卷積。
這樣就相當于在做 3×3 的卷積之處比原來降低了 4 倍,也就是將模型降低到原來的四分之一。
谷歌于 2017 年 3 月提出了一個較小的模型 MobileNets。它主要采取了 Group 的策略,核心思想是:不再與前面的所有層進行操作,只跟對應的上一層通道做卷積。
例如:以前的權重是 3×3×32,再乘以前面的 32 個通道,就是有兩個 32 相乘。
通過使用 Group,我們只需一個 32,因為后面乘的是 1(跟每一個通道相乘),此處比原來減少了 32 倍。
如果后面的通道數越多,如 512,則會比原來相應地減少 512 個,所以這是非常可觀的。
2017 年 7 月,Face++ 團隊提出了更進一步的做法 ShuffleNet。考慮到在通道大的時候,1×1 的卷積到 1024 或者 2048 的計算量也會很大,因此它將 1×1 的卷積也進行了 Group 操作。
在分組之后,我們單獨地進行 1×1 的卷積,并且隨即將順序打亂。這樣便可以把通道與通道之間的關系表達進去。
這個是我們在公開數據集 ImageNet 上的數據集實驗。我們將原大小為 98M 的模型,通過模型壓縮后降到了 49M,而在權重的量化之后,繼續減到了 15M。
在整個過程中,Top-1 從 75% 變成 72.4%,該降幅比較少,而業界一般準確率是在 75%~76%。可見這個實驗是比較可觀和可用的。
上圖的結果源自蘑菇街自己的實際數據。當前我們的數據樣本大致是 1200 萬,通過“剪枝”之后,Top-1 基本保持在 48%。
而 Top-5 降低了 1 個點,從 82.2% 降至 81.5%,但是模型的大小則從 86M 降到了 31M;同時 Inference Time 為 45 毫秒。這就意味著效率提升了一倍。
另一個嘗試是語意分割網絡。蘑菇街基于服裝的特點對人體的各個部位進行了“分割”,包括手、腳、鞋子、衣服、褲子等。該語義分割模型的基礎網絡為 MobileNet,最終模型只有 13M。
移動端優化實踐
在將模型做得足夠小之后,我們又是如何讓它跑在手機之上的呢?
在手機上做深度學習時,由于計算量非常大,我們不應該將訓練放在手機上,而是仍然交給 GPU 來實現。而在訓練完成之后,我們再將模型部署到手機端。
如今業界常用且好用的深度學習框架包括:
- Facebook 推出的 Caffe2,亞馬遜選用的 MXNet。不過我們試用下來發現,它們在手機上的實際性能表現卻不盡人意,對于一張圖的識別可能需要 8~9 秒。
- NCNN 是騰訊開源的框架,而 MDL 則是百度開源的移動端深度學習框架。
- CoreML 是蘋果在 2017 年 WWDC 上發布的在手機上的深度學習框架。
- Tensorflow Lite 是谷歌在 2017 年 I/O 大會上發布的開源產品。
那么對于一個網絡,我們是否非要將 Inference 與訓練網絡做得一樣呢?如今業界大部分框架的做法的確如此,例如 NCNN 和 MDL,它們都是直接把訓練好的網絡轉到手機上運行。
但是我們發現在訓練的時候,需要做一些梯度計算和反向傳播,而在 Inference 時,我們實際上并沒有必要做反向傳播。
上圖中是一個典型 CNN 網絡里的單個 Block(塊),從 Convolution 到 BN(BachNormalization)再到 Relu。
這三層在存儲時對于內存的需求非常大,實際上我們完全可以將它們合為一層,從而減少內存的使用,并加快速度。
在具體實現過程中,我們將 BN 放到 Convolution 里的轉變是不需要改動框架代碼的。但是如果要把 Relu 放入 Convolution,則需要修改此框架的源代碼。
優化卷積計算
由于深度學習在處理圖像時,大部分的計算都涉及到卷積,因此比較直觀的做法就是直接進行 3×3 Filter。
因為數據和圖像在內存里的存儲是連續的,從而導致了讀取時經常需要到各處跳轉,這造成了指針跨度巨大,極大降低了 cache 命中率。
所以大部分的卷積算法優化都采取將 Filter 乘法轉化成傳統的矩陣乘法。如上圖右側所示,原來 7x7 矩陣和 3x3 矩陣分別被轉化成了 25×9 的矩陣和 9×1 矩陣。
我們通過直接對大型矩陣進行乘法操作,便可得到結果,且該結果跟原來是一模一樣的。這也是目前許多針對矩陣運算的加速庫所普遍采用的優化方式。
2017 年 MEC 算法被推出,由于原來 7×7 矩陣的轉化率 25×9 中存在著冗余和復制,該算法把它變換成為 5×21。就數量級而言,該 5×21 比 25×9 降低約一倍的內存,其性能更為直觀。
浮點運算定點化
由于深度學習模型的權重和特征圖的值是浮點數,而計算機對于浮點的運算能力遠不及定點的運算,例如:計算 3+2 和 3.0+2.0 的速度肯定是不一樣的,因此我們需要將傳過來的浮點數先給轉化成定點數。
例如:如果權重的大部分都是 0.1 或 0.2 的話,那么我們通過求最小、最大值的方式將其映射到了 0 到 2 上,而 0 到 2 正好是一個字節,因此一個 8 位就能夠予以表達了。
如此,我們在計算 Convolution 矩陣相乘時,完全可以直接使用典型的矩陣來進行計算,其速度會比使用浮點數計算快很多。
當然,在計算完成之后,我們還需將結果轉換成浮點數予以輸出。
除了上述提到的優化卷積的核心方法,我們還能怎么進化呢?
- 再牛的核心算法,都不如硬件實現來得直接。此處主要是針對蘋果產品,蘋果在做圖像識別時使用的就是自己開發的帶有卷積乘法的 GPU 硬件。
我們在二次開發時可以直接調用它提供的基礎卷積操作,而不必使用任何前面提到的算法。
- 另外,前面提到的許多框架都是通用的優化算法。但是在實際深度學習中,我們根本不需要那么多具有通用性的卷積。
例如:剛才列出的很多網絡,要么是 1×1 的卷積,要么是 3×3 的卷積,基本上不會出現 2×2 的卷積。
因此我們只需要使用 3×3 的卷積優化便可。正如騰訊 NCNN 所采用的特定卷積策略,僅優化 3×3 和 1×1 的卷積。我們同樣可以不必考慮其他的矩陣相乘方式,如此便可提高實現速度。
通過深入分析,我們發現:騰訊與百度在安卓上的效果差不多。如前所述,由于騰訊針對 3×3 和 1×1 優化采取的是特定卷積,而百度采取的是通用做法,所以后者更耗內存。
當然兩者性能都在 200 毫秒左右,而對于開源的 Tensorflow Lite,由于它將浮點型轉為整型進行運算,其性能會比上述兩者更快,只需 85 毫秒,基本可以滿足實時性的要求。
針對深度學習,蘋果于 2017 年發布了 CoreML。它在網上被炒得特別火,其框架如上圖所示,最下面被分化出了負責加速的一層 BNNS,它是用 C 語言寫的機器學習庫。
旁邊的 Metal Performance Shaders 屬于蘋果自己的硬件,它封裝好了與機器學習相關的底層 API。
二次開發人員可以在 CoreML 的底層基礎上,進行適當的應用添加。不過我們并沒有采用該 CoreML,原因如下:
- 由于蘋果比較封閉,它只能提供現成的框架和既定的模型。而計算機視覺的算法開發領域發展速度非常快,我們經常需要開發出一些新的層(layer)。因此 CoreML 無法滿足我們的算法要求。
- CoreML 的庫需要調用最新的 iOS 包,而許多蘋果手機的 iOS 版本并未升級到 iOS11 以上。
所以在 iOS 上,我們是在 MPSCNN 層實現計算卷積的,好處在于:
- Metal 的機制充分利用了 GPU 資源,而在 iOS 上不會搶占 CPU 資源。
- 運用蘋果自己的 Metal 語言去開發新的一層會非常的方便。
同時需要注意如下兩點:
- Metal 實現的是 16 位 Float 數的計算,并非 32 位,因此屬于半精度。
- 其權重的格式是 NHWC。
有人可能會質疑半精度的計算準確率,然而,由于深度學習有著非常強的泛化能力,就算減少計算精度,受到的影響基本上也并不大,同樣可以完成任務。
上圖展示的是蘋果 MPSCNN 的設計思想。不同于其他常見框架的組織結構:它對權重以 4 的整數倍通道數去進行存儲。
例如:有 9 個通道需要用 3 個 Slices 時,那么到了最后一次 Slice,就只需存儲一個通道并空閑另外三個通道,以預留空間。因此,理解了這個核心點將有助于我們加快開發的進程。
如上圖所示,如果你想用 Metal 來開發新的一層,而且已經有了一定的算法基礎,那么上述幾行代碼就夠了。
根據上圖的 NCNN 與蘋果 MPSCNN 對比可知,運用開源框架的 CPU 耗時為 110 毫秒,而蘋果要少于一半,只要 45 毫秒。可見蘋果的效果確實不錯。
就自行搭建深度學習框架而言,我們需要注意如下的策略方面:
- 優化 Inference 網絡結構。請牢記 Inference 網絡與 Training 的不同之處。如前所述,通過將傳統的三層合并為一層,我們能夠大幅降低開銷。
- GPU 加速。由于蘋果使用 Metal 進行封閉存儲,因此對于 GPU 的加速在 iOS 上做得比較好。而其他非 iOS 的安卓生態,目前尚無較好的 GPU 加速硬件。
- 指令加速。如今 99% 以上的安卓手機里都是使用的 ARM 芯片,該芯片能夠提供一些統一的指令集,以供我們實現底層的加速。
- 鑒于 CPU 普遍為多核的特點,我們也可以采取多線程的方式進行加速。
- 采取內存布局優化,將傳統的 NCHW(N:number、C:channel、H:height、W:width)多維方式中的 channel 維度放到最后,變成 NHWC 以提高速度。
- 將浮點運算轉到定點化,以提升計算速度。
基于 NCNN 的工具包框架
Mogu Deep Learning Toolkit 是蘑菇街于 2016 開發的僅供公司內部使用的深度學習工具包。
由于各層都被做得十分專業極致,其高內聚低耦合的特點在網絡設計上顯得非常靈活,對于專業人士來說也比較好用。
該工具包的設計思想為:在優化掉 Training 網絡的基礎上,我們在手機上為從網絡進來的圖片創建一個網絡模型→對它進行初始化→通過前端傳播進行Inference→針對具體的 Task,對傳播的結果進行諸如 Classification、Detection、Segmentation 等操作→獲取結果或與其他業務相結合。
上圖是利用該工具包開發的一個簡單案例,是用 C++ 實現的。這是 MobileNet 的一個 Class,我們將全部各層都放入了 Private 中,它只有一個對外的接口,通過初始化便可拿到其結果。
這里輸入的是一張圖片,最后 Output 的是該圖片的識別結果。
深度學習優化在業務中的嘗試
上圖是我們做的一些實戰:
- 左圖是用 ImageNet 識別圖片,它給出了排名第一的可能性是顯示屏,而排名第二的是筆記本。
- 右圖通過語義分割,我們使用 MobileNet 作為特征網絡,訓練出一個可以分割的網絡。
通過對圖中人物的分割,我們區分出了頭發、衣服、包、鞋子、腿、手等不同部分,以供進一步進行分析。
其消耗時間也相當可觀,大概在 40 毫秒左右。
上圖左側展示了我們的另一個訓練模型--識別領形。通過圖像識別的方法,我們分辨出該T恤是圓領還是 v 領、是長袖還是無袖。根據其下方的判斷,它是圓領的概率為 66.7%。
上圖右側展示的是我們公司內部的通訊工具。它被安裝在手機上,并在本地運行,能夠根據深度學習的結果,執行圖片分類。
總結
要想把深度學習做到移動端上,一定要將算法與工程相結合。
黃文波,蘑菇街圖像算法工程師,主要從事深度學習相關工作,包括模型加速壓縮、GAN、藝術風格轉換以及人臉相關應用,尤其對深度學習在移動端的優化有較深入的研究。
【51CTO原創稿件,合作站點轉載請注明原文作者和出處為51CTO.com】