卷積神經網絡的複雜度分析

轉載自:https://zhuanlan.zhihu.com/p/31575074

在梳理CNN經典模型的過程中,我理解到其實經典模型演進中的很多創新點都與改善模型計算複雜度緊密相關,因此今天就讓我們對卷積神經網絡的複雜度分析簡單總結一下下。

本文主要關注的是針對模型本身的複雜度分析(其實並不是很複雜啦~)。如果想要進一步評估模型在計算平臺上的理論計算性能,則需要了解 Roofline Model 的相關理論,歡迎閱讀本文的進階版: Roofline Model與深度學習模型的性能分析

1. 時間複雜度

即模型的運算次數,可用 \textbf{FLOPs} 衡量。


1.1 單個卷積層的時間複雜度

\quad\quad\quad\quad\quad\quad\quad\quad\quad\textbf{Time} \sim O(M^2 \cdot K^2 \cdot C_{in} \cdot C_{out})\quad\quad\quad\quad\quad\quad\quad\quad\quad

  • M 每個卷積核輸出特徵圖 (Feature~\color{red}{M}ap) 的邊長
  • K 每個卷積核 (\color{red}{K}ernel) 的邊長
  • C_{in} 每個卷積核的通道數,也即輸入通道數,也即上一層的輸出通道數。
  • C_{out} 本卷積層具有的卷積核個數,也即輸出通道數。
  • 可見,每個卷積層的時間複雜度由輸出特徵圖面積 M^2 、卷積核面積 K^2 、輸入 C_{in} 和輸出通道數 C_{out} 完全決定。
  • 其中,輸出特徵圖尺寸本身又由輸入矩陣尺寸 X 、卷積核尺寸 KPaddingStride 這四個參數所決定,表示如下:

\quad\quad\quad\quad\quad\quad\quad\quad M = (X - K + 2 * Padding) / Stride + 1 \quad\quad\quad\quad\quad\quad\quad\quad

  • 注1:爲了簡化表達式中的變量個數,這裏統一假設輸入和卷積核的形狀都是正方形。
  • 注2:嚴格來講每層應該還包含 1 個 Bias 參數,這裏爲了簡潔就省略了。


1.2 卷積神經網絡整體的時間複雜度

\quad\quad\quad\quad\quad\quad\quad\quad\quad \textbf{Time} \sim O\Bigg(\sum_{l=1}^{D} M_l^2 \cdot K_l^2 \cdot C_{l-1} \cdot C_{l}\Bigg) \quad\quad\quad\quad\quad\quad\quad\quad\quad

  • D 神經網絡所具有的卷積層數,也即網絡的深度
  • l 神經網絡第 l 個卷積層
  • C_l 神經網絡第 l 個卷積層的輸出通道數 C_{out} ,也即該層的卷積核個數。
  • 對於第 l 個卷積層而言,其輸入通道數 C_{in} 就是第 (l - 1) 個卷積層的輸出通道數。
  • 可見,CNN整體的時間複雜度並不神祕,只是所有卷積層的時間複雜度累加而已。
  • 簡而言之,層內連乘,層間累加。


示例:用 Numpy 手動簡單實現二維卷積

假設 Stride = 1, Padding = 0, img 和 kernel 都是 np.ndarray.

def conv2d(img, kernel):
    height, width, in_channels = img.shape
    kernel_height, kernel_width, in_channels, out_channels = kernel.shape
    out_height = height - kernel_height + 1
    out_width = width - kernel_width + 1
    feature_maps = np.zeros(shape=(out_height, out_width, out_channels))
    for oc in range(out_channels):              # Iterate out_channels (# of kernels)
        for h in range(out_height):             # Iterate out_height
            for w in range(out_width):          # Iterate out_width
                for ic in range(in_channels):   # Iterate in_channels
                    patch = img[h: h + kernel_height, w: w + kernel_width, ic]
                    feature_maps[h, w, oc] += np.sum(patch * kernel[:, :, ic, oc])

    return feature_maps


2. 空間複雜度

空間複雜度包括模型的參數數量(模型本身的體積)和每層輸出的特徵圖大小(會影響模型運行時的內存佔用情況)。

\quad\quad\quad\quad\quad\quad\quad\quad\quad \textbf{Space} \sim O\Bigg(\sum_{l=1}^{D} K_l^2 \cdot C_{l-1} \cdot C_{l}\Bigg) \quad\quad\quad\quad\quad\quad\quad\quad\quad

  • 可見,網絡的參數量只與卷積核的尺寸 K 、通道數 C 、網絡的深度 D 相關。而與輸入數據的大小無關
  • 當我們需要裁剪模型時,由於卷積核的尺寸通常已經很小,而網絡的深度又與模型的能力緊密相關,不宜過多削減,因此模型裁剪通常最先下手的地方就是通道數。




3. 複雜度對模型的影響

  • 時間複雜度決定了模型的訓練/預測時間。如果複雜度過高,則會導致模型訓練和預測耗費大量時間,既無法快速的驗證想法和改善模型,也無法做到快速的預測。
  • 空間複雜度決定了模型的參數數量。由於維度詛咒的限制,模型的參數越多,訓練模型所需的數據量就越大,而現實生活中的數據集通常不會太大,這會導致模型的訓練更容易過擬合。




4. Inception 系列模型是如何優化複雜度的

通過五個小例子說明模型的演進過程中是如何優化複雜度的。


4.1 \textbf{InceptionV1} 中的 1 \times 1 卷積降維


(圖像被壓縮的慘不忍睹...)
  • InceptionV1 借鑑了 Network in Network 的思想,在一個 Inception Module 中構造了四個並行的不同尺寸的卷積/池化模塊(上圖左),有效的提升了網絡的寬度。但是這麼做也造成了網絡的時間和空間複雜度的激增。對策就是添加 1 x 1 卷積(上圖右紅色模塊)將輸入通道數先降到一個較低的值,再進行真正的卷積。
  • 以 InceptionV1 論文中的 (3b) 模塊爲例,輸入尺寸爲 28 \times 28 \times 2561 \times 1 卷積核 128 個, 3 \times 3 卷積核 192 個, 5 \times 5 卷積核 96 個,卷積核一律採用 Same Padding 確保輸出不改變尺寸。
  • 3 \times 3 卷積分支上加入 641 \times 1 卷積前後的時間複雜度對比如下式:

\begin{aligned} \quad\quad\quad &\textbf{Before}\quad 28^2 \cdot 3^2 \cdot \color{blue}{256} \cdot \color{blue}{192} = 3.5 \times 10^8 \quad\quad\quad\\[2ex] \quad\quad\quad &\textbf{After} \quad~~ 28^2 \cdot 1^2 \cdot \color{blue}{256} \cdot \color{red}{64} + 28^2 \cdot 3^2 \cdot \color{red}{64} \cdot \color{blue}{192} = 1 \times 10^8 \quad\quad\quad \end{aligned}

  • 同理,在 5 \times 5 卷積分支上加入 641 \times 1 卷積前後的時間複雜度對比如下式:

\begin{aligned} \quad\quad\quad &\textbf{Before}\quad 28^2 \cdot 5^2 \cdot \color{blue}{256} \cdot \color{blue}{96} = 4.8 \times 10^8 \quad\quad\quad\\[2ex] \quad\quad\quad &\textbf{After} \quad~~ 28^2 \cdot 1^2 \cdot \color{blue}{256} \cdot \color{red}{64} + 28^2 \cdot 5^2 \cdot \color{red}{64} \cdot \color{blue}{96} = 1.3 \times 10^8 \quad\quad\quad \end{aligned}

  • 可見,使用 1 \times 1 卷積降維可以降低時間複雜度3倍以上。該層完整的運算量可以在論文中查到,爲 300 M,即 3 \times 10^8
  • 另外在空間複雜度上,雖然降維引入了三組 1 \times 1 卷積核的參數,但新增參數量僅佔整體的 5%,影響並不大。


4.2 \textbf{InceptionV1} 中使用 GAP 代替 Flatten
  • 全連接層可以視爲一種特殊的卷積層,其卷積核尺寸 K 與輸入矩陣尺寸 X 一模一樣。每個卷積核的輸出特徵圖是一個標量點,即 M = 1 。複雜度分析如下:

\begin{aligned} &\quad\quad\quad\quad\quad\quad\quad\quad\quad\textbf{Time} \sim O(1^2 \cdot X^2 \cdot C_{in} \cdot C_{out})\quad\quad\quad\quad\quad\quad\quad\quad\quad\\[2ex] &\quad\quad\quad\quad\quad\quad\quad\quad\quad\textbf{Space} \sim O(X^2 \cdot C_{in} \cdot C_{out})\quad\quad\quad\quad\quad\quad\quad\quad\quad \end{aligned}

  • 可見,與真正的卷積層不同,全連接層的空間複雜度與輸入數據的尺寸密切相關。因此如果輸入圖像尺寸越大,模型的體積也就會越大,這顯然是不可接受的。例如早期的VGG系列模型,其 90% 的參數都耗費在全連接層上。
  • InceptionV1 中使用的全局最大池化 GAP 改善了這個問題。由於每個卷積核輸出的特徵圖在經過全局最大池化後都會直接精煉成一個標量點,因此全連接層的複雜度不再與輸入圖像尺寸有關,運算量和參數數量都得以大規模削減。複雜度分析如下:

\begin{aligned} &\quad\quad\quad\quad\quad\quad\quad\quad\quad\textbf{Time} \sim O(C_{in} \cdot C_{out})\quad\quad\quad\quad\quad\quad\quad\quad\quad\\[2ex] &\quad\quad\quad\quad\quad\quad\quad\quad\quad\textbf{Space} \sim O(C_{in} \cdot C_{out})\quad\quad\quad\quad\quad\quad\quad\quad\quad \end{aligned}


4.3 \textbf{InceptionV2} 中使用兩個 3 \times 3 卷積級聯替代 5 \times 5 卷積分支

感受野不變
  • 根據上面提到的二維卷積輸入輸出尺寸關係公式,可知:對於同一個輸入尺寸,單個 5 \times 5 卷積的輸出與兩個 3 \times 3 卷積級聯輸出的尺寸完全一樣,即感受野相同。
  • 同樣根據上面提到的複雜度分析公式,可知:這種替換能夠非常有效的降低時間和空間複雜度。我們可以把辛辛苦苦省出來的這些複雜度用來提升模型的深度和寬度,使得我們的模型能夠在複雜度不變的前提下,具有更大的容量,爽爽的。
  • 同樣以 InceptionV1 裏的 (3b) 模塊爲例,替換前後的 5 \times 5 卷積分支複雜度如下:

\begin{aligned} \quad\quad\quad &\textbf{Before}\quad 28^2 \cdot 1^2 \cdot 256 \cdot 64 + 28^2 \cdot \color{green}{5}^2 \cdot 64 \cdot 96 = 1.3 \times 10^8 \quad\quad\quad\\[2ex] \quad\quad\quad &\textbf{After} \quad~~ 28^2 \cdot 1^2 \cdot 256 \cdot 64 + 28^2 \cdot \color{green}{3}^2 \cdot 64 \cdot 96 \cdot \color{red}{2} = 1.0 \times 10^8 \quad\quad\quad \end{aligned}


4.4 \textbf{InceptionV3} 中使用 N \times 11 \times N 卷積級聯替代 N \times N 卷積

  • InceptionV3 中提出了卷積的 Factorization,在確保感受野不變的前提下進一步簡化。
  • 複雜度的改善同理可得,不再贅述。


4.5 \textbf{Xception} 中使用 \textbf{Depth-wise Separable Convolution}

  • 我們之前討論的都是標準卷積運算,每個卷積核都對輸入的所有通道進行卷積。
  • Xception 模型挑戰了這個思維定勢,它讓每個卷積核只負責輸入的某一個通道,這就是所謂的 Depth-wise Separable Convolution。
  • 從輸入通道的視角看,標準卷積中每個輸入通道都會被所有卷積核蹂躪一遍,而 Xception 中每個輸入通道只會被對應的一個卷積核掃描,降低了模型的冗餘度。
  • 標準卷積與可分離卷積的時間複雜度對比:可以看到本質上是把連乘轉化成爲相加。

\begin{aligned} \textbf{Standard Convolution} \quad {Time} &\sim O\Big(M^2 \cdot K^2 \cdot C_{in} \cdot C_{out}\Big)\\[3ex] \textbf{Depth-wise Separable Convolution} \quad {Time} &\sim O\Big(M^2 \cdot K^2 \cdot C_{in} + M^2 \cdot C_{in} \cdot C_{out}\Big) \end{aligned}




5. 總結

通過上面的推導和經典模型的案例分析,我們可以清楚的看到其實很多創新點都是圍繞模型複雜度的優化展開的,其基本邏輯就是乘變加。模型的優化換來了更少的運算次數和更少的參數數量,一方面促使我們能夠構建更輕更快的模型(例如MobileNet),一方面促使我們能夠構建更深更寬的網絡(例如Xception),提升模型的容量,打敗各種大怪獸,歐耶~


參考論文


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