1. 程式人生 > >Winograd 方法快速計算卷積

Winograd 方法快速計算卷積

在 ConvNet 中, 大部分的計算耗費在計算卷積的過程中, 尤其是在端上裝置中, 對於效能的要求更為苛刻. 程式的效能優化是一個複雜而龐大的話題. 高效能運算就像系統設計一樣, 雖然有一些指導原則, 但是, 對於不同的場景需要有不同的設計方案, 因此, 對於同一個優化問題, 不同的人可能會給出完全不同的優化方案. 本文不是探討硬體和程式碼級的優化, 僅針對計算卷積的一個特定方法 — Winograd 方法做一個簡單的記述.

計算卷積的各種方法

首先要明確一點, 這裡說的卷積是是指 ConvNet 中預設的卷積, 而不是數學意義上的卷積. 其實, ConvNet 中的卷積對於與數學中的 cross correlation.
計算卷積的方法有很多種, 常見的有以下幾種方法:

  1. 滑窗: 這種方法是最直觀最簡單的方法. 但是, 該方法不容易實現大規模加速, 因此, 通常情況下不採用這種方法 (但是也不是絕對不會用, 在一些特定的條件下該方法反而是最高效的.)
  2. im2col: 目前幾乎所有的主流計算框架包括 Caffe, MXNet 等都實現了該方法. 該方法把整個卷積過程轉化成了 GEMM 過程, 而 GEMM 在各種 BLAS 庫中都是被極致優化的, 一般來說, 速度較快.
  3. FFT: 傅立葉變換和快速傅立葉變化是在經典影象處理裡面經常使用的計算方法, 但是, 在 ConvNet 中通常不採用, 主要是因為在 ConvNet 中的卷積模板通常都比較小, 例如 3×33×3 等, 這種情況下, FFT 的時間開銷反而更大.
  4. Winograd: Winograd 是存在已久最近被重新發現的方法, 在大部分場景中, Winograd 方法都顯示和較大的優勢, 目前 cudnn 中計算卷積就使用了該方法.

Winograd 方法

Winograd 方法簡單來講, 就是用更多的加法計算來減少乘法計算. 因此, 一個前提就是, 在處理器中, 乘法計算的時鐘週期數要大於加法計算的時鐘週期數.
Winograd 計算卷積需要完成的乘法的次數為:

μ(F(m×n,r×s))=(m+n−1)×(n+s−1)μ(F(m×n,r×s))=(m+n−1)×(n+s−1)


r×sr×s 表示卷積核的大小, m×nm×n 表示輸出大小.

所以, 做一個簡單的對比計算: 3×33×3 的卷積核, 輸出為 2×22×2, 那麼, 滑窗或者 im2col 需要的乘法計算次數為 3×3×2×2=363×3×2×2=36, Winograd 需要的乘法計算次數為 (3+2−1)×(3+2−1)=16(3+2−1)×(3+2−1)=16.

Winograd 的證明方法較為複雜, 要用到數論中的一些知識, 但是, 使用起來很簡單. 只需要按照如下公式計算:

Y=AT[[GgGT]⊙[BTdB]]AY=AT[[GgGT]⊙[BTdB]]A

其中, ⊙⊙ 表示 element-wise multiplication. A,G,BA,G,B 根據輸出大小和卷積核大小不同有不同的定義, 並且是提前確定了的. 每種輸出大小和卷積核的 A,G,BA,G,B 具體是多少, 可以通過 https://github.com/andravin/wincnn 的指令碼計算. gg 表示的是卷積核, dd 表示要進行卷積計算的 data. gg 的 shape 為 r×rr×r, dd 的 shape 為 (m+r−1)×(m+r−1)(m+r−1)×(m+r−1)

後注

  1. 以上描述的 Winograd 演算法只展示了在二維的影象 (更確切的說是 tile) 上的過程, 具體在 ConvNet 的多個 channel 的情況, 直接逐個 channel 按照上述方法計算完然後相加即可;
  2. 按照 #1 的思路, 在計算多個 channel 的時候, 仍然有可減少計算次數的地方.
  3. 按照 #2 的思路, Winograd 在目前使用越來越多的 depthwise conv 中其優勢不明顯了.
  4. 在 tile 較大的時候, Winograd 方法不適用, 因為, 在做 inverse transform 的時候的計算開銷抵消了 Winograd 帶來的計算節省.
  5. Winograd 會產生誤差