一、總體流程
caffe主要有四個功能模塊。
a) ?Blob 主要用來表示網絡中的數據,包括訓練數據,網絡各層自身的參數(包括權值、偏置以及它們的梯度),網絡之間傳遞的數據都是通過 Blob 來實現的,同時 Blob 數據也支持在 CPU 與 GPU 上存儲,能夠在兩者之間做同步。
b) ?Layer 是對神經網絡中各種層的一個抽象,包括我們熟知的卷積層和下采樣層,還有全連接層和各種激活函數層等等。同時每種 Layer 都實現了前向傳播和反向傳播,并通過 Blob 來傳遞數據。
c) Net 是對整個網絡的表示,由各種 Layer 前后連接組合而成,也是我們所構建的網絡模型。
d) Solver 定義了針對 Net 網絡模型的求解方法,記錄網絡的訓練過程,保存網絡模型參數,中斷并恢復網絡的訓練過程。自定義 Solver 能夠實現不同的網絡求解方式。
整體的框架是以layer為主,不同的layer完成不同的功能,如卷積,反卷積等。net是管理layer的,solver是做訓練用的,而核心的創新就是blob數據了,以一個四元組完成了各種數據的傳輸,同時解決了cpu和gpu共享數據的問題。
二. 卷積操作
對圖像(不同的數據窗口數據)和濾波矩陣(一組固定的權重:因為每個神經元的多個權重固定,所以又可以看做一個恒定的濾波器filter)做內積(逐個元素相乘再求和)的操作就是所謂的『卷積』操作,也是卷積神經網絡的名字來源。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(上圖可以看作一個濾波器filter,多個filter構成一個卷積層)
3. caffe中的數學函數
A) ?caffe_cpu_gemm 函數:
void caffe_cpu_gemm(const CBLAS_TRANSPOSE TransA, const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const float alpha, const float* A, const float* B, const float beta, float* C)
功能: C=alpha*A*B+beta*C
A,B,C 是輸入矩陣(一維數組格式)
CblasRowMajor :數據是行主序的(二維數據也是用一維數組儲存的)
TransA, TransB:是否要對A和B做轉置操作(CblasTrans CblasNoTrans)
M: A、C 的行數
N: B、C 的列數
K: A 的列數, B 的行數
lda : A的列數(不做轉置)行數(做轉置)
ldb: B的列數(不做轉置)行數(做轉置)
這個函數主要是用了庫函數的運算公式進行矩陣相乘,那接下來就是怎么將卷積運算轉化為矩陣相乘的問題了,這就需要用到image2col的函數了。
B) image2col 函數:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?最終:Filter Matrix乘以Feature Matrix,得到輸出矩陣Cout x (H x W)。
再說的詳細點:
如上圖
(1)在矩陣A中
M為卷積核個數,K=k*k,等于卷積核大小,即第一個矩陣每行為一個卷積核向量(是將二維的卷積核轉化為一維),總共有M行,表示有M個卷積核。
(2)在矩陣B中
因此,N為輸出圖像大小的長寬乘積,也是卷積核在輸入圖像上滑動可截取的最大特征數。
K=k*k,表示利用卷積核大小的框在輸入圖像上滑動所截取的數據大小,與卷積核大小一樣大。
(3)在矩陣C中
矩陣C為矩陣A和矩陣B相乘的結果,得到一個M*N的矩陣,其中每行表示一個輸出圖像即feature map,共有M個輸出圖像(輸出圖像數目等于卷積核數目)
Caffe中的卷積計算是將卷積核矩陣和輸入圖像矩陣變換為兩個大的矩陣A與B,然后A與B進行矩陣相乘得到結果C。
C)im2col_cpu分析
1.結合代碼分析以下公式的由來
int output_h = (height + 2 * pad_h -? (dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
int output_w = (width + 2 * pad_w -(dilation_w * (kernel_w - 1) + 1)) / stride_w +1;
2.先不要dilation_h 和dilation_w ,如下:
N=((image_h + 2*pad_h – kernel_h)/stride_h+ 1)*((image_w +2*pad_w – kernel_w)/stride_w + 1)
這里要結合動態stride滑動卷積圖
原理: N = H*W
H:? 在image高度方向上滑動的總次數。W:在image寬度方向上滑動的次數
D)Dilation分析
1.由于池化層的存在,響應張量的大小(長和寬)越來越小。
2.池化層去掉隨之帶來的是網絡各層的感受野(receptive field)變小,這樣會降低整個模型的預測精度。
3. Dilated convolution 的主要貢獻就是,如何在去掉池化下采樣操作的同時,而不降低網絡的感受野。
結合這個圖再推倒一下output_h 和output_w的公式
Dilation :表示每個元素的相隔距離,如果相鄰,dilation=1
運算公式: (除去原點的長度)×dilation(擴張系數)+ 1(原點)
即: kernel_h_new = (kernel_h-1)*dilation_h +1
kernel_w_new = (kernel_w-1)*dilation_w +1
E)im2col_cpu整體分析
F)Forward_cpu的整體分析
1. forward_cpu_gemm:
這個主要是處理處理Wx 的函數,里面分為兩個步驟:
a)conv_im2col_cpu:將image轉為矩陣
?b) caffe_cpu_gemm: 將A中的矩陣和卷積核的矩陣相乘,得到特征圖像的矩陣。
2. forward_cpu_bias:
這個主要是處理偏置數據。
G)Conv_layer的整體分析
1. Forward_cpu
2. Backward_cpu
F)BP流程
Backward_cpu函數的代碼,整個更新過程大概可以分成三步
1. caffe_cpu_gemm(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1., top_diff, bottom_data, (Dtype)1., this->blobs_[0]->mutable_cpu_diff());
其中的bottom_data對應的是a,即輸入的神經元激活值,維數為K_×N_,top_diff對應的是delta,維數是M_×N_,而caffe_cpu_gemm函數是對blas中的函數進行封裝,實現了一個N_×M_的矩陣與一個M_×K_的矩陣相乘(注意此處乘之前對top_diff進行了轉置)。相乘得到的結果保存于blobs_[0]->mutable_cpu_diff(),對應dW。
2. caffe_cpu_gemv(CblasTrans, M_, N_, (Dtype)1., top_diff, bias_multiplier_.cpu_data(), (Dtype)1., this->blobs_[1]->mutable_cpu_diff());
caffe_cpu_gemv函數實現了一個M_×N_的矩陣與N_×1的向量進行乘積,其實主要實現的是對delta進行了一下轉置,就得到了db的值,保存于blobs_[1]->mutable_cpu_diff()中。此處的與bias_multiplier_.cpu_data()相乘是實現對M_個樣本求和,bias_multiplier_.cpu_data()是全1向量,從公式上看應該是取平均的,但是從loss傳過來時已經取過平均了,此處直接求和即可。
3.3 . caffe_cpu_gemm(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1., top_diff, this->blobs_[0]->cpu_data(), (Dtype)1., bottom[0]->mutable_cpu_diff());
主要Inner_product層里面并沒有激活函數,因此沒有乘f’,與f’的相乘寫在ReLU層的Backward函數里了,因此這一句里只有W和delta_l+1相乘。blobs_[0]->cpu_data()對應W,維度是N_×K_,bottom[0]->mutable_cpu_diff()是本層的delta_l,維度是M_×K_。
caffe里的反向傳播過程只是計算每層的梯度的導,把所有層都計算完之后,在solver.cpp里面統一對整個網絡進行更新。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 先寫到這里吧,學無止境,且學且記錄。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? END