個人模型能跑多快——神經網絡模型速度調研(一)

前言

對於神經網絡,咱們更多談的是其精度怎麼樣,有百分之零點幾的提高。可是若是談到速度的話,深度學習神經網絡相比於傳統的算法來講,速度恐怕沒有那麼快了。html

那麼咱們何時須要提高速度呢?假若有如下的場景:git

  • 將模型運行在手機上
  • 須要實時的場景,好比高速攝像機捕捉動做
  • 在嵌入式設備上運行

對於有桌面級顯卡這種利器來講,速度彷佛很容易獲得很快,可是放到以上這些設備時,在有效的硬件上若是速度提高不上來,那麼所設計的算法也就沒什麼用處了。算法

《個人模型能跑多快——神經網絡模型速度調研(一)》

所謂提高速度,不談論硬件級別的優化,對於神經網絡來講無非也就兩點:編程

  • 網絡的設計
  • 輸入數據的大小

輸入數據大小咱們姑且不談,而神經網絡的設計這一點就顯得比較重要了,網絡的設計能夠細分爲:網絡模型權重的大小、網絡運行過程當中產生的中間變量的大小、網絡設計中各類計算的執行速度等等這些都會對速度產生影響,通常來講,模型參數和模型速度是成正比的。bash

關於速度和精度來講,這每每是一個衡量,精度和速度通常沒法兼顧,正如在工業界使用很火的YOLO和在學術界名聲遠揚的Mask-Rcnn,一個追求速度一個追求精度(固然速度的前提是精度在可接受範圍以內)。網絡

《個人模型能跑多快——神經網絡模型速度調研(一)》

運算量

接觸過ACM的童鞋必定知道時間複雜度空間複雜度這兩個概念,時間複雜度即衡量一個算法運行的時間量級,而空間負責度則衡量一個算法所佔用的空間大小。神經網絡則相似,如何判斷一個網絡的速度快不快,最直觀最直接地就是看其包含多少個浮點運算(固然與內存帶寬也有關係)。ide

與這個概念密切相關的就是FLOPS(Floating-point operations per second,每秒執行的浮點運算數)。如今的桌面級顯卡,大部分都是TFLOPs級別了,1TFLOP也就是每秒執行1,000,000,000,000次浮點運算。函數

矩陣乘法

在神經網絡中,最多見的就是矩陣乘法:性能

正以下方的輸入4×4的圖像,卷積核爲3×3,輸出爲2×2:學習

《個人模型能跑多快——神經網絡模型速度調研(一)》

在計算機中將上述運算分解爲:

《個人模型能跑多快——神經網絡模型速度調研(一)》

結果中一個標量的計算過程能夠用公式表示爲:

y = w[0]*x[0] + w[1]*x[1] + w[2]*x[2] + ... + w[n-1]*x[n-1]
複製代碼

w和x都是向量,w是權重,x是輸入。最終的結果是y是一個標量(scalar)。這個運算稱做multipy-accumulate operations。而其中的一個操做w[0]*x[0] + ..(先乘後加)稱爲一個MACC(multipy-accumulate operation)。而上面的計算一共包含n個MACCs。

簡而言之,兩個n維向量的點乘所須要n個MACCs運算(其實能夠說是n-1個,由於第一個不算,可是咱們近似地認爲是n個)。而n個MACCs運算包括2n-1個FLOPs(n個乘法和n-1個加法),咱們近似爲2n個FLOPs。

也就是說,兩個n維向量的乘積所須要的FLOPs是2n個。

固然,在不少的硬件設施中(好比顯卡),一個MACC就能夠稱做一個運算單位了,而不是將加法和乘法分開,由於硬件已經對其進行了大量的優化,咱們以後在測一個卷積運算量就能夠按照MACC這樣的單位來計算了。

全鏈接層

全鏈接層是除了卷積層最多見的層,在全鏈接層中,輸入數量爲I和輸出數量爲O,這些節點一一相連,而後權重W保存在I x J的矩陣中,因而對於一個全鏈接層來講,其計算量爲:

y = matmul(x,W) + b
複製代碼

《個人模型能跑多快——神經網絡模型速度調研(一)》
(來自:leonardoaraujosantos.gitbooks.io/artificial-…)

在上面的這個式子中(結合上圖),咱們的x維數I爲3,x是一個3維的向量,輸出y是二維的向量,所以權重W的數量就是3 x 2,最後加上偏置量b

那咱們要計算全鏈接層一共執行了幾個MACC,首先看全鏈接層中的運算matmul。這是一個矩陣運算。

矩陣運算說白了就是一堆乘法和加法的集合,咱們輸入的維度是I輸出維度是O,其次中間的W的維度爲I x O(在上圖中是3x2)那麼很簡單,咱們一共要作的就是I x O個MACCs,能夠發現和權重矩陣的數量是同樣的。

哦,還有個bias偏置b沒有算裏頭,這個其實能夠忽略不計了,在平時的計算量中這個偏置直接就不算了。

咱們在看一些全鏈接層計算公式的時候,可能會發現計算中將偏置移到了矩陣中而不是先矩陣運算完再加偏置向量。也就是執行了一個 (I + 1) x O的矩陣運算,這個就是爲了計算步驟簡便一些,對計算量沒有任何影響。

也就是說,加入咱們的全鏈接層有100個輸入,200個輸出,那麼一共執行了100 x 200 = 20,000個MACCs。

一般,輸入I向量輸出O向量的時候,執行了I x J個MACCs和(2I - 1) x J個FLOPs。

全鏈接層就是向量以前的運算,一般會將全鏈接層放在卷積層的後面,而咱們在編程計算這些值的時候都要對卷積後的值進行Flatten操做,相比你們應該很熟悉了,Flatten就是將一個(N,C,H,W)的張量變形爲(N,I)的形狀,從而去執行全鏈接運算。

激活函數

一般咱們會在卷積層或者全鏈接層以後加一個非線性的激活函數,好比RELU或者Sigmoid。在這裏咱們使用FLOPs去衡量其計算量,由於激活函數不涉及到點乘操做,因此用不到MACCs

對於RELU來講:

y = max(x, 0)
複製代碼

x爲輸入,這裏的輸入就是其餘層的輸出,假如其它層傳遞給RELU層n個向量,那麼RELU層對這n個向量進行計算,也就是n個FLOPs。

對於sigmoid來講:

y = 1 / (1 + exp(-x))
複製代碼

上式包含了一個加法、一個減法、一個除法和一個取冪運算,咱們將這些運算都歸結爲一個單獨的FLOP(還有乘法、求根號等)。所以一個sigmoid的運算量爲4個FLOPs。假如輸入時n那個計算量爲4 x n個FLOPs。

但通常咱們只關心比較大的矩陣運算,像這種計算量通常也就忽略了。

卷積層

卷積層中主要的處理對象不是以前提到的向量,而是咱們日常見到的(C,H,W)三通道的張量,其中C表明通道數,HW表明這個特徵圖的高和寬。

對於一個kernel爲K的卷積層來講(這裏只說方形的卷積層,咱們平時用到的也都是方形的卷積),所須要的MACCs爲:

K  x  K  x  Cin  x  Hout  x  Wout  x  Cout
複製代碼

怎麼來的:

  • 輸出的特徵圖大小爲Hout x Wout,由計算中的每一個像素點組成
  • 權重(weights)和輸入特徵圖的計算的窗口大小爲K x K
  • 輸入特徵圖的通道數爲Cin
  • 對應每個通道的卷積產生的通道數爲Cout

這裏忽略了偏置,一般咱們在計算參數時會算上偏置,可是在計算FLOPs則直接忽略。

舉個例子,假如輸入三通道256*256的圖像,使用的卷積核大小爲3,卷積的層數爲128,計算總共的運算量爲:

256 x 256 x 3 x 3 x 3 x 128 = 226,492,416 
複製代碼

差很少226M-FLOPs,計算量仍是蠻大的。

以上使用的stride爲1,也就是每隔一步在特徵圖上進行卷積操做,若是上述的卷積層的strid爲2,那麼至關於在一半大小的圖像中進行卷積,上面的256×256則變成128×128

深度可分離卷積結構

深度可分離的卷積構架是衆多高效網絡的基本結構,例如MobileNetXception。都採用了depthwise-separable convolution的網絡結構,該網絡結構並不複雜,能夠分爲兩個部分:

《個人模型能跑多快——神經網絡模型速度調研(一)》

(來源於 machinethink.net/blog/mobile… )

須要注意下,下文中的深度可分離卷積對應是Depthwise Separable Convolution,它分別兩個部分,分別是深度分離(depthwise)的卷積和點(pointwise)卷積(也就是所謂的1×1卷積)。

其中深度分離的卷積運算和普通的運算相似,只不過再也不將三個通道(RGB)變成一個通道了(普通卷積核通常是對圖像的三通道分別進行卷積再相加化爲一個通道),此次是直接三個通道輸入三個通道輸出,也就是對應三個獨立參數,不一樣參數內容的卷積,每個卷積覈對應一個通道(輸入一個通道,輸出一個通道)。

有一個稱之爲 depthwise channel multiplier 的概念,也就是深度分離通道放大器,若是這個放大器大於1,好比爲5,那麼一個卷積核就至關於輸入一個通道輸出5個通道了,這個參數就是調整模型大小的一個超參數。

執行的運算次數爲:

K x K x C X Hout X Wout

注意相比以前普通的卷積運算能夠說少乘了個C,運算量能夠說是大大提高了。

舉個例子,好比利用3x3的深度可分離卷積去對一張112 x 112的特徵圖作卷積操做,通道爲64,那麼咱們所須要的MACCs爲:

3 x 3 x 64 x 112 x 112 = 7,225,344
複製代碼

對於點(pointwise)卷積運算來講,須要的運算量爲:

Cin X Hout X Wout X Cout
複製代碼

這裏的K,核大小爲1。

一樣舉個例子,假如咱們有個112x112x64維數的數據,咱們利用點分離卷積將其投射到128維中,去建立一個112x112x128維數的數據,這時咱們須要的MACCs爲:

64 x 112 x 112 x 128 = 102,760,448
複製代碼

能夠看到點分離運算所須要的運算量還大於深度分離運算。

咱們將上述兩個運算加起來和普通的3x3卷積操做運算相比:

3×3 depthwise          : 7,225,344
1×1 pointwise          : 102,760,448
depthwise separable    : 109,985,792 MACCs

regular 3×3 convolution: 924,844,032 MACCs
複製代碼

能夠發現速度提高了8倍(8.4)多~

可是這樣比較有點不是很公平,由於普通的3x3卷積學習到的信息更加完整,能夠學習到更多的信息,可是咱們要知道在同等的計算量下,相比傳統的3x3卷積,咱們可使用8個多的深度可分離卷積,這樣比下來差距就顯現出來了。

關於模型中的參數量計算請看這篇文章:淺談深度學習:如何計算模型以及中間變量的顯存佔用大小

咱們整理一下,深度可分離具體須要的MACCs爲:

(K x K x Cin X Hout X Wout) + (Cin x Hout x Wout x Cout)
複製代碼

簡化爲:

Cin x Hout x Wout X (K x K + Cout)
複製代碼

若是咱們將其跟普通的3x3卷積對比的話就會發現,上式最後的+ Cout在普通的3x3卷積中爲x Cout。就這個小小的差異形成了性能上極大的差別。

深度可分離卷積核傳統的卷積的提速比例能夠認爲爲K x K(也就是卷積越大,提速越快),上面咱們按照3x3卷積舉例發現提速8.4倍,其實和3x3=9倍是相差無幾的。

其實實際上的提速比例是:K x K x Cout / (K x K + Cout)
另外須要注意的是,深度可分離卷積也能夠像傳統卷積同樣,使用stride大於1,當這個時候深度可分離卷積的第一部分輸出的特徵大小會降低,而深度可分離的第二部分點卷積則保持輸入卷積的維度。

上面介紹的深度可分離卷積是MobileNet V1中的經典結構,在MobileNet V2中,這個結構稍微變化了一下下,具體來講就是多了一個擴張和縮小的部分:

  • 第一個部分是1×1卷積,這個卷積用來在輸入特徵圖像上添加更多的通道(這個能夠理解爲擴張層-expansion_layer)
  • 第二個部分就是已經提到的3×3深度分離卷積(depthwise)
  • 第三部分又是一個1×1卷積,這個卷積用來減小輸入特徵圖像上的通道(這個稱之爲投射層-projection_layer,也就是所謂的瓶頸層-bottleneck convolution)

再討論下上面這個結構的計算數量:

Cexp = (Cin × expansion_factor)

expansion_layer = Cin × Hin × Win × Cexp

depthwise_layer = K × K × Cexp × Hout × Wout

projection_layer = Cexp × Hout × Wout × Cout
複製代碼

上式中的Cexp表明擴張層擴張後的層數,雖然不管是擴張層仍是瓶頸層都不會改變特徵圖的H和W,可是其中的深度分離層若是stride大於1的話會發生改變,因此這裏的Hin WinHout Wout有時候會不一樣。

將上式進行簡化:

Cin x Hin X Win X Cexp + (K x K + Cout) x Cexp x Hout x Wout
複製代碼

當stride=1的時候,上式簡化爲:(K x K + Cout + Cin) x Cexp x Hout x Wout

和以前MobileNet V1版的深度可分離卷積對比一下,咱們一樣使用112x112x64做爲輸入,取擴張參數(expansion_factor)爲6,3x3的深度分離卷積的stride爲1,這時V2版的計算量爲:

(3 × 3 + 128 + 64) × (64 × 6) × 112 × 112 = 968,196,096
複製代碼

能夠發現,這個計算量貌似比以前的V1版大了不少,並且比普通的3x3卷積都大了很多,爲何,緣由很簡單,咱們設置了擴張係數爲6,這樣的話咱們計算了64 x 6 = 384個通道,比以前的64 -> 128學習到更多的參數,可是計算量卻差很少。

批標準化-BatchNorm

批標準化能夠說是現代神經網絡中除了卷積操做以外必不可少的操做了,批標準化一般是放在卷積層或者全鏈接層以後,激活函數以前。對於上一個層中輸出的y來講,批標準化採起的操做爲:

z = gamma * (y - mean) / sqrt(variance + epsilon) + beta
複製代碼

首先將上一次輸出的y進行標準化(減去其平均值並處以方差,這裏的epsilon是0.001從而避免計算問題)。可是咱們又將標準化後的數於gamma相乘,加上beta。這兩個參數是可學習的。

也就是對於每一個通道來講,咱們須要的參數爲4個,也就是對於C個通道,批標準化須要學習C x 4個參數。

看來貌似須要計算的參數還很多,可是實際中咱們還能夠對其進行優化,將批標準化和卷積或者全鏈接層合併起來,這樣的話速度會進一步提高,這裏暫時先不討論。

總之,咱們在討論模型計算量的時候,通常不討論批標準化產生的計算量,由於咱們在inference的時候並不使用它。

其餘層

除了上述的一些基本層以外(卷積,全鏈接,特殊卷積,批標準化),池化層也會產生一部分計算量,可是相比卷積層和全鏈接層池化層產生的也能夠忽略不計了,並且在新型的神經網絡的設計中,池化層能夠經過卷積層進行代替,因此咱們通常來講對這些層並不着重討論。

下一步

這篇文章僅僅是討論了一些模型計算量的問題,一個網絡運行的快否,與不只與網絡的計算量有關,網絡的大小、網絡參數精度的高低、中間變量的優化以及混合精度等等均可以做爲提速的一部分,限於篇幅將在下一部分進行討論。

文章來源於OLDPAN博客,歡迎來訪:Oldpan博客

歡迎關注Oldpan博客公衆號,持續醞釀深度學習質量文:

相關文章
相關標籤/搜索