【轉】caffe中backward過程總結

轉自:http://blog.csdn.net/buyi_shizi/article/details/51512848

backward是利用代價函數求取關於網絡中每個參數梯度的過程,爲後面更新網絡參數做準備。求取梯度的過程也是一個矩陣運算的過程,後面會有詳細介紹,本身求取梯度的過程並不是很複雜,而且網絡中的各層求取梯度的過程都是相似的。下面就按照backward的運行順序,從最後一層向前介紹caffe的backward的過程。

  1. softmax with loss layer:
    按理說每一層應該都要求一層梯度,其中包括對權值,對輸入數據,對偏置分別求取梯度。但是在softmax with loss layer這一層求取梯度的一些過程被省去了,首先這一次只是一個激活函數層,沒有權值和偏置參數,然後我們只需要對輸入數據求取梯度,softmax with loss layer的輸入數據其實表示的是原始輸入數據相對於各個標籤的打分,而對於代價函數對這個輸入的梯度已經有專門的迭代算法來求解。參考:http://ufldl.stanford.edu/wiki/index.php/Softmax%E5%9B%9E%E5%BD%92。
    caffe中的程序對應如下:
    1. for (int i = 0; i < outer_num_; ++i) {  
    2.       for (int j = 0; j < inner_num_; ++j) {  
    3.         const int label_value = static_cast<int>(label[i * inner_num_ + j]);  
    4.         if (has_ignore_label_ && label_value == ignore_label_) {  
    5.           for (int c = 0; c < bottom[0]->shape(softmax_axis_); ++c) {  
    6.             bottom_diff[i * dim + c * inner_num_ + j] = 0;  
    7.           }  
    8.         } else {  
    9.           bottom_diff[i * dim + label_value * inner_num_ + j] -= 1; //http://ufldl.stanford.edu/wiki/index.php/Softmax  
    10.           ++count;  
    11.         }  
    12.       }  
    13.     }  
    14.     // Scale gradient  
    15.     Dtype loss_weight = top[0]->cpu_diff()[0] /  
    16.                         get_normalizer(normalization_, count);  
    17.     caffe_scal(prob_.count(), loss_weight, bottom_diff);  

  2. inner product layer:
    這是一層全連接層,包含權值,偏置參數,所以在這一層我們要求3個梯度值,第一是對偏置求取梯度,第二是對權值求取梯度,第三是對輸入數據求取梯度,其中對輸入數據求取的梯度和前面類似會反向傳播到前一層。
    • 在對權值參數求取梯度之前,我們首先要明確當前層有多少個權值參數,權值參數相互之間又是什麼結構的。該層網絡的輸入數據寬度是500,輸出數據寬度是10,而且每個神經元都與輸入全連接,所以該層網絡應該有500x10個權值參數,那我們最終求得的對於權值的梯度應該也是有500x10個數據。而由於網絡的訓練以batch爲單位,一個batch中又包含64個samples,所以對於500x10個權值參數中的一個,應該是對一個batch中的64個輸出都有作用,所以我們求取關於某一個權值參數的梯度時,就要包含64個samples的數據。另外一方面根據weight_diff = top_diff * bottom_data(即該層輸出對於輸入的偏導,鏈式法則)。整個求取梯度的運算也是可以轉化爲矩陣運算的,這裏caffe也是用一次矩陣運算計算對所有權值參數的梯度,caffe的矩陣構造如下:
    • 對輸入數據求取梯度,對輸入數據求取梯度的時候,我們又要明確單個輸入數據是和哪些神經元連接,在caffe中的lenet模型中都是全連接,即輸入連接到所有的神經元上,在這種情況下,求取關於單個輸入的梯度就要包含所有的神經元,具體一點就是包含所有神經元對應在這單個輸入的權值。同時又要考慮網絡訓練是以batch爲單位的,即我們要處理64個樣本,在對輸入數據求取梯度上,caffe構造的矩陣如下:


  3. relu layer:
    這一層是inner product layer的激活函數層,caffe把每層網絡的加權求和和激活函數分成兩層網絡來對待,在這層函數裏面由於沒有權值和偏置,所以我們也只是需要對輸入數據求取梯度即可。而由於激活函數採用的是relu函數,所以對輸入求取梯度也比較簡單,我們利用的結果就是softmax with loss layer層求取的梯度結果進行鏈式求導的,程序如下:
    1. if (propagate_down[0]) {  
    2.     const Dtype* bottom_data = bottom[0]->cpu_data();  
    3.     const Dtype* top_diff = top[0]->cpu_diff();  
    4.     Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();  
    5.     const int count = bottom[0]->count();  
    6.     Dtype negative_slope = this->layer_param_.relu_param().negative_slope();  
    7.     for (int i = 0; i < count; ++i) {  
    8.       bottom_diff[i] = top_diff[i] * ((bottom_data[i] > 0)  
    9.           + negative_slope * (bottom_data[i] <= 0));  
    10.     }  
    11.   }  
  4. inner product layer:
    這一層和前面說的類似,也是一個全連接層,要分別對權值,偏置,輸入分別求取梯度,其中對輸入參數的梯度會反向傳播到前一層。
    • 對權值的梯度:和前面分析類似,該層的輸入寬度是800,輸出數據寬度是500,所以權值參數共有800x500個。而每個權值又回涉及到一個batch中的所有的64個輸入樣本,所以caffe對權值梯度的矩陣構造如下:
    • 對輸入數據的梯度:輸入總共有64個樣本,每個樣本的寬度是800,所以對輸入數據的梯度也是64x800,而由於網絡是全連接,所以每個輸入數據又會和該層所有500個神經元連接,所以每個輸入數據都會和500個權值有聯繫,所以對單個輸入數據求梯度就會設計500個權值,所以在caffe中矩陣構造如下:

  5. pooling layer:
    下采樣層根據下采樣方式的不同,求取梯度的方式也不同,對於pooling層,只需要對輸入參數求梯度即可,poling層是沒有權值和偏置參數的。然後再把梯度反向傳播給前一層。在lenet中,pooling層採用的下采樣方法是PoolingParameter_PoolMethod_MAX,即在pooling核區域選取一個最大值作爲下采樣的像素值。那麼對於輸入求取梯度就很容易了,即對輸入的梯度其實就是輸出梯度中對應位置的梯度。關於這個位置信息是在forward過程中存儲起來的。
  6. convlution layer:
    卷積層包含權值參數,偏置參數,所有對卷積層的梯度計算包含計算對權值的梯度,對偏置的梯度,對輸入參數的梯度,同樣對輸入參數的梯度也會反向傳播到前一層中。
    • 對權值參數的梯度:在該層卷積層上,卷積核的大小是5x5,總共有50個卷積核,所以權值參數共有50x25個。輸入數據寬度是20x12x12,輸出數據寬度是50x8x8。所以對於一個卷積核卷積一幅圖像,每一個卷積參數就會和8x8=64個輸入相乘,而同時有20幅圖像和該卷積核相連接,所以對於一個樣本,一個卷積核中的一個權值參數就會和20x8x8的輸入相乘;而一個batch中又有64個樣本,所以一個權值參數總共和64x20x8x8個輸入相乘。這時如果想一次求取所有對權值參數的梯度就比較困難,caffe中是逐樣本對權值參數求梯度。然後再把所有的樣本中的對於權值的梯度相加。對於單個樣本caffe構造的矩陣如下圖所示:

      從上圖我們發現一個問題,就是該層權值參數總共有50x25個,爲何Weight diff卻是50x500呢,難道實際上權值參數有50x500個嗎,原因還要從farword過程中的卷積矩陣開始說起,在forward過程中,我們想一次把一個卷積核卷積20個不同的輸入都計算出來,那麼在矩陣相乘的時候,我們就需要把卷積核的參數複製20個,然後完成矩陣相乘,500其實就表示20個相同的5x5卷積核的權值參數。另外上面的計算過程要循環64次,對應一個batch中的64個樣本。
    • 對輸入的梯度:由於該層有50個卷積核,而且卷積核和輸入也是全連接的,所以每一個輸入都會被50個卷積核的卷積涉及到。所以對於每一個輸入梯度的計算都會涉及50個卷積核的對應權值參數,caffe構造的矩陣如下:

      對於Bottom diff的數據寬度500情況和上面類似,就是該層的輸入其實是20x12x12,而卷積核大小是5x5,所以對於20個輸入每一個位置的卷積,都會有20x25=500個數據參與卷積。64和輸出尺寸8x8對應,表示在12x12的輸入上移動了8x8次,即一張輸入上卷積了64次。

  7. pooling layer:
    這一層和上面類似,這裏不再介紹。
  8. convolution layer:
    該層是對原始輸入數據做第一次卷積,所以這裏只對權值參數和偏置參數計算梯度,而輸入參數就不用計算梯度了,因爲這已經是第一層,輸入是原始像素了,所以也就不需要進行反向傳播了。該層的輸入是28x28的原始像素,輸出是20個24x24的卷積後的圖像,卷積核的大小是5x5。
    • 該層的權值參數共有20x25個,類似,每一個權值參數都會涉及到24x24=576個數據,所以在caffe中矩陣的構造如下:

      有關偏置梯度的計算都沒有寫出來,偏置梯度的計算本身也不復雜,就沒有必要寫了。計算完每個參數的梯度之後,下面就是利用梯度對參數進行更新了,這也是深度學習中很重要的一部分,在以後的文章中回重點總結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章