模型性能估計(計算量篇)

模型性能估計(計算量篇)

來自專欄移動與嵌入式深度學習11 人贊了文章

微信正文鏈接:mp.weixin.qq.com/s/nknW

微信號:deep_intelligence

在移動設備上進行深度學習時,模型預測的好壞並不是唯一的考慮因素。你還需要擔心:

· 模型在應用程序包中佔用的空間量- 單個模型有可能為應用程序的下載大小增加100個MB

· 它在運行時佔用的內存量- 在iPhone和iPad上,GPU可以使用設備中的所有RAM,但這仍然只有幾GB,當你的可用內存耗盡時,應用程序會被操作系統終止

· 模型運行的速度有多 - 特別是在使用實時視頻或大圖像時(如果模型需要幾秒鐘來處理單個圖像,那麼使用雲服務可能會更好)

· 它有多快耗盡電池 - 或使設備太熱而無法運行!

學術論文的作者通常不擔心這些事情。他們可以在桌面GPU或計算群集上運行他們的模型。但是如果你有興趣將這種模型轉換為在移動設備上運行,你需要知道模型在目標設備上的速度以及它使用的電池功率。

測量模型速度的最佳方法是連續多次運行並獲取平均經過的時間。任何單次運行測量的時間可能具有相當大的誤差 - CPU或GPU可能正在忙於執行其他任務(例如,繪製屏幕) - 但是當您對多次運行進行平均時,這將顯著縮小誤差。

由於訓練費用昂貴,因此開始訓練之前,對模型的完成程度有一定的理論洞察力是非常必要的。

以MobileNet V1 和 MobileNetV2為例:

MobileNetV2層替換了他們模型中的MobileNetV1層。V2使用的計算量比V1少得多,因此您認為此更改會使模型更快(模型中有許多其他層,但這些層沒有更改)。

對於V2的層,使用深度乘數1.4,這為每一層增加了更多的過濾器,但這仍然導致網路參數比V1少。即便如此,我還是預感到,特定配置的V2層不會比原始V1的層快得多。

事實證明我的預感是正確的 - V2模型實際上更慢!在這篇博文中將展示為什麼會這樣,以及如何計算這些值。

計算量

了解模型速度的一種方法是簡單計算它執行的計算量。我們通常將其視為FLOPS,每秒浮點運算。稍有不同的其他度量是MACC或乘法累加運算,也稱為MADD。

注意:在繼續之前,我必須指出,計算出自己的計算次數並不能告訴你需要知道的一切。計算計算次數只是為了大致了解模型的計算成本,但其他因素(如內存帶寬)通常更為重要(我們稍後會詳細介紹)。

點乘

為什麼要多累積?神經網路中的許多計算都是點乘,例如:

y = w[0]*x[0] + w[1]*x[1] + w[2]*x[2] + ... + w[n-1]*x[n-1]

這裡,w和x是兩個矢量,結果y是一個標量(單數)。

現代神經網路中的兩種主要類型的層——在卷積層或全連接層中, w是層的學習權重,x是該層的輸入。

y是該層的輸出之一。通常,層將具有多個輸出,因此我們計算了許多這些點積。我們把w[0]*x[0] + ...當作一個乘法累加或1個MACC。這裡的「累積」操作是加法,因為我們把所有乘法的結果相加。上述公式包含n個這樣的MACC。因此,兩個大小為n的矢量之間的點積需要n個MACC。

注意:從技術上講,上述公式中只有n-1個MACC,比乘法數少一個。將MACC的數量視為近似值,就像Big-O表示法是演算法複雜度的近似值一樣。

就FLOPS而言,點積產生2n - 1個FLOPS,因為存在n次乘法和n - 1次加法,。因此一個MACC大約相當於兩個FLOPS,儘管乘法累加是如此常見以至於許多硬體可以進行合成乘法 - 加法運算,這樣MACC是單個指令。

現在讓我們看一些不同的層類型,以了解如何計算這些層的MACC數量。

全連接層

在全連接層中,所有輸入都連接到所有輸出。對於具有I個輸入值和J個輸出值的層,其權重W可以存儲在I × J矩陣中。全連接層執行的計算是:

y = matmul(x, W) + b

這裡,x是I個輸入值的矢量,W是包含層權重的IXJ矩陣,b是包含J個元素的偏置值矢量。結果y包含由層計算的輸出值,也是大小為J的向量。

為了計算MACC的數量,我們來看看點乘的發生位置。全連接層是矩陣相乘matmul(x, W)。

矩陣乘法只包含一大堆的點積運算。每個點積都在輸入x和矩陣W的一列間發生。兩者都有個I元素,因此這算作I個MACC。我們必須計算J個這樣的點積,因此MACC的總數I × J與權重矩陣的大小相同。

偏置b並不會真正影響MACC的數量。回想一下,無論如何,點積比加法少一個,所以添加這個偏置值只會在最後的乘法累加中被吸收。

示例:具有300個輸入神經元和100個輸出神經元的全連接層執行300 × 100 = 30,000個MACC。

注意:有時,全連接層的公式是在沒有明確偏置值的情況下編寫的。在這種情況下,偏置向量作為一行添加到權重矩陣中,所以是(I + 1) × J,但這實際上更像是一種數學簡化 - 我認為這種操作並不像真實軟體那樣實現。在任何情況下,它只會增加J個額外的乘法,所以無論如何MACC的數量都不會受到太大影響。記住這是一個近似值。

通常,將長度I的向量與I × J矩陣相乘以得到長度為J的向量,需要I × J個MACC或(2I - 1) × J個FLOPS。

如果全連接層直接跟隨卷積層,則其輸入大小可能不會被指定為單個矢量長度I,但是可能被指定為具有諸如形狀(512, 7, 7)的特徵圖。例如Keras要求你先將這個輸入「壓扁」成一個向量,這樣就可以得到I = 512×7×7個輸入。

注意:在所有這些計算中,我假設批量大小為1.如果您想知道批量大小為B的MACC,那麼只需將結果乘以B。

激活函數

通常,全連接層之後是非線性激活函數,例如ReLU或sigmoid。當然,計算這些激活函數需要時間。我們不使用MACC測量它而是用FLOPS,因為它們不是點積。

某些激活函數比其他函數更難計算。例如,ReLU:

y = max(x, 0)

這是GPU上的單個操作。激活僅應用於層的輸出。在具有J個輸出神經元的全連接層上,ReLU使用J個這樣的計算,所以有J個FLOPS。

Sigmoid激活成本更高,因為它涉及一個指數:

y = 1 / (1 + exp(-x))

在計算FLOPS時,我們通常將加法,減法,乘法,除法,取冪,平方根等計為單個FLOP。由於sigmoid函數中有四個不同的操作,因此每個輸出計為4 FLOPS或總層輸出計為J × 4個FLOPS 。

實際上通常計算這些操作,因為它們只佔總時間的一小部分。我們最感興趣的是(大)矩陣乘法和點積,我們只是假設激活函數是免費的。

總結:不需要擔憂激活函數。

卷積層

卷積層的輸入和輸出不是矢量,而是三維特徵圖H × W × C,其中H是特徵圖的高度,W寬度和C是通道數。

今天使用的大多數卷積層都是方形內核。對於具有內核大小K的卷積層,MACC的數量為:

K × K × Cin × Hout × Wout × Cout

以下是該公式的來源:

· 對於輸出特徵圖中的每個像素,它的大小為Hout × Wout,

· 權重和K × K窗口的點乘

· 我們對所有輸入通道 Cin都做這樣的操作

· 並且因為該層具有Cout個不同的卷積內核,所以我們重複做Cout次以創建所有輸出通道。

同樣,我們在這裡為了方便忽略了偏置和激活。

我們不應該忽略的是層的stride,以及任何dilation因子,padding等。這就是為什麼我們需要參看層的輸出特徵圖的尺寸Hout × Wout,因它考慮到了stride等因素。

示例:對於3×3,128個filter的卷積,在112×112帶有64個通道的輸入特徵圖上,我們執行多個MACC:

3 × 3 × 64 × 112 × 112 × 128 = 924,844,032

這幾乎是10 次累積運算!使得GPU忙於計算......

注意:在此示例中,我們使用「same」填充和stride = 1,以便輸出特徵圖與輸入特徵圖具有相同的大小。通常看到卷積層使用stride = 2,這會將輸出特徵圖大小減少一半,在上面的計算中,我們將使用56 × 56而不是112 × 112。

可深度分離的卷積(Depthwise-separable convolution)

進深度可分離卷積是一個普通的卷積的分解成兩個較小的操作。它們總共佔用更少的內存(更少的權重)並且速度更快。當然,這只是近似於「完整」卷積層可以做的事情,因此您可能實際上需要更多這樣的運算來獲得相當於模型中原常規卷積層相同的表現力,但即便需要更多層,您這樣做仍然更快。

這些層在移動設備上運行良好,是MobileNet的基礎,也是Xception等大型模型的基礎

第一個運算是深度卷積depthwise convolution(深度卷積)。這在很多方面類似於常規卷積,除了我們沒有組合輸入通道。總是有相同數量的輸入通道和輸出通道。

深度卷積的MACC總數為:

K × K × C × Hout × Wout

這可以減少C倍工作量,使其比常規卷積層有效率。

示例:在112×112特徵圖上使用輸入通道為64的3×3核進行深度卷積,MACC:

3 × 3 × 64 × 112 × 112 = 7,225,344

請注意,此卷積始終具有與輸入通道一樣多的濾波器,並且每個濾波器僅應用於單個通道。這就是上述計算中沒有× 128的原因。

注意:有一個稱為「深度通道乘數(depthwise channel multiplier)」的東西。如果此乘數大於1,則每個輸入通道有D個輸出通道。因此,不是每個通道只有一個過濾器,現在每個通道都有D個過濾器。但深度乘數(depthwise multiplier)在實踐中應用很少。

僅僅深度卷積是不夠的,我們還需要添加「可分離(separable)」。第二個運算是常規卷積,但始終使用內核大小1×1,也稱為「逐點(pointwise)」卷積。

對於這個逐點卷積層,MACC的數量是:

Cin × Hout × Wout × Cout

因為K = 1。

示例:讓我們從深度卷積中獲取具有112×112×64特徵圖的輸出,並將其投影到128維中以創建新的112×112×128特徵圖。那麼MACC:

64 × 112 × 112 × 128 = 102,760,448

如您所見,逐點卷積比深度卷積貴很多倍。但是,如果我們將它們組合在一起,則MACC的總數遠少於常規的3×3卷積:

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.4倍!

現在,比較這兩種層有點不公平,因為常規的3×3卷積更具表現力:它可以計算更多感興趣的東西。但是以相同的成本,你可以使用8倍以上這樣的深度可分離層,或者給它們有更多的過濾器。

深度可分層的總MACC是:

(K × K × Cin × Hout × Wout) + (Cin × Hout × Wout × Cout)

這簡化為:

Cin × Hout × Wout × (K × K + Cout)

如果將其與常規卷積層的公式進行比較,您會發現唯一的區別是我們最初× Cout在此處所做的事情+ Cout。做加法而不是乘法會產生很大的影響......

作為一個快速的經驗法則,使用深度可分層幾乎是比常規轉換層的成本少K×K倍。在上面的例子中,它是8.4,實際上幾乎相當於K × K = 3 × 3 = 9。

注意:確切倍數是:K × K × Cout / (K × K + Cout)。

我還應該指出,深度卷積有時會有一個> 1的步幅,這會減小輸出特徵圖的尺寸。但是逐點層通常具有stride = 1,因此其輸出特徵圖將始終具有與深度層有相同的尺寸。

深度可分層是MobileNet V1中的主要構建塊。然而,MobileNet V2稍微做了一些修改並使用了由三層組成的「擴展塊」:

1. 一個1×1卷積,為特徵圖添加更多通道(稱為「擴展」層)

2. 3×3深度卷積,用於過濾數據

3. 1×1卷積,再次減少通道數(「投影」層,作為瓶頸卷積)

這種擴展塊中MACC數量的公式:

Cexp = (Cin × expansion_factor)

expansion_layer = Cin × Hin × Win × Cexp

depthwise_layer = K × K × Cexp × Hout × Wout

projection_layer = Cexp × Hout × Wout × Cout

這些是我之前給出的相同公式。expansion_factor用於創建深度層要處理的額外通道,使得Cexp在此塊內使用的通道數量。

注意:擴展層的輸出尺寸是Hin × Win因為1×1卷積不會更改特徵圖的寬度和高度。但如果深度層的步幅> 1,那麼Hout × Wout將不同於Hin × Win。

把所有這些放在一起:

Cin × Hin × Win × Cexp + (K × K + Cout) × Cexp × Hout × Wout

如果stride = 1,則簡化為:

(K × K + Cout + Cin) × Cexp × Hout × Wout

這與V1使用的深度可分層相比如何?如果我們使用輸入特徵圖112×112×64擴展因子6,以及stride = 1的3×3深度卷積和128輸出通道,那麼MACC的總數是:

(3 × 3 + 128 + 64) × (64 × 6) × 112 × 112 = 968,196,096

這不是比以前更多嗎?是的,它甚至超過了最初的3×3卷積。但是......請注意,由於擴展層,在這個塊內,我們實際上使用了64 × 6 = 384通道。因此,這組層比原始的3×3卷積做得更多(從64到128個通道),而計算成本大致相同。

批量標準化(Batch normalization)

我已經提到過,我們並沒有真正計算激活函數,但批量標準化化呢?在現代網路中,通常在每個卷積層之後包括一個batchnorm層。

批量標準化獲取層的輸出,並將以下公式應用於每個輸出值:

z = gamma * (y - mean) / sqrt(variance + epsilon) + beta

這y是前一層輸出特徵圖中的元素。我們首先通過減去輸出通道的mean併除以標準偏差來標準化該值(epsilon用於確保我們不除以0,它通常是這樣的0.001)。然後我們按比例縮放gamma並添加偏差或偏移量beta。

每個通道有它自己的gamma,beta,mean,和variance的值,因此,如果在卷積層有C個輸出通道,則該批量標準化層學習到C×4個參數。看起來有相當多的FLOPS,因為上面的公式應用於輸出特徵圖中的每個元素。

然而......通常批量標準化應用於卷積層的輸出但非線性(ReLU)之前。在這種情況下,我們可以做一些數學運算來使批量標準化層消失!

由於在全連接層中進行的卷積或矩陣乘法只是一組點積,這是一個線性變換,並且上面給出的批量標準化公式也是線性變換,我們可以將這兩個公式組合成一個單一運算。換句話說,我們可以將批量標準化層的學習參數「摺疊」到前一個卷積/全連接層的權重中。

將批量標準參數摺疊成前面層的權重的數學計算是相當簡單的。在上面的公式中,y表示來自前一層的單個輸出值。讓我們擴展y:

z = gamma * ((x[0]*w[0] + x[1]*w[1] + ... + x[n-1]*w[n-1] + b) - mean)

/ sqrt(variance + epsilon) + beta

像往常一樣,這是一個點積,來自卷積核或來自矩陣乘法。像往常一樣,x意味著輸入數據,w是該層的權重,並且b是該層的偏置值。

為了摺疊批量標準化參數到上一層,我們要改寫這個公式,這樣gamma,beta,mean,並且variance只應用於w和b,沒有x。

w_new[i] = w[i] * gamma / sqrt(variance + epsilon)

b_new = (b - mean) * gamma / sqrt(variance + epsilon) + beta

這裡w_new[i]是新的第i個權重,b_new是偏置的新值。

從現在開始,我們可以將這些值用於卷積或全連接層的權重。我們現在可以寫了:

z = x[0]*w_new[0] + x[1]*w_new[1] + ... + x[n-1]*w_new[n-1] + b_new

這給出了與以前完全相同的結果,但無需使用批量標準化層。替換上述公式中w_new和b_new,並簡化。您應該再次獲得原始批量標準化公式。

請注意,緊跟batchnorm的層通常本身沒有偏置b,因為batchnorm層已經提供了一個(beta)。在這種情況下,公式b_new變得更簡單(我們設置b為0):

b_new = beta - mean * gamma / sqrt(variance + epsilon)

因此,即使原始層沒有偏置,無論如何都會得到一個摺疊的批量標準化層。

簡而言之:我們可以完全忽略批量標準化層的影響,因為我們在進行推理時實際上將其從模型中刪除。

注意:此技巧僅在層的順序為:卷積,批量標準化,ReLU時才有效 - 但不適用於:卷積,ReLU,批量標準化。ReLU是一個非線性操作,它會把數據弄亂。(雖然我認為如果批量標準化後面緊跟一個新的卷積層,你可以反過來摺疊參數。無論如何,你的深度學習庫已經為你做了這些優化。)

其他層類型

我們研究了卷積層和全連接層,這兩個是現代神經網路中最重要的組成部分。但是也有其他類型的層,例如池化層。

這些其他層類型肯定需要時間,但它們不使用點積,因此不能用MACC測量。如果您對計算FLOPS感興趣,只需獲取特徵圖大小並將其乘以表示處理單個輸入元素的難度的常量。

示例:在112×112具有128通道的特徵圖上具有過濾器大小2和步幅2的最大池化層需要112 × 112 × 128 = 1,605,632FLOPS或1.6兆FLOPS。當然,如果步幅與濾波器尺寸不同(例如3×3窗口,2×2步幅),則這些數字會稍微改變。

但是,在確定網路的複雜性時,通常會忽略這些附加層。畢竟,與具有100個MFLOPS的卷積/全連接層相比,1.6 MFLOPS非常小。因此,它成為網路總計算複雜度的舍入誤差。

某些類型的操作,例如結果的連接,通常甚至可以免費完成。不是將兩個層分別寫入自己的輸出張量中,然後有一個將這兩個張量複製到一個大張量的連接層。相反,第一層可以直接寫入大張量的前半部分,第二層可以直接寫入後半部分。不需要單獨的複製步驟。

注意:在這個討論中,我目前忽略了遞歸神經網路(RNN),它通常由LSTM或GRU層組成。LSTM層涉及兩個大的矩陣乘法,一些sigmoids,一個tanh,以及一些元素乘法。本質上它與2個全連接層相同,因此MACC的數量主要取決於輸入和輸出向量的大小,以及隱藏狀態向量的大小。同樣,矩陣乘法中的點積是關鍵因素。

參考連接:How fast is my model?

關於公眾號(微信號:deep_intelligence)

本號每天都會更新一些關於深度學習,機器學習和強化學習相關的專業知識,最新論文推薦,論文解讀,項目實踐,代碼解讀等等。

!!!歡迎關注!!!


推薦閱讀:

[論文翻譯]Learnable pooling——視頻分類
一種基於視覺辭彙的文本分類方法
視覺偉業VMFace團隊攻克群體骨架多粒度實時提取與追蹤技術
ACMMM2016_UnitBox
video segmentation 論文思想概述

TAG:深度學習DeepLearning | 嵌入式開發 | 計算機視覺 |