深度學習中常用的層:Full Connect全連線層
全連線層的推導
全連線層的每一個結點都與上一層的所有結點相連,用來把前邊提取到的特徵綜合起來。由於其全相連的特性,一般全連線層的引數也是最多的。
全連線層的前向計算
下圖中連線最密集的2個地方就是全連線層,這很明顯的可以看出全連線層的引數的確很多。在前向計算過程,也就是一個線性的加權求和的過程,全連線層的每一個輸出都可以看成前一層的每一個結點乘以一個權重係數W,最後加上一個偏置值b得到,即 。如下圖中第一個全連線層,輸入有50*4*4個神經元結點,輸出有500個結點,則一共需要50*4*4*500=400000個權值引數W和500個偏置引數b。
下面用一個簡單的網路具體介紹一下推導過程
其中,x1、x2、x3為全連線層的輸入,a1、a2、a3為輸出,根據我前邊在筆記1中的推導,有
可以寫成如下矩陣形式:
全連線層的反向傳播
以我們的第一個全連線層為例,該層有50*4*4=800個輸入結點和500個輸出結點。
由於需要對W和b進行更新,還要向前傳遞梯度,所以我們需要計算如下三個偏導數。
1、對上一層的輸出(即當前層的輸入)求導
若我們已知轉遞到該層的梯度,則我們可以通過鏈式法則求得loss對x的偏導數。
首先需要求得該層的輸出ai對輸入xj的偏導數
再通過鏈式法則求得loss對x的偏導數:
上邊求導的結果也印證了我前邊那句話:在反向傳播過程中,若第x層的a節點通過權值W對x+1層的b節點有貢獻,則在反向傳播過程中,梯度通過權值W從b節點傳播回a節點。
若我們的一次訓練16張圖片,即batch_size=16,則我們可以把計算轉化為如下矩陣形式。
2、對權重係數W求導
我們前向計算的公式如下圖,
由圖可知,所以:。
當batch_size=16時,寫成矩陣形式:
3、對偏置係數b求導
由上面前向推導公式可知
,
即loss對偏置係數的偏導數等於對上一層輸出的偏導數。
當batch_size=16時,將不同batch對應的相同b的偏導相加即可,寫成矩陣形式即為乘以一個全1的矩陣:
Caffe中全連線層的實現
在caffe中,關於全連線層的配置資訊如下:
- layer {
- name: "ip1"
- type: "InnerProduct"
- bottom: "pool2"
- top: "ip1"
- param {
- lr_mult: 1
- }
- param {
- lr_mult: 2
- }
- inner_product_param {
- num_output: 500
- weight_filler {
- type: "xavier"
- }
- bias_filler {
- type: "constant"
- }
- }
- }
該層型別為InnerProduct內積,也就是我們常說的全連線層,前一層(底層)為pool2一個池化層,頂層,即該層的輸出ip1,即為一個全連線層。關於學習率的引數lr_mult我們後面在權值更新章節再看。其他的引數我們在之前的卷積層都遇到過,含義和卷積層也一樣,這裡就不再多說。
Caffe中全連線層相關的GPU檔案有1個,為\src\caffe\layersi\nner_product_layer.cu 。
前向計算
前向過程程式碼如下,具體解釋見註釋部分:
- template <typename Dtype>
- void InnerProductLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
- const vector<Blob<Dtype>*>& top) {
- const Dtype* bottom_data = bottom[0]->gpu_data();
- Dtype* top_data = top[0]->mutable_gpu_data();
- const Dtype* weight = this->blobs_[0]->gpu_data();
- //M_為batch_size
- if (M_ == 1) {
- //top_data(M*N) = bottom_data(M*K) * weight(K*N)
- //這裡的計算實際呼叫了cublas中的矩陣計算函式,我們之前也有講解,有興趣可以深入看一下
- caffe_gpu_gemv<Dtype>(CblasNoTrans, N_, K_, (Dtype)1.,
- weight, bottom_data, (Dtype)0., top_data);
- //若有偏置,加上偏置
- if (bias_term_)
- caffe_gpu_axpy<Dtype>(N_, bias_multiplier_.cpu_data()[0],
- this->blobs_[1]->gpu_data(), top_data);
- } else {
- //同上面
- caffe_gpu_gemm<Dtype>(CblasNoTrans,
- transpose_ ? CblasNoTrans : CblasTrans,
- M_, N_, K_, (Dtype)1.,
- bottom_data, weight, (Dtype)0., top_data);
- if (bias_term_)
- caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, N_, 1, (Dtype)1.,
- bias_multiplier_.gpu_data(),
- this->blobs_[1]->gpu_data(), (Dtype)1., top_data);
- }
- }
反向傳播
程式碼及註釋如下
- template <typename Dtype>
- void InnerProductLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
- const vector<bool>& propagate_down,
- const vector<Blob<Dtype>*>& bottom) {
- if (this->param_propagate_down_[0]) {
- const Dtype* top_diff = top[0]->gpu_diff();
- const Dtype* bottom_data = bottom[0]->gpu_data();
- // Gradient with respect to weight
- //對權重求導weight_diff = top_diff * bottom_data
- if (transpose_) {
- caffe_gpu_gemm<Dtype>(CblasTrans, CblasNoTrans,
- K_, N_, M_,
- (Dtype)1., bottom_data, top_diff,
- (Dtype)1., this->blobs_[0]->mutable_gpu_diff());
- } else {
- caffe_gpu_gemm<Dtype>(CblasTrans, CblasNoTrans,
- N_, K_, M_,
- (Dtype)1., top_diff, bottom_data,
- (Dtype)1., this->blobs_[0]->mutable_gpu_diff());
- }
- }
- if (bias_term_ && this->param_propagate_down_[1]) {
- const Dtype* top_diff = top[0]->gpu_diff();
- // Gradient with respect to bias
- //對偏置值b求導 bias_diff = bias * top_diff
- //這個和我之前公式推匯出來的不一樣,不知道為什麼,如果有誰知道請留言告訴我,謝謝
- caffe_gpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff,
- bias_multiplier_.gpu_data(), (Dtype)1.,
- this->blobs_[1]->mutable_gpu_diff());
- }
- if (propagate_down[0]) {
- const Dtype* top_diff = top[0]->gpu_diff();
- // Gradient with respect to bottom data
- //對上一層的輸出求導bottom_diff = top_diff * weight
- if (transpose_) {
- caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasTrans,
- M_, K_, N_,
- (Dtype)1., top_diff, this->blobs_[0]->gpu_data(),
- (Dtype)0., bottom[0]->mutable_gpu_diff());
- } else {
- caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans,
- M_, K_, N_,
- (Dtype)1., top_diff, this->blobs_[0]->gpu_data(),
- (Dtype)0., bottom[0]->mutable_gpu_diff());
- }
- }
- }