【深度學習】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),呼叫LAPACK的dgemm_
或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