1. 程式人生 > >【深度學習】Torch卷積層原始碼詳解

【深度學習】Torch卷積層原始碼詳解

本文以前向傳播為例,詳細分析Torch的nn包中,SpatialConvolution函式的實現方式。
在分析原始檔時,同時給出了github上的連結以及安裝後的檔案位置。

初始化

定義一個卷積層需要如下輸入引數

nInputPlane\nOutputPlane    -- 輸入\輸出通道數,M\N
kW\kH                       -- 核尺寸,K
dW\dH                       -- 步長
padW\padH                   -- 補邊

卷積層的核心變數

weight         -- 卷積核權重,N*M*K*K
bias -- 卷積核偏置,N gradWeight -- 權重導數,N*M*K*K gradBia -- 偏置導數,N

為效率起見,torch的層採用分層方式實現:

nn(lua)->THNN(C)->THTensor(C)->THBlas(C)->LAPACK(Fortran)

nn(lua)層次

input.THNN.SpatialConvolutionMM_updateOutput(
      input:cdata(),            self.output:cdata(),
      self.weight:cdata(),      THNN.optionalTensor(self.bias),
      self.finput:cdata(),      self.fgradInput:cdata(),
      self.kW, self.kH,         self.dW, self.dH,      self.padW, self.padH
   )

其中input.THNN是輸入Tensor的一個C介面,傳入的引數也都用:cdata()轉化成是C型別。

Torch中另有/extra/nn/SpatialConvolutionMM.lua,未在文件中出現,內容幾乎相同,不做分別。

THNN庫

THNN是一個C庫,包含了nn包中的C實現,可以不依賴Lua執行。

THNN庫中大量採用了巨集定義的方式來命名,例如:

TH_API void THNN_(SpatialConvolutionMM_updateOutput)(...)
TH_API void THNN_(SpatialConvolutionMM_updateGradInput)(...)

THNN_開頭的函式定義在/extra/nn/lib/THNN/generic/目錄下,這兩個在SpatialConvolutionMM.c檔案中。

其他幾個庫

順便辨識一下幾個容易混淆的庫/包:

  • nn(lua)->THNN(C)
  • cunn(lua)->THCUNN(cuda)

在Torch自己的github下維護;
lua檔案在/extra/nn/目錄下;
C檔案在/extra/nn/lib/THNN/generic/目錄下,cuda檔案在/extra/nn/lib/THCUNN/目錄下;
nn中的資料/層通過:cuda()可以轉化為cunn中的資料/層;反之,則使用:float()

  • cudnn(lua)->cuDNN庫

在Torch的重要作者soumith的gihub下維護;
lua檔案在/extra/cudnn/目錄下;
實現部分需要安裝cuDNN;
nn中的層可以通過cudnn.convert(net,cudnn)轉化為cudnn中的層;反之則使用cudnn.convert(net,nn)

THNN(C)層次

Step 1

首先,把輸入的3D或4D的input展開成2D或3D的finput:

THNN_(unfolded_copy)(finput, input, kW, kH, dW, dH, padW, padH, nInputPlane, inputWidth, inputHeight, outputWidth, outputHeight);

input尺寸為M*H*W。對於每一通道,根據卷積尺寸,將其進行平移,獲得K*K個結果。這些結果摞起來得到(M*K*K)*(H*W)的finput

例:設input尺寸為2*4*4,兩通道如下
這裡寫圖片描述

使用3*3卷積核時,每一通道共有3*3=9個平移結果。卷積模板9個畫素位置對應的平移為:
這裡寫圖片描述
平移1=右移1+下移1:
這裡寫圖片描述

平移4 = 右移1
這裡寫圖片描述

相應地,把卷積權重weight也整理成2D矩陣N*(M*K*K)

Step 2

接下來,建立N*(H*W)的輸出矩陣output:

  output2d = THTensor_(newWithStorage2d)(output->storage, output->storageOffset,
                                nOutputPlane, -1, outputHeight*outputWidth, -1);

THTensor_開頭的函式在/pkg/torch/lib/TH/generic/THTensor.h中宣告。

然後把卷積層的bias逐通道地複製到輸出output中。

for(i = 0; i < nOutputPlane; i++)
        THVector_(fill)(output->storage->data+output->storageOffset+output->stride[0]*i, THTensor_(get1d)(bias, i), outputHeight*outputWidth);

相似地,THVector_開頭的函式直接在/pkg/torch/lib/TH/generic/THVector.c中宣告和定義。

Step 3

平移展開後的輸入finput,通過與weight的矩陣乘法,得到N*M*H*W的卷積結果output
這裡寫圖片描述

THTensor_(addmm)(output2d, 1, output2d, 1, weight, finput);

THTensor層次

許多以THTensor_開頭的函式都定義在/pkg/torch/lib/TH/generic/目錄下,包括THTensor.c,THTensorConv.c,THTensorRandom.c等。前述矩陣乘法定義在THTensorMath.c中。

經過一系列合法性檢查,執行乘法的是一個THBlas_函式:

  THBlas_(gemm)(transpose_m1,
                transpose_m2,
                r__->size[(transpose_r == 'n' ? 0 : 1)],
                r__->size[(transpose_r == 'n' ? 1 : 0)],
                m1_->size[(transpose_r == 'n' ? 1 : 0)],
                alpha,
                THTensor_(data)(m1_),
                (transpose_m1 == 'n' ? m1_->stride[(transpose_r == 'n' ? 1 : 0)] : m1_->stride[(transpose_r == 'n' ? 0 : 1)]),
                THTensor_(data)(m2_),
                (transpose_m2 == 'n' ? m2_->stride[(transpose_r == 'n' ? 1 : 0)] : m2_->stride[(transpose_r == 'n' ? 0 : 1)]),
                beta,
                THTensor_(data)(r__),
                r__->stride[(transpose_r == 'n' ? 1 : 0)]);

其中m1是卷積權重,m2是展開的輸入。

THBlas(C)層次

根據資料的型別(double/float),呼叫LAPACKdgemm_sgemm_函式:

#if defined(TH_REAL_IS_DOUBLE)
    dgemm_(&transa, &transb, &i_m, &i_n, &i_k, &alpha, a, &i_lda, b, &i_ldb, &beta, c, &i_ldc);
#else
    sgemm_(&transa, &transb, &i_m, &i_n, &i_k, &alpha, a, &i_lda, b, &i_ldb, &beta, c, &i_ldc);
#endif